archbyte 0.4.1 → 0.5.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 CHANGED
@@ -8,40 +8,24 @@ AI-powered architecture visualization for your codebase. Analyzes code, generate
8
8
  # Install
9
9
  curl -fsSL https://archbyte.heartbyte.io/install.sh | bash
10
10
 
11
- # Sign in or create a free account
12
- archbyte login
13
-
14
- # Configure your AI provider (BYOK)
15
- archbyte init
16
-
17
- # Using Claude Code / Codex? Install the MCP server too:
18
- archbyte mcp install
11
+ # Run (auto-configures on first use)
12
+ archbyte run
19
13
  ```
20
14
 
21
- Your API keys stay on your machine. ArchByte never stores or transmits your provider credentials.
15
+ On first run, ArchByte walks you through sign-in and provider setup interactively. Your API keys stay on your machine ArchByte never stores or transmits your provider credentials.
22
16
 
23
- ### Setup with Claude Code
17
+ ### Manual setup
24
18
 
25
- **Option A: MCP** — use ArchByte tools directly in Claude Code. You still need to sign in and configure a provider first:
19
+ Prefer to configure each step separately:
26
20
 
27
21
  ```bash
28
- archbyte mcp install
29
- # or manually:
30
- claude mcp add archbyte --transport stdio -- npx -y archbyte@latest mcp
22
+ archbyte login # Sign in or create a free account
23
+ archbyte init # Configure your AI provider (BYOK)
31
24
  ```
32
25
 
33
- Then just ask Claude Code in plain English:
34
-
35
- ```
36
- "Analyze the architecture of this project"
37
- "Export the architecture as a Mermaid diagram"
38
- "Show me the architecture stats"
39
- "Read the current architecture diagram"
40
- ```
41
-
42
- Available MCP tools: `archbyte_analyze`, `archbyte_generate`, `archbyte_validate` (Pro), `archbyte_export`, `archbyte_stats`, `archbyte_diff`, `archbyte_read_diagram`.
26
+ ### Setup with Claude Code
43
27
 
44
- **Option B: Slash commands** — installed globally so they work in any project:
28
+ Slash commands — installed globally so they work in any project:
45
29
 
46
30
  ```bash
47
31
  mkdir -p ~/.claude/commands
package/bin/archbyte.js CHANGED
@@ -11,10 +11,7 @@ import { Command } from 'commander';
11
11
  import { handleServe } from '../dist/cli/serve.js';
12
12
  import { handleGenerate } from '../dist/cli/generate.js';
13
13
  import { handleStats } from '../dist/cli/stats.js';
14
- import { handleValidate } from '../dist/cli/validate.js';
15
14
  import { handleExport } from '../dist/cli/export.js';
16
- import { handleDiff } from '../dist/cli/diff.js';
17
- import { handlePatrol } from '../dist/cli/patrol.js';
18
15
  import { handleWorkflow } from '../dist/cli/workflow.js';
19
16
  import { handleAnalyze } from '../dist/cli/analyze.js';
20
17
  import { handleConfig } from '../dist/cli/config.js';
@@ -36,9 +33,13 @@ program
36
33
  .version(PKG_VERSION, '-v, --version', 'Show version number')
37
34
  .addHelpText('after', `
38
35
  Quick start:
36
+ $ archbyte run Analyze + open diagram (auto-configures on first run)
37
+
38
+ Or step by step:
39
39
  $ archbyte login Sign in or create a free account
40
40
  $ archbyte init Configure your model provider
41
- $ archbyte run Analyze + open interactive diagram UI
41
+
42
+ Run from your project root, or pass --dir <path> to specify the directory.
42
43
 
43
44
  https://archbyte.heartbyte.io
44
45
  `);
@@ -93,7 +94,7 @@ program
93
94
  .option('--force', 'Force full re-scan (skip incremental detection)')
94
95
  .option('--dry-run', 'Preview without running')
95
96
  .action(async (options) => {
96
- await requireLicense('analyze');
97
+ // handleRun manages login + setup + requireLicense internally
97
98
  await handleRun(options);
98
99
  });
99
100
 
@@ -145,27 +146,6 @@ program
145
146
  await handleStats(options);
146
147
  });
147
148
 
