@indigoai-us/hq-cli 5.14.0 → 5.15.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.
@@ -0,0 +1,374 @@
1
+
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="8237a835-5842-52be-bb27-c4ffb7944d89")}catch(e){}}();
3
+ import chalk from "chalk";
4
+ import { ensureCognitoToken } from "../utils/cognito-session.js";
5
+ import { vaultApiFetch } from "../utils/vault-api.js";
6
+ function formatDuration(seconds) {
7
+ const h = Math.floor(seconds / 3600);
8
+ const m = Math.floor((seconds % 3600) / 60);
9
+ const s = Math.floor(seconds % 60);
10
+ if (h > 0)
11
+ return `${h}h ${m}m`;
12
+ if (m > 0)
13
+ return `${m}m ${s}s`;
14
+ return `${s}s`;
15
+ }
16
+ function formatTimestamp(ts) {
17
+ const m = Math.floor(ts / 60);
18
+ const s = Math.floor(ts % 60);
19
+ return `${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
20
+ }
21
+ function statusBadge(status) {
22
+ switch (status) {
23
+ case "completed":
24
+ return chalk.green(status);
25
+ case "recording":
26
+ return chalk.red(status);
27
+ case "processing":
28
+ return chalk.yellow(status);
29
+ case "failed":
30
+ return chalk.red(status);
31
+ default:
32
+ return chalk.dim(status);
33
+ }
34
+ }
35
+ async function resolveShortId(token, prefix, query) {
36
+ if (prefix.includes("-") && prefix.length > 8)
37
+ return prefix;
38
+ const res = await vaultApiFetch({ token, path: "/v1/meetings", query });
39
+ if (!res.ok)
40
+ return prefix;
41
+ const data = (await res.json());
42
+ const matches = data.meetings.filter((m) => m.meetingId.startsWith(prefix));
43
+ if (matches.length === 1)
44
+ return matches[0].meetingId;
45
+ if (matches.length > 1) {
46
+ console.error(chalk.red(`Ambiguous ID prefix "${prefix}" — matches ${matches.length} meetings. Use a longer prefix.`));
47
+ process.exit(1);
48
+ }
49
+ return prefix;
50
+ }
51
+ async function handleApiError(res) {
52
+ const body = (await res.json().catch(() => ({})));
53
+ if (res.status === 401) {
54
+ console.error(chalk.red("Not authenticated — run `hq login` first"));
55
+ }
56
+ else if (res.status === 403) {
57
+ console.error(chalk.red(body.error ?? "Not authorized"));
58
+ }
59
+ else if (res.status === 404) {
60
+ console.error(chalk.red(body.error ?? "Not found"));
61
+ }
62
+ else {
63
+ console.error(chalk.red(`API error (${res.status}): ${body.error ?? res.statusText}`));
64
+ }
65
+ process.exit(1);
66
+ }
67
+ function printMeetingTable(meetings) {
68
+ if (meetings.length === 0) {
69
+ console.log(chalk.dim(" No meetings found."));
70
+ return;
71
+ }
72
+ const ID_W = 8;
73
+ const TITLE_W = Math.min(40, Math.max(10, ...meetings.map((m) => m.title.length)));
74
+ const DATE_W = 16;
75
+ const DUR_W = 8;
76
+ const STATUS_W = 12;
77
+ const PARTS_W = 6;
78
+ const FLAGS_W = 5;
79
+ console.log(chalk.bold([
80
+ "ID".padEnd(ID_W),
81
+ "TITLE".padEnd(TITLE_W),
82
+ "DATE".padEnd(DATE_W),
83
+ "DUR".padEnd(DUR_W),
84
+ "STATUS".padEnd(STATUS_W),
85
+ "PARTS".padEnd(PARTS_W),
86
+ "FLAGS",
87
+ ].join(" ")));
88
+ for (const m of meetings) {
89
+ const id = m.meetingId.slice(0, 8);
90
+ const title = m.title.length > TITLE_W ? m.title.slice(0, TITLE_W - 1) + "…" : m.title;
91
+ const date = new Date(m.startTime).toLocaleDateString("en-US", {
92
+ month: "short",
93
+ day: "numeric",
94
+ hour: "2-digit",
95
+ minute: "2-digit",
96
+ });
97
+ const dur = formatDuration(m.duration);
98
+ const flags = [
99
+ m.hasTranscript ? "T" : "",
100
+ m.hasNotes ? "N" : "",
101
+ ].filter(Boolean).join("") || "-";
102
+ console.log([
103
+ chalk.cyan(id.padEnd(ID_W)),
104
+ title.padEnd(TITLE_W),
105
+ chalk.dim(date.padEnd(DATE_W)),
106
+ dur.padEnd(DUR_W),
107
+ statusBadge(m.status).padEnd(STATUS_W + 10), // chalk adds escape chars
108
+ String(m.participantCount).padEnd(PARTS_W),
109
+ flags,
110
+ ].join(" "));
111
+ }
112
+ }
113
+ export function registerMeetingsCommand(program) {
114
+ const meetings = program
115
+ .command("meetings")
116
+ .description("View and search meeting recordings, transcripts, and notes")
117
+ .option("--company <slug>", "Company slug (for multi-company users)")
118
+ .option("--json", "Output raw JSON instead of formatted text");
119
+ // ── hq meetings list ──────────────────────────────────────────────
120
+ meetings
121
+ .command("list")
122
+ .description("List recorded meetings (newest first)")
123
+ .option("--limit <n>", "Number of meetings to return (default: 20)")
124
+ .option("--next <token>", "Pagination token from a previous response")
125
+ .action(async (opts) => {
126
+ try {
127
+ const token = await ensureCognitoToken();
128
+ const query = {};
129
+ const companySlug = meetings.opts().company;
130
+ if (opts.limit)
131
+ query.limit = opts.limit;
132
+ if (opts.next)
133
+ query.nextToken = opts.next;
134
+ if (companySlug)
135
+ query.companyId = companySlug;
136
+ const res = await vaultApiFetch({ token, path: "/v1/meetings", query });
137
+ if (!res.ok)
138
+ await handleApiError(res);
139
+ const data = (await res.json());
140
+ if (meetings.opts().json) {
141
+ console.log(JSON.stringify(data, null, 2));
142
+ return;
143
+ }
144
+ console.log(chalk.bold(`\nMeetings (${data.meetings.length}):\n`));
145
+ printMeetingTable(data.meetings);
146
+ if (data.nextToken) {
147
+ console.log(chalk.dim(`\n More results available. Run with --next ${data.nextToken}`));
148
+ }
149
+ console.log();
150
+ }
151
+ catch (err) {
152
+ console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
153
+ process.exit(1);
154
+ }
155
+ });
156
+ // ── hq meetings get <id> ──────────────────────────────────────────
157
+ meetings
158
+ .command("get <meetingId>")
159
+ .description("Show meeting details")
160
+ .action(async (rawId) => {
161
+ try {
162
+ const token = await ensureCognitoToken();
163
+ const query = {};
164
+ const companySlug = meetings.opts().company;
165
+ if (companySlug)
166
+ query.companyId = companySlug;
167
+ const meetingId = await resolveShortId(token, rawId, query);
168
+ const res = await vaultApiFetch({
169
+ token,
170
+ path: `/v1/meetings/${encodeURIComponent(meetingId)}`,
171
+ query,
172
+ });
173
+ if (!res.ok)
174
+ await handleApiError(res);
175
+ const detail = (await res.json());
176
+ if (meetings.opts().json) {
177
+ console.log(JSON.stringify(detail, null, 2));
178
+ return;
179
+ }
180
+ console.log(chalk.bold(`\n${detail.title}\n`));
181
+ console.log(` ID: ${chalk.cyan(detail.meetingId)}`);
182
+ console.log(` Status: ${statusBadge(detail.status)}`);
183
+ console.log(` Date: ${new Date(detail.startTime).toLocaleString()}`);
184
+ console.log(` Duration: ${formatDuration(detail.duration)}`);
185
+ console.log(` Source: ${detail.sourceApp} (${detail.botProvider})`);
186
+ console.log(` Shared: ${detail.isShared ? "yes" : "no"}`);
187
+ if (detail.participants.length > 0) {
188
+ console.log(chalk.bold("\n Participants:"));
189
+ for (const p of detail.participants) {
190
+ const name = p.name ?? p.email;
191
+ const role = p.role === "organizer" ? chalk.yellow(" (organizer)") : "";
192
+ console.log(` - ${name}${role}`);
193
+ }
194
+ }
195
+ const flags = [];
196
+ if (detail.hasTranscript)
197
+ flags.push("transcript");
198
+ if (detail.hasNotes)
199
+ flags.push("notes");
200
+ if (flags.length > 0) {
201
+ console.log(chalk.dim(`\n Available: ${flags.join(", ")}. Use \`hq meetings transcript ${detail.meetingId.slice(0, 8)}\` or \`hq meetings notes ${detail.meetingId.slice(0, 8)}\`.`));
202
+ }
203
+ console.log();
204
+ }
205
+ catch (err) {
206
+ console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
207
+ process.exit(1);
208
+ }
209
+ });
210
+ // ── hq meetings search <query> ─────────────────────────────────────
211
+ meetings
212
+ .command("search <query>")
213
+ .description("Search meetings by title or participant name")
214
+ .action(async (query) => {
215
+ try {
216
+ const token = await ensureCognitoToken();
217
+ const params = { q: query };
218
+ const companySlug = meetings.opts().company;
219
+ if (companySlug)
220
+ params.companyId = companySlug;
221
+ const res = await vaultApiFetch({
222
+ token,
223
+ path: "/v1/meetings/search",
224
+ query: params,
225
+ });
226
+ if (!res.ok)
227
+ await handleApiError(res);
228
+ const data = (await res.json());
229
+ if (meetings.opts().json) {
230
+ console.log(JSON.stringify(data, null, 2));
231
+ return;
232
+ }
233
+ console.log(chalk.bold(`\nSearch results for "${data.query}" (${data.results.length}):\n`));
234
+ printMeetingTable(data.results);
235
+ console.log();
236
+ }
237
+ catch (err) {
238
+ console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
239
+ process.exit(1);
240
+ }
241
+ });
242
+ // ── hq meetings transcript <id> ────────────────────────────────────
243
+ meetings
244
+ .command("transcript <meetingId>")
245
+ .description("Print the meeting transcript")
246
+ .action(async (rawId) => {
247
+ try {
248
+ const token = await ensureCognitoToken();
249
+ const query = {};
250
+ const companySlug = meetings.opts().company;
251
+ if (companySlug)
252
+ query.companyId = companySlug;
253
+ const meetingId = await resolveShortId(token, rawId, query);
254
+ const res = await vaultApiFetch({
255
+ token,
256
+ path: `/v1/meetings/${encodeURIComponent(meetingId)}`,
257
+ query,
258
+ });
259
+ if (!res.ok)
260
+ await handleApiError(res);
261
+ const detail = (await res.json());
262
+ if (!detail.documentUrl) {
263
+ console.error(chalk.red("No document URL available for this meeting."));
264
+ process.exit(1);
265
+ }
266
+ const docRes = await fetch(detail.documentUrl);
267
+ if (!docRes.ok) {
268
+ console.error(chalk.red(`Failed to download meeting document (${docRes.status})`));
269
+ process.exit(1);
270
+ }
271
+ const doc = (await docRes.json());
272
+ if (meetings.opts().json) {
273
+ console.log(JSON.stringify(doc.transcript, null, 2));
274
+ return;
275
+ }
276
+ if (!doc.transcript || doc.transcript.length === 0) {
277
+ console.log(chalk.yellow("No transcript available for this meeting."));
278
+ return;
279
+ }
280
+ console.log(chalk.bold(`\nTranscript: ${doc.title}\n`));
281
+ for (const seg of doc.transcript) {
282
+ const time = formatTimestamp(seg.startTime);
283
+ console.log(`${chalk.dim(time)} ${chalk.cyan(seg.speaker)}`);
284
+ console.log(` ${seg.text}\n`);
285
+ }
286
+ }
287
+ catch (err) {
288
+ console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
289
+ process.exit(1);
290
+ }
291
+ });
292
+ // ── hq meetings notes <id> ─────────────────────────────────────────
293
+ meetings
294
+ .command("notes <meetingId>")
295
+ .description("Print AI-generated meeting notes")
296
+ .action(async (rawId) => {
297
+ try {
298
+ const token = await ensureCognitoToken();
299
+ const query = {};
300
+ const companySlug = meetings.opts().company;
301
+ if (companySlug)
302
+ query.companyId = companySlug;
303
+ const meetingId = await resolveShortId(token, rawId, query);
304
+ const res = await vaultApiFetch({
305
+ token,
306
+ path: `/v1/meetings/${encodeURIComponent(meetingId)}`,
307
+ query,
308
+ });
309
+ if (!res.ok)
310
+ await handleApiError(res);
311
+ const detail = (await res.json());
312
+ if (!detail.documentUrl) {
313
+ console.error(chalk.red("No document URL available for this meeting."));
314
+ process.exit(1);
315
+ }
316
+ const docRes = await fetch(detail.documentUrl);
317
+ if (!docRes.ok) {
318
+ console.error(chalk.red(`Failed to download meeting document (${docRes.status})`));
319
+ process.exit(1);
320
+ }
321
+ const doc = (await docRes.json());
322
+ if (meetings.opts().json) {
323
+ console.log(JSON.stringify(doc.notes, null, 2));
324
+ return;
325
+ }
326
+ if (!doc.notes) {
327
+ console.log(chalk.yellow("No notes available for this meeting."));
328
+ return;
329
+ }
330
+ const notes = doc.notes;
331
+ console.log(chalk.bold(`\nMeeting Notes: ${doc.title}\n`));
332
+ console.log(chalk.bold("Summary"));
333
+ console.log(` ${notes.summary}\n`);
334
+ if (notes.keyPoints.length > 0) {
335
+ console.log(chalk.bold("Key Points"));
336
+ for (const point of notes.keyPoints) {
337
+ console.log(` • ${point}`);
338
+ }
339
+ console.log();
340
+ }
341
+ if (notes.decisions.length > 0) {
342
+ console.log(chalk.bold("Decisions"));
343
+ for (const d of notes.decisions) {
344
+ console.log(` ✓ ${d}`);
345
+ }
346
+ console.log();
347
+ }
348
+ if (notes.actionItems.length > 0) {
349
+ console.log(chalk.bold("Action Items"));
350
+ for (const item of notes.actionItems) {
351
+ const assignee = item.assignee ? chalk.dim(` → ${item.assignee}`) : "";
352
+ console.log(` □ ${item.task}${assignee}`);
353
+ }
354
+ console.log();
355
+ }
356
+ if (notes.participantContributions.length > 0) {
357
+ console.log(chalk.bold("Participant Contributions"));
358
+ for (const p of notes.participantContributions) {
359
+ console.log(` ${p.name} (${p.speakingTimePercent}%)`);
360
+ console.log(` ${chalk.dim(p.topicsSummary)}`);
361
+ }
362
+ console.log();
363
+ }
364
+ console.log(chalk.dim(`Generated by ${notes.model} at ${notes.generatedAt}`));
365
+ console.log();
366
+ }
367
+ catch (err) {
368
+ console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
369
+ process.exit(1);
370
+ }
371
+ });
372
+ }
373
+ //# sourceMappingURL=meetings.js.map
374
+ //# debugId=8237a835-5842-52be-bb27-c4ffb7944d89
@@ -26,16 +26,44 @@
26
26
  * 3. Parse + validate package.yaml (10 checks from spec)
