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.
@@ -226,9 +226,6 @@ export function sessionCommand(cli) {
226
226
  cli.command("recent", "Show saved recent repo activity").action(async () => {
227
227
  await sessionCommandAction("recent");
228
228
  });
229
- cli.command("activity", "Show the saved richer repo activity feed").action(async () => {
230
- await sessionCommandAction("activity");
231
- });
232
229
  cli.command("timeline", "Show the saved session event timeline").action(async () => {
233
230
  await sessionCommandAction("timeline");
234
231
  });
@@ -6,6 +6,7 @@ import { UmbrellaRequestError } from "../umbrella.js";
6
6
  import { setUmbrellaAuthAuthorized, setUmbrellaAuthChecking, setUmbrellaAuthFailure, } from "../adapters/umbrella-auth-runtime.js";
7
7
  import { checkUmbrellaOnboardingServer, connectUmbrellaDevice, createUmbrellaOnboardingCompany, createUmbrellaOnboardingSpace, loadUmbrellaOnboardingSpaces, normalizeUmbrellaServerUrl, signInUmbrellaOnboarding, } from "../adapters/umbrella-onboarding.js";
8
8
  const DEFAULT_UMBRELLA_SERVER_URL = "http://5.161.55.138:3100";
9
+ const LOCAL_UMBRELLA_SERVER_URL = "http://127.0.0.1:3100";
9
10
  function isLocalOnlyServerUrl(value) {
10
11
  try {
11
12
  const parsed = new URL(value);
@@ -16,6 +17,29 @@ function isLocalOnlyServerUrl(value) {
16
17
  return false;
17
18
  }
18
19
  }
20
+ function detectMixedSavedConnection(umbrellaUrl, serverUrl) {
21
+ if (!umbrellaUrl || !serverUrl)
22
+ return null;
23
+ try {
24
+ const umbrella = new URL(umbrellaUrl);
25
+ const backend = new URL(serverUrl);
26
+ const umbrellaIsLoopback = isLocalOnlyServerUrl(umbrellaUrl);
27
+ const backendIsLoopback = isLocalOnlyServerUrl(serverUrl);
28
+ const differentHosts = umbrella.hostname !== backend.hostname || umbrella.port !== backend.port;
29
+ if (!differentHosts && umbrellaIsLoopback === backendIsLoopback) {
30
+ return null;
31
+ }
32
+ return {
33
+ backendUrl: serverUrl,
34
+ umbrellaUrl,
35
+ umbrellaIsLoopback,
36
+ backendIsLoopback,
37
+ };
38
+ }
39
+ catch {
40
+ return null;
41
+ }
42
+ }
19
43
  function printConnected(setup) {
20
44
  console.log(chalk.green(`\n Connected to ${setup.companyName} / ${setup.activeSpaceName}`));
21
45
  console.log(chalk.gray(` Context space: ${setup.activeSpaceName}`));
@@ -63,6 +87,69 @@ function describeSetupFailure(error) {
63
87
  }
64
88
  return error.message;
65
89
  }
90
+ async function promptForCredentials(options) {
91
+ const emailPrompt = await prompts({
92
+ message: "Umbrella email",
93
+ name: "value",
94
+ type: options.email ? null : "text",
95
+ });
96
+ const passwordPrompt = await prompts({
97
+ message: "Umbrella password",
98
+ name: "value",
99
+ type: options.password ? null : "password",
100
+ });
101
+ const email = options.email?.trim() || emailPrompt.value;
102
+ const password = options.password?.trim() || passwordPrompt.value;
103
+ if (!email || !password) {
104
+ return null;
105
+ }
106
+ return { email, password };
107
+ }
108
+ async function attemptAuthenticatedSignIn(serverUrl, options) {
109
+ const canRetryInteractively = !options.email && !options.password;
110
+ const maxAttempts = canRetryInteractively ? 3 : 1;
111
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
112
+ const credentials = await promptForCredentials(options);
113
+ if (!credentials) {
114
+ return { kind: "cancelled", error: new Error("Sign-in cancelled.") };
115
+ }
116
+ const { email, password } = credentials;
117
+ try {
118
+ console.log(chalk.gray(` Signing into Umbrella as ${email} ...`));
119
+ const signIn = await signInUmbrellaOnboarding(serverUrl, email, password);
120
+ return {
121
+ companies: signIn.companies,
122
+ cookie: signIn.cookie,
123
+ email,
124
+ kind: "success",
125
+ };
126
+ }
127
+ catch (error) {
128
+ const message = describeSetupFailure(error);
129
+ console.log(chalk.red(` Sign-in failed: ${message}`));
130
+ if (attempt >= maxAttempts) {
131
+ return { kind: "failure", error };
132
+ }
133
+ const retryPrompt = await prompts({
134
+ choices: [
135
+ { title: "Try email and password again", value: "retry" },
136
+ { title: "Cancel setup", value: "cancel" },
137
+ ],
138
+ initial: 0,
139
+ message: "The live Umbrella sign-in did not work. What do you want to do?",
140
+ name: "value",
141
+ type: "select",
142
+ });
143
+ if (retryPrompt.value !== "retry") {
144
+ return { kind: "cancelled", error: new Error("Sign-in cancelled.") };
145
+ }
146
+ }
147
+ }
148
+ return {
149
+ kind: "failure",
150
+ error: new Error("Sign-in failed."),
151
+ };
152
+ }
66
153
  async function chooseCompany(serverUrl, companies, cookie, initialCompanyId) {
67
154
  if (initialCompanyId) {
68
155
  const matched = companies.find((company) => company.id === initialCompanyId);
@@ -132,30 +219,108 @@ async function chooseSpace(serverUrl, companyId, spaces, cookie, initialSpaceId)
132
219
  }
133
220
  export function setupCommand(cli) {
134
221
  cli
135
- .command("setup", "Sign into Umbrella, choose a company, and connect this device to a context space")
222
+ .command("setup", `Sign into Umbrella, choose a company, and connect this device to a context space
223
+
224
+ Use this the first time you connect a machine, when the saved company or space looks wrong, or when status says your live Umbrella connection has not been verified yet.`)
136
225
  .option("--server-url <url>", "Umbrella server URL")
137
226
  .option("--email <email>", "Umbrella email for authenticated mode")
138
227
  .option("--password <password>", "Umbrella password for authenticated mode")
139
228
  .option("--company-id <id>", "Existing Umbrella company ID")
140
229
  .option("--space-id <id>", "Existing context space ID")
230
+ .example("setup")
231
+ .example("setup --server-url http://5.161.55.138:3100")
232
+ .example("setup --server-url http://127.0.0.1:3100")
141
233
  .action(async (options) => {
142
234
  console.log(chalk.bold("\n Umbrella Context Setup\n"));
143
235
  const existing = configManager.config;
144
236
  const envServerUrl = process.env.UMBRELLA_SERVER_URL?.trim();
237
+ const mixedSavedConnection = detectMixedSavedConnection(existing?.umbrellaUrl, existing?.serverUrl);
145
238
  const suggestedServerUrl = options.serverUrl?.trim() ||
146
239
  envServerUrl ||
240
+ mixedSavedConnection?.backendUrl ||
147
241
  existing?.umbrellaUrl ||
148
242
  existing?.serverUrl ||
149
243
  DEFAULT_UMBRELLA_SERVER_URL;
150
244
  console.log(chalk.gray(` Defaulting to the live Umbrella server at ${DEFAULT_UMBRELLA_SERVER_URL}. Only use 127.0.0.1 if you are intentionally running Umbrella on this same computer instead.`));
151
- const serverUrlPrompt = await prompts({
152
- initial: suggestedServerUrl,
153
- message: "Umbrella server URL",
154
- name: "value",
155
- type: options.serverUrl ? null : "text",
156
- });
157
- const serverUrl = normalizeUmbrellaServerUrl(options.serverUrl?.trim() ||
158
- serverUrlPrompt.value ||
245
+ if (mixedSavedConnection) {
246
+ console.log(chalk.yellow(` Saved setup is mixed right now: Umbrella app URL is ${mixedSavedConnection.umbrellaUrl}, but the Context backend URL is ${mixedSavedConnection.backendUrl}.`));
247
+ console.log(chalk.yellow(" That usually means the device was partly pointed at local Umbrella and partly at the live server. Setup can repair that now."));
248
+ }
249
+ let serverUrlInput = options.serverUrl?.trim() || "";
250
+ if (!serverUrlInput) {
251
+ const normalizedSuggestedServer = normalizeUmbrellaServerUrl(suggestedServerUrl);
252
+ const defaultServerChoice = mixedSavedConnection
253
+ ? mixedSavedConnection.backendIsLoopback
254
+ ? "local"
255
+ : normalizedSuggestedServer === DEFAULT_UMBRELLA_SERVER_URL
256
+ ? "live"
257
+ : "custom"
258
+ : normalizedSuggestedServer === DEFAULT_UMBRELLA_SERVER_URL
259
+ ? "live"
260
+ : isLocalOnlyServerUrl(normalizedSuggestedServer)
261
+ ? "local"
262
+ : "custom";
263
+ const serverChoicePrompt = await prompts({
264
+ choices: [
265
+ {
266
+ description: "Recommended for most people. Uses the shared live Umbrella server.",
267
+ title: `Live Umbrella server (${DEFAULT_UMBRELLA_SERVER_URL})`,
268
+ value: "live",
269
+ },
270
+ {
271
+ description: "Only use this if Umbrella is running on the same computer right now.",
272
+ title: `Local development server (${LOCAL_UMBRELLA_SERVER_URL})`,
273
+ value: "local",
274
+ },
275
+ {
276
+ description: mixedSavedConnection
277
+ ? "Use this if the saved app/backend URLs were mixed and you want to repair them to some other server."
278
+ : "Type a different server URL yourself.",
279
+ title: "Custom URL",
280
+ value: "custom",
281
+ },
282
+ ],
283
+ initial: ["live", "local", "custom"].indexOf(defaultServerChoice),
284
+ message: "Which Umbrella server should this device use?",
285
+ name: "value",
286
+ type: "select",
287
+ });
288
+ if (!serverChoicePrompt.value) {
289
+ console.log(chalk.red("\n Setup cancelled."));
290
+ return;
291
+ }
292
+ if (serverChoicePrompt.value === "live") {
293
+ serverUrlInput = DEFAULT_UMBRELLA_SERVER_URL;
294
+ }
295
+ else if (serverChoicePrompt.value === "local") {
296
+ serverUrlInput = LOCAL_UMBRELLA_SERVER_URL;
297
+ }
298
+ else {
299
+ const customServerPrompt = await prompts({
300
+ initial: defaultServerChoice === "custom"
301
+ ? normalizedSuggestedServer
302
+ : DEFAULT_UMBRELLA_SERVER_URL,
303
+ message: "Custom Umbrella server URL",
304
+ name: "value",
305
+ type: "text",
306
+ validate: (value) => {
307
+ try {
308
+ normalizeUmbrellaServerUrl(value);
309
+ return true;
310
+ }
311
+ catch {
312
+ return "Enter a valid URL like http://5.161.55.138:3100";
313
+ }
314
+ },
315
+ });
316
+ if (!customServerPrompt.value) {
317
+ console.log(chalk.red("\n Setup cancelled."));
318
+ return;
319
+ }
320
+ serverUrlInput = customServerPrompt.value;
321
+ }
322
+ }
323
+ const serverUrl = normalizeUmbrellaServerUrl(serverUrlInput ||
159
324
  envServerUrl ||
160
325
  existing?.umbrellaUrl ||
161
326
  existing?.serverUrl ||
@@ -163,29 +328,24 @@ export function setupCommand(cli) {
163
328
  try {
164
329
  let cookie = null;
165
330
  let companies = [];
331
+ let signedInEmail = options.email?.trim() || null;
332
+ console.log(chalk.gray(` Checking ${serverUrl} ...`));
166
333
  setUmbrellaAuthChecking(serverUrl);
167
334
  const serverCheck = await checkUmbrellaOnboardingServer(serverUrl);
168
335
  if (serverCheck.deploymentMode === "authenticated") {
169
- const emailPrompt = await prompts({
170
- message: "Umbrella email",
171
- name: "value",
172
- type: options.email ? null : "text",
173
- });
174
- const passwordPrompt = await prompts({
175
- message: "Umbrella password",
176
- name: "value",
177
- type: options.password ? null : "password",
178
- });
179
- const email = options.email?.trim() || emailPrompt.value;
180
- const password = options.password?.trim() || passwordPrompt.value;
181
- if (!email || !password) {
336
+ const signInResult = await attemptAuthenticatedSignIn(serverUrl, options);
337
+ if (signInResult.kind === "cancelled") {
182
338
  console.log(chalk.red("\n Sign-in cancelled."));
183
339
  return;
184
340
  }
185
- const signIn = await signInUmbrellaOnboarding(serverUrl, email, password);
186
- cookie = signIn.cookie;
187
- companies = signIn.companies;
188
- const authSnapshot = setUmbrellaAuthAuthorized(serverUrl, email, companies.length);
341
+ if (signInResult.kind === "failure") {
342
+ throw signInResult.error;
343
+ }
344
+ const successfulSignIn = signInResult;
345
+ cookie = successfulSignIn.cookie;
346
+ companies = successfulSignIn.companies;
347
+ signedInEmail = successfulSignIn.email;
348
+ const authSnapshot = setUmbrellaAuthAuthorized(serverUrl, signedInEmail, companies.length);
189
349
  console.log(chalk.gray(` Auth state: ${formatUmbrellaAuthStatus(authSnapshot.status)}`));
190
350
  if (companies.length === 0) {
191
351
  console.log(chalk.yellow("\n Signed in, but this account does not currently see any companies. That usually means the account exists but has not been given board access or company membership yet."));
@@ -1,7 +1,7 @@
1
1
  import chalk from "chalk";
2
2
  import prompts from "prompts";
3
3
  import { configManager } from "../config.js";
4
- import { ensureRepoContext, recordSessionEvent, setSessionPanel } from "../repo-state.js";
4
+ import { ensureRepoContext, findRepoRoot, recordSessionEvent, setSessionPanel } from "../repo-state.js";
5
5
  import { pullCommandAction } from "./pull.js";
6
6
  import { createContextSpace, getCliSetup, getCompanyContextSummary, toContextSpaces } from "../umbrella.js";
7
7
  export async function spaceCommandAction(action, spaceArg) {
@@ -15,6 +15,7 @@ export async function spaceCommandAction(action, spaceArg) {
15
15
  return;
16
16
  }
17
17
  const normalizedAction = action.toLowerCase();
18
+ const repoRoot = await findRepoRoot(process.cwd());
18
19
  const summary = await getCompanyContextSummary(config.umbrellaUrl, config.companyId);
19
20
  const spaces = toContextSpaces(summary);
20
21
  if (normalizedAction === "list") {
@@ -67,7 +68,7 @@ export async function spaceCommandAction(action, spaceArg) {
67
68
  projectName: setup.activeSpaceName,
68
69
  });
69
70
  configManager.upsertLocation({
70
- repoRoot: process.cwd(),
71
+ repoRoot,
71
72
  companyId: setup.companyId,
72
73
  companyName: setup.companyName,
73
74
  projectId: setup.activeSpaceId,
@@ -129,7 +130,7 @@ export async function spaceCommandAction(action, spaceArg) {
129
130
  projectName: setup.activeSpaceName,
130
131
  });
131
132
  configManager.upsertLocation({
132
- repoRoot: process.cwd(),
133
+ repoRoot,
133
134
  companyId: setup.companyId,
134
135
  companyName: setup.companyName,
135
136
  projectId: setup.activeSpaceId,
@@ -1,10 +1,13 @@
1
1
  import { type UmbrellaAuthSnapshot } from "../auth-state.js";
2
+ import { type AgentMemoryConfig } from "../config.js";
2
3
  export type StatusSnapshot = {
3
4
  authSnapshot: UmbrellaAuthSnapshot;
4
5
  companyName: string;
5
6
  spaceName: string;
6
7
  umbrellaUrl: string | null;
7
8
  serverUrl: string;
9
+ connectionMode: "saved_local_config" | "verified_live_connection";
10
+ connectionSummary: string;
8
11
  currentDirectory: string;
9
12
  repoRoot: string;
10
13
  umDir: string;
@@ -26,8 +29,20 @@ export type StatusSnapshot = {
26
29
  transportQueue: number;
27
30
  transportActiveTask: string;
28
31
  contextTreeSummary: string;
32
+ warnings: string[];
29
33
  nextSteps: string[];
30
34
  };
35
+ type OutputFormat = "text" | "json";
36
+ export type ConnectionPresentation = {
37
+ authSnapshot: UmbrellaAuthSnapshot;
38
+ connectionMode: "saved_local_config" | "verified_live_connection";
39
+ connectionSummary: string;
40
+ warnings: string[];
41
+ };
42
+ export declare function buildConnectionPresentation(config: Pick<AgentMemoryConfig, "serverUrl" | "umbrellaUrl">, authSnapshot?: UmbrellaAuthSnapshot): ConnectionPresentation;
31
43
  export declare function getStatusSnapshot(): Promise<StatusSnapshot>;
32
- export declare function statusCommandAction(): Promise<void>;
44
+ export declare function statusCommandAction(opts?: {
45
+ format?: OutputFormat;
46
+ }): Promise<void>;
33
47
  export declare function statusCommand(cli: any): void;
48
+ export {};
@@ -5,6 +5,46 @@ import { formatUmbrellaAuthStatus } from "../auth-state.js";
5
5
  import { getStoredUmbrellaAuthSnapshot } from "../adapters/umbrella-auth-runtime.js";
6
6
  import { configManager } from "../config.js";
7
7
  import { getConnectorRuns, getContextTreeState, getInstalledConnectors, getInstalledHubEntries, getPendingMemories, getPulledFixes, getPulledMemories, getRepoContext, ensureSessionState, getTransportState, } from "../repo-state.js";
8
+ export function buildConnectionPresentation(config, authSnapshot = getStoredUmbrellaAuthSnapshot()) {
9
+ const warnings = [];
10
+ const connectionMode = authSnapshot.status === "authorized"
11
+ ? "verified_live_connection"
12
+ : "saved_local_config";
13
+ const normalizedAuthSnapshot = connectionMode === "saved_local_config" && authSnapshot.status === "checking"
14
+ ? {
15
+ ...authSnapshot,
16
+ hint: "The last live sign-in check did not finish, so this device is still falling back to saved local setup.",
17
+ message: "Saved local setup is present, but the live Umbrella connection has not been verified yet.",
18
+ status: "not_initialized",
19
+ }
20
+ : authSnapshot;
21
+ const connectionSummary = connectionMode === "verified_live_connection"
22
+ ? "This device last completed a real Umbrella sign-in check, so the saved company and space should match a live server session."
23
+ : "This device still has saved local setup details, but the current Umbrella sign-in has not been verified yet. Treat the company and space below as saved local selections until setup succeeds again.";
24
+ if (config.umbrellaUrl && config.serverUrl) {
25
+ try {
26
+ const umbrella = new URL(config.umbrellaUrl);
27
+ const backend = new URL(config.serverUrl);
28
+ const umbrellaIsLoopback = ["127.0.0.1", "localhost"].includes(umbrella.hostname.toLowerCase());
29
+ const backendIsLoopback = ["127.0.0.1", "localhost"].includes(backend.hostname.toLowerCase());
30
+ if (umbrella.hostname !== backend.hostname || umbrella.port !== backend.port) {
31
+ warnings.push(`The saved Umbrella app URL (${config.umbrellaUrl}) and Context backend URL (${config.serverUrl}) point to different places.`);
32
+ }
33
+ if (umbrellaIsLoopback !== backendIsLoopback) {
34
+ warnings.push("One saved URL points to a local-only address while the other points somewhere else. This usually means setup mixed a local app URL with a remote backend URL.");
35
+ }
36
+ }
37
+ catch {
38
+ warnings.push("One of the saved server URLs could not be parsed cleanly. Re-run setup to refresh the connection details.");
39
+ }
40
+ }
41
+ return {
42
+ authSnapshot: normalizedAuthSnapshot,
43
+ connectionMode,
44
+ connectionSummary,
45
+ warnings,
46
+ };
47
+ }
8
48
  export async function getStatusSnapshot() {
9
49
  const config = configManager.config;
10
50
  if (!config) {
@@ -34,6 +74,7 @@ export async function getStatusSnapshot() {
34
74
  mcpConfigured = false;
35
75
  }
36
76
  const nextSteps = [];
77
+ const warnings = [];
37
78
  if (pending.length > 0)
38
79
  nextSteps.push(`Run "umbrella-context push" to share ${pending.length} local draft${pending.length === 1 ? "" : "s"}.`);
39
80
  if (!activeProvider || !config.activeModel) {
@@ -45,12 +86,18 @@ export async function getStatusSnapshot() {
45
86
  nextSteps.push('Add a first repo connector with "umbrella-context connectors install".');
46
87
  if (hubEntries.length === 0)
47
88
  nextSteps.push('Browse reusable bundles with "umbrella-context hub list".');
89
+ const connection = buildConnectionPresentation(config);
90
+ if (connection.connectionMode === "saved_local_config") {
91
+ nextSteps.unshift('Re-run "umbrella-context setup" to verify the live Umbrella connection before trusting the saved company and space.');
92
+ }
48
93
  return {
49
- authSnapshot: getStoredUmbrellaAuthSnapshot(),
94
+ authSnapshot: connection.authSnapshot,
50
95
  companyName: config.companyName,
51
96
  spaceName: config.projectName,
52
97
  umbrellaUrl: config.umbrellaUrl ?? null,
53
98
  serverUrl: config.serverUrl,
99
+ connectionMode: connection.connectionMode,
100
+ connectionSummary: connection.connectionSummary,
54
101
  currentDirectory: process.cwd(),
55
102
  repoRoot,
56
103
  umDir,
@@ -74,72 +121,89 @@ export async function getStatusSnapshot() {
74
121
  transportQueue: transport?.queue.length ?? 0,
75
122
  transportActiveTask: transport?.activeTaskId ?? "None",
76
123
  contextTreeSummary: contextTree?.summaryHandle ?? "No context tree summary yet",
124
+ warnings: connection.warnings,
77
125
  nextSteps,
78
126
  };
79
127
  }
80
- export async function statusCommandAction() {
128
+ function printStatusJson(snapshot) {
129
+ console.log(JSON.stringify({ ok: true, action: "status", ...snapshot }, null, 2));
130
+ }
131
+ function formatTextConnectionMode(mode) {
132
+ return mode === "verified_live_connection" ? "Verified live connection" : "Saved local config only";
133
+ }
134
+ function printBullets(items) {
135
+ for (const item of items) {
136
+ console.log(`- ${item}`);
137
+ }
138
+ }
139
+ export async function statusCommandAction(opts = {}) {
81
140
  let snapshot;
82
141
  try {
83
142
  snapshot = await getStatusSnapshot();
84
143
  }
85
144
  catch (err) {
145
+ if (opts.format === "json") {
146
+ console.log(JSON.stringify({ ok: false, action: "status", error: err.message }, null, 2));
147
+ return;
148
+ }
86
149
  console.log(chalk.red(err.message));
87
150
  return;
88
151
  }
152
+ if (opts.format === "json") {
153
+ printStatusJson(snapshot);
154
+ return;
155
+ }
89
156
  console.log(chalk.bold("\n Umbrella Context Status\n"));
90
- console.log(chalk.cyan(" Auth"));
91
- console.log(` State: ${formatUmbrellaAuthStatus(snapshot.authSnapshot.status)}`);
92
- console.log(` Checked At: ${snapshot.authSnapshot.checkedAt === new Date(0).toISOString() ? "Never" : snapshot.authSnapshot.checkedAt}`);
93
- console.log(` Auth Server: ${snapshot.authSnapshot.serverUrl ?? "Not checked yet"}`);
94
- console.log(` Email: ${snapshot.authSnapshot.email ?? "Not saved"}`);
95
- console.log(` Companies Visible: ${snapshot.authSnapshot.companiesVisible === null ? "Unknown" : String(snapshot.authSnapshot.companiesVisible)}`);
96
- console.log(` Auth Message: ${snapshot.authSnapshot.message ?? "No auth check has been saved yet"}`);
97
- console.log(` Auth Hint: ${snapshot.authSnapshot.hint ?? "No auth hint recorded yet"}`);
98
- console.log("");
99
- console.log(chalk.cyan(" Connection"));
100
- console.log(` Company: ${snapshot.companyName}`);
101
- console.log(` Space: ${snapshot.spaceName}`);
102
- console.log(` Umbrella: ${snapshot.umbrellaUrl ?? "Not saved"}`);
103
- console.log(` Context Backend: ${snapshot.serverUrl}`);
157
+ console.log(`Connection: ${formatTextConnectionMode(snapshot.connectionMode)}`);
158
+ console.log(`Auth: ${formatUmbrellaAuthStatus(snapshot.authSnapshot.status)}`);
159
+ console.log(`Workspace: ${snapshot.companyName} / ${snapshot.spaceName}`);
160
+ console.log(`Current Directory: ${snapshot.currentDirectory}`);
161
+ console.log(`Provider: ${snapshot.providerLabel}`);
162
+ console.log(`Model: ${snapshot.modelLabel}`);
163
+ console.log(`Context Tree: ${snapshot.contextTreeSummary}`);
104
164
  console.log("");
105
- console.log(chalk.cyan(" Local Repo"));
106
- console.log(` Current Directory: ${snapshot.currentDirectory}`);
107
- console.log(` Repo Root: ${snapshot.repoRoot}`);
108
- console.log(` Local Context Folder: ${snapshot.umDir}`);
109
- console.log(` Pending Local Context: ${snapshot.pendingCount}`);
110
- console.log(` Pulled Context Snapshot: ${snapshot.pulledContextCount}`);
111
- console.log(` Pulled Known Fixes: ${snapshot.pulledFixCount}`);
112
- console.log(` Last Push: ${snapshot.lastPushAt}`);
113
- console.log(` Last Pull: ${snapshot.lastPullAt}`);
114
- console.log("");
115
- console.log(chalk.cyan(" Repo Ecosystem"));
116
- console.log(` Hub Entries Installed: ${snapshot.hubEntriesCount}`);
117
- console.log(` Connectors Installed: ${snapshot.connectorsCount}`);
118
- console.log(` Repo MCP Wired: ${snapshot.mcpConfigured ? "Yes" : "No"}`);
119
- console.log(` Last Connector Run: ${snapshot.latestConnectorRun}`);
165
+ console.log(chalk.cyan("Summary"));
166
+ console.log(snapshot.connectionSummary);
167
+ if (snapshot.authSnapshot.message) {
168
+ console.log(snapshot.authSnapshot.message);
169
+ }
120
170
  console.log("");
121
- console.log(chalk.cyan(" Model Runtime"));
122
- console.log(` Provider: ${snapshot.providerLabel}`);
123
- console.log(` Model: ${snapshot.modelLabel}`);
124
- console.log(` Session Panel: ${snapshot.currentPanel}`);
125
- console.log(` Session Focus: ${snapshot.currentFocus}`);
126
- console.log(` Latest Session Event: ${snapshot.latestEvent}`);
127
- console.log(` Transport Status: ${snapshot.transportStatus}`);
128
- console.log(` Transport Queue: ${snapshot.transportQueue}`);
129
- console.log(` Active Task: ${snapshot.transportActiveTask}`);
171
+ console.log(chalk.cyan("Local Context"));
172
+ printBullets([
173
+ `${snapshot.pendingCount} pending draft${snapshot.pendingCount === 1 ? "" : "s"}`,
174
+ `${snapshot.pulledContextCount} pulled note${snapshot.pulledContextCount === 1 ? "" : "s"}`,
175
+ `${snapshot.pulledFixCount} pulled known fix${snapshot.pulledFixCount === 1 ? "" : "es"}`,
176
+ `Last push: ${snapshot.lastPushAt}`,
177
+ `Last pull: ${snapshot.lastPullAt}`,
178
+ ]);
130
179
  console.log("");
131
- console.log(chalk.cyan(" Context Tree"));
132
- console.log(` Summary: ${snapshot.contextTreeSummary}`);
180
+ console.log(chalk.cyan("Runtime"));
181
+ printBullets([
182
+ `Panel: ${snapshot.currentPanel}`,
183
+ `Focus: ${snapshot.currentFocus}`,
184
+ `Latest event: ${snapshot.latestEvent}`,
185
+ `Transport: ${snapshot.transportStatus}`,
186
+ `Queue: ${snapshot.transportQueue}`,
187
+ `Active task: ${snapshot.transportActiveTask}`,
188
+ ]);
189
+ if (snapshot.warnings.length > 0) {
190
+ console.log("");
191
+ console.log(chalk.yellow(" Warnings"));
192
+ printBullets(snapshot.warnings);
193
+ }
133
194
  if (snapshot.nextSteps.length > 0) {
134
195
  console.log("");
135
196
  console.log(chalk.cyan(" Suggested Next Steps"));
136
- snapshot.nextSteps.forEach((step) => {
137
- console.log(` - ${step}`);
138
- });
197
+ printBullets(snapshot.nextSteps);
139
198
  }
140
199
  }
141
200
  export function statusCommand(cli) {
142
- cli.command("status", "Show the current repo, company, space, and local sync state").action(async () => {
143
- await statusCommandAction();
201
+ cli
202
+ .command("status", "Show the current repo, company, space, and local sync state")
203
+ .option("--format <format>", "Output format (text or json)")
204
+ .example("status")
205
+ .example("status --format json")
206
+ .action(async (opts) => {
207
+ await statusCommandAction(opts);
144
208
  });
145
209
  }