@getmarrow/install 0.1.16 → 0.1.18

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
@@ -11,16 +11,27 @@ npx @getmarrow/install --repair
11
11
  npx @getmarrow/install doctor
12
12
  ```
13
13
 
14
- ## What's New in v0.1.16
14
+ ## What's New in v0.1.18
15
15
 
16
- v0.1.16 expands the Govern TUI harness addon coverage while preserving adaptive mode recommendations.
16
+ v0.1.18 makes Marrow key loading more reliable for agents after install.
17
17
 
18
- - `npx @getmarrow/install govern` now shows first-class rows for Codex, Claude Code, Cursor, Gemini CLI, Grok CLI, DeepSeek CLI, Hermes, GLM CLI, Qwen CLI, OpenCode, OpenClaw, MCP-compatible clients, CI scripts, and custom commands.
18
+ - Installer, SDK passive runtime, and MCP hooks now all recognize the same key locations: process env, MCP/agent secret env, `.marrow/env`, `.marrow/env.local`, `.env`, `.env.local`, `~/.marrow/env`, and `~/.marrow/env.local`.
19
+ - `MARROW_API_KEY` remains the canonical variable. `MARROW_KEY` is accepted as an alias for fleet runners and secret managers.
20
+ - `npx @getmarrow/install doctor` now reports whether a key is loaded, where a likely key file exists, and the exact repair command without printing the key.
21
+ - Generated `.marrow/passive-runtime.mjs` loads `.marrow/env` before deciding Marrow is missing, so agents keep logging even when the shell did not export the key.
22
+ - Full keys are never printed in diagnostics. Keep `.marrow/env` out of git and set file permissions to owner-only when possible.
23
+
24
+ ## What's New in v0.1.17
25
+
26
+ v0.1.17 expands the Govern TUI harness addon coverage while preserving adaptive mode recommendations.
27
+
28
+ - `npx @getmarrow/install govern` now shows first-class rows for Codex, Claude Code, Cursor, Cline, Gemini CLI, Grok CLI, DeepSeek CLI, Minimax CLI, Kimi CLI, Hermes, GLM CLI, Qwen CLI, OpenCode, OpenClaw, MCP-compatible clients, CI scripts, and custom commands.
19
29
  - Marrow remains a thin governance layer. It does not replace your model or harness; it wraps the command your agent already runs with pre-action risk gates, proof requirements, and automatic outcome closure.
20
30
  - Detection stays recommendation-first. Marrow uses local config, instruction, CI, and MCP markers to suggest the safest path, then the user or owner accepts, overrides, or saves a policy profile.
31
+ - Cursor is treated as a development workflow surface. Marrow can govern Cursor-backed code review, edits, tests, deploys, and release steps through MCP setup, project rules, or the governed command wrapper.
21
32
  - If a harness is not detected yet, use **Custom command** or `npx @getmarrow/install run -- <your-agent-command>` to govern it immediately.
22
33
 
23
- Business value: teams can add Marrow to the agent stack they already use instead of migrating to a new agent host. Codex, Claude Code, Cursor, Gemini, Grok, DeepSeek, Hermes, GLM, Qwen, OpenClaw, OpenCode, MCP clients, and CI scripts can all be brought under the same governance loop.
34
+ Business value: teams can add Marrow to the agent stack they already use instead of migrating to a new agent host. Codex, Claude Code, Cursor, Cline, Gemini, Grok, DeepSeek, Minimax, Kimi/Moonshot, Hermes, GLM, Qwen, OpenClaw, OpenCode, MCP clients, and CI scripts can all be brought under the same governance loop.
24
35
 
25
36
  ## What's New in v0.1.14
26
37
 
@@ -170,7 +181,7 @@ Marrow should answer with `proceed`, `warn`, `block`, or `owner_approval_require
170
181
  - Codex/agent instruction files such as `AGENTS.md`
171
182
  - Claude Code settings and hooks
172
183
  - Cursor project folders
