@getmarrow/install 0.1.0 → 0.1.3

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
@@ -7,8 +7,17 @@ Use it when you want Marrow to detect the local agent/runtime environment and wi
7
7
  ```bash
8
8
  npx @getmarrow/install --dry-run
9
9
  npx @getmarrow/install --yes
10
+ npx @getmarrow/install --repair
11
+ npx @getmarrow/install doctor
10
12
  ```
11
13
 
14
+ ## What's New in v0.1.3
15
+
16
+ - Generated SDK passive runtime enables one-call agent runtime checks by default with `MARROW_AGENT_RUNTIME=true`.
17
+ - Self-test now verifies the runtime endpoint in addition to creating a harmless decision and committing its outcome.
18
+ - `--repair` keeps dry-run behavior safe when `--dry-run` is present and reports whether one-call runtime was verified.
19
+ - Doctor/repair output still gives exact safe setup and repair commands without printing credential values.
20
+
12
21
  ## What It Detects
13
22
 
14
23
  - OpenClaw-style workspaces
@@ -53,6 +62,18 @@ Skip self-test:
53
62
  npx @getmarrow/install --yes --no-self-test
54
63
  ```
55
64
 
65
+ Doctor check:
66
+
67
+ ```bash
68
+ MARROW_API_KEY=mrw_live_xxx npx @getmarrow/install doctor
69
+ ```
70
+
71
+ Repair missing hooks/config:
72
+
73
+ ```bash
74
+ MARROW_API_KEY=mrw_live_xxx npx @getmarrow/install --repair
75
+ ```
76
+
56
77
  ## Trust Model
57
78
 
58
79
  This package is intended to be open source and auditable. It prints every file it will touch, requires `--yes` to write, does not store API keys in project files, and supports MCP-only, SDK-only, both, and markdown-only setups.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getmarrow/install",
3
- "version": "0.1.0",
3
+ "version": "0.1.3",
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
@@ -11,6 +11,8 @@ function parseArgs(argv) {
11
11
  cwd: process.cwd(),
12
12
  yes: false,
13
13
  dryRun: false,
14
+ doctor: false,
15
+ repair: false,
14
16
  mode: 'auto',
15
17
  apiKey: process.env.MARROW_API_KEY || '',
16
18
  baseUrl: process.env.MARROW_BASE_URL || DEFAULT_BASE_URL,
@@ -22,7 +24,12 @@ function parseArgs(argv) {
22
24
  for (let i = 0; i < argv.length; i += 1) {
23
25
  const arg = argv[i];
24
26
  if (arg === '--yes' || arg === '-y') options.yes = true;
27
+ else if (arg === '--repair' || arg === 'repair') {
28
+ options.repair = true;
29
+ options.yes = true;
30
+ }
25
31
  else if (arg === '--dry-run') options.dryRun = true;
32
+ else if (arg === '--doctor' || arg === 'doctor' || arg === 'check') options.doctor = true;
26
33
  else if (arg === '--json') options.json = true;
27
34
  else if (arg === '--no-self-test') options.selfTest = false;
28
35
  else if (arg === '--self-test') options.selfTest = true;
@@ -56,11 +63,15 @@ function usage() {
56
63
  return `Usage:
57
64
  npx @getmarrow/install --dry-run
58
65
  npx @getmarrow/install --yes
66
+ npx @getmarrow/install --repair
67
+ npx @getmarrow/install doctor
59
68
  npx @getmarrow/install --mcp --yes
60
69
  npx @getmarrow/install --sdk --yes
61
70
 
62
71
  Options:
63
72
  --dry-run Print planned changes without writing
73
+ --doctor Check install health without writing
74
+ --repair Write missing hooks/config, then run self-test and status check
64
75
  --yes, -y Write detected config files
65
76
  --mode <mode> auto, mcp, sdk, both, or md
66
77
  --key <key> Marrow API key for self-test. Prefer MARROW_API_KEY because CLI args can appear in process listings.
@@ -131,6 +142,24 @@ function detectEnvironment(cwd = process.cwd(), env = process.env) {
131
142
  };
132
143
  }
133
144
 
