umbrella-context 0.1.37 → 0.1.39
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/README.md +9 -2
- package/dist/adapters/umbrella-onboarding.js +3 -2
- package/dist/commands/connect.js +3 -2
- package/dist/commands/connectors.js +3 -0
- package/dist/commands/curate.d.ts +13 -1
- package/dist/commands/curate.js +146 -10
- package/dist/commands/fix.js +2 -10
- package/dist/commands/hub.d.ts +3 -1
- package/dist/commands/hub.js +218 -68
- package/dist/commands/interactive.js +21 -10
- package/dist/commands/locations.d.ts +6 -1
- package/dist/commands/locations.js +91 -5
- package/dist/commands/mcp.js +7 -2
- package/dist/commands/pull.d.ts +6 -1
- package/dist/commands/pull.js +119 -3
- package/dist/commands/push.d.ts +6 -1
- package/dist/commands/push.js +147 -3
- package/dist/commands/restart.js +6 -1
- package/dist/commands/search.d.ts +5 -1
- package/dist/commands/search.js +857 -36
- package/dist/commands/session.js +0 -3
- package/dist/commands/setup.js +186 -26
- package/dist/commands/space.js +4 -3
- package/dist/commands/status.d.ts +16 -1
- package/dist/commands/status.js +111 -47
- package/dist/commands/tui.js +339 -107
- package/dist/config.d.ts +4 -0
- package/dist/config.js +6 -0
- package/dist/index.js +21 -3
- package/dist/repo-state.d.ts +115 -0
- package/dist/repo-state.js +195 -12
- package/package.json +2 -2
package/dist/commands/hub.js
CHANGED
|
@@ -2,59 +2,8 @@ import { randomUUID } from "crypto";
|
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import prompts from "prompts";
|
|
4
4
|
import { configManager } from "../config.js";
|
|
5
|
-
import { getInstalledHubEntries, getSessionState, recordSessionEvent, recordSessionHubEntry, setInstalledHubEntries, setSessionPanel, writeHubAsset, writeHubAssetFiles } from "../repo-state.js";
|
|
5
|
+
import { getInstalledHubEntries, getSessionState, recordSessionEvent, recordSessionHubEntry, setInstalledHubEntries, setSessionPanel, writeHubAsset, writeHubAssetFiles, } from "../repo-state.js";
|
|
6
6
|
import { connectorsCommandAction } from "./connectors.js";
|
|
7
|
-
async function runHubBrowseFlow() {
|
|
8
|
-
const answer = await prompts({
|
|
9
|
-
type: "select",
|
|
10
|
-
name: "value",
|
|
11
|
-
message: "Choose a hub entry to browse",
|
|
12
|
-
choices: DEFAULT_HUB_ENTRIES.map((entry) => ({
|
|
13
|
-
title: `${entry.name} (${entry.type})`,
|
|
14
|
-
description: entry.description,
|
|
15
|
-
value: entry.slug,
|
|
16
|
-
})),
|
|
17
|
-
});
|
|
18
|
-
if (!answer.value) {
|
|
19
|
-
console.log(chalk.yellow("\n No hub entry selected."));
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
const inspectResult = await hubCommandAction("inspect", answer.value);
|
|
23
|
-
if (!inspectResult?.installedEntry)
|
|
24
|
-
return;
|
|
25
|
-
const next = await prompts({
|
|
26
|
-
type: "select",
|
|
27
|
-
name: "value",
|
|
28
|
-
message: `What do you want to do with ${inspectResult.installedEntry.name}?`,
|
|
29
|
-
choices: [
|
|
30
|
-
{ title: "Install it into this repo", value: "install" },
|
|
31
|
-
...(inspectResult.recommendedConnector
|
|
32
|
-
? [{ title: `Install and run ${inspectResult.recommendedConnector}`, value: "connector" }]
|
|
33
|
-
: []),
|
|
34
|
-
{ title: "Back", value: "back" },
|
|
35
|
-
],
|
|
36
|
-
});
|
|
37
|
-
if (!next.value || next.value === "back") {
|
|
38
|
-
return {
|
|
39
|
-
action: "browse",
|
|
40
|
-
changed: false,
|
|
41
|
-
installedEntry: inspectResult.installedEntry,
|
|
42
|
-
recommendedConnector: inspectResult.recommendedConnector,
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
const installResult = await hubCommandAction("install", inspectResult.installedEntry.slug);
|
|
46
|
-
if (next.value === "connector" && inspectResult.recommendedConnector) {
|
|
47
|
-
await connectorsCommandAction("run", inspectResult.recommendedConnector);
|
|
48
|
-
}
|
|
49
|
-
return {
|
|
50
|
-
action: "browse",
|
|
51
|
-
changed: Boolean(installResult?.changed),
|
|
52
|
-
installedEntry: installResult?.installedEntry ?? inspectResult.installedEntry,
|
|
53
|
-
assetPath: installResult?.assetPath,
|
|
54
|
-
filePaths: installResult?.filePaths,
|
|
55
|
-
recommendedConnector: inspectResult.recommendedConnector,
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
7
|
const DEFAULT_HUB_ENTRIES = [
|
|
59
8
|
{
|
|
60
9
|
slug: "quick-ping-growth-bundle",
|
|
@@ -93,8 +42,108 @@ const DEFAULT_HUB_ENTRIES = [
|
|
|
93
42
|
installsConnector: "codex-mcp",
|
|
94
43
|
},
|
|
95
44
|
];
|
|
96
|
-
|
|
97
|
-
|
|
45
|
+
function normalizeRemoteHubEntry(raw, registry) {
|
|
46
|
+
if (!raw.slug || !raw.name) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
if (raw.type !== "bundle" && raw.type !== "skill" && raw.type !== "connector") {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
slug: raw.slug,
|
|
54
|
+
name: raw.name,
|
|
55
|
+
type: raw.type,
|
|
56
|
+
description: raw.description ?? "No description provided.",
|
|
57
|
+
includes: Array.isArray(raw.includes) ? raw.includes : [],
|
|
58
|
+
nextSteps: Array.isArray(raw.nextSteps) ? raw.nextSteps : [],
|
|
59
|
+
installsConnector: raw.installsConnector,
|
|
60
|
+
registryName: registry.name,
|
|
61
|
+
registryUrl: registry.url,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function buildRegistryHeaders(registry) {
|
|
65
|
+
const token = registry.token?.trim();
|
|
66
|
+
const scheme = registry.authScheme ?? "none";
|
|
67
|
+
if (!token || scheme === "none") {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
if (scheme === "bearer") {
|
|
71
|
+
return { Authorization: `Bearer ${token}` };
|
|
72
|
+
}
|
|
73
|
+
if (scheme === "token") {
|
|
74
|
+
return { Authorization: `Token ${token}` };
|
|
75
|
+
}
|
|
76
|
+
if (scheme === "basic") {
|
|
77
|
+
return { Authorization: `Basic ${token}` };
|
|
78
|
+
}
|
|
79
|
+
if (scheme === "custom-header") {
|
|
80
|
+
return { [registry.headerName?.trim() || "X-Registry-Token"]: token };
|
|
81
|
+
}
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
async function fetchRegistryStatus(registry) {
|
|
85
|
+
if (registry.url.startsWith("https://hub.umbrella.local/")) {
|
|
86
|
+
return {
|
|
87
|
+
registry,
|
|
88
|
+
status: "ok",
|
|
89
|
+
entryCount: DEFAULT_HUB_ENTRIES.length,
|
|
90
|
+
entries: DEFAULT_HUB_ENTRIES.map((entry) => ({
|
|
91
|
+
...entry,
|
|
92
|
+
registryName: registry.name,
|
|
93
|
+
registryUrl: registry.url,
|
|
94
|
+
})),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
const response = await fetch(registry.url, {
|
|
99
|
+
headers: buildRegistryHeaders(registry),
|
|
100
|
+
});
|
|
101
|
+
if (!response.ok) {
|
|
102
|
+
return {
|
|
103
|
+
registry,
|
|
104
|
+
status: "error",
|
|
105
|
+
entryCount: 0,
|
|
106
|
+
error: `HTTP ${response.status}`,
|
|
107
|
+
entries: [],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
const payload = (await response.json());
|
|
111
|
+
const entries = (payload.entries ?? [])
|
|
112
|
+
.map((entry) => normalizeRemoteHubEntry(entry, registry))
|
|
113
|
+
.filter((entry) => Boolean(entry));
|
|
114
|
+
return {
|
|
115
|
+
registry,
|
|
116
|
+
status: "ok",
|
|
117
|
+
entryCount: entries.length,
|
|
118
|
+
entries,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
return {
|
|
123
|
+
registry,
|
|
124
|
+
status: "error",
|
|
125
|
+
entryCount: 0,
|
|
126
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
127
|
+
entries: [],
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async function getHubCatalog() {
|
|
132
|
+
const statuses = await Promise.all(configManager.hubRegistries.map((registry) => fetchRegistryStatus(registry)));
|
|
133
|
+
const deduped = new Map();
|
|
134
|
+
for (const entry of statuses.flatMap((status) => status.entries)) {
|
|
135
|
+
if (!deduped.has(entry.slug)) {
|
|
136
|
+
deduped.set(entry.slug, entry);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
statuses,
|
|
141
|
+
entries: [...deduped.values()],
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
export async function listHubRegistryEntries() {
|
|
145
|
+
const { entries } = await getHubCatalog();
|
|
146
|
+
return entries;
|
|
98
147
|
}
|
|
99
148
|
function buildHubAsset(entry) {
|
|
100
149
|
return [
|
|
@@ -102,6 +151,7 @@ function buildHubAsset(entry) {
|
|
|
102
151
|
"",
|
|
103
152
|
`Type: ${entry.type}`,
|
|
104
153
|
`Slug: ${entry.slug}`,
|
|
154
|
+
`Registry: ${entry.registryName ?? "Umbrella Hub"}`,
|
|
105
155
|
"",
|
|
106
156
|
"## Why this exists",
|
|
107
157
|
"",
|
|
@@ -246,22 +296,86 @@ function printInstalled(installed) {
|
|
|
246
296
|
console.log(chalk.gray(` Installed from ${entry.registryName} at ${new Date(entry.installedAt).toLocaleString()}`));
|
|
247
297
|
});
|
|
248
298
|
}
|
|
249
|
-
function printRegistryList() {
|
|
299
|
+
function printRegistryList(statuses) {
|
|
250
300
|
console.log(chalk.bold("\n Hub Registries\n"));
|
|
251
|
-
|
|
301
|
+
statuses.forEach(({ registry, status, entryCount, error }, index) => {
|
|
302
|
+
const authLabel = registry.authScheme && registry.authScheme !== "none"
|
|
303
|
+
? ` [${registry.authScheme}]`
|
|
304
|
+
: "";
|
|
305
|
+
const tokenLabel = registry.token ? " (authenticated)" : "";
|
|
306
|
+
const statusLabel = status === "ok"
|
|
307
|
+
? `${entryCount} entr${entryCount === 1 ? "y" : "ies"}`
|
|
308
|
+
: `error: ${error ?? "unknown"}`;
|
|
252
309
|
console.log(` ${index + 1}. ${registry.name}`);
|
|
253
|
-
console.log(chalk.gray(` ${registry.url}`));
|
|
310
|
+
console.log(chalk.gray(` ${registry.url}${authLabel}${tokenLabel}`));
|
|
311
|
+
console.log(chalk.gray(` ${statusLabel}`));
|
|
254
312
|
});
|
|
255
313
|
}
|
|
314
|
+
async function runHubBrowseFlow() {
|
|
315
|
+
const { entries } = await getHubCatalog();
|
|
316
|
+
const answer = await prompts({
|
|
317
|
+
type: "select",
|
|
318
|
+
name: "value",
|
|
319
|
+
message: "Choose a hub entry to browse",
|
|
320
|
+
choices: entries.map((entry) => ({
|
|
321
|
+
title: `${entry.name} (${entry.type})`,
|
|
322
|
+
description: entry.registryName ? `${entry.description} [${entry.registryName}]` : entry.description,
|
|
323
|
+
value: entry.slug,
|
|
324
|
+
})),
|
|
325
|
+
});
|
|
326
|
+
if (!answer.value) {
|
|
327
|
+
console.log(chalk.yellow("\n No hub entry selected."));
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
const inspectResult = await hubCommandAction("inspect", answer.value);
|
|
331
|
+
if (!inspectResult?.installedEntry)
|
|
332
|
+
return;
|
|
333
|
+
const next = await prompts({
|
|
334
|
+
type: "select",
|
|
335
|
+
name: "value",
|
|
336
|
+
message: `What do you want to do with ${inspectResult.installedEntry.name}?`,
|
|
337
|
+
choices: [
|
|
338
|
+
{ title: "Install it into this repo", value: "install" },
|
|
339
|
+
...(inspectResult.recommendedConnector
|
|
340
|
+
? [{ title: `Install and run ${inspectResult.recommendedConnector}`, value: "connector" }]
|
|
341
|
+
: []),
|
|
342
|
+
{ title: "Back", value: "back" },
|
|
343
|
+
],
|
|
344
|
+
});
|
|
345
|
+
if (!next.value || next.value === "back") {
|
|
346
|
+
return {
|
|
347
|
+
action: "browse",
|
|
348
|
+
changed: false,
|
|
349
|
+
installedEntry: inspectResult.installedEntry,
|
|
350
|
+
recommendedConnector: inspectResult.recommendedConnector,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
const installResult = await hubCommandAction("install", inspectResult.installedEntry.slug);
|
|
354
|
+
if (next.value === "connector" && inspectResult.recommendedConnector) {
|
|
355
|
+
await connectorsCommandAction("run", inspectResult.recommendedConnector);
|
|
356
|
+
}
|
|
357
|
+
return {
|
|
358
|
+
action: "browse",
|
|
359
|
+
changed: Boolean(installResult?.changed),
|
|
360
|
+
installedEntry: installResult?.installedEntry ?? inspectResult.installedEntry,
|
|
361
|
+
assetPath: installResult?.assetPath,
|
|
362
|
+
filePaths: installResult?.filePaths,
|
|
363
|
+
recommendedConnector: inspectResult.recommendedConnector,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
256
366
|
export async function hubCommandAction(action, subAction) {
|
|
257
367
|
const normalized = action.toLowerCase();
|
|
258
368
|
await setSessionPanel("hub", subAction ?? normalized);
|
|
259
369
|
if (normalized === "list") {
|
|
260
370
|
await setSessionPanel("hub", "list");
|
|
371
|
+
const { entries } = await getHubCatalog();
|
|
261
372
|
console.log(chalk.bold("\n Hub Catalog\n"));
|
|
262
|
-
|
|
373
|
+
entries.forEach((entry, index) => {
|
|
263
374
|
console.log(` ${index + 1}. ${entry.name} [${entry.type}]`);
|
|
264
375
|
console.log(chalk.gray(` ${entry.description}`));
|
|
376
|
+
if (entry.registryName) {
|
|
377
|
+
console.log(chalk.gray(` Source: ${entry.registryName}`));
|
|
378
|
+
}
|
|
265
379
|
});
|
|
266
380
|
const installed = await getInstalledHubEntries();
|
|
267
381
|
if (installed.length > 0) {
|
|
@@ -274,20 +388,21 @@ export async function hubCommandAction(action, subAction) {
|
|
|
274
388
|
return runHubBrowseFlow();
|
|
275
389
|
}
|
|
276
390
|
if (normalized === "install") {
|
|
391
|
+
const { entries } = await getHubCatalog();
|
|
277
392
|
const installed = await getInstalledHubEntries();
|
|
278
|
-
let selected = subAction ?
|
|
393
|
+
let selected = subAction ? entries.find((entry) => entry.slug === subAction) : undefined;
|
|
279
394
|
if (!selected) {
|
|
280
395
|
const answer = await prompts({
|
|
281
396
|
type: "select",
|
|
282
397
|
name: "value",
|
|
283
398
|
message: "Choose a hub entry to install into this repo",
|
|
284
|
-
choices:
|
|
399
|
+
choices: entries.map((entry) => ({
|
|
285
400
|
title: `${entry.name} (${entry.type})`,
|
|
286
|
-
description: entry.description,
|
|
401
|
+
description: entry.registryName ? `${entry.description} [${entry.registryName}]` : entry.description,
|
|
287
402
|
value: entry.slug,
|
|
288
403
|
})),
|
|
289
404
|
});
|
|
290
|
-
selected =
|
|
405
|
+
selected = entries.find((entry) => entry.slug === answer.value);
|
|
291
406
|
}
|
|
292
407
|
if (!selected) {
|
|
293
408
|
console.log(chalk.yellow("\n No hub entry selected."));
|
|
@@ -311,7 +426,6 @@ export async function hubCommandAction(action, subAction) {
|
|
|
311
426
|
installedEntry: selected,
|
|
312
427
|
};
|
|
313
428
|
}
|
|
314
|
-
const registry = configManager.hubRegistries[0];
|
|
315
429
|
await setInstalledHubEntries([
|
|
316
430
|
...installed,
|
|
317
431
|
{
|
|
@@ -321,7 +435,7 @@ export async function hubCommandAction(action, subAction) {
|
|
|
321
435
|
type: selected.type,
|
|
322
436
|
description: selected.description,
|
|
323
437
|
installedAt: new Date().toISOString(),
|
|
324
|
-
registryName:
|
|
438
|
+
registryName: selected.registryName ?? "Umbrella Hub",
|
|
325
439
|
},
|
|
326
440
|
]);
|
|
327
441
|
const assetPath = await writeHubAsset(selected.slug, buildHubAsset(selected));
|
|
@@ -353,9 +467,8 @@ export async function hubCommandAction(action, subAction) {
|
|
|
353
467
|
};
|
|
354
468
|
}
|
|
355
469
|
if (normalized === "inspect") {
|
|
356
|
-
const
|
|
357
|
-
|
|
358
|
-
: undefined;
|
|
470
|
+
const { entries } = await getHubCatalog();
|
|
471
|
+
const target = subAction ? entries.find((entry) => entry.slug === subAction) : undefined;
|
|
359
472
|
if (!target) {
|
|
360
473
|
console.log(chalk.red("Use: hub inspect <slug>"));
|
|
361
474
|
return;
|
|
@@ -376,6 +489,7 @@ export async function hubCommandAction(action, subAction) {
|
|
|
376
489
|
console.log(` Type: ${target.type}`);
|
|
377
490
|
console.log(` Slug: ${target.slug}`);
|
|
378
491
|
console.log(` Installed: ${isInstalled ? "Yes" : "No"}`);
|
|
492
|
+
console.log(` Registry: ${target.registryName ?? "Umbrella Hub"}`);
|
|
379
493
|
console.log(chalk.gray(` ${target.description}`));
|
|
380
494
|
console.log("");
|
|
381
495
|
console.log(chalk.cyan(" Includes"));
|
|
@@ -417,13 +531,38 @@ export async function hubCommandAction(action, subAction) {
|
|
|
417
531
|
await setSessionPanel("hub", `registry:${subAction ?? "list"}`);
|
|
418
532
|
const mode = (subAction ?? "list").toLowerCase();
|
|
419
533
|
if (mode === "list") {
|
|
420
|
-
|
|
534
|
+
const { statuses } = await getHubCatalog();
|
|
535
|
+
printRegistryList(statuses);
|
|
421
536
|
return { action: "registry", changed: false };
|
|
422
537
|
}
|
|
423
538
|
if (mode === "add") {
|
|
424
539
|
const answer = await prompts([
|
|
425
540
|
{ type: "text", name: "name", message: "Registry name" },
|
|
426
541
|
{ type: "text", name: "url", message: "Registry URL" },
|
|
542
|
+
{
|
|
543
|
+
type: "select",
|
|
544
|
+
name: "authScheme",
|
|
545
|
+
message: "Authentication",
|
|
546
|
+
choices: [
|
|
547
|
+
{ title: "No auth", value: "none" },
|
|
548
|
+
{ title: "Bearer token", value: "bearer" },
|
|
549
|
+
{ title: "Token header", value: "token" },
|
|
550
|
+
{ title: "Basic header", value: "basic" },
|
|
551
|
+
{ title: "Custom header", value: "custom-header" },
|
|
552
|
+
],
|
|
553
|
+
initial: 0,
|
|
554
|
+
},
|
|
555
|
+
{
|
|
556
|
+
type: (_prev, values) => values.authScheme && values.authScheme !== "none" ? "password" : null,
|
|
557
|
+
name: "token",
|
|
558
|
+
message: "Registry token",
|
|
559
|
+
},
|
|
560
|
+
{
|
|
561
|
+
type: (_prev, values) => values.authScheme === "custom-header" ? "text" : null,
|
|
562
|
+
name: "headerName",
|
|
563
|
+
message: "Custom header name",
|
|
564
|
+
initial: "X-Registry-Token",
|
|
565
|
+
},
|
|
427
566
|
]);
|
|
428
567
|
if (!answer.name || !answer.url) {
|
|
429
568
|
console.log(chalk.yellow("\n Registry add cancelled."));
|
|
@@ -433,6 +572,9 @@ export async function hubCommandAction(action, subAction) {
|
|
|
433
572
|
id: randomUUID(),
|
|
434
573
|
name: answer.name,
|
|
435
574
|
url: answer.url,
|
|
575
|
+
authScheme: answer.authScheme ?? "none",
|
|
576
|
+
token: answer.token || undefined,
|
|
577
|
+
headerName: answer.headerName || undefined,
|
|
436
578
|
updatedAt: new Date().toISOString(),
|
|
437
579
|
});
|
|
438
580
|
await recordSessionEvent({
|
|
@@ -444,6 +586,14 @@ export async function hubCommandAction(action, subAction) {
|
|
|
444
586
|
status: "success",
|
|
445
587
|
});
|
|
446
588
|
console.log(chalk.green(`\n Added registry ${answer.name}.`));
|
|
589
|
+
const { statuses } = await getHubCatalog();
|
|
590
|
+
const addedStatus = statuses.find((status) => status.registry.url === answer.url);
|
|
591
|
+
if (addedStatus?.status === "ok") {
|
|
592
|
+
console.log(chalk.gray(` Found ${addedStatus.entryCount} hub entr${addedStatus.entryCount === 1 ? "y" : "ies"} immediately.`));
|
|
593
|
+
}
|
|
594
|
+
else if (addedStatus?.error) {
|
|
595
|
+
console.log(chalk.yellow(` Registry saved, but the first fetch failed: ${addedStatus.error}`));
|
|
596
|
+
}
|
|
447
597
|
return { action: "registry", changed: true };
|
|
448
598
|
}
|
|
449
599
|
if (mode === "remove") {
|
|
@@ -46,7 +46,7 @@ function printWelcomeHeader(providerLabel, modelLabel) {
|
|
|
46
46
|
console.log(` ${chalk.yellow("/reset")} Reset this repo's local Context state`);
|
|
47
47
|
console.log(` ${chalk.yellow("/")} Full command list`);
|
|
48
48
|
console.log(divider());
|
|
49
|
-
console.log(` ${chalk.gray("Shortcuts:")}
|
|
49
|
+
console.log(` ${chalk.gray("Shortcuts:")} start with ${chalk.yellow("?")} to query, or start with ${chalk.yellow("+")} to curate quickly.`);
|
|
50
50
|
console.log("");
|
|
51
51
|
}
|
|
52
52
|
async function printRuntimeSnapshot() {
|
|
@@ -794,7 +794,7 @@ export async function interactiveCommand(_args) {
|
|
|
794
794
|
await setSessionPanel("home", configManager.config.projectName);
|
|
795
795
|
await printRuntimeSnapshot();
|
|
796
796
|
await printHomeView(configManager.config);
|
|
797
|
-
console.log(chalk.gray(" Type / to see commands. Use
|
|
797
|
+
console.log(chalk.gray(" Type / to see commands. Use ? to query or + to curate.\n"));
|
|
798
798
|
while (true) {
|
|
799
799
|
const currentConfig = configManager.config;
|
|
800
800
|
if (!currentConfig) {
|
|
@@ -837,21 +837,32 @@ export async function interactiveCommand(_args) {
|
|
|
837
837
|
}
|
|
838
838
|
continue;
|
|
839
839
|
}
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
840
|
+
if (raw.startsWith("?")) {
|
|
841
|
+
const query = raw.slice(1).trim();
|
|
842
|
+
if (!query) {
|
|
843
|
+
console.log(chalk.yellow("Use ? followed by a real question, for example ? what do we already know about auth timeouts"));
|
|
844
|
+
continue;
|
|
845
|
+
}
|
|
846
|
+
commandHistory.push(`/query ${query}`);
|
|
847
|
+
await recordSessionCommand(`/query ${query}`);
|
|
848
|
+
await recordSessionQuery(query);
|
|
849
|
+
await setSessionSummary(`Queried Context: ${query.length > 48 ? `${query.slice(0, 48)}...` : query}`);
|
|
847
850
|
try {
|
|
848
|
-
await searchCommandAction(
|
|
851
|
+
await searchCommandAction(query);
|
|
849
852
|
}
|
|
850
853
|
catch (err) {
|
|
851
854
|
console.log(chalk.red(`Command failed: ${err.message}`));
|
|
852
855
|
}
|
|
853
856
|
continue;
|
|
854
857
|
}
|
|
858
|
+
const { command, args } = parseInteractiveInput(raw);
|
|
859
|
+
const spec = resolveCommand(command);
|
|
860
|
+
if (!raw.startsWith("/") && !spec) {
|
|
861
|
+
console.log(chalk.yellow("That text was not run automatically."));
|
|
862
|
+
console.log(chalk.gray("Use /query <question> or start with ? to search Context."));
|
|
863
|
+
console.log(chalk.gray("Use /curate <note> or start with + to save something locally."));
|
|
864
|
+
continue;
|
|
865
|
+
}
|
|
855
866
|
if (!spec) {
|
|
856
867
|
console.log(chalk.yellow(`Unknown command: ${raw}. Type / to see available commands.`));
|
|
857
868
|
continue;
|
|
@@ -1,2 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
type OutputFormat = "text" | "json";
|
|
2
|
+
export declare function locationsCommandAction(opts?: {
|
|
3
|
+
format?: OutputFormat;
|
|
4
|
+
detail?: boolean;
|
|
5
|
+
}): Promise<void>;
|
|
2
6
|
export declare function locationsCommand(cli: any): void;
|
|
7
|
+
export {};
|
|
@@ -1,20 +1,106 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
+
import { promises as fs } from "fs";
|
|
3
|
+
import path from "path";
|
|
2
4
|
import { configManager } from "../config.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
+
import { findRepoRoot, getRepoContext } from "../repo-state.js";
|
|
6
|
+
async function getCurrentRepoRoot() {
|
|
7
|
+
try {
|
|
8
|
+
const { repoRoot } = await getRepoContext();
|
|
9
|
+
return repoRoot;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
async function summarizeSavedLocations() {
|
|
16
|
+
const currentRepoRoot = await getCurrentRepoRoot();
|
|
17
|
+
const storedLocations = configManager.locations;
|
|
18
|
+
const normalizedLocations = await Promise.all(storedLocations.map(async (location) => {
|
|
19
|
+
try {
|
|
20
|
+
const canonicalRepoRoot = await findRepoRoot(location.repoRoot);
|
|
21
|
+
return canonicalRepoRoot === location.repoRoot ? location : { ...location, repoRoot: canonicalRepoRoot };
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return location;
|
|
25
|
+
}
|
|
26
|
+
}));
|
|
27
|
+
const dedupedLocations = Array.from(new Map(normalizedLocations.map((location) => [
|
|
28
|
+
location.repoRoot,
|
|
29
|
+
location,
|
|
30
|
+
])).values());
|
|
31
|
+
const locationChanged = dedupedLocations.length !== storedLocations.length
|
|
32
|
+
|| dedupedLocations.some((location, index) => {
|
|
33
|
+
const original = storedLocations[index];
|
|
34
|
+
return !original || original.repoRoot !== location.repoRoot;
|
|
35
|
+
});
|
|
36
|
+
if (locationChanged) {
|
|
37
|
+
configManager.replaceLocations(dedupedLocations);
|
|
38
|
+
}
|
|
39
|
+
const snapshots = await Promise.all(dedupedLocations.map(async (location) => {
|
|
40
|
+
const umDir = path.join(location.repoRoot, ".um");
|
|
41
|
+
const contextFile = path.join(umDir, "context.json");
|
|
42
|
+
const [umDirStat, contextStat] = await Promise.allSettled([fs.stat(umDir), fs.stat(contextFile)]);
|
|
43
|
+
const hasUmDir = umDirStat.status === "fulfilled" && umDirStat.value.isDirectory();
|
|
44
|
+
const hasContextFile = contextStat.status === "fulfilled" && contextStat.value.isFile();
|
|
45
|
+
return {
|
|
46
|
+
...location,
|
|
47
|
+
isCurrentRepo: currentRepoRoot !== null && currentRepoRoot === location.repoRoot,
|
|
48
|
+
hasUmDir,
|
|
49
|
+
hasContextFile,
|
|
50
|
+
isLinked: hasUmDir && hasContextFile,
|
|
51
|
+
};
|
|
52
|
+
}));
|
|
53
|
+
return snapshots.sort((a, b) => a.repoRoot.localeCompare(b.repoRoot));
|
|
54
|
+
}
|
|
55
|
+
function printLocationsJson(locations) {
|
|
56
|
+
console.log(JSON.stringify({
|
|
57
|
+
ok: true,
|
|
58
|
+
action: "locations",
|
|
59
|
+
count: locations.length,
|
|
60
|
+
locations,
|
|
61
|
+
}, null, 2));
|
|
62
|
+
}
|
|
63
|
+
export async function locationsCommandAction(opts = {}) {
|
|
64
|
+
const locations = await summarizeSavedLocations();
|
|
5
65
|
if (locations.length === 0) {
|
|
66
|
+
if (opts.format === "json") {
|
|
67
|
+
printLocationsJson([]);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
6
70
|
console.log(chalk.yellow("\n No repo locations saved yet."));
|
|
7
71
|
return;
|
|
8
72
|
}
|
|
73
|
+
if (opts.format === "json") {
|
|
74
|
+
printLocationsJson(locations);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
9
77
|
console.log(chalk.bold("\n Saved Context Locations\n"));
|
|
10
78
|
locations.forEach((location, index) => {
|
|
11
|
-
|
|
79
|
+
const badges = [];
|
|
80
|
+
if (location.isCurrentRepo)
|
|
81
|
+
badges.push(chalk.green("CURRENT"));
|
|
82
|
+
if (location.isLinked)
|
|
83
|
+
badges.push(chalk.cyan("LINKED"));
|
|
84
|
+
else
|
|
85
|
+
badges.push(chalk.yellow("SAVED ONLY"));
|
|
86
|
+
console.log(chalk.cyan(` ${index + 1}. ${location.companyName} / ${location.projectName} ${badges.join(" ")}`));
|
|
12
87
|
console.log(chalk.gray(` ${location.repoRoot}`));
|
|
13
88
|
console.log(chalk.gray(` Updated: ${new Date(location.updatedAt).toLocaleString()}`));
|
|
89
|
+
if (opts.detail) {
|
|
90
|
+
console.log(chalk.gray(` .um folder: ${location.hasUmDir ? "Yes" : "No"}`));
|
|
91
|
+
console.log(chalk.gray(` .um/context.json: ${location.hasContextFile ? "Yes" : "No"}`));
|
|
92
|
+
}
|
|
14
93
|
});
|
|
15
94
|
}
|
|
16
95
|
export function locationsCommand(cli) {
|
|
17
|
-
cli
|
|
18
|
-
|
|
96
|
+
cli
|
|
97
|
+
.command("locations", "List repo folders that have been connected on this machine")
|
|
98
|
+
.option("--format <format>", "Output format (text or json)")
|
|
99
|
+
.option("--detail", "Show whether each saved repo still has a linked .um folder and context file")
|
|
100
|
+
.example("locations")
|
|
101
|
+
.example("locations --detail")
|
|
102
|
+
.example("locations --format json")
|
|
103
|
+
.action(async (opts) => {
|
|
104
|
+
await locationsCommandAction(opts);
|
|
19
105
|
});
|
|
20
106
|
}
|
package/dist/commands/mcp.js
CHANGED
|
@@ -22,11 +22,16 @@ export function mcpCommand(cli) {
|
|
|
22
22
|
const data = await res.json();
|
|
23
23
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
24
24
|
});
|
|
25
|
-
server.tool("memory_push", "Save a memory or note to the current context space", {
|
|
25
|
+
server.tool("memory_push", "Save a memory or note to the current context space", {
|
|
26
|
+
content: z.string().describe("The memory content"),
|
|
27
|
+
tags: z.array(z.string()).optional(),
|
|
28
|
+
category: z.enum(["project", "team", "convention", "environment", "preference", "incident", "other"]).optional(),
|
|
29
|
+
accessLevel: z.enum(["space", "company"]).optional(),
|
|
30
|
+
}, async ({ content, tags, category, accessLevel }) => {
|
|
26
31
|
const res = await fetch(`${config.serverUrl}/api/memories`, {
|
|
27
32
|
method: "POST",
|
|
28
33
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${config.apiKey}` },
|
|
29
|
-
body: JSON.stringify({ content, tags: tags || [], source: "mcp" }),
|
|
34
|
+
body: JSON.stringify({ content, tags: tags || [], category, accessLevel, source: "mcp" }),
|
|
30
35
|
});
|
|
31
36
|
const data = await res.json();
|
|
32
37
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
package/dist/commands/pull.d.ts
CHANGED
|
@@ -1,2 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
type OutputFormat = "text" | "json";
|
|
2
|
+
export declare function pullCommandAction(opts?: {
|
|
3
|
+
format?: OutputFormat;
|
|
4
|
+
dryRun?: boolean;
|
|
5
|
+
}): Promise<void>;
|
|
2
6
|
export declare function pullCommand(cli: any): void;
|
|
7
|
+
export {};
|