27
27
  * 4. Evaluate `conditional` predicate — skip if exits non-zero
28
28
  * 5. Confirm hooks if `contributes.hooks` non-empty (unless --allow-hooks)
29
- * 6. Move into packages/{name}/
30
- * 7. Append entry to modules.yaml with strategy: package
31
- * 8. Run scan-packages.sh to wire contributions into host paths
29
+ * 6. Move into core/packages/{name}/
30
+ * 7. Run core/scripts/scan-packages.sh to wire contributions into host paths
31
+ *
32
+ * Installed packs are tracked by filesystem presence — there's no separate
33
+ * registry file under the v12 layout. (`hq update <pack>` re-resolves source
34
+ * from each pack's package.yaml; rationale lives in the layout-fix PR.)
32
35
  */
36
+ import type { PackManifest } from '../types.js';
33
37
  /**
34
38
  * sourceMatchesPackPattern — exported for the dispatcher in pkg-install.ts
35
39
  * so it can decide whether to route to the new content-pack handler or fall
36
40
  * back to the legacy registry flow.
37
41
  */
38
42
  export declare function sourceMatchesPackPattern(source: string): boolean;
43
+ /**
44
+ * Install the fetched payload to `<hqRoot>/core/packages/<pkg.name>/` (HQ
45
+ * v12+ layout). The HQ template (`hq-core` / `hq-core-staging`) ships
46
+ * `core/packages/` as the canonical pack root; writing to top-level
47
+ * `packages/` would leave an orphan tree alongside the real one.
48
+ *
49
+ * Re-installs replace the existing destination so stale contributions don't
50
+ * linger (the post-install `scan-packages.sh` would otherwise wire them
51
+ * back into host paths).
52
+ *
53
+ * Exported for tests in pack-install.test.ts — see that file for the
54
+ * contract this function pins.
55
+ */
56
+ export declare function installToPackages(payloadDir: string, pkg: PackManifest, hqRoot: string): string;
57
+ /**
58
+ * Run `<hqRoot>/core/scripts/scan-packages.sh` to wire the newly installed
59
+ * pack's contributions into the host paths (skills, hooks, policies, etc.).
60
+ * Skipping with a dim warning if the script is missing keeps fresh HQs (or
61
+ * older templates) usable — the next session start picks them up via its
62
+ * own scan.
63
+ *
64
+ * Exported for tests.
65
+ */
66
+ export declare function runScanPackages(hqRoot: string): void;
39
67
  export interface InstallPackOptions {
40
68
  allowHooks?: boolean;
41
69
  followBranch?: boolean;
@@ -26,12 +26,15 @@
26
26
  * 3. Parse + validate package.yaml (10 checks from spec)
27
27
  * 4. Evaluate `conditional` predicate — skip if exits non-zero
28
28
  * 5. Confirm hooks if `contributes.hooks` non-empty (unless --allow-hooks)
29
- * 6. Move into packages/{name}/
30
- * 7. Append entry to modules.yaml with strategy: package
31
- * 8. Run scan-packages.sh to wire contributions into host paths
29
+ * 6. Move into core/packages/{name}/
30
+ * 7. Run core/scripts/scan-packages.sh to wire contributions into host paths
31
+ *
32
+ * Installed packs are tracked by filesystem presence — there's no separate
33
+ * registry file under the v12 layout. (`hq update <pack>` re-resolves source
34
+ * from each pack's package.yaml; rationale lives in the layout-fix PR.)
32
35
  */
33
36
 
34
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="9c0fc9f1-9a26-5a3f-a795-7254534316d5")}catch(e){}}();
37
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="5dea7752-f10c-553c-a419-cf05441b3c13")}catch(e){}}();
35
38
  import * as fs from 'fs';
