archbyte 0.2.2 → 0.2.3
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/bin/archbyte.js +28 -6
- 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 +1 -2
- package/dist/cli/auth.js +2 -5
- package/dist/cli/config.d.ts +1 -0
- package/dist/cli/config.js +76 -57
- package/dist/cli/constants.d.ts +13 -0
- package/dist/cli/constants.js +20 -0
- package/dist/cli/gate.js +3 -3
- package/dist/cli/generate.js +2 -1
- package/dist/cli/license-gate.js +3 -3
- 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 +112 -72
- package/dist/cli/version.d.ts +2 -0
- package/dist/cli/version.js +84 -0
- package/dist/server/src/index.js +40 -0
- package/package.json +1 -1
- 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,20 @@
|
|
|
1
|
+
// Shared constants for the ArchByte CLI.
|
|
2
|
+
// Single source of truth for URLs, ports, paths, and timeouts.
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
// ─── API ───
|
|
5
|
+
export const API_BASE = process.env.ARCHBYTE_API_URL ?? "https://api.heartbyte.io";
|
|
6
|
+
export const SITE_URL = "https://archbyte.heartbyte.io";
|
|
7
|
+
// ─── Ports ───
|
|
8
|
+
export const DEFAULT_PORT = 3847;
|
|
9
|
+
export const CLI_CALLBACK_PORT = 19274;
|
|
10
|
+
// ─── Paths ───
|
|
11
|
+
export const CONFIG_DIR = path.join(process.env.HOME ?? process.env.USERPROFILE ?? ".", ".archbyte");
|
|
12
|
+
export const CONFIG_PATH = path.join(CONFIG_DIR, "config.json");
|
|
13
|
+
export const CREDENTIALS_PATH = path.join(CONFIG_DIR, "credentials.json");
|
|
14
|
+
/** Project-local .archbyte directory name */
|
|
15
|
+
export const PROJECT_DIR = ".archbyte";
|
|
16
|
+
// ─── Timeouts ───
|
|
17
|
+
/** Timeout for non-critical network checks (license, version, gate) */
|
|
18
|
+
export const NETWORK_TIMEOUT_MS = 5000;
|
|
19
|
+
/** Timeout for OAuth callback server */
|
|
20
|
+
export const OAUTH_TIMEOUT_MS = 60_000;
|
package/dist/cli/gate.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { loadCredentials, cacheVerifiedTier, resetOfflineActions, checkOfflineAction } from "./auth.js";
|
|
2
|
-
|
|
2
|
+
import { API_BASE, NETWORK_TIMEOUT_MS } from "./constants.js";
|
|
3
3
|
/**
|
|
4
4
|
* Auth gate command for Claude Code integration.
|
|
5
5
|
*
|
|
@@ -48,7 +48,7 @@ async function checkGate(action) {
|
|
|
48
48
|
"Content-Type": "application/json",
|
|
49
49
|
},
|
|
50
50
|
body: JSON.stringify({ action }),
|
|
51
|
-
signal: AbortSignal.timeout(
|
|
51
|
+
signal: AbortSignal.timeout(NETWORK_TIMEOUT_MS),
|
|
52
52
|
});
|
|
53
53
|
if (res.status === 401) {
|
|
54
54
|
const result = {
|
|
@@ -120,7 +120,7 @@ async function recordScan() {
|
|
|
120
120
|
"Content-Type": "application/json",
|
|
121
121
|
},
|
|
122
122
|
body: JSON.stringify({}),
|
|
123
|
-
signal: AbortSignal.timeout(
|
|
123
|
+
signal: AbortSignal.timeout(NETWORK_TIMEOUT_MS),
|
|
124
124
|
});
|
|
125
125
|
console.log(JSON.stringify({ recorded: true }));
|
|
126
126
|
}
|
package/dist/cli/generate.js
CHANGED
|
@@ -4,6 +4,7 @@ import chalk from "chalk";
|
|
|
4
4
|
import { generateArchitecture } from "../server/src/generator/index.js";
|
|
5
5
|
import { loadSpec, specToAnalysis } from "./yaml-io.js";
|
|
6
6
|
import { spinner } from "./ui.js";
|
|
7
|
+
import { DEFAULT_PORT } from "./constants.js";
|
|
7
8
|
/**
|
|
8
9
|
* Generate excalidraw diagram from analysis JSON
|
|
9
10
|
*/
|
|
@@ -233,7 +234,7 @@ export async function handleGenerate(options) {
|
|
|
233
234
|
console.log();
|
|
234
235
|
console.log(chalk.bold("Next steps:"));
|
|
235
236
|
console.log(chalk.gray(` 1. Run ${chalk.cyan("archbyte serve")} to start the visualization server`));
|
|
236
|
-
console.log(chalk.gray(` 2. Open ${chalk.cyan(
|
|
237
|
+
console.log(chalk.gray(` 2. Open ${chalk.cyan(`http://localhost:${DEFAULT_PORT}`)} to view and adjust the diagram`));
|
|
237
238
|
console.log();
|
|
238
239
|
}
|
|
239
240
|
catch (error) {
|
package/dist/cli/license-gate.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { loadCredentials, cacheVerifiedTier, resetOfflineActions, checkOfflineAction } from "./auth.js";
|
|
3
|
-
|
|
3
|
+
import { API_BASE, NETWORK_TIMEOUT_MS } from "./constants.js";
|
|
4
4
|
/**
|
|
5
5
|
* Pre-flight license check. Must be called before scan/analyze/generate.
|
|
6
6
|
*
|
|
@@ -44,7 +44,7 @@ export async function requireLicense(action) {
|
|
|
44
44
|
"Content-Type": "application/json",
|
|
45
45
|
},
|
|
46
46
|
body: JSON.stringify({ action }),
|
|
47
|
-
signal: AbortSignal.timeout(
|
|
47
|
+
signal: AbortSignal.timeout(NETWORK_TIMEOUT_MS),
|
|
48
48
|
});
|
|
49
49
|
if (res.status === 401) {
|
|
50
50
|
console.error();
|
|
@@ -112,7 +112,7 @@ export async function recordUsage(meta) {
|
|
|
112
112
|
"Content-Type": "application/json",
|
|
113
113
|
},
|
|
114
114
|
body: JSON.stringify(meta),
|
|
115
|
-
signal: AbortSignal.timeout(
|
|
115
|
+
signal: AbortSignal.timeout(NETWORK_TIMEOUT_MS),
|
|
116
116
|
});
|
|
117
117
|
}
|
|
118
118
|
catch {
|
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();
|
package/dist/cli/setup.js
CHANGED
|
@@ -5,16 +5,33 @@ import chalk from "chalk";
|
|
|
5
5
|
import { resolveModel } from "../agents/runtime/types.js";
|
|
6
6
|
import { createProvider } from "../agents/providers/router.js";
|
|
7
7
|
import { select, spinner, confirm } from "./ui.js";
|
|
8
|
+
import { CONFIG_DIR, CONFIG_PATH } from "./constants.js";
|
|
8
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
9
10
|
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
11
|
const PROVIDERS = [
|
|
13
|
-
{ name: "anthropic", label: "Anthropic", hint: "Claude
|
|
14
|
-
{ name: "openai", label: "OpenAI", hint: "
|
|
12
|
+
{ name: "anthropic", label: "Anthropic", hint: "Claude Sonnet / Opus" },
|
|
13
|
+
{ name: "openai", label: "OpenAI", hint: "Codex / GPT-5.2" },
|
|
15
14
|
{ name: "google", label: "Google", hint: "Gemini Flash / Pro" },
|
|
16
|
-
{ name: "ollama", label: "Ollama", hint: "Local models (no key needed)" },
|
|
17
15
|
];
|
|
16
|
+
const PROVIDER_MODELS = {
|
|
17
|
+
anthropic: [
|
|
18
|
+
{ id: "", label: "Default (recommended)", hint: "Haiku for fast agents, Sonnet for connections" },
|
|
19
|
+
{ id: "claude-haiku-4-5-20251001", label: "Claude Haiku 4.5", hint: "Fastest, cheapest" },
|
|
20
|
+
{ id: "claude-sonnet-4-5-20250929", label: "Claude Sonnet 4.5", hint: "Balanced, great quality" },
|
|
21
|
+
{ id: "claude-opus-4-6", label: "Claude Opus 4.6", hint: "Most capable" },
|
|
22
|
+
],
|
|
23
|
+
openai: [
|
|
24
|
+
{ id: "", label: "Default (recommended)", hint: "Codex for fast agents, GPT-5.2 for connections" },
|
|
25
|
+
{ id: "gpt-5.2-codex", label: "GPT-5.2 Codex", hint: "Fast, coding-optimized" },
|
|
26
|
+
{ id: "gpt-5.2", label: "GPT-5.2", hint: "General purpose, thinking model" },
|
|
27
|
+
{ id: "o3", label: "o3", hint: "Reasoning model" },
|
|
28
|
+
],
|
|
29
|
+
google: [
|
|
30
|
+
{ id: "", label: "Default (recommended)", hint: "Flash for fast agents, Pro for connections" },
|
|
31
|
+
{ id: "gemini-2.5-flash", label: "Gemini 2.5 Flash", hint: "Fastest, cheapest" },
|
|
32
|
+
{ id: "gemini-2.5-pro", label: "Gemini 2.5 Pro", hint: "Most capable" },
|
|
33
|
+
],
|
|
34
|
+
};
|
|
18
35
|
function loadConfig() {
|
|
19
36
|
try {
|
|
20
37
|
if (fs.existsSync(CONFIG_PATH)) {
|
|
@@ -75,40 +92,8 @@ function askHidden(prompt) {
|
|
|
75
92
|
stdin.on("data", onData);
|
|
76
93
|
});
|
|
77
94
|
}
|
|
78
|
-
async function
|
|
95
|
+
async function validateProviderSilent(providerName, apiKey) {
|
|
79
96
|
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
97
|
const provider = createProvider({ provider: providerName, apiKey });
|
|
113
98
|
const model = resolveModel(providerName, "fast");
|
|
114
99
|
await provider.chat({
|
|
@@ -123,61 +108,113 @@ async function validateProviderSilent(providerName, apiKey, ollamaBaseUrl) {
|
|
|
123
108
|
return false;
|
|
124
109
|
}
|
|
125
110
|
}
|
|
111
|
+
function getProfiles(config) {
|
|
112
|
+
return config.profiles ?? {};
|
|
113
|
+
}
|
|
126
114
|
export async function handleSetup() {
|
|
127
115
|
console.log();
|
|
128
116
|
console.log(chalk.bold.cyan("ArchByte Setup"));
|
|
129
117
|
console.log(chalk.gray("Configure your model provider and API key.\n"));
|
|
130
118
|
const config = loadConfig();
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
119
|
+
const profiles = getProfiles(config);
|
|
120
|
+
// Migrate legacy flat config → profiles
|
|
121
|
+
if (!config.profiles && config.provider && config.apiKey) {
|
|
122
|
+
const legacy = config.provider;
|
|
123
|
+
const validNames = PROVIDERS.map((p) => p.name);
|
|
124
|
+
if (validNames.includes(legacy)) {
|
|
125
|
+
// Known provider — migrate to profile
|
|
126
|
+
profiles[legacy] = { apiKey: config.apiKey };
|
|
127
|
+
if (config.model)
|
|
128
|
+
profiles[legacy].model = config.model;
|
|
129
|
+
config.profiles = profiles;
|
|
130
|
+
}
|
|
131
|
+
// Clean up legacy flat keys regardless
|
|
132
|
+
delete config.apiKey;
|
|
133
|
+
delete config.model;
|
|
134
|
+
delete config.ollamaBaseUrl;
|
|
135
|
+
if (!validNames.includes(legacy)) {
|
|
136
|
+
delete config.provider;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Show current config + configured profiles
|
|
140
|
+
const configured = Object.keys(profiles).filter((p) => profiles[p]?.apiKey);
|
|
141
|
+
if (configured.length > 0) {
|
|
142
|
+
console.log(chalk.gray(` Active provider: ${config.provider ?? "none"}`));
|
|
143
|
+
for (const p of configured) {
|
|
144
|
+
const active = p === config.provider ? chalk.green(" (active)") : "";
|
|
145
|
+
const model = profiles[p].model ? chalk.gray(` model: ${profiles[p].model}`) : "";
|
|
146
|
+
console.log(chalk.gray(` ${p}: ${maskKey(profiles[p].apiKey)}${model}${active}`));
|
|
136
147
|
}
|
|
137
148
|
console.log();
|
|
138
149
|
}
|
|
139
|
-
// Step 1: Choose provider
|
|
150
|
+
// Step 1: Choose provider
|
|
140
151
|
const idx = await select("Choose your model provider:", PROVIDERS.map((p) => {
|
|
141
|
-
const
|
|
142
|
-
|
|
152
|
+
const active = config.provider === p.name ? chalk.green(" (active)") : "";
|
|
153
|
+
const hasKey = profiles[p.name]?.apiKey ? chalk.cyan(" ✓ configured") : "";
|
|
154
|
+
return `${p.label} ${chalk.gray("— " + p.hint)}${hasKey}${active}`;
|
|
143
155
|
}));
|
|
144
156
|
const provider = PROVIDERS[idx].name;
|
|
145
157
|
config.provider = provider;
|
|
146
158
|
const selected = PROVIDERS[idx];
|
|
147
159
|
console.log(chalk.green(`\n ✓ Provider: ${selected.label}`));
|
|
148
|
-
// Step 2: API key
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
160
|
+
// Step 2: API key (show existing if profile exists)
|
|
161
|
+
const existing = profiles[provider];
|
|
162
|
+
let apiKey;
|
|
163
|
+
if (existing?.apiKey) {
|
|
164
|
+
console.log(chalk.gray(`\n Existing key: ${maskKey(existing.apiKey)}`));
|
|
165
|
+
const keepIdx = await select(" Update API key?", [
|
|
166
|
+
`Keep existing ${chalk.gray("— " + maskKey(existing.apiKey))}`,
|
|
167
|
+
"Enter new key",
|
|
168
|
+
]);
|
|
169
|
+
if (keepIdx === 0) {
|
|
170
|
+
apiKey = existing.apiKey;
|
|
171
|
+
console.log(chalk.green(` ✓ API key: ${maskKey(apiKey)} (kept)`));
|
|
158
172
|
}
|
|
159
173
|
else {
|
|
160
|
-
console.log(
|
|
161
|
-
|
|
174
|
+
console.log();
|
|
175
|
+
apiKey = await askHidden(chalk.bold(" API key: "));
|
|
176
|
+
if (!apiKey) {
|
|
177
|
+
console.log(chalk.red(" No API key entered. Setup cancelled."));
|
|
178
|
+
process.exit(1);
|
|
179
|
+
}
|
|
180
|
+
console.log(chalk.green(` ✓ API key: ${maskKey(apiKey)}`));
|
|
162
181
|
}
|
|
163
182
|
}
|
|
164
183
|
else {
|
|
165
|
-
// Clear Ollama model override — model names are provider-specific
|
|
166
|
-
if (config.model) {
|
|
167
|
-
delete config.model;
|
|
168
|
-
}
|
|
169
184
|
console.log();
|
|
170
|
-
|
|
185
|
+
apiKey = await askHidden(chalk.bold(" API key: "));
|
|
171
186
|
if (!apiKey) {
|
|
172
187
|
console.log(chalk.red(" No API key entered. Setup cancelled."));
|
|
173
188
|
process.exit(1);
|
|
174
189
|
}
|
|
175
|
-
config.apiKey = apiKey;
|
|
176
190
|
console.log(chalk.green(` ✓ API key: ${maskKey(apiKey)}`));
|
|
177
191
|
}
|
|
192
|
+
// Save to profile
|
|
193
|
+
if (!profiles[provider])
|
|
194
|
+
profiles[provider] = { apiKey: "" };
|
|
195
|
+
profiles[provider].apiKey = apiKey;
|
|
196
|
+
// Step 3: Model selection
|
|
197
|
+
const models = PROVIDER_MODELS[provider];
|
|
198
|
+
if (models) {
|
|
199
|
+
const currentModel = profiles[provider].model;
|
|
200
|
+
const modelIdx = await select("\n Choose a model:", models.map((m) => {
|
|
201
|
+
const current = currentModel === m.id && m.id ? chalk.green(" (current)") : "";
|
|
202
|
+
return `${m.label} ${chalk.gray("— " + m.hint)}${current}`;
|
|
203
|
+
}));
|
|
204
|
+
const chosen = models[modelIdx];
|
|
205
|
+
if (chosen.id) {
|
|
206
|
+
profiles[provider].model = chosen.id;
|
|
207
|
+
console.log(chalk.green(` ✓ Model: ${chosen.label}`));
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
delete profiles[provider].model;
|
|
211
|
+
console.log(chalk.green(` ✓ Model: Default (auto per agent tier)`));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
config.profiles = profiles;
|
|
178
215
|
// Validate provider with spinner
|
|
179
216
|
const s = spinner("Validating credentials");
|
|
180
|
-
let isValid = await validateProviderSilent(provider,
|
|
217
|
+
let isValid = await validateProviderSilent(provider, apiKey);
|
|
181
218
|
s.stop(isValid ? "valid" : "invalid", isValid ? "green" : "red");
|
|
182
219
|
// Retry loop on failure
|
|
183
220
|
if (!isValid) {
|
|
@@ -186,21 +223,23 @@ export async function handleSetup() {
|
|
|
186
223
|
if (!await confirm(" Retry?"))
|
|
187
224
|
break;
|
|
188
225
|
retries++;
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
console.log(chalk.green(` ✓ API key: ${maskKey(newKey)}`));
|
|
194
|
-
}
|
|
226
|
+
const newKey = await askHidden(chalk.bold(" API key: "));
|
|
227
|
+
if (newKey) {
|
|
228
|
+
profiles[provider].apiKey = newKey;
|
|
229
|
+
console.log(chalk.green(` ✓ API key: ${maskKey(newKey)}`));
|
|
195
230
|
}
|
|
196
231
|
const s2 = spinner("Validating credentials");
|
|
197
|
-
isValid = await validateProviderSilent(provider,
|
|
232
|
+
isValid = await validateProviderSilent(provider, profiles[provider].apiKey);
|
|
198
233
|
s2.stop(isValid ? "valid" : "invalid", isValid ? "green" : "red");
|
|
199
234
|
}
|
|
200
235
|
if (!isValid) {
|
|
201
236
|
console.log(chalk.yellow(" Warning: credentials unverified. Saving anyway."));
|
|
202
237
|
}
|
|
203
238
|
}
|
|
239
|
+
// Clean up legacy top-level keys
|
|
240
|
+
delete config.apiKey;
|
|
241
|
+
delete config.model;
|
|
242
|
+
delete config.ollamaBaseUrl;
|
|
204
243
|
// Save config
|
|
205
244
|
saveConfig(config);
|
|
206
245
|
console.log(chalk.gray(`\n Config saved to ${CONFIG_PATH}`));
|
|
@@ -236,5 +275,6 @@ export async function handleSetup() {
|
|
|
236
275
|
console.log(chalk.green(" Setup complete!"));
|
|
237
276
|
console.log();
|
|
238
277
|
console.log(chalk.bold(" Next: ") + chalk.cyan("archbyte run") + chalk.bold(" to analyze your codebase."));
|
|
278
|
+
console.log(chalk.gray(" Run from your project root, or use ") + chalk.cyan("archbyte run -d /path/to/project"));
|
|
239
279
|
console.log();
|
|
240
280
|
}
|
|
@@ -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/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" });
|