@meshxdata/fops 0.0.3 → 0.0.5

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.
@@ -1,4 +1,5 @@
1
1
  import fs from "node:fs";
2
+ import http from "node:http";
2
3
  import path from "node:path";
3
4
  import { execa } from "execa";
4
5
  import { loadSkills, searchKnowledge } from "../plugins/index.js";
@@ -17,8 +18,23 @@ When suggesting commands, ALWAYS use \`fops\` commands, not raw \`make\` or \`gi
17
18
  **Always suggest 2–3 commands** so the user can pick. Pair a primary action with a follow-up (e.g. restart + logs, doctor + up).
18
19
 
19
20
  ## Accuracy Rules
20
- - ALWAYS check the container status context carefully. If ANY container is exited, unhealthy, or failed, report it never claim "all healthy" when failures exist.
21
- - When containers have failed, lead with the failures and suggest diagnostics.
21
+ - ALWAYS check BOTH container status AND service health context. Container "running (healthy)" only means the Docker healthcheck passedthe service may still be initializing, have failed migrations, or be unresponsive.
22
+ - Cross-reference the "Service health" section (HTTP endpoint checks) with container status. If any endpoint is DOWN or unreachable, the stack is NOT fully ready — report this even if containers look healthy.
23
+ - If ANY container is exited, unhealthy, or failed, report it — never claim "all healthy" when failures exist.
24
+ - When containers have failed or services are unreachable, lead with the failures and suggest diagnostics.
25
+ - If "Missing images" context is present, report which images are missing and whether they need building or pulling. Suggest \`fops build\` for buildable images or \`make download\` (after ECR auth) for registry images.
26
+
27
+ ## Auto-Fix Rules
28
+ When you detect failing containers, DO NOT just report them — diagnose and fix:
29
+ 1. Read the container logs provided in context. Look for: missing files/volumes, permission errors, config issues, dependency failures, image problems.
30
+ 2. Apply the RIGHT fix based on the diagnosis:
31
+ - **Restarting/crash-loop with no logs**: likely a missing volume mount or stale image → suggest \`fops build\` then \`docker compose up -d <service>\`
32
+ - **Image not found / pull access denied**: missing image → \`fops build\` (for buildable) or \`fops download\` (for registry images)
33
+ - **Dependency unhealthy**: fix the dependency first, then restart dependents → \`docker compose up -d <dep-service>\`
34
+ - **Port conflict**: another process using the port → identify and kill or change port in .env
35
+ - **Migration failed**: database issue → \`docker compose restart <service>-migrations\`
36
+ - **Config error / env missing**: check .env file → \`fops setup\`
37
+ 3. Output the fix commands in fenced bash blocks so they auto-execute. Be decisive — don't hedge with "you might try", just say what to do and give the command.
22
38
 
23
39
  ## Security Rules
24
40
  - Never output API keys, passwords, or tokens in responses
@@ -42,8 +58,10 @@ async function gatherDockerStatus(root) {
42
58
  if (!parsed.length) return "No containers running.";
43
59
 
44
60
  let healthy = 0, unhealthy = 0, exited = 0, running = 0;
61
+ const failingContainers = [];
45
62
  const services = parsed.map((o) => {
46
63
  const name = o.Name || o.name || o.Service || "?";
64
+ const service = o.Service || name;
47
65
  const state = (o.State || "").toLowerCase();
48
66
  const status = o.Status || "";
49
67
  const health = (o.Health || "").toLowerCase();
@@ -51,11 +69,13 @@ async function gatherDockerStatus(root) {
51
69
 
52
70
  if (state === "exited" || state === "dead") {
53
71
  exited++;
72
+ failingContainers.push(service);
54
73
  return `${name}: EXITED (code ${exitCode}) — ${status}`;
55
74
  }
56
- if (health === "unhealthy") {
75
+ if (health === "unhealthy" || state === "restarting") {
57
76
  unhealthy++;
58
- return `${name}: UNHEALTHY — ${status}`;
77
+ failingContainers.push(service);
78
+ return `${name}: ${state === "restarting" ? "RESTARTING" : "UNHEALTHY"} — ${status}`;
59
79
  }
60
80
  if (state === "running" && (health === "healthy" || !health)) {
61
81
  healthy++;
@@ -69,15 +89,36 @@ async function gatherDockerStatus(root) {
69
89
  const summary = [];
70
90
  if (running) summary.push(`${running} running`);
71
91
  if (healthy) summary.push(`${healthy} healthy`);
72
- if (unhealthy) summary.push(`${unhealthy} UNHEALTHY`);
92
+ if (unhealthy) summary.push(`${unhealthy} UNHEALTHY/RESTARTING`);
73
93
  if (exited) summary.push(`${exited} EXITED/FAILED`);
74
94
 
75
95
  let header = `Container summary: ${parsed.length} total — ${summary.join(", ")}`;
76
96
  if (unhealthy || exited) {
77
- header += "\n⚠ ATTENTION: Some containers are failing. Diagnose and report the failures.";
97
+ header += "\n⚠ ATTENTION: Some containers are failing. Diagnose and fix the failures.";
98
+ }
99
+
100
+ let result = header + "\n\nContainer details:\n" + services.join("\n");
101
+
102
+ // Auto-collect logs from failing containers (last 15 lines each, max 3)
103
+ if (failingContainers.length > 0) {
104
+ const logsToFetch = failingContainers.slice(0, 3);
105
+ const logResults = await Promise.all(
106
+ logsToFetch.map(async (svc) => {
107
+ try {
108
+ const { stdout, stderr } = await execa(
109
+ "docker", ["compose", "logs", svc, "--tail", "15", "--no-color"],
110
+ { cwd: root, reject: false, timeout: 5000 },
111
+ );
112
+ const output = (stdout || "") + (stderr || "");
113
+ if (output.trim()) return `\n--- Logs: ${svc} (last 15 lines) ---\n${output.trim()}`;
114
+ } catch { /* skip */ }
115
+ return `\n--- Logs: ${svc} ---\n(no logs available)`;
116
+ }),
117
+ );
118
+ result += "\n" + logResults.join("\n");
78
119
  }
79
120
 
80
- return header + "\n\nContainer details:\n" + services.join("\n");
121
+ return result;
81
122
  }
82
123
  } catch {
83
124
  return "Docker: not available or not running.";
@@ -85,6 +126,95 @@ async function gatherDockerStatus(root) {
85
126
  return null;
86
127
  }
87
128
 
129
+ /**
130
+ * HTTP GET with timeout. Returns { ok, status } or { ok: false, error }.
131
+ */
132
+ function httpPing(url, timeout = 3000) {
133
+ return new Promise((resolve) => {
134
+ const req = http.get(url, { timeout }, (res) => {
135
+ res.resume(); // drain
136
+ resolve({ ok: res.statusCode < 500, status: res.statusCode });
137
+ });
138
+ req.on("error", (err) => resolve({ ok: false, error: err.code || err.message }));
139
+ req.on("timeout", () => { req.destroy(); resolve({ ok: false, error: "TIMEOUT" }); });
140
+ });
141
+ }
142
+
143
+ const SERVICE_ENDPOINTS = [
144
+ { name: "Backend API", url: "http://localhost:9001/api/data/mesh/list?per_page=1", port: 9001 },
145
+ { name: "Frontend", url: "http://localhost:3002", port: 3002 },
146
+ { name: "Storage Engine (MinIO)", url: "http://localhost:9002/minio/health/live", port: 9002 },
147
+ { name: "Trino", url: "http://localhost:8081/v1/info", port: 8081 },
148
+ ];
149
+
150
+ async function gatherServiceHealth() {
151
+ const results = await Promise.all(
152
+ SERVICE_ENDPOINTS.map(async ({ name, url }) => {
153
+ const r = await httpPing(url);
154
+ if (r.ok) return `${name}: UP (HTTP ${r.status})`;
155
+ return `${name}: DOWN (${r.error || "HTTP " + r.status})`;
156
+ }),
157
+ );
158
+ return "Service health (HTTP checks):\n" + results.join("\n");
159
+ }
160
+
161
+ /**
162
+ * Detect which compose images are missing locally.
163
+ * Compares `docker compose config --images` against `docker images`.
164
+ */
165
+ async function gatherMissingImages(root) {
166
+ try {
167
+ // Get all images required by compose
168
+ const { stdout: configImages } = await execa(
169
+ "docker", ["compose", "config", "--images"],
170
+ { cwd: root, reject: false, timeout: 10000 },
171
+ );
172
+ if (!configImages?.trim()) return null;
173
+
174
+ const required = [...new Set(configImages.trim().split("\n").filter(Boolean))];
175
+
176
+ // Get locally available images
177
+ const { stdout: localImages } = await execa(
178
+ "docker", ["images", "--format", "{{.Repository}}:{{.Tag}}"],
179
+ { reject: false, timeout: 5000 },
180
+ );
181
+ const localSet = new Set((localImages || "").trim().split("\n").filter(Boolean));
182
+
183
+ // Also check by repo without tag (docker sometimes lists <none> tag)
184
+ const localRepos = new Set(
185
+ (localImages || "").trim().split("\n").filter(Boolean).map((i) => i.split(":")[0]),
186
+ );
187
+
188
+ // Find which compose services have build contexts
189
+ const { stdout: configJson } = await execa(
190
+ "docker", ["compose", "config", "--format", "json"],
191
+ { cwd: root, reject: false, timeout: 10000 },
192
+ );
193
+ const buildableImages = new Set();
194
+ if (configJson?.trim()) {
195
+ try {
196
+ const config = JSON.parse(configJson);
197
+ for (const [, svc] of Object.entries(config.services || {})) {
198
+ if (svc.build && svc.image) buildableImages.add(svc.image);
199
+ }
200
+ } catch { /* ignore */ }
201
+ }
202
+
203
+ const missing = required.filter((img) => !localSet.has(img) && !localRepos.has(img.split(":")[0]));
204
+ if (missing.length === 0) return null;
205
+
206
+ const lines = missing.map((img) => {
207
+ const action = buildableImages.has(img) ? "needs BUILD (has build context)" : "needs PULL from registry";
208
+ return ` ${img} — ${action}`;
209
+ });
210
+
211
+ return `Missing images (${missing.length}/${required.length}):\n` + lines.join("\n") +
212
+ "\n\nTo build local images: make build\nTo pull registry images: make download (requires ECR auth)";
213
+ } catch {
214
+ return null;
215
+ }
216
+ }
217
+
88
218
  async function gatherImageAges(root) {
89
219
  try {
90
220
  const { stdout: imgOut } = await execa("docker", ["compose", "images", "--format", "json"], { cwd: root, reject: false, timeout: 5000 });
@@ -152,9 +282,9 @@ function checkEnvFile(root) {
152
282
  return null;
153
283
  }
154
284
 
155
- async function gatherSkills() {
285
+ async function gatherSkills(registry) {
156
286
  try {
157
- const skills = await loadSkills();
287
+ const skills = await loadSkills(registry);
158
288
  if (skills.length) {
159
289
  return "## Additional Skills\n" + skills.map((s) => s.content).join("\n\n");
160
290
  }
@@ -169,16 +299,20 @@ export async function gatherStackContext(root, { registry, message } = {}) {
169
299
 
170
300
  if (root) {
171
301
  // Run all independent checks in parallel
172
- const [dockerStatus, imageAges, prereqs, envInfo, skills, knowledge] = await Promise.all([
302
+ const [dockerStatus, serviceHealth, missingImages, imageAges, prereqs, envInfo, skills, knowledge] = await Promise.all([
173
303
  gatherDockerStatus(root),
304
+ gatherServiceHealth(),
305
+ gatherMissingImages(root),
174
306
  gatherImageAges(root),
175
307
  gatherPrereqs(),
176
308
  Promise.resolve(checkEnvFile(root)),
177
- gatherSkills(),
309
+ gatherSkills(registry),
178
310
  registry && message ? searchKnowledge(registry, message) : Promise.resolve(null),
179
311
  ]);
180
312
 
181
313
  if (dockerStatus) parts.push(dockerStatus);
314
+ if (serviceHealth) parts.push(serviceHealth);
315
+ if (missingImages) parts.push(missingImages);
182
316
  if (imageAges) parts.push(imageAges);
183
317
  parts.push(prereqs);
184
318
  if (envInfo) parts.push(envInfo);
@@ -188,7 +322,7 @@ export async function gatherStackContext(root, { registry, message } = {}) {
188
322
  // No root — still check prereqs, skills, and knowledge
189
323
  const [prereqs, skills, knowledge] = await Promise.all([
190
324
  gatherPrereqs(),
191
- gatherSkills(),
325
+ gatherSkills(registry),
192
326
  registry && message ? searchKnowledge(registry, message) : Promise.resolve(null),
193
327
  ]);
194
328
  parts.push(prereqs);
@@ -1,2 +1,3 @@
1
1
  export { runAgentSingleTurn, runAgentInteractive } from "./agent.js";
2
2
  export { gatherStackContext } from "./context.js";
3
+ export { BUILTIN_AGENTS, loadBuiltinAgents } from "./agents.js";
package/src/agent/llm.js CHANGED
@@ -37,10 +37,10 @@ function buildConversationPrompt(messages) {
37
37
  /**
38
38
  * Run a prompt through Claude Code CLI (uses OAuth auth)
39
39
  */
40
- export async function runViaClaudeCode(prompt, systemPrompt) {
40
+ export async function runViaClaudeCode(prompt, systemPrompt, { replaceSystemPrompt = false } = {}) {
41
41
  const args = ["-p", "--no-session-persistence"];
42
42
  if (systemPrompt) {
43
- args.push("--append-system-prompt", systemPrompt);
43
+ args.push(replaceSystemPrompt ? "--system-prompt" : "--append-system-prompt", systemPrompt);
44
44
  }
45
45
 
46
46
  const { stdout } = await execa("claude", args, {
@@ -53,12 +53,20 @@ export async function runViaClaudeCode(prompt, systemPrompt) {
53
53
  }
54
54
 
55
55
  /**
56
- * Stream response via Claude Code CLI with thinking display
56
+ * Stream response via Claude Code CLI with thinking display.
57
+ * Uses --output-format stream-json --include-partial-messages to get
58
+ * token-level streaming events (content_block_delta for text and thinking).
57
59
  */
58
- export async function streamViaClaudeCode(prompt, systemPrompt, onChunk, onThinking) {
59
- const args = ["-p", "--no-session-persistence"];
60
+ export async function streamViaClaudeCode(prompt, systemPrompt, onChunk, onThinking, onBlockStart, { replaceSystemPrompt = false } = {}) {
61
+ const args = [
62
+ "-p",
63
+ "--output-format", "stream-json",
64
+ "--verbose",
65
+ "--include-partial-messages",
66
+ "--no-session-persistence",
67
+ ];
60
68
  if (systemPrompt) {
61
- args.push("--append-system-prompt", systemPrompt);
69
+ args.push(replaceSystemPrompt ? "--system-prompt" : "--append-system-prompt", systemPrompt);
62
70
  }
63
71
 
64
72
  const proc = execa("claude", args, {
@@ -68,14 +76,62 @@ export async function streamViaClaudeCode(prompt, systemPrompt, onChunk, onThink
68
76
  });
69
77
 
70
78
  let fullText = "";
79
+ let lineBuf = "";
71
80
 
72
81
  proc.stdout.on("data", (chunk) => {
73
- const text = chunk.toString();
74
- fullText += text;
75
- if (onChunk) onChunk(text);
82
+ lineBuf += chunk.toString();
83
+ const lines = lineBuf.split("\n");
84
+ lineBuf = lines.pop(); // keep incomplete line
85
+
86
+ for (const line of lines) {
87
+ if (!line.trim()) continue;
88
+ let evt;
89
+ try { evt = JSON.parse(line); } catch { continue; }
90
+
91
+ if (evt.type === "stream_event") {
92
+ const inner = evt.event;
93
+ if (inner?.type === "content_block_start") {
94
+ const blockType = inner.content_block?.type;
95
+ if (onBlockStart) onBlockStart(blockType);
96
+ } else if (inner?.type === "content_block_delta") {
97
+ if (inner.delta?.type === "thinking_delta" && inner.delta.thinking) {
98
+ if (onThinking) onThinking(inner.delta.thinking);
99
+ }
100
+ if (inner.delta?.type === "text_delta" && inner.delta.text) {
101
+ fullText += inner.delta.text;
102
+ if (onChunk) onChunk(inner.delta.text);
103
+ }
104
+ }
105
+ } else if (evt.type === "assistant") {
106
+ // Final assistant message — extract any remaining text
107
+ const content = evt.message?.content || [];
108
+ const textParts = content.filter(b => b.type === "text").map(b => b.text).join("");
109
+ // Only use this if streaming didn't capture everything
110
+ if (textParts && !fullText) {
111
+ fullText = textParts;
112
+ if (onChunk) onChunk(textParts);
113
+ }
114
+ }
115
+ }
76
116
  });
77
117
 
78
118
  await proc;
119
+
120
+ // Process any remaining buffer
121
+ if (lineBuf.trim()) {
122
+ try {
123
+ const evt = JSON.parse(lineBuf);
124
+ if (evt.type === "assistant") {
125
+ const content = evt.message?.content || [];
126
+ const textParts = content.filter(b => b.type === "text").map(b => b.text).join("");
127
+ if (textParts && !fullText) {
128
+ fullText = textParts;
129
+ if (onChunk) onChunk(textParts);
130
+ }
131
+ }
132
+ } catch { /* ignore */ }
133
+ }
134
+
79
135
  return fullText;
80
136
  }
81
137
 
@@ -91,10 +147,25 @@ export async function streamAssistantReply(root, messages, systemContent, opts)
91
147
  try {
92
148
  if (useClaudeCode) {
93
149
  const prompt = buildConversationPrompt(messages);
94
- fullText = await streamViaClaudeCode(prompt, systemContent, (chunk) => {
95
- display.setStatus("Responding");
96
- display.appendContent(chunk);
97
- });
150
+ fullText = await streamViaClaudeCode(
151
+ prompt,
152
+ systemContent,
153
+ (chunk) => {
154
+ display.appendContent(chunk);
155
+ },
156
+ (thinking) => {
157
+ display.appendThinking(thinking);
158
+ },
159
+ (blockType) => {
160
+ if (blockType === "thinking") {
161
+ display.setStatus("Reasoning");
162
+ } else if (blockType === "text") {
163
+ display.setThinking(""); // clear thinking preview
164
+ display.setStatus("Responding");
165
+ }
166
+ },
167
+ { replaceSystemPrompt: !!opts.replaceSystemPrompt },
168
+ );
98
169
  } else if (anthropicKey) {
99
170
  const { default: Anthropic } = await import("@anthropic-ai/sdk");
100
171
  const client = new Anthropic({ apiKey: anthropicKey });
package/src/auth/coda.js CHANGED
@@ -67,7 +67,7 @@ export async function runCodaLogin() {
67
67
  default: false,
68
68
  }]);
69
69
  if (!overwrite) {
70
- console.log(chalk.gray("Keeping existing Coda token."));
70
+ console.log(chalk.dim("Keeping existing Coda token."));
71
71
  return true;
72
72
  }
73
73
  }
@@ -77,11 +77,11 @@ export async function runCodaLogin() {
77
77
  console.log("");
78
78
  console.log(chalk.white(" To connect Foundation to Coda, you need an API token."));
79
79
  console.log(chalk.white(" Here's how to get one:\n"));
80
- console.log(chalk.gray(" 1. Go to ") + chalk.cyan(CODA_ACCOUNT_URL));
81
- console.log(chalk.gray(" 2. Scroll to ") + chalk.white("\"API Settings\""));
82
- console.log(chalk.gray(" 3. Click ") + chalk.white("\"Generate API token\""));
83
- console.log(chalk.gray(" 4. Give it a name (e.g. \"Foundation CLI\")"));
84
- console.log(chalk.gray(" 5. Copy the token and paste it below"));
80
+ console.log(chalk.dim(" 1. Go to ") + chalk.cyan(CODA_ACCOUNT_URL));
81
+ console.log(chalk.dim(" 2. Scroll to ") + chalk.white("\"API Settings\""));
82
+ console.log(chalk.dim(" 3. Click ") + chalk.white("\"Generate API token\""));
83
+ console.log(chalk.dim(" 4. Give it a name (e.g. \"Foundation CLI\")"));
84
+ console.log(chalk.dim(" 5. Copy the token and paste it below"));
85
85
  console.log("");
86
86
 
87
87
  const { openIt } = await inquirer.prompt([{
@@ -111,18 +111,18 @@ export async function runCodaLogin() {
111
111
  },
112
112
  }]);
113
113
 
114
- console.log(chalk.gray("\n Validating token..."));
114
+ console.log(chalk.dim("\n Validating token..."));
115
115
  const result = await validateCodaToken(token.trim());
116
116
 
117
117
  if (!result.valid) {
118
118
  console.log(chalk.red(`\n ${result.error}`));
119
- console.log(chalk.gray(" Check the token and try again with: fops login coda\n"));
119
+ console.log(chalk.dim(" Check the token and try again with: fops login coda\n"));
120
120
  return false;
121
121
  }
122
122
 
123
123
  saveCodaToken(token);
124
124
  console.log(chalk.green(`\n Coda login successful!`));
125
- console.log(chalk.gray(` Logged in as: ${result.name || result.loginId}`));
126
- console.log(chalk.gray(" Token saved to ~/.fops.json\n"));
125
+ console.log(chalk.dim(` Logged in as: ${result.name || result.loginId}`));
126
+ console.log(chalk.dim(" Token saved to ~/.fops.json\n"));
127
127
  return true;
128
128
  }
package/src/auth/login.js CHANGED
@@ -9,10 +9,10 @@ const ANTHROPIC_KEYS_URL = "https://console.anthropic.com/settings/keys";
9
9
 
10
10
  export function authHelp() {
11
11
  console.log(chalk.yellow("No API key found. Try one of:"));
12
- console.log(chalk.gray(" • Open " + chalk.cyan(ANTHROPIC_KEYS_URL) + " in your browser to sign in and create a key"));
13
- console.log(chalk.gray(" • ANTHROPIC_API_KEY or OPENAI_API_KEY environment variable"));
14
- console.log(chalk.gray(" • ~/.claude/.credentials.json with anthropic_api_key or apiKey"));
15
- console.log(chalk.gray(" • ~/.claude/settings.json with apiKeyHelper (script that prints the key)\n"));
12
+ console.log(chalk.dim(" • Open " + chalk.cyan(ANTHROPIC_KEYS_URL) + " in your browser to sign in and create a key"));
13
+ console.log(chalk.dim(" • ANTHROPIC_API_KEY or OPENAI_API_KEY environment variable"));
14
+ console.log(chalk.dim(" • ~/.claude/.credentials.json with anthropic_api_key or apiKey"));
15
+ console.log(chalk.dim(" • ~/.claude/settings.json with apiKeyHelper (script that prints the key)\n"));
16
16
  }
17
17
 
18
18
  export function openBrowser(url) {
@@ -46,7 +46,7 @@ export function saveApiKey(apiKey) {
46
46
  fs.writeFileSync(CLAUDE_CREDENTIALS, JSON.stringify(creds, null, 2) + "\n", { mode: 0o600 });
47
47
 
48
48
  console.log(chalk.green("\nLogin successful"));
49
- console.log(chalk.gray("Key saved to ~/.claude/.credentials.json\n"));
49
+ console.log(chalk.dim("Key saved to ~/.claude/.credentials.json\n"));
50
50
  return true;
51
51
  }
52
52
 
@@ -65,11 +65,11 @@ export async function offerClaudeLogin() {
65
65
  if (!opened) {
66
66
  console.log(chalk.yellow(" Could not open browser. Visit: " + ANTHROPIC_KEYS_URL + "\n"));
67
67
  }
68
- console.log(chalk.gray(" 1. Sign in and create an API key"));
69
- console.log(chalk.gray(" 2. Add it to ~/.claude/.credentials.json:"));
70
- console.log(chalk.gray(' { "anthropic_api_key": "sk-ant-..." }'));
71
- console.log(chalk.gray(" 3. Or run: export ANTHROPIC_API_KEY=\"sk-ant-...\""));
72
- console.log(chalk.gray(" 4. Then run foundation chat again.\n"));
68
+ console.log(chalk.dim(" 1. Sign in and create an API key"));
69
+ console.log(chalk.dim(" 2. Add it to ~/.claude/.credentials.json:"));
70
+ console.log(chalk.dim(' { "anthropic_api_key": "sk-ant-..." }'));
71
+ console.log(chalk.dim(" 3. Or run: export ANTHROPIC_API_KEY=\"sk-ant-...\""));
72
+ console.log(chalk.dim(" 4. Then run foundation chat again.\n"));
73
73
  return true;
74
74
  }
75
75
 
@@ -310,7 +310,7 @@ export async function runLogin(options = {}) {
310
310
  },
311
311
  ]);
312
312
  if (!overwrite) {
313
- console.log(chalk.gray("Keeping existing credentials."));
313
+ console.log(chalk.dim("Keeping existing credentials."));
314
314
  return true;
315
315
  }
316
316
  }
@@ -405,8 +405,8 @@ async function runDeviceLogin() {
405
405
  const url = `http://127.0.0.1:${port}`;
