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,897 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code importer — reads .claude/ directory structure and constructs
|
|
3
|
+
* a TopologyAST suitable for serialization to .at format.
|
|
4
|
+
*
|
|
5
|
+
* @module
|
|
6
|
+
*/
|
|
7
|
+
import { extractSectionUntilKnownHeading, CLAUDE_CODE_STRUCTURAL_HEADINGS } from "../sync/index.js";
|
|
8
|
+
function parseFrontmatter(content) {
|
|
9
|
+
const fields = {};
|
|
10
|
+
let body = content;
|
|
11
|
+
if (!content.startsWith("---")) {
|
|
12
|
+
return { fields, body };
|
|
13
|
+
}
|
|
14
|
+
const endIdx = content.indexOf("---", 3);
|
|
15
|
+
if (endIdx === -1)
|
|
16
|
+
return { fields, body };
|
|
17
|
+
const yaml = content.slice(3, endIdx).trim();
|
|
18
|
+
body = content.slice(endIdx + 3).trim();
|
|
19
|
+
let currentKey = null;
|
|
20
|
+
let currentList = null;
|
|
21
|
+
for (const line of yaml.split("\n")) {
|
|
22
|
+
const trimmed = line.trim();
|
|
23
|
+
if (!trimmed)
|
|
24
|
+
continue;
|
|
25
|
+
// List item continuation
|
|
26
|
+
if (trimmed.startsWith("- ") && currentKey && currentList !== null) {
|
|
27
|
+
currentList.push(trimmed.slice(2).trim());
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
// Flush previous list
|
|
31
|
+
if (currentKey && currentList !== null) {
|
|
32
|
+
fields[currentKey] = currentList;
|
|
33
|
+
currentKey = null;
|
|
34
|
+
currentList = null;
|
|
35
|
+
}
|
|
36
|
+
const colonIdx = trimmed.indexOf(":");
|
|
37
|
+
if (colonIdx === -1)
|
|
38
|
+
continue;
|
|
39
|
+
const key = trimmed.slice(0, colonIdx).trim();
|
|
40
|
+
const rawValue = trimmed.slice(colonIdx + 1).trim();
|
|
41
|
+
if (rawValue === "" || rawValue === undefined) {
|
|
42
|
+
// Start of a list
|
|
43
|
+
currentKey = key;
|
|
44
|
+
currentList = [];
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
// Boolean
|
|
48
|
+
if (rawValue === "true") {
|
|
49
|
+
fields[key] = true;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (rawValue === "false") {
|
|
53
|
+
fields[key] = false;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
// Unquote
|
|
57
|
+
const value = rawValue.replace(/^["']|["']$/g, "");
|
|
58
|
+
fields[key] = value;
|
|
59
|
+
}
|
|
60
|
+
// Flush last list
|
|
61
|
+
if (currentKey && currentList !== null) {
|
|
62
|
+
fields[currentKey] = currentList;
|
|
63
|
+
}
|
|
64
|
+
return { fields, body };
|
|
65
|
+
}
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// Section extraction helpers
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
/** Known structural headings that delimit sections in AGENT.md body. */
|
|
70
|
+
const AGENT_KNOWN_HEADINGS = [
|
|
71
|
+
...CLAUDE_CODE_STRUCTURAL_HEADINGS,
|
|
72
|
+
"## Instructions",
|
|
73
|
+
"## Circuit Breaker",
|
|
74
|
+
"## Input Schema",
|
|
75
|
+
"## Output Schema",
|
|
76
|
+
"## Variants",
|
|
77
|
+
"## Artifacts",
|
|
78
|
+
"## Model Configuration",
|
|
79
|
+
"## Scale",
|
|
80
|
+
];
|
|
81
|
+
/** Known structural headings in SKILL.md body. */
|
|
82
|
+
const SKILL_KNOWN_HEADINGS = [
|
|
83
|
+
"## Orchestrator",
|
|
84
|
+
"## Flow",
|
|
85
|
+
"## Gates",
|
|
86
|
+
"## Triggers",
|
|
87
|
+
"## Depth Levels",
|
|
88
|
+
"## Batch",
|
|
89
|
+
"## Environments",
|
|
90
|
+
"## Memory",
|
|
91
|
+
"## Metering",
|
|
92
|
+
"## Schedules",
|
|
93
|
+
"## Interfaces",
|
|
94
|
+
"## Parameters",
|
|
95
|
+
"## Interface",
|
|
96
|
+
"## Imports",
|
|
97
|
+
"## Includes",
|
|
98
|
+
"## Error Handling",
|
|
99
|
+
"## Fallback Chain",
|
|
100
|
+
"## How to Use",
|
|
101
|
+
];
|
|
102
|
+
/** Extract a markdown section by heading, stopping at any known heading. */
|
|
103
|
+
function extractSection(body, heading) {
|
|
104
|
+
return extractSectionUntilKnownHeading(body, heading, AGENT_KNOWN_HEADINGS.filter((h) => h !== heading));
|
|
105
|
+
}
|
|
106
|
+
/** Extract a markdown section from SKILL.md, stopping at known skill headings. */
|
|
107
|
+
function extractSkillSection(body, heading) {
|
|
108
|
+
return extractSectionUntilKnownHeading(body, heading, SKILL_KNOWN_HEADINGS.filter((h) => h !== heading));
|
|
109
|
+
}
|
|
110
|
+
/** Parse bullet list items from a section: "- item" → ["item"] */
|
|
111
|
+
function parseBulletList(text) {
|
|
112
|
+
if (!text)
|
|
113
|
+
return [];
|
|
114
|
+
return text
|
|
115
|
+
.split("\n")
|
|
116
|
+
.filter((line) => line.trim().startsWith("- "))
|
|
117
|
+
.map((line) => line.trim().slice(2).trim());
|
|
118
|
+
}
|
|
119
|
+
/** Parse output enum lines: "- field: val1 | val2" → OutputsMap */
|
|
120
|
+
function parseOutputLines(text) {
|
|
121
|
+
if (!text)
|
|
122
|
+
return undefined;
|
|
123
|
+
const outputs = {};
|
|
124
|
+
for (const line of text.split("\n")) {
|
|
125
|
+
const trimmed = line.trim();
|
|
126
|
+
if (!trimmed.startsWith("- "))
|
|
127
|
+
continue;
|
|
128
|
+
const content = trimmed.slice(2);
|
|
129
|
+
const colonIdx = content.indexOf(":");
|
|
130
|
+
if (colonIdx === -1)
|
|
131
|
+
continue;
|
|
132
|
+
const field = content.slice(0, colonIdx).trim();
|
|
133
|
+
const values = content.slice(colonIdx + 1).trim().split(/\s*\|\s*/);
|
|
134
|
+
if (field && values.length > 0) {
|
|
135
|
+
outputs[field] = values;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return Object.keys(outputs).length > 0 ? outputs : undefined;
|
|
139
|
+
}
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
// Inline pattern extractors
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
function extractInlinePattern(body, pattern) {
|
|
144
|
+
const match = pattern.exec(body);
|
|
145
|
+
return match ? match[1] : null;
|
|
146
|
+
}
|
|
147
|
+
function extractTimeout(body) {
|
|
148
|
+
return extractInlinePattern(body, /You have a maximum of (\S+) to complete/) ?? undefined;
|
|
149
|
+
}
|
|
150
|
+
function extractOnFail(body) {
|
|
151
|
+
return extractInlinePattern(body, /If you fail: (.+)/) ?? undefined;
|
|
152
|
+
}
|
|
153
|
+
function extractRetry(body) {
|
|
154
|
+
const simple = extractInlinePattern(body, /Retry up to (\d+) times/);
|
|
155
|
+
if (simple)
|
|
156
|
+
return parseInt(simple, 10);
|
|
157
|
+
const structured = body.match(/Retry strategy: max (\d+) attempts,\s*(\w+) backoff,\s*interval (\S+)/);
|
|
158
|
+
if (structured) {
|
|
159
|
+
const config = {
|
|
160
|
+
max: parseInt(structured[1], 10),
|
|
161
|
+
backoff: structured[2],
|
|
162
|
+
interval: structured[3],
|
|
163
|
+
};
|
|
164
|
+
const maxInt = body.match(/max interval (\S+)/);
|
|
165
|
+
if (maxInt)
|
|
166
|
+
config.maxInterval = maxInt[1].replace(/,$/, "");
|
|
167
|
+
if (/with jitter/.test(body))
|
|
168
|
+
config.jitter = true;
|
|
169
|
+
const nonRetryable = body.match(/non-retryable: (.+)/);
|
|
170
|
+
if (nonRetryable) {
|
|
171
|
+
config.nonRetryable = nonRetryable[1].split(/,\s*/);
|
|
172
|
+
}
|
|
173
|
+
return config;
|
|
174
|
+
}
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
function extractThinking(body) {
|
|
178
|
+
const match = body.match(/Use (\w+) level reasoning(?: \(budget: (\d+) tokens\))?/);
|
|
179
|
+
if (!match)
|
|
180
|
+
return {};
|
|
181
|
+
const result = { thinking: match[1] };
|
|
182
|
+
if (match[2])
|
|
183
|
+
result.thinkingBudget = parseInt(match[2], 10);
|
|
184
|
+
return result;
|
|
185
|
+
}
|
|
186
|
+
// ---------------------------------------------------------------------------
|
|
187
|
+
// Reverse permission mapping
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
function reversePermissionMode(mode) {
|
|
190
|
+
if (!mode)
|
|
191
|
+
return undefined;
|
|
192
|
+
switch (mode) {
|
|
193
|
+
case "plan": return "supervised";
|
|
194
|
+
case "bypassPermissions": return "unrestricted";
|
|
195
|
+
case "askUser": return "interactive";
|
|
196
|
+
case "auto": return "autonomous";
|
|
197
|
+
case "confirm": return "confirm";
|
|
198
|
+
default: return mode;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// ---------------------------------------------------------------------------
|
|
202
|
+
// AGENT.md parser
|
|
203
|
+
// ---------------------------------------------------------------------------
|
|
204
|
+
const KNOWN_FRONTMATTER_KEYS = new Set([
|
|
205
|
+
"name", "description", "model", "maxTurns", "tools", "disallowed-tools",
|
|
206
|
+
"mcpServers", "background", "permissionMode", "isolation", "sandbox",
|
|
207
|
+
"fallback-chain", "type", "members",
|
|
208
|
+
]);
|
|
209
|
+
export function parseAgentMd(agentId, content) {
|
|
210
|
+
const { fields, body } = parseFrontmatter(content);
|
|
211
|
+
// Detect node type
|
|
212
|
+
if (fields.type === "group" || body.includes("(Group Chat)")) {
|
|
213
|
+
const group = {
|
|
214
|
+
type: "group",
|
|
215
|
+
id: agentId,
|
|
216
|
+
label: agentId,
|
|
217
|
+
members: fields.members ?? [],
|
|
218
|
+
description: fields.description,
|
|
219
|
+
};
|
|
220
|
+
// Try to extract group-specific fields from body
|
|
221
|
+
const speakerMatch = body.match(/Speaker selection:\s*(.+)/);
|
|
222
|
+
if (speakerMatch)
|
|
223
|
+
group.speakerSelection = speakerMatch[1].trim();
|
|
224
|
+
const roundsMatch = body.match(/Max rounds:\s*(\d+)/);
|
|
225
|
+
if (roundsMatch)
|
|
226
|
+
group.maxRounds = parseInt(roundsMatch[1], 10);
|
|
227
|
+
const termMatch = body.match(/Termination:\s*(.+)/);
|
|
228
|
+
if (termMatch)
|
|
229
|
+
group.termination = termMatch[1].trim();
|
|
230
|
+
const timeoutMatch = body.match(/Timeout:\s*(\S+)/);
|
|
231
|
+
if (timeoutMatch)
|
|
232
|
+
group.timeout = timeoutMatch[1];
|
|
233
|
+
return group;
|
|
234
|
+
}
|
|
235
|
+
if (body.includes("(Human-in-the-Loop)")) {
|
|
236
|
+
const human = {
|
|
237
|
+
type: "human",
|
|
238
|
+
id: agentId,
|
|
239
|
+
label: agentId,
|
|
240
|
+
};
|
|
241
|
+
const descMatch = body.match(/\n\n(.+?)(?:\n\n|$)/s);
|
|
242
|
+
if (descMatch)
|
|
243
|
+
human.description = descMatch[1].trim();
|
|
244
|
+
const timeoutMatch = body.match(/Timeout:\s*(\S+)/);
|
|
245
|
+
if (timeoutMatch)
|
|
246
|
+
human.timeout = timeoutMatch[1];
|
|
247
|
+
const onTimeoutMatch = body.match(/On timeout:\s*(.+)/);
|
|
248
|
+
if (onTimeoutMatch)
|
|
249
|
+
human.onTimeout = onTimeoutMatch[1].trim();
|
|
250
|
+
return human;
|
|
251
|
+
}
|
|
252
|
+
// Regular agent
|
|
253
|
+
const agent = {
|
|
254
|
+
type: "agent",
|
|
255
|
+
id: agentId,
|
|
256
|
+
label: agentId,
|
|
257
|
+
};
|
|
258
|
+
// Frontmatter fields
|
|
259
|
+
if (fields.model)
|
|
260
|
+
agent.model = fields.model;
|
|
261
|
+
if (fields.description)
|
|
262
|
+
agent.description = fields.description;
|
|
263
|
+
if (fields.maxTurns)
|
|
264
|
+
agent.maxTurns = parseInt(fields.maxTurns, 10);
|
|
265
|
+
if (fields.tools)
|
|
266
|
+
agent.tools = fields.tools;
|
|
267
|
+
if (fields["disallowed-tools"])
|
|
268
|
+
agent.disallowedTools = fields["disallowed-tools"];
|
|
269
|
+
if (fields.mcpServers)
|
|
270
|
+
agent.mcpServers = fields.mcpServers;
|
|
271
|
+
if (fields.background === true)
|
|
272
|
+
agent.background = true;
|
|
273
|
+
if (fields.permissionMode)
|
|
274
|
+
agent.permissions = reversePermissionMode(fields.permissionMode);
|
|
275
|
+
if (fields.isolation)
|
|
276
|
+
agent.isolation = fields.isolation;
|
|
277
|
+
if (fields.sandbox !== undefined) {
|
|
278
|
+
agent.sandbox = fields.sandbox;
|
|
279
|
+
}
|
|
280
|
+
if (fields["fallback-chain"]) {
|
|
281
|
+
const fc = fields["fallback-chain"];
|
|
282
|
+
agent.fallbackChain = typeof fc === "string" ? fc.split(/,\s*/) : fc;
|
|
283
|
+
}
|
|
284
|
+
// Extensions: any unknown frontmatter keys
|
|
285
|
+
const extensions = {};
|
|
286
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
287
|
+
if (!KNOWN_FRONTMATTER_KEYS.has(key)) {
|
|
288
|
+
extensions[key] = value;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
if (Object.keys(extensions).length > 0) {
|
|
292
|
+
agent.extensions = { "claude-code": extensions };
|
|
293
|
+
}
|
|
294
|
+
// Body sections
|
|
295
|
+
agent.role = extractSection(body, "## Role")?.trim() ?? undefined;
|
|
296
|
+
// If role matches a single line, it's a role reference, not inline
|
|
297
|
+
// Keep it as the role key for now
|
|
298
|
+
const prompt = extractSection(body, "## Instructions");
|
|
299
|
+
if (prompt)
|
|
300
|
+
agent.prompt = prompt;
|
|
301
|
+
agent.reads = parseBulletList(extractSection(body, "## Reads"));
|
|
302
|
+
if (agent.reads.length === 0)
|
|
303
|
+
agent.reads = undefined;
|
|
304
|
+
agent.writes = parseBulletList(extractSection(body, "## Writes"));
|
|
305
|
+
if (agent.writes.length === 0)
|
|
306
|
+
agent.writes = undefined;
|
|
307
|
+
agent.outputs = parseOutputLines(extractSection(body, "## Outputs"));
|
|
308
|
+
// Scale
|
|
309
|
+
const scaleSection = extractSection(body, "## Scale");
|
|
310
|
+
if (scaleSection) {
|
|
311
|
+
const mode = scaleSection.match(/Mode:\s*(\S+)/);
|
|
312
|
+
const by = scaleSection.match(/By:\s*(.+)/);
|
|
313
|
+
const min = scaleSection.match(/Min:\s*(\d+)/);
|
|
314
|
+
const max = scaleSection.match(/Max:\s*(\d+)/);
|
|
315
|
+
const batchSize = scaleSection.match(/Batch size:\s*(\d+)/);
|
|
316
|
+
if (mode && by && min && max) {
|
|
317
|
+
agent.scale = {
|
|
318
|
+
mode: mode[1],
|
|
319
|
+
by: by[1].trim(),
|
|
320
|
+
min: parseInt(min[1], 10),
|
|
321
|
+
max: parseInt(max[1], 10),
|
|
322
|
+
batchSize: batchSize ? parseInt(batchSize[1], 10) : null,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
// Circuit breaker
|
|
327
|
+
const cbSection = extractSection(body, "## Circuit Breaker");
|
|
328
|
+
if (cbSection) {
|
|
329
|
+
const threshold = cbSection.match(/Failure threshold:\s*(\d+)/);
|
|
330
|
+
const window = cbSection.match(/Window:\s*(\S+)/);
|
|
331
|
+
const cooldown = cbSection.match(/Cooldown:\s*(\S+)/);
|
|
332
|
+
if (threshold && window && cooldown) {
|
|
333
|
+
agent.circuitBreaker = {
|
|
334
|
+
threshold: parseInt(threshold[1], 10),
|
|
335
|
+
window: window[1],
|
|
336
|
+
cooldown: cooldown[1],
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
// Model configuration
|
|
341
|
+
const modelConfig = extractSection(body, "## Model Configuration");
|
|
342
|
+
if (modelConfig) {
|
|
343
|
+
const temp = modelConfig.match(/Temperature:\s*([\d.]+)/);
|
|
344
|
+
if (temp)
|
|
345
|
+
agent.temperature = parseFloat(temp[1]);
|
|
346
|
+
const maxTokens = modelConfig.match(/Max tokens:\s*(\d+)/);
|
|
347
|
+
if (maxTokens)
|
|
348
|
+
agent.maxTokens = parseInt(maxTokens[1], 10);
|
|
349
|
+
const topP = modelConfig.match(/Top-p:\s*([\d.]+)/);
|
|
350
|
+
if (topP)
|
|
351
|
+
agent.topP = parseFloat(topP[1]);
|
|
352
|
+
const topK = modelConfig.match(/Top-k:\s*(\d+)/);
|
|
353
|
+
if (topK)
|
|
354
|
+
agent.topK = parseInt(topK[1], 10);
|
|
355
|
+
const stopMatch = modelConfig.match(/Stop sequences:\s*(.+)/);
|
|
356
|
+
if (stopMatch)
|
|
357
|
+
agent.stop = stopMatch[1].split(/,\s*/);
|
|
358
|
+
const seed = modelConfig.match(/Seed:\s*(\d+)/);
|
|
359
|
+
if (seed)
|
|
360
|
+
agent.seed = parseInt(seed[1], 10);
|
|
361
|
+
}
|
|
362
|
+
// Input/Output schema
|
|
363
|
+
const inputSchema = extractSection(body, "## Input Schema");
|
|
364
|
+
if (inputSchema)
|
|
365
|
+
agent.inputSchema = parseSchemaFields(inputSchema);
|
|
366
|
+
const outputSchema = extractSection(body, "## Output Schema");
|
|
367
|
+
if (outputSchema)
|
|
368
|
+
agent.outputSchema = parseSchemaFields(outputSchema);
|
|
369
|
+
// Artifacts
|
|
370
|
+
const artifactsSection = extractSection(body, "## Artifacts");
|
|
371
|
+
if (artifactsSection) {
|
|
372
|
+
const producesMatch = artifactsSection.match(/Produces:\s*(.+)/);
|
|
373
|
+
if (producesMatch)
|
|
374
|
+
agent.produces = producesMatch[1].split(/,\s*/);
|
|
375
|
+
const consumesMatch = artifactsSection.match(/Consumes:\s*(.+)/);
|
|
376
|
+
if (consumesMatch)
|
|
377
|
+
agent.consumes = consumesMatch[1].split(/,\s*/);
|
|
378
|
+
}
|
|
379
|
+
// Inline patterns
|
|
380
|
+
agent.timeout = extractTimeout(body);
|
|
381
|
+
agent.onFail = extractOnFail(body);
|
|
382
|
+
agent.retry = extractRetry(body);
|
|
383
|
+
const thinkingInfo = extractThinking(body);
|
|
384
|
+
if (thinkingInfo.thinking)
|
|
385
|
+
agent.thinking = thinkingInfo.thinking;
|
|
386
|
+
if (thinkingInfo.thinkingBudget)
|
|
387
|
+
agent.thinkingBudget = thinkingInfo.thinkingBudget;
|
|
388
|
+
const outputFormat = extractInlinePattern(body, /Output format:\s*(\S+)/);
|
|
389
|
+
if (outputFormat)
|
|
390
|
+
agent.outputFormat = outputFormat;
|
|
391
|
+
const logLevel = extractInlinePattern(body, /Log level:\s*(\S+)/);
|
|
392
|
+
if (logLevel)
|
|
393
|
+
agent.logLevel = logLevel;
|
|
394
|
+
const join = extractInlinePattern(body, /Wait strategy:\s*(\S+)/);
|
|
395
|
+
if (join)
|
|
396
|
+
agent.join = join;
|
|
397
|
+
const compensates = extractInlinePattern(body, /compensates.*?:\s*(\S+)/);
|
|
398
|
+
if (compensates)
|
|
399
|
+
agent.compensates = compensates;
|
|
400
|
+
const invocation = extractInlinePattern(body, /Invocation:\s*(\S+)/);
|
|
401
|
+
if (invocation)
|
|
402
|
+
agent.invocation = invocation;
|
|
403
|
+
const skillsInline = extractInlinePattern(body, /Skills:\s*(.+)/);
|
|
404
|
+
if (skillsInline)
|
|
405
|
+
agent.skills = skillsInline.split(/,\s*/);
|
|
406
|
+
const behavior = extractInlinePattern(body, /Behavior:\s*(\S+)/);
|
|
407
|
+
if (behavior)
|
|
408
|
+
agent.behavior = behavior;
|
|
409
|
+
const skip = extractInlinePattern(body, /Skip when:\s*(.+)/);
|
|
410
|
+
if (skip)
|
|
411
|
+
agent.skip = skip;
|
|
412
|
+
const rateLimit = extractInlinePattern(body, /Rate limit:\s*(.+)/);
|
|
413
|
+
if (rateLimit)
|
|
414
|
+
agent.rateLimit = rateLimit;
|
|
415
|
+
return agent;
|
|
416
|
+
}
|
|
417
|
+
function parseSchemaFields(text) {
|
|
418
|
+
const fields = [];
|
|
419
|
+
for (const line of text.split("\n")) {
|
|
420
|
+
const match = line.trim().match(/^-\s+\*\*(\w+?)(\?)?\*\*.*?:\s*(.+)/);
|
|
421
|
+
if (!match)
|
|
422
|
+
continue;
|
|
423
|
+
const optional = match[2] === "?";
|
|
424
|
+
fields.push({
|
|
425
|
+
name: match[1],
|
|
426
|
+
type: parseSchemaTypeStr(match[3].trim()),
|
|
427
|
+
optional,
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
return fields;
|
|
431
|
+
}
|
|
432
|
+
function parseSchemaTypeStr(typeStr) {
|
|
433
|
+
if (typeStr.endsWith("[]")) {
|
|
434
|
+
return { kind: "array", itemType: parseSchemaTypeStr(typeStr.slice(0, -2)) };
|
|
435
|
+
}
|
|
436
|
+
if (typeStr.includes(" | ")) {
|
|
437
|
+
return { kind: "enum", values: typeStr.split(/\s*\|\s*/) };
|
|
438
|
+
}
|
|
439
|
+
const primitives = ["string", "number", "integer", "boolean", "object"];
|
|
440
|
+
if (primitives.includes(typeStr)) {
|
|
441
|
+
return { kind: "primitive", value: typeStr };
|
|
442
|
+
}
|
|
443
|
+
return { kind: "ref", name: typeStr };
|
|
444
|
+
}
|
|
445
|
+
export function parseSkillMd(content) {
|
|
446
|
+
const { fields, body } = parseFrontmatter(content);
|
|
447
|
+
const result = {
|
|
448
|
+
isTopology: !!fields.topology,
|
|
449
|
+
name: (fields.topology || fields.name),
|
|
450
|
+
version: fields.version,
|
|
451
|
+
description: fields.description,
|
|
452
|
+
patterns: fields.patterns,
|
|
453
|
+
edges: [],
|
|
454
|
+
gates: [],
|
|
455
|
+
triggers: [],
|
|
456
|
+
agentIds: new Set(),
|
|
457
|
+
};
|
|
458
|
+
// Orchestrator
|
|
459
|
+
const orchSection = extractSkillSection(body, "## Orchestrator");
|
|
460
|
+
if (orchSection) {
|
|
461
|
+
const modelMatch = orchSection.match(/Model:\s*(\S+)/);
|
|
462
|
+
const handlesMatch = orchSection.match(/Handles:\s*(.+)/);
|
|
463
|
+
if (modelMatch) {
|
|
464
|
+
const orch = {
|
|
465
|
+
type: "orchestrator",
|
|
466
|
+
id: "orchestrator",
|
|
467
|
+
label: "Orchestrator",
|
|
468
|
+
model: modelMatch[1],
|
|
469
|
+
handles: handlesMatch ? handlesMatch[1].split(/,\s*/) : [],
|
|
470
|
+
};
|
|
471
|
+
// Outputs
|
|
472
|
+
const outputsSection = extractSection(orchSection, "### Outputs");
|
|
473
|
+
if (outputsSection) {
|
|
474
|
+
orch.outputs = parseOutputLines(outputsSection);
|
|
475
|
+
}
|
|
476
|
+
const genMatch = orchSection.match(/Generates:\s*(.+)/);
|
|
477
|
+
if (genMatch)
|
|
478
|
+
orch.generates = genMatch[1].trim();
|
|
479
|
+
result.orchestrator = orch;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
// Flow
|
|
483
|
+
const flowSection = extractSkillSection(body, "## Flow");
|
|
484
|
+
if (flowSection) {
|
|
485
|
+
for (const line of flowSection.split("\n")) {
|
|
486
|
+
const trimmed = line.trim();
|
|
487
|
+
if (!trimmed.startsWith("- "))
|
|
488
|
+
continue;
|
|
489
|
+
const edgeLine = trimmed.slice(2).trim();
|
|
490
|
+
const edge = parseEdgeLine(edgeLine);
|
|
491
|
+
if (edge) {
|
|
492
|
+
result.edges.push(edge);
|
|
493
|
+
result.agentIds.add(edge.from);
|
|
494
|
+
result.agentIds.add(edge.to);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
// Gates
|
|
499
|
+
const gatesSection = extractSkillSection(body, "## Gates");
|
|
500
|
+
if (gatesSection) {
|
|
501
|
+
// Split by ### headings
|
|
502
|
+
const gateParts = gatesSection.split(/^### /m).filter((s) => s.trim());
|
|
503
|
+
for (const part of gateParts) {
|
|
504
|
+
const lines = part.split("\n");
|
|
505
|
+
const gateId = lines[0].trim().toLowerCase().replace(/\s+/g, "-");
|
|
506
|
+
const gate = {
|
|
507
|
+
type: "gate",
|
|
508
|
+
id: gateId,
|
|
509
|
+
label: gateId,
|
|
510
|
+
};
|
|
511
|
+
for (const line of lines.slice(1)) {
|
|
512
|
+
const trimmed = line.trim();
|
|
513
|
+
if (trimmed.startsWith("After:"))
|
|
514
|
+
gate.after = trimmed.slice(6).trim();
|
|
515
|
+
if (trimmed.startsWith("Before:"))
|
|
516
|
+
gate.before = trimmed.slice(7).trim();
|
|
517
|
+
if (trimmed.startsWith("Run:"))
|
|
518
|
+
gate.run = trimmed.slice(4).trim();
|
|
519
|
+
if (trimmed.startsWith("Checks:"))
|
|
520
|
+
gate.checks = trimmed.slice(7).trim().split(/,\s*/);
|
|
521
|
+
if (trimmed.startsWith("On fail:"))
|
|
522
|
+
gate.onFail = trimmed.slice(8).trim();
|
|
523
|
+
if (trimmed.startsWith("Retry:"))
|
|
524
|
+
gate.retry = parseInt(trimmed.slice(6).trim(), 10);
|
|
525
|
+
if (trimmed.startsWith("Timeout:"))
|
|
526
|
+
gate.timeout = trimmed.slice(8).trim();
|
|
527
|
+
}
|
|
528
|
+
result.gates.push(gate);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
// Triggers
|
|
532
|
+
const triggersSection = extractSkillSection(body, "## Triggers");
|
|
533
|
+
if (triggersSection) {
|
|
534
|
+
const triggerParts = triggersSection.split(/^### /m).filter((s) => s.trim());
|
|
535
|
+
for (const part of triggerParts) {
|
|
536
|
+
const lines = part.split("\n");
|
|
537
|
+
const name = lines[0].trim().replace(/^\//, "");
|
|
538
|
+
let pattern = "";
|
|
539
|
+
let argument;
|
|
540
|
+
for (const line of lines.slice(1)) {
|
|
541
|
+
const trimmed = line.trim();
|
|
542
|
+
const patternMatch = trimmed.match(/^Pattern:\s*`?(.+?)`?\s*$/);
|
|
543
|
+
if (patternMatch)
|
|
544
|
+
pattern = patternMatch[1];
|
|
545
|
+
const argMatch = trimmed.match(/^Argument:\s*(\S+)/);
|
|
546
|
+
if (argMatch)
|
|
547
|
+
argument = argMatch[1];
|
|
548
|
+
}
|
|
549
|
+
if (name && pattern) {
|
|
550
|
+
result.triggers.push({ name, pattern, argument });
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
// Extract additional topology-level metadata from body text
|
|
555
|
+
const domainMatch = body.match(/^Domain:\s*(.+)/m);
|
|
556
|
+
if (domainMatch)
|
|
557
|
+
result.domain = domainMatch[1].trim();
|
|
558
|
+
const foundationsMatch = body.match(/^Foundations:\s*(.+)/m);
|
|
559
|
+
if (foundationsMatch)
|
|
560
|
+
result.foundations = foundationsMatch[1].split(/,\s*/);
|
|
561
|
+
const advancedMatch = body.match(/^Advanced:\s*(.+)/m);
|
|
562
|
+
if (advancedMatch)
|
|
563
|
+
result.advanced = advancedMatch[1].split(/,\s*/);
|
|
564
|
+
const timeoutMatch = body.match(/^Timeout:\s*(\S+)/m);
|
|
565
|
+
if (timeoutMatch)
|
|
566
|
+
result.timeout = timeoutMatch[1];
|
|
567
|
+
const errorMatch = body.match(/handled by:\s*\*\*(\S+)\*\*/);
|
|
568
|
+
if (errorMatch)
|
|
569
|
+
result.errorHandler = errorMatch[1];
|
|
570
|
+
if (/Durable:\s*yes/i.test(body))
|
|
571
|
+
result.durable = true;
|
|
572
|
+
return result;
|
|
573
|
+
}
|
|
574
|
+
function parseEdgeLine(line) {
|
|
575
|
+
// Match: from -> to [attrs]... (one or more bracket groups)
|
|
576
|
+
// Also: from -x-> to, from -x[type]-> to
|
|
577
|
+
const match = line.match(/^(\S+)\s+(->|-x(?:\[(\w+)\])?->)\s+(\S+)((?:\s+\[[^\]]+\])*)$/);
|
|
578
|
+
if (!match)
|
|
579
|
+
return null;
|
|
580
|
+
const edge = {
|
|
581
|
+
from: match[1],
|
|
582
|
+
to: match[4],
|
|
583
|
+
condition: null,
|
|
584
|
+
maxIterations: null,
|
|
585
|
+
};
|
|
586
|
+
// Error edge
|
|
587
|
+
if (match[2].startsWith("-x")) {
|
|
588
|
+
edge.isError = true;
|
|
589
|
+
if (match[3])
|
|
590
|
+
edge.errorType = match[3];
|
|
591
|
+
}
|
|
592
|
+
// Parse attributes — merge all bracket groups into one string
|
|
593
|
+
const bracketGroups = match[5]?.trim();
|
|
594
|
+
if (bracketGroups) {
|
|
595
|
+
// Extract contents from all [...] groups and merge
|
|
596
|
+
const allAttrs = Array.from(bracketGroups.matchAll(/\[([^\]]+)\]/g))
|
|
597
|
+
.map((m) => m[1])
|
|
598
|
+
.join(", ");
|
|
599
|
+
const whenMatch = allAttrs.match(/when\s+(.+?)(?:,\s*(?:max|per|race|tolerance|wait|weight|reflection)|$)/);
|
|
600
|
+
if (whenMatch)
|
|
601
|
+
edge.condition = whenMatch[1].trim();
|
|
602
|
+
const maxMatch = allAttrs.match(/max\s+(\d+)/);
|
|
603
|
+
if (maxMatch)
|
|
604
|
+
edge.maxIterations = parseInt(maxMatch[1], 10);
|
|
605
|
+
const perMatch = allAttrs.match(/per\s+(\S+)/);
|
|
606
|
+
if (perMatch)
|
|
607
|
+
edge.per = perMatch[1];
|
|
608
|
+
if (/\brace\b/.test(allAttrs))
|
|
609
|
+
edge.race = true;
|
|
610
|
+
const tolMatch = allAttrs.match(/tolerance:\s*(\S+)/);
|
|
611
|
+
if (tolMatch)
|
|
612
|
+
edge.tolerance = tolMatch[1].includes("%") ? tolMatch[1] : parseInt(tolMatch[1], 10);
|
|
613
|
+
const waitMatch = allAttrs.match(/wait\s+(\S+)/);
|
|
614
|
+
if (waitMatch)
|
|
615
|
+
edge.wait = waitMatch[1];
|
|
616
|
+
const weightMatch = allAttrs.match(/weight\s+([\d.]+)/);
|
|
617
|
+
if (weightMatch)
|
|
618
|
+
edge.weight = parseFloat(weightMatch[1]);
|
|
619
|
+
if (/\breflection\b/.test(allAttrs))
|
|
620
|
+
edge.reflection = true;
|
|
621
|
+
}
|
|
622
|
+
return edge;
|
|
623
|
+
}
|
|
624
|
+
// ---------------------------------------------------------------------------
|
|
625
|
+
// settings.json parser
|
|
626
|
+
// ---------------------------------------------------------------------------
|
|
627
|
+
export function parseSettingsJson(content) {
|
|
628
|
+
const json = JSON.parse(content);
|
|
629
|
+
const settings = {};
|
|
630
|
+
const hooks = [];
|
|
631
|
+
if (json.allow)
|
|
632
|
+
settings.allow = json.allow;
|
|
633
|
+
if (json.deny)
|
|
634
|
+
settings.deny = json.deny;
|
|
635
|
+
if (json.ask)
|
|
636
|
+
settings.ask = json.ask;
|
|
637
|
+
// Parse hooks
|
|
638
|
+
if (json.hooks) {
|
|
639
|
+
for (const [event, handlers] of Object.entries(json.hooks)) {
|
|
640
|
+
if (!Array.isArray(handlers))
|
|
641
|
+
continue;
|
|
642
|
+
for (const handler of handlers) {
|
|
643
|
+
const hookList = handler.hooks ?? [];
|
|
644
|
+
const matcher = handler.matcher ?? "";
|
|
645
|
+
for (const hookEntry of hookList) {
|
|
646
|
+
// Skip gate enforcement hooks (generated, not user-defined)
|
|
647
|
+
if (hookEntry.command && /gate-[\w-]+\.sh/.test(hookEntry.command))
|
|
648
|
+
continue;
|
|
649
|
+
const command = hookEntry.command ?? "";
|
|
650
|
+
// Extract script name for hook id
|
|
651
|
+
const nameMatch = command.match(/([^/]+?)(?:\.sh)?$/);
|
|
652
|
+
const name = nameMatch ? nameMatch[1] : event.toLowerCase();
|
|
653
|
+
hooks.push({
|
|
654
|
+
name,
|
|
655
|
+
on: event,
|
|
656
|
+
matcher,
|
|
657
|
+
run: command,
|
|
658
|
+
type: hookEntry.type,
|
|
659
|
+
timeout: hookEntry.timeout,
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
return { settings, hooks };
|
|
666
|
+
}
|
|
667
|
+
// ---------------------------------------------------------------------------
|
|
668
|
+
// .mcp.json parser
|
|
669
|
+
// ---------------------------------------------------------------------------
|
|
670
|
+
export function parseMcpJson(content) {
|
|
671
|
+
const json = JSON.parse(content);
|
|
672
|
+
return json.mcpServers ?? {};
|
|
673
|
+
}
|
|
674
|
+
// ---------------------------------------------------------------------------
|
|
675
|
+
// Command file parser
|
|
676
|
+
// ---------------------------------------------------------------------------
|
|
677
|
+
export function parseCommandMd(filename, content) {
|
|
678
|
+
const name = filename.replace(/\.md$/, "");
|
|
679
|
+
const { body } = parseFrontmatter(content);
|
|
680
|
+
// Look for pattern in body: /command-name or just the first line
|
|
681
|
+
const patternMatch = body.match(/^#\s+\/(.+)/m);
|
|
682
|
+
const pattern = patternMatch ? `/${patternMatch[1].trim()}` : `/${name}`;
|
|
683
|
+
// Look for argument
|
|
684
|
+
const argMatch = body.match(/## Arguments\s*\n-\s*(\w+)/);
|
|
685
|
+
const argument = argMatch ? argMatch[1] : undefined;
|
|
686
|
+
return { name, pattern, argument };
|
|
687
|
+
}
|
|
688
|
+
// ---------------------------------------------------------------------------
|
|
689
|
+
// Action script parser
|
|
690
|
+
// ---------------------------------------------------------------------------
|
|
691
|
+
export function parseActionScript(filename, content) {
|
|
692
|
+
// Extract id from filename: action-<id>.sh → <id>
|
|
693
|
+
const idMatch = filename.match(/action-(.+?)\.sh$/);
|
|
694
|
+
if (!idMatch)
|
|
695
|
+
return null;
|
|
696
|
+
const id = idMatch[1];
|
|
697
|
+
const action = {
|
|
698
|
+
type: "action",
|
|
699
|
+
id,
|
|
700
|
+
label: id,
|
|
701
|
+
};
|
|
702
|
+
// Parse comment headers
|
|
703
|
+
const descMatch = content.match(/# Action:\s*\S+\s*—\s*(.+)/);
|
|
704
|
+
if (descMatch)
|
|
705
|
+
action.description = descMatch[1].trim();
|
|
706
|
+
if (content.includes("# External source:")) {
|
|
707
|
+
action.kind = "external";
|
|
708
|
+
const sourceMatch = content.match(/# External source:\s*(.+)/);
|
|
709
|
+
if (sourceMatch)
|
|
710
|
+
action.source = sourceMatch[1].trim();
|
|
711
|
+
}
|
|
712
|
+
else if (content.includes("git action")) {
|
|
713
|
+
action.kind = "git";
|
|
714
|
+
}
|
|
715
|
+
else if (content.includes("report action")) {
|
|
716
|
+
action.kind = "report";
|
|
717
|
+
}
|
|
718
|
+
else {
|
|
719
|
+
action.kind = "inline";
|
|
720
|
+
}
|
|
721
|
+
const timeoutMatch = content.match(/# Timeout:\s*(\S+)/);
|
|
722
|
+
if (timeoutMatch)
|
|
723
|
+
action.timeout = timeoutMatch[1];
|
|
724
|
+
const onFailMatch = content.match(/# On failure:\s*(.+)/);
|
|
725
|
+
if (onFailMatch)
|
|
726
|
+
action.onFail = onFailMatch[1].trim();
|
|
727
|
+
return action;
|
|
728
|
+
}
|
|
729
|
+
// ---------------------------------------------------------------------------
|
|
730
|
+
// Main import function
|
|
731
|
+
// ---------------------------------------------------------------------------
|
|
732
|
+
export function importClaudeCode(files, topologyName) {
|
|
733
|
+
const nodes = [];
|
|
734
|
+
const roles = {};
|
|
735
|
+
// 1. Parse AGENT.md files
|
|
736
|
+
for (const file of files) {
|
|
737
|
+
const match = file.path.match(/(?:^|[\\/])agents[\\/]([^\\/]+)[\\/]AGENT\.md$/);
|
|
738
|
+
if (!match)
|
|
739
|
+
continue;
|
|
740
|
+
const agentId = match[1];
|
|
741
|
+
const node = parseAgentMd(agentId, file.content);
|
|
742
|
+
if (node) {
|
|
743
|
+
// Extract role description into roles map and set agent.role to the key
|
|
744
|
+
if (node.type === "agent" && node.role) {
|
|
745
|
+
roles[agentId] = node.role;
|
|
746
|
+
node.role = agentId;
|
|
747
|
+
}
|
|
748
|
+
nodes.push(node);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
// 2. Parse SKILL.md files for topology metadata
|
|
752
|
+
let topologyMeta = null;
|
|
753
|
+
const additionalSkills = [];
|
|
754
|
+
for (const file of files) {
|
|
755
|
+
const match = file.path.match(/(?:^|[\\/])skills[\\/]([^\\/]+)[\\/]SKILL\.md$/);
|
|
756
|
+
if (!match)
|
|
757
|
+
continue;
|
|
758
|
+
const skillResult = parseSkillMd(file.content);
|
|
759
|
+
if (skillResult.isTopology && !topologyMeta) {
|
|
760
|
+
topologyMeta = skillResult;
|
|
761
|
+
}
|
|
762
|
+
else {
|
|
763
|
+
// Non-topology skill
|
|
764
|
+
const { fields } = parseFrontmatter(file.content);
|
|
765
|
+
additionalSkills.push({
|
|
766
|
+
id: match[1],
|
|
767
|
+
description: fields.description ?? "",
|
|
768
|
+
disableModelInvocation: fields["disable-model-invocation"],
|
|
769
|
+
userInvocable: fields["user-invocable"],
|
|
770
|
+
context: fields.context,
|
|
771
|
+
agent: fields.agent,
|
|
772
|
+
allowedTools: fields["allowed-tools"],
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
// 3. Parse settings.json
|
|
777
|
+
let settings = {};
|
|
778
|
+
let hooks = [];
|
|
779
|
+
const settingsFile = files.find((f) => f.path.endsWith("settings.json") && f.path.includes(".claude"));
|
|
780
|
+
if (settingsFile) {
|
|
781
|
+
const parsed = parseSettingsJson(settingsFile.content);
|
|
782
|
+
settings = parsed.settings;
|
|
783
|
+
hooks = parsed.hooks;
|
|
784
|
+
}
|
|
785
|
+
// 4. Parse .mcp.json
|
|
786
|
+
let mcpServers = {};
|
|
787
|
+
const mcpFile = files.find((f) => f.path.endsWith(".mcp.json"));
|
|
788
|
+
if (mcpFile) {
|
|
789
|
+
mcpServers = parseMcpJson(mcpFile.content);
|
|
790
|
+
}
|
|
791
|
+
// 5. Parse command files for triggers (only if no SKILL.md triggers)
|
|
792
|
+
let triggers = topologyMeta?.triggers ?? [];
|
|
793
|
+
if (triggers.length === 0) {
|
|
794
|
+
for (const file of files) {
|
|
795
|
+
const match = file.path.match(/(?:^|[\\/])commands[\\/](.+\.md)$/);
|
|
796
|
+
if (!match)
|
|
797
|
+
continue;
|
|
798
|
+
const trigger = parseCommandMd(match[1], file.content);
|
|
799
|
+
if (trigger)
|
|
800
|
+
triggers.push(trigger);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
// 6. Parse action scripts
|
|
804
|
+
for (const file of files) {
|
|
805
|
+
const match = file.path.match(/(?:^|[\\/])scripts[\\/](action-.+\.sh)$/);
|
|
806
|
+
if (!match)
|
|
807
|
+
continue;
|
|
808
|
+
const action = parseActionScript(match[1], file.content);
|
|
809
|
+
if (action) {
|
|
810
|
+
// Don't add if already in nodes (from SKILL.md orchestrator handles)
|
|
811
|
+
if (!nodes.some((n) => n.id === action.id)) {
|
|
812
|
+
nodes.push(action);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
// 7. Build orchestrator
|
|
817
|
+
if (topologyMeta?.orchestrator) {
|
|
818
|
+
nodes.unshift(topologyMeta.orchestrator);
|
|
819
|
+
}
|
|
820
|
+
// 8. Add gates from SKILL.md
|
|
821
|
+
if (topologyMeta?.gates) {
|
|
822
|
+
nodes.push(...topologyMeta.gates);
|
|
823
|
+
}
|
|
824
|
+
// 9. Build edges — from SKILL.md or generate linear flow
|
|
825
|
+
let edges = topologyMeta?.edges ?? [];
|
|
826
|
+
if (edges.length === 0) {
|
|
827
|
+
// Generate linear flow from agent ordering
|
|
828
|
+
const agentNodes = nodes.filter((n) => n.type === "agent");
|
|
829
|
+
for (let i = 0; i < agentNodes.length - 1; i++) {
|
|
830
|
+
edges.push({
|
|
831
|
+
from: agentNodes[i].id,
|
|
832
|
+
to: agentNodes[i + 1].id,
|
|
833
|
+
condition: null,
|
|
834
|
+
maxIterations: null,
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
// 10. Add action nodes for orchestrator handles
|
|
839
|
+
if (topologyMeta?.orchestrator) {
|
|
840
|
+
for (const handleId of topologyMeta.orchestrator.handles) {
|
|
841
|
+
if (!nodes.some((n) => n.id === handleId)) {
|
|
842
|
+
nodes.push({
|
|
843
|
+
type: "action",
|
|
844
|
+
id: handleId,
|
|
845
|
+
label: handleId,
|
|
846
|
+
kind: "inline",
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
// Build topology meta
|
|
852
|
+
const meta = {
|
|
853
|
+
name: topologyMeta?.name ?? topologyName,
|
|
854
|
+
version: topologyMeta?.version ?? "1.0.0",
|
|
855
|
+
description: topologyMeta?.description ?? "",
|
|
856
|
+
patterns: topologyMeta?.patterns ?? [],
|
|
857
|
+
foundations: topologyMeta?.foundations,
|
|
858
|
+
advanced: topologyMeta?.advanced,
|
|
859
|
+
domain: topologyMeta?.domain,
|
|
860
|
+
timeout: topologyMeta?.timeout,
|
|
861
|
+
errorHandler: topologyMeta?.errorHandler,
|
|
862
|
+
durable: topologyMeta?.durable,
|
|
863
|
+
};
|
|
864
|
+
const ast = {
|
|
865
|
+
topology: meta,
|
|
866
|
+
nodes,
|
|
867
|
+
edges,
|
|
868
|
+
depth: { factors: [], levels: [] },
|
|
869
|
+
memory: {},
|
|
870
|
+
batch: {},
|
|
871
|
+
environments: {},
|
|
872
|
+
triggers,
|
|
873
|
+
hooks,
|
|
874
|
+
settings,
|
|
875
|
+
mcpServers,
|
|
876
|
+
metering: null,
|
|
877
|
+
skills: additionalSkills,
|
|
878
|
+
toolDefs: [],
|
|
879
|
+
roles,
|
|
880
|
+
context: {},
|
|
881
|
+
env: {},
|
|
882
|
+
providers: [],
|
|
883
|
+
schedules: [],
|
|
884
|
+
interfaces: [],
|
|
885
|
+
defaults: null,
|
|
886
|
+
schemas: [],
|
|
887
|
+
observability: null,
|
|
888
|
+
params: [],
|
|
889
|
+
interfaceEndpoints: null,
|
|
890
|
+
imports: [],
|
|
891
|
+
includes: [],
|
|
892
|
+
checkpoint: null,
|
|
893
|
+
artifacts: [],
|
|
894
|
+
};
|
|
895
|
+
return ast;
|
|
896
|
+
}
|
|
897
|
+
//# sourceMappingURL=claude-code.js.map
|