archbyte 0.1.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.
Files changed (142) hide show
  1. package/README.md +282 -0
  2. package/bin/archbyte.js +213 -0
  3. package/dist/agents/core/component-detector.d.ts +2 -0
  4. package/dist/agents/core/component-detector.js +57 -0
  5. package/dist/agents/core/connection-mapper.d.ts +2 -0
  6. package/dist/agents/core/connection-mapper.js +77 -0
  7. package/dist/agents/core/doc-parser.d.ts +2 -0
  8. package/dist/agents/core/doc-parser.js +64 -0
  9. package/dist/agents/core/env-detector.d.ts +2 -0
  10. package/dist/agents/core/env-detector.js +51 -0
  11. package/dist/agents/core/event-detector.d.ts +2 -0
  12. package/dist/agents/core/event-detector.js +59 -0
  13. package/dist/agents/core/infra-analyzer.d.ts +2 -0
  14. package/dist/agents/core/infra-analyzer.js +72 -0
  15. package/dist/agents/core/structure-scanner.d.ts +2 -0
  16. package/dist/agents/core/structure-scanner.js +55 -0
  17. package/dist/agents/core/validator.d.ts +2 -0
  18. package/dist/agents/core/validator.js +74 -0
  19. package/dist/agents/index.d.ts +24 -0
  20. package/dist/agents/index.js +73 -0
  21. package/dist/agents/llm/index.d.ts +8 -0
  22. package/dist/agents/llm/index.js +185 -0
  23. package/dist/agents/llm/prompt-builder.d.ts +3 -0
  24. package/dist/agents/llm/prompt-builder.js +251 -0
  25. package/dist/agents/llm/response-parser.d.ts +6 -0
  26. package/dist/agents/llm/response-parser.js +174 -0
  27. package/dist/agents/llm/types.d.ts +31 -0
  28. package/dist/agents/llm/types.js +2 -0
  29. package/dist/agents/pipeline/agents/component-identifier.d.ts +3 -0
  30. package/dist/agents/pipeline/agents/component-identifier.js +102 -0
  31. package/dist/agents/pipeline/agents/connection-mapper.d.ts +3 -0
  32. package/dist/agents/pipeline/agents/connection-mapper.js +126 -0
  33. package/dist/agents/pipeline/agents/flow-detector.d.ts +3 -0
  34. package/dist/agents/pipeline/agents/flow-detector.js +101 -0
  35. package/dist/agents/pipeline/agents/service-describer.d.ts +3 -0
  36. package/dist/agents/pipeline/agents/service-describer.js +100 -0
  37. package/dist/agents/pipeline/agents/validator.d.ts +3 -0
  38. package/dist/agents/pipeline/agents/validator.js +102 -0
  39. package/dist/agents/pipeline/index.d.ts +13 -0
  40. package/dist/agents/pipeline/index.js +128 -0
  41. package/dist/agents/pipeline/merger.d.ts +7 -0
  42. package/dist/agents/pipeline/merger.js +212 -0
  43. package/dist/agents/pipeline/response-parser.d.ts +5 -0
  44. package/dist/agents/pipeline/response-parser.js +43 -0
  45. package/dist/agents/pipeline/types.d.ts +92 -0
  46. package/dist/agents/pipeline/types.js +3 -0
  47. package/dist/agents/prompt-data.d.ts +1 -0
  48. package/dist/agents/prompt-data.js +15 -0
  49. package/dist/agents/prompts-encode.d.ts +9 -0
  50. package/dist/agents/prompts-encode.js +26 -0
  51. package/dist/agents/prompts.d.ts +12 -0
  52. package/dist/agents/prompts.js +30 -0
  53. package/dist/agents/providers/anthropic.d.ts +10 -0
  54. package/dist/agents/providers/anthropic.js +117 -0
  55. package/dist/agents/providers/google.d.ts +10 -0
  56. package/dist/agents/providers/google.js +136 -0
  57. package/dist/agents/providers/ollama.d.ts +9 -0
  58. package/dist/agents/providers/ollama.js +162 -0
  59. package/dist/agents/providers/openai.d.ts +9 -0
  60. package/dist/agents/providers/openai.js +142 -0
  61. package/dist/agents/providers/router.d.ts +7 -0
  62. package/dist/agents/providers/router.js +55 -0
  63. package/dist/agents/runtime/orchestrator.d.ts +34 -0
  64. package/dist/agents/runtime/orchestrator.js +193 -0
  65. package/dist/agents/runtime/registry.d.ts +23 -0
  66. package/dist/agents/runtime/registry.js +56 -0
  67. package/dist/agents/runtime/types.d.ts +117 -0
  68. package/dist/agents/runtime/types.js +29 -0
  69. package/dist/agents/static/code-sampler.d.ts +3 -0
  70. package/dist/agents/static/code-sampler.js +153 -0
  71. package/dist/agents/static/component-detector.d.ts +3 -0
  72. package/dist/agents/static/component-detector.js +404 -0
  73. package/dist/agents/static/connection-mapper.d.ts +3 -0
  74. package/dist/agents/static/connection-mapper.js +280 -0
  75. package/dist/agents/static/doc-parser.d.ts +3 -0
  76. package/dist/agents/static/doc-parser.js +358 -0
  77. package/dist/agents/static/env-detector.d.ts +3 -0
  78. package/dist/agents/static/env-detector.js +73 -0
  79. package/dist/agents/static/event-detector.d.ts +3 -0
  80. package/dist/agents/static/event-detector.js +70 -0
  81. package/dist/agents/static/file-tree-collector.d.ts +3 -0
  82. package/dist/agents/static/file-tree-collector.js +51 -0
  83. package/dist/agents/static/index.d.ts +19 -0
  84. package/dist/agents/static/index.js +307 -0
  85. package/dist/agents/static/infra-analyzer.d.ts +3 -0
  86. package/dist/agents/static/infra-analyzer.js +208 -0
  87. package/dist/agents/static/structure-scanner.d.ts +3 -0
  88. package/dist/agents/static/structure-scanner.js +195 -0
  89. package/dist/agents/static/types.d.ts +165 -0
  90. package/dist/agents/static/types.js +2 -0
  91. package/dist/agents/static/utils.d.ts +21 -0
  92. package/dist/agents/static/utils.js +146 -0
  93. package/dist/agents/static/validator.d.ts +2 -0
  94. package/dist/agents/static/validator.js +75 -0
  95. package/dist/agents/tools/claude-code.d.ts +38 -0
  96. package/dist/agents/tools/claude-code.js +129 -0
  97. package/dist/agents/tools/local-fs.d.ts +12 -0
  98. package/dist/agents/tools/local-fs.js +112 -0
  99. package/dist/agents/tools/tool-definitions.d.ts +6 -0
  100. package/dist/agents/tools/tool-definitions.js +66 -0
  101. package/dist/cli/analyze.d.ts +27 -0
  102. package/dist/cli/analyze.js +586 -0
  103. package/dist/cli/auth.d.ts +46 -0
  104. package/dist/cli/auth.js +397 -0
  105. package/dist/cli/config.d.ts +11 -0
  106. package/dist/cli/config.js +177 -0
  107. package/dist/cli/diff.d.ts +10 -0
  108. package/dist/cli/diff.js +144 -0
  109. package/dist/cli/export.d.ts +10 -0
  110. package/dist/cli/export.js +321 -0
  111. package/dist/cli/gate.d.ts +13 -0
  112. package/dist/cli/gate.js +131 -0
  113. package/dist/cli/generate.d.ts +10 -0
  114. package/dist/cli/generate.js +213 -0
  115. package/dist/cli/license-gate.d.ts +27 -0
  116. package/dist/cli/license-gate.js +121 -0
  117. package/dist/cli/patrol.d.ts +15 -0
  118. package/dist/cli/patrol.js +212 -0
  119. package/dist/cli/run.d.ts +11 -0
  120. package/dist/cli/run.js +24 -0
  121. package/dist/cli/serve.d.ts +9 -0
  122. package/dist/cli/serve.js +65 -0
  123. package/dist/cli/setup.d.ts +1 -0
  124. package/dist/cli/setup.js +233 -0
  125. package/dist/cli/shared.d.ts +68 -0
  126. package/dist/cli/shared.js +275 -0
  127. package/dist/cli/stats.d.ts +9 -0
  128. package/dist/cli/stats.js +158 -0
  129. package/dist/cli/ui.d.ts +18 -0
  130. package/dist/cli/ui.js +144 -0
  131. package/dist/cli/validate.d.ts +54 -0
  132. package/dist/cli/validate.js +315 -0
  133. package/dist/cli/workflow.d.ts +10 -0
  134. package/dist/cli/workflow.js +594 -0
  135. package/dist/server/src/generator/index.d.ts +123 -0
  136. package/dist/server/src/generator/index.js +254 -0
  137. package/dist/server/src/index.d.ts +8 -0
  138. package/dist/server/src/index.js +1311 -0
  139. package/package.json +62 -0
  140. package/ui/dist/assets/index-B66Til39.js +70 -0
  141. package/ui/dist/assets/index-BE2OWbzu.css +1 -0
  142. package/ui/dist/index.html +14 -0
