Skip to content

Add attachment support: attach, upload, --attach, inline images#242

Merged
jeremy merged 10 commits intomainfrom
attachments
Mar 11, 2026
Merged

Add attachment support: attach, upload, --attach, inline images#242
jeremy merged 10 commits intomainfrom
attachments

Conversation

@jeremy
Copy link
Copy Markdown
Member

@jeremy jeremy commented Mar 10, 2026

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. uploads is now a standalone command (list, create, show) instead of a clone of files, so this routes correctly.
  • basecamp campfire upload <file> — direct file upload to a Campfire chat room
  • --attach flag on messages create, message, comments create, comment, todos create, todo, docs create, checkins answer create. Repeatable. Uploads and appends <bc-attachment> tags.
  • Inline image resolution![alt](./path) 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 create and docs update now run content through MarkdownToHTML (was raw text)
  • checkins answer create/update now uses MarkdownToHTML (was <div> wrapped)
  • Image source classification uses RFC 3986 URI scheme regex instead of naive colon check

Test plan

  • basecamp attach ./test.png — TTY shows styled output, --json returns structured array
  • basecamp upload ./test.png --in <project> — file appears in Docs & Files
  • basecamp uploads create ./test.png --in <project> — same via CRUD path
  • basecamp message "test" "See ![img](./test.png)" --in <project> — inline image uploaded and embedded
  • basecamp comment <id> "text" --attach ./test.png — comment with attachment
  • basecamp campfire upload ./test.png --in <project> — file in Campfire
  • basecamp todo "task" --description "notes" --attach ./doc.pdf --in <project> — todo with attachment in description
  • make check passes (fmt, vet, lint, unit tests, e2e, surface, skill drift)

@jeremy jeremy requested a review from a team as a code owner March 10, 2026 22:08
Copilot AI review requested due to automatic review settings March 10, 2026 22:08
@github-actions github-actions bot added commands CLI command implementations tests Tests (unit and e2e) enhancement New feature or request labels Mar 10, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread internal/commands/attach.go Outdated
Comment thread internal/commands/attach_helpers.go
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 attach and basecamp upload shortcuts plus a standalone uploads command with list/create/show.
  • Adds repeatable --attach support 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 uploads help 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.

Comment thread internal/commands/todos.go
Comment thread internal/commands/attach.go Outdated
Comment thread internal/commands/files.go Outdated
Comment thread internal/commands/files.go Outdated
Comment thread internal/commands/campfire.go
Comment thread internal/commands/todos.go
Comment thread internal/commands/attach.go Outdated
Comment thread internal/commands/attach_helpers.go
Comment thread internal/commands/attach.go
Comment thread internal/commands/files.go
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 `![alt](./path)` 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. `&` → `&amp;`, `"` → `&quot;`), so a markdown image like `![x](./a&b.png)` produces `src="./a&amp;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.

Comment thread internal/commands/checkins.go Outdated
Comment thread internal/commands/files.go Outdated
Comment thread internal/commands/attach_helpers.go Outdated
Comment thread internal/commands/attach.go Outdated
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread internal/commands/attach_helpers.go
Copilot AI review requested due to automatic review settings March 11, 2026 00:15
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread internal/commands/files.go Outdated
jeremy added 2 commits March 10, 2026 22:14
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 — ![alt](./path) 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 &amp; 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
Copilot AI review requested due to automatic review settings March 11, 2026 05:20
jeremy added 3 commits March 10, 2026 22:28
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.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread internal/commands/attach_helpers.go Outdated
Comment thread internal/commands/todos.go
Comment thread internal/commands/todos.go
Comment thread internal/commands/messages.go
Comment thread internal/commands/files.go
Comment thread internal/commands/campfire.go Outdated
Comment thread internal/commands/messages.go
Comment thread internal/commands/files.go
…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
Copilot AI review requested due to automatic review settings March 11, 2026 06:31
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread internal/commands/campfire.go Outdated
Comment thread internal/commands/campfire.go
Comment thread internal/commands/files.go
jeremy added 2 commits March 10, 2026 23:49
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.
Copilot AI review requested due to automatic review settings March 11, 2026 06:53
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread internal/commands/files.go Outdated
Comment thread internal/commands/files.go Outdated
Comment thread .surface-breaking Outdated
Comment thread .surface-breaking Outdated
- 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)
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread internal/commands/files.go Outdated
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.
Copilot AI review requested due to automatic review settings March 11, 2026 07:21
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@jeremy jeremy merged commit 9ae3919 into main Mar 11, 2026
26 checks passed
@jeremy jeremy deleted the attachments branch March 11, 2026 07:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

commands CLI command implementations enhancement New feature or request tests Tests (unit and e2e)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants