archbyte 0.2.3 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,21 +6,49 @@ AI-powered architecture visualization for your codebase. Analyzes code, generate
6
6
 
7
7
  ```bash
8
8
  # Install
9
- npm install -g archbyte
10
- # or from source
11
- git clone https://github.com/diabhey/archbyte.git && cd archbyte && npm install && npm run build && npm link
9
+ curl -fsSL https://archbyte.heartbyte.io/install.sh | bash
10
+
11
+ # Sign in or create a free account
12
+ archbyte login
13
+
14
+ # Configure your AI provider (BYOK)
15
+ archbyte init
16
+
17
+ # Or, if you use Claude Code / Codex — skip BYOK entirely:
18
+ archbyte mcp install
12
19
  ```
13
20
 
21
+ Your API keys stay on your machine. ArchByte never stores or transmits your provider credentials.
22
+
14
23
  ### Setup with Claude Code
15
24
 
16
- Slash commands are installed globally so they work in any project:
25
+ **Option A: MCP (recommended)** use ArchByte tools directly in Claude Code, no API key needed:
26
+
27
+ ```bash
28
+ archbyte mcp install
29
+ # or manually:
30
+ claude mcp add archbyte --transport stdio -- npx -y archbyte@latest mcp
31
+ ```
32
+
33
+ Then just ask Claude Code in plain English:
34
+
35
+ ```
36
+ "Analyze the architecture of this project"
37
+ "Export the architecture as a Mermaid diagram"
38
+ "Show me the architecture stats"
39
+ "Read the current architecture diagram"
40
+ ```
41
+
42
+ Available MCP tools: `archbyte_analyze`, `archbyte_generate`, `archbyte_validate` (Pro), `archbyte_export`, `archbyte_stats`, `archbyte_diff`, `archbyte_read_diagram`.
43
+
44
+ **Option B: Slash commands** — installed globally so they work in any project:
17
45
 
18
46
  ```bash
19
47
  mkdir -p ~/.claude/commands
20
48
  cp commands/archbyte-*.md ~/.claude/commands/
21
49
  ```
22
50
 
23
- Run `/archbyte-help` in Claude Code to see all commands. Every command shows its flags and examples when run without arguments (e.g. `/archbyte-export -h`).
51
+ Run `/archbyte-help` in Claude Code to see all commands.
24
52
 
25
53
  ## Usage (Claude Code slash commands)
26
54
 
package/bin/archbyte.js CHANGED
@@ -47,7 +47,7 @@ program
47
47
 
48
48
  program
49
49
  .command('login')
50
- .description('Sign in to ArchByte (required for scan/analyze/generate)')
50
+ .description('Sign in or create a free account (required for scan/analyze/generate)')
51
51
  .option('--token <jwt>', 'Login with a pre-existing JWT token')
52
52
  .option('--github', 'Sign in with GitHub')
53
53
  .option('--google', 'Sign in with Google')
@@ -236,6 +236,24 @@ program
236
236
  await handleUpdate();
237
237
  });
238
238
 
239
+ // — MCP server —
240
+
241
+ const mcpCmd = program
242
+ .command('mcp')
243
+ .description('Start MCP server for AI coding tools (Claude Code, Codex)')
244
+ .action(async () => {
245
+ const { startMcpServer } = await import('../dist/cli/mcp-server.js');
246
+ await startMcpServer();
247
+ });
248
+
249
+ mcpCmd
250
+ .command('install')
251
+ .description('Auto-configure Claude Code and/or Codex CLI')
252
+ .action(async () => {
253
+ const { handleMcpInstall } = await import('../dist/cli/mcp.js');
254
+ await handleMcpInstall();
255
+ });
256
+
239
257
  // Default: show help
240
258
  program
