archbyte 0.3.5 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -0
- package/bin/archbyte.js +26 -25
- package/dist/agents/pipeline/merger.d.ts +2 -2
- package/dist/agents/pipeline/merger.js +165 -35
- package/dist/agents/pipeline/types.d.ts +29 -1
- package/dist/agents/pipeline/types.js +0 -1
- package/dist/agents/providers/claude-sdk.d.ts +7 -0
- package/dist/agents/providers/claude-sdk.js +83 -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 +6 -2
- package/dist/agents/runtime/types.js +6 -1
- package/dist/agents/static/component-detector.js +35 -3
- package/dist/agents/static/connection-mapper.d.ts +1 -1
- package/dist/agents/static/connection-mapper.js +74 -1
- package/dist/agents/static/index.js +5 -2
- package/dist/agents/static/types.d.ts +26 -0
- package/dist/cli/analyze.js +65 -18
- package/dist/cli/arch-diff.d.ts +38 -0
- package/dist/cli/arch-diff.js +61 -0
- 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/patrol.d.ts +5 -3
- package/dist/cli/patrol.js +417 -65
- package/dist/cli/setup.js +76 -8
- package/dist/cli/shared.d.ts +11 -0
- package/dist/cli/shared.js +61 -0
- package/dist/cli/ui.d.ts +9 -0
- package/dist/cli/ui.js +59 -5
- package/dist/cli/validate.d.ts +0 -1
- package/dist/cli/validate.js +0 -16
- package/dist/server/src/index.js +593 -19
- package/package.json +4 -1
- package/templates/archbyte.yaml +8 -0
- package/ui/dist/assets/index-DDCNauh7.css +1 -0
- package/ui/dist/assets/index-DO4t5Xu1.js +72 -0
- package/ui/dist/index.html +2 -2
- package/dist/cli/mcp-server.d.ts +0 -1
- package/dist/cli/mcp-server.js +0 -443
- package/dist/cli/mcp.d.ts +0 -1
- package/dist/cli/mcp.js +0 -98
- package/ui/dist/assets/index-0_XpUUZQ.css +0 -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/patrol.d.ts
CHANGED
|
@@ -4,13 +4,15 @@ interface PatrolOptions {
|
|
|
4
4
|
interval?: string;
|
|
5
5
|
onViolation?: string;
|
|
6
6
|
daemon?: boolean;
|
|
7
|
+
once?: boolean;
|
|
8
|
+
watch?: boolean;
|
|
7
9
|
history?: boolean;
|
|
8
|
-
rescan?: boolean;
|
|
9
10
|
}
|
|
10
11
|
/**
|
|
11
12
|
* Run the architecture patrol daemon.
|
|
12
|
-
*
|
|
13
|
-
*
|
|
13
|
+
* Each cycle: snapshot → analyze (incremental) → generate → validate → diff
|
|
14
|
+
*
|
|
15
|
+
* --once: run a single cycle then exit (used by UI "Run Now")
|
|
14
16
|
*/
|
|
15
17
|
export declare function handlePatrol(options: PatrolOptions): Promise<void>;
|
|
16
18
|
export {};
|