Add attachment support: attach, upload, --attach, inline images#242
Add attachment support: attach, upload, --attach, inline images#242
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a06f9bb11f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
Adds first-class attachment uploading and embedding across the Basecamp CLI, including new top-level commands and richer Markdown handling (inline local images → Basecamp attachments).
Changes:
- Introduces
basecamp attachandbasecamp uploadshortcuts plus a standaloneuploadscommand withlist/create/show. - Adds repeatable
--attachsupport and inline local image resolution for multiple “create” flows (messages, comments, todos, docs, check-in answers). - Updates help/surface snapshots and adjusts e2e expectations for the new
uploadshelp behavior.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| internal/commands/todos.go | Adds --description (Markdown) and --attach support for todo creation (shortcut + CRUD). |
| internal/commands/messages.go | Adds --attach and inline local image resolution to message creation (shortcut + CRUD). |
| internal/commands/files.go | Reworks top-level uploads command and adds uploads create; converts docs create/update content to HTML. |
| internal/commands/comment.go | Adds --attach and inline local image resolution to comment creation. |
| internal/commands/checkins.go | Converts check-in answers to HTML; adds --attach + inline image resolution for answer creation. |
| internal/commands/campfire.go | Adds campfire upload for direct file uploads to a room. |
| internal/commands/attach.go | Adds attach staging command plus shared attachment upload + inline image resolution helpers. |
| internal/commands/attach_helpers.go | Adds image-src classification and URI scheme detection helpers for inline image handling. |
| internal/commands/attach_helpers_test.go | Adds unit tests for helper regex/classification logic. |
| internal/commands/commands.go | Updates command catalog (adds shortcuts, campfire upload, uploads create). |
| internal/commands/commands_test.go | Registers new top-level commands for root build tests. |
| internal/cli/root.go | Adds new top-level commands to the CLI root. |
| internal/cli/help.go | Adds new shortcuts to curated help output. |
| e2e/files.bats | Updates e2e expectation for uploads --help. |
| .surface | Updates CLI surface snapshot for new commands/flags and reshaped uploads tree. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
4 issues found across 15 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="internal/commands/checkins.go">
<violation number="1" location="internal/commands/checkins.go:890">
P1: `newCheckinsAnswerUpdateCmd` converts content through `MarkdownToHTML` but never calls `resolveLocalImages`, so inline image references like `` produce broken `<img>` tags with local paths. The create command handles this correctly — the update command should apply the same `resolveLocalImages` (and `--attach` upload) pipeline.</violation>
</file>
<file name="internal/commands/files.go">
<violation number="1" location="internal/commands/files.go:676">
P2: Use `defer f.Close()` immediately after the successful `os.Open` instead of calling `f.Close()` inline. This ensures the descriptor is released even if a panic occurs during the upload.</violation>
</file>
<file name="internal/commands/attach_helpers.go">
<violation number="1" location="internal/commands/attach_helpers.go:26">
P2: Windows drive-letter paths (e.g. `C:\path\img.png`) match the URI-scheme regex and would be silently skipped instead of uploaded. Changing `*` to `+` requires at least two characters before the colon, which excludes drive letters while still matching every real URI scheme.</violation>
</file>
<file name="internal/commands/attach.go">
<violation number="1" location="internal/commands/attach.go:145">
P2: HTML-entity-decode `src` before using it as a file path. `MarkdownToHTML` escapes attribute characters (e.g. `&` → `&`, `"` → `"`), so a markdown image like `` produces `src="./a&b.png"` in the rendered HTML. Passing that escaped string to `ValidateFile` and `os.Open` will fail to find the actual file on disk. Use `html.UnescapeString(src)` before classification and file operations.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
There was a problem hiding this comment.
1 issue found across 6 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="internal/commands/attach_helpers.go">
<violation number="1" location="internal/commands/attach_helpers.go:26">
P3: The comment on the line above still quotes the RFC 3986 production with `*` (zero or more), but the regex now uses `+` (one or more). Update the comment to note the intentional deviation that excludes single-letter schemes so Windows drive letters (`C:`, `D:`) aren't misclassified as URI schemes.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Three tiers of file attachment support: 1. `basecamp attach <file>` — staging primitive that uploads files and returns SGID + <bc-attachment> HTML. Account-scoped, no project needed. Styled output on TTY, auto-JSON on non-TTY. 2. `--attach` flag on content-creating commands — repeatable flag on messages create, message, comments create, comment, todos create, todo, docs create, and checkins answer create. Each uploads and appends <bc-attachment> tags to the content HTML. 3. Inline image resolution —  in Markdown body auto-uploads the local file and replaces the <img> with <bc-attachment>. Missing files fail fast. Placeholders (?) error. Remote URLs and non-file URIs (data:, cid:, blob:) pass through untouched. Additional commands: - `basecamp upload <file>` — shortcut for uploading to Docs & Files - `basecamp uploads create <file>` — canonical CRUD path (standalone uploads command, no longer a clone of files) - `basecamp campfire upload <file>` — direct file upload to chat Fixes: - MarkdownToHTML now applied to docs create, docs update, and checkins answer create/update (were previously raw text or <div> wrapped) - RFC 3986 URI scheme detection replaces naive colon heuristic for classifying non-file image sources Testing: - classifyImageSrc decision matrix: 16 cases covering skip/upload/error - Helper predicates: hasImgTag, isRemoteURL, isNonFileURI, imgTagPattern - Catalog parity, surface snapshot, e2e help text updated
…pdate consistency - HTML-unescape img src before file lookup in resolveLocalImages (handles & etc.) - Change URI scheme regex from * to + quantifier so single-letter drive prefixes (C:, D:) are not misclassified as URI schemes on Windows - Use defer f.Close() in runUploadFile for panic safety - Add resolveLocalImages to document update and checkins answer update paths for consistency with their create counterparts - NormalizeDragPath in uploadAttachments, runUploadFile, runCampfireUpload for drag/paste and file:// URL handling - Add Windows drive-letter test cases
The old `upload`/`uploads` aliases cloned the entire `files` command tree, producing entries like `basecamp uploads doc create`, `basecamp uploads vault`, etc. Now that `uploads` is standalone with just `list`, `create`, `show`, these cloned entries are intentionally removed.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated 8 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…eadcrumbs - Treat file:// URIs as local paths in isNonFileURI (not skipped) - Apply NormalizeDragPath to file:// src in resolveLocalImages - Fix EmbedAttachments leading newline when html is empty (attach-only case) - Resolve project for breadcrumbs in campfire upload when --campfire provided - Add file:// test cases
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 17 out of 17 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
The flag only applies to posting messages, not to list/messages/delete/ line/upload. Also fix error handling in campfire upload breadcrumb resolution — return errors instead of silently discarding them.
The chat alias for campfire also inherited --content-type as a persistent flag. Add all chat subcommand entries to .surface-breaking and remove the BATS test that asserted --content-type on the parent.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 19 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Derive breadcrumb prefix from cmd.Parent().CommandPath() so it matches the invoked command path (basecamp files uploads vs basecamp uploads) instead of hard-coding "basecamp files" - Remove campfire/chat upload entries from .surface-breaking since those commands are new in this PR (not removals from main)
There was a problem hiding this comment.
1 issue found across 2 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="internal/commands/files.go">
<violation number="1" location="internal/commands/files.go:712">
P2: `cmd.Parent().CommandPath()` returns `"basecamp"` when called from the `upload` shortcut command (whose parent is root), producing invalid breadcrumbs like `"basecamp show 42 --in proj"`. This path is only correct for the `create` subcommand where the parent is the `uploads` group. Add a fallback for the shortcut case, e.g. when the parent is root, use `"basecamp uploads"`.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
When runUploadFile is called from the top-level "basecamp upload" shortcut, cmd.Parent() is root so CommandPath() returns "basecamp". Fall back to "basecamp uploads" when the parent has no parent.
Summary
basecamp attach <file>— staging primitive. Uploads files, returns SGID +<bc-attachment>HTML. Account-scoped, no project needed.basecamp upload <file>— shortcut for uploading to Docs & Files (two-step: attachment → upload in vault)basecamp uploads create <file>— canonical CRUD path.uploadsis now a standalone command (list,create,show) instead of a clone offiles, so this routes correctly.basecamp campfire upload <file>— direct file upload to a Campfire chat room--attachflag onmessages create,message,comments create,comment,todos create,todo,docs create,checkins answer create. Repeatable. Uploads and appends<bc-attachment>tags.in Markdown auto-uploads and replaces<img>with<bc-attachment>. Remote URLs and non-file URIs (data:,cid:,blob:) pass through. Missing files fail fast. Placeholders (?) error.Also fixes
docs createanddocs updatenow run content throughMarkdownToHTML(was raw text)checkins answer create/updatenow usesMarkdownToHTML(was<div>wrapped)Test plan
basecamp attach ./test.png— TTY shows styled output,--jsonreturns structured arraybasecamp upload ./test.png --in <project>— file appears in Docs & Filesbasecamp uploads create ./test.png --in <project>— same via CRUD pathbasecamp message "test" "See " --in <project>— inline image uploaded and embeddedbasecamp comment <id> "text" --attach ./test.png— comment with attachmentbasecamp campfire upload ./test.png --in <project>— file in Campfirebasecamp todo "task" --description "notes" --attach ./doc.pdf --in <project>— todo with attachment in descriptionmake checkpasses (fmt, vet, lint, unit tests, e2e, surface, skill drift)