@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 +14 -5
- package/package.json +1 -1
- package/src/installer.js +152 -3
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.
|
|
14
|
+
## What's New in v0.1.4
|
|
14
15
|
|
|
15
|
-
-
|
|
16
|
-
- Generated SDK passive runtime enables
|
|
17
|
-
-
|
|
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,
|
|
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
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)
|
|
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.
|
|
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
|
|
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.']
|