archbyte 0.7.1 → 0.7.3

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
@@ -47,11 +47,8 @@ Run `/archbyte-help` in Claude Code to see all commands.
47
47
  | `/archbyte-analyze` | Scans your codebase and generates `.archbyte/analysis.json` |
48
48
  | `/archbyte-generate` | Converts analysis into `.archbyte/architecture.json` with positioned nodes |
49
49
  | `/archbyte-serve` | Opens interactive diagram UI at http://localhost:3847 |
50
- | `/archbyte-validate` | Runs fitness rules against your architecture |
51
50
  | `/archbyte-stats` | Shows architecture health dashboard |
52
51
  | `/archbyte-export` | Exports diagram to Mermaid, Markdown, or other formats |
53
- | `/archbyte-diff` | Compares architecture snapshots to detect drift |
54
- | `/archbyte-patrol` | Starts continuous architecture health monitoring |
55
52
  | `/archbyte-workflow` | Runs composable multi-step architecture pipelines |
56
53
  | `/archbyte-init` | Initializes `archbyte.yaml` config for your project |
57
54
  | `/archbyte-help` | Shows this command reference |
@@ -153,46 +150,6 @@ Features: real-time SSE updates, drag-and-drop nodes, environment filtering, flo
153
150
  >
154
151
  > *Example:* During an incident review, run `npx archbyte serve` and drag the failing service to the center. Click it to highlight all upstream dependencies — now the whole team can see which services are affected and trace the blast radius visually instead of grep-ing through YAML manifests.
155
152
 
156
- ### `archbyte validate`
157
-
158
- Run architecture fitness function rules.
159
-
160
- ```bash
161
- npx archbyte validate
162
- npx archbyte validate --ci # JSON output, exit code 1 on errors
163
- npx archbyte validate --watch # re-validate on file changes
164
- ```
165
-
166
- **Built-in rules:**
167
-
168
- | Rule | Default | Description |
169
- |------|---------|-------------|
170
- | `no-layer-bypass` | error | Connections that skip layers (e.g. presentation -> data) |
171
- | `max-connections` | warn (6) | Nodes exceeding connection threshold |
172
- | `no-orphans` | warn | Nodes with zero connections |
173
- | `no-circular-deps` | error | Cycles in the dependency graph |
174
-
175
- Configure in `archbyte.yaml`:
176
-
177
- ```yaml
178
- rules:
179
- no-layer-bypass: error
180
- max-connections:
181
- level: warn
182
- threshold: 8
183
- no-orphans: off
184
- no-circular-deps: error
185
- custom:
186
- - name: "no-direct-db-from-frontend"
187
- from: { layer: "presentation" }
188
- to: { type: "database" }
189
- level: error
190
- ```
191
-
192
- > **Why this matters:** Code review catches syntax and logic bugs, but architectural violations slip through because no human reviewer holds the entire dependency graph in their head. Validate acts as an automated architect that never misses a rule.
193
- >
194
- > *Example:* A junior developer adds a direct import from a React component to a Postgres client library. `npx archbyte validate --ci` in your GitHub Actions pipeline catches the layer bypass (`presentation -> data`) and fails the build *before* the PR is merged — no manual review needed.
195
-
196
153
  ### `archbyte stats`
197
154
 
198
155
  Architecture health dashboard.
@@ -225,51 +182,17 @@ npx archbyte export --output docs/architecture.mmd
225
182
  >
226
183
  > *Example:* Before each release, you run `npx archbyte export --format mermaid --output docs/architecture.mmd` and commit it. GitHub renders the Mermaid diagram inline in your repo — anyone browsing the codebase sees an always-current architecture map without installing anything.
227
184
 
228
- ### `archbyte diff`
185
+ ### `archbyte mcp`
229
186
 
230
- Compare architecture snapshots and detect drift.
187
+ Start the MCP (Model Context Protocol) server for AI agent integration.
231
188
 
232
189
  ```bash
233
- npx archbyte diff --baseline .archbyte/baseline.json
234
- npx archbyte diff --baseline old.json --current new.json
190
+ npx archbyte mcp # start MCP server on stdio
235
191
  ```
236
192
 
