umbrella-context 0.1.2 → 0.1.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/commands/catalog.d.ts +33 -0
  2. package/dist/commands/catalog.js +211 -0
  3. package/dist/commands/connect.js +14 -14
  4. package/dist/commands/connectors.d.ts +15 -0
  5. package/dist/commands/connectors.js +620 -0
  6. package/dist/commands/curate.d.ts +1 -0
  7. package/dist/commands/curate.js +39 -3
  8. package/dist/commands/debug.d.ts +2 -0
  9. package/dist/commands/debug.js +55 -0
  10. package/dist/commands/hub.d.ts +20 -0
  11. package/dist/commands/hub.js +429 -0
  12. package/dist/commands/interactive.d.ts +2 -0
  13. package/dist/commands/interactive.js +918 -62
  14. package/dist/commands/locations.d.ts +1 -0
  15. package/dist/commands/locations.js +15 -12
  16. package/dist/commands/logout.d.ts +2 -0
  17. package/dist/commands/logout.js +34 -0
  18. package/dist/commands/model.d.ts +11 -0
  19. package/dist/commands/model.js +211 -0
  20. package/dist/commands/providers.d.ts +17 -0
  21. package/dist/commands/providers.js +344 -0
  22. package/dist/commands/pull.js +10 -1
  23. package/dist/commands/push.js +18 -1
  24. package/dist/commands/restart.d.ts +2 -0
  25. package/dist/commands/restart.js +21 -0
  26. package/dist/commands/search.js +19 -1
  27. package/dist/commands/session.d.ts +2 -0
  28. package/dist/commands/session.js +128 -0
  29. package/dist/commands/space.d.ts +1 -0
  30. package/dist/commands/space.js +81 -63
  31. package/dist/commands/status.d.ts +25 -0
  32. package/dist/commands/status.js +104 -16
  33. package/dist/config.d.ts +23 -0
  34. package/dist/config.js +69 -0
  35. package/dist/index.js +26 -4
  36. package/dist/repo-state.d.ts +84 -0
  37. package/dist/repo-state.js +419 -3
  38. package/package.json +1 -1
@@ -1,96 +1,945 @@
1
1
  import chalk from "chalk";
2
2
  import prompts from "prompts";
3
3
  import { configManager } from "../config.js";
4
- import { getCompanyContextSummary, toContextSpaces } from "../umbrella.js";
5
- import { curateCommandAction } from "./curate.js";
6
- import { pushCommandAction } from "./push.js";
7
- import { pullCommandAction } from "./pull.js";
4
+ import { ensureSessionState, getConnectorRuns, getInstalledConnectors, getInstalledHubEntries, getPendingMemories, getPulledFixes, getPulledMemories, recordSessionEvent, recordSessionCommand, recordSessionCuration, recordSessionQuery, setSessionPanel, resetSessionState, setSessionSummary, } from "../repo-state.js";
5
+ import { createContextSpace, getCompanyContextSummary, toContextSpaces } from "../umbrella.js";
6
+ import { parseInteractiveInput, renderCommandList, resolveCommand } from "./catalog.js";
8
7
  import { searchCommandAction } from "./search.js";