36
39
  import * as os from 'os';
37
40
  import * as path from 'path';
@@ -42,7 +45,7 @@ import chalk from 'chalk';
42
45
  import semverSatisfies from 'semver/functions/satisfies.js';
43
46
  import semverValid from 'semver/functions/valid.js';
44
47
  import semverValidRange from 'semver/ranges/valid.js';
45
- import { findHqRoot, readManifest, writeManifest, } from '../utils/manifest.js';
48
+ import { findHqRoot } from '../utils/manifest.js';
46
49
  function classify(source) {
47
50
  if (source.startsWith('@'))
48
51
  return 'npm';
@@ -415,10 +418,23 @@ function evalConditional(expr) {
415
418
  return r.status === 0;
416
419
  }
417
420
  // ---------------------------------------------------------------------------
418
- // Move into packages/ + update modules.yaml + scan
421
+ // Move into core/packages/ + run core/scripts/scan-packages.sh
419
422
  // ---------------------------------------------------------------------------
420
- function installToPackages(payloadDir, pkg, hqRoot) {
421
- const packagesDir = path.join(hqRoot, 'packages');
423
+ /**
424
+ * Install the fetched payload to `<hqRoot>/core/packages/<pkg.name>/` (HQ
425
+ * v12+ layout). The HQ template (`hq-core` / `hq-core-staging`) ships
426
+ * `core/packages/` as the canonical pack root; writing to top-level
427
+ * `packages/` would leave an orphan tree alongside the real one.
428
+ *
429
+ * Re-installs replace the existing destination so stale contributions don't
430
+ * linger (the post-install `scan-packages.sh` would otherwise wire them
431
+ * back into host paths).
432
+ *
433
+ * Exported for tests in pack-install.test.ts — see that file for the
434
+ * contract this function pins.
435
+ */
436
+ export function installToPackages(payloadDir, pkg, hqRoot) {
437
+ const packagesDir = path.join(hqRoot, 'core', 'packages');
422
438
  fs.mkdirSync(packagesDir, { recursive: true });
423
439
  const destDir = path.join(packagesDir, pkg.name);
424
440
  if (fs.existsSync(destDir)) {
@@ -430,28 +446,19 @@ function installToPackages(payloadDir, pkg, hqRoot) {
430
446
  execFileSync('rsync', ['-a', srcSlashed, destSlashed], { stdio: 'inherit' });
431
447
  return destDir;
432
448
  }
433
- function updateModulesYaml(hqRoot, pkg, fetched) {
434
- const manifest = readManifest(hqRoot) ?? { version: '1', modules: [] };
435
- // Drop any existing entry for this pack (idempotent re-install)
436
- manifest.modules = manifest.modules.filter((m) => m.name !== pkg.name);
437
- const entry = {
438
- name: pkg.name,
439
- strategy: 'package',
440
- source: fetched.resolvedSource,
441
- version: pkg.version,
442
- installed_at: path.posix.join('packages', pkg.name),
443
- installed_at_iso: new Date().toISOString(),
444
- access: pkg.access === 'public' ? 'public' : undefined,
445
- };
446
- if (fetched.resolvedSha)
447
- entry.resolved_sha = fetched.resolvedSha;
448
- manifest.modules.push(entry);
449
- writeManifest(hqRoot, manifest);
450
- }
451
- function runScanPackages(hqRoot) {
452
- const script = path.join(hqRoot, 'scripts', 'scan-packages.sh');
449
+ /**
450
+ * Run `<hqRoot>/core/scripts/scan-packages.sh` to wire the newly installed
451
+ * pack's contributions into the host paths (skills, hooks, policies, etc.).
452
+ * Skipping with a dim warning if the script is missing keeps fresh HQs (or
453
+ * older templates) usable — the next session start picks them up via its
454
+ * own scan.
455
+ *
456
+ * Exported for tests.
457
+ */
458
+ export function runScanPackages(hqRoot) {
459
+ const script = path.join(hqRoot, 'core', 'scripts', 'scan-packages.sh');
453
460
  if (!fs.existsSync(script)) {
454
- console.log(chalk.dim(` (scripts/scan-packages.sh not present — skipping auto-wire; ` +
461
+ console.log(chalk.dim(` (core/scripts/scan-packages.sh not present — skipping auto-wire; ` +
455
462
  `will run on next session start)`));
456
463
  return;
457
464
  }
@@ -502,7 +509,12 @@ export async function installPack(source, opts = {}) {
502
509
  return;
503
510
  }
504
511
  const destDir = installToPackages(fetched.payloadDir, pkg, hqRoot);
505
- updateModulesYaml(hqRoot, pkg, fetched);
512
+ // Under the v12 HQ layout, packs live at `core/packages/<name>/` and are
513
+ // tracked by filesystem presence alone — no `modules.yaml` write. That
514
+ // removes the side effect that created a top-level `modules/` directory
515
+ // alongside the canonical `core/`. `hq update <pack>` will re-resolve a
516
+ // pack's source from its on-disk package.yaml or prompt for it, but that
517
+ // tradeoff is intentional — see the layout-fix PR for rationale.
506
518
  runScanPackages(hqRoot);
507
519
  console.log(chalk.green(`\n✓ Installed ${pkg.name}@${pkg.version} → ${path.relative(hqRoot, destDir)}/`));
508
520
  console.log(chalk.dim(` Wired ${Object.values(pkg.contributes).flat().filter(Boolean).length} ` +
@@ -513,4 +525,4 @@ export async function installPack(source, opts = {}) {
513
525
  }
514
526
  }
515
527
  //# sourceMappingURL=pack-install.js.map
516
- //# debugId=9c0fc9f1-9a26-5a3f-a795-7254534316d5
528
+ //# debugId=5dea7752-f10c-553c-a419-cf05441b3c13
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * HQ CLI - Module management, package management, and cloud sync for HQ
4
4
  */
5
5
 
6
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="9cc0bf93-9c18-53aa-addd-954425115886")}catch(e){}}();
6
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="15889dc3-5d7e-5fb9-8df3-33d5aebdc945")}catch(e){}}();
7
7
  import { Command } from "commander";
8
8
  import { initSentry, Sentry } from "./sentry.js";
9
9
  import { registerAddCommand } from "./commands/add.js";
@@ -29,6 +29,7 @@ import { registerGroupsCommand } from "./commands/groups.js";
29
29
  import { registerFilesCommand } from "./commands/files.js";
30
30
  import { registerMembersCommand } from "./commands/members.js";
31
31
  import { registerFeedbackCommand } from "./commands/feedback.js";
32
+ import { registerMeetingsCommand } from "./commands/meetings.js";
32
33
  import { sanitizeArgv } from "./utils/feedback-diagnostics.js";
33
34
  import { maybeWarnNewVersion, refreshVersionCache, } from "./utils/version-check.js";
34
35
  import { CLI_VERSION } from "./cli-version.js";
@@ -102,6 +103,8 @@ registerMembersCommand(program);
102
103
  registerOnboardCommand(program);
103
104
  // Feedback (subcommand group — hq feedback bug|feature)
104
105
  registerFeedbackCommand(program);
106
+ // Meetings (subcommand group — hq meetings list|get|search|transcript|notes)
107
+ registerMeetingsCommand(program);
105
108
  (async () => {
106
109
  try {
107
110
  Sentry.addBreadcrumb({
@@ -120,4 +123,4 @@ registerFeedbackCommand(program);
120
123
  }
121
124
  })();
122
125
  //# sourceMappingURL=index.js.map
123
- //# debugId=9cc0bf93-9c18-53aa-addd-954425115886
126
+ //# debugId=15889dc3-5d7e-5fb9-8df3-33d5aebdc945
@@ -21,6 +21,31 @@
21
21
  import { type CognitoAuthConfig, type VaultServiceConfig } from "@indigoai-us/hq-cloud";
22
22
  export declare const DEFAULT_COGNITO: CognitoAuthConfig;
23
23
  export declare const DEFAULT_VAULT_API_URL: string;
24
+ /**
25
+ * Resolve the default HQ tree root for cloud-aware subcommands.
26
+ *
27
+ * Priority order:
28
+ * 1. `$HQ_ROOT` env var (explicit user override)
29
+ * 2. Walk up from `process.cwd()` to the nearest dir containing BOTH a
30
+ * `core.yaml` file AND a `companies/` directory (root-unique marker
31
+ * pair — see note below).
32
+ * 3. Fall back to `~/hq` (the historical default)
33
+ *
34
+ * Why both markers?
35
+ * The HQ root has `core.yaml` AND a sibling `companies/` directory. The
36
+ * synced `core/` subtree (which is itself part of the root's personal-vault
37
+ * scope) ALSO contains a `core.yaml` (the template's version-source-of-
38
+ * truth), but does NOT contain `companies/`. Single-marker `core.yaml`
39
+ * detection would stop at `<hqRoot>/core/` when the CLI is launched from
40
+ * somewhere inside that subtree, and downstream `companies/` lookups would
41
+ * silently miss the real content. Requiring `companies/` as well guarantees
42
+ * we resolve to the actual HQ root (Codex P2 on hq#146).
43
+ *
44
+ * Evaluated once at module load — commander.js `.option()` callers pin the
45
+ * value at registration time, which matches the user's actual cwd at process
46
+ * start. Re-importable as a function for tests and command-time resolution.
47
+ */
48
+ export declare function resolveDefaultHqRoot(): string;
24
49
  export declare const DEFAULT_HQ_ROOT: string;
25
50
  /**
26
51
  * Return a non-expired Cognito access token, refreshing or browser-logging-in
@@ -19,7 +19,8 @@
19
19
  * HQ_VAULT_API_URL — vault-service API Gateway URL
20
20
  */
21
21
 
22
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="71d8f748-f249-50df-aa1f-eae381ea608d")}catch(e){}}();
22
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="6a8ac157-0903-513d-b4c5-45fb532b8026")}catch(e){}}();
23
+ import * as fs from "fs";
23
24
  import * as os from "os";
