Skip to content

feat(org): add commands to discover and manage organizations#297

Merged
BlackHole1 merged 1 commit into
mainfrom
feat/org-identity-commands
Jul 8, 2026
Merged

feat(org): add commands to discover and manage organizations#297
BlackHole1 merged 1 commit into
mainfrom
feat/org-identity-commands

Conversation

@BlackHole1

Copy link
Copy Markdown
Member

Connector commands (oo connector run, oo connector proxy, oo connector apps) accept --organization <name> and read an identity.organization default, but nothing let you discover which organizations the active account can actually use — agents saw the flag yet had to already know the valid names, and oo auth status never listed organizations (#296).

This adds an oo org command group backed by the existing org-control service. oo org list calls GET /v1/me/organizations, which already returns the account's full membership set — organizations you created come back with role: "creator" — so a single request answers "what can I pass to --organization", with no backend change required. The listing marks the entry matching the configured default, so you can see which identity connector commands use by default.

  • oo org list [--json] — list accessible organizations (name, id, role, current); text output is a color-coded aligned table.
  • oo org current [--json] — show the default organization identity, offline.
  • oo org use <name> — validate the name against the account's memberships, then persist identity.organization.
  • oo org clear — return connector commands to the personal identity, offline.

The group resolves an OOMOL account only (honoring the OO_API_KEY override) and is rejected when just a self-hosted connector is configured, consistent with other non-connector commands. Telemetry records bucketed counts and a configured-default flag, never organization names. Covered by unit tests for the request and formatting layers plus CLI tests for each subcommand, including the no-account and inaccessible-name paths.

Closes #296

Connector commands accept `--organization <name>` and read an
`identity.organization` default, but there was no way to discover which
organizations the active account can use. Agents could see the flag yet
had to already know the valid names, and `oo auth status` never listed
organizations.

Add an `oo org` command group backed by the existing org-control
service:

- `oo org list` fetches `GET /v1/me/organizations`, which already
  returns the full membership set (organizations you created appear with
  `role: "creator"`), so one request answers "what can I pass to
  `--organization`". It marks the entry matching the configured default.
- `oo org current` reports the default identity offline.
- `oo org use <name>` validates the name against the account's
  memberships before persisting `identity.organization`.
- `oo org clear` returns connector commands to the personal identity.

The group resolves an OOMOL account only and is rejected when just a
self-hosted connector is configured. Telemetry records bucketed counts
and a configured-default flag, never organization names.

Closes #296

Signed-off-by: Kevin Cui <bh@bugs.cc>
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

Summary by CodeRabbit

  • New Features

    • Added organization commands to manage and view your active organization context.
    • You can now list organizations, see the current default, switch the default, or clear it back to personal identity.
    • Organization lists support both readable table output and JSON.
  • Bug Fixes

    • Improved handling for empty or unavailable organization access, with clearer messages and safer fallback behavior.
    • Added more consistent output formatting for names, roles, and default indicators.

Walkthrough

This PR adds a new oo org CLI command group with four subcommands: list (shows accessible organizations with JSON/text output), current (shows the configured default organization offline), use <name> (validates and persists a default organization), and clear (removes the default organization setting). A shared module fetches and normalizes organization membership data from an API endpoint. The commands are wired into the CLI catalog, given telemetry decisions, English/Chinese i18n strings, and documentation updates. Tests cover unit and CLI-level behavior.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant CLI as oo org use
  participant Shared as listMemberOrganizations
  participant API as org-control API
  participant Settings as settingsStore

  User->>CLI: oo org use <name>
  CLI->>CLI: trim/validate name
  CLI->>Shared: fetch accessible organizations
  Shared->>API: GET /v1/me/organizations
  API-->>Shared: organizations list
  Shared-->>CLI: OrganizationView[]
  alt name matches accessible organization
    CLI->>Settings: setIdentityOrganization(name)
    CLI-->>User: success message
  else name not accessible
    CLI-->>User: CliUserError (not accessible)
  end
Loading

Possibly related PRs

  • oomol-lab/oo-cli#64: Both PRs modify the CLI command catalog wiring in catalog.ts, registering a new top-level command group into createCliCatalog().
  • oomol-lab/oo-cli#274: Both PRs build around the identity.organization config value, with the related PR introducing it for connector run identity resolution and this PR adding commands to list/use/clear it.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title matches the PR’s main change and follows the required type(scope): subject format.
Description check ✅ Passed The description is directly related to adding organization discovery and management commands.
Linked Issues check ✅ Passed The PR adds oo org list with JSON output, which satisfies the linked issue’s core requirement and related follow-ups.
Out of Scope Changes check ✅ Passed The added docs, tests, telemetry, and org subcommands all support the requested organization command group.
✨ Finishing Touches
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch feat/org-identity-commands

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (3)
src/application/commands/org/shared.test.ts (1)

15-108: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Missing coverage for the requestError path.

All three CliUserError branches in listMemberOrganizations are covered except errors.org.requestError (thrown when the fetcher itself throws, e.g. network failure). Consider adding a test where fetcher rejects/throws to assert this branch and its message construction via getUnexpectedRequestErrorMessage.

As per coding guidelines, "Any modification must include sufficient tests."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/application/commands/org/shared.test.ts` around lines 15 - 108,
`listMemberOrganizations` is missing test coverage for the
`errors.org.requestError` branch when the fetcher throws before a response is
returned. Add a new test in `shared.test.ts` that uses `createRequestContext`
with a `fetcher` which rejects/throws, then assert `expectCliUserError` returns
`errors.org.requestError` and that the message comes from
`getUnexpectedRequestErrorMessage`. Keep the existing success and
invalid-response tests unchanged while covering this unique error path.

Source: Coding guidelines

src/application/commands/org/shared.ts (1)

72-80: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Swallowed parse error loses debugging context.

The bare catch discards the original JSON.parse/schema-validation error entirely before throwing the generic errors.org.invalidResponse. Unlike the requestError path (line 58-62), which forwards error.message via getUnexpectedRequestErrorMessage, this path gives no way to diagnose malformed API responses in production logs.

♻️ Suggested fix: log the underlying cause before rethrowing
     try {
         return organizationsResponseSchema
             .parse(JSON.parse(rawResponse) as unknown)
             .organizations
             .map(toOrganizationView);
     }
-    catch {
+    catch (error) {
+        context.logger.warn({ error }, "Failed to parse organization membership response");
         throw new CliUserError("errors.org.invalidResponse", 1);
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/application/commands/org/shared.ts` around lines 72 - 80, The parsing
branch in shared.ts swallows the underlying JSON.parse or
organizationsResponseSchema validation error, so add handling in the try/catch
around organizationsResponseSchema.parse and toOrganizationView to preserve
debugging context. Catch the error as a symbol, log or surface its
message/details before throwing CliUserError("errors.org.invalidResponse", 1),
and keep the existing generic user-facing error while ensuring the original
cause is available in logs similar to the requestError path.
src/application/commands/org/list.ts (1)

90-156: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Extract the ANSI-aware table renderer shared by list commands. src/application/commands/org/list.ts and src/application/commands/connector/apps.ts both build dimmed headers, measure cells with Bun.stringWidth, and pad rows the same way. A small shared helper would remove the duplication and keep future list commands consistent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/application/commands/org/list.ts` around lines 90 - 156, The ANSI-aware
table rendering logic in formatOrganizationsAsText is duplicated with the same
header dimming, width calculation via visibleWidth/Bun.stringWidth, and row
padding used by the connector apps list command. Extract this into a shared
helper used by formatOrganizationsAsText and the corresponding renderer in
connector/apps.ts, keeping createOrgListColumns and joinOrgListRow as the local
entry points or adapting them to the shared formatter. Ensure the shared helper
preserves ANSI-safe width handling and two-space gutter alignment so all list
commands stay consistent.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/application/commands/org/list.ts`:
- Around line 90-156: The ANSI-aware table rendering logic in
formatOrganizationsAsText is duplicated with the same header dimming, width
calculation via visibleWidth/Bun.stringWidth, and row padding used by the
connector apps list command. Extract this into a shared helper used by
formatOrganizationsAsText and the corresponding renderer in connector/apps.ts,
keeping createOrgListColumns and joinOrgListRow as the local entry points or
adapting them to the shared formatter. Ensure the shared helper preserves
ANSI-safe width handling and two-space gutter alignment so all list commands
stay consistent.

In `@src/application/commands/org/shared.test.ts`:
- Around line 15-108: `listMemberOrganizations` is missing test coverage for the
`errors.org.requestError` branch when the fetcher throws before a response is
returned. Add a new test in `shared.test.ts` that uses `createRequestContext`
with a `fetcher` which rejects/throws, then assert `expectCliUserError` returns
`errors.org.requestError` and that the message comes from
`getUnexpectedRequestErrorMessage`. Keep the existing success and
invalid-response tests unchanged while covering this unique error path.

In `@src/application/commands/org/shared.ts`:
- Around line 72-80: The parsing branch in shared.ts swallows the underlying
JSON.parse or organizationsResponseSchema validation error, so add handling in
the try/catch around organizationsResponseSchema.parse and toOrganizationView to
preserve debugging context. Catch the error as a symbol, log or surface its
message/details before throwing CliUserError("errors.org.invalidResponse", 1),
and keep the existing generic user-facing error while ensuring the original
cause is available in logs similar to the requestError path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 652018b5-1cfd-4989-833e-1992f4ca4c78

📥 Commits

Reviewing files that changed from the base of the PR and between 2c04b6a and 4c1e2b5.

⛔ Files ignored due to path filters (2)
  • src/application/bootstrap/__snapshots__/run-cli.test.ts.snap is excluded by !**/*.snap
  • src/application/commands/config/__snapshots__/index.cli.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (14)
  • docs/commands.md
  • docs/commands.zh-CN.md
  • src/application/commands/catalog.ts
  • src/application/commands/org/clear.ts
  • src/application/commands/org/current.ts
  • src/application/commands/org/index.cli.test.ts
  • src/application/commands/org/index.ts
  • src/application/commands/org/list.test.ts
  • src/application/commands/org/list.ts
  • src/application/commands/org/shared.test.ts
  • src/application/commands/org/shared.ts
  • src/application/commands/org/use.ts
  • src/application/commands/telemetry-decisions.test.ts
  • src/i18n/catalog.ts

@BlackHole1 BlackHole1 merged commit cecfa31 into main Jul 8, 2026
6 checks passed
@BlackHole1 BlackHole1 deleted the feat/org-identity-commands branch July 8, 2026 09:02
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 CLI support for listing accessible organizations

1 participant