@indigoai-us/hq-cli 5.310.0 → 5.311.0
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 +16 -0
- package/assets/scaffold/core/scripts/jobs-validate.sh +1 -1
- package/dist/command-catalog.generated.d.ts +2 -2
- package/dist/command-catalog.generated.js +2 -2
- package/dist/commands/channels.d.ts +1 -9
- package/dist/commands/channels.js +2 -2
- package/dist/commands/company-transfer.d.ts +4 -28
- package/dist/commands/company-transfer.js +5 -49
- package/dist/commands/core-checkpoint-stop-gate-native.d.ts +34 -0
- package/dist/commands/core-checkpoint-stop-gate-native.js +713 -0
- package/dist/commands/core-checkpoint.d.ts +0 -24
- package/dist/commands/core-checkpoint.js +4 -4
- package/dist/commands/core-command-catalog.js +1 -1
- package/dist/commands/core-hq-session-native.d.ts +39 -0
- package/dist/commands/core-hq-session-native.js +579 -0
- package/dist/commands/core-jobs-native.js +1 -4
- package/dist/commands/core-native-session-flag.d.ts +4 -0
- package/dist/commands/core-native-session-flag.js +7 -0
- package/dist/commands/core-native-worker-skills-flag.d.ts +4 -0
- package/dist/commands/core-native-worker-skills-flag.js +7 -0
- package/dist/commands/core-worker-lint-native.d.ts +4 -0
- package/dist/commands/core-worker-lint-native.js +160 -0
- package/dist/commands/core-worker-share-native.d.ts +5 -0
- package/dist/commands/core-worker-share-native.js +260 -0
- package/dist/commands/core-worker-skills-command.d.ts +8 -0
- package/dist/commands/core-worker-skills-command.js +33 -0
- package/dist/commands/core-worker-skills-runtime.d.ts +33 -0
- package/dist/commands/core-worker-skills-runtime.js +86 -0
- package/dist/commands/core.js +4 -2
- package/dist/commands/dm.d.ts +3 -7
- package/dist/commands/dm.js +3 -3
- package/dist/commands/integrations-import.d.ts +1 -1
- package/dist/commands/integrations-import.js +34 -19
- package/dist/commands/lanes-accounts.js +3 -1
- package/dist/commands/lanes-message.js +84 -10
- package/dist/commands/lanes-shared.d.ts +4 -1
- package/dist/commands/lanes.d.ts +4 -1
- package/dist/commands/lanes.js +48 -7
- package/dist/commands/personal-connector-settings-flag.d.ts +4 -0
- package/dist/commands/personal-connector-settings-flag.js +7 -0
- package/dist/commands/report.d.ts +1 -26
- package/dist/commands/report.js +6 -6
- package/dist/fast-core.js +15 -2
- package/dist/fast-scaffold.js +25 -2
- package/dist/index.js +8 -0
- package/dist/lib/bot/continuity.d.ts +2 -1
- package/dist/lib/bot/job-completions.d.ts +0 -2
- package/dist/lib/bot/job-completions.js +2 -2
- package/dist/lib/bot/local-config.d.ts +0 -10
- package/dist/lib/bot/local-config.js +3 -9
- package/dist/lib/bot/owner-context.d.ts +2 -6
- package/dist/lib/bot/owner-context.js +3 -3
- package/dist/lib/bot/runtime/windows.d.ts +0 -2
- package/dist/lib/bot/runtime/windows.js +1 -1
- package/dist/lib/bot/setup-explain.d.ts +0 -4
- package/dist/lib/bot/setup-explain.js +2 -2
- package/dist/lib/daemon/catalog.d.ts +3 -2
- package/dist/lib/daemon/hq-cloud.d.ts +2 -2
- package/dist/lib/daemon/hq-cloud.js +1 -1
- package/dist/lib/daemon/machine-type.d.ts +3 -4
- package/dist/lib/daemon/machine-type.js +2 -2
- package/dist/lib/daemon/remote-control.d.ts +0 -1
- package/dist/lib/daemon/remote-control.js +1 -1
- package/dist/lib/daemon/supervisor.d.ts +4 -9
- package/dist/lib/daemon/supervisor.js +5 -5
- package/dist/lib/lanes/admission-host.d.ts +3 -0
- package/dist/lib/lanes/admission-host.js +4 -0
- package/dist/lib/lanes/autopilot-registry.d.ts +62 -0
- package/dist/lib/lanes/autopilot-registry.js +192 -0
- package/dist/lib/lanes/autopilot.d.ts +2 -0
- package/dist/lib/lanes/autopilot.js +18 -5
- package/dist/lib/lanes/claude-balance.d.ts +6 -0
- package/dist/lib/lanes/claude-balance.js +17 -2
- package/dist/lib/lanes/keep-alive.d.ts +3 -0
- package/dist/lib/lanes/keep-alive.js +3 -0
- package/dist/lib/lanes/reconcile.js +14 -6
- package/dist/lib/lanes/spawn.js +2 -0
- package/dist/lib/mesh/live/daemon/credentials.d.ts +2 -17
- package/dist/lib/mesh/live/daemon/credentials.js +9 -9
- package/dist/lib/mesh/live/daemon/index.d.ts +5 -5
- package/dist/lib/mesh/live/daemon/index.js +2 -2
- package/dist/lib/mesh/live/daemon/state.d.ts +1 -1
- package/dist/lib/mesh/live/daemon/transcript-watch.d.ts +4 -11
- package/dist/lib/mesh/live/daemon/transcript-watch.js +5 -5
- package/dist/lib/work-context/company.d.ts +2 -25
- package/dist/lib/work-context/company.js +3 -3
- package/dist/lib/work-context/config.d.ts +3 -8
- package/dist/lib/work-context/config.js +2 -13
- package/dist/lib/work-context/state.d.ts +4 -13
- package/dist/lib/work-context/state.js +2 -30
- package/package.json +1 -1
package/dist/commands/dm.js
CHANGED
|
@@ -892,7 +892,7 @@ export function callerFromToken(token) {
|
|
|
892
892
|
...(emailNorm ? { email: emailNorm } : {}),
|
|
893
893
|
};
|
|
894
894
|
}
|
|
895
|
-
|
|
895
|
+
function isCallerMessage(m, me) {
|
|
896
896
|
if (me.personUid && m.fromPersonUid === me.personUid)
|
|
897
897
|
return true;
|
|
898
898
|
if (me.email && (m.fromEmail ?? "").trim().toLowerCase() === me.email) {
|
|
@@ -1060,7 +1060,7 @@ export function agentCallerUid(token, machine = isMachineIdentity) {
|
|
|
1060
1060
|
export const AGENT_THREAD_UNAVAILABLE = "`hq dm thread` reads /v1/notify/thread, which is not available to agent identities. " +
|
|
1061
1061
|
"Use `hq dm inbox` — for agents it reads GET /v1/agents/{uid}/inbox.";
|
|
1062
1062
|
/** One-line summary of an agent inbox item (DM, email, … — shapes vary by channel). Pure. */
|
|
1063
|
-
|
|
1063
|
+
function formatAgentInboxItem(item) {
|
|
1064
1064
|
const str = (k) => (typeof item[k] === "string" ? item[k] : "");
|
|
1065
1065
|
const id = str("messageId") || str("id") || "?";
|
|
1066
1066
|
const channel = str("channel") || "inbox";
|
|
@@ -1070,7 +1070,7 @@ export function formatAgentInboxItem(item) {
|
|
|
1070
1070
|
return `${chalk.bold(from)} ${chalk.dim(`[${channel}] ${id}${when ? ` ${when}` : ""}`)}\n ${firstLine(text)}`;
|
|
1071
1071
|
}
|
|
1072
1072
|
/** Remove delegated execution context before agent inbox items can be emitted. */
|
|
1073
|
-
|
|
1073
|
+
function redactAgentInboxItem(item) {
|
|
1074
1074
|
const { actingContext: _actingContext, ...safe } = item;
|
|
1075
1075
|
return safe;
|
|
1076
1076
|
}
|
|
@@ -3,7 +3,7 @@ import { type McpManifest } from "./mcp-registration.js";
|
|
|
3
3
|
export type DesktopConnector = McpManifest & {
|
|
4
4
|
[key: string]: unknown;
|
|
5
5
|
};
|
|
6
|
-
export type ImportStatus = "imported" | "needs-signin" | "shared-for-local" | "skipped";
|
|
6
|
+
export type ImportStatus = "imported" | "needs-signin" | "shared-for-local" | "personal-for-local" | "skipped";
|
|
7
7
|
export interface ImportOutcome {
|
|
8
8
|
name: string;
|
|
9
9
|
status: ImportStatus;
|
|
@@ -15,6 +15,7 @@ import { bareProvider, IntegrationsCliError, printJson } from "./integrations-co
|
|
|
15
15
|
import { installIntegration, startOAuth } from "./integrations-api.js";
|
|
16
16
|
import { loadRevealedSecrets } from "./secrets.js";
|
|
17
17
|
import { registerServer } from "./mcp-registration.js";
|
|
18
|
+
import { personalConnectorSettingsEnabled } from "./personal-connector-settings-flag.js";
|
|
18
19
|
const OAUTH_REQUIRED_CODE = "INTEGRATION_FACTORY_OAUTH_REQUIRED";
|
|
19
20
|
/** Claude Desktop's documented connector-config location for the current OS. */
|
|
20
21
|
export function claudeDesktopConfigPath(platform = process.platform, home = os.homedir(), env = process.env) {
|
|
@@ -149,7 +150,13 @@ function oauthRequired(error) {
|
|
|
149
150
|
function reconnectCommand(name, provider) {
|
|
150
151
|
return `hq integrations reconnect ${name} --provider ${bareProvider(provider)}`;
|
|
151
152
|
}
|
|
152
|
-
function connectorPath(hqRoot, company,
|
|
153
|
+
function connectorPath(hqRoot, name, company, personal) {
|
|
154
|
+
if (personal) {
|
|
155
|
+
return path.join(hqRoot, "personal", "settings", "connectors", `${name}.json`);
|
|
156
|
+
}
|
|
157
|
+
if (!company) {
|
|
158
|
+
throw new IntegrationsCliError("A company is required for a shared connector manifest.", { expected: true });
|
|
159
|
+
}
|
|
153
160
|
return path.join(hqRoot, "companies", company, "settings", "connectors", `${name}.json`);
|
|
154
161
|
}
|
|
155
162
|
/**
|
|
@@ -172,8 +179,8 @@ async function companyFolderSlug(token, companyUid) {
|
|
|
172
179
|
}
|
|
173
180
|
return folderSlug;
|
|
174
181
|
}
|
|
175
|
-
function writeLocalConnector(hqRoot, company,
|
|
176
|
-
const target = connectorPath(hqRoot, company,
|
|
182
|
+
function writeLocalConnector(hqRoot, name, company, personal, entry) {
|
|
183
|
+
const target = connectorPath(hqRoot, name, company, personal);
|
|
177
184
|
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
178
185
|
fs.writeFileSync(target, `${JSON.stringify(stripConnectorSecrets(entry), null, 2)}\n`, { mode: 0o600 });
|
|
179
186
|
return target;
|
|
@@ -225,11 +232,13 @@ function renderOutcomes(outcomes) {
|
|
|
225
232
|
for (const outcome of outcomes) {
|
|
226
233
|
console.log(`${redactUrlsInText(outcome.name).padEnd(25)} ${outcome.status.padEnd(19)} ${redactUrlsInText(outcome.reason ?? outcome.path ?? "")}`);
|
|
227
234
|
}
|
|
228
|
-
for (const outcome of outcomes.filter((item) => item.status === "shared-for-local")) {
|
|
229
|
-
|
|
230
|
-
|
|
235
|
+
for (const outcome of outcomes.filter((item) => item.status === "shared-for-local" || item.status === "personal-for-local")) {
|
|
236
|
+
const label = outcome.status === "personal-for-local" ? "Personal manifest" : "Shared manifest";
|
|
237
|
+
const installLabel = outcome.status === "personal-for-local" ? "Personal install" : "Teammate install";
|
|
238
|
+
console.log(chalk.dim(` ${label}: ${redactUrlsInText(outcome.path ?? "")}`));
|
|
239
|
+
console.log(chalk.dim(` ${installLabel}: ${redactUrlsInText(outcome.installCommand ?? "")}`));
|
|
231
240
|
}
|
|
232
|
-
if (outcomes.some((item) => item.status === "shared-for-local")) {
|
|
241
|
+
if (outcomes.some((item) => item.status === "shared-for-local" || item.status === "personal-for-local")) {
|
|
233
242
|
console.log(chalk.yellow(" Local connector credentials were replaced with ${secret:ENV_NAME}. Set them with `hq secrets` before installing."));
|
|
234
243
|
}
|
|
235
244
|
if (outcomes.some((item) => item.redactedArgumentCredentials)) {
|
|
@@ -245,7 +254,7 @@ function renderOutcomes(outcomes) {
|
|
|
245
254
|
export function registerImportCommands(integrations) {
|
|
246
255
|
integrations
|
|
247
256
|
.command("import")
|
|
248
|
-
.description("Import Claude Desktop connectors into
|
|
257
|
+
.description("Import Claude Desktop connectors into HQ Integrations")
|
|
249
258
|
.option("--company <slug>", "Company slug (defaults to your single active company)")
|
|
250
259
|
.option("--dry-run", "Detect and classify connectors without writing or connecting")
|
|
251
260
|
.option("--only <name[,name...]>", "Import only these Claude Desktop connector names")
|
|
@@ -272,6 +281,7 @@ export function registerImportCommands(integrations) {
|
|
|
272
281
|
const outcomes = [];
|
|
273
282
|
let company;
|
|
274
283
|
let hqRoot;
|
|
284
|
+
const personalSettings = personalConnectorSettingsEnabled();
|
|
275
285
|
const localCompany = async () => company ??= await companyFolderSlug(token, companyUid);
|
|
276
286
|
const localHqRoot = () => hqRoot ??= resolveDefaultHqRoot({ onMissing: "throw" });
|
|
277
287
|
const normalizedNames = new Map();
|
|
@@ -297,13 +307,17 @@ export function registerImportCommands(integrations) {
|
|
|
297
307
|
continue;
|
|
298
308
|
}
|
|
299
309
|
if (isLocal(entry)) {
|
|
300
|
-
const resolvedCompany = await localCompany();
|
|
301
|
-
const target = connectorPath(localHqRoot(), resolvedCompany,
|
|
310
|
+
const resolvedCompany = personalSettings ? undefined : await localCompany();
|
|
311
|
+
const target = connectorPath(localHqRoot(), safeName, resolvedCompany, personalSettings);
|
|
302
312
|
outcomes.push({
|
|
303
313
|
name,
|
|
304
|
-
status: "shared-for-local",
|
|
305
|
-
...(opts.dryRun
|
|
306
|
-
|
|
314
|
+
status: personalSettings ? "personal-for-local" : "shared-for-local",
|
|
315
|
+
...(opts.dryRun
|
|
316
|
+
? { reason: personalSettings ? "would write secret-stripped personal manifest" : "would write secret-stripped local manifest" }
|
|
317
|
+
: { path: writeLocalConnector(localHqRoot(), safeName, resolvedCompany, personalSettings, entry) }),
|
|
318
|
+
installCommand: personalSettings
|
|
319
|
+
? `hq integrations install-local ${safeName}`
|
|
320
|
+
: `hq integrations install-local ${safeName} --company ${resolvedCompany}`,
|
|
307
321
|
...(hasRedactedArgumentCredentials(entry) ? { redactedArgumentCredentials: true } : {}),
|
|
308
322
|
});
|
|
309
323
|
// Keep dry-run output useful without leaking a filesystem write target.
|
|
@@ -324,7 +338,7 @@ export function registerImportCommands(integrations) {
|
|
|
324
338
|
});
|
|
325
339
|
integrations
|
|
326
340
|
.command("install-local <name>")
|
|
327
|
-
.description("
|
|
341
|
+
.description("Install local connector settings in this machine's Claude/Codex MCP config")
|
|
328
342
|
.option("--company <slug>", "Company slug (defaults to your single active company)")
|
|
329
343
|
.option("--json", "Machine-readable registration result")
|
|
330
344
|
.action(async (name, opts) => {
|
|
@@ -334,11 +348,12 @@ export function registerImportCommands(integrations) {
|
|
|
334
348
|
}
|
|
335
349
|
const token = await ensureCognitoToken();
|
|
336
350
|
const companyUid = await getCompanyUid(token, opts.company);
|
|
337
|
-
const
|
|
351
|
+
const personalSettings = personalConnectorSettingsEnabled();
|
|
352
|
+
const company = personalSettings ? undefined : await companyFolderSlug(token, companyUid);
|
|
338
353
|
const hqRoot = resolveDefaultHqRoot({ onMissing: "throw" });
|
|
339
|
-
const manifestPath = connectorPath(hqRoot, company,
|
|
354
|
+
const manifestPath = connectorPath(hqRoot, safeName, company, personalSettings);
|
|
340
355
|
if (!fs.existsSync(manifestPath)) {
|
|
341
|
-
throw new IntegrationsCliError(`
|
|
356
|
+
throw new IntegrationsCliError(`Connector settings not found: ${manifestPath}`, { expected: true });
|
|
342
357
|
}
|
|
343
358
|
let manifest;
|
|
344
359
|
try {
|
|
@@ -346,7 +361,7 @@ export function registerImportCommands(integrations) {
|
|
|
346
361
|
}
|
|
347
362
|
catch (error) {
|
|
348
363
|
const message = error instanceof Error ? error.message : String(error);
|
|
349
|
-
throw new IntegrationsCliError(`Could not parse
|
|
364
|
+
throw new IntegrationsCliError(`Could not parse connector settings ${manifestPath}: ${message}`, { expected: true });
|
|
350
365
|
}
|
|
351
366
|
const secretNames = [...Object.values({ ...(manifest.env ?? {}), ...(manifest.headers ?? {}) }), ...(manifest.args ?? [])]
|
|
352
367
|
.flatMap((value) => [...value.matchAll(/\$\{secret:([A-Z][A-Z0-9_]*(?:\/[A-Z][A-Z0-9_]*)*)\}/g)].map((match) => match[1]));
|
|
@@ -356,7 +371,7 @@ export function registerImportCommands(integrations) {
|
|
|
356
371
|
const result = registerServer({
|
|
357
372
|
name: safeName,
|
|
358
373
|
manifest,
|
|
359
|
-
pack: `company-${company}-connectors`,
|
|
374
|
+
pack: personalSettings ? "personal-connectors" : `company-${company}-connectors`,
|
|
360
375
|
resolveSecret: (secret) => secrets.get(secret) ?? null,
|
|
361
376
|
});
|
|
362
377
|
if (opts.json)
|
|
@@ -165,12 +165,13 @@ export function createLaneAccountsHandlers(deps) {
|
|
|
165
165
|
}
|
|
166
166
|
const hqRoot = ctx.hqRoot();
|
|
167
167
|
const config = loadLanesConfig(hqRoot);
|
|
168
|
+
const quotaState = loadQuotaState(hqRoot);
|
|
168
169
|
const loaded = await loadClaudeBalanceHostSnapshots(ctx, hqRoot, config);
|
|
169
170
|
// Retain the established response field for clients; balancing is now
|
|
170
171
|
// unconditional, so this compatibility value is always true.
|
|
171
172
|
const balanceGateEnabled = true;
|
|
172
173
|
const recentUsageEnabled = await deps.readClaudeAccountBalanceSignalsEnabled(ctx);
|
|
173
|
-
const loads = accountLoadsForReport({ hosts: loaded.snapshots, config });
|
|
174
|
+
const loads = accountLoadsForReport({ hosts: loaded.snapshots, config, quotaState });
|
|
174
175
|
const suggestions = planClaudeBalanceMoves({ hosts: loaded.snapshots, config });
|
|
175
176
|
const selections = loaded.snapshots.map((host) => {
|
|
176
177
|
try {
|
|
@@ -178,6 +179,7 @@ export function createLaneAccountsHandlers(deps) {
|
|
|
178
179
|
hosts: loaded.snapshots,
|
|
179
180
|
targetHost: host.capacity.host,
|
|
180
181
|
config,
|
|
182
|
+
quotaState,
|
|
181
183
|
recentUsageEnabled,
|
|
182
184
|
now: ctx.now(),
|
|
183
185
|
unavailableHosts: loaded.unavailableHosts,
|
|
@@ -396,9 +396,31 @@ export function createLaneMessageHandlers(deps) {
|
|
|
396
396
|
if (opts.model !== undefined && opts.account === undefined) {
|
|
397
397
|
throw new LanesError("model_requires_account", "--model on hq lanes message is only accepted together with --account", {});
|
|
398
398
|
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
399
|
+
let quotaHoldDeferred;
|
|
400
|
+
let automaticAccountSelection = undefined;
|
|
401
|
+
if (opts.account === undefined && mechanism === "resume") {
|
|
402
|
+
try {
|
|
403
|
+
automaticAccountSelection = await selectAutomaticClaudeResumeAccount(ctx, lane, resumeProfile);
|
|
404
|
+
}
|
|
405
|
+
catch (err) {
|
|
406
|
+
const reasons = err instanceof LanesError && err.error === "claude_account_balance_no_eligible_account" &&
|
|
407
|
+
Array.isArray(err.details.account_reasons)
|
|
408
|
+
? err.details.account_reasons.filter((reason) => reason !== null && typeof reason === "object" && !Array.isArray(reason))
|
|
409
|
+
: [];
|
|
410
|
+
const held = reasons.filter((reason) => reason.reason === "quota_held");
|
|
411
|
+
if (held.length === 0)
|
|
412
|
+
throw err;
|
|
413
|
+
const resetTimes = held
|
|
414
|
+
.map((reason) => reason.quota_reset_at)
|
|
415
|
+
.filter((value) => typeof value === "string" && Number.isFinite(Date.parse(value)))
|
|
416
|
+
.sort((a, b) => Date.parse(a) - Date.parse(b));
|
|
417
|
+
quotaHoldDeferred = {
|
|
418
|
+
...(resetTimes[0] !== undefined ? { quota_reset_at: resetTimes[0] } : {}),
|
|
419
|
+
...(lane.model !== undefined ? { model: lane.model } : {}),
|
|
420
|
+
};
|
|
421
|
+
automaticAccountSelection = undefined;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
402
424
|
const resumeEffort = resolveResumeEffort(ctx, lane, {
|
|
403
425
|
cliEffort,
|
|
404
426
|
profile: resumeProfile,
|
|
@@ -488,7 +510,49 @@ export function createLaneMessageHandlers(deps) {
|
|
|
488
510
|
let resumed = null;
|
|
489
511
|
let move = null;
|
|
490
512
|
let queuedOnAdmission = false;
|
|
491
|
-
|
|
513
|
+
let queuedOnQuotaHold = false;
|
|
514
|
+
if (quotaHoldDeferred !== undefined) {
|
|
515
|
+
const sessionId = lane.provider_session_id;
|
|
516
|
+
if (!sessionId) {
|
|
517
|
+
throw new LanesError("no_provider_session", `Lane ${laneId} has no provider session id to queue this message`, {
|
|
518
|
+
lane_id: laneId,
|
|
519
|
+
provider: lane.provider,
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
lane = casUpdateLane(hqRoot, laneId, (current) => {
|
|
523
|
+
if (current.version !== lane.version || current.state !== lane.state) {
|
|
524
|
+
throw new LanesError("lane_admission_conflict", `Lane ${laneId} changed while its quota-held message was queued`, {
|
|
525
|
+
lane_id: laneId,
|
|
526
|
+
prepared_version: lane.version,
|
|
527
|
+
version: current.version,
|
|
528
|
+
state: current.state,
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
return {
|
|
532
|
+
...current,
|
|
533
|
+
state: "queued",
|
|
534
|
+
queued_reason: "account_retry",
|
|
535
|
+
queued_details: undefined,
|
|
536
|
+
queued_at: at,
|
|
537
|
+
admission_reserved_at: undefined,
|
|
538
|
+
pending_resume: {
|
|
539
|
+
session_id: sessionId,
|
|
540
|
+
trigger: "message",
|
|
541
|
+
caller: ctx.env.HQ_LANE_ID?.trim() || "user",
|
|
542
|
+
text: deliveryText,
|
|
543
|
+
...(opts.monitorEvent === undefined ? {} : { monitor_event: true }),
|
|
544
|
+
message_id: messageId,
|
|
545
|
+
compact: opts.compact !== false,
|
|
546
|
+
at,
|
|
547
|
+
},
|
|
548
|
+
};
|
|
549
|
+
});
|
|
550
|
+
queuedOnQuotaHold = true;
|
|
551
|
+
storySwitchDeliveryStarted = true;
|
|
552
|
+
markAccepted();
|
|
553
|
+
deps.onLaneMessageQueued?.(laneId, "quota_hold");
|
|
554
|
+
}
|
|
555
|
+
else if (opts.account !== undefined && !keepCurrentAccount) {
|
|
492
556
|
// A moved message is always a resume on the target account: a running
|
|
493
557
|
// lane (the dropbox mechanism) is refused inside prepareAccountMove.
|
|
494
558
|
move = await prepareAccountMove(ctx, lane, {
|
|
@@ -556,7 +620,7 @@ export function createLaneMessageHandlers(deps) {
|
|
|
556
620
|
});
|
|
557
621
|
}
|
|
558
622
|
let questionCancellation = null;
|
|
559
|
-
if (move?.queued) {
|
|
623
|
+
if (move?.queued || queuedOnQuotaHold) {
|
|
560
624
|
// Parked at the target's cap: reconcile resumes the session there.
|
|
561
625
|
markAccepted();
|
|
562
626
|
questionCancellation = awaitingInputFallback
|
|
@@ -678,11 +742,13 @@ export function createLaneMessageHandlers(deps) {
|
|
|
678
742
|
const drainHook = drainHookStatusFor(ctx, next);
|
|
679
743
|
const deliveryStatus = move?.queued
|
|
680
744
|
? "queued_on_target_account"
|
|
681
|
-
:
|
|
682
|
-
? "
|
|
683
|
-
:
|
|
684
|
-
? "
|
|
685
|
-
: "
|
|
745
|
+
: queuedOnQuotaHold
|
|
746
|
+
? "queued_on_quota_hold"
|
|
747
|
+
: queuedOnAdmission
|
|
748
|
+
? "queued_on_admission"
|
|
749
|
+
: mechanism === "dropbox"
|
|
750
|
+
? "awaiting_next_tool_call"
|
|
751
|
+
: "worker_spawned";
|
|
686
752
|
const warning = [
|
|
687
753
|
eligibility.supersededMessageWarning,
|
|
688
754
|
mechanism === "dropbox" && drainHook !== null && !drainHook.registered
|
|
@@ -702,6 +768,14 @@ export function createLaneMessageHandlers(deps) {
|
|
|
702
768
|
delivery: mechanism,
|
|
703
769
|
delivered_to: { host: lane.host.id, lane_id: laneId, mechanism },
|
|
704
770
|
delivery_status: deliveryStatus,
|
|
771
|
+
...(queuedOnQuotaHold ? {
|
|
772
|
+
queued_reason: "account_quota_held",
|
|
773
|
+
quota_hold: {
|
|
774
|
+
reason: "account_quota_held",
|
|
775
|
+
quota_reset_at: quotaHoldDeferred?.quota_reset_at ?? null,
|
|
776
|
+
model: quotaHoldDeferred?.model ?? null,
|
|
777
|
+
},
|
|
778
|
+
} : {}),
|
|
705
779
|
inbox_path: mechanism === "dropbox" ? dropboxInboxPath(hqRoot, laneId) : null,
|
|
706
780
|
drain_hook: drainHook,
|
|
707
781
|
warning,
|
|
@@ -156,7 +156,7 @@ export interface LaneMessageHandlerDeps {
|
|
|
156
156
|
/** Test seam invoked after a provider worker has been started. */
|
|
157
157
|
onLaneWorkerStarted?: (laneId: string, command: "message" | "resume") => void;
|
|
158
158
|
/** Test seam invoked after a message has been durably queued for a lane. */
|
|
159
|
-
onLaneMessageQueued?: (laneId: string, path: "admission" | "account_move") => void;
|
|
159
|
+
onLaneMessageQueued?: (laneId: string, path: "admission" | "account_move" | "quota_hold") => void;
|
|
160
160
|
archiveResumeMessage(hqRoot: string, laneId: string, message: {
|
|
161
161
|
message_id: string;
|
|
162
162
|
[key: string]: unknown;
|
|
@@ -264,6 +264,9 @@ export interface Ctx {
|
|
|
264
264
|
backfillOutpostIdentity: (expectedId: string) => "created" | "unchanged";
|
|
265
265
|
fetchText: (url: string) => Promise<string>;
|
|
266
266
|
lanePlacementEnabled: (company: LaneCompanyRef | undefined) => Promise<boolean>;
|
|
267
|
+
readLeadAutopilotFlag: (flag: string, options: {
|
|
268
|
+
company: LaneCompanyRef;
|
|
269
|
+
}) => Promise<boolean>;
|
|
267
270
|
storyLedgerEnabled: () => boolean;
|
|
268
271
|
isSsmOwnerDispatch: () => boolean;
|
|
269
272
|
withGrantHireLocks: typeof withLaneHireLocks;
|
package/dist/commands/lanes.d.ts
CHANGED
|
@@ -32,7 +32,7 @@ export interface LanesCommandDeps {
|
|
|
32
32
|
/** Test seam for failures after a lane provider worker is started. */
|
|
33
33
|
onLaneWorkerStarted?: (laneId: string, command: "message" | "resume") => void;
|
|
34
34
|
/** Test seam for failures after a message is durably queued. */
|
|
35
|
-
onLaneMessageQueued?: (laneId: string, path: "admission" | "account_move") => void;
|
|
35
|
+
onLaneMessageQueued?: (laneId: string, path: "admission" | "account_move" | "quota_hold") => void;
|
|
36
36
|
hqRoot?: () => string;
|
|
37
37
|
/** Start HQ-root walk here instead of process.cwd(). Tests only. */
|
|
38
38
|
cwd?: () => string;
|
|
@@ -40,6 +40,9 @@ export interface LanesCommandDeps {
|
|
|
40
40
|
now?: () => Date;
|
|
41
41
|
hostname?: () => string;
|
|
42
42
|
resolveCompany?: (company?: string) => Promise<LaneCompanyRef>;
|
|
43
|
+
readLeadAutopilotFlag?: (flag: string, options: {
|
|
44
|
+
company: LaneCompanyRef;
|
|
45
|
+
}) => Promise<boolean>;
|
|
43
46
|
/** Membership-backed explicit company selection for account-home writers. */
|
|
44
47
|
resolveActiveCompany?: (company: string) => Promise<LaneCompanyRef | null>;
|
|
45
48
|
listOutpostIds?: () => Promise<string[]>;
|
package/dist/commands/lanes.js
CHANGED
|
@@ -33,7 +33,9 @@ import { withCliFeatureUsed } from "../utils/cli-telemetry.js";
|
|
|
33
33
|
import { resolveLanesHqRoot } from "../lib/lanes/hq-root.js";
|
|
34
34
|
import { isSsmRunCommandProcess } from "../lib/lanes/ssm-command-context.js";
|
|
35
35
|
import { readLanesRegistryFlag } from "../lib/lanes/registry-flag.js";
|
|
36
|
+
import { isControllerAliasOutpostId } from "../lib/lanes/admission-host.js";
|
|
36
37
|
import { runLeadAutopilotPass } from "../lib/lanes/autopilot.js";
|
|
38
|
+
import { syncOutpostLeadRegistries } from "../lib/lanes/autopilot-registry.js";
|
|
37
39
|
import { OutpostSnapshotInvocationMemo } from "../lib/lanes/outpost-snapshot-memo.js";
|
|
38
40
|
import { createOutpostHireGrantContext, parseOutpostHireGrantContext, selectOutpostHireGrantHierarchy, sameHireGrantSenior, } from "../lib/lanes/hire-grant-dispatch.js";
|
|
39
41
|
import { createOutpostProfileRepinContext, parseOutpostProfileRepinContext, } from "../lib/lanes/outpost-profile-repin.js";
|
|
@@ -253,6 +255,7 @@ function resolveDeps(deps = {}) {
|
|
|
253
255
|
now: deps.now ?? (() => new Date()),
|
|
254
256
|
hostname: deps.hostname ?? (() => os.hostname()),
|
|
255
257
|
resolveCompany: deps.resolveCompany ?? defaultResolveCompany,
|
|
258
|
+
readLeadAutopilotFlag: deps.readLeadAutopilotFlag ?? readLanesRegistryFlag,
|
|
256
259
|
resolveActiveCompany: deps.resolveActiveCompany ?? defaultResolveActiveCompany,
|
|
257
260
|
listOutpostIds: deps.listOutpostIds ?? defaultListOutpostIds,
|
|
258
261
|
listOutpostRoutes,
|
|
@@ -1112,8 +1115,7 @@ async function loadHostCapacitySnapshots(ctx, hqRoot, config = loadLanesConfig(h
|
|
|
1112
1115
|
.map((id) => id.trim())
|
|
1113
1116
|
// `primary` aliases the controller when running on the primary box. From
|
|
1114
1117
|
// another Outpost it identifies a separate host and must be measured.
|
|
1115
|
-
.filter((id) => id
|
|
1116
|
-
(localHost.kind === "outpost" && localHost.id !== "primary"))
|
|
1118
|
+
.filter((id) => !isControllerAliasOutpostId(id, localHost))
|
|
1117
1119
|
.filter((id) => localHost.kind !== "outpost" || id !== localHost.id)
|
|
1118
1120
|
.sort();
|
|
1119
1121
|
const local = buildHostCapacitySnapshot({
|
|
@@ -1676,7 +1678,7 @@ async function loadClaudeBalanceHostSnapshots(ctx, hqRoot, config) {
|
|
|
1676
1678
|
});
|
|
1677
1679
|
}
|
|
1678
1680
|
const remoteIds = outpostIds.map((id) => id.trim())
|
|
1679
|
-
.filter((id) => id
|
|
1681
|
+
.filter((id) => !isControllerAliasOutpostId(id, localHost))
|
|
1680
1682
|
.filter((id) => localHost.kind !== "outpost" || id !== localHost.id)
|
|
1681
1683
|
.sort();
|
|
1682
1684
|
const remoteResults = await mapWithConcurrency(remoteIds, OUTPOST_HIRE_SUMMARY_CONCURRENCY, async (id) => {
|
|
@@ -3282,6 +3284,8 @@ async function runCreate(ctx, opts) {
|
|
|
3282
3284
|
hosts: balanceHostLoad.snapshots,
|
|
3283
3285
|
targetHost,
|
|
3284
3286
|
config,
|
|
3287
|
+
quotaState: loadQuotaState(hqRoot),
|
|
3288
|
+
...(modelToValidate !== undefined ? { model: modelToValidate } : {}),
|
|
3285
3289
|
recentUsageEnabled: await readClaudeAccountBalanceSignalsEnabled(ctx, company ?? undefined),
|
|
3286
3290
|
now: ctx.now(),
|
|
3287
3291
|
...(allowedAccounts !== undefined ? { allowedAccounts } : {}),
|
|
@@ -3329,6 +3333,8 @@ async function runCreate(ctx, opts) {
|
|
|
3329
3333
|
hosts,
|
|
3330
3334
|
targetHost: balanceTargetHost,
|
|
3331
3335
|
config,
|
|
3336
|
+
quotaState: loadQuotaState(hqRoot),
|
|
3337
|
+
...(modelToValidate !== undefined ? { model: modelToValidate } : {}),
|
|
3332
3338
|
recentUsageEnabled: await readClaudeAccountBalanceSignalsEnabled(ctx, company ?? undefined),
|
|
3333
3339
|
now: ctx.now(),
|
|
3334
3340
|
...(allowedAccounts !== undefined ? { allowedAccounts } : {}),
|
|
@@ -6391,9 +6397,8 @@ async function selectAutomaticClaudeResumeAccount(ctx, lane, workerProfile, opti
|
|
|
6391
6397
|
probe: ctx.probeProvider,
|
|
6392
6398
|
env: ctx.env,
|
|
6393
6399
|
}).logged_in;
|
|
6394
|
-
const
|
|
6400
|
+
const recordedAccountUnavailable = lane.account !== undefined &&
|
|
6395
6401
|
(currentConfig?.enabled !== true || currentLogin === false);
|
|
6396
|
-
const repairUnavailableAccount = options.repairUnavailableAccount === true && accountNeedsRepair;
|
|
6397
6402
|
const profile = workerProfile?.profile ?? (lane.worker_profile !== undefined
|
|
6398
6403
|
? loadWorkerProfile(hqRoot, lane.worker_profile)
|
|
6399
6404
|
: undefined);
|
|
@@ -6413,6 +6418,16 @@ async function selectAutomaticClaudeResumeAccount(ctx, lane, workerProfile, opti
|
|
|
6413
6418
|
(lane.model_source === "cli" || lane.model_source === "preserved")
|
|
6414
6419
|
? lane.account
|
|
6415
6420
|
: undefined;
|
|
6421
|
+
const quotaModel = resolveLaneModel({
|
|
6422
|
+
cliModel: pinnedModel ?? (lane.model_source === "account" ? lane.model : undefined),
|
|
6423
|
+
profileModel: profileDefaultsApply ? profile?.meta.model : undefined,
|
|
6424
|
+
accountModel: lane.account === undefined ? undefined : config.providers.claude.accounts[lane.account]?.model,
|
|
6425
|
+
providerModel: config.providers.claude.model,
|
|
6426
|
+
});
|
|
6427
|
+
const recordedAccountQuotaHeld = lane.account !== undefined &&
|
|
6428
|
+
quotaBlockFor(loadQuotaState(hqRoot), "claude", lane.account, quotaModel, ctx.now()).blocked;
|
|
6429
|
+
const accountNeedsRepair = recordedAccountUnavailable || recordedAccountQuotaHeld;
|
|
6430
|
+
const repairUnavailableAccount = options.repairUnavailableAccount === true && accountNeedsRepair;
|
|
6416
6431
|
const preservedModel = repairUnavailableAccount && lane.model_source === "account"
|
|
6417
6432
|
? lane.model ?? currentConfig?.model
|
|
6418
6433
|
: undefined;
|
|
@@ -6430,7 +6445,8 @@ async function selectAutomaticClaudeResumeAccount(ctx, lane, workerProfile, opti
|
|
|
6430
6445
|
const currentTargetAccount = lane.account === undefined
|
|
6431
6446
|
? undefined
|
|
6432
6447
|
: targetHost?.capacity.providers.claude.accounts[lane.account];
|
|
6433
|
-
if (
|
|
6448
|
+
if (!recordedAccountQuotaHeld &&
|
|
6449
|
+
profilePin !== undefined &&
|
|
6434
6450
|
lane.account !== undefined &&
|
|
6435
6451
|
allowedAccounts?.includes(lane.account) &&
|
|
6436
6452
|
currentConfig?.enabled === true &&
|
|
@@ -6443,6 +6459,7 @@ async function selectAutomaticClaudeResumeAccount(ctx, lane, workerProfile, opti
|
|
|
6443
6459
|
hosts: balanceHosts.snapshots,
|
|
6444
6460
|
targetHost: lane.host,
|
|
6445
6461
|
config,
|
|
6462
|
+
quotaState: loadQuotaState(hqRoot),
|
|
6446
6463
|
recentUsageEnabled: await readClaudeAccountBalanceSignalsEnabled(ctx, lane.company ?? undefined),
|
|
6447
6464
|
now: ctx.now(),
|
|
6448
6465
|
...(!repairUnavailableAccount && allowedAccounts !== undefined ? { allowedAccounts } : {}),
|
|
@@ -7810,6 +7827,29 @@ async function runReconcile(ctx, opts, recoverQueuedMessages) {
|
|
|
7810
7827
|
summary.ok = false;
|
|
7811
7828
|
}
|
|
7812
7829
|
const terminalizedRemoteIds = new Set(remoteLost.map(({ id }) => id));
|
|
7830
|
+
let autopilotRegistrySync;
|
|
7831
|
+
const reconcileHost = reconcileHostFromCtx(ctx);
|
|
7832
|
+
const localHost = laneHostFromCtx(ctx);
|
|
7833
|
+
if (!opts.dryRun && (reconcileHost.outpostId === null || isControllerAliasOutpostId(reconcileHost.outpostId, localHost))) {
|
|
7834
|
+
try {
|
|
7835
|
+
autopilotRegistrySync = await syncOutpostLeadRegistries({
|
|
7836
|
+
hqRoot,
|
|
7837
|
+
outpostIds: await ctx.listOutpostIds(),
|
|
7838
|
+
generatedAt: ctx.now().toISOString(),
|
|
7839
|
+
localHost,
|
|
7840
|
+
resolveCompany: (slug) => ctx.resolveCompany(slug),
|
|
7841
|
+
readFlag: (flag, options) => ctx.readLeadAutopilotFlag(flag, options),
|
|
7842
|
+
dispatch: ctx.dispatchOutpostExec,
|
|
7843
|
+
});
|
|
7844
|
+
}
|
|
7845
|
+
catch (error) {
|
|
7846
|
+
const code = error instanceof LanesError ? error.error : "lead_autopilot_registry_sync_failed";
|
|
7847
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
7848
|
+
summary.ok = false;
|
|
7849
|
+
summary.errors.push({ code, message });
|
|
7850
|
+
process.stderr.write(`hq lanes autopilot registry: ${message}\n`);
|
|
7851
|
+
}
|
|
7852
|
+
}
|
|
7813
7853
|
let autopilot;
|
|
7814
7854
|
try {
|
|
7815
7855
|
const host = reconcileHostFromCtx(ctx);
|
|
@@ -7842,11 +7882,12 @@ async function runReconcile(ctx, opts, recoverQueuedMessages) {
|
|
|
7842
7882
|
catch (error) {
|
|
7843
7883
|
const message = error instanceof Error ? error.message : String(error);
|
|
7844
7884
|
summary.ok = false;
|
|
7845
|
-
summary.errors.push({ code: "lead_autopilot_failed", message });
|
|
7885
|
+
summary.errors.push({ code: error instanceof LanesError ? error.error : "lead_autopilot_failed", message });
|
|
7846
7886
|
process.stderr.write(`hq lanes autopilot: ${message}\n`);
|
|
7847
7887
|
}
|
|
7848
7888
|
printJson({
|
|
7849
7889
|
...summary,
|
|
7890
|
+
...(autopilotRegistrySync ? { autopilot_registry_sync: autopilotRegistrySync } : {}),
|
|
7850
7891
|
...(autopilot ? { autopilot } : {}),
|
|
7851
7892
|
...(remoteLost.length > 0 ? { remote_lost: remoteLost } : {}),
|
|
7852
7893
|
...(mirrorRefresh.unverifiedRemote.size > 0
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const CLI_PERSONAL_CONNECTOR_SETTINGS_FLAG = "cli.personal-connector-settings";
|
|
2
|
+
/** Keep company-shared connector storage unchanged until the flag is explicitly enabled. */
|
|
3
|
+
export declare function personalConnectorSettingsEnabled(): boolean;
|
|
4
|
+
//# sourceMappingURL=personal-connector-settings-flag.d.ts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { resolveProcessFlagGate } from "../lib/flag-registry.js";
|
|
2
|
+
export const CLI_PERSONAL_CONNECTOR_SETTINGS_FLAG = "cli.personal-connector-settings";
|
|
3
|
+
/** Keep company-shared connector storage unchanged until the flag is explicitly enabled. */
|
|
4
|
+
export function personalConnectorSettingsEnabled() {
|
|
5
|
+
return resolveProcessFlagGate(CLI_PERSONAL_CONNECTOR_SETTINGS_FLAG, { fallback: false }, () => false);
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=personal-connector-settings-flag.js.map
|
|
@@ -1,44 +1,19 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import {
|
|
3
|
-
type ChannelRow = {
|
|
4
|
-
channelId?: string;
|
|
5
|
-
scope?: string;
|
|
6
|
-
companyUid?: string | null;
|
|
7
|
-
};
|
|
2
|
+
import { deployOrgContext, type ChannelReport, type ReportPatch } from "../lib/report-publish.js";
|
|
8
3
|
type ReportPatchInput = ReportPatch | ((current: ChannelReport) => ReportPatch);
|
|
9
|
-
/** Any company-owned channel (company or project scope) can carry reports; personal channels cannot. */
|
|
10
|
-
export declare function isCompanyReportChannel(channel: ChannelRow): boolean;
|
|
11
|
-
/**
|
|
12
|
-
* Records created before hq-pro stamped companyUid carry null. The channel the
|
|
13
|
-
* caller can see knows its company, and publish needs it to pick the deploy
|
|
14
|
-
* org, so fill it from there rather than failing every publish.
|
|
15
|
-
*/
|
|
16
|
-
export declare function withChannelCompany(report: ChannelReport, channels: ChannelRow[]): ChannelReport;
|
|
17
4
|
/** Explicit context has priority; discovery is the authenticated fallback for normal CLI use. */
|
|
18
5
|
export declare function resolveReport(token: string, reportId: string, requestedChannelId?: string): Promise<ChannelReport>;
|
|
19
6
|
export declare function patchReport(token: string, report: ChannelReport, patch: ReportPatchInput): Promise<ChannelReport>;
|
|
20
|
-
/** hq-deploy refused the subdomain: another app, not visible to this caller, holds it. */
|
|
21
|
-
export declare class SubdomainTakenError extends ReportPublishError {
|
|
22
|
-
constructor();
|
|
23
|
-
}
|
|
24
7
|
/**
|
|
25
8
|
* Subdomains to try, in order. The first is the report's own slug; the rest
|
|
26
9
|
* are used only when an app the caller cannot see already holds it (for
|
|
27
10
|
* example, one left by an earlier publish under a different account).
|
|
28
11
|
*/
|
|
29
12
|
export declare function candidateSlugs(slug: string, attempts?: number): string[];
|
|
30
|
-
export declare function resolveCompanySlug(token: string, companyUid: string): Promise<string>;
|
|
31
13
|
export declare function findOrCreateApp(token: string, slug: string, org: ReturnType<typeof deployOrgContext>): Promise<{
|
|
32
14
|
id: string;
|
|
33
15
|
subdomain: string;
|
|
34
16
|
}>;
|
|
35
|
-
export declare function deployReportStatic(token: string, input: {
|
|
36
|
-
slug: string;
|
|
37
|
-
files: Parameters<typeof buildReportTarball>[0];
|
|
38
|
-
access: DeployAccessPolicy;
|
|
39
|
-
org: ReturnType<typeof deployOrgContext>;
|
|
40
|
-
}): Promise<ReportDeployment>;
|
|
41
17
|
export declare function registerReportCommand(program: Command): void;
|
|
42
|
-
export declare function runReportCommand(action: () => Promise<void>): Promise<void>;
|
|
43
18
|
export {};
|
|
44
19
|
//# sourceMappingURL=report.d.ts.map
|
package/dist/commands/report.js
CHANGED
|
@@ -99,7 +99,7 @@ async function listReportChannels(token) {
|
|
|
99
99
|
return Array.isArray(listed.channels) ? listed.channels : [];
|
|
100
100
|
}
|
|
101
101
|
/** Any company-owned channel (company or project scope) can carry reports; personal channels cannot. */
|
|
102
|
-
|
|
102
|
+
function isCompanyReportChannel(channel) {
|
|
103
103
|
if (channel.scope === "personal")
|
|
104
104
|
return false;
|
|
105
105
|
return channel.scope === "company" || channel.scope === "project" || Boolean(channel.companyUid);
|
|
@@ -125,7 +125,7 @@ async function readReportInChannel(token, channelId, reportId) {
|
|
|
125
125
|
* caller can see knows its company, and publish needs it to pick the deploy
|
|
126
126
|
* org, so fill it from there rather than failing every publish.
|
|
127
127
|
*/
|
|
128
|
-
|
|
128
|
+
function withChannelCompany(report, channels) {
|
|
129
129
|
if (report.companyUid)
|
|
130
130
|
return report;
|
|
131
131
|
const companyUid = channels.find((row) => row.channelId === report.channelId)?.companyUid;
|
|
@@ -232,7 +232,7 @@ async function deployJson(token, route, method, body, extraHeaders = {}) {
|
|
|
232
232
|
return await res.json();
|
|
233
233
|
}
|
|
234
234
|
/** hq-deploy refused the subdomain: another app, not visible to this caller, holds it. */
|
|
235
|
-
|
|
235
|
+
class SubdomainTakenError extends ReportPublishError {
|
|
236
236
|
constructor() {
|
|
237
237
|
super("hq-deploy rejected the report deployment.");
|
|
238
238
|
}
|
|
@@ -245,7 +245,7 @@ export class SubdomainTakenError extends ReportPublishError {
|
|
|
245
245
|
export function candidateSlugs(slug, attempts = 3) {
|
|
246
246
|
return [slug, ...Array.from({ length: attempts }, (_, i) => `${slug}-${i + 2}`)];
|
|
247
247
|
}
|
|
248
|
-
|
|
248
|
+
async function resolveCompanySlug(token, companyUid) {
|
|
249
249
|
const res = await vaultApiFetch({ token, path: `/entity/${encodeURIComponent(companyUid)}` });
|
|
250
250
|
if (!res.ok)
|
|
251
251
|
throw new ReportPublishError("Could not resolve the report's company for deploy.");
|
|
@@ -290,7 +290,7 @@ export async function findOrCreateApp(token, slug, org) {
|
|
|
290
290
|
throw new ReportPublishError("hq-deploy returned an incomplete app record.");
|
|
291
291
|
return { id, subdomain };
|
|
292
292
|
}
|
|
293
|
-
|
|
293
|
+
async function deployReportStatic(token, input) {
|
|
294
294
|
const app = await findOrCreateApp(token, input.slug, input.org);
|
|
295
295
|
await deployJson(token, `/api/apps/${encodeURIComponent(app.id)}/access-policy`, "PUT", input.access, input.org.headers);
|
|
296
296
|
const tarball = buildReportTarball(input.files);
|
|
@@ -525,7 +525,7 @@ export function registerReportCommand(program) {
|
|
|
525
525
|
print(templates, options.json === true);
|
|
526
526
|
});
|
|
527
527
|
}
|
|
528
|
-
|
|
528
|
+
async function runReportCommand(action) {
|
|
529
529
|
try {
|
|
530
530
|
await action();
|
|
531
531
|
}
|