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.
Files changed (45) hide show
  1. package/README.md +42 -0
  2. package/bin/archbyte.js +26 -25
  3. package/dist/agents/pipeline/merger.d.ts +2 -2
  4. package/dist/agents/pipeline/merger.js +165 -35
  5. package/dist/agents/pipeline/types.d.ts +29 -1
  6. package/dist/agents/pipeline/types.js +0 -1
  7. package/dist/agents/providers/claude-sdk.d.ts +7 -0
  8. package/dist/agents/providers/claude-sdk.js +83 -0
  9. package/dist/agents/providers/router.d.ts +5 -0
  10. package/dist/agents/providers/router.js +23 -1
  11. package/dist/agents/runtime/types.d.ts +6 -2
  12. package/dist/agents/runtime/types.js +6 -1
  13. package/dist/agents/static/component-detector.js +35 -3
  14. package/dist/agents/static/connection-mapper.d.ts +1 -1
  15. package/dist/agents/static/connection-mapper.js +74 -1
  16. package/dist/agents/static/index.js +5 -2
  17. package/dist/agents/static/types.d.ts +26 -0
  18. package/dist/cli/analyze.js +65 -18
  19. package/dist/cli/arch-diff.d.ts +38 -0
  20. package/dist/cli/arch-diff.js +61 -0
  21. package/dist/cli/auth.d.ts +8 -2
  22. package/dist/cli/auth.js +241 -31
  23. package/dist/cli/config.js +31 -5
  24. package/dist/cli/export.js +64 -2
  25. package/dist/cli/patrol.d.ts +5 -3
  26. package/dist/cli/patrol.js +417 -65
  27. package/dist/cli/setup.js +76 -8
  28. package/dist/cli/shared.d.ts +11 -0
  29. package/dist/cli/shared.js +61 -0
  30. package/dist/cli/ui.d.ts +9 -0
  31. package/dist/cli/ui.js +59 -5
  32. package/dist/cli/validate.d.ts +0 -1
  33. package/dist/cli/validate.js +0 -16
  34. package/dist/server/src/index.js +593 -19
  35. package/package.json +4 -1
  36. package/templates/archbyte.yaml +8 -0
  37. package/ui/dist/assets/index-DDCNauh7.css +1 -0
  38. package/ui/dist/assets/index-DO4t5Xu1.js +72 -0
  39. package/ui/dist/index.html +2 -2
  40. package/dist/cli/mcp-server.d.ts +0 -1
  41. package/dist/cli/mcp-server.js +0 -443
  42. package/dist/cli/mcp.d.ts +0 -1
  43. package/dist/cli/mcp.js +0 -98
  44. package/ui/dist/assets/index-0_XpUUZQ.css +0 -1
  45. 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(chalk.gray("Run `archbyte logout` to sign out."));
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
- const selectedProvider = provider ?? "github";
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 creds = loadCredentials();
98
- if (!creds) {
127
+ const store = loadStore();
128
+ if (!store) {
99
129
  console.log(chalk.gray("Not logged in."));
100
130
  return;
101
131
  }
102
- try {
103
- fs.unlinkSync(CREDENTIALS_PATH);
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
- catch {
106
- // Ignore
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
- // Clean up tier cache and offline action tracker
109
- try {
110
- fs.unlinkSync(TIER_CACHE_PATH);
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
- catch { /* Ignore */ }
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
- export function loadCredentials() {
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
- const data = JSON.parse(fs.readFileSync(CREDENTIALS_PATH, "utf-8"));
153
- // Validate structure reject malformed credentials
154
- if (typeof data?.token !== "string" ||
155
- typeof data?.email !== "string" ||
156
- typeof data?.tier !== "string" ||
157
- typeof data?.expiresAt !== "string") {
158
- return null;
159
- }
160
- return data;
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
- // Ignore — corrupt file treated as no credentials
300
+ return null;
165
301
  }
166
- return null;
167
302
  }
168
- function saveCredentials(creds) {
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(creds, null, 2), "utf-8");
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) => {
@@ -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
- console.log(` ${chalk.bold("provider")}: ${config.provider ?? chalk.gray("not set")}`);
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
- // Check if this provider has a stored profile
112
- if (profiles[value]?.apiKey) {
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
- // Auto-detect from known env vars if nothing explicit
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
+ }
@@ -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
- console.error(chalk.red(`Unknown format: "${format}". Supported: ${SUPPORTED_FORMATS.join(", ")}`));
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
  */
@@ -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
- * Inspired by Gastown's patrol loop pattern cyclic monitoring
13
- * that detects drift and reports violations.
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 {};