@darkhorseprojects/circuitry 0.3.1 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bundle.d.ts CHANGED
@@ -16,13 +16,14 @@ type SceneElement = {
16
16
  elementId?: string;
17
17
  } | null;
18
18
  };
19
- export declare const BUNDLE_MIME = "application/vnd.circuitry.bundle+json";
19
+ export declare const BUNDLE_MIME = "application/vnd.circuitry.bundle+yaml";
20
20
  export type CircuitryBundle = {
21
21
  version: 1;
22
22
  createdAt: string;
23
23
  elements: SceneElement[];
24
24
  };
25
25
  export declare const createBundleFromSelection: (elements: readonly SceneElement[], selectedIds: Readonly<Record<string, true>>) => CircuitryBundle | null;
26
+ export declare const stringifyBundle: (bundle: CircuitryBundle) => string;
26
27
  export declare const parseBundleText: (text: string) => CircuitryBundle;
27
28
  export declare const importBundleElements: (bundle: CircuitryBundle, opts?: {
28
29
  offsetX?: number;
package/dist/cli.js CHANGED
@@ -1,14 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ // src/cli.ts
4
+ import { readFile as readFile2 } from "node:fs/promises";
5
+
3
6
  // src/node.ts
4
7
  import { access, readFile, writeFile } from "node:fs/promises";
5
8
  import path from "node:path";
6
9
 
7
10
  // src/yaml.ts
8
- import YAML from "yaml";
11
+ import YAML2 from "yaml";
9
12
 
10
13
  // src/graph.ts
11
- var CIRCUITRY_SPEC_VERSION = "0.3.1";
14
+ import YAML from "yaml";
15
+ var CIRCUITRY_SPEC_VERSION = "0.3.2";
12
16
  var DEFAULT_CIRCUITRY_VALIDATION_RULES = [
13
17
  "no-self-loops",
14
18
  "no-unknown-edge-endpoints",
@@ -21,25 +25,15 @@ var DEFAULT_CIRCUITRY_VALIDATION_STANDARD = {
21
25
  rules: DEFAULT_CIRCUITRY_VALIDATION_RULES,
22
26
  executableKinds: ["agent", "tool", "output"]
23
27
  };
24
- var executableKindsFromRules = (rules = []) => {
25
- const executableRule = rules.find(
26
- (entry) => typeof entry !== "string" && entry.rule === "require-executable-inputs"
27
- );
28
- return executableRule?.executableKinds;
29
- };
28
+ var ruleName = (rule) => typeof rule === "string" ? rule : rule.rule;
29
+ var executableKindsFromRules = (rules) => rules.find((rule) => typeof rule !== "string" && rule.rule === "require-executable-inputs")?.executableKinds;
30
30
  var createCircuitryValidationStandard = (standard = {}, graph) => {
31
- const rules = [
32
- ...DEFAULT_CIRCUITRY_VALIDATION_RULES,
33
- ...graph?.validation?.rules || [],
34
- ...standard.rules || []
35
- ];
31
+ const rules = [...DEFAULT_CIRCUITRY_VALIDATION_RULES, ...graph?.validation?.rules || [], ...standard.rules || []];
36
32
  return {
37
33
  version: standard.version || DEFAULT_CIRCUITRY_VALIDATION_STANDARD.version,
38
34
  requireSpecVersion: standard.requireSpecVersion ?? DEFAULT_CIRCUITRY_VALIDATION_STANDARD.requireSpecVersion,
39
35
  rules,
40
- executableKinds: standard.executableKinds || executableKindsFromRules(rules) || [
41
- ...DEFAULT_CIRCUITRY_VALIDATION_STANDARD.executableKinds
42
- ]
36
+ executableKinds: standard.executableKinds || executableKindsFromRules(rules) || [...DEFAULT_CIRCUITRY_VALIDATION_STANDARD.executableKinds]
43
37
  };
44
38
  };
45
39
  var expandResources = (resources) => {
@@ -47,12 +41,7 @@ var expandResources = (resources) => {
47
41
  const edges = [];
48
42
  for (const [id, resource] of Object.entries(resources)) {
49
43
  if (resource.type === "text") {
50
- nodes.push({
51
- id,
52
- kind: "input",
53
- label: resource.label || id,
54
- input: { type: "text", value: resource.value }
55
- });
44
+ nodes.push({ id, kind: "input", label: resource.label || id, input: { type: "text", value: resource.value } });
56
45
  } else if (resource.type === "agent") {
57
46
  const agent = resource;
58
47
  nodes.push({
@@ -62,7 +51,6 @@ var expandResources = (resources) => {
62
51
  agent: {
63
52
  identity: agent.identity || agent.label || id,
64
53
  model: agent.model,
65
- thinkingLevel: agent.thinkingLevel,
66
54
  tools: agent.tools,
67
55
  instructions: agent.instructions,
68
56
  personality: agent.personality,
@@ -71,274 +59,131 @@ var expandResources = (resources) => {
71
59
  skills: agent.skills,
72
60
  expect: agent.expect
73
61
  });
74
- for (const inputRef of agent.inputs || []) {
75
- edges.push({ from: inputRef, to: id, kind: "dependency" });
76
- }
62
+ for (const input of agent.inputs || []) edges.push({ from: input, to: id, kind: "dependency" });
77
63
  } else if (resource.type === "tool") {
78
64
  const tool = resource;
79
- nodes.push({
80
- id,
81
- kind: "tool",
82
- label: tool.label || id,
83
- agent: { instructions: tool.instructions }
84
- });
85
- for (const inputRef of tool.inputs || []) {
86
- edges.push({ from: inputRef, to: id, kind: "dependency" });
87
- }
65
+ nodes.push({ id, kind: "tool", label: tool.label || id, agent: { instructions: tool.instructions } });
66
+ for (const input of tool.inputs || []) edges.push({ from: input, to: id, kind: "dependency" });
88
67
  } else {
89
68
  const input = resource;
90
- nodes.push({
91
- id,
92
- kind: "input",
93
- label: input.label || id,
94
- input: {
95
- type: input.type,
96
- value: input.value,
97
- uri: input.uri || input.path,
98
- mimeType: input.mimeType,
99
- data: input.data
100
- }
101
- });
69
+ nodes.push({ id, kind: "input", label: input.label || id, input: { type: input.type, value: input.value, uri: input.uri || input.path, mimeType: input.mimeType, data: input.data } });
102
70
  }
103
71
  }
104
72
  return { nodes, edges };
105
73
  };
106
74
  var normalizeCircuitryGraph = (graph) => {
107
- if (!graph.resources || Object.keys(graph.resources).length === 0) return { ...graph };
108
- const { nodes, edges } = expandResources(graph.resources);
109
- return { ...graph, nodes, edges };
75
+ if (!graph.resources) return { ...graph };
76
+ return { ...graph, ...expandResources(graph.resources) };
110
77
  };
111
- var VALID_SPEC_VERSIONS = /* @__PURE__ */ new Set([CIRCUITRY_SPEC_VERSION]);
112
- var validateCircuitryGraphInternal = (graph, standard = {}, options) => {
113
- const resolvedStandard = createCircuitryValidationStandard(
114
- standard,
115
- graph && typeof graph === "object" && !Array.isArray(graph) ? graph : void 0
116
- );
78
+ var validateCircuitryGraphInternal = (graph, standard = {}, source) => {
79
+ const resolved = createCircuitryValidationStandard(standard, graph && typeof graph === "object" && !Array.isArray(graph) ? graph : void 0);
117
80
  const errors = [];
118
- const addError = (code, message, path3) => errors.push({ code, message, ...path3 ? { path: path3 } : {} });
119
- if (!graph || typeof graph !== "object" || Array.isArray(graph)) {
120
- addError("invalid_graph", "Circuitry graph must be an object");
121
- return { ok: false, errors, standard: resolvedStandard };
81
+ const issue = (code, message = code, path2) => errors.push({ code, message, ...path2 ? { path: path2 } : {} });
82
+ if (!graph || typeof graph !== "object" || Array.isArray(graph)) return { ok: false, errors: [{ code: "invalid_graph", message: "invalid graph" }], standard: resolved };
83
+ const g = graph;
84
+ if (resolved.requireSpecVersion && g.circuitry !== resolved.version) issue("invalid_version", "invalid version", ["circuitry"]);
85
+ if (source) {
86
+ const raw = g;
87
+ if (!g.resources || Object.keys(g.resources).length === 0) issue("missing_resources", "missing resources", ["resources"]);
88
+ if (raw.agents) issue("forbidden_agents", "forbidden agents", ["agents"]);
89
+ if (raw.inputs) issue("forbidden_inputs", "forbidden inputs", ["inputs"]);
90
+ if (g.nodes?.length) issue("forbidden_nodes", "forbidden nodes", ["nodes"]);
91
+ if (g.edges?.length) issue("forbidden_edges", "forbidden edges", ["edges"]);
92
+ if (raw.links) issue("forbidden_links", "forbidden links", ["links"]);
122
93
  }
123
- const graphObject = graph;
124
- const rules = resolvedStandard.rules;
125
- const hasRule = (name) => rules.some((entry) => (typeof entry === "string" ? entry : entry.rule) === name);
126
- if (resolvedStandard.requireSpecVersion && !VALID_SPEC_VERSIONS.has(String(graphObject.circuitry))) {
127
- addError(
128
- "invalid_spec_version",
129
- `Expected circuitry: "${CIRCUITRY_SPEC_VERSION}"`,
130
- ["circuitry"]
131
- );
132
- }
133
- if (options.sourceFormat) {
134
- if (!graphObject.resources || Object.keys(graphObject.resources).length === 0) {
135
- addError("missing_resources", "Circuitry graphs must use a resources: section", ["resources"]);
136
- }
137
- if (graphObject.agents && Object.keys(graphObject.agents).length > 0) {
138
- addError("forbidden_agents", "Circuitry graph files must not use top-level agents:; use resources:", ["agents"]);
139
- }
140
- if (graphObject.inputs && Object.keys(graphObject.inputs).length > 0) {
141
- addError("forbidden_inputs", "Circuitry graph files use args: for runtime arguments and resource inputs for edges", ["inputs"]);
142
- }
143
- if (graphObject.nodes && graphObject.nodes.length > 0) {
144
- addError("forbidden_nodes", "Circuitry graph files must not use top-level nodes:; use resources:", ["nodes"]);
145
- }
146
- if (graphObject.edges && graphObject.edges.length > 0) {
147
- addError("forbidden_edges", "Circuitry graph files must not use top-level edges:; use resource inputs:", ["edges"]);
148
- }
149
- if (graphObject.links && graphObject.links.length > 0) {
150
- addError("forbidden_links", "Circuitry graph files use imports: for multi-file graphs", ["links"]);
151
- }
152
- }
153
- const normalized = normalizeCircuitryGraph(graphObject);
94
+ const rules = new Set(resolved.rules.map(ruleName));
95
+ const normalized = normalizeCircuitryGraph(g);
154
96
  const ids = /* @__PURE__ */ new Set();
155
97
  for (const [index, node] of (normalized.nodes || []).entries()) {
156
98
  if (!node.id) {
157
- addError("missing_node_id", "Node is missing id", ["nodes", index, "id"]);
99
+ issue("missing_node_id", "missing node id", ["nodes", index, "id"]);
158
100
  continue;
159
101
  }
160
- if (ids.has(node.id)) {
161
- addError("duplicate_node_id", `Duplicate node id: ${node.id}`, ["nodes", index, "id"]);
162
- }
102
+ if (ids.has(node.id)) issue("duplicate_node_id", "duplicate node id", ["nodes", index, "id"]);
163
103
  ids.add(node.id);
164
- if (!node.kind) {
165
- addError("missing_node_kind", `Node ${node.id} is missing kind`, ["nodes", index, "kind"]);
166
- }
104
+ if (!node.kind) issue("missing_node_kind", "missing node kind", ["nodes", index, "kind"]);
167
105
  }
168
106
  const adjacency = /* @__PURE__ */ new Map();
169
- const incomingCount = /* @__PURE__ */ new Map();
107
+ const incoming = /* @__PURE__ */ new Map();
170
108
  for (const id of ids) {
171
109
  adjacency.set(id, []);
172
- incomingCount.set(id, 0);
110
+ incoming.set(id, 0);
173
111
  }
174
112
  for (const [index, edge] of (normalized.edges || []).entries()) {
175
113
  if (!edge.from || !edge.to) {
176
- addError("missing_edge_endpoint", "Edge is missing from/to", ["edges", index]);
114
+ issue("missing_edge_endpoint", "missing edge endpoint", ["edges", index]);
177
115
  continue;
178
116
  }
179
- if (hasRule("no-self-loops") && edge.from === edge.to) {
180
- addError("self_loop", `Edge creates self loop: ${edge.from} -> ${edge.to}`, ["edges", index]);
117
+ if (rules.has("no-self-loops") && edge.from === edge.to) {
118
+ issue("self_loop", "self loop", ["edges", index]);
181
119
  continue;
182
120
  }
183
- if (hasRule("no-unknown-edge-endpoints") && !ids.has(edge.from)) {
184
- addError("unknown_edge_source", `Edge references unknown source: ${edge.from}`, ["edges", index, "from"]);
121
+ const ok = ids.has(edge.from) && ids.has(edge.to);
122
+ if (rules.has("no-unknown-edge-endpoints")) {
123
+ if (!ids.has(edge.from)) issue("unknown_edge_source", "unknown edge source", ["edges", index, "from"]);
124
+ if (!ids.has(edge.to)) issue("unknown_edge_target", "unknown edge target", ["edges", index, "to"]);
185
125
  }
186
- if (hasRule("no-unknown-edge-endpoints") && !ids.has(edge.to)) {
187
- addError("unknown_edge_target", `Edge references unknown target: ${edge.to}`, ["edges", index, "to"]);
188
- }
189
- if (ids.has(edge.from) && ids.has(edge.to)) {
126
+ if (ok) {
190
127
  adjacency.get(edge.from).push(edge.to);
191
- incomingCount.set(edge.to, (incomingCount.get(edge.to) || 0) + 1);
128
+ incoming.set(edge.to, (incoming.get(edge.to) || 0) + 1);
192
129
  }
193
130
  }
194
- const executableKinds = new Set(resolvedStandard.executableKinds);
195
- if (hasRule("require-executable-inputs")) {
131
+ if (rules.has("require-executable-inputs")) {
132
+ const executable = new Set(resolved.executableKinds);
196
133
  for (const [index, node] of (normalized.nodes || []).entries()) {
197
- if (executableKinds.has(node.kind) && (incomingCount.get(node.id) || 0) === 0) {
198
- addError("executable_without_inputs", `Executable node has no inputs: ${node.id}`, ["nodes", index]);
199
- }
134
+ if (executable.has(node.kind) && (incoming.get(node.id) || 0) === 0) issue("missing_executable_input", "missing executable input", ["nodes", index]);
200
135
  }
201
136
  }
202
- const visiting = /* @__PURE__ */ new Set();
203
- const visited = /* @__PURE__ */ new Set();
204
- const path2 = [];
205
- let cycleMessage = "";
206
- const visit = (id) => {
207
- if (cycleMessage) return;
208
- if (visiting.has(id)) {
209
- cycleMessage = [...path2.slice(path2.indexOf(id)), id].join(" -> ");
210
- return;
211
- }
212
- if (visited.has(id)) return;
213
- visiting.add(id);
214
- path2.push(id);
215
- for (const next of adjacency.get(id) || []) visit(next);
216
- path2.pop();
217
- visiting.delete(id);
218
- visited.add(id);
219
- };
220
- if (hasRule("no-cycles")) {
221
- for (const id of ids) {
222
- visit(id);
223
- if (cycleMessage) {
224
- addError("cycle", `Graph contains cycle: ${cycleMessage}`);
225
- break;
226
- }
227
- }
228
- }
229
- const additionalRules = rules.filter((rule) => {
230
- if (typeof rule === "string") return false;
231
- return rule.rule === "require-graph-field" || rule.rule === "require-node-field" || rule.rule === "require-edge-field" || rule.rule === "require-string" || rule.rule === "reject-string";
232
- });
233
- const fieldPath = (field) => field.split(".").filter(Boolean);
234
- const getField = (value, field) => fieldPath(field).reduce((current, key) => {
235
- if (!current || typeof current !== "object") return void 0;
236
- return current[key];
237
- }, value);
238
- const isPresent = (value) => value !== void 0 && value !== null && value !== "";
239
- for (const rule of additionalRules) {
240
- if (rule.rule === "require-graph-field") {
241
- if (!isPresent(getField(normalized, rule.field))) {
242
- addError("missing_required_graph_field", `Graph is missing required field: ${rule.field}`, fieldPath(rule.field));
243
- }
244
- continue;
245
- }
246
- if (rule.rule === "require-node-field") {
247
- const nodeKinds = new Set(rule.nodeKinds || []);
248
- for (const [index, node] of (normalized.nodes || []).entries()) {
249
- if (nodeKinds.size > 0 && !nodeKinds.has(node.kind)) continue;
250
- if (!isPresent(getField(node, rule.field))) {
251
- addError("missing_required_node_field", `Node ${node.id || "<missing id>"} is missing required field: ${rule.field}`, ["nodes", index, ...fieldPath(rule.field)]);
252
- }
253
- }
254
- continue;
255
- }
256
- if (rule.rule === "require-edge-field") {
257
- const edgeKinds = new Set(rule.edgeKinds || []);
258
- for (const [index, edge] of (normalized.edges || []).entries()) {
259
- if (edgeKinds.size > 0 && (!edge.kind || !edgeKinds.has(edge.kind))) continue;
260
- if (!isPresent(getField(edge, rule.field))) {
261
- addError("missing_required_edge_field", `Edge ${edge.id || index} is missing required field: ${rule.field}`, ["edges", index, ...fieldPath(rule.field)]);
262
- }
263
- }
264
- continue;
265
- }
266
- const checkString = (target, subject, id, match, path3) => {
267
- const value = getField(subject, match.field);
268
- const text = typeof value === "string" ? value : "";
269
- const includes = text.includes(match.value);
270
- if (rule.rule === "require-string" && !includes) {
271
- addError("missing_required_string", `${target} ${id} field ${match.field} must include string: ${match.value}`, path3);
272
- }
273
- if (rule.rule === "reject-string" && includes) {
274
- addError("rejected_string", `${target} ${id} field ${match.field} must not include string: ${match.value}`, path3);
275
- }
137
+ if (rules.has("no-cycles")) {
138
+ const visiting = /* @__PURE__ */ new Set();
139
+ const visited = /* @__PURE__ */ new Set();
140
+ const visit = (id) => {
141
+ if (visiting.has(id)) return true;
142
+ if (visited.has(id)) return false;
143
+ visiting.add(id);
144
+ for (const next of adjacency.get(id) || []) if (visit(next)) return true;
145
+ visiting.delete(id);
146
+ visited.add(id);
147
+ return false;
276
148
  };
277
- for (const match of rule.graph || []) checkString("graph", normalized, "<root>", match, fieldPath(match.field));
278
- for (const match of rule.nodes || []) {
279
- const nodeKinds = new Set(match.nodeKinds || []);
280
- for (const [index, node] of (normalized.nodes || []).entries()) {
281
- if (nodeKinds.size > 0 && !nodeKinds.has(node.kind)) continue;
282
- checkString("node", node, node.id || "<missing id>", match, ["nodes", index, ...fieldPath(match.field)]);
283
- }
284
- }
285
- for (const match of rule.edges || []) {
286
- const edgeKinds = new Set(match.edgeKinds || []);
287
- for (const [index, edge] of (normalized.edges || []).entries()) {
288
- if (edgeKinds.size > 0 && (!edge.kind || !edgeKinds.has(edge.kind))) continue;
289
- checkString("edge", edge, edge.id || String(index), match, ["edges", index, ...fieldPath(match.field)]);
290
- }
149
+ for (const id of ids) if (visit(id)) {
150
+ issue("cycle", "cycle");
151
+ break;
291
152
  }
292
153
  }
293
- return { ok: errors.length === 0, errors, standard: resolvedStandard };
294
- };
295
- var validateCircuitryGraphWithStandard = (graph, standard = {}) => validateCircuitryGraphInternal(graph, standard, { sourceFormat: true });
296
- var validateCircuitryExecutionGraphWithStandard = (graph, standard = {}) => validateCircuitryGraphInternal(graph, standard, { sourceFormat: false });
297
- var validateCircuitryGraph = (graph, standard = {}) => validateCircuitryGraphWithStandard(graph, standard).errors.map(
298
- (error) => error.message
299
- );
300
- var parseCircuitryJson = (text, standard = {}, options = {}) => {
301
- let graph;
302
- try {
303
- graph = JSON.parse(text);
304
- } catch (error) {
305
- const message = error instanceof Error ? error.message : String(error);
306
- throw new Error(`Could not parse Circuitry JSON. Check commas, quotes, and braces.
307
- ${message}`);
308
- }
309
- if (options.validate === false) return graph;
310
- const errors = validateCircuitryGraph(graph, standard);
311
- if (errors.length) {
312
- throw new Error(`Invalid Circuitry graph:
313
- ${errors.join("\n")}`);
314
- }
315
- return normalizeCircuitryGraph(graph);
154
+ return { ok: errors.length === 0, errors, standard: resolved };
316
155
  };
156
+ var validateCircuitryGraphWithStandard = (graph, standard = {}) => validateCircuitryGraphInternal(graph, standard, true);
157
+ var validateCircuitryExecutionGraphWithStandard = (graph, standard = {}) => validateCircuitryGraphInternal(graph, standard, false);
158
+ var validateCircuitryGraph = (graph, standard = {}) => validateCircuitryGraphWithStandard(graph, standard).errors.map((error) => error.message);
317
159
 
318
160
  // src/yaml.ts
319
- var parseCircuitryYaml = (text, standard = {}, options = {}) => {
320
- let graph;
161
+ var parseYamlData = (text) => {
321
162
  try {
322
- graph = YAML.parse(text);
163
+ return YAML2.parse(text);
323
164
  } catch (error) {
324
165
  const message = error instanceof Error ? error.message : String(error);
325
- throw new Error(`Could not parse Circuitry YAML. Check indentation and quotes.
166
+ throw new Error(`Could not parse YAML.
326
167
  ${message}`);
327
168
  }
169
+ };
170
+ var stringifyYamlData = (value) => YAML2.stringify(value);
171
+ var parseCircuitryYaml = (text, standard = {}, options = {}) => {
172
+ const graph = parseYamlData(text);
328
173
  if (!graph || typeof graph !== "object" || Array.isArray(graph)) {
329
- throw new Error("Could not parse Circuitry YAML. Expected a graph object.");
174
+ throw new Error("Circuitry YAML must be a graph object.");
330
175
  }
331
- if (options.validate === false) return graph;
332
- const errors = validateCircuitryGraph(graph, standard);
333
- if (errors.length) {
334
- throw new Error(`Invalid Circuitry graph:
176
+ if (options.validate !== false) {
177
+ const errors = validateCircuitryGraph(graph, standard);
178
+ if (errors.length) throw new Error(`Invalid Circuitry graph:
335
179
  ${errors.join("\n")}`);
336
180
  }
337
- return normalizeCircuitryGraph(graph);
338
- };
339
- var parseCircuitryText = (text, filename2 = "graph.circuitry.yaml", standard = {}, options = {}) => {
340
- return filename2.endsWith(".json") ? parseCircuitryJson(text, standard, options) : parseCircuitryYaml(text, standard, options);
181
+ return graph;
341
182
  };
183
+ var parseCircuitryText = (text, standard = {}, options = {}) => parseCircuitryYaml(text, standard, options);
184
+
185
+ // src/scheduler.ts
186
+ import YAML3 from "yaml";
342
187
 
343
188
  // src/node.ts
344
189
  var defaultFilename = "graph.circuitry.yaml";
@@ -396,30 +241,38 @@ ${validation.errors.map((e) => e.message).join("\n")}`);
396
241
  var loadCircuitryGraphFile = async (filename2 = defaultFilename, standard, stack = []) => {
397
242
  const graphFile = path.resolve(process.cwd(), filename2);
398
243
  const text = await readFile(graphFile, "utf8");
399
- const parsed = parseCircuitryText(text, graphFile, standard, { validate: false });
244
+ const parsed = parseCircuitryText(text, standard, { validate: false });
400
245
  return resolveCircuitryGraph(parsed, graphFile, text, standard, stack);
401
246
  };
402
247
 
403
248
  // src/cli.ts
404
249
  var [, , command, filename] = process.argv;
405
250
  var usage = () => {
406
- console.error("usage: circuitry <check|normalize> <graph.circuitry.yaml>");
251
+ console.error("usage: circuitry <check|normalize|normalize-json|parse> <file.yaml>");
407
252
  process.exit(2);
408
253
  };
409
254
  if (!command || !filename) usage();
410
255
  try {
411
- const loaded = await loadCircuitryGraphFile(filename);
412
- if (command === "check") {
413
- const result = validateCircuitryExecutionGraphWithStandard(loaded.graph);
414
- if (!result.ok) {
415
- console.error(result.errors.map((error) => error.message).join("\n"));
416
- process.exit(1);
417
- }
418
- console.log(`ok: ${loaded.filename}`);
419
- } else if (command === "normalize") {
420
- console.log(JSON.stringify({ graph: loaded.graph, origins: loaded.origins }, null, 2));
256
+ if (command === "parse") {
257
+ const text = await readFile2(filename, "utf8");
258
+ process.stdout.write(stringifyYamlData(parseYamlData(text)));
421
259
  } else {
422
- usage();
260
+ const loaded = await loadCircuitryGraphFile(filename);
261
+ if (command === "check") {
262
+ const result = validateCircuitryExecutionGraphWithStandard(loaded.graph);
263
+ if (!result.ok) {
264
+ console.error(result.errors.map((error) => error.message).join("\n"));
265
+ process.exit(1);
266
+ }
267
+ console.log(`ok: ${loaded.filename}`);
268
+ } else if (command === "normalize") {
269
+ process.stdout.write(stringifyYamlData({ circuitryBundle: CIRCUITRY_SPEC_VERSION, graph: loaded.graph, origins: loaded.origins }));
270
+ } else if (command === "normalize-json") {
271
+ process.stdout.write(JSON.stringify({ circuitryBundle: CIRCUITRY_SPEC_VERSION, graph: loaded.graph, origins: loaded.origins }));
272
+ process.stdout.write("\n");
273
+ } else {
274
+ usage();
275
+ }
423
276
  }
424
277
  } catch (error) {
425
278
  console.error(error instanceof Error ? error.message : String(error));
package/dist/graph.d.ts CHANGED
@@ -1,13 +1,12 @@
1
- export declare const CIRCUITRY_SPEC_VERSION: "0.3.1";
1
+ export declare const CIRCUITRY_SPEC_VERSION: "0.3.2";
2
2
  export type CircuitrySpecVersion = typeof CIRCUITRY_SPEC_VERSION;
3
3
  export type CircuitryNodeKind = "agent" | "input" | "tool" | "output" | string;
4
- export type CircuitryEdgeKind = "context" | "dependency" | "message" | "control" | string;
4
+ export type CircuitryEdgeKind = "dependency" | "context" | "message" | "control" | string;
5
5
  export type CircuitryInputKind = "text" | "file" | "url" | "image" | "uri" | "canvas" | "mcp" | string;
6
6
  export type CircuitryAgent = {
7
7
  uses?: string;
8
8
  model?: string;
9
9
  tools?: string[];
10
- thinkingLevel?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
11
10
  identity?: string;
12
11
  personality?: string;
13
12
  instructions?: string;
@@ -51,9 +50,7 @@ export type CircuitryEdge = {
51
50
  export type CircuitryExpectFieldType = "str" | "int" | "float" | "bool" | "list" | "dict" | (string & {});
52
51
  export type CircuitryExpectField = {
53
52
  type: CircuitryExpectFieldType;
54
- /** Optional substring that must appear in the string value. */
55
53
  contains?: string;
56
- /** For list fields: shape of each item. */
57
54
  items?: CircuitryExpectSchema | CircuitryExpectFieldType;
58
55
  optional?: boolean;
59
56
  };
@@ -70,14 +67,9 @@ export type CircuitryResourceAgent = {
70
67
  identity?: string;
71
68
  label?: string;
72
69
  model?: string;
73
- thinkingLevel?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
74
- /** References to other resource ids that feed this agent. */
75
70
  inputs?: string[];
76
- /** Per-resource tool whitelist. */
77
71
  tools?: string[];
78
- /** Per-resource skill whitelist. */
79
72
  skills?: string[];
80
- /** Expected output schema — stored as metadata, used for optional validation. */
81
73
  expect?: CircuitryExpectSchema;
82
74
  instructions?: string;
83
75
  personality?: string;
@@ -135,38 +127,10 @@ export type CircuitryRuntime = {
135
127
  permissions?: Record<string, unknown>;
136
128
  };
137
129
  export type CircuitryBuiltInValidationRule = "no-self-loops" | "no-unknown-edge-endpoints" | "require-executable-inputs" | "no-cycles";
138
- export type CircuitryStringValidationMatch = {
139
- field: string;
140
- value: string;
141
- nodeKinds?: string[];
142
- edgeKinds?: string[];
143
- };
144
- export type CircuitryAdditionalValidationRule = {
145
- rule: "require-graph-field";
146
- field: string;
147
- } | {
148
- rule: "require-node-field";
149
- field: string;
150
- nodeKinds?: string[];
151
- } | {
152
- rule: "require-edge-field";
153
- field: string;
154
- edgeKinds?: string[];
155
- } | {
156
- rule: "require-string";
157
- graph?: CircuitryStringValidationMatch[];
158
- nodes?: CircuitryStringValidationMatch[];
159
- edges?: CircuitryStringValidationMatch[];
160
- } | {
161
- rule: "reject-string";
162
- graph?: CircuitryStringValidationMatch[];
163
- nodes?: CircuitryStringValidationMatch[];
164
- edges?: CircuitryStringValidationMatch[];
165
- };
166
130
  export type CircuitryValidationRuleEntry = CircuitryBuiltInValidationRule | {
167
- rule: CircuitryBuiltInValidationRule;
131
+ rule: "require-executable-inputs";
168
132
  executableKinds?: string[];
169
- } | CircuitryAdditionalValidationRule;
133
+ };
170
134
  export type CircuitryGraphValidation = {
171
135
  rules?: CircuitryValidationRuleEntry[];
172
136
  };
@@ -179,44 +143,31 @@ export type CircuitryGraphArg = {
179
143
  mimeType?: string;
180
144
  };
181
145
  export type CircuitryRuntimeInputs = Record<string, unknown>;
182
- /**
183
- * Return a graph copy with runtime input values applied to existing text inputs.
184
- *
185
- * Runtime inputs are execution-time args for graph programs: small overlays on
186
- * declared text inputs. They must target declared input resources; this keeps
187
- * graph source stable and catches misspelled input names before execution.
188
- */
189
- export declare const applyCircuitryRuntimeInputs: (graph: CircuitryGraph, inputs?: CircuitryRuntimeInputs) => CircuitryGraph;
190
- /** Import declaration for bringing resources from another graph file. */
191
146
  export type CircuitryImport = {
192
- /** Path to another .circuitry.yaml/.json file, resolved relative to this file. */
193
147
  path: string;
194
- /** Import all resources, a resource list, or an old->new resource id map. */
195
148
  resources: "*" | string[] | Record<string, string>;
196
- /** Optional namespace applied to imported resource ids and their internal refs. */
197
149
  prefix?: string;
198
150
  };
199
151
  export type CircuitryGraph = {
200
- /** Spec version. */
201
152
  circuitry: CircuitrySpecVersion | string;
202
153
  id?: string;
203
154
  title?: string;
204
155
  description?: string;
205
- /** Explicit imports of resources from other graph files. */
206
156
  imports?: CircuitryImport[];
207
- /** Runtime call contract. Args bind to resources with matching ids. */
208
157
  args?: Record<string, CircuitryGraphArg>;
209
158
  resources?: Record<string, CircuitryResourceEntry>;
210
- /** Internal normalized execution nodes. Authored graph files must not set this. */
211
159
  nodes?: CircuitryNode[];
212
- /** Internal normalized execution edges. Authored graph files must not set this. */
213
160
  edges?: CircuitryEdge[];
214
- /** Forbidden in authored graph files. */
215
161
  agents?: never;
216
162
  runtime?: CircuitryRuntime;
217
163
  validation?: CircuitryGraphValidation;
218
164
  metadata?: Record<string, unknown>;
219
165
  };
166
+ export type LoadedCircuitryBundle = {
167
+ circuitryBundle: CircuitrySpecVersion;
168
+ graph: CircuitryGraph;
169
+ origins: Record<string, string>;
170
+ };
220
171
  export type CircuitryAgentPreset = {
221
172
  name: string;
222
173
  description?: string;
@@ -243,18 +194,15 @@ export type CircuitryValidationResult = {
243
194
  };
244
195
  export declare const DEFAULT_CIRCUITRY_VALIDATION_RULES: CircuitryValidationRuleEntry[];
245
196
  export declare const DEFAULT_CIRCUITRY_VALIDATION_STANDARD: {
246
- readonly version: "0.3.1";
197
+ readonly version: "0.3.2";
247
198
  readonly requireSpecVersion: true;
248
199
  readonly rules: CircuitryValidationRuleEntry[];
249
200
  readonly executableKinds: ["agent", "tool", "output"];
250
201
  };
202
+ export declare const applyCircuitryRuntimeInputs: (graph: CircuitryGraph, inputs?: CircuitryRuntimeInputs) => CircuitryGraph;
251
203
  export declare const createCircuitryValidationStandard: (standard?: CircuitryValidationStandard, graph?: CircuitryGraph) => Required<CircuitryValidationStandard>;
252
204
  export declare const normalizeCircuitryGraph: (graph: CircuitryGraph) => CircuitryGraph;
253
205
  export declare const validateCircuitryGraphWithStandard: (graph: unknown, standard?: CircuitryValidationStandard) => CircuitryValidationResult;
254
206
  export declare const validateCircuitryExecutionGraphWithStandard: (graph: unknown, standard?: CircuitryValidationStandard) => CircuitryValidationResult;
255
207
  export declare const validateCircuitryGraph: (graph: unknown, standard?: CircuitryValidationStandard) => string[];
256
- export declare const parseCircuitryJson: (text: string, standard?: CircuitryValidationStandard, options?: {
257
- validate?: boolean;
258
- }) => CircuitryGraph;
259
- export declare const stringifyCircuitryJson: (graph: CircuitryGraph) => string;
260
208
  export declare const isUriInput: (input: CircuitryInput | Omit<CircuitryInput, "id">) => boolean;