archbyte 0.2.2 → 0.2.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.
- package/README.md +33 -5
- package/bin/archbyte.js +47 -7
- package/dist/agents/prompt-data.js +4 -4
- package/dist/agents/providers/router.js +1 -16
- package/dist/agents/runtime/types.d.ts +5 -1
- package/dist/agents/runtime/types.js +3 -3
- package/dist/agents/static/code-sampler.js +1 -4
- package/dist/agents/static/component-detector.js +1 -4
- package/dist/agents/static/event-detector.js +2 -1
- package/dist/agents/static/excluded-dirs.d.ts +3 -0
- package/dist/agents/static/excluded-dirs.js +11 -0
- package/dist/agents/static/file-tree-collector.js +1 -5
- package/dist/agents/tools/claude-code.js +2 -5
- package/dist/agents/tools/local-fs.js +2 -6
- package/dist/cli/analyze.d.ts +1 -0
- package/dist/cli/analyze.js +5 -5
- package/dist/cli/auth.js +8 -6
- package/dist/cli/config.d.ts +1 -0
- package/dist/cli/config.js +80 -61
- package/dist/cli/constants.d.ts +13 -0
- package/dist/cli/constants.js +20 -0
- package/dist/cli/diff.js +1 -1
- package/dist/cli/export.js +3 -3
- package/dist/cli/gate.js +3 -3
- package/dist/cli/generate.js +2 -1
- package/dist/cli/license-gate.js +5 -5
- package/dist/cli/mcp-server.d.ts +1 -0
- package/dist/cli/mcp-server.js +443 -0
- package/dist/cli/mcp.d.ts +1 -0
- package/dist/cli/mcp.js +102 -0
- package/dist/cli/patrol.js +4 -4
- package/dist/cli/run.d.ts +1 -0
- package/dist/cli/run.js +3 -7
- package/dist/cli/serve.js +3 -2
- package/dist/cli/setup.js +153 -72
- package/dist/cli/stats.js +1 -1
- package/dist/cli/ui.js +3 -3
- package/dist/cli/validate.js +2 -2
- package/dist/cli/version.d.ts +2 -0
- package/dist/cli/version.js +84 -0
- package/dist/cli/workflow.js +7 -7
- package/dist/cli/yaml-io.js +1 -1
- package/dist/server/src/index.js +52 -0
- package/package.json +4 -2
- package/ui/dist/assets/{index-Bl1r8zrI.css → index-0_XpUUZQ.css} +1 -1
- package/ui/dist/assets/index-BdfGbhpp.js +70 -0
- package/ui/dist/index.html +2 -2
- package/ui/dist/assets/index-CqbB6DOK.js +0 -70
|
@@ -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>;
|
package/dist/cli/mcp.js
ADDED
|
@@ -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
|
+
}
|
package/dist/cli/patrol.js
CHANGED
|
@@ -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}
|
|
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
|
|
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
|
|
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
|
|
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/run.d.ts
CHANGED
package/dist/cli/run.js
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
1
|
import { handleAnalyze } from "./analyze.js";
|
|
3
2
|
import { handleServe } from "./serve.js";
|
|
3
|
+
import { DEFAULT_PORT } from "./constants.js";
|
|
4
4
|
export async function handleRun(options) {
|
|
5
|
-
const port = options.port ||
|
|
6
|
-
console.log();
|
|
7
|
-
console.log(chalk.bold.cyan(" ArchByte Run"));
|
|
8
|
-
console.log();
|
|
5
|
+
const port = options.port || DEFAULT_PORT;
|
|
9
6
|
// 1. Analyze (includes auto-generate)
|
|
10
7
|
await handleAnalyze({
|
|
11
8
|
verbose: options.verbose,
|
|
@@ -15,11 +12,10 @@ export async function handleRun(options) {
|
|
|
15
12
|
apiKey: options.apiKey,
|
|
16
13
|
force: options.force,
|
|
17
14
|
dryRun: options.dryRun,
|
|
15
|
+
dir: options.dir,
|
|
18
16
|
});
|
|
19
17
|
if (options.dryRun)
|
|
20
18
|
return;
|
|
21
19
|
// 2. Serve the UI
|
|
22
|
-
console.log(chalk.bold.cyan(" Starting UI..."));
|
|
23
|
-
console.log();
|
|
24
20
|
await handleServe({ port });
|
|
25
21
|
}
|
package/dist/cli/serve.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import * as path from "path";
|
|
2
2
|
import * as fs from "fs";
|
|
3
3
|
import chalk from "chalk";
|
|
4
|
+
import { DEFAULT_PORT } from "./constants.js";
|
|
4
5
|
/**
|
|
5
6
|
* Start the ArchByte UI server
|
|
6
7
|
*/
|
|
7
8
|
export async function handleServe(options) {
|
|
8
9
|
const rootDir = process.cwd();
|
|
9
|
-
const port = options.port ||
|
|
10
|
+
const port = options.port || DEFAULT_PORT;
|
|
10
11
|
const diagramPath = options.diagram
|
|
11
12
|
? path.resolve(rootDir, options.diagram)
|
|
12
13
|
: path.join(rootDir, ".archbyte", "architecture.json");
|
|
@@ -22,7 +23,7 @@ export async function handleServe(options) {
|
|
|
22
23
|
// ignore
|
|
23
24
|
}
|
|
24
25
|
}
|
|
25
|
-
console.log(chalk.cyan(`🏗️ ArchByte -
|
|
26
|
+
console.log(chalk.cyan(`🏗️ ArchByte - See what agents build. As they build it.`));
|
|
26
27
|
console.log(chalk.gray(`Diagram: ${path.relative(rootDir, diagramPath)}`));
|
|
27
28
|
console.log(chalk.gray(`Port: ${port}`));
|
|
28
29
|
console.log();
|