@indigoai-us/hq-cli 5.345.149 → 5.345.150
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -0
- package/dist/command-catalog.generated.d.ts +18 -6
- package/dist/command-catalog.generated.js +21 -6
- package/dist/commands/_patterns.d.ts +6 -0
- package/dist/commands/_patterns.js +6 -0
- package/dist/commands/groups.js +9 -6
- package/dist/commands/lanes-questions.js +7 -4
- package/dist/commands/lanes.js +13 -6
- package/dist/commands/members.d.ts +35 -0
- package/dist/commands/members.js +65 -14
- package/dist/commands/secrets.d.ts +5 -0
- package/dist/commands/secrets.js +11 -6
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +63282 -62480
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
- package/dist/lib/flag-registry-worker-source.generated.js +1 -1
- package/dist/lib/lanes/index.d.ts +1 -1
- package/dist/lib/lanes/index.js +1 -1
- package/dist/lib/lanes/store.d.ts +11 -0
- package/dist/lib/lanes/store.js +20 -0
- package/dist/lib/lanes/watch.js +5 -3
- package/package.json +8 -8
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,46 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.345.150] — 2026-10-19
|
|
6
|
+
- The bundled sync engine now uses hq-cloud 6.18.90, which brings two fixes.
|
|
7
|
+
- Owners and admins can now destroy one of a company's Outposts with
|
|
8
|
+
`hq outposts destroy --company <slug>`. Add `--outpost <name|id>` when the
|
|
9
|
+
company has more than one. Without `--yes`, hq shows the company, Outpost
|
|
10
|
+
name and id, and destroys it only after you type the Outpost name or `yes`.
|
|
11
|
+
A script without a terminal must pass `--yes`. With `--yes --json` it prints
|
|
12
|
+
one result object. After the request, hq keeps checking status and reports
|
|
13
|
+
whether the Outpost is destroyed or still being destroyed, instead of
|
|
14
|
+
failing on a slow or errored response. It never falls back to your personal
|
|
15
|
+
Outpost. The Company Outpost commands from the last release are unchanged:
|
|
16
|
+
`hq outposts list --company`, `--outpost <name|id>` on status, login,
|
|
17
|
+
login-code, refresh-config and the jobs commands, and `hq outposts jobs`
|
|
18
|
+
edit, pause, resume, delete and run-now.
|
|
19
|
+
- HQ Sync uses much less CPU and memory on Windows when HQ holds git
|
|
20
|
+
worktrees. The sync watcher no longer watches `worktrees/` at the top of
|
|
21
|
+
HQ, `.worktrees/`, `.claude/worktrees/`, `workspace/worktrees/`,
|
|
22
|
+
`node_modules` or nested `.git` folders, including during startup. A top
|
|
23
|
+
level `worktrees/` folder and any `.worktrees/` folder also stop syncing;
|
|
24
|
+
files from them that are already in the vault are not deleted. Sync passes
|
|
25
|
+
started by local changes now run at most once every 30 seconds, and a file
|
|
26
|
+
hq just downloaded no longer triggers an upload of itself. If the sync
|
|
27
|
+
runner uses more than a quarter of a CPU core or 600 MB of memory for a
|
|
28
|
+
while, it writes one warning line to its log. You can opt in to a local
|
|
29
|
+
diagnostics capture, and `hq-sync-runner --diagnostics` prints a redacted
|
|
30
|
+
summary to paste into a bug report. Nothing is uploaded.
|
|
31
|
+
- A company Outpost can now be given access to a secret. Use its `out_` id:
|
|
32
|
+
`hq secrets share <path> --with out_…` gives it that one secret, and
|
|
33
|
+
`hq groups add <group> out_…` lets it read the secrets shared with that
|
|
34
|
+
group. It gets nothing it was not explicitly given.
|
|
35
|
+
- `hq members list` now shows the company Outpost as a read-only row labeled
|
|
36
|
+
"Outpost", and `--json` lists it under `outposts`. Promoting, removing or
|
|
37
|
+
revoking an Outpost is refused with a plain message.
|
|
38
|
+
- `hq lanes create` now prints the new lane's `lane_id` before the list of other lanes it admitted, so a script that takes the first lane id from the output gets the lane it just created. Lane commands that accept a lane id (`hq lanes message`, `hq lanes questions list --from`, `hq lanes watch` and the other lane-target commands) still match the full id first. They now also accept a ULID prefix of 8 or more characters when it names exactly one local lane, and they refuse an ambiguous prefix with an error that lists every matching lane. `hq lanes watch` always prints full lane ids.
|
|
39
|
+
- `hq lanes create --runner subagent` accepts `--provider codex` (and `grok`),
|
|
40
|
+
so Codex `spawn_agent` work from `/conduct` and `/run-project` shows on the
|
|
41
|
+
lanes board like Claude `Agent` work. A subagent lane is a board record only;
|
|
42
|
+
the provider is attribution and no process is started. The
|
|
43
|
+
`subagent_runner_requires_claude` refusal is gone.
|
|
44
|
+
|
|
5
45
|
## [5.345.149] — 2026-10-19
|
|
6
46
|
- `hq access` answers faster. It checks the exact path first and, on a miss,
|
|
7
47
|
looks only in the nearest folder instead of listing the whole vault. It
|
|
@@ -2039,7 +2039,7 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
2039
2039
|
readonly subcommands: readonly [];
|
|
2040
2040
|
}, {
|
|
2041
2041
|
readonly name: "share";
|
|
2042
|
-
readonly description: "Share a secret with an email address, group, or the entire company (@all)";
|
|
2042
|
+
readonly description: "Share a secret with an email address, group, company Outpost (out_…), or the entire company (@all)";
|
|
2043
2043
|
readonly aliases: readonly [];
|
|
2044
2044
|
readonly hidden: false;
|
|
2045
2045
|
readonly helpOption: true;
|
|
@@ -2054,7 +2054,7 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
2054
2054
|
}];
|
|
2055
2055
|
readonly options: readonly [{
|
|
2056
2056
|
readonly flags: "--with <principal>";
|
|
2057
|
-
readonly description: "Email address, group id, or @all (entire company) to share with";
|
|
2057
|
+
readonly description: "Email address, group id, out_ Outpost id, or @all (entire company) to share with";
|
|
2058
2058
|
readonly hidden: false;
|
|
2059
2059
|
}, {
|
|
2060
2060
|
readonly flags: "--permission <level>";
|
|
@@ -2079,7 +2079,7 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
2079
2079
|
}];
|
|
2080
2080
|
readonly options: readonly [{
|
|
2081
2081
|
readonly flags: "--from <principal>";
|
|
2082
|
-
readonly description: "Email address, group id, or @all (entire company) to remove";
|
|
2082
|
+
readonly description: "Email address, group id, out_ Outpost id, or @all (entire company) to remove";
|
|
2083
2083
|
readonly hidden: false;
|
|
2084
2084
|
}];
|
|
2085
2085
|
readonly subcommands: readonly [];
|
|
@@ -2555,7 +2555,7 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
2555
2555
|
readonly subcommands: readonly [];
|
|
2556
2556
|
}, {
|
|
2557
2557
|
readonly name: "add";
|
|
2558
|
-
readonly description: "Add a person or
|
|
2558
|
+
readonly description: "Add a person, agent, or company Outpost to a group (principal: email, personUid, agentUid, or out_ Outpost id)";
|
|
2559
2559
|
readonly aliases: readonly [];
|
|
2560
2560
|
readonly hidden: false;
|
|
2561
2561
|
readonly helpOption: true;
|
|
@@ -2577,7 +2577,7 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
2577
2577
|
readonly subcommands: readonly [];
|
|
2578
2578
|
}, {
|
|
2579
2579
|
readonly name: "remove";
|
|
2580
|
-
readonly description: "Remove a person or
|
|
2580
|
+
readonly description: "Remove a person, agent, or company Outpost from a group (principal: email, personUid, agentUid, or out_ Outpost id)";
|
|
2581
2581
|
readonly aliases: readonly [];
|
|
2582
2582
|
readonly hidden: false;
|
|
2583
2583
|
readonly helpOption: true;
|
|
@@ -7970,6 +7970,18 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
7970
7970
|
readonly flags: "--id <outpostId>";
|
|
7971
7971
|
readonly description: "Outpost id (defaults to your primary box)";
|
|
7972
7972
|
readonly hidden: false;
|
|
7973
|
+
}, {
|
|
7974
|
+
readonly flags: "--company <slug>";
|
|
7975
|
+
readonly description: "Destroy one of the company's Outposts (owner or admin)";
|
|
7976
|
+
readonly hidden: false;
|
|
7977
|
+
}, {
|
|
7978
|
+
readonly flags: "--outpost <name|id>";
|
|
7979
|
+
readonly description: "Which company Outpost, when the company has more than one";
|
|
7980
|
+
readonly hidden: false;
|
|
7981
|
+
}, {
|
|
7982
|
+
readonly flags: "--json";
|
|
7983
|
+
readonly description: "With --company and --yes: print one result object on stdout";
|
|
7984
|
+
readonly hidden: false;
|
|
7973
7985
|
}, {
|
|
7974
7986
|
readonly flags: "--yes";
|
|
7975
7987
|
readonly description: "Confirm the irreversible teardown (required)";
|
|
@@ -14367,7 +14379,7 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
14367
14379
|
readonly hidden: false;
|
|
14368
14380
|
}, {
|
|
14369
14381
|
readonly flags: "--runner <kind>";
|
|
14370
|
-
readonly description: "process (default) | subagent: register a lane an in-session Claude
|
|
14382
|
+
readonly description: "process (default) | subagent: register a lane an in-session subagent runs (Claude Agent, Codex spawn_agent); skips admission and starts no process";
|
|
14371
14383
|
readonly hidden: false;
|
|
14372
14384
|
}, {
|
|
14373
14385
|
readonly flags: "--remote-control";
|
|
@@ -2446,7 +2446,7 @@ export const COMMAND_CATALOG = [
|
|
|
2446
2446
|
},
|
|
2447
2447
|
{
|
|
2448
2448
|
"name": "share",
|
|
2449
|
-
"description": "Share a secret with an email address, group, or the entire company (@all)",
|
|
2449
|
+
"description": "Share a secret with an email address, group, company Outpost (out_…), or the entire company (@all)",
|
|
2450
2450
|
"aliases": [],
|
|
2451
2451
|
"hidden": false,
|
|
2452
2452
|
"helpOption": true,
|
|
@@ -2464,7 +2464,7 @@ export const COMMAND_CATALOG = [
|
|
|
2464
2464
|
"options": [
|
|
2465
2465
|
{
|
|
2466
2466
|
"flags": "--with <principal>",
|
|
2467
|
-
"description": "Email address, group id, or @all (entire company) to share with",
|
|
2467
|
+
"description": "Email address, group id, out_ Outpost id, or @all (entire company) to share with",
|
|
2468
2468
|
"hidden": false
|
|
2469
2469
|
},
|
|
2470
2470
|
{
|
|
@@ -2495,7 +2495,7 @@ export const COMMAND_CATALOG = [
|
|
|
2495
2495
|
"options": [
|
|
2496
2496
|
{
|
|
2497
2497
|
"flags": "--from <principal>",
|
|
2498
|
-
"description": "Email address, group id, or @all (entire company) to remove",
|
|
2498
|
+
"description": "Email address, group id, out_ Outpost id, or @all (entire company) to remove",
|
|
2499
2499
|
"hidden": false
|
|
2500
2500
|
}
|
|
2501
2501
|
],
|
|
@@ -3071,7 +3071,7 @@ export const COMMAND_CATALOG = [
|
|
|
3071
3071
|
},
|
|
3072
3072
|
{
|
|
3073
3073
|
"name": "add",
|
|
3074
|
-
"description": "Add a person or
|
|
3074
|
+
"description": "Add a person, agent, or company Outpost to a group (principal: email, personUid, agentUid, or out_ Outpost id)",
|
|
3075
3075
|
"aliases": [],
|
|
3076
3076
|
"hidden": false,
|
|
3077
3077
|
"helpOption": true,
|
|
@@ -3097,7 +3097,7 @@ export const COMMAND_CATALOG = [
|
|
|
3097
3097
|
},
|
|
3098
3098
|
{
|
|
3099
3099
|
"name": "remove",
|
|
3100
|
-
"description": "Remove a person or
|
|
3100
|
+
"description": "Remove a person, agent, or company Outpost from a group (principal: email, personUid, agentUid, or out_ Outpost id)",
|
|
3101
3101
|
"aliases": [],
|
|
3102
3102
|
"hidden": false,
|
|
3103
3103
|
"helpOption": true,
|
|
@@ -9714,6 +9714,21 @@ export const COMMAND_CATALOG = [
|
|
|
9714
9714
|
"description": "Outpost id (defaults to your primary box)",
|
|
9715
9715
|
"hidden": false
|
|
9716
9716
|
},
|
|
9717
|
+
{
|
|
9718
|
+
"flags": "--company <slug>",
|
|
9719
|
+
"description": "Destroy one of the company's Outposts (owner or admin)",
|
|
9720
|
+
"hidden": false
|
|
9721
|
+
},
|
|
9722
|
+
{
|
|
9723
|
+
"flags": "--outpost <name|id>",
|
|
9724
|
+
"description": "Which company Outpost, when the company has more than one",
|
|
9725
|
+
"hidden": false
|
|
9726
|
+
},
|
|
9727
|
+
{
|
|
9728
|
+
"flags": "--json",
|
|
9729
|
+
"description": "With --company and --yes: print one result object on stdout",
|
|
9730
|
+
"hidden": false
|
|
9731
|
+
},
|
|
9717
9732
|
{
|
|
9718
9733
|
"flags": "--yes",
|
|
9719
9734
|
"description": "Confirm the irreversible teardown (required)",
|
|
@@ -17472,7 +17487,7 @@ export const COMMAND_CATALOG = [
|
|
|
17472
17487
|
},
|
|
17473
17488
|
{
|
|
17474
17489
|
"flags": "--runner <kind>",
|
|
17475
|
-
"description": "process (default) | subagent: register a lane an in-session Claude
|
|
17490
|
+
"description": "process (default) | subagent: register a lane an in-session subagent runs (Claude Agent, Codex spawn_agent); skips admission and starts no process",
|
|
17476
17491
|
"hidden": false
|
|
17477
17492
|
},
|
|
17478
17493
|
{
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
export declare const SECRET_NAME_PATTERN: RegExp;
|
|
2
2
|
export declare const GROUP_ID_PATTERN: RegExp;
|
|
3
3
|
export declare const EMAIL_PATTERN: RegExp;
|
|
4
|
+
/**
|
|
5
|
+
* A company Outpost identity: `out_` plus a 26-character Crockford ULID, the
|
|
6
|
+
* same shape hq-pro accepts as an Outpost grantee. It rides the `person`
|
|
7
|
+
* grantee slot on the wire.
|
|
8
|
+
*/
|
|
9
|
+
export declare const OUTPOST_UID_PATTERN: RegExp;
|
|
4
10
|
/**
|
|
5
11
|
* Keep an ACL pattern exactly as the caller supplied it.
|
|
6
12
|
*
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
export const SECRET_NAME_PATTERN = /^[A-Z][A-Z0-9_]*(?:\/[A-Z][A-Z0-9_]+)*$/;
|
|
2
2
|
export const GROUP_ID_PATTERN = /^grp_[A-Za-z0-9_-]+$/;
|
|
3
3
|
export const EMAIL_PATTERN = /^[^\s]+@[^\s]+$/;
|
|
4
|
+
/**
|
|
5
|
+
* A company Outpost identity: `out_` plus a 26-character Crockford ULID, the
|
|
6
|
+
* same shape hq-pro accepts as an Outpost grantee. It rides the `person`
|
|
7
|
+
* grantee slot on the wire.
|
|
8
|
+
*/
|
|
9
|
+
export const OUTPOST_UID_PATTERN = /^out_[0-9A-HJKMNP-TV-Z]{26}$/;
|
|
4
10
|
/**
|
|
5
11
|
* Keep an ACL pattern exactly as the caller supplied it.
|
|
6
12
|
*
|
package/dist/commands/groups.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { ensureCognitoToken } from "../utils/cognito-session.js";
|
|
3
3
|
import { vaultApiFetch, getCompanyUid } from "./secrets.js";
|
|
4
|
-
import { GROUP_ID_PATTERN } from "./_patterns.js";
|
|
4
|
+
import { GROUP_ID_PATTERN, OUTPOST_UID_PATTERN } from "./_patterns.js";
|
|
5
5
|
import { maxLength } from "../utils/table-width.js";
|
|
6
6
|
const EMAIL_PATTERN = /^[^\s]+@[^\s]+$/;
|
|
7
7
|
const PERSON_UID_PATTERN = /^prs_[A-Za-z0-9_-]+$/;
|
|
8
8
|
const AGENT_UID_PATTERN = /^agt_[A-Za-z0-9_-]+$/;
|
|
9
|
-
const INVALID_PRINCIPAL_HINT = "must be an email address, a personUid (prs_…),
|
|
9
|
+
const INVALID_PRINCIPAL_HINT = "must be an email address, a personUid (prs_…), an agentUid (agt_…), or a company Outpost id (out_…)";
|
|
10
10
|
export function detectPrincipalType(principal) {
|
|
11
11
|
if (EMAIL_PATTERN.test(principal)) {
|
|
12
12
|
// Server normalizes email again; we normalize here so local validation /
|
|
@@ -15,8 +15,11 @@ export function detectPrincipalType(principal) {
|
|
|
15
15
|
}
|
|
16
16
|
// Agent uids ride the same personUid wire slot as people — that is the
|
|
17
17
|
// server contract (group members, DM recipients, and memberships all carry
|
|
18
|
-
// agt_* in the personUid field; see hq-pro handlers).
|
|
19
|
-
|
|
18
|
+
// agt_* in the personUid field; see hq-pro handlers). The company Outpost
|
|
19
|
+
// (out_*) rides it too; the server checks it is the active Outpost.
|
|
20
|
+
if (PERSON_UID_PATTERN.test(principal) ||
|
|
21
|
+
AGENT_UID_PATTERN.test(principal) ||
|
|
22
|
+
OUTPOST_UID_PATTERN.test(principal)) {
|
|
20
23
|
return { granteeType: "person", granteeId: principal };
|
|
21
24
|
}
|
|
22
25
|
return null;
|
|
@@ -128,7 +131,7 @@ export function registerGroupsCommand(program) {
|
|
|
128
131
|
});
|
|
129
132
|
groups
|
|
130
133
|
.command("add <groupId> <principal>")
|
|
131
|
-
.description("Add a person or
|
|
134
|
+
.description("Add a person, agent, or company Outpost to a group (principal: email, personUid, agentUid, or out_ Outpost id)")
|
|
132
135
|
.action(async (groupId, principal) => {
|
|
133
136
|
try {
|
|
134
137
|
if (!GROUP_ID_PATTERN.test(groupId)) {
|
|
@@ -178,7 +181,7 @@ export function registerGroupsCommand(program) {
|
|
|
178
181
|
});
|
|
179
182
|
groups
|
|
180
183
|
.command("remove <groupId> <principal>")
|
|
181
|
-
.description("Remove a person or
|
|
184
|
+
.description("Remove a person, agent, or company Outpost from a group (principal: email, personUid, agentUid, or out_ Outpost id)")
|
|
182
185
|
.action(async (groupId, principal) => {
|
|
183
186
|
try {
|
|
184
187
|
if (!GROUP_ID_PATTERN.test(groupId)) {
|
|
@@ -2,7 +2,7 @@ import * as path from "node:path";
|
|
|
2
2
|
import { buildOutpostQuestionAnswerCommand, buildOutpostQuestionCancelCommand, buildOutpostQuestionShowCommand, dispatchErrorMessage } from "../lib/lanes/outpost-dispatch.js";
|
|
3
3
|
import { assertNoSecretText } from "../lib/lanes/text-scan.js";
|
|
4
4
|
import { sessionIdFromEnv } from "../lib/mesh/live/session-identity.js";
|
|
5
|
-
import { appendCappedHistory, createQuestion, isLanesError, listSessionLiveness, parseLivenessState, parseQuestionRecord, parseQuestionStatus, readDaemonPresenceConnected, readLane, readQuestion, wakeAnsweredLaneNow, sessionLiveness, appendDropboxMessage, answerDropboxMessage, laneAcceptsDropboxDelivery, laneIdForAnswerDelivery, writeQuestionBody } from "../lib/lanes/index.js";
|
|
5
|
+
import { appendCappedHistory, createQuestion, expandLocalLaneIdPrefix, isLanesError, listSessionLiveness, parseLivenessState, parseQuestionRecord, parseQuestionStatus, readDaemonPresenceConnected, readLane, readQuestion, wakeAnsweredLaneNow, sessionLiveness, appendDropboxMessage, answerDropboxMessage, laneAcceptsDropboxDelivery, laneIdForAnswerDelivery, writeQuestionBody } from "../lib/lanes/index.js";
|
|
6
6
|
import { resolveQuestionCompanyWithFallback } from "../lib/lanes/questions.js";
|
|
7
7
|
import { LanesError } from "../lib/lanes/errors.js";
|
|
8
8
|
import { applyLoopStallAnswer, findLoopStallResult } from "../lib/lanes/loop.js";
|
|
@@ -565,6 +565,9 @@ export function createLaneQuestionsHandlers(deps) {
|
|
|
565
565
|
const hqRoot = ctx.hqRoot();
|
|
566
566
|
const status = opts.status ? parseQuestionStatus(opts.status) : undefined;
|
|
567
567
|
const forFilter = opts.for ? parseForFilter(opts.for, ctx) : undefined;
|
|
568
|
+
// --from matches the full lane id; a short prefix expands only when it
|
|
569
|
+
// names exactly one local lane (ambiguous prefixes are refused).
|
|
570
|
+
const from = opts.from ? (expandLocalLaneIdPrefix(hqRoot, opts.from) ?? opts.from) : undefined;
|
|
568
571
|
const company = opts.company
|
|
569
572
|
? await ctx.resolveCompany(opts.company)
|
|
570
573
|
: await resolveActiveQuestionCompany(ctx, "Cannot access lane questions because the active HQ company could not be resolved; re-run with --company <slug>");
|
|
@@ -574,7 +577,7 @@ export function createLaneQuestionsHandlers(deps) {
|
|
|
574
577
|
.filter((q) => {
|
|
575
578
|
if (status && q.status !== status)
|
|
576
579
|
return false;
|
|
577
|
-
if (
|
|
580
|
+
if (from && q.lane_id !== from && q.origin.id !== from)
|
|
578
581
|
return false;
|
|
579
582
|
if (opts.project && q.project_id !== opts.project)
|
|
580
583
|
return false;
|
|
@@ -618,7 +621,7 @@ export function createLaneQuestionsHandlers(deps) {
|
|
|
618
621
|
? await loadPrimaryOwnedOutpostQuestions(ctx, hqRoot, company, undefined, {
|
|
619
622
|
sessionId: currentSessionId ?? undefined,
|
|
620
623
|
for: opts.for,
|
|
621
|
-
from
|
|
624
|
+
from,
|
|
622
625
|
project: opts.project,
|
|
623
626
|
story: opts.story,
|
|
624
627
|
status: opts.status,
|
|
@@ -651,7 +654,7 @@ export function createLaneQuestionsHandlers(deps) {
|
|
|
651
654
|
.filter(({ question: q, lane, audience }) => {
|
|
652
655
|
if (status && q.status !== status)
|
|
653
656
|
return false;
|
|
654
|
-
if (
|
|
657
|
+
if (from && q.lane_id !== from && q.origin.id !== from)
|
|
655
658
|
return false;
|
|
656
659
|
if (opts.project && q.project_id !== opts.project)
|
|
657
660
|
return false;
|
package/dist/commands/lanes.js
CHANGED
|
@@ -78,7 +78,7 @@ import { sessionIdFromEnv } from "../lib/mesh/live/session-identity.js";
|
|
|
78
78
|
import { listLaneReviews } from "../lib/lanes/reviews.js";
|
|
79
79
|
import { effectiveAccountHome } from "../lib/lanes/account-home.js";
|
|
80
80
|
import { remoteControlSelectionConfig, remoteControlSessionHome } from "../lib/lanes/remote-control-lane.js";
|
|
81
|
-
import { AccountResolutionError, compareLaneQueueOrder, LANE_PROVIDERS, LANE_STATES, QUESTION_STATUSES, SENIOR_KINDS, accountStatus, appendCappedHistory, canonicalizeHome, createLane, decideAdmission, defaultLivenessDeps, defaultSystemdUserDir, defaultThrottleDeps, installReconcileTimer, providerPathEnv, probeDrainHookRegistration, isLanesError, isLaneVersionConflict, isTerminalLaneState, isFinishedResumableHireChild, childrenHoldingHireGrantCarve, laneHoldsHireGrantCarve, assignedChildHireGrants, hireGrantCapacityError, planHireGrantBackfill, terminalChildResumeHireGrantError, withHireGrantAdmission, laneStoryLedgerEnabled, syncLaneToProjectStoryLedger, removeLaneFromProjectStoryLedger, validateProjectStories, buildLaneStatusSnapshot, formatLaneStatusHuman, listLaneIds, listQuestionIds, loadLanesConfig, laneRunsHere, MAX_LANE_DEPTH, MONITOR_ERROR_EXIT_CODE, readLiveMonitorEvidence, resolveMachineIdentity as lookupMachineIdentity, loadLivenessIndex, parseLaneProvider, isLaneProvider, parseLaneRecord, parseLaneState, parseQuestionRecord, parseSeniorKind, readAllLanes, readLane, sanitizeHostComponent, retryUpdateLane, syncLaneRecordFromOwner, readQuestion, resolveAccount, resolveLaneWorker, ignoredWorkerProfileFields, loadWorkerProfile, loadWorkerProfileWithRegistry, registryWorkerRecord, runReconcilePass, DEAD_HOST_MS, sessionLiveness, tryReadLane, uninstallReconcileTimer, updateLane, updateQuestion, dropboxDir, laneDeliveryMechanism, hasActiveAdmissionReservation, hasActivePendingResume, hasRecordedSessionId, hasEarlierEligibleQueuedLane, laneAdmissionCapReason, providerSupportsSessionResume, providerSessionPortable, isFailedQuotaKeepAliveLane, sessionNotPortableMessage, withLaneAdmissionLock, withAllLaneAdmissionLocks, readWorkerRunResult, resumeCommandRefusal, withLaneHireLocks, raiseLaneQuestion, sha256Text, } from "../lib/lanes/index.js";
|
|
81
|
+
import { AccountResolutionError, compareLaneQueueOrder, LANE_PROVIDERS, LANE_STATES, QUESTION_STATUSES, SENIOR_KINDS, accountStatus, appendCappedHistory, canonicalizeHome, createLane, decideAdmission, defaultLivenessDeps, defaultSystemdUserDir, defaultThrottleDeps, installReconcileTimer, providerPathEnv, probeDrainHookRegistration, isLanesError, isLaneVersionConflict, isTerminalLaneState, isFinishedResumableHireChild, childrenHoldingHireGrantCarve, laneHoldsHireGrantCarve, assignedChildHireGrants, hireGrantCapacityError, planHireGrantBackfill, terminalChildResumeHireGrantError, withHireGrantAdmission, laneStoryLedgerEnabled, syncLaneToProjectStoryLedger, removeLaneFromProjectStoryLedger, validateProjectStories, buildLaneStatusSnapshot, formatLaneStatusHuman, expandLocalLaneIdPrefix, listLaneIds, listQuestionIds, loadLanesConfig, laneRunsHere, MAX_LANE_DEPTH, MONITOR_ERROR_EXIT_CODE, readLiveMonitorEvidence, resolveMachineIdentity as lookupMachineIdentity, loadLivenessIndex, parseLaneProvider, isLaneProvider, parseLaneRecord, parseLaneState, parseQuestionRecord, parseSeniorKind, readAllLanes, readLane, sanitizeHostComponent, retryUpdateLane, syncLaneRecordFromOwner, readQuestion, resolveAccount, resolveLaneWorker, ignoredWorkerProfileFields, loadWorkerProfile, loadWorkerProfileWithRegistry, registryWorkerRecord, runReconcilePass, DEAD_HOST_MS, sessionLiveness, tryReadLane, uninstallReconcileTimer, updateLane, updateQuestion, dropboxDir, laneDeliveryMechanism, hasActiveAdmissionReservation, hasActivePendingResume, hasRecordedSessionId, hasEarlierEligibleQueuedLane, laneAdmissionCapReason, providerSupportsSessionResume, providerSessionPortable, isFailedQuotaKeepAliveLane, sessionNotPortableMessage, withLaneAdmissionLock, withAllLaneAdmissionLocks, readWorkerRunResult, resumeCommandRefusal, withLaneHireLocks, raiseLaneQuestion, sha256Text, } from "../lib/lanes/index.js";
|
|
82
82
|
import { admitQueuedNow } from "../lib/lanes/reconcile.js";
|
|
83
83
|
import { laneStories, storyWarning, } from "../lib/lanes/stories.js";
|
|
84
84
|
import { resolveLaneKeepAlive } from "../lib/lanes/profile.js";
|
|
@@ -1979,6 +1979,11 @@ async function resolveCommandLaneTarget(ctx, laneId, options = {}) {
|
|
|
1979
1979
|
if (localMatch)
|
|
1980
1980
|
return { lane: readLane(ctx.hqRoot(), localMatch), remoteOnly: false };
|
|
1981
1981
|
}
|
|
1982
|
+
// A short prefix expands only to exactly one local lane; more than one is
|
|
1983
|
+
// refused with the candidates listed (lane_id_ambiguous).
|
|
1984
|
+
const prefixMatch = expandLocalLaneIdPrefix(ctx.hqRoot(), laneId);
|
|
1985
|
+
if (prefixMatch)
|
|
1986
|
+
return { lane: readLane(ctx.hqRoot(), prefixMatch), remoteOnly: false };
|
|
1982
1987
|
const separator = laneId.lastIndexOf("_");
|
|
1983
1988
|
const hostSuffix = separator < 0 ? "" : laneId.slice(separator + 1);
|
|
1984
1989
|
const ownerLookupGate = !hostSuffix && requestedUlid !== undefined && options.allowHostlessOwnerLookup
|
|
@@ -4561,7 +4566,8 @@ async function runCreate(ctx, opts) {
|
|
|
4561
4566
|
printJson({
|
|
4562
4567
|
ok: true,
|
|
4563
4568
|
action: "create",
|
|
4564
|
-
admitted
|
|
4569
|
+
// lane_id stays ahead of `admitted`: callers that take the first lane id
|
|
4570
|
+
// in this output must get the lane they created, not one it admitted.
|
|
4565
4571
|
lane_id: started.lane_id,
|
|
4566
4572
|
state: started.state,
|
|
4567
4573
|
queued_reason: started.queued_reason ?? null,
|
|
@@ -4603,6 +4609,7 @@ async function runCreate(ctx, opts) {
|
|
|
4603
4609
|
.outpost_command_id,
|
|
4604
4610
|
}
|
|
4605
4611
|
: {}),
|
|
4612
|
+
admitted,
|
|
4606
4613
|
lane: started,
|
|
4607
4614
|
});
|
|
4608
4615
|
return started;
|
|
@@ -8942,10 +8949,10 @@ async function runCreateSubagent(ctx, opts) {
|
|
|
8942
8949
|
}
|
|
8943
8950
|
assertNoSecretText("focus override reason", opts.focusOverride.trim());
|
|
8944
8951
|
}
|
|
8952
|
+
// A subagent lane is a board record for work the senior session runs with
|
|
8953
|
+
// its own host's subagent tool (Claude's Agent, Codex's spawn_agent). No
|
|
8954
|
+
// process is spawned, so any provider is only attribution.
|
|
8945
8955
|
const provider = opts.provider ? parseLaneProvider(opts.provider) : "claude";
|
|
8946
|
-
if (provider !== "claude") {
|
|
8947
|
-
throw new LanesError("subagent_runner_requires_claude", "--runner subagent records a Claude subagent; use --provider claude or omit it", { provider });
|
|
8948
|
-
}
|
|
8949
8956
|
if (ctx.env.HQ_LANE_ID?.trim()) {
|
|
8950
8957
|
throw new LanesError("subagent_runner_requires_session", "A subagent lane belongs to the session that runs the subagent; a lane worker cannot register one", { caller_lane_id: ctx.env.HQ_LANE_ID.trim() });
|
|
8951
8958
|
}
|
|
@@ -10277,7 +10284,7 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
10277
10284
|
.option("--fresh-thread", "Skip worker continuity and start a cold provider session")
|
|
10278
10285
|
.option("--loop", "Keep one worker lane registered to process queued phase envelopes")
|
|
10279
10286
|
.option("--provider <name>", "claude | codex | grok")
|
|
10280
|
-
.option("--runner <kind>", "process (default) | subagent: register a lane an in-session Claude
|
|
10287
|
+
.option("--runner <kind>", "process (default) | subagent: register a lane an in-session subagent runs (Claude Agent, Codex spawn_agent); skips admission and starts no process")
|
|
10281
10288
|
.option("--remote-control", "Open a Claude lane in Remote Control using account a for login and the selected account for inference")
|
|
10282
10289
|
.option("--title <text>", "Human-readable Claude Remote Control session title; required with --remote-control")
|
|
10283
10290
|
.option("--account <id>", "Provider account id, or 'auto' to pick the account with the most room (all accounts are one pool across companies)")
|
|
@@ -30,6 +30,23 @@ export interface ActiveMember {
|
|
|
30
30
|
personName?: string;
|
|
31
31
|
personSlug?: string;
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* The company's active Outpost, as listed beside the members by
|
|
35
|
+
* `GET /membership/company/{companyUid}`. It is read-only: it can be granted
|
|
36
|
+
* secrets (`hq secrets share --with out_…`, `hq groups add`), but it cannot be
|
|
37
|
+
* invited, promoted or removed.
|
|
38
|
+
*/
|
|
39
|
+
export interface CompanyOutpostMember {
|
|
40
|
+
uid: string;
|
|
41
|
+
kind: "outpost";
|
|
42
|
+
label: string;
|
|
43
|
+
outpostId: string;
|
|
44
|
+
readOnly: true;
|
|
45
|
+
}
|
|
46
|
+
export interface CompanyRoster {
|
|
47
|
+
members: ActiveMember[];
|
|
48
|
+
outposts: CompanyOutpostMember[];
|
|
49
|
+
}
|
|
33
50
|
export interface InviteOptions {
|
|
34
51
|
target: string;
|
|
35
52
|
role: string;
|
|
@@ -165,7 +182,25 @@ export declare function pendingInviteJson(inv: PendingInvite): {
|
|
|
165
182
|
invited_at: string;
|
|
166
183
|
membership_key: string;
|
|
167
184
|
};
|
|
185
|
+
/** JSON row for a company Outpost in `hq members list --json`. */
|
|
186
|
+
export declare function companyOutpostJson(o: CompanyOutpostMember): {
|
|
187
|
+
person_id: string;
|
|
188
|
+
kind: "outpost";
|
|
189
|
+
label: string;
|
|
190
|
+
outpost_id: string;
|
|
191
|
+
read_only: true;
|
|
192
|
+
};
|
|
168
193
|
export declare function listActiveMembers(token: string, companyUid: string): Promise<ActiveMember[]>;
|
|
194
|
+
/**
|
|
195
|
+
* Active members plus the company's active Outpost. Servers that predate
|
|
196
|
+
* Outpost listing omit `outposts`; that reads as none.
|
|
197
|
+
*/
|
|
198
|
+
export declare function listCompanyRoster(token: string, companyUid: string): Promise<CompanyRoster>;
|
|
199
|
+
/**
|
|
200
|
+
* The company Outpost is listed read-only. hq-pro rejects promote, remove and
|
|
201
|
+
* revoke for it (OUTPOST_MEMBER_READ_ONLY); say so before any request goes out.
|
|
202
|
+
*/
|
|
203
|
+
export declare const OUTPOST_READ_ONLY_MESSAGE: string;
|
|
169
204
|
/**
|
|
170
205
|
* Resolve a role-change target to an active membership key.
|
|
171
206
|
*
|
package/dist/commands/members.js
CHANGED
|
@@ -4,6 +4,7 @@ import { gateApiKeyCapabilities } from "../utils/api-key-command-gate.js";
|
|
|
4
4
|
import { vaultApiFetch, getCompanyUid } from "../utils/vault-api.js";
|
|
5
5
|
import { isPlanGateError } from "../utils/plan-gate-error.js";
|
|
6
6
|
import { queueCliFeatureUsed } from "../utils/cli-telemetry.js";
|
|
7
|
+
import { OUTPOST_UID_PATTERN } from "./_patterns.js";
|
|
7
8
|
const EMAIL_PATTERN = /^[^\s]+@[^\s]+$/;
|
|
8
9
|
const PERSON_UID_PATTERN = /^prs_[A-Za-z0-9_-]+$/;
|
|
9
10
|
const AGENT_UID_PATTERN = /^agt_[A-Za-z0-9_-]+$/i;
|
|
@@ -261,7 +262,24 @@ export function pendingInviteJson(inv) {
|
|
|
261
262
|
membership_key: inv.membershipKey,
|
|
262
263
|
};
|
|
263
264
|
}
|
|
265
|
+
/** JSON row for a company Outpost in `hq members list --json`. */
|
|
266
|
+
export function companyOutpostJson(o) {
|
|
267
|
+
return {
|
|
268
|
+
person_id: o.uid,
|
|
269
|
+
kind: "outpost",
|
|
270
|
+
label: o.label || "Outpost",
|
|
271
|
+
outpost_id: o.outpostId,
|
|
272
|
+
read_only: true,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
264
275
|
export async function listActiveMembers(token, companyUid) {
|
|
276
|
+
return (await listCompanyRoster(token, companyUid)).members;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Active members plus the company's active Outpost. Servers that predate
|
|
280
|
+
* Outpost listing omit `outposts`; that reads as none.
|
|
281
|
+
*/
|
|
282
|
+
export async function listCompanyRoster(token, companyUid) {
|
|
265
283
|
const res = await vaultApiFetch({
|
|
266
284
|
token,
|
|
267
285
|
path: `/membership/company/${encodeURIComponent(companyUid)}`,
|
|
@@ -270,10 +288,26 @@ export async function listActiveMembers(token, companyUid) {
|
|
|
270
288
|
const err = (await res.json().catch(() => ({})));
|
|
271
289
|
throw new InviteHttpError(res.status, err.message ?? err.error ?? res.statusText, err.code);
|
|
272
290
|
}
|
|
273
|
-
// Server schema: `{ members: [...] }` — active members
|
|
274
|
-
// resolved person metadata (personEmail / personName /
|
|
291
|
+
// Server schema: `{ members: [...], outposts: [...] }` — active members
|
|
292
|
+
// only, enriched with resolved person metadata (personEmail / personName /
|
|
293
|
+
// personSlug), plus the read-only company Outpost rows.
|
|
275
294
|
const data = (await res.json());
|
|
276
|
-
|
|
295
|
+
const outposts = (data?.outposts ?? []).filter((o) => o?.kind === "outpost" &&
|
|
296
|
+
typeof o.uid === "string" &&
|
|
297
|
+
OUTPOST_UID_PATTERN.test(o.uid));
|
|
298
|
+
return { members: data?.members ?? [], outposts };
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* The company Outpost is listed read-only. hq-pro rejects promote, remove and
|
|
302
|
+
* revoke for it (OUTPOST_MEMBER_READ_ONLY); say so before any request goes out.
|
|
303
|
+
*/
|
|
304
|
+
export const OUTPOST_READ_ONLY_MESSAGE = "An Outpost is shown read-only and cannot be invited, promoted or removed. " +
|
|
305
|
+
"To change what it can read, use `hq secrets share` / `hq secrets unshare` or `hq groups`.";
|
|
306
|
+
function assertNotOutpost(target) {
|
|
307
|
+
const uid = (target.split("#")[0] ?? "").trim();
|
|
308
|
+
if (OUTPOST_UID_PATTERN.test(uid)) {
|
|
309
|
+
throw new Error(OUTPOST_READ_ONLY_MESSAGE);
|
|
310
|
+
}
|
|
277
311
|
}
|
|
278
312
|
/**
|
|
279
313
|
* Resolve a role-change target to an active membership key.
|
|
@@ -283,6 +317,7 @@ export async function listActiveMembers(token, companyUid) {
|
|
|
283
317
|
* active member list instead of synthesizing an `email:...#company` key.
|
|
284
318
|
*/
|
|
285
319
|
export async function resolveRoleChangeTarget(token, companyUid, target) {
|
|
320
|
+
assertNotOutpost(target);
|
|
286
321
|
if (target.includes("#"))
|
|
287
322
|
return target;
|
|
288
323
|
const detected = detectTarget(target);
|
|
@@ -325,6 +360,7 @@ export async function resolveRoleChangeTarget(token, companyUid, target) {
|
|
|
325
360
|
* that exact email was just shown by `hq members list`.
|
|
326
361
|
*/
|
|
327
362
|
export function resolveRevokeTargetToMembershipKey(arg, companyUid) {
|
|
363
|
+
assertNotOutpost(arg);
|
|
328
364
|
if (arg.includes("#"))
|
|
329
365
|
return arg;
|
|
330
366
|
const detected = detectTarget(arg);
|
|
@@ -820,33 +856,48 @@ export function registerMembersCommand(program) {
|
|
|
820
856
|
}
|
|
821
857
|
// Default: list ACTIVE members so their emails drop straight into
|
|
822
858
|
// `hq secrets share <path> --with <email>`.
|
|
823
|
-
|
|
859
|
+
// The company Outpost is listed after the people, read-only; its
|
|
860
|
+
// out_ id drops into `hq secrets share <path> --with out_…`.
|
|
861
|
+
const { members: activeMembers, outposts } = await listCompanyRoster(token, companyUid);
|
|
824
862
|
if (opts.json) {
|
|
825
863
|
console.log(JSON.stringify({
|
|
826
864
|
company_uid: companyUid,
|
|
827
865
|
members: activeMembers.map(activeMemberJson),
|
|
866
|
+
outposts: outposts.map(companyOutpostJson),
|
|
828
867
|
}, null, 2));
|
|
829
868
|
return;
|
|
830
869
|
}
|
|
831
|
-
if (activeMembers.length === 0) {
|
|
870
|
+
if (activeMembers.length === 0 && outposts.length === 0) {
|
|
832
871
|
console.log(chalk.gray("No active members found for this company."));
|
|
833
872
|
return;
|
|
834
873
|
}
|
|
835
|
-
const
|
|
836
|
-
|
|
837
|
-
|
|
874
|
+
const rows = [
|
|
875
|
+
...activeMembers.map((m) => ({
|
|
876
|
+
email: m.personEmail ?? m.personUid,
|
|
877
|
+
role: m.role,
|
|
878
|
+
id: m.personUid,
|
|
879
|
+
name: m.personName ?? m.personSlug ?? "",
|
|
880
|
+
})),
|
|
881
|
+
...outposts.map((o) => ({
|
|
882
|
+
email: o.uid,
|
|
883
|
+
role: "outpost",
|
|
884
|
+
id: o.uid,
|
|
885
|
+
name: `${o.label || "Outpost"} (read-only)`,
|
|
886
|
+
})),
|
|
887
|
+
];
|
|
888
|
+
const emailW = Math.max(5, ...rows.map((r) => r.email.length));
|
|
889
|
+
const roleW = Math.max(4, ...rows.map((r) => r.role.length));
|
|
890
|
+
const idW = Math.max(9, ...rows.map((r) => r.id.length));
|
|
838
891
|
const header = ["EMAIL".padEnd(emailW), "ROLE".padEnd(roleW)];
|
|
839
892
|
if (opts.ids)
|
|
840
893
|
header.push("PERSON_ID".padEnd(idW));
|
|
841
894
|
header.push("NAME");
|
|
842
895
|
console.log(chalk.bold(header.join(" ")));
|
|
843
|
-
for (const
|
|
844
|
-
const
|
|
845
|
-
const name = m.personName ?? m.personSlug ?? "";
|
|
846
|
-
const row = [email.padEnd(emailW), m.role.padEnd(roleW)];
|
|
896
|
+
for (const r of rows) {
|
|
897
|
+
const row = [r.email.padEnd(emailW), r.role.padEnd(roleW)];
|
|
847
898
|
if (opts.ids)
|
|
848
|
-
row.push(
|
|
849
|
-
row.push(name);
|
|
899
|
+
row.push(r.id.padEnd(idW));
|
|
900
|
+
row.push(r.name);
|
|
850
901
|
console.log(row.join(" "));
|
|
851
902
|
}
|
|
852
903
|
}
|
|
@@ -13,6 +13,11 @@ export { vaultApiFetch, getCompanyUid, getEntityUid };
|
|
|
13
13
|
*/
|
|
14
14
|
export declare const HQ_PRO_RESOURCE_SURFACES: readonly ["api", "bot_invite", "bot_join_now", "calendar_auto_schedule", "calendar_preferences", "cli_secrets_set", "console_secrets_form", "deploy_precheck", "external_connection", "factory_install", "integrations_connect", "oauth_callback", "plan_limit_check", "secrets_input_link", "web"];
|
|
15
15
|
export declare const CLI_SECRETS_SET_SURFACE = "cli_secrets_set";
|
|
16
|
+
interface SecretAclPrincipal {
|
|
17
|
+
granteeType: "email" | "group" | "company-wide" | "person";
|
|
18
|
+
granteeId: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function parseSecretAclPrincipal(principal: string): SecretAclPrincipal | null;
|
|
16
21
|
export interface SecretInjectionRecipe {
|
|
17
22
|
header: string;
|
|
18
23
|
scheme: "raw" | "bearer";
|
package/dist/commands/secrets.js
CHANGED
|
@@ -60,7 +60,7 @@ import { fastFlagEnabledDefaultOn } from "../native-fast-route.js";
|
|
|
60
60
|
import { MULTI_COMPANY_SESSION_LOCK_FLAG } from "../lib/cli-flag-keys.js";
|
|
61
61
|
import { secretsExecChildExitCode } from "../utils/child-wrapper-exit.js";
|
|
62
62
|
import { isPlanGateError } from "../utils/plan-gate-error.js";
|
|
63
|
-
import { SECRET_NAME_PATTERN, GROUP_ID_PATTERN, EMAIL_PATTERN } from "./_patterns.js";
|
|
63
|
+
import { SECRET_NAME_PATTERN, GROUP_ID_PATTERN, EMAIL_PATTERN, OUTPOST_UID_PATTERN, } from "./_patterns.js";
|
|
64
64
|
import { describeSecretsScope, formatSecretSaved, formatSecretsListEmpty, formatSecretsListHeader, } from "./secrets-scope.js";
|
|
65
65
|
import { vaultApiFetch, getCompanyUid, getEntityUid, looksLikeCompanyUid, } from "../utils/vault-api.js";
|
|
66
66
|
import { formatApiKeyCapabilityDenial, requireApiKeyCapability, resolveVaultCredentialForCapability, } from "../utils/resolve-vault-credential.js";
|
|
@@ -277,7 +277,7 @@ function promptSecretInteractively() {
|
|
|
277
277
|
process.stdin.resume();
|
|
278
278
|
});
|
|
279
279
|
}
|
|
280
|
-
function parseSecretAclPrincipal(principal) {
|
|
280
|
+
export function parseSecretAclPrincipal(principal) {
|
|
281
281
|
const p = principal.trim();
|
|
282
282
|
if (p === "@all") {
|
|
283
283
|
// Company-wide grant: every active member of the company. The server
|
|
@@ -290,7 +290,12 @@ function parseSecretAclPrincipal(principal) {
|
|
|
290
290
|
if (GROUP_ID_PATTERN.test(p)) {
|
|
291
291
|
return { granteeType: "group", granteeId: p };
|
|
292
292
|
}
|
|
293
|
-
|
|
293
|
+
if (OUTPOST_UID_PATTERN.test(p)) {
|
|
294
|
+
// The company Outpost rides the person grantee slot; the server checks
|
|
295
|
+
// that this id is the company's active Outpost.
|
|
296
|
+
return { granteeType: "person", granteeId: p };
|
|
297
|
+
}
|
|
298
|
+
console.error(chalk.red(`Invalid principal '${principal}': must be '@all', an email address, a group id matching grp_<alphanumeric, underscore, hyphen>, or a company Outpost id (out_…)`));
|
|
294
299
|
return null;
|
|
295
300
|
}
|
|
296
301
|
function describeSecretAclPrincipal(principal) {
|
|
@@ -1312,8 +1317,8 @@ export function registerSecretsCommand(program) {
|
|
|
1312
1317
|
});
|
|
1313
1318
|
secrets
|
|
1314
1319
|
.command("share <path>")
|
|
1315
|
-
.description("Share a secret with an email address, group, or the entire company (@all)")
|
|
1316
|
-
.requiredOption("--with <principal>", "Email address, group id, or @all (entire company) to share with")
|
|
1320
|
+
.description("Share a secret with an email address, group, company Outpost (out_…), or the entire company (@all)")
|
|
1321
|
+
.requiredOption("--with <principal>", "Email address, group id, out_ Outpost id, or @all (entire company) to share with")
|
|
1317
1322
|
.requiredOption("--permission <level>", "Permission level: read | write | admin")
|
|
1318
1323
|
.action(async (path, opts) => {
|
|
1319
1324
|
try {
|
|
@@ -1376,7 +1381,7 @@ export function registerSecretsCommand(program) {
|
|
|
1376
1381
|
secrets
|
|
1377
1382
|
.command("unshare <path>")
|
|
1378
1383
|
.description("Remove a grant from a secret")
|
|
1379
|
-
.requiredOption("--from <principal>", "Email address, group id, or @all (entire company) to remove")
|
|
1384
|
+
.requiredOption("--from <principal>", "Email address, group id, out_ Outpost id, or @all (entire company) to remove")
|
|
1380
1385
|
.action(async (path, opts) => {
|
|
1381
1386
|
try {
|
|
1382
1387
|
rejectIfPersonal(secrets.opts(), "unshare");
|