@indigoai-us/hq-cli 5.271.0 → 5.273.0-alpha.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 CHANGED
@@ -2,6 +2,22 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.273.0-alpha.0] — 2026-09-28
6
+
7
+ ### Added
8
+
9
+ - `hq daemon sync` reports runner ownership and health, starts an immediate sync pass, pauses or resumes sync, and sets per-company sync mode. The daemon waits for an existing desktop runner to release its lease before starting its own.
10
+
11
+ ## [5.272.0] — 2026-09-28
12
+
13
+ ### Added
14
+
15
+ - Add `hq core ontology merge-copies`, gated by
16
+ `checkpoint.ontology.merge-copies`. It merges each `<name> N.md` entity copy
17
+ into its own original (aliases, back-references, relationship links and
18
+ signal count), repoints links to the copy, and with `--delete` removes merged
19
+ copies from the vault. Dry run by default.
20
+
5
21
  ## [5.271.0] — 2026-09-28
6
22
 
7
23
  ### Fixed
@@ -5148,6 +5148,33 @@ export declare const COMMAND_CATALOG: readonly [{
5148
5148
  readonly description: "HQ installation to operate on";
5149
5149
  }];
5150
5150
  readonly subcommands: readonly [];
5151
+ }, {
5152
+ readonly name: "merge-copies";
5153
+ readonly description: "Merge '<name> N.md' entity copies into their original, then delete them from the vault (dry run by default)";
5154
+ readonly aliases: readonly [];
5155
+ readonly hidden: false;
5156
+ readonly usage: "[options]";
5157
+ readonly arguments: readonly [];
5158
+ readonly options: readonly [{
5159
+ readonly flags: "--company <slug>";
5160
+ readonly description: "company to change";
5161
+ }, {
5162
+ readonly flags: "--apply";
5163
+ readonly description: "merge each copy into its original and re-point links (writes entity files)";
5164
+ }, {
5165
+ readonly flags: "--delete";
5166
+ readonly description: "delete fully merged copies from the vault by exact key (after sync pushed the merge)";
5167
+ }, {
5168
+ readonly flags: "--limit <n>";
5169
+ readonly description: "handle at most this many copies";
5170
+ }, {
5171
+ readonly flags: "--json";
5172
+ readonly description: "print JSON";
5173
+ }, {
5174
+ readonly flags: "--hq-root <path>";
5175
+ readonly description: "HQ installation to operate on";
5176
+ }];
5177
+ readonly subcommands: readonly [];
5151
5178
  }, {
5152
5179
  readonly name: "apply";
5153
5180
  readonly description: "Validate the run's entity candidates and write them to the payload company";
@@ -9071,6 +9098,120 @@ export declare const COMMAND_CATALOG: readonly [{
9071
9098
  readonly description: "Print machine-readable JSON";
9072
9099
  }];
9073
9100
  readonly subcommands: readonly [];
9101
+ }, {
9102
+ readonly name: "sync";
9103
+ readonly description: "Inspect and control the daemon's company sync runner";
9104
+ readonly aliases: readonly [];
9105
+ readonly hidden: false;
9106
+ readonly usage: "[options] [command]";
9107
+ readonly arguments: readonly [];
9108
+ readonly options: readonly [];
9109
+ readonly subcommands: readonly [{
9110
+ readonly name: "status";
9111
+ readonly description: "Show sync ownership, heartbeat, last pass and log path";
9112
+ readonly aliases: readonly [];
9113
+ readonly hidden: false;
9114
+ readonly usage: "[options]";
9115
+ readonly arguments: readonly [];
9116
+ readonly options: readonly [{
9117
+ readonly flags: "--json";
9118
+ readonly description: "Print machine-readable JSON";
9119
+ }];
9120
+ readonly subcommands: readonly [];
9121
+ }, {
9122
+ readonly name: "now";
9123
+ readonly description: "Start a fresh sync pass immediately";
9124
+ readonly aliases: readonly [];
9125
+ readonly hidden: false;
9126
+ readonly usage: "[options]";
9127
+ readonly arguments: readonly [];
9128
+ readonly options: readonly [{
9129
+ readonly flags: "--json";
9130
+ readonly description: "Print machine-readable JSON";
9131
+ }];
9132
+ readonly subcommands: readonly [];
9133
+ }, {
9134
+ readonly name: "pause";
9135
+ readonly description: "Pause daemon sync until resumed";
9136
+ readonly aliases: readonly [];
9137
+ readonly hidden: false;
9138
+ readonly usage: "[options]";
9139
+ readonly arguments: readonly [];
9140
+ readonly options: readonly [{
9141
+ readonly flags: "--json";
9142
+ readonly description: "Print machine-readable JSON";
9143
+ }];
9144
+ readonly subcommands: readonly [];
9145
+ }, {
9146
+ readonly name: "resume";
9147
+ readonly description: "Resume daemon sync after a pause";
9148
+ readonly aliases: readonly [];
9149
+ readonly hidden: false;
9150
+ readonly usage: "[options]";
9151
+ readonly arguments: readonly [];
9152
+ readonly options: readonly [{
9153
+ readonly flags: "--json";
9154
+ readonly description: "Print machine-readable JSON";
9155
+ }];
9156
+ readonly subcommands: readonly [];
9157
+ }, {
9158
+ readonly name: "take-ownership";
9159
+ readonly description: "Persist daemon ownership of desktop sync and reconcile";
9160
+ readonly aliases: readonly [];
9161
+ readonly hidden: false;
9162
+ readonly usage: "[options]";
9163
+ readonly arguments: readonly [];
9164
+ readonly options: readonly [{
9165
+ readonly flags: "--json";
9166
+ readonly description: "Print machine-readable JSON";
9167
+ }];
9168
+ readonly subcommands: readonly [];
9169
+ }, {
9170
+ readonly name: "mode";
9171
+ readonly description: "Read or change a company's sync footprint";
9172
+ readonly aliases: readonly [];
9173
+ readonly hidden: false;
9174
+ readonly usage: "[options] [command]";
9175
+ readonly arguments: readonly [];
9176
+ readonly options: readonly [];
9177
+ readonly subcommands: readonly [{
9178
+ readonly name: "get";
9179
+ readonly description: "Read a company's current all/shared sync mode";
9180
+ readonly aliases: readonly [];
9181
+ readonly hidden: false;
9182
+ readonly usage: "[options] <company>";
9183
+ readonly arguments: readonly [{
9184
+ readonly name: "company";
9185
+ readonly required: true;
9186
+ readonly variadic: false;
9187
+ }];
9188
+ readonly options: readonly [{
9189
+ readonly flags: "--json";
9190
+ readonly description: "Print machine-readable JSON";
9191
+ }];
9192
+ readonly subcommands: readonly [];
9193
+ }, {
9194
+ readonly name: "set";
9195
+ readonly description: "Set a company's sync mode to all or shared";
9196
+ readonly aliases: readonly [];
9197
+ readonly hidden: false;
9198
+ readonly usage: "[options] <company> <mode>";
9199
+ readonly arguments: readonly [{
9200
+ readonly name: "company";
9201
+ readonly required: true;
9202
+ readonly variadic: false;
9203
+ }, {
9204
+ readonly name: "mode";
9205
+ readonly required: true;
9206
+ readonly variadic: false;
9207
+ }];
9208
+ readonly options: readonly [{
9209
+ readonly flags: "--json";
9210
+ readonly description: "Print machine-readable JSON";
9211
+ }];
9212
+ readonly subcommands: readonly [];
9213
+ }];
9214
+ }];
9074
9215
  }, {
9075
9216
  readonly name: "logs";
9076
9217
  readonly description: "Print the supervisor log, or one service's log";
@@ -6657,6 +6657,41 @@ export const COMMAND_CATALOG = [
6657
6657
  ],
6658
6658
  "subcommands": []
6659
6659
  },