@@ -0,0 +1,397 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import * as http from "http";
4
+ import { spawn } from "child_process";
5
+ import chalk from "chalk";
6
+ const CONFIG_DIR = path.join(process.env.HOME ?? process.env.USERPROFILE ?? ".", ".archbyte");
7
+ const CREDENTIALS_PATH = path.join(CONFIG_DIR, "credentials.json");
8
+ const API_BASE = process.env.ARCHBYTE_API_URL ?? "https://api.heartbyte.io";
9
+ const CLI_CALLBACK_PORT = 19274;
10
+ export async function handleLogin(provider) {
11
+ console.log();
12
+ console.log(chalk.bold.cyan("ArchByte Login"));
13
+ console.log();
14
+ const existing = loadCredentials();
15
+ if (existing && !isExpired(existing)) {
16
+ // Refresh tier from server in case it changed (e.g. upgraded via dashboard)
17
+ try {
18
+ const res = await fetch(`${API_BASE}/api/v1/me`, {
19
+ headers: { Authorization: `Bearer ${existing.token}` },
20
+ });
21
+ if (res.ok) {
22
+ const { user } = (await res.json());
23
+ if (user.tier !== existing.tier) {
24
+ existing.tier = user.tier;
25
+ saveCredentials(existing);
26
+ }
27
+ // Cache server-verified tier
28
+ cacheVerifiedTier(user.tier === "premium" ? "premium" : "free", existing.email);
29
+ }
30
+ }
31
+ catch {
32
+ // Offline — use cached tier
33
+ }
34
+ console.log(chalk.green(`Already logged in as ${chalk.bold(existing.email)} (${existing.tier} tier)`));
35
+ console.log(chalk.gray("Run `archbyte logout` to sign out."));
36
+ return;
37
+ }
38
+ const selectedProvider = provider ?? "github";
39
+ console.log(chalk.gray(`Opening browser for ${selectedProvider} authentication...`));
40
+ console.log();
41
+ try {
42
+ const credentials = await startOAuthFlow(selectedProvider);
43
+ saveCredentials(credentials);
44
+ // Cache server-verified tier from fresh login
45
+ cacheVerifiedTier(credentials.tier === "premium" ? "premium" : "free", credentials.email);
46
+ console.log();
47
+ console.log(chalk.green(`Logged in as ${chalk.bold(credentials.email)} (${credentials.tier} tier)`));
48
+ console.log(chalk.gray(`Credentials saved to ${CREDENTIALS_PATH}`));
49
+ }
50
+ catch (err) {
51
+ console.error(chalk.red(`Login failed: ${err instanceof Error ? err.message : "Unknown error"}`));
52
+ console.log();
53
+ console.log(chalk.gray("You can also log in manually:"));
54
+ console.log(chalk.gray(` 1. Visit ${API_BASE.replace("api.", "")}/auth`));
55
+ console.log(chalk.gray(" 2. Copy your token"));
56
+ console.log(chalk.gray(" 3. Run: archbyte login --token <your-token>"));
57
+ process.exit(1);
58
+ }
59
+ }
60
+ export async function handleLoginWithToken(token) {
61
+ console.log();
62
+ console.log(chalk.bold.cyan("ArchByte Login (token)"));
63
+ console.log();
64
+ try {
65
+ const res = await fetch(`${API_BASE}/api/v1/me`, {
66
+ headers: { Authorization: `Bearer ${token}` },
67
+ });
68
+ if (!res.ok) {
69
+ console.error(chalk.red("Invalid token."));
70
+ process.exit(1);
71
+ }
72
+ const { user } = (await res.json());
73
+ const payload = parseJWTPayload(token);
74
+ saveCredentials({
75
+ token,
76
+ email: user.email,
77
+ tier: user.tier,
78
+ expiresAt: payload?.exp
79
+ ? new Date(payload.exp * 1000).toISOString()
80
+ : new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
81
+ });
82
+ // Cache server-verified tier
83
+ cacheVerifiedTier(user.tier === "premium" ? "premium" : "free", user.email);
84
+ console.log(chalk.green(`Logged in as ${chalk.bold(user.email)} (${user.tier} tier)`));
85
+ }
86
+ catch (err) {
87
+ console.error(chalk.red(`Login failed: ${err instanceof Error ? err.message : "Unknown error"}`));
88
+ process.exit(1);
89
+ }
90
+ }
91
+ // === Logout ===
92
+ export async function handleLogout() {
93
+ console.log();
94
+ const creds = loadCredentials();
95
+ if (!creds) {
96
+ console.log(chalk.gray("Not logged in."));
97
+ return;
98
+ }
99
+ try {
100
+ fs.unlinkSync(CREDENTIALS_PATH);
101
+ }
102
+ catch {
103
+ // Ignore
104
+ }
105
+ // Clean up tier cache and offline action tracker
106
+ try {
107
+ fs.unlinkSync(TIER_CACHE_PATH);
108
+ }
109
+ catch { /* Ignore */ }
110
+ resetOfflineActions();
111
+ console.log(chalk.green(`Logged out (was ${creds.email}).`));
112
+ }
113
+ // === Status ===
114
+ export async function handleStatus() {
115
+ console.log();
116
+ console.log(chalk.bold.cyan("ArchByte Account"));
117
+ console.log();
118
+ const creds = loadCredentials();
119
+ if (!creds) {
120
+ console.log(chalk.yellow("Not logged in. Run `archbyte login` to sign in."));
121
+ return;
122
+ }
123
+ if (isExpired(creds)) {
124
+ console.log(chalk.yellow("Session expired. Run `archbyte login` to refresh."));
125
+ return;
126
+ }
127
+ console.log(` ${chalk.bold("Email")}: ${creds.email}`);
128
+ console.log(` ${chalk.bold("Tier")}: ${creds.tier === "premium" ? chalk.green("Pro") : "Basic"}`);
129
+ console.log(` ${chalk.bold("Expires")}: ${new Date(creds.expiresAt).toLocaleDateString()}`);
130
+ // Fetch live usage from server
131
+ try {
132
+ const res = await fetch(`${API_BASE}/api/v1/scans/count`, {
133
+ headers: { Authorization: `Bearer ${creds.token}` },
134
+ });
135
+ if (res.ok) {
136
+ const data = (await res.json());
137
+ console.log(` ${chalk.bold("Scans")}: ${data.scansUsed}${data.scansAllowed === -1 ? " (unlimited)" : `/${data.scansAllowed}`}`);
138
+ }
139
+ }
140
+ catch {
141
+ // Offline — show cached info only
142
+ }
143
+ console.log();
144
+ }
145
+ // === Credential Helpers (exported for license gate) ===
146
+ export function loadCredentials() {
147
+ try {
148
+ if (fs.existsSync(CREDENTIALS_PATH)) {
149
+ const data = JSON.parse(fs.readFileSync(CREDENTIALS_PATH, "utf-8"));
150
+ // Validate structure — reject malformed credentials
151
+ if (typeof data?.token !== "string" ||
152
+ typeof data?.email !== "string" ||
153
+ typeof data?.tier !== "string" ||
154
+ typeof data?.expiresAt !== "string") {
155
+ return null;
156
+ }
157
+ return data;
158
+ }
159
+ }
160
+ catch {
161
+ // Ignore — corrupt file treated as no credentials
162
+ }
163
+ return null;
164
+ }
165
+ function saveCredentials(creds) {
166
+ if (!fs.existsSync(CONFIG_DIR)) {
167
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
168
+ }
169
+ fs.writeFileSync(CREDENTIALS_PATH, JSON.stringify(creds, null, 2), "utf-8");
170
+ // Restrict permissions (owner-only read/write)
171
+ try {
172
+ fs.chmodSync(CREDENTIALS_PATH, 0o600);
173
+ }
174
+ catch {
175
+ // Windows doesn't support chmod
176
+ }
177
+ }
178
+ function isExpired(creds) {
179
+ return new Date(creds.expiresAt) < new Date();
180
+ }
181
+ function parseJWTPayload(token) {
182
+ try {
183
+ const parts = token.split(".");
184
+ if (parts.length !== 3)
185
+ return null;
186
+ return JSON.parse(Buffer.from(parts[1], "base64url").toString());
187
+ }
188
+ catch {
189
+ return null;
190
+ }
191
+ }
192
+ /**
193
+ * Get the tier from the JWT token payload. NEVER falls back to the
194
+ * editable credentials file — that would allow trivial tier bypass
195
+ * by editing ~/.archbyte/credentials.json.
196
+ *
197
+ * WARNING: This reads from an UNVERIFIED JWT payload. The CLI cannot
198
+ * verify HMAC signatures (no shared secret). Use getVerifiedTier()
199
+ * instead, which reads from the server-verified tier cache.
200
+ */
201
+ export function getTierFromToken() {
202
+ const creds = loadCredentials();
203
+ if (!creds?.token)
204
+ return "free";
205
+ const payload = parseJWTPayload(creds.token);
206
+ return payload?.tier === "premium" ? "premium" : "free";
207
+ }
208
+ // === Server-Verified Tier Cache ===
209
+ // The CLI cannot verify JWT signatures (HMAC requires the server secret).
210
+ // Instead, we cache the tier returned by the server after successful
211
+ // verification. This cache is the ONLY trusted source of tier info locally.
212
+ const TIER_CACHE_PATH = path.join(CONFIG_DIR, "tier-cache.json");
213
+ const TIER_CACHE_MAX_AGE_MS = 60 * 60 * 1000; // 1 hour
214
+ /**
215
+ * Cache the server-verified tier. Called by license-gate after a
216
+ * successful server response. Uses email as the identifier since
217
+ * the license-gate has access to it from credentials.
218
+ */
219
+ export function cacheVerifiedTier(tier, email) {
220
+ const data = { tier, verifiedAt: Date.now(), email };
221
+ try {
222
+ if (!fs.existsSync(CONFIG_DIR)) {
223
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
224
+ }
225
+ fs.writeFileSync(TIER_CACHE_PATH, JSON.stringify(data), "utf-8");
226
+ try {
227
+ fs.chmodSync(TIER_CACHE_PATH, 0o600);
228
+ }
229
+ catch { /* Windows */ }
230
+ }
231
+ catch {
232
+ // Non-critical — offline fallback just won't work
233
+ }
234
+ }
235
+ /**
236
+ * Get the tier from the server-verified cache. Returns "free" if cache
237
+ * is missing, stale (> 1 hour), or belongs to a different user.
238
+ */
239
+ export function getVerifiedTier() {
240
+ try {
241
+ if (!fs.existsSync(TIER_CACHE_PATH))
242
+ return "free";
243
+ const data = JSON.parse(fs.readFileSync(TIER_CACHE_PATH, "utf-8"));
244
+ // Validate cache structure
245
+ if (!data || typeof data.verifiedAt !== "number" || typeof data.tier !== "string") {
246
+ return "free";
247
+ }
248
+ // Check staleness
249
+ if (Date.now() - data.verifiedAt > TIER_CACHE_MAX_AGE_MS)
250
+ return "free";
251
+ // Check user match (prevent tier cache from a different account)
252
+ const creds = loadCredentials();
253
+ if (creds?.email && data.email !== creds.email)
254
+ return "free";
255
+ return data.tier === "premium" ? "premium" : "free";
256
+ }
257
+ catch {
258
+ return "free";
259
+ }
260
+ }
261
+ // === Offline Action Tracking ===
262
+ const OFFLINE_ACTIONS_PATH = path.join(CONFIG_DIR, "offline-actions.json");
263
+ const OFFLINE_MAX_PREMIUM = 3; // Premium users: 3 offline actions within cache window
264
+ const OFFLINE_MAX_FREE = 0; // Free users: 0 offline actions (must verify online)
265
+ /**
266
+ * Check if an offline action is allowed. Returns true if within limits.
267
+ * Increments the counter when allowed.
268
+ */
269
+ export function checkOfflineAction() {
270
+ const tier = getVerifiedTier();
271
+ const maxActions = tier === "premium" ? OFFLINE_MAX_PREMIUM : OFFLINE_MAX_FREE;
272
+ if (maxActions === 0) {
273
+ return {
274
+ allowed: false,
275
+ reason: "Server verification required. Check your internet connection and try again.",
276
+ };
277
+ }
278
+ try {
279
+ let data = { count: 0, lastReset: Date.now() };
280
+ if (fs.existsSync(OFFLINE_ACTIONS_PATH)) {
281
+ data = JSON.parse(fs.readFileSync(OFFLINE_ACTIONS_PATH, "utf-8"));
282
+ }
283
+ // Reset counter if cache window expired
284
+ if (Date.now() - data.lastReset > TIER_CACHE_MAX_AGE_MS) {
285
+ data = { count: 0, lastReset: Date.now() };
286
+ }
287
+ if (data.count >= maxActions) {
288
+ return {
289
+ allowed: false,
290
+ reason: `Offline action limit reached (${maxActions}/${maxActions}). Reconnect to the license server to continue.`,
291
+ };
292
+ }
293
+ // Increment and save
294
+ data.count++;
295
+ fs.writeFileSync(OFFLINE_ACTIONS_PATH, JSON.stringify(data), "utf-8");
296
+ try {
297
+ fs.chmodSync(OFFLINE_ACTIONS_PATH, 0o600);
298
+ }
299
+ catch { /* Windows */ }
300
+ return { allowed: true };
301
+ }
302
+ catch {
303
+ return { allowed: false, reason: "Could not verify offline action limit." };
304
+ }
305
+ }
306
+ /**
307
+ * Reset offline action counter. Called after successful server verification.
308
+ */
309
+ export function resetOfflineActions() {
310
+ try {
311
+ if (fs.existsSync(OFFLINE_ACTIONS_PATH)) {
312
+ fs.unlinkSync(OFFLINE_ACTIONS_PATH);
313
+ }
314
+ }
315
+ catch {
316
+ // Non-critical
317
+ }
318
+ }
319
+ // === OAuth Flow ===
320
+ function startOAuthFlow(provider = "github") {
321
+ return new Promise((resolve, reject) => {
322
+ const timeout = setTimeout(() => {
323
+ server.close();
324
+ reject(new Error("Login timed out (60s). Try again or use --token."));
325
+ }, 60000);
326
+ const server = http.createServer(async (req, res) => {
327
+ const url = new URL(req.url ?? "/", `http://localhost:${CLI_CALLBACK_PORT}`);
328
+ if (url.pathname === "/callback") {
329
+ const token = url.searchParams.get("token");
330
+ if (!token) {
331
+ res.writeHead(400, { "Content-Type": "text/html" });
332
+ res.end("<h1>Login failed</h1><p>No token received. Close this window and try again.</p>");
333
+ return;
334
+ }
335
+ res.writeHead(200, { "Content-Type": "text/html" });
336
+ res.end("<h1>Login successful!</h1><p>You can close this window and return to your terminal.</p>");
337
+ clearTimeout(timeout);
338
+ server.close();
339
+ // Fetch user info with the token
340
+ try {
341
+ const meRes = await fetch(`${API_BASE}/api/v1/me`, {
342
+ headers: { Authorization: `Bearer ${token}` },
343
+ });
344
+ if (!meRes.ok) {
345
+ reject(new Error("Failed to fetch user info"));
346
+ return;
347
+ }
348
+ const { user } = (await meRes.json());
349
+ const payload = parseJWTPayload(token);
350
+ resolve({
351
+ token,
352
+ email: user.email,
353
+ tier: user.tier,
354
+ expiresAt: payload?.exp
355
+ ? new Date(payload.exp * 1000).toISOString()
356
+ : new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
357
+ });
358
+ }
359
+ catch (err) {
360
+ reject(err);
361
+ }
362
+ }
363
+ });
364
+ server.listen(CLI_CALLBACK_PORT, "127.0.0.1", () => {
365
+ const authUrl = `${API_BASE}/api/v1/auth/${provider}?cli=1`;
366
+ // Open browser without shell interpolation (prevents command injection)
367
+ const opener = process.platform === "darwin"
368
+ ? "open"
369
+ : process.platform === "win32"
370
+ ? "start"
371
+ : "xdg-open";
372
+ const child = spawn(opener, [authUrl], { stdio: "ignore" });
373
+ child.on("error", () => {
374
+ console.log(chalk.yellow("Could not open browser automatically."));
375
+ console.log(chalk.gray(`Open this URL manually: ${authUrl}`));
376
+ });
377
+ child.on("close", (code) => {
378
+ if (code === 0) {
379
+ console.log(chalk.gray("Browser opened. Waiting for authentication..."));
380
+ }
381
+ else {
382
+ console.log(chalk.yellow("Could not open browser automatically."));
383
+ console.log(chalk.gray(`Open this URL manually: ${authUrl}`));
384
+ }
385
+ });
386
+ });
387
+ server.on("error", (err) => {
388
+ clearTimeout(timeout);
389
+ if (err.code === "EADDRINUSE") {
390
+ reject(new Error(`Port ${CLI_CALLBACK_PORT} is in use. Close other archbyte instances and try again.`));
391
+ }
392
+ else {
393
+ reject(err);
394
+ }
395
+ });
396
+ });
397
+ }
@@ -0,0 +1,11 @@
1
+ import type { ArchByteConfig } from "../agents/runtime/types.js";
2
+ interface ConfigOptions {
3
+ args: string[];
4
+ }
5
+ export declare function handleConfig(options: ConfigOptions): Promise<void>;
6
+ /**
7
+ * Resolve the full ArchByteConfig from config file + env vars.
8
+ * Env vars override config file.
9
+ */
10
+ export declare function resolveConfig(): ArchByteConfig | null;
11
+ export {};
@@ -0,0 +1,177 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import chalk from "chalk";
4
+ const CONFIG_DIR = path.join(process.env.HOME ?? process.env.USERPROFILE ?? ".", ".archbyte");
5
+ const CONFIG_PATH = path.join(CONFIG_DIR, "config.json");
6
+ const VALID_PROVIDERS = ["anthropic", "openai", "google", "ollama"];
7
+ export async function handleConfig(options) {
8
+ const [action, key, value] = options.args;
9
+ if (!action || action === "show") {
10
+ showConfig();
11
+ return;
12
+ }
13
+ if (action === "set") {
14
+ if (!key || !value) {
15
+ console.error(chalk.red("Usage: archbyte config set <key> <value>"));
16
+ console.error(chalk.gray(" Keys: provider, api-key, ollama-url"));
17
+ process.exit(1);
18
+ }
19
+ setConfig(key, value);
20
+ return;
21
+ }
22
+ if (action === "get") {
23
+ if (!key) {
24
+ console.error(chalk.red("Usage: archbyte config get <key>"));
25
+ process.exit(1);
26
+ }
27
+ getConfig(key);
28
+ return;
29
+ }
30
+ if (action === "path") {
31
+ console.log(CONFIG_PATH);
32
+ return;
33
+ }
34
+ console.error(chalk.red(`Unknown action: ${action}`));
35
+ console.error(chalk.gray(" archbyte config show — show current config"));
36
+ console.error(chalk.gray(" archbyte config set <k> <v> — set a config value"));
37
+ console.error(chalk.gray(" archbyte config get <k> — get a config value"));
38
+ console.error(chalk.gray(" archbyte config path — show config file path"));
39
+ process.exit(1);
40
+ }
41
+ function loadConfig() {
42
+ try {
43
+ if (fs.existsSync(CONFIG_PATH)) {
44
+ return JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8"));
45
+ }
46
+ }
47
+ catch {
48
+ // Ignore
49
+ }
50
+ return {};
51
+ }
52
+ function saveConfig(config) {
53
+ if (!fs.existsSync(CONFIG_DIR)) {
54
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
55
+ }
56
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
57
+ }
58
+ function showConfig() {
59
+ const config = loadConfig();
60
+ console.log();
61
+ console.log(chalk.bold.cyan("ArchByte Configuration"));
62
+ console.log(chalk.gray(`Config file: ${CONFIG_PATH}`));
63
+ console.log();
64
+ if (!config.provider && !config.apiKey) {
65
+ console.log(chalk.yellow("No configuration found. Set up with:"));
66
+ console.log(chalk.gray(" archbyte config set provider anthropic"));
67
+ console.log(chalk.gray(" archbyte config set api-key sk-ant-..."));
68
+ console.log();
69
+ console.log(chalk.gray("Or use environment variables:"));
70
+ console.log(chalk.gray(" export ARCHBYTE_PROVIDER=anthropic"));
71
+ console.log(chalk.gray(" export ARCHBYTE_API_KEY=sk-ant-..."));
72
+ return;
73
+ }
74
+ console.log(` ${chalk.bold("provider")}: ${config.provider ?? chalk.gray("not set")}`);
75
+ console.log(` ${chalk.bold("api-key")}: ${config.apiKey ? maskKey(config.apiKey) : chalk.gray("not set")}`);
76
+ if (config.ollamaBaseUrl) {
77
+ console.log(` ${chalk.bold("ollama-url")}: ${config.ollamaBaseUrl}`);
78
+ }
79
+ console.log();
80
+ }
81
+ function setConfig(key, value) {
82
+ const config = loadConfig();
83
+ switch (key) {
84
+ case "provider": {
85
+ if (!VALID_PROVIDERS.includes(value)) {
86
+ console.error(chalk.red(`Invalid provider: ${value}. Must be: ${VALID_PROVIDERS.join(", ")}`));
87
+ process.exit(1);
88
+ }
89
+ config.provider = value;
90
+ break;
91
+ }
92
+ case "api-key":
93
+ case "apiKey":
94
+ case "key":
95
+ config.apiKey = value;
96
+ break;
97
+ case "ollama-url":
98
+ case "ollamaUrl":
99
+ config.ollamaBaseUrl = value;
100
+ break;
101
+ default:
102
+ console.error(chalk.red(`Unknown config key: ${key}`));
103
+ console.error(chalk.gray(" Valid keys: provider, api-key, ollama-url"));
104
+ process.exit(1);
105
+ }
106
+ saveConfig(config);
107
+ console.log(chalk.green(`Set ${key} = ${key.includes("key") ? maskKey(value) : value}`));
108
+ }
109
+ function getConfig(key) {
110
+ const config = loadConfig();
111
+ switch (key) {
112
+ case "provider":
113
+ console.log(config.provider ?? "");
114
+ break;
115
+ case "api-key":
116
+ case "apiKey":
117
+ case "key":
118
+ console.log(config.apiKey ?? "");
119
+ break;
120
+ case "ollama-url":
121
+ case "ollamaUrl":
122
+ console.log(config.ollamaBaseUrl ?? "");
123
+ break;
124
+ default:
125
+ console.error(chalk.red(`Unknown config key: ${key}`));
126
+ process.exit(1);
127
+ }
128
+ }
129
+ function maskKey(key) {
130
+ if (key.length <= 8)
131
+ return "****";
132
+ return key.slice(0, 6) + "..." + key.slice(-4);
133
+ }
134
+ /**
135
+ * Resolve the full ArchByteConfig from config file + env vars.
136
+ * Env vars override config file.
137
+ */
138
+ export function resolveConfig() {
139
+ const config = loadConfig();
140
+ const provider = process.env.ARCHBYTE_PROVIDER ?? config.provider;
141
+ const apiKey = process.env.ARCHBYTE_API_KEY ?? config.apiKey;
142
+ // Auto-detect from known env vars if nothing explicit
143
+ if (!provider && !apiKey) {
144
+ if (process.env.ANTHROPIC_API_KEY) {
145
+ return { provider: "anthropic", apiKey: process.env.ANTHROPIC_API_KEY };
146
+ }
147
+ if (process.env.OPENAI_API_KEY) {
148
+ return { provider: "openai", apiKey: process.env.OPENAI_API_KEY };
149
+ }
150
+ if (process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY) {
151
+ return {
152
+ provider: "google",
153
+ apiKey: process.env.GOOGLE_API_KEY ?? process.env.GEMINI_API_KEY ?? "",
154
+ };
155
+ }
156
+ return null;
157
+ }
158
+ if (!provider)
159
+ return null;
160
+ // Ollama doesn't need an API key
161
+ if (provider === "ollama") {
162
+ return {
163
+ provider: "ollama",
164
+ apiKey: "",
165
+ ollamaBaseUrl: process.env.OLLAMA_BASE_URL ??
166
+ config.ollamaBaseUrl ??
167
+ "http://localhost:11434",
168
+ };
169
+ }
170
+ if (!apiKey)
171
+ return null;
172
+ return {
173
+ provider,
174
+ apiKey,
175
+ ollamaBaseUrl: config.ollamaBaseUrl,
176
+ };
177
+ }
@@ -0,0 +1,10 @@
1
+ interface DiffOptions {
2
+ baseline: string;
3
+ current?: string;
4
+ config?: string;
5
+ }
6
+ /**
7
+ * Compare two architecture snapshots and show drift report.
8
+ */
9
+ export declare function handleDiff(options: DiffOptions): Promise<void>;
10
+ export {};