agentopology 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/LICENSE +191 -0
- package/README.md +450 -0
- package/dist/analyzer/__tests__/analyzer.test.d.ts +2 -0
- package/dist/analyzer/__tests__/analyzer.test.d.ts.map +1 -0
- package/dist/analyzer/__tests__/analyzer.test.js +258 -0
- package/dist/analyzer/__tests__/analyzer.test.js.map +1 -0
- package/dist/analyzer/index.d.ts +44 -0
- package/dist/analyzer/index.d.ts.map +1 -0
- package/dist/analyzer/index.js +308 -0
- package/dist/analyzer/index.js.map +1 -0
- package/dist/bindings/__tests__/bindings.test.d.ts +2 -0
- package/dist/bindings/__tests__/bindings.test.d.ts.map +1 -0
- package/dist/bindings/__tests__/bindings.test.js +2089 -0
- package/dist/bindings/__tests__/bindings.test.js.map +1 -0
- package/dist/bindings/claude-code.d.ts +12 -0
- package/dist/bindings/claude-code.d.ts.map +1 -0
- package/dist/bindings/claude-code.js +2063 -0
- package/dist/bindings/claude-code.js.map +1 -0
- package/dist/bindings/codex.d.ts +18 -0
- package/dist/bindings/codex.d.ts.map +1 -0
- package/dist/bindings/codex.js +1633 -0
- package/dist/bindings/codex.js.map +1 -0
- package/dist/bindings/copilot-cli.d.ts +20 -0
- package/dist/bindings/copilot-cli.d.ts.map +1 -0
- package/dist/bindings/copilot-cli.js +1515 -0
- package/dist/bindings/copilot-cli.js.map +1 -0
- package/dist/bindings/cursor.d.ts +17 -0
- package/dist/bindings/cursor.d.ts.map +1 -0
- package/dist/bindings/cursor.js +1715 -0
- package/dist/bindings/cursor.js.map +1 -0
- package/dist/bindings/gemini-cli.d.ts +18 -0
- package/dist/bindings/gemini-cli.d.ts.map +1 -0
- package/dist/bindings/gemini-cli.js +1327 -0
- package/dist/bindings/gemini-cli.js.map +1 -0
- package/dist/bindings/index.d.ts +20 -0
- package/dist/bindings/index.d.ts.map +1 -0
- package/dist/bindings/index.js +33 -0
- package/dist/bindings/index.js.map +1 -0
- package/dist/bindings/kiro.d.ts +17 -0
- package/dist/bindings/kiro.d.ts.map +1 -0
- package/dist/bindings/kiro.js +1624 -0
- package/dist/bindings/kiro.js.map +1 -0
- package/dist/bindings/openclaw.d.ts +18 -0
- package/dist/bindings/openclaw.d.ts.map +1 -0
- package/dist/bindings/openclaw.js +1578 -0
- package/dist/bindings/openclaw.js.map +1 -0
- package/dist/bindings/types.d.ts +33 -0
- package/dist/bindings/types.d.ts.map +1 -0
- package/dist/bindings/types.js +23 -0
- package/dist/bindings/types.js.map +1 -0
- package/dist/cli/index.d.ts +19 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +679 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/docs/__tests__/docs.test.d.ts +2 -0
- package/dist/docs/__tests__/docs.test.d.ts.map +1 -0
- package/dist/docs/__tests__/docs.test.js +438 -0
- package/dist/docs/__tests__/docs.test.js.map +1 -0
- package/dist/docs/content.d.ts +16 -0
- package/dist/docs/content.d.ts.map +1 -0
- package/dist/docs/content.js +3273 -0
- package/dist/docs/content.js.map +1 -0
- package/dist/docs/index.d.ts +17 -0
- package/dist/docs/index.d.ts.map +1 -0
- package/dist/docs/index.js +117 -0
- package/dist/docs/index.js.map +1 -0
- package/dist/exporters/__tests__/exporters.test.d.ts +2 -0
- package/dist/exporters/__tests__/exporters.test.d.ts.map +1 -0
- package/dist/exporters/__tests__/exporters.test.js +731 -0
- package/dist/exporters/__tests__/exporters.test.js.map +1 -0
- package/dist/exporters/index.d.ts +13 -0
- package/dist/exporters/index.d.ts.map +1 -0
- package/dist/exporters/index.js +18 -0
- package/dist/exporters/index.js.map +1 -0
- package/dist/exporters/json.d.ts +8 -0
- package/dist/exporters/json.d.ts.map +1 -0
- package/dist/exporters/json.js +25 -0
- package/dist/exporters/json.js.map +1 -0
- package/dist/exporters/markdown.d.ts +9 -0
- package/dist/exporters/markdown.d.ts.map +1 -0
- package/dist/exporters/markdown.js +686 -0
- package/dist/exporters/markdown.js.map +1 -0
- package/dist/exporters/mermaid.d.ts +11 -0
- package/dist/exporters/mermaid.d.ts.map +1 -0
- package/dist/exporters/mermaid.js +161 -0
- package/dist/exporters/mermaid.js.map +1 -0
- package/dist/exporters/types.d.ts +22 -0
- package/dist/exporters/types.d.ts.map +1 -0
- package/dist/exporters/types.js +10 -0
- package/dist/exporters/types.js.map +1 -0
- package/dist/import/__tests__/import.test.d.ts +2 -0
- package/dist/import/__tests__/import.test.d.ts.map +1 -0
- package/dist/import/__tests__/import.test.js +781 -0
- package/dist/import/__tests__/import.test.js.map +1 -0
- package/dist/import/claude-code.d.ts +38 -0
- package/dist/import/claude-code.d.ts.map +1 -0
- package/dist/import/claude-code.js +897 -0
- package/dist/import/claude-code.js.map +1 -0
- package/dist/import/index.d.ts +18 -0
- package/dist/import/index.d.ts.map +1 -0
- package/dist/import/index.js +28 -0
- package/dist/import/index.js.map +1 -0
- package/dist/import/serializer.d.ts +11 -0
- package/dist/import/serializer.d.ts.map +1 -0
- package/dist/import/serializer.js +960 -0
- package/dist/import/serializer.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/parser/__tests__/parser.test.d.ts +2 -0
- package/dist/parser/__tests__/parser.test.d.ts.map +1 -0
- package/dist/parser/__tests__/parser.test.js +6514 -0
- package/dist/parser/__tests__/parser.test.js.map +1 -0
- package/dist/parser/ast.d.ts +708 -0
- package/dist/parser/ast.d.ts.map +1 -0
- package/dist/parser/ast.js +12 -0
- package/dist/parser/ast.js.map +1 -0
- package/dist/parser/index.d.ts +253 -0
- package/dist/parser/index.d.ts.map +1 -0
- package/dist/parser/index.js +1985 -0
- package/dist/parser/index.js.map +1 -0
- package/dist/parser/lexer.d.ts +100 -0
- package/dist/parser/lexer.d.ts.map +1 -0
- package/dist/parser/lexer.js +304 -0
- package/dist/parser/lexer.js.map +1 -0
- package/dist/parser/validator.d.ts +42 -0
- package/dist/parser/validator.d.ts.map +1 -0
- package/dist/parser/validator.js +2568 -0
- package/dist/parser/validator.js.map +1 -0
- package/dist/scaffold/__tests__/scaffold.test.d.ts +7 -0
- package/dist/scaffold/__tests__/scaffold.test.d.ts.map +1 -0
- package/dist/scaffold/__tests__/scaffold.test.js +295 -0
- package/dist/scaffold/__tests__/scaffold.test.js.map +1 -0
- package/dist/scaffold/incremental.d.ts +43 -0
- package/dist/scaffold/incremental.d.ts.map +1 -0
- package/dist/scaffold/incremental.js +185 -0
- package/dist/scaffold/incremental.js.map +1 -0
- package/dist/scaffold/index.d.ts +13 -0
- package/dist/scaffold/index.d.ts.map +1 -0
- package/dist/scaffold/index.js +13 -0
- package/dist/scaffold/index.js.map +1 -0
- package/dist/scaffold/manifest.d.ts +30 -0
- package/dist/scaffold/manifest.d.ts.map +1 -0
- package/dist/scaffold/manifest.js +71 -0
- package/dist/scaffold/manifest.js.map +1 -0
- package/dist/scaffold/merge.d.ts +52 -0
- package/dist/scaffold/merge.d.ts.map +1 -0
- package/dist/scaffold/merge.js +134 -0
- package/dist/scaffold/merge.js.map +1 -0
- package/dist/scaffold/types.d.ts +51 -0
- package/dist/scaffold/types.d.ts.map +1 -0
- package/dist/scaffold/types.js +10 -0
- package/dist/scaffold/types.js.map +1 -0
- package/dist/sync/index.d.ts +34 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/index.js +392 -0
- package/dist/sync/index.js.map +1 -0
- package/dist/visualizer/index.d.ts +17 -0
- package/dist/visualizer/index.d.ts.map +1 -0
- package/dist/visualizer/index.js +1976 -0
- package/dist/visualizer/index.js.map +1 -0
- package/examples/code-review.at +167 -0
- package/examples/data-processing.at +296 -0
- package/examples/openclaw-assistant.at +142 -0
- package/examples/scheduled-monitor.at +193 -0
- package/examples/simple-pipeline.at +163 -0
- package/package.json +77 -0
- package/skill/SKILL.md +403 -0
- package/skill/scripts/emit.ts +853 -0
- package/skill/scripts/visualize.ts +89 -0
- package/spec/grammar.md +1466 -0
- package/spec/reserved-keywords.md +147 -0
- package/spec/validation.md +535 -0
|
@@ -0,0 +1,679 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* AgenTopology CLI.
|
|
4
|
+
*
|
|
5
|
+
* Commands:
|
|
6
|
+
* agentopology validate <file.at> — parse and validate
|
|
7
|
+
* agentopology scaffold <file.at> --target <binding> — generate files
|
|
8
|
+
* agentopology scaffold <file.at> --target <binding> --dry-run — preview only
|
|
9
|
+
* agentopology sync <file.at> --target <binding> --dir <path> — sync prompts back
|
|
10
|
+
* agentopology visualize <file.at> — generate HTML visualization
|
|
11
|
+
* agentopology targets — list bindings
|
|
12
|
+
* agentopology docs [topic] — language reference
|
|
13
|
+
* agentopology docs --all — all docs (LLM ingestion)
|
|
14
|
+
* agentopology docs --search <term> — search docs
|
|
15
|
+
*
|
|
16
|
+
* @module
|
|
17
|
+
*/
|
|
18
|
+
import * as fs from "node:fs";
|
|
19
|
+
import * as path from "node:path";
|
|
20
|
+
import { execSync } from "node:child_process";
|
|
21
|
+
import { parse } from "../parser/index.js";
|
|
22
|
+
import { validate } from "../parser/validator.js";
|
|
23
|
+
import { bindings } from "../bindings/index.js";
|
|
24
|
+
import { syncFromPlatform } from "../sync/index.js";
|
|
25
|
+
import { generateVisualization } from "../visualizer/index.js";
|
|
26
|
+
import { exporters } from "../exporters/index.js";
|
|
27
|
+
import { analyze } from "../analyzer/index.js";
|
|
28
|
+
import { listTopics, getTopic, getAllTopics, searchTopics } from "../docs/index.js";
|
|
29
|
+
import { importFromPlatform } from "../import/index.js";
|
|
30
|
+
import { readManifest, writeManifest, hashContent } from "../scaffold/manifest.js";
|
|
31
|
+
import { computeIncrementalPlan, executeActions } from "../scaffold/incremental.js";
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// ANSI colors (no external deps)
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
const isColorSupported = process.env.NO_COLOR === undefined && process.stdout.isTTY;
|
|
36
|
+
const c = {
|
|
37
|
+
red: (s) => (isColorSupported ? `\x1b[31m${s}\x1b[0m` : s),
|
|
38
|
+
green: (s) => (isColorSupported ? `\x1b[32m${s}\x1b[0m` : s),
|
|
39
|
+
yellow: (s) => (isColorSupported ? `\x1b[33m${s}\x1b[0m` : s),
|
|
40
|
+
cyan: (s) => (isColorSupported ? `\x1b[36m${s}\x1b[0m` : s),
|
|
41
|
+
bold: (s) => (isColorSupported ? `\x1b[1m${s}\x1b[0m` : s),
|
|
42
|
+
dim: (s) => (isColorSupported ? `\x1b[2m${s}\x1b[0m` : s),
|
|
43
|
+
};
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Helpers
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
function usage() {
|
|
48
|
+
console.log(`
|
|
49
|
+
${c.bold("agentopology")} — AgenTopology CLI
|
|
50
|
+
|
|
51
|
+
${c.bold("Usage:")}
|
|
52
|
+
agentopology validate <file.at>
|
|
53
|
+
agentopology scaffold <file.at> --target <binding> [--dry-run] [--force] [--prune] [--output <dir>]
|
|
54
|
+
agentopology sync <file.at> --target <binding> --dir <path>
|
|
55
|
+
agentopology visualize <file.at> [--output <dir>]
|
|
56
|
+
agentopology export <file.at> --format <markdown|mermaid|json> [--output <dir>]
|
|
57
|
+
agentopology info <file.at>
|
|
58
|
+
agentopology import --target <binding> --dir <path> [--name <topology-name>] [--output <dir>]
|
|
59
|
+
agentopology targets
|
|
60
|
+
agentopology docs [topic]
|
|
61
|
+
agentopology docs --all
|
|
62
|
+
agentopology docs --search <term>
|
|
63
|
+
|
|
64
|
+
${c.bold("Commands:")}
|
|
65
|
+
validate Parse an .at file and run all 29 validation rules.
|
|
66
|
+
scaffold Generate project files for a target platform.
|
|
67
|
+
sync Sync prompt content from platform files back into .at source.
|
|
68
|
+
visualize Generate an interactive HTML visualization of the topology.
|
|
69
|
+
export Export topology as Markdown documentation or Mermaid diagram.
|
|
70
|
+
info Analyze topology: detect patterns, compute layers, suggest improvements.
|
|
71
|
+
import Reverse-engineer platform files into an .at topology file.
|
|
72
|
+
targets List available binding targets.
|
|
73
|
+
docs Language reference — show documentation for .at syntax and features.
|
|
74
|
+
|
|
75
|
+
${c.bold("Options:")}
|
|
76
|
+
--target <name> Binding target (e.g. claude-code, codex, gemini-cli, copilot-cli, kiro)
|
|
77
|
+
--format <name> Export format (markdown, mermaid, json).
|
|
78
|
+
--dir <path> Directory to read platform files from (used with sync, import).
|
|
79
|
+
--name <name> Topology name for the generated .at file (used with import).
|
|
80
|
+
--output, -o <dir> Output directory for generated files (scaffold, visualize, export).
|
|
81
|
+
--dry-run Preview generated files without writing to disk.
|
|
82
|
+
--force Overwrite all files, ignoring manifest and conflicts.
|
|
83
|
+
--prune Delete files that were previously scaffolded but are no longer generated.
|
|
84
|
+
--all Show all documentation topics (for LLM ingestion).
|
|
85
|
+
--search <term> Search across all documentation topics.
|
|
86
|
+
--help, -h Show this help message.
|
|
87
|
+
`);
|
|
88
|
+
}
|
|
89
|
+
function readFile(filePath) {
|
|
90
|
+
const resolved = path.resolve(filePath);
|
|
91
|
+
if (!fs.existsSync(resolved)) {
|
|
92
|
+
console.error(c.red(`Error: file not found: ${resolved}`));
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
return fs.readFileSync(resolved, "utf-8");
|
|
96
|
+
}
|
|
97
|
+
/** Recursively create directories and write a file. */
|
|
98
|
+
function writeFile(basePath, relativePath, content) {
|
|
99
|
+
const fullPath = path.join(basePath, relativePath);
|
|
100
|
+
const dir = path.dirname(fullPath);
|
|
101
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
102
|
+
fs.writeFileSync(fullPath, content, "utf-8");
|
|
103
|
+
}
|
|
104
|
+
function parseArgs(argv) {
|
|
105
|
+
const args = argv.slice(2); // skip node and script path
|
|
106
|
+
const result = {
|
|
107
|
+
command: undefined,
|
|
108
|
+
file: undefined,
|
|
109
|
+
target: undefined,
|
|
110
|
+
format: undefined,
|
|
111
|
+
dir: undefined,
|
|
112
|
+
output: undefined,
|
|
113
|
+
name: undefined,
|
|
114
|
+
dryRun: false,
|
|
115
|
+
force: false,
|
|
116
|
+
prune: false,
|
|
117
|
+
all: false,
|
|
118
|
+
search: undefined,
|
|
119
|
+
help: false,
|
|
120
|
+
};
|
|
121
|
+
let i = 0;
|
|
122
|
+
while (i < args.length) {
|
|
123
|
+
const arg = args[i];
|
|
124
|
+
if (arg === "--help" || arg === "-h") {
|
|
125
|
+
result.help = true;
|
|
126
|
+
i++;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (arg === "--dry-run") {
|
|
130
|
+
result.dryRun = true;
|
|
131
|
+
i++;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (arg === "--force") {
|
|
135
|
+
result.force = true;
|
|
136
|
+
i++;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (arg === "--prune") {
|
|
140
|
+
result.prune = true;
|
|
141
|
+
i++;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (arg === "--all") {
|
|
145
|
+
result.all = true;
|
|
146
|
+
i++;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (arg === "--search" && i + 1 < args.length) {
|
|
150
|
+
result.search = args[i + 1];
|
|
151
|
+
i += 2;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (arg === "--target" && i + 1 < args.length) {
|
|
155
|
+
result.target = args[i + 1];
|
|
156
|
+
i += 2;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (arg === "--format" && i + 1 < args.length) {
|
|
160
|
+
result.format = args[i + 1];
|
|
161
|
+
i += 2;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (arg === "--dir" && i + 1 < args.length) {
|
|
165
|
+
result.dir = args[i + 1];
|
|
166
|
+
i += 2;
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if ((arg === "--output" || arg === "-o") && i + 1 < args.length) {
|
|
170
|
+
result.output = args[i + 1];
|
|
171
|
+
i += 2;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (arg === "--name" && i + 1 < args.length) {
|
|
175
|
+
result.name = args[i + 1];
|
|
176
|
+
i += 2;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
// Positional arguments
|
|
180
|
+
if (!result.command) {
|
|
181
|
+
result.command = arg;
|
|
182
|
+
}
|
|
183
|
+
else if (!result.file) {
|
|
184
|
+
result.file = arg;
|
|
185
|
+
}
|
|
186
|
+
i++;
|
|
187
|
+
}
|
|
188
|
+
return result;
|
|
189
|
+
}
|
|
190
|
+
// ---------------------------------------------------------------------------
|
|
191
|
+
// Commands
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
function cmdValidate(filePath) {
|
|
194
|
+
const source = readFile(filePath);
|
|
195
|
+
let ast;
|
|
196
|
+
try {
|
|
197
|
+
ast = parse(source);
|
|
198
|
+
}
|
|
199
|
+
catch (err) {
|
|
200
|
+
console.error(c.red(`Parse error: ${err.message}`));
|
|
201
|
+
process.exit(1);
|
|
202
|
+
}
|
|
203
|
+
console.log(c.bold(`Validating ${path.basename(filePath)} (${ast.topology.name} v${ast.topology.version})`));
|
|
204
|
+
console.log("");
|
|
205
|
+
const results = validate(ast);
|
|
206
|
+
if (results.length === 0) {
|
|
207
|
+
console.log(c.green(" All 29 validation rules passed."));
|
|
208
|
+
console.log("");
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const errors = results.filter((r) => r.level === "error");
|
|
212
|
+
const warnings = results.filter((r) => r.level === "warning");
|
|
213
|
+
for (const result of results) {
|
|
214
|
+
const prefix = result.level === "error"
|
|
215
|
+
? c.red(` ERROR [${result.rule}]`)
|
|
216
|
+
: c.yellow(` WARN [${result.rule}]`);
|
|
217
|
+
const linePart = result.line ? ` line ${result.line}:` : "";
|
|
218
|
+
const nodePart = result.node ? c.dim(` (${result.node})`) : "";
|
|
219
|
+
console.log(`${prefix}${linePart} ${result.message}${nodePart}`);
|
|
220
|
+
}
|
|
221
|
+
console.log("");
|
|
222
|
+
console.log(` ${c.red(`${errors.length} error(s)`)}, ${c.yellow(`${warnings.length} warning(s)`)}`);
|
|
223
|
+
console.log("");
|
|
224
|
+
if (errors.length > 0) {
|
|
225
|
+
process.exit(1);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function cmdScaffold(filePath, targetName, dryRun, outputDir, force, prune) {
|
|
229
|
+
const source = readFile(filePath);
|
|
230
|
+
let ast;
|
|
231
|
+
try {
|
|
232
|
+
ast = parse(source);
|
|
233
|
+
}
|
|
234
|
+
catch (err) {
|
|
235
|
+
console.error(c.red(`Parse error: ${err.message}`));
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
const binding = bindings[targetName];
|
|
239
|
+
if (!binding) {
|
|
240
|
+
console.error(c.red(`Unknown target: "${targetName}"`));
|
|
241
|
+
console.error(`Available targets: ${Object.keys(bindings).join(", ")}`);
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
244
|
+
console.log(c.bold(`Scaffolding ${ast.topology.name} for ${binding.description}`));
|
|
245
|
+
console.log("");
|
|
246
|
+
const files = binding.scaffold(ast);
|
|
247
|
+
if (files.length === 0) {
|
|
248
|
+
console.log(c.yellow(" No files generated."));
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const basePath = outputDir ? path.resolve(outputDir) : process.cwd();
|
|
252
|
+
if (dryRun) {
|
|
253
|
+
console.log(c.cyan(" Dry run — files that would be generated:"));
|
|
254
|
+
console.log("");
|
|
255
|
+
const manifest = readManifest(basePath, targetName);
|
|
256
|
+
if (manifest && !force) {
|
|
257
|
+
const actions = computeIncrementalPlan(basePath, targetName, files, manifest);
|
|
258
|
+
for (const action of actions) {
|
|
259
|
+
switch (action.type) {
|
|
260
|
+
case "create":
|
|
261
|
+
console.log(` ${c.green("+")} ${action.path}`);
|
|
262
|
+
break;
|
|
263
|
+
case "update":
|
|
264
|
+
console.log(` ${c.yellow("~")} ${action.path} (${action.detail})`);
|
|
265
|
+
break;
|
|
266
|
+
case "delete":
|
|
267
|
+
console.log(` ${c.red("-")} ${action.path}`);
|
|
268
|
+
break;
|
|
269
|
+
case "unchanged":
|
|
270
|
+
console.log(` ${c.dim("=")} ${action.path}`);
|
|
271
|
+
break;
|
|
272
|
+
case "conflict":
|
|
273
|
+
console.log(` ${c.bold("!")} ${action.path} (${action.detail})`);
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
for (const file of files) {
|
|
280
|
+
console.log(` ${c.green("+")} ${file.path} (${file.content.length} bytes)`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
console.log("");
|
|
284
|
+
console.log(` ${files.length} file(s) would be generated.`);
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
const manifest = readManifest(basePath, targetName);
|
|
288
|
+
if (manifest && !force) {
|
|
289
|
+
// INCREMENTAL MODE
|
|
290
|
+
const actions = computeIncrementalPlan(basePath, targetName, files, manifest);
|
|
291
|
+
const result = executeActions(basePath, actions, { prune: !!prune, force: !!force });
|
|
292
|
+
for (const action of actions) {
|
|
293
|
+
switch (action.type) {
|
|
294
|
+
case "create":
|
|
295
|
+
console.log(` ${c.green("+")} ${action.path}`);
|
|
296
|
+
break;
|
|
297
|
+
case "update":
|
|
298
|
+
console.log(` ${c.yellow("~")} ${action.path}`);
|
|
299
|
+
break;
|
|
300
|
+
case "delete":
|
|
301
|
+
if (prune)
|
|
302
|
+
console.log(` ${c.red("-")} ${action.path}`);
|
|
303
|
+
break;
|
|
304
|
+
case "unchanged": break;
|
|
305
|
+
case "conflict":
|
|
306
|
+
console.log(` ${c.bold("!")} ${action.path} (preserved — user edited)`);
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
console.log("");
|
|
311
|
+
console.log(` ${result.created} created, ${result.updated} updated, ${result.unchanged} unchanged, ${result.conflicts} conflicts${prune ? `, ${result.deleted} deleted` : ""}`);
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
// FIRST RUN or --force — write everything
|
|
315
|
+
for (const file of files) {
|
|
316
|
+
writeFile(basePath, file.path, file.content);
|
|
317
|
+
console.log(` ${c.green("+")} ${file.path}`);
|
|
318
|
+
}
|
|
319
|
+
console.log("");
|
|
320
|
+
console.log(` ${c.bold(`${files.length}`)} file(s) written to ${basePath}`);
|
|
321
|
+
}
|
|
322
|
+
// Always write manifest after successful scaffold
|
|
323
|
+
const newManifest = {
|
|
324
|
+
source: path.basename(filePath),
|
|
325
|
+
sourceHash: hashContent(source),
|
|
326
|
+
target: targetName,
|
|
327
|
+
generatedAt: new Date().toISOString(),
|
|
328
|
+
files: {},
|
|
329
|
+
};
|
|
330
|
+
for (const file of files) {
|
|
331
|
+
newManifest.files[file.path] = {
|
|
332
|
+
hash: hashContent(file.content),
|
|
333
|
+
category: file.category || "machine",
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
writeManifest(basePath, targetName, newManifest);
|
|
337
|
+
}
|
|
338
|
+
console.log("");
|
|
339
|
+
}
|
|
340
|
+
function readDirRecursive(dirPath) {
|
|
341
|
+
const resolved = path.resolve(dirPath);
|
|
342
|
+
if (!fs.existsSync(resolved)) {
|
|
343
|
+
console.error(c.red(`Error: directory not found: ${resolved}`));
|
|
344
|
+
process.exit(1);
|
|
345
|
+
}
|
|
346
|
+
const files = [];
|
|
347
|
+
function walk(dir, prefix) {
|
|
348
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
349
|
+
const rel = path.join(prefix, entry.name);
|
|
350
|
+
const full = path.join(dir, entry.name);
|
|
351
|
+
if (entry.isDirectory()) {
|
|
352
|
+
walk(full, rel);
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
files.push({ path: rel, content: fs.readFileSync(full, "utf-8") });
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
walk(resolved, "");
|
|
360
|
+
return files;
|
|
361
|
+
}
|
|
362
|
+
function cmdSync(filePath, targetName, dirPath) {
|
|
363
|
+
const atSource = readFile(filePath);
|
|
364
|
+
const files = readDirRecursive(dirPath);
|
|
365
|
+
const updated = syncFromPlatform(atSource, files, targetName);
|
|
366
|
+
const resolved = path.resolve(filePath);
|
|
367
|
+
fs.writeFileSync(resolved, updated, "utf-8");
|
|
368
|
+
console.log(c.green(` Updated ${path.basename(filePath)} with prompt blocks from ${targetName} files.`));
|
|
369
|
+
}
|
|
370
|
+
function cmdVisualize(filePath, outputDir) {
|
|
371
|
+
const source = readFile(filePath);
|
|
372
|
+
let ast;
|
|
373
|
+
try {
|
|
374
|
+
ast = parse(source);
|
|
375
|
+
}
|
|
376
|
+
catch (err) {
|
|
377
|
+
console.error(c.red(`Parse error: ${err.message}`));
|
|
378
|
+
process.exit(1);
|
|
379
|
+
}
|
|
380
|
+
const html = generateVisualization(ast);
|
|
381
|
+
const resolved = path.resolve(filePath);
|
|
382
|
+
const stem = path.basename(resolved, ".at");
|
|
383
|
+
const outDir = outputDir ? path.resolve(outputDir) : path.dirname(resolved);
|
|
384
|
+
const outFile = path.join(outDir, `${stem}-topology.html`);
|
|
385
|
+
try {
|
|
386
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
387
|
+
fs.writeFileSync(outFile, html, "utf-8");
|
|
388
|
+
}
|
|
389
|
+
catch (err) {
|
|
390
|
+
console.error(c.red(`Error: Cannot write "${outFile}"`));
|
|
391
|
+
console.error(err.message);
|
|
392
|
+
process.exit(1);
|
|
393
|
+
}
|
|
394
|
+
console.log(c.green(` Visualization written to ${outFile}`));
|
|
395
|
+
// Try to open in the default browser (non-fatal if it fails).
|
|
396
|
+
const openCmd = process.platform === "darwin" ? "open" : "xdg-open";
|
|
397
|
+
try {
|
|
398
|
+
execSync(`${openCmd} "${outFile}"`, { stdio: "ignore" });
|
|
399
|
+
}
|
|
400
|
+
catch {
|
|
401
|
+
// Non-fatal — the file was written successfully regardless.
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
function cmdExport(filePath, formatName, outputDir) {
|
|
405
|
+
const source = readFile(filePath);
|
|
406
|
+
let ast;
|
|
407
|
+
try {
|
|
408
|
+
ast = parse(source);
|
|
409
|
+
}
|
|
410
|
+
catch (err) {
|
|
411
|
+
console.error(c.red(`Parse error: ${err.message}`));
|
|
412
|
+
process.exit(1);
|
|
413
|
+
}
|
|
414
|
+
const exporter = exporters[formatName];
|
|
415
|
+
if (!exporter) {
|
|
416
|
+
console.error(c.red(`Unknown format: "${formatName}"`));
|
|
417
|
+
console.error(`Available formats: ${Object.keys(exporters).join(", ")}`);
|
|
418
|
+
process.exit(1);
|
|
419
|
+
}
|
|
420
|
+
console.log(c.bold(`Exporting ${ast.topology.name} as ${exporter.description}`));
|
|
421
|
+
console.log("");
|
|
422
|
+
const files = exporter.export(ast);
|
|
423
|
+
const resolved = path.resolve(filePath);
|
|
424
|
+
const basePath = outputDir ? path.resolve(outputDir) : path.dirname(resolved);
|
|
425
|
+
for (const file of files) {
|
|
426
|
+
writeFile(basePath, file.path, file.content);
|
|
427
|
+
console.log(` ${c.green("+")} ${file.path}`);
|
|
428
|
+
}
|
|
429
|
+
console.log("");
|
|
430
|
+
console.log(` ${c.bold(`${files.length}`)} file(s) written to ${basePath}`);
|
|
431
|
+
console.log("");
|
|
432
|
+
}
|
|
433
|
+
function cmdInfo(filePath) {
|
|
434
|
+
const source = readFile(filePath);
|
|
435
|
+
let ast;
|
|
436
|
+
try {
|
|
437
|
+
ast = parse(source);
|
|
438
|
+
}
|
|
439
|
+
catch (err) {
|
|
440
|
+
console.error(c.red(`Parse error: ${err.message}`));
|
|
441
|
+
process.exit(1);
|
|
442
|
+
}
|
|
443
|
+
const result = analyze(ast);
|
|
444
|
+
const { summary, patterns, layers, suggestions } = result;
|
|
445
|
+
// Summary
|
|
446
|
+
console.log(c.bold(`Topology: ${summary.name} v${summary.version}`));
|
|
447
|
+
if (summary.description) {
|
|
448
|
+
console.log(c.dim(` ${summary.description}`));
|
|
449
|
+
}
|
|
450
|
+
console.log("");
|
|
451
|
+
const counts = summary.nodeCount;
|
|
452
|
+
const parts = [];
|
|
453
|
+
if (counts.agents)
|
|
454
|
+
parts.push(`${counts.agents} agent${counts.agents !== 1 ? "s" : ""}`);
|
|
455
|
+
if (counts.actions)
|
|
456
|
+
parts.push(`${counts.actions} action${counts.actions !== 1 ? "s" : ""}`);
|
|
457
|
+
if (counts.gates)
|
|
458
|
+
parts.push(`${counts.gates} gate${counts.gates !== 1 ? "s" : ""}`);
|
|
459
|
+
if (counts.groups)
|
|
460
|
+
parts.push(`${counts.groups} group${counts.groups !== 1 ? "s" : ""}`);
|
|
461
|
+
if (counts.humans)
|
|
462
|
+
parts.push(`${counts.humans} human${counts.humans !== 1 ? "s" : ""}`);
|
|
463
|
+
if (counts.orchestrators)
|
|
464
|
+
parts.push(`${counts.orchestrators} orchestrator${counts.orchestrators !== 1 ? "s" : ""}`);
|
|
465
|
+
console.log(` ${parts.join(", ")}`);
|
|
466
|
+
const condEdges = ast.edges.filter((e) => e.condition).length;
|
|
467
|
+
const loopEdges = ast.edges.filter((e) => e.maxIterations).length;
|
|
468
|
+
const edgeParts = [`${summary.edgeCount} edge${summary.edgeCount !== 1 ? "s" : ""}`];
|
|
469
|
+
if (condEdges)
|
|
470
|
+
edgeParts.push(`${condEdges} conditional`);
|
|
471
|
+
if (loopEdges)
|
|
472
|
+
edgeParts.push(`${loopEdges} loop${loopEdges !== 1 ? "s" : ""}`);
|
|
473
|
+
console.log(` ${edgeParts.join(", ")}`);
|
|
474
|
+
if (summary.declaredPatterns.length > 0) {
|
|
475
|
+
console.log(` Declared patterns: ${summary.declaredPatterns.join(", ")}`);
|
|
476
|
+
}
|
|
477
|
+
console.log("");
|
|
478
|
+
// Detected patterns
|
|
479
|
+
if (patterns.length > 0) {
|
|
480
|
+
console.log(c.bold("Detected Patterns:"));
|
|
481
|
+
for (const p of patterns) {
|
|
482
|
+
const conf = p.confidence === "definite" ? "" : c.dim(" (likely)");
|
|
483
|
+
console.log(` ${c.cyan(p.name)}${conf}`);
|
|
484
|
+
console.log(` ${c.dim(p.description)}`);
|
|
485
|
+
}
|
|
486
|
+
console.log("");
|
|
487
|
+
}
|
|
488
|
+
// Layers
|
|
489
|
+
if (layers.length > 0) {
|
|
490
|
+
console.log(c.bold("Layers:"));
|
|
491
|
+
for (const layer of layers) {
|
|
492
|
+
const label = layer.depth === -1 ? "?" : String(layer.depth);
|
|
493
|
+
console.log(` ${c.dim(label + ":")} ${layer.nodes.join(", ")}`);
|
|
494
|
+
}
|
|
495
|
+
console.log("");
|
|
496
|
+
}
|
|
497
|
+
// Suggestions
|
|
498
|
+
if (suggestions.length > 0) {
|
|
499
|
+
console.log(c.bold("Suggestions:"));
|
|
500
|
+
for (const s of suggestions) {
|
|
501
|
+
const prefix = s.level === "improvement" ? c.yellow("[improvement]") : c.dim("[info]");
|
|
502
|
+
const nodePart = s.node ? ` ${c.cyan(s.node)}:` : "";
|
|
503
|
+
console.log(` ${prefix}${nodePart} ${s.message}`);
|
|
504
|
+
}
|
|
505
|
+
console.log("");
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
function cmdImport(targetName, dirPath, topologyName, outputDir) {
|
|
509
|
+
const files = readDirRecursive(dirPath);
|
|
510
|
+
const name = topologyName ?? path.basename(path.resolve(dirPath)).replace(/^\./, "");
|
|
511
|
+
console.log(c.bold(`Importing from ${targetName} files in ${dirPath}`));
|
|
512
|
+
console.log("");
|
|
513
|
+
let atSource;
|
|
514
|
+
try {
|
|
515
|
+
atSource = importFromPlatform(files, targetName, name);
|
|
516
|
+
}
|
|
517
|
+
catch (err) {
|
|
518
|
+
console.error(c.red(`Import error: ${err.message}`));
|
|
519
|
+
process.exit(1);
|
|
520
|
+
}
|
|
521
|
+
// Determine output path
|
|
522
|
+
const outDir = outputDir ? path.resolve(outputDir) : process.cwd();
|
|
523
|
+
const outFile = path.join(outDir, `${name}.at`);
|
|
524
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
525
|
+
fs.writeFileSync(outFile, atSource, "utf-8");
|
|
526
|
+
console.log(c.green(` Written: ${outFile}`));
|
|
527
|
+
// Validate the generated file
|
|
528
|
+
try {
|
|
529
|
+
const ast = parse(atSource);
|
|
530
|
+
const results = validate(ast);
|
|
531
|
+
const errors = results.filter((r) => r.level === "error");
|
|
532
|
+
const warnings = results.filter((r) => r.level === "warning");
|
|
533
|
+
if (errors.length > 0 || warnings.length > 0) {
|
|
534
|
+
console.log("");
|
|
535
|
+
console.log(` ${c.yellow(`${errors.length} error(s), ${warnings.length} warning(s) in generated file.`)}`);
|
|
536
|
+
console.log(` ${c.dim("Run")} agentopology validate ${outFile} ${c.dim("for details.")}`);
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
console.log(` ${c.green("Generated file passes all validation rules.")}`);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
catch {
|
|
543
|
+
// Validation is best-effort; don't fail the import
|
|
544
|
+
console.log(` ${c.yellow("Note: generated file may need manual review.")}`);
|
|
545
|
+
}
|
|
546
|
+
console.log("");
|
|
547
|
+
}
|
|
548
|
+
function cmdTargets() {
|
|
549
|
+
console.log(c.bold("Available binding targets:"));
|
|
550
|
+
console.log("");
|
|
551
|
+
for (const [name, binding] of Object.entries(bindings)) {
|
|
552
|
+
console.log(` ${c.cyan(name.padEnd(16))} ${binding.description}`);
|
|
553
|
+
}
|
|
554
|
+
console.log("");
|
|
555
|
+
}
|
|
556
|
+
// ---------------------------------------------------------------------------
|
|
557
|
+
// Main
|
|
558
|
+
// ---------------------------------------------------------------------------
|
|
559
|
+
function main() {
|
|
560
|
+
const args = parseArgs(process.argv);
|
|
561
|
+
if (args.help || !args.command) {
|
|
562
|
+
usage();
|
|
563
|
+
process.exit(args.help ? 0 : 1);
|
|
564
|
+
}
|
|
565
|
+
switch (args.command) {
|
|
566
|
+
case "validate":
|
|
567
|
+
if (!args.file) {
|
|
568
|
+
console.error(c.red("Error: validate requires a file argument."));
|
|
569
|
+
usage();
|
|
570
|
+
process.exit(1);
|
|
571
|
+
}
|
|
572
|
+
cmdValidate(args.file);
|
|
573
|
+
break;
|
|
574
|
+
case "scaffold":
|
|
575
|
+
if (!args.file) {
|
|
576
|
+
console.error(c.red("Error: scaffold requires a file argument."));
|
|
577
|
+
usage();
|
|
578
|
+
process.exit(1);
|
|
579
|
+
}
|
|
580
|
+
if (!args.target) {
|
|
581
|
+
console.error(c.red("Error: scaffold requires --target <binding>."));
|
|
582
|
+
usage();
|
|
583
|
+
process.exit(1);
|
|
584
|
+
}
|
|
585
|
+
cmdScaffold(args.file, args.target, args.dryRun, args.output || args.dir, args.force, args.prune);
|
|
586
|
+
break;
|
|
587
|
+
case "sync":
|
|
588
|
+
if (!args.file) {
|
|
589
|
+
console.error(c.red("Error: sync requires a file argument."));
|
|
590
|
+
usage();
|
|
591
|
+
process.exit(1);
|
|
592
|
+
}
|
|
593
|
+
if (!args.target) {
|
|
594
|
+
console.error(c.red("Error: sync requires --target <binding>."));
|
|
595
|
+
usage();
|
|
596
|
+
process.exit(1);
|
|
597
|
+
}
|
|
598
|
+
if (!args.dir) {
|
|
599
|
+
console.error(c.red("Error: sync requires --dir <path>."));
|
|
600
|
+
usage();
|
|
601
|
+
process.exit(1);
|
|
602
|
+
}
|
|
603
|
+
cmdSync(args.file, args.target, args.dir);
|
|
604
|
+
break;
|
|
605
|
+
case "visualize":
|
|
606
|
+
if (!args.file) {
|
|
607
|
+
console.error(c.red("Error: visualize requires a file argument."));
|
|
608
|
+
usage();
|
|
609
|
+
process.exit(1);
|
|
610
|
+
}
|
|
611
|
+
cmdVisualize(args.file, args.output);
|
|
612
|
+
break;
|
|
613
|
+
case "export":
|
|
614
|
+
if (!args.file) {
|
|
615
|
+
console.error(c.red("Error: export requires a file argument."));
|
|
616
|
+
usage();
|
|
617
|
+
process.exit(1);
|
|
618
|
+
}
|
|
619
|
+
if (!args.format) {
|
|
620
|
+
console.error(c.red("Error: export requires --format <markdown|mermaid|json>."));
|
|
621
|
+
usage();
|
|
622
|
+
process.exit(1);
|
|
623
|
+
}
|
|
624
|
+
cmdExport(args.file, args.format, args.output);
|
|
625
|
+
break;
|
|
626
|
+
case "info":
|
|
627
|
+
if (!args.file) {
|
|
628
|
+
console.error(c.red("Error: info requires a file argument."));
|
|
629
|
+
usage();
|
|
630
|
+
process.exit(1);
|
|
631
|
+
}
|
|
632
|
+
cmdInfo(args.file);
|
|
633
|
+
break;
|
|
634
|
+
case "import":
|
|
635
|
+
if (!args.target) {
|
|
636
|
+
console.error(c.red("Error: import requires --target <binding>."));
|
|
637
|
+
usage();
|
|
638
|
+
process.exit(1);
|
|
639
|
+
}
|
|
640
|
+
if (!args.dir) {
|
|
641
|
+
console.error(c.red("Error: import requires --dir <path>."));
|
|
642
|
+
usage();
|
|
643
|
+
process.exit(1);
|
|
644
|
+
}
|
|
645
|
+
cmdImport(args.target, args.dir, args.name, args.output);
|
|
646
|
+
break;
|
|
647
|
+
case "targets":
|
|
648
|
+
cmdTargets();
|
|
649
|
+
break;
|
|
650
|
+
case "docs":
|
|
651
|
+
if (args.all) {
|
|
652
|
+
console.log(getAllTopics());
|
|
653
|
+
}
|
|
654
|
+
else if (args.search) {
|
|
655
|
+
console.log(searchTopics(args.search));
|
|
656
|
+
}
|
|
657
|
+
else if (args.file) {
|
|
658
|
+
// args.file is actually the topic name (second positional arg)
|
|
659
|
+
const content = getTopic(args.file);
|
|
660
|
+
if (!content) {
|
|
661
|
+
console.error(c.red(`Unknown topic: "${args.file}"`));
|
|
662
|
+
console.log("");
|
|
663
|
+
console.log(listTopics());
|
|
664
|
+
process.exit(1);
|
|
665
|
+
}
|
|
666
|
+
console.log(content);
|
|
667
|
+
}
|
|
668
|
+
else {
|
|
669
|
+
console.log(listTopics());
|
|
670
|
+
}
|
|
671
|
+
break;
|
|
672
|
+
default:
|
|
673
|
+
console.error(c.red(`Unknown command: "${args.command}"`));
|
|
674
|
+
usage();
|
|
675
|
+
process.exit(1);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
main();
|
|
679
|
+
//# sourceMappingURL=index.js.map
|