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
package/dist/cli/setup.js
CHANGED
|
@@ -1,20 +1,38 @@
|
|
|
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";
|
|
7
8
|
import { select, spinner, confirm } from "./ui.js";
|
|
9
|
+
import { CONFIG_DIR, CONFIG_PATH } from "./constants.js";
|
|
8
10
|
const __filename = fileURLToPath(import.meta.url);
|
|
9
11
|
const __dirname = path.dirname(__filename);
|
|
10
|
-
const CONFIG_DIR = path.join(process.env.HOME ?? process.env.USERPROFILE ?? ".", ".archbyte");
|
|
11
|
-
const CONFIG_PATH = path.join(CONFIG_DIR, "config.json");
|
|
12
12
|
const PROVIDERS = [
|
|
13
|
-
{ name: "anthropic", label: "Anthropic", hint: "Claude
|
|
14
|
-
{ name: "openai", label: "OpenAI", hint: "
|
|
13
|
+
{ name: "anthropic", label: "Anthropic", hint: "Claude Sonnet / Opus" },
|
|
14
|
+
{ name: "openai", label: "OpenAI", hint: "Codex / GPT-5.2" },
|
|
15
15
|
{ name: "google", label: "Google", hint: "Gemini Flash / Pro" },
|
|
16
|
-
{ name: "ollama", label: "Ollama", hint: "Local models (no key needed)" },
|
|
17
16
|
];
|
|
17
|
+
const PROVIDER_MODELS = {
|
|
18
|
+
anthropic: [
|
|
19
|
+
{ id: "", label: "Default (recommended)", hint: "Haiku for fast agents, Sonnet for connections" },
|
|
20
|
+
{ id: "claude-haiku-4-5-20251001", label: "Claude Haiku 4.5", hint: "Fastest, cheapest" },
|
|
21
|
+
{ id: "claude-sonnet-4-5-20250929", label: "Claude Sonnet 4.5", hint: "Balanced, great quality" },
|
|
22
|
+
{ id: "claude-opus-4-6", label: "Claude Opus 4.6", hint: "Most capable" },
|
|
23
|
+
],
|
|
24
|
+
openai: [
|
|
25
|
+
{ id: "", label: "Default (recommended)", hint: "Codex for fast agents, GPT-5.2 for connections" },
|
|
26
|
+
{ id: "gpt-5.2-codex", label: "GPT-5.2 Codex", hint: "Fast, coding-optimized" },
|
|
27
|
+
{ id: "gpt-5.2", label: "GPT-5.2", hint: "General purpose, thinking model" },
|
|
28
|
+
{ id: "o3", label: "o3", hint: "Reasoning model" },
|
|
29
|
+
],
|
|
30
|
+
google: [
|
|
31
|
+
{ id: "", label: "Default (recommended)", hint: "Flash for fast agents, Pro for connections" },
|
|
32
|
+
{ id: "gemini-2.5-flash", label: "Gemini 2.5 Flash", hint: "Fastest, cheapest" },
|
|
33
|
+
{ id: "gemini-2.5-pro", label: "Gemini 2.5 Pro", hint: "Most capable" },
|
|
34
|
+
],
|
|
35
|
+
};
|
|
18
36
|
function loadConfig() {
|
|
19
37
|
try {
|
|
20
38
|
if (fs.existsSync(CONFIG_PATH)) {
|
|
@@ -75,40 +93,8 @@ function askHidden(prompt) {
|
|
|
75
93
|
stdin.on("data", onData);
|
|
76
94
|
});
|
|
77
95
|
}
|
|
78
|
-
async function
|
|
96
|
+
async function validateProviderSilent(providerName, apiKey) {
|
|
79
97
|
try {
|
|
80
|
-
const controller = new AbortController();
|
|
81
|
-
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
82
|
-
const res = await fetch(`${baseUrl}/api/tags`, { signal: controller.signal });
|
|
83
|
-
clearTimeout(timeout);
|
|
84
|
-
if (!res.ok)
|
|
85
|
-
return [];
|
|
86
|
-
const data = await res.json();
|
|
87
|
-
return (data.models ?? []).map((m) => m.name);
|
|
88
|
-
}
|
|
89
|
-
catch {
|
|
90
|
-
return [];
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
async function validateProviderSilent(providerName, apiKey, ollamaBaseUrl) {
|
|
94
|
-
try {
|
|
95
|
-
if (providerName === "ollama") {
|
|
96
|
-
const url = ollamaBaseUrl ?? "http://localhost:11434";
|
|
97
|
-
const controller = new AbortController();
|
|
98
|
-
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
99
|
-
try {
|
|
100
|
-
const res = await fetch(url, { signal: controller.signal });
|
|
101
|
-
clearTimeout(timeout);
|
|
102
|
-
if (!res.ok)
|
|
103
|
-
return false;
|
|
104
|
-
// Model listing happens in setup flow, not here
|
|
105
|
-
return true;
|
|
106
|
-
}
|
|
107
|
-
catch {
|
|
108
|
-
clearTimeout(timeout);
|
|
109
|
-
return false;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
98
|
const provider = createProvider({ provider: providerName, apiKey });
|
|
113
99
|
const model = resolveModel(providerName, "fast");
|
|
114
100
|
await provider.chat({
|
|
@@ -123,61 +109,151 @@ async function validateProviderSilent(providerName, apiKey, ollamaBaseUrl) {
|
|
|
123
109
|
return false;
|
|
124
110
|
}
|
|
125
111
|
}
|
|
112
|
+
function getProfiles(config) {
|
|
113
|
+
return config.profiles ?? {};
|
|
114
|
+
}
|
|
115
|
+
function isInPath(cmd) {
|
|
116
|
+
try {
|
|
117
|
+
execSync(`which ${cmd}`, { stdio: "ignore" });
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
126
124
|
export async function handleSetup() {
|
|
127
125
|
console.log();
|
|
128
126
|
console.log(chalk.bold.cyan("ArchByte Setup"));
|
|
129
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
|
+
}
|
|
130
157
|
const config = loadConfig();
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
158
|
+
const profiles = getProfiles(config);
|
|
159
|
+
// Migrate legacy flat config → profiles
|
|
160
|
+
if (!config.profiles && config.provider && config.apiKey) {
|
|
161
|
+
const legacy = config.provider;
|
|
162
|
+
const validNames = PROVIDERS.map((p) => p.name);
|
|
163
|
+
if (validNames.includes(legacy)) {
|
|
164
|
+
// Known provider — migrate to profile
|
|
165
|
+
profiles[legacy] = { apiKey: config.apiKey };
|
|
166
|
+
if (config.model)
|
|
167
|
+
profiles[legacy].model = config.model;
|
|
168
|
+
config.profiles = profiles;
|
|
169
|
+
}
|
|
170
|
+
// Clean up legacy flat keys regardless
|
|
171
|
+
delete config.apiKey;
|
|
172
|
+
delete config.model;
|
|
173
|
+
delete config.ollamaBaseUrl;
|
|
174
|
+
if (!validNames.includes(legacy)) {
|
|
175
|
+
delete config.provider;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// Show current config + configured profiles
|
|
179
|
+
const configured = Object.keys(profiles).filter((p) => profiles[p]?.apiKey);
|
|
180
|
+
if (configured.length > 0) {
|
|
181
|
+
console.log(chalk.gray(` Active provider: ${config.provider ?? "none"}`));
|
|
182
|
+
for (const p of configured) {
|
|
183
|
+
const active = p === config.provider ? chalk.green(" (active)") : "";
|
|
184
|
+
const model = profiles[p].model ? chalk.gray(` model: ${profiles[p].model}`) : "";
|
|
185
|
+
console.log(chalk.gray(` ${p}: ${maskKey(profiles[p].apiKey)}${model}${active}`));
|
|
136
186
|
}
|
|
137
187
|
console.log();
|
|
138
188
|
}
|
|
139
|
-
// Step 1: Choose provider
|
|
189
|
+
// Step 1: Choose provider
|
|
140
190
|
const idx = await select("Choose your model provider:", PROVIDERS.map((p) => {
|
|
141
|
-
const
|
|
142
|
-
|
|
191
|
+
const active = config.provider === p.name ? chalk.green(" (active)") : "";
|
|
192
|
+
const hasKey = profiles[p.name]?.apiKey ? chalk.cyan(" ✓ configured") : "";
|
|
193
|
+
return `${p.label} ${chalk.gray(p.hint)}${hasKey}${active}`;
|
|
143
194
|
}));
|
|
144
195
|
const provider = PROVIDERS[idx].name;
|
|
145
196
|
config.provider = provider;
|
|
146
197
|
const selected = PROVIDERS[idx];
|
|
147
198
|
console.log(chalk.green(`\n ✓ Provider: ${selected.label}`));
|
|
148
|
-
// Step 2: API key
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
199
|
+
// Step 2: API key (show existing if profile exists)
|
|
200
|
+
const existing = profiles[provider];
|
|
201
|
+
let apiKey;
|
|
202
|
+
if (existing?.apiKey) {
|
|
203
|
+
console.log(chalk.gray(`\n Existing key: ${maskKey(existing.apiKey)}`));
|
|
204
|
+
const keepIdx = await select(" Update API key?", [
|
|
205
|
+
`Keep existing ${chalk.gray("(" + maskKey(existing.apiKey) + ")")}`,
|
|
206
|
+
"Enter new key",
|
|
207
|
+
]);
|
|
208
|
+
if (keepIdx === 0) {
|
|
209
|
+
apiKey = existing.apiKey;
|
|
210
|
+
console.log(chalk.green(` ✓ API key: ${maskKey(apiKey)} (kept)`));
|
|
158
211
|
}
|
|
159
212
|
else {
|
|
160
|
-
console.log(
|
|
161
|
-
|
|
213
|
+
console.log();
|
|
214
|
+
apiKey = await askHidden(chalk.bold(" API key: "));
|
|
215
|
+
if (!apiKey) {
|
|
216
|
+
console.log(chalk.red(" No API key entered. Setup cancelled."));
|
|
217
|
+
process.exit(1);
|
|
218
|
+
}
|
|
219
|
+
console.log(chalk.green(` ✓ API key: ${maskKey(apiKey)}`));
|
|
162
220
|
}
|
|
163
221
|
}
|
|
164
222
|
else {
|
|
165
|
-
// Clear Ollama model override — model names are provider-specific
|
|
166
|
-
if (config.model) {
|
|
167
|
-
delete config.model;
|
|
168
|
-
}
|
|
169
223
|
console.log();
|
|
170
|
-
|
|
224
|
+
apiKey = await askHidden(chalk.bold(" API key: "));
|
|
171
225
|
if (!apiKey) {
|
|
172
226
|
console.log(chalk.red(" No API key entered. Setup cancelled."));
|
|
173
227
|
process.exit(1);
|
|
174
228
|
}
|
|
175
|
-
config.apiKey = apiKey;
|
|
176
229
|
console.log(chalk.green(` ✓ API key: ${maskKey(apiKey)}`));
|
|
177
230
|
}
|
|
231
|
+
// Save to profile
|
|
232
|
+
if (!profiles[provider])
|
|
233
|
+
profiles[provider] = { apiKey: "" };
|
|
234
|
+
profiles[provider].apiKey = apiKey;
|
|
235
|
+
// Step 3: Model selection
|
|
236
|
+
const models = PROVIDER_MODELS[provider];
|
|
237
|
+
if (models) {
|
|
238
|
+
const currentModel = profiles[provider].model;
|
|
239
|
+
const modelIdx = await select("\n Choose a model:", models.map((m) => {
|
|
240
|
+
const current = currentModel === m.id && m.id ? chalk.green(" (current)") : "";
|
|
241
|
+
return `${m.label} ${chalk.gray(m.hint)}${current}`;
|
|
242
|
+
}));
|
|
243
|
+
const chosen = models[modelIdx];
|
|
244
|
+
if (chosen.id) {
|
|
245
|
+
profiles[provider].model = chosen.id;
|
|
246
|
+
console.log(chalk.green(` ✓ Model: ${chosen.label}`));
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
delete profiles[provider].model;
|
|
250
|
+
console.log(chalk.green(` ✓ Model: Default (auto per agent tier)`));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
config.profiles = profiles;
|
|
178
254
|
// Validate provider with spinner
|
|
179
255
|
const s = spinner("Validating credentials");
|
|
180
|
-
let isValid = await validateProviderSilent(provider,
|
|
256
|
+
let isValid = await validateProviderSilent(provider, apiKey);
|
|
181
257
|
s.stop(isValid ? "valid" : "invalid", isValid ? "green" : "red");
|
|
182
258
|
// Retry loop on failure
|
|
183
259
|
if (!isValid) {
|
|
@@ -186,24 +262,28 @@ export async function handleSetup() {
|
|
|
186
262
|
if (!await confirm(" Retry?"))
|
|
187
263
|
break;
|
|
188
264
|
retries++;
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
console.log(chalk.green(` ✓ API key: ${maskKey(newKey)}`));
|
|
194
|
-
}
|
|
265
|
+
const newKey = await askHidden(chalk.bold(" API key: "));
|
|
266
|
+
if (newKey) {
|
|
267
|
+
profiles[provider].apiKey = newKey;
|
|
268
|
+
console.log(chalk.green(` ✓ API key: ${maskKey(newKey)}`));
|
|
195
269
|
}
|
|
196
270
|
const s2 = spinner("Validating credentials");
|
|
197
|
-
isValid = await validateProviderSilent(provider,
|
|
271
|
+
isValid = await validateProviderSilent(provider, profiles[provider].apiKey);
|
|
198
272
|
s2.stop(isValid ? "valid" : "invalid", isValid ? "green" : "red");
|
|
199
273
|
}
|
|
200
274
|
if (!isValid) {
|
|
201
275
|
console.log(chalk.yellow(" Warning: credentials unverified. Saving anyway."));
|
|
202
276
|
}
|
|
203
277
|
}
|
|
278
|
+
// Clean up legacy top-level keys
|
|
279
|
+
delete config.apiKey;
|
|
280
|
+
delete config.model;
|
|
281
|
+
delete config.ollamaBaseUrl;
|
|
204
282
|
// Save config
|
|
205
283
|
saveConfig(config);
|
|
206
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."));
|
|
207
287
|
// Generate archbyte.yaml in .archbyte/ if it doesn't exist
|
|
208
288
|
const projectDir = process.cwd();
|
|
209
289
|
const archbyteDir = path.join(projectDir, ".archbyte");
|
|
@@ -236,5 +316,6 @@ export async function handleSetup() {
|
|
|
236
316
|
console.log(chalk.green(" Setup complete!"));
|
|
237
317
|
console.log();
|
|
238
318
|
console.log(chalk.bold(" Next: ") + chalk.cyan("archbyte run") + chalk.bold(" to analyze your codebase."));
|
|
319
|
+
console.log(chalk.gray(" Run from your project root, or use ") + chalk.cyan("archbyte run -d /path/to/project"));
|
|
239
320
|
console.log();
|
|
240
321
|
}
|
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
|
|
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
|
}
|
package/dist/cli/validate.js
CHANGED
|
@@ -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
|
|
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" : ""}
|
|
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) {
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import { execSync } from "child_process";
|
|
6
|
+
import { NETWORK_TIMEOUT_MS } from "./constants.js";
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = path.dirname(__filename);
|
|
9
|
+
function getLocalVersion() {
|
|
10
|
+
const pkgPath = path.resolve(__dirname, "../../package.json");
|
|
11
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
12
|
+
return pkg.version;
|
|
13
|
+
}
|
|
14
|
+
export async function handleVersion() {
|
|
15
|
+
const version = getLocalVersion();
|
|
16
|
+
console.log();
|
|
17
|
+
console.log(chalk.bold.cyan("ArchByte") + chalk.gray(` v${version}`));
|
|
18
|
+
console.log();
|
|
19
|
+
console.log(` ${chalk.bold("version")}: ${version}`);
|
|
20
|
+
console.log(` ${chalk.bold("node")}: ${process.versions.node}`);
|
|
21
|
+
console.log(` ${chalk.bold("platform")}: ${process.platform} ${process.arch}`);
|
|
22
|
+
// Show install path
|
|
23
|
+
try {
|
|
24
|
+
const which = execSync("which archbyte", { encoding: "utf-8" }).trim();
|
|
25
|
+
console.log(` ${chalk.bold("binary")}: ${which}`);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// not globally installed or `which` not available
|
|
29
|
+
}
|
|
30
|
+
console.log();
|
|
31
|
+
}
|
|
32
|
+
async function fetchLatestVersion() {
|
|
33
|
+
try {
|
|
34
|
+
const controller = new AbortController();
|
|
35
|
+
const timeout = setTimeout(() => controller.abort(), NETWORK_TIMEOUT_MS);
|
|
36
|
+
const res = await fetch("https://registry.npmjs.org/archbyte/latest", {
|
|
37
|
+
signal: controller.signal,
|
|
38
|
+
});
|
|
39
|
+
clearTimeout(timeout);
|
|
40
|
+
if (!res.ok)
|
|
41
|
+
return null;
|
|
42
|
+
const data = (await res.json());
|
|
43
|
+
return data.version ?? null;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export async function handleUpdate() {
|
|
50
|
+
const current = getLocalVersion();
|
|
51
|
+
console.log();
|
|
52
|
+
console.log(chalk.bold.cyan("ArchByte Update"));
|
|
53
|
+
console.log(chalk.gray(` Current version: ${current}`));
|
|
54
|
+
console.log(chalk.gray(" Checking for updates..."));
|
|
55
|
+
const latest = await fetchLatestVersion();
|
|
56
|
+
if (!latest) {
|
|
57
|
+
console.log(chalk.yellow(" Could not reach npm registry. Update manually:"));
|
|
58
|
+
console.log(chalk.cyan(" npm install -g archbyte@latest"));
|
|
59
|
+
console.log();
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (latest === current) {
|
|
63
|
+
console.log(chalk.green(` You're on the latest version (${current}).`));
|
|
64
|
+
console.log();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
console.log(chalk.yellow(` New version available: ${current} → ${chalk.bold(latest)}`));
|
|
68
|
+
console.log();
|
|
69
|
+
// Run the update
|
|
70
|
+
console.log(chalk.gray(" Updating..."));
|
|
71
|
+
try {
|
|
72
|
+
execSync("npm install -g archbyte@latest", {
|
|
73
|
+
stdio: "inherit",
|
|
74
|
+
});
|
|
75
|
+
console.log();
|
|
76
|
+
console.log(chalk.green(` Updated to v${latest}`));
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
console.log();
|
|
80
|
+
console.log(chalk.red(" Update failed. Try manually:"));
|
|
81
|
+
console.log(chalk.cyan(" npm install -g archbyte@latest"));
|
|
82
|
+
}
|
|
83
|
+
console.log();
|
|
84
|
+
}
|
package/dist/cli/workflow.js
CHANGED
|
@@ -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}
|
|
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
|
|
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}
|
|
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(`
|
|
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
|
|
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}%
|
|
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}
|
|
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);
|
package/dist/cli/yaml-io.js
CHANGED
|
@@ -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
|
|
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,
|
package/dist/server/src/index.js
CHANGED
|
@@ -135,6 +135,46 @@ function createHttpServer() {
|
|
|
135
135
|
res.end(JSON.stringify(currentArchitecture || { nodes: [], edges: [] }));
|
|
136
136
|
return;
|
|
137
137
|
}
|
|
138
|
+
// API: Update node positions (save layout changes)
|
|
139
|
+
if (url === "/api/update-positions" && req.method === "POST") {
|
|
140
|
+
let body = "";
|
|
141
|
+
req.on("data", (chunk) => { body += chunk.toString(); });
|
|
142
|
+
req.on("end", async () => {
|
|
143
|
+
try {
|
|
144
|
+
const { updates } = JSON.parse(body);
|
|
145
|
+
if (!updates || !Array.isArray(updates)) {
|
|
146
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
147
|
+
res.end(JSON.stringify({ error: "Missing updates array" }));
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
// Read current architecture file
|
|
151
|
+
const content = await readFile(config.diagramPath, "utf-8");
|
|
152
|
+
const arch = JSON.parse(content);
|
|
153
|
+
// Apply position/dimension updates
|
|
154
|
+
for (const update of updates) {
|
|
155
|
+
const node = arch.nodes.find((n) => n.id === update.id);
|
|
156
|
+
if (node) {
|
|
157
|
+
node.x = update.x;
|
|
158
|
+
node.y = update.y;
|
|
159
|
+
node.width = update.width;
|
|
160
|
+
node.height = update.height;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Mark layout as user-saved so UI uses these positions
|
|
164
|
+
arch.layoutSaved = true;
|
|
165
|
+
// Write back
|
|
166
|
+
await writeFile(config.diagramPath, JSON.stringify(arch, null, 2), "utf-8");
|
|
167
|
+
currentArchitecture = arch;
|
|
168
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
169
|
+
res.end(JSON.stringify({ ok: true, updated: updates.length }));
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
173
|
+
res.end(JSON.stringify({ error: "Failed to save positions" }));
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
138
178
|
// API: Health
|
|
139
179
|
if (url === "/health") {
|
|
140
180
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
@@ -1344,4 +1384,16 @@ export async function startServer(cfg) {
|
|
|
1344
1384
|
setupWatcher();
|
|
1345
1385
|
console.error(`[archbyte] Serving ${config.name}`);
|
|
1346
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
|
+
}
|
|
1347
1399
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "archbyte",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
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",
|