@getmarrow/install 0.1.1 → 0.1.4

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,14 +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
10
11
  npx @getmarrow/install doctor
11
12
  ```
12
13
 
13
- ## What's New in v0.1.1
14
+ ## What's New in v0.1.4
14
15
 
15
- - `doctor` / `--doctor` checks whether Marrow is active without writing files.
16
- - Generated SDK passive runtime enables workflow gates and value summaries by default.
17
- - Doctor output reports missing env, missing hooks/config, self-test state, and recommended fix.
16
+ - Installer self-test now finishes with a first-value summary: Marrow active state, captured surfaces, first useful lesson, value proof, and exact next action.
17
+ - Generated SDK passive runtime enables one-call agent runtime checks by default with `MARROW_AGENT_RUNTIME=true`.
18
+ - Self-test now verifies the runtime endpoint in addition to creating a harmless decision and committing its outcome.
19
+ - `--repair` keeps dry-run behavior safe when `--dry-run` is present and reports whether one-call runtime was verified.
20
+ - Doctor/repair output still gives exact safe setup and repair commands without printing credential values.
18
21
 
19
22
  ## What It Detects
20
23
 
@@ -48,7 +51,7 @@ npx @getmarrow/install --md --dry-run
48
51
 
49
52
  ## Self-Test
50
53
 
51
- When `MARROW_API_KEY` is present, the installer creates a harmless test decision, commits the outcome, and reads `/v1/agent/status`.
54
+ When `MARROW_API_KEY` is present, the installer creates a harmless test decision, commits the outcome, reads `/v1/agent/status`, calls the one-call runtime, and prints the first useful Marrow signal.
52
55
 
53
56
  ```bash
54
57
  MARROW_API_KEY=mrw_live_xxx npx @getmarrow/install --yes
@@ -66,6 +69,12 @@ Doctor check:
66
69
  MARROW_API_KEY=mrw_live_xxx npx @getmarrow/install doctor
67
70
  ```
68
71
 
72
+ Repair missing hooks/config:
73
+
74
+ ```bash
75
+ MARROW_API_KEY=mrw_live_xxx npx @getmarrow/install --repair
76
+ ```
77
+
69
78
  ## Trust Model
70
79
 
71
80
  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.1",
3
+ "version": "0.1.4",
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
@@ -12,6 +12,7 @@ function parseArgs(argv) {
12
12
  yes: false,
13
13
  dryRun: false,
14
14
  doctor: false,
15
+ repair: false,
15
16
  mode: 'auto',
16
17
  apiKey: process.env.MARROW_API_KEY || '',
17
18
  baseUrl: process.env.MARROW_BASE_URL || DEFAULT_BASE_URL,
@@ -23,6 +24,10 @@ function parseArgs(argv) {
23
24
  for (let i = 0; i < argv.length; i += 1) {
24
25
  const arg = argv[i];
25
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
+ }
26
31
  else if (arg === '--dry-run') options.dryRun = true;
27
32
  else if (arg === '--doctor' || arg === 'doctor' || arg === 'check') options.doctor = true;
28
33
  else if (arg === '--json') options.json = true;
@@ -58,6 +63,7 @@ function usage() {
58
63
  return `Usage:
59
64
  npx @getmarrow/install --dry-run
60
65
  npx @getmarrow/install --yes
66
+ npx @getmarrow/install --repair
61
67
  npx @getmarrow/install doctor
62
68
  npx @getmarrow/install --mcp --yes
63
69
  npx @getmarrow/install --sdk --yes
@@ -65,6 +71,7 @@ function usage() {
65
71
  Options:
66
72
  --dry-run Print planned changes without writing
67
73
  --doctor Check install health without writing
74
+ --repair Write missing hooks/config, then run self-test and status check
68
75
  --yes, -y Write detected config files
69
76
  --mode <mode> auto, mcp, sdk, both, or md
70
77
  --key <key> Marrow API key for self-test. Prefer MARROW_API_KEY because CLI args can appear in process listings.
@@ -135,6 +142,24 @@ function detectEnvironment(cwd = process.cwd(), env = process.env) {
135
142
  };
136
143
  }
