@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 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, Cursor, CI scripts, and custom shell-based agents.
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
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getmarrow/install",
3
- "version": "0.1.24",
3
+ "version": "0.1.26",
4
4
  "description": "Universal installer for Marrow agent governance, proof, and passive control setup.",
5
5
  "bin": {
6
6
  "marrow-install": "bin/marrow-install.js"
@@ -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 (parsed.command !== 'run' && parsed.command !== 'fleet') process.stdout.write('Marrow command completed.\n');
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
  };