@darkhorseprojects/circuitry 0.2.99 → 0.3.1

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/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,427 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/node.ts
4
+ import { access, readFile, writeFile } from "node:fs/promises";
5
+ import path from "node:path";
6
+
7
+ // src/yaml.ts
8
+ import YAML from "yaml";
9
+
10
+ // src/graph.ts
11
+ var CIRCUITRY_SPEC_VERSION = "0.3.1";
12
+ var DEFAULT_CIRCUITRY_VALIDATION_RULES = [
13
+ "no-self-loops",
14
+ "no-unknown-edge-endpoints",
15
+ "require-executable-inputs",
16
+ "no-cycles"
17
+ ];
18
+ var DEFAULT_CIRCUITRY_VALIDATION_STANDARD = {
19
+ version: CIRCUITRY_SPEC_VERSION,
20
+ requireSpecVersion: true,
21
+ rules: DEFAULT_CIRCUITRY_VALIDATION_RULES,
22
+ executableKinds: ["agent", "tool", "output"]
23
+ };
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
+ };
30
+ var createCircuitryValidationStandard = (standard = {}, graph) => {
31
+ const rules = [
32
+ ...DEFAULT_CIRCUITRY_VALIDATION_RULES,
33
+ ...graph?.validation?.rules || [],
34
+ ...standard.rules || []
35
+ ];
36
+ return {
37
+ version: standard.version || DEFAULT_CIRCUITRY_VALIDATION_STANDARD.version,
38
+ requireSpecVersion: standard.requireSpecVersion ?? DEFAULT_CIRCUITRY_VALIDATION_STANDARD.requireSpecVersion,
39
+ rules,
40
+ executableKinds: standard.executableKinds || executableKindsFromRules(rules) || [
41
+ ...DEFAULT_CIRCUITRY_VALIDATION_STANDARD.executableKinds
42
+ ]
43
+ };
44
+ };
45
+ var expandResources = (resources) => {
46
+ const nodes = [];
47
+ const edges = [];
48
+ for (const [id, resource] of Object.entries(resources)) {
49
+ 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
+ });
56
+ } else if (resource.type === "agent") {
57
+ const agent = resource;
58
+ nodes.push({
59
+ id,
60
+ kind: "agent",
61
+ label: agent.label || agent.identity || id,
62
+ agent: {
63
+ identity: agent.identity || agent.label || id,
64
+ model: agent.model,
65
+ thinkingLevel: agent.thinkingLevel,
66
+ tools: agent.tools,
67
+ instructions: agent.instructions,
68
+ personality: agent.personality,
69
+ context: agent.context
70
+ },
71
+ skills: agent.skills,
72
+ expect: agent.expect
73
+ });
74
+ for (const inputRef of agent.inputs || []) {
75
+ edges.push({ from: inputRef, to: id, kind: "dependency" });
76
+ }
77
+ } else if (resource.type === "tool") {
78
+ 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
+ }
88
+ } else {
89
+ 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
+ });
102
+ }
103
+ }
104
+ return { nodes, edges };
105
+ };
106
+ 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 };
110
+ };
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
+ );
117
+ 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 };
122
+ }
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);
154
+ const ids = /* @__PURE__ */ new Set();
155
+ for (const [index, node] of (normalized.nodes || []).entries()) {
156
+ if (!node.id) {
157
+ addError("missing_node_id", "Node is missing id", ["nodes", index, "id"]);
158
+ continue;
159
+ }
160
+ if (ids.has(node.id)) {
161
+ addError("duplicate_node_id", `Duplicate node id: ${node.id}`, ["nodes", index, "id"]);
162
+ }
163
+ ids.add(node.id);
164
+ if (!node.kind) {
165
+ addError("missing_node_kind", `Node ${node.id} is missing kind`, ["nodes", index, "kind"]);
166
+ }
167
+ }
168
+ const adjacency = /* @__PURE__ */ new Map();
169
+ const incomingCount = /* @__PURE__ */ new Map();
170
+ for (const id of ids) {
171
+ adjacency.set(id, []);
172
+ incomingCount.set(id, 0);
173
+ }
174
+ for (const [index, edge] of (normalized.edges || []).entries()) {
175
+ if (!edge.from || !edge.to) {
176
+ addError("missing_edge_endpoint", "Edge is missing from/to", ["edges", index]);
177
+ continue;
178
+ }
179
+ if (hasRule("no-self-loops") && edge.from === edge.to) {
180
+ addError("self_loop", `Edge creates self loop: ${edge.from} -> ${edge.to}`, ["edges", index]);
181
+ continue;
182
+ }
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"]);
185
+ }
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)) {
190
+ adjacency.get(edge.from).push(edge.to);
191
+ incomingCount.set(edge.to, (incomingCount.get(edge.to) || 0) + 1);
192
+ }
193
+ }
194
+ const executableKinds = new Set(resolvedStandard.executableKinds);
195
+ if (hasRule("require-executable-inputs")) {
196
+ 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
+ }
200
+ }
201
+ }
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
+ }
276
+ };
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
+ }
291
+ }
292
+ }
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);
316
+ };
317
+
318
+ // src/yaml.ts
319
+ var parseCircuitryYaml = (text, standard = {}, options = {}) => {
320
+ let graph;
321
+ try {
322
+ graph = YAML.parse(text);
323
+ } catch (error) {
324
+ const message = error instanceof Error ? error.message : String(error);
325
+ throw new Error(`Could not parse Circuitry YAML. Check indentation and quotes.
326
+ ${message}`);
327
+ }
328
+ if (!graph || typeof graph !== "object" || Array.isArray(graph)) {
329
+ throw new Error("Could not parse Circuitry YAML. Expected a graph object.");
330
+ }
331
+ if (options.validate === false) return graph;
332
+ const errors = validateCircuitryGraph(graph, standard);
333
+ if (errors.length) {
334
+ throw new Error(`Invalid Circuitry graph:
335
+ ${errors.join("\n")}`);
336
+ }
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);
341
+ };
342
+
343
+ // src/node.ts
344
+ var defaultFilename = "graph.circuitry.yaml";
345
+ var resourceInputs = (resource) => "inputs" in resource && Array.isArray(resource.inputs) ? resource.inputs : [];
346
+ var withResourceInputs = (resource, inputs) => "inputs" in resource ? { ...resource, inputs } : resource;
347
+ var selectedImportResources = (imp, resources) => {
348
+ if (imp.resources === "*") return Object.fromEntries(Object.keys(resources).map((id) => [id, id]));
349
+ if (Array.isArray(imp.resources)) return Object.fromEntries(imp.resources.map((id) => [id, id]));
350
+ return imp.resources;
351
+ };
352
+ var importedId = (localId, prefix) => `${prefix || ""}${localId}`;
353
+ var rewriteResourceInputs = (resource, aliasMap) => {
354
+ const inputs = resourceInputs(resource);
355
+ if (inputs.length === 0) return resource;
356
+ return withResourceInputs(resource, inputs.map((input) => aliasMap[input] || input));
357
+ };
358
+ var resolveCircuitryGraph = async (parsed, graphFile, text, standard, stack = []) => {
359
+ if (stack.includes(graphFile)) {
360
+ throw new Error(`Circuitry graph import cycle: ${[...stack, graphFile].join(" -> ")}`);
361
+ }
362
+ const nextStack = [...stack, graphFile];
363
+ const resources = {};
364
+ const origins = {};
365
+ for (const imp of parsed.imports || []) {
366
+ const linkedFile = path.resolve(path.dirname(graphFile), imp.path);
367
+ const loaded = await loadCircuitryGraphFile(linkedFile, standard, nextStack);
368
+ const available = loaded.graph.resources || {};
369
+ const aliases = selectedImportResources(imp, available);
370
+ const aliasMap = {};
371
+ for (const [sourceId, localId] of Object.entries(aliases)) aliasMap[sourceId] = importedId(localId, imp.prefix);
372
+ for (const [sourceId, localId] of Object.entries(aliases)) {
373
+ const resource = available[sourceId];
374
+ if (!resource) throw new Error(`Imported resource not found: ${sourceId} in ${imp.path}`);
375
+ const nextId = importedId(localId, imp.prefix);
376
+ if (resources[nextId]) throw new Error(`Imported Circuitry resource id collision: ${nextId} from ${imp.path}`);
377
+ resources[nextId] = rewriteResourceInputs(resource, aliasMap);
378
+ origins[nextId] = loaded.origins[sourceId] || linkedFile;
379
+ }
380
+ }
381
+ for (const [id, resource] of Object.entries(parsed.resources || {})) {
382
+ if (resources[id]) throw new Error(`Circuitry resource id collision: ${id} in ${graphFile}`);
383
+ resources[id] = resource;
384
+ origins[id] = graphFile;
385
+ }
386
+ const graph = normalizeCircuitryGraph({ ...parsed, imports: [], resources });
387
+ if (stack.length === 0) {
388
+ const validation = validateCircuitryExecutionGraphWithStandard(graph, standard);
389
+ if (validation.errors.length) {
390
+ throw new Error(`Invalid Circuitry graph:
391
+ ${validation.errors.map((e) => e.message).join("\n")}`);
392
+ }
393
+ }
394
+ return { filename: graphFile, text, graph, origins };
395
+ };
396
+ var loadCircuitryGraphFile = async (filename2 = defaultFilename, standard, stack = []) => {
397
+ const graphFile = path.resolve(process.cwd(), filename2);
398
+ const text = await readFile(graphFile, "utf8");
399
+ const parsed = parseCircuitryText(text, graphFile, standard, { validate: false });
400
+ return resolveCircuitryGraph(parsed, graphFile, text, standard, stack);
401
+ };
402
+
403
+ // src/cli.ts
404
+ var [, , command, filename] = process.argv;
405
+ var usage = () => {
406
+ console.error("usage: circuitry <check|normalize> <graph.circuitry.yaml>");
407
+ process.exit(2);
408
+ };
409
+ if (!command || !filename) usage();
410
+ 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));
421
+ } else {
422
+ usage();
423
+ }
424
+ } catch (error) {
425
+ console.error(error instanceof Error ? error.message : String(error));
426
+ process.exit(1);
427
+ }
package/dist/graph.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- export declare const CIRCUITRY_SPEC_VERSION: "0.2.99";
2
- export type CircuitrySpecVersion = typeof CIRCUITRY_SPEC_VERSION | "0.2";
1
+ export declare const CIRCUITRY_SPEC_VERSION: "0.3.1";
2
+ export type CircuitrySpecVersion = typeof CIRCUITRY_SPEC_VERSION;
3
3
  export type CircuitryNodeKind = "agent" | "input" | "tool" | "output" | string;