241
259
  .action(() => {
@@ -27,7 +27,7 @@ export async function handleAnalyze(options) {
27
27
  const diagramPath = path.join(rootDir, ".archbyte", "architecture.json");
28
28
  const diagramExists = fs.existsSync(diagramPath);
29
29
  const outputPath = options.output ?? ".archbyte/analysis.json";
30
- console.log(chalk.bold("ArchByte Analyze Dry Run"));
30
+ console.log(chalk.bold("ArchByte Analyze: Dry Run"));
31
31
  console.log(chalk.gray(` Project: ${path.basename(rootDir)}`));
32
32
  console.log(chalk.gray(` Mode: ${mode}`));
33
33
  console.log(chalk.gray(` Provider: ${provider}`));
@@ -123,7 +123,7 @@ export async function handleAnalyze(options) {
123
123
  }
124
124
  const { affected, unmapped } = mapFilesToComponents(changedFiles, priorSpec);
125
125
  if (!shouldRunAgents(affected, unmapped)) {
126
- console.log(chalk.green("Only config changes detected no re-scan needed. Use --force to re-scan."));
126
+ console.log(chalk.green("Only config changes detected. No re-scan needed. Use --force to re-scan."));
127
127
  console.log();
128
128
  return;
129
129
  }
@@ -319,8 +319,9 @@ function printSummary(analysis, durationMs, mode) {
319
319
  console.log();
320
320
  console.log(chalk.bold("Next steps:"));
321
321
  console.log(chalk.gray(` ${chalk.cyan("archbyte serve")} -- view the architecture diagram`));
322
- console.log(chalk.gray(` ${chalk.cyan("archbyte validate")} -- check architecture fitness rules`));
323
322
  console.log(chalk.gray(` ${chalk.cyan("archbyte export")} -- export to mermaid, plantuml, etc.`));
323
+ console.log(chalk.gray(` ${chalk.cyan("archbyte validate")} -- check architecture fitness rules ${chalk.yellow("[Pro]")}`));
324
+ console.log(chalk.gray(` ${chalk.cyan("archbyte patrol")} -- continuous architecture monitoring ${chalk.yellow("[Pro]")}`));
324
325
  console.log();
325
326
  }
326
327
  // ─── Analysis converters ───
package/dist/cli/auth.js CHANGED
@@ -7,6 +7,7 @@ import { CONFIG_DIR, CREDENTIALS_PATH, API_BASE, CLI_CALLBACK_PORT, OAUTH_TIMEOU
7
7
  export async function handleLogin(provider) {
8
8
  console.log();
9
9
  console.log(chalk.bold.cyan("ArchByte Login"));
10
+ console.log(chalk.gray("Sign in or create a free account to get started."));
10
11
  console.log();
11
12
  const existing = loadCredentials();
12
13
  if (existing && !isExpired(existing)) {
@@ -33,7 +34,8 @@ export async function handleLogin(provider) {
33
34
  return;
34
35
  }
35
36
  const selectedProvider = provider ?? "github";
36
- console.log(chalk.gray(`Opening browser for ${selectedProvider} authentication...`));
37
+ console.log(chalk.gray(`Opening browser for ${selectedProvider} sign-in...`));
38
+ console.log(chalk.gray("Don't have an account? One will be created automatically."));
37
39
  console.log();
38
40
  try {
39
41
  const credentials = await startOAuthFlow(selectedProvider);
@@ -43,6 +45,9 @@ export async function handleLogin(provider) {
43
45
  console.log();
44
46
  console.log(chalk.green(`Logged in as ${chalk.bold(credentials.email)} (${credentials.tier} tier)`));
45
47
  console.log(chalk.gray(`Credentials saved to ${CREDENTIALS_PATH}`));
48
+ console.log();
49
+ console.log(chalk.gray("Your credentials and API keys are stored locally on your machine."));
50
+ console.log(chalk.gray("ArchByte never transmits or stores your model provider keys."));
46
51
  }
47
52
  catch (err) {
48
53
  console.error(chalk.red(`Login failed: ${err instanceof Error ? err.message : "Unknown error"}`));
@@ -30,10 +30,10 @@ export async function handleConfig(options) {
30
30
  return;
31
31
  }
32
32
  console.error(chalk.red(`Unknown action: ${action}`));
33
- console.error(chalk.gray(" archbyte config showshow current config"));
34
- console.error(chalk.gray(" archbyte config set <k> <v>set a config value"));
35
- console.error(chalk.gray(" archbyte config get <k>get a config value"));
36
- console.error(chalk.gray(" archbyte config pathshow config file path"));
33
+ console.error(chalk.gray(" archbyte config show show current config"));
34
+ console.error(chalk.gray(" archbyte config set <k> <v> set a config value"));
35
+ console.error(chalk.gray(" archbyte config get <k> get a config value"));
36
+ console.error(chalk.gray(" archbyte config path show config file path"));
37
37
  process.exit(1);
38
38
  }
39
39
  function loadConfig() {
@@ -105,7 +105,7 @@ function setConfig(key, value) {
105
105
  console.log(chalk.green(`Switched to ${value} (credentials on file)`));
106
106
  }
107
107
  else {
108
- console.log(chalk.yellow(`Switched to ${value} run archbyte init to configure API key`));
108
+ console.log(chalk.yellow(`Switched to ${value}. Run archbyte init to configure API key.`));
109
109
  }
110
110
  break;
111
111
  }
package/dist/cli/diff.js CHANGED
@@ -17,7 +17,7 @@ export async function handleDiff(options) {
17
17
  const currentArch = loadArchitectureFile(currentPath);
18
18
  const projectName = process.cwd().split("/").pop() || "project";
19
19
  console.log();
20
- console.log(chalk.bold.cyan(`⚡ ArchByte Diff ${projectName}`));
20
+ console.log(chalk.bold.cyan(`⚡ ArchByte Diff: ${projectName}`));
21
21
  console.log(chalk.gray(` Baseline: ${options.baseline}`));
22
22
  console.log(chalk.gray(` Current: ${options.current || ".archbyte/architecture.json"}`));
23
23
  console.log();
@@ -104,7 +104,7 @@ function exportMarkdown(arch) {
104
104
  const realNodes = arch.nodes;
105
105
  const projectName = process.cwd().split("/").pop() || "project";
106
106
  const lines = [];
107
- lines.push(`# Architecture ${projectName}`);
107
+ lines.push(`# Architecture: ${projectName}`);
108
108
  lines.push("");
109
109
  lines.push(`> Generated by ArchByte on ${new Date().toISOString().slice(0, 10)}`);
110
110
  lines.push("");
@@ -127,7 +127,7 @@ function exportMarkdown(arch) {
127
127
  for (const node of realNodes) {
128
128
  const tech = node.techStack && node.techStack.length > 0
129
129
  ? node.techStack.join(", ")
130
- : "";
130
+ : "-";
131
131
  lines.push(`| ${node.label} | ${node.type} | ${node.layer} | ${tech} |`);
132
132
  }
133
133
  lines.push("");
@@ -145,7 +145,7 @@ function exportMarkdown(arch) {
145
145
  for (const edge of arch.edges) {
146
146
  const src = nodeMap.get(edge.source)?.label || edge.source;
147
147
  const tgt = nodeMap.get(edge.target)?.label || edge.target;
148
- const label = edge.label ? ` ${edge.label}` : "";
148
+ const label = edge.label ? `: ${edge.label}` : "";
149
149
  lines.push(`- **${src}** → **${tgt}**${label}`);
150
150
  }
151
151
  }
@@ -22,10 +22,10 @@ export async function requireLicense(action) {
22
22
  console.error();
23
23
  console.error(chalk.red("Authentication required."));
24
24
  console.error();
25
- console.error(chalk.gray("Sign in to use ArchByte:"));
25
+ console.error(chalk.gray("Sign in or create a free account:"));
26
26
  console.error(chalk.gray(" archbyte login"));
27
27
  console.error();
28
- console.error(chalk.gray("Basic tier includes unlimited scans."));
28
+ console.error(chalk.gray("Free tier includes unlimited scans. No credit card required."));
29
29
  process.exit(1);
30
30
  }
31
31
  // Token expired locally
@@ -0,0 +1 @@
1
+ export declare function startMcpServer(): Promise<void>;
@@ -0,0 +1,443 @@
1
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { ListToolsRequestSchema, CallToolRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
4
+ import { spawn } from "child_process";
5
+ import * as fs from "fs";
6
+ import * as path from "path";
7
+ import { fileURLToPath } from "url";
8
+ import { loadCredentials, getVerifiedTier, cacheVerifiedTier, resetOfflineActions, checkOfflineAction, } from "./auth.js";
9
+ import { API_BASE, NETWORK_TIMEOUT_MS } from "./constants.js";
10
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
+ // At runtime, __dirname = dist/cli/ — project root is two levels up
12
+ const PROJECT_ROOT = path.resolve(__dirname, "..", "..");
13
+ const BIN_PATH = path.join(PROJECT_ROOT, "bin", "archbyte.js");
14
+ // Read version from package.json
15
+ function getVersion() {
16
+ try {
17
+ const pkg = JSON.parse(fs.readFileSync(path.join(PROJECT_ROOT, "package.json"), "utf-8"));
18
+ return pkg.version ?? "0.0.0";
19
+ }
20
+ catch {
21
+ return "0.0.0";
22
+ }
23
+ }
24
+ // ─── ANSI strip ───
25
+ const ANSI_RE = /\x1B\[[0-9;]*[A-Za-z]/g;
26
+ function stripAnsi(s) {
27
+ return s.replace(ANSI_RE, "");
28
+ }
29
+ // ─── Auth check ───
30
+ function checkAuth() {
31
+ const creds = loadCredentials();
32
+ if (!creds) {
33
+ return {
34
+ ok: false,
35
+ error: "Not logged in. Run `archbyte login` to sign in.",
36
+ };
37
+ }
38
+ if (new Date(creds.expiresAt) < new Date()) {
39
+ return {
40
+ ok: false,
41
+ error: "Session expired. Run `archbyte login` to refresh.",
42
+ };
43
+ }
44
+ return { ok: true };
45
+ }
46
+ // ─── License check ───
47
+ async function checkLicense(action) {
48
+ const creds = loadCredentials();
49
+ if (!creds) {
50
+ return {
51
+ allowed: false,
52
+ reason: "Not logged in. Run `archbyte login` to sign in.",
53
+ };
54
+ }
55
+ try {
56
+ const res = await fetch(`${API_BASE}/api/v1/check-usage`, {
57
+ method: "POST",
58
+ headers: {
59
+ Authorization: `Bearer ${creds.token}`,
60
+ "Content-Type": "application/json",
61
+ },
62
+ body: JSON.stringify({ action }),
63
+ signal: AbortSignal.timeout(NETWORK_TIMEOUT_MS),
64
+ });
65
+ if (res.status === 401) {
66
+ return {
67
+ allowed: false,
68
+ reason: "Session invalid. Run `archbyte login` again.",
69
+ };
70
+ }
71
+ if (!res.ok) {
72
+ return handleOffline();
73
+ }
74
+ const data = (await res.json());
75
+ const tier = data.tier === "premium" ? "premium" : "free";
76
+ cacheVerifiedTier(tier, creds.email);
77
+ resetOfflineActions();
78
+ if (!data.allowed) {
79
+ return {
80
+ allowed: false,
81
+ reason: data.message ?? "Scan not allowed. Check your account status.",
82
+ };
83
+ }
84
+ return { allowed: true };
85
+ }
86
+ catch {
87
+ return handleOffline();
88
+ }
89
+ }
90
+ function handleOffline() {
91
+ const { allowed, reason } = checkOfflineAction();
92
+ if (!allowed) {
93
+ return {
94
+ allowed: false,
95
+ reason: reason ??
96
+ "License server unreachable and offline actions not permitted.",
97
+ };
98
+ }
99
+ return { allowed: true };
100
+ }
101
+ function runArchbyte(args, cwd, timeoutMs = 300_000) {
102
+ return new Promise((resolve) => {
103
+ const child = spawn(process.execPath, [BIN_PATH, ...args], {
104
+ cwd,
105
+ env: { ...process.env, NO_COLOR: "1", FORCE_COLOR: "0" },
106
+ stdio: ["ignore", "pipe", "pipe"],
107
+ timeout: timeoutMs,
108
+ });
109
+ const stdoutChunks = [];
110
+ const stderrChunks = [];
111
+ child.stdout.on("data", (chunk) => stdoutChunks.push(chunk));
112
+ child.stderr.on("data", (chunk) => stderrChunks.push(chunk));
113
+ child.on("close", (code) => {
114
+ resolve({
115
+ stdout: stripAnsi(Buffer.concat(stdoutChunks).toString("utf-8")),
116
+ stderr: stripAnsi(Buffer.concat(stderrChunks).toString("utf-8")),
117
+ exitCode: code ?? 1,
118
+ });
119
+ });
120
+ child.on("error", (err) => {
121
+ resolve({
122
+ stdout: "",
123
+ stderr: `Failed to spawn archbyte: ${err.message}`,
124
+ exitCode: 1,
125
+ });
126
+ });
127
+ });
128
+ }
129
+ // ─── Result helpers ───
130
+ function errorResult(msg) {
131
+ return { content: [{ type: "text", text: msg }], isError: true };
132
+ }
133
+ function textResult(text, isError = false) {
134
+ return { content: [{ type: "text", text }], isError };
135
+ }
136
+ function defineTools() {
137
+ return [
138
+ {
139
+ tool: {
140
+ name: "archbyte_analyze",
141
+ description: "Run AI-powered architecture analysis on a codebase. Scans source code and produces a structured analysis of components, services, connections, and data flows. Requires a configured model provider (archbyte init) unless --static is used.",
142
+ inputSchema: {
143
+ type: "object",
144
+ properties: {
145
+ dir: {
146
+ type: "string",
147
+ description: "Project root directory (default: current directory)",
148
+ },
149
+ static: {
150
+ type: "boolean",
151
+ description: "Static-only analysis. No model needed, free.",
152
+ },
153
+ provider: {
154
+ type: "string",
155
+ description: "Model provider: anthropic, openai, google",
156
+ },
157
+ apiKey: {
158
+ type: "string",
159
+ description: "Model API key (overrides config)",
160
+ },
161
+ force: {
162
+ type: "boolean",
163
+ description: "Force full re-scan (skip incremental detection)",
164
+ },
165
+ },
166
+ },
167
+ },
168
+ minTier: "free",
169
+ licenseAction: "analyze",
170
+ handler: async (params) => {
171
+ const args = ["analyze"];
172
+ const cwd = params.dir ?? process.cwd();
173
+ if (params.dir)
174
+ args.push("--dir", params.dir);
175
+ if (params.static)
176
+ args.push("--static");
177
+ if (params.provider)
178
+ args.push("--provider", params.provider);
179
+ if (params.apiKey)
180
+ args.push("--api-key", params.apiKey);
181
+ if (params.force)
182
+ args.push("--force");
183
+ const result = await runArchbyte(args, cwd);
184
+ const output = (result.stdout + (result.stderr ? `\n${result.stderr}` : "")).trim();
185
+ return textResult(output || "Analysis complete.", result.exitCode !== 0);
186
+ },
187
+ },
188
+ {
189
+ tool: {
190
+ name: "archbyte_generate",
191
+ description: "Generate an interactive architecture diagram from analysis output. Reads analysis.json and produces architecture.json for the visualization UI.",
192
+ inputSchema: {
193
+ type: "object",
194
+ properties: {
195
+ input: {
196
+ type: "string",
197
+ description: "Input analysis path (default: .archbyte/analysis.json)",
198
+ },
199
+ dir: {
200
+ type: "string",
201
+ description: "Project root directory (default: current directory)",
202
+ },
203
+ },
204
+ },
205
+ },
206
+ minTier: "free",
207
+ licenseAction: "generate",
208
+ handler: async (params) => {
209
+ const args = ["generate"];
210
+ const cwd = params.dir ?? process.cwd();
211
+ if (params.input)
212
+ args.push("--input", params.input);
213
+ const result = await runArchbyte(args, cwd);
214
+ const output = (result.stdout + (result.stderr ? `\n${result.stderr}` : "")).trim();
215
+ return textResult(output || "Diagram generated.", result.exitCode !== 0);
216
+ },
217
+ },
218
+ {
219
+ tool: {
220
+ name: "archbyte_validate",
221
+ description: "Run architecture fitness function rules against the current diagram. Returns structured JSON results with pass/fail status for each rule. Useful for CI/CD pipelines and automated quality checks.",
222
+ inputSchema: {
223
+ type: "object",
224
+ properties: {
225
+ diagram: {
226
+ type: "string",
227
+ description: "Path to architecture JSON (default: .archbyte/architecture.json)",
228
+ },
229
+ config: {
230
+ type: "string",
231
+ description: "Path to archbyte.yaml config",
232
+ },
233
+ dir: {
234
+ type: "string",
235
+ description: "Project root directory (default: current directory)",
236
+ },
237
+ },
238
+ },
239
+ },
240
+ minTier: "premium",
241
+ licenseAction: "analyze",
242
+ handler: async (params) => {
243
+ const args = ["validate", "--ci"];
244
+ const cwd = params.dir ?? process.cwd();
245
+ if (params.diagram)
246
+ args.push("--diagram", params.diagram);
247
+ if (params.config)
248
+ args.push("--config", params.config);
249
+ const result = await runArchbyte(args, cwd);
250
+ const output = (result.stdout + (result.stderr ? `\n${result.stderr}` : "")).trim();
251
+ return textResult(output || "Validation complete.", result.exitCode !== 0);
252
+ },
253
+ },
254
+ {
255
+ tool: {
256
+ name: "archbyte_export",
257
+ description: "Export architecture diagram to various formats: mermaid, markdown, json, plantuml, or dot. Returns the exported content as text.",
258
+ inputSchema: {
259
+ type: "object",
260
+ properties: {
261
+ format: {
262
+ type: "string",
263
+ enum: ["mermaid", "markdown", "json", "plantuml", "dot"],
264
+ description: "Output format (default: mermaid)",
265
+ },
266
+ diagram: {
267
+ type: "string",
268
+ description: "Path to architecture JSON (default: .archbyte/architecture.json)",
269
+ },
270
+ dir: {
271
+ type: "string",
272
+ description: "Project root directory (default: current directory)",
273
+ },
274
+ },
275
+ },
276
+ },
277
+ minTier: "free",
278
+ licenseAction: null,
279
+ handler: async (params) => {
280
+ const args = ["export"];
281
+ const cwd = params.dir ?? process.cwd();
282
+ if (params.format)
283
+ args.push("--format", params.format);
284
+ if (params.diagram)
285
+ args.push("--diagram", params.diagram);
286
+ const result = await runArchbyte(args, cwd);
287
+ const output = (result.stdout + (result.stderr ? `\n${result.stderr}` : "")).trim();
288
+ return textResult(output || "Export complete.", result.exitCode !== 0);
289
+ },
290
+ },
291
+ {
292
+ tool: {
293
+ name: "archbyte_stats",
294
+ description: "Show architecture health dashboard with metrics: node count, edge count, complexity scores, and component breakdown.",
295
+ inputSchema: {
296
+ type: "object",
297
+ properties: {
298
+ diagram: {
299
+ type: "string",
300
+ description: "Path to architecture JSON (default: .archbyte/architecture.json)",
301
+ },
302
+ dir: {
303
+ type: "string",
304
+ description: "Project root directory (default: current directory)",
305
+ },
306
+ },
307
+ },
308
+ },
309
+ minTier: "free",
310
+ licenseAction: null,
311
+ handler: async (params) => {
312
+ const args = ["stats"];
313
+ const cwd = params.dir ?? process.cwd();
314
+ if (params.diagram)
315
+ args.push("--diagram", params.diagram);
316
+ const result = await runArchbyte(args, cwd);
317
+ const output = (result.stdout + (result.stderr ? `\n${result.stderr}` : "")).trim();
318
+ return textResult(output || "No stats available.", result.exitCode !== 0);
319
+ },
320
+ },
321
+ {
322
+ tool: {
323
+ name: "archbyte_diff",
324
+ description: "Compare two architecture snapshots to detect drift. Shows added, removed, and modified nodes and edges between a baseline and current diagram.",
325
+ inputSchema: {
326
+ type: "object",
327
+ properties: {
328
+ baseline: {
329
+ type: "string",
330
+ description: "Path to baseline architecture JSON",
331
+ },
332
+ current: {
333
+ type: "string",
334
+ description: "Path to current architecture JSON (default: .archbyte/architecture.json)",
335
+ },
336
+ dir: {
337
+ type: "string",
338
+ description: "Project root directory (default: current directory)",
339
+ },
340
+ },
341
+ required: ["baseline"],
342
+ },
343
+ },
344
+ minTier: "free",
345
+ licenseAction: null,
346
+ handler: async (params) => {
347
+ const args = ["diff", "--baseline", params.baseline];
348
+ const cwd = params.dir ?? process.cwd();
349
+ if (params.current)
350
+ args.push("--current", params.current);
351
+ const result = await runArchbyte(args, cwd);
352
+ const output = (result.stdout + (result.stderr ? `\n${result.stderr}` : "")).trim();
353
+ return textResult(output || "No differences found.", result.exitCode !== 0);
354
+ },
355
+ },
356
+ {
357
+ tool: {
358
+ name: "archbyte_read_diagram",
359
+ description: "Read the current architecture diagram JSON. Returns the full architecture.json content including all nodes, edges, and metadata. Useful for understanding the project structure without re-running analysis.",
360
+ inputSchema: {
361
+ type: "object",
362
+ properties: {
363
+ diagram: {
364
+ type: "string",
365
+ description: "Path to architecture JSON (default: .archbyte/architecture.json)",
366
+ },
367
+ dir: {
368
+ type: "string",
369
+ description: "Project root directory (default: current directory)",
370
+ },
371
+ },
372
+ },
373
+ },
374
+ minTier: "free",
375
+ licenseAction: null,
376
+ handler: async (params) => {
377
+ const cwd = params.dir ?? process.cwd();
378
+ const diagramPath = params.diagram ??
379
+ path.join(cwd, ".archbyte", "architecture.json");
380
+ const resolved = path.isAbsolute(diagramPath)
381
+ ? diagramPath
382
+ : path.join(cwd, diagramPath);
383
+ try {
384
+ const content = fs.readFileSync(resolved, "utf-8");
385
+ return textResult(content);
386
+ }
387
+ catch {
388
+ return errorResult(`Diagram not found at ${resolved}. Run \`archbyte analyze\` then \`archbyte generate\` first.`);
389
+ }
390
+ },
391
+ },
392
+ ];
393
+ }
394
+ // ─── Tier comparison ───
395
+ const TIER_RANK = { free: 0, premium: 1 };
396
+ function tierSatisfies(userTier, required) {
397
+ return TIER_RANK[userTier] >= TIER_RANK[required];
398
+ }
399
+ // ─── Start MCP server ───
400
+ export async function startMcpServer() {
401
+ const allTools = defineTools();
402
+ const toolMap = new Map(allTools.map((t) => [t.tool.name, t]));
403
+ const server = new Server({ name: "archbyte", version: getVersion() }, { capabilities: { tools: { listChanged: true } } });
404
+ // ─── tools/list — filtered by user tier ───
405
+ // Always show free-tier tools (even when not logged in) so the AI agent
406
+ // can attempt to call them and receive the "please login" error message.
407
+ // Premium tools are only shown to premium users.
408
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
409
+ const auth = checkAuth();
410
+ const userTier = auth.ok ? getVerifiedTier() : "free";
411
+ const visible = allTools
412
+ .filter((t) => tierSatisfies(userTier, t.minTier))
413
+ .map((t) => t.tool);
414
+ return { tools: visible };
415
+ });
416
+ // ─── tools/call — auth + license + execute ───
417
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
418
+ const { name, arguments: args } = request.params;
419
+ const def = toolMap.get(name);
420
+ if (!def) {
421
+ return errorResult(`Unknown tool: ${name}`);
422
+ }
423
+ // Auth check
424
+ const auth = checkAuth();
425
+ if (!auth.ok)
426
+ return errorResult(auth.error);
427
+ // Tier check (defense in depth — tool might not be in list but called directly)
428
+ const userTier = getVerifiedTier();
429
+ if (!tierSatisfies(userTier, def.minTier)) {
430
+ return errorResult(`${name} requires a Pro subscription. Upgrade at https://archbyte.heartbyte.io`);
431
+ }
432
+ // License check for gated tools
433
+ if (def.licenseAction) {
434
+ const license = await checkLicense(def.licenseAction);
435
+ if (!license.allowed)
436
+ return errorResult(license.reason);
437
+ }
438
+ return def.handler(args ?? {});
439
+ });
440
+ // ─── Connect transport ───
441
+ const transport = new StdioServerTransport();
442
+ await server.connect(transport);
443
+ }
@@ -0,0 +1 @@
1
+ export declare function handleMcpInstall(): Promise<void>;
@@ -0,0 +1,102 @@
1
+ import { execSync, spawnSync } from "child_process";
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ import chalk from "chalk";
5
+ function isInPath(cmd) {
6
+ try {
7
+ execSync(`which ${cmd}`, { stdio: "ignore" });
8
+ return true;
9
+ }
10
+ catch {
11
+ return false;
12
+ }
13
+ }
14
+ export async function handleMcpInstall() {
15
+ console.log();
16
+ console.log(chalk.bold.cyan("ArchByte MCP Setup"));
17
+ console.log();
18
+ let configured = false;
19
+ // ─── Claude Code ───
20
+ if (isInPath("claude")) {
21
+ console.log(chalk.white("Detected Claude Code."));
22
+ const result = spawnSync("claude", ["mcp", "add", "archbyte", "--transport", "stdio", "--", "npx", "-y", "archbyte@latest", "mcp"], { stdio: "inherit" });
23
+ if (result.status === 0) {
24
+ console.log(chalk.green(" Configured Claude Code MCP server."));
25
+ configured = true;
26
+ }
27
+ else {
28
+ console.log(chalk.yellow(" Failed to configure Claude Code. See error above."));
29
+ }
30
+ console.log();
31
+ }
32
+ // ─── Codex CLI ───
33
+ const codexDir = path.join(process.env.HOME ?? process.env.USERPROFILE ?? ".", ".codex");
34
+ const codexConfig = path.join(codexDir, "config.toml");
35
+ if (fs.existsSync(codexDir)) {
36
+ console.log(chalk.white("Detected Codex CLI."));
37
+ let existing = "";
38
+ try {
39
+ existing = fs.readFileSync(codexConfig, "utf-8");
40
+ }
41
+ catch {
42
+ // File doesn't exist yet — we'll create it
43
+ }
44
+ if (existing.includes("[mcp_servers.archbyte]")) {
45
+ console.log(chalk.gray(" Codex already configured for archbyte."));
46
+ configured = true;
47
+ }
48
+ else {
49
+ const block = `
50
+ [mcp_servers.archbyte]
51
+ type = "stdio"
52
+ command = "npx"
53
+ args = ["-y", "archbyte@latest", "mcp"]
54
+ `;
55
+ fs.appendFileSync(codexConfig, block, "utf-8");
56
+ console.log(chalk.green(" Configured Codex CLI MCP server."));
57
+ configured = true;
58
+ }
59
+ console.log();
60
+ }
61
+ // ─── Fallback instructions ───
62
+ if (!configured) {
63
+ console.log(chalk.yellow("No supported AI coding tool detected."));
64
+ console.log();
65
+ }
66
+ // ─── Usage examples ───
67
+ if (configured) {
68
+ console.log(chalk.bold("What to do next:"));
69
+ console.log();
70
+ console.log(chalk.white(" Open your AI coding tool and ask it to use ArchByte. For example:"));
71
+ console.log();
72
+ console.log(chalk.cyan(' "Analyze the architecture of this project"'));
73
+ console.log(chalk.cyan(' "Export the architecture as a Mermaid diagram"'));
74
+ console.log(chalk.cyan(' "Show me the architecture stats"'));
75
+ console.log(chalk.cyan(' "Read the current architecture diagram"'));
76
+ console.log();
77
+ console.log(chalk.gray(" Your AI tool will call ArchByte tools automatically via MCP."));
78
+ console.log(chalk.gray(" No commands to memorize. Just describe what you need."));
79
+ console.log();
80
+ }
81
+ console.log(chalk.bold("Manual setup:"));
82
+ console.log();
83
+ console.log(chalk.white(" Claude Code:"));
84
+ console.log(chalk.gray(" claude mcp add archbyte --transport stdio -- npx -y archbyte@latest mcp"));
85
+ console.log();
86
+ console.log(chalk.white(" Codex CLI:"));
87
+ console.log(chalk.gray(" Add to ~/.codex/config.toml:"));
88
+ console.log(chalk.gray(' [mcp_servers.archbyte]'));
89
+ console.log(chalk.gray(' type = "stdio"'));
90
+ console.log(chalk.gray(' command = "npx"'));
91
+ console.log(chalk.gray(' args = ["-y", "archbyte@latest", "mcp"]'));
92
+ console.log();
93
+ console.log(chalk.bold(" Available MCP tools:"));
94
+ console.log(chalk.gray(" archbyte_analyze Scan codebase with AI agents"));
95
+ console.log(chalk.gray(" archbyte_generate Generate diagram from analysis"));
96
+ console.log(chalk.gray(" archbyte_validate Run fitness rules (Pro)"));
97
+ console.log(chalk.gray(" archbyte_export Export to Mermaid, PlantUML, DOT, etc."));
98
+ console.log(chalk.gray(" archbyte_stats Architecture health metrics"));
99
+ console.log(chalk.gray(" archbyte_diff Compare snapshots, detect drift"));
100
+ console.log(chalk.gray(" archbyte_read_diagram Read current architecture JSON"));
101
+ console.log();
102
+ }
@@ -106,7 +106,7 @@ function printPatrolResult(record, cycleNum) {
106
106
  const time = new Date(record.timestamp).toLocaleTimeString();
107
107
  const status = record.passed ? chalk.green("HEALTHY") : chalk.red("VIOLATION");
108
108
  console.log();
109
- console.log(chalk.bold.cyan(` Patrol #${cycleNum} ${time} ${status}`));
109
+ console.log(chalk.bold.cyan(` Patrol #${cycleNum} | ${time} | ${status}`));
110
110
  if (record.newViolations.length > 0) {
111
111
  console.log(chalk.red(` ${record.newViolations.length} new violation(s):`));
112
112
  for (const v of record.newViolations) {
@@ -121,7 +121,7 @@ function printPatrolResult(record, cycleNum) {
121
121
  }
122
122
  }
123
123
  if (record.newViolations.length === 0 && record.resolvedViolations.length === 0) {
124
- console.log(chalk.gray(` No changes ${record.errors} errors, ${record.warnings} warnings`));
124
+ console.log(chalk.gray(` No changes. ${record.errors} errors, ${record.warnings} warnings`));
125
125
  }
126
126
  }
127
127
  function printHistory(patrolDir) {
@@ -137,7 +137,7 @@ function printHistory(patrolDir) {
137
137
  }
138
138
  const projectName = process.cwd().split("/").pop() || "project";
139
139
  console.log();
140
- console.log(chalk.bold.cyan(` ArchByte Patrol History ${projectName}`));
140
+ console.log(chalk.bold.cyan(` ArchByte Patrol History: ${projectName}`));
141
141
  console.log();
142
142
  // Show last 20 records (skip malformed lines)
143
143
  const records = lines.slice(-20).flatMap((l) => {
@@ -208,7 +208,7 @@ export async function handlePatrol(options) {
208
208
  const intervalStr = options.interval || "5m";
209
209
  const action = options.onViolation || "log";
210
210
  console.log();
211
- console.log(chalk.bold.cyan(` ArchByte Patrol ${projectName}`));
211
+ console.log(chalk.bold.cyan(` ArchByte Patrol: ${projectName}`));
212
212
  console.log(chalk.gray(` Interval: ${intervalStr} | On violation: ${action}`));
213
213
  console.log(chalk.gray(` History: ${path.join(PATROL_DIR, HISTORY_FILE)}`));
214
214
  console.log(chalk.gray(" Press Ctrl+C to stop."));
package/dist/cli/setup.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as fs from "fs";
2
2
  import * as path from "path";
3
3
  import { fileURLToPath } from "url";
4
+ import { execSync } from "child_process";
4
5
  import chalk from "chalk";
5
6
  import { resolveModel } from "../agents/runtime/types.js";
6
7
  import { createProvider } from "../agents/providers/router.js";
@@ -111,10 +112,48 @@ async function validateProviderSilent(providerName, apiKey) {
111
112
  function getProfiles(config) {
112
113
  return config.profiles ?? {};
113
114
  }
115
+ function isInPath(cmd) {
116
+ try {
117
+ execSync(`which ${cmd}`, { stdio: "ignore" });
118
+ return true;
119
+ }
120
+ catch {
121
+ return false;
122
+ }
123
+ }
114
124
  export async function handleSetup() {
115
125
  console.log();
116
126
  console.log(chalk.bold.cyan("ArchByte Setup"));
117
127
  console.log(chalk.gray("Configure your model provider and API key.\n"));
128
+ // Detect AI coding tools — suggest MCP instead of BYOK
129
+ const hasClaude = isInPath("claude");
130
+ const codexDir = path.join(process.env.HOME ?? process.env.USERPROFILE ?? ".", ".codex");
131
+ const hasCodex = fs.existsSync(codexDir);
132
+ if (hasClaude || hasCodex) {
133
+ const tools = [hasClaude && "Claude Code", hasCodex && "Codex CLI"].filter(Boolean).join(" and ");
134
+ console.log(chalk.cyan(` Detected ${tools} on this machine.`));
135
+ console.log(chalk.white(` You can use ArchByte directly through MCP. No API key needed.`));
136
+ console.log(chalk.white(` Your AI tool already provides the model, so you skip the BYOK step.`));
137
+ console.log();
138
+ console.log(chalk.white(` Run: `) + chalk.bold.cyan(`archbyte mcp install`));
139
+ console.log();
140
+ const continueIdx = await select("Continue with BYOK setup anyway?", [
141
+ `Skip ${chalk.gray("(use MCP instead, recommended)")}`,
142
+ `Continue ${chalk.gray("(set up your own API key)")}`,
143
+ ]);
144
+ if (continueIdx === 0) {
145
+ console.log();
146
+ console.log(chalk.gray(" Run `archbyte mcp install` to configure MCP for your AI tool."));
147
+ console.log();
148
+ console.log(chalk.gray(" Then open your AI tool and try:"));
149
+ console.log(chalk.cyan(' "Analyze the architecture of this project"'));
150
+ console.log(chalk.cyan(' "Export the architecture as a Mermaid diagram"'));
151
+ console.log(chalk.cyan(' "Show me the architecture stats"'));
152
+ console.log();
153
+ return;
154
+ }
155
+ console.log();
156
+ }
118
157
  const config = loadConfig();
119
158
  const profiles = getProfiles(config);
120
159
  // Migrate legacy flat config → profiles
@@ -151,7 +190,7 @@ export async function handleSetup() {
151
190
  const idx = await select("Choose your model provider:", PROVIDERS.map((p) => {
152
191
  const active = config.provider === p.name ? chalk.green(" (active)") : "";
153
192
  const hasKey = profiles[p.name]?.apiKey ? chalk.cyan(" ✓ configured") : "";
154
- return `${p.label} ${chalk.gray("— " + p.hint)}${hasKey}${active}`;
193
+ return `${p.label} ${chalk.gray(p.hint)}${hasKey}${active}`;
155
194
  }));
156
195
  const provider = PROVIDERS[idx].name;
157
196
  config.provider = provider;
@@ -163,7 +202,7 @@ export async function handleSetup() {
163
202
  if (existing?.apiKey) {
164
203
  console.log(chalk.gray(`\n Existing key: ${maskKey(existing.apiKey)}`));
165
204
  const keepIdx = await select(" Update API key?", [
166
- `Keep existing ${chalk.gray("" + maskKey(existing.apiKey))}`,
205
+ `Keep existing ${chalk.gray("(" + maskKey(existing.apiKey) + ")")}`,
167
206
  "Enter new key",
168
207
  ]);
169
208
  if (keepIdx === 0) {
@@ -199,7 +238,7 @@ export async function handleSetup() {
199
238
  const currentModel = profiles[provider].model;
200
239
  const modelIdx = await select("\n Choose a model:", models.map((m) => {
201
240
  const current = currentModel === m.id && m.id ? chalk.green(" (current)") : "";
202
- return `${m.label} ${chalk.gray("— " + m.hint)}${current}`;
241
+ return `${m.label} ${chalk.gray(m.hint)}${current}`;
203
242
  }));
204
243
  const chosen = models[modelIdx];
205
244
  if (chosen.id) {
@@ -243,6 +282,8 @@ export async function handleSetup() {
243
282
  // Save config
244
283
  saveConfig(config);
245
284
  console.log(chalk.gray(`\n Config saved to ${CONFIG_PATH}`));
285
+ console.log(chalk.gray(" Your API key is stored locally on this machine and never sent to ArchByte."));
286
+ console.log(chalk.gray(" All model calls go directly from your machine to your provider."));
246
287
  // Generate archbyte.yaml in .archbyte/ if it doesn't exist
247
288
  const projectDir = process.cwd();
248
289
  const archbyteDir = path.join(projectDir, ".archbyte");
@@ -271,6 +312,8 @@ export async function handleSetup() {
271
312
  else {
272
313
  console.log(chalk.gray(` .archbyte/archbyte.yaml already exists`));
273
314
  }
315
+ // Generate README.md in .archbyte/
316
+ writeArchbyteReadme(archbyteDir);
274
317
  console.log();
275
318
  console.log(chalk.green(" Setup complete!"));
276
319
  console.log();
@@ -278,3 +321,39 @@ export async function handleSetup() {
278
321
  console.log(chalk.gray(" Run from your project root, or use ") + chalk.cyan("archbyte run -d /path/to/project"));
279
322
  console.log();
280
323
  }
324
+ function writeArchbyteReadme(archbyteDir) {
325
+ const readmePath = path.join(archbyteDir, "README.md");
326
+ if (fs.existsSync(readmePath))
327
+ return;
328
+ const content = `# .archbyte
329
+
330
+ This directory is managed by [ArchByte](https://diabhey.com) — your codebase's architecture intelligence layer.
331
+
332
+ ## Files
333
+
334
+ | File | What it does |
335
+ |------|-------------|
336
+ | \`archbyte.yaml\` | Your architecture spec. Human-readable, human-editable. This is the source of truth. |
337
+ | \`architecture.json\` | Generated diagram data. Fed directly to the interactive UI. |
338
+ | \`analysis.json\` | Raw analysis output from the agent pipeline. Used by \`archbyte generate\`. |
339
+ | \`metadata.json\` | Scan metadata — duration, mode, commit hash. Powers \`archbyte stats\` and \`archbyte diff\`. |
340
+ | \`analysis-status.json\` | Status of the last scan (success/error). Used by the dev server for live updates. |
341
+ | \`static-context.json\` | Cached static scanner output. Speeds up incremental re-scans. |
342
+
343
+ ## Workflow
344
+
345
+ \`\`\`
346
+ archbyte analyze → writes archbyte.yaml + analysis.json
347
+ archbyte generate → reads archbyte.yaml → writes architecture.json
348
+ archbyte serve → reads architecture.json → opens interactive diagram
349
+ \`\`\`
350
+
351
+ You can edit \`archbyte.yaml\` by hand — your changes are preserved on re-scan.
352
+
353
+ ---
354
+
355
+ Thanks for using ArchByte. Built with love by [diabhey](https://diabhey.com).
356
+ `;
357
+ fs.writeFileSync(readmePath, content, "utf-8");
358
+ console.log(chalk.green(` Created .archbyte/README.md`));
359
+ }
package/dist/cli/stats.js CHANGED
@@ -17,7 +17,7 @@ export async function handleStats(options) {
17
17
  // Auto-detect project name from cwd
18
18
  const projectName = process.cwd().split("/").pop() || "project";
19
19
  console.log();
20
- console.log(chalk.bold.cyan(`⚡ ArchByte Stats ${projectName}`));
20
+ console.log(chalk.bold.cyan(`⚡ ArchByte Stats: ${projectName}`));
21
21
  console.log();
22
22
  // ── Scan Info (from metadata.json, fallback to analysis.json) ──
23
23
  const metadataJsonPath = path.join(process.cwd(), ".archbyte", "metadata.json");
package/dist/cli/ui.js CHANGED
@@ -89,8 +89,8 @@ export function select(prompt, options) {
89
89
  cleanup();
90
90
  resolve(selected);
91
91
  }
92
- else if (data === "\x03") {
93
- // Ctrl+C
92
+ else if (data === "\x03" || data === "q" || data === "Q") {
93
+ // Ctrl+C or q to quit
94
94
  cleanup();
95
95
  process.exit(0);
96
96
  }
@@ -173,7 +173,7 @@ export function confirm(prompt) {
173
173
  process.stdout.write("n\n");
174
174
  resolve(false);
175
175
  }
176
- else if (data === "\x03") {
176
+ else if (data === "\x03" || data === "q" || data === "Q") {
177
177
  process.stdout.write("\n");
178
178
  process.exit(0);
179
179
  }
@@ -225,7 +225,7 @@ export async function handleValidate(options) {
225
225
  function printValidationReport(result) {
226
226
  const projectName = process.cwd().split("/").pop() || "project";
227
227
  console.log();
228
- console.log(chalk.bold.cyan(`⚡ ArchByte Validate ${projectName}`));
228
+ console.log(chalk.bold.cyan(`⚡ ArchByte Validate: ${projectName}`));
229
229
  console.log();
230
230
  // Group violations by rule for display
231
231
  const ruleViolations = new Map();
@@ -248,7 +248,7 @@ function printValidationReport(result) {
248
248
  }
249
249
  console.log();
250
250
  const resultStr = result.errors > 0 ? chalk.red("FAIL") : chalk.green("PASS");
251
- console.log(` Result: ${result.warnings} warning${result.warnings !== 1 ? "s" : ""}, ${result.errors} error${result.errors !== 1 ? "s" : ""} ${resultStr}`);
251
+ console.log(` Result: ${result.warnings} warning${result.warnings !== 1 ? "s" : ""}, ${result.errors} error${result.errors !== 1 ? "s" : ""} ${resultStr}`);
252
252
  console.log();
253
253
  }
254
254
  function printRuleResult(rule, level, count, violations) {
@@ -349,7 +349,7 @@ async function runWorkflow(workflow) {
349
349
  // Check dependencies
350
350
  const unmetDeps = step.needs.filter((dep) => state.steps[dep]?.status !== "completed");
351
351
  if (unmetDeps.length > 0) {
352
- console.log(chalk.yellow(` [skip] ${step.name} waiting on: ${unmetDeps.join(", ")}`));
352
+ console.log(chalk.yellow(` [skip] ${step.name} (waiting on: ${unmetDeps.join(", ")})`));
353
353
  state.steps[step.id] = { status: "pending" };
354
354
  saveState(state);
355
355
  continue;
@@ -414,7 +414,7 @@ function listWorkflows() {
414
414
  const workflows = loadWorkflows();
415
415
  const projectName = process.cwd().split("/").pop() || "project";
416
416
  console.log();
417
- console.log(chalk.bold.cyan(` ArchByte Workflows ${projectName}`));
417
+ console.log(chalk.bold.cyan(` ArchByte Workflows: ${projectName}`));
418
418
  console.log();
419
419
  for (const w of workflows) {
420
420
  const state = loadState(w.id);
@@ -428,7 +428,7 @@ function listWorkflows() {
428
428
  const builtinTag = BUILTIN_WORKFLOWS.some((b) => b.id === w.id)
429
429
  ? chalk.gray(" (built-in)")
430
430
  : "";
431
- console.log(` ${chalk.bold(w.id)}${builtinTag} ${statusStr}`);
431
+ console.log(` ${chalk.bold(w.id)}${builtinTag} ${statusStr}`);
432
432
  console.log(chalk.gray(` ${w.description}`));
433
433
  console.log(chalk.gray(` Steps: ${w.steps.map((s) => s.id).join(" → ")}`));
434
434
  console.log();
@@ -464,7 +464,7 @@ function showWorkflow(id) {
464
464
  }
465
465
  else if (stepState?.status === "failed") {
466
466
  icon = chalk.red("FAIL");
467
- statusLabel = chalk.red(` ${stepState.error?.slice(0, 60)}`);
467
+ statusLabel = chalk.red(` ${stepState.error?.slice(0, 60)}`);
468
468
  }
469
469
  const depsStr = step.needs.length > 0
470
470
  ? chalk.gray(` [needs: ${step.needs.join(", ")}]`)
@@ -481,7 +481,7 @@ function showStatus() {
481
481
  const workflows = loadWorkflows();
482
482
  const projectName = process.cwd().split("/").pop() || "project";
483
483
  console.log();
484
- console.log(chalk.bold.cyan(` Workflow Status ${projectName}`));
484
+ console.log(chalk.bold.cyan(` Workflow Status: ${projectName}`));
485
485
  console.log();
486
486
  let anyActive = false;
487
487
  for (const w of workflows) {
@@ -499,7 +499,7 @@ function showStatus() {
499
499
  statusIcon = chalk.green("done");
500
500
  if (state.status === "failed")
501
501
  statusIcon = chalk.red("failed");
502
- console.log(` ${chalk.bold(w.id)} [${bar}] ${pct}% ${statusIcon}`);
502
+ console.log(` ${chalk.bold(w.id)} [${bar}] ${pct}% ${statusIcon}`);
503
503
  console.log(chalk.gray(` ${completed}/${total} steps complete`));
504
504
  console.log();
505
505
  }
@@ -583,7 +583,7 @@ export async function handleWorkflow(options) {
583
583
  }
584
584
  const projectName = process.cwd().split("/").pop() || "project";
585
585
  console.log();
586
- console.log(chalk.bold.cyan(` ArchByte Workflow: ${workflow.name} ${projectName}`));
586
+ console.log(chalk.bold.cyan(` ArchByte Workflow: ${workflow.name} | ${projectName}`));
587
587
  console.log(chalk.gray(` ${workflow.description}`));
588
588
  console.log(chalk.gray(` Steps: ${workflow.steps.length}`));
589
589
  await runWorkflow(workflow);
@@ -36,7 +36,7 @@ export function writeSpec(rootDir, spec) {
36
36
  if (!fs.existsSync(dir)) {
37
37
  fs.mkdirSync(dir, { recursive: true });
38
38
  }
39
- const header = `# Generated by ArchByte ${new Date().toISOString().split("T")[0]}\n# Human-editable. Changes are preserved on re-scan.\n\n`;
39
+ const header = `# Generated by ArchByte ${new Date().toISOString().split("T")[0]}\n# Human-editable. Changes are preserved on re-scan.\n\n`;
40
40
  const yamlStr = yaml.dump(spec, {
41
41
  indent: 2,
42
42
  lineWidth: 120,
@@ -1384,4 +1384,16 @@ export async function startServer(cfg) {
1384
1384
  setupWatcher();
1385
1385
  console.error(`[archbyte] Serving ${config.name}`);
1386
1386
  console.error(`[archbyte] Diagram: ${config.diagramPath}`);
1387
+ // Listen for 'q' keypress to quit gracefully
1388
+ if (process.stdin.isTTY) {
1389
+ process.stdin.setRawMode(true);
1390
+ process.stdin.resume();
1391
+ process.stdin.setEncoding("utf8");
1392
+ process.stdin.on("data", (key) => {
1393
+ if (key === "q" || key === "Q" || key === "\x03") {
1394
+ process.emit("SIGINT");
1395
+ }
1396
+ });
1397
+ console.error(`[archbyte] Press q to quit`);
1398
+ }
1387
1399
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "archbyte",
3
- "version": "0.2.3",
3
+ "version": "0.2.6",
4
4
  "description": "ArchByte - See what agents build. As they build it.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -37,11 +37,13 @@
37
37
  "dependencies": {
38
38
  "@anthropic-ai/sdk": "^0.74.0",
39
39
  "@google/generative-ai": "^0.24.1",
40
+ "@modelcontextprotocol/sdk": "^1.26.0",
40
41
  "chalk": "^5.3.0",
41
42
  "chokidar": "^3.5.3",
42
43
  "commander": "^12.0.0",
43
44
  "js-yaml": "^4.1.1",
44
- "openai": "^6.19.0"
45
+ "openai": "^6.19.0",
46
+ "zod": "^4.3.6"
45
47
  },
46
48
  "devDependencies": {
47
49
  "@types/js-yaml": "^4.0.9",
Binary file
Binary file
@@ -4,7 +4,8 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>ArchByte</title>
7
- <link rel="icon" href="/favicon.ico">
7
+ <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png">
8
+ <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16.png">
8
9
  <script type="module" crossorigin src="/assets/index-BdfGbhpp.js"></script>
9
10
  <link rel="stylesheet" crossorigin href="/assets/index-0_XpUUZQ.css">
10
11
  </head>