maqam 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,14 +2,38 @@
2
2
 
3
3
  ![Maqam governed agent framework hero](app/assets/maqam-readme-hero.png)
4
4
 
5
- Maqam is an MIT-licensed Ajnas agent framework for governed workflows. It combines a local agent runtime, policy engine, evidence ledger, skill registry, tool gateway, generic agent adapter, human-review-ready approval errors, and a crawler-backed research workflow.
5
+ Maqam is an MIT-licensed agent framework for governed workflows. It combines a local agent runtime, policy engine, evidence ledger, skill registry, tool gateway, generic agent adapter, CLI worker adapter, human-review-ready approval errors, and a crawler-backed research workflow.
6
6
 
7
- The crawler is not the product center; it is only the first built-in connector. Maqam can govern any agent or tool you register through `ToolGateway`, including function agents, object agents with `run`/`invoke`/`call`, browser agents, research agents, internal SaaS connectors, and write-action agents that need human approval.
7
+ The crawler is not the product center; it is only one built-in connector. Maqam can govern any agent or tool you register through `ToolGateway`, including function agents, object agents with `run`/`invoke`/`call`, command-line workers, browser agents, research agents, internal SaaS connectors, and write-action agents that need human approval.
8
8
 
9
9
  Full documentation: [docs/usage.md](https://github.com/AjnasNB/maqam/blob/main/docs/usage.md)
10
10
 
11
11
  ![Maqam system map](app/assets/maqam-system-map.svg)
12
12
 
13
+ ![Maqam governed CLI worker flow](app/assets/maqam-cli-agent-flow.png)
14
+
15
+ ## Universal Agent Control
16
+
17
+ Maqam controls agents by putting every worker behind the same gateway:
18
+
19
+ ```mermaid
20
+ flowchart LR
21
+ Goal["Goal"] --> Policy["PolicyEngine"]
22
+ Policy --> Runtime["AgentRuntime"]
23
+ Runtime --> Gateway["ToolGateway"]
24
+ Gateway --> FunctionAgent["Function agent"]
25
+ Gateway --> ObjectAgent["run / invoke / call agent"]
26
+ Gateway --> CliWorker["CLI worker"]
27
+ Gateway --> Connector["Crawler or SaaS connector"]
28
+ FunctionAgent --> Evidence["EvidenceLedger"]
29
+ ObjectAgent --> Evidence
30
+ CliWorker --> Evidence
31
+ Connector --> Evidence
32
+ Evidence --> Review["Trace, claims, approval path"]
33
+ ```
34
+
35
+ That means Maqam is not limited to crawling. If an agent can be called as a function, object method, HTTP/SDK connector, or fixed command-line worker, Maqam can route it through policy, limits, trace capture, evidence, and human approval gates.
36
+
13
37
  ## What Ships
14
38
 
15
39
  - `AgentRuntime`: sequential workflow execution with retries, trace events, task outputs, and policy preflight.
@@ -17,6 +41,7 @@ Full documentation: [docs/usage.md](https://github.com/AjnasNB/maqam/blob/main/d
17
41
  - `EvidenceLedger`: provenance records, claim links, source hashes, confidence, and unsupported-claim checks.
18
42
  - `ToolGateway`: one governed path for external tool execution.
19
43
  - `createAgentTool`: wraps any function agent or object agent so Maqam can control it through policy, trace, approval, and evidence.
44
+ - `createCliAgentTool`: wraps fixed command-line workers with timeout, approximate input-token limits, output byte limits, and no shell execution by default.
20
45
  - `SkillRegistry`: lightweight skill metadata registration and selection.
21
46
  - `createResearchWorkflow`: crawler-backed source collection, synthesis, and quality checks.
22
47
  - `maqam`: local web console for running governed research workflows.
@@ -82,6 +107,7 @@ import {
82
107
  PolicyEngine,
83
108
  ToolGateway,
84
109
  createAgentTool,
110
+ createCliAgentTool,
85
111
  createCrawlerTool,
86
112
  createResearchWorkflow
87
113
  } from "maqam";
@@ -97,6 +123,15 @@ gateway.registerTool("crawler", createCrawlerTool());
97
123
  gateway.registerTool("summarizer", createAgentTool(async (input) => ({
98
124
  summary: `Reviewed ${input.topic}`
99
125
  }), { name: "summarizer" }));
126
+ gateway.registerTool("localWorker", createCliAgentTool({
127
+ name: "localWorker",
128
+ command: process.execPath,
129
+ args: ["--version"],
130
+ stdin: "none",
131
+ timeoutMs: 5000,
132
+ maxInputTokens: 20,
133
+ maxOutputBytes: 2048
134
+ }));
100
135
 
101
136
  const runtime = new AgentRuntime({ policyEngine, evidenceLedger, toolGateway: gateway });
102
137
  const result = await runtime.runWorkflow(
@@ -154,7 +189,7 @@ Brand assets live in `app/assets/`, including `maqam-logo.svg` and `maqam-brand-
154
189
  - Use a clear user agent.
155
190
  - Rate-limit per origin.
156
191
  - Avoid bypassing access controls, paywalls, anti-bot systems, or private content.
157
- - No required AI provider dependency.
192
+ - No required model provider dependency.
158
193
  - No required external hosted service.
159
194
  - Produce JSON/JSONL output that agents can consume directly.
160
195
 
@@ -181,3 +216,7 @@ Publishing requires an authenticated npm session with permission to publish the
181
216
  ## License
182
217
 
183
218
  MIT
219
+
220
+ ## Open Development
221
+
222
+ Maqam is open source under MIT and open for development, issues, ideas, and contributions.
package/app/index.html CHANGED
@@ -3,7 +3,7 @@
3
3
  <head>
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <meta name="description" content="Maqam is an enterprise agent framework console for governed research workflows.">
6
+ <meta name="description" content="Maqam is an agent framework console for governed workflows, CLI workers, connectors, and evidence-backed runs.">
7
7
  <title>Maqam | Compose governed agents</title>
8
8
  <link rel="icon" href="/assets/maqam-logo.svg" type="image/svg+xml">
9
9
  <link rel="stylesheet" href="/styles.css">
@@ -28,9 +28,9 @@
28
28
  <main>
29
29
  <section class="hero" aria-labelledby="hero-title">
30
30
  <div class="hero-copy">
31
- <p class="kicker">Enterprise agent framework</p>
31
+ <p class="kicker">Agent framework</p>
32
32
  <h1 id="hero-title">Build agent workflows that can be inspected, governed, and trusted.</h1>
33
- <p class="lede">Maqam composes skills, tools, policy, evidence, and runtime traces into one controlled workflow surface.</p>
33
+ <p class="lede">Maqam composes agents, CLI workers, connectors, policy, evidence, and runtime traces into one controlled workflow surface.</p>
34
34
  </div>
35
35
  <div class="run-panel" id="run">
36
36
  <div class="panel-header">
@@ -78,6 +78,45 @@
78
78
  </article>
79
79
  </section>
80
80
 
81
+ <section class="control-map" aria-labelledby="control-map-title">
82
+ <div class="section-heading">
83
+ <h2 id="control-map-title">Agent control map</h2>
84
+ <p>Every worker enters through the same governed path before it can read, write, crawl, call, or publish.</p>
85
+ </div>
86
+ <div class="control-grid">
87
+ <article>
88
+ <span>01</span>
89
+ <strong>Function agents</strong>
90
+ <p>Wrap local functions with `createAgentTool` and record their evidence and claims.</p>
91
+ </article>
92
+ <article>
93
+ <span>02</span>
94
+ <strong>Object agents</strong>
95
+ <p>Control existing workers that expose `run`, `invoke`, or `call` methods.</p>
96
+ </article>
97
+ <article>
98
+ <span>03</span>
99
+ <strong>CLI workers</strong>
100
+ <p>Run fixed commands with timeout, input-token, output-byte, and exit-code controls.</p>
101
+ </article>
102
+ <article>
103
+ <span>04</span>
104
+ <strong>Connectors</strong>
105
+ <p>Route crawlers, browser tools, SaaS APIs, and internal services through `ToolGateway`.</p>
106
+ </article>
107
+ <article>
108
+ <span>05</span>
109
+ <strong>Approvals</strong>
110
+ <p>Fail closed when a workflow reaches a write, publish, send, or modify action.</p>
111
+ </article>
112
+ <article>
113
+ <span>06</span>
114
+ <strong>Evidence</strong>
115
+ <p>Attach source records to claims so each run can be reviewed and audited.</p>
116
+ </article>
117
+ </div>
118
+ </section>
119
+
81
120
  <section class="workspace">
82
121
  <div class="column">
83
122
  <div class="section-heading">
package/app/styles.css CHANGED
@@ -95,12 +95,11 @@ a {
95
95
  }
96
96
 
97
97
  .hero {
98
- min-height: calc(100dvh - 76px);
99
98
  display: grid;
100
99
  grid-template-columns: minmax(0, 1.08fr) minmax(340px, .72fr);
101
100
  align-items: center;
102
101
  gap: 52px;
103
- padding: 52px 0;
102
+ padding: 44px 0 34px;
104
103
  }
105
104
 
106
105
  .kicker {
@@ -117,10 +116,10 @@ p {
117
116
 
118
117
  h1 {
119
118
  max-width: 820px;
120
- font-size: clamp(3rem, 8vw, 7.6rem);
121
- line-height: .96;
119
+ font-size: clamp(2.7rem, 5.4vw, 5rem);
120
+ line-height: 1;
122
121
  letter-spacing: 0;
123
- margin-bottom: 28px;
122
+ margin-bottom: 22px;
124
123
  }
125
124
 
126
125
  h2 {
@@ -131,12 +130,13 @@ h2 {
131
130
  .lede {
132
131
  max-width: 680px;
133
132
  color: var(--muted);
134
- font-size: 1.2rem;
135
- line-height: 1.65;
133
+ font-size: 1.08rem;
134
+ line-height: 1.55;
136
135
  }
137
136
 
138
137
  .run-panel,
139
138
  .metrics article,
139
+ .control-grid article,
140
140
  .stack > article,
141
141
  .policy-band {
142
142
  border: 1px solid var(--line);
@@ -246,6 +246,41 @@ button:disabled {
246
246
  font-size: 1.25rem;
247
247
  }
248
248
 
249
+ .control-map {
250
+ padding: 34px 0 12px;
251
+ }
252
+
253
+ .control-grid {
254
+ display: grid;
255
+ grid-template-columns: repeat(3, minmax(0, 1fr));
256
+ gap: 14px;
257
+ }
258
+
259
+ .control-grid article {
260
+ min-height: 172px;
261
+ padding: 18px;
262
+ display: grid;
263
+ align-content: start;
264
+ gap: 10px;
265
+ }
266
+
267
+ .control-grid span {
268
+ color: var(--blue);
269
+ font-size: .78rem;
270
+ font-weight: 800;
271
+ letter-spacing: .08em;
272
+ }
273
+
274
+ .control-grid strong {
275
+ font-size: 1.05rem;
276
+ }
277
+
278
+ .control-grid p {
279
+ color: var(--muted);
280
+ line-height: 1.5;
281
+ margin-bottom: 0;
282
+ }
283
+
249
284
  .workspace {
250
285
  display: grid;
251
286
  grid-template-columns: 1fr 1fr;
@@ -352,8 +387,7 @@ button:disabled {
352
387
  }
353
388
 
354
389
  .hero {
355
- min-height: auto;
356
- padding: 38px 0;
390
+ padding: 32px 0;
357
391
  gap: 30px;
358
392
  }
359
393
 
@@ -366,7 +400,8 @@ button:disabled {
366
400
  line-height: 1.02;
367
401
  }
368
402
 
369
- .metrics {
403
+ .metrics,
404
+ .control-grid {
370
405
  grid-template-columns: 1fr 1fr;
371
406
  }
372
407
  }
@@ -377,6 +412,7 @@ button:disabled {
377
412
  }
378
413
 
379
414
  .metrics,
415
+ .control-grid,
380
416
  .form-grid {
381
417
  grid-template-columns: 1fr;
382
418
  }
package/docs/usage.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Maqam Usage Guide
2
2
 
3
- Maqam is an MIT-licensed Ajnas agent framework for governed workflows. It gives you a small local runtime for building agent systems that can be inspected, policy-checked, and connected to evidence. The crawler is only one built-in connector; Maqam can also govern arbitrary agents and tools through `createAgentTool` and `ToolGateway`.
3
+ Maqam is an MIT-licensed agent framework for governed workflows. It gives you a small local runtime for building agent systems that can be inspected, policy-checked, and connected to evidence. The crawler is only one built-in connector; Maqam can also govern arbitrary agents and tools through `createAgentTool`, `createCliAgentTool`, and `ToolGateway`.
4
4
 
5
5
  This guide covers installation, CLI usage, SDK usage, the local console, crawler usage, API reference, common patterns, and troubleshooting.
6
6
 
@@ -14,7 +14,9 @@ This guide covers installation, CLI usage, SDK usage, the local console, crawler
14
14
  - [Architecture](#architecture)
15
15
  - [API Reference](#api-reference)
16
16
  - [Build A Custom Workflow](#build-a-custom-workflow)
17
+ - [Universal Agent Control](#universal-agent-control)
17
18
  - [Control Any Agent](#control-any-agent)
19
+ - [Control CLI Workers](#control-cli-workers)
18
20
  - [Register A Custom Tool](#register-a-custom-tool)
19
21
  - [Use Policy And Approvals](#use-policy-and-approvals)
20
22
  - [Use Evidence And Claims](#use-evidence-and-claims)
@@ -79,6 +81,7 @@ import {
79
81
  PolicyEngine,
80
82
  ToolGateway,
81
83
  createAgentTool,
84
+ createCliAgentTool,
82
85
  createCrawlerTool,
83
86
  createResearchWorkflow
84
87
  } from "maqam";
@@ -94,6 +97,15 @@ toolGateway.registerTool("crawler", createCrawlerTool());
94
97
  toolGateway.registerTool("summarizer", createAgentTool(async (input) => ({
95
98
  summary: `Reviewed ${input.topic}`
96
99
  }), { name: "summarizer" }));
100
+ toolGateway.registerTool("localWorker", createCliAgentTool({
101
+ name: "localWorker",
102
+ command: process.execPath,
103
+ args: ["--version"],
104
+ stdin: "none",
105
+ timeoutMs: 5000,
106
+ maxInputTokens: 20,
107
+ maxOutputBytes: 2048
108
+ }));
97
109
 
98
110
  const runtime = new AgentRuntime({ policyEngine, evidenceLedger, toolGateway });
99
111
  const result = await runtime.runWorkflow(
@@ -216,6 +228,7 @@ import {
216
228
  ToolGateway,
217
229
  SkillRegistry,
218
230
  createAgentTool,
231
+ createCliAgentTool,
219
232
  createCrawlerTool,
220
233
  createResearchWorkflow,
221
234
  crawl,
@@ -252,6 +265,7 @@ Core objects:
252
265
  - `PolicyEngine`: decides what is allowed, denied, or approval-gated.
253
266
  - `ToolGateway`: routes all external tool calls through policy.
254
267
  - `createAgentTool`: wraps arbitrary agents so they can be governed like any other tool.
268
+ - `createCliAgentTool`: wraps fixed command-line workers with timeout, approximate input-token limits, output byte limits, and no shell execution by default.
255
269
  - `EvidenceLedger`: stores source evidence and claim support.
256
270
  - `SkillRegistry`: stores skill metadata and selects matching skills.
257
271
  - `createResearchWorkflow`: bundled workflow for public web research.
@@ -643,6 +657,93 @@ const workflow = createResearchWorkflow({
643
657
  });
644
658
  ```
645
659
 
660
+ ### `createCliAgentTool(options)`
661
+
662
+ Wraps a fixed command-line worker so it can run through Maqam policy and trace capture.
663
+
664
+ ```js
665
+ const localWorker = createCliAgentTool({
666
+ name: "localWorker",
667
+ command: process.execPath,
668
+ args: ["--input-type=module", "-e", "let body=''; for await (const c of process.stdin) body += c; const input = JSON.parse(body); console.log(JSON.stringify({ artifact: `built:${input.name}` }));"],
669
+ stdin: "json",
670
+ parseJson: true,
671
+ timeoutMs: 5000,
672
+ maxInputTokens: 50,
673
+ maxOutputBytes: 2048
674
+ });
675
+
676
+ toolGateway.registerTool("localWorker", localWorker);
677
+
678
+ const result = await toolGateway.call("localWorker", {
679
+ name: "demo-widget"
680
+ });
681
+
682
+ console.log(result.json.artifact);
683
+ ```
684
+
685
+ Options:
686
+
687
+ | Field | Type | Description |
688
+ | --- | --- | --- |
689
+ | `name` | `string` | Name used in result metadata. |
690
+ | `command` | `string` | Fixed executable path or command. Required. |
691
+ | `args` | `string[]` | Fixed argument list. Dynamic user input should go through stdin, not command args. |
692
+ | `cwd` | `string` | Optional working directory. |
693
+ | `env` | `object` | Extra environment variables. |
694
+ | `inheritEnv` | `boolean` | Inherit `process.env`. Default: `true`. |
695
+ | `stdin` | `"json" | "text" | "none"` | How input is passed to the worker. Default: `"json"`. |
696
+ | `parseJson` | `boolean` | Parse stdout as JSON and expose it as `result.json`. |
697
+ | `timeoutMs` | `number` | Hard runtime timeout. Default: `30000`. |
698
+ | `maxInputTokens` | `number` | Approximate input token limit. Default: `4000`. |
699
+ | `maxOutputBytes` | `number` | Maximum combined stdout/stderr bytes. Default: `65536`. |
700
+ | `rejectOnNonZero` | `boolean` | Reject when exit code is not zero. Default: `true`. |
701
+ | `shell` | `boolean` | Run through a shell. Default: `false`. Use only when a platform wrapper requires it. |
702
+
703
+ Result shape:
704
+
705
+ ```json
706
+ {
707
+ "name": "localWorker",
708
+ "command": "node",
709
+ "args": ["--version"],
710
+ "exitCode": 0,
711
+ "signal": null,
712
+ "timedOut": false,
713
+ "stdout": "v20.0.0\n",
714
+ "stderr": "",
715
+ "durationMs": 42,
716
+ "approxInputTokens": 0,
717
+ "outputBytes": 9,
718
+ "limits": {
719
+ "maxInputTokens": 50,
720
+ "maxOutputBytes": 2048,
721
+ "timeoutMs": 5000
722
+ }
723
+ }
724
+ ```
725
+
726
+ Limit errors:
727
+
728
+ | Code | Meaning |
729
+ | --- | --- |
730
+ | `CLI_INPUT_LIMIT_EXCEEDED` | Input was too large before execution. |
731
+ | `CLI_OUTPUT_LIMIT_EXCEEDED` | stdout/stderr exceeded the configured byte limit. |
732
+ | `CLI_TIMEOUT` | Process exceeded `timeoutMs`. |
733
+ | `CLI_EXIT_NONZERO` | Process exited with a non-zero code. |
734
+ | `CLI_JSON_PARSE_FAILED` | `parseJson` was enabled but stdout was not valid JSON. |
735
+ | `CLI_SPAWN_FAILED` | The process could not be started. |
736
+
737
+ Security notes:
738
+
739
+ - Maqam does not use a shell for CLI workers by default.
740
+ - Keep `command` and `args` fixed in code.
741
+ - Send user input through stdin.
742
+ - Use narrow `allowedTools`.
743
+ - Set short `timeoutMs` and small `maxOutputBytes` for untrusted workers.
744
+ - Use approval gates for workers that write, publish, send, or modify state.
745
+ - Prefer direct executable paths over platform wrapper scripts. On Windows, some `.cmd` or `.ps1` shims may require `shell: true` or a direct underlying executable path.
746
+
646
747
  Tasks:
647
748
 
648
749
  | Task ID | Purpose |
@@ -658,7 +759,7 @@ Candidate shape:
658
759
  "name": "Maqam",
659
760
  "url": "https://github.com/AjnasNB/maqam",
660
761
  "whatItDoes": "Summary excerpt...",
661
- "whyUseful": "Potential source or reference for enterprise agent framework capabilities.",
762
+ "whyUseful": "Potential source or reference for governed agent framework capabilities.",
662
763
  "risks": ["Requires license and maintenance review before reuse."],
663
764
  "recommendation": "inspiration_first",
664
765
  "evidenceIds": ["ev_1"]
@@ -811,6 +912,46 @@ console.log(result.outputs.record_summary);
811
912
  console.log(result.evidence.unsupportedClaims);
812
913
  ```
813
914
 
915
+ ## Universal Agent Control
916
+
917
+ Maqam is designed to control workers through stable adapter boundaries, not through one specific agent implementation.
918
+
919
+ ```mermaid
920
+ flowchart LR
921
+ Goal["Goal"] --> Policy["PolicyEngine"]
922
+ Policy --> Runtime["AgentRuntime"]
923
+ Runtime --> Gateway["ToolGateway"]
924
+ Gateway --> FunctionAgent["Function agent"]
925
+ Gateway --> ObjectAgent["Object agent: run / invoke / call"]
926
+ Gateway --> CliWorker["Fixed CLI worker"]
927
+ Gateway --> Connector["Crawler, browser, SaaS, or internal connector"]
928
+ FunctionAgent --> Evidence["EvidenceLedger"]
929
+ ObjectAgent --> Evidence
930
+ CliWorker --> Evidence
931
+ Connector --> Evidence
932
+ Evidence --> Trace["Trace, claims, approval, review"]
933
+ ```
934
+
935
+ Adapter matrix:
936
+
937
+ | Worker shape | Maqam adapter | Best for |
938
+ | --- | --- | --- |
939
+ | Function agent | `createAgentTool(fn)` | Small local agents, scoring functions, synthesis, transforms. |
940
+ | Object agent with `run`, `invoke`, or `call` | `createAgentTool(objectAgent)` | Existing agent frameworks and SDK objects. |
941
+ | Command-line worker | `createCliAgentTool(options)` | Local CLIs, sandboxed scripts, code tools, packaged executables. |
942
+ | HTTP or SDK connector | Custom `ToolGateway` tool function | SaaS tools, internal APIs, browser services, queues, storage. |
943
+ | Crawler-backed public research | `createCrawlerTool()` and `createResearchWorkflow()` | Source collection, discovery, evidence-backed research. |
944
+
945
+ Control path:
946
+
947
+ 1. `PolicyEngine` checks whether the goal, tool, origin, and budget are allowed.
948
+ 2. `AgentRuntime` executes workflow tasks in order and records task traces.
949
+ 3. `ToolGateway` is the only path to external work.
950
+ 4. Agent outputs can add evidence and claims to `EvidenceLedger`.
951
+ 5. Risky tools can be configured as approval-required and fail closed until a human approves.
952
+
953
+ What this proves in practice: Maqam does not need the worker to be written for Maqam. The worker only needs a stable callable boundary. That boundary can be a JavaScript function, an object method, a CLI command, or a connector function.
954
+
814
955
  ## Control Any Agent
815
956
 
816
957
  Yes, Maqam can control agents beyond crawling. The pattern is:
@@ -895,7 +1036,7 @@ What Maqam can control:
895
1036
 
896
1037
  - Function agents.
897
1038
  - LangChain/LangGraph-style agents if exposed through `invoke` or wrapped in a function.
898
- - OpenAI Agents SDK-style functions if wrapped in a function.
1039
+ - External SDK-style functions if wrapped in a function.
899
1040
  - Browser agents.
900
1041
  - Research agents.
901
1042
  - GitHub/npm/internal API agents.
@@ -907,6 +1048,91 @@ What Maqam cannot do automatically:
907
1048
  - It cannot make an unsafe third-party agent safe if that agent bypasses the wrapper and performs side effects internally.
908
1049
  - It cannot approve risky actions by itself; approval-gated actions should be routed to humans.
909
1050
 
1051
+ ## Control CLI Workers
1052
+
1053
+ Maqam can govern command-line workers the same way it governs function agents.
1054
+
1055
+ The pattern is:
1056
+
1057
+ 1. Create a fixed CLI adapter with `createCliAgentTool`.
1058
+ 2. Register it with `ToolGateway`.
1059
+ 3. Add the worker name to `allowedTools`.
1060
+ 4. Configure `timeoutMs`, `maxInputTokens`, and `maxOutputBytes`.
1061
+ 5. Call the worker from a workflow task.
1062
+
1063
+ Example workflow:
1064
+
1065
+ ```js
1066
+ import {
1067
+ AgentRuntime,
1068
+ EvidenceLedger,
1069
+ PolicyEngine,
1070
+ ToolGateway,
1071
+ createCliAgentTool
1072
+ } from "maqam";
1073
+
1074
+ const evidenceLedger = new EvidenceLedger();
1075
+ const policyEngine = new PolicyEngine({
1076
+ allowedTools: ["builderWorker"]
1077
+ });
1078
+ const toolGateway = new ToolGateway({ policyEngine, evidenceLedger });
1079
+
1080
+ toolGateway.registerTool("builderWorker", createCliAgentTool({
1081
+ name: "builderWorker",
1082
+ command: process.execPath,
1083
+ args: ["--input-type=module", "-e", "let body=''; for await (const c of process.stdin) body += c; const input = JSON.parse(body); console.log(JSON.stringify({ fileName: `${input.name}.txt`, content: `Built ${input.name}` }));"],
1084
+ stdin: "json",
1085
+ parseJson: true,
1086
+ timeoutMs: 5000,
1087
+ maxInputTokens: 100,
1088
+ maxOutputBytes: 4096
1089
+ }));
1090
+
1091
+ const workflow = {
1092
+ name: "governed_cli_build",
1093
+ tasks: [
1094
+ {
1095
+ id: "build",
1096
+ run: (context) => context.tools.call("builderWorker", {
1097
+ name: "demo-widget"
1098
+ }, context)
1099
+ },
1100
+ {
1101
+ id: "record",
1102
+ run: (context) => {
1103
+ const output = context.outputs.build.json;
1104
+ const evidence = context.evidence.addEvidence({
1105
+ runId: context.runId,
1106
+ taskId: "record",
1107
+ sourceType: "cli_worker_output",
1108
+ source: "builderWorker",
1109
+ excerpt: output.content,
1110
+ tool: "builderWorker",
1111
+ confidence: 0.8
1112
+ });
1113
+ context.evidence.addClaim({
1114
+ runId: context.runId,
1115
+ taskId: "record",
1116
+ text: `The worker created ${output.fileName}.`,
1117
+ evidenceIds: [evidence.evidenceId],
1118
+ confidence: 0.8
1119
+ });
1120
+ return output;
1121
+ }
1122
+ }
1123
+ ]
1124
+ };
1125
+
1126
+ const runtime = new AgentRuntime({ policyEngine, evidenceLedger, toolGateway });
1127
+ const result = await runtime.runWorkflow(workflow, {
1128
+ objective: "Build a small artifact through a governed CLI worker",
1129
+ allowedTools: ["builderWorker"]
1130
+ });
1131
+
1132
+ console.log(result.outputs.record);
1133
+ console.log(result.evidence.unsupportedClaims);
1134
+ ```
1135
+
910
1136
  ## Register A Custom Tool
911
1137
 
912
1138
  Tools should be small and explicit. The gateway handles policy and trace capture.
@@ -982,7 +1208,7 @@ Use evidence for source facts:
982
1208
  const evidence = evidenceLedger.addEvidence({
983
1209
  sourceType: "url",
984
1210
  source: "https://github.com/AjnasNB/maqam",
985
- excerpt: "Maqam is an MIT-licensed Ajnas agent framework...",
1211
+ excerpt: "Maqam is an MIT-licensed agent framework...",
986
1212
  tool: "crawler",
987
1213
  confidence: 0.85
988
1214
  });
@@ -1053,7 +1279,7 @@ Response:
1053
1279
  "product": {
1054
1280
  "name": "Maqam",
1055
1281
  "tagline": "Compose governed agents",
1056
- "description": "Enterprise agent framework console for policy-bound research, evidence capture, and auditable workflow runs."
1282
+ "description": "Agent framework console for policy-bound workflows, evidence capture, CLI workers, connectors, and auditable runs."
1057
1283
  },
1058
1284
  "status": "ok"
1059
1285
  }
@@ -1258,3 +1484,7 @@ Useful next packages or modules:
1258
1484
  - Browser automation connector.
1259
1485
  - GitHub and npm metadata connectors.
1260
1486
  - Tenant-aware configuration and audit export.
1487
+
1488
+ ## Open Development
1489
+
1490
+ Maqam is open source under MIT and open for development, issues, ideas, and contributions.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "maqam",
3
- "version": "0.1.3",
4
- "description": "Maqam is an MIT-licensed Ajnas agent framework for governed workflows, policy, evidence, skills, and crawler-backed research.",
3
+ "version": "0.1.5",
4
+ "description": "Maqam is an MIT-licensed agent framework for governed workflows, policy, evidence, skills, CLI workers, and crawler-backed research.",
5
5
  "license": "MIT",
6
6
  "author": {
7
7
  "name": "Ajnas",
@@ -43,6 +43,8 @@
43
43
  "skills",
44
44
  "tool-orchestration",
45
45
  "human-approval",
46
+ "cli-agent",
47
+ "command-runner",
46
48
  "crawler",
47
49
  "agent",
48
50
  "web-crawler",
@@ -0,0 +1,185 @@
1
+ import { spawn } from "node:child_process";
2
+ import { AjnasFrameworkError } from "./errors.js";
3
+
4
+ const DEFAULT_TIMEOUT_MS = 30_000;
5
+ const DEFAULT_MAX_INPUT_TOKENS = 4_000;
6
+ const DEFAULT_MAX_OUTPUT_BYTES = 64 * 1024;
7
+
8
+ function estimateTokens(value) {
9
+ return Math.ceil(Buffer.byteLength(value || "", "utf8") / 4);
10
+ }
11
+
12
+ function buildStdin(input, mode) {
13
+ if (mode === "none") return null;
14
+ if (mode === "text") return String(input.prompt ?? input.text ?? "");
15
+ return JSON.stringify(input);
16
+ }
17
+
18
+ function cliError(message, code, details = {}) {
19
+ return new AjnasFrameworkError(message, {
20
+ code,
21
+ details
22
+ });
23
+ }
24
+
25
+ export function createCliAgentTool(options = {}) {
26
+ const {
27
+ name = "cliAgent",
28
+ command,
29
+ args = [],
30
+ cwd,
31
+ env = {},
32
+ inheritEnv = true,
33
+ stdin = "json",
34
+ parseJson = false,
35
+ timeoutMs = DEFAULT_TIMEOUT_MS,
36
+ maxInputTokens = DEFAULT_MAX_INPUT_TOKENS,
37
+ maxOutputBytes = DEFAULT_MAX_OUTPUT_BYTES,
38
+ rejectOnNonZero = true,
39
+ shell = false
40
+ } = options;
41
+
42
+ if (!command || typeof command !== "string") {
43
+ throw new TypeError("createCliAgentTool requires a fixed command string.");
44
+ }
45
+ if (!Array.isArray(args) || args.some((arg) => typeof arg !== "string")) {
46
+ throw new TypeError("createCliAgentTool args must be an array of strings.");
47
+ }
48
+
49
+ return async function cliAgentTool(input = {}) {
50
+ const stdinBody = buildStdin(input, stdin);
51
+ const approxInputTokens = estimateTokens(stdinBody || "");
52
+ if (maxInputTokens && approxInputTokens > maxInputTokens) {
53
+ throw cliError(`CLI input exceeds maxInputTokens (${approxInputTokens} > ${maxInputTokens}).`, "CLI_INPUT_LIMIT_EXCEEDED", {
54
+ name,
55
+ approxInputTokens,
56
+ maxInputTokens
57
+ });
58
+ }
59
+
60
+ return new Promise((resolve, reject) => {
61
+ const startedAt = Date.now();
62
+ let child;
63
+ try {
64
+ child = spawn(command, args, {
65
+ cwd,
66
+ env: inheritEnv ? { ...process.env, ...env } : { ...env },
67
+ shell,
68
+ windowsHide: true,
69
+ stdio: ["pipe", "pipe", "pipe"]
70
+ });
71
+ } catch (error) {
72
+ reject(cliError(error.message, "CLI_SPAWN_FAILED", {
73
+ name,
74
+ command,
75
+ cause: error.code || error.name
76
+ }));
77
+ return;
78
+ }
79
+
80
+ const stdout = [];
81
+ const stderr = [];
82
+ let outputBytes = 0;
83
+ let settled = false;
84
+
85
+ const finish = (callback, value) => {
86
+ if (settled) return;
87
+ settled = true;
88
+ clearTimeout(timer);
89
+ callback(value);
90
+ };
91
+
92
+ const stopWithError = (error) => {
93
+ if (!child.killed) child.kill();
94
+ finish(reject, error);
95
+ };
96
+
97
+ const timer = setTimeout(() => {
98
+ stopWithError(cliError(`CLI agent '${name}' timed out after ${timeoutMs}ms.`, "CLI_TIMEOUT", {
99
+ name,
100
+ timeoutMs
101
+ }));
102
+ }, timeoutMs);
103
+
104
+ const collect = (target, chunk) => {
105
+ outputBytes += chunk.byteLength;
106
+ if (outputBytes > maxOutputBytes) {
107
+ stopWithError(cliError(`CLI output exceeds maxOutputBytes (${outputBytes} > ${maxOutputBytes}).`, "CLI_OUTPUT_LIMIT_EXCEEDED", {
108
+ name,
109
+ maxOutputBytes,
110
+ outputBytes
111
+ }));
112
+ return;
113
+ }
114
+ target.push(Buffer.from(chunk));
115
+ };
116
+
117
+ child.stdout.on("data", (chunk) => collect(stdout, chunk));
118
+ child.stderr.on("data", (chunk) => collect(stderr, chunk));
119
+
120
+ child.on("error", (error) => {
121
+ finish(reject, cliError(error.message, "CLI_SPAWN_FAILED", {
122
+ name,
123
+ command,
124
+ cause: error.code || error.name
125
+ }));
126
+ });
127
+
128
+ child.on("close", (exitCode, signal) => {
129
+ if (settled) return;
130
+
131
+ const stdoutText = Buffer.concat(stdout).toString("utf8");
132
+ const stderrText = Buffer.concat(stderr).toString("utf8");
133
+ const result = {
134
+ name,
135
+ command,
136
+ args,
137
+ exitCode,
138
+ signal,
139
+ timedOut: false,
140
+ stdout: stdoutText,
141
+ stderr: stderrText,
142
+ durationMs: Date.now() - startedAt,
143
+ approxInputTokens,
144
+ outputBytes,
145
+ limits: {
146
+ maxInputTokens,
147
+ maxOutputBytes,
148
+ timeoutMs
149
+ }
150
+ };
151
+
152
+ if (parseJson && stdoutText.trim()) {
153
+ try {
154
+ result.json = JSON.parse(stdoutText.trim());
155
+ } catch (error) {
156
+ finish(reject, cliError("CLI stdout was not valid JSON.", "CLI_JSON_PARSE_FAILED", {
157
+ name,
158
+ message: error.message
159
+ }));
160
+ return;
161
+ }
162
+ }
163
+
164
+ if (rejectOnNonZero && exitCode !== 0) {
165
+ finish(reject, cliError(`CLI agent '${name}' exited with code ${exitCode}.`, "CLI_EXIT_NONZERO", {
166
+ ...result,
167
+ stdout: stdoutText.slice(0, 2048),
168
+ stderr: stderrText.slice(0, 2048)
169
+ }));
170
+ return;
171
+ }
172
+
173
+ finish(resolve, result);
174
+ });
175
+
176
+ if (stdinBody === null) {
177
+ child.stdin.end();
178
+ } else {
179
+ child.stdin.end(stdinBody);
180
+ }
181
+ });
182
+ };
183
+ }
184
+
185
+ export { estimateTokens as estimateCliInputTokens };
@@ -58,7 +58,7 @@ export function createResearchWorkflow(options = {}) {
58
58
  name,
59
59
  url: page.url,
60
60
  whatItDoes: page.description || page.text?.slice(0, 240) || page.title || "",
61
- whyUseful: "Potential source or reference for enterprise agent framework capabilities.",
61
+ whyUseful: "Potential source or reference for governed agent framework capabilities.",
62
62
  risks: ["Requires license and maintenance review before reuse."],
63
63
  recommendation: "inspiration_first",
64
64
  evidenceIds: [evidenceId]
package/src/index.js CHANGED
@@ -340,6 +340,7 @@ export { ToolGateway } from "./framework/tool-gateway.js";
340
340
  export { SkillRegistry } from "./framework/skill-registry.js";
341
341
  export { AgentRuntime } from "./framework/runtime.js";
342
342
  export { createAgentTool } from "./framework/agent-tool.js";
343
+ export { createCliAgentTool, estimateCliInputTokens } from "./framework/cli-agent-tool.js";
343
344
  export { createResearchWorkflow } from "./framework/research-workflow.js";
344
345
 
345
346
  export function createCrawlerTool(defaultOptions = {}) {
@@ -14,7 +14,7 @@ import {
14
14
  const PRODUCT = {
15
15
  name: "Maqam",
16
16
  tagline: "Compose governed agents",
17
- description: "Enterprise agent framework console for policy-bound research, evidence capture, and auditable workflow runs."
17
+ description: "Agent framework console for policy-bound workflows, evidence capture, CLI workers, connectors, and auditable runs."
18
18
  };
19
19
 
20
20
  const DEFAULT_PUBLIC_DIR = fileURLToPath(new URL("../../app/", import.meta.url));