@indigoai-us/hq-cli 5.345.160 → 5.345.162

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 (73) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/assets/scaffold/core/scripts/session-journal.sh +116 -24
  3. package/dist/command-catalog.generated.d.ts +262 -5
  4. package/dist/command-catalog.generated.js +322 -5
  5. package/dist/command-registration-plan.d.ts +6 -0
  6. package/dist/command-registration-plan.js +1 -0
  7. package/dist/commands/agent-inbox.js +6 -2
  8. package/dist/commands/auth.js +6 -5
  9. package/dist/commands/core-session-helper-runtime.d.ts +1 -0
  10. package/dist/commands/core-session-journal-native.d.ts +1 -0
  11. package/dist/commands/core-session-journal-native.js +158 -34
  12. package/dist/commands/feedback.d.ts +12 -0
  13. package/dist/commands/feedback.js +52 -0
  14. package/dist/commands/files-browse.d.ts +8 -0
  15. package/dist/commands/files-browse.js +83 -1
  16. package/dist/commands/install-global.d.ts +2 -0
  17. package/dist/commands/install-global.js +24 -1
  18. package/dist/commands/integrations-connect.js +1 -0
  19. package/dist/commands/integrations-manage.d.ts +2 -0
  20. package/dist/commands/integrations-manage.js +6 -4
  21. package/dist/commands/lanes-link.js +5 -3
  22. package/dist/commands/lanes-loop.js +7 -7
  23. package/dist/commands/lanes-questions.js +54 -4
  24. package/dist/commands/lanes-session-start.d.ts +12 -0
  25. package/dist/commands/lanes-session-start.js +93 -12
  26. package/dist/commands/lanes.js +2 -1
  27. package/dist/commands/meetings.d.ts +11 -0
  28. package/dist/commands/meetings.js +41 -11
  29. package/dist/commands/sync-held.d.ts +87 -0
  30. package/dist/commands/sync-held.js +191 -0
  31. package/dist/hq-cloud-sync-runner/bin/sync-runner.js +3830 -2812
  32. package/dist/hq-cloud-sync-runner/package.json +1 -1
  33. package/dist/index.js +21 -0
  34. package/dist/lanes-session-start-argv.d.ts +7 -0
  35. package/dist/lanes-session-start-argv.js +9 -0
  36. package/dist/lib/agent-kit/inbox-state.d.ts +5 -4
  37. package/dist/lib/agent-kit/inbox-state.js +24 -3
  38. package/dist/lib/bot/log.d.ts +3 -2
  39. package/dist/lib/bot/progress.d.ts +23 -1
  40. package/dist/lib/bot/progress.js +43 -5
  41. package/dist/lib/bot/run.js +2 -0
  42. package/dist/lib/bot/runtime/claude.d.ts +12 -0
  43. package/dist/lib/bot/runtime/claude.js +20 -0
  44. package/dist/lib/bot/runtime/index.d.ts +10 -1
  45. package/dist/lib/bot/runtime/index.js +14 -2
  46. package/dist/lib/bot/runtime/types.d.ts +8 -0
  47. package/dist/lib/doctor/checks/ps1-shims.d.ts +21 -0
  48. package/dist/lib/doctor/checks/ps1-shims.js +82 -0
  49. package/dist/lib/doctor/fix/apply.d.ts +13 -1
  50. package/dist/lib/doctor/fix/apply.js +38 -3
  51. package/dist/lib/doctor/fix/remediation.d.ts +9 -1
  52. package/dist/lib/doctor/fix/remediation.js +22 -0
  53. package/dist/lib/doctor/registry.js +2 -0
  54. package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
  55. package/dist/lib/flag-registry-worker-source.generated.js +1 -1
  56. package/dist/lib/lanes/links.d.ts +2 -1
  57. package/dist/lib/lanes/links.js +235 -17
  58. package/dist/lib/lanes/loop.d.ts +17 -0
  59. package/dist/lib/lanes/loop.js +235 -5
  60. package/dist/lib/lanes/questions.d.ts +1 -0
  61. package/dist/lib/lanes/registry-flag.d.ts +24 -0
  62. package/dist/lib/lanes/registry-flag.js +72 -6
  63. package/dist/lib/lanes/store.d.ts +4 -2
  64. package/dist/lib/lanes/store.js +8 -8
  65. package/dist/lib/windows/ps1-shims.d.ts +60 -0
  66. package/dist/lib/windows/ps1-shims.js +152 -0
  67. package/dist/utils/self-update.d.ts +2 -0
  68. package/dist/utils/self-update.js +8 -0
  69. package/dist/utils/staged-npm-install.js +13 -2
  70. package/dist/utils/token-keychain.d.ts +11 -8
  71. package/dist/utils/token-keychain.js +36 -15
  72. package/package.json +8 -8
  73. package/scripts/install-hook-engine.mjs +12 -0