24
25
  import * as path from "path";
25
26
  import chalk from "chalk";
@@ -41,7 +42,53 @@ export const DEFAULT_COGNITO = {
41
42
  : "Google",
42
43
  };
43
44
  export const DEFAULT_VAULT_API_URL = process.env.HQ_VAULT_API_URL ?? "https://hqapi.getindigo.ai";
44
- export const DEFAULT_HQ_ROOT = path.join(os.homedir(), "hq");
45
+ /**
46
+ * Resolve the default HQ tree root for cloud-aware subcommands.
47
+ *
48
+ * Priority order:
49
+ * 1. `$HQ_ROOT` env var (explicit user override)
50
+ * 2. Walk up from `process.cwd()` to the nearest dir containing BOTH a
51
+ * `core.yaml` file AND a `companies/` directory (root-unique marker
52
+ * pair — see note below).
53
+ * 3. Fall back to `~/hq` (the historical default)
54
+ *
55
+ * Why both markers?
56
+ * The HQ root has `core.yaml` AND a sibling `companies/` directory. The
57
+ * synced `core/` subtree (which is itself part of the root's personal-vault
58
+ * scope) ALSO contains a `core.yaml` (the template's version-source-of-
59
+ * truth), but does NOT contain `companies/`. Single-marker `core.yaml`
60
+ * detection would stop at `<hqRoot>/core/` when the CLI is launched from
61
+ * somewhere inside that subtree, and downstream `companies/` lookups would
62
+ * silently miss the real content. Requiring `companies/` as well guarantees
63
+ * we resolve to the actual HQ root (Codex P2 on hq#146).
64
+ *
65
+ * Evaluated once at module load — commander.js `.option()` callers pin the
66
+ * value at registration time, which matches the user's actual cwd at process
67
+ * start. Re-importable as a function for tests and command-time resolution.
68
+ */
69
+ export function resolveDefaultHqRoot() {
70
+ if (process.env.HQ_ROOT)
71
+ return path.resolve(process.env.HQ_ROOT);
72
+ let cur = path.resolve(process.cwd());
73
+ while (cur !== path.dirname(cur)) {
74
+ if (isHqRoot(cur))
75
+ return cur;
76
+ cur = path.dirname(cur);
77
+ }
78
+ return path.join(os.homedir(), "hq");
79
+ }
80
+ /** True iff `dir` looks like an HQ root (has core.yaml + companies/ dir). */
81
+ function isHqRoot(dir) {
82
+ if (!fs.existsSync(path.join(dir, "core.yaml")))
83
+ return false;
84
+ try {
85
+ return fs.statSync(path.join(dir, "companies")).isDirectory();
86
+ }
87
+ catch {
88
+ return false;
89
+ }
90
+ }
91
+ export const DEFAULT_HQ_ROOT = resolveDefaultHqRoot();
45
92
  /**
46
93
  * Return a non-expired Cognito access token, refreshing or browser-logging-in
47
94
  * as needed. Cache lives at ~/.hq/cognito-tokens.json.
@@ -108,4 +155,4 @@ export async function refreshCachedSession() {
108
155
  }
109
156
  }
110
157
  //# sourceMappingURL=cognito-session.js.map
111
- //# debugId=71d8f748-f249-50df-aa1f-eae381ea608d
158
+ //# debugId=6a8ac157-0903-513d-b4c5-45fb532b8026