@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 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.4
14
+ ## What's New in v0.1.6
15
15
 
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.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getmarrow/install",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
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
@@ -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
  };