406
406
 
407
407
  console.log(chalk.blue("\nOpening browser for authentication...\n"));
408
- console.log(chalk.gray(` If browser doesn't open, visit: ${chalk.cyan(url)}\n`));
409
- console.log(chalk.gray(" Waiting for authentication..."));
408
+ console.log(chalk.dim(` If browser doesn't open, visit: ${chalk.cyan(url)}\n`));
409
+ console.log(chalk.dim(" Waiting for authentication..."));
410
410
 
411
411
  openBrowser(url);
412
412
  });
package/src/auth/oauth.js CHANGED
@@ -153,9 +153,9 @@ export async function runOAuthLogin() {
153
153
 
154
154
  console.log(chalk.green("\nLogin successful"));
155
155
  if (process.platform === "darwin") {
156
- console.log(chalk.gray("Token saved to macOS Keychain\n"));
156
+ console.log(chalk.dim("Token saved to macOS Keychain\n"));
157
157
  } else {
158
- console.log(chalk.gray("Token saved to ~/.claude/.credentials.json\n"));
158
+ console.log(chalk.dim("Token saved to ~/.claude/.credentials.json\n"));
159
159
  }
160
160
 
161
161
  server.close();
@@ -187,8 +187,8 @@ export async function runOAuthLogin() {
187
187
  authUrl.searchParams.set("code_challenge_method", "S256");
188
188
 
189
189
  console.log(chalk.blue("\nOpening browser for Claude login...\n"));
190
- console.log(chalk.gray(` If browser doesn't open, visit:\n ${chalk.cyan(authUrl.toString())}\n`));
191
- console.log(chalk.gray(" Waiting for authentication..."));
190
+ console.log(chalk.dim(` If browser doesn't open, visit:\n ${chalk.cyan(authUrl.toString())}\n`));
191
+ console.log(chalk.dim(" Waiting for authentication..."));
192
192
 
193
193
  openBrowser(authUrl.toString());
194
194
  });