6660
+ {
6661
+ "name": "merge-copies",
6662
+ "description": "Merge '<name> N.md' entity copies into their original, then delete them from the vault (dry run by default)",
6663
+ "aliases": [],
6664
+ "hidden": false,
6665
+ "usage": "[options]",
6666
+ "arguments": [],
6667
+ "options": [
6668
+ {
6669
+ "flags": "--company <slug>",
6670
+ "description": "company to change"
6671
+ },
6672
+ {
6673
+ "flags": "--apply",
6674
+ "description": "merge each copy into its original and re-point links (writes entity files)"
6675
+ },
6676
+ {
6677
+ "flags": "--delete",
6678
+ "description": "delete fully merged copies from the vault by exact key (after sync pushed the merge)"
6679
+ },
6680
+ {
6681
+ "flags": "--limit <n>",
6682
+ "description": "handle at most this many copies"
6683
+ },
6684
+ {
6685
+ "flags": "--json",
6686
+ "description": "print JSON"
6687
+ },
6688
+ {
6689
+ "flags": "--hq-root <path>",
6690
+ "description": "HQ installation to operate on"
6691
+ }
6692
+ ],
6693
+ "subcommands": []
6694
+ },
6660
6695
  {
6661
6696
  "name": "apply",
6662
6697
  "description": "Validate the run's entity candidates and write them to the payload company",
@@ -11732,6 +11767,150 @@ export const COMMAND_CATALOG = [
11732
11767
  ],
11733
11768
  "subcommands": []
11734
11769
  },
