@indigoai-us/hq-cli 5.345.149 → 5.345.151
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 +46 -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/cloud-off-gate.d.ts +138 -0
- package/dist/commands/cloud-off-gate.js +234 -0
- package/dist/commands/cloud.d.ts +7 -0
- package/dist/commands/cloud.js +59 -17
- package/dist/commands/core-handoff.js +6 -1
- 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/meetings.js +7 -3
- 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/daemon/catalog.d.ts +7 -0
- package/dist/lib/daemon/catalog.js +10 -1
- package/dist/lib/daemon/run.js +13 -0
- 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/links.js +27 -11
- 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/dist/lib/vault-sync-switches.d.ts +41 -0
- package/dist/lib/vault-sync-switches.js +86 -0
- package/package.json +8 -8
package/dist/commands/cloud.js
CHANGED
|
@@ -28,6 +28,7 @@ import { maybeShowCliInviteNudge } from "../lib/cli-invite-nudge.js";
|
|
|
28
28
|
import { createPlanLimitRefusalTracker, describeRefusedCount, describeRefusedFiles, mergePlanLimitRefusals, renderPlanLimitRefusalLines, trackingProgressRecorder, } from "../lib/billing/plan-limit-refusals.js";
|
|
29
29
|
import { formatPlanGateError, planGateErrorFromVaultError, } from "../utils/plan-gate-error.js";
|
|
30
30
|
import { personalTargetedPushPathsEnabled } from "./personal-targeted-push-flag.js";
|
|
31
|
+
import { CLOUD_OFF_EXIT_CODE, VaultSyncOffError, assertEntityVaultSyncAllowed, refuseSyncForOffVaults, writeVaultRefusal, } from "./cloud-off-gate.js";
|
|
31
32
|
import { resolveExplicitPersonalPushPaths } from "./personal-targeted-push-paths.js";
|
|
32
33
|
/**
|
|
33
34
|
* Terminal-failure signal for the sync fanout runners. The runners
|
|
@@ -295,6 +296,12 @@ function narrowNudgeExceedsSize(hqRoot, ref) {
|
|
|
295
296
|
const threshold = resolveNarrowHintMinBytes({ hqRoot });
|
|
296
297
|
return companyFolderExceedsThreshold(companyDir, threshold);
|
|
297
298
|
}
|
|
299
|
+
/** True when a membership's vault is switched off on this computer (US-016). */
|
|
300
|
+
function isSkippedCompany(skip, slug, companyUid) {
|
|
301
|
+
if (!skip || skip.size === 0)
|
|
302
|
+
return false;
|
|
303
|
+
return skip.has(slug.toLowerCase()) || skip.has(companyUid.toLowerCase());
|
|
304
|
+
}
|
|
298
305
|
export async function pullAll(options, deps) {
|
|
299
306
|
const memberships = await deps.vaultClient.listMyMemberships();
|
|
300
307
|
const persons = await deps.vaultClient.listPersonEntities();
|
|
@@ -311,6 +318,8 @@ export async function pullAll(options, deps) {
|
|
|
311
318
|
catch {
|
|
312
319
|
// Best-effort — keep UID as the row label rather than aborting the run.
|
|
313
320
|
}
|
|
321
|
+
if (isSkippedCompany(options.skipCompanies, slug, m.companyUid))
|
|
322
|
+
continue;
|
|
314
323
|
plan.push({
|
|
315
324
|
slug,
|
|
316
325
|
companyUid: m.companyUid,
|
|
@@ -464,6 +473,8 @@ export async function pushAll(options, deps) {
|
|
|
464
473
|
catch {
|
|
465
474
|
// Best-effort — keep UID as the row label.
|
|
466
475
|
}
|
|
476
|
+
if (isSkippedCompany(options.skipCompanies, slug, m.companyUid))
|
|
477
|
+
continue;
|
|
467
478
|
plan.push({
|
|
468
479
|
slug,
|
|
469
480
|
companyUid: m.companyUid,
|
|
@@ -804,6 +815,13 @@ export function registerCloudCommands(program) {
|
|
|
804
815
|
"a toggle, and so CLI users can opt out one-off. Ignored outside " +
|
|
805
816
|
"`--all`.")
|
|
806
817
|
.action(async (paths, options) => {
|
|
818
|
+
const skips = refuseSyncForOffVaults("push", {
|
|
819
|
+
all: options.all,
|
|
820
|
+
personal: options.personal,
|
|
821
|
+
company: options.company,
|
|
822
|
+
hqRoot: options.hqRoot,
|
|
823
|
+
credsFromStdin: options.credsFromStdin,
|
|
824
|
+
}, { json: options.json === true });
|
|
807
825
|
try {
|
|
808
826
|
configureSyncLockTimeout(options.lockTimeout);
|
|
809
827
|
assertSingleSelector(options, "push");
|
|
@@ -833,7 +851,7 @@ export function registerCloudCommands(program) {
|
|
|
833
851
|
// `--all` the flag has no effect (logged above as part of the
|
|
834
852
|
// option's help text).
|
|
835
853
|
await withSyncHealthReport(async () => {
|
|
836
|
-
const refused = await runPushAll(options.hqRoot, options.message, options.onConflict,
|
|
854
|
+
const refused = await runPushAll(options.hqRoot, options.message, options.onConflict, skips.skipPersonal, skips.skipCompanies);
|
|
837
855
|
// A plan-limit refusal is not a sync failure, but the push did
|
|
838
856
|
// not upload everything it was asked to: exit non-zero.
|
|
839
857
|
if (refused)
|
|
@@ -891,6 +909,9 @@ export function registerCloudCommands(program) {
|
|
|
891
909
|
catch (e) {
|
|
892
910
|
throw new Error(`--creds-from-stdin: failed to parse stdin as JSON: ${e instanceof Error ? e.message : String(e)}`);
|
|
893
911
|
}
|
|
912
|
+
// Sync switches (US-016): with no --company the target is only
|
|
913
|
+
// known now. Check it before any login or transfer.
|
|
914
|
+
assertEntityVaultSyncAllowed("push", entityContext, options.hqRoot);
|
|
894
915
|
// Upload creds are pre-vended in entityContext, but prefix filtering
|
|
895
916
|
// still needs vault-service membership/grant resolution.
|
|
896
917
|
const accessToken = await ensureSyncCognitoToken();
|
|
@@ -1085,6 +1106,11 @@ export function registerCloudCommands(program) {
|
|
|
1085
1106
|
}
|
|
1086
1107
|
}
|
|
1087
1108
|
catch (err) {
|
|
1109
|
+
if (err instanceof VaultSyncOffError) {
|
|
1110
|
+
// Not a sync failure: the target vault is switched off.
|
|
1111
|
+
writeVaultRefusal(err.refusal, { json: jsonMode });
|
|
1112
|
+
process.exit(CLOUD_OFF_EXIT_CODE);
|
|
1113
|
+
}
|
|
1088
1114
|
if (syncHealth)
|
|
1089
1115
|
await syncHealth.failed();
|
|
1090
1116
|
const message = formatSyncFailure(err);
|
|
@@ -1128,6 +1154,12 @@ export function registerCloudCommands(program) {
|
|
|
1128
1154
|
"disk and only un-tracked from sync; clean out-of-scope files are " +
|
|
1129
1155
|
"quarantined under .hq/scope-quarantine/ (recoverable).")
|
|
1130
1156
|
.action(async (options) => {
|
|
1157
|
+
const skips = refuseSyncForOffVaults("pull", {
|
|
1158
|
+
all: options.all,
|
|
1159
|
+
personal: options.personal,
|
|
1160
|
+
company: options.company,
|
|
1161
|
+
hqRoot: options.hqRoot,
|
|
1162
|
+
});
|
|
1131
1163
|
try {
|
|
1132
1164
|
configureSyncLockTimeout(options.lockTimeout);
|
|
1133
1165
|
assertSingleSelector(options, "pull");
|
|
@@ -1140,7 +1172,7 @@ export function registerCloudCommands(program) {
|
|
|
1140
1172
|
// `options.personal === false` is Commander's auto-negation of
|
|
1141
1173
|
// `--personal`; in `--all` mode that means "drop the personal
|
|
1142
1174
|
// leg from the fanout" (see `--no-personal` option above).
|
|
1143
|
-
await withSyncHealthReport(() => runPullAll(options.hqRoot, options.onConflict, options.modeAll === true,
|
|
1175
|
+
await withSyncHealthReport(() => runPullAll(options.hqRoot, options.onConflict, options.modeAll === true, skips.skipPersonal, options.forceScopeShrink === true, skips.skipCompanies));
|
|
1144
1176
|
return;
|
|
1145
1177
|
}
|
|
1146
1178
|
if (options.personal) {
|
|
@@ -1352,6 +1384,12 @@ export function registerCloudCommands(program) {
|
|
|
1352
1384
|
"anyway. Dirty files are KEPT on disk (only un-tracked); clean " +
|
|
1353
1385
|
"out-of-scope files are quarantined under .hq/scope-quarantine/.")
|
|
1354
1386
|
.action(async (options) => {
|
|
1387
|
+
const skips = refuseSyncForOffVaults("now", {
|
|
1388
|
+
all: options.all,
|
|
1389
|
+
personal: options.personal,
|
|
1390
|
+
company: options.company,
|
|
1391
|
+
hqRoot: options.hqRoot,
|
|
1392
|
+
});
|
|
1355
1393
|
try {
|
|
1356
1394
|
configureSyncLockTimeout(options.lockTimeout);
|
|
1357
1395
|
assertSingleSelector(options, "now");
|
|
@@ -1360,7 +1398,7 @@ export function registerCloudCommands(program) {
|
|
|
1360
1398
|
// of `--personal`; in `--all` mode that means "drop the
|
|
1361
1399
|
// personal leg from both legs of the bidirectional fanout"
|
|
1362
1400
|
// (see `--no-personal` option above).
|
|
1363
|
-
await withSyncHealthReport(() => runNowAll(options.hqRoot, options.message, options.onConflict, options.modeAll === true,
|
|
1401
|
+
await withSyncHealthReport(() => runNowAll(options.hqRoot, options.message, options.onConflict, options.modeAll === true, skips.skipPersonal, options.forceScopeShrink === true, skips.skipCompanies));
|
|
1364
1402
|
return;
|
|
1365
1403
|
}
|
|
1366
1404
|
await withSyncHealthReport(() => runNowSingle(options.hqRoot, options.company, options.personal === true, options.message, options.onConflict, options.modeAll === true, options.forceScopeShrink === true));
|
|
@@ -1446,13 +1484,19 @@ export async function runSyncDoctorReconcileConflicts(hqRoot, yes) {
|
|
|
1446
1484
|
console.log(chalk.dim(" Re-run with --yes to apply the plan above."));
|
|
1447
1485
|
}
|
|
1448
1486
|
}
|
|
1449
|
-
|
|
1487
|
+
/** Banner lines for the vaults an `--all` run leaves out. */
|
|
1488
|
+
function logVaultSkips(skipPersonal, skipCompanies) {
|
|
1489
|
+
if (skipPersonal)
|
|
1490
|
+
console.log(` Personal: skipped (sync is off for it, or --no-personal)`);
|
|
1491
|
+
if (skipCompanies && skipCompanies.size > 0) {
|
|
1492
|
+
console.log(` Skipped: ${[...skipCompanies].sort().join(", ")} (sync is off on this computer)`);
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
async function runPullAll(hqRoot, onConflict, modeAllOverride, skipPersonal, forceScopeShrink, skipCompanies) {
|
|
1450
1496
|
console.log(chalk.bold("\nHQ Sync — Pull (all)"));
|
|
1451
1497
|
console.log(` HQ root: ${hqRoot}`);
|
|
1452
1498
|
console.log(` Strategy: ${onConflict ?? "(interactive)"}`);
|
|
1453
|
-
|
|
1454
|
-
console.log(` Personal: skipped (--no-personal)`);
|
|
1455
|
-
}
|
|
1499
|
+
logVaultSkips(skipPersonal, skipCompanies);
|
|
1456
1500
|
console.log("");
|
|
1457
1501
|
let result;
|
|
1458
1502
|
try {
|
|
@@ -1480,6 +1524,7 @@ async function runPullAll(hqRoot, onConflict, modeAllOverride, skipPersonal, for
|
|
|
1480
1524
|
...(modeAllOverride ? { modeAllOverride: true } : {}),
|
|
1481
1525
|
...(skipPersonal ? { skipPersonal: true } : {}),
|
|
1482
1526
|
...(forceScopeShrink ? { forceScopeShrink: true } : {}),
|
|
1527
|
+
...(skipCompanies && skipCompanies.size > 0 ? { skipCompanies } : {}),
|
|
1483
1528
|
}, {
|
|
1484
1529
|
vaultClient: adapter,
|
|
1485
1530
|
// DEV-1768: resolve each company's REAL pull scope (mode + prefixSet)
|
|
@@ -1587,13 +1632,11 @@ export async function runPullPersonal(hqRoot, onConflict) {
|
|
|
1587
1632
|
* limit refused across every target (null when none were), so the caller can
|
|
1588
1633
|
* refuse to report success; hard failures still throw {@link SyncExitError}.
|
|
1589
1634
|
*/
|
|
1590
|
-
async function runPushAll(hqRoot, message, onConflict, skipPersonal) {
|
|
1635
|
+
async function runPushAll(hqRoot, message, onConflict, skipPersonal, skipCompanies) {
|
|
1591
1636
|
console.log(chalk.bold("\nHQ Sync — Push (all)"));
|
|
1592
1637
|
console.log(` HQ root: ${hqRoot}`);
|
|
1593
1638
|
console.log(` Strategy: ${onConflict ?? "(interactive)"}`);
|
|
1594
|
-
|
|
1595
|
-
console.log(` Personal: skipped (--no-personal)`);
|
|
1596
|
-
}
|
|
1639
|
+
logVaultSkips(skipPersonal, skipCompanies);
|
|
1597
1640
|
console.log("");
|
|
1598
1641
|
let result;
|
|
1599
1642
|
try {
|
|
@@ -1618,6 +1661,7 @@ async function runPushAll(hqRoot, message, onConflict, skipPersonal) {
|
|
|
1618
1661
|
...(onConflict ? { onConflict } : {}),
|
|
1619
1662
|
...(message ? { message } : {}),
|
|
1620
1663
|
...(skipPersonal ? { skipPersonal: true } : {}),
|
|
1664
|
+
...(skipCompanies && skipCompanies.size > 0 ? { skipCompanies } : {}),
|
|
1621
1665
|
}, {
|
|
1622
1666
|
vaultClient: adapter,
|
|
1623
1667
|
resolveScope: (companyUid, slug) => resolvePullScope(realClient, companyUid, slug, hqRoot),
|
|
@@ -1998,24 +2042,22 @@ async function runNowSingle(hqRoot, company, personal, message, onConflict, mode
|
|
|
1998
2042
|
throw new SyncExitError(1);
|
|
1999
2043
|
}
|
|
2000
2044
|
}
|
|
2001
|
-
async function runNowAll(hqRoot, message, onConflict, modeAllOverride, skipPersonal, forceScopeShrink) {
|
|
2045
|
+
async function runNowAll(hqRoot, message, onConflict, modeAllOverride, skipPersonal, forceScopeShrink, skipCompanies) {
|
|
2002
2046
|
console.log(chalk.bold("\nHQ Sync — Now (all)"));
|
|
2003
2047
|
console.log(` HQ root: ${hqRoot}`);
|
|
2004
2048
|
console.log(` Strategy: ${onConflict ?? "(interactive)"}`);
|
|
2005
|
-
|
|
2006
|
-
console.log(` Personal: skipped (--no-personal)`);
|
|
2007
|
-
}
|
|
2049
|
+
logVaultSkips(skipPersonal, skipCompanies);
|
|
2008
2050
|
console.log("");
|
|
2009
2051
|
// Push first (matches runner), then pull. Re-uses the per-leg orchestrators
|
|
2010
2052
|
// so the per-target rendering, error isolation, and exit codes are
|
|
2011
2053
|
// identical to running `push --all` then `pull --all` back-to-back.
|
|
2012
2054
|
console.log(chalk.dim("→ push --all"));
|
|
2013
|
-
const refused = await runPushAll(hqRoot, message, onConflict, skipPersonal);
|
|
2055
|
+
const refused = await runPushAll(hqRoot, message, onConflict, skipPersonal, skipCompanies);
|
|
2014
2056
|
console.log(chalk.dim("\n→ pull --all"));
|
|
2015
2057
|
// US-011: forward --mode-all so the strict refusal applies to the
|
|
2016
2058
|
// pull leg (push doesn't need a narrow-hint — the narrow ritual is
|
|
2017
2059
|
// pull-side).
|
|
2018
|
-
await runPullAll(hqRoot, onConflict, modeAllOverride, skipPersonal, forceScopeShrink);
|
|
2060
|
+
await runPullAll(hqRoot, onConflict, modeAllOverride, skipPersonal, forceScopeShrink, skipCompanies);
|
|
2019
2061
|
if (refused) {
|
|
2020
2062
|
// A refused push does not stop the pull, but the run must not end looking
|
|
2021
2063
|
// like a clean sync. Its per-target details were printed above.
|
|
@@ -13,6 +13,7 @@ import { defaultBackgroundDependencies, runBackgroundLauncher, } from "../lib/se
|
|
|
13
13
|
import { resolveLiveRoot } from "../utils/hq-roots.js";
|
|
14
14
|
import { runBundledScript } from "../utils/run-bundled-script.js";
|
|
15
15
|
import { describeCoreCommand } from "./core-command-catalog.js";
|
|
16
|
+
import { CLOUD_OFF_EXIT_CODE } from "./cloud-off-gate.js";
|
|
16
17
|
import { shellDisplayPath } from "../lib/core-utils/shell-display-path.js";
|
|
17
18
|
import { fingerprintTrailerArgs, offlineFingerprint } from "../lib/fingerprint/offline.js";
|
|
18
19
|
import { compactJson, isRecord, jqTruthy, prettyJson, runHandoffOpenSteps, shellDate, } from "./core-handoff-open-steps.js";
|
|
@@ -1057,7 +1058,11 @@ export function runHandoffPostWorker(args, context) {
|
|
|
1057
1058
|
stdout: typeof pushed.stdout === "string" ? pushed.stdout : "",
|
|
1058
1059
|
stderr: typeof pushed.stderr === "string" ? pushed.stderr : "",
|
|
1059
1060
|
});
|
|
1060
|
-
log(pushed.status === 0
|
|
1061
|
+
log(pushed.status === 0
|
|
1062
|
+
? `workspace-sync: pushed companies/${company}/workspace`
|
|
1063
|
+
: pushed.status === CLOUD_OFF_EXIT_CODE
|
|
1064
|
+
? `workspace-sync: skipped (sync is off) companies/${company}/workspace`
|
|
1065
|
+
: `workspace-sync: failed companies/${company}/workspace`);
|
|
1061
1066
|
}
|
|
1062
1067
|
}
|
|
1063
1068
|
else if (threadFile && fs.existsSync(threadFile))
|
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)")
|
|
@@ -658,9 +658,13 @@ export function registerMeetingsCommand(program) {
|
|
|
658
658
|
try {
|
|
659
659
|
const token = await ensureCognitoToken();
|
|
660
660
|
const companySlug = meetings.opts().company;
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
661
|
+
// Always name a company. With no companyId the backend files the bot
|
|
662
|
+
// under Personal, which can never record, so an entitled user would
|
|
663
|
+
// get the HQ Workforce upgrade prompt. Without --company this uses the
|
|
664
|
+
// caller's only active company, or asks them to pick one.
|
|
665
|
+
const query = {
|
|
666
|
+
companyId: await getCompanyUid(token, companySlug),
|
|
667
|
+
};
|
|
664
668
|
const res = await vaultApiFetch({
|
|
665
669
|
token,
|
|
666
670
|
method: "POST",
|
|
@@ -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";
|