137
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
+
138
163
  function passiveInstructions() {
139
164
  return `${MARROW_BLOCK_START}
140
165
  ## Marrow Passive Agent Memory
@@ -169,6 +194,7 @@ if (apiKey && !globalThis.__MARROW_PASSIVE_RUNTIME__) {
169
194
  const runtime = marrow.createPassiveRuntime({
170
195
  includeValueReport: process.env.MARROW_PASSIVE_VALUE_REPORT !== 'false',
171
196
  valueReportPeriod: process.env.MARROW_VALUE_REPORT_PERIOD || '7d',
197
+ useAgentRuntime: process.env.MARROW_AGENT_RUNTIME !== 'false',
172
198
  useWorkflowGate: process.env.MARROW_WORKFLOW_GATE !== 'false',
173
199
  });
174
200
 
@@ -185,6 +211,7 @@ MARROW_FLEET_AGENT_ID=agent-or-fleet-id
185
211
  MARROW_ENFORCEMENT_MODE=auto
186
212
  MARROW_PASSIVE_BRIEF=auto
187
213
  MARROW_PASSIVE_VALUE_REPORT=true
214
+ MARROW_AGENT_RUNTIME=true
188
215
  MARROW_WORKFLOW_GATE=true
189
216
  `;
190
217
  }
@@ -370,7 +397,13 @@ async function requestJson(url, options) {
370
397
 
371
398
  async function runSelfTest(options) {
372
399
  if (!options.selfTest) return { skipped: true, reason: 'disabled' };
373
- 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
+ }
374
407
 
375
408
  const headers = {
376
409
  authorization: `Bearer ${options.apiKey}`,
@@ -403,6 +436,29 @@ async function runSelfTest(options) {
403
436
  });
404
437
 
405
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
+ }));
456
+ const performance = await requestJson(`${baseUrl}/v1/analytics/agent-performance?period=7`, { headers })
457
+ .catch((error) => ({
458
+ ok: false,
459
+ error: error instanceof Error ? error.message : String(error),
460
+ }));
461
+ const firstValueSignal = buildFirstValueSignal(status, runtime, performance);
406
462
  return {
407
463
  skipped: false,
408
464
  decision_id: decisionId,
@@ -410,6 +466,55 @@ async function runSelfTest(options) {
410
466
  health: status.health || null,
411
467
  last_event_at: status.last_event_at || null,
412
468
  recommended_fix: status.recommended_fix || null,
469
+ next_action: status.next_action || null,
470
+ auto_outcome_closure: status.auto_outcome_closure || null,
471
+ runtime_active: Boolean(runtime && runtime.ok !== false),
472
+ runtime_exact_next_action: runtime.exact_next_action || null,
473
+ runtime_before_you_act: runtime.before_you_act || null,
474
+ first_value_signal: firstValueSignal,
475
+ performance_proof: performance && performance.ok !== false ? {
476
+ avoided_mistakes: performance.avoided_mistakes ?? performance.avoided_repeated_mistakes ?? 0,
477
+ reused_winning_decisions: performance.reused_winning_decisions ?? 0,
478
+ prevented_bad_actions: performance.prevented_bad_actions ?? 0,
479
+ estimated_tokens_saved: performance.token_time_saved_estimate?.estimated_tokens_saved ?? 0,
480
+ estimated_minutes_saved: performance.token_time_saved_estimate?.estimated_minutes_saved ?? 0,
481
+ reliability_score: performance.agent_reliability_score ?? null,
482
+ } : null,
483
+ };
484
+ }
485
+
486
+ function buildFirstValueSignal(status, runtime, performance) {
487
+ const capture = status.capture_coverage || {};
488
+ const closure = status.auto_outcome_closure || {};
489
+ const captured = [];
490
+ if (status.enabled || capture.decisions) captured.push('decisions');
491
+ if (capture.tools === 'detected') captured.push('tools');
492
+ if (capture.commands === 'detected') captured.push('commands');
493
+ if (capture.deploys === 'detected') captured.push('deploys');
494
+ if (capture.publishes === 'detected') captured.push('publishes');
495
+ if (closure.state) captured.push(`outcomes:${closure.state}`);
496
+
497
+ const proof = performance && performance.ok !== false ? performance : {};
498
+ const proofBits = [];
499
+ if (Number(proof.avoided_mistakes || proof.avoided_repeated_mistakes || 0) > 0) proofBits.push(`${proof.avoided_mistakes || proof.avoided_repeated_mistakes} avoided mistake(s)`);
500
+ if (Number(proof.reused_winning_decisions || 0) > 0) proofBits.push(`${proof.reused_winning_decisions} reused winning decision(s)`);
501
+ if (Number(proof.prevented_bad_actions || 0) > 0) proofBits.push(`${proof.prevented_bad_actions} prevented risky action(s)`);
502
+ const tokens = proof.token_time_saved_estimate?.estimated_tokens_saved || 0;
503
+ if (tokens > 0) proofBits.push(`~${tokens} tokens saved`);
504
+
505
+ const firstLesson = runtime.before_you_act
506
+ || runtime.before_you_act_injection?.message
507
+ || runtime.exact_next_action
508
+ || status.recommended_fix
509
+ || 'Marrow will surface prior lessons before risky or repeated work.';
510
+
511
+ return {
512
+ active: Boolean(status.enabled ?? status.ok),
513
+ headline: `Marrow active: ${captured.length ? captured.join(', ') : 'decisions'} captured.`,
514
+ captured,
515
+ first_lesson: firstLesson,
516
+ value_proof: proofBits,
517
+ next_action: runtime.exact_next_action || status.next_action || 'Keep working; Marrow will capture outcomes and reuse lessons automatically.',
413
518
  };
414
519
  }