145
+ function findLikelyEnvFiles(detection, env = process.env) {
146
+ const home = env.HOME || env.USERPROFILE || os.homedir();
147
+ const candidates = [
148
+ path.join(detection.root, '.env'),
149
+ path.join(detection.root, '.env.local'),
150
+ path.join(detection.root, '.marrow', 'env'),
151
+ path.join(detection.root, '.marrow', 'env.local'),
152
+ path.join(home, '.marrow', 'env'),
153
+ path.join(home, '.openclaw', 'credentials', 'marrow-mcp.env'),
154
+ path.join(home, '.openclaw', 'gateway.systemd.env'),
155
+ ];
156
+ return candidates.filter((filePath) => {
157
+ if (!exists(filePath)) return false;
158
+ const raw = safeRead(filePath);
159
+ return /\bMARROW_API_KEY\s*=/.test(raw) || /\bMARROW_KEY(_[A-Z0-9]+)?\s*=/.test(raw);
160
+ });
161
+ }
162
+
134
163
  function passiveInstructions() {
135
164
  return `${MARROW_BLOCK_START}
136
165
  ## Marrow Passive Agent Memory
@@ -163,8 +192,10 @@ if (apiKey && !globalThis.__MARROW_PASSIVE_RUNTIME__) {
163
192
  });
164
193
 
165
194
  const runtime = marrow.createPassiveRuntime({
166
- includeValueReport: process.env.MARROW_PASSIVE_VALUE_REPORT === 'true',
195
+ includeValueReport: process.env.MARROW_PASSIVE_VALUE_REPORT !== 'false',
167
196
  valueReportPeriod: process.env.MARROW_VALUE_REPORT_PERIOD || '7d',
197
+ useAgentRuntime: process.env.MARROW_AGENT_RUNTIME !== 'false',
198
+ useWorkflowGate: process.env.MARROW_WORKFLOW_GATE !== 'false',
168
199
  });
169
200
 
170
201
  runtime.install();
@@ -179,6 +210,9 @@ MARROW_BASE_URL=${DEFAULT_BASE_URL}
179
210
  MARROW_FLEET_AGENT_ID=agent-or-fleet-id
180
211
  MARROW_ENFORCEMENT_MODE=auto
181
212
  MARROW_PASSIVE_BRIEF=auto
213
+ MARROW_PASSIVE_VALUE_REPORT=true
214
+ MARROW_AGENT_RUNTIME=true
215
+ MARROW_WORKFLOW_GATE=true
182
216
  `;
183
217
  }
184
218
 
