@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.
- package/.github/workflows/ci.yml +21 -0
- package/.github/workflows/publish.yml +86 -0
- package/.github/workflows/scripts/smoke-test-pkg.sh +97 -0
- package/CHANGELOG.md +19 -0
- package/dist/commands/cloud-provision.d.ts +40 -3
- package/dist/commands/cloud-provision.js +131 -8
- package/dist/commands/meetings.d.ts +3 -0
- package/dist/commands/meetings.js +374 -0
- package/dist/commands/pack-install.d.ts +31 -3
- package/dist/commands/pack-install.js +43 -31
- package/dist/index.js +5 -2
- package/dist/utils/cognito-session.d.ts +25 -0
- package/dist/utils/cognito-session.js +50 -3
- package/package.json +3 -2
- package/src/commands/cloud-provision.test.ts +291 -0
- package/src/commands/cloud-provision.ts +143 -9
- package/src/commands/meetings.ts +487 -0
- package/src/commands/pack-install.test.ts +148 -0
- package/src/commands/pack-install.ts +42 -44
- package/src/index.ts +4 -0
- package/src/utils/cognito-session.test.ts +112 -0
- package/src/utils/cognito-session.ts +46 -1
- package/tsconfig.json +12 -1
|
@@ -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.
|
|
31
|
-
*
|
|
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.
|
|
31
|
-
*
|
|
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]="
|
|
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
|
|
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/ +
|
|
421
|
+
// Move into core/packages/ + run core/scripts/scan-packages.sh
|
|
419
422
|
// ---------------------------------------------------------------------------
|
|
420
|
-
|
|
421
|
-
|
|
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
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
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
|
-
|
|
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=
|
|
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]="
|
|
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=
|
|
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]="
|
|
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
|
-
|
|
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=
|
|
158
|
+
//# debugId=6a8ac157-0903-513d-b4c5-45fb532b8026
|