148
- program
149
- .command('validate')
150
- .description('Run architecture fitness function rules')
151
- .option('-d, --diagram <path>', 'Path to architecture JSON (default: .archbyte/architecture.json)')
152
- .option('-c, --config <path>', 'Path to archbyte.yaml config')
153
- .option('--ci', 'Machine-readable JSON output for CI pipelines')
154
- .action(async (options) => {
155
- await requireLicense('analyze');
156
- await handleValidate(options);
157
- });
158
-
159
- program
160
- .command('diff')
161
- .description('Compare architecture snapshots and detect drift')
162
- .requiredOption('-b, --baseline <path>', 'Path to baseline architecture JSON')
163
- .option('-c, --current <path>', 'Path to current architecture JSON (default: .archbyte/architecture.json)')
164
- .option('--config <path>', 'Path to archbyte.yaml config')
165
- .action(async (options) => {
166
- await handleDiff(options);
167
- });
168
-
169
149
  program
170
150
  .command('export')
171
151
  .description('Export architecture to various formats')
@@ -178,21 +158,6 @@ program
178
158
 
179
159
  // — Advanced (Pro) —
180
160
 
181
- program
182
- .command('patrol')
183
- .description('Run continuous architecture health monitoring (Gastown-inspired patrol loop)')
184
- .option('-d, --diagram <path>', 'Path to architecture JSON (default: .archbyte/architecture.json)')
185
- .option('-c, --config <path>', 'Path to archbyte.yaml config')
186
- .option('-i, --interval <duration>', 'Patrol interval: 30s, 5m, 1h (default: 5m)')
187
- .option('--on-violation <action>', 'Action on new violations: log, json (default: log)')
188
- .option('--once', 'Run a single patrol cycle then exit')
189
- .option('-w, --watch', 'Watch source files for changes instead of polling on interval')
190
- .option('--history', 'Show patrol history dashboard')
191
- .action(async (options) => {
192
- await requireLicense('analyze');
193
- await handlePatrol(options);
194
- });
195
-
196
161
  program
197
162
  .command('workflow')
198
163
  .description('Run composable architecture workflows (MEOW-inspired pipeline system)')
@@ -6,7 +6,7 @@ import { resolveConfig } from "./config.js";
6
6
  import { recordUsage } from "./license-gate.js";
7
7
  import { staticResultToSpec, writeSpec, writeMetadata, loadSpec, loadMetadata } from "./yaml-io.js";
8
8
  import { getChangedFiles, mapFilesToComponents, shouldRunAgents, isGitAvailable, categorizeChanges, computeNeighbors, getCommitCount } from "./incremental.js";
9
- import { progressBar } from "./ui.js";
9
+ import { progressBar, confirm } from "./ui.js";
10
10
  export async function handleAnalyze(options) {
11
11
  const rootDir = options.dir ? path.resolve(options.dir) : process.cwd();
12
12
  const isStaticOnly = options.static || options.skipLlm;
@@ -48,12 +48,18 @@ export async function handleAnalyze(options) {
48
48
  progress.update(0, `Static analysis: ${msg}`);
49
49
  });
50
50
  progress.update(1, "Building analysis...");
51
- const analysis = buildAnalysisFromStatic(result, rootDir);
51
+ const freshAnalysis = buildAnalysisFromStatic(result, rootDir);
52
52
  const duration = Date.now() - startTime;
53
+ // Merge into existing analysis if it was produced by an agentic run,
54
+ // preserving LLM-generated components/connections while refreshing
55
+ // static data (environments, metadata, project info).
56
+ const existingAnalysis = loadExistingAnalysis(rootDir);
57
+ const wasAgentic = existingAnalysis && existingAnalysis.metadata?.mode !== "static";
58
+ const analysis = wasAgentic ? mergeStaticIntoExisting(existingAnalysis, freshAnalysis) : freshAnalysis;
53
59
  // Stamp scan metadata on analysis.json (backward compat)
54
60
  const ameta = analysis.metadata;
55
61
  ameta.durationMs = duration;
56
- ameta.mode = "static";
62
+ ameta.mode = wasAgentic ? "static-refresh" : "static";
57
63
  writeAnalysis(rootDir, analysis);
58
64
  // Dual-write: archbyte.yaml + metadata.json
