Skip to content

feat(cli): add bm cloud share command group (create/list/update/revoke)#965

Merged
phernandez merged 5 commits into
mainfrom
feat/880-cloud-share-cli
Jul 15, 2026
Merged

feat(cli): add bm cloud share command group (create/list/update/revoke)#965
phernandez merged 5 commits into
mainfrom
feat/880-cloud-share-cli

Conversation

@groksrc

@groksrc groksrc commented Jun 11, 2026

Copy link
Copy Markdown
Member

Closes #880

Summary

Adds a bm cloud share command group for the cloud /api/shares endpoints:

  • create <project> <permalink> [--expires-at/-e] creates a public share link.
  • list [--project/-p] lists links in a Rich table and safely encodes project filters.
  • update <token> [--enable|--disable|--expires-at] updates link state or expiry; --expires-at none clears it.
  • revoke <token> [--force/-f] revokes a link, with confirmation unless --force is passed.

All four commands support explicit workspace routing by slug, display name, or tenant ID and preserve the existing configured-workspace fallback.

Design

  • Uses an exact note permalink, keeping the initial CLI surface small and deterministic.
  • Reuses the existing cloud API client, configuration, workspace resolution, and error handling.
  • Validates conflicting or empty updates before making a network request.
  • Treats API responses as boundary data and renders the fields needed by each command.

Maintainer follow-through

  • Rebased the branch onto current main.
  • Verified workspace selection and URL-encoding fixes from review.
  • Kept ISO-8601 parsing compatible with Python 3.12+'s trailing-Z support.
  • Replaced already-expired user-facing examples with 2099-12-31 and added a help-output regression test.