11770
+ {
11771
+ "name": "sync",
11772
+ "description": "Inspect and control the daemon's company sync runner",
11773
+ "aliases": [],
11774
+ "hidden": false,
11775
+ "usage": "[options] [command]",
11776
+ "arguments": [],
11777
+ "options": [],
11778
+ "subcommands": [
11779
+ {
11780
+ "name": "status",
11781
+ "description": "Show sync ownership, heartbeat, last pass and log path",
11782
+ "aliases": [],
11783
+ "hidden": false,
11784
+ "usage": "[options]",
11785
+ "arguments": [],
11786
+ "options": [
11787
+ {
11788
+ "flags": "--json",
11789
+ "description": "Print machine-readable JSON"
11790
+ }
11791
+ ],
11792
+ "subcommands": []
11793
+ },
11794
+ {
11795
+ "name": "now",
11796
+ "description": "Start a fresh sync pass immediately",
11797
+ "aliases": [],
11798
+ "hidden": false,
11799
+ "usage": "[options]",
11800
+ "arguments": [],
11801
+ "options": [
11802
+ {
11803
+ "flags": "--json",
11804
+ "description": "Print machine-readable JSON"
11805
+ }
11806
+ ],
11807
+ "subcommands": []
11808
+ },
11809
+ {
11810
+ "name": "pause",
11811
+ "description": "Pause daemon sync until resumed",
11812
+ "aliases": [],
11813
+ "hidden": false,
11814
+ "usage": "[options]",
11815
+ "arguments": [],
11816
+ "options": [
11817
+ {
11818
+ "flags": "--json",
11819
+ "description": "Print machine-readable JSON"
11820
+ }
11821
+ ],
11822
+ "subcommands": []
11823
+ },
11824
+ {
11825
+ "name": "resume",
11826
+ "description": "Resume daemon sync after a pause",
11827
+ "aliases": [],
11828
+ "hidden": false,
11829
+ "usage": "[options]",
11830
+ "arguments": [],
11831
+ "options": [
11832
+ {
11833
+ "flags": "--json",
11834
+ "description": "Print machine-readable JSON"
11835
+ }
11836
+ ],
11837
+ "subcommands": []
11838
+ },
11839
+ {
11840
+ "name": "take-ownership",
11841
+ "description": "Persist daemon ownership of desktop sync and reconcile",
11842
+ "aliases": [],
11843
+ "hidden": false,
11844
+ "usage": "[options]",
11845
+ "arguments": [],
11846
+ "options": [
11847
+ {
11848
+ "flags": "--json",
11849
+ "description": "Print machine-readable JSON"
11850
+ }
11851
+ ],
11852
+ "subcommands": []
11853
+ },
11854
+ {
11855
+ "name": "mode",
11856
+ "description": "Read or change a company's sync footprint",
11857
+ "aliases": [],
11858
+ "hidden": false,
11859
+ "usage": "[options] [command]",
11860
+ "arguments": [],
11861
+ "options": [],
11862
+ "subcommands": [
11863
+ {
11864
+ "name": "get",
11865
+ "description": "Read a company's current all/shared sync mode",
11866
+ "aliases": [],
11867
+ "hidden": false,
11868
+ "usage": "[options] <company>",
11869
+ "arguments": [
11870
+ {
11871
+ "name": "company",
11872
+ "required": true,
11873
+ "variadic": false
11874
+ }
11875
+ ],
11876
+ "options": [
11877
+ {
11878
+ "flags": "--json",
11879
+ "description": "Print machine-readable JSON"
11880
+ }
11881
+ ],
11882
+ "subcommands": []
11883
+ },
11884
+ {
11885
+ "name": "set",
11886
+ "description": "Set a company's sync mode to all or shared",
11887
+ "aliases": [],
11888
+ "hidden": false,
11889
+ "usage": "[options] <company> <mode>",
11890
+ "arguments": [
11891
+ {
11892
+ "name": "company",
11893
+ "required": true,
11894
+ "variadic": false
11895
+ },
11896
+ {
11897
+ "name": "mode",
11898
+ "required": true,
11899
+ "variadic": false
11900
+ }
11901
+ ],
11902
+ "options": [
11903
+ {
11904
+ "flags": "--json",
11905
+ "description": "Print machine-readable JSON"
11906
+ }
11907
+ ],
11908
+ "subcommands": []
11909
+ }
11910
+ ]
11911
+ }
11912
+ ]
11913
+ },
11735
11914
  {
11736
11915
  "name": "logs",
11737
11916
  "description": "Print the supervisor log, or one service's log",
@@ -9,12 +9,15 @@
9
9
  */
10
10
  import { Command } from "commander";
11
11
  import { type FlagReader } from "../lib/checkpoint-ontology/plan.js";
12
+ import { type DeleteVaultKey } from "../lib/checkpoint-ontology/merge-copies.js";
12
13
  export declare const ONTOLOGY_PAYLOAD_FILE = "payload.json";
13
14
  /** The hq-flags read, scoped to the payload company. */
14
15
  export declare const registryFlagReader: FlagReader;
15
16
  export interface OntologyCommandDeps {
16
17
  readFlag?: FlagReader;
17
18
  now?: () => Date;
19
+ /** Builds the exact-key vault delete for a company uid. */
20
+ vaultDeleter?: (companyUid: string) => Promise<DeleteVaultKey>;
18
21
  }
19
22
  export declare function runOntologyPlan(hqRoot: string, runDirOption: string | undefined, deps?: OntologyCommandDeps): Promise<string>;
20
23
  export declare function runOntologyApply(hqRoot: string, runDirOption: string | undefined, deps?: OntologyCommandDeps): string;
@@ -24,5 +27,18 @@ export declare function runOntologyApply(hqRoot: string, runDirOption: string |
24
27
  * `checkpoint.ontology.multi-writer`.
25
28
  */
26
29
  export declare function runOntologyDuplicates(hqRoot: string, slug: string | undefined, json: boolean, deps?: OntologyCommandDeps): Promise<string>;
30
+ export interface MergeCopiesCommandOptions {
31
+ apply?: boolean;
32
+ delete?: boolean;
33
+ limit?: number;
34
+ json?: boolean;
35
+ }
36
+ /**
37
+ * `hq core ontology merge-copies`: merge `<slug> <n>.md` copies into their
38
+ * original (`--apply`), then delete merged copies from the vault
39
+ * (`--delete`). Without either it only reports. Gated by
40
+ * `checkpoint.ontology.merge-copies` and `checkpoint.ontology.multi-writer`.
41
+ */
42
+ export declare function runOntologyMergeCopies(hqRoot: string, slug: string | undefined, options: MergeCopiesCommandOptions, deps?: OntologyCommandDeps): Promise<string>;
27
43
  export declare function registerCoreOntologyCommand(core: Command): void;
28
44
  //# sourceMappingURL=core-ontology.d.ts.map
@@ -16,6 +16,9 @@ import { applyOntologyRun, ApplyError } from "../lib/checkpoint-ontology/apply.j
16
16
  import { resolveOntologyCompany, WriteBoundsError } from "../lib/checkpoint-ontology/company.js";
17
17
  import { findDuplicateGroups, summarizeMergeBack, } from "../lib/checkpoint-ontology/multi-writer.js";
18
18
  import { buildEntityIndex } from "../lib/checkpoint-ontology/tree.js";
19
+ import { MERGE_COPIES_FLAG, mergeCopies, } from "../lib/checkpoint-ontology/merge-copies.js";
20
+ import { callDeleteEndpoint } from "./files.js";
21
+ import { resolveVaultCredential } from "../utils/resolve-vault-credential.js";
19
22
  export const ONTOLOGY_PAYLOAD_FILE = "payload.json";
20
23
  class OntologyUsageError extends Error {
21
24
  }
@@ -124,6 +127,72 @@ export async function runOntologyDuplicates(hqRoot, slug, json, deps = {}) {
124
127
  }
125
128
  return lines.join("\n");
126
129
  }
130
+ /** Delete one exact company vault key, refusing when the server would match anything else. */
131
+ async function companyVaultDeleter(companyUid) {
132
+ const token = (await resolveVaultCredential()).token;
133
+ return async (key) => {
134
+ const preview = await callDeleteEndpoint({ token, companyUid, prefix: key, dryRun: true });
135
+ if (preview.matched === 0)
136
+ return 0;
137
+ if (preview.matched !== 1 || preview.keys.length !== 1 || preview.keys[0] !== key) {
138
+ throw new Error(`vault preview matched ${preview.matched} object(s), not exactly ${key}`);
139
+ }
140
+ return (await callDeleteEndpoint({ token, companyUid, prefix: key, dryRun: false })).deleted;
141
+ };
142
+ }
143
+ function mergeCopiesSummary(report) {
144
+ const held = report.plans.filter((plan) => plan.held).length;
145
+ const lines = [
146
+ `ontology merge-copies (${report.mode.replace("_", " ")}) in ${report.company}: ${report.plans.length} of ${report.copies_found} copies, ${held} held, ${report.skipped.length} skipped`,
147
+ ` merged ${report.merged.length}, relinked ${report.relinked.length}, deleted ${report.deleted.length}, not ready ${report.not_ready.length}, errors ${report.errors.length}`,
148
+ ` duplicate groups ${report.duplicate_groups_before} -> ${report.duplicate_groups_after}; broken entity links ${report.broken_links_before} -> ${report.broken_links_after}`,
149
+ ];
150
+ for (const plan of report.plans.filter((entry) => entry.held))
151
+ lines.push(` held ${plan.copy}: ${plan.held}`);
152
+ for (const error of report.errors)
153
+ lines.push(` error ${error.path}: ${error.error}`);
154
+ return lines.join("\n");
155
+ }
156
+ /**
157
+ * `hq core ontology merge-copies`: merge `<slug> <n>.md` copies into their
158
+ * original (`--apply`), then delete merged copies from the vault
159
+ * (`--delete`). Without either it only reports. Gated by
160
+ * `checkpoint.ontology.merge-copies` and `checkpoint.ontology.multi-writer`.
161
+ */
162
+ export async function runOntologyMergeCopies(hqRoot, slug, options, deps = {}) {
163
+ if (options.apply && options.delete) {
164
+ throw new OntologyUsageError("ontology: run --apply first, let sync push the merged files, then run --delete");
165
+ }
166
+ if (options.limit !== undefined && (!Number.isInteger(options.limit) || options.limit < 1)) {
167
+ throw new OntologyUsageError("ontology: --limit must be a positive whole number");
168
+ }
169
+ const resolution = resolveOntologyCompany(hqRoot, slug);
170
+ if (!resolution.ok)
171
+ throw new OntologyUsageError(`ontology: company rejected: ${resolution.reason}`);
172
+ const company = resolution.company;
173
+ const readFlag = deps.readFlag ?? registryFlagReader;
174
+ for (const flag of [MERGE_COPIES_FLAG, MULTI_WRITER_FLAG]) {
175
+ if (!(await readFlag(flag, company))) {
176
+ return options.json ? JSON.stringify({ company: company.slug, skipped: `${flag} off` }) : `ontology merge-copies: skipped (${flag} is off)`;
177
+ }
178
+ }
179
+ const mode = options.apply ? "merge" : options.delete ? "delete" : "dry_run";
180
+ let deleteVaultKey;
181
+ if (mode === "delete") {
182
+ if (!company.cloudUid)
183
+ throw new OntologyUsageError("ontology: the company has no cloud_uid in the manifest");
184
+ deleteVaultKey = await (deps.vaultDeleter ?? companyVaultDeleter)(company.cloudUid);
185
+ }
186
+ const report = await mergeCopies({
187
+ hqRoot,
188
+ company,
189
+ mode,
190
+ now: deps.now?.() ?? new Date(),
191
+ ...(options.limit !== undefined ? { limit: options.limit } : {}),
192
+ ...(deleteVaultKey ? { deleteVaultKey } : {}),
193
+ });
194
+ return options.json ? JSON.stringify(report, null, 2) : mergeCopiesSummary(report);
195
+ }
127
196
  function fail(error) {
128
197
  if (error instanceof OntologyUsageError || error instanceof ApplyError || error instanceof WriteBoundsError) {
129
198
  process.stderr.write(`${error.message}\n`);
@@ -163,6 +232,23 @@ export function registerCoreOntologyCommand(core) {
163
232
  fail(error);
164
233
  }
165
234
  });
235
+ ontology
236
+ .command("merge-copies")
237
+ .description("Merge '<name> N.md' entity copies into their original, then delete them from the vault (dry run by default)")
238
+ .requiredOption("--company <slug>", "company to change")
239
+ .option("--apply", "merge each copy into its original and re-point links (writes entity files)")
240
+ .option("--delete", "delete fully merged copies from the vault by exact key (after sync pushed the merge)")
241
+ .option("--limit <n>", "handle at most this many copies", (value) => Number(value))
242
+ .option("--json", "print JSON")
243
+ .option("--hq-root <path>", "HQ installation to operate on")
244
+ .action(async (options) => {
245
+ try {
246
+ process.stdout.write(`${await runOntologyMergeCopies(rootFor(options), options.company, options)}\n`);
247
+ }
248
+ catch (error) {
249
+ fail(error);
250
+ }
251
+ });
166
252
  ontology
167
253
  .command("apply")
168
254
  .description("Validate the run's entity candidates and write them to the payload company")
@@ -16,6 +16,7 @@
16
16
  import type { Command } from "commander";
17
17
  import { type LegacyUnit } from "../lib/daemon/legacy.js";
18
18
  import type { HostApp } from "../lib/daemon/supervisor.js";
19
+ import { type SyncModeVaultClient } from "./sync-mode.js";
19
20
  import type { RunCommand } from "../lib/service-manager/launchd.js";
20
21
  export interface DaemonCommandDeps {
21
22
  env: NodeJS.ProcessEnv;
@@ -38,6 +39,8 @@ export interface DaemonCommandDeps {
38
39
  userHome: (user: string) => string;
39
40
  /** The pid is a live `hq daemon run`. */
40
41
  daemonAlive: (pid: number) => boolean;
42
+ /** Test seam for the company sync-mode commands. */
43
+ syncModeClient?: () => Promise<SyncModeVaultClient>;
41
44
  windowsUser?: string;
42
45
  windowsHeadlessLauncher?: string;
43
46
  systemUnitDir?: string;