237
- Reports added/removed components, added/removed connections, density change, new/resolved violations. Exit code 1 on new errors.
238
-
239
- > **Why this matters:** Architecture erosion happens one commit at a time. By the time someone notices, the codebase has drifted far from its intended design. Diff catches drift early by comparing snapshots and surfacing exactly what changed.
240
- >
241
- > *Example:* You save a baseline after your v2.0 release with `cp .archbyte/architecture.json .archbyte/baseline.json`. Two months later, run `npx archbyte diff --baseline .archbyte/baseline.json` and discover 3 new circular dependencies and a removed service boundary. You can now decide whether to accept the drift or fix it — before it compounds further.
242
-
243
- ### `archbyte patrol`
193
+ Exposes ArchByte tools (analyze, status, export, stats, validate) and resources to any MCP-compatible client (Claude Code, VS Code, etc.).
244
194
 
245
- Continuous architecture health monitoring runs the validation pipeline on a repeating interval, diffs each cycle to detect new and resolved violations, and maintains a persistent health history.
246
-
247
- Inspired by [Gastown's](https://github.com/steveyegge/gastown) patrol loop pattern.
248
-
249
- ```bash
250
- npx archbyte patrol # default 5m interval
251
- npx archbyte patrol --interval 30s # 30 second cycles
252
- npx archbyte patrol --interval 1h # hourly
253
- npx archbyte patrol --on-violation json # JSON output for piping
254
- npx archbyte patrol --history # view patrol history dashboard
255
- ```
256
-
257
- **Options:**
258
-
259
- | Flag | Default | Description |
260
- |------|---------|-------------|
261
- | `--interval <duration>` | `5m` | Cycle interval: `30s`, `5m`, `1h` |
262
- | `--on-violation <action>` | `log` | Action on new violations: `log` or `json` |
263
- | `--history` | — | Show health sparkline, history table, health rate |
264
-
265
- The patrol daemon:
266
- - Reports **new** violations and **resolved** ones each cycle
267
- - Persists history to `.archbyte/patrols/history.jsonl`
268
- - `--history` shows a sparkline health chart and stats
269
-
270
- > **Why this matters:** Validate catches violations at a point in time; Patrol catches them *over time*. Running continuously, it detects the moment a violation appears and tells you when violations get resolved — giving you a living health timeline for your architecture.
271
- >
272
- > *Example:* You start `npx archbyte patrol --interval 5m` in a tmux session during a refactoring sprint. Mid-afternoon, it flags a new circular dependency introduced in the latest commit. You fix it immediately instead of discovering it three weeks later in a failing CI pipeline. At the end of the sprint, `npx archbyte patrol --history` shows the team a sparkline proving architecture health improved from 72% to 95%.
195
+ > **Why this matters:** MCP lets AI agents call ArchByte tools directly without going through the CLI. This enables seamless integration where Claude Code can analyze your architecture, check stats, and export diagrams as part of a larger agentic workflow.
273
196
 
274
197
  ### `archbyte workflow`
275
198
 
@@ -322,6 +245,22 @@ Workflows **resume on failure** — completed steps are skipped when you re-run.
322
245
  >
323
246
  > *Example:* You add a `ci-check` workflow to your GitHub Actions. On every PR, it runs `npx archbyte workflow --run ci-check`, which validates architecture rules in CI mode and exits non-zero on violations. No manual steps, no forgotten checks. For bigger releases, `npx archbyte workflow --run full-analysis` runs the entire generate-validate-stats-export pipeline in one command and produces a Mermaid diagram committed to `docs/`.
324
247
 
248
+ ## Global Flags
249
+
250
+ These flags work with any command:
251
+
252
+ | Flag | Description |
253
+ |------|-------------|
254
+ | `--json` | Output structured JSON instead of human-readable text |
255
+ | `--quiet` | Suppress all non-essential output (spinners, progress bars) |
256
+
257
+ ```bash
258
+ npx archbyte status --json # machine-readable status
259
+ npx archbyte stats --json # structured stats output
260
+ npx archbyte export --json # JSON envelope around export
261
+ npx archbyte analyze --quiet # suppress progress output
262
+ ```
263
+
325
264
  ## Keyboard Shortcuts (UI)
326
265
 
327
266
  | Key | Action |
package/bin/archbyte.js CHANGED
@@ -21,6 +21,7 @@ import { handleSetup } from '../dist/cli/setup.js';
21
21
  import { handleVersion, handleUpdate } from '../dist/cli/version.js';
22
22
  import { requireLicense } from '../dist/cli/license-gate.js';
23
23
  import { DEFAULT_PORT } from '../dist/cli/constants.js';
24
+ import { setOutputMode, handleError, isJsonMode } from '../dist/cli/output.js';
24
25
 
25
26
  // When spawned by `archbyte serve` (internal), skip interactive license checks.
26
27
  // The user already authenticated when they started the server.
@@ -36,6 +37,8 @@ program
36
37
  .name('archbyte')
37
38
  .description('ArchByte - AI architecture analysis with an interactive diagram UI')
38
39
  .version(PKG_VERSION, '-v, --version', 'Show version number')
40
+ .option('--json', 'Output structured JSON (for agents and scripts)')
41
+ .option('--quiet', 'Suppress non-essential output')
39
42
  .addHelpText('after', `
40
43
  Quick start:
41
44
  $ archbyte run Analyze + open diagram (auto-configures on first run)
@@ -49,6 +52,17 @@ program
49
52
  https://archbyte.heartbyte.io
50
53
  `);
51
54
 
55
+ // Set output mode before every command
56
+ program.hook('preAction', (thisCommand, actionCommand) => {
57
+ const opts = program.opts();
58
+ setOutputMode({
59
+ json: opts.json ?? false,
60
+ quiet: opts.quiet ?? false,
61
+ command: actionCommand.name(),
62
+ version: PKG_VERSION,
63
+ });
64
+ });
65
+
52
66
  // — Getting started —
53
67
 
54
68
  program
@@ -229,6 +243,16 @@ program
229
243
  await handleUpdate();
230
244
  });
