@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.
Files changed (122) hide show
  1. package/CHANGELOG.md +398 -0
  2. package/assets/bot-workers/setup/worker.yaml +91 -34
  3. package/dist/command-catalog.generated.d.ts +96 -19
  4. package/dist/command-catalog.generated.js +124 -21
  5. package/dist/commands/bot-jobs.d.ts +21 -18
  6. package/dist/commands/bot-jobs.js +127 -39
  7. package/dist/commands/bot.d.ts +4 -3
  8. package/dist/commands/bot.js +265 -140
  9. package/dist/commands/core-jobs-schedule.js +10 -0
  10. package/dist/commands/daemon.js +37 -0
  11. package/dist/commands/files.d.ts +41 -6
  12. package/dist/commands/files.js +108 -27
  13. package/dist/commands/pack-start-bot.d.ts +4 -6
  14. package/dist/commands/pack-start-bot.js +4 -8
  15. package/dist/commands/signals-run.d.ts +45 -0
  16. package/dist/commands/signals-run.js +175 -0
  17. package/dist/commands/signals.d.ts +1 -0
  18. package/dist/commands/signals.js +3 -0
  19. package/dist/commands/sources.d.ts +11 -0
  20. package/dist/commands/sources.js +137 -25
  21. package/dist/commands/staff/reset-test-account.d.ts +10 -0
  22. package/dist/commands/staff/reset-test-account.js +18 -2
  23. package/dist/hq-cloud-sync-runner/bin/sync-runner.js +31171 -52512
  24. package/dist/hq-cloud-sync-runner/package.json +1 -1
  25. package/dist/lib/bot/api.d.ts +3 -2
  26. package/dist/lib/bot/config.d.ts +2 -0
  27. package/dist/lib/bot/fleet-cron.d.ts +121 -0
  28. package/dist/lib/bot/fleet-cron.js +319 -0
  29. package/dist/lib/bot/fleet-local.d.ts +93 -1
  30. package/dist/lib/bot/fleet-local.js +546 -34
  31. package/dist/lib/bot/fleet-owner-companies.d.ts +12 -0
  32. package/dist/lib/bot/fleet-owner-companies.js +30 -0
  33. package/dist/lib/bot/fleet-watch.d.ts +25 -0
  34. package/dist/lib/bot/fleet-watch.js +51 -0
  35. package/dist/lib/bot/index.d.ts +1 -1
  36. package/dist/lib/bot/index.js +1 -1
  37. package/dist/lib/bot/job-run.d.ts +5 -4
  38. package/dist/lib/bot/job-run.js +5 -4
  39. package/dist/lib/bot/jobs/hot-signal-sync.d.ts +57 -3
  40. package/dist/lib/bot/jobs/hot-signal-sync.js +163 -4
  41. package/dist/lib/bot/local-jobs.d.ts +13 -2
  42. package/dist/lib/bot/local-jobs.js +17 -4
  43. package/dist/lib/bot/progress.d.ts +14 -0
  44. package/dist/lib/bot/progress.js +47 -1
  45. package/dist/lib/bot/run.d.ts +55 -2
  46. package/dist/lib/bot/run.js +192 -10
  47. package/dist/lib/bot/setup-tool-offer.d.ts +1 -0
  48. package/dist/lib/bot/setup-tool-offer.js +16 -0
  49. package/dist/lib/bot/signals-engine-flag.d.ts +18 -0
  50. package/dist/lib/bot/signals-engine-flag.js +30 -0
  51. package/dist/lib/daemon/catalog.d.ts +4 -0
  52. package/dist/lib/daemon/catalog.js +29 -9
  53. package/dist/lib/daemon/legacy.d.ts +6 -0
  54. package/dist/lib/daemon/legacy.js +16 -10
  55. package/dist/lib/daemon/run.d.ts +1 -0
  56. package/dist/lib/daemon/run.js +10 -0
  57. package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
  58. package/dist/lib/flag-registry-worker-source.generated.js +1 -1
  59. package/dist/lib/flag-registry.d.ts +13 -1
  60. package/dist/lib/flag-registry.js +46 -0
  61. package/dist/lib/hot-channels/adapters/attio.d.ts +75 -2
  62. package/dist/lib/hot-channels/adapters/attio.js +367 -29
  63. package/dist/lib/hot-channels/adapters/gmail.d.ts +14 -0
  64. package/dist/lib/hot-channels/adapters/gmail.js +59 -2
  65. package/dist/lib/hot-channels/adapters/linear.d.ts +24 -0
  66. package/dist/lib/hot-channels/adapters/linear.js +71 -1
  67. package/dist/lib/hot-channels/adapters/meetings.js +4 -1
  68. package/dist/lib/hot-channels/adapters/slack.js +7 -3
  69. package/dist/lib/hot-channels/alert.js +1 -1
  70. package/dist/lib/hot-channels/contract.d.ts +11 -0
  71. package/dist/lib/hot-channels/contract.js +1 -0
  72. package/dist/lib/hot-channels/cursors.js +1 -1
  73. package/dist/lib/hot-channels/landing.d.ts +6 -2
  74. package/dist/lib/hot-channels/landing.js +13 -6
  75. package/dist/lib/hot-channels/ledger.js +2 -0
  76. package/dist/lib/hot-channels/loop.js +18 -4
  77. package/dist/lib/hot-channels/registry.js +9 -1
  78. package/dist/lib/hot-channels/run.d.ts +18 -0
  79. package/dist/lib/hot-channels/run.js +15 -4
  80. package/dist/lib/hot-channels/signals-pass.d.ts +1 -0
  81. package/dist/lib/hot-channels/signals-pass.js +7 -5
  82. package/dist/lib/hot-channels/sync-lock.js +22 -6
  83. package/dist/lib/mesh/live/daemon/install.d.ts +22 -0
  84. package/dist/lib/mesh/live/daemon/install.js +117 -30
  85. package/dist/lib/signals/adapters-catalog.d.ts +77 -0
  86. package/dist/lib/signals/adapters-catalog.js +194 -0
  87. package/dist/lib/signals/engine.d.ts +196 -0
  88. package/dist/lib/signals/engine.js +495 -0
  89. package/dist/lib/signals/failure-alert.d.ts +89 -0
  90. package/dist/lib/signals/failure-alert.js +175 -0
  91. package/dist/lib/signals/local-date.d.ts +3 -0
  92. package/dist/lib/signals/local-date.js +6 -0
  93. package/dist/lib/signals/projection.d.ts +42 -0
  94. package/dist/lib/signals/projection.js +67 -0
  95. package/dist/lib/signals/reply.d.ts +112 -0
  96. package/dist/lib/signals/reply.js +368 -0
  97. package/dist/lib/signals/run-lock.d.ts +36 -0
  98. package/dist/lib/signals/run-lock.js +148 -0
  99. package/dist/lib/signals/run-record.d.ts +24 -0
  100. package/dist/lib/signals/run-record.js +82 -0
  101. package/dist/lib/signals/sanitize.d.ts +105 -0
  102. package/dist/lib/signals/sanitize.js +439 -0
  103. package/dist/lib/signals/settings.d.ts +10 -0
  104. package/dist/lib/signals/settings.js +48 -0
  105. package/dist/lib/signals/steps/adapters.d.ts +14 -0
  106. package/dist/lib/signals/steps/adapters.js +156 -0
  107. package/dist/lib/signals/steps/entity-similarity.d.ts +10 -0
  108. package/dist/lib/signals/steps/entity-similarity.js +47 -0
  109. package/dist/lib/signals/steps/extract.d.ts +113 -0
  110. package/dist/lib/signals/steps/extract.js +765 -0
  111. package/dist/lib/signals/steps/garden.d.ts +24 -0
  112. package/dist/lib/signals/steps/garden.js +250 -0
  113. package/dist/lib/signals/steps/types.d.ts +112 -0
  114. package/dist/lib/signals/steps/types.js +4 -0
  115. package/dist/lib/signals/steps/upload.d.ts +43 -0
  116. package/dist/lib/signals/steps/upload.js +324 -0
  117. package/dist/lib/signals/write-guard.d.ts +63 -0
  118. package/dist/lib/signals/write-guard.js +287 -0
  119. package/dist/utils/resident-package-preload.js +16 -0
  120. package/package.json +9 -9
  121. package/dist/lib/bot/fleet-default-flag.d.ts +0 -31
  122. package/dist/lib/bot/fleet-default-flag.js +0 -127