@@ -337,7 +371,7 @@ function applyPlan(plan, options) {
337
371
 
338
372
  const changed = before !== after;
339
373
  changes.push({ path: write.path, label: write.label, changed });
340
- if (changed && options.yes && !options.dryRun) {
374
+ if (changed && options.yes && !options.dryRun && !options.doctor) {
341
375
  fs.mkdirSync(path.dirname(write.path), { recursive: true });
342
376
  fs.writeFileSync(write.path, after);
343
377
  }
@@ -363,7 +397,13 @@ async function requestJson(url, options) {
363
397
 
364
398
  async function runSelfTest(options) {
365
399
  if (!options.selfTest) return { skipped: true, reason: 'disabled' };
366
- if (!options.apiKey) return { skipped: true, reason: 'missing MARROW_API_KEY' };
400
+ if (!options.apiKey) {
401
+ return {
402
+ skipped: true,
403
+ reason: 'missing MARROW_API_KEY',
404
+ exact_fix: 'export MARROW_API_KEY=mrw_live_... && npx @getmarrow/install --repair',
405
+ };
406
+ }
367
407
 
368
408
  const headers = {
369
409
  authorization: `Bearer ${options.apiKey}`,
@@ -396,6 +436,23 @@ async function runSelfTest(options) {
396
436
  });
397
437
 
398
438
  const status = await requestJson(`${baseUrl}/v1/agent/status`, { headers });
439
+ const runtime = await requestJson(`${baseUrl}/v1/agent/runtime`, {
440
+ method: 'POST',
441
+ headers,
442
+ body: JSON.stringify({
443
+ action: 'Marrow passive install self-test: verify one-call agent runtime and outcome closure',
444
+ type: 'process',
445
+ role: 'general',
446
+ surfaces: ['workspace'],
447
+ proof: {
448
+ checks: ['installer self-test'],
449
+ outcome: 'self-test outcome committed',
450
+ },
451
+ }),
452
+ }).catch((error) => ({
453
+ ok: false,
454
+ error: error instanceof Error ? error.message : String(error),
455
+ }));
399
456
  return {
400
457
  skipped: false,
401
458
  decision_id: decisionId,
@@ -403,6 +460,11 @@ async function runSelfTest(options) {
403
460
  health: status.health || null,
404
461
  last_event_at: status.last_event_at || null,
405
462
  recommended_fix: status.recommended_fix || null,
463
+ next_action: status.next_action || null,
464
+ auto_outcome_closure: status.auto_outcome_closure || null,
465
+ runtime_active: Boolean(runtime && runtime.ok !== false),
466
+ runtime_exact_next_action: runtime.exact_next_action || null,
467
+ runtime_before_you_act: runtime.before_you_act || null,
406
468
  };
407
469
  }
408
470
 
@@ -426,10 +488,30 @@ function printReport(report) {
426
488
  process.stdout.write('\nSelf-test:\n');
427
489
  if (report.selfTest.skipped) {
428
490
  process.stdout.write(`- skipped: ${report.selfTest.reason}\n`);
491
+ if (report.selfTest.exact_fix) process.stdout.write(`- exact fix: ${report.selfTest.exact_fix}\n`);
429
492
  } else {
430
493
  process.stdout.write(`- active: ${report.selfTest.active ? 'yes' : 'no'}\n`);
431
494
  process.stdout.write(`- decision_id: ${report.selfTest.decision_id}\n`);
432
495
  process.stdout.write(`- health: ${report.selfTest.health || 'unknown'}\n`);
496
+ process.stdout.write(`- one-call runtime: ${report.selfTest.runtime_active ? 'active' : 'not verified'}\n`);
497
+ if (report.selfTest.next_action) process.stdout.write(`- next action: ${report.selfTest.next_action}\n`);
498
+ }
499
+
500
+ if (report.remediation) {
501
+ process.stdout.write('\nRemediation:\n');
502
+ process.stdout.write(`- attempted: ${report.remediation.attempted ? 'yes' : 'no'}\n`);
503
+ process.stdout.write(`- fixed config: ${report.remediation.fixedConfig ? 'yes' : 'no'}\n`);
504
+ process.stdout.write(`- self-test passed: ${report.remediation.selfTestPassed ? 'yes' : 'no'}\n`);
505
+ if (report.remediation.message) process.stdout.write(`- result: ${report.remediation.message}\n`);
506
+ }
507
+
508
+ if (report.writeMode === 'doctor') {
509
+ process.stdout.write('\nDoctor:\n');
510
+ process.stdout.write(`- Marrow active: ${report.doctor.active ? 'yes' : 'no'}\n`);
511
+ process.stdout.write(`- missing env: ${report.doctor.missingEnv.length ? report.doctor.missingEnv.join(', ') : 'none'}\n`);
512
+ if (report.doctor.envHints.length) process.stdout.write(`- possible env files: ${report.doctor.envHints.join(', ')}\n`);
513
+ process.stdout.write(`- missing hooks/config: ${report.doctor.missingHooks.length ? report.doctor.missingHooks.join('; ') : 'none'}\n`);
514
+ if (report.doctor.recommendedFix) process.stdout.write(`- recommended fix: ${report.doctor.recommendedFix}\n`);
433
515
  }
434
516
 
435
517
  if (report.writeMode === 'dry-run') {
@@ -447,13 +529,30 @@ function printReport(report) {
447
529
  async function install(options) {
448
530
  const detection = detectEnvironment(options.cwd);
449
531
  const plan = buildPlan(detection, options);
450
- const writeMode = options.yes && !options.dryRun ? 'write' : 'dry-run';
532
+ const writeMode = options.doctor ? 'doctor' : options.dryRun ? 'dry-run' : options.repair ? 'repair' : options.yes ? 'write' : 'dry-run';
451
533
  const changes = applyPlan(plan, options);
534
+ const envHints = options.apiKey ? [] : findLikelyEnvFiles(detection);
452
535
  const selfTest = await runSelfTest(options).catch((error) => ({
453
536
  skipped: false,
454
537
  active: false,
455
538
  error: error instanceof Error ? error.message : String(error),
456
539
  }));
540
+ const changedConfig = changes.some((change) => change.changed);
541
+ const selfTestPassed = Boolean(!selfTest.skipped && selfTest.active && !selfTest.error);
542
+ const remediation = options.repair
543
+ ? {
544
+ attempted: true,
545
+ fixedConfig: changedConfig,
546
+ selfTestPassed,
547
+ message: selfTestPassed
548
+ ? selfTest.health === 'healthy'
549
+ ? 'I fixed Marrow passive config, one-call runtime is active, and self-test passed.'
550
+ : `I fixed Marrow passive config and self-test passed; status is ${selfTest.health || 'unknown'}${selfTest.next_action ? `. Next action: ${selfTest.next_action}` : ''}.`
551
+ : selfTest.skipped
552
+ ? `Config repair ran, but self-test skipped: ${selfTest.reason}.`
553
+ : `Config repair ran, but self-test failed: ${selfTest.error || 'unknown error'}.`,
554
+ }
555
+ : null;
457
556
 
458
557
  return {
459
558
  root: detection.root,
@@ -469,6 +568,18 @@ async function install(options) {
469
568
  mcpConfig: detection.mcpConfig,
470
569
  },
471
570
  changes,
571
+ doctor: {
572
+ active: Boolean(!selfTest.skipped && selfTest.active),
573
+ missingEnv: options.apiKey ? [] : ['MARROW_API_KEY'],
574
+ envHints,
575
+ missingHooks: changes.filter((change) => change.changed).map((change) => change.label),
576
+ recommendedFix: selfTest.recommended_fix || (!options.apiKey
577
+ ? envHints.length
578
+ ? `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.`
579
+ : 'Set MARROW_API_KEY, then run npx @getmarrow/install --repair.'
580
+ : null),
581
+ },
582
+ remediation,
472
583
  selfTest,
473
584
  warnings: options.keyFromArg
474
585
  ? ['Avoid --key in shared shells because command-line arguments can be visible in process listings. Prefer MARROW_API_KEY in your environment or secret manager.']
@@ -1,9 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- const { runCli } = require('../src/installer');
4
-
5
- runCli(process.argv.slice(2)).catch((error) => {
6
- const message = error instanceof Error ? error.message : String(error);
7
- process.stderr.write(`marrow-install failed: ${message}\n`);
8
- process.exit(1);
9
- });