4
4
  export type CircuitryEdgeKind = "context" | "dependency" | "message" | "control" | string;
5
5
  export type CircuitryInputKind = "text" | "file" | "url" | "image" | "uri" | "canvas" | "mcp" | string;
@@ -89,7 +89,16 @@ export type CircuitryResourceTool = {
89
89
  inputs?: string[];
90
90
  instructions?: string;
91
91
  };
92
- export type CircuitryResourceEntry = CircuitryResourceText | CircuitryResourceAgent | CircuitryResourceTool;
92
+ export type CircuitryResourceData = {
93
+ type: CircuitryInputKind;
94
+ value?: string;
95
+ uri?: string;
96
+ path?: string;
97
+ mimeType?: string;
98
+ label?: string;
99
+ data?: Record<string, unknown>;
100
+ };
101
+ export type CircuitryResourceEntry = CircuitryResourceText | CircuitryResourceAgent | CircuitryResourceTool | CircuitryResourceData;
93
102
  export type CircuitrySandboxProvider = "local" | "docker" | "e2b" | "daytona" | "cloudflare" | "modal" | "openai" | (string & {});
94
103
  export type CircuitrySandboxClient = {
95
104
  provider: CircuitrySandboxProvider;
@@ -161,6 +170,14 @@ export type CircuitryValidationRuleEntry = CircuitryBuiltInValidationRule | {
161
170
  export type CircuitryGraphValidation = {
162
171
  rules?: CircuitryValidationRuleEntry[];
163
172
  };
173
+ export type CircuitryGraphArg = {
174
+ type: CircuitryInputKind;
175
+ required?: boolean;
176
+ label?: string;
177
+ description?: string;
178
+ default?: unknown;
179
+ mimeType?: string;
180
+ };
164
181
  export type CircuitryRuntimeInputs = Record<string, unknown>;
165
182
  /**
166
183
  * Return a graph copy with runtime input values applied to existing text inputs.
@@ -170,29 +187,32 @@ export type CircuitryRuntimeInputs = Record<string, unknown>;
170
187
  * graph source stable and catches misspelled input names before execution.
171
188
  */
172
189
  export declare const applyCircuitryRuntimeInputs: (graph: CircuitryGraph, inputs?: CircuitryRuntimeInputs) => CircuitryGraph;
173
- export type CircuitryGraphLink = string | {
190
+ /** Import declaration for bringing resources from another graph file. */
191
+ export type CircuitryImport = {
174
192
  /** Path to another .circuitry.yaml/.json file, resolved relative to this file. */
175
193
  path: string;
176
- /** Optional id prefix applied to imported resources. */
194
+ /** Import all resources, a resource list, or an old->new resource id map. */
195
+ resources: "*" | string[] | Record<string, string>;
196
+ /** Optional namespace applied to imported resource ids and their internal refs. */
177
197
  prefix?: string;
178
198
  };
179
199
  export type CircuitryGraph = {
180
- /** Spec version. Use "0.2.99" for linked graph files. */
200
+ /** Spec version. */
181
201
  circuitry: CircuitrySpecVersion | string;
182
202
  id?: string;
183
203
  title?: string;
184
204
  description?: string;
185
- /** Files whose resources are merged into this graph before validation/execution. */
186
- links?: CircuitryGraphLink[];
205
+ /** Explicit imports of resources from other graph files. */
206
+ imports?: CircuitryImport[];
207
+ /** Runtime call contract. Args bind to resources with matching ids. */
208
+ args?: Record<string, CircuitryGraphArg>;
187
209
  resources?: Record<string, CircuitryResourceEntry>;
188
210
  /** Internal normalized execution nodes. Authored graph files must not set this. */
189
211
  nodes?: CircuitryNode[];
190
212
  /** Internal normalized execution edges. Authored graph files must not set this. */
191
213
  edges?: CircuitryEdge[];
192
- /** Forbidden in authored v0.2 graph files. */
214
+ /** Forbidden in authored graph files. */
193
215
  agents?: never;
194
- /** Forbidden in authored v0.2 graph files. */
195
- inputs?: never;
196
216
  runtime?: CircuitryRuntime;
197
217
  validation?: CircuitryGraphValidation;
198
218
  metadata?: Record<string, unknown>;
@@ -223,7 +243,7 @@ export type CircuitryValidationResult = {
223
243
  };
224
244
  export declare const DEFAULT_CIRCUITRY_VALIDATION_RULES: CircuitryValidationRuleEntry[];
225
245
  export declare const DEFAULT_CIRCUITRY_VALIDATION_STANDARD: {
226
- readonly version: "0.2.99";
246
+ readonly version: "0.3.1";
227
247
  readonly requireSpecVersion: true;
228
248
  readonly rules: CircuitryValidationRuleEntry[];
229
249
  readonly executableKinds: ["agent", "tool", "output"];
package/dist/index.js CHANGED
@@ -27,7 +27,7 @@ var isNodeElement = (element) => {
27
27
  };
28
28
 
29
29
  // src/graph.ts
30
- var CIRCUITRY_SPEC_VERSION = "0.2.99";
30
+ var CIRCUITRY_SPEC_VERSION = "0.3.1";
31
31
  var runtimeInputToText = (value) => {
32
32
  if (typeof value === "string") return value;
33
33
  if (value === void 0) return "";
@@ -98,35 +98,51 @@ var expandResources = (resources) => {
98
98
  input: { type: "text", value: resource.value }
99
99
  });
100
100
  } else if (resource.type === "agent") {
101
+ const agent = resource;
101
102
  nodes.push({
102
103
  id,
103
104
  kind: "agent",
104
- label: resource.label || resource.identity || id,
105
+ label: agent.label || agent.identity || id,
105
106
  agent: {
106
- identity: resource.identity || resource.label || id,
107
- model: resource.model,
108
- thinkingLevel: resource.thinkingLevel,
109
- tools: resource.tools,
110
- instructions: resource.instructions,
111
- personality: resource.personality,
112
- context: resource.context
107
+ identity: agent.identity || agent.label || id,
108
+ model: agent.model,
109
+ thinkingLevel: agent.thinkingLevel,
110
+ tools: agent.tools,
111
+ instructions: agent.instructions,
112
+ personality: agent.personality,
113
+ context: agent.context
113
114
  },
114
- skills: resource.skills,
115
- expect: resource.expect
115
+ skills: agent.skills,
116
+ expect: agent.expect
116
117
  });
117
- for (const inputRef of resource.inputs || []) {
118
+ for (const inputRef of agent.inputs || []) {
118
119
  edges.push({ from: inputRef, to: id, kind: "dependency" });
119
120
  }
120
121
  } else if (resource.type === "tool") {
122
+ const tool = resource;
121
123
  nodes.push({
122
124
  id,
123
125
  kind: "tool",
124
- label: resource.label || id,
125
- agent: { instructions: resource.instructions }
126
+ label: tool.label || id,
127
+ agent: { instructions: tool.instructions }
126
128
  });
127
- for (const inputRef of resource.inputs || []) {
129
+ for (const inputRef of tool.inputs || []) {
128
130
  edges.push({ from: inputRef, to: id, kind: "dependency" });
129
131
  }
132
+ } else {
133
+ const input = resource;
134
+ nodes.push({
135
+ id,
136
+ kind: "input",
137
+ label: input.label || id,
138
+ input: {
139
+ type: input.type,
140
+ value: input.value,
141
+ uri: input.uri || input.path,
142
+ mimeType: input.mimeType,
143
+ data: input.data
144
+ }
145
+ });
130
146
  }
131
147
  }
132
148
  return { nodes, edges };
@@ -136,7 +152,7 @@ var normalizeCircuitryGraph = (graph) => {
136
152
  const { nodes, edges } = expandResources(graph.resources);
137
153
  return { ...graph, nodes, edges };
138
154
  };
139
- var VALID_SPEC_VERSIONS = /* @__PURE__ */ new Set(["0.2", CIRCUITRY_SPEC_VERSION]);
155
+ var VALID_SPEC_VERSIONS = /* @__PURE__ */ new Set([CIRCUITRY_SPEC_VERSION]);
140
156
  var validateCircuitryGraphInternal = (graph, standard = {}, options) => {
141
157
  const resolvedStandard = createCircuitryValidationStandard(
142
158
  standard,
@@ -160,19 +176,22 @@ var validateCircuitryGraphInternal = (graph, standard = {}, options) => {
160
176
  }
161
177
  if (options.sourceFormat) {
162
178
  if (!graphObject.resources || Object.keys(graphObject.resources).length === 0) {
163
- addError("missing_resources", "Circuitry v0.2 graphs must use a resources: section", ["resources"]);
179
+ addError("missing_resources", "Circuitry graphs must use a resources: section", ["resources"]);
164
180
  }
165
181
  if (graphObject.agents && Object.keys(graphObject.agents).length > 0) {
166
- addError("forbidden_agents", "Circuitry v0.2 graph files must not use top-level agents:; use resources:", ["agents"]);
182
+ addError("forbidden_agents", "Circuitry graph files must not use top-level agents:; use resources:", ["agents"]);
167
183
  }
168
184
  if (graphObject.inputs && Object.keys(graphObject.inputs).length > 0) {
169
- addError("forbidden_inputs", "Circuitry v0.2 graph files must not use top-level inputs:; use resources:", ["inputs"]);
185
+ addError("forbidden_inputs", "Circuitry graph files use args: for runtime arguments and resource inputs for edges", ["inputs"]);
170
186
  }
171
187
  if (graphObject.nodes && graphObject.nodes.length > 0) {
172
- addError("forbidden_nodes", "Circuitry v0.2 graph files must not use top-level nodes:; use resources:", ["nodes"]);
188
+ addError("forbidden_nodes", "Circuitry graph files must not use top-level nodes:; use resources:", ["nodes"]);
173
189
  }
174
190
  if (graphObject.edges && graphObject.edges.length > 0) {
175
- addError("forbidden_edges", "Circuitry v0.2 graph files must not use top-level edges:; use resource inputs:", ["edges"]);
191
+ addError("forbidden_edges", "Circuitry graph files must not use top-level edges:; use resource inputs:", ["edges"]);
192
+ }
193
+ if (graphObject.links && graphObject.links.length > 0) {
194
+ addError("forbidden_links", "Circuitry graph files use imports: for multi-file graphs", ["links"]);
176
195
  }
177
196
  }
178
197
  const normalized = normalizeCircuitryGraph(graphObject);
package/dist/node.d.ts CHANGED
@@ -2,17 +2,20 @@ import type { CircuitryHost } from "./host";
2
2
  import type { CircuitryGraph, CircuitryRuntimeInputs, CircuitryValidationStandard } from "./graph";
3
3
  import type { NodeExecutionRequest, NodeExecutionResult, SimulationRunItem } from "./simulation";
4
4
  export declare const runNodeWithPiSDK: ({ model, prompt, images, tools, thinkingLevel, }: NodeExecutionRequest) => Promise<NodeExecutionResult>;
5
+ export type CircuitryResourceOrigins = Record<string, string>;
6
+ export type LoadedCircuitryGraph = {
7
+ filename: string;
8
+ text: string;
9
+ graph: CircuitryGraph;
10
+ origins: CircuitryResourceOrigins;
11
+ };
12
+ export declare const loadCircuitryGraphFile: (filename?: string, standard?: CircuitryValidationStandard, stack?: string[]) => Promise<LoadedCircuitryGraph>;
5
13
  export declare class NodeCircuitryHost implements CircuitryHost {
6
14
  private resolveGraphFile;
7
15
  private runFileFor;
8
16
  private exists;
9
17
  private parseIssue;
10
18
  private parseGraphForValidation;
11
- private linkPath;
12
- private linkPrefix;
13
- private prefixResourceIds;
14
- private mergeResources;
15
- private resolveGraphLinks;
16
19
  private parseAndResolveGraph;
17
20
  private readGraphFile;
18
21
  readGraph(input?: {
package/dist/node.js CHANGED
@@ -8,7 +8,7 @@ import os from "node:os";
8
8
  import YAML from "yaml";
9
9
 
10
10
  // src/graph.ts
11
- var CIRCUITRY_SPEC_VERSION = "0.2.99";
11
+ var CIRCUITRY_SPEC_VERSION = "0.3.1";
12
12
  var runtimeInputToText = (value) => {
13
13
  if (typeof value === "string") return value;
14
14
  if (value === void 0) return "";
@@ -79,35 +79,51 @@ var expandResources = (resources) => {
79
79
  input: { type: "text", value: resource.value }
80
80
  });
81
81
  } else if (resource.type === "agent") {
82
+ const agent = resource;
82
83
  nodes.push({
83
84
  id,
84
85
  kind: "agent",
85
- label: resource.label || resource.identity || id,
86
+ label: agent.label || agent.identity || id,
86
87
  agent: {
87
- identity: resource.identity || resource.label || id,
88
- model: resource.model,
89
- thinkingLevel: resource.thinkingLevel,
90
- tools: resource.tools,
91
- instructions: resource.instructions,
92
- personality: resource.personality,
93
- context: resource.context
88
+ identity: agent.identity || agent.label || id,
89
+ model: agent.model,
90
+ thinkingLevel: agent.thinkingLevel,
91
+ tools: agent.tools,
92
+ instructions: agent.instructions,
93
+ personality: agent.personality,
94
+ context: agent.context
94
95
  },
95
- skills: resource.skills,
96
- expect: resource.expect
96
+ skills: agent.skills,
97
+ expect: agent.expect
97
98
  });
98
- for (const inputRef of resource.inputs || []) {
99
+ for (const inputRef of agent.inputs || []) {
99
100
  edges.push({ from: inputRef, to: id, kind: "dependency" });
100
101
  }
101
102
  } else if (resource.type === "tool") {
103
+ const tool = resource;
102
104
  nodes.push({
103
105
  id,
104
106
  kind: "tool",
105
- label: resource.label || id,
106
- agent: { instructions: resource.instructions }
107
+ label: tool.label || id,
108
+ agent: { instructions: tool.instructions }
107
109
  });
108
- for (const inputRef of resource.inputs || []) {
110
+ for (const inputRef of tool.inputs || []) {
109
111
  edges.push({ from: inputRef, to: id, kind: "dependency" });
110
112
  }
113
+ } else {
114
+ const input = resource;
115
+ nodes.push({
116
+ id,
117
+ kind: "input",
118
+ label: input.label || id,
119
+ input: {
120
+ type: input.type,
121
+ value: input.value,
122
+ uri: input.uri || input.path,
123
+ mimeType: input.mimeType,
124
+ data: input.data
125
+ }
126
+ });
111
127
  }
112
128
  }
113
129
  return { nodes, edges };
@@ -117,7 +133,7 @@ var normalizeCircuitryGraph = (graph) => {
117
133
  const { nodes, edges } = expandResources(graph.resources);
118
134
  return { ...graph, nodes, edges };
119
135
  };
120
- var VALID_SPEC_VERSIONS = /* @__PURE__ */ new Set(["0.2", CIRCUITRY_SPEC_VERSION]);
136
+ var VALID_SPEC_VERSIONS = /* @__PURE__ */ new Set([CIRCUITRY_SPEC_VERSION]);
121
137
  var validateCircuitryGraphInternal = (graph, standard = {}, options) => {
122
138
  const resolvedStandard = createCircuitryValidationStandard(
123
139
  standard,
@@ -141,19 +157,22 @@ var validateCircuitryGraphInternal = (graph, standard = {}, options) => {
141
157
  }
142
158
  if (options.sourceFormat) {
143
159
  if (!graphObject.resources || Object.keys(graphObject.resources).length === 0) {
144
- addError("missing_resources", "Circuitry v0.2 graphs must use a resources: section", ["resources"]);
160
+ addError("missing_resources", "Circuitry graphs must use a resources: section", ["resources"]);
145
161
  }
146
162
  if (graphObject.agents && Object.keys(graphObject.agents).length > 0) {
147
- addError("forbidden_agents", "Circuitry v0.2 graph files must not use top-level agents:; use resources:", ["agents"]);
163
+ addError("forbidden_agents", "Circuitry graph files must not use top-level agents:; use resources:", ["agents"]);
148
164
  }
149
165
  if (graphObject.inputs && Object.keys(graphObject.inputs).length > 0) {
150
- addError("forbidden_inputs", "Circuitry v0.2 graph files must not use top-level inputs:; use resources:", ["inputs"]);
166
+ addError("forbidden_inputs", "Circuitry graph files use args: for runtime arguments and resource inputs for edges", ["inputs"]);
151
167
  }
152
168
  if (graphObject.nodes && graphObject.nodes.length > 0) {
153
- addError("forbidden_nodes", "Circuitry v0.2 graph files must not use top-level nodes:; use resources:", ["nodes"]);
169
+ addError("forbidden_nodes", "Circuitry graph files must not use top-level nodes:; use resources:", ["nodes"]);
154
170
  }
155
171
  if (graphObject.edges && graphObject.edges.length > 0) {
156
- addError("forbidden_edges", "Circuitry v0.2 graph files must not use top-level edges:; use resource inputs:", ["edges"]);
172
+ addError("forbidden_edges", "Circuitry graph files must not use top-level edges:; use resource inputs:", ["edges"]);
173
+ }
174
+ if (graphObject.links && graphObject.links.length > 0) {
175
+ addError("forbidden_links", "Circuitry graph files use imports: for multi-file graphs", ["links"]);
157
176
  }
158
177
  }
159
178
  const normalized = normalizeCircuitryGraph(graphObject);
@@ -801,6 +820,63 @@ var runNodeWithPiSDK = async ({
801
820
  }
802
821
  };
803
822
  var defaultFilename = "graph.circuitry.yaml";
823
+ var resourceInputs = (resource) => "inputs" in resource && Array.isArray(resource.inputs) ? resource.inputs : [];
824
+ var withResourceInputs = (resource, inputs) => "inputs" in resource ? { ...resource, inputs } : resource;
825
+ var selectedImportResources = (imp, resources) => {
826
+ if (imp.resources === "*") return Object.fromEntries(Object.keys(resources).map((id) => [id, id]));
827
+ if (Array.isArray(imp.resources)) return Object.fromEntries(imp.resources.map((id) => [id, id]));
828
+ return imp.resources;
829
+ };
830
+ var importedId = (localId, prefix) => `${prefix || ""}${localId}`;
831
+ var rewriteResourceInputs = (resource, aliasMap) => {
832
+ const inputs = resourceInputs(resource);
833
+ if (inputs.length === 0) return resource;
834
+ return withResourceInputs(resource, inputs.map((input) => aliasMap[input] || input));
835
+ };
836
+ var resolveCircuitryGraph = async (parsed, graphFile, text, standard, stack = []) => {
837
+ if (stack.includes(graphFile)) {
838
+ throw new Error(`Circuitry graph import cycle: ${[...stack, graphFile].join(" -> ")}`);
839
+ }
840
+ const nextStack = [...stack, graphFile];
841
+ const resources = {};
842
+ const origins = {};
843
+ for (const imp of parsed.imports || []) {
844
+ const linkedFile = path.resolve(path.dirname(graphFile), imp.path);
845
+ const loaded = await loadCircuitryGraphFile(linkedFile, standard, nextStack);
846
+ const available = loaded.graph.resources || {};
847
+ const aliases = selectedImportResources(imp, available);
848
+ const aliasMap = {};
849
+ for (const [sourceId, localId] of Object.entries(aliases)) aliasMap[sourceId] = importedId(localId, imp.prefix);
850
+ for (const [sourceId, localId] of Object.entries(aliases)) {
851
+ const resource = available[sourceId];
852
+ if (!resource) throw new Error(`Imported resource not found: ${sourceId} in ${imp.path}`);
853
+ const nextId = importedId(localId, imp.prefix);
854
+ if (resources[nextId]) throw new Error(`Imported Circuitry resource id collision: ${nextId} from ${imp.path}`);
855
+ resources[nextId] = rewriteResourceInputs(resource, aliasMap);
856
+ origins[nextId] = loaded.origins[sourceId] || linkedFile;
857
+ }
858
+ }
859
+ for (const [id, resource] of Object.entries(parsed.resources || {})) {
860
+ if (resources[id]) throw new Error(`Circuitry resource id collision: ${id} in ${graphFile}`);
861
+ resources[id] = resource;
862
+ origins[id] = graphFile;
863
+ }
864
+ const graph = normalizeCircuitryGraph({ ...parsed, imports: [], resources });
865
+ if (stack.length === 0) {
866
+ const validation = validateCircuitryExecutionGraphWithStandard(graph, standard);
867
+ if (validation.errors.length) {
868
+ throw new Error(`Invalid Circuitry graph:
869
+ ${validation.errors.map((e) => e.message).join("\n")}`);
870
+ }
871
+ }
872
+ return { filename: graphFile, text, graph, origins };
873
+ };
874
+ var loadCircuitryGraphFile = async (filename = defaultFilename, standard, stack = []) => {
875
+ const graphFile = path.resolve(process.cwd(), filename);
876
+ const text = await readFile(graphFile, "utf8");
877
+ const parsed = parseCircuitryText(text, graphFile, standard, { validate: false });
878
+ return resolveCircuitryGraph(parsed, graphFile, text, standard, stack);
879
+ };
804
880
  var NodeCircuitryHost = class {
805
881
  resolveGraphFile(filename) {
806
882
  return path.resolve(
@@ -837,84 +913,19 @@ var NodeCircuitryHost = class {
837
913
  return { error: this.parseIssue(error, filename) };
838
914
  }
839
915
  }
840
- linkPath(link) {
841
- return typeof link === "string" ? link : link.path;
842
- }
843
- linkPrefix(link) {
844
- return typeof link === "string" ? "" : link.prefix || "";
845
- }
846
- prefixResourceIds(resources, prefix) {
847
- if (!prefix) return resources;
848
- const ids = new Set(Object.keys(resources));
849
- const prefixed = {};
850
- for (const [id, resource] of Object.entries(resources)) {
851
- const next = { ...resource };
852
- if ((next.type === "agent" || next.type === "tool") && next.inputs) {
853
- next.inputs = next.inputs.map((input) => ids.has(input) ? `${prefix}${input}` : input);
854
- }
855
- prefixed[`${prefix}${id}`] = next;
856
- }
857
- return prefixed;
858
- }
859
- mergeResources(target, source, fromFile) {
860
- for (const [id, resource] of Object.entries(source)) {
861
- if (target[id]) {
862
- throw new Error(`Linked Circuitry resource id collision: ${id} from ${fromFile}`);
863
- }
864
- target[id] = resource;
865
- }
866
- }
867
- async resolveGraphLinks(graph, filename, standard, stack = []) {
868
- const graphFile = path.resolve(filename);
869
- if (stack.includes(graphFile)) {
870
- throw new Error(`Circuitry graph link cycle: ${[...stack, graphFile].join(" -> ")}`);
871
- }
872
- const linkedResources = {};
873
- const nextStack = [...stack, graphFile];
874
- for (const link of graph.links || []) {
875
- const rawPath = this.linkPath(link);
876
- if (!rawPath) throw new Error(`Circuitry graph link is missing path in ${graphFile}`);
877
- const linkedFile = path.resolve(path.dirname(graphFile), rawPath);
878
- const linkedText = await readFile(linkedFile, "utf8");
879
- const parsed = parseCircuitryText(linkedText, linkedFile, standard, { validate: false });
880
- const resolved = await this.resolveGraphLinks(parsed, linkedFile, standard, nextStack);
881
- this.mergeResources(
882
- linkedResources,
883
- this.prefixResourceIds(resolved.resources || {}, this.linkPrefix(link)),
884
- linkedFile
885
- );
886
- }
887
- return {
888
- ...graph,
889
- resources: {
890
- ...linkedResources,
891
- ...graph.resources || {}
892
- }
893
- };
894
- }
895
916
  async parseAndResolveGraph(text, filename, standard) {
896
- const parsed = this.parseGraphForValidation(text, filename, standard);
897
- if (!parsed.graph) return parsed;
898
917
  try {
899
- return { graph: await this.resolveGraphLinks(parsed.graph, filename, standard) };
918
+ const graphFile = path.resolve(process.cwd(), filename);
919
+ const source = text || "";
920
+ const parsed = parseCircuitryText(source, graphFile, standard, { validate: false });
921
+ return { graph: (await resolveCircuitryGraph(parsed, graphFile, source, standard)).graph };
900
922
  } catch (error) {
901
923
  return { error: this.parseIssue(error, filename) };
902
924
  }
903
925
  }
904
926
  async readGraphFile(filename) {
905
- const graphFile = this.resolveGraphFile(filename);
906
- const text = await readFile(graphFile, "utf8");
907
- const parsed = await this.parseAndResolveGraph(text, graphFile);
908
- if (!parsed.graph) throw new Error(`Invalid Circuitry graph:
909
- ${parsed.error?.message}`);
910
- const validation = validateCircuitryGraphWithStandard(parsed.graph);
911
- if (validation.errors.length) {
912
- throw new Error(
913
- `Invalid Circuitry graph:
914
- ${validation.errors.map((e) => e.message).join("\n")}`
915
- );
916
- }
917
- return { graphFile, text, graph: normalizeCircuitryGraph(parsed.graph) };
927
+ const loaded = await loadCircuitryGraphFile(this.resolveGraphFile(filename));
928
+ return { graphFile: loaded.filename, text: loaded.text, graph: loaded.graph, origins: loaded.origins };
918
929
  }
919
930
  async readGraph(input = {}) {
920
931
  const { graphFile, text, graph } = await this.readGraphFile(input.filename);
@@ -927,10 +938,10 @@ ${validation.errors.map((e) => e.message).join("\n")}`
927
938
  return validateCircuitryGraphWithStandard(input.graph, input.standard);
928
939
  const filename = input.filename || defaultFilename;
929
940
  const parsed = await this.parseAndResolveGraph(input.text, filename, input.standard);
930
- if ("error" in parsed) {
941
+ if (!("graph" in parsed)) {
931
942
  return {
932
943
  ok: false,
933
- errors: [parsed.error],
944
+ errors: parsed.error ? [parsed.error] : [],
934
945
  standard: validateCircuitryGraphWithStandard({}, input.standard).standard
935
946
  };
936
947
  }
@@ -943,11 +954,12 @@ ${validation.errors.map((e) => e.message).join("\n")}`
943
954
  input.filename || graphFile,
944
955
  input.standard
945
956
  );
946
- if (!parsed.graph)
957
+ if (!("graph" in parsed))
947
958
  throw new Error(`Invalid Circuitry graph:
948
959
  ${parsed.error?.message}`);
960
+ const graph = parsed.graph;
949
961
  const validation = await this.validateGraph({
950
- graph: parsed.graph,
962
+ graph,
951
963
  standard: input.standard
952
964
  });
953
965
  if (validation.errors.length)
@@ -957,12 +969,12 @@ ${validation.errors.map((e) => e.message).join("\n")}`
957
969
  );
958
970
  await writeFile(
959
971
  graphFile,
960
- stringifyCircuitryText(parsed.graph, graphFile),
972
+ stringifyCircuitryText(graph, graphFile),
961
973
  "utf8"
962
974
  );
963
975
  return {
964
976
  filename: graphFile,
965
- graph: parsed.graph,
977
+ graph,
966
978
  mode: "replace"
967
979
  };
968
980
  }
@@ -1004,5 +1016,6 @@ ${validation.errors.map((e) => e.message).join("\n")}`
1004
1016
  };
1005
1017
  export {
1006
1018
  NodeCircuitryHost,
1019
+ loadCircuitryGraphFile,
1007
1020
  runNodeWithPiSDK
1008
1021
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@darkhorseprojects/circuitry",
3
- "version": "0.2.99",
3
+ "version": "0.3.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -14,6 +14,9 @@
14
14
  "import": "./dist/node.js"
15
15
  }
16
16
  },
17
+ "bin": {
18
+ "circuitry": "./dist/cli.js"
19
+ },
17
20
  "license": "LGPL-3.0-only",
18
21
  "files": [
19
22
  "dist",