@@ -0,0 +1,191 @@
1
+ /**
2
+ * `hq sync held` and `hq sync release-held` (feedback_3bf773c5).
3
+ *
4
+ * After a different person signs in on the same computer, hq-cloud holds every
5
+ * upload and cloud delete for each sync scope (the personal vault and each
6
+ * company) until the signed-in person reviews what is held and releases it.
7
+ * These commands are the person's way to see the hold and make that decision.
8
+ *
9
+ * The hq-cloud exports (`listPushIdentityFences`, `releasePushIdentityFences`)
10
+ * ship after the hq-cloud version this CLI pins, so they are feature-detected
11
+ * on the module namespace, the same way `lib/hq-cloud-manifest.ts` does it. A
12
+ * static named import of a missing ESM export would stop every command that
13
+ * loads this file.
14
+ */
15
+ import * as hqCloud from "@indigoai-us/hq-cloud";
16
+ import chalk from "chalk";
17
+ import * as readline from "node:readline/promises";
18
+ import { VaultClient, PERSONAL_VAULT_JOURNAL_SLUG, getStateDir } from "@indigoai-us/hq-cloud";
19
+ import { buildVaultConfig, cognitoSubjectFromToken, ensureCognitoToken } from "../utils/cognito-session.js";
20
+ import { decodeJwtPayload } from "../utils/person-token-cache.js";
21
+ import { resolveCanonicalPersonUid } from "./cloud.js";
22
+ export const HELD_UNAVAILABLE_MESSAGE = "this HQ build cannot list or release held uploads yet; update HQ and try again";
23
+ export function fenceExports(mod = hqCloud) {
24
+ const list = mod.listPushIdentityFences;
25
+ const release = mod.releasePushIdentityFences;
26
+ if (typeof list !== "function" || typeof release !== "function")
27
+ return undefined;
28
+ return {
29
+ listPushIdentityFences: list,
30
+ releasePushIdentityFences: release,
31
+ };
32
+ }
33
+ export function scopeLabel(slug) {
34
+ return slug === PERSONAL_VAULT_JOURNAL_SLUG || slug === "personal" ? "personal vault" : `company ${slug}`;
35
+ }
36
+ function describe(scope) {
37
+ const since = new Date(scope.sinceMs).toISOString();
38
+ const lines = [` ${scopeLabel(scope.slug)}: held since ${since}`];
39
+ if (scope.held) {
40
+ lines.push(` ${scope.held.count} file${scope.held.count === 1 ? "" : "s"} waiting to upload (last checked ${scope.held.at})`);
41
+ for (const sample of scope.held.samplePaths.slice(0, 20))
42
+ lines.push(` ${sample}`);
43
+ if (scope.held.count > Math.min(20, scope.held.samplePaths.length))
44
+ lines.push(" ...");
45
+ }
46
+ return lines;
47
+ }
48
+ export async function runSyncHeld(deps, json = false) {
49
+ if (!deps.exports) {
50
+ deps.log(`${chalk.red("✗")} ${HELD_UNAVAILABLE_MESSAGE}`);
51
+ return 1;
52
+ }
53
+ const scopes = deps.exports.listPushIdentityFences(deps.stateDir);
54
+ if (json) {
55
+ deps.log(JSON.stringify({ scopes }));
56
+ return 0;
57
+ }
58
+ if (scopes.length === 0) {
59
+ deps.log("No uploads are held on this computer.");
60
+ return 0;
61
+ }
62
+ deps.log("Someone else was signed in on this computer, so these uploads are held:");
63
+ for (const scope of scopes)
64
+ for (const line of describe(scope))
65
+ deps.log(line);
66
+ deps.log("");
67
+ deps.log("Nothing has been uploaded or deleted in the cloud. If these files are yours, run `hq sync release-held`.");
68
+ return 0;
69
+ }
70
+ export async function runReleaseHeld(deps, options) {
71
+ if (!deps.exports) {
72
+ deps.log(`${chalk.red("✗")} ${HELD_UNAVAILABLE_MESSAGE}`);
73
+ return 1;
74
+ }
75
+ let scopes = deps.exports.listPushIdentityFences(deps.stateDir);
76
+ if (options.personal)
77
+ scopes = scopes.filter((scope) => scope.slug === PERSONAL_VAULT_JOURNAL_SLUG || scope.slug === "personal");
78
+ if (options.company)
79
+ scopes = scopes.filter((scope) => scope.slug === options.company);
80
+ if (scopes.length === 0) {
81
+ deps.log(options.json ? JSON.stringify({ released: [] }) : "No held uploads match.");
82
+ return 0;
83
+ }
84
+ const needPersonal = scopes.some((scope) => scope.slug === PERSONAL_VAULT_JOURNAL_SLUG || scope.slug === "personal");
85
+ const snapshot = await deps.identity({ needPersonal, interactive: true });
86
+ const identities = [snapshot.sub, ...(snapshot.personalUid ? [snapshot.personalUid] : [])];
87
+ const mine = scopes.filter((scope) => identities.includes(scope.uid));
88
+ if (mine.length === 0) {
89
+ deps.log(options.json
90
+ ? JSON.stringify({ released: [], reason: "not-yours" })
91
+ : "These held uploads belong to a different signed-in person. Sign in as that person to release them.");
92
+ return 1;
93
+ }
94
+ if (!options.json) {
95
+ deps.log("Releasing these uploads sends the files below to the cloud as yours:");
96
+ for (const scope of mine)
97
+ for (const line of describe(scope))
98
+ deps.log(line);
99
+ }
100
+ if (!options.yes) {
101
+ const ok = await deps.confirm("Are these files yours to upload? [y/N] ");
102
+ if (!ok) {
103
+ deps.log(options.json ? JSON.stringify({ released: [] }) : "Nothing released.");
104
+ return 1;
105
+ }
106
+ }
107
+ // Re-read the signed-in identity right before releasing, without a sign-in.
108
+ // If the account changed since the snapshot (including during the prompt),
109
+ // release nothing: the scopes were judged against the earlier account.
110
+ const current = await deps.identity({ needPersonal, interactive: false });
111
+ if (current.sub !== snapshot.sub || current.personalUid !== snapshot.personalUid) {
112
+ deps.log(options.json
113
+ ? JSON.stringify({ released: [], reason: "identity-changed" })
114
+ : "The signed-in account changed while releasing. Nothing was released; run the command again.");
115
+ return 1;
116
+ }
117
+ const released = deps.exports.releasePushIdentityFences(deps.stateDir, identities, mine.map((scope) => scope.slug));
118
+ deps.log(options.json
119
+ ? JSON.stringify({ released })
120
+ : `Released ${released.map(scopeLabel).join(", ")}. The next sync uploads these files.`);
121
+ return 0;
122
+ }
123
+ async function confirmOnTty(question) {
124
+ if (process.stdin.isTTY !== true || process.stdout.isTTY !== true)
125
+ return false;
126
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
127
+ try {
128
+ const answer = await rl.question(question);
129
+ return /^y(es)?$/i.test(answer.trim());
130
+ }
131
+ finally {
132
+ rl.close();
133
+ }
134
+ }
135
+ /**
136
+ * The signed-in identity from the session token `ensureCognitoToken` selects
137
+ * (person or machine, the same choice every vault call makes). The personal
138
+ * vault uid is resolved with that same token: an agent principal uses its own
139
+ * entity uid, as the sync runner does; a person uses the canonical person
140
+ * entity.
141
+ */
142
+ export async function resolveHeldIdentity(options, io) {
143
+ const token = await io.ensureToken(options.interactive);
144
+ const { sub, entityUid } = cognitoSubjectFromToken(token);
145
+ if (!sub)
146
+ throw new Error("could not read the signed-in account from the session; run `hq login` and try again");
147
+ if (!options.needPersonal)
148
+ return { sub };
149
+ const entityType = decodeJwtPayload(token)?.["custom:entityType"];
150
+ if (entityType === "agent" && entityUid)
151
+ return { sub, personalUid: entityUid };
152
+ const personalUid = await resolveCanonicalPersonUid({
153
+ listMyMemberships: async () => [],
154
+ listPersonEntities: () => io.listPersons(token),
155
+ getEntity: async () => null,
156
+ });
157
+ return { sub, personalUid };
158
+ }
159
+ export function createDefaultSyncHeldDeps() {
160
+ const tty = process.stdin.isTTY === true && process.stdout.isTTY === true;
161
+ return {
162
+ exports: fenceExports(),
163
+ stateDir: getStateDir(),
164
+ identity: (options) => resolveHeldIdentity({ needPersonal: options.needPersonal, interactive: options.interactive && tty }, {
165
+ ensureToken: (interactive) => ensureCognitoToken({ interactive }),
166
+ listPersons: async (token) => (await new VaultClient(buildVaultConfig(token)).entity.listByType("person")),
167
+ }),
168
+ confirm: confirmOnTty,
169
+ log: (line) => console.log(line),
170
+ };
171
+ }
172
+ export function registerSyncHeldCommands(syncCmd) {
173
+ syncCmd
174
+ .command("held")
175
+ .description("Show uploads held because someone else was signed in on this computer")
176
+ .option("--json", "Print the held scopes as JSON")
177
+ .action(async (options) => {
178
+ process.exitCode = await runSyncHeld(createDefaultSyncHeldDeps(), options.json === true);
179
+ });
180
+ syncCmd
181
+ .command("release-held")
182
+ .description("Confirm held files are yours and let sync upload them")
183
+ .option("--company <slug>", "Release only this company")
184
+ .option("--personal", "Release only the personal vault")
185
+ .option("--yes", "Do not ask for confirmation")
186
+ .option("--json", "Print the result as JSON")
187
+ .action(async (options) => {
188
+ process.exitCode = await runReleaseHeld(createDefaultSyncHeldDeps(), options);
189
+ });
190
+ }
191
+ //# sourceMappingURL=sync-held.js.map