Testing

  • uv run pytest -q tests/cli/test_share_commands.py — 31 passed.
  • Share + snapshot + workspace CLI suites — 56 passed.
  • uv run ruff check src/basic_memory/cli/commands/cloud/shares.py tests/cli/test_share_commands.py — passed.
  • uv run ty check src/basic_memory/cli/commands/cloud/shares.py tests/cli/test_share_commands.py — passed.
  • just fast-check — passed (with the repository's existing Python 3.14 asyncio deprecation diagnostics).

Files changed

File Change
src/basic_memory/cli/commands/cloud/shares.py Share command implementation
src/basic_memory/cli/commands/cloud/__init__.py Registers share_app
tests/cli/test_share_commands.py 31 command, routing, validation, and rendering tests

Generated with Claude Code; maintainer follow-through by Basic Machines.

@groksrc
groksrc marked this pull request as ready for review June 11, 2026 07:02

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

Copy link
Copy Markdown

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: 22901c366d

ℹ️ 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 src/basic_memory/cli/commands/cloud/shares.py
Comment thread src/basic_memory/cli/commands/cloud/shares.py Outdated
@groksrc

groksrc commented Jun 11, 2026

Copy link
Copy Markdown
Member Author

Live prod QA found that bm cloud share ... --workspace <slug> failed with an opaque 400, while the same command with the org tenant UUID succeeded end-to-end. Root cause: resolve_configured_workspace returns the explicit --workspace value verbatim, but the cloud's X-Workspace-ID resolver only accepts a workspace/tenant UUID — yet users only ever see slugs and display names (in list-workspaces output and memory:// URLs), so the natural input never routed.

This commit resolves the workspace header client-side in the share commands:

  • A UUID is forwarded verbatim (covers per-project config workspace_id and the default chain, zero extra API calls).
  • Any other value is treated as a human identifier and mapped to the tenant UUID via a single get_available_workspaces lookup, matching with slug > tenant_id > name precedence (mirroring fix(mcp): resolve workspace display names and tenant ids in qualified project routes #979).
  • Ambiguous identifiers (a display name / slug matching >1 workspace) and unknown identifiers fail fast with clear errors that name the candidate / available workspace slugs, so users can self-serve.
  • The lookup happens once per invocation, only when the resolved value isn't already a UUID.
  • The list command now re-raises typer.Exit ahead of its broad handler, so these resolution errors aren't re-wrapped as "Unexpected error" (the double-error bug caught earlier on this branch).

--workspace help text updated to state it accepts a workspace slug, display name, or tenant ID. Scope kept to shares.py + its tests; resolve_configured_workspace and pull/push semantics untouched. Gates: pytest tests/cli/test_share_commands.py (30 passed), ruff check/format --check clean, ty check src tests test-int clean.

🤖 Generated with Claude Code

@groksrc groksrc added the On Hold Don't review or merge. Work is pending label Jun 12, 2026
@groksrc

groksrc commented Jun 12, 2026

Copy link
Copy Markdown
Member Author

This is good to go, but it needs to ship in v0.next and we have a maintenance release ahead of it.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds a new bm cloud share Typer subcommand group to manage public share links via the cloud /api/shares endpoints, aligning CLI capabilities with cloud share functionality requested in #880.

Changes:

  • Introduces bm cloud share create/list/update/revoke commands with workspace routing support and rich output formatting.
  • Registers the new share command group under existing bm cloud commands.
  • Adds a dedicated test suite covering success/error paths, flag validation, workspace resolution behavior, and URL encoding.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
src/basic_memory/cli/commands/cloud/shares.py New CLI implementation for share management (create/list/update/revoke) including workspace header routing and output formatting.
src/basic_memory/cli/commands/cloud/__init__.py Registers the new share_app Typer subcommand group under bm cloud share.
tests/cli/test_share_commands.py Adds comprehensive CLI tests for share commands, including workspace resolution and request payload assertions.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/basic_memory/cli/commands/cloud/shares.py
@phernandez

Copy link
Copy Markdown
Member

@codex review

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

Copy link
Copy Markdown

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: 440a2aa923

ℹ️ 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 src/basic_memory/cli/commands/cloud/shares.py Outdated
@phernandez phernandez removed the On Hold Don't review or merge. Work is pending label Jul 15, 2026
@phernandez
phernandez force-pushed the feat/880-cloud-share-cli branch from 440a2aa to a29860c Compare July 15, 2026 02:56
@phernandez

Copy link
Copy Markdown
Member

@codex review

@phernandez

Copy link
Copy Markdown
Member

recheck

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Breezy!

Reviewed commit: a29860cbdc

ℹ️ 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".

groksrc and others added 5 commits July 14, 2026 23:37
Surface the cloud /api/shares endpoints from the CLI so users can manage
public share links for notes without leaving the terminal.

New `bm cloud share` subcommand group:
- create <project> <permalink> [--expires-at] -> POST   /api/shares
- list [--project]                            -> GET    /api/shares
- update <token> [--enable|--disable|--expires-at] -> PATCH /api/shares/{token}
- revoke <token> [--force]                    -> DELETE /api/shares/{token}

Reuses the existing make_api_request() helper, ConfigManager cloud_host
lookup, and SubscriptionRequired/CloudAPIError handling that snapshot.py
uses. Rich table output mirrors `bm project list`. Payloads match the
cloud CreateShareRequest/UpdateShareRequest contracts (project_name,
note_permalink, expires_at, enabled).

Adds PublicShareResponse/PublicShareListResponse schemas and unit tests
with mocked HTTP following the snapshot CLI test patterns.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Drew Cain <groksrc@gmail.com>
…emas

Address review findings on `bm cloud share`:

- `create` re-wrapped the `typer.Exit(1)` from `_parse_expires_at()` as a
  spurious "Unexpected error: 1" trailing line because the broad
  `except Exception` caught it (typer.Exit subclasses Exception). Parse and
  validate --expires-at before entering the try block so the error surfaces
  as a single clean message, and add the `except typer.Exit: raise` guard for
  parity with `update`/`revoke`.
- Strengthen test_create_share_invalid_expires_at to assert
  "Unexpected error" is absent, which was masking the bug.
- Drop unused PublicShareResponse/PublicShareListResponse schemas; responses
  are parsed as raw dicts, so the schemas were dead code.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Drew Cain <groksrc@gmail.com>
… project filter

The `bm cloud share` commands authenticated but never sent the
X-Workspace-ID header that sibling cloud calls (cloud_utils._workspace_headers)
use for workspace scoping. The cloud /api/shares endpoints resolve the target
tenant from that header (resolve_workspace in basic-memory-cloud deps.py), so
share create/list/update/revoke for a team-workspace project were evaluated
against the caller's default tenant.

Resolve the workspace via resolve_configured_workspace (explicit --workspace >
project's configured workspace_id > global default) and pass X-Workspace-ID on
all four commands, mirroring the established cloud command pattern. Adds a
--workspace option to each, matching `bm cloud pull/push`.

Also URL-encode the list `--project` filter with urllib.parse.urlencode so
project names containing query-reserved characters (&, +, #, spaces) reach the
server as a single faithful value instead of splitting into stray query params.

Extends the mocked tests to assert the header is built/routed and that a
project name with special characters is percent-encoded.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Drew Cain <groksrc@gmail.com>
…space

Live prod QA found `bm cloud share ... --workspace <slug>` failed with an
opaque 400: resolve_configured_workspace returns the explicit --workspace
value verbatim, but the cloud's X-Workspace-ID resolver only accepts a
workspace/tenant UUID. Users see slugs and display names (list-workspaces
output, memory:// URLs), so the natural input never routed.

The share commands now resolve the workspace header client-side: a UUID is
forwarded verbatim (covers per-project config workspace_id and the default
chain, zero extra API calls), while any other value is treated as a human
identifier and mapped to the tenant UUID via a single get_available_workspaces
lookup, matching with slug > tenant_id > name precedence (mirroring #979).
Ambiguous and unknown identifiers fail fast with errors that name the
candidate / available workspace slugs, and the list command now re-raises
typer.Exit ahead of its broad handler so the resolution errors aren't
re-wrapped as "Unexpected error".

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Drew Cain <groksrc@gmail.com>
Signed-off-by: phernandez <paul@basicmachines.co>
@phernandez

Copy link
Copy Markdown
Member

@codex review

recheck

@phernandez
phernandez force-pushed the feat/880-cloud-share-cli branch from a29860c to 9b7df0d Compare July 15, 2026 04:37
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. What shall we delve into next?

Reviewed commit: 9b7df0d924

ℹ️ 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".

@phernandez
phernandez merged commit ddc7b2a into main Jul 15, 2026
23 checks passed
@phernandez
phernandez deleted the feat/880-cloud-share-cli branch July 15, 2026 05:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add bm cloud share command (create / list / unshare)

3 participants