@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 +21 -0
- package/package.json +1 -1
- package/src/installer.js +115 -4
- package/bin/marrow-install.js +0 -9
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
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
|
|
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)
|
|
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.
|
|
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.']
|
package/bin/marrow-install.js
DELETED
|
@@ -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
|
-
});
|