59
65
  const existingSpec = loadSpec(rootDir);
@@ -79,28 +85,20 @@ export async function handleAnalyze(options) {
79
85
  config.apiKey = options.apiKey;
80
86
  }
81
87
  if (!config) {
82
- const msg = [
83
- chalk.red("No model provider configured."),
84
- "",
85
- chalk.bold("Zero-config (Claude Code users):"),
86
- chalk.gray(" Install Claude Code archbyte analyze just works"),
87
- "",
88
- chalk.bold("Or set up with:"),
89
- chalk.gray(" archbyte config set provider anthropic"),
90
- chalk.gray(" archbyte config set api-key sk-ant-..."),
91
- "",
92
- chalk.bold("Or use environment variables:"),
93
- chalk.gray(" export ARCHBYTE_PROVIDER=anthropic"),
94
- chalk.gray(" export ARCHBYTE_API_KEY=sk-ant-..."),
95
- "",
96
- chalk.bold("Or run without a model:"),
97
- chalk.gray(" archbyte analyze --static"),
98
- "",
99
- chalk.bold("Supported providers:"),
100
- chalk.gray(" anthropic, openai, google, claude-sdk"),
101
- ].join("\n");
102
- console.error(msg);
103
- throw new Error("No model provider configured");
88
+ console.log(chalk.yellow("No model provider configured."));
89
+ console.log();
90
+ const shouldSetup = await confirm("Set up your AI provider now?");
91
+ if (shouldSetup) {
92
+ const { handleSetup } = await import("./setup.js");
93
+ await handleSetup();
94
+ config = resolveConfig();
95
+ }
96
+ if (!config) {
97
+ console.log();
98
+ console.log(chalk.gray("Running static-only analysis (no AI)."));
99
+ console.log();
100
+ return handleAnalyze({ ...options, static: true });
101
+ }
104
102
  }
105
103
  const providerLabel = config.provider === "claude-sdk" ? "Claude Code (SDK)" : config.provider;
106
104
  console.log(chalk.gray(`Provider: ${chalk.white(providerLabel)}`));
@@ -413,10 +411,34 @@ function printSummary(analysis, durationMs, mode, options) {
413
411
  if (!options?.skipServeHint) {
414
412
  console.log(` ${chalk.cyan("archbyte serve")} Open the interactive diagram`);
415
413
  }
416
- console.log(` ${chalk.cyan("archbyte validate")} Check architecture fitness rules ${chalk.yellow("[Pro]")}`);
417
- console.log(` ${chalk.cyan("archbyte patrol")} Continuous architecture monitoring ${chalk.yellow("[Pro]")}`);
418
414
  console.log();
419
415
  }
416
+ // ─── Analysis merge helpers ───
417
+ function loadExistingAnalysis(rootDir) {
418
+ const p = path.join(rootDir, ".archbyte", "analysis.json");
419
+ try {
420
+ return JSON.parse(fs.readFileSync(p, "utf-8"));
421
+ }
422
+ catch {
423
+ return null;
424
+ }
425
+ }
426
+ /**
427
+ * Merge fresh static scan into an existing agentic analysis.
428
+ * Preserves: components, connections, flows, databases, externalServices (LLM-generated).
429
+ * Updates: project info, environments, metadata, c4/infra if present.
430
+ */
431
+ function mergeStaticIntoExisting(existing, fresh) {
432
+ return {
433
+ ...existing,
434
+ project: fresh.project,
435
+ environments: fresh.environments,
436
+ metadata: {
437
+ ...(existing.metadata ?? {}),
438
+ ...(fresh.metadata ?? {}),
439
+ },
440
+ };
441
+ }
420
442
  // ─── Analysis converters ───
421
443
  /**
422
444
  * Convert a StaticAnalysisResult (from pipeline or static-only) into the
@@ -1,6 +1,7 @@
1
1
  import chalk from "chalk";
2
2
  import { loadCredentials, cacheVerifiedTier, resetOfflineActions, checkOfflineAction } from "./auth.js";
3
3
  import { API_BASE, NETWORK_TIMEOUT_MS } from "./constants.js";
4
+ import { confirm } from "./ui.js";
4
5
  /**
5
6
  * Pre-flight license check. Must be called before scan/analyze/generate.
6
7
  *
@@ -16,25 +17,44 @@ import { API_BASE, NETWORK_TIMEOUT_MS } from "./constants.js";
16
17
  * the 1-hour cache window. Exceeding limits blocks the action.
17
18
  */
