From fa0032c9505deaf87519450d858b1e21f81d7701 Mon Sep 17 00:00:00 2001 From: Kevin Cui Date: Fri, 28 Aug 2026 12:13:55 -0400 Subject: [PATCH 1/2] feat(team)!: drop --personal and oo team clear Every team-aware route (connector, cli-api, open-flow, fusion-api) now has the gateway apply the account's default team whenever a request carries no team header, so `--personal` no longer selects a personal identity: it only drops the header and lands on the same server-side default team. The flag, its conflict guard, `oo team clear` (never used since launch), and the "personal identity" wording in help, status output, docs, and the bundled skill are gone. `identity_source` reports `none` instead of `personal` when nothing selects a team. With the flag gone the variables, file upload, and connector session resolvers became identical, so they collapse into `resolveAccountTeamIdentity` in _src/application/commands/team/identity.ts_, which owns the `--team` guard, the account default, the ladder, the execution gate, and the telemetry property. `teamIdentityOptions` shrinks to `teamOption`. Nothing on disk changes: `--personal` never persisted, and an account whose default was cleared simply has no `team` fields, which the schema already allows. BREAKING CHANGE: `--personal` is removed from `oo connector run/proxy/apps/search`, `oo search`, `oo variables *`, and `oo file upload` (exit 2 as an unknown option). `oo team clear` is removed. To act as the server-side default team, omit `--team`. Telemetry `identity_source` reports `none` where it reported `personal`. Signed-off-by: Kevin Cui --- .../oo/references/connector-execution.md | 11 +- docs/commands.md | 178 +++++++----------- docs/commands.zh-CN.md | 122 +++++------- docs/self-hosted-connector.md | 5 +- docs/self-hosted-connector.zh-CN.md | 5 +- src/application/auth/default-team.test.ts | 61 +----- src/application/auth/default-team.ts | 36 +--- .../__snapshots__/search.cli.test.ts.snap | 4 - .../auth/__snapshots__/index.cli.test.ts.snap | 6 +- .../commands/auth/index.cli.test.ts | 4 +- src/application/commands/auth/status.ts | 4 +- .../__snapshots__/index.cli.test.ts.snap | 4 - src/application/commands/connector/apps.ts | 9 +- .../commands/connector/index.cli.test.ts | 56 +----- src/application/commands/connector/proxy.ts | 9 +- src/application/commands/connector/run.ts | 9 +- src/application/commands/connector/search.ts | 9 +- .../commands/connector/session.test.ts | 48 ++--- src/application/commands/connector/session.ts | 80 ++++---- .../commands/connector/shared.test.ts | 2 +- .../__snapshots__/upload.cli.test.ts.snap | 3 - src/application/commands/file/shared.ts | 59 +----- .../commands/file/upload.cli.test.ts | 58 ------ src/application/commands/file/upload.ts | 12 +- src/application/commands/team/clear.ts | 77 -------- src/application/commands/team/current.ts | 10 +- .../commands/team/identity.test.ts | 87 +++------ src/application/commands/team/identity.ts | 134 ++++++++----- .../commands/team/index.cli.test.ts | 101 +--------- src/application/commands/team/index.ts | 2 - src/application/commands/team/shared.ts | 2 +- .../commands/telemetry-decisions.test.ts | 25 +-- src/application/commands/variables/create.ts | 15 +- src/application/commands/variables/delete.ts | 15 +- src/application/commands/variables/get.ts | 15 +- .../commands/variables/index.cli.test.ts | 45 ----- src/application/commands/variables/list.ts | 16 +- src/application/commands/variables/shared.ts | 63 +------ src/application/schemas/auth.test.ts | 24 --- src/application/schemas/auth.ts | 14 -- src/i18n/catalog.ts | 72 +------ 41 files changed, 372 insertions(+), 1139 deletions(-) delete mode 100644 src/application/commands/team/clear.ts diff --git a/contrib/skills/shared/oo/references/connector-execution.md b/contrib/skills/shared/oo/references/connector-execution.md index 058c93f2..6e06c5e1 100644 --- a/contrib/skills/shared/oo/references/connector-execution.md +++ b/contrib/skills/shared/oo/references/connector-execution.md @@ -171,8 +171,8 @@ the user said: - If the user does not mention any team, add nothing extra. The run then uses the team selected by the `OO_TEAM_ID` / `OO_TEAM_NAME` environment variables when set, then the default team saved on their account when one is set, and - their personal identity otherwise — omitting the flags does not force a - personal run. Check `oo team current` when you need to know which one applies. + the server-side default team otherwise. Check `oo team current` when you need + to know which one applies. - If the user asks to run as a specific team (for example "run this as Acme" or "use my Acme team"), add `--team ""`, using the team name the user gave: @@ -194,13 +194,12 @@ oo connector proxy "" \ --json ``` -- If the user has a default team (from the env variables or the config default) - but explicitly asks for this one run to be personal, add `--personal`. This is - the only way to force a personal run when a default team is in effect. +- There is no personal run: every run is attributed to a team, and with no + flag the server applies the account's default team. To run as a different + team than the default, name it with `--team`. Facts: -- `--team` and `--personal` cannot be combined. - Do not guess a team name. If the user is vague about which team to use, ask before running. - The action schema itself is identity-independent, so `oo connector schema` diff --git a/docs/commands.md b/docs/commands.md index 99c74434..27939a96 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -50,12 +50,12 @@ use. Truthy values are `1`, `true`, `yes`, or `on` (case-insensitive). login. When set, the CLI builds an in-memory account and does not read, require, or write `auth.toml`, and it takes precedence over any saved account. Because no saved account can be in effect while it is set, `oo auth logout`, - `oo auth switch`, `oo team use`, and `oo team clear` become no-ops that leave - `auth.toml` untouched, and `oo auth login` still saves the account but reports - that this variable outranks it. `oo auth status` reports the identity this - variable provides. No saved default team applies either: the key may belong to - a different account, so commands run under the personal identity unless - `OO_TEAM_ID` or `OO_TEAM_NAME` selects a team. + `oo auth switch`, and `oo team use` become no-ops that leave `auth.toml` + untouched, and `oo auth login` still saves the account but reports that this + variable outranks it. `oo auth status` reports the identity this variable + provides. No saved default team applies either: the key may belong to a + different account, so commands send no team selection (the server applies + its default team) unless `OO_TEAM_ID` or `OO_TEAM_NAME` selects a team. - `OO_ENDPOINT`: Base endpoint domain (for example `oomol.com` or `oomol.dev`) used to derive every service URL for execution commands. It pairs with `OO_API_KEY`, overrides the endpoint of a saved account (including the @@ -76,9 +76,8 @@ use. Truthy values are `1`, `true`, `yes`, or `on` (case-insensitive). - `OO_TEAM_ID`: Run team-aware commands (`oo connector run`, `oo connector proxy`, `oo connector apps`, `oo connector search` / `oo search`, `oo variables list/get/create/delete`, and `oo file upload`) under the team - with this id. It takes - precedence over `OO_TEAM_NAME` and the account's default team; the per-run - `--team` and `--personal` flags still outrank it. Before execution the CLI + with this id. It takes precedence over `OO_TEAM_NAME` and the account's + default team; the per-run `--team` flag still outranks it. Before execution the CLI validates the id and resolves its team name (one extra request per invocation), so requests carry both the name and the id; an id the account cannot use — not a member, no such team, team deleted — fails with exit @@ -95,10 +94,9 @@ use. Truthy values are `1`, `true`, `yes`, or `on` (case-insensitive). `OO_TEAM_ID` is set, and ignored by connector commands when the connector target is self-hosted; variables commands always honor it. - Team-aware commands resolve their team identity with this precedence: - `--personal` / `--team` > `OO_TEAM_ID` > `OO_TEAM_NAME` > the active account's - default team > your personal identity. Variables commands and `oo file - upload` have no personal scope: with nothing selected they fall back to the - server-side default team. + `--team` > `OO_TEAM_ID` > `OO_TEAM_NAME` > the active account's default team. + With nothing selected they send no team selection and the server applies its + own default team; there is no private, per-user scope. - `OO_SKILLS_SYNC_DISABLED`: A truthy value disables the startup managed-skill synchronization, so the CLI writes no skill files into agent home directories such as `~/.agents` or `~/.claude`. @@ -359,9 +357,9 @@ Show every saved auth account and validate the API key of the active one. - The active-identity block also shows a `Default team` line, resolved the same way `oo team current` resolves it: the `OO_TEAM_ID` / `OO_TEAM_NAME` env override when set (annotated with the variable that supplies it), otherwise - the active account's default team, otherwise `personal (no default team)`. - With `OO_API_KEY` set the line always reads `personal (no default team)` - unless an `OO_TEAM_*` variable selects one. + the active account's default team, otherwise `server default (no saved + team)`. With `OO_API_KEY` set the line always reads `server default (no saved + team)` unless an `OO_TEAM_*` variable selects one. - When `OO_TEAM_ID` or `OO_TEAM_NAME` supplies the identity, the missing half is looked up — the id resolves to its name, the name to its id through the account's team memberships — and the line shows ` ()`. If the @@ -570,15 +568,15 @@ Alias for `oo auth logout`. ## Teams -Team identity lets team-aware commands act as a team instead of your personal -account: the connector commands (`oo connector run`, `oo connector proxy`, -`oo connector apps`), the variables commands (`oo variables -list/get/create/delete`), whose data is team-owned in the first place, and -`oo file upload`, whose upload is billed and metered under the team. One -ladder selects it: the per-run `--personal` (drop every saved and env-selected -team) or `--team ` first, then the `OO_TEAM_ID` / `OO_TEAM_NAME` -environment overrides, then the default saved on the active account. These -commands help discover which teams your account can use and manage that +Team identity selects which team team-aware commands act for: the connector +commands (`oo connector run`, `oo connector proxy`, `oo connector apps`), the +variables commands (`oo variables list/get/create/delete`), whose data is +team-owned in the first place, and `oo file upload`, whose upload is billed and +metered under the team. One ladder selects it: the per-run `--team ` +first, then the `OO_TEAM_ID` / `OO_TEAM_NAME` environment overrides, then the +default saved on the active account. When nothing selects a team the command +sends no team selection and the server applies the account's default team. +These commands help discover which teams your account can use and manage that default. The default team belongs to the saved account, so switching accounts with @@ -590,9 +588,9 @@ no saved default applies at all — use `OO_TEAM_ID` / `OO_TEAM_NAME` there. `oo team list` and `oo team use` query OOMOL for your team memberships, so they require an OOMOL account and are unavailable when only a self-hosted connector -is configured. `oo team current` and `oo team clear` work regardless: they read -and write local state, and `oo team current` only enriches its output with a -team name when an account is available to look one up. +is configured. `oo team current` works regardless: it reads local state and +only enriches its output with a team name when an account is available to look +one up. `oo auth login` (and its `oo login` alias) persists the default automatically: it keeps the account's still-valid stored default, otherwise adopts the @@ -613,9 +611,8 @@ read-only. - Output: pass the `name` value to `--team ` (or `oo team use `), and the `id` value to `OO_TEAM_ID`. - Output: text output prints one column-aligned row per team and marks the - current default. When the account has no teams, it reports that connector - commands run under your personal identity and variables commands use the - server-side default team. + current default. When the account has no teams, it reports that team-aware + commands send no team selection. - Behavior: when the account's default team was stored without its id, this command fills the id in from the listing it already fetched. No extra request is sent, and a failure to write is ignored. @@ -623,9 +620,8 @@ read-only. ### `oo team current` Show the team identity used by team-aware commands (connector, variables, file -upload) when no `--team` / `--personal` flag is given: the `OO_TEAM_ID` / -`OO_TEAM_NAME` environment override when set, otherwise the active account's -default team. +upload) when no `--team` flag is given: the `OO_TEAM_ID` / `OO_TEAM_NAME` +environment override when set, otherwise the active account's default team. - Sends one request only when `OO_TEAM_ID` or `OO_TEAM_NAME` supplies the identity, to complete and validate the missing half: an id resolves to its @@ -639,8 +635,9 @@ default team. - Options: `--format=json` and `--json` print a JSON object. - Output: JSON is `{ "team": , "teamId": , "source": <"env_id"|"env_name"|"account"|null>, "status": }`. `source` - says which mechanism selects the team, and is `null` when connector commands - run under your personal identity. `status` reports the team lookup: `null` + says which mechanism selects the team, and is `null` when no default team is + saved (commands then use the server-side default team). `status` reports the + team lookup: `null` whenever none was attempted (the `account` source, or `--dry-run`-style offline paths), otherwise one of `valid`, `not_a_member`, `not_found`, `deleted`, `request_failed`, `request_failed_sandbox`, or `no_credential`. @@ -669,24 +666,6 @@ access it. - Behavior: with `OO_API_KEY` set the command saves nothing, exits `0`, and reports that this variable has no saved default team. -### `oo team clear` - -Clear the active account's default team identity. Connector commands then run -under your personal identity and variables commands use the server-side default -team, unless `OO_TEAM_ID` / `OO_TEAM_NAME` still selects a team — this command -removes only the saved default and does not affect the environment override. -This command is offline. - -- Behavior: removes the default team from the active account. When no default - is saved it reports that connector commands already run under your personal - identity and variables commands use the server-side default team. -- Behavior: with `OO_API_KEY` set the command clears nothing, exits `0`, and - reports that this variable already runs under your personal identity. -- Behavior: when `OO_TEAM_ID` / `OO_TEAM_NAME` is set, the output reports that - the environment variable still selects a team for team-aware commands, so - clearing the default does not stop a team from being selected. Unset the - variable to do that. - ## LLM ### `oo llm config` @@ -1067,12 +1046,9 @@ Search connector actions with free-form text. - Options: `--format=json` and `--json` print a JSON array of matching action entries. - Options: `--team ` reports each result's `authenticated` state under the - given team identity instead of your personal identity. When omitted, the - effective identity follows `OO_TEAM_ID` / `OO_TEAM_NAME`, then the - active account's default team, otherwise your personal identity. -- Options: `--personal` reports `authenticated` under your personal identity and - ignores the `OO_TEAM_ID` / `OO_TEAM_NAME` environment variables and any - configured default team. It cannot be combined with `--team`. + given team identity. When omitted, the effective identity follows + `OO_TEAM_ID` / `OO_TEAM_NAME`, then the active account's default team, + otherwise the server-side default team. - Output: every match includes `authenticated` and `accessStatus`. - Output: JSON entries include the stable CLI fields `service`, `name`, `description`, `authenticated`, and `accessStatus`. `accessStatus` is either @@ -1143,13 +1119,10 @@ Validate input data and run one connector action. - Options: `--wait-result` submits an async submit action and then polls its configured result action. This option is only valid when the selected action schema declares an async submit lifecycle. -- Options: `--team ` runs the action under the given team identity - instead of your personal identity. When omitted, the action runs under the - team selected by `OO_TEAM_ID` / `OO_TEAM_NAME` if set, then the - active account's default team, otherwise your personal identity. -- Options: `--personal` runs the action under your personal identity and - ignores the `OO_TEAM_ID` / `OO_TEAM_NAME` environment variables and any - configured default team. It cannot be combined with `--team`. +- Options: `--team ` runs the action under the given team identity. When + omitted, the action runs under the team selected by `OO_TEAM_ID` / + `OO_TEAM_NAME` if set, then the active account's default team, otherwise the + server-side default team. - Options: `--format=json` and `--json` print a JSON object. - Output: non-dry-run JSON output mirrors the stable response shape `{ data, meta: { executionId } }`. @@ -1172,8 +1145,8 @@ Validate input data and run one connector action. terminals show progress on stderr. JSON output does not include progress text. - Notes: against a self-hosted connector, `--team` is rejected with exit `2`, the account's default team and the `OO_TEAM_ID` / - `OO_TEAM_NAME` environment variables are ignored, and `--personal` is - accepted. `--wait` and `--wait-result` fail with the existing unsupported + `OO_TEAM_NAME` environment variables are ignored. `--wait` and + `--wait-result` fail with the existing unsupported errors because the self-hosted runtime does not expose the async lifecycle contract. @@ -1185,13 +1158,10 @@ read-only. - Arguments: `[serviceName]` is optional. When omitted, the command lists every connected app across all providers. When provided, the listing is scoped to that one service. -- Options: `--team ` lists connected apps under the given team identity - instead of your personal identity. When omitted, the listing uses the team - selected by `OO_TEAM_ID` / `OO_TEAM_NAME` if set, then the account's default - team, otherwise your personal identity. -- Options: `--personal` lists connected apps under your personal identity and - ignores the `OO_TEAM_ID` / `OO_TEAM_NAME` environment variables and any - configured default team. It cannot be combined with `--team`. +- Options: `--team ` lists connected apps under the given team identity. + When omitted, the listing uses the team selected by `OO_TEAM_ID` / + `OO_TEAM_NAME` if set, then the account's default team, otherwise the + server-side default team. - Options: `--format=json` and `--json` print a JSON array. - Output: JSON entries include the stable CLI fields `service`, `connectionName`, `displayName`, `accountLabel`, `status`, `authType`, @@ -1206,9 +1176,8 @@ read-only. - Notes: use the listed `connectionName` value with `oo connector run --connection-name `. - Notes: against a self-hosted connector, `--team` is rejected with exit - `2`, the account's default team and the `OO_TEAM_ID` / - `OO_TEAM_NAME` environment variables are ignored, and `--personal` is - accepted. + `2`, and the account's default team and the `OO_TEAM_ID` / + `OO_TEAM_NAME` environment variables are ignored. ### `oo connector proxy ` @@ -1235,13 +1204,10 @@ Proxy a provider API request through a connected connector app. options. - Options: `--body` is parsed as JSON. To send a text body, pass a JSON string such as `"hello"`. -- Options: `--team ` runs the proxy request under the given team identity - instead of your personal identity. When omitted, the request runs under the - team selected by `OO_TEAM_ID` / `OO_TEAM_NAME` if set, then the - active account's default team, otherwise your personal identity. -- Options: `--personal` runs the proxy request under your personal identity and - ignores the `OO_TEAM_ID` / `OO_TEAM_NAME` environment variables and any - configured default team. It cannot be combined with `--team`. +- Options: `--team ` runs the proxy request under the given team + identity. When omitted, the request runs under the team selected by + `OO_TEAM_ID` / `OO_TEAM_NAME` if set, then the active account's default team, + otherwise the server-side default team. - Options: `--format=json` and `--json` print a JSON object. - Output: JSON output keeps the stable shape `{ data: { status, headers, data }, meta: { executionId, service } }`. @@ -1310,12 +1276,9 @@ Search connector actions with one free-form query. - Options: `--format=json` and `--json` print a JSON array of matching action entries. - Options: `--team ` reports each result's `authenticated` state under the - given team identity instead of your personal identity. When omitted, the - effective identity follows `OO_TEAM_ID` / `OO_TEAM_NAME`, then the - active account's default team, otherwise your personal identity. -- Options: `--personal` reports `authenticated` under your personal identity and - ignores the `OO_TEAM_ID` / `OO_TEAM_NAME` environment variables and any - configured default team. It cannot be combined with `--team`. + given team identity. When omitted, the effective identity follows + `OO_TEAM_ID` / `OO_TEAM_NAME`, then the active account's default team, + otherwise the server-side default team. - Output: every match includes `authenticated` and `accessStatus`. - Output: JSON entries include the stable CLI fields `service`, `name`, `description`, `authenticated`, and `accessStatus`. `accessStatus` is either @@ -2604,9 +2567,6 @@ Upload one file to the temporary file cache. is billed and metered under that team. When omitted, the team comes from `OO_TEAM_ID` / `OO_TEAM_NAME` when set, otherwise the active account's default team. -- Options: `--personal` sends no team selection, ignoring `OO_TEAM_ID` / - `OO_TEAM_NAME` and the account default, and lets the server apply its own - default team. It cannot be combined with `--team`. - Notes: the team selection is sent only to the file service requests; the presigned part uploads go straight to storage without it. - Notes: the uploaded file expires after seven days and is deleted on the server. @@ -2651,14 +2611,13 @@ account; values are stored as strings (serialize JSON yourself if needed). Variables belong to a team, not to a single user: every member of the team reads and writes the same set, and the last write wins. Each subcommand -resolves the team it acts for with the shared ladder `--personal` > `--team -` > `OO_TEAM_ID` > `OO_TEAM_NAME` > the active account's default team > -the server-side default team. `--personal` means "send no team selection", so -the server applies its own default team; it does not create a private, -per-user scope, and it cannot be combined with `--team` (exit `2`, as is an -empty `--team` value). - -Every subcommand accepts `--team ` and `--personal`. An account that +resolves the team it acts for with the shared ladder `--team ` > +`OO_TEAM_ID` > `OO_TEAM_NAME` > the active account's default team > the +server-side default team. With nothing selected the command sends no team +selection and the server applies its own default team; there is no private, +per-user scope. An empty `--team` value exits `2`. + +Every subcommand accepts `--team `. An account that belongs to no team at all cannot use variables: the command exits `1` and says so. Selecting a team the account is not a member of, or one that does not exist, also exits `1`. @@ -2670,9 +2629,7 @@ pagination; up to 200 per team). - Text output: one line per variable, `name` and `updatedAt` only. Full values are not printed; use `oo variables get` or `--json` to read a value. -- Options: `--team ` runs the command for that team; `--personal` uses - the server-side default team, ignoring `OO_TEAM_ID` / `OO_TEAM_NAME` and any - saved default team. +- Options: `--team ` runs the command for that team. - Options: `--format ` / `--json` return structured output as `{ "variables": [{ "name", "value", "updatedAt", "updatedBy" }] }` with full values. `updatedBy` is the id of the team member who last wrote the variable @@ -2685,8 +2642,7 @@ Read the value of a variable of the current team. - Arguments: `` is required (1-256 characters; no `/` or control characters). - Text output: the raw value followed by a newline. -- Options: `--team ` and `--personal` select the team, as for - `oo variables list`. +- Options: `--team ` selects the team, as for `oo variables list`. - Options: `--format ` / `--json` return `{ "name", "value", "updatedAt", "updatedBy" }`. - Notes: exits non-zero if the variable does not exist. @@ -2702,8 +2658,7 @@ Create or replace a variable for the current team (last-write-wins). - Options: `--from-file ` reads the value verbatim from a UTF-8 file. - Options: `--stdin` reads the value verbatim from standard input until EOF; it errors if stdin is an interactive terminal. -- Options: `--team ` and `--personal` select the team, as for - `oo variables list`. +- Options: `--team ` selects the team, as for `oo variables list`. - Options: `--format ` / `--json` return `{ "name", "value", "updatedAt", "updatedBy" }`. - Notes: the value is limited to 64 KiB (65536 bytes, UTF-8). @@ -2716,8 +2671,7 @@ Delete a variable of the current team. Idempotent: succeeds even if the name does not exist. - Arguments: `` is required. -- Options: `--team ` and `--personal` select the team, as for - `oo variables list`. +- Options: `--team ` selects the team, as for `oo variables list`. - Options: `--json` returns `{ "name", "deleted": true }`. ## Shell Completion diff --git a/docs/commands.zh-CN.md b/docs/commands.zh-CN.md index 4bd32b54..a913db6e 100644 --- a/docs/commands.zh-CN.md +++ b/docs/commands.zh-CN.md @@ -41,10 +41,11 @@ CLI 读取以下环境变量以支持内置和自动化场景。真值为 `1`、 - `OO_API_KEY`:使用该 API key 执行命令,无需交互式登录。设置后 CLI 会构造一个 内存账号,不读取、不要求、也不写入 `auth.toml`,且优先级高于任何已保存的账号。 由于设置它之后任何已保存账号都不会生效,`oo auth logout`、`oo auth switch`、 - `oo team use` 与 `oo team clear` 会成为空操作并保持 `auth.toml` 不变; + `oo team use` 会成为空操作并保持 `auth.toml` 不变; `oo auth login` 仍会保存账号,但会说明该变量的优先级高于它。`oo auth status` 会展示该变量提供的身份。已保存的默认团队同样不会生效:该 key 可能属于另一个 - 账号,因此除非设置了 `OO_TEAM_ID` 或 `OO_TEAM_NAME`,命令都以个人身份运行。 + 账号,因此除非设置了 `OO_TEAM_ID` 或 `OO_TEAM_NAME`,命令都不发送团队选择, + 由服务端套用默认团队。 - `OO_ENDPOINT`:基础域名(例如 `oomol.com` 或 `oomol.dev`),用于派生执行命令的 所有服务 URL。它与 `OO_API_KEY` 搭配使用,会覆盖已保存账号的 endpoint(包括 `oo auth status` 展示与校验所用的 endpoint),并决定 `oo auth login` 校验所用的 @@ -62,9 +63,8 @@ CLI 读取以下环境变量以支持内置和自动化场景。真值为 `1`、 - `OO_TEAM_ID`:让团队相关命令(`oo connector run`、`oo connector proxy`、 `oo connector apps`、`oo connector search` / `oo search`、 `oo variables list/get/create/delete`,以及 `oo file upload`)以该 id - 对应的团队身份运行。 - 优先级高于 `OO_TEAM_NAME` 和账号保存的默认团队;每次运行的 `--team` - 与 `--personal` 标志仍然优先于它。执行前 CLI 会校验该 id 并解析出团队 + 对应的团队身份运行。优先级高于 `OO_TEAM_NAME` 和账号保存的默认团队; + 每次运行的 `--team` 标志仍然优先于它。执行前 CLI 会校验该 id 并解析出团队 名称(每次调用多一个请求),因此请求会同时携带名称与 id;账号无法使用 的 id——不是成员、团队不存在、团队已删除——以退出码 `1` 失败。若查询 本身无法完成,则只按原样发送 id,由服务端裁决。connector 目标为自部署 @@ -77,9 +77,9 @@ CLI 读取以下环境变量以支持内置和自动化场景。真值为 `1`、 `OO_TEAM_ID` 时会被忽略;connector 目标为自部署服务时,只有 connector 命令会忽略它,variables 命令始终遵循它。 - 团队相关命令按以下优先级解析团队身份: - `--personal` / `--team` > `OO_TEAM_ID` > `OO_TEAM_NAME` > - 当前账号保存的默认团队 > 个人身份。variables 命令与 `oo file upload` - 没有个人作用域:没有选中任何团队时回落到服务端默认团队。 + `--team` > `OO_TEAM_ID` > `OO_TEAM_NAME` > 当前账号保存的默认团队。 + 没有选中任何团队时不发送团队选择,由服务端套用它自己的默认团队;不存在 + 按用户私有的作用域。 - `OO_SKILLS_SYNC_DISABLED`:设为真值会禁用启动时的 managed skill 同步, 使 CLI 不会向 `~/.agents`、`~/.claude` 等代理主目录写入任何 skill 文件。 - `OO_NO_SELF_UPDATE`:设为真值会禁用 `oo update`、`oo install` 和 @@ -290,9 +290,9 @@ oo flow 发出的请求数。 - 当前身份区块还会显示一行「默认团队」,其解析方式与 `oo team current` 相同:设置了 `OO_TEAM_ID` / `OO_TEAM_NAME` 时显示 env 覆盖值(并标注来源 - 变量),否则显示当前账号保存的默认团队,都未设置时显示个人身份 - (未设置默认团队)。设置了 `OO_API_KEY` 时,除非 `OO_TEAM_*` 变量选择了 - 团队,否则该行恒为个人身份。 + 变量),否则显示当前账号保存的默认团队,都未设置时显示服务端默认团队 + (未保存默认团队)。设置了 `OO_API_KEY` 时,除非 `OO_TEAM_*` 变量选择了 + 团队,否则该行恒为服务端默认团队。 - 当身份来自 `OO_TEAM_ID` 或 `OO_TEAM_NAME` 时,会查询补全缺失的那一半——id 解析出名称,名称通过账号的团队成员关系解析出 id——该行显示为 `<名称>()`。查询未成功时仍会显示 env 提供的值,并附上原因:当前账号 @@ -473,13 +473,13 @@ oo flow ## 团队 -团队身份让团队相关命令以某个团队身份运行,而非个人账号:包括 connector 命令 +团队身份决定团队相关命令以哪个团队运行:包括 connector 命令 (`oo connector run`、`oo connector proxy`、`oo connector apps`)、variables 命令(`oo variables list/get/create/delete`,其数据本身就归团队所有),以及 -`oo file upload`(上传按该团队计费与计量)。它由同一条 -优先级阶梯选出:先是每次运行的 `--personal`(放弃所有已保存与 env 选定的团队)或 -`--team `,其次是环境变量 `OO_TEAM_ID` / `OO_TEAM_NAME`,最后是保存在当前 -账号上的默认团队。下列命令用于发现当前账号可用的团队并管理该默认值。 +`oo file upload`(上传按该团队计费与计量)。它由同一条优先级阶梯选出:先是每次 +运行的 `--team `,其次是环境变量 `OO_TEAM_ID` / `OO_TEAM_NAME`,最后是 +保存在当前账号上的默认团队。没有任何一项选中团队时,命令不发送团队选择,由服务端 +套用该账号的默认团队。下列命令用于发现当前账号可用的团队并管理该默认值。 默认团队属于已保存的账号:用 `oo auth switch` 切换账号时默认团队随之切换, `oo auth logout` 会连同账号一起移除它。旧版本把默认团队保存在全局配置项 @@ -488,9 +488,8 @@ oo flow `OO_TEAM_NAME`。 `oo team list` 与 `oo team use` 需要向 OOMOL 查询团队成员关系,因此需要 OOMOL -账号;当仅配置了自部署 Connector 时不可用。`oo team current` 与 `oo team clear` -不受此限制:它们读写本地状态,`oo team current` 只在有账号可用时才额外查询 -团队名称来丰富输出。 +账号;当仅配置了自部署 Connector 时不可用。`oo team current` 不受此限制:它只读 +本地状态,且只在有账号可用时才额外查询团队名称来丰富输出。 `oo auth login`(及其别名 `oo login`)会自动持久化该默认值:账号上仍然有效的 默认团队会被保留,否则采用后端创建的 `system_created` 默认团队 @@ -508,15 +507,13 @@ oo flow - 输出:将 `name` 的值传给 `--team `(或 `oo team use `),将 `id` 的值传给 `OO_TEAM_ID`。 - 输出:文本输出为每个团队打印一行列对齐的记录,并标出当前默认团队。当账号没有任何 - 团队时,会提示 connector 命令以个人身份运行,variables 命令使用服务端 - 默认团队。 + 团队时,会提示团队相关命令不会发送团队选择。 - 行为:当账号保存的默认团队缺少团队 id 时,该命令会用刚获取的成员关系列表补齐 它,不发送额外请求;写入失败会被忽略。 ### `oo team current` -显示未传 `--team` / `--personal` 时团队相关命令(connector、variables、file -upload)使用的 +显示未传 `--team` 时团队相关命令(connector、variables、file upload)使用的 团队身份:设置了 `OO_TEAM_ID` / `OO_TEAM_NAME` 环境变量时为 env 指定的团队, 否则为当前账号保存的默认团队。 @@ -529,7 +526,7 @@ upload)使用的 - 选项:`--format=json` 与 `--json` 输出 JSON 对象。 - 输出:JSON 为 `{ "team": , "teamId": , "source": <"env_id"|"env_name"|"account"|null>, "status": }`。`source` - 表示团队由哪种机制选定;connector 命令以个人身份运行时为 `null`。`status` + 表示团队由哪种机制选定;未保存默认团队 (命令使用服务端默认团队) 时为 `null`。`status` 报告团队查询的结果:未尝试查询时为 `null`(account 来源,或 `--dry-run` 这类 离线路径),否则为 `valid`、`not_a_member`、`not_found`、`deleted`、 `request_failed`、`request_failed_sandbox` 或 `no_credential` 之一。 @@ -553,20 +550,6 @@ upload)使用的 - 行为:设置了 `OO_API_KEY` 时命令不保存任何内容,以 `0` 退出,并说明该变量没有 可保存的默认团队。 -### `oo team clear` - -清除当前账号保存的默认团队身份。之后 connector 命令以个人身份运行,variables -命令使用服务端默认团队;但若 `OO_TEAM_ID` / `OO_TEAM_NAME` 仍在选择团队则不然 -——该命令只移除已保存的默认值,不影响环境变量覆盖。该命令离线运行。 - -- 行为:从当前账号移除默认团队。当未保存默认值时,会提示 connector 命令 - 本就以个人身份运行,variables 命令使用服务端默认团队。 -- 行为:设置了 `OO_API_KEY` 时命令不清除任何内容,以 `0` 退出,并说明该变量 - 本就以个人身份运行。 -- 行为:设置了 `OO_TEAM_ID` / `OO_TEAM_NAME` 时,输出会说明该环境变量仍会为 - 团队相关命令选择团队,因此清除默认值并不会让它们不再选择团队;需要取消 - 该环境变量才可以。 - ## LLM ### `oo llm config` @@ -647,7 +630,7 @@ upload)使用的 CLI 还会立即尝试清空待发送 telemetry 事件,并且本次 `config set` 调用自身不会被记录为 telemetry。 它设置 `oo connector run`、`oo connector proxy`、`oo connector apps` 和 - `oo connector search` / `oo search` 在未传 `--team` 或 `--personal`、且未 + `oo connector search` / `oo search` 在未传 `--team`、且未 设置 `OO_TEAM_ID` / `OO_TEAM_NAME` 环境变量时使用的默认团队身份。 ### `oo config unset ` @@ -890,11 +873,9 @@ CLI 默认记录受隐私约束的命令使用 telemetry。事件不包含 free- - 参数:`` 为语义搜索文本。 - 选项:`--format=json` 和 `--json` 会输出匹配 action 条目的 JSON 数组。 -- 选项:`--team ` 以指定团队身份(而非个人身份)报告每条结果的 - `authenticated` 状态。省略时有效身份依次取 `OO_TEAM_ID` / `OO_TEAM_NAME`、 - 当前账号保存的默认团队,最后回退到个人身份。 -- 选项:`--personal` 以个人身份报告 `authenticated`,忽略 `OO_TEAM_ID` / - `OO_TEAM_NAME` 环境变量与任何已配置的默认团队;不能与 `--team` 同用。 +- 选项:`--team ` 以指定团队身份报告每条结果的 `authenticated` 状态。 + 省略时有效身份依次取 `OO_TEAM_ID` / `OO_TEAM_NAME`、当前账号保存的默认团队, + 最后回退到服务端默认团队。 - 输出:每条结果都会包含 `authenticated` 和 `accessStatus`。 - 输出:JSON 条目只包含稳定的 CLI 字段:`service`、`name`、`description`、 `authenticated` 和 `accessStatus`。`accessStatus` 的值为 `available` 或 @@ -958,11 +939,9 @@ CLI 默认记录受隐私约束的命令使用 telemetry。事件不包含 free- schema 声明了异步结果 lifecycle 时,这个选项才有效。 - 选项:`--wait-result` 会提交异步 submit action,然后轮询它配置的结果 action。只有选中 action 的 schema 声明了异步 submit lifecycle 时,这个选项才有效。 -- 选项:`--team ` 以指定团队身份运行该 action,而非个人身份。省略时,若设置了 +- 选项:`--team ` 以指定团队身份运行该 action。省略时,若设置了 `OO_TEAM_ID` / `OO_TEAM_NAME` 则使用 env 选定的团队,其次使用 - 当前账号保存的默认团队,否则使用个人身份。 -- 选项:`--personal` 以个人身份运行该 action,并忽略 `OO_TEAM_ID` / - `OO_TEAM_NAME` 环境变量和已配置的默认团队。不能与 `--team` 同时使用。 + 当前账号保存的默认团队,否则使用服务端默认团队。 - 选项:`--format=json` 和 `--json` 会输出 JSON 对象。 - 输出:非 dry-run 的 JSON 输出会保持稳定结构 `{ data, meta: { executionId } }`。 @@ -981,7 +960,7 @@ CLI 默认记录受隐私约束的命令使用 telemetry。事件不包含 free- 显示进度。JSON 输出不会混入进度文本。 - 说明:面向自部署 Connector 时,传入 `--team` 会被拒绝(exit `2`), 账号保存的默认团队和 `OO_TEAM_ID` / `OO_TEAM_NAME` - 环境变量会被忽略,`--personal` 仍可使用。 + 环境变量会被忽略。 由于自部署 runtime 不提供异步 lifecycle contract,`--wait` 和 `--wait-result` 会以现有的“不支持”错误失败。 @@ -991,11 +970,9 @@ CLI 默认记录受隐私约束的命令使用 telemetry。事件不包含 free- - 参数:`[serviceName]` 可选。省略时列出所有 provider 下已连接的 app;提供时仅列出 该服务的 app。 -- 选项:`--team ` 以指定团队身份列出已连接的 app,而非个人身份。省略时,若设置了 +- 选项:`--team ` 以指定团队身份列出已连接的 app。省略时,若设置了 `OO_TEAM_ID` / `OO_TEAM_NAME` 则按 env 选定的团队列出,其次按 - 当前账号保存的默认团队列出,否则按个人身份列出。 -- 选项:`--personal` 以个人身份列出已连接的 app,并忽略 `OO_TEAM_ID` / - `OO_TEAM_NAME` 环境变量和已配置的默认团队。该选项不能与 `--team` 同时使用。 + 当前账号保存的默认团队列出,否则按服务端默认团队列出。 - 选项:`--format=json` 和 `--json` 会输出 JSON 数组。 - 输出:JSON 条目包含稳定 CLI 字段 `service`、`connectionName`、`displayName`、 `accountLabel`、`status`、`authType`、`isDefault` 和 `scopes`。不会包含 @@ -1007,8 +984,7 @@ CLI 默认记录受隐私约束的命令使用 telemetry。事件不包含 free- - 说明:可将列出的 `connectionName` 值传给 `oo connector run --connection-name `。 - 说明:对自部署 Connector,`--team` 会以退出码 `2` 拒绝,已配置的 - 账号保存的默认团队和 `OO_TEAM_ID` / `OO_TEAM_NAME` 环境变量会被忽略, - `--personal` 可正常使用。 + 账号保存的默认团队和 `OO_TEAM_ID` / `OO_TEAM_NAME` 环境变量会被忽略。 ### `oo connector proxy ` @@ -1033,11 +1009,9 @@ CLI 默认记录受隐私约束的命令使用 telemetry。事件不包含 free- credential。 - 选项:`--body` 会按 JSON 解析。如需发送文本 body,请传 JSON string,例如 `"hello"`。 -- 选项:`--team ` 以指定团队身份运行该 proxy 请求,而非个人身份。省略时,若设置了 +- 选项:`--team ` 以指定团队身份运行该 proxy 请求。省略时,若设置了 `OO_TEAM_ID` / `OO_TEAM_NAME` 则使用 env 选定的团队,其次使用 - 当前账号保存的默认团队,否则使用个人身份。 -- 选项:`--personal` 以个人身份运行该 proxy 请求,并忽略 `OO_TEAM_ID` / - `OO_TEAM_NAME` 环境变量和已配置的默认团队。不能与 `--team` 同时使用。 + 当前账号保存的默认团队,否则使用服务端默认团队。 - 选项:`--format=json` 和 `--json` 会输出 JSON 对象。 - 输出:JSON 输出保持稳定结构 `{ data: { status, headers, data }, meta: { executionId, service } }`。 @@ -1092,11 +1066,9 @@ CLI 默认记录受隐私约束的命令使用 telemetry。事件不包含 free- - 参数:`` 为语义搜索文本。 - 选项:`--format=json` 和 `--json` 会输出匹配 action 条目的 JSON 数组。 -- 选项:`--team ` 以指定团队身份(而非个人身份)报告每条结果的 - `authenticated` 状态。省略时有效身份依次取 `OO_TEAM_ID` / `OO_TEAM_NAME`、 - 当前账号保存的默认团队,最后回退到个人身份。 -- 选项:`--personal` 以个人身份报告 `authenticated`,忽略 `OO_TEAM_ID` / - `OO_TEAM_NAME` 环境变量与任何已配置的默认团队;不能与 `--team` 同用。 +- 选项:`--team ` 以指定团队身份报告每条结果的 `authenticated` 状态。 + 省略时有效身份依次取 `OO_TEAM_ID` / `OO_TEAM_NAME`、当前账号保存的默认团队, + 最后回退到服务端默认团队。 - 输出:每条结果都会包含 `authenticated` 和 `accessStatus`。 - 输出:JSON 条目只包含稳定的 CLI 字段:`service`、`name`、`description`、 `authenticated` 和 `accessStatus`。`accessStatus` 的值为 `available` 或 @@ -2180,8 +2152,6 @@ message,也不会出现在 `path` / `sourcePath` 字段之外的额外文件 - 选项:`--team ` 以指定团队上传该文件,上传按该团队计费与计量。 未传时,团队来自已设置的 `OO_TEAM_ID` / `OO_TEAM_NAME`,否则为当前账号 保存的默认团队。 -- 选项:`--personal` 不发送任何团队选择,忽略 `OO_TEAM_ID` / `OO_TEAM_NAME` - 与账号默认团队,由服务端套用它自己的默认团队。不能与 `--team` 同时使用。 - 说明:团队选择只随文件服务请求发送;分片上传直接发往存储服务,不携带它。 - 说明:上传后的文件有效期为七天,到期后会由服务端删除。 - 说明:文件大小超过 `500 MiB` 时会被拒绝。 @@ -2218,13 +2188,12 @@ message,也不会出现在 `path` / `sourcePath` 字段之外的额外文件 `oo vars`。所有子命令都需要当前账号;value 以字符串存储(如需存 JSON 请自行序列化)。 变量属于团队而不是单个用户:团队全体成员读写同一份变量,同名写入 last-write-wins。 -每个子命令按同一条阶梯解析所使用的团队:`--personal` > `--team ` > -`OO_TEAM_ID` > `OO_TEAM_NAME` > 当前账号保存的默认团队 > 服务端默认团队。 -`--personal` 的含义是"不发送任何团队选择",由服务端套用它自己的默认团队;它不会 -产生按用户私有的作用域,也不能与 `--team` 同时使用(同时使用以退出码 `2` 失败, -`--team` 传空值同样如此)。 +每个子命令按同一条阶梯解析所使用的团队:`--team ` > `OO_TEAM_ID` > +`OO_TEAM_NAME` > 当前账号保存的默认团队 > 服务端默认团队。没有选中任何团队时 +不发送团队选择,由服务端套用它自己的默认团队;不存在按用户私有的作用域。 +`--team` 传空值以退出码 `2` 失败。 -所有子命令都支持 `--team ` 与 `--personal`。当前账号不属于任何团队时无法使用 +所有子命令都支持 `--team `。当前账号不属于任何团队时无法使用 变量:命令以退出码 `1` 失败并给出说明。选择了账号不是成员的团队,或团队不存在时, 同样以退出码 `1` 失败。 @@ -2234,8 +2203,7 @@ message,也不会出现在 `path` / `sourcePath` 字段之外的额外文件 - 文本输出:每行一个变量,只显示 `name` 和 `updatedAt`;不打印完整 value。读取 value 请用 `oo variables get` 或 `--json`。 -- 选项:`--team ` 以该团队执行命令;`--personal` 使用服务端默认团队,忽略 - `OO_TEAM_ID` / `OO_TEAM_NAME` 与已保存的默认团队。 +- 选项:`--team ` 以该团队执行命令。 - 选项:`--format ` / `--json` 返回结构化输出 `{ "variables": [{ "name", "value", "updatedAt", "updatedBy" }] }`,包含完整 value。`updatedBy` 为最后写入该变量的团队成员 id,服务端未提供时不出现。 @@ -2246,7 +2214,7 @@ message,也不会出现在 `path` / `sourcePath` 字段之外的额外文件 - 参数:`` 必填(1-256 个字符;不能包含 `/` 或控制字符)。 - 文本输出:原始 value,并追加换行。 -- 选项:`--team ` 与 `--personal` 用于选择团队,含义同 `oo variables list`。 +- 选项:`--team ` 用于选择团队,含义同 `oo variables list`。 - 选项:`--format ` / `--json` 返回 `{ "name", "value", "updatedAt", "updatedBy" }`。 - 说明:变量不存在时以非零码退出。 @@ -2260,7 +2228,7 @@ message,也不会出现在 `path` / `sourcePath` 字段之外的额外文件 允许空字符串。 - 选项:`--from-file ` 按 UTF-8 原文读取文件内容作为 value。 - 选项:`--stdin` 从标准输入读取到 EOF 作为 value(原文);当 stdin 是交互式终端时报错。 -- 选项:`--team ` 与 `--personal` 用于选择团队,含义同 `oo variables list`。 +- 选项:`--team ` 用于选择团队,含义同 `oo variables list`。 - 选项:`--format ` / `--json` 返回 `{ "name", "value", "updatedAt", "updatedBy" }`。 - 说明:value 上限为 64 KiB(65536 字节,UTF-8)。 @@ -2271,7 +2239,7 @@ message,也不会出现在 `path` / `sourcePath` 字段之外的额外文件 删除当前团队的变量。幂等:即使 name 不存在也成功。 - 参数:`` 必填。 -- 选项:`--team ` 与 `--personal` 用于选择团队,含义同 `oo variables list`。 +- 选项:`--team ` 用于选择团队,含义同 `oo variables list`。 - 选项:`--json` 返回 `{ "name", "deleted": true }`。 ## Shell 补全 diff --git a/docs/self-hosted-connector.md b/docs/self-hosted-connector.md index a7d267cd..8db87a06 100644 --- a/docs/self-hosted-connector.md +++ b/docs/self-hosted-connector.md @@ -180,8 +180,7 @@ adapts as follows: - **Team identity is not supported.** `--team` is rejected with exit code `2` on `oo connector run`, `oo connector proxy`, and `oo connector apps`, and any saved account default team and the `OO_TEAM_ID` / `OO_TEAM_NAME` - environment variables are ignored. `--personal` is accepted (it is already - the effective behavior). + environment variables are ignored. - **Async lifecycle waiting is unavailable.** `--wait` and `--wait-result` fail with the existing "unsupported" errors because the self-hosted runtime does not expose the async result-lifecycle contract. @@ -364,5 +363,5 @@ and manage runtime tokens. The CLI references it in login output and in the | `The connector server rejected the request (HTTP 401).` (exit 1) | The server requires a token. Create one at `/access` and pass it with `--token`. | | `The server at ... did not return a connector health response.` (exit 1) | The URL points at something that is not a connector server, or `/v1/health` does not return the expected envelope. | | `Could not reach the self-hosted connector at ...` (during a command) | The saved server is down. Start it, or run `oo connector login` to reconfigure. This is not a sandbox or permission problem — do not retry with elevated permissions. | -| `The --team option is not supported by a self-hosted connector.` (exit 2) | Self-hosted runtimes have no team identity. Drop `--team`, or use `--personal`. | +| `The --team option is not supported by a self-hosted connector.` (exit 2) | Self-hosted runtimes have no team identity. Drop `--team`. | | A command needs an OOMOL account | Non-connector features (file upload, LLM, skills) still require `oo auth login`. | diff --git a/docs/self-hosted-connector.zh-CN.md b/docs/self-hosted-connector.zh-CN.md index 7afafa4a..7e475a3b 100644 --- a/docs/self-hosted-connector.zh-CN.md +++ b/docs/self-hosted-connector.zh-CN.md @@ -160,8 +160,7 @@ oo connector run gmail --action send_email --data '@payload.json' - **不支持团队身份。** `oo connector run`、`oo connector proxy` 与 `oo connector apps` 上的 `--team` 会以退出码 `2` 被拒绝,账号保存的默认团队 - 和 `OO_TEAM_ID` / `OO_TEAM_NAME` 环境变量都会被忽略;`--personal` 可以使用 - (它本就是实际行为)。 + 和 `OO_TEAM_ID` / `OO_TEAM_NAME` 环境变量都会被忽略。 - **无法等待异步生命周期。** `--wait` 与 `--wait-result` 会以既有的「不支持」 错误失败,因为自部署运行时未暴露异步结果生命周期契约。 - **Proxy 取决于服务支持。** 只有当你的服务实现了 proxy 端点时, @@ -327,5 +326,5 @@ CLI 会发送 `Content-Type: application/json`,请求体形如: | `The connector server rejected the request (HTTP 401).`(退出码 1) | 服务需要令牌。请在 `/access` 创建令牌,并通过 `--token` 传入。 | | `The server at ... did not return a connector health response.`(退出码 1) | URL 指向的并非 connector 服务,或 `/v1/health` 未返回预期信封。 | | `Could not reach the self-hosted connector at ...`(执行命令时) | 已保存的服务已宕机。请启动它,或运行 `oo connector login` 重新配置。这不是沙箱或权限问题,请勿以提升权限重试。 | -| `The --team option is not supported by a self-hosted connector.`(退出码 2) | 自部署运行时没有团队身份。请去掉 `--team`,或改用 `--personal`。 | +| `The --team option is not supported by a self-hosted connector.`(退出码 2) | 自部署运行时没有团队身份。请去掉 `--team`。 | | 某命令需要 OOMOL 账号 | 非 connector 功能(文件上传、LLM、skills)仍需 `oo auth login`。 | diff --git a/src/application/auth/default-team.test.ts b/src/application/auth/default-team.test.ts index 303828c5..120bf3f2 100644 --- a/src/application/auth/default-team.test.ts +++ b/src/application/auth/default-team.test.ts @@ -12,7 +12,6 @@ import { createSettingsStore, } from "../../../__tests__/helpers.ts"; import { - clearDefaultTeam, migrateLegacyDefaultTeam, readDefaultTeam, writeDefaultTeam, @@ -86,7 +85,7 @@ describe("readDefaultTeam", () => { }); }); - test("resolves to personal when nothing is stored", async () => { + test("resolves to no default team when nothing is stored", async () => { const { context } = createDefaultTeamContext({ authFile: { auth: [account], id: "user-1" }, }); @@ -94,7 +93,7 @@ describe("readDefaultTeam", () => { await expect(readDefaultTeam(context)).resolves.toBeUndefined(); }); - test("resolves to personal under OO_API_KEY without touching any store", async () => { + test("resolves to no default team under OO_API_KEY without touching any store", async () => { const { context } = createDefaultTeamContext({ authStore: createThrowingAuthStore(), env: { OO_API_KEY: "env-key" }, @@ -149,62 +148,6 @@ describe("writeDefaultTeam", () => { }); }); -describe("clearDefaultTeam", () => { - test("clears the account default", async () => { - const { context, readAuthFile } = createDefaultTeamContext({ - authFile: { - auth: [{ ...account, team: "acme", teamId: "team-1" }], - id: "user-1", - }, - }); - - await expect(clearDefaultTeam(context)).resolves.toBe(true); - expect((await readAuthFile()).auth[0]).toEqual(account); - }); - - test("clears a legacy value that no account could hold", async () => { - const { context, readSettings } = createDefaultTeamContext({ - authFile: { auth: [], id: "" }, - settings: { identity: { team: "legacy-team" } }, - }); - - await expect(clearDefaultTeam(context)).resolves.toBe(true); - expect(await readSettings()).toEqual({}); - }); - - test("reports nothing to clear for an account already on the personal identity", async () => { - const { context } = createDefaultTeamContext({ - authFile: { auth: [account], id: "user-1" }, - }); - - await expect(clearDefaultTeam(context)).resolves.toBe(false); - }); - - test("still clears the legacy value when auth.toml cannot be read", async () => { - const { context, readSettings } = createDefaultTeamContext({ - authStore: createUnreadableAuthStore(), - settings: { identity: { team: "legacy-team" } }, - }); - - // readDefaultTeam keeps honouring the legacy value in this state, so - // clearing has to reach it too — otherwise the one default still in - // effect is the one that cannot be cleared. - await expect(clearDefaultTeam(context)).resolves.toBe(true); - expect(await readSettings()).toEqual({}); - }); - - test("reports the legacy removal as a migration", async () => { - const { context, recordedProperties } = createDefaultTeamContext({ - authFile: { auth: [account], id: "user-1" }, - settings: { identity: { team: "legacy-team" } }, - }); - - await clearDefaultTeam(context); - - expect(recordedProperties).toEqual([{ team_default_migrated: true }]); - }); -}); - describe("migrateLegacyDefaultTeam", () => { test("moves the legacy value onto the active account and deletes it", async () => { const { diff --git a/src/application/auth/default-team.ts b/src/application/auth/default-team.ts index d6f6de1d..e1207d0d 100644 --- a/src/application/auth/default-team.ts +++ b/src/application/auth/default-team.ts @@ -18,7 +18,6 @@ import type { CliExecutionContext } from "../contracts/cli.ts"; import type { AppSettings } from "../schemas/settings.ts"; import { - clearAccountDefaultTeam, getCurrentAuthAccount, setAccountDefaultTeam, } from "../schemas/auth.ts"; @@ -54,8 +53,9 @@ type MigrateDefaultTeamContext = Pick< >; /** - * Reads the default team in effect for the active account, or undefined for - * the personal identity. + * Reads the default team in effect for the active account, or undefined when + * none is saved (team-aware commands then send no team header and the gateway + * applies the server-side default team). * * With OO_API_KEY set there is no persisted default at all: the credential * may belong to an entirely different account, and lending it a saved @@ -128,36 +128,6 @@ export async function writeDefaultTeam( return true; } -/** - * Clears the default team. The legacy value goes too: leaving it behind would - * let the next read resurrect the team this call just cleared. - * - * Returns whether anything was actually cleared, so the caller can tell - * "cleared" from "already personal" without reading the stores itself. - * - * The auth read is tolerant, unlike the one in `writeDefaultTeam`: clearing is - * the one write here that runs without resolving an identity first, so it is - * what a user with an unreadable auth.toml reaches for — and the legacy value - * `readDefaultTeam` still honours in that state has to stay clearable. An - * unreadable file yields no account, so nothing overwrites it. - */ -export async function clearDefaultTeam( - context: WriteDefaultTeamContext, -): Promise { - const { authFile } = await context.authStore.readTolerantState(); - const account = getCurrentAuthAccount(authFile); - const nextAuthFile = account === undefined - ? authFile - : clearAccountDefaultTeam(authFile, account.id); - const clearedAccountTeam = nextAuthFile !== authFile; - - if (clearedAccountTeam) { - await context.authStore.write(nextAuthFile); - } - - return await dropLegacyIdentityTeam(context) || clearedAccountTeam; -} - /** * Moves the legacy global default team onto the active account, then deletes * it. Runs once per invocation before any command, so the value is gone diff --git a/src/application/commands/__snapshots__/search.cli.test.ts.snap b/src/application/commands/__snapshots__/search.cli.test.ts.snap index e3892d40..e3cccde2 100644 --- a/src/application/commands/__snapshots__/search.cli.test.ts.snap +++ b/src/application/commands/__snapshots__/search.cli.test.ts.snap @@ -50,8 +50,6 @@ Arguments: Options: --team Report each action's authenticated status under the given team identity - --personal Report authenticated status under your personal - identity, ignoring any configured default team --format Specify output format (use json for structured output) --json Alias for --format=json --show-schema-version Include schemaVersion in JSON output (no effect without @@ -77,8 +75,6 @@ Arguments: Options: --team Report each action's authenticated status under the given team identity - --personal Report authenticated status under your personal - identity, ignoring any configured default team --format Specify output format (use json for structured output) --json Alias for --format=json --show-schema-version Include schemaVersion in JSON output (no effect without diff --git a/src/application/commands/auth/__snapshots__/index.cli.test.ts.snap b/src/application/commands/auth/__snapshots__/index.cli.test.ts.snap index 9ffa7969..91aff560 100644 --- a/src/application/commands/auth/__snapshots__/index.cli.test.ts.snap +++ b/src/application/commands/auth/__snapshots__/index.cli.test.ts.snap @@ -24,7 +24,7 @@ exports[`auth CLI writes auth-store and auth status logs 1`] = ` "✓ Logged in to oomol.com account Alice - Active account: true - API key status: Valid - - Default team: personal (no default team) + - Default team: server default (no saved team) - Accounts: * Alice [active] (oomol.com) " @@ -168,7 +168,7 @@ exports[`auth CLI supports auth status for valid and invalid api keys 1`] = ` "X Logged in to oomol.com account Alice - Active account: true - API key status: Invalid - - Default team: personal (no default team) + - Default team: server default (no saved team) - Accounts: * Alice [active] (oomol.com) " @@ -181,7 +181,7 @@ exports[`auth CLI supports auth status for valid and invalid api keys 1`] = ` "✓ Logged in to oomol.com account Alice - Active account: true - API key status: Valid - - Default team: personal (no default team) + - Default team: server default (no saved team) - Accounts: * Alice [active] (oomol.com) " diff --git a/src/application/commands/auth/index.cli.test.ts b/src/application/commands/auth/index.cli.test.ts index 643c6658..880bfd95 100644 --- a/src/application/commands/auth/index.cli.test.ts +++ b/src/application/commands/auth/index.cli.test.ts @@ -2782,7 +2782,7 @@ describe("auth CLI status default team", () => { } }); - test("reports the personal identity under OO_API_KEY despite a saved default", async () => { + test("reports the server default team under OO_API_KEY despite a saved default", async () => { const sandbox = await createCliSandbox(); sandbox.env.OO_API_KEY = "env-key-1"; @@ -2807,7 +2807,7 @@ describe("auth CLI status default team", () => { // OO_TEAM_NAME. expect(textResult.exitCode).toBe(0); expect(textResult.stdout).toContain( - "- Default team: personal (no default team)", + "- Default team: server default (no saved team)", ); expect(payload.status).toBe("logged-in"); expect(payload.envOverride).toBeDefined(); diff --git a/src/application/commands/auth/status.ts b/src/application/commands/auth/status.ts index 813d172f..ac9f179a 100644 --- a/src/application/commands/auth/status.ts +++ b/src/application/commands/auth/status.ts @@ -344,7 +344,7 @@ function writeSelfHostedConnectorText( // Renders the default-team detail row. The value spells out the identity in // effect: the account default by name, an env override with the variable that -// supplies it, or the personal fallback when no default team is set. +// supplies it, or the server-side default when no default team is saved. function formatStatusTeamDetail( context: CliExecutionContext, teamIdentity: TeamIdentity | undefined, @@ -354,7 +354,7 @@ function formatStatusTeamDetail( if (teamIdentity === undefined) { return { label, - value: context.translator.t("auth.status.teamPersonal"), + value: context.translator.t("auth.status.teamServerDefault"), }; } diff --git a/src/application/commands/connector/__snapshots__/index.cli.test.ts.snap b/src/application/commands/connector/__snapshots__/index.cli.test.ts.snap index 76a3d9bf..aac66270 100644 --- a/src/application/commands/connector/__snapshots__/index.cli.test.ts.snap +++ b/src/application/commands/connector/__snapshots__/index.cli.test.ts.snap @@ -64,8 +64,6 @@ Arguments: Options: --team Report each action's authenticated status under the given team identity - --personal Report authenticated status under your personal - identity, ignoring any configured default team --format Specify output format (use json for structured output) --json Alias for --format=json --show-schema-version Include schemaVersion in JSON output (no effect without @@ -91,8 +89,6 @@ Arguments: Options: --team Report each action's authenticated status under the given team identity - --personal Report authenticated status under your personal - identity, ignoring any configured default team --format Specify output format (use json for structured output) --json Alias for --format=json --show-schema-version Include schemaVersion in JSON output (no effect without diff --git a/src/application/commands/connector/apps.ts b/src/application/commands/connector/apps.ts index 79a155d6..cdc178d0 100644 --- a/src/application/commands/connector/apps.ts +++ b/src/application/commands/connector/apps.ts @@ -10,7 +10,7 @@ import { createWriterColors } from "../../terminal-colors.ts"; import { formatTextTable } from "../shared/text-table.ts"; import { teamIdentityInputShape, - teamIdentityOptions, + teamOption, } from "../team/identity.ts"; import { connectorSearchServiceColor } from "./search-provider.ts"; import { resolveConnectorSession } from "./session.ts"; @@ -35,7 +35,6 @@ type ConnectorAppsListScope = "all" | "service"; interface ConnectorAppsInput { team?: string; - personal?: boolean; serviceName?: string; } @@ -62,10 +61,7 @@ export const connectorAppsCommand: CliCommandDefinition = { }, ], options: [ - ...teamIdentityOptions({ - personal: "options.connectorAppsPersonal", - team: "options.connectorAppsTeam", - }), + teamOption("options.connectorAppsTeam"), ], output: "standard", inputSchema: z.object({ @@ -79,7 +75,6 @@ export const connectorAppsCommand: CliCommandDefinition = { const { identity, target } = await resolveConnectorSession( { - personal: input.personal, team: input.team, }, context, diff --git a/src/application/commands/connector/index.cli.test.ts b/src/application/commands/connector/index.cli.test.ts index 18910425..efe1f2b4 100644 --- a/src/application/commands/connector/index.cli.test.ts +++ b/src/application/commands/connector/index.cli.test.ts @@ -583,7 +583,6 @@ describe("connectorCommand CLI", () => { "Submit an async action and wait for its result action", ); expect(result.stdout).toContain("--team"); - expect(result.stdout).toContain("--personal"); expect(result.stdout).toContain("--connection-name"); expect(help).toContain( "Run the action under the given team identity", @@ -612,7 +611,6 @@ describe("connectorCommand CLI", () => { expect(result.stdout).toContain("--headers"); expect(result.stdout).toContain("--body"); expect(result.stdout).toContain("--team"); - expect(result.stdout).toContain("--personal"); expect(help).toContain( "Proxy a provider API request through a connected connector app", ); @@ -1014,7 +1012,7 @@ describe("connectorCommand CLI", () => { error_code: "invalid_input", has_body: false, http_status: 400, - identity_source: "personal", + identity_source: "none", method: "GET", }, }); @@ -1339,7 +1337,6 @@ describe("connectorCommand CLI", () => { expect(appsHelp.exitCode).toBe(0); expect(appsHelp.stdout).toContain("--json"); expect(appsHelp.stdout).toContain("--team"); - expect(appsHelp.stdout).toContain("--personal"); expect(appsHelp.stdout).toContain("List connected connector apps"); expect(appsHelp.stdout).not.toContain("disconnect"); expect(appsHelp.stdout).not.toContain("reconnect"); @@ -1434,7 +1431,7 @@ describe("connectorCommand CLI", () => { expect(telemetryPayload).toMatchObject({ properties: { command_full: "connector.apps", - identity_source: "personal", + identity_source: "none", list_scope: "all", result_count_bucket: "1-5", }, @@ -1654,7 +1651,7 @@ describe("connectorCommand CLI", () => { expect(telemetryPayload).toMatchObject({ properties: { connector_kind: "self_hosted", - identity_source: "personal", + identity_source: "none", list_scope: "all", }, }); @@ -1760,7 +1757,7 @@ describe("connectorCommand CLI", () => { connection_selector: "none", data_size_bucket: "<1KB", dry_run: false, - identity_source: "personal", + identity_source: "none", service: "gmail", wait: false, }, @@ -4990,51 +4987,6 @@ describe("connectorCommand CLI", () => { } }); - test("rejects combining --team and --personal before sending requests", async () => { - const sandbox = await createCliSandbox(); - - try { - let requestCount = 0; - const result = await sandbox.run( - [ - "connector", - "run", - "gmail", - "-a", - "send_mail", - "-d", - "{\"to\":\"foo@bar.com\"}", - "--team", - "acme", - "--personal", - "--json", - ], - { - fetcher: async () => { - requestCount += 1; - - return new Response(JSON.stringify({ - data: {}, - meta: { - executionId: "exec-1", - }, - })); - }, - }, - ); - - expect(result.exitCode).toBe(2); - expect(result.stdout).toBe(""); - expect(result.stderr).toContain( - "Use either --team or --personal, not both.", - ); - expect(requestCount).toBe(0); - } - finally { - await sandbox.cleanup(); - } - }); - test("runs connector search via OO_API_KEY without login and routes through OO_ENDPOINT", async () => { const sandbox = await createCliSandbox(); diff --git a/src/application/commands/connector/proxy.ts b/src/application/commands/connector/proxy.ts index 65d0a0d4..185081a1 100644 --- a/src/application/commands/connector/proxy.ts +++ b/src/application/commands/connector/proxy.ts @@ -8,7 +8,7 @@ import { bucketTelemetryBytes } from "../../telemetry/buckets.ts"; import { readJsonInputValue } from "../shared/json-input.ts"; import { teamIdentityInputShape, - teamIdentityOptions, + teamOption, } from "../team/identity.ts"; import { resolveConnectorSession } from "./session.ts"; import { runConnectorProxy } from "./shared.ts"; @@ -51,7 +51,6 @@ interface ConnectorProxyInput { headers?: string; method?: string; team?: string; - personal?: boolean; query?: string; serviceName: string; } @@ -107,10 +106,7 @@ export const connectorProxyCommand: CliCommandDefinition = valueName: "body", descriptionKey: "options.connectorProxyBody", }, - ...teamIdentityOptions({ - personal: "options.connectorProxyPersonal", - team: "options.connectorProxyTeam", - }), + teamOption("options.connectorProxyTeam"), ], output: "standard", inputSchema: z.object({ @@ -129,7 +125,6 @@ export const connectorProxyCommand: CliCommandDefinition = const proxyRequest = await buildConnectorProxyRequest(input, context); const { identity, target } = await resolveConnectorSession( { - personal: input.personal, team: input.team, }, context, diff --git a/src/application/commands/connector/run.ts b/src/application/commands/connector/run.ts index 75ed7383..20dd8e59 100644 --- a/src/application/commands/connector/run.ts +++ b/src/application/commands/connector/run.ts @@ -16,7 +16,7 @@ import { readJsonInputValue } from "../shared/json-input.ts"; import { TerminalProgressRenderer } from "../shared/terminal-progress-renderer.ts"; import { teamIdentityInputShape, - teamIdentityOptions, + teamOption, } from "../team/identity.ts"; import { invalidateConnectorActionSchemaOnNotFound, @@ -60,7 +60,6 @@ interface ConnectorRunInput { data?: string; dryRun?: boolean; team?: string; - personal?: boolean; serviceName: string; wait?: boolean; waitResult?: boolean; @@ -115,10 +114,7 @@ export const connectorRunCommand: CliCommandDefinition = { longFlag: "--wait-result", descriptionKey: "options.connectorRunWaitResult", }, - ...teamIdentityOptions({ - personal: "options.connectorRunPersonal", - team: "options.connectorRunTeam", - }), + teamOption("options.connectorRunTeam"), ], output: "standard", inputSchema: z.object({ @@ -150,7 +146,6 @@ export const connectorRunCommand: CliCommandDefinition = { const { identity, target } = await resolveConnectorSession( { - personal: input.personal, team: input.team, // A dry run never sends the execution request that needs the // completed identity, so it must not pay (or fail on) the diff --git a/src/application/commands/connector/search.ts b/src/application/commands/connector/search.ts index 9660ba78..84b26ce9 100644 --- a/src/application/commands/connector/search.ts +++ b/src/application/commands/connector/search.ts @@ -7,7 +7,7 @@ import { } from "../../telemetry/buckets.ts"; import { teamIdentityInputShape, - teamIdentityOptions, + teamOption, } from "../team/identity.ts"; import { formatConnectorSearchResultsAsText, @@ -17,7 +17,6 @@ import { resolveConnectorSession } from "./session.ts"; interface ConnectorSearchInput { team?: string; - personal?: boolean; text: string; } @@ -34,10 +33,7 @@ export const connectorSearchCommand: CliCommandDefinition }, ], options: [ - ...teamIdentityOptions({ - personal: "options.searchPersonal", - team: "options.searchTeam", - }), + teamOption("options.searchTeam"), ], output: "standard", inputSchema: z.object({ @@ -51,7 +47,6 @@ export const connectorSearchCommand: CliCommandDefinition const { identity, target } = await resolveConnectorSession( { - personal: input.personal, team: input.team, }, context, diff --git a/src/application/commands/connector/session.test.ts b/src/application/commands/connector/session.test.ts index 73915d96..4229351b 100644 --- a/src/application/commands/connector/session.test.ts +++ b/src/application/commands/connector/session.test.ts @@ -38,20 +38,6 @@ const teamByIdResponse = { }; describe("resolveConnectorSession flag guards", () => { - test("rejects combining --team and --personal before resolving anything", async () => { - // No auth on the context: the guard must fire before target - // resolution gets a chance to demand a login. - const context = createSessionContext(); - - const error = await expectCliUserError( - resolveConnectorSession({ personal: true, team: "acme" }, context), - ); - - expect(error.key).toBe("errors.team.identityConflict"); - expect(error.exitCode).toBe(2); - expect(context.requests).toHaveLength(0); - }); - test.each([ { case: "an empty --team value", team: "" }, { case: "a whitespace --team value", team: " " }, @@ -83,7 +69,7 @@ describe("resolveConnectorSession flag guards", () => { }); describe("resolveConnectorSession on a self-hosted target", () => { - test("pins the personal identity, ignoring the config default and the env team", async () => { + test("pins no team identity for a self-hosted target, ignoring the config default and the env team", async () => { const context = createSessionContext({ env: { OO_CONNECTOR_URL: "http://localhost:3000", @@ -108,13 +94,13 @@ describe("resolveConnectorSession on a self-hosted target", () => { }); expect(context.requests).toHaveLength(0); expect(context.recordedProperties).toEqual([ - { connector_kind: "self_hosted", identity_source: "personal" }, + { connector_kind: "self_hosted", identity_source: "none" }, ]); }); }); describe("resolveConnectorSession identity ladder", () => { - test("resolves to personal when nothing selects a team", async () => { + test("resolves to no team identity when nothing selects a team", async () => { const context = createSessionContext({ auth: authFileWith(testAccount) }); const session = await resolveConnectorSession({}, context); @@ -131,23 +117,8 @@ describe("resolveConnectorSession identity ladder", () => { accountEndpoint: "oomol.com", }); expect(context.recordedProperties).toEqual([ - { connector_kind: "oomol", identity_source: "personal" }, - ]); - }); - - test("forces the personal identity with --personal over the config default and env team", async () => { - const context = createSessionContext({ - auth: authFileWith(testAccount), - env: { OO_TEAM_ID: "team-1" }, - settings: { identity: { team: "acme" } }, - }); - - const session = await resolveConnectorSession({ personal: true }, context); - - expect(session.identity).toBeUndefined(); - expect(context.requests).toHaveLength(0); - expect(context.recordedProperties).toEqual([ - { connector_kind: "oomol", identity_source: "personal" }, + { connector_kind: "oomol" }, + { identity_source: "none" }, ]); }); @@ -168,7 +139,8 @@ describe("resolveConnectorSession identity ladder", () => { }); expect(context.requests).toHaveLength(0); expect(context.recordedProperties).toEqual([ - { connector_kind: "oomol", identity_source: "flag" }, + { connector_kind: "oomol" }, + { identity_source: "flag" }, ]); }); @@ -213,7 +185,8 @@ describe("resolveConnectorSession identity ladder", () => { ); expect(context.requests[0]!.authorization).toBe("api-secret-1"); expect(context.recordedProperties).toEqual([ - { connector_kind: "oomol", identity_source: "env_id" }, + { connector_kind: "oomol" }, + { identity_source: "env_id" }, ]); }); @@ -295,7 +268,8 @@ describe("resolveConnectorSession backend policy", () => { envVar: "OO_TEAM_ID", }); expect(context.recordedProperties).toEqual([ - { connector_kind: "oomol", identity_source: "env_id" }, + { connector_kind: "oomol" }, + { identity_source: "env_id" }, ]); }); }); diff --git a/src/application/commands/connector/session.ts b/src/application/commands/connector/session.ts index dce4fdc3..db2e4f5c 100644 --- a/src/application/commands/connector/session.ts +++ b/src/application/commands/connector/session.ts @@ -1,7 +1,7 @@ // The connector session: which connector server this invocation talks to, and // under whose team identity — resolved once, behind one call. Handlers do // their own pure input validation first, then everything identity-shaped -// happens here: the shared --team/--personal flag guards, target resolution, +// happens here: the shared --team flag guard, target resolution, // the self-hosted team rejection, the configured default, the team identity // ladder with its execution gate, and the identity telemetry. // @@ -14,19 +14,18 @@ import type { CliExecutionContext } from "../../contracts/cli.ts"; import type { TeamIdentity } from "../team/identity.ts"; import type { ConnectorTarget } from "./target.ts"; -import { readDefaultTeam } from "../../auth/default-team.ts"; import { CliUserError } from "../../contracts/cli.ts"; import { - assertTeamIdentityFlags, - requireValidTeamIdentity, - resolveTeamIdentity, + readTeamFlag, + resolveAccountTeamIdentity, + teamSourceForTelemetry, } from "../team/identity.ts"; import { resolveConnectorTarget } from "./target.ts"; export interface ConnectorSession { - // The effective team identity; undefined is the personal identity, - // whether picked explicitly, by default, or pinned by a self-hosted - // target. + // The effective team identity; undefined sends no team header (the + // gateway applies the server-side default team), whether because nothing + // selected a team or because a self-hosted target has no team concept. identity: TeamIdentity | undefined; target: ConnectorTarget; } @@ -44,14 +43,13 @@ export type ConnectorSessionContext = Pick< >; /** - * Resolves the connector session from the raw `--team` / `--personal` input. + * Resolves the connector session from the raw `--team` input. * - * Guards fire before any resolution: combining the two flags or passing a - * blank `--team` is a usage error, and a self-hosted target rejects `--team` - * outright. The team identity then resolves through the one ladder - * (`--personal` > `--team` > `OO_TEAM_ID` > `OO_TEAM_NAME` > the account - * default > personal) with the target's credential backing the env lookups, and - * `requireValidTeamIdentity` gates execution on the outcome. + * Guards fire before any resolution: a blank `--team` is a usage error, and a + * self-hosted target rejects `--team` outright. The team identity then + * resolves through the one ladder (`--team` > `OO_TEAM_ID` > `OO_TEAM_NAME` > + * the account default) with the target's credential backing the env lookups, + * and `requireValidTeamIdentity` gates execution on the outcome. * * `resolveAgainstBackend: false` (a dry run) keeps the resolution fully * offline. Records the `connector_kind` and `identity_source` telemetry @@ -59,46 +57,40 @@ export type ConnectorSessionContext = Pick< */ export async function resolveConnectorSession( options: { - personal?: boolean; team?: string; resolveAgainstBackend?: boolean; }, context: ConnectorSessionContext, ): Promise { - const teamFlag = assertTeamIdentityFlags(options); + // The guard runs before the target resolves so a blank `--team` is a + // usage error even when no login is configured. + const teamFlag = readTeamFlag(options); const target = await resolveConnectorTarget(context); - if (target.kind === "self_hosted" && teamFlag !== undefined) { - throw new CliUserError("errors.connector.teamUnsupported", 2); + if (target.kind === "self_hosted") { + if (teamFlag !== undefined) { + throw new CliUserError("errors.connector.teamUnsupported", 2); + } + + context.telemetry?.recordProperties({ + connector_kind: target.kind, + identity_source: teamSourceForTelemetry(undefined), + }); + + return { identity: undefined, target }; } - const identity = target.kind === "self_hosted" - ? undefined - : requireValidTeamIdentity( - await resolveTeamIdentity( - { - // The target lends its credential and account endpoint - // to the env-team lookups; only an OOMOL target has an - // account endpoint to lend. - account: { - apiKey: target.authorization, - endpoint: target.accountEndpoint, - }, - defaultTeam: await readDefaultTeam(context), - teamFlag, - personalFlag: options.personal === true, - resolveAgainstBackend: options.resolveAgainstBackend !== false, - }, - context, - ), - context, - ); + context.telemetry?.recordProperties({ connector_kind: target.kind }); - context.telemetry?.recordProperties({ - connector_kind: target.kind, - identity_source: identity?.source ?? "personal", - }); + // The target lends its credential and account endpoint to the env-team + // lookups; only an OOMOL target has an account endpoint to lend. + const identity = await resolveAccountTeamIdentity( + options, + { apiKey: target.authorization, endpoint: target.accountEndpoint }, + context, + { resolveAgainstBackend: options.resolveAgainstBackend }, + ); return { identity, target }; } diff --git a/src/application/commands/connector/shared.test.ts b/src/application/commands/connector/shared.test.ts index 04c0a22a..8817ffef 100644 --- a/src/application/commands/connector/shared.test.ts +++ b/src/application/commands/connector/shared.test.ts @@ -439,7 +439,7 @@ describe("connector shared requests", () => { expect(requests[0]?.headers.get("x-oo-connector-alias")).toBe("work"); }); - test("runConnectorAction omits the team query and header for the personal identity", async () => { + test("runConnectorAction omits the team query and header when no team identity is selected", async () => { const requests: Request[] = []; await runConnectorAction( { diff --git a/src/application/commands/file/__snapshots__/upload.cli.test.ts.snap b/src/application/commands/file/__snapshots__/upload.cli.test.ts.snap index caef6abf..6039c873 100644 --- a/src/application/commands/file/__snapshots__/upload.cli.test.ts.snap +++ b/src/application/commands/file/__snapshots__/upload.cli.test.ts.snap @@ -50,9 +50,6 @@ Arguments: Options: --team Upload the file under the given team identity - --personal Upload the file under the server-side default team, - ignoring OO_TEAM_ID / OO_TEAM_NAME and any saved - default team --format Specify output format (use json for structured output) --json Alias for --format=json --show-schema-version Include schemaVersion in JSON output (no effect without diff --git a/src/application/commands/file/shared.ts b/src/application/commands/file/shared.ts index 6e3714bb..8dfbe964 100644 --- a/src/application/commands/file/shared.ts +++ b/src/application/commands/file/shared.ts @@ -8,17 +8,11 @@ import type { AuthAccount } from "../../schemas/auth.ts"; import type { TeamIdentity } from "../team/identity.ts"; import { z } from "zod"; -import { readDefaultTeam } from "../../auth/default-team.ts"; import { CliUserError } from "../../contracts/cli.ts"; import { createRetryingFetcher } from "../../shared/retrying-fetcher.ts"; import { parsePositiveIntegerOption } from "../shared/input-parsing.ts"; import { requestOo, requestOoResponse } from "../shared/oo-request.ts"; -import { - assertTeamIdentityFlags, - requireValidTeamIdentity, - resolveTeamIdentity, - teamIdentityHeaders, -} from "../team/identity.ts"; +import { teamIdentityHeaders } from "../team/identity.ts"; export const fileUploadExpiresInMs = ((7 * 24 * 60 * 60) - 1) * 1000; export const maxFileUploadSizeBytes = 500 * 1024 * 1024; @@ -157,57 +151,6 @@ export function normalizeFileUploadDownloadUrlForDisplay( } } -// MARK: - Team identity - -type FileUploadIdentityContext = Pick< - CliExecutionContext, - | "authStore" - | "env" - | "fetcher" - | "logger" - | "settingsStore" - | "telemetry" - | "translator" ->; - -/** - * Resolves the team the upload is attributed to: the shared flag guards, the - * one shared identity ladder (`--personal` > `--team` > `OO_TEAM_ID` > - * `OO_TEAM_NAME` > the account default), its execution gate, and the identity - * telemetry. The gateway bills the resolved team's payer and the file service - * meters the upload under that team. - * - * `undefined` means no team header is sent, which lets the gateway apply the - * server-side default team; it is not a private, per-user scope. - */ -export async function resolveFileUploadIdentity( - input: { personal?: boolean; team?: string }, - account: Pick, - context: FileUploadIdentityContext, -): Promise { - const teamFlag = assertTeamIdentityFlags(input); - - const identity = requireValidTeamIdentity( - await resolveTeamIdentity( - { - account, - defaultTeam: await readDefaultTeam(context), - teamFlag, - personalFlag: input.personal === true, - resolveAgainstBackend: true, - }, - context, - ), - context, - ); - - context.telemetry?.recordProperties({ - identity_source: identity?.source ?? "personal", - }); - - return identity; -} - // MARK: - Requests export async function createMultipartFileUpload( diff --git a/src/application/commands/file/upload.cli.test.ts b/src/application/commands/file/upload.cli.test.ts index 4d1d4b50..6add57ec 100644 --- a/src/application/commands/file/upload.cli.test.ts +++ b/src/application/commands/file/upload.cli.test.ts @@ -606,64 +606,6 @@ describe("file upload team identity", () => { await sandbox.cleanup(); } }); - - test("sends no team headers with --personal", async () => { - const sandbox = await createCliSandbox(); - const localFilePath = join(sandbox.env.HOME!, "sample.txt"); - - try { - await writeAuthFileWithDefaultTeam(sandbox, "alice-team", { - teamId: "team-system-1", - }); - await Bun.write(localFilePath, "hello world"); - - const { requests, result } = await runRecordedUpload(sandbox, [ - "file", - "upload", - localFilePath, - "--personal", - ]); - const telemetryPayload = readUploadTelemetryPayload(sandbox); - - expect(result.exitCode).toBe(0); - expect(readTeamHeaders(fileServiceRequests(requests))).toEqual([ - [null, null], - [null, null], - [null, null], - ]); - expect(telemetryPayload?.properties).toMatchObject({ - identity_source: "personal", - }); - } - finally { - await sandbox.cleanup(); - } - }); - - test("rejects --team combined with --personal before any request", async () => { - const sandbox = await createCliSandbox(); - const localFilePath = join(sandbox.env.HOME!, "sample.txt"); - - try { - await writeAuthFile(sandbox); - await Bun.write(localFilePath, "hello world"); - - const { requests, result } = await runRecordedUpload(sandbox, [ - "file", - "upload", - localFilePath, - "--team", - "acme", - "--personal", - ]); - - expect(result.exitCode).toBe(2); - expect(requests).toHaveLength(0); - } - finally { - await sandbox.cleanup(); - } - }); }); async function runRecordedUpload( diff --git a/src/application/commands/file/upload.ts b/src/application/commands/file/upload.ts index 2099daac..706bfe08 100644 --- a/src/application/commands/file/upload.ts +++ b/src/application/commands/file/upload.ts @@ -11,8 +11,9 @@ import { requireIdentity } from "../../auth/identity.ts"; import { CliUserError } from "../../contracts/cli.ts"; import { bucketTelemetryBytes } from "../../telemetry/buckets.ts"; import { + resolveAccountTeamIdentity, teamIdentityInputShape, - teamIdentityOptions, + teamOption, } from "../team/identity.ts"; import { completeMultipartFileUpload, @@ -20,7 +21,6 @@ import { fileUploadExpiresInMs, generatePresignedFileUploadPartUrls, maxFileUploadSizeBytes, - resolveFileUploadIdentity, serializeFileUploadRecord, uploadFileParts, } from "./shared.ts"; @@ -28,7 +28,6 @@ import { formatFileUploadRecordDetailsAsText } from "./text.ts"; interface FileUploadInput { filePath: string; - personal?: boolean; team?: string; } @@ -48,10 +47,7 @@ export const fileUploadCommand: CliCommandDefinition = { required: true, }, ], - options: teamIdentityOptions({ - personal: "options.fileUploadPersonal", - team: "options.fileUploadTeam", - }), + options: [teamOption("options.fileUploadTeam")], output: "standard", inputSchema: z.object({ filePath: z.string(), @@ -59,7 +55,7 @@ export const fileUploadCommand: CliCommandDefinition = { }), handler: async (input, context) => { const { account } = await requireIdentity(context); - const identity = await resolveFileUploadIdentity(input, account, context); + const identity = await resolveAccountTeamIdentity(input, account, context); const sourceFile = await readSourceFile( input.filePath, context.cwd, diff --git a/src/application/commands/team/clear.ts b/src/application/commands/team/clear.ts deleted file mode 100644 index f91e83a8..00000000 --- a/src/application/commands/team/clear.ts +++ /dev/null @@ -1,77 +0,0 @@ -import type { CliCommandDefinition } from "../../contracts/cli.ts"; - -import { z } from "zod"; -import { clearDefaultTeam } from "../../auth/default-team.ts"; -import { - buildEnvApiKeyAccount, - reportOverriddenWrite, -} from "../../auth/identity.ts"; -import { writeLine } from "../shared/output.ts"; -import { resolveTeamIdentity } from "./identity.ts"; - -// Clears the active account's default team identity, returning connector -// commands to the personal identity. Offline: it only rewrites local state. -// When OO_TEAM_ID / OO_TEAM_NAME is set, the env override keeps selecting a -// team regardless of the cleared default, so the output says so instead of -// promising a personal identity. -export const teamClearCommand: CliCommandDefinition = { - name: "clear", - summaryKey: "commands.team.clear.summary", - descriptionKey: "commands.team.clear.description", - inputSchema: z.object({}), - handler: async (_input, context) => { - // Nothing persisted is in effect under OO_API_KEY, so there is nothing - // this command could clear that a later command would notice. - if (buildEnvApiKeyAccount(context.env) !== undefined) { - reportOverriddenWrite(context, { - summaryKey: "team.clear.envOverrideNoop", - }); - return; - } - - // Offline resolution with no account default: what remains is exactly - // the env override that would keep selecting a team after the clear, - // with `envVar` naming it for the hint. - const envIdentity = await resolveTeamIdentity( - { - account: undefined, - defaultTeam: undefined, - resolveAgainstBackend: false, - }, - context, - ); - const hadDefaultTeam = await clearDefaultTeam(context); - - if (!hadDefaultTeam) { - writeLine( - context.stdout, - envIdentity?.envVar === undefined - ? context.translator.t("team.clear.alreadyPersonal") - : context.translator.t( - "team.clear.alreadyPersonalEnvHint", - { - envVar: envIdentity.envVar, - }, - ), - ); - return; - } - - context.logger.info( - { teamConfigured: false }, - "Default team identity cleared.", - ); - // One line per outcome: the plain success message promises a personal - // identity, which is untrue while the env override still selects a - // team, so the override case gets its own message instead of a - // follow-up hint that contradicts the line above it. - writeLine( - context.stdout, - envIdentity?.envVar === undefined - ? context.translator.t("team.clear.success") - : context.translator.t("team.clear.successEnvOverride", { - envVar: envIdentity.envVar, - }), - ); - }, -}; diff --git a/src/application/commands/team/current.ts b/src/application/commands/team/current.ts index dd6bd981..6af208fa 100644 --- a/src/application/commands/team/current.ts +++ b/src/application/commands/team/current.ts @@ -16,8 +16,8 @@ import { } from "./identity.ts"; // `source` says which mechanism selects the team: the OO_TEAM_ID / -// OO_TEAM_NAME env override, the account's saved default, or none -// (personal). `team` carries the name and `teamId` the id. +// OO_TEAM_NAME env override, the account's saved default, or none (the +// server-side default team). `team` carries the name and `teamId` the id. // // `status` reports how the backend lookup ended and is `null` whenever none // was attempted — env-selected identities are looked up in whichever @@ -29,8 +29,8 @@ interface TeamCurrentJsonPayload { status: TeamNameStatus | null; } -// Reports the team identity that connector commands use when no `--team` / -// `--personal` flag is given: the OO_TEAM_ID / OO_TEAM_NAME env override when +// Reports the team identity that team-aware commands use when no `--team` +// flag is given: the OO_TEAM_ID / OO_TEAM_NAME env override when // set, otherwise the active account's saved default team. // // An env-selected identity starts out with only the dimension the variable @@ -73,7 +73,7 @@ export const teamCurrentCommand: CliCommandDefinition = { if (identity === undefined) { writeLine( context.stdout, - context.translator.t("team.current.text.personal"), + context.translator.t("team.current.text.serverDefault"), ); return; } diff --git a/src/application/commands/team/identity.test.ts b/src/application/commands/team/identity.test.ts index 70804585..b4ba544d 100644 --- a/src/application/commands/team/identity.test.ts +++ b/src/application/commands/team/identity.test.ts @@ -12,12 +12,13 @@ import { import { createTranslator } from "../../../i18n/translator.ts"; import { appendTeamIdentityStatus, - assertTeamIdentityFlags, formatTeamIdentityValue, + readTeamFlag, requireValidTeamIdentity, resolveTeamIdentity, - teamIdentityOptions, teamNameStatusForTelemetry, + teamOption, + teamSourceForTelemetry, } from "./identity.ts"; const testAccount = { @@ -40,7 +41,7 @@ const teamByIdResponse = { }; describe("resolveTeamIdentity precedence", () => { - test("resolves to personal when nothing selects a team", async () => { + test("resolves to no team identity when nothing selects a team", async () => { expect(await resolveTeamIdentity( { account: testAccount, @@ -51,29 +52,6 @@ describe("resolveTeamIdentity precedence", () => { )).toBeUndefined(); }); - test("personal flag overrides the team flag, the env override, and the config default", async () => { - let requested = false; - - expect(await resolveTeamIdentity( - { - account: testAccount, - defaultTeam: { id: null, name: "config-team" }, - teamFlag: "flag-team", - personalFlag: true, - resolveAgainstBackend: true, - }, - createContext( - { OO_TEAM_ID: "team-1", OO_TEAM_NAME: "beta" }, - async () => { - requested = true; - - return new Response(JSON.stringify(teamByIdResponse)); - }, - ), - )).toBeUndefined(); - expect(requested).toBe(false); - }); - test("team flag overrides the env override and the config default without a lookup", async () => { let requested = false; @@ -440,7 +418,7 @@ describe("resolveTeamIdentity env validation", () => { describe("requireValidTeamIdentity", () => { const passThroughCases: { case: string; identity: TeamIdentity | undefined }[] = [ - { case: "the personal identity", identity: undefined }, + { case: "no team identity", identity: undefined }, { case: "a flag identity", identity: { name: "flag-team", id: null, source: "flag", status: null }, @@ -674,21 +652,12 @@ describe("teamNameStatusForTelemetry", () => { }); }); -describe("assertTeamIdentityFlags", () => { - test("rejects combining --team and --personal", () => { - const error = expectCliUserError( - () => assertTeamIdentityFlags({ personal: true, team: "acme" }), - ); - - expect(error.key).toBe("errors.team.identityConflict"); - expect(error.exitCode).toBe(2); - }); - +describe("readTeamFlag", () => { test.each([ { case: "an empty --team value", team: "" }, { case: "a whitespace --team value", team: " " }, ])("rejects $case", ({ team }) => { - const error = expectCliUserError(() => assertTeamIdentityFlags({ team })); + const error = expectCliUserError(() => readTeamFlag({ team })); expect(error.key).toBe("errors.team.teamEmpty"); expect(error.exitCode).toBe(2); @@ -696,31 +665,33 @@ describe("assertTeamIdentityFlags", () => { test.each([ { case: "no flags", expected: undefined, input: {} }, - { case: "--personal alone", expected: undefined, input: { personal: true } }, { case: "a padded --team value", expected: "acme", input: { team: " acme " } }, ])("returns $expected for $case", ({ expected, input }) => { - expect(assertTeamIdentityFlags(input)).toBe(expected); + expect(readTeamFlag(input)).toBe(expected); }); }); -describe("teamIdentityOptions", () => { - test("declares the shared flags with the caller's description keys", () => { - expect(teamIdentityOptions({ - personal: "options.connectorRunPersonal", - team: "options.connectorRunTeam", - })).toEqual([ - { - name: "team", - longFlag: "--team", - valueName: "team", - descriptionKey: "options.connectorRunTeam", - }, - { - name: "personal", - longFlag: "--personal", - descriptionKey: "options.connectorRunPersonal", - }, - ]); +describe("teamOption", () => { + test("declares the shared flag with the caller's description key", () => { + expect(teamOption("options.connectorRunTeam")).toEqual({ + name: "team", + longFlag: "--team", + valueName: "team", + descriptionKey: "options.connectorRunTeam", + }); + }); +}); + +describe("teamSourceForTelemetry", () => { + test.each([ + { case: "no team identity", expected: "none", identity: undefined }, + { + case: "an account default", + expected: "account", + identity: { id: null, name: "acme", source: "account", status: null } as const, + }, + ])("reports $expected for $case", ({ expected, identity }) => { + expect(teamSourceForTelemetry(identity)).toBe(expected); }); }); diff --git a/src/application/commands/team/identity.ts b/src/application/commands/team/identity.ts index 31e0e0f2..79a0facb 100644 --- a/src/application/commands/team/identity.ts +++ b/src/application/commands/team/identity.ts @@ -1,16 +1,16 @@ // The team identity: which team an invocation acts for, and whether that team // is real. // -// Three modules used to each encode the "--personal > --team > OO_TEAM_ID > -// OO_TEAM_NAME > the account default > personal" ladder and validate only the -// direction they happened to resolve, which is how `oo auth status` could -// vouch for an identity `oo connector run` rejected — and vice versa. Every -// team-aware command now resolves through this module, so the ladder, the -// validation policy, and the source vocabulary have exactly one owner. +// Three modules used to each encode the "--team > OO_TEAM_ID > OO_TEAM_NAME > +// the account default" ladder and validate only the direction they happened +// to resolve, which is how `oo auth status` could vouch for an identity +// `oo connector run` rejected — and vice versa. Every team-aware command now +// resolves through this module, so the ladder, the validation policy, and the +// source vocabulary have exactly one owner. // -// Personal is the absence of a team identity, not a fifth kind of team: -// resolveTeamIdentity returns undefined for it, whether it was picked -// explicitly (`--personal`) or by nothing else selecting a team. +// `undefined` is the absence of a team identity, not a fifth kind of team: no +// team header is sent and the gateway applies the account's server-side +// default team. It is never a private, per-user scope. import type { AccountDefaultTeam } from "../../auth/default-team.ts"; import type { CliExecutionContext, CliOptionDefinition } from "../../contracts/cli.ts"; @@ -18,6 +18,7 @@ import type { AuthAccount } from "../../schemas/auth.ts"; import type { TeamLookupStatus } from "./shared.ts"; import { z } from "zod"; +import { readDefaultTeam } from "../../auth/default-team.ts"; import { readTrimmedEnv } from "../../auth/identity.ts"; import { CliUserError } from "../../contracts/cli.ts"; import { fetchTeamById, fetchTeamByName } from "./shared.ts"; @@ -53,9 +54,9 @@ type ResolveTeamIdentityContext = Pick< /** * Resolves the team identity from the per-run flags, the env override, and * the account default — one ladder for every team-aware command: - * `--personal` > `--team` > OO_TEAM_ID > OO_TEAM_NAME > the account default > - * personal (undefined). A higher tier fully replaces the lower ones, and a - * trimmed-empty flag or stored name counts as unset. + * `--team` > OO_TEAM_ID > OO_TEAM_NAME > the account default > none + * (undefined, the server-side default team). A higher tier fully replaces the + * lower ones, and a trimmed-empty flag or stored name counts as unset. * * With `resolveAgainstBackend: true`, an env-selected identity is completed * and validated through the lookup matching its direction (id-to-name via the @@ -73,15 +74,10 @@ export async function resolveTeamIdentity( account: Pick | undefined; defaultTeam: AccountDefaultTeam | undefined; teamFlag?: string; - personalFlag?: boolean; resolveAgainstBackend: boolean; }, context: ResolveTeamIdentityContext, ): Promise { - if (input.personalFlag === true) { - return undefined; - } - const teamFlag = normalizeTeamValue(input.teamFlag); if (teamFlag !== undefined) { @@ -177,6 +173,63 @@ export function teamNameStatusForTelemetry( return identity?.status ?? "none"; } +// The identity-source telemetry enum: the mechanism that selected the team, +// or `none` when nothing did and the server-side default team applies. +export function teamSourceForTelemetry( + identity: TeamIdentity | undefined, +): TeamIdentitySource | "none" { + return identity?.source ?? "none"; +} + +type ResolveAccountTeamIdentityContext = Pick< + CliExecutionContext, + | "authStore" + | "env" + | "fetcher" + | "logger" + | "settingsStore" + | "telemetry" + | "translator" +>; + +/** + * The one pipeline every team-aware command runs before acting for a team: + * the `--team` guard, the account's saved default, the shared ladder, the + * execution gate, and the `identity_source` telemetry property. `account` is + * the credential backing any env-team lookup. + * + * `undefined` means no team header is sent, which lets the gateway apply the + * server-side default team; it is not a private, per-user scope. + * + * `resolveAgainstBackend: false` (`--dry-run`) keeps the resolution fully + * offline. + */ +export async function resolveAccountTeamIdentity( + input: { team?: string }, + account: Pick, + context: ResolveAccountTeamIdentityContext, + options: { resolveAgainstBackend?: boolean } = {}, +): Promise { + const identity = requireValidTeamIdentity( + await resolveTeamIdentity( + { + account, + defaultTeam: await readDefaultTeam(context), + teamFlag: readTeamFlag(input), + resolveAgainstBackend: options.resolveAgainstBackend !== false, + }, + context, + ), + context, + ); + + context.telemetry?.recordProperties({ + identity_source: teamSourceForTelemetry(identity), + }); + + return identity; +} + // Builds the standard team identity headers for OOMOL service requests. export function teamIdentityHeaders( identity: TeamIdentity | undefined, @@ -199,22 +252,14 @@ export function teamIdentityHeaders( } /** - * The usage guards on the `--team` / `--personal` pair, shared by every - * team-aware command so one flag combination cannot be a usage error in one - * command and accepted in another: the two flags are mutually exclusive, and a + * The usage guard on `--team`, shared by every team-aware command so the same + * input cannot be a usage error in one command and accepted in another: a * `--team` that was passed but is blank is a typo, not an unset flag. * * Returns the trimmed `--team` value, ready for the ladder — `undefined` when * the flag was not passed, and never the empty string. */ -export function assertTeamIdentityFlags(input: { - personal?: boolean; - team?: string; -}): string | undefined { - if (input.personal === true && input.team !== undefined) { - throw new CliUserError("errors.team.identityConflict", 2); - } - +export function readTeamFlag(input: { team?: string }): string | undefined { const teamFlag = input.team?.trim(); if (input.team !== undefined && teamFlag === "") { @@ -225,33 +270,22 @@ export function assertTeamIdentityFlags(input: { } /** - * The `--team` / `--personal` option pair every team-aware command declares. - * Flags and value names live here once; each command supplies its own - * description keys so the help text keeps its verb. + * The `--team` option every team-aware command declares. The flag and value + * name live here once; each command supplies its own description key so the + * help text keeps its verb. */ -export function teamIdentityOptions(descriptionKeys: { - personal: string; - team: string; -}): readonly CliOptionDefinition[] { - return [ - { - name: "team", - longFlag: "--team", - valueName: "team", - descriptionKey: descriptionKeys.team, - }, - { - name: "personal", - longFlag: "--personal", - descriptionKey: descriptionKeys.personal, - }, - ]; +export function teamOption(descriptionKey: string): CliOptionDefinition { + return { + name: "team", + longFlag: "--team", + valueName: "team", + descriptionKey, + }; } // The input-schema counterpart of teamIdentityOptions, spread into each -// command's zod object so the field pair cannot drift across commands. +// command's zod object so the field cannot drift across commands. export const teamIdentityInputShape = { - personal: z.boolean().optional(), team: z.string().optional(), }; diff --git a/src/application/commands/team/index.cli.test.ts b/src/application/commands/team/index.cli.test.ts index 817cd5c2..462cb0bf 100644 --- a/src/application/commands/team/index.cli.test.ts +++ b/src/application/commands/team/index.cli.test.ts @@ -103,7 +103,7 @@ describe("teamCommand CLI", () => { } }); - test("reports personal identity when the account has no teams", async () => { + test("reports no team selection when the account has no teams", async () => { const sandbox = await createCliSandbox(); try { @@ -114,7 +114,7 @@ describe("teamCommand CLI", () => { }); expect(result.exitCode).toBe(0); - expect(result.stdout).toContain("personal identity"); + expect(result.stdout).toContain("send no team selection"); } finally { await sandbox.cleanup(); @@ -165,7 +165,7 @@ describe("teamCommand CLI", () => { } }); - test("reports personal identity via current when no default is configured", async () => { + test("reports the server-side default team via current when no default is configured", async () => { const sandbox = await createCliSandbox(); try { @@ -181,7 +181,7 @@ describe("teamCommand CLI", () => { source: null, status: null, }); - expect(textResult.stdout).toContain("personal identity"); + expect(textResult.stdout).toContain("server-side default team"); } finally { await sandbox.cleanup(); @@ -244,44 +244,6 @@ describe("teamCommand CLI", () => { } }); - test("clears the default team identity", async () => { - const sandbox = await createCliSandbox(); - - try { - await writeAuthFileWithDefaultTeam(sandbox, "acme"); - - const clearResult = await sandbox.run(["team", "clear"]); - const currentResult = await sandbox.run(["team", "current", "--json"]); - - expect(clearResult.exitCode).toBe(0); - expect(JSON.parse(currentResult.stdout)).toEqual({ - team: null, - teamId: null, - source: null, - status: null, - }); - } - finally { - await sandbox.cleanup(); - } - }); - - test("reports already-personal when clearing with no configured team", async () => { - const sandbox = await createCliSandbox(); - - try { - await writeAuthFile(sandbox); - - const result = await sandbox.run(["team", "clear"]); - - expect(result.exitCode).toBe(0); - expect(result.stdout).toContain("personal identity"); - } - finally { - await sandbox.cleanup(); - } - }); - test("resolves the OO_TEAM_ID env override to its team name via current", async () => { const sandbox = await createCliSandbox(); @@ -524,36 +486,6 @@ describe("teamCommand CLI", () => { } }); - test("hints that the env override still selects a team after clearing", async () => { - const sandbox = await createCliSandbox(); - - try { - await writeAuthFileWithDefaultTeam(sandbox, "acme"); - sandbox.env.OO_TEAM_ID = "team-1"; - - const clearResult = await sandbox.run(["team", "clear"]); - const repeatResult = await sandbox.run(["team", "clear"]); - - expect(clearResult.exitCode).toBe(0); - expect(clearResult.stdout).toContain("OO_TEAM_ID"); - // The saved default is gone, but the env override still selects a - // team, so the output must not claim connector commands now run - // personally — it reports the override instead. - expect(clearResult.stdout).toContain("Cleared the default team identity"); - expect(clearResult.stdout).not.toContain( - "connector commands now run under your personal identity", - ); - // The follow-up clear has nothing to remove but still points at - // the env override instead of promising a personal identity. - expect(repeatResult.exitCode).toBe(0); - expect(repeatResult.stdout).toContain("OO_TEAM_ID"); - expect(repeatResult.stdout).not.toContain("personal identity"); - } - finally { - await sandbox.cleanup(); - } - }); - test("backfills the team id of a default that only has a name", async () => { const sandbox = await createCliSandbox(); @@ -607,27 +539,6 @@ describe("teamCommand CLI", () => { await sandbox.cleanup(); } }); - - test("reports that clearing does nothing under OO_API_KEY", async () => { - const sandbox = await createCliSandbox(); - - sandbox.env.OO_API_KEY = "env-key-1"; - - try { - await writeAuthFileWithDefaultTeam(sandbox, "acme"); - - const result = await sandbox.run(["team", "clear"]); - - expect(result.exitCode).toBe(0); - expect(result.stdout).toContain("OO_API_KEY"); - // The saved account keeps its default; it simply does not apply - // while OO_API_KEY supplies the credential. - expect(await readAuthFileContent(sandbox)).toContain("team = \"acme\""); - } - finally { - await sandbox.cleanup(); - } - }); }); describe("legacy default team migration", () => { @@ -669,7 +580,7 @@ describe("legacy default team migration", () => { const currentResult = await sandbox.run(["team", "current", "--json"]); // Nothing to migrate onto, so the value stays put and still - // resolves rather than silently becoming a personal identity. + // resolves rather than silently falling back to the server default. expect(await readSettingsFileContent(sandbox)).toContain( "team = \"acme\"", ); @@ -694,7 +605,7 @@ describe("legacy default team migration", () => { const currentResult = await sandbox.run(["team", "current", "--json"]); - // OO_API_KEY runs as a personal identity, and the untouched value + // OO_API_KEY has no saved default team, and the untouched value // still applies the moment the variable is unset. expect(JSON.parse(currentResult.stdout)).toEqual({ team: null, diff --git a/src/application/commands/team/index.ts b/src/application/commands/team/index.ts index ee9d155f..00114c34 100644 --- a/src/application/commands/team/index.ts +++ b/src/application/commands/team/index.ts @@ -1,6 +1,5 @@ import type { CliCommandDefinition } from "../../contracts/cli.ts"; -import { teamClearCommand } from "./clear.ts"; import { teamCurrentCommand } from "./current.ts"; import { teamListCommand } from "./list.ts"; import { teamUseCommand } from "./use.ts"; @@ -13,6 +12,5 @@ export const teamCommand: CliCommandDefinition = { teamListCommand, teamCurrentCommand, teamUseCommand, - teamClearCommand, ], }; diff --git a/src/application/commands/team/shared.ts b/src/application/commands/team/shared.ts index ada239db..d85459c2 100644 --- a/src/application/commands/team/shared.ts +++ b/src/application/commands/team/shared.ts @@ -18,7 +18,7 @@ const teamResponseItemSchema = z.object({ }); // `GET /v1/me/teams` wraps the memberships in a `teams` array. The array is -// optional/defaulted so an empty account (personal identity only) parses +// optional/defaulted so an account with no teams parses // cleanly instead of failing validation. const teamsResponseSchema = z.object({ teams: z.array(teamResponseItemSchema).optional().default([]), diff --git a/src/application/commands/telemetry-decisions.test.ts b/src/application/commands/telemetry-decisions.test.ts index 73d550e0..5a8f7319 100644 --- a/src/application/commands/telemetry-decisions.test.ts +++ b/src/application/commands/telemetry-decisions.test.ts @@ -167,7 +167,7 @@ const commandTelemetryDecisions = { "list_scope", "result_count_bucket", ], - reason: "Records bounded connector app list size, the connector target kind (oomol/self_hosted), the identity source (personal/flag/env_id/env_name/account), and whether the listing was scoped to all apps or one service, without app ids, connection names, account labels, team names or ids, or server URLs.", + reason: "Records bounded connector app list size, the connector target kind (oomol/self_hosted), the identity source (none/flag/env_id/env_name/account), and whether the listing was scoped to all apps or one service, without app ids, connection names, account labels, team names or ids, or server URLs.", }, "connector.login": { kind: "properties", @@ -193,7 +193,7 @@ const commandTelemetryDecisions = { "wait", "wait_result", ], - reason: "Records connector product dimensions, bucketed payload size, async wait modes, stable error code, identity source (personal/flag/env_id/env_name/account), and none/connectionName selector mode without the team name/id or connection name value.", + reason: "Records connector product dimensions, bucketed payload size, async wait modes, stable error code, identity source (none/flag/env_id/env_name/account), and none/connectionName selector mode without the team name/id or connection name value.", }, "connector.proxy": { kind: "properties", @@ -206,7 +206,7 @@ const commandTelemetryDecisions = { "identity_source", "method", ], - reason: "Records connector proxy bucketed payload size, method enum, identity source (personal/flag/env_id/env_name/account), stable error code, and HTTP status without service name, endpoint, headers, body, or team name/id.", + reason: "Records connector proxy bucketed payload size, method enum, identity source (none/flag/env_id/env_name/account), stable error code, and HTTP status without service name, endpoint, headers, body, or team name/id.", }, "connector.search": { kind: "properties", @@ -216,7 +216,7 @@ const commandTelemetryDecisions = { "query_length_bucket", "result_count_bucket", ], - reason: "Records query and result buckets, the connector target kind (oomol/self_hosted), and the identity source (personal/flag/env_id/env_name/account) whose connected apps set the authenticated flag, without query text, team name/id, or server URLs.", + reason: "Records query and result buckets, the connector target kind (oomol/self_hosted), and the identity source (none/flag/env_id/env_name/account) whose connected apps set the authenticated flag, without query text, team name/id, or server URLs.", }, "connector.schema": { kind: "properties", @@ -247,7 +247,7 @@ const commandTelemetryDecisions = { "file.upload": { kind: "properties", properties: ["bytes_total_bucket", "identity_source", "rejected_too_large"], - reason: "Records upload size bucket, rejection state, and the identity source (personal/flag/env_id/env_name/account) without path, filename, or team name/id.", + reason: "Records upload size bucket, rejection state, and the identity source (none/flag/env_id/env_name/account) without path, filename, or team name/id.", }, "flow": { kind: "generic", @@ -326,11 +326,6 @@ const commandTelemetryDecisions = { properties: ["credential_source"], reason: "Records whether OO_API_KEY made the command a no-op (it has no saved account to hold a default team); the team name is never recorded.", }, - "team.clear": { - kind: "properties", - properties: ["credential_source"], - reason: "Records whether OO_API_KEY made the command a no-op; no team details are recorded.", - }, "search": { kind: "properties", properties: [ @@ -339,7 +334,7 @@ const commandTelemetryDecisions = { "query_length_bucket", "result_count_bucket", ], - reason: "Records query and result buckets, the connector target kind (oomol/self_hosted), and the identity source (personal/flag/env_id/env_name/account) whose connected apps set the authenticated flag, without query text, team name/id, or server URLs.", + reason: "Records query and result buckets, the connector target kind (oomol/self_hosted), and the identity source (none/flag/env_id/env_name/account) whose connected apps set the authenticated flag, without query text, team name/id, or server URLs.", }, "skills": { kind: "generic", @@ -615,22 +610,22 @@ const commandTelemetryDecisions = { "variables.list": { kind: "properties", properties: ["identity_source"], - reason: "Records the identity source (personal/flag/env_id/env_name/account) that selected the team; never records team names or ids, variable names, or values.", + reason: "Records the identity source (none/flag/env_id/env_name/account) that selected the team; never records team names or ids, variable names, or values.", }, "variables.get": { kind: "properties", properties: ["identity_source"], - reason: "Records the identity source (personal/flag/env_id/env_name/account) that selected the team; never records team names or ids, the variable name, or its value.", + reason: "Records the identity source (none/flag/env_id/env_name/account) that selected the team; never records team names or ids, the variable name, or its value.", }, "variables.create": { kind: "properties", properties: ["identity_source"], - reason: "Records the identity source (personal/flag/env_id/env_name/account) that selected the team; never records team names or ids, the variable name, or its value.", + reason: "Records the identity source (none/flag/env_id/env_name/account) that selected the team; never records team names or ids, the variable name, or its value.", }, "variables.delete": { kind: "properties", properties: ["identity_source"], - reason: "Records the identity source (personal/flag/env_id/env_name/account) that selected the team; never records team names or ids or the variable name.", + reason: "Records the identity source (none/flag/env_id/env_name/account) that selected the team; never records team names or ids or the variable name.", }, "version": { kind: "generic", diff --git a/src/application/commands/variables/create.ts b/src/application/commands/variables/create.ts index 2f121999..62481222 100644 --- a/src/application/commands/variables/create.ts +++ b/src/application/commands/variables/create.ts @@ -2,11 +2,14 @@ import type { CliCommandDefinition } from "../../contracts/cli.ts"; import { z } from "zod"; import { requireIdentity } from "../../auth/identity.ts"; import { writeLine } from "../shared/output.ts"; -import { teamIdentityInputShape, teamIdentityOptions } from "../team/identity.ts"; +import { + resolveAccountTeamIdentity, + teamIdentityInputShape, + teamOption, +} from "../team/identity.ts"; import { mapVariablesInputError, putVariable, - resolveVariablesIdentity, resolveVariableValue, variableNameSchema, } from "./shared.ts"; @@ -17,7 +20,6 @@ interface VariablesCreateInput { fromFile?: string; stdin?: boolean; team?: string; - personal?: boolean; } export const variablesCreateCommand: CliCommandDefinition = { @@ -50,10 +52,7 @@ export const variablesCreateCommand: CliCommandDefinition longFlag: "--stdin", descriptionKey: "options.variablesStdin", }, - ...teamIdentityOptions({ - personal: "options.variablesPersonal", - team: "options.variablesTeam", - }), + teamOption("options.variablesTeam"), ], output: "standard", inputSchema: z.object({ @@ -66,7 +65,7 @@ export const variablesCreateCommand: CliCommandDefinition mapInputError: mapVariablesInputError, handler: async (input, context) => { const { account } = await requireIdentity(context); - const identity = await resolveVariablesIdentity(input, account, context); + const identity = await resolveAccountTeamIdentity(input, account, context); const value = await resolveVariableValue(input, context); const variable = await putVariable(account, identity, input.name, value, context); diff --git a/src/application/commands/variables/delete.ts b/src/application/commands/variables/delete.ts index 108d7087..5da0cdee 100644 --- a/src/application/commands/variables/delete.ts +++ b/src/application/commands/variables/delete.ts @@ -2,18 +2,20 @@ import type { CliCommandDefinition } from "../../contracts/cli.ts"; import { z } from "zod"; import { requireIdentity } from "../../auth/identity.ts"; import { writeLine } from "../shared/output.ts"; -import { teamIdentityInputShape, teamIdentityOptions } from "../team/identity.ts"; +import { + resolveAccountTeamIdentity, + teamIdentityInputShape, + teamOption, +} from "../team/identity.ts"; import { deleteVariable, mapVariablesInputError, - resolveVariablesIdentity, variableNameSchema, } from "./shared.ts"; interface VariablesDeleteInput { name: string; team?: string; - personal?: boolean; } export const variablesDeleteCommand: CliCommandDefinition = { @@ -29,10 +31,7 @@ export const variablesDeleteCommand: CliCommandDefinition }, ], options: [ - ...teamIdentityOptions({ - personal: "options.variablesPersonal", - team: "options.variablesTeam", - }), + teamOption("options.variablesTeam"), ], output: "standard", inputSchema: z.object({ @@ -42,7 +41,7 @@ export const variablesDeleteCommand: CliCommandDefinition mapInputError: mapVariablesInputError, handler: async (input, context) => { const { account } = await requireIdentity(context); - const identity = await resolveVariablesIdentity(input, account, context); + const identity = await resolveAccountTeamIdentity(input, account, context); await deleteVariable(account, identity, input.name, context); context.output.emit({ name: input.name, deleted: true }, () => { diff --git a/src/application/commands/variables/get.ts b/src/application/commands/variables/get.ts index eae6862f..85e4e7c2 100644 --- a/src/application/commands/variables/get.ts +++ b/src/application/commands/variables/get.ts @@ -2,18 +2,20 @@ import type { CliCommandDefinition } from "../../contracts/cli.ts"; import { z } from "zod"; import { requireIdentity } from "../../auth/identity.ts"; import { writeLine } from "../shared/output.ts"; -import { teamIdentityInputShape, teamIdentityOptions } from "../team/identity.ts"; +import { + resolveAccountTeamIdentity, + teamIdentityInputShape, + teamOption, +} from "../team/identity.ts"; import { getVariable, mapVariablesInputError, - resolveVariablesIdentity, variableNameSchema, } from "./shared.ts"; interface VariablesGetInput { name: string; team?: string; - personal?: boolean; } export const variablesGetCommand: CliCommandDefinition = { @@ -29,10 +31,7 @@ export const variablesGetCommand: CliCommandDefinition = { }, ], options: [ - ...teamIdentityOptions({ - personal: "options.variablesPersonal", - team: "options.variablesTeam", - }), + teamOption("options.variablesTeam"), ], output: "standard", inputSchema: z.object({ @@ -42,7 +41,7 @@ export const variablesGetCommand: CliCommandDefinition = { mapInputError: mapVariablesInputError, handler: async (input, context) => { const { account } = await requireIdentity(context); - const identity = await resolveVariablesIdentity(input, account, context); + const identity = await resolveAccountTeamIdentity(input, account, context); const variable = await getVariable(account, identity, input.name, context); context.output.emit(variable, () => { diff --git a/src/application/commands/variables/index.cli.test.ts b/src/application/commands/variables/index.cli.test.ts index 5e1439fc..88bdf166 100644 --- a/src/application/commands/variables/index.cli.test.ts +++ b/src/application/commands/variables/index.cli.test.ts @@ -505,29 +505,6 @@ describe("variables team identity", () => { } }); - test("--personal drops the saved default and sends no team header", async () => { - const sandbox = await createCliSandbox(); - const requests: Request[] = []; - - try { - await writeAuthFileWithDefaultTeam(sandbox, "acme", { teamId: "team-1" }); - - const result = await sandbox.run(["variables", "delete", "k", "--personal"], { - fetcher: async (input, init) => { - requests.push(toRequest(input, init)); - return new Response("", { status: 204 }); - }, - }); - - expect(result.exitCode).toBe(0); - expect(requests[0]!.headers.get("x-oo-team-name")).toBeNull(); - expect(requests[0]!.headers.get("x-oo-team-id")).toBeNull(); - } - finally { - await sandbox.cleanup(); - } - }); - test("OO_TEAM_ID is validated and sends both halves of the identity", async () => { const sandbox = await createCliSandbox(); const requests: Request[] = []; @@ -567,28 +544,6 @@ describe("variables team identity", () => { } }); - test("--team with --personal errors with exit 2 and sends no request", async () => { - const sandbox = await createCliSandbox(); - let called = false; - - try { - await writeAuthFile(sandbox); - - const result = await sandbox.run(["variables", "list", "--team", "acme", "--personal"], { - fetcher: async () => { - called = true; - return new Response("{}"); - }, - }); - - expect(result.exitCode).toBe(2); - expect(called).toBe(false); - } - finally { - await sandbox.cleanup(); - } - }); - test("an empty --team errors with exit 2 and sends no request", async () => { const sandbox = await createCliSandbox(); let called = false; diff --git a/src/application/commands/variables/list.ts b/src/application/commands/variables/list.ts index 0bbb0b8b..953b12fe 100644 --- a/src/application/commands/variables/list.ts +++ b/src/application/commands/variables/list.ts @@ -2,13 +2,16 @@ import type { CliCommandDefinition } from "../../contracts/cli.ts"; import { z } from "zod"; import { requireIdentity } from "../../auth/identity.ts"; import { writeLine } from "../shared/output.ts"; -import { teamIdentityInputShape, teamIdentityOptions } from "../team/identity.ts"; -import { listVariables, resolveVariablesIdentity } from "./shared.ts"; +import { + resolveAccountTeamIdentity, + teamIdentityInputShape, + teamOption, +} from "../team/identity.ts"; +import { listVariables } from "./shared.ts"; import { formatVariableListLine } from "./text.ts"; interface VariablesListInput { team?: string; - personal?: boolean; } export const variablesListCommand: CliCommandDefinition = { @@ -16,10 +19,7 @@ export const variablesListCommand: CliCommandDefinition = { summaryKey: "commands.variables.list.summary", descriptionKey: "commands.variables.list.description", options: [ - ...teamIdentityOptions({ - personal: "options.variablesPersonal", - team: "options.variablesTeam", - }), + teamOption("options.variablesTeam"), ], output: "standard", inputSchema: z.object({ @@ -27,7 +27,7 @@ export const variablesListCommand: CliCommandDefinition = { }), handler: async (input, context) => { const { account } = await requireIdentity(context); - const identity = await resolveVariablesIdentity(input, account, context); + const identity = await resolveAccountTeamIdentity(input, account, context); const variables = await listVariables(account, identity, context); context.output.emit({ variables }, () => { diff --git a/src/application/commands/variables/shared.ts b/src/application/commands/variables/shared.ts index 6f338e41..15758771 100644 --- a/src/application/commands/variables/shared.ts +++ b/src/application/commands/variables/shared.ts @@ -6,16 +6,10 @@ import type { TeamIdentity } from "../team/identity.ts"; import { Buffer } from "node:buffer"; import { readFile } from "node:fs/promises"; import { z } from "zod"; -import { readDefaultTeam } from "../../auth/default-team.ts"; import { CliUserError } from "../../contracts/cli.ts"; import { requestOo, requestOoResponse } from "../shared/oo-request.ts"; import { readStdinToEnd } from "../shared/stdin.ts"; -import { - assertTeamIdentityFlags, - requireValidTeamIdentity, - resolveTeamIdentity, - teamIdentityHeaders, -} from "../team/identity.ts"; +import { teamIdentityHeaders } from "../team/identity.ts"; export const MAX_VARIABLE_NAME_LENGTH = 256; export const MAX_VARIABLE_VALUE_BYTES = 65536; @@ -33,22 +27,6 @@ export interface Variable { type RequestContext = Pick; type VariableAccount = Pick; -type VariablesIdentityContext = Pick< - CliExecutionContext, - | "authStore" - | "env" - | "fetcher" - | "logger" - | "settingsStore" - | "telemetry" - | "translator" ->; - -interface VariablesIdentityInput { - personal?: boolean; - team?: string; -} - // MARK: - Validation function hasControlCharacter(value: string): boolean { @@ -130,45 +108,6 @@ export async function resolveVariableValue( return value; } -// MARK: - Team identity - -/** - * Resolves the team the variables commands act for: the shared flag guards, - * the one shared identity ladder (`--personal` > `--team` > `OO_TEAM_ID` > - * `OO_TEAM_NAME` > the account default), its execution gate, and the identity - * telemetry — everything the four subcommands must agree on. - * - * `undefined` means no team header is sent, which lets the gateway apply the - * server-side default team; it is not a private, per-user scope. - */ -export async function resolveVariablesIdentity( - input: VariablesIdentityInput, - account: VariableAccount, - context: VariablesIdentityContext, -): Promise { - const teamFlag = assertTeamIdentityFlags(input); - - const identity = requireValidTeamIdentity( - await resolveTeamIdentity( - { - account, - defaultTeam: await readDefaultTeam(context), - teamFlag, - personalFlag: input.personal === true, - resolveAgainstBackend: true, - }, - context, - ), - context, - ); - - context.telemetry?.recordProperties({ - identity_source: identity?.source ?? "personal", - }); - - return identity; -} - // MARK: - Requests const variableSchema = z.object({ diff --git a/src/application/schemas/auth.test.ts b/src/application/schemas/auth.test.ts index c14c6a46..01f4b292 100644 --- a/src/application/schemas/auth.test.ts +++ b/src/application/schemas/auth.test.ts @@ -3,7 +3,6 @@ import type { AuthFile } from "./auth.ts"; import { describe, expect, test } from "bun:test"; import { authTomlFileSchema, - clearAccountDefaultTeam, getNextAuthAccount, renderAuthFile, setAccountDefaultTeam, @@ -254,29 +253,6 @@ describe("setAccountDefaultTeam", () => { }); }); -describe("clearAccountDefaultTeam", () => { - test("removes both team fields", () => { - const withTeam = setAccountDefaultTeam(createAuthFile(), "user-1", { - id: "team-1", - name: "acme", - }); - const next = clearAccountDefaultTeam(withTeam, "user-1"); - - expect(next.auth[0]).toEqual({ - apiKey: "secret-1", - endpoint: "oomol.com", - id: "user-1", - name: "Alice", - }); - }); - - test("leaves the file untouched when no default team is stored", () => { - const authFile = createAuthFile(); - - expect(clearAccountDefaultTeam(authFile, "user-1")).toBe(authFile); - }); -}); - describe("upsertAuthAccount", () => { test("keeps the stored Team and Flow Project when the account logs in again", () => { const authFile = createAuthFile({ diff --git a/src/application/schemas/auth.ts b/src/application/schemas/auth.ts index 033fb209..626e857a 100644 --- a/src/application/schemas/auth.ts +++ b/src/application/schemas/auth.ts @@ -166,20 +166,6 @@ export function setAccountDefaultTeam( })); } -/** Removes the default team identity from one account. */ -export function clearAccountDefaultTeam( - authFile: AuthFile, - accountId: string, -): AuthFile { - return mapAuthAccount(authFile, accountId, (account) => { - if (account.team === undefined && account.teamId === undefined) { - return account; - } - - return withoutTeamFields(account); - }); -} - export function removeCurrentAuthAccount(authFile: AuthFile): AuthFile { return { auth: authFile.auth.filter(account => account.id !== authFile.id), diff --git a/src/i18n/catalog.ts b/src/i18n/catalog.ts index 8a8b094b..f06d23d1 100644 --- a/src/i18n/catalog.ts +++ b/src/i18n/catalog.ts @@ -45,7 +45,7 @@ export const enMessages = { "auth.status.selfHostedConnectorToken.yes": "yes", "auth.status.team": "Default team", "auth.status.teamEnvOverride": "{team} (via {envVar})", - "auth.status.teamPersonal": "personal (no default team)", + "auth.status.teamServerDefault": "server default (no saved team)", "auth.switch.envOverrideNoop": "Nothing was switched: the active credential comes from OO_API_KEY, not from a saved account.", "auth.switch.success": "Switched active account for {endpoint} to {name}", @@ -198,14 +198,11 @@ export const enMessages = { "List the teams the active account can authenticate as, marking the current default.", "commands.team.list.summary": "List accessible teams", "commands.team.current.description": - "Show the default team identity used by team-aware commands (connector, variables) when no --team / --personal flag is given.", + "Show the default team identity used by team-aware commands (connector, variables, file upload) when no --team flag is given.", "commands.team.current.summary": "Show the default team identity", "commands.team.use.description": "Set the default team identity used by team-aware commands (connector, variables), after checking the account can access it.", "commands.team.use.summary": "Set the default team identity", - "commands.team.clear.description": - "Clear the default team identity so team-aware commands (connector, variables) stop using it.", - "commands.team.clear.summary": "Clear the default team identity", "commands.search.description": "Search connector actions with one free-form query.", "commands.search.summary": "Search connector actions", @@ -403,8 +400,6 @@ export const enMessages = { "The team id \"{teamId}\" from OO_TEAM_ID cannot be used: {reason}. Run `oo team list` to see the teams you can use.", "errors.team.envNameNotAccessible": "The active account cannot access the team \"{team}\" from OO_TEAM_NAME. Run `oo team list` to see the teams you can use.", - "errors.team.identityConflict": - "Use either --team or --personal, not both.", "errors.team.invalidResponse": "The team list response body is unsupported.", "errors.team.nameEmpty": "The team name must not be empty.", @@ -999,20 +994,12 @@ export const enMessages = { "Run the action with the connector app connection name", "options.connectorAppsTeam": "List connected apps under the given team identity", - "options.connectorAppsPersonal": - "List connected apps under your personal identity, ignoring any configured default team", "options.searchTeam": "Report each action's authenticated status under the given team identity", - "options.searchPersonal": - "Report authenticated status under your personal identity, ignoring any configured default team", "options.connectorRunTeam": "Run the action under the given team identity", - "options.connectorRunPersonal": - "Run the action under your personal identity, ignoring any configured default team", "options.fileUploadTeam": "Upload the file under the given team identity", - "options.fileUploadPersonal": - "Upload the file under the server-side default team, ignoring OO_TEAM_ID / OO_TEAM_NAME and any saved default team", "options.connectorLoginToken": "Runtime API token for the self-hosted connector (created on the server's /access page)", "options.connectorProxyBody": @@ -1027,8 +1014,6 @@ export const enMessages = { "Specify the upstream HTTP method", "options.connectorProxyTeam": "Run the proxy request under the given team identity", - "options.connectorProxyPersonal": - "Run the proxy request under your personal identity, ignoring any configured default team", "options.connectorProxyQuery": "Specify upstream query parameters as a JSON object", "options.debug": "Print the current log file path when the CLI exits", @@ -1303,10 +1288,10 @@ export const enMessages = { "team.list.text.role": "Role", "team.list.text.default": "Default", "team.list.text.noTeams": - "The active account has no teams; connector commands run under your personal identity and variables commands use the server-side default team.", + "The active account has no teams; team-aware commands send no team selection.", "team.current.text.accountDefault": "Default team identity: {team}", - "team.current.text.personal": - "No default team; connector commands run under your personal identity and variables commands use the server-side default team.", + "team.current.text.serverDefault": + "No default team saved; team-aware commands use the server-side default team.", "team.current.text.envId": "Team identity comes from the OO_TEAM_ID environment variable: {team}", "team.current.text.envName": @@ -1329,16 +1314,6 @@ export const enMessages = { "Nothing was saved: the active credential comes from OO_API_KEY, which has no saved default team. Pin a team with OO_TEAM_ID or OO_TEAM_NAME instead.", "team.use.envOverrideHint": "Team-aware commands keep using the team from {envVar}, not this default. Unset {envVar} to use the saved default.", - "team.clear.success": - "Cleared the default team identity; connector commands now run under your personal identity and variables commands use the server-side default team.", - "team.clear.alreadyPersonal": - "No default team was set; connector commands already run under your personal identity and variables commands use the server-side default team.", - "team.clear.successEnvOverride": - "Cleared the default team identity, but {envVar} still selects the team for team-aware commands. Unset {envVar} to stop selecting a team.", - "team.clear.alreadyPersonalEnvHint": - "No default team was set, but {envVar} still selects a team for team-aware commands.", - "team.clear.envOverrideNoop": - "Nothing was cleared: the active credential comes from OO_API_KEY, which already runs under your personal identity unless OO_TEAM_ID or OO_TEAM_NAME selects a team.", "connector.run.text.dryRunPassed": "Validation passed.", "connector.login.manageTokens": "Manage runtime tokens at {accessUrl}", "connector.login.noToken": @@ -1397,7 +1372,6 @@ export const enMessages = { "options.variablesFromFile": "Read the variable value from a file (UTF-8)", "options.variablesStdin": "Read the variable value from standard input (UTF-8)", "options.variablesTeam": "Run the command for the given team", - "options.variablesPersonal": "Use the server-side default team, ignoring OO_TEAM_ID / OO_TEAM_NAME and any saved default team", "errors.variables.invalidName": "Invalid variable name: {value}. Names must be 1-256 characters and must not contain '/' or control characters.", "errors.variables.valueSource": "Provide exactly one variable value source: a value argument, --from-file, or --stdin.", "errors.variables.stdinTty": "--stdin requires piped input; refusing to read from an interactive terminal.", @@ -1458,7 +1432,7 @@ export const zhMessages = { "auth.status.savedAccountsIgnored": "设置了 OO_API_KEY 时,保存的账号不会被使用。", "auth.status.team": "默认团队", "auth.status.teamEnvOverride": "{team}(来自 {envVar})", - "auth.status.teamPersonal": "个人身份(未设置默认团队)", + "auth.status.teamServerDefault": "服务端默认团队(未保存默认团队)", "auth.switch.envOverrideNoop": "没有切换任何账号:当前生效的凭证来自 OO_API_KEY,而不是保存的账号。", "auth.switch.success": "已将 {endpoint} 的当前激活账号切换为 {name}", @@ -1598,14 +1572,11 @@ export const zhMessages = { "列出当前活动账号可认证的团队,并标出当前默认团队。", "commands.team.list.summary": "列出可访问的团队", "commands.team.current.description": - "显示未传 --team / --personal 时团队相关命令(connector、variables)使用的默认团队身份。", + "显示未传 --team 时团队相关命令(connector、variables、file upload)使用的默认团队身份。", "commands.team.current.summary": "显示默认团队身份", "commands.team.use.description": "在确认账号可访问后,设置团队相关命令(connector、variables)使用的默认团队身份。", "commands.team.use.summary": "设置默认团队身份", - "commands.team.clear.description": - "清除默认团队身份,让团队相关命令(connector、variables)不再使用它。", - "commands.team.clear.summary": "清除默认团队身份", "commands.search.description": "使用一个自由文本查询搜索 connector action。", "commands.search.summary": "搜索 connector action", @@ -1780,8 +1751,6 @@ export const zhMessages = { "无法使用 OO_TEAM_ID 指定的团队 id “{teamId}”:{reason}。运行 `oo team list` 查看可用的团队。", "errors.team.envNameNotAccessible": "当前活动账号无法访问 OO_TEAM_NAME 指定的团队 “{team}”。运行 `oo team list` 查看可用的团队。", - "errors.team.identityConflict": - "--team 和 --personal 只能使用其中一个。", "errors.team.invalidResponse": "团队列表返回了不受支持的响应内容。", "errors.team.nameEmpty": "团队名称不能为空。", @@ -2371,20 +2340,12 @@ export const zhMessages = { "使用指定 connector app 连接名称运行该 action", "options.connectorAppsTeam": "以指定团队身份列出已连接的 app", - "options.connectorAppsPersonal": - "以个人身份列出已连接的 app,忽略已配置的默认团队", "options.searchTeam": "以指定团队身份报告每个 action 的 authenticated 状态", - "options.searchPersonal": - "以个人身份报告 authenticated 状态,忽略已配置的默认团队", "options.connectorRunTeam": "以指定团队身份运行该 action", - "options.connectorRunPersonal": - "以个人身份运行该 action,忽略已配置的默认团队", "options.fileUploadTeam": "以指定团队身份上传该文件", - "options.fileUploadPersonal": - "以服务端默认团队上传该文件,忽略 OO_TEAM_ID / OO_TEAM_NAME 与已保存的默认团队", "options.connectorLoginToken": "自部署 Connector 的 Runtime API 令牌(在服务的 /access 页面创建)", "options.connectorProxyBody": @@ -2399,8 +2360,6 @@ export const zhMessages = { "指定上游 HTTP method", "options.connectorProxyTeam": "以指定团队身份运行该 proxy 请求", - "options.connectorProxyPersonal": - "以个人身份运行该 proxy 请求,忽略已配置的默认团队", "options.connectorProxyQuery": "以 JSON object 指定上游 query 参数", "options.debug": "在 CLI 退出时打印当前日志文件路径", @@ -2672,10 +2631,10 @@ export const zhMessages = { "team.list.text.role": "角色", "team.list.text.default": "默认", "team.list.text.noTeams": - "当前活动账号没有任何团队;connector 命令以个人身份运行,variables 命令使用服务端默认团队。", + "当前活动账号没有任何团队;团队相关命令不会发送团队选择。", "team.current.text.accountDefault": "默认团队身份:{team}", - "team.current.text.personal": - "未设置默认团队;connector 命令以个人身份运行,variables 命令使用服务端默认团队。", + "team.current.text.serverDefault": + "未保存默认团队;团队相关命令使用服务端默认团队。", "team.current.text.envId": "团队身份来自 OO_TEAM_ID 环境变量:{team}", "team.current.text.envName": @@ -2696,16 +2655,6 @@ export const zhMessages = { "未保存任何内容:当前凭据来自 OO_API_KEY,它没有保存的默认团队。如需固定团队,请使用 OO_TEAM_ID 或 OO_TEAM_NAME。", "team.use.envOverrideHint": "团队相关命令仍会使用 {envVar} 指定的团队,而不是这个默认值。取消 {envVar} 后保存的默认值才会生效。", - "team.clear.success": - "已清除默认团队身份;connector 命令现在以个人身份运行,variables 命令使用服务端默认团队。", - "team.clear.alreadyPersonal": - "未设置默认团队;connector 命令本就以个人身份运行,variables 命令使用服务端默认团队。", - "team.clear.successEnvOverride": - "已清除默认团队身份,但 {envVar} 仍会为团队相关命令选择团队。取消 {envVar} 后才不会选择团队。", - "team.clear.alreadyPersonalEnvHint": - "未设置默认团队,但 {envVar} 仍会为团队相关命令选择团队。", - "team.clear.envOverrideNoop": - "未清除任何内容:当前凭据来自 OO_API_KEY,除非 OO_TEAM_ID 或 OO_TEAM_NAME 选择了团队,否则它本就以个人身份运行。", "connector.run.text.dryRunPassed": "校验通过。", "connector.login.manageTokens": "可在 {accessUrl} 管理 Runtime Token。", "connector.login.noToken": @@ -2762,7 +2711,6 @@ export const zhMessages = { "options.variablesFromFile": "从文件读取变量值(UTF-8)", "options.variablesStdin": "从标准输入读取变量值(UTF-8)", "options.variablesTeam": "以指定团队执行该命令", - "options.variablesPersonal": "使用服务端默认团队,忽略 OO_TEAM_ID / OO_TEAM_NAME 与已保存的默认团队", "errors.variables.invalidName": "无效的变量 name:{value}。name 必须为 1-256 个字符,且不能包含 '/' 或控制字符。", "errors.variables.valueSource": "请只提供一个变量值来源:value 参数、--from-file 或 --stdin。", "errors.variables.stdinTty": "--stdin 需要管道输入;拒绝从交互式终端读取。", From 0638c0ba9437e4195fa0d7e290527122f203c39e Mon Sep 17 00:00:00 2001 From: Kevin Cui Date: Fri, 28 Aug 2026 12:44:15 -0400 Subject: [PATCH 2/2] docs(team): align team identity wording across help, docs, and skill Review follow-ups: `oo team` and `oo team use` help now list file upload with connector and variables, `oo team current` documents the server-side fallback when neither the env override nor a saved default selects a team, the self-hosted docs list `oo connector search` among the commands that reject `--team`, and the bundled skill describes the hosted ladder and the self-hosted exception instead of claiming `--team` is the only way to pick a team. Signed-off-by: Kevin Cui --- .../skills/shared/oo/references/connector-execution.md | 9 ++++++--- docs/commands.md | 4 +++- docs/commands.zh-CN.md | 3 ++- docs/self-hosted-connector.md | 3 ++- docs/self-hosted-connector.zh-CN.md | 5 +++-- src/i18n/catalog.ts | 8 ++++---- 6 files changed, 20 insertions(+), 12 deletions(-) diff --git a/contrib/skills/shared/oo/references/connector-execution.md b/contrib/skills/shared/oo/references/connector-execution.md index 6e06c5e1..27bd3b55 100644 --- a/contrib/skills/shared/oo/references/connector-execution.md +++ b/contrib/skills/shared/oo/references/connector-execution.md @@ -194,9 +194,12 @@ oo connector proxy "" \ --json ``` -- There is no personal run: every run is attributed to a team, and with no - flag the server applies the account's default team. To run as a different - team than the default, name it with `--team`. +- There is no personal run against the OOMOL connector: every run is + attributed to a team. With no flag, the team comes from `OO_TEAM_ID` / + `OO_TEAM_NAME`, then the saved account default, and otherwise the server + applies the account's default team. `--team` overrides all of those for one + run. A self-hosted connector has no team concept: it rejects `--team` and + ignores the env variables and the saved default. Facts: diff --git a/docs/commands.md b/docs/commands.md index 27939a96..797ed384 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -568,7 +568,7 @@ Alias for `oo auth logout`. ## Teams -Team identity selects which team team-aware commands act for: the connector +Team identity selects the team that team-aware commands act for: the connector commands (`oo connector run`, `oo connector proxy`, `oo connector apps`), the variables commands (`oo variables list/get/create/delete`), whose data is team-owned in the first place, and `oo file upload`, whose upload is billed and @@ -622,6 +622,8 @@ read-only. Show the team identity used by team-aware commands (connector, variables, file upload) when no `--team` flag is given: the `OO_TEAM_ID` / `OO_TEAM_NAME` environment override when set, otherwise the active account's default team. +When neither is set, the commands send no team selection and the server +applies its own default team. - Sends one request only when `OO_TEAM_ID` or `OO_TEAM_NAME` supplies the identity, to complete and validate the missing half: an id resolves to its diff --git a/docs/commands.zh-CN.md b/docs/commands.zh-CN.md index a913db6e..915401b3 100644 --- a/docs/commands.zh-CN.md +++ b/docs/commands.zh-CN.md @@ -515,7 +515,8 @@ oo flow 显示未传 `--team` 时团队相关命令(connector、variables、file upload)使用的 团队身份:设置了 `OO_TEAM_ID` / `OO_TEAM_NAME` 环境变量时为 env 指定的团队, -否则为当前账号保存的默认团队。 +否则为当前账号保存的默认团队。两者都未设置时,命令不发送团队选择,由服务端 +套用它自己的默认团队。 - 仅当身份来自 `OO_TEAM_ID` 或 `OO_TEAM_NAME` 时才发送 1 次请求,补全并校验 缺失的那一半:id 解析出团队名称,名称通过账号的团队成员关系解析出 id——这 diff --git a/docs/self-hosted-connector.md b/docs/self-hosted-connector.md index 8db87a06..1a65bc47 100644 --- a/docs/self-hosted-connector.md +++ b/docs/self-hosted-connector.md @@ -178,7 +178,8 @@ A self-hosted runtime exposes a smaller surface than the OOMOL service. The CLI adapts as follows: - **Team identity is not supported.** `--team` is rejected with exit code `2` on - `oo connector run`, `oo connector proxy`, and `oo connector apps`, and any + `oo connector run`, `oo connector proxy`, `oo connector apps`, and + `oo connector search` / `oo search`, and any saved account default team and the `OO_TEAM_ID` / `OO_TEAM_NAME` environment variables are ignored. - **Async lifecycle waiting is unavailable.** `--wait` and `--wait-result` fail diff --git a/docs/self-hosted-connector.zh-CN.md b/docs/self-hosted-connector.zh-CN.md index 7e475a3b..a9c8cc0d 100644 --- a/docs/self-hosted-connector.zh-CN.md +++ b/docs/self-hosted-connector.zh-CN.md @@ -158,8 +158,9 @@ oo connector run gmail --action send_email --data '@payload.json' 自部署运行时暴露的能力比 OOMOL 服务更小,CLI 会做如下适配: -- **不支持团队身份。** `oo connector run`、`oo connector proxy` 与 `oo connector - apps` 上的 `--team` 会以退出码 `2` 被拒绝,账号保存的默认团队 +- **不支持团队身份。** `oo connector run`、`oo connector proxy`、`oo connector + apps` 与 `oo connector search` / `oo search` 上的 `--team` 会以退出码 `2` + 被拒绝,账号保存的默认团队 和 `OO_TEAM_ID` / `OO_TEAM_NAME` 环境变量都会被忽略。 - **无法等待异步生命周期。** `--wait` 与 `--wait-result` 会以既有的「不支持」 错误失败,因为自部署运行时未暴露异步结果生命周期契约。 diff --git a/src/i18n/catalog.ts b/src/i18n/catalog.ts index f06d23d1..7ad6fd4a 100644 --- a/src/i18n/catalog.ts +++ b/src/i18n/catalog.ts @@ -192,7 +192,7 @@ export const enMessages = { "commands.logout.summary": "Log out the current account (alias for auth logout)", "commands.team.description": - "List the teams your account can access and manage the default team identity used by team-aware commands (connector, variables).", + "List the teams your account can access and manage the default team identity used by team-aware commands (connector, variables, file upload).", "commands.team.summary": "Manage team identity", "commands.team.list.description": "List the teams the active account can authenticate as, marking the current default.", @@ -201,7 +201,7 @@ export const enMessages = { "Show the default team identity used by team-aware commands (connector, variables, file upload) when no --team flag is given.", "commands.team.current.summary": "Show the default team identity", "commands.team.use.description": - "Set the default team identity used by team-aware commands (connector, variables), after checking the account can access it.", + "Set the default team identity used by team-aware commands (connector, variables, file upload), after checking the account can access it.", "commands.team.use.summary": "Set the default team identity", "commands.search.description": "Search connector actions with one free-form query.", @@ -1566,7 +1566,7 @@ export const zhMessages = { "commands.logout.description": "从持久化认证数据中移除当前账号。是 auth logout 的别名。", "commands.logout.summary": "登出当前账号(auth logout 的别名)", "commands.team.description": - "列出当前账号可访问的团队,并管理团队相关命令(connector、variables)使用的默认团队身份。", + "列出当前账号可访问的团队,并管理团队相关命令(connector、variables、file upload)使用的默认团队身份。", "commands.team.summary": "管理团队身份", "commands.team.list.description": "列出当前活动账号可认证的团队,并标出当前默认团队。", @@ -1575,7 +1575,7 @@ export const zhMessages = { "显示未传 --team 时团队相关命令(connector、variables、file upload)使用的默认团队身份。", "commands.team.current.summary": "显示默认团队身份", "commands.team.use.description": - "在确认账号可访问后,设置团队相关命令(connector、variables)使用的默认团队身份。", + "在确认账号可访问后,设置团队相关命令(connector、variables、file upload)使用的默认团队身份。", "commands.team.use.summary": "设置默认团队身份", "commands.search.description": "使用一个自由文本查询搜索 connector action。",