archbyte 0.3.5 → 0.4.0
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 +42 -0
- package/bin/archbyte.js +24 -5
- package/dist/agents/pipeline/merger.js +16 -11
- package/dist/agents/providers/claude-sdk.d.ts +7 -0
- package/dist/agents/providers/claude-sdk.js +59 -0
- package/dist/agents/providers/router.d.ts +5 -0
- package/dist/agents/providers/router.js +23 -1
- package/dist/agents/runtime/types.d.ts +2 -2
- package/dist/agents/runtime/types.js +5 -0
- package/dist/cli/analyze.js +8 -4
- package/dist/cli/auth.d.ts +8 -2
- package/dist/cli/auth.js +241 -31
- package/dist/cli/config.js +31 -5
- package/dist/cli/export.js +64 -2
- package/dist/cli/setup.js +79 -6
- package/dist/cli/ui.d.ts +9 -0
- package/dist/cli/ui.js +59 -5
- package/dist/server/src/index.js +56 -2
- package/package.json +4 -1
- package/ui/dist/assets/index-DmO1qYan.js +70 -0
- package/ui/dist/index.html +1 -1
- package/ui/dist/assets/index-BTo0zV5E.js +0 -70
package/dist/cli/auth.js
CHANGED
|
@@ -4,6 +4,7 @@ import * as http from "http";
|
|
|
4
4
|
import { spawn } from "child_process";
|
|
5
5
|
import chalk from "chalk";
|
|
6
6
|
import { CONFIG_DIR, CREDENTIALS_PATH, API_BASE, CLI_CALLBACK_PORT, OAUTH_TIMEOUT_MS, } from "./constants.js";
|
|
7
|
+
import { confirm, select, textInput } from "./ui.js";
|
|
7
8
|
export async function handleLogin(provider) {
|
|
8
9
|
console.log();
|
|
9
10
|
console.log(chalk.bold.cyan("ArchByte Login"));
|
|
@@ -32,10 +33,39 @@ export async function handleLogin(provider) {
|
|
|
32
33
|
// Offline — use cached tier
|
|
33
34
|
}
|
|
34
35
|
console.log(chalk.green(`Already logged in as ${chalk.bold(existing.email)} (${existing.tier} tier)`));
|
|
35
|
-
console.log(
|
|
36
|
+
console.log();
|
|
37
|
+
const addAnother = await confirm("Add a different account?");
|
|
38
|
+
if (!addAnother) {
|
|
39
|
+
console.log(chalk.gray("Run `archbyte logout` to sign out."));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
console.log();
|
|
43
|
+
}
|
|
44
|
+
// Pick provider: use explicit flag, or show interactive picker
|
|
45
|
+
let selectedProvider = provider ?? "github";
|
|
46
|
+
if (!provider) {
|
|
47
|
+
const providers = ["GitHub", "Google", "Email & Password"];
|
|
48
|
+
const idx = await select("Sign in with:", providers);
|
|
49
|
+
selectedProvider = ["github", "google", "email"][idx];
|
|
50
|
+
console.log();
|
|
51
|
+
}
|
|
52
|
+
// Email/password flow — direct API call, no browser
|
|
53
|
+
if (selectedProvider === "email") {
|
|
54
|
+
try {
|
|
55
|
+
const credentials = await emailPasswordFlow();
|
|
56
|
+
saveCredentials(credentials);
|
|
57
|
+
cacheVerifiedTier(credentials.tier === "premium" ? "premium" : "free", credentials.email);
|
|
58
|
+
console.log();
|
|
59
|
+
console.log(chalk.green(`Logged in as ${chalk.bold(credentials.email)} (${credentials.tier} tier)`));
|
|
60
|
+
console.log(chalk.gray(`Credentials saved to ${CREDENTIALS_PATH}`));
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
console.error(chalk.red(`Login failed: ${err instanceof Error ? err.message : "Unknown error"}`));
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
36
66
|
return;
|
|
37
67
|
}
|
|
38
|
-
|
|
68
|
+
// OAuth flow (GitHub / Google)
|
|
39
69
|
console.log(chalk.gray(`Opening browser for ${selectedProvider} sign-in...`));
|
|
40
70
|
console.log();
|
|
41
71
|
try {
|
|
@@ -92,26 +122,57 @@ export async function handleLoginWithToken(token) {
|
|
|
92
122
|
}
|
|
93
123
|
}
|
|
94
124
|
// === Logout ===
|
|
95
|
-
export async function handleLogout() {
|
|
125
|
+
export async function handleLogout(options) {
|
|
96
126
|
console.log();
|
|
97
|
-
const
|
|
98
|
-
if (!
|
|
127
|
+
const store = loadStore();
|
|
128
|
+
if (!store) {
|
|
99
129
|
console.log(chalk.gray("Not logged in."));
|
|
100
130
|
return;
|
|
101
131
|
}
|
|
102
|
-
|
|
103
|
-
|
|
132
|
+
const emails = Object.keys(store.accounts);
|
|
133
|
+
// Logout all accounts
|
|
134
|
+
if (options?.all) {
|
|
135
|
+
try {
|
|
136
|
+
fs.unlinkSync(CREDENTIALS_PATH);
|
|
137
|
+
}
|
|
138
|
+
catch { /* Ignore */ }
|
|
139
|
+
try {
|
|
140
|
+
fs.unlinkSync(TIER_CACHE_PATH);
|
|
141
|
+
}
|
|
142
|
+
catch { /* Ignore */ }
|
|
143
|
+
resetOfflineActions();
|
|
144
|
+
console.log(chalk.green(`Logged out of ${emails.length} account${emails.length === 1 ? "" : "s"}.`));
|
|
145
|
+
return;
|
|
104
146
|
}
|
|
105
|
-
|
|
106
|
-
|
|
147
|
+
// Determine which account to remove
|
|
148
|
+
const targetEmail = options?.email ?? store.active;
|
|
149
|
+
if (!store.accounts[targetEmail]) {
|
|
150
|
+
console.log(chalk.yellow(`No account found for ${targetEmail}.`));
|
|
151
|
+
return;
|
|
107
152
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
153
|
+
delete store.accounts[targetEmail];
|
|
154
|
+
const remaining = Object.keys(store.accounts);
|
|
155
|
+
if (remaining.length === 0) {
|
|
156
|
+
// Last account — delete the file entirely
|
|
157
|
+
try {
|
|
158
|
+
fs.unlinkSync(CREDENTIALS_PATH);
|
|
159
|
+
}
|
|
160
|
+
catch { /* Ignore */ }
|
|
161
|
+
try {
|
|
162
|
+
fs.unlinkSync(TIER_CACHE_PATH);
|
|
163
|
+
}
|
|
164
|
+
catch { /* Ignore */ }
|
|
165
|
+
resetOfflineActions();
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
// Auto-switch if we removed the active account
|
|
169
|
+
if (store.active === targetEmail) {
|
|
170
|
+
store.active = remaining[0];
|
|
171
|
+
console.log(chalk.gray(`Switched active account to ${store.active}`));
|
|
172
|
+
}
|
|
173
|
+
saveStore(store);
|
|
111
174
|
}
|
|
112
|
-
|
|
113
|
-
resetOfflineActions();
|
|
114
|
-
console.log(chalk.green(`Logged out (was ${creds.email}).`));
|
|
175
|
+
console.log(chalk.green(`Logged out (was ${targetEmail}).`));
|
|
115
176
|
}
|
|
116
177
|
// === Status ===
|
|
117
178
|
export async function handleStatus() {
|
|
@@ -145,32 +206,105 @@ export async function handleStatus() {
|
|
|
145
206
|
}
|
|
146
207
|
console.log();
|
|
147
208
|
}
|
|
209
|
+
// === Accounts ===
|
|
210
|
+
export async function handleAccounts() {
|
|
211
|
+
console.log();
|
|
212
|
+
console.log(chalk.bold.cyan("ArchByte Accounts"));
|
|
213
|
+
console.log();
|
|
214
|
+
const store = loadStore();
|
|
215
|
+
if (!store || Object.keys(store.accounts).length === 0) {
|
|
216
|
+
console.log(chalk.yellow("No accounts. Run `archbyte login` to sign in."));
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
for (const [email, creds] of Object.entries(store.accounts)) {
|
|
220
|
+
const isActive = email === store.active;
|
|
221
|
+
const marker = isActive ? chalk.green("*") : " ";
|
|
222
|
+
const tier = creds.tier === "premium" ? chalk.green("Pro") : "Basic";
|
|
223
|
+
const expired = isExpired(creds) ? chalk.red(" (expired)") : "";
|
|
224
|
+
console.log(` ${marker} ${chalk.bold(email)} ${tier}${expired}`);
|
|
225
|
+
}
|
|
226
|
+
console.log();
|
|
227
|
+
console.log(chalk.gray("* = active account"));
|
|
228
|
+
console.log(chalk.gray("Switch: archbyte accounts switch [email]"));
|
|
229
|
+
console.log();
|
|
230
|
+
}
|
|
231
|
+
export async function handleAccountSwitch(email) {
|
|
232
|
+
console.log();
|
|
233
|
+
const store = loadStore();
|
|
234
|
+
if (!store || Object.keys(store.accounts).length === 0) {
|
|
235
|
+
console.log(chalk.yellow("No accounts. Run `archbyte login` to sign in."));
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const emails = Object.keys(store.accounts);
|
|
239
|
+
if (emails.length === 1) {
|
|
240
|
+
console.log(chalk.gray(`Only one account: ${emails[0]}`));
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
let target = email;
|
|
244
|
+
if (!target) {
|
|
245
|
+
// Interactive selection
|
|
246
|
+
const idx = await select("Switch to account:", emails.map((e) => {
|
|
247
|
+
const active = e === store.active ? chalk.green(" (active)") : "";
|
|
248
|
+
const tier = store.accounts[e].tier === "premium" ? " Pro" : "";
|
|
249
|
+
return `${e}${tier}${active}`;
|
|
250
|
+
}));
|
|
251
|
+
target = emails[idx];
|
|
252
|
+
}
|
|
253
|
+
if (!store.accounts[target]) {
|
|
254
|
+
console.log(chalk.red(`Account not found: ${target}`));
|
|
255
|
+
console.log(chalk.gray(`Available: ${emails.join(", ")}`));
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (store.active === target) {
|
|
259
|
+
console.log(chalk.gray(`Already active: ${target}`));
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
store.active = target;
|
|
263
|
+
saveStore(store);
|
|
264
|
+
console.log(chalk.green(`Switched to ${chalk.bold(target)}`));
|
|
265
|
+
}
|
|
148
266
|
// === Credential Helpers (exported for license gate) ===
|
|
149
|
-
|
|
267
|
+
function isMultiAccountStore(data) {
|
|
268
|
+
return (typeof data === "object" &&
|
|
269
|
+
data !== null &&
|
|
270
|
+
data.version === 2 &&
|
|
271
|
+
typeof data.active === "string" &&
|
|
272
|
+
typeof data.accounts === "object" &&
|
|
273
|
+
data.accounts !== null);
|
|
274
|
+
}
|
|
275
|
+
function loadStore() {
|
|
150
276
|
try {
|
|
151
|
-
if (fs.existsSync(CREDENTIALS_PATH))
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
277
|
+
if (!fs.existsSync(CREDENTIALS_PATH))
|
|
278
|
+
return null;
|
|
279
|
+
const raw = JSON.parse(fs.readFileSync(CREDENTIALS_PATH, "utf-8"));
|
|
280
|
+
// Already multi-account format
|
|
281
|
+
if (isMultiAccountStore(raw))
|
|
282
|
+
return raw;
|
|
283
|
+
// Auto-migrate old single-account format
|
|
284
|
+
if (typeof raw?.token === "string" &&
|
|
285
|
+
typeof raw?.email === "string" &&
|
|
286
|
+
typeof raw?.tier === "string" &&
|
|
287
|
+
typeof raw?.expiresAt === "string") {
|
|
288
|
+
const creds = raw;
|
|
289
|
+
const store = {
|
|
290
|
+
version: 2,
|
|
291
|
+
active: creds.email,
|
|
292
|
+
accounts: { [creds.email]: creds },
|
|
293
|
+
};
|
|
294
|
+
saveStore(store);
|
|
295
|
+
return store;
|
|
161
296
|
}
|
|
297
|
+
return null;
|
|
162
298
|
}
|
|
163
299
|
catch {
|
|
164
|
-
|
|
300
|
+
return null;
|
|
165
301
|
}
|
|
166
|
-
return null;
|
|
167
302
|
}
|
|
168
|
-
function
|
|
303
|
+
function saveStore(store) {
|
|
169
304
|
if (!fs.existsSync(CONFIG_DIR)) {
|
|
170
305
|
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
171
306
|
}
|
|
172
|
-
fs.writeFileSync(CREDENTIALS_PATH, JSON.stringify(
|
|
173
|
-
// Restrict permissions (owner-only read/write)
|
|
307
|
+
fs.writeFileSync(CREDENTIALS_PATH, JSON.stringify(store, null, 2), "utf-8");
|
|
174
308
|
try {
|
|
175
309
|
fs.chmodSync(CREDENTIALS_PATH, 0o600);
|
|
176
310
|
}
|
|
@@ -178,6 +312,30 @@ function saveCredentials(creds) {
|
|
|
178
312
|
// Windows doesn't support chmod
|
|
179
313
|
}
|
|
180
314
|
}
|
|
315
|
+
export function loadCredentials() {
|
|
316
|
+
const store = loadStore();
|
|
317
|
+
if (!store)
|
|
318
|
+
return null;
|
|
319
|
+
const creds = store.accounts[store.active];
|
|
320
|
+
if (!creds ||
|
|
321
|
+
typeof creds.token !== "string" ||
|
|
322
|
+
typeof creds.email !== "string" ||
|
|
323
|
+
typeof creds.tier !== "string" ||
|
|
324
|
+
typeof creds.expiresAt !== "string") {
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
return creds;
|
|
328
|
+
}
|
|
329
|
+
function saveCredentials(creds) {
|
|
330
|
+
const store = loadStore() ?? {
|
|
331
|
+
version: 2,
|
|
332
|
+
active: creds.email,
|
|
333
|
+
accounts: {},
|
|
334
|
+
};
|
|
335
|
+
store.accounts[creds.email] = creds;
|
|
336
|
+
store.active = creds.email;
|
|
337
|
+
saveStore(store);
|
|
338
|
+
}
|
|
181
339
|
/**
|
|
182
340
|
* Check if credentials are expired. Treats invalid/unparseable dates
|
|
183
341
|
* as expired (fail-closed) to prevent corrupted files from bypassing
|
|
@@ -333,6 +491,58 @@ export function resetOfflineActions() {
|
|
|
333
491
|
// Non-critical
|
|
334
492
|
}
|
|
335
493
|
}
|
|
494
|
+
// === Email/Password Flow ===
|
|
495
|
+
async function emailPasswordFlow() {
|
|
496
|
+
const email = await textInput("Email");
|
|
497
|
+
if (!email)
|
|
498
|
+
throw new Error("Email is required.");
|
|
499
|
+
const password = await textInput("Password", { mask: true });
|
|
500
|
+
if (!password)
|
|
501
|
+
throw new Error("Password is required.");
|
|
502
|
+
const res = await fetch(`${API_BASE}/api/v1/auth/login`, {
|
|
503
|
+
method: "POST",
|
|
504
|
+
headers: { "Content-Type": "application/json" },
|
|
505
|
+
body: JSON.stringify({ email, password }),
|
|
506
|
+
});
|
|
507
|
+
if (res.status === 401) {
|
|
508
|
+
console.log();
|
|
509
|
+
console.log(chalk.yellow("No account found with those credentials."));
|
|
510
|
+
const shouldSignUp = await confirm("Create a new account?");
|
|
511
|
+
if (!shouldSignUp)
|
|
512
|
+
throw new Error("Login cancelled.");
|
|
513
|
+
return emailSignupFlow(email, password);
|
|
514
|
+
}
|
|
515
|
+
if (!res.ok) {
|
|
516
|
+
const body = await res.json().catch(() => ({ error: "Unknown error" }));
|
|
517
|
+
throw new Error(body.error ?? `Login failed (${res.status})`);
|
|
518
|
+
}
|
|
519
|
+
return parseTokenResponse(await res.json());
|
|
520
|
+
}
|
|
521
|
+
async function emailSignupFlow(email, password) {
|
|
522
|
+
const name = await textInput("Name (optional)");
|
|
523
|
+
const res = await fetch(`${API_BASE}/api/v1/auth/signup`, {
|
|
524
|
+
method: "POST",
|
|
525
|
+
headers: { "Content-Type": "application/json" },
|
|
526
|
+
body: JSON.stringify({ email, password, name: name || undefined }),
|
|
527
|
+
});
|
|
528
|
+
if (!res.ok) {
|
|
529
|
+
const body = await res.json().catch(() => ({ error: "Unknown error" }));
|
|
530
|
+
throw new Error(body.error ?? `Signup failed (${res.status})`);
|
|
531
|
+
}
|
|
532
|
+
return parseTokenResponse(await res.json());
|
|
533
|
+
}
|
|
534
|
+
function parseTokenResponse(data) {
|
|
535
|
+
const { token, user } = data;
|
|
536
|
+
const payload = parseJWTPayload(token);
|
|
537
|
+
return {
|
|
538
|
+
token,
|
|
539
|
+
email: user.email,
|
|
540
|
+
tier: user.tier,
|
|
541
|
+
expiresAt: payload?.exp
|
|
542
|
+
? new Date(payload.exp * 1000).toISOString()
|
|
543
|
+
: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
|
|
544
|
+
};
|
|
545
|
+
}
|
|
336
546
|
// === OAuth Flow ===
|
|
337
547
|
function startOAuthFlow(provider = "github") {
|
|
338
548
|
return new Promise((resolve, reject) => {
|
package/dist/cli/config.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
|
+
import { execSync } from "child_process";
|
|
2
3
|
import chalk from "chalk";
|
|
3
4
|
import { CONFIG_DIR, CONFIG_PATH } from "./constants.js";
|
|
4
5
|
import { maskKey } from "./utils.js";
|
|
5
|
-
const VALID_PROVIDERS = ["anthropic", "openai", "google"];
|
|
6
|
+
const VALID_PROVIDERS = ["anthropic", "openai", "google", "claude-sdk"];
|
|
6
7
|
export async function handleConfig(options) {
|
|
7
8
|
const [action, key, value] = options.args;
|
|
8
9
|
if (!action || action === "show") {
|
|
@@ -74,7 +75,8 @@ function showConfig() {
|
|
|
74
75
|
console.log(chalk.gray(" archbyte init"));
|
|
75
76
|
return;
|
|
76
77
|
}
|
|
77
|
-
|
|
78
|
+
const providerDisplay = config.provider === "claude-sdk" ? "Claude Code (SDK)" : config.provider;
|
|
79
|
+
console.log(` ${chalk.bold("provider")}: ${providerDisplay ?? chalk.gray("not set")}`);
|
|
78
80
|
// Show profiles
|
|
79
81
|
if (configured.length > 0) {
|
|
80
82
|
console.log();
|
|
@@ -108,8 +110,10 @@ function setConfig(key, value) {
|
|
|
108
110
|
process.exit(1);
|
|
109
111
|
}
|
|
110
112
|
config.provider = value;
|
|
111
|
-
|
|
112
|
-
|
|
113
|
+
if (value === "claude-sdk") {
|
|
114
|
+
console.log(chalk.green("Switched to Claude Code (SDK) — no API key needed."));
|
|
115
|
+
}
|
|
116
|
+
else if (profiles[value]?.apiKey) {
|
|
113
117
|
console.log(chalk.green(`Switched to ${value} (credentials on file)`));
|
|
114
118
|
}
|
|
115
119
|
else {
|
|
@@ -203,8 +207,20 @@ export function resolveConfig() {
|
|
|
203
207
|
const profile = provider ? profiles[provider] : undefined;
|
|
204
208
|
const apiKey = process.env.ARCHBYTE_API_KEY ?? profile?.apiKey ?? config.apiKey;
|
|
205
209
|
const model = process.env.ARCHBYTE_MODEL ?? profile?.model ?? config.model;
|
|
206
|
-
//
|
|
210
|
+
// claude-sdk provider doesn't need an API key
|
|
211
|
+
if (provider === "claude-sdk") {
|
|
212
|
+
return {
|
|
213
|
+
provider,
|
|
214
|
+
model: model,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
// Auto-detect if nothing explicitly configured
|
|
207
218
|
if (!provider && !apiKey) {
|
|
219
|
+
// Claude Code on PATH → zero-config, preferred
|
|
220
|
+
if (isClaudeCodeOnPath()) {
|
|
221
|
+
return { provider: "claude-sdk" };
|
|
222
|
+
}
|
|
223
|
+
// Fall back to API key env vars
|
|
208
224
|
if (process.env.ANTHROPIC_API_KEY) {
|
|
209
225
|
return { provider: "anthropic", apiKey: process.env.ANTHROPIC_API_KEY };
|
|
210
226
|
}
|
|
@@ -227,3 +243,13 @@ export function resolveConfig() {
|
|
|
227
243
|
model: model,
|
|
228
244
|
};
|
|
229
245
|
}
|
|
246
|
+
function isClaudeCodeOnPath() {
|
|
247
|
+
try {
|
|
248
|
+
const cmd = process.platform === "win32" ? "where claude" : "which claude";
|
|
249
|
+
execSync(cmd, { stdio: "pipe" });
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
}
|
package/dist/cli/export.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
+
import { fileURLToPath } from "url";
|
|
2
3
|
import { resolveArchitecturePath, loadArchitectureFile } from "./shared.js";
|
|
3
4
|
/**
|
|
4
5
|
* Export architecture diagram to Mermaid, Markdown, or JSON format.
|
|
@@ -7,11 +8,24 @@ export async function handleExport(options) {
|
|
|
7
8
|
const diagramPath = resolveArchitecturePath(options);
|
|
8
9
|
const arch = loadArchitectureFile(diagramPath);
|
|
9
10
|
const format = options.format || "mermaid";
|
|
10
|
-
const SUPPORTED_FORMATS = ["mermaid", "markdown", "json", "plantuml", "dot"];
|
|
11
|
+
const SUPPORTED_FORMATS = ["mermaid", "markdown", "json", "plantuml", "dot", "html"];
|
|
11
12
|
if (!SUPPORTED_FORMATS.includes(format)) {
|
|
12
|
-
|
|
13
|
+
const formatList = SUPPORTED_FORMATS.map(f => f === "html" ? `html ${chalk.yellow("[Pro]")}` : f).join(", ");
|
|
14
|
+
console.error(chalk.red(`Unknown format: "${format}". Supported: ${formatList}`));
|
|
13
15
|
process.exit(1);
|
|
14
16
|
}
|
|
17
|
+
// HTML export requires Pro tier (skip in dev/local builds)
|
|
18
|
+
if (format === "html" && !process.env.ARCHBYTE_DEV) {
|
|
19
|
+
const { loadCredentials } = await import("./auth.js");
|
|
20
|
+
const creds = loadCredentials();
|
|
21
|
+
if (!creds || creds.tier !== "premium") {
|
|
22
|
+
console.error();
|
|
23
|
+
console.error(chalk.red("HTML export requires a Pro subscription."));
|
|
24
|
+
console.error(chalk.gray("Upgrade at https://heartbyte.io/archbyte"));
|
|
25
|
+
console.error();
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
15
29
|
let output;
|
|
16
30
|
switch (format) {
|
|
17
31
|
case "mermaid":
|
|
@@ -26,6 +40,9 @@ export async function handleExport(options) {
|
|
|
26
40
|
case "dot":
|
|
27
41
|
output = exportDot(arch);
|
|
28
42
|
break;
|
|
43
|
+
case "html":
|
|
44
|
+
output = await exportHtml(arch);
|
|
45
|
+
break;
|
|
29
46
|
default:
|
|
30
47
|
output = exportMarkdown(arch);
|
|
31
48
|
break;
|
|
@@ -306,6 +323,51 @@ function exportDot(arch) {
|
|
|
306
323
|
lines.push("}");
|
|
307
324
|
return lines.join("\n");
|
|
308
325
|
}
|
|
326
|
+
/**
|
|
327
|
+
* Export architecture as a self-contained interactive HTML file.
|
|
328
|
+
* Reads the pre-built UI assets from ui/dist/ and inlines them
|
|
329
|
+
* with the architecture data injected as window.__ARCHBYTE_DATA__.
|
|
330
|
+
*/
|
|
331
|
+
async function exportHtml(arch) {
|
|
332
|
+
const fs = await import("fs");
|
|
333
|
+
const pathMod = await import("path");
|
|
334
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
335
|
+
const __dirname = pathMod.dirname(__filename);
|
|
336
|
+
const uiDist = pathMod.resolve(__dirname, "../../ui/dist");
|
|
337
|
+
if (!fs.existsSync(uiDist)) {
|
|
338
|
+
console.error(chalk.red("UI build not found at " + uiDist));
|
|
339
|
+
console.error(chalk.gray("Run: npm run build:ui"));
|
|
340
|
+
process.exit(1);
|
|
341
|
+
}
|
|
342
|
+
// Read CSS and JS assets
|
|
343
|
+
const assetsDir = pathMod.join(uiDist, "assets");
|
|
344
|
+
const assetFiles = fs.readdirSync(assetsDir);
|
|
345
|
+
let cssContent = "";
|
|
346
|
+
for (const f of assetFiles.filter((f) => f.endsWith(".css"))) {
|
|
347
|
+
cssContent += fs.readFileSync(pathMod.join(assetsDir, f), "utf-8") + "\n";
|
|
348
|
+
}
|
|
349
|
+
let jsContent = "";
|
|
350
|
+
for (const f of assetFiles.filter((f) => f.endsWith(".js"))) {
|
|
351
|
+
jsContent += fs.readFileSync(pathMod.join(assetsDir, f), "utf-8") + "\n";
|
|
352
|
+
}
|
|
353
|
+
const projectName = process.cwd().split("/").pop() || "Architecture";
|
|
354
|
+
// Build HTML from scratch (avoids fragile regex on Vite template)
|
|
355
|
+
return `<!DOCTYPE html>
|
|
356
|
+
<html lang="en" data-theme="dark">
|
|
357
|
+
<head>
|
|
358
|
+
<meta charset="UTF-8">
|
|
359
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
360
|
+
<title>${projectName} - ArchByte Architecture</title>
|
|
361
|
+
<style>${cssContent}</style>
|
|
362
|
+
</head>
|
|
363
|
+
<body>
|
|
364
|
+
<script>try{if(localStorage.getItem('archbyte-theme')==='light')document.documentElement.setAttribute('data-theme','light')}catch(e){}</script>
|
|
365
|
+
<div id="root"></div>
|
|
366
|
+
<script>window.__ARCHBYTE_DATA__ = ${JSON.stringify(arch)};</script>
|
|
367
|
+
<script type="module">${jsContent}</script>
|
|
368
|
+
</body>
|
|
369
|
+
</html>`;
|
|
370
|
+
}
|
|
309
371
|
/**
|
|
310
372
|
* Sanitize an ID for use in PlantUML diagrams.
|
|
311
373
|
*/
|
package/dist/cli/setup.js
CHANGED
|
@@ -15,6 +15,12 @@ const PROVIDERS = [
|
|
|
15
15
|
{ name: "google", label: "Google", hint: "Gemini 2.5 Pro / Flash" },
|
|
16
16
|
];
|
|
17
17
|
const PROVIDER_MODELS = {
|
|
18
|
+
"claude-sdk": [
|
|
19
|
+
{ id: "", label: "Default (recommended)", hint: "Sonnet for all agents" },
|
|
20
|
+
{ id: "opus", label: "Claude Opus 4.6", hint: "Most capable" },
|
|
21
|
+
{ id: "sonnet", label: "Claude Sonnet 4.5", hint: "Fast, great quality" },
|
|
22
|
+
{ id: "haiku", label: "Claude Haiku 4.5", hint: "Fastest, cheapest" },
|
|
23
|
+
],
|
|
18
24
|
anthropic: [
|
|
19
25
|
{ id: "", label: "Default (recommended)", hint: "Opus for all agents" },
|
|
20
26
|
{ id: "claude-opus-4-6", label: "Claude Opus 4.6", hint: "Most capable" },
|
|
@@ -209,12 +215,6 @@ export async function handleSetup() {
|
|
|
209
215
|
const hasClaude = isInPath("claude");
|
|
210
216
|
const codexDir = path.join(CONFIG_DIR, "../.codex");
|
|
211
217
|
const hasCodex = fs.existsSync(codexDir);
|
|
212
|
-
if (hasClaude || hasCodex) {
|
|
213
|
-
const tools = [hasClaude && "Claude Code", hasCodex && "Codex CLI"].filter(Boolean).join(" and ");
|
|
214
|
-
console.log(chalk.cyan(` Detected ${tools} on this machine.`));
|
|
215
|
-
console.log(chalk.white(` After setup, run `) + chalk.bold.cyan(`archbyte mcp install`) + chalk.white(` to use ArchByte from your AI tool.`));
|
|
216
|
-
console.log();
|
|
217
|
-
}
|
|
218
218
|
const config = loadConfig();
|
|
219
219
|
const profiles = getProfiles(config);
|
|
220
220
|
// Migrate legacy flat config → profiles
|
|
@@ -247,6 +247,79 @@ export async function handleSetup() {
|
|
|
247
247
|
}
|
|
248
248
|
console.log();
|
|
249
249
|
}
|
|
250
|
+
// Detect AI coding tools and offer zero-config options
|
|
251
|
+
if (hasClaude || hasCodex) {
|
|
252
|
+
const tools = [hasClaude && "Claude Code", hasCodex && "Codex CLI"].filter(Boolean).join(" and ");
|
|
253
|
+
console.log(chalk.cyan(` Detected ${tools} on this machine.\n`));
|
|
254
|
+
// Build options based on what's detected
|
|
255
|
+
const toolOptions = [];
|
|
256
|
+
if (hasClaude) {
|
|
257
|
+
toolOptions.push({
|
|
258
|
+
label: `Claude Code (SDK) ${chalk.gray("zero config — uses your Claude Code subscription")}`,
|
|
259
|
+
value: "claude-sdk",
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
if (hasCodex) {
|
|
263
|
+
toolOptions.push({
|
|
264
|
+
label: `Codex CLI ${chalk.gray("zero config — uses your Codex subscription")}`,
|
|
265
|
+
value: "codex",
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
toolOptions.push({
|
|
269
|
+
label: `Bring your own API key ${chalk.gray("Anthropic, OpenAI, or Google")}`,
|
|
270
|
+
value: "byok",
|
|
271
|
+
});
|
|
272
|
+
const toolIdx = await select("How do you want to run ArchByte?", toolOptions.map((o) => o.label));
|
|
273
|
+
const choice = toolOptions[toolIdx].value;
|
|
274
|
+
if (choice === "claude-sdk") {
|
|
275
|
+
config.provider = "claude-sdk";
|
|
276
|
+
// Model selection
|
|
277
|
+
const models = PROVIDER_MODELS["claude-sdk"];
|
|
278
|
+
const modelIdx = await select("\n Choose a model:", models.map((m) => `${m.label} ${chalk.gray(m.hint)}`));
|
|
279
|
+
const chosenModel = models[modelIdx];
|
|
280
|
+
if (chosenModel.id) {
|
|
281
|
+
config.model = chosenModel.id;
|
|
282
|
+
console.log(chalk.green(` ✓ Model: ${chosenModel.label}`));
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
delete config.model;
|
|
286
|
+
console.log(chalk.green(` ✓ Model: Sonnet (default)`));
|
|
287
|
+
}
|
|
288
|
+
config.profiles = profiles;
|
|
289
|
+
delete config.apiKey;
|
|
290
|
+
saveConfig(config);
|
|
291
|
+
const dim = chalk.gray;
|
|
292
|
+
const sep = dim(" ───");
|
|
293
|
+
console.log();
|
|
294
|
+
console.log(chalk.bold.green(" ✓ Setup complete — using Claude Code (SDK)"));
|
|
295
|
+
console.log();
|
|
296
|
+
console.log(sep);
|
|
297
|
+
console.log();
|
|
298
|
+
console.log(dim(" No API key needed. ArchByte uses your Claude Code subscription."));
|
|
299
|
+
console.log(dim(" All model calls go through Claude Code on this machine."));
|
|
300
|
+
console.log();
|
|
301
|
+
console.log(sep);
|
|
302
|
+
console.log();
|
|
303
|
+
console.log(" " + chalk.bold("Next steps"));
|
|
304
|
+
console.log();
|
|
305
|
+
console.log(" " + chalk.cyan("archbyte run") + " Analyze your codebase");
|
|
306
|
+
if (hasCodex) {
|
|
307
|
+
console.log(" " + chalk.cyan("archbyte mcp install") + " Use from Codex CLI");
|
|
308
|
+
}
|
|
309
|
+
console.log(" " + chalk.cyan("archbyte status") + " Check account and usage");
|
|
310
|
+
console.log(" " + chalk.cyan("archbyte --help") + " See all commands");
|
|
311
|
+
console.log();
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
if (choice === "codex") {
|
|
315
|
+
// TODO: Add Codex SDK provider when available
|
|
316
|
+
console.log(chalk.yellow("\n Codex SDK provider coming soon. Setting up with API key for now."));
|
|
317
|
+
console.log(chalk.gray(" In the meantime, use archbyte mcp install to run ArchByte from Codex.\n"));
|
|
318
|
+
}
|
|
319
|
+
// User chose BYOK — continue to normal provider selection below
|
|
320
|
+
if (choice === "byok")
|
|
321
|
+
console.log();
|
|
322
|
+
}
|
|
250
323
|
// Step 1: Choose provider
|
|
251
324
|
const idx = await select("Choose your model provider:", PROVIDERS.map((p) => {
|
|
252
325
|
const active = config.provider === p.name ? chalk.green(" (active)") : "";
|
package/dist/cli/ui.d.ts
CHANGED
|
@@ -30,4 +30,13 @@ export declare function progressBar(totalSteps: number): ProgressBar;
|
|
|
30
30
|
* (arrow keys, etc.) to prevent accidental confirmation.
|
|
31
31
|
*/
|
|
32
32
|
export declare function confirm(prompt: string): Promise<boolean>;
|
|
33
|
+
/**
|
|
34
|
+
* Text input prompt. Returns the entered string.
|
|
35
|
+
* Non-TTY fallback: returns empty string.
|
|
36
|
+
*
|
|
37
|
+
* @param mask - If true, replaces each character with * (for passwords).
|
|
38
|
+
*/
|
|
39
|
+
export declare function textInput(prompt: string, opts?: {
|
|
40
|
+
mask?: boolean;
|
|
41
|
+
}): Promise<string>;
|
|
33
42
|
export {};
|