@@ -91,6 +91,16 @@ function parseNaturalLanguage(text) {
91
91
  return [null, "hour interval must be 1..23"];
92
92
  return [`0 */${hours} * * *`, `Every ${hours} hours`];
93
93
  }
94
+ // "every morning at 6" / "every evening at 7": a bare hour takes the part of
95
+ // day's meridiem (morning = am, afternoon/evening/night = pm).
96
+ match = /^(?:every\s+)?(morning|afternoon|evening|night)\s+(?:at\s+)?(\d{1,2}(?::\d{2})?)$/.exec(normalized);
97
+ if (match) {
98
+ const clock = parseClock(match[2] + (match[1] === "morning" ? "am" : "pm"));
99
+ if (!clock)
100
+ return [null, "could not parse time: " + match[2]];
101
+ const [hour, minute] = clock;
102
+ return [String(minute) + " " + String(hour) + " * * *", "Every day at " + formatClock(hour, minute)];
103
+ }
94
104
  match = /^(?:every\s+)?(weekday|weekdays|day|daily)\s+(?:at\s+)?(.+)$/.exec(normalized);
95
105
  if (match) {
96
106
  const clock = parseClock(match[2]);
@@ -136,6 +136,7 @@ async function loadDefaults(over) {
136
136
  windowsSid: over.windowsSid ?? readWindowsSid(run),
137
137
  windowsUser: over.windowsUser ?? mesh.resolveWindowsUser(env),
138
138
  }).state,
139
+ windowsRecordedMeshTask: platform === "win32" && mesh ? () => mesh.recordedWindowsMeshTaskName(dir ?? home, env) : undefined,
139
140
  })),
140
141
  resolveHqRoot: over.resolveHqRoot ?? ((flag) => resolveDaemonHqRoot(flag, { env, cwd: process.cwd(), home })),
141
142
  userHome: over.userHome ?? ((user) => loginHome(user, run)),