18
19
  export async function requireLicense(action) {
19
- const creds = loadCredentials();
20
- // Not logged in
20
+ let creds = loadCredentials();
21
+ // Not logged in — offer interactive login
21
22
  if (!creds) {
22
- console.error();
23
- console.error(chalk.red("Authentication required."));
24
- console.error();
25
- console.error(chalk.gray("Sign in or create a free account:"));
26
- console.error(chalk.gray(" archbyte login"));
27
- console.error();
28
- console.error(chalk.gray("Free tier includes unlimited scans. No credit card required."));
29
- process.exit(1);
23
+ console.log();
24
+ console.log(chalk.yellow("Not signed in."));
25
+ console.log(chalk.gray("Free tier includes unlimited scans. No credit card required."));
26
+ console.log();
27
+ const shouldLogin = await confirm("Sign in now?");
28
+ if (!shouldLogin)
29
+ process.exit(1);
30
+ const { handleLogin } = await import("./auth.js");
31
+ await handleLogin();
32
+ creds = loadCredentials();
33
+ if (!creds) {
34
+ console.error(chalk.red("Login failed. Please try again with `archbyte login`."));
35
+ process.exit(1);
36
+ }
30
37
  }
31
- // Token expired locally (treat invalid dates as expired — fail-closed)
38
+ // Token expired locally offer re-login
32
39
  const expiry = new Date(creds.expiresAt);
33
40
  if (isNaN(expiry.getTime()) || expiry < new Date()) {
34
- console.error();
35
- console.error(chalk.red("Session expired."));
36
- console.error(chalk.gray("Run `archbyte login` to refresh your session."));
37
- process.exit(1);
41
+ console.log();
42
+ console.log(chalk.yellow("Session expired."));
43
+ const shouldRelogin = await confirm("Sign in again?");
44
+ if (!shouldRelogin)
45
+ process.exit(1);
46
+ const { handleLogin } = await import("./auth.js");
47
+ await handleLogin();
48
+ creds = loadCredentials();
49
+ if (!creds) {
50
+ console.error(chalk.red("Login failed. Please try again with `archbyte login`."));
51
+ process.exit(1);
52
+ }
53
+ const freshExpiry = new Date(creds.expiresAt);
54
+ if (isNaN(freshExpiry.getTime()) || freshExpiry < new Date()) {
55
+ console.error(chalk.red("Session still expired. Please try `archbyte login`."));
56
+ process.exit(1);
57
+ }
38
58
  }
39
59
  // Check usage with server
40
60
  try {
@@ -48,10 +68,18 @@ export async function requireLicense(action) {
48
68
  signal: AbortSignal.timeout(NETWORK_TIMEOUT_MS),
49
69
  });
50
70
  if (res.status === 401) {
51
- console.error();
52
- console.error(chalk.red("Session invalid. Please log in again."));
53
- console.error(chalk.gray(" archbyte login"));
54
- process.exit(1);
71
+ console.log();
72
+ console.log(chalk.yellow("Session invalid."));
73
+ const shouldRelogin = await confirm("Sign in again?");
74
+ if (!shouldRelogin)
75
+ process.exit(1);
76
+ const { handleLogin } = await import("./auth.js");
77
+ await handleLogin();
78
+ creds = loadCredentials();
79
+ if (!creds)
80
+ process.exit(1);
81
+ // Re-check usage with fresh credentials
82
+ return requireLicense(action);
55
83
  }
56
84
  if (!res.ok) {
57
85
  // Server error — enforce offline limits instead of allowing freely
package/dist/cli/run.js CHANGED
@@ -1,8 +1,124 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import chalk from "chalk";
1
4
  import { handleAnalyze } from "./analyze.js";
2
5
  import { handleServe } from "./serve.js";
3
- import { DEFAULT_PORT } from "./constants.js";
6
+ import { DEFAULT_PORT, CONFIG_PATH } from "./constants.js";
7
+ import { loadCredentials } from "./auth.js";
8
+ import { resolveConfig } from "./config.js";
9
+ import { requireLicense } from "./license-gate.js";
10
+ import { isTTY } from "./utils.js";
11
+ /**
12
+ * Check if the user has explicitly configured a provider via `archbyte init`.
13
+ * This reads the raw config file — unlike resolveConfig() which auto-detects
14
+ * Claude Code on PATH, env vars, etc. We want to trigger setup only when
15
+ * the user hasn't gone through init yet.
16
+ */
17
+ function hasExplicitConfig() {
18
+ try {
19
+ if (!fs.existsSync(CONFIG_PATH))
20
+ return false;
21
+ const config = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8"));
22
+ return !!config.provider;
23
+ }
24
+ catch {
25
+ return false;
26
+ }
27
+ }
4
28
  export async function handleRun(options) {
5
29
  const port = options.port || DEFAULT_PORT;
30
+ const isStaticOnly = options.static || options.skipLlm;
31
+ // ─── First-run onboarding: login + provider setup ───
32
+ const creds = loadCredentials();
33
+ const needsLogin = !creds;
34
+ const needsSetup = !hasExplicitConfig() && !isStaticOnly && !options.provider;
35
+ if (needsLogin || needsSetup) {
36
+ const dim = chalk.gray;
37
+ const sep = dim(" ───");
38
+ console.log();
39
+ console.log(chalk.bold.cyan(" Welcome to ArchByte"));
40
+ console.log(dim(" Visual observability for agentic development."));
41
+ console.log(dim(" Understand what your agents are building, in real time."));
42
+ console.log();
43
+ console.log(sep);
44
+ console.log();
45
+ console.log(dim(" Docs ") + chalk.cyan("https://archbyte.heartbyte.io/setup.html"));
46
+ console.log(dim(" Website ") + chalk.cyan("https://archbyte.heartbyte.io"));
47
+ console.log();
48
+ console.log(sep);
49
+ console.log();
50
+ console.log(dim(" Let's get you set up. This takes about 30 seconds."));
51
+ console.log();
52
+ // Step 1: Login
53
+ if (needsLogin) {
54
+ const { handleLogin } = await import("./auth.js");
55
+ await handleLogin();
56
+ if (!loadCredentials()) {
57
+ console.error(chalk.red("Login required. Run `archbyte login` to try again."));
58
+ process.exit(1);
59
+ }
60
+ }
61
+ // Step 2: Provider setup
62
+ if (needsSetup) {
63
+ const { handleSetup } = await import("./setup.js");
64
+ await handleSetup({ calledFromRun: true });
65
+ }
66
+ // Show config summary and pause before scanning
67
+ const resolvedConfig = resolveConfig();
68
+ const resolvedCreds = loadCredentials();
69
+ const rootDir = options.dir ? path.resolve(options.dir) : process.cwd();
70
+ console.log(sep);
71
+ console.log();
72
+ console.log(" " + chalk.bold("Ready to scan"));
73
+ console.log();
74
+ if (resolvedCreds) {
75
+ console.log(dim(" Account ") + chalk.white(resolvedCreds.email));
76
+ }
77
+ if (resolvedConfig) {
78
+ const providerLabel = resolvedConfig.provider === "claude-sdk"
79
+ ? "Claude Code (SDK)"
80
+ : resolvedConfig.provider;
81
+ console.log(dim(" Provider ") + chalk.white(providerLabel));
82
+ if (resolvedConfig.model) {
83
+ console.log(dim(" Model ") + chalk.white(resolvedConfig.model));
84
+ }
85
+ if (resolvedConfig.provider !== "claude-sdk") {
86
+ console.log(dim(" API key ") + (resolvedConfig.apiKey ? chalk.green("configured") : chalk.yellow("not set")));
87
+ }
88
+ }
89
+ console.log(dim(" Project ") + chalk.white(path.basename(rootDir)));
90
+ console.log(dim(" Directory ") + chalk.white(rootDir));
91
+ console.log();
92
+ // Pause — let the user absorb the config before scanning
93
+ if (isTTY()) {
94
+ await new Promise((resolve) => {
95
+ process.stdout.write(dim(" Press Enter to start analysis (q to quit)..."));
96
+ const stdin = process.stdin;
97
+ stdin.setRawMode(true);
98
+ stdin.resume();
99
+ stdin.setEncoding("utf8");
100
+ const onData = (data) => {
101
+ if (data === "\r" || data === "\n" || data === " ") {
102
+ stdin.setRawMode(false);
103
+ stdin.pause();
104
+ stdin.removeListener("data", onData);
105
+ process.stdout.write("\n\n");
106
+ resolve();
107
+ }
108
+ else if (data === "q" || data === "Q" || data === "\u0003" || data === "\u001b") {
109
+ stdin.setRawMode(false);
110
+ stdin.pause();
111
+ stdin.removeListener("data", onData);
112
+ process.stdout.write("\n");
113
+ process.exit(0);
114
+ }
115
+ };
116
+ stdin.on("data", onData);
117
+ });
118
+ }
119
+ }
120
+ // License/usage check (creds guaranteed to exist after onboarding)
121
+ await requireLicense("analyze");
6
122
  // 1. Analyze (includes auto-generate)
7
123
  await handleAnalyze({
8
124
  verbose: options.verbose,
@@ -1 +1,6 @@
1
- export declare function handleSetup(): Promise<void>;
1
+ interface SetupOptions {
2
+ /** When true, skip "archbyte run" in next steps (called from `archbyte run`) */
3
+ calledFromRun?: boolean;
4
+ }
5
+ export declare function handleSetup(opts?: SetupOptions): Promise<void>;
6
+ export {};
package/dist/cli/setup.js CHANGED
@@ -207,7 +207,8 @@ async function validateProviderSilent(providerName, apiKey, model) {
207
207
  function getProfiles(config) {
208
208
  return config.profiles ?? {};
209
209
  }
210
- export async function handleSetup() {
210
+ export async function handleSetup(opts) {
211
+ const calledFromRun = opts?.calledFromRun ?? false;
211
212
  console.log();
212
213
  console.log(chalk.bold.cyan("ArchByte Setup"));
213
214
  console.log(chalk.gray("Configure your model provider and API key.\n"));
@@ -300,12 +301,20 @@ export async function handleSetup() {
300
301
  console.log();
301
302
  console.log(sep);
302
303
  console.log();
303
- console.log(" " + chalk.bold("Next steps"));
304
- console.log();
305
- console.log(" " + chalk.cyan("archbyte run") + " Analyze your codebase");
306
- console.log(" " + chalk.cyan("archbyte status") + " Check account and usage");
307
- console.log(" " + chalk.cyan("archbyte --help") + " See all commands");
308
- console.log();
304
+ if (calledFromRun) {
305
+ console.log(dim(" Continuing to scan your codebase..."));
306
+ console.log();
307
+ }
308
+ else {
309
+ console.log(" " + chalk.bold("Next steps"));
310
+ console.log();
311
+ console.log(" " + chalk.cyan("archbyte run") + " Analyze your codebase");
312
+ console.log(" " + chalk.cyan("archbyte status") + " Check account and usage");
313
+ console.log(" " + chalk.cyan("archbyte --help") + " See all commands");
314
+ console.log();
315
+ console.log(dim(" Run from your project root, or use --dir <path> to specify the directory."));
316
+ console.log();
317
+ }
309
318
  return;
310
319
  }
311
320
  if (choice === "codex") {
@@ -588,18 +597,28 @@ export async function handleSetup() {
588
597
  console.log();
589
598
  console.log(sep);
590
599
  console.log();
591
- console.log(" " + chalk.bold("Next steps"));
592
- console.log();
593
- console.log(" " + chalk.cyan("archbyte run") + " Analyze your codebase");
594
- if (hasClaude || hasCodex) {
600
+ if (calledFromRun) {
601
+ if (result === false) {
602
+ console.log(chalk.yellow(" Warning: credentials unverified. Saving anyway."));
603
+ console.log();
604
+ }
605
+ console.log(dim(" Continuing to scan your codebase..."));
606
+ console.log();
595
607
  }
596
- console.log(" " + chalk.cyan("archbyte status") + " Check account and usage");
597
- console.log(" " + chalk.cyan("archbyte --help") + " See all commands");
598
- if (result === false) {
608
+ else {
609
+ console.log(" " + chalk.bold("Next steps"));
610
+ console.log();
611
+ console.log(" " + chalk.cyan("archbyte run") + " Analyze your codebase");
612
+ console.log(" " + chalk.cyan("archbyte status") + " Check account and usage");
613
+ console.log(" " + chalk.cyan("archbyte --help") + " See all commands");
614
+ console.log();
615
+ console.log(dim(" Run from your project root, or use --dir <path> to specify the directory."));
616
+ if (result === false) {
617
+ console.log();
618
+ console.log(chalk.yellow(" Warning: credentials unverified. Saving anyway."));
619
+ }
599
620
  console.log();
600
- console.log(chalk.yellow(" Warning: credentials unverified. Saving anyway."));
601
621
  }
602
- console.log();
603
622
  }
604
623
  function writeArchbyteReadme(archbyteDir) {
605
624
  const readmePath = path.join(archbyteDir, "README.md");
@@ -64,16 +64,5 @@ export declare function parseRulesFromYaml(content: string): RuleConfig;
64
64
  * level: error
65
65
  */
66
66
  export declare function parseCustomRulesFromYaml(content: string): CustomRule[];
67
- /**
68
- * Parse the patrol.ignore list from archbyte.yaml.
69
- * Returns user-defined glob patterns for watch mode to ignore.
70
- *
71
- * patrol:
72
- * ignore:
73
- * - "docs/"
74
- * - "*.md"
75
- * - "build/"
76
- */
77
- export declare function loadPatrolIgnore(configPath?: string): string[];
78
67
  export declare function getRuleLevel(config: RuleConfig, rule: keyof RuleConfig, defaultLevel: RuleLevel): RuleLevel;
79
68
  export declare function getThreshold(config: RuleConfig, rule: "max-connections", defaultVal: number): number;
@@ -259,67 +259,6 @@ export function parseCustomRulesFromYaml(content) {
259
259
  flushItem();
260
260
  return rules;
261
261
  }
262
- /**
263
- * Parse the patrol.ignore list from archbyte.yaml.
264
- * Returns user-defined glob patterns for watch mode to ignore.
265
- *
266
- * patrol:
267
- * ignore:
268
- * - "docs/"
269
- * - "*.md"
270
- * - "build/"
271
- */
272
- export function loadPatrolIgnore(configPath) {
273
- const rootDir = process.cwd();
274
- const yamlPath = configPath
275
- ? path.resolve(rootDir, configPath)
276
- : path.join(rootDir, ".archbyte", "archbyte.yaml");
277
- if (!fs.existsSync(yamlPath))
278
- return [];
279
- try {
280
- const lines = fs.readFileSync(yamlPath, "utf-8").split("\n");
281
- const patterns = [];
282
- let inPatrol = false;
283
- let inIgnore = false;
284
- for (const line of lines) {
285
- const trimmed = line.trimEnd();
286
- if (/^patrol:\s*$/.test(trimmed)) {
287
- inPatrol = true;
288
- continue;
289
- }
290
- // Another top-level section ends patrol
291
- if (inPatrol && /^\S/.test(trimmed) && !trimmed.startsWith("#")) {
292
- inPatrol = false;
293
- inIgnore = false;
294
- continue;
295
- }
296
- if (!inPatrol)
297
- continue;
298
- if (trimmed === "" || trimmed.trim().startsWith("#"))
299
- continue;
300
- if (/^ {2}ignore:\s*$/.test(trimmed)) {
301
- inIgnore = true;
302
- continue;
303
- }
304
- // Another patrol sub-key ends ignore
305
- if (inIgnore && /^ {2}\S/.test(trimmed) && !/^ {2}ignore:/.test(trimmed)) {
306
- inIgnore = false;
307
- continue;
308
- }
309
- if (!inIgnore)
310
- continue;
311
- // List item: " - pattern"
312
- const itemMatch = trimmed.match(/^ {4}-\s+"?([^"]+)"?\s*$/);
313
- if (itemMatch) {
314
- patterns.push(itemMatch[1].trim());
315
- }
316
- }
317
- return patterns;
318
- }
319
- catch {
320
- return [];
321
- }
322
- }
323
262
  export function getRuleLevel(config, rule, defaultLevel) {
324
263
  const entry = config[rule];
325
264
  if (!entry)