@getmarrow/install 0.1.5 → 0.1.7
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 +22 -6
- package/package.json +1 -1
- package/src/installer.js +143 -2
package/README.md
CHANGED
|
@@ -11,13 +11,29 @@ npx @getmarrow/install --repair
|
|
|
11
11
|
npx @getmarrow/install doctor
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
## What's New in v0.1.
|
|
14
|
+
## What's New in v0.1.6
|
|
15
15
|
|
|
16
|
-
- Installer
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
- Doctor/repair output
|
|
16
|
+
- Installer doctor/repair now detects npm token/config path mismatches like `~/.openclaw/.env` versus `~/.npmrc` without printing token values.
|
|
17
|
+
- `--repair` can sync the active npm token into `~/.npmrc` when a mismatch is detected, preserving a local backup.
|
|
18
|
+
- Generated SDK passive runtime now requires outcome closure by default with `MARROW_REQUIRE_OUTCOME_CLOSURE=true`.
|
|
19
|
+
- Self-test still creates a harmless decision, commits the outcome, checks status, calls the one-call runtime, and prints first-value proof.
|
|
20
|
+
- Doctor/repair output gives exact safe setup and repair commands without exposing credentials.
|
|
21
|
+
|
|
22
|
+
## Agent Value Proof Quickstart
|
|
23
|
+
|
|
24
|
+
One command should prove Marrow is active and useful:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
MARROW_API_KEY=mrw_live_xxx npx @getmarrow/install --yes
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Expected result:
|
|
31
|
+
|
|
32
|
+
- Marrow writes the safest detected MCP/SDK/agent config.
|
|
33
|
+
- A harmless setup decision is created and its outcome is committed.
|
|
34
|
+
- `/v1/agent/status` confirms capture health and missing hooks.
|
|
35
|
+
- `/v1/agent/runtime` returns the first "before you act" lesson or exact next action.
|
|
36
|
+
- The installer prints first-value proof such as captured surfaces, reused lessons, prevented risky actions, or estimated time/token savings when enough history exists.
|
|
21
37
|
|
|
22
38
|
## What It Detects
|
|
23
39
|
|
package/package.json
CHANGED
package/src/installer.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const fs = require('node:fs');
|
|
2
2
|
const path = require('node:path');
|
|
3
3
|
const os = require('node:os');
|
|
4
|
+
const crypto = require('node:crypto');
|
|
4
5
|
|
|
5
6
|
const DEFAULT_BASE_URL = 'https://api.getmarrow.ai';
|
|
6
7
|
const MARROW_BLOCK_START = '<!-- marrow:passive-start -->';
|
|
@@ -160,6 +161,120 @@ function findLikelyEnvFiles(detection, env = process.env) {
|
|
|
160
161
|
});
|
|
161
162
|
}
|
|
162
163
|
|
|
164
|
+
function stripQuotes(value) {
|
|
165
|
+
const trimmed = String(value || '').trim();
|
|
166
|
+
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) || (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
|
167
|
+
return trimmed.slice(1, -1);
|
|
168
|
+
}
|
|
169
|
+
return trimmed;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function readEnvVar(filePath, name) {
|
|
173
|
+
if (!exists(filePath)) return '';
|
|
174
|
+
const raw = safeRead(filePath);
|
|
175
|
+
const pattern = new RegExp(`^\\s*(?:export\\s+)?${name}\\s*=\\s*([^\\n#]+)`, 'm');
|
|
176
|
+
const match = raw.match(pattern);
|
|
177
|
+
return match ? stripQuotes(match[1]) : '';
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function readFirstLineSecret(filePath) {
|
|
181
|
+
if (!exists(filePath)) return '';
|
|
182
|
+
return safeRead(filePath).split(/\r?\n/).map((line) => line.trim()).find(Boolean) || '';
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function readNpmrcToken(filePath) {
|
|
186
|
+
if (!exists(filePath)) return '';
|
|
187
|
+
const raw = safeRead(filePath);
|
|
188
|
+
const match = raw.match(/\/\/registry\.npmjs\.org\/:_authToken\s*=\s*([^\s]+)/);
|
|
189
|
+
return match ? stripQuotes(match[1]) : '';
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function fingerprint(value) {
|
|
193
|
+
const secret = String(value || '').trim();
|
|
194
|
+
if (!secret) return null;
|
|
195
|
+
return crypto.createHash('sha256').update(secret).digest('hex').slice(0, 12);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function npmTokenPaths(env = process.env) {
|
|
199
|
+
const home = env.HOME || env.USERPROFILE || os.homedir();
|
|
200
|
+
return {
|
|
201
|
+
openclawEnv: path.join(home, '.openclaw', '.env'),
|
|
202
|
+
credentialFile: path.join(home, '.openclaw', 'credentials', 'npm-getmarrow-token.txt'),
|
|
203
|
+
npmrc: path.join(home, '.npmrc'),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function inspectNpmTokenConfig(env = process.env) {
|
|
208
|
+
const paths = npmTokenPaths(env);
|
|
209
|
+
const openclawToken = readEnvVar(paths.openclawEnv, 'NPM_TOKEN');
|
|
210
|
+
const credentialToken = readFirstLineSecret(paths.credentialFile);
|
|
211
|
+
const npmrcToken = readNpmrcToken(paths.npmrc);
|
|
212
|
+
const sourceToken = openclawToken || credentialToken;
|
|
213
|
+
const mismatch = Boolean(sourceToken && npmrcToken && fingerprint(sourceToken) !== fingerprint(npmrcToken));
|
|
214
|
+
const missingNpmrcToken = Boolean(sourceToken && !npmrcToken);
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
safe: {
|
|
218
|
+
npm_token: {
|
|
219
|
+
checked: true,
|
|
220
|
+
repairable: Boolean(sourceToken && (mismatch || missingNpmrcToken)),
|
|
221
|
+
mismatch,
|
|
222
|
+
missing_npmrc_token: missingNpmrcToken,
|
|
223
|
+
sources: {
|
|
224
|
+
openclaw_env: { path: paths.openclawEnv, present: Boolean(openclawToken), fingerprint: fingerprint(openclawToken) },
|
|
225
|
+
credential_file: { path: paths.credentialFile, present: Boolean(credentialToken), fingerprint: fingerprint(credentialToken) },
|
|
226
|
+
npmrc: { path: paths.npmrc, present: Boolean(npmrcToken), fingerprint: fingerprint(npmrcToken) },
|
|
227
|
+
},
|
|
228
|
+
recommended_fix: mismatch || missingNpmrcToken
|
|
229
|
+
? 'Run npx @getmarrow/install --repair to sync ~/.npmrc from the active OpenClaw/getmarrow npm token source.'
|
|
230
|
+
: null,
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
raw: { paths, sourceToken, npmrcToken },
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function upsertNpmrcToken(filePath, token) {
|
|
238
|
+
const before = safeRead(filePath);
|
|
239
|
+
const tokenLine = `//registry.npmjs.org/:_authToken=${token}`;
|
|
240
|
+
let after;
|
|
241
|
+
if (/\/\/registry\.npmjs\.org\/:_authToken\s*=/.test(before)) {
|
|
242
|
+
after = before.replace(/\/\/registry\.npmjs\.org\/:_authToken\s*=\s*[^\n\r]+/, tokenLine);
|
|
243
|
+
} else {
|
|
244
|
+
const separator = before && !before.endsWith('\n') ? '\n' : '';
|
|
245
|
+
after = `${before}${separator}${tokenLine}\n`;
|
|
246
|
+
}
|
|
247
|
+
if (before !== after) {
|
|
248
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
249
|
+
if (before) {
|
|
250
|
+
const backupPath = `${filePath}.marrow-backup`;
|
|
251
|
+
fs.writeFileSync(backupPath, before, { mode: 0o600 });
|
|
252
|
+
fs.chmodSync(backupPath, 0o600);
|
|
253
|
+
}
|
|
254
|
+
fs.writeFileSync(filePath, after, { mode: 0o600 });
|
|
255
|
+
fs.chmodSync(filePath, 0o600);
|
|
256
|
+
}
|
|
257
|
+
return before !== after;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function repairConfigDiagnostics(diagnostics, env = process.env) {
|
|
261
|
+
const inspection = inspectNpmTokenConfig(env);
|
|
262
|
+
const npm = diagnostics.npm_token;
|
|
263
|
+
const repairs = [];
|
|
264
|
+
if (npm?.repairable && inspection.raw.sourceToken) {
|
|
265
|
+
const changed = upsertNpmrcToken(inspection.raw.paths.npmrc, inspection.raw.sourceToken);
|
|
266
|
+
repairs.push({
|
|
267
|
+
type: 'npm_token_npmrc_sync',
|
|
268
|
+
changed,
|
|
269
|
+
path: inspection.raw.paths.npmrc,
|
|
270
|
+
message: changed
|
|
271
|
+
? 'Synced ~/.npmrc npm token from active OpenClaw/getmarrow token source.'
|
|
272
|
+
: '~/.npmrc already matched the active OpenClaw/getmarrow token source.',
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
return repairs;
|
|
276
|
+
}
|
|
277
|
+
|
|
163
278
|
function passiveInstructions() {
|
|
164
279
|
return `${MARROW_BLOCK_START}
|
|
165
280
|
## Marrow Passive Agent Memory
|
|
@@ -196,6 +311,7 @@ if (apiKey && !globalThis.__MARROW_PASSIVE_RUNTIME__) {
|
|
|
196
311
|
valueReportPeriod: process.env.MARROW_VALUE_REPORT_PERIOD || '7d',
|
|
197
312
|
useAgentRuntime: process.env.MARROW_AGENT_RUNTIME !== 'false',
|
|
198
313
|
useWorkflowGate: process.env.MARROW_WORKFLOW_GATE !== 'false',
|
|
314
|
+
requireOutcomeClosure: process.env.MARROW_REQUIRE_OUTCOME_CLOSURE !== 'false',
|
|
199
315
|
});
|
|
200
316
|
|
|
201
317
|
runtime.install();
|
|
@@ -213,6 +329,7 @@ MARROW_PASSIVE_BRIEF=auto
|
|
|
213
329
|
MARROW_PASSIVE_VALUE_REPORT=true
|
|
214
330
|
MARROW_AGENT_RUNTIME=true
|
|
215
331
|
MARROW_WORKFLOW_GATE=true
|
|
332
|
+
MARROW_REQUIRE_OUTCOME_CLOSURE=true
|
|
216
333
|
`;
|
|
217
334
|
}
|
|
218
335
|
|
|
@@ -564,6 +681,22 @@ function printReport(report) {
|
|
|
564
681
|
if (report.remediation.message) process.stdout.write(`- result: ${report.remediation.message}\n`);
|
|
565
682
|
}
|
|
566
683
|
|
|
684
|
+
if (report.configDiagnostics?.npm_token?.mismatch || report.configDiagnostics?.npm_token?.missing_npmrc_token) {
|
|
685
|
+
const npm = report.configDiagnostics.npm_token;
|
|
686
|
+
process.stdout.write('\nConfig diagnostics:\n');
|
|
687
|
+
process.stdout.write(`- npm token mismatch: ${npm.mismatch ? 'yes' : 'no'}\n`);
|
|
688
|
+
process.stdout.write(`- npmrc token missing: ${npm.missing_npmrc_token ? 'yes' : 'no'}\n`);
|
|
689
|
+
process.stdout.write(`- repairable: ${npm.repairable ? 'yes' : 'no'}\n`);
|
|
690
|
+
if (npm.recommended_fix) process.stdout.write(`- exact fix: ${npm.recommended_fix}\n`);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
if (report.configRepairs?.length) {
|
|
694
|
+
process.stdout.write('\nConfig repairs:\n');
|
|
695
|
+
for (const repair of report.configRepairs) {
|
|
696
|
+
process.stdout.write(`- ${repair.changed ? 'fixed' : 'checked'}: ${repair.message}\n`);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
567
700
|
if (report.writeMode === 'doctor') {
|
|
568
701
|
process.stdout.write('\nDoctor:\n');
|
|
569
702
|
process.stdout.write(`- Marrow active: ${report.doctor.active ? 'yes' : 'no'}\n`);
|
|
@@ -590,13 +723,18 @@ async function install(options) {
|
|
|
590
723
|
const plan = buildPlan(detection, options);
|
|
591
724
|
const writeMode = options.doctor ? 'doctor' : options.dryRun ? 'dry-run' : options.repair ? 'repair' : options.yes ? 'write' : 'dry-run';
|
|
592
725
|
const changes = applyPlan(plan, options);
|
|
726
|
+
const configInspection = inspectNpmTokenConfig();
|
|
727
|
+
const configDiagnostics = configInspection.safe;
|
|
728
|
+
const configRepairs = options.repair && !options.dryRun && !options.doctor
|
|
729
|
+
? repairConfigDiagnostics(configDiagnostics)
|
|
730
|
+
: [];
|
|
593
731
|
const envHints = options.apiKey ? [] : findLikelyEnvFiles(detection);
|
|
594
732
|
const selfTest = await runSelfTest(options).catch((error) => ({
|
|
595
733
|
skipped: false,
|
|
596
734
|
active: false,
|
|
597
735
|
error: error instanceof Error ? error.message : String(error),
|
|
598
736
|
}));
|
|
599
|
-
const changedConfig = changes.some((change) => change.changed);
|
|
737
|
+
const changedConfig = changes.some((change) => change.changed) || configRepairs.some((repair) => repair.changed);
|
|
600
738
|
const selfTestPassed = Boolean(!selfTest.skipped && selfTest.active && !selfTest.error);
|
|
601
739
|
const remediation = options.repair
|
|
602
740
|
? {
|
|
@@ -632,13 +770,15 @@ async function install(options) {
|
|
|
632
770
|
missingEnv: options.apiKey ? [] : ['MARROW_API_KEY'],
|
|
633
771
|
envHints,
|
|
634
772
|
missingHooks: changes.filter((change) => change.changed).map((change) => change.label),
|
|
635
|
-
recommendedFix: selfTest.recommended_fix || (!options.apiKey
|
|
773
|
+
recommendedFix: configDiagnostics.npm_token.recommended_fix || selfTest.recommended_fix || (!options.apiKey
|
|
636
774
|
? envHints.length
|
|
637
775
|
? `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
776
|
: 'Set MARROW_API_KEY, then run npx @getmarrow/install --repair.'
|
|
639
777
|
: null),
|
|
640
778
|
},
|
|
641
779
|
remediation,
|
|
780
|
+
configDiagnostics,
|
|
781
|
+
configRepairs,
|
|
642
782
|
selfTest,
|
|
643
783
|
warnings: options.keyFromArg
|
|
644
784
|
? ['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.']
|
|
@@ -669,4 +809,5 @@ module.exports = {
|
|
|
669
809
|
runSelfTest,
|
|
670
810
|
runCli,
|
|
671
811
|
passiveRuntimeSource,
|
|
812
|
+
inspectNpmTokenConfig,
|
|
672
813
|
};
|