@@ -1549,6 +1550,42 @@ export function registerDaemonCommand(program, over = {}) {
1549
1550
  .command("self-update")
1550
1551
  .description("Update the hq CLI, Claude, Codex and Grok, then restart the daemon onto a new CLI")
1551
1552
  .action(wrap((d) => runServiceSelfUpdate(d)));
1553
+ service
1554
+ .command("fleet-bots-update")
1555
+ .description("Move each installed HQ Fleet bot to the current runtime release while it is idle, and restart it")
1556
+ .action(wrap(async (d) => {
1557
+ const [{ runFleetBotsUpdate, listFleetBots }, local, bot, { botsRoot }, { hasPromotionHold }] = await Promise.all([
1558
+ import("../lib/bot/fleet-watch.js"),
1559
+ import("../lib/bot/fleet-local.js"),
1560
+ import("./bot.js"),
1561
+ import("../lib/bot/paths.js"),
1562
+ import("../lib/bot/promotion-hold.js"),
1563
+ ]);
1564
+ const alive = (pid) => {
1565
+ try {
1566
+ process.kill(pid, 0);
1567
+ return true;
1568
+ }
1569
+ catch (error) {
1570
+ return error.code === "EPERM";
1571
+ }
1572
+ };
1573
+ await runFleetBotsUpdate({
1574
+ listBots: () => listFleetBots(botsRoot()),
1575
+ target: () => local.resolveFleetLocalPointerTarget(),
1576
+ installed: (b) => {
1577
+ if (b.config.enabled === false)
1578
+ return false;
1579
+ const pid = local.fleetLocalGatewayPid(b.config.agentUid);
1580
+ return pid !== null && alive(pid);
1581
+ },
1582
+ heldForPromotion: (b) => hasPromotionHold(b.dir),
1583
+ needsAdoption: (b, release) => local.fleetLocalProfileNeedsAdoption(b.config.agentUid, { release }),
1584
+ busy: (b) => local.fleetLocalBusyReason(b.config.agentUid),
1585
+ update: (b) => bot.updateFleetRuntimeBot(b.name, { busy: () => local.fleetLocalBusyReason(b.config.agentUid) }),
1586
+ log: (line) => d.out(line),
1587
+ });
1588
+ }));
1552
1589
  service
1553
1590
  .command("sync-core-reconcile")
1554
1591
  .description("Read each company's new-sync-engine switch and write this machine's engine homes and legacy off-switches")
@@ -242,14 +242,41 @@ export declare function runFilesRestore(params: {
242
242
  }, deps?: {
243
243
  confirm?: ConfirmFn;
244
244
  }): Promise<FilesRestoreResponse | undefined>;
245
- export interface FilesPurgeResponse {
245
+ /** Grace period hq-pro applies before a scheduled version purge runs. */
246
+ export declare const PURGE_GRACE_DAYS = 7;
247
+ /**
248
+ * Outcome of `hq files purge`. A current hq-pro schedules the purge
249
+ * (`scheduled`); a server from before the 7-day window deletes the version
250
+ * at once (`purged`), and the CLI reports that rather than a schedule.
251
+ */
252
+ export type FilesPurgeResult = {
253
+ key: string;
254
+ versionId: string;
255
+ status: "scheduled";
256
+ purgeAt: string;
257
+ } | {
246
258
  key: string;
247
- purgedVersionId: string;
259
+ versionId: string;
260
+ status: "purged";
261
+ };
262
+ export interface FilesPurgeCancelResult {
263
+ key: string;
264
+ versionId: string;
265
+ status: "cancelled";
248
266
  }
267
+ /** The exact command that cancels this purge, including the scope flags. */
268
+ export declare function formatPurgeCancelCommand(params: {
269
+ key: string;
270
+ versionId: string;
271
+ personal: boolean;
272
+ companySlug: string | undefined;
273
+ }): string;
249
274
  /**
250
- * feedback_fc711fc3: permanently delete one noncurrent version. The server
251
- * (hq-pro POST /v1/files/restore, action "purge") enforces owner-only access,
252
- * refuses the current version and delete markers, and audits before deleting.
275
+ * feedback_fc711fc3: schedule permanent deletion of one noncurrent version.
276
+ * The server (hq-pro POST /v1/files/restore, action "purge") enforces
277
+ * owner-only access, refuses the current version and delete markers, audits,
278
+ * and schedules the deletion PURGE_GRACE_DAYS out (cancelable). An older
279
+ * server deletes immediately; the CLI reports what the server says it did.
253
280
  */
254
281
  export declare function runFilesPurge(params: {
255
282
  key: string;
@@ -259,7 +286,15 @@ export declare function runFilesPurge(params: {
259
286
  companySlug: string | undefined;
260
287
  }, deps?: {
261
288
  confirm?: ConfirmFn;
262
- }): Promise<FilesPurgeResponse | undefined>;
289
+ now?: () => Date;
290
+ }): Promise<FilesPurgeResult | undefined>;
291
+ /** Cancel a scheduled purge (hq-pro action "cancel-purge"). */
292
+ export declare function runFilesPurgeCancel(params: {
293
+ key: string;
294
+ versionId: string;
295
+ personal: boolean;
296
+ companySlug: string | undefined;
297
+ }): Promise<FilesPurgeCancelResult>;
263
298
  export declare function runFilesTrash(params: {
264
299
  prefix: string;
265
300
  cursor?: string;
@@ -733,15 +733,25 @@ export function registerFilesCommand(program) {
733
733
  });
734
734
  files
735
735
  .command("purge <path>")
736
- .description("Permanently delete ONE retained (noncurrent) version of an exact vault key, for example a version that held data which must not live in HQ. Owner only; audited; cannot be undone. Delete the file first if the version is current. Use --personal for your personal vault.")
736
+ .description("Schedule permanent deletion of ONE retained (noncurrent) version of an exact vault key, for example a version that held data which must not live in HQ. The version is deleted 7 days later and can be cancelled until then with --cancel. Owner only; audited. Delete the file first if the version is current. Use --personal for your personal vault.")
737
737
  .option("--personal", "Target your own personal vault instead of a company vault (mutually exclusive with --company)")
738
738
  .requiredOption("--version-id <id>", "Exact version ID to purge (see `hq files versions`)")
739
+ .option("--cancel", "Cancel a scheduled purge of this version instead of scheduling one")
739
740
  .option("-y, --yes", "Skip the confirmation prompt (for scripts)")
740
741
  .action(async (path, opts) => {
741
742
  try {
742
743
  const companySlug = files.opts().company;
743
744
  const personal = opts.personal === true;
744
745
  assertRecoveryScope(personal, companySlug);
746
+ if (opts.cancel === true) {
747
+ await runFilesPurgeCancel({
748
+ key: path,
749
+ versionId: opts.versionId,
750
+ personal,
751
+ companySlug,
752
+ });
753
+ return;
754
+ }
745
755
  await runFilesPurge({
746
756
  key: path,
747
757
  versionId: opts.versionId,
@@ -1791,31 +1801,39 @@ export async function runFilesRestore(params, deps = {}) {
1791
1801
  console.log(chalk.dim("New version: " + data.newVersionId));
1792
1802
  return data;
1793
1803
  }
1794
- /**
1795
- * feedback_fc711fc3: permanently delete one noncurrent version. The server
1796
- * (hq-pro POST /v1/files/restore, action "purge") enforces owner-only access,
1797
- * refuses the current version and delete markers, and audits before deleting.
1798
- */
1799
- export async function runFilesPurge(params, deps = {}) {
1800
- assertExactRecoveryKey(params.key);
1801
- if (typeof params.versionId !== "string" || params.versionId.trim() === "") {
1804
+ /** Grace period hq-pro applies before a scheduled version purge runs. */
1805
+ export const PURGE_GRACE_DAYS = 7;
1806
+ function formatPurgeDate(iso) {
1807
+ const d = new Date(iso);
1808
+ if (Number.isNaN(d.getTime()))
1809
+ return iso;
1810
+ return d.toISOString().replace("T", " ").replace(/\.\d{3}Z$/, " UTC");
1811
+ }
1812
+ function shellQuote(value) {
1813
+ return "'" + value.replace(/'/g, "'\\''") + "'";
1814
+ }
1815
+ /** The exact command that cancels this purge, including the scope flags. */
1816
+ export function formatPurgeCancelCommand(params) {
1817
+ const scope = params.personal
1818
+ ? ""
1819
+ : params.companySlug
1820
+ ? " --company " + shellQuote(params.companySlug)
1821
+ : "";
1822
+ return ("hq files" + scope + " purge --cancel " + shellQuote(params.key) +
1823
+ " --version-id " + shellQuote(params.versionId) +
1824
+ (params.personal ? " --personal" : ""));
1825
+ }
1826
+ function assertPurgeVersionId(versionId) {
1827
+ if (typeof versionId !== "string" || versionId.trim() === "") {
1802
1828
  throw new Error("--version-id is required and must not be empty.");
1803
1829
  }
1804
- const confirm = deps.confirm ?? realConfirm;
1805
- if (!params.yes) {
1806
- const ok = await confirm("Permanently delete version '" + params.versionId + "' of '" + params.key + "'" +
1807
- (params.personal ? " in your personal vault" : "") +
1808
- "? This cannot be undone.");
1809
- if (!ok) {
1810
- console.log(chalk.dim("Aborted — nothing was purged."));
1811
- return undefined;
1812
- }
1813
- }
1830
+ }
1831
+ async function postPurgeAction(params, action) {
1814
1832
  const scope = await resolveRecoveryScope(params);
1815
1833
  const body = scope.personal
1816
1834
  ? { personal: true, key: params.key }
1817
1835
  : { company: scope.companyUid, key: params.key };
1818
- body.action = "purge";
1836
+ body.action = action;
1819
1837
  body.purgeVersionId = params.versionId;
1820
1838
  // Tripwire: a server without purge support rejects `versionId: null` with
1821
1839
  // 400 instead of ignoring `action` and restoring (re-publishing) the version.
@@ -1829,16 +1847,79 @@ export async function runFilesPurge(params, deps = {}) {
1829
1847
  if (!res.ok) {
1830
1848
  const errBody = (await res.json().catch(() => ({})));
1831
1849
  const err = new FilesRecoveryHttpError(res.status, errBody.message ?? errBody.error ?? res.statusText, errBody.code);
1850
+ if (action === "cancel-purge" && res.status === 400) {
1851
+ throw new Error("The server rejected the cancellation: " + err.message + ". If the server predates the 7-day purge window it cannot cancel. Check `hq files versions` to see whether the version still exists.");
1852
+ }
1832
1853
  throw new Error(formatFilesRecoveryError(err, params.key));
1833
1854
  }
1834
- const data = (await res.json());
1835
- // Belt and braces with the tripwire above: only a server that echoes the
1836
- // purged version id has purged it.
1837
- if (data.purgedVersionId !== params.versionId) {
1838
- throw new Error("The server did not confirm the purge (it may not support purge yet). Check `hq files versions` before retrying.");
1855
+ const parsed = await res.json().catch(() => ({}));
1856
+ return parsed !== null && typeof parsed === "object" ? parsed : {};
1857
+ }
1858
+ /**
1859
+ * feedback_fc711fc3: schedule permanent deletion of one noncurrent version.
1860
+ * The server (hq-pro POST /v1/files/restore, action "purge") enforces
1861
+ * owner-only access, refuses the current version and delete markers, audits,
1862
+ * and schedules the deletion PURGE_GRACE_DAYS out (cancelable). An older
1863
+ * server deletes immediately; the CLI reports what the server says it did.
1864
+ */
1865
+ export async function runFilesPurge(params, deps = {}) {
1866
+ assertExactRecoveryKey(params.key);
1867
+ assertPurgeVersionId(params.versionId);
1868
+ const confirm = deps.confirm ?? realConfirm;
1869
+ if (!params.yes) {
1870
+ const now = (deps.now ?? (() => new Date()))();
1871
+ const expected = new Date(now.getTime() + PURGE_GRACE_DAYS * 24 * 60 * 60 * 1000);
1872
+ const ok = await confirm("Version '" + params.versionId + "' of '" + params.key + "'" +
1873
+ (params.personal ? " in your personal vault" : "") +
1874
+ " will be permanently deleted on " + formatPurgeDate(expected.toISOString()) +
1875
+ " and can be cancelled until then with `hq files purge --cancel`." +
1876
+ " (A server without the 7-day window deletes it immediately.) Continue?");
1877
+ if (!ok) {
1878
+ console.log(chalk.dim("Aborted — nothing was purged."));
1879
+ return undefined;
1880
+ }
1839
1881
  }
1840
- console.log(chalk.green("Purged version '" + data.purgedVersionId + "' of '" + params.key + "'."));
1841
- return { key: data.key ?? params.key, purgedVersionId: data.purgedVersionId };
1882
+ const data = await postPurgeAction(params, "purge");
1883
+ const key = typeof data.key === "string" ? data.key : params.key;
1884
+ const saysScheduled = data.status === "scheduled" || data.scheduled === true;
1885
+ const scheduled = saysScheduled &&
1886
+ typeof data.purgeAt === "string" &&
1887
+ !Number.isNaN(new Date(data.purgeAt).getTime()) &&
1888
+ data.purgeVersionId === params.versionId;
1889
+ if (saysScheduled && !scheduled) {
1890
+ // The server says something is scheduled but the response is incomplete.
1891
+ // Do not report "nothing happened": a deletion may be pending.
1892
+ throw new Error("The server reported a scheduled purge but did not return a valid purge date and version id. A purge of this version may be pending. To cancel it, run: " +
1893
+ formatPurgeCancelCommand(params));
1894
+ }
1895
+ if (scheduled) {
1896
+ const purgeAt = data.purgeAt;
1897
+ console.log(chalk.green("Version '" + params.versionId + "' of '" + key + "' will be permanently deleted on " +
1898
+ formatPurgeDate(purgeAt) + ". It can be cancelled until then:"));
1899
+ console.log(chalk.dim(" " + formatPurgeCancelCommand(params)));
1900
+ return { key, versionId: params.versionId, status: "scheduled", purgeAt };
1901
+ }
1902
+ // Older server: it purged at once and echoes purgedVersionId. Say so plainly.
1903
+ if (data.purgedVersionId === params.versionId) {
1904
+ console.log(chalk.yellow("The server deleted version '" + params.versionId + "' of '" + key +
1905
+ "' immediately (it does not support the 7-day purge window yet). This cannot be cancelled."));
1906
+ return { key, versionId: params.versionId, status: "purged" };
1907
+ }
1908
+ throw new Error("The server did not confirm the purge (it may not support purge yet). Check `hq files versions` before retrying.");
1909
+ }
1910
+ /** Cancel a scheduled purge (hq-pro action "cancel-purge"). */
1911
+ export async function runFilesPurgeCancel(params) {
1912
+ assertExactRecoveryKey(params.key);
1913
+ assertPurgeVersionId(params.versionId);
1914
+ const data = await postPurgeAction(params, "cancel-purge");
1915
+ const key = typeof data.key === "string" ? data.key : params.key;
1916
+ const confirmed = (data.status === "cancelled" || data.cancelled === true) &&
1917
+ data.purgeVersionId === params.versionId;
1918
+ if (!confirmed) {
1919
+ throw new Error("The server did not confirm the cancellation (it may not support the 7-day purge window yet). Check `hq files versions` to see whether the version still exists.");
1920
+ }
1921
+ console.log(chalk.green("Cancelled the scheduled purge of version '" + params.versionId + "' of '" + key + "'."));
1922
+ return { key, versionId: params.versionId, status: "cancelled" };
1842
1923
  }
1843
1924
  export async function runFilesTrash(params) {
1844
1925
  if (params.prefix.includes("*")) {
@@ -7,14 +7,12 @@
7
7
  import type { InstallPackResult } from "./pack-install.js";
8
8
  import { type StartPackBotDeps, type StartPackBotResult } from "../lib/packages/start-bot.js";
9
9
  /**
10
- * The model and thinking level a pack's setup bot runs on. Left unset, Claude
11
- * Code used the account's default model (Claude Fable 5.1 in a 2026-10-12
12
- * walkthrough), and one setup turn took nine minutes. Pack setup is guided
13
- * reading and writing: Opus 5.5 at medium thinking does it well and faster.
14
- * The person can still change either with `hq bot set`.
10
+ * The model a pack's setup bot runs on. Left unset, Claude Code used the
11
+ * account's default model (Claude Fable 5.1 in a 2026-10-12 walkthrough), and
12
+ * one setup turn took nine minutes. Pack setup is guided reading and writing:
13
+ * Opus 5.5 does it well and faster.
15
14
  */
16
15
  export declare const PACK_BOT_MODEL = "claude-opus-5-5";
17
- export declare const PACK_BOT_EFFORT = "medium";
18
16
  export declare function defaultStartPackBotDeps(): StartPackBotDeps;
19
17
  /**
20
18
  * Start the pack's setup bot after a successful install. Never throws: a bot
@@ -32,14 +32,12 @@ async function dmOwner(name, body) {
32
32
  await new BotApi({ token: async () => token }).sendDm({ toPersonUid: owner, body });
33
33
  }
34
34
  /**
35
- * The model and thinking level a pack's setup bot runs on. Left unset, Claude
36
- * Code used the account's default model (Claude Fable 5.1 in a 2026-10-12
37
- * walkthrough), and one setup turn took nine minutes. Pack setup is guided
38
- * reading and writing: Opus 5.5 at medium thinking does it well and faster.
39
- * The person can still change either with `hq bot set`.
35
+ * The model a pack's setup bot runs on. Left unset, Claude Code used the
36
+ * account's default model (Claude Fable 5.1 in a 2026-10-12 walkthrough), and
37
+ * one setup turn took nine minutes. Pack setup is guided reading and writing:
38
+ * Opus 5.5 does it well and faster.
40
39
  */
41
40
  export const PACK_BOT_MODEL = "claude-opus-5-5";
42
- export const PACK_BOT_EFFORT = "medium";
43
41
  export function defaultStartPackBotDeps() {
44
42
  return {
45
43
  botExists,
@@ -48,9 +46,7 @@ export function defaultStartPackBotDeps() {
48
46
  displayName: opts.displayName,
49
47
  intro: opts.intro,
50
48
  kickoff: opts.kickoff,
51
- lean: true,
52
49
  model: PACK_BOT_MODEL,
53
- effort: PACK_BOT_EFFORT,
54
50
  }),
55
51
  restartBot: (name) => runBotRestart(name, { output: "text" }),
56
52
  sendIntro: (name) => runBotIntro(name),
@@ -0,0 +1,45 @@
1
+ /**
2
+ * `hq signals run` — local signals engine (Hot-Signal Sync US-002).
3
+ *
4
+ * Loops over every company in scope whose signals_capture or
5
+ * ontology_capture preference is on and runs the fixed step order
6
+ * garden -> adapters -> extract -> garden -> upload, writing a run record
7
+ * under workspace/signals/runs/{date}/. The engine lives in
8
+ * src/lib/signals/engine.ts and is callable as runSignals().
9
+ */
10
+ import { Command } from "commander";
11
+ import { type RunSignalsResult, type SignalsStepChoice, type SignalsTrigger } from "../lib/signals/engine.js";
12
+ import type { SignalsRunAlertSenders } from "../lib/signals/failure-alert.js";
13
+ interface RunCliOptions {
14
+ company?: string;
15
+ dryRun?: boolean;
16
+ step: SignalsStepChoice;
17
+ trigger: SignalsTrigger;
18
+ force?: boolean;
19
+ catchUp?: boolean;
20
+ maxTurns?: number;
21
+ maxBudgetUsd?: number;
22
+ json?: boolean;
23
+ reportFound?: boolean;
24
+ hqRoot: string;
25
+ }
26
+ export declare function formatRunResult(result: RunSignalsResult, dryRun: boolean): string;
27
+ export declare function foundSignalsReply(result: RunSignalsResult, company: string | undefined, timeZone: string): string | null;
28
+ /**
29
+ * The terminal alert sender for `hq signals run` (US-018). The CLI has no
30
+ * owner-DM sender, so the terminal is the primary alert, as in
31
+ * `hq sources sync`. A printed alert does not record the hourly dedupe entry,
32
+ * so it never holds back the bot's owner DM for the same company.
33
+ */
34
+ export declare function terminalSignalsAlert(write?: (text: string) => void): SignalsRunAlertSenders;
35
+ /**
36
+ * Exit codes (run record contract v2): 0 complete, partial, skipped or the
37
+ * job already ran today; 1 failed; 2 usage or config error; 3 another run
38
+ * holds a company's lock (stdout then carries {"status":"running",...}).
39
+ */
40
+ export declare function runSignalsCli(options: RunCliOptions, overrides?: {
41
+ signalsAlert?: SignalsRunAlertSenders;
42
+ }): Promise<void>;
43
+ export declare function registerSignalsRunSubcommand(signals: Command): void;
44
+ export {};
45
+ //# sourceMappingURL=signals-run.d.ts.map
@@ -0,0 +1,175 @@
1
+ /**
2
+ * `hq signals run` — local signals engine (Hot-Signal Sync US-002).
3
+ *
4
+ * Loops over every company in scope whose signals_capture or
5
+ * ontology_capture preference is on and runs the fixed step order
6
+ * garden -> adapters -> extract -> garden -> upload, writing a run record
7
+ * under workspace/signals/runs/{date}/. The engine lives in
8
+ * src/lib/signals/engine.ts and is callable as runSignals().
9
+ */
10
+ import { InvalidArgumentError, Option } from "commander";
11
+ import chalk from "chalk";
12
+ import { DEFAULT_HQ_ROOT } from "../utils/cognito-session.js";
13
+ import { Sentry } from "../sentry.js";
14
+ import { runSignals, signalsExitCode, SignalsUsageError, SIGNALS_STEP_CHOICES, SIGNALS_TRIGGERS, } from "../lib/signals/engine.js";
15
+ import { formatRunReply } from "../lib/signals/reply.js";
16
+ function parsePositiveInt(v) {
17
+ const n = Number(v);
18
+ if (!Number.isInteger(n) || n <= 0)
19
+ throw new InvalidArgumentError("must be a positive integer");
20
+ return n;
21
+ }
22
+ function parseNonNegative(v) {
23
+ const n = Number(v);
24
+ if (!Number.isFinite(n) || n < 0)
25
+ throw new InvalidArgumentError("must be a non-negative number");
26
+ return n;
27
+ }
28
+ function envCatchUp() {
29
+ const v = process.env.HQ_SIGNALS_CATCH_UP;
30
+ return v === "1" || v === "true";
31
+ }
32
+ export function formatRunResult(result, dryRun) {
33
+ const lines = [];
34
+ lines.push(`${dryRun ? "Dry run" : "Signals run"} ${result.date}: ${result.summary.status}`);
35
+ if (result.records.length === 0 && result.locked.length === 0)
36
+ lines.push(" no companies in scope");
37
+ for (const l of result.locked) {
38
+ lines.push(` ${l.company}: running (another ${l.trigger} run started ${l.startedAt}; nothing started)`);
39
+ }
40
+ for (const r of result.records) {
41
+ let head = ` ${r.company}: ${r.status}`;
42
+ if (r.skipReason)
43
+ head += ` (${r.skipReason})`;
44
+ if (r.alreadyRanToday)
45
+ head += " (already ran today)";
46
+ if (r.partialReason)
47
+ head += ` (${r.partialReason})`;
48
+ lines.push(head);
49
+ if (r.failureReason && !r.alreadyRanToday)
50
+ lines.push(` ${r.failureReason}`);
51
+ if (r.nextRetryAt && !r.alreadyRanToday)
52
+ lines.push(` next retry after ${r.nextRetryAt}`);
53
+ for (const s of r.steps) {
54
+ if (dryRun) {
55
+ lines.push(` would run ${s.name}`);
56
+ for (const p of s.plan ?? [])
57
+ lines.push(` ${p}`);
58
+ continue;
59
+ }
60
+ const extra = s.skippedReason ? ` ${s.skippedReason}` : s.error ? ` ${s.error}` : "";
61
+ const up = s.uploaded ? ` uploaded=${s.uploaded.count} (${s.uploaded.bytes}B)` : "";
62
+ lines.push(` ${s.name.padEnd(9)} ${s.status} in=${s.itemsIn} out=${s.itemsOut}${up}${extra}`);
63
+ }
64
+ }
65
+ return `${lines.join("\n")}\n`;
66
+ }
67
+ export function foundSignalsReply(result, company, timeZone) {
68
+ const record = result.records.find((r) => !company || r.company === company);
69
+ if (!record || record.alreadyRanToday || (record.status !== "complete" && record.status !== "partial"))
70
+ return null;
71
+ const extract = record.steps.find((s) => s.name === "extract" && s.status !== "skipped");
72
+ if (!extract || extract.itemsOut <= 0)
73
+ return null;
74
+ return formatRunReply(record, result.summary, { timeZone }).text;
75
+ }
76
+ function localTimeZone() {
77
+ try {
78
+ return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
79
+ }
80
+ catch {
81
+ return "UTC";
82
+ }
83
+ }
84
+ /**
85
+ * The terminal alert sender for `hq signals run` (US-018). The CLI has no
86
+ * owner-DM sender, so the terminal is the primary alert, as in
87
+ * `hq sources sync`. A printed alert does not record the hourly dedupe entry,
88
+ * so it never holds back the bot's owner DM for the same company.
89
+ */
90
+ export function terminalSignalsAlert(write = (text) => void process.stderr.write(text)) {
91
+ return {
92
+ sendOwnerDm: async (text) => {
93
+ write(chalk.red(`${text}\n`));
94
+ },
95
+ log: (message) => write(chalk.yellow(`${message}\n`)),
96
+ // A printed alert is not a delivered owner DM: leave the hourly dedupe to the bot.
97
+ recordsDelivery: false,
98
+ };
99
+ }
100
+ /**
101
+ * Exit codes (run record contract v2): 0 complete, partial, skipped or the
102
+ * job already ran today; 1 failed; 2 usage or config error; 3 another run
103
+ * holds a company's lock (stdout then carries {"status":"running",...}).
104
+ */
105
+ export async function runSignalsCli(options, overrides = {}) {
106
+ const result = await runSignals({
107
+ hqRoot: options.hqRoot,
108
+ company: options.company,
109
+ dryRun: options.dryRun === true,
110
+ step: options.step,
111
+ trigger: options.trigger,
112
+ force: options.force === true,
113
+ catchUp: options.catchUp === true || envCatchUp(),
114
+ maxTurns: options.maxTurns,
115
+ maxBudgetUsd: options.maxBudgetUsd,
116
+ signalsAlert: overrides.signalsAlert ?? terminalSignalsAlert(),
117
+ });
118
+ const code = signalsExitCode(result);
119
+ if (options.reportFound) {
120
+ const reply = foundSignalsReply(result, options.company, localTimeZone());
121
+ if (reply)
122
+ process.stdout.write(`${reply}\n`);
123
+ if (code !== 0)
124
+ process.exitCode = code;
125
+ return;
126
+ }
127
+ const onlyLocked = result.records.length === 0 && result.locked.length === 1 ? result.locked[0] : undefined;
128
+ if (onlyLocked) {
129
+ const { status, startedAt, trigger } = onlyLocked;
130
+ process.stdout.write(`${JSON.stringify({ status, startedAt, trigger })}\n`);
131
+ }
132
+ else if (options.json) {
133
+ const payload = options.company && result.records.length === 1 ? result.records[0] : result;
134
+ process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
135
+ }
136
+ else {
137
+ process.stdout.write(formatRunResult(result, options.dryRun === true));
138
+ }
139
+ if (code !== 0)
140
+ process.exitCode = code;
141
+ }
142
+ export function registerSignalsRunSubcommand(signals) {
143
+ const run = signals.command("run");
144
+ // Usage errors (bad flag or choice) exit 2, not commander's default 1.
145
+ run.exitOverride((err) => process.exit(err.exitCode === 0 ? 0 : 2));
146
+ run
147
+ .description("Run the local signals engine (garden, adapters, extract, garden, upload) for companies with capture on")
148
+ .option("--company <slug>", "Run one company (default: every company listed in the companies manifest)")
149
+ .option("--dry-run", "Print what would run; run no mutating step and write nothing")
150
+ .addOption(new Option("--step <step>", "Run one step").choices([...SIGNALS_STEP_CHOICES]).default("all"))
151
+ .addOption(new Option("--trigger <trigger>", "What started the run").choices([...SIGNALS_TRIGGERS]).default("manual"))
152
+ .option("--force", "Rerun a job trigger that already ran today")
153
+ .option("--catch-up", "Mark the run as a catch-up run (also HQ_SIGNALS_CATCH_UP=1)")
154
+ .option("--max-turns <n>", "Model turn cap for the extract step (default from core/settings/signals.yaml, else 40)", parsePositiveInt)
155
+ .option("--max-budget-usd <x>", "Spend cap in USD for the extract step (default from core/settings/signals.yaml, else 2.00)", parseNonNegative)
156
+ .option("--json", "Print the run record as JSON")
157
+ .option("--report-found", "Print the owner's run reply only when the run found new signals; print nothing otherwise")
158
+ .option("--hq-root <path>", `HQ root (default: ${DEFAULT_HQ_ROOT})`, DEFAULT_HQ_ROOT)
159
+ .action(async (options) => {
160
+ try {
161
+ await runSignalsCli(options);
162
+ }
163
+ catch (err) {
164
+ const message = err instanceof Error ? err.message : String(err);
165
+ process.stderr.write(chalk.red(`✗ ${message}\n`));
166
+ if (err instanceof SignalsUsageError) {
167
+ process.exitCode = 2;
168
+ return;
169
+ }
170
+ Sentry.captureException(err);
171
+ process.exit(1);
172
+ }
173
+ });
174
+ }
175
+ //# sourceMappingURL=signals-run.js.map
@@ -12,6 +12,7 @@
12
12
  * hq signals get Fetch one signal by id.
13
13
  * hq signals types Print the canonical SIGNAL_TYPES enum.
14
14
  * hq signals entities List entities the caller has access to.
15
+ * hq signals run Run the local signals engine (signals-run.ts).
15
16
  *
16
17
  * Mirrors `hq sources` exactly — same flags, same TTY-aware defaults,
17
18
  * same error/Sentry plumbing. Defense-in-depth signal-type validation
@@ -12,6 +12,7 @@
12
12
  * hq signals get Fetch one signal by id.
13
13
  * hq signals types Print the canonical SIGNAL_TYPES enum.
14
14
  * hq signals entities List entities the caller has access to.
15
+ * hq signals run Run the local signals engine (signals-run.ts).
15
16
  *
16
17
  * Mirrors `hq sources` exactly — same flags, same TTY-aware defaults,
17
18
  * same error/Sentry plumbing. Defense-in-depth signal-type validation
@@ -25,6 +26,7 @@ import chalk from "chalk";
25
26
  import { SIGNAL_TYPES, assertSignalType, resolveEntity, listAvailableEntities, listSignals, getSignal, VaultClient, } from "@indigoai-us/hq-cloud";
26
27
  import { ensureCognitoToken, buildVaultConfig, DEFAULT_HQ_ROOT, } from "../utils/cognito-session.js";
27
28
  import { Sentry } from "../sentry.js";
29
+ import { registerSignalsRunSubcommand } from "./signals-run.js";
28
30
  // ---------------------------------------------------------------------------
29
31
  // Access token resolution — honors HQ_ACCESS_TOKEN for tests + CI smoke
30
32
  // ---------------------------------------------------------------------------
@@ -262,5 +264,6 @@ export function registerSignalsCommand(program) {
262
264
  .description("List entities (companies/personal) your account has access to")
263
265
  .option("--format <fmt>", "Output format: table | json (default: table for TTY, json piped)")
264
266
  .action(withErrorHandling(async (options) => runEntities(options)));
267
+ registerSignalsRunSubcommand(signals);
265
268
  }
266
269
  //# sourceMappingURL=signals.js.map
@@ -23,7 +23,9 @@
23
23
  import { Command } from "commander";
24
24
  import { type RunLedgerAdapterSummary, type RunLedgerEntry, type RunTrigger, type SignalsGeneratedFn } from "../lib/hot-channels/ledger.js";
25
25
  import type { HotChannelRegistry } from "../lib/hot-channels/registry.js";
26
+ import { type AdapterFlagReader } from "../lib/signals/adapters-catalog.js";
26
27
  import { type SelectionLoader } from "../lib/hot-channels/selection.js";
28
+ import { type FleetCronOps } from "../lib/bot/fleet-cron.js";
27
29
  import { type ScheduleReport } from "../lib/hot-channels/schedule.js";
28
30
  import { type OnDemandSyncDeps, type OnDemandSyncReport } from "../lib/hot-channels/run.js";
29
31
  type ListFormat = "table" | "json";
@@ -74,6 +76,13 @@ export interface CompanyHotChannelSyncInput {
74
76
  hqRoot: string;
75
77
  company: string;
76
78
  adapter?: string;
79
+ /** Registered adapter ids allowed to run; see OnDemandSyncInput.onlyAdapters. */
80
+ onlyAdapters?: readonly string[];
81
+ /**
82
+ * Company flag reader for the adapter gate. Defaults to the shared hq-flags
83
+ * evaluator for this company with the signed-in session.
84
+ */
85
+ isFlagOn?: AdapterFlagReader;
77
86
  registry?: HotChannelRegistry;
78
87
  now?: () => Date;
79
88
  /** `scheduled` for the bot's hot-signal-sync job; `on-demand` by default. */
@@ -131,6 +140,8 @@ export interface ScheduleOverrides {
131
140
  botsRoot?: string;
132
141
  /** Whose ledger to read; defaults to the signed-in person. */
133
142
  email?: string;
143
+ /** The HQ Fleet Runtime schedule of one bot; defaults to the bot's own runtime. */
144
+ fleetOps?: (agentUid: string) => FleetCronOps;
134
145
  }
135
146
  export declare class NoHotSignalSyncJobError extends Error {
136
147
  readonly company: string;