231
245
 
246
+ // — Agent integration —
247
+
248
+ program
249
+ .command('mcp')
250
+ .description('Start MCP server (stdio transport) for AI agent integration')
251
+ .action(async () => {
252
+ const { startMcpServer } = await import('../dist/mcp/server.js');
253
+ await startMcpServer(PKG_VERSION);
254
+ });
255
+
232
256
  // Default: show help
233
257
  program
234
258
  .action(() => {
@@ -240,4 +264,6 @@ program.on('command:*', () => {
240
264
  program.help();
241
265
  });
242
266
 
243
- program.parse();
267
+ program.parseAsync().catch((err) => {
268
+ handleError(err);
269
+ });
@@ -4,6 +4,7 @@ import { execSync } from "child_process";
4
4
  import chalk from "chalk";
5
5
  import { resolveConfig } from "./config.js";
6
6
  import { recordUsage } from "./license-gate.js";
7
+ import { isJsonMode, outputSuccess, ArchByteError, EXIT } from "./output.js";
7
8
  import { staticResultToSpec, writeSpec, writeMetadata, loadSpec, loadMetadata, resolvePrivacy } from "./yaml-io.js";
8
9
  import { getChangedFiles, mapFilesToComponents, shouldRunAgents, isGitAvailable, categorizeChanges, computeNeighbors, getCommitCount } from "./incremental.js";
9
10
  import { progressBar, confirm } from "./ui.js";
@@ -122,8 +123,7 @@ export async function handleAnalyze(options) {
122
123
  provider = createProvider(config);
123
124
  }
124
125
  catch (err) {
125
- console.error(chalk.red(`Failed to create ${config.provider} provider: ${err instanceof Error ? err.message : err}`));
126
- process.exit(1);
126
+ throw new ArchByteError("PROVIDER_ERROR", `Failed to create ${config.provider} provider: ${err instanceof Error ? err.message : err}`, EXIT.PROVIDER_ERROR);
127
127
  }
128
128
  // 3. Incremental detection
129
129
  const startTime = Date.now();
@@ -290,8 +290,7 @@ export async function handleAnalyze(options) {
290
290
  printSummary(analysis, duration, "static", { skipServeHint: options.skipServeHint });
291
291
  }
292
292
  catch (fallbackErr) {
293
- console.error(chalk.red(` Static fallback also failed: ${fallbackErr instanceof Error ? fallbackErr.message : String(fallbackErr)}`));
294
- process.exit(1);
293
+ throw new ArchByteError("ANALYSIS_FAILED", `Static fallback also failed: ${fallbackErr instanceof Error ? fallbackErr.message : String(fallbackErr)}`, EXIT.ANALYSIS_FAILED);
295
294
  }
296
295
  return;
297
296
  }
@@ -517,6 +516,16 @@ async function autoGenerate(rootDir, options) {
517
516
  function printSummary(analysis, durationMs, mode, options) {
518
517
  const components = analysis.components ?? [];
519
518
  const connections = analysis.connections ?? [];
519
+ if (isJsonMode()) {
520
+ outputSuccess({
521
+ outputPath: options?.outputPath ?? ".archbyte/analysis.json",
522
+ components: components.length,
523
+ connections: connections.length,
524
+ duration: durationMs,
525
+ mode,
526
+ });
527
+ return;
528
+ }
520
529
  console.log();
521
530
  console.log(chalk.bold.green("Analysis complete!"));
522
531
  console.log(chalk.gray(` Mode: ${mode === "pipeline" ? "static + agents" : "static"}`));
@@ -12,7 +12,33 @@ export declare function handleLogout(options?: {
12
12
  all?: boolean;
13
13
  email?: string;
14
14
  }): Promise<void>;
15
+ export interface AccountStatusData {
16
+ email: string;
17
+ tier: string;
18
+ expiresAt: string;
19
+ expired: boolean;
20
+ scans?: {
21
+ used: number;
22
+ allowed: number;
23
+ };
24
+ }
25
+ /**
26
+ * Get account status as structured data. Returns null if not logged in.
27
+ */
28
+ export declare function getAccountStatus(): Promise<AccountStatusData | null>;
15
29
  export declare function handleStatus(): Promise<void>;
30
+ export interface AccountsListData {
31
+ active: string;
32
+ accounts: Array<{
33
+ email: string;
34
+ tier: string;
35
+ expired: boolean;
36
+ }>;
37
+ }
38
+ /**
39
+ * Get the list of logged-in accounts as structured data.
40
+ */
41
+ export declare function getAccountsList(): AccountsListData | null;
16
42
  export declare function handleAccounts(): Promise<void>;
17
43
  export declare function handleAccountSwitch(email?: string): Promise<void>;
18
44
  export declare function loadCredentials(): Credentials | null;
package/dist/cli/auth.js CHANGED
@@ -5,6 +5,7 @@ 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
7
  import { confirm, select, textInput } from "./ui.js";
8
+ import { isJsonMode, outputSuccess, outputError, EXIT, ArchByteError } from "./output.js";
8
9
  export async function handleLogin(provider) {
9
10
  console.log();
10
11
  console.log(chalk.bold.cyan("ArchByte Login"));
@@ -60,8 +61,7 @@ export async function handleLogin(provider) {
60
61
  console.log(chalk.gray(`Credentials saved to ${CREDENTIALS_PATH}`));
61
62
  }
62
63
  catch (err) {
63
- console.error(chalk.red(`Login failed: ${err instanceof Error ? err.message : "Unknown error"}`));
64
- process.exit(1);
64
+ throw new ArchByteError("AUTH_INVALID", `Login failed: ${err instanceof Error ? err.message : "Unknown error"}`, EXIT.AUTH_INVALID);
65
65
  }
66
66
  return;
67
67
  }
@@ -87,7 +87,7 @@ export async function handleLogin(provider) {
87
87
  console.log(chalk.gray(" 1. Visit https://archbyte.heartbyte.io"));
88
88
  console.log(chalk.gray(" 2. Sign in and copy your token from the dashboard"));
89
89
  console.log(chalk.gray(" 3. Run: archbyte login --token <your-token>"));
90
- process.exit(1);
90
+ throw new ArchByteError("AUTH_INVALID", `Login failed: ${err instanceof Error ? err.message : "Unknown error"}`, EXIT.AUTH_INVALID);
91
91
  }
92
92
  }
93
93
  export async function handleLoginWithToken(token) {
@@ -99,8 +99,7 @@ export async function handleLoginWithToken(token) {
99
99
  headers: { Authorization: `Bearer ${token}` },
100
100
  });
101
101
  if (!res.ok) {
102
- console.error(chalk.red("Invalid token."));
103
- process.exit(1);
102
+ throw new ArchByteError("AUTH_INVALID", "Invalid token.", EXIT.AUTH_INVALID);
104
103
  }
105
104
  const { user } = (await res.json());
106
105
  const payload = parseJWTPayload(token);
@@ -117,8 +116,9 @@ export async function handleLoginWithToken(token) {
117
116
  console.log(chalk.green(`Logged in as ${chalk.bold(user.email)} (${user.tier} tier)`));
118
117
  }
119
118
  catch (err) {
120
- console.error(chalk.red(`Login failed: ${err instanceof Error ? err.message : "Unknown error"}`));
121
- process.exit(1);
119
+ if (err instanceof ArchByteError)
120
+ throw err;
121
+ throw new ArchByteError("AUTH_INVALID", `Login failed: ${err instanceof Error ? err.message : "Unknown error"}`, EXIT.AUTH_INVALID);
122
122
  }
123
123
  }
124
124
  // === Logout ===
@@ -174,54 +174,105 @@ export async function handleLogout(options) {
174
174
  }
175
175
  console.log(chalk.green(`Logged out (was ${targetEmail}).`));
176
176
  }