173
- - Gemini, Grok, DeepSeek, Hermes, GLM, and Qwen project marker files when present
184
+ - Gemini, Grok, DeepSeek, Minimax, Kimi/Moonshot, Hermes, GLM, and Qwen project marker files when present
174
185
  - MCP config files
175
186
  - CI workflow/script markers
176
187
  - Node projects
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getmarrow/install",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
4
4
  "description": "Universal installer for Marrow passive agent setup.",
5
5
  "bin": {
6
6
  "marrow-install": "bin/marrow-install.js"
@@ -581,9 +581,12 @@ function detectHarnesses(cwd = process.cwd()) {
581
581
  { name: 'Gemini CLI', command: 'gemini', detected: existsAny([path.join(cwd, 'GEMINI.md'), path.join(cwd, '.gemini'), path.join(home, '.gemini')]) },
582
582
  { name: 'Grok CLI', command: 'grok', detected: existsAny([path.join(cwd, 'GROK.md'), path.join(cwd, '.grok'), path.join(home, '.grok')]) },
583
583
  { name: 'DeepSeek CLI', command: 'deepseek', detected: existsAny([path.join(cwd, 'DEEPSEEK.md'), path.join(cwd, '.deepseek'), path.join(home, '.deepseek')]) },
584
+ { name: 'Minimax CLI', command: 'minimax', detected: existsAny([path.join(cwd, 'MINIMAX.md'), path.join(cwd, '.minimax'), path.join(home, '.minimax')]) },
585
+ { name: 'Kimi CLI', command: 'kimi', detected: existsAny([path.join(cwd, 'KIMI.md'), path.join(cwd, 'MOONSHOT.md'), path.join(cwd, '.kimi'), path.join(cwd, '.moonshot'), path.join(home, '.kimi'), path.join(home, '.moonshot')]) },
584
586
  { name: 'Hermes', command: 'hermes', detected: existsAny([path.join(cwd, 'HERMES.md'), path.join(cwd, 'hermes.json'), path.join(cwd, '.hermes'), path.join(home, '.hermes')]) },
585
587
  { name: 'GLM CLI', command: 'glm', detected: existsAny([path.join(cwd, 'GLM.md'), path.join(cwd, '.glm'), path.join(home, '.glm')]) },
586
588
  { name: 'Qwen CLI', command: 'qwen', detected: existsAny([path.join(cwd, 'QWEN.md'), path.join(cwd, '.qwen'), path.join(home, '.qwen')]) },
589
+ { name: 'Cline', command: 'cline', detected: existsAny([path.join(cwd, '.cline'), path.join(cwd, 'CLINE.md'), path.join(home, '.cline')]) },
587
590
  { name: 'OpenCode', command: 'opencode', detected: existsAny([path.join(cwd, 'opencode.json'), path.join(cwd, '.opencode'), path.join(home, '.opencode')]) },
588
591
  { name: 'OpenClaw', command: 'openclaw agent', detected: existsAny([path.join(cwd, 'openclaw.json'), path.join(home, '.openclaw')]) },
589
592
  { name: 'MCP-compatible client', command: '<mcp-client>', detected: existsAny([path.join(cwd, '.mcp.json'), path.join(cwd, '.cursor', 'mcp.json'), path.join(cwd, '.claude', 'settings.json')]) },
package/src/installer.js CHANGED
@@ -15,7 +15,7 @@ function parseArgs(argv) {
15
15
  doctor: false,
16
16
  repair: false,
17
17
  mode: 'auto',
18
- apiKey: process.env.MARROW_API_KEY || '',
18
+ apiKey: process.env.MARROW_API_KEY || process.env.MARROW_KEY || '',
19
19
  baseUrl: process.env.MARROW_BASE_URL || DEFAULT_BASE_URL,
20
20
  agentId: process.env.MARROW_FLEET_AGENT_ID || process.env.MARROW_AGENT_ID || '',
21
21
  selfTest: true,
@@ -57,6 +57,12 @@ function parseArgs(argv) {
57
57
  throw new Error('--mode must be one of auto, mcp, sdk, both, md');
58
58
  }
59
59
 
60
+ const resolved = resolveMarrowKeyMaterial(options.cwd);
61
+ if (!options.apiKey && resolved.apiKey) options.apiKey = resolved.apiKey;
62
+ if (!options.agentId && resolved.agentId) options.agentId = resolved.agentId;
63
+ if (options.baseUrl === DEFAULT_BASE_URL && resolved.baseUrl) options.baseUrl = resolved.baseUrl;
64
+ if (!options.keySource && resolved.source) options.keySource = resolved.source;
65
+
60
66
  return options;
61
67
  }
62
68
 
@@ -157,8 +163,7 @@ function findLikelyEnvFiles(detection, env = process.env) {
157
163
  path.join(detection.root, '.marrow', 'env'),
158
164
  path.join(detection.root, '.marrow', 'env.local'),
159
165
  path.join(home, '.marrow', 'env'),
160
- path.join(home, '.openclaw', 'credentials', 'marrow-mcp.env'),
161
- path.join(home, '.openclaw', 'gateway.systemd.env'),
166
+ path.join(home, '.marrow', 'env.local'),
162
167
  ];
163
168
  return candidates.filter((filePath) => {
164
169
  if (!exists(filePath)) return false;
@@ -183,6 +188,36 @@ function readEnvVar(filePath, name) {
183
188
  return match ? stripQuotes(match[1]) : '';
184
189
  }
185
190
 
191
+ function resolveMarrowKeyMaterial(cwd = process.cwd(), env = process.env) {
192
+ if (env.MARROW_API_KEY || env.MARROW_KEY) {
193
+ return {
194
+ apiKey: env.MARROW_API_KEY || env.MARROW_KEY,
195
+ baseUrl: env.MARROW_BASE_URL || DEFAULT_BASE_URL,
196
+ agentId: env.MARROW_FLEET_AGENT_ID || env.MARROW_AGENT_ID || '',
197
+ source: env.MARROW_API_KEY ? 'MARROW_API_KEY' : 'MARROW_KEY',
198
+ };
199
+ }
200
+
201
+ const detection = detectEnvironment(cwd, env);
202
+ for (const filePath of findLikelyEnvFiles(detection, env)) {
203
+ const apiKey = readEnvVar(filePath, 'MARROW_API_KEY') || readEnvVar(filePath, 'MARROW_KEY');
204
+ if (!apiKey) continue;
205
+ return {
206
+ apiKey,
207
+ baseUrl: readEnvVar(filePath, 'MARROW_BASE_URL') || env.MARROW_BASE_URL || DEFAULT_BASE_URL,
208
+ agentId: readEnvVar(filePath, 'MARROW_FLEET_AGENT_ID') || readEnvVar(filePath, 'MARROW_AGENT_ID') || env.MARROW_FLEET_AGENT_ID || env.MARROW_AGENT_ID || '',
209
+ source: filePath,
210
+ };
211
+ }
212
+
213
+ return {
214
+ apiKey: '',
215
+ baseUrl: env.MARROW_BASE_URL || DEFAULT_BASE_URL,
216
+ agentId: env.MARROW_FLEET_AGENT_ID || env.MARROW_AGENT_ID || '',
217
+ source: null,
218
+ };
219
+ }
220
+
186
221
  function readFirstLineSecret(filePath) {
187
222
  if (!exists(filePath)) return '';
188
223
  return safeRead(filePath).split(/\r?\n/).map((line) => line.trim()).find(Boolean) || '';
@@ -297,27 +332,79 @@ Required environment:
297
332
 
298
333
  - \`MARROW_API_KEY\`
299
334
  - Optional: \`MARROW_BASE_URL\`, \`MARROW_FLEET_AGENT_ID\`
335
+
336
+ Key loading:
337
+
338
+ - Prefer your shell, MCP secret store, or agent secret manager.
339
+ - Marrow also auto-detects \`.marrow/env\`, \`.marrow/env.local\`, \`.env\`, \`.env.local\`, and \`~/.marrow/env\`.
340
+ - Run \`npx @getmarrow/install doctor\` any time an agent says Marrow is missing or degraded.
300
341
  ${MARROW_BLOCK_END}`;
301
342
  }
302
343
 
303
344
  function passiveRuntimeSource() {
304
- return `const apiKey = process.env.MARROW_API_KEY;
345
+ return `import fs from 'node:fs';
346
+ import path from 'node:path';
347
+ import os from 'node:os';
348
+
349
+ function stripQuotes(value) {
350
+ const trimmed = String(value || '').trim();
351
+ if ((trimmed.startsWith('"') && trimmed.endsWith('"')) || (trimmed.startsWith("'") && trimmed.endsWith("'"))) return trimmed.slice(1, -1);
352
+ return trimmed;
353
+ }
354
+
355
+ function readEnvFile(filePath) {
356
+ if (!fs.existsSync(filePath)) return {};
357
+ const values = {};
358
+ const allowed = new Set(['MARROW_API_KEY', 'MARROW_KEY', 'MARROW_BASE_URL', 'MARROW_FLEET_AGENT_ID', 'MARROW_AGENT_ID', 'MARROW_SESSION_ID', 'MARROW_ENFORCEMENT_MODE', 'MARROW_PASSIVE_VALUE_REPORT', 'MARROW_VALUE_REPORT_PERIOD', 'MARROW_AGENT_RUNTIME', 'MARROW_WORKFLOW_GATE', 'MARROW_REQUIRE_OUTCOME_CLOSURE']);
359
+ for (const line of fs.readFileSync(filePath, 'utf8').split(/\\r?\\n/)) {
360
+ const match = line.match(/^\\s*(?:export\\s+)?([A-Z_][A-Z0-9_]*)\\s*=\\s*(.*?)\\s*$/);
361
+ if (!match) continue;
362
+ if (!allowed.has(match[1])) continue;
363
+ let value = match[2] || '';
364
+ const hashIndex = value.search(/\\s+#/);
365
+ if (hashIndex >= 0) value = value.slice(0, hashIndex);
366
+ values[match[1]] = stripQuotes(value);
367
+ }
368
+ return values;
369
+ }
370
+
371
+ function resolveMarrowEnv() {
372
+ if (process.env.MARROW_API_KEY || process.env.MARROW_KEY) return process.env;
373
+ const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
374
+ const files = [];
375
+ let dir = process.cwd();
376
+ for (let depth = 0; depth < 8; depth += 1) {
377
+ files.push(path.join(dir, '.marrow', 'env'), path.join(dir, '.marrow', 'env.local'), path.join(dir, '.env'), path.join(dir, '.env.local'));
378
+ const parent = path.dirname(dir);
379
+ if (parent === dir) break;
380
+ dir = parent;
381
+ }
382
+ files.push(path.join(home, '.marrow', 'env'), path.join(home, '.marrow', 'env.local'));
383
+ for (const filePath of [...new Set(files)]) {
384
+ const values = readEnvFile(filePath);
385
+ if (values.MARROW_API_KEY || values.MARROW_KEY) return { ...process.env, ...values };
386
+ }
387
+ return process.env;
388
+ }
389
+
390
+ const marrowEnv = resolveMarrowEnv();
391
+ const apiKey = marrowEnv.MARROW_API_KEY || marrowEnv.MARROW_KEY;
305
392
  if (apiKey && !globalThis.__MARROW_PASSIVE_RUNTIME__) {
306
393
  try {
307
394
  const { MarrowClient } = await import('@getmarrow/sdk');
308
395
  const marrow = new MarrowClient(apiKey, {
309
- baseUrl: process.env.MARROW_BASE_URL,
310
- agentId: process.env.MARROW_FLEET_AGENT_ID || process.env.MARROW_AGENT_ID,
311
- sessionId: process.env.MARROW_SESSION_ID,
312
- mode: process.env.MARROW_ENFORCEMENT_MODE || 'auto',
396
+ baseUrl: marrowEnv.MARROW_BASE_URL,
397
+ agentId: marrowEnv.MARROW_FLEET_AGENT_ID || marrowEnv.MARROW_AGENT_ID,
398
+ sessionId: marrowEnv.MARROW_SESSION_ID,
399
+ mode: marrowEnv.MARROW_ENFORCEMENT_MODE || 'auto',
313
400
  });
314
401
 
315
402
  const runtime = marrow.createPassiveRuntime({
316
- includeValueReport: process.env.MARROW_PASSIVE_VALUE_REPORT !== 'false',
317
- valueReportPeriod: process.env.MARROW_VALUE_REPORT_PERIOD || '7d',
318
- useAgentRuntime: process.env.MARROW_AGENT_RUNTIME !== 'false',
319
- useWorkflowGate: process.env.MARROW_WORKFLOW_GATE !== 'false',
320
- requireOutcomeClosure: process.env.MARROW_REQUIRE_OUTCOME_CLOSURE !== 'false',
403
+ includeValueReport: marrowEnv.MARROW_PASSIVE_VALUE_REPORT !== 'false',
404
+ valueReportPeriod: marrowEnv.MARROW_VALUE_REPORT_PERIOD || '7d',
405
+ useAgentRuntime: marrowEnv.MARROW_AGENT_RUNTIME !== 'false',
406
+ useWorkflowGate: marrowEnv.MARROW_WORKFLOW_GATE !== 'false',
407
+ requireOutcomeClosure: marrowEnv.MARROW_REQUIRE_OUTCOME_CLOSURE !== 'false',
321
408
  });
322
409
 
323
410
  runtime.install();
@@ -325,6 +412,8 @@ if (apiKey && !globalThis.__MARROW_PASSIVE_RUNTIME__) {
325
412
  } catch {
326
413
  console.warn('[Marrow] passive runtime skipped: install @getmarrow/sdk or verify SDK initialization. Run npm install @getmarrow/sdk, then rerun npx @getmarrow/install --repair.');
327
414
  }
415
+ } else if (!apiKey) {
416
+ console.warn('[Marrow] passive runtime skipped: MARROW_API_KEY missing. Put it in .marrow/env or run npx @getmarrow/install doctor for the exact fix.');
328
417
  }
329
418
  `;
330
419
  }
@@ -908,8 +997,8 @@ async function install(options) {
908
997
  missingHooks: changes.filter((change) => change.changed).map((change) => change.label),
909
998
  recommendedFix: configDiagnostics.npm_token.recommended_fix || selfTest.recommended_fix || (!options.apiKey
910
999
  ? envHints.length
911
- ? `MARROW_API_KEY was found in a likely env file at ${envHints[0]}. Load that key from trusted secret storage, export only MARROW_API_KEY, then run npx @getmarrow/install --repair.`
912
- : 'Set MARROW_API_KEY, then run npx @getmarrow/install --repair.'
1000
+ ? `MARROW_API_KEY was found in ${envHints[0]} and can now be auto-loaded by Marrow SDK/MCP runtimes. Run npx @getmarrow/install --repair to refresh hooks and self-test.`
1001
+ : 'Set MARROW_API_KEY or MARROW_KEY in your shell, MCP secret store, .marrow/env, or ~/.marrow/env, then run npx @getmarrow/install --repair.'
913
1002
  : null),
914
1003
  },
915
1004
  remediation,
@@ -946,6 +1035,7 @@ module.exports = {
946
1035
  runSelfTest,
947
1036
  runCli,
948
1037
  passiveRuntimeSource,
1038
+ resolveMarrowKeyMaterial,
949
1039
  inspectNpmTokenConfig,
950
1040
  inspectSdkDependency,
951
1041
  buildInstallValueMoment,