@getmarrow/install 0.1.24 → 0.1.26
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 +21 -1
- package/bin/marrow-install.js +1 -1
- package/package.json +1 -1
- package/src/governed-runner.js +277 -41
package/README.md
CHANGED
|
@@ -9,8 +9,25 @@ npx @getmarrow/install --dry-run
|
|
|
9
9
|
npx @getmarrow/install --yes
|
|
10
10
|
npx @getmarrow/install --repair
|
|
11
11
|
npx @getmarrow/install doctor
|
|
12
|
+
npx @getmarrow/install govern
|
|
13
|
+
npx @getmarrow/install fleet
|
|
14
|
+
npx @getmarrow/install integrations
|
|
15
|
+
npx @getmarrow/install hermes
|
|
16
|
+
npx @getmarrow/install openclaw
|
|
12
17
|
```
|
|
13
18
|
|
|
19
|
+
## What's New in v0.1.26
|
|
20
|
+
|
|
21
|
+
v0.1.26 expands Marrow's supported harness registry so teams can attach governance to the agent tools, IDE agents, model CLIs, MCP clients, CI runners, and custom harnesses they already use.
|
|
22
|
+
|
|
23
|
+
- `npx @getmarrow/install integrations` lists supported surfaces, support level, install command, capture path, and detection signals.
|
|
24
|
+
- `npx @getmarrow/install govern` detects Codex, Claude Code, Cursor, Cursor Composer, Windsurf, Cline, OpenCode, Hermes, OpenClaw, Gemini, Grok, DeepSeek, Qwen, Kimi, MiniMax, GLM, MCP clients, CI scripts, and custom commands.
|
|
25
|
+
- Hermes Agent and OpenClaw remain first-class add-ons.
|
|
26
|
+
- Codex, Claude Code, Cursor, Cursor Composer, Windsurf, Cline, OpenCode, Gemini, Grok, DeepSeek, Qwen, Kimi, MiniMax, GLM, MCP clients, CI scripts, and custom shell/API harnesses can use MCP/SDK hooks, the governed runner, or `POST /v1/agent/integrations/events`.
|
|
27
|
+
- Custom harness events should include `harness`, `event_type`, `agent_id`, and `action`, so Marrow can return the right runtime, commit, or batch-ingest next step without storing unsupported or under-attributed raw payloads.
|
|
28
|
+
|
|
29
|
+
The positioning is simple: keep the harness your team already uses, and add Marrow as the governance, proof, and fleet-intelligence layer around it.
|
|
30
|
+
|
|
14
31
|
## What's New in v0.1.24
|
|
15
32
|
|
|
16
33
|
v0.1.24 aligns the installer docs with Marrow's control-plane direction.
|
|
@@ -41,7 +58,7 @@ The Marrow governed runner is for businesses that want agent governance without
|
|
|
41
58
|
- Risky actions can fail closed by default when Marrow requires owner approval, blocks an action, or requires missing proof.
|
|
42
59
|
- Successful and failed commands automatically close outcomes through `/v1/agent/commit` with a redacted proof pack.
|
|
43
60
|
- The runner sends action and command metadata only; it does not upload command stdout, stderr, full environment values, or plaintext API keys.
|
|
44
|
-
- This gives teams a thin governance path for Codex, Claude Code, OpenClaw, OpenCode,
|
|
61
|
+
- This gives teams a thin governance path for Codex, Claude Code, Cursor, Cursor Composer, Windsurf, Cline, OpenClaw, Hermes, OpenCode, Gemini, Grok, DeepSeek, Qwen, Kimi, MiniMax, GLM, MCP clients, CI scripts, and custom shell-based agents.
|
|
45
62
|
|
|
46
63
|
Business value: Marrow can sit in front of the commands that matter most, tell the agent what prior lesson or proof is required before action, and produce an audit-ready outcome trail after the command finishes.
|
|
47
64
|
|
|
@@ -137,9 +154,12 @@ Marrow should answer with `proceed`, `warn`, `block`, or `owner_approval_require
|
|
|
137
154
|
## What It Detects
|
|
138
155
|
|
|
139
156
|
- OpenClaw-style workspaces
|
|
157
|
+
- Hermes Agent config files
|
|
140
158
|
- Codex/agent instruction files such as `AGENTS.md`
|
|
141
159
|
- Claude Code settings and hooks
|
|
142
160
|
- Cursor project folders
|
|
161
|
+
- Cursor Composer, Windsurf, Cline, and OpenCode project markers
|
|
162
|
+
- Gemini, Grok, DeepSeek, Qwen, Kimi, MiniMax, and GLM command/profile markers
|
|
143
163
|
- MCP config files
|
|
144
164
|
- Node projects
|
|
145
165
|
- Python projects
|
package/bin/marrow-install.js
CHANGED
|
@@ -4,7 +4,7 @@ const installer = require('../src/installer');
|
|
|
4
4
|
const governedRunner = require('../src/governed-runner');
|
|
5
5
|
|
|
6
6
|
const argv = process.argv.slice(2);
|
|
7
|
-
const governedCommands = new Set(['run', 'gate', 'proof', 'status', 'govern', 'fleet']);
|
|
7
|
+
const governedCommands = new Set(['run', 'gate', 'proof', 'status', 'govern', 'fleet', 'hermes', 'openclaw', 'integrations']);
|
|
8
8
|
const runCli = governedCommands.has(argv[0]) ? governedRunner.runCli : installer.runCli;
|
|
9
9
|
|
|
10
10
|
runCli(argv).catch((error) => {
|
package/package.json
CHANGED
package/src/governed-runner.js
CHANGED
|
@@ -9,36 +9,6 @@ const DEFAULT_BASE_URL = 'https://api.getmarrow.ai';
|
|
|
9
9
|
const HIGH_RISK_TERMS = /\b(deploy|prod|production|publish|release|merge|migration|migrate|secret|token|key|cloudflare|wrangler|npm publish|gh pr merge|git push|terraform apply|kubectl apply|delete|destroy|drop)\b/i;
|
|
10
10
|
const GOVERN_TUI_ROW_COUNT = 7;
|
|
11
11
|
const FLEET_TUI_ROW_COUNT = 11;
|
|
12
|
-
const SOURCE_CLIENTS = new Set(['claude-code', 'cursor', 'windsurf', 'openclaw', 'codex', 'gemini', 'grok', 'deepseek', 'qwen', 'kimi', 'minimax', 'cline', 'opencode', 'hermes', 'glm', 'custom', 'unknown']);
|
|
13
|
-
|
|
14
|
-
function sourceClient() {
|
|
15
|
-
const raw = String(process.env.MARROW_CLIENT || process.env.MARROW_HARNESS || process.env.MARROW_AGENT_CLIENT || '').trim().toLowerCase().replace(/\s+/g, '-').replace(/^@/, '');
|
|
16
|
-
const aliases = {
|
|
17
|
-
claude: 'claude-code',
|
|
18
|
-
claude_code: 'claude-code',
|
|
19
|
-
'claude-code': 'claude-code',
|
|
20
|
-
cursor: 'cursor',
|
|
21
|
-
windsurf: 'windsurf',
|
|
22
|
-
openclaw: 'openclaw',
|
|
23
|
-
codex: 'codex',
|
|
24
|
-
'openai-codex': 'codex',
|
|
25
|
-
gemini: 'gemini',
|
|
26
|
-
google: 'gemini',
|
|
27
|
-
grok: 'grok',
|
|
28
|
-
deepseek: 'deepseek',
|
|
29
|
-
qwen: 'qwen',
|
|
30
|
-
kimi: 'kimi',
|
|
31
|
-
minimax: 'minimax',
|
|
32
|
-
cline: 'cline',
|
|
33
|
-
opencode: 'opencode',
|
|
34
|
-
'open-code': 'opencode',
|
|
35
|
-
hermes: 'hermes',
|
|
36
|
-
'hermes-agent': 'hermes',
|
|
37
|
-
glm: 'glm',
|
|
38
|
-
};
|
|
39
|
-
return aliases[raw] || (SOURCE_CLIENTS.has(raw) ? raw : 'custom');
|
|
40
|
-
}
|
|
41
|
-
|
|
42
12
|
function usage() {
|
|
43
13
|
return `Usage:
|
|
44
14
|
npx @getmarrow/install run --agent deploy-agent -- npm test
|
|
@@ -49,6 +19,9 @@ function usage() {
|
|
|
49
19
|
npx @getmarrow/install govern
|
|
50
20
|
npx @getmarrow/install govern --no-interactive
|
|
51
21
|
npx @getmarrow/install fleet
|
|
22
|
+
npx @getmarrow/install integrations
|
|
23
|
+
npx @getmarrow/install hermes
|
|
24
|
+
npx @getmarrow/install openclaw
|
|
52
25
|
|
|
53
26
|
Commands:
|
|
54
27
|
run Run a command through Marrow pre-action gate and automatic outcome closure
|
|
@@ -57,6 +30,9 @@ Commands:
|
|
|
57
30
|
status Read /v1/agent/status
|
|
58
31
|
govern Interactive setup TUI when run in a terminal; text panel in CI/non-TTY
|
|
59
32
|
fleet Fleet operator TUI for live agents, workflows, gates, proof debt, and exact fixes
|
|
33
|
+
integrations List Marrow-supported harness add-ons
|
|
34
|
+
hermes Show and verify the Marrow add-on path for Hermes Agent
|
|
35
|
+
openclaw Show and verify the Marrow add-on path for OpenClaw
|
|
60
36
|
|
|
61
37
|
Options:
|
|
62
38
|
--agent <id> Agent identity. Defaults to MARROW_FLEET_AGENT_ID, MARROW_AGENT_ID, or local user
|
|
@@ -69,6 +45,7 @@ Options:
|
|
|
69
45
|
--fail-closed If Marrow is unreachable, block the command
|
|
70
46
|
--owner-approved <ref> Owner approval reference for review-required gates
|
|
71
47
|
--proof-file <path> JSON proof to include on outcome commit
|
|
48
|
+
--client <label> Harness/client label. Defaults to MARROW_CLIENT, MARROW_HARNESS, or MARROW_AGENT_CLIENT
|
|
72
49
|
--base-url <url> Marrow API base URL
|
|
73
50
|
--key <key> Marrow API key. Prefer MARROW_API_KEY
|
|
74
51
|
--json Print machine-readable result after completion
|
|
@@ -101,6 +78,40 @@ function redactedCommand(command) {
|
|
|
101
78
|
return command.map((part) => shellQuote(redact(part))).join(' ');
|
|
102
79
|
}
|
|
103
80
|
|
|
81
|
+
function normalizeClientLabel(value) {
|
|
82
|
+
const raw = String(value || '').trim().toLowerCase();
|
|
83
|
+
if (!raw) return '';
|
|
84
|
+
const normalized = raw
|
|
85
|
+
.replace(/[^a-z0-9._-]+/g, '-')
|
|
86
|
+
.replace(/^-+|-+$/g, '')
|
|
87
|
+
.slice(0, 64);
|
|
88
|
+
const aliases = {
|
|
89
|
+
claude: 'claude-code',
|
|
90
|
+
claude_code: 'claude-code',
|
|
91
|
+
'cursor-composer': 'composer',
|
|
92
|
+
'openai-codex': 'codex',
|
|
93
|
+
openai: 'codex',
|
|
94
|
+
'gemini-cli': 'gemini',
|
|
95
|
+
'grok-cli': 'grok',
|
|
96
|
+
'deepseek-cli': 'deepseek',
|
|
97
|
+
'qwen-cli': 'qwen',
|
|
98
|
+
'kimi-cli': 'kimi',
|
|
99
|
+
'minimax-cli': 'minimax',
|
|
100
|
+
'glm-cli': 'glm',
|
|
101
|
+
};
|
|
102
|
+
return aliases[normalized] || normalized || 'custom';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function sourceClient(value) {
|
|
106
|
+
return normalizeClientLabel(
|
|
107
|
+
value
|
|
108
|
+
|| process.env.MARROW_CLIENT
|
|
109
|
+
|| process.env.MARROW_HARNESS
|
|
110
|
+
|| process.env.MARROW_AGENT_CLIENT
|
|
111
|
+
|| '@getmarrow/install',
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
104
115
|
function displayText(value, maxLength = 120) {
|
|
105
116
|
const text = redact(String(value || ''))
|
|
106
117
|
.replace(/\u001b\][^\u0007]*(?:\u0007|\u001b\\)/g, '')
|
|
@@ -184,7 +195,23 @@ function detectProjectSignals(cwd = process.cwd()) {
|
|
|
184
195
|
addFile('AGENTS.md', 'agent_instructions');
|
|
185
196
|
addFile('CLAUDE.md', 'agent_instructions');
|
|
186
197
|
addFile('.mcp.json', 'mcp_config');
|
|
198
|
+
addFile('mcp.json', 'mcp_config');
|
|
187
199
|
addFile('.cursor', 'cursor_project');
|
|
200
|
+
addFile('.windsurf', 'windsurf_project');
|
|
201
|
+
addFile('.cline', 'cline_project');
|
|
202
|
+
addFile('hermes.json', 'hermes_config');
|
|
203
|
+
addFile('hermes.yaml', 'hermes_config');
|
|
204
|
+
addFile('hermes.yml', 'hermes_config');
|
|
205
|
+
addFile('.hermes', 'hermes_config');
|
|
206
|
+
addFile('openclaw.json', 'openclaw_config');
|
|
207
|
+
addFile('.openclaw', 'openclaw_config');
|
|
208
|
+
addFile('.gemini', 'gemini_profile');
|
|
209
|
+
addFile('.grok', 'grok_profile');
|
|
210
|
+
addFile('.deepseek', 'deepseek_profile');
|
|
211
|
+
addFile('.qwen', 'qwen_profile');
|
|
212
|
+
addFile('.kimi', 'kimi_profile');
|
|
213
|
+
addFile('.minimax', 'minimax_profile');
|
|
214
|
+
addFile('.glm', 'glm_profile');
|
|
188
215
|
|
|
189
216
|
for (const script of packageScripts) {
|
|
190
217
|
if (/\b(deploy|publish|release|migrate|migration|smoke|check|test)\b/i.test(script)) {
|
|
@@ -222,6 +249,7 @@ function parseBaseOptions(argv, startIndex = 0) {
|
|
|
222
249
|
proofFile: '',
|
|
223
250
|
type: '',
|
|
224
251
|
action: '',
|
|
252
|
+
client: sourceClient(),
|
|
225
253
|
interactive: null,
|
|
226
254
|
};
|
|
227
255
|
let i = startIndex;
|
|
@@ -242,6 +270,7 @@ function parseBaseOptions(argv, startIndex = 0) {
|
|
|
242
270
|
options.failOpen = false;
|
|
243
271
|
} else if (arg === '--owner-approved') options.ownerApproval = argv[++i] || options.ownerApproval;
|
|
244
272
|
else if (arg === '--proof-file') options.proofFile = argv[++i] || options.proofFile;
|
|
273
|
+
else if (arg === '--client' || arg === '--harness') options.client = sourceClient(argv[++i] || options.client);
|
|
245
274
|
else if (arg === '--base-url') options.baseUrl = argv[++i] || options.baseUrl;
|
|
246
275
|
else if (arg === '--key') {
|
|
247
276
|
options.apiKey = argv[++i] || options.apiKey;
|
|
@@ -298,7 +327,7 @@ function parseArgs(argv) {
|
|
|
298
327
|
return { command, options };
|
|
299
328
|
}
|
|
300
329
|
|
|
301
|
-
if (command === 'status' || command === 'govern' || command === 'fleet') {
|
|
330
|
+
if (command === 'status' || command === 'govern' || command === 'fleet' || command === 'hermes' || command === 'openclaw' || command === 'integrations') {
|
|
302
331
|
const parsed = parseBaseOptions(argv, 1);
|
|
303
332
|
if (parsed.options.help) return { command: 'help' };
|
|
304
333
|
return { command, options: parsed.options };
|
|
@@ -313,12 +342,28 @@ function headers(options) {
|
|
|
313
342
|
'Content-Type': 'application/json',
|
|
314
343
|
'X-Marrow-Agent-Id': options.agentId,
|
|
315
344
|
'X-Marrow-Session-Id': options.sessionId,
|
|
316
|
-
'X-Marrow-Client': sourceClient(),
|
|
345
|
+
'X-Marrow-Client': sourceClient(options.client),
|
|
317
346
|
'User-Agent': '@getmarrow/install governed-runner',
|
|
318
347
|
};
|
|
319
348
|
return h;
|
|
320
349
|
}
|
|
321
350
|
|
|
351
|
+
function sourceMeta(options, channel, extra = {}) {
|
|
352
|
+
const client = sourceClient(options.client);
|
|
353
|
+
return {
|
|
354
|
+
channel,
|
|
355
|
+
client,
|
|
356
|
+
harness: client,
|
|
357
|
+
runner: '@getmarrow/install',
|
|
358
|
+
agent_id: options.agentId,
|
|
359
|
+
session_id: options.sessionId,
|
|
360
|
+
profile: options.profile,
|
|
361
|
+
governed: true,
|
|
362
|
+
...(extra.action ? { user_intent: displayText(extra.action, 160) } : {}),
|
|
363
|
+
...extra,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
322
367
|
function dataOf(json) {
|
|
323
368
|
return json && typeof json === 'object' && json.data && typeof json.data === 'object' ? json.data : json;
|
|
324
369
|
}
|
|
@@ -359,22 +404,26 @@ function defaultProof(input) {
|
|
|
359
404
|
exit_code: input.exitCode,
|
|
360
405
|
runner: '@getmarrow/install run',
|
|
361
406
|
profile: input.options.profile,
|
|
407
|
+
source_meta: sourceMeta(input.options, 'proof', { action: input.action }),
|
|
362
408
|
...(input.options.ownerApproval ? { owner_approval: { approved_by: 'owner', reference: input.options.ownerApproval } } : {}),
|
|
363
409
|
...proof,
|
|
364
410
|
};
|
|
365
411
|
}
|
|
366
412
|
|
|
367
413
|
async function preflightRuntime(options, action, type, commandText) {
|
|
414
|
+
const meta = sourceMeta(options, 'runtime', { action, command: commandText, action_type: type });
|
|
368
415
|
return requestJson(options, 'POST', '/v1/agent/runtime', {
|
|
369
416
|
action,
|
|
370
417
|
type,
|
|
371
418
|
surfaces: inferSurfaces(commandText || action),
|
|
419
|
+
source_meta: meta,
|
|
372
420
|
context: {
|
|
373
421
|
runner: '@getmarrow/install run',
|
|
374
422
|
profile: options.profile,
|
|
375
423
|
command: commandText,
|
|
376
424
|
policy: options.policy,
|
|
377
425
|
governed: true,
|
|
426
|
+
source_meta: meta,
|
|
378
427
|
},
|
|
379
428
|
});
|
|
380
429
|
}
|
|
@@ -402,6 +451,7 @@ async function recommendGovernanceMode(options, project = detectProjectSignals()
|
|
|
402
451
|
id: options.agentId,
|
|
403
452
|
role: 'setup',
|
|
404
453
|
},
|
|
454
|
+
source_meta: sourceMeta(options, 'mode_recommend', { action }),
|
|
405
455
|
});
|
|
406
456
|
}
|
|
407
457
|
|
|
@@ -421,6 +471,7 @@ async function recordGovernanceModeSelection(options, state) {
|
|
|
421
471
|
},
|
|
422
472
|
selected_mode: selected,
|
|
423
473
|
selection_source: selected === state.recommendation.recommended_mode ? 'accepted' : 'overridden',
|
|
474
|
+
source_meta: sourceMeta(options, 'mode_selection', { action: 'selected Marrow governance mode for this project' }),
|
|
424
475
|
});
|
|
425
476
|
}
|
|
426
477
|
|
|
@@ -472,18 +523,16 @@ function runChild(command, env = process.env) {
|
|
|
472
523
|
}
|
|
473
524
|
|
|
474
525
|
async function createDecision(options, action, type) {
|
|
526
|
+
const meta = sourceMeta(options, 'think', { action, action_type: type });
|
|
475
527
|
return requestJson(options, 'POST', '/v1/agent/think', {
|
|
476
528
|
action,
|
|
477
529
|
type,
|
|
530
|
+
source_meta: meta,
|
|
478
531
|
context: {
|
|
479
532
|
runner: '@getmarrow/install run',
|
|
480
533
|
profile: options.profile,
|
|
481
534
|
governed: true,
|
|
482
|
-
|
|
483
|
-
source_meta: {
|
|
484
|
-
channel: 'cli',
|
|
485
|
-
client: sourceClient(),
|
|
486
|
-
user_intent: type === 'deploy' ? 'deploy' : 'operate',
|
|
535
|
+
source_meta: meta,
|
|
487
536
|
},
|
|
488
537
|
});
|
|
489
538
|
}
|
|
@@ -494,6 +543,7 @@ async function commitOutcome(options, decisionId, success, outcome, proof, gateR
|
|
|
494
543
|
success,
|
|
495
544
|
outcome,
|
|
496
545
|
proof,
|
|
546
|
+
source_meta: sourceMeta(options, 'commit', { action: outcome }),
|
|
497
547
|
};
|
|
498
548
|
if (gateReceiptId) body.gate_receipt_id = gateReceiptId;
|
|
499
549
|
return requestJson(options, 'POST', '/v1/agent/commit', body);
|
|
@@ -724,13 +774,10 @@ function normalizeFixCommands(status, capacity) {
|
|
|
724
774
|
add(status.route_contract?.exact_fix);
|
|
725
775
|
add(status.auto_outcome_closure?.exact_fix);
|
|
726
776
|
add(status.capture_coverage?.exact_fix);
|
|
727
|
-
add(status.token_capture?.exact_fix);
|
|
728
|
-
add(status.token_capture?.fix_command, true);
|
|
729
777
|
add(capacity.exact_next_action, true);
|
|
730
778
|
add(capacity.next_action, true);
|
|
731
779
|
if (listValue(status.missed_hooks, status.degraded_hooks).length) add('npx @getmarrow/install --repair');
|
|
732
780
|
if (status.auto_outcome_closure?.status === 'degraded') add('npx @getmarrow/install --repair');
|
|
733
|
-
if (status.token_capture?.detected === false) add('npx @getmarrow/install --repair');
|
|
734
781
|
return commands.slice(0, 6);
|
|
735
782
|
}
|
|
736
783
|
|
|
@@ -878,19 +925,193 @@ function fleetPanel(snapshot) {
|
|
|
878
925
|
].filter(Boolean).join('\n');
|
|
879
926
|
}
|
|
880
927
|
|
|
928
|
+
const GENERIC_GOVERNED_COMMAND = 'MARROW_API_KEY=mrw_live_xxx npx @getmarrow/install run --agent <agent-id> --profile production --policy warn -- <harness-command>';
|
|
929
|
+
|
|
930
|
+
function localSupportedHarnesses() {
|
|
931
|
+
return [
|
|
932
|
+
{ display_name: 'OpenAI Codex', client_label: 'codex', category: 'agent_harness', support_level: 'governed_runner', install_command: 'MARROW_API_KEY=mrw_live_xxx npx @getmarrow/install run --agent codex-prod -- codex' },
|
|
933
|
+
{ display_name: 'Claude Code', client_label: 'claude-code', category: 'agent_harness', support_level: 'native_mcp_or_sdk', install_command: 'MARROW_API_KEY=mrw_live_xxx npx @getmarrow/mcp setup' },
|
|
934
|
+
{ display_name: 'Cursor', client_label: 'cursor', category: 'ide_agent', support_level: 'native_mcp_or_sdk', install_command: 'MARROW_API_KEY=mrw_live_xxx npx @getmarrow/mcp setup' },
|
|
935
|
+
{ display_name: 'Cursor Composer', client_label: 'composer', category: 'ide_agent', support_level: 'native_mcp_or_sdk', install_command: 'MARROW_API_KEY=mrw_live_xxx npx @getmarrow/mcp setup' },
|
|
936
|
+
{ display_name: 'Windsurf', client_label: 'windsurf', category: 'ide_agent', support_level: 'native_mcp_or_sdk', install_command: 'MARROW_API_KEY=mrw_live_xxx npx @getmarrow/mcp setup' },
|
|
937
|
+
{ display_name: 'Cline', client_label: 'cline', category: 'ide_agent', support_level: 'native_mcp_or_sdk', install_command: 'MARROW_API_KEY=mrw_live_xxx npx @getmarrow/mcp setup' },
|
|
938
|
+
{ display_name: 'OpenCode', client_label: 'opencode', category: 'agent_harness', support_level: 'governed_runner', install_command: 'MARROW_API_KEY=mrw_live_xxx npx @getmarrow/install run --agent opencode-prod -- opencode' },
|
|
939
|
+
{ display_name: 'Hermes Agent', client_label: 'hermes', category: 'agent_harness', support_level: 'first_class_addon', install_command: 'MARROW_API_KEY=mrw_live_xxx npx @getmarrow/install hermes' },
|
|
940
|
+
{ display_name: 'OpenClaw', client_label: 'openclaw', category: 'agent_harness', support_level: 'first_class_addon', install_command: 'MARROW_API_KEY=mrw_live_xxx npx @getmarrow/install openclaw' },
|
|
941
|
+
{ display_name: 'Gemini CLI', client_label: 'gemini', category: 'model_cli', support_level: 'governed_runner', install_command: GENERIC_GOVERNED_COMMAND },
|
|
942
|
+
{ display_name: 'Grok CLI', client_label: 'grok', category: 'model_cli', support_level: 'governed_runner', install_command: GENERIC_GOVERNED_COMMAND },
|
|
943
|
+
{ display_name: 'DeepSeek', client_label: 'deepseek', category: 'model_cli', support_level: 'governed_runner', install_command: GENERIC_GOVERNED_COMMAND },
|
|
944
|
+
{ display_name: 'Qwen', client_label: 'qwen', category: 'model_cli', support_level: 'governed_runner', install_command: GENERIC_GOVERNED_COMMAND },
|
|
945
|
+
{ display_name: 'Kimi', client_label: 'kimi', category: 'model_cli', support_level: 'governed_runner', install_command: GENERIC_GOVERNED_COMMAND },
|
|
946
|
+
{ display_name: 'MiniMax', client_label: 'minimax', category: 'model_cli', support_level: 'governed_runner', install_command: GENERIC_GOVERNED_COMMAND },
|
|
947
|
+
{ display_name: 'GLM', client_label: 'glm', category: 'model_cli', support_level: 'governed_runner', install_command: GENERIC_GOVERNED_COMMAND },
|
|
948
|
+
{ display_name: 'MCP-compatible clients', client_label: 'mcp', category: 'mcp_client', support_level: 'native_mcp_or_sdk', install_command: 'MARROW_API_KEY=mrw_live_xxx npx @getmarrow/mcp setup' },
|
|
949
|
+
{ display_name: 'CI scripts and deploy runners', client_label: 'ci', category: 'ci_runner', support_level: 'governed_runner', install_command: 'MARROW_API_KEY=mrw_live_xxx npx @getmarrow/install run --agent ci-release --profile production --policy enforce -- <ci-or-deploy-command>' },
|
|
950
|
+
{ display_name: 'Custom shell/API harness', client_label: 'custom', category: 'custom_runner', support_level: 'event_contract', install_command: 'POST /v1/agent/integrations/events with harness, event_type, agent_id, and action' },
|
|
951
|
+
];
|
|
952
|
+
}
|
|
953
|
+
|
|
881
954
|
function detectHarnesses(cwd = process.cwd()) {
|
|
882
955
|
const candidates = [
|
|
883
956
|
{ name: 'Codex', command: 'codex', detected: fs.existsSync(path.join(cwd, 'AGENTS.md')) || fs.existsSync(path.join(os.homedir(), '.codex')) },
|
|
884
957
|
{ name: 'Claude Code', command: 'claude -p', detected: fs.existsSync(path.join(cwd, 'CLAUDE.md')) || fs.existsSync(path.join(os.homedir(), '.claude.json')) },
|
|
885
958
|
{ name: 'Cursor', command: 'cursor', detected: fs.existsSync(path.join(cwd, '.cursor')) || fs.existsSync(path.join(os.homedir(), '.cursor')) },
|
|
959
|
+
{ name: 'Cursor Composer', command: 'cursor composer', detected: fs.existsSync(path.join(cwd, '.cursor')) || fs.existsSync(path.join(os.homedir(), '.cursor')) },
|
|
960
|
+
{ name: 'Windsurf', command: 'windsurf', detected: fs.existsSync(path.join(cwd, '.windsurf')) || fs.existsSync(path.join(os.homedir(), '.windsurf')) },
|
|
961
|
+
{ name: 'Cline', command: 'cline', detected: fs.existsSync(path.join(cwd, '.cline')) || fs.existsSync(path.join(cwd, '.vscode')) },
|
|
886
962
|
{ name: 'OpenCode', command: 'opencode', detected: fs.existsSync(path.join(cwd, 'opencode.json')) || fs.existsSync(path.join(os.homedir(), '.opencode')) },
|
|
963
|
+
{ name: 'Hermes Agent', command: 'hermes', detected: fs.existsSync(path.join(cwd, 'hermes.json')) || fs.existsSync(path.join(cwd, '.hermes')) || fs.existsSync(path.join(os.homedir(), '.hermes')) || fs.existsSync(path.join(os.homedir(), '.hermes-agent')) },
|
|
887
964
|
{ name: 'OpenClaw', command: 'openclaw agent', detected: fs.existsSync(path.join(os.homedir(), '.openclaw')) },
|
|
965
|
+
{ name: 'Gemini CLI', command: 'gemini', detected: fs.existsSync(path.join(cwd, '.gemini')) || fs.existsSync(path.join(os.homedir(), '.gemini')) },
|
|
966
|
+
{ name: 'Grok CLI', command: 'grok', detected: fs.existsSync(path.join(cwd, '.grok')) || fs.existsSync(path.join(os.homedir(), '.grok')) },
|
|
967
|
+
{ name: 'DeepSeek', command: 'deepseek', detected: fs.existsSync(path.join(cwd, '.deepseek')) || fs.existsSync(path.join(os.homedir(), '.deepseek')) },
|
|
968
|
+
{ name: 'Qwen', command: 'qwen', detected: fs.existsSync(path.join(cwd, '.qwen')) || fs.existsSync(path.join(os.homedir(), '.qwen')) },
|
|
969
|
+
{ name: 'Kimi', command: 'kimi', detected: fs.existsSync(path.join(cwd, '.kimi')) || fs.existsSync(path.join(os.homedir(), '.kimi')) },
|
|
970
|
+
{ name: 'MiniMax', command: 'minimax', detected: fs.existsSync(path.join(cwd, '.minimax')) || fs.existsSync(path.join(os.homedir(), '.minimax')) },
|
|
971
|
+
{ name: 'GLM', command: 'glm', detected: fs.existsSync(path.join(cwd, '.glm')) || fs.existsSync(path.join(os.homedir(), '.glm')) },
|
|
972
|
+
{ name: 'MCP-compatible client', command: 'mcp client', detected: fs.existsSync(path.join(cwd, '.mcp.json')) || fs.existsSync(path.join(cwd, 'mcp.json')) },
|
|
888
973
|
{ name: 'CI script', command: 'npm test', detected: fs.existsSync(path.join(cwd, 'package.json')) },
|
|
889
974
|
{ name: 'Custom command', command: '<your-agent-command>', detected: true },
|
|
890
975
|
];
|
|
891
976
|
return candidates;
|
|
892
977
|
}
|
|
893
978
|
|
|
979
|
+
function localIntegrationManifest(name) {
|
|
980
|
+
const key = String(name || '').toLowerCase().replace(/\s+/g, '-');
|
|
981
|
+
if (key === 'hermes' || key === 'hermes-agent') {
|
|
982
|
+
return {
|
|
983
|
+
integration: 'hermes-agent',
|
|
984
|
+
title: 'Marrow + Hermes Agent',
|
|
985
|
+
client_label: 'hermes',
|
|
986
|
+
command: 'hermes',
|
|
987
|
+
install_command: 'MARROW_API_KEY=mrw_live_xxx npx @getmarrow/install hermes',
|
|
988
|
+
governed_command: 'MARROW_API_KEY=mrw_live_xxx npx @getmarrow/install run --agent hermes-prod --profile production --policy enforce -- hermes',
|
|
989
|
+
capture_points: [
|
|
990
|
+
'/goal -> Marrow completion contract',
|
|
991
|
+
'verification evidence -> Marrow proof pack',
|
|
992
|
+
'/learn -> outcome-ranked fleet lesson',
|
|
993
|
+
'/journey -> governance timeline',
|
|
994
|
+
'background subagents -> agent_id + source_meta.client=hermes',
|
|
995
|
+
],
|
|
996
|
+
exact_next_action: 'Run the Hermes add-on, then call Marrow runtime before deploy, merge, publish, migration, credential, or customer-facing work.',
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
if (key === 'openclaw' || key === 'open-claw') {
|
|
1000
|
+
return {
|
|
1001
|
+
integration: 'openclaw',
|
|
1002
|
+
title: 'Marrow + OpenClaw',
|
|
1003
|
+
client_label: 'openclaw',
|
|
1004
|
+
command: 'openclaw agent',
|
|
1005
|
+
install_command: 'MARROW_API_KEY=mrw_live_xxx npx @getmarrow/install openclaw',
|
|
1006
|
+
governed_command: 'MARROW_API_KEY=mrw_live_xxx npx @getmarrow/install run --agent openclaw-release --profile production --policy enforce -- openclaw agent',
|
|
1007
|
+
capture_points: [
|
|
1008
|
+
'agent sessions -> workflow sessions',
|
|
1009
|
+
'handoff/result files -> proof packs',
|
|
1010
|
+
'timeouts/silent quits -> failed or stale outcomes',
|
|
1011
|
+
'release supervisor results -> deploy proof',
|
|
1012
|
+
'watchdog checkpoints -> fleet handoff lessons',
|
|
1013
|
+
],
|
|
1014
|
+
exact_next_action: 'Run the OpenClaw add-on, then wrap release and handoff commands with the Marrow governed runner.',
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
return null;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
async function integrationManifest(options, name) {
|
|
1021
|
+
const local = localIntegrationManifest(name);
|
|
1022
|
+
if (!local) throw new Error(`Unsupported integration: ${name}`);
|
|
1023
|
+
if (!options.apiKey) return { source: 'local', manifest: local };
|
|
1024
|
+
const route = name === 'openclaw' ? '/v1/agent/integrations/openclaw' : '/v1/agent/integrations/hermes';
|
|
1025
|
+
try {
|
|
1026
|
+
const remote = await requestJson(options, 'GET', route);
|
|
1027
|
+
return { source: 'api', manifest: remote };
|
|
1028
|
+
} catch (error) {
|
|
1029
|
+
return { source: 'local', manifest: { ...local, api_warning: error instanceof Error ? error.message : String(error) } };
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
function renderIntegrationPanel(name, manifest, source = 'local', detectedHarnesses = detectHarnesses()) {
|
|
1034
|
+
const title = manifest.title || (manifest.integration === 'openclaw' ? 'Marrow + OpenClaw' : 'Marrow + Hermes Agent');
|
|
1035
|
+
const capturePoints = Array.isArray(manifest.capture_points)
|
|
1036
|
+
? manifest.capture_points.map((point) => {
|
|
1037
|
+
if (typeof point === 'string') return point;
|
|
1038
|
+
return `${point.harness_surface || point.hermes_surface || 'harness event'} -> ${point.marrow_mapping || 'Marrow event'}`;
|
|
1039
|
+
})
|
|
1040
|
+
: [];
|
|
1041
|
+
const installCommands = Array.isArray(manifest.install_commands)
|
|
1042
|
+
? manifest.install_commands
|
|
1043
|
+
: [manifest.install_command, manifest.governed_command].filter(Boolean);
|
|
1044
|
+
const detected = detectedHarnesses.find((harness) => harness.name.toLowerCase().includes(name === 'openclaw' ? 'openclaw' : 'hermes'))?.detected;
|
|
1045
|
+
const lines = [
|
|
1046
|
+
title,
|
|
1047
|
+
'',
|
|
1048
|
+
`Status: ${manifest.status || 'supported'} (${source})`,
|
|
1049
|
+
`Detected locally: ${detected ? 'yes' : 'not yet'}`,
|
|
1050
|
+
`Client label: ${manifest.client_label}`,
|
|
1051
|
+
'',
|
|
1052
|
+
'What Marrow captures:',
|
|
1053
|
+
...capturePoints.map((point) => ` - ${displayText(point, 110)}`),
|
|
1054
|
+
'',
|
|
1055
|
+
'Install / run:',
|
|
1056
|
+
...installCommands.map((command) => ` ${displayText(command, 140)}`),
|
|
1057
|
+
'',
|
|
1058
|
+
'Why this matters:',
|
|
1059
|
+
` ${displayText(manifest.marrow_positioning || 'Keep the harness. Add Marrow governance, proof packs, outcomes, and buyer-grade fleet value.', 140)}`,
|
|
1060
|
+
'',
|
|
1061
|
+
`Next: ${displayText(manifest.exact_next_action, 140)}`,
|
|
1062
|
+
];
|
|
1063
|
+
if (manifest.api_warning) lines.push('', `API warning: ${displayText(manifest.api_warning, 120)}`);
|
|
1064
|
+
return lines.join('\n');
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
async function integrationOnly(parsed, name) {
|
|
1068
|
+
const result = await integrationManifest(parsed.options, name);
|
|
1069
|
+
const panel = renderIntegrationPanel(name, result.manifest, result.source);
|
|
1070
|
+
if (parsed.options.json) {
|
|
1071
|
+
return {
|
|
1072
|
+
ok: true,
|
|
1073
|
+
source: result.source,
|
|
1074
|
+
integration: result.manifest,
|
|
1075
|
+
panel,
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
process.stdout.write(`${panel}\n`);
|
|
1079
|
+
return { ok: true, source: result.source, integration: result.manifest };
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
async function integrationsOnly(parsed) {
|
|
1083
|
+
const local = {
|
|
1084
|
+
integration_registry_version: 'local.broad-harness-support-v2',
|
|
1085
|
+
first_class_addons: ['hermes', 'openclaw'].map((name) => localIntegrationManifest(name)),
|
|
1086
|
+
supported_harnesses: localSupportedHarnesses(),
|
|
1087
|
+
exact_next_action: 'Pick the harness your team already uses. Use the install_command shown here, or send compact events to /v1/agent/integrations/events.',
|
|
1088
|
+
};
|
|
1089
|
+
let registry = local;
|
|
1090
|
+
let source = 'local';
|
|
1091
|
+
if (parsed.options.apiKey) {
|
|
1092
|
+
try {
|
|
1093
|
+
registry = await requestJson(parsed.options, 'GET', '/v1/agent/integrations');
|
|
1094
|
+
source = 'api';
|
|
1095
|
+
} catch (error) {
|
|
1096
|
+
registry = { ...local, api_warning: error instanceof Error ? error.message : String(error) };
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
if (parsed.options.json) return { ok: true, source, registry };
|
|
1100
|
+
const harnesses = Array.isArray(registry.supported_harnesses) ? registry.supported_harnesses : [];
|
|
1101
|
+
process.stdout.write([
|
|
1102
|
+
'Marrow Harness Integrations',
|
|
1103
|
+
'',
|
|
1104
|
+
`Source: ${source}`,
|
|
1105
|
+
'Supported harnesses and model CLIs:',
|
|
1106
|
+
...harnesses.map((harness) => ` - ${displayText(harness.display_name || harness.client_label || harness.integration || harness.title, 40)} [${displayText(harness.support_level || 'supported', 24)}] ${displayText(harness.install_command || harness.install_commands?.[0] || '', 120)}`),
|
|
1107
|
+
'',
|
|
1108
|
+
'First-class add-on guides: hermes, openclaw',
|
|
1109
|
+
`Next: ${displayText(registry.exact_next_action, 140)}`,
|
|
1110
|
+
registry.api_warning ? `API warning: ${displayText(registry.api_warning, 120)}` : '',
|
|
1111
|
+
].filter(Boolean).join('\n') + '\n');
|
|
1112
|
+
return { ok: true, source, registry };
|
|
1113
|
+
}
|
|
1114
|
+
|
|
894
1115
|
function governPanel(options) {
|
|
895
1116
|
const rows = detectHarnesses();
|
|
896
1117
|
const project = detectProjectSignals();
|
|
@@ -1495,11 +1716,17 @@ async function runCli(argv) {
|
|
|
1495
1716
|
return;
|
|
1496
1717
|
} else if (parsed.command === 'fleet') {
|
|
1497
1718
|
result = await runFleetInteractive(parsed.options);
|
|
1719
|
+
} else if (parsed.command === 'hermes') {
|
|
1720
|
+
result = await integrationOnly(parsed, 'hermes');
|
|
1721
|
+
} else if (parsed.command === 'openclaw') {
|
|
1722
|
+
result = await integrationOnly(parsed, 'openclaw');
|
|
1723
|
+
} else if (parsed.command === 'integrations') {
|
|
1724
|
+
result = await integrationsOnly(parsed);
|
|
1498
1725
|
}
|
|
1499
1726
|
|
|
1500
1727
|
if (parsed.options?.json) process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
1501
1728
|
else if (result?.blocked) process.stderr.write(`BLOCKED: ${result.message || 'Marrow blocked this action.'}\n`);
|
|
1502
|
-
else if (
|
|
1729
|
+
else if (!['run', 'fleet', 'hermes', 'openclaw', 'integrations'].includes(parsed.command)) process.stdout.write('Marrow command completed.\n');
|
|
1503
1730
|
|
|
1504
1731
|
if (parsed.command === 'run' || result?.blocked) process.exitCode = result?.exitCode ?? (result?.ok === false ? 1 : 0);
|
|
1505
1732
|
}
|
|
@@ -1508,6 +1735,10 @@ module.exports = {
|
|
|
1508
1735
|
parseArgs,
|
|
1509
1736
|
redact,
|
|
1510
1737
|
redactedCommand,
|
|
1738
|
+
normalizeClientLabel,
|
|
1739
|
+
sourceClient,
|
|
1740
|
+
sourceMeta,
|
|
1741
|
+
headers,
|
|
1511
1742
|
inferType,
|
|
1512
1743
|
inferSurfaces,
|
|
1513
1744
|
commandForSelection,
|
|
@@ -1534,5 +1765,10 @@ module.exports = {
|
|
|
1534
1765
|
buildFleetState,
|
|
1535
1766
|
renderFleetTui,
|
|
1536
1767
|
runFleetInteractive,
|
|
1768
|
+
localSupportedHarnesses,
|
|
1769
|
+
localIntegrationManifest,
|
|
1770
|
+
renderIntegrationPanel,
|
|
1771
|
+
integrationOnly,
|
|
1772
|
+
integrationsOnly,
|
|
1537
1773
|
runCli,
|
|
1538
1774
|
};
|