archbyte 0.4.0 → 0.4.1

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.
@@ -6,8 +6,8 @@
6
6
  <title>ArchByte</title>
7
7
  <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png">
8
8
  <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16.png">
9
- <script type="module" crossorigin src="/assets/index-DmO1qYan.js"></script>
10
- <link rel="stylesheet" crossorigin href="/assets/index-0_XpUUZQ.css">
9
+ <script type="module" crossorigin src="/assets/index-DO4t5Xu1.js"></script>
10
+ <link rel="stylesheet" crossorigin href="/assets/index-DDCNauh7.css">
11
11
  </head>
12
12
  <body>
13
13
  <script>try{if(localStorage.getItem('archbyte-theme')==='light')document.documentElement.setAttribute('data-theme','light')}catch(e){}</script>
@@ -1 +0,0 @@
1
- export declare function startMcpServer(): Promise<void>;
@@ -1,443 +0,0 @@
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
- }
package/dist/cli/mcp.d.ts DELETED
@@ -1 +0,0 @@
1
- export declare function handleMcpInstall(): Promise<void>;
package/dist/cli/mcp.js DELETED
@@ -1,98 +0,0 @@
1
- import { spawnSync } from "child_process";
2
- import * as fs from "fs";
3
- import * as path from "path";
4
- import chalk from "chalk";
5
- import { isInPath } from "./utils.js";
6
- import { CONFIG_DIR } from "./constants.js";
7
- export async function handleMcpInstall() {
8
- console.log();
9
- console.log(chalk.bold.cyan("ArchByte MCP Setup"));
10
- console.log();
11
- let configured = false;
12
- // ─── Claude Code ───
13
- if (isInPath("claude")) {
14
- console.log(chalk.white("Detected Claude Code."));
15
- const result = spawnSync("claude", ["mcp", "add", "archbyte", "--transport", "stdio", "--", "npx", "-y", "archbyte@latest", "mcp"], { stdio: "inherit" });
16
- if (result.status === 0) {
17
- console.log(chalk.green(" Configured Claude Code MCP server."));
18
- configured = true;
19
- }
20
- else {
21
- console.log(chalk.yellow(" Failed to configure Claude Code. See error above."));
22
- }
23
- console.log();
24
- }
25
- // ─── Codex CLI ───
26
- const codexDir = path.join(CONFIG_DIR, "../.codex");
27
- const codexConfig = path.join(codexDir, "config.toml");
28
- if (fs.existsSync(codexDir)) {
29
- console.log(chalk.white("Detected Codex CLI."));
30
- let existing = "";
31
- try {
32
- existing = fs.readFileSync(codexConfig, "utf-8");
33
- }
34
- catch {
35
- // File doesn't exist yet — we'll create it
36
- }
37
- if (existing.includes("[mcp_servers.archbyte]")) {
38
- console.log(chalk.gray(" Codex already configured for archbyte."));
39
- configured = true;
40
- }
41
- else {
42
- // Ensure a trailing newline before appending the TOML block
43
- // so headers don't merge with the last line of the existing file.
44
- const needsNewline = existing.length > 0 && !existing.endsWith("\n");
45
- const block = `${needsNewline ? "\n" : ""}
46
- [mcp_servers.archbyte]
47
- type = "stdio"
48
- command = "npx"
49
- args = ["-y", "archbyte@latest", "mcp"]
50
- `;
51
- fs.appendFileSync(codexConfig, block, "utf-8");
52
- console.log(chalk.green(" Configured Codex CLI MCP server."));
53
- configured = true;
54
- }
55
- console.log();
56
- }
57
- // ─── Fallback instructions ───
58
- if (!configured) {
59
- console.log(chalk.yellow("No supported AI coding tool detected."));
60
- console.log();
61
- }
62
- // ─── Usage examples ───
63
- if (configured) {
64
- console.log(chalk.bold("What to do next:"));
65
- console.log();
66
- console.log(chalk.white(" Open your AI coding tool and ask it to use ArchByte. For example:"));
67
- console.log();
68
- console.log(chalk.cyan(' "Analyze the architecture of this project"'));
69
- console.log(chalk.cyan(' "Export the architecture as a Mermaid diagram"'));
70
- console.log(chalk.cyan(' "Show me the architecture stats"'));
71
- console.log(chalk.cyan(' "Read the current architecture diagram"'));
72
- console.log();
73
- console.log(chalk.gray(" Your AI tool will call ArchByte tools automatically via MCP."));
74
- console.log(chalk.gray(" No commands to memorize. Just describe what you need."));
75
- console.log();
76
- }
77
- console.log(chalk.bold("Manual setup:"));
78
- console.log();
79
- console.log(chalk.white(" Claude Code:"));
80
- console.log(chalk.gray(" claude mcp add archbyte --transport stdio -- npx -y archbyte@latest mcp"));
81
- console.log();
82
- console.log(chalk.white(" Codex CLI:"));
83
- console.log(chalk.gray(" Add to ~/.codex/config.toml:"));
84
- console.log(chalk.gray(' [mcp_servers.archbyte]'));
85
- console.log(chalk.gray(' type = "stdio"'));
86
- console.log(chalk.gray(' command = "npx"'));
87
- console.log(chalk.gray(' args = ["-y", "archbyte@latest", "mcp"]'));
88
- console.log();
89
- console.log(chalk.bold(" Available MCP tools:"));
90
- console.log(chalk.gray(" archbyte_analyze Scan codebase with AI agents"));
91
- console.log(chalk.gray(" archbyte_generate Generate diagram from analysis"));
92
- console.log(chalk.gray(" archbyte_validate Run fitness rules (Pro)"));
93
- console.log(chalk.gray(" archbyte_export Export to Mermaid, PlantUML, DOT, etc."));
94
- console.log(chalk.gray(" archbyte_stats Architecture health metrics"));
95
- console.log(chalk.gray(" archbyte_diff Compare snapshots, detect drift"));
96
- console.log(chalk.gray(" archbyte_read_diagram Read current architecture JSON"));
97
- console.log();
98
- }