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 +21 -82
- package/bin/archbyte.js +27 -1
- package/dist/cli/analyze.js +13 -4
- package/dist/cli/auth.d.ts +26 -0
- package/dist/cli/auth.js +85 -34
- package/dist/cli/config.js +20 -22
- package/dist/cli/export.d.ts +10 -0
- package/dist/cli/export.js +34 -27
- package/dist/cli/license-gate.js +10 -21
- package/dist/cli/output.d.ts +57 -0
- package/dist/cli/output.js +111 -0
- package/dist/cli/shared.d.ts +6 -1
- package/dist/cli/shared.js +20 -10
- package/dist/cli/stats.d.ts +36 -0
- package/dist/cli/stats.js +146 -90
- package/dist/cli/ui.js +27 -14
- package/dist/mcp/server.d.ts +13 -0
- package/dist/mcp/server.js +253 -0
- package/package.json +4 -2
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
|
|
185
|
+
### `archbyte mcp`
|
|
229
186
|
|
|
230
|
-
|
|
187
|
+
Start the MCP (Model Context Protocol) server for AI agent integration.
|
|
231
188
|
|
|
232
189
|
```bash
|
|
233
|
-
npx archbyte
|
|
234
|
-
npx archbyte diff --baseline old.json --current new.json
|
|
190
|
+
npx archbyte mcp # start MCP server on stdio
|
|
235
191
|
```
|
|
236
192
|
|
|
237
|
-
|
|
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
|
-
|
|
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.
|
|
267
|
+
program.parseAsync().catch((err) => {
|
|
268
|
+
handleError(err);
|
|
269
|
+
});
|
package/dist/cli/analyze.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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"}`));
|
package/dist/cli/auth.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
121
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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")}: ${
|
|
192
|
-
console.log(` ${chalk.bold("Tier")}: ${
|
|
193
|
-
console.log(` ${chalk.bold("Expires")}: ${new Date(
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
220
|
-
const isActive = email ===
|
|
270
|
+
for (const acct of data.accounts) {
|
|
271
|
+
const isActive = acct.email === data.active;
|
|
221
272
|
const marker = isActive ? chalk.green("*") : " ";
|
|
222
|
-
const tier =
|
|
223
|
-
const 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"));
|
package/dist/cli/config.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
207
|
-
process.exit(1);
|
|
205
|
+
throw new ArchByteError("CONFIG_INVALID", `Unknown config key: ${key}`, EXIT.CONFIG_INVALID);
|
|
208
206
|
}
|
|
209
207
|
}
|
|
210
208
|
/**
|
package/dist/cli/export.d.ts
CHANGED
|
@@ -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
|
*/
|