9
- import { statusCommandAction } from "./status.js";
10
- export async function interactiveCommand(_args) {
8
+ import { curateCommandAction } from "./curate.js";
9
+ import { spaceCommandAction } from "./space.js";
10
+ import { getProviderReadinessSummary, providersCommandAction } from "./providers.js";
11
+ import { getModelsForActiveProvider, modelCommandAction } from "./model.js";
12
+ import { hubCommandAction } from "./hub.js";
13
+ import { connectorsCommandAction } from "./connectors.js";
14
+ import { getStatusSnapshot } from "./status.js";
15
+ import { debugCommandAction } from "./debug.js";
16
+ import { logoutCommandAction } from "./logout.js";
17
+ import { restartCommandAction } from "./restart.js";
18
+ function divider() {
19
+ return chalk.gray("----------------------------------------------------------------");
20
+ }
21
+ function labelValue(label, value) {
22
+ return ` ${chalk.cyan(label.padEnd(18))} ${value}`;
23
+ }
24
+ function printWelcomeHeader(providerLabel, modelLabel) {
25
+ const timeLabel = new Date().toLocaleTimeString([], { hour: "numeric", minute: "2-digit" });
26
+ console.log(chalk.cyan("UMBRELLA CONTEXT") + chalk.gray(` ${timeLabel}`));
27
+ console.log(divider());
28
+ console.log(chalk.bold(" Command-first local Context for this repo"));
29
+ console.log(chalk.gray(` Runtime: ${providerLabel}${modelLabel ? ` (${modelLabel})` : ""}`));
30
+ console.log(chalk.gray(" Save to .um first. Push only when you want the team to share it."));
31
+ console.log(divider());
32
+ console.log(` ${chalk.yellow("/home")} Current repo and sync overview`);
33
+ console.log(` ${chalk.yellow("/query")} Ask Context a question`);
34
+ console.log(` ${chalk.yellow("/curate")} Save a reusable note locally`);
35
+ console.log(` ${chalk.yellow("/recent")} Show recent local activity`);
36
+ console.log(` ${chalk.yellow("/timeline")} Show the recent session event timeline`);
37
+ console.log(` ${chalk.yellow("/session")} Current terminal session summary`);
38
+ console.log(` ${chalk.yellow("/panel")} Show the active panel and focus`);
39
+ console.log(` ${chalk.yellow("/continue")} Re-open the last active panel`);
40
+ console.log(` ${chalk.yellow("/status")} Inspect sync, provider, and MCP state`);
41
+ console.log(` ${chalk.yellow("/restart")} Refresh this live terminal session`);
42
+ console.log(` ${chalk.yellow("/debug")} Show config paths and local runtime details`);
43
+ console.log(` ${chalk.yellow("/logout")} Disconnect this device from Umbrella Context`);
44
+ console.log(` ${chalk.yellow("/")} Full command list`);
45
+ console.log(divider());
46
+ console.log(` ${chalk.gray("Shortcuts:")} type plain text to query, or start with ${chalk.yellow("+")} to curate quickly.`);
47
+ console.log("");
48
+ }
49
+ async function printRuntimeSnapshot() {
11
50
  const config = configManager.config;
12
- if (!config) {
13
- console.log(chalk.red("Not configured. Run: umbrella-context setup"));
51
+ if (!config)
52
+ return;
53
+ const activeProvider = configManager.providers.find((entry) => entry.id === config.activeProvider) ?? null;
54
+ const providerLabel = activeProvider ? activeProvider.name : "No provider connected";
55
+ const modelLabel = config.activeModel ?? "";
56
+ printWelcomeHeader(providerLabel, modelLabel);
57
+ }
58
+ async function printHomeView(config) {
59
+ await setSessionPanel("home", config.projectName);
60
+ const snapshot = await getStatusSnapshot();
61
+ const pending = await getPendingMemories();
62
+ const hubEntries = await getInstalledHubEntries();
63
+ const connectors = await getInstalledConnectors();
64
+ const connectorRuns = await getConnectorRuns();
65
+ const session = await ensureSessionState();
66
+ const latestConnectorRun = connectorRuns[0] ?? null;
67
+ console.log(chalk.bold(` ${config.companyName} / ${config.projectName}`));
68
+ console.log(divider());
69
+ console.log(chalk.bold(" Sync"));
70
+ console.log(labelValue("Pending drafts", String(snapshot.pendingCount)));
71
+ console.log(labelValue("Pulled context", String(snapshot.pulledContextCount)));
72
+ console.log(labelValue("Known fixes", String(snapshot.pulledFixCount)));
73
+ console.log("");
74
+ console.log(chalk.bold(" Repo"));
75
+ console.log(labelValue("Hub installs", String(hubEntries.length)));
76
+ console.log(labelValue("Connectors", String(connectors.length)));
77
+ console.log(labelValue("Last connector run", latestConnectorRun ? `${latestConnectorRun.connectorName} (${latestConnectorRun.status})` : "Never"));
78
+ console.log("");
79
+ console.log(chalk.bold(" Runtime"));
80
+ console.log(labelValue("Provider", snapshot.providerLabel));
81
+ console.log(labelValue("Model", snapshot.modelLabel));
82
+ console.log(labelValue("Repo MCP", snapshot.mcpConfigured ? "Ready" : "Needs setup"));
83
+ console.log(labelValue("Current panel", session.currentPanel ?? "Home"));
84
+ console.log(labelValue("Current focus", session.currentFocus ?? "None"));
85
+ console.log(labelValue("Latest event", session.events?.[0]?.title ?? "Nothing yet"));
86
+ console.log("");
87
+ console.log(chalk.bold(" Suggested next steps"));
88
+ if (snapshot.nextSteps.length > 0) {
89
+ snapshot.nextSteps.forEach((step) => console.log(` - ${step}`));
90
+ }
91
+ else if (pending.length > 0) {
92
+ console.log(` - ${chalk.yellow("/push")} to share ${pending.length} local draft${pending.length === 1 ? "" : "s"}`);
93
+ }
94
+ else {
95
+ console.log(` - ${chalk.yellow('/curate "What you learned"')} to stage a new local note`);
96
+ console.log(` - ${chalk.yellow("/query What do we already know about this?")} to search local and server memory`);
97
+ }
98
+ console.log(` - ${chalk.yellow("/recent")} to inspect the latest repo activity`);
99
+ console.log("");
100
+ }
101
+ function printSessionHint(title, lines) {
102
+ console.log("");
103
+ console.log(chalk.bold(` ${title}`));
104
+ lines.forEach((line) => console.log(` - ${line}`));
105
+ console.log("");
106
+ }
107
+ function printRecentSection(title, items, render) {
108
+ console.log(chalk.bold(` ${title}`));
109
+ if (items.length === 0) {
110
+ console.log(chalk.gray(" Nothing recent yet."));
111
+ console.log("");
112
+ return;
113
+ }
114
+ items.forEach((item, index) => {
115
+ console.log(render(item, index));
116
+ });
117
+ console.log("");
118
+ }
119
+ async function printSessionView() {
120
+ await setSessionPanel("session", "summary");
121
+ const session = await ensureSessionState();
122
+ console.log("");
123
+ console.log(chalk.bold(" Current Session"));
124
+ console.log(divider());
125
+ console.log(labelValue("Session ID", session.id));
126
+ console.log(labelValue("Started", session.startedAt));
127
+ console.log(labelValue("Updated", session.updatedAt));
128
+ console.log(labelValue("Commands", String(session.commandHistory.length)));
129
+ console.log(labelValue("Queries", String(session.recentQueries.length)));
130
+ console.log(labelValue("Curations", String(session.recentCurations.length)));
131
+ console.log(labelValue("Last summary", session.lastSummary ?? "Nothing recorded yet"));
132
+ console.log(labelValue("Current panel", session.currentPanel ?? "None"));
133
+ console.log(labelValue("Current focus", session.currentFocus ?? "None"));
134
+ console.log(labelValue("Events", String(session.events?.length ?? 0)));
135
+ console.log("");
136
+ if (session.panelHistory.length > 0) {
137
+ console.log(chalk.bold(" Panel history"));
138
+ session.panelHistory.slice(0, 5).forEach((entry, index) => console.log(` ${index + 1}. ${entry}`));
139
+ console.log("");
140
+ }
141
+ if (session.recentQueries.length > 0) {
142
+ console.log(chalk.bold(" Recent Queries"));
143
+ session.recentQueries.slice(0, 3).forEach((entry, index) => console.log(` ${index + 1}. ${entry}`));
144
+ console.log("");
145
+ }
146
+ if (session.recentCurations.length > 0) {
147
+ console.log(chalk.bold(" Recent Curations"));
148
+ session.recentCurations.slice(0, 3).forEach((entry, index) => console.log(` ${index + 1}. ${entry}`));
149
+ console.log("");
150
+ }
151
+ if (session.recentProviderIds.length > 0 || session.recentModels.length > 0) {
152
+ console.log(chalk.bold(" Runtime recents"));
153
+ if (session.recentProviderIds.length > 0) {
154
+ const names = session.recentProviderIds
155
+ .map((id) => configManager.providers.find((entry) => entry.id === id)?.name ?? id)
156
+ .join(", ");
157
+ console.log(` Providers: ${names}`);
158
+ }
159
+ if (session.recentModels.length > 0) {
160
+ console.log(` Models: ${session.recentModels.join(", ")}`);
161
+ }
162
+ console.log("");
163
+ }
164
+ if (session.recentHubSlugs.length > 0 || session.recentConnectorSources.length > 0) {
165
+ console.log(chalk.bold(" Ecosystem recents"));
166
+ if (session.recentHubSlugs.length > 0) {
167
+ console.log(` Hub: ${session.recentHubSlugs.join(", ")}`);
168
+ }
169
+ if (session.recentConnectorSources.length > 0) {
170
+ console.log(` Connectors: ${session.recentConnectorSources.join(", ")}`);
171
+ }
172
+ console.log("");
173
+ }
174
+ if ((session.events?.length ?? 0) > 0) {
175
+ console.log(chalk.bold(" Recent timeline"));
176
+ session.events.slice(0, 5).forEach((entry, index) => {
177
+ console.log(` ${index + 1}. [${entry.kind}] ${entry.title}`);
178
+ if (entry.detail) {
179
+ console.log(chalk.gray(` ${entry.detail}`));
180
+ }
181
+ });
182
+ console.log("");
183
+ }
184
+ }
185
+ async function printProviderOverview() {
186
+ const readiness = getProviderReadinessSummary();
187
+ const providerCount = configManager.providers.length;
188
+ console.log("");
189
+ console.log(chalk.bold(" Provider Runtime"));
190
+ console.log(divider());
191
+ console.log(labelValue("Connected", String(providerCount)));
192
+ console.log(labelValue("Active provider", readiness.activeProvider?.name ?? "Not connected"));
193
+ console.log(labelValue("Active model", readiness.activeModel ?? "Not selected"));
194
+ console.log(labelValue("Runtime state", !readiness.providerReady
195
+ ? "Needs provider"
196
+ : !readiness.modelReady
197
+ ? "Needs model"
198
+ : "Ready"));
199
+ console.log("");
200
+ }
201
+ async function printModelOverview() {
202
+ const readiness = getProviderReadinessSummary();
203
+ const models = getModelsForActiveProvider();
204
+ console.log("");
205
+ console.log(chalk.bold(" Model Runtime"));
206
+ console.log(divider());
207
+ console.log(labelValue("Provider", readiness.activeProvider?.name ?? "Not connected"));
208
+ console.log(labelValue("Active model", readiness.activeModel ?? "Not selected"));
209
+ console.log(labelValue("Available models", String(models.length)));
210
+ if (models.length > 0) {
211
+ console.log("");
212
+ console.log(chalk.bold(" Available Models"));
213
+ models.forEach((model, index) => {
214
+ const active = readiness.activeModel === model ? " (active)" : "";
215
+ console.log(` ${index + 1}. ${model}${active}`);
216
+ });
217
+ console.log("");
218
+ }
219
+ }
220
+ async function printHubOverview() {
221
+ const installed = await getInstalledHubEntries();
222
+ console.log("");
223
+ console.log(chalk.bold(" Hub Overview"));
224
+ console.log(divider());
225
+ console.log(labelValue("Catalog entries", "3"));
226
+ console.log(labelValue("Installed", String(installed.length)));
227
+ if (installed.length > 0) {
228
+ console.log("");
229
+ console.log(chalk.bold(" Installed Bundles"));
230
+ installed.slice(0, 5).forEach((entry, index) => {
231
+ console.log(` ${index + 1}. ${entry.name} [${entry.type}]`);
232
+ });
233
+ console.log("");
234
+ }
235
+ }
236
+ async function printConnectorOverview() {
237
+ const connectors = await getInstalledConnectors();
238
+ const connectorRuns = await getConnectorRuns();
239
+ const latest = connectorRuns[0] ?? null;
240
+ console.log("");
241
+ console.log(chalk.bold(" Connector Overview"));
242
+ console.log(divider());
243
+ console.log(labelValue("Installed", String(connectors.length)));
244
+ console.log(labelValue("Last run", latest ? `${latest.connectorName} (${latest.status})` : "Never"));
245
+ if (connectors.length > 0) {
246
+ console.log("");
247
+ console.log(chalk.bold(" Installed Connectors"));
248
+ connectors.forEach((entry, index) => {
249
+ console.log(` ${index + 1}. ${entry.name} [${entry.type}]`);
250
+ });
251
+ console.log("");
252
+ }
253
+ }
254
+ async function printCurrentPanelView() {
255
+ const session = await ensureSessionState();
256
+ const latestPanelEvent = (session.events ?? []).find((entry) => entry.panel === session.currentPanel);
257
+ console.log("");
258
+ console.log(chalk.bold(" Live Panel"));
259
+ console.log(divider());
260
+ console.log(labelValue("Panel", session.currentPanel ?? "None"));
261
+ console.log(labelValue("Focus", session.currentFocus ?? "None"));
262
+ console.log(labelValue("Last panel event", latestPanelEvent?.title ?? "Nothing panel-specific yet"));
263
+ if (session.panelHistory.length > 0) {
264
+ console.log("");
265
+ console.log(chalk.bold(" Recent panels"));
266
+ session.panelHistory.slice(0, 5).forEach((entry, index) => console.log(` ${index + 1}. ${entry}`));
267
+ }
268
+ console.log("");
269
+ }
270
+ async function printTimelineView() {
271
+ await setSessionPanel("timeline", "events");
272
+ const session = await ensureSessionState();
273
+ console.log("");
274
+ console.log(chalk.bold(" Session Timeline"));
275
+ console.log(divider());
276
+ if ((session.events?.length ?? 0) === 0) {
277
+ console.log(chalk.gray(" No session events yet."));
278
+ console.log("");
279
+ return;
280
+ }
281
+ session.events.slice(0, 12).forEach((entry, index) => {
282
+ const badge = entry.status === "success" ? chalk.green(`[${entry.kind}]`)
283
+ : entry.status === "warning" ? chalk.yellow(`[${entry.kind}]`)
284
+ : entry.status === "failure" ? chalk.red(`[${entry.kind}]`)
285
+ : chalk.cyan(`[${entry.kind}]`);
286
+ const focusSuffix = entry.focus ? ` -> ${entry.focus}` : "";
287
+ console.log(` ${index + 1}. ${badge} ${entry.title}${focusSuffix}`);
288
+ console.log(chalk.gray(` ${new Date(entry.at).toLocaleString()}`));
289
+ if (entry.detail) {
290
+ console.log(chalk.gray(` ${entry.detail}`));
291
+ }
292
+ });
293
+ console.log("");
294
+ }
295
+ async function continueLastPanel(config) {
296
+ const session = await ensureSessionState();
297
+ const panel = session.currentPanel;
298
+ const focus = session.currentFocus ?? undefined;
299
+ if (!panel) {
300
+ console.log(chalk.yellow("\n No current panel to continue yet."));
301
+ return;
302
+ }
303
+ if (panel === "home") {
304
+ await printHomeView(config);
305
+ return;
306
+ }
307
+ if (panel === "providers") {
308
+ if (focus) {
309
+ await providersCommandAction("inspect", focus);
310
+ return;
311
+ }
312
+ await openProvidersMenu();
313
+ return;
314
+ }
315
+ if (panel === "model") {
316
+ if (focus) {
317
+ await modelCommandAction("inspect", focus);
318
+ return;
319
+ }
320
+ await openModelMenu();
14
321
  return;
15
322
  }
16
- console.log(chalk.bold(`\n Umbrella Context - ${config.companyName} / ${config.projectName}\n`));
17
- console.log(chalk.gray(" Commands: curate, query, push, pull, fix, record, spaces, status, exit\n"));
323
+ if (panel === "hub") {
324
+ if (focus && !focus.startsWith("registry:") && focus !== "list" && focus !== "installed") {
325
+ await hubCommandAction("inspect", focus);
326
+ return;
327
+ }
328
+ if (focus?.startsWith("registry:")) {
329
+ await hubCommandAction("registry", "list");
330
+ return;
331
+ }
332
+ await openHubMenu();
333
+ return;
334
+ }
335
+ if (panel === "connectors") {
336
+ if (focus && focus !== "list") {
337
+ await connectorsCommandAction("inspect", focus);
338
+ return;
339
+ }
340
+ await openConnectorsMenu();
341
+ return;
342
+ }
343
+ if (panel === "space" || panel === "spaces" || panel === "projects") {
344
+ await openSpaceMenu(config);
345
+ return;
346
+ }
347
+ if (panel === "session") {
348
+ await printSessionView();
349
+ return;
350
+ }
351
+ if (panel === "recent") {
352
+ await handleRecent(config);
353
+ return;
354
+ }
355
+ if (panel === "timeline") {
356
+ await printTimelineView();
357
+ return;
358
+ }
359
+ if (panel === "status") {
360
+ await import("./status.js").then((mod) => mod.statusCommandAction());
361
+ return;
362
+ }
363
+ console.log(chalk.yellow(`\n No resume handler yet for panel "${panel}".`));
364
+ }
365
+ async function openSpaceMenu(config) {
18
366
  while (true) {
19
- const input = await prompts({
20
- type: "text",
367
+ await setSessionPanel("space", config.projectName);
368
+ const summary = await getCompanyContextSummary(config.umbrellaUrl, config.companyId);
369
+ const spaces = toContextSpaces(summary);
370
+ const answer = await prompts({
371
+ type: "select",
21
372
  name: "value",
22
- message: ">",
373
+ message: `Context spaces for ${config.companyName}`,
374
+ choices: [
375
+ { title: "Overview", description: "See the current company and space summary", value: "overview" },
376
+ { title: "List spaces", description: "Show all spaces for this company", value: "list" },
377
+ { title: "Switch space", description: "Move this repo to another space", value: "switch" },
378
+ { title: "Create space", description: "Add a new team lane like Growth or Engineering", value: "create" },
379
+ { title: "Back", value: "back" },
380
+ ],
23
381
  });
24
- if (!input.value)
382
+ if (!answer.value || answer.value === "back")
383
+ return;
384
+ if (answer.value === "overview") {
385
+ await handleSpaces(config);
386
+ continue;
387
+ }
388
+ if (answer.value === "list") {
389
+ await spaceCommandAction("list");
25
390
  continue;
26
- const parts = input.value.trim().split(/\s+/);
27
- const cmd = parts[0].toLowerCase();
28
- const rest = parts.slice(1).join(" ");
29
- if (cmd === "exit" || cmd === "quit")
30
- break;
31
- if (cmd === "curate") {
32
- await curateCommandAction(rest);
33
391
  }
34
- else if (cmd === "push") {
35
- await pushCommandAction();
392
+ if (answer.value === "switch") {
393
+ if (spaces.length === 0) {
394
+ console.log(chalk.yellow("\n No spaces found for this company."));
395
+ continue;
396
+ }
397
+ await spaceCommandAction("switch");
398
+ continue;
399
+ }
400
+ if (answer.value === "create") {
401
+ const createAnswer = await prompts({
402
+ type: "text",
403
+ name: "name",
404
+ message: "Name the new context space",
405
+ validate: (value) => (value.trim().length > 1 ? true : "Use at least 2 characters."),
406
+ });
407
+ const nextName = String(createAnswer.name ?? "").trim();
408
+ if (!nextName) {
409
+ console.log(chalk.yellow("\n No space name provided."));
410
+ continue;
411
+ }
412
+ await createContextSpace(config.umbrellaUrl, config.companyId, nextName);
413
+ await recordSessionEvent({
414
+ kind: "space",
415
+ title: `Created space ${nextName}`,
416
+ detail: `A new Context space was created inside ${config.companyName}.`,
417
+ panel: "space",
418
+ focus: nextName,
419
+ status: "success",
420
+ });
421
+ console.log(chalk.green(`\n Created ${nextName}.`));
422
+ const switchNow = await prompts({
423
+ type: "confirm",
424
+ name: "value",
425
+ message: `Switch this repo to ${nextName} now?`,
426
+ initial: true,
427
+ });
428
+ if (switchNow.value) {
429
+ await spaceCommandAction("switch", nextName);
430
+ }
431
+ continue;
36
432
  }
37
- else if (cmd === "pull") {
38
- await pullCommandAction();
433
+ }
434
+ }
435
+ async function openProvidersMenu() {
436
+ while (true) {
437
+ const readiness = getProviderReadinessSummary();
438
+ const activeProvider = readiness.activeProvider;
439
+ await setSessionPanel("providers", activeProvider?.id ?? null);
440
+ const answer = await prompts({
441
+ type: "select",
442
+ name: "value",
443
+ message: activeProvider ? `Provider flow (active: ${activeProvider.name})` : "Provider flow",
444
+ choices: [
445
+ { title: "Overview", description: "See provider runtime readiness for this device", value: "overview" },
446
+ { title: "List providers", description: "See providers already saved on this device", value: "list" },
447
+ { title: "Inspect provider", description: "Read the details and next steps for one provider", value: "inspect" },
448
+ { title: "Inspect recent provider", description: "Open the provider you touched most recently", value: "recent" },
449
+ { title: "Test runtime", description: "Check whether the active provider is actually ready to run", value: "test" },
450
+ { title: "Connect provider", description: "Add a new provider API key on this device", value: "connect" },
451
+ { title: "Switch provider", description: "Choose the active provider", value: "switch" },
452
+ { title: "Disconnect provider", description: "Remove a saved provider from this device", value: "disconnect" },
453
+ { title: "Choose model", description: "Pick the active model for the current provider", value: "model" },
454
+ { title: "Back", value: "back" },
455
+ ],
456
+ });
457
+ if (!answer.value || answer.value === "back")
458
+ return;
459
+ if (answer.value === "overview") {
460
+ await printProviderOverview();
461
+ continue;
39
462
  }
40
- else if (cmd === "search" || cmd === "query") {
41
- await searchCommandAction(rest);
463
+ if (answer.value === "model") {
464
+ await openModelMenu();
465
+ continue;
42
466
  }
43
- else if (cmd === "fix") {
44
- await handleFix(rest, config);
467
+ const result = await providersCommandAction(answer.value);
468
+ if (result?.activeProvider) {
469
+ const lines = [`Active provider: ${result.activeProvider.name}`];
470
+ if (result.activeProvider.baseUrl) {
471
+ lines.push(`Base URL: ${result.activeProvider.baseUrl}`);
472
+ }
473
+ lines.push(result.needsModelSelection
474
+ ? "No model is selected yet, so the runtime is only partially ready."
475
+ : "Provider and model are both ready for terminal work.");
476
+ printSessionHint("Provider Session Summary", lines);
45
477
  }
46
- else if (cmd === "record") {
47
- await handleRecord(parts.slice(1), config);
478
+ if ((answer.value === "connect" || answer.value === "switch") && result?.needsModelSelection) {
479
+ const chooseModel = await prompts({
480
+ type: "confirm",
481
+ name: "value",
482
+ message: "Choose a model now?",
483
+ initial: true,
484
+ });
485
+ if (chooseModel.value) {
486
+ const modelResult = await modelCommandAction("switch");
487
+ if (modelResult?.activeModel) {
488
+ printSessionHint("Runtime Ready", [
489
+ `Provider: ${modelResult.providerName ?? "Unknown"}`,
490
+ `Model: ${modelResult.activeModel}`,
491
+ 'You can now type a plain question or use "/query ..." directly.',
492
+ ]);
493
+ }
494
+ }
48
495
  }
49
- else if (cmd === "projects" || cmd === "spaces") {
50
- await handleSpaces(config);
496
+ }
497
+ }
498
+ async function openModelMenu() {
499
+ while (true) {
500
+ const currentConfig = configManager.config;
501
+ const provider = configManager.providers.find((entry) => entry.id === currentConfig?.activeProvider) ?? null;
502
+ const models = getModelsForActiveProvider();
503
+ await setSessionPanel("model", currentConfig?.activeModel ?? provider?.id ?? null);
504
+ const answer = await prompts({
505
+ type: "select",
506
+ name: "value",
507
+ message: provider ? `Model flow (${provider.name})` : "Model flow",
508
+ choices: [
509
+ { title: "Overview", description: "See active provider, model, and available choices", value: "overview" },
510
+ { title: "List models", description: `See models for the active provider (${models.length})`, value: "list" },
511
+ { title: "Inspect model", description: "Read details and the next step for a specific model", value: "inspect" },
512
+ { title: "Inspect recent model", description: "Open the model you touched most recently", value: "recent" },
513
+ { title: "Show readiness", description: "Check whether the model runtime is fully ready", value: "ready" },
514
+ { title: "Switch model", description: "Choose the active model", value: "switch" },
515
+ { title: "Back", value: "back" },
516
+ ],
517
+ });
518
+ if (!answer.value || answer.value === "back")
519
+ return;
520
+ if (answer.value === "overview") {
521
+ await printModelOverview();
522
+ continue;
523
+ }
524
+ const result = await modelCommandAction(answer.value);
525
+ if (result?.action === "switch" && result.activeModel) {
526
+ printSessionHint("Model Session Summary", [
527
+ `Provider: ${result.providerName ?? "Unknown"}`,
528
+ `Active model: ${result.activeModel}`,
529
+ 'The runtime is ready. Try a plain question or run "/query ...".',
530
+ ]);
531
+ }
532
+ }
533
+ }
534
+ async function openHubMenu() {
535
+ while (true) {
536
+ const session = await ensureSessionState();
537
+ await setSessionPanel("hub", session.currentFocus);
538
+ const answer = await prompts({
539
+ type: "select",
540
+ name: "value",
541
+ message: "Hub flow",
542
+ choices: [
543
+ { title: "Overview", description: "See installed bundles and the local hub state", value: "overview" },
544
+ { title: "Browse catalog", description: "See available bundles, skills, and connector packs", value: "list" },
545
+ { title: "Install hub entry", description: "Install a bundle into this repo", value: "install" },
546
+ { title: "Inspect bundle", description: "Pick a hub entry and read what it includes", value: "inspect" },
547
+ { title: "Inspect recent bundle", description: "Open the last hub entry you touched in this session", value: "recent" },
548
+ { title: "Installed hub entries", description: "See what this repo already has", value: "installed" },
549
+ { title: "Registries", description: "See configured hub registries", value: "registry" },
550
+ { title: "Back", value: "back" },
551
+ ],
552
+ });
553
+ if (!answer.value || answer.value === "back")
554
+ return;
555
+ if (answer.value === "overview") {
556
+ await printHubOverview();
557
+ continue;
558
+ }
559
+ if (answer.value === "registry") {
560
+ await hubCommandAction("registry", "list");
561
+ continue;
562
+ }
563
+ if (answer.value === "recent") {
564
+ const recent = session.recentHubSlugs[0];
565
+ if (!recent) {
566
+ console.log(chalk.yellow("\n No recent hub entry in this session yet."));
567
+ continue;
568
+ }
569
+ await hubCommandAction("inspect", recent);
570
+ continue;
571
+ }
572
+ if (answer.value === "inspect") {
573
+ const inspectAnswer = await prompts({
574
+ type: "select",
575
+ name: "value",
576
+ message: "Choose a hub entry to inspect",
577
+ choices: [
578
+ { title: "Quick Ping Growth Bundle", value: "quick-ping-growth-bundle" },
579
+ { title: "Engineering Debug Skill Pack", value: "engineering-debug-skill-pack" },
580
+ { title: "Umbrella Context MCP Connector", value: "umbrella-context-mcp-connector" },
581
+ ],
582
+ });
583
+ if (!inspectAnswer.value) {
584
+ console.log(chalk.yellow("\n No hub entry selected."));
585
+ continue;
586
+ }
587
+ await hubCommandAction("inspect", inspectAnswer.value);
588
+ continue;
589
+ }
590
+ const result = await hubCommandAction(answer.value);
591
+ if (result?.action === "install" && result.installedEntry) {
592
+ const lines = [`Installed: ${result.installedEntry.name}`];
593
+ if (result.assetPath)
594
+ lines.push(`Asset file: ${result.assetPath}`);
595
+ if (result.filePaths && result.filePaths.length > 0) {
596
+ lines.push(`${result.filePaths.length} repo file${result.filePaths.length === 1 ? "" : "s"} added or refreshed`);
597
+ }
598
+ if (result.recommendedConnector) {
599
+ lines.push(`Recommended connector: ${result.recommendedConnector}`);
600
+ }
601
+ printSessionHint("Hub Install Summary", lines);
602
+ const next = await prompts({
603
+ type: "confirm",
604
+ name: "value",
605
+ message: "Open connectors now to run anything that bundle installed?",
606
+ initial: true,
607
+ });
608
+ if (next.value) {
609
+ await openConnectorsMenu();
610
+ }
611
+ }
612
+ }
613
+ }
614
+ async function openConnectorsMenu() {
615
+ while (true) {
616
+ const session = await ensureSessionState();
617
+ await setSessionPanel("connectors", session.currentFocus);
618
+ const answer = await prompts({
619
+ type: "select",
620
+ name: "value",
621
+ message: "Connector flow",
622
+ choices: [
623
+ { title: "Overview", description: "See installed connectors and the latest run state", value: "overview" },
624
+ { title: "List connectors", description: "See repo connectors already installed", value: "list" },
625
+ { title: "Inspect connector", description: "Read connector details and recommended next steps", value: "inspect" },
626
+ { title: "Inspect recent connector", description: "Open the connector you touched most recently", value: "recent" },
627
+ { title: "View outputs", description: "See connector-written files and the latest run summary", value: "outputs" },
628
+ { title: "Install connector", description: "Add a connector into this repo", value: "install" },
629
+ { title: "Run connector", description: "Execute a connector inside this repo", value: "run" },
630
+ { title: "Back", value: "back" },
631
+ ],
632
+ });
633
+ if (!answer.value || answer.value === "back")
634
+ return;
635
+ if (answer.value === "overview") {
636
+ await printConnectorOverview();
637
+ continue;
51
638
  }
52
- else if (cmd === "status") {
53
- await statusCommandAction();
639
+ let target;
640
+ if (answer.value === "run" || answer.value === "inspect" || answer.value === "outputs") {
641
+ const installed = await getInstalledConnectors();
642
+ if (installed.length === 0) {
643
+ console.log(chalk.yellow("\n No connectors installed in this repo yet."));
644
+ continue;
645
+ }
646
+ const runAnswer = await prompts({
647
+ type: "select",
648
+ name: "value",
649
+ message: answer.value === "outputs" ? "Choose a connector to inspect outputs for" : "Choose a connector to run",
650
+ choices: installed.map((entry) => ({
651
+ title: `${entry.name} (${entry.type})`,
652
+ value: entry.source,
653
+ })),
654
+ });
655
+ target = runAnswer.value;
656
+ if (!target) {
657
+ console.log(chalk.yellow("\n No connector selected."));
658
+ continue;
659
+ }
54
660
  }
55
- else if (cmd === "help") {
56
- console.log(chalk.gray(`
57
- curate <content> Save a local context note into .um
58
- query <query> Search local and server context
59
- push Sync local .um notes to the server
60
- pull Refresh the local snapshot from the server
61
- fix <error> Search known fixes
62
- record <id> <outcome> Record success/failure
63
- spaces Show the current company and active space
64
- status Show current sync status
65
- exit Quit
66
- `));
661
+ const result = await connectorsCommandAction(answer.value, target);
662
+ if (result?.action === "install" && result.connectorName) {
663
+ const lines = [`Connector: ${result.connectorName}`];
664
+ if (result.repoConfigPath)
665
+ lines.push(`Repo MCP wiring: ${result.repoConfigPath}`);
666
+ if (result.filePaths && result.filePaths.length > 0) {
667
+ lines.push(`${result.filePaths.length} connector file${result.filePaths.length === 1 ? "" : "s"} added or refreshed`);
668
+ }
669
+ lines.push('Running the connector will verify repo wiring and save a local run report in ".um".');
670
+ printSessionHint("Connector Install Summary", lines);
671
+ const runNow = await prompts({
672
+ type: "confirm",
673
+ name: "value",
674
+ message: "Run a connector now?",
675
+ initial: true,
676
+ });
677
+ if (runNow.value) {
678
+ const runResult = await connectorsCommandAction("run");
679
+ if (runResult?.connectorName) {
680
+ const lines = [
681
+ `Connector: ${runResult.connectorName}`,
682
+ `Status: ${runResult.status ?? "unknown"}`,
683
+ ];
684
+ if (runResult.summary)
685
+ lines.push(runResult.summary);
686
+ if (runResult.stagedMemoryId)
687
+ lines.push(`Staged local draft: ${runResult.stagedMemoryId}`);
688
+ if (runResult.reportPath)
689
+ lines.push(`Run report: ${runResult.reportPath}`);
690
+ if (runResult.status === "success")
691
+ lines.push('Next move: run "/status" to review the updated repo state.');
692
+ printSessionHint("Connector Run Summary", lines);
693
+ }
694
+ }
67
695
  }
68
- else {
69
- console.log(chalk.yellow(`Unknown command: ${cmd}. Type 'help' for commands.`));
696
+ else if (result?.action === "run" && result.connectorName) {
697
+ const lines = [
698
+ `Connector: ${result.connectorName}`,
699
+ `Status: ${result.status ?? "unknown"}`,
700
+ ];
701
+ if (result.summary)
702
+ lines.push(result.summary);
703
+ if (result.stagedMemoryId)
704
+ lines.push(`Staged local draft: ${result.stagedMemoryId}`);
705
+ if (result.reportPath)
706
+ lines.push(`Run report: ${result.reportPath}`);
707
+ printSessionHint("Connector Run Summary", lines);
70
708
  }
71
709
  }
72
710
  }
711
+ async function maybeHandleNativeMenu(command, args, config) {
712
+ if (args.length > 0)
713
+ return false;
714
+ if (command === "space" || command === "spaces" || command === "projects") {
715
+ await openSpaceMenu(config);
716
+ return true;
717
+ }
718
+ if (command === "providers") {
719
+ await openProvidersMenu();
720
+ return true;
721
+ }
722
+ if (command === "model") {
723
+ await openModelMenu();
724
+ return true;
725
+ }
726
+ if (command === "hub") {
727
+ await openHubMenu();
728
+ return true;
729
+ }
730
+ if (command === "connectors") {
731
+ await openConnectorsMenu();
732
+ return true;
733
+ }
734
+ return false;
735
+ }
736
+ export async function interactiveCommand(_args) {
737
+ if (!configManager.config) {
738
+ console.log(chalk.red("Not configured. Run: umbrella-context setup"));
739
+ return;
740
+ }
741
+ const commandHistory = [];
742
+ await ensureSessionState();
743
+ await setSessionPanel("home", configManager.config.projectName);
744
+ await printRuntimeSnapshot();
745
+ await printHomeView(configManager.config);
746
+ console.log(chalk.gray(" Type / to see commands. Use plain text to query or start with + to curate.\n"));
747
+ while (true) {
748
+ const currentConfig = configManager.config;
749
+ if (!currentConfig) {
750
+ console.log(chalk.red("Config was cleared. Run umbrella-context setup again."));
751
+ break;
752
+ }
753
+ const input = await prompts({
754
+ type: "text",
755
+ name: "value",
756
+ message: chalk.cyan(`${currentConfig.companyName.toLowerCase().replace(/\s+/g, "-")}:${currentConfig.projectName.toLowerCase()}`),
757
+ initial: "/",
758
+ });
759
+ if (typeof input.value !== "string")
760
+ break;
761
+ const raw = input.value.trim();
762
+ if (!raw)
763
+ continue;
764
+ if (raw === "/" || raw === "help" || raw === "/help") {
765
+ console.log(chalk.gray(`\n${renderCommandList()}\n`));
766
+ continue;
767
+ }
768
+ if (raw === "exit" || raw === "/exit" || raw === "quit" || raw === "/quit") {
769
+ break;
770
+ }
771
+ if (raw.startsWith("+")) {
772
+ const content = raw.slice(1).trim();
773
+ if (!content) {
774
+ console.log(chalk.yellow("Use + followed by what you learned, for example +We learned the CTA must name the next step."));
775
+ continue;
776
+ }
777
+ commandHistory.push(`/curate ${content}`);
778
+ await recordSessionCommand(`/curate ${content}`);
779
+ await recordSessionCuration(content);
780
+ await setSessionSummary(`Curated a new local note: ${content.length > 48 ? `${content.slice(0, 48)}...` : content}`);
781
+ try {
782
+ await curateCommandAction(content);
783
+ }
784
+ catch (err) {
785
+ console.log(chalk.red(`Command failed: ${err.message}`));
786
+ }
787
+ continue;
788
+ }
789
+ const { command, args } = parseInteractiveInput(raw);
790
+ const spec = resolveCommand(command);
791
+ if (!raw.startsWith("/") && !spec) {
792
+ commandHistory.push(`/query ${raw}`);
793
+ await recordSessionCommand(`/query ${raw}`);
794
+ await recordSessionQuery(raw);
795
+ await setSessionSummary(`Queried Context: ${raw.length > 48 ? `${raw.slice(0, 48)}...` : raw}`);
796
+ try {
797
+ await searchCommandAction(raw);
798
+ }
799
+ catch (err) {
800
+ console.log(chalk.red(`Command failed: ${err.message}`));
801
+ }
802
+ continue;
803
+ }
804
+ if (!spec) {
805
+ console.log(chalk.yellow(`Unknown command: ${raw}. Type / to see available commands.`));
806
+ continue;
807
+ }
808
+ commandHistory.push(raw.startsWith("/") ? raw : `/${command} ${args.join(" ")}`.trim());
809
+ await recordSessionCommand(raw.startsWith("/") ? raw : `/${command} ${args.join(" ")}`.trim());
810
+ try {
811
+ const handledByMenu = await maybeHandleNativeMenu(command, args, currentConfig);
812
+ if (handledByMenu) {
813
+ continue;
814
+ }
815
+ await spec.action(args, {
816
+ config: currentConfig,
817
+ handleFix,
818
+ handleRecord,
819
+ handleSpaces,
820
+ handleHome: async (config) => printHomeView(config),
821
+ handleRecent,
822
+ handleTimeline: async () => printTimelineView(),
823
+ handleHistory: async () => printHistory(commandHistory),
824
+ handleSession: async () => printSessionView(),
825
+ handleNewSession: async () => {
826
+ const next = await resetSessionState();
827
+ commandHistory.length = 0;
828
+ await recordSessionEvent({
829
+ kind: "session",
830
+ title: "Started a fresh shell session",
831
+ detail: `Session ID: ${next.id}`,
832
+ panel: "session",
833
+ focus: next.id,
834
+ status: "info",
835
+ });
836
+ printSessionHint("Fresh Session Started", [
837
+ `Session ID: ${next.id}`,
838
+ "Your repo context and .um data were kept.",
839
+ "Your command history for this shell was reset.",
840
+ ]);
841
+ },
842
+ handleRestart: async () => {
843
+ await restartCommandAction();
844
+ commandHistory.length = 0;
845
+ },
846
+ handleDebug: async () => {
847
+ await debugCommandAction();
848
+ },
849
+ handleLogout: async () => {
850
+ await logoutCommandAction();
851
+ commandHistory.length = 0;
852
+ },
853
+ handlePanel: async () => {
854
+ await printCurrentPanelView();
855
+ },
856
+ handleContinue: async () => {
857
+ await continueLastPanel(currentConfig);
858
+ },
859
+ });
860
+ }
861
+ catch (err) {
862
+ console.log(chalk.red(`Command failed: ${err.message}`));
863
+ }
864
+ }
865
+ }
866
+ async function handleRecent(_config) {
867
+ await setSessionPanel("recent", "activity");
868
+ const pending = await getPendingMemories();
869
+ const pulled = await getPulledMemories();
870
+ const runs = await getConnectorRuns();
871
+ const session = await ensureSessionState();
872
+ console.log("");
873
+ printRecentSection("Recent session queries", session.recentQueries.slice(0, 3), (item, index) => {
874
+ return ` ${index + 1}. ${item}`;
875
+ });
876
+ printRecentSection("Recent session curations", session.recentCurations.slice(0, 3), (item, index) => {
877
+ return ` ${index + 1}. ${item}`;
878
+ });
879
+ printRecentSection("Recent local drafts", pending.slice(-3).reverse(), (item, index) => {
880
+ const preview = item.content.length > 72 ? `${item.content.slice(0, 72)}...` : item.content;
881
+ return ` ${index + 1}. ${preview} ${chalk.gray(`[${item.source}]`)}`;
882
+ });
883
+ printRecentSection("Recent pulled context", pulled.slice(-3).reverse(), (item, index) => {
884
+ const preview = item.content.length > 72 ? `${item.content.slice(0, 72)}...` : item.content;
885
+ return ` ${index + 1}. ${preview}`;
886
+ });
887
+ printRecentSection("Recent connector runs", runs.slice(0, 3), (item, index) => {
888
+ return ` ${index + 1}. ${item.connectorName} ${chalk.gray(`(${item.status})`)} - ${item.summary}`;
889
+ });
890
+ }
891
+ async function printHistory(commandHistory) {
892
+ const session = await ensureSessionState();
893
+ console.log("");
894
+ console.log(chalk.bold(" Session command history"));
895
+ const history = session.commandHistory.length > 0 ? session.commandHistory : commandHistory;
896
+ if (history.length === 0) {
897
+ console.log(chalk.gray(" No commands yet."));
898
+ console.log("");
899
+ return;
900
+ }
901
+ history.slice(-10).forEach((entry, index) => {
902
+ console.log(` ${index + 1}. ${entry}`);
903
+ });
904
+ console.log("");
905
+ }
73
906
  async function handleFix(error, config) {
74
907
  if (!error) {
75
- console.log(chalk.red("Usage: fix <error>"));
908
+ console.log(chalk.red("Usage: /fix <error>"));
76
909
  return;
77
910
  }
78
911
  try {
912
+ const localFixes = await getPulledFixes();
913
+ const localMatches = localFixes.filter((entry) => [entry.errorSignal, entry.solution].join(" ").toLowerCase().includes(error.toLowerCase()));
914
+ if (localMatches.length > 0) {
915
+ console.log(chalk.cyan("\n Local cached fixes\n"));
916
+ localMatches.forEach((result, index) => {
917
+ const pct = Math.round(result.confidence * 100);
918
+ console.log(chalk.cyan(` ${index + 1}. [${pct}%] ${result.errorSignal}`));
919
+ console.log(` ${result.solution}`);
920
+ });
921
+ console.log("");
922
+ }
79
923
  const res = await fetch(`${config.serverUrl}/api/evolutions/search?error=${encodeURIComponent(error)}`, { headers: { Authorization: `Bearer ${config.apiKey}` } });
80
- if (!res.ok)
924
+ if (!res.ok) {
925
+ console.log(chalk.red("Server search failed."));
81
926
  return;
927
+ }
82
928
  const data = await res.json();
83
- if (data.results.length === 0) {
929
+ if (data.results.length === 0 && localMatches.length === 0) {
84
930
  console.log(chalk.yellow("No known fixes"));
85
931
  return;
86
932
  }
87
- data.results.forEach((result, index) => {
88
- const pct = Math.round(result.confidence * 100);
89
- console.log(chalk.cyan(` ${index + 1}. [${pct}%]`));
90
- console.log(` ${result.solution}`);
91
- console.log(chalk.gray(` ${result.timesSucceeded}/${result.timesApplied} successes | ID: ${result.id}`));
92
- console.log("");
93
- });
933
+ if (data.results.length > 0) {
934
+ console.log(chalk.cyan(" Server results\n"));
935
+ data.results.forEach((result, index) => {
936
+ const pct = Math.round(result.confidence * 100);
937
+ console.log(chalk.cyan(` ${index + 1}. [${pct}%]`));
938
+ console.log(` ${result.solution}`);
939
+ console.log(chalk.gray(` ${result.timesSucceeded}/${result.timesApplied} successes | ID: ${result.id}`));
940
+ console.log("");
941
+ });
942
+ }
94
943
  }
95
944
  catch (err) {
96
945
  console.log(chalk.red(`Error: ${err.message}`));
@@ -99,7 +948,7 @@ async function handleFix(error, config) {
99
948
  async function handleRecord(parts, config) {
100
949
  const [id, outcome] = parts;
101
950
  if (!id || !["success", "failure"].includes(outcome)) {
102
- console.log(chalk.red("Usage: record <id> <success|failure>"));
951
+ console.log(chalk.red("Usage: /record <id> <success|failure>"));
103
952
  return;
104
953
  }
105
954
  try {
@@ -111,8 +960,10 @@ async function handleRecord(parts, config) {
111
960
  },
112
961
  body: JSON.stringify({ outcome }),
113
962
  });
114
- if (!res.ok)
963
+ if (!res.ok) {
964
+ console.log(chalk.red("Could not record the fix outcome."));
115
965
  return;
966
+ }
116
967
  const data = await res.json();
117
968
  const pct = Math.round(data.confidence * 100);
118
969
  console.log(chalk.green(`Recorded ${outcome} - confidence: ${pct}%`));
@@ -122,8 +973,12 @@ async function handleRecord(parts, config) {
122
973
  }
123
974
  }
124
975
  async function handleSpaces(config) {
976
+ const pending = await getPendingMemories();
977
+ const pulled = await getPulledMemories();
125
978
  console.log(chalk.cyan(` Current company: ${config.companyName}`));
126
979
  console.log(chalk.cyan(` Current space: ${config.projectName}`));
980
+ console.log(chalk.gray(` Local context notes waiting to push: ${pending.length}`));
981
+ console.log(chalk.gray(` Local pulled snapshot size: ${pulled.length}`));
127
982
  if (!config.umbrellaUrl) {
128
983
  console.log(chalk.gray(" Run setup again if you want this terminal to list all company spaces."));
129
984
  return;
@@ -137,9 +992,10 @@ async function handleSpaces(config) {
137
992
  const core = space.isPrimary ? " [core]" : "";
138
993
  console.log(chalk.gray(` ${index + 1}. ${space.name}${core}${active}`));
139
994
  });
140
- console.log(chalk.gray('\n To switch spaces, run "umbrella-context space switch".'));
995
+ console.log(chalk.gray('\n Run "/space switch" to move this repo to another space.'));
141
996
  }
142
997
  catch (err) {
143
998
  console.log(chalk.red(` Could not load spaces: ${err.message}`));
144
999
  }
145
1000
  }
1001
+ export { renderCommandList };