@indigoai-us/hq-cli 5.345.191 → 5.345.193
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 +398 -0
- package/assets/bot-workers/setup/worker.yaml +91 -34
- package/dist/command-catalog.generated.d.ts +96 -19
- package/dist/command-catalog.generated.js +124 -21
- package/dist/commands/bot-jobs.d.ts +21 -18
- package/dist/commands/bot-jobs.js +127 -39
- package/dist/commands/bot.d.ts +4 -3
- package/dist/commands/bot.js +265 -140
- package/dist/commands/core-jobs-schedule.js +10 -0
- package/dist/commands/daemon.js +37 -0
- package/dist/commands/files.d.ts +41 -6
- package/dist/commands/files.js +108 -27
- package/dist/commands/pack-start-bot.d.ts +4 -6
- package/dist/commands/pack-start-bot.js +4 -8
- package/dist/commands/signals-run.d.ts +45 -0
- package/dist/commands/signals-run.js +175 -0
- package/dist/commands/signals.d.ts +1 -0
- package/dist/commands/signals.js +3 -0
- package/dist/commands/sources.d.ts +11 -0
- package/dist/commands/sources.js +137 -25
- package/dist/commands/staff/reset-test-account.d.ts +10 -0
- package/dist/commands/staff/reset-test-account.js +18 -2
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +31171 -52512
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/lib/bot/api.d.ts +3 -2
- package/dist/lib/bot/config.d.ts +2 -0
- package/dist/lib/bot/fleet-cron.d.ts +121 -0
- package/dist/lib/bot/fleet-cron.js +319 -0
- package/dist/lib/bot/fleet-local.d.ts +93 -1
- package/dist/lib/bot/fleet-local.js +546 -34
- package/dist/lib/bot/fleet-owner-companies.d.ts +12 -0
- package/dist/lib/bot/fleet-owner-companies.js +30 -0
- package/dist/lib/bot/fleet-watch.d.ts +25 -0
- package/dist/lib/bot/fleet-watch.js +51 -0
- package/dist/lib/bot/index.d.ts +1 -1
- package/dist/lib/bot/index.js +1 -1
- package/dist/lib/bot/job-run.d.ts +5 -4
- package/dist/lib/bot/job-run.js +5 -4
- package/dist/lib/bot/jobs/hot-signal-sync.d.ts +57 -3
- package/dist/lib/bot/jobs/hot-signal-sync.js +163 -4
- package/dist/lib/bot/local-jobs.d.ts +13 -2
- package/dist/lib/bot/local-jobs.js +17 -4
- package/dist/lib/bot/progress.d.ts +14 -0
- package/dist/lib/bot/progress.js +47 -1
- package/dist/lib/bot/run.d.ts +55 -2
- package/dist/lib/bot/run.js +192 -10
- package/dist/lib/bot/setup-tool-offer.d.ts +1 -0
- package/dist/lib/bot/setup-tool-offer.js +16 -0
- package/dist/lib/bot/signals-engine-flag.d.ts +18 -0
- package/dist/lib/bot/signals-engine-flag.js +30 -0
- package/dist/lib/daemon/catalog.d.ts +4 -0
- package/dist/lib/daemon/catalog.js +29 -9
- package/dist/lib/daemon/legacy.d.ts +6 -0
- package/dist/lib/daemon/legacy.js +16 -10
- package/dist/lib/daemon/run.d.ts +1 -0
- package/dist/lib/daemon/run.js +10 -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/flag-registry.d.ts +13 -1
- package/dist/lib/flag-registry.js +46 -0
- package/dist/lib/hot-channels/adapters/attio.d.ts +75 -2
- package/dist/lib/hot-channels/adapters/attio.js +367 -29
- package/dist/lib/hot-channels/adapters/gmail.d.ts +14 -0
- package/dist/lib/hot-channels/adapters/gmail.js +59 -2
- package/dist/lib/hot-channels/adapters/linear.d.ts +24 -0
- package/dist/lib/hot-channels/adapters/linear.js +71 -1
- package/dist/lib/hot-channels/adapters/meetings.js +4 -1
- package/dist/lib/hot-channels/adapters/slack.js +7 -3
- package/dist/lib/hot-channels/alert.js +1 -1
- package/dist/lib/hot-channels/contract.d.ts +11 -0
- package/dist/lib/hot-channels/contract.js +1 -0
- package/dist/lib/hot-channels/cursors.js +1 -1
- package/dist/lib/hot-channels/landing.d.ts +6 -2
- package/dist/lib/hot-channels/landing.js +13 -6
- package/dist/lib/hot-channels/ledger.js +2 -0
- package/dist/lib/hot-channels/loop.js +18 -4
- package/dist/lib/hot-channels/registry.js +9 -1
- package/dist/lib/hot-channels/run.d.ts +18 -0
- package/dist/lib/hot-channels/run.js +15 -4
- package/dist/lib/hot-channels/signals-pass.d.ts +1 -0
- package/dist/lib/hot-channels/signals-pass.js +7 -5
- package/dist/lib/hot-channels/sync-lock.js +22 -6
- package/dist/lib/mesh/live/daemon/install.d.ts +22 -0
- package/dist/lib/mesh/live/daemon/install.js +117 -30
- package/dist/lib/signals/adapters-catalog.d.ts +77 -0
- package/dist/lib/signals/adapters-catalog.js +194 -0
- package/dist/lib/signals/engine.d.ts +196 -0
- package/dist/lib/signals/engine.js +495 -0
- package/dist/lib/signals/failure-alert.d.ts +89 -0
- package/dist/lib/signals/failure-alert.js +175 -0
- package/dist/lib/signals/local-date.d.ts +3 -0
- package/dist/lib/signals/local-date.js +6 -0
- package/dist/lib/signals/projection.d.ts +42 -0
- package/dist/lib/signals/projection.js +67 -0
- package/dist/lib/signals/reply.d.ts +112 -0
- package/dist/lib/signals/reply.js +368 -0
- package/dist/lib/signals/run-lock.d.ts +36 -0
- package/dist/lib/signals/run-lock.js +148 -0
- package/dist/lib/signals/run-record.d.ts +24 -0
- package/dist/lib/signals/run-record.js +82 -0
- package/dist/lib/signals/sanitize.d.ts +105 -0
- package/dist/lib/signals/sanitize.js +439 -0
- package/dist/lib/signals/settings.d.ts +10 -0
- package/dist/lib/signals/settings.js +48 -0
- package/dist/lib/signals/steps/adapters.d.ts +14 -0
- package/dist/lib/signals/steps/adapters.js +156 -0
- package/dist/lib/signals/steps/entity-similarity.d.ts +10 -0
- package/dist/lib/signals/steps/entity-similarity.js +47 -0
- package/dist/lib/signals/steps/extract.d.ts +113 -0
- package/dist/lib/signals/steps/extract.js +765 -0
- package/dist/lib/signals/steps/garden.d.ts +24 -0
- package/dist/lib/signals/steps/garden.js +250 -0
- package/dist/lib/signals/steps/types.d.ts +112 -0
- package/dist/lib/signals/steps/types.js +4 -0
- package/dist/lib/signals/steps/upload.d.ts +43 -0
- package/dist/lib/signals/steps/upload.js +324 -0
- package/dist/lib/signals/write-guard.d.ts +63 -0
- package/dist/lib/signals/write-guard.js +287 -0
- package/dist/utils/resident-package-preload.js +16 -0
- package/package.json +9 -9
- package/dist/lib/bot/fleet-default-flag.d.ts +0 -31
- package/dist/lib/bot/fleet-default-flag.js +0 -127
package/dist/commands/sources.js
CHANGED
|
@@ -38,12 +38,15 @@ import { createClaudeSignalsPass } from "../lib/hot-channels/signals-pass.js";
|
|
|
38
38
|
import { assertSafeSegment } from "../lib/hot-channels/landing.js";
|
|
39
39
|
import { acquireSyncLock, SYNC_LOCK_BUSY_REASON } from "../lib/hot-channels/sync-lock.js";
|
|
40
40
|
import { createBuiltinHotChannelRegistry } from "../lib/hot-channels/adapters/index.js";
|
|
41
|
+
import { companyAdapterFlagReader, gateAdapters, readSignalsAdaptersCatalog, } from "../lib/signals/adapters-catalog.js";
|
|
41
42
|
import { createSelectionLoader } from "../lib/hot-channels/selection.js";
|
|
42
43
|
import { createPersonVaultReader } from "../lib/hot-channels/person-vault-reader.js";
|
|
43
44
|
import { S3Client } from "@aws-sdk/client-s3";
|
|
44
45
|
import { resolveCanonicalPersonUid } from "./cloud.js";
|
|
45
46
|
import { botsRoot as defaultBotsRoot } from "../lib/bot/paths.js";
|
|
46
47
|
import { ensureHotSignalSyncJob } from "../lib/bot/jobs/hot-signal-sync.js";
|
|
48
|
+
import { companyOfHotSignalSyncJob, fleetCronOps } from "../lib/bot/fleet-cron.js";
|
|
49
|
+
import { HQ_FLEET_RUNTIME_ENGINE_ID, readBotConfig } from "../lib/bot/config.js";
|
|
47
50
|
import { findHotSignalSyncJobs, lastRunsPerAdapter, parseDailyAt, renderScheduleReport, rescheduleDaily, toJobView, } from "../lib/hot-channels/schedule.js";
|
|
48
51
|
import { listActiveMembers } from "./members.js";
|
|
49
52
|
import { runOnDemandSync, } from "../lib/hot-channels/run.js";
|
|
@@ -485,32 +488,78 @@ async function skippedReport(input, vaultRoot, personKey, personUid, now) {
|
|
|
485
488
|
runId: line.runId,
|
|
486
489
|
signInRequired: false,
|
|
487
490
|
skipped: true,
|
|
491
|
+
personKey,
|
|
488
492
|
outcomes: [],
|
|
489
493
|
entries: [line],
|
|
490
494
|
notices: [`Another hot-channel sync for ${input.company} is already running, so this one was skipped.`],
|
|
491
495
|
exitCode: 0,
|
|
492
496
|
};
|
|
493
497
|
}
|
|
498
|
+
/**
|
|
499
|
+
* One `gated` outcome per adapter the local gate turned off (yaml or company
|
|
500
|
+
* flag). With `--adapter`, only that adapter's. Nothing is fetched for them.
|
|
501
|
+
*/
|
|
502
|
+
function gatedAdapterOutcomes(registry, gating, wanted) {
|
|
503
|
+
const want = wanted?.toLowerCase();
|
|
504
|
+
const out = [];
|
|
505
|
+
for (const adapter of registry.list()) {
|
|
506
|
+
if (!(adapter.id in gating.skips))
|
|
507
|
+
continue;
|
|
508
|
+
const provider = adapter.tokenScope.provider || adapter.id;
|
|
509
|
+
if (want !== undefined && adapter.id !== want && provider.toLowerCase() !== want)
|
|
510
|
+
continue;
|
|
511
|
+
const flag = gating.offFlags[adapter.id];
|
|
512
|
+
out.push({
|
|
513
|
+
adapter: adapter.id,
|
|
514
|
+
provider,
|
|
515
|
+
connectionId: null,
|
|
516
|
+
appName: null,
|
|
517
|
+
status: "gated",
|
|
518
|
+
landed: 0,
|
|
519
|
+
duplicates: 0,
|
|
520
|
+
message: flag
|
|
521
|
+
? `${adapter.id} is not enabled for this company (flag ${flag} is off)`
|
|
522
|
+
: `${adapter.id} is turned off in core/settings/signals-adapters.yaml`,
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
return out;
|
|
526
|
+
}
|
|
494
527
|
async function runLockedCompanySync(input, vaultRoot, personKey, author, session, lockHeld) {
|
|
495
528
|
const deps = gatewaySyncDeps(session.token, session.companyUid);
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
529
|
+
const registry = input.registry ??
|
|
530
|
+
createBuiltinHotChannelRegistry({ vaultRoot, identities: await meetingIdentities(input, session, author.email) });
|
|
531
|
+
// The adapter gate for every caller: signals-adapters.yaml plus the company
|
|
532
|
+
// flags, read through hq-flags.
|
|
533
|
+
const gating = await gateAdapters(registry.list().map((a) => a.id), readSignalsAdaptersCatalog(input.hqRoot), input.isFlagOn ??
|
|
534
|
+
companyAdapterFlagReader(session.companyUid, session.token, { hqRoot: input.hqRoot, company: input.company }));
|
|
535
|
+
const gated = gatedAdapterOutcomes(registry, gating, input.adapter);
|
|
536
|
+
const onlyAdapters = (input.onlyAdapters ?? gating.enabled).filter((id) => gating.enabled.includes(id));
|
|
537
|
+
let report;
|
|
538
|
+
if (input.adapter !== undefined && gated.length > 0) {
|
|
539
|
+
// `--adapter` named an adapter the gate turned off: there is nothing to run.
|
|
540
|
+
report = { runId: null, signInRequired: false, personKey, outcomes: [], entries: [], notices: [], exitCode: 0 };
|
|
541
|
+
}
|
|
542
|
+
else {
|
|
543
|
+
// A fresh loader per run: selections are cached for this run only.
|
|
544
|
+
const selections = input.createSelections ? input.createSelections() : defaultSelections();
|
|
545
|
+
report = await runOnDemandSync({
|
|
546
|
+
...(selections ? { selections } : {}),
|
|
547
|
+
deps,
|
|
548
|
+
registry,
|
|
549
|
+
vaultRoot,
|
|
550
|
+
personUid: author.userSub,
|
|
551
|
+
personKey,
|
|
552
|
+
...(input.adapter !== undefined ? { adapter: input.adapter } : {}),
|
|
553
|
+
...(input.onlyAdapters !== undefined || Object.keys(gating.skips).length > 0 ? { onlyAdapters } : {}),
|
|
554
|
+
...(input.now ? { now: input.now } : {}),
|
|
555
|
+
...(input.trigger ? { trigger: input.trigger } : {}),
|
|
556
|
+
...(input.catchUp === undefined ? {} : { catchUp: input.catchUp }),
|
|
557
|
+
...(input.firstRun ? { firstRun: true } : {}),
|
|
558
|
+
...(input.signalsGenerated ? { signalsGenerated: input.signalsGenerated } : {}),
|
|
559
|
+
beforeWrite: lockHeld,
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
report.outcomes.push(...gated);
|
|
514
563
|
if (input.signalsAlert) {
|
|
515
564
|
const postCompanyChannel = input.signalsAlert.postCompanyChannel ??
|
|
516
565
|
createSignalsAlertChannelPoster({ hqRoot: input.hqRoot, company: input.company, deps });
|
|
@@ -593,6 +642,55 @@ function renderSyncTable(company, report) {
|
|
|
593
642
|
}
|
|
594
643
|
return lines.join("\n");
|
|
595
644
|
}
|
|
645
|
+
function fleetBotsIn(root) {
|
|
646
|
+
let names;
|
|
647
|
+
try {
|
|
648
|
+
names = fs.readdirSync(root).sort();
|
|
649
|
+
}
|
|
650
|
+
catch {
|
|
651
|
+
return [];
|
|
652
|
+
}
|
|
653
|
+
const bots = [];
|
|
654
|
+
for (const botName of names) {
|
|
655
|
+
const config = readBotConfig(path.join(root, botName));
|
|
656
|
+
if (config && config.engine?.id === HQ_FLEET_RUNTIME_ENGINE_ID)
|
|
657
|
+
bots.push({ botName, agentUid: config.agentUid });
|
|
658
|
+
}
|
|
659
|
+
return bots;
|
|
660
|
+
}
|
|
661
|
+
function findFleetHotSignalSyncJob(root, company, opsFor) {
|
|
662
|
+
for (const bot of fleetBotsIn(root)) {
|
|
663
|
+
let ops;
|
|
664
|
+
let jobs;
|
|
665
|
+
try {
|
|
666
|
+
ops = opsFor(bot.agentUid);
|
|
667
|
+
jobs = ops.list();
|
|
668
|
+
}
|
|
669
|
+
catch {
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
const job = jobs.find((candidate) => companyOfHotSignalSyncJob(candidate) === company);
|
|
673
|
+
if (job)
|
|
674
|
+
return { botName: bot.botName, ops, job };
|
|
675
|
+
}
|
|
676
|
+
return null;
|
|
677
|
+
}
|
|
678
|
+
function fleetDailyAt(expr) {
|
|
679
|
+
const match = /^(\d{1,2}) (\d{1,2}) \* \* \*$/.exec((expr ?? "").trim());
|
|
680
|
+
return match ? `${match[2].padStart(2, "0")}:${match[1].padStart(2, "0")}` : null;
|
|
681
|
+
}
|
|
682
|
+
function fleetJobView(found) {
|
|
683
|
+
const expr = found.job.schedule?.expr ?? "";
|
|
684
|
+
return {
|
|
685
|
+
botName: found.botName,
|
|
686
|
+
jobId: found.job.id,
|
|
687
|
+
cron: expr,
|
|
688
|
+
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC",
|
|
689
|
+
dailyAt: fleetDailyAt(expr),
|
|
690
|
+
nextRunAt: found.job.next_run_at ?? null,
|
|
691
|
+
lastRunAt: found.job.last_run_at ?? null,
|
|
692
|
+
};
|
|
693
|
+
}
|
|
596
694
|
export class NoHotSignalSyncJobError extends Error {
|
|
597
695
|
company;
|
|
598
696
|
constructor(company) {
|
|
@@ -618,7 +716,22 @@ export async function runSchedule(options, overrides = {}) {
|
|
|
618
716
|
// Validate before touching any job file.
|
|
619
717
|
if (options.at !== undefined)
|
|
620
718
|
parseDailyAt(options.at);
|
|
621
|
-
|
|
719
|
+
const botsRootDir = overrides.botsRoot ?? defaultBotsRoot();
|
|
720
|
+
const fleetFound = findFleetHotSignalSyncJob(botsRootDir, company, overrides.fleetOps ?? fleetCronOps);
|
|
721
|
+
if (fleetFound) {
|
|
722
|
+
if (options.at !== undefined) {
|
|
723
|
+
const { hour, minute } = parseDailyAt(options.at);
|
|
724
|
+
const updated = fleetFound.ops.reschedule(fleetFound.job.id, `${minute} ${hour} * * *`);
|
|
725
|
+
if (!updated)
|
|
726
|
+
throw new NoHotSignalSyncJobError(company);
|
|
727
|
+
fleetFound.job = updated;
|
|
728
|
+
}
|
|
729
|
+
return finishScheduleReport(options, overrides, company, fleetJobView(fleetFound), [], now);
|
|
730
|
+
}
|
|
731
|
+
if (options.at !== undefined && fleetBotsIn(botsRootDir).some((bot) => bot.botName === "setup")) {
|
|
732
|
+
throw new Error(`The setup bot runs on the HQ Fleet Runtime and has no hot-signal-sync job for ${company} yet. Start it once with \`hq bot start setup\` so it adds the job, then set the time again.`);
|
|
733
|
+
}
|
|
734
|
+
let found = findHotSignalSyncJobs(botsRootDir, company);
|
|
622
735
|
if (options.at !== undefined) {
|
|
623
736
|
if (found.length === 0) {
|
|
624
737
|
// Setting a time turns the daily sync on: register it on the personal setup bot.
|
|
@@ -638,20 +751,19 @@ export async function runSchedule(options, overrides = {}) {
|
|
|
638
751
|
throw new NoHotSignalSyncJobError(company);
|
|
639
752
|
found = rescheduleDaily(found, options.at, now);
|
|
640
753
|
}
|
|
754
|
+
return finishScheduleReport(options, overrides, company, found[0] ? toJobView(found[0], now) : null, found.slice(1).map((f) => f.botName), now);
|
|
755
|
+
}
|
|
756
|
+
async function finishScheduleReport(options, overrides, company, job, extraBots, now) {
|
|
641
757
|
const email = overrides.email ?? resolveUploadAuthor()?.email;
|
|
642
758
|
const lastRuns = email
|
|
643
759
|
? await lastRunsPerAdapter(path.join(options.hqRoot, "companies", company), audienceKey([email]), now)
|
|
644
760
|
: [];
|
|
645
|
-
const report = {
|
|
646
|
-
company,
|
|
647
|
-
job: found[0] ? toJobView(found[0], now) : null,
|
|
648
|
-
lastRuns,
|
|
649
|
-
};
|
|
761
|
+
const report = { company, job, lastRuns };
|
|
650
762
|
if (options.json) {
|
|
651
763
|
process.stdout.write(JSON.stringify(report, null, 2) + "\n");
|
|
652
764
|
}
|
|
653
765
|
else {
|
|
654
|
-
process.stdout.write(renderScheduleReport(report,
|
|
766
|
+
process.stdout.write(renderScheduleReport(report, extraBots) + "\n");
|
|
655
767
|
if (!email)
|
|
656
768
|
process.stdout.write("Sign in (hq login) to see recent runs.\n");
|
|
657
769
|
}
|
|
@@ -24,6 +24,12 @@ interface PlanCompany {
|
|
|
24
24
|
slug: string;
|
|
25
25
|
memberCount: number;
|
|
26
26
|
}
|
|
27
|
+
interface PlanCompanyHandoff {
|
|
28
|
+
companyUid: string;
|
|
29
|
+
companySlug: string;
|
|
30
|
+
toPersonUid: string;
|
|
31
|
+
toEmail: string;
|
|
32
|
+
}
|
|
27
33
|
interface Plan {
|
|
28
34
|
target: {
|
|
29
35
|
input: string;
|
|
@@ -32,6 +38,8 @@ interface Plan {
|
|
|
32
38
|
personUid: string | null;
|
|
33
39
|
cognitoUsers: PlanCognitoUser[];
|
|
34
40
|
ownedCompanies: PlanCompany[];
|
|
41
|
+
/** Present only when the request opted in to the handoff. */
|
|
42
|
+
sharedCompanyHandoffs?: PlanCompanyHandoff[];
|
|
35
43
|
personalVault: {
|
|
36
44
|
bucketName: string | null;
|
|
37
45
|
kmsKeyAlias: string | null;
|
|
@@ -89,6 +97,8 @@ export interface RunResetTestAccountParams {
|
|
|
89
97
|
execute: boolean;
|
|
90
98
|
/** Skip the typed-confirmation prompt on --execute (for scripts). */
|
|
91
99
|
yes?: boolean;
|
|
100
|
+
/** Keep a company with real members and hand it to one, instead of refusing. */
|
|
101
|
+
handoffSharedCompanies?: boolean;
|
|
92
102
|
}
|
|
93
103
|
export interface RunResetTestAccountDeps {
|
|
94
104
|
prompt?: ConfirmFn;
|
|
@@ -76,6 +76,13 @@ export function renderPlan(plan) {
|
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
lines.push("");
|
|
79
|
+
if (plan.sharedCompanyHandoffs && plan.sharedCompanyHandoffs.length > 0) {
|
|
80
|
+
lines.push(chalk.bold(" Companies kept and handed over"));
|
|
81
|
+
for (const h of plan.sharedCompanyHandoffs) {
|
|
82
|
+
lines.push(` handoff: ${h.companySlug} to ${h.toEmail}`);
|
|
83
|
+
}
|
|
84
|
+
lines.push("");
|
|
85
|
+
}
|
|
79
86
|
lines.push(chalk.bold(" Personal vault"));
|
|
80
87
|
lines.push(` bucket: ${plan.personalVault.bucketName ?? chalk.dim("(none)")}`);
|
|
81
88
|
lines.push(` kms alias: ${plan.personalVault.kmsKeyAlias ?? chalk.dim("(none)")}`);
|
|
@@ -166,13 +173,17 @@ export async function runResetTestAccount(params, deps = {}) {
|
|
|
166
173
|
throw new Error("reset-test-account requires a super-admin (staff) HQ session. " +
|
|
167
174
|
"Sign in with an @getindigo.ai account and retry.");
|
|
168
175
|
}
|
|
176
|
+
// Absent unless asked for, so the server's refusal stays the default.
|
|
177
|
+
const handoff = params.handoffSharedCompanies === true
|
|
178
|
+
? { handoffSharedCompanies: true }
|
|
179
|
+
: {};
|
|
169
180
|
// Dry-run first. The AC calls out "Without --execute, runs dry-run and
|
|
170
181
|
// prints the plan" — so a bare invocation always ends here.
|
|
171
182
|
const dryRunRes = await vaultApiFetch({
|
|
172
183
|
token,
|
|
173
184
|
path: "/v1/admin/test-account/reset",
|
|
174
185
|
method: "POST",
|
|
175
|
-
body: { email, dryRun: true },
|
|
186
|
+
body: { email, dryRun: true, ...handoff },
|
|
176
187
|
});
|
|
177
188
|
const dryRunBody = await readBody(dryRunRes);
|
|
178
189
|
if (!dryRunRes.ok) {
|
|
@@ -221,7 +232,7 @@ export async function runResetTestAccount(params, deps = {}) {
|
|
|
221
232
|
token,
|
|
222
233
|
path: "/v1/admin/test-account/reset",
|
|
223
234
|
method: "POST",
|
|
224
|
-
body: { email, dryRun: false, confirmEmail },
|
|
235
|
+
body: { email, dryRun: false, confirmEmail, ...handoff },
|
|
225
236
|
});
|
|
226
237
|
const executeBody = await readBody(executeRes);
|
|
227
238
|
// The handler answers 200 (all-ok) and 207 (best-effort partial). Anything
|
|
@@ -234,6 +245,9 @@ export async function runResetTestAccount(params, deps = {}) {
|
|
|
234
245
|
throw new Error("server did not return an execute result");
|
|
235
246
|
}
|
|
236
247
|
console.log(renderExecuteSummary(result));
|
|
248
|
+
for (const h of plan.sharedCompanyHandoffs ?? []) {
|
|
249
|
+
console.log(` handoff: ${h.companySlug} to ${h.toEmail}`);
|
|
250
|
+
}
|
|
237
251
|
if (!result.ok) {
|
|
238
252
|
// Non-zero exit so a scripted caller can react. The summary line above
|
|
239
253
|
// already shows which step failed.
|
|
@@ -253,12 +267,14 @@ export function registerResetTestAccountCommand(staff, deps = {}) {
|
|
|
253
267
|
.argument("<email>", "Designated test-account email (must be in the registry)")
|
|
254
268
|
.option("--execute", "Actually delete artifacts. Prompts for a typed-email confirmation.")
|
|
255
269
|
.option("-y, --yes", "Skip the typed-email confirmation prompt (still sends confirmEmail).")
|
|
270
|
+
.option("--handoff-shared-companies", "Keep a company with real members and hand it to one, instead of refusing the reset.")
|
|
256
271
|
.action(async (email, opts) => {
|
|
257
272
|
try {
|
|
258
273
|
await runResetTestAccount({
|
|
259
274
|
email,
|
|
260
275
|
execute: opts.execute === true,
|
|
261
276
|
yes: opts.yes === true,
|
|
277
|
+
handoffSharedCompanies: opts.handoffSharedCompanies === true,
|
|
262
278
|
}, deps);
|
|
263
279
|
}
|
|
264
280
|
catch (err) {
|