@getmarrow/install 0.1.17 → 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.
Files changed (3) hide show
  1. package/README.md +10 -0
  2. package/package.json +1 -1
  3. package/src/installer.js +105 -15
package/README.md CHANGED
@@ -11,6 +11,16 @@ npx @getmarrow/install --repair
11
11
  npx @getmarrow/install doctor
12
12
  ```
13
13
 
14
+ ## What's New in v0.1.18
15
+
16
+ v0.1.18 makes Marrow key loading more reliable for agents after install.
17
+
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
+
14
24
  ## What's New in v0.1.17
15
25
 
16
26
  v0.1.17 expands the Govern TUI harness addon coverage while preserving adaptive mode recommendations.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getmarrow/install",
3
- "version": "0.1.17",
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"
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,