415
520
 
@@ -433,16 +538,37 @@ function printReport(report) {
433
538
  process.stdout.write('\nSelf-test:\n');
434
539
  if (report.selfTest.skipped) {
435
540
  process.stdout.write(`- skipped: ${report.selfTest.reason}\n`);
541
+ if (report.selfTest.exact_fix) process.stdout.write(`- exact fix: ${report.selfTest.exact_fix}\n`);
436
542
  } else {
437
543
  process.stdout.write(`- active: ${report.selfTest.active ? 'yes' : 'no'}\n`);
438
544
  process.stdout.write(`- decision_id: ${report.selfTest.decision_id}\n`);
439
545
  process.stdout.write(`- health: ${report.selfTest.health || 'unknown'}\n`);
546
+ process.stdout.write(`- one-call runtime: ${report.selfTest.runtime_active ? 'active' : 'not verified'}\n`);
547
+ if (report.selfTest.next_action) process.stdout.write(`- next action: ${report.selfTest.next_action}\n`);
548
+ if (report.selfTest.first_value_signal) {
549
+ process.stdout.write('\nFirst value:\n');
550
+ process.stdout.write(`- ${report.selfTest.first_value_signal.headline}\n`);
551
+ process.stdout.write(`- First useful lesson: ${report.selfTest.first_value_signal.first_lesson}\n`);
552
+ if (report.selfTest.first_value_signal.value_proof.length) {
553
+ process.stdout.write(`- Proof: ${report.selfTest.first_value_signal.value_proof.join('; ')}\n`);
554
+ }
555
+ process.stdout.write(`- Next: ${report.selfTest.first_value_signal.next_action}\n`);
556
+ }
557
+ }
558
+
559
+ if (report.remediation) {
560
+ process.stdout.write('\nRemediation:\n');
561
+ process.stdout.write(`- attempted: ${report.remediation.attempted ? 'yes' : 'no'}\n`);
562
+ process.stdout.write(`- fixed config: ${report.remediation.fixedConfig ? 'yes' : 'no'}\n`);
563
+ process.stdout.write(`- self-test passed: ${report.remediation.selfTestPassed ? 'yes' : 'no'}\n`);
564
+ if (report.remediation.message) process.stdout.write(`- result: ${report.remediation.message}\n`);
440
565
  }
441
566
 
442
567
  if (report.writeMode === 'doctor') {
443
568
  process.stdout.write('\nDoctor:\n');
444
569
  process.stdout.write(`- Marrow active: ${report.doctor.active ? 'yes' : 'no'}\n`);
445
570
  process.stdout.write(`- missing env: ${report.doctor.missingEnv.length ? report.doctor.missingEnv.join(', ') : 'none'}\n`);
571
+ if (report.doctor.envHints.length) process.stdout.write(`- possible env files: ${report.doctor.envHints.join(', ')}\n`);
446
572
  process.stdout.write(`- missing hooks/config: ${report.doctor.missingHooks.length ? report.doctor.missingHooks.join('; ') : 'none'}\n`);
447
573
  if (report.doctor.recommendedFix) process.stdout.write(`- recommended fix: ${report.doctor.recommendedFix}\n`);
448
574
  }
@@ -462,13 +588,30 @@ function printReport(report) {
462
588
  async function install(options) {
463
589
  const detection = detectEnvironment(options.cwd);
464
590
  const plan = buildPlan(detection, options);
465
- const writeMode = options.doctor ? 'doctor' : options.yes && !options.dryRun ? 'write' : 'dry-run';
591
+ const writeMode = options.doctor ? 'doctor' : options.dryRun ? 'dry-run' : options.repair ? 'repair' : options.yes ? 'write' : 'dry-run';
466
592
  const changes = applyPlan(plan, options);
593
+ const envHints = options.apiKey ? [] : findLikelyEnvFiles(detection);
467
594
  const selfTest = await runSelfTest(options).catch((error) => ({
468
595
  skipped: false,
469
596
  active: false,
470
597
  error: error instanceof Error ? error.message : String(error),
471
598
  }));
599
+ const changedConfig = changes.some((change) => change.changed);
600
+ const selfTestPassed = Boolean(!selfTest.skipped && selfTest.active && !selfTest.error);
601
+ const remediation = options.repair
602
+ ? {
603
+ attempted: true,
604
+ fixedConfig: changedConfig,
605
+ selfTestPassed,
606
+ message: selfTestPassed
607
+ ? selfTest.health === 'healthy'
608
+ ? 'I fixed Marrow passive config, one-call runtime is active, and self-test passed.'
609
+ : `I fixed Marrow passive config and self-test passed; status is ${selfTest.health || 'unknown'}${selfTest.next_action ? `. Next action: ${selfTest.next_action}` : ''}.`
610
+ : selfTest.skipped
611
+ ? `Config repair ran, but self-test skipped: ${selfTest.reason}.`
612
+ : `Config repair ran, but self-test failed: ${selfTest.error || 'unknown error'}.`,
613
+ }
614
+ : null;
472
615
 
473
616
  return {
474
617
  root: detection.root,
@@ -487,9 +630,15 @@ async function install(options) {
487
630
  doctor: {
488
631
  active: Boolean(!selfTest.skipped && selfTest.active),
489
632
  missingEnv: options.apiKey ? [] : ['MARROW_API_KEY'],
633
+ envHints,
490
634
  missingHooks: changes.filter((change) => change.changed).map((change) => change.label),
491
- recommendedFix: selfTest.recommended_fix || (!options.apiKey ? 'Set MARROW_API_KEY, then run npx @getmarrow/install doctor.' : null),
635
+ recommendedFix: selfTest.recommended_fix || (!options.apiKey
636
+ ? envHints.length
637
+ ? `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.`
638
+ : 'Set MARROW_API_KEY, then run npx @getmarrow/install --repair.'
639
+ : null),
492
640
  },
641
+ remediation,
493
642
  selfTest,
494
643
  warnings: options.keyFromArg
495
644
  ? ['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.']