177
- // === Status ===
177
+ /**
178
+ * Get account status as structured data. Returns null if not logged in.
179
+ */
180
+ export async function getAccountStatus() {
181
+ const creds = loadCredentials();
182
+ if (!creds)
183
+ return null;
184
+ const expired = isExpired(creds);
185
+ const result = {
186
+ email: creds.email,
187
+ tier: creds.tier,
188
+ expiresAt: creds.expiresAt,
189
+ expired,
190
+ };
191
+ if (!expired) {
192
+ try {
193
+ const res = await fetch(`${API_BASE}/api/v1/scans/count`, {
194
+ headers: { Authorization: `Bearer ${creds.token}` },
195
+ });
196
+ if (res.ok) {
197
+ const data = (await res.json());
198
+ result.scans = { used: data.scansUsed, allowed: data.scansAllowed };
199
+ }
200
+ }
201
+ catch {
202
+ // Offline — no scan data
203
+ }
204
+ }
205
+ return result;
206
+ }
178
207
  export async function handleStatus() {
208
+ const status = await getAccountStatus();
209
+ if (isJsonMode()) {
210
+ if (!status) {
211
+ outputError("AUTH_REQUIRED", "Not logged in", EXIT.AUTH_REQUIRED);
212
+ }
213
+ if (status.expired) {
214
+ outputError("AUTH_EXPIRED", "Session expired", EXIT.AUTH_EXPIRED);
215
+ }
216
+ outputSuccess(status);
217
+ return;
218
+ }
179
219
  console.log();
180
220
  console.log(chalk.bold.cyan("ArchByte Account"));
181
221
  console.log();
182
- const creds = loadCredentials();
183
- if (!creds) {
222
+ if (!status) {
184
223
  console.log(chalk.yellow("Not logged in. Run `archbyte login` to sign in."));
185
224
  return;
186
225
  }
187
- if (isExpired(creds)) {
226
+ if (status.expired) {
188
227
  console.log(chalk.yellow("Session expired. Run `archbyte login` to refresh."));
189
228
  return;
190
229
  }
191
- console.log(` ${chalk.bold("Email")}: ${creds.email}`);
192
- console.log(` ${chalk.bold("Tier")}: ${creds.tier === "premium" ? chalk.green("Pro") : "Basic"}`);
193
- console.log(` ${chalk.bold("Expires")}: ${new Date(creds.expiresAt).toLocaleDateString()}`);
194
- // Fetch live usage from server
195
- try {
196
- const res = await fetch(`${API_BASE}/api/v1/scans/count`, {
197
- headers: { Authorization: `Bearer ${creds.token}` },
198
- });
199
- if (res.ok) {
200
- const data = (await res.json());
201
- console.log(` ${chalk.bold("Scans")}: ${data.scansUsed}${data.scansAllowed === -1 ? " (unlimited)" : `/${data.scansAllowed}`}`);
202
- }
203
- }
204
- catch {
205
- // Offline — show cached info only
230
+ console.log(` ${chalk.bold("Email")}: ${status.email}`);
231
+ console.log(` ${chalk.bold("Tier")}: ${status.tier === "premium" ? chalk.green("Pro") : "Basic"}`);
232
+ console.log(` ${chalk.bold("Expires")}: ${new Date(status.expiresAt).toLocaleDateString()}`);
233
+ if (status.scans) {
234
+ console.log(` ${chalk.bold("Scans")}: ${status.scans.used}${status.scans.allowed === -1 ? " (unlimited)" : `/${status.scans.allowed}`}`);
206
235
  }
207
236
  console.log();
208
237
  }
209
- // === Accounts ===
238
+ /**
239
+ * Get the list of logged-in accounts as structured data.
240
+ */
241
+ export function getAccountsList() {
242
+ const store = loadStore();
243
+ if (!store || Object.keys(store.accounts).length === 0)
244
+ return null;
245
+ return {
246
+ active: store.active,
247
+ accounts: Object.entries(store.accounts).map(([email, creds]) => ({
248
+ email,
249
+ tier: creds.tier,
250
+ expired: isExpired(creds),
251
+ })),
252
+ };
253
+ }
210
254
  export async function handleAccounts() {
255
+ const data = getAccountsList();
256
+ if (isJsonMode()) {
257
+ if (!data) {
258
+ outputError("AUTH_REQUIRED", "No accounts", EXIT.AUTH_REQUIRED);
259
+ }
260
+ outputSuccess(data);
261
+ return;
262
+ }
211
263
  console.log();
212
264
  console.log(chalk.bold.cyan("ArchByte Accounts"));
213
265
  console.log();
214
- const store = loadStore();
215
- if (!store || Object.keys(store.accounts).length === 0) {
266
+ if (!data) {
216
267
  console.log(chalk.yellow("No accounts. Run `archbyte login` to sign in."));
217
268
  return;
218
269
  }
219
- for (const [email, creds] of Object.entries(store.accounts)) {
220
- const isActive = email === store.active;
270
+ for (const acct of data.accounts) {
271
+ const isActive = acct.email === data.active;
221
272
  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}`);
273
+ const tier = acct.tier === "premium" ? chalk.green("Pro") : "Basic";
274
+ const expired = acct.expired ? chalk.red(" (expired)") : "";
275
+ console.log(` ${marker} ${chalk.bold(acct.email)} ${tier}${expired}`);
225
276
  }
226
277
  console.log();
227
278
  console.log(chalk.gray("* = active account"));
@@ -3,26 +3,35 @@ import { execSync } from "child_process";
3
3
  import chalk from "chalk";
4
4
  import { CONFIG_DIR, CONFIG_PATH } from "./constants.js";
5
5
  import { maskKey } from "./utils.js";
6
+ import { isJsonMode, outputSuccess, ArchByteError, EXIT } from "./output.js";
6
7
  const VALID_PROVIDERS = ["anthropic", "openai", "google", "claude-sdk"];
7
8
  export async function handleConfig(options) {
8
9
  const [action, key, value] = options.args;
9
10
  if (!action || action === "show") {
11
+ if (isJsonMode()) {
12
+ const config = loadConfig();
13
+ const profiles = (config.profiles ?? {});
14
+ const active = config.provider;
15
+ outputSuccess({
16
+ provider: config.provider ?? null,
17
+ model: active ? profiles[active]?.model ?? config.model ?? null : config.model ?? null,
18
+ hasApiKey: active ? !!profiles[active]?.apiKey : !!config.apiKey,
19
+ });
20
+ return;
21
+ }
10
22
  showConfig();
11
23
  return;
12
24
  }
13
25
  if (action === "set") {
14
26
  if (!key || !value) {
15
- console.error(chalk.red("Usage: archbyte config set <key> <value>"));
16
- console.error(chalk.gray(" Keys: provider, api-key, model"));
17
- process.exit(1);
27
+ throw new ArchByteError("CONFIG_INVALID", "Usage: archbyte config set <key> <value>. Keys: provider, api-key, model", EXIT.CONFIG_INVALID);
18
28
  }
19
29
  setConfig(key, value);
20
30
  return;
21
31
  }
22
32
  if (action === "get") {
23
33
  if (!key) {
24
- console.error(chalk.red("Usage: archbyte config get <key>"));
25
- process.exit(1);
34
+ throw new ArchByteError("CONFIG_INVALID", "Usage: archbyte config get <key>", EXIT.CONFIG_INVALID);
26
35
  }
27
36
  getConfig(key, options.raw);
28
37
  return;
@@ -31,12 +40,7 @@ export async function handleConfig(options) {
31
40
  console.log(CONFIG_PATH);
32
41
  return;
33
42
  }
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);
43
+ throw new ArchByteError("CONFIG_INVALID", `Unknown action: ${action}. Valid: show, set, get, path`, EXIT.CONFIG_INVALID);
40
44
  }
41
45
  function loadConfig() {
42
46
  try {
@@ -114,8 +118,7 @@ function setConfig(key, value) {
114
118
  switch (key) {
115
119
  case "provider": {
116
120
  if (!VALID_PROVIDERS.includes(value)) {
117
- console.error(chalk.red(`Invalid provider: ${value}. Must be: ${VALID_PROVIDERS.join(", ")}`));
118
- process.exit(1);
121
+ throw new ArchByteError("CONFIG_INVALID", `Invalid provider: ${value}. Must be: ${VALID_PROVIDERS.join(", ")}`, EXIT.CONFIG_INVALID);
119
122
  }
120
123
  config.provider = value;
121
124
  if (value === "claude-sdk") {
@@ -134,8 +137,7 @@ function setConfig(key, value) {
134
137
  case "key": {
135
138
  const activeProvider = config.provider;
136
139
  if (!activeProvider) {
137
- console.error(chalk.red("Set a provider first: archbyte config set provider <name>"));
138
- process.exit(1);
140
+ throw new ArchByteError("CONFIG_MISSING", "Set a provider first: archbyte config set provider <name>", EXIT.CONFIG_MISSING);
139
141
  }
140
142
  if (!profiles[activeProvider])
141
143
  profiles[activeProvider] = {};
@@ -145,8 +147,7 @@ function setConfig(key, value) {
145
147
  case "model": {
146
148
  const activeProvider2 = config.provider;
147
149
  if (!activeProvider2) {
148
- console.error(chalk.red("Set a provider first: archbyte config set provider <name>"));
149
- process.exit(1);
150
+ throw new ArchByteError("CONFIG_MISSING", "Set a provider first: archbyte config set provider <name>", EXIT.CONFIG_MISSING);
150
151
  }
151
152
  if (!profiles[activeProvider2])
152
153
  profiles[activeProvider2] = {};
@@ -166,9 +167,7 @@ function setConfig(key, value) {
166
167
  break;
167
168
  }
168
169
  default:
169
- console.error(chalk.red(`Unknown config key: ${key}`));
170
- console.error(chalk.gray(" Valid keys: provider, api-key, model, sessions-path"));
171
- process.exit(1);
170
+ throw new ArchByteError("CONFIG_INVALID", `Unknown config key: ${key}. Valid keys: provider, api-key, model, sessions-path`, EXIT.CONFIG_INVALID);
172
171
  }
173
172
  saveConfig(config);
174
173
  if (key !== "provider") {
@@ -203,8 +202,7 @@ function getConfig(key, raw = false) {
203
202
  break;
204
203
  }
205
204
  default:
206
- console.error(chalk.red(`Unknown config key: ${key}`));
207
- process.exit(1);
205
+ throw new ArchByteError("CONFIG_INVALID", `Unknown config key: ${key}`, EXIT.CONFIG_INVALID);
208
206
  }
209
207
  }
210
208
  /**
@@ -3,6 +3,16 @@ interface ExportOptions {
3
3
  format?: string;
4
4
  output?: string;
5
5
  }
6
+ export interface ExportResult {
7
+ format: string;
8
+ content: string;
9
+ outputPath?: string;
10
+ }
11
+ /**
12
+ * Generate export content as structured data.
13
+ * Pure function — no console output, no process.exit().
14
+ */
15
+ export declare function generateExport(options: ExportOptions): Promise<ExportResult>;
6
16
  /**
7
17
  * Export architecture diagram to Mermaid, Markdown, or JSON format.
8
18
  */