archbyte 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +282 -0
- package/bin/archbyte.js +213 -0
- package/dist/agents/core/component-detector.d.ts +2 -0
- package/dist/agents/core/component-detector.js +57 -0
- package/dist/agents/core/connection-mapper.d.ts +2 -0
- package/dist/agents/core/connection-mapper.js +77 -0
- package/dist/agents/core/doc-parser.d.ts +2 -0
- package/dist/agents/core/doc-parser.js +64 -0
- package/dist/agents/core/env-detector.d.ts +2 -0
- package/dist/agents/core/env-detector.js +51 -0
- package/dist/agents/core/event-detector.d.ts +2 -0
- package/dist/agents/core/event-detector.js +59 -0
- package/dist/agents/core/infra-analyzer.d.ts +2 -0
- package/dist/agents/core/infra-analyzer.js +72 -0
- package/dist/agents/core/structure-scanner.d.ts +2 -0
- package/dist/agents/core/structure-scanner.js +55 -0
- package/dist/agents/core/validator.d.ts +2 -0
- package/dist/agents/core/validator.js +74 -0
- package/dist/agents/index.d.ts +24 -0
- package/dist/agents/index.js +73 -0
- package/dist/agents/llm/index.d.ts +8 -0
- package/dist/agents/llm/index.js +185 -0
- package/dist/agents/llm/prompt-builder.d.ts +3 -0
- package/dist/agents/llm/prompt-builder.js +251 -0
- package/dist/agents/llm/response-parser.d.ts +6 -0
- package/dist/agents/llm/response-parser.js +174 -0
- package/dist/agents/llm/types.d.ts +31 -0
- package/dist/agents/llm/types.js +2 -0
- package/dist/agents/pipeline/agents/component-identifier.d.ts +3 -0
- package/dist/agents/pipeline/agents/component-identifier.js +102 -0
- package/dist/agents/pipeline/agents/connection-mapper.d.ts +3 -0
- package/dist/agents/pipeline/agents/connection-mapper.js +126 -0
- package/dist/agents/pipeline/agents/flow-detector.d.ts +3 -0
- package/dist/agents/pipeline/agents/flow-detector.js +101 -0
- package/dist/agents/pipeline/agents/service-describer.d.ts +3 -0
- package/dist/agents/pipeline/agents/service-describer.js +100 -0
- package/dist/agents/pipeline/agents/validator.d.ts +3 -0
- package/dist/agents/pipeline/agents/validator.js +102 -0
- package/dist/agents/pipeline/index.d.ts +13 -0
- package/dist/agents/pipeline/index.js +128 -0
- package/dist/agents/pipeline/merger.d.ts +7 -0
- package/dist/agents/pipeline/merger.js +212 -0
- package/dist/agents/pipeline/response-parser.d.ts +5 -0
- package/dist/agents/pipeline/response-parser.js +43 -0
- package/dist/agents/pipeline/types.d.ts +92 -0
- package/dist/agents/pipeline/types.js +3 -0
- package/dist/agents/prompt-data.d.ts +1 -0
- package/dist/agents/prompt-data.js +15 -0
- package/dist/agents/prompts-encode.d.ts +9 -0
- package/dist/agents/prompts-encode.js +26 -0
- package/dist/agents/prompts.d.ts +12 -0
- package/dist/agents/prompts.js +30 -0
- package/dist/agents/providers/anthropic.d.ts +10 -0
- package/dist/agents/providers/anthropic.js +117 -0
- package/dist/agents/providers/google.d.ts +10 -0
- package/dist/agents/providers/google.js +136 -0
- package/dist/agents/providers/ollama.d.ts +9 -0
- package/dist/agents/providers/ollama.js +162 -0
- package/dist/agents/providers/openai.d.ts +9 -0
- package/dist/agents/providers/openai.js +142 -0
- package/dist/agents/providers/router.d.ts +7 -0
- package/dist/agents/providers/router.js +55 -0
- package/dist/agents/runtime/orchestrator.d.ts +34 -0
- package/dist/agents/runtime/orchestrator.js +193 -0
- package/dist/agents/runtime/registry.d.ts +23 -0
- package/dist/agents/runtime/registry.js +56 -0
- package/dist/agents/runtime/types.d.ts +117 -0
- package/dist/agents/runtime/types.js +29 -0
- package/dist/agents/static/code-sampler.d.ts +3 -0
- package/dist/agents/static/code-sampler.js +153 -0
- package/dist/agents/static/component-detector.d.ts +3 -0
- package/dist/agents/static/component-detector.js +404 -0
- package/dist/agents/static/connection-mapper.d.ts +3 -0
- package/dist/agents/static/connection-mapper.js +280 -0
- package/dist/agents/static/doc-parser.d.ts +3 -0
- package/dist/agents/static/doc-parser.js +358 -0
- package/dist/agents/static/env-detector.d.ts +3 -0
- package/dist/agents/static/env-detector.js +73 -0
- package/dist/agents/static/event-detector.d.ts +3 -0
- package/dist/agents/static/event-detector.js +70 -0
- package/dist/agents/static/file-tree-collector.d.ts +3 -0
- package/dist/agents/static/file-tree-collector.js +51 -0
- package/dist/agents/static/index.d.ts +19 -0
- package/dist/agents/static/index.js +307 -0
- package/dist/agents/static/infra-analyzer.d.ts +3 -0
- package/dist/agents/static/infra-analyzer.js +208 -0
- package/dist/agents/static/structure-scanner.d.ts +3 -0
- package/dist/agents/static/structure-scanner.js +195 -0
- package/dist/agents/static/types.d.ts +165 -0
- package/dist/agents/static/types.js +2 -0
- package/dist/agents/static/utils.d.ts +21 -0
- package/dist/agents/static/utils.js +146 -0
- package/dist/agents/static/validator.d.ts +2 -0
- package/dist/agents/static/validator.js +75 -0
- package/dist/agents/tools/claude-code.d.ts +38 -0
- package/dist/agents/tools/claude-code.js +129 -0
- package/dist/agents/tools/local-fs.d.ts +12 -0
- package/dist/agents/tools/local-fs.js +112 -0
- package/dist/agents/tools/tool-definitions.d.ts +6 -0
- package/dist/agents/tools/tool-definitions.js +66 -0
- package/dist/cli/analyze.d.ts +27 -0
- package/dist/cli/analyze.js +586 -0
- package/dist/cli/auth.d.ts +46 -0
- package/dist/cli/auth.js +397 -0
- package/dist/cli/config.d.ts +11 -0
- package/dist/cli/config.js +177 -0
- package/dist/cli/diff.d.ts +10 -0
- package/dist/cli/diff.js +144 -0
- package/dist/cli/export.d.ts +10 -0
- package/dist/cli/export.js +321 -0
- package/dist/cli/gate.d.ts +13 -0
- package/dist/cli/gate.js +131 -0
- package/dist/cli/generate.d.ts +10 -0
- package/dist/cli/generate.js +213 -0
- package/dist/cli/license-gate.d.ts +27 -0
- package/dist/cli/license-gate.js +121 -0
- package/dist/cli/patrol.d.ts +15 -0
- package/dist/cli/patrol.js +212 -0
- package/dist/cli/run.d.ts +11 -0
- package/dist/cli/run.js +24 -0
- package/dist/cli/serve.d.ts +9 -0
- package/dist/cli/serve.js +65 -0
- package/dist/cli/setup.d.ts +1 -0
- package/dist/cli/setup.js +233 -0
- package/dist/cli/shared.d.ts +68 -0
- package/dist/cli/shared.js +275 -0
- package/dist/cli/stats.d.ts +9 -0
- package/dist/cli/stats.js +158 -0
- package/dist/cli/ui.d.ts +18 -0
- package/dist/cli/ui.js +144 -0
- package/dist/cli/validate.d.ts +54 -0
- package/dist/cli/validate.js +315 -0
- package/dist/cli/workflow.d.ts +10 -0
- package/dist/cli/workflow.js +594 -0
- package/dist/server/src/generator/index.d.ts +123 -0
- package/dist/server/src/generator/index.js +254 -0
- package/dist/server/src/index.d.ts +8 -0
- package/dist/server/src/index.js +1311 -0
- package/package.json +62 -0
- package/ui/dist/assets/index-B66Til39.js +70 -0
- package/ui/dist/assets/index-BE2OWbzu.css +1 -0
- package/ui/dist/index.html +14 -0
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
import * as path from "path";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { resolveConfig } from "./config.js";
|
|
5
|
+
import { recordUsage } from "./license-gate.js";
|
|
6
|
+
export async function handleAnalyze(options) {
|
|
7
|
+
const rootDir = process.cwd();
|
|
8
|
+
const isStaticOnly = options.static || options.skipLlm;
|
|
9
|
+
console.log();
|
|
10
|
+
console.log(chalk.bold.cyan("ArchByte Analyzer"));
|
|
11
|
+
if (isStaticOnly) {
|
|
12
|
+
console.log(chalk.gray("Static-only mode: deterministic analysis, no model"));
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
console.log(chalk.gray("Static context + model pipeline"));
|
|
16
|
+
}
|
|
17
|
+
console.log();
|
|
18
|
+
// ─── Dry-run mode ───
|
|
19
|
+
if (options.dryRun) {
|
|
20
|
+
const config = resolveConfig();
|
|
21
|
+
const provider = options.provider ?? config?.provider ?? "none";
|
|
22
|
+
const mode = isStaticOnly ? "static (no LLM)" : "pipeline (static + LLM)";
|
|
23
|
+
const diagramPath = path.join(rootDir, ".archbyte", "architecture.json");
|
|
24
|
+
const diagramExists = fs.existsSync(diagramPath);
|
|
25
|
+
const outputPath = options.output ?? ".archbyte/analysis.json";
|
|
26
|
+
console.log(chalk.bold("ArchByte Analyze — Dry Run"));
|
|
27
|
+
console.log(chalk.gray(` Project: ${path.basename(rootDir)}`));
|
|
28
|
+
console.log(chalk.gray(` Mode: ${mode}`));
|
|
29
|
+
console.log(chalk.gray(` Provider: ${provider}`));
|
|
30
|
+
console.log(chalk.gray(` Diagram: ${diagramExists ? "exists" : "not found"}`));
|
|
31
|
+
console.log(chalk.gray(` Output: ${outputPath}`));
|
|
32
|
+
console.log();
|
|
33
|
+
console.log(chalk.yellow("No changes made. Remove --dry-run to execute."));
|
|
34
|
+
console.log();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
// ─── Premium-only mode (unchanged — uses Orchestrator) ───
|
|
38
|
+
if (options.premiumOnly) {
|
|
39
|
+
await runLegacyOrchestrator(rootDir, options, true);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// ─── Static-only mode (--static / --skip-llm) ───
|
|
43
|
+
if (isStaticOnly) {
|
|
44
|
+
const startTime = Date.now();
|
|
45
|
+
console.log(chalk.bold("Running static analysis (no model)..."));
|
|
46
|
+
console.log();
|
|
47
|
+
const { runStaticAnalysis } = await import("../agents/static/index.js");
|
|
48
|
+
const result = await runStaticAnalysis(rootDir, (msg) => {
|
|
49
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
50
|
+
console.log(chalk.gray(` [${elapsed}s] ${msg}`));
|
|
51
|
+
});
|
|
52
|
+
const analysis = buildAnalysisFromStatic(result, rootDir);
|
|
53
|
+
const duration = Date.now() - startTime;
|
|
54
|
+
// Stamp scan metadata
|
|
55
|
+
const meta = analysis.metadata;
|
|
56
|
+
meta.durationMs = duration;
|
|
57
|
+
meta.mode = "static";
|
|
58
|
+
writeAnalysis(rootDir, analysis);
|
|
59
|
+
await autoGenerate(rootDir, options);
|
|
60
|
+
printSummary(analysis, duration, "static");
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
// ─── Default mode: static context → LLM pipeline ───
|
|
64
|
+
// 1. Resolve LLM config
|
|
65
|
+
let config = resolveConfig();
|
|
66
|
+
if (options.provider) {
|
|
67
|
+
config = {
|
|
68
|
+
provider: options.provider,
|
|
69
|
+
apiKey: options.apiKey ?? config?.apiKey ?? "",
|
|
70
|
+
ollamaBaseUrl: config?.ollamaBaseUrl,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
if (options.apiKey && config) {
|
|
74
|
+
config.apiKey = options.apiKey;
|
|
75
|
+
}
|
|
76
|
+
if (!config) {
|
|
77
|
+
console.error(chalk.red("No model provider configured."));
|
|
78
|
+
console.error();
|
|
79
|
+
console.error(chalk.bold("Set up with:"));
|
|
80
|
+
console.error(chalk.gray(" archbyte config set provider anthropic"));
|
|
81
|
+
console.error(chalk.gray(" archbyte config set api-key sk-ant-..."));
|
|
82
|
+
console.error();
|
|
83
|
+
console.error(chalk.bold("Or use environment variables:"));
|
|
84
|
+
console.error(chalk.gray(" export ARCHBYTE_PROVIDER=anthropic"));
|
|
85
|
+
console.error(chalk.gray(" export ARCHBYTE_API_KEY=sk-ant-..."));
|
|
86
|
+
console.error();
|
|
87
|
+
console.error(chalk.bold("Or run without a model:"));
|
|
88
|
+
console.error(chalk.gray(" archbyte analyze --static"));
|
|
89
|
+
console.error();
|
|
90
|
+
console.error(chalk.bold("Supported providers:"));
|
|
91
|
+
console.error(chalk.gray(" anthropic, openai, google, ollama"));
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
console.log(chalk.gray(`Provider: ${chalk.white(config.provider)}`));
|
|
95
|
+
console.log(chalk.gray(`Project: ${chalk.white(path.basename(rootDir))}`));
|
|
96
|
+
console.log();
|
|
97
|
+
// 2. Create provider
|
|
98
|
+
const { createProvider } = await import("../agents/providers/router.js");
|
|
99
|
+
let provider;
|
|
100
|
+
try {
|
|
101
|
+
provider = createProvider(config);
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
console.error(chalk.red(`Failed to create ${config.provider} provider: ${err instanceof Error ? err.message : err}`));
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
// 3. Run static context collection → LLM pipeline
|
|
108
|
+
const startTime = Date.now();
|
|
109
|
+
console.log(chalk.bold("Phase 1: Collecting static context..."));
|
|
110
|
+
const { runStaticContextCollection } = await import("../agents/static/index.js");
|
|
111
|
+
const ctx = await runStaticContextCollection(rootDir, (msg) => {
|
|
112
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
113
|
+
console.log(chalk.gray(` [${elapsed}s] ${msg}`));
|
|
114
|
+
});
|
|
115
|
+
// Save static context for debugging / re-runs
|
|
116
|
+
const ctxPath = path.join(rootDir, ".archbyte", "static-context.json");
|
|
117
|
+
if (!fs.existsSync(path.dirname(ctxPath))) {
|
|
118
|
+
fs.mkdirSync(path.dirname(ctxPath), { recursive: true });
|
|
119
|
+
}
|
|
120
|
+
fs.writeFileSync(ctxPath, JSON.stringify(ctx, null, 2), "utf-8");
|
|
121
|
+
console.log(chalk.gray(` Saved static context to: .archbyte/static-context.json`));
|
|
122
|
+
console.log();
|
|
123
|
+
console.log(chalk.bold("Phase 2: Running model pipeline (3 parallel + 2 sequential)..."));
|
|
124
|
+
const { runPipeline } = await import("../agents/pipeline/index.js");
|
|
125
|
+
let result;
|
|
126
|
+
try {
|
|
127
|
+
result = await runPipeline(ctx, provider, config, (msg) => {
|
|
128
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
129
|
+
console.log(chalk.gray(` [${elapsed}s] ${msg}`));
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
134
|
+
writeAnalysisStatus(rootDir, "error", errorMsg);
|
|
135
|
+
console.error(chalk.yellow(`\n Pipeline failed: ${errorMsg}`));
|
|
136
|
+
console.log(chalk.bold.cyan("\n Falling back to static-only analysis..."));
|
|
137
|
+
try {
|
|
138
|
+
const { runStaticAnalysis } = await import("../agents/static/index.js");
|
|
139
|
+
const staticResult = await runStaticAnalysis(rootDir, (msg) => {
|
|
140
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
141
|
+
console.log(chalk.gray(` [${elapsed}s] ${msg}`));
|
|
142
|
+
});
|
|
143
|
+
const analysis = buildAnalysisFromStatic(staticResult, rootDir);
|
|
144
|
+
const duration = Date.now() - startTime;
|
|
145
|
+
const meta = analysis.metadata;
|
|
146
|
+
meta.durationMs = duration;
|
|
147
|
+
meta.mode = "static-fallback";
|
|
148
|
+
meta.pipelineError = errorMsg;
|
|
149
|
+
writeAnalysis(rootDir, analysis);
|
|
150
|
+
writeAnalysisStatus(rootDir, "success");
|
|
151
|
+
await autoGenerate(rootDir, options);
|
|
152
|
+
printSummary(analysis, duration, "static");
|
|
153
|
+
}
|
|
154
|
+
catch (fallbackErr) {
|
|
155
|
+
console.error(chalk.red(` Static fallback also failed: ${fallbackErr instanceof Error ? fallbackErr.message : String(fallbackErr)}`));
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
console.log();
|
|
161
|
+
// 4. Convert to analysis.json format and write
|
|
162
|
+
const analysis = buildAnalysisFromStatic(result, rootDir);
|
|
163
|
+
const duration = Date.now() - startTime;
|
|
164
|
+
// Stamp scan metadata
|
|
165
|
+
const meta = analysis.metadata;
|
|
166
|
+
meta.durationMs = duration;
|
|
167
|
+
meta.filesScanned = ctx.fileTree.totalFiles;
|
|
168
|
+
meta.mode = "pipeline";
|
|
169
|
+
if (result.tokenUsage) {
|
|
170
|
+
meta.tokenUsage = { input: result.tokenUsage.input, output: result.tokenUsage.output };
|
|
171
|
+
}
|
|
172
|
+
writeAnalysis(rootDir, analysis);
|
|
173
|
+
writeAnalysisStatus(rootDir, "success");
|
|
174
|
+
await autoGenerate(rootDir, options);
|
|
175
|
+
// Record usage (best-effort, non-blocking)
|
|
176
|
+
recordUsage({
|
|
177
|
+
projectName: path.basename(rootDir),
|
|
178
|
+
agentCount: 5,
|
|
179
|
+
durationMs: duration,
|
|
180
|
+
});
|
|
181
|
+
printSummary(analysis, duration, "pipeline");
|
|
182
|
+
}
|
|
183
|
+
// ─── Legacy orchestrator (--legacy and --premium-only) ───
|
|
184
|
+
async function runLegacyOrchestrator(rootDir, options, premiumOnly) {
|
|
185
|
+
// Resolve LLM config
|
|
186
|
+
let config = resolveConfig();
|
|
187
|
+
if (options.provider) {
|
|
188
|
+
config = {
|
|
189
|
+
provider: options.provider,
|
|
190
|
+
apiKey: options.apiKey ?? config?.apiKey ?? "",
|
|
191
|
+
ollamaBaseUrl: config?.ollamaBaseUrl,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
if (options.apiKey && config) {
|
|
195
|
+
config.apiKey = options.apiKey;
|
|
196
|
+
}
|
|
197
|
+
if (!config) {
|
|
198
|
+
console.error(chalk.red("No model provider configured."));
|
|
199
|
+
console.error(chalk.gray(" archbyte config set provider anthropic"));
|
|
200
|
+
console.error(chalk.gray(" archbyte config set api-key sk-ant-..."));
|
|
201
|
+
process.exit(1);
|
|
202
|
+
}
|
|
203
|
+
console.log(chalk.gray(`Provider: ${chalk.white(config.provider)}`));
|
|
204
|
+
console.log(chalk.gray(`Project: ${chalk.white(path.basename(rootDir))}`));
|
|
205
|
+
console.log();
|
|
206
|
+
const { createProvider } = await import("../agents/providers/router.js");
|
|
207
|
+
const { Orchestrator } = await import("../agents/runtime/orchestrator.js");
|
|
208
|
+
const { LocalFSBackend } = await import("../agents/tools/local-fs.js");
|
|
209
|
+
const { CORE_AGENTS, loadPremiumAgents } = await import("../agents/index.js");
|
|
210
|
+
let provider;
|
|
211
|
+
try {
|
|
212
|
+
provider = createProvider(config);
|
|
213
|
+
}
|
|
214
|
+
catch (err) {
|
|
215
|
+
console.error(chalk.red(`Failed to create ${config.provider} provider: ${err instanceof Error ? err.message : err}`));
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
const tools = new LocalFSBackend(rootDir);
|
|
219
|
+
// Build license — use server-verified tier cache, not unverified JWT
|
|
220
|
+
const { loadCredentials, getVerifiedTier } = await import("./auth.js");
|
|
221
|
+
const creds = loadCredentials();
|
|
222
|
+
const verifiedTier = getVerifiedTier();
|
|
223
|
+
const license = {
|
|
224
|
+
tier: verifiedTier,
|
|
225
|
+
userId: "local",
|
|
226
|
+
email: creds?.email ?? "local",
|
|
227
|
+
scansUsed: 0,
|
|
228
|
+
scansAllowed: -1, // unlimited for all tiers
|
|
229
|
+
expiresAt: creds?.expiresAt ?? new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
|
|
230
|
+
isValid: true,
|
|
231
|
+
};
|
|
232
|
+
const orchestrator = new Orchestrator({ provider, tools, projectRoot: rootDir, config, license });
|
|
233
|
+
// Premium agents loaded dynamically (not shipped in npm package)
|
|
234
|
+
const PREMIUM_AGENTS = await loadPremiumAgents();
|
|
235
|
+
const ALL_AGENTS = [...CORE_AGENTS, ...PREMIUM_AGENTS];
|
|
236
|
+
if (premiumOnly) {
|
|
237
|
+
if (PREMIUM_AGENTS.length === 0) {
|
|
238
|
+
console.error(chalk.red("Premium agents are not available in this installation."));
|
|
239
|
+
process.exit(1);
|
|
240
|
+
}
|
|
241
|
+
orchestrator.registerAll(PREMIUM_AGENTS);
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
orchestrator.registerAll(ALL_AGENTS);
|
|
245
|
+
}
|
|
246
|
+
const activeAgents = premiumOnly
|
|
247
|
+
? PREMIUM_AGENTS
|
|
248
|
+
: license.tier === "premium" && PREMIUM_AGENTS.length > 0
|
|
249
|
+
? ALL_AGENTS
|
|
250
|
+
: CORE_AGENTS;
|
|
251
|
+
const startTime = Date.now();
|
|
252
|
+
console.log(chalk.bold("Running legacy analysis pipeline..."));
|
|
253
|
+
console.log(chalk.gray(`Agents: ${activeAgents.length} (${activeAgents.filter(a => a.phase === "parallel").length} parallel, ${activeAgents.filter(a => a.phase === "sequential").length} sequential)`));
|
|
254
|
+
if (license.tier === "premium") {
|
|
255
|
+
console.log(chalk.magenta(` Pro agents: ${PREMIUM_AGENTS.length}`));
|
|
256
|
+
}
|
|
257
|
+
console.log();
|
|
258
|
+
const result = await orchestrator.run((msg) => {
|
|
259
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
260
|
+
console.log(chalk.gray(` [${elapsed}s] ${msg}`));
|
|
261
|
+
});
|
|
262
|
+
console.log();
|
|
263
|
+
if (result.errors.length > 0) {
|
|
264
|
+
console.log(chalk.yellow(`Completed with ${result.errors.length} error(s):`));
|
|
265
|
+
for (const err of result.errors) {
|
|
266
|
+
console.log(chalk.red(` ${err.agentId}: ${err.error}`));
|
|
267
|
+
}
|
|
268
|
+
console.log();
|
|
269
|
+
}
|
|
270
|
+
const archbyteDir = path.join(rootDir, ".archbyte");
|
|
271
|
+
if (!fs.existsSync(archbyteDir)) {
|
|
272
|
+
fs.mkdirSync(archbyteDir, { recursive: true });
|
|
273
|
+
}
|
|
274
|
+
if (premiumOnly) {
|
|
275
|
+
const premiumResults = result.agents.map((a) => ({
|
|
276
|
+
agentId: a.agentId,
|
|
277
|
+
confidence: a.confidence,
|
|
278
|
+
timestamp: new Date().toISOString(),
|
|
279
|
+
data: a.data,
|
|
280
|
+
}));
|
|
281
|
+
const premiumPath = path.join(archbyteDir, "premium-results.json");
|
|
282
|
+
fs.writeFileSync(premiumPath, JSON.stringify(premiumResults, null, 2), "utf-8");
|
|
283
|
+
console.log(chalk.green(`Saved premium results to: ${path.relative(rootDir, premiumPath)}`));
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
const analysis = buildAnalysis(result.agents, rootDir);
|
|
287
|
+
writeAnalysis(rootDir, analysis);
|
|
288
|
+
await autoGenerate(rootDir, options);
|
|
289
|
+
}
|
|
290
|
+
// Telemetry
|
|
291
|
+
const telemetry = {
|
|
292
|
+
timestamp: new Date().toISOString(),
|
|
293
|
+
provider: config.provider,
|
|
294
|
+
totalDuration: result.totalDuration,
|
|
295
|
+
agents: result.agents.map((a) => ({
|
|
296
|
+
id: a.agentId,
|
|
297
|
+
duration: a.duration,
|
|
298
|
+
confidence: a.confidence,
|
|
299
|
+
})),
|
|
300
|
+
errors: result.errors,
|
|
301
|
+
};
|
|
302
|
+
const telemetryPath = path.join(archbyteDir, "telemetry.json");
|
|
303
|
+
fs.writeFileSync(telemetryPath, JSON.stringify(telemetry, null, 2), "utf-8");
|
|
304
|
+
recordUsage({
|
|
305
|
+
projectName: path.basename(rootDir),
|
|
306
|
+
agentCount: result.agents.length,
|
|
307
|
+
durationMs: result.totalDuration,
|
|
308
|
+
});
|
|
309
|
+
// Summary
|
|
310
|
+
console.log();
|
|
311
|
+
console.log(chalk.bold.green(premiumOnly ? "Pro analysis complete!" : "Legacy analysis complete!"));
|
|
312
|
+
console.log(chalk.gray(` Duration: ${(result.totalDuration / 1000).toFixed(1)}s`));
|
|
313
|
+
console.log(chalk.gray(` Agents: ${result.agents.length}/${activeAgents.length} succeeded`));
|
|
314
|
+
if (!premiumOnly) {
|
|
315
|
+
const analysisPath = path.join(rootDir, ".archbyte", "analysis.json");
|
|
316
|
+
try {
|
|
317
|
+
const analysis = JSON.parse(fs.readFileSync(analysisPath, "utf-8"));
|
|
318
|
+
console.log(chalk.gray(` Components: ${(analysis.components ?? []).length}`));
|
|
319
|
+
console.log(chalk.gray(` Connections: ${(analysis.connections ?? []).length}`));
|
|
320
|
+
}
|
|
321
|
+
catch { /* ignore */ }
|
|
322
|
+
}
|
|
323
|
+
console.log();
|
|
324
|
+
if (premiumOnly) {
|
|
325
|
+
console.log(chalk.bold("Results saved to .archbyte/premium-results.json"));
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
console.log(chalk.bold("Next steps:"));
|
|
329
|
+
console.log(chalk.gray(` ${chalk.cyan("archbyte serve")} -- view the architecture diagram`));
|
|
330
|
+
console.log(chalk.gray(` ${chalk.cyan("archbyte validate")} -- check architecture fitness rules`));
|
|
331
|
+
console.log(chalk.gray(` ${chalk.cyan("archbyte export")} -- export to mermaid, plantuml, etc.`));
|
|
332
|
+
}
|
|
333
|
+
console.log();
|
|
334
|
+
}
|
|
335
|
+
// ─── Shared helpers ───
|
|
336
|
+
function writeAnalysisStatus(rootDir, status, error) {
|
|
337
|
+
const archbyteDir = path.join(rootDir, ".archbyte");
|
|
338
|
+
if (!fs.existsSync(archbyteDir)) {
|
|
339
|
+
fs.mkdirSync(archbyteDir, { recursive: true });
|
|
340
|
+
}
|
|
341
|
+
const statusPath = path.join(archbyteDir, "analysis-status.json");
|
|
342
|
+
fs.writeFileSync(statusPath, JSON.stringify({
|
|
343
|
+
status,
|
|
344
|
+
...(error ? { error } : {}),
|
|
345
|
+
timestamp: new Date().toISOString(),
|
|
346
|
+
}, null, 2), "utf-8");
|
|
347
|
+
}
|
|
348
|
+
function writeAnalysis(rootDir, analysis) {
|
|
349
|
+
const archbyteDir = path.join(rootDir, ".archbyte");
|
|
350
|
+
if (!fs.existsSync(archbyteDir)) {
|
|
351
|
+
fs.mkdirSync(archbyteDir, { recursive: true });
|
|
352
|
+
}
|
|
353
|
+
const analysisPath = path.join(archbyteDir, "analysis.json");
|
|
354
|
+
fs.writeFileSync(analysisPath, JSON.stringify(analysis, null, 2), "utf-8");
|
|
355
|
+
console.log(chalk.green(`Saved analysis to: ${path.relative(rootDir, analysisPath)}`));
|
|
356
|
+
}
|
|
357
|
+
async function autoGenerate(rootDir, options) {
|
|
358
|
+
console.log();
|
|
359
|
+
console.log(chalk.gray("Generating architecture diagram..."));
|
|
360
|
+
const analysisPath = path.join(rootDir, ".archbyte", "analysis.json");
|
|
361
|
+
try {
|
|
362
|
+
const { handleGenerate } = await import("./generate.js");
|
|
363
|
+
await handleGenerate({ input: analysisPath, verbose: options.verbose });
|
|
364
|
+
}
|
|
365
|
+
catch (err) {
|
|
366
|
+
console.error(chalk.yellow(`Warning: diagram generation failed: ${err instanceof Error ? err.message : err}`));
|
|
367
|
+
console.error(chalk.gray("You can run 'archbyte generate' manually."));
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
function printSummary(analysis, durationMs, mode) {
|
|
371
|
+
const components = analysis.components ?? [];
|
|
372
|
+
const connections = analysis.connections ?? [];
|
|
373
|
+
console.log();
|
|
374
|
+
console.log(chalk.bold.green("Analysis complete!"));
|
|
375
|
+
console.log(chalk.gray(` Mode: ${mode === "pipeline" ? "static + agents" : "static"}`));
|
|
376
|
+
console.log(chalk.gray(` Duration: ${(durationMs / 1000).toFixed(1)}s`));
|
|
377
|
+
console.log(chalk.gray(` Components: ${components.length}`));
|
|
378
|
+
console.log(chalk.gray(` Connections: ${connections.length}`));
|
|
379
|
+
console.log();
|
|
380
|
+
console.log(chalk.bold("Next steps:"));
|
|
381
|
+
console.log(chalk.gray(` ${chalk.cyan("archbyte serve")} -- view the architecture diagram`));
|
|
382
|
+
console.log(chalk.gray(` ${chalk.cyan("archbyte validate")} -- check architecture fitness rules`));
|
|
383
|
+
console.log(chalk.gray(` ${chalk.cyan("archbyte export")} -- export to mermaid, plantuml, etc.`));
|
|
384
|
+
console.log();
|
|
385
|
+
}
|
|
386
|
+
// ─── Analysis converters ───
|
|
387
|
+
/**
|
|
388
|
+
* Convert a StaticAnalysisResult (from pipeline or static-only) into the
|
|
389
|
+
* flat analysis.json format expected by generate.ts.
|
|
390
|
+
*/
|
|
391
|
+
export function buildAnalysisFromStatic(result, projectRoot) {
|
|
392
|
+
const allComponents = result.components.components;
|
|
393
|
+
// Partition components by role
|
|
394
|
+
const appComponents = allComponents
|
|
395
|
+
.filter((c) => c.layer !== "external" && c.type !== "database" && c.layer !== "data")
|
|
396
|
+
.map((c) => ({
|
|
397
|
+
id: c.id,
|
|
398
|
+
name: c.name,
|
|
399
|
+
type: mapComponentType(c.type),
|
|
400
|
+
path: c.path,
|
|
401
|
+
techStack: c.technologies,
|
|
402
|
+
description: c.description,
|
|
403
|
+
layer: c.layer,
|
|
404
|
+
}));
|
|
405
|
+
const databases = allComponents
|
|
406
|
+
.filter((c) => c.type === "database" || c.layer === "data")
|
|
407
|
+
.map((c) => ({
|
|
408
|
+
id: c.id,
|
|
409
|
+
name: c.name,
|
|
410
|
+
type: c.technologies[0] ?? "database",
|
|
411
|
+
}));
|
|
412
|
+
const externalServices = allComponents
|
|
413
|
+
.filter((c) => c.layer === "external")
|
|
414
|
+
.map((c) => ({
|
|
415
|
+
id: c.id,
|
|
416
|
+
name: c.name,
|
|
417
|
+
type: c.technologies[0] ?? "api",
|
|
418
|
+
}));
|
|
419
|
+
// Connections
|
|
420
|
+
const mappedConnections = result.connections.connections.map((c) => ({
|
|
421
|
+
from: c.from,
|
|
422
|
+
to: c.to,
|
|
423
|
+
type: c.type,
|
|
424
|
+
description: c.description,
|
|
425
|
+
async: c.async,
|
|
426
|
+
}));
|
|
427
|
+
// Environments
|
|
428
|
+
const environments = result.envs.environments.map((e) => ({
|
|
429
|
+
name: e.name,
|
|
430
|
+
label: e.name.charAt(0).toUpperCase() + e.name.slice(1),
|
|
431
|
+
color: e.name === "production" ? "#ef4444" : e.name === "staging" ? "#f59e0b" : "#22c55e",
|
|
432
|
+
}));
|
|
433
|
+
// Flows — only keep steps that are valid "componentId → componentId" pairs
|
|
434
|
+
const allComponentIds = new Set(allComponents.map((c) => c.id));
|
|
435
|
+
const flows = result.connections.flows.map((f, i) => ({
|
|
436
|
+
id: `flow-${i}`,
|
|
437
|
+
name: f.name,
|
|
438
|
+
description: f.description,
|
|
439
|
+
steps: f.steps
|
|
440
|
+
.map((step) => {
|
|
441
|
+
const parts = step.split("\u2192").map((s) => s.trim());
|
|
442
|
+
const from = parts[0] ?? "";
|
|
443
|
+
const to = parts[1] ?? "";
|
|
444
|
+
return { from, to, label: step };
|
|
445
|
+
})
|
|
446
|
+
.filter((s) => s.from && s.to && allComponentIds.has(s.from) && allComponentIds.has(s.to)),
|
|
447
|
+
})).filter((f) => f.steps.length > 0);
|
|
448
|
+
return {
|
|
449
|
+
project: {
|
|
450
|
+
name: result.structure.projectName ?? path.basename(projectRoot),
|
|
451
|
+
description: result.docs.projectDescription ?? "",
|
|
452
|
+
primaryLanguage: result.structure.language,
|
|
453
|
+
isMonorepo: result.structure.isMonorepo,
|
|
454
|
+
},
|
|
455
|
+
components: appComponents,
|
|
456
|
+
databases,
|
|
457
|
+
externalServices,
|
|
458
|
+
connections: mappedConnections,
|
|
459
|
+
environments,
|
|
460
|
+
flows,
|
|
461
|
+
metadata: {
|
|
462
|
+
analyzedAt: new Date().toISOString(),
|
|
463
|
+
validation: result.validation,
|
|
464
|
+
gaps: result.gaps,
|
|
465
|
+
},
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Aggregate agent results into the analysis.json format expected by the generator.
|
|
470
|
+
* Used by legacy mode (--legacy flag) only.
|
|
471
|
+
*/
|
|
472
|
+
export function buildAnalysis(agents, projectRoot) {
|
|
473
|
+
const getAgentData = (id) => {
|
|
474
|
+
const agent = agents.find((a) => a.agentId === id);
|
|
475
|
+
if (!agent?.data || typeof agent.data !== "object")
|
|
476
|
+
return {};
|
|
477
|
+
return agent.data;
|
|
478
|
+
};
|
|
479
|
+
const structure = getAgentData("structure-scanner");
|
|
480
|
+
const components = getAgentData("component-detector");
|
|
481
|
+
const connections = getAgentData("connection-mapper");
|
|
482
|
+
const docs = getAgentData("doc-parser");
|
|
483
|
+
const envs = getAgentData("env-detector");
|
|
484
|
+
const validation = getAgentData("validator");
|
|
485
|
+
const componentList = components.components ?? [];
|
|
486
|
+
const mappedComponents = componentList.map((c, i) => ({
|
|
487
|
+
id: slugify(c.name) ?? `component-${i}`,
|
|
488
|
+
name: c.name ?? `Component ${i}`,
|
|
489
|
+
type: mapComponentType(c.type),
|
|
490
|
+
path: c.path ?? "",
|
|
491
|
+
techStack: c.technologies ?? [],
|
|
492
|
+
description: c.description ?? "",
|
|
493
|
+
layer: c.layer ?? "application",
|
|
494
|
+
}));
|
|
495
|
+
const connectionList = connections.connections ?? [];
|
|
496
|
+
const mappedConnections = connectionList.map((c) => ({
|
|
497
|
+
from: slugify(c.source) ?? "",
|
|
498
|
+
to: slugify(c.target) ?? "",
|
|
499
|
+
type: c.type ?? "import",
|
|
500
|
+
description: c.label ?? "",
|
|
501
|
+
async: c.style === "dashed",
|
|
502
|
+
}));
|
|
503
|
+
const databases = mappedComponents
|
|
504
|
+
.filter((c) => c.type === "database" || c.layer === "data")
|
|
505
|
+
.map((c) => ({
|
|
506
|
+
id: c.id,
|
|
507
|
+
name: c.name,
|
|
508
|
+
type: "postgresql",
|
|
509
|
+
}));
|
|
510
|
+
const externalServices = mappedComponents
|
|
511
|
+
.filter((c) => c.layer === "external")
|
|
512
|
+
.map((c) => ({
|
|
513
|
+
id: c.id,
|
|
514
|
+
name: c.name,
|
|
515
|
+
type: "api",
|
|
516
|
+
}));
|
|
517
|
+
const appComponents = mappedComponents.filter((c) => c.layer !== "external" && c.type !== "database");
|
|
518
|
+
const envList = envs.environments ?? [];
|
|
519
|
+
const environments = envList.map((e) => ({
|
|
520
|
+
name: e.name ?? "default",
|
|
521
|
+
label: (e.name ?? "default").charAt(0).toUpperCase() + (e.name ?? "default").slice(1),
|
|
522
|
+
color: e.name === "production" ? "#ef4444" : e.name === "staging" ? "#f59e0b" : "#22c55e",
|
|
523
|
+
}));
|
|
524
|
+
const flowList = connections.flows ?? [];
|
|
525
|
+
const flows = flowList.map((f, i) => ({
|
|
526
|
+
id: `flow-${i}`,
|
|
527
|
+
name: f.name ?? `Flow ${i}`,
|
|
528
|
+
description: f.description ?? "",
|
|
529
|
+
steps: (f.steps ?? []).map((step) => {
|
|
530
|
+
const parts = step.split("\u2192").map((s) => s.trim());
|
|
531
|
+
return {
|
|
532
|
+
from: slugify(parts[0]) ?? "",
|
|
533
|
+
to: slugify(parts[1]) ?? "",
|
|
534
|
+
label: step,
|
|
535
|
+
};
|
|
536
|
+
}),
|
|
537
|
+
}));
|
|
538
|
+
return {
|
|
539
|
+
project: {
|
|
540
|
+
name: structure.projectName ?? path.basename(projectRoot),
|
|
541
|
+
description: docs.projectDescription ?? "",
|
|
542
|
+
primaryLanguage: structure.language,
|
|
543
|
+
isMonorepo: structure.isMonorepo ?? false,
|
|
544
|
+
},
|
|
545
|
+
components: appComponents,
|
|
546
|
+
databases,
|
|
547
|
+
externalServices,
|
|
548
|
+
connections: mappedConnections,
|
|
549
|
+
environments,
|
|
550
|
+
flows,
|
|
551
|
+
metadata: {
|
|
552
|
+
analyzedAt: new Date().toISOString(),
|
|
553
|
+
agentResults: agents.map((a) => ({
|
|
554
|
+
id: a.agentId,
|
|
555
|
+
confidence: a.confidence,
|
|
556
|
+
})),
|
|
557
|
+
validation,
|
|
558
|
+
},
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
function slugify(name) {
|
|
562
|
+
if (!name)
|
|
563
|
+
return undefined;
|
|
564
|
+
return name
|
|
565
|
+
.toLowerCase()
|
|
566
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
567
|
+
.replace(/^-|-$/g, "");
|
|
568
|
+
}
|
|
569
|
+
function mapComponentType(type) {
|
|
570
|
+
if (!type)
|
|
571
|
+
return "service";
|
|
572
|
+
const t = type.toLowerCase();
|
|
573
|
+
if (t.includes("frontend") || t.includes("ui") || t.includes("web"))
|
|
574
|
+
return "frontend";
|
|
575
|
+
if (t.includes("api") || t.includes("gateway"))
|
|
576
|
+
return "api";
|
|
577
|
+
if (t.includes("database") || t.includes("db"))
|
|
578
|
+
return "database";
|
|
579
|
+
if (t.includes("worker") || t.includes("job") || t.includes("queue"))
|
|
580
|
+
return "worker";
|
|
581
|
+
if (t.includes("library") || t.includes("lib") || t.includes("package"))
|
|
582
|
+
return "library";
|
|
583
|
+
if (t.includes("cache"))
|
|
584
|
+
return "cache";
|
|
585
|
+
return "service";
|
|
586
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
interface Credentials {
|
|
2
|
+
token: string;
|
|
3
|
+
email: string;
|
|
4
|
+
tier: string;
|
|
5
|
+
expiresAt: string;
|
|
6
|
+
}
|
|
7
|
+
export type OAuthProvider = "github" | "google";
|
|
8
|
+
export declare function handleLogin(provider?: OAuthProvider): Promise<void>;
|
|
9
|
+
export declare function handleLoginWithToken(token: string): Promise<void>;
|
|
10
|
+
export declare function handleLogout(): Promise<void>;
|
|
11
|
+
export declare function handleStatus(): Promise<void>;
|
|
12
|
+
export declare function loadCredentials(): Credentials | null;
|
|
13
|
+
/**
|
|
14
|
+
* Get the tier from the JWT token payload. NEVER falls back to the
|
|
15
|
+
* editable credentials file — that would allow trivial tier bypass
|
|
16
|
+
* by editing ~/.archbyte/credentials.json.
|
|
17
|
+
*
|
|
18
|
+
* WARNING: This reads from an UNVERIFIED JWT payload. The CLI cannot
|
|
19
|
+
* verify HMAC signatures (no shared secret). Use getVerifiedTier()
|
|
20
|
+
* instead, which reads from the server-verified tier cache.
|
|
21
|
+
*/
|
|
22
|
+
export declare function getTierFromToken(): "free" | "premium";
|
|
23
|
+
/**
|
|
24
|
+
* Cache the server-verified tier. Called by license-gate after a
|
|
25
|
+
* successful server response. Uses email as the identifier since
|
|
26
|
+
* the license-gate has access to it from credentials.
|
|
27
|
+
*/
|
|
28
|
+
export declare function cacheVerifiedTier(tier: "free" | "premium", email: string): void;
|
|
29
|
+
/**
|
|
30
|
+
* Get the tier from the server-verified cache. Returns "free" if cache
|
|
31
|
+
* is missing, stale (> 1 hour), or belongs to a different user.
|
|
32
|
+
*/
|
|
33
|
+
export declare function getVerifiedTier(): "free" | "premium";
|
|
34
|
+
/**
|
|
35
|
+
* Check if an offline action is allowed. Returns true if within limits.
|
|
36
|
+
* Increments the counter when allowed.
|
|
37
|
+
*/
|
|
38
|
+
export declare function checkOfflineAction(): {
|
|
39
|
+
allowed: boolean;
|
|
40
|
+
reason?: string;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Reset offline action counter. Called after successful server verification.
|
|
44
|
+
*/
|
|
45
|
+
export declare function resetOfflineActions(): void;
|
|
46
|
+
export {};
|