@getmarrow/install 0.1.28 → 0.1.29
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 +25 -12
- package/package.json +1 -1
- package/src/installer.js +144 -20
package/README.md
CHANGED
|
@@ -18,16 +18,20 @@ Required secret:
|
|
|
18
18
|
export MARROW_API_KEY=mrw_live_...
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
## What's New in v0.1.
|
|
21
|
+
## What's New in v0.1.29
|
|
22
22
|
|
|
23
|
-
v0.1.
|
|
23
|
+
v0.1.29 makes first-run activation server-verifiable instead of relying on local setup output:
|
|
24
24
|
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
25
|
+
- `npx @getmarrow/install activate` detects the current harness, writes supported passive controls, creates and closes a harmless decision, and asks Marrow to verify that exact outcome;
|
|
26
|
+
- activation succeeds only when the API returns a tenant-scoped activation receipt;
|
|
27
|
+
- the receipt reports capture, before-action intervention, outcome closure, and first-value state;
|
|
28
|
+
- existing setup, governed runner, and TUI commands remain compatible.
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
Use `activate` when you want one command with an explicit success contract. Use `--yes` when an existing automation already handles setup prompts and verification output.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npx @getmarrow/install activate
|
|
34
|
+
```
|
|
31
35
|
|
|
32
36
|
## What It Detects
|
|
33
37
|
|
|
@@ -41,18 +45,19 @@ The installer detects supported configuration and project signals for:
|
|
|
41
45
|
|
|
42
46
|
Marrow does not replace these models or harnesses. It adds a common business control, proof, and outcome layer around the actions they perform.
|
|
43
47
|
|
|
44
|
-
## First-Run
|
|
48
|
+
## First-Run Activation
|
|
45
49
|
|
|
46
|
-
With a valid key,
|
|
50
|
+
With a valid key, `activate`:
|
|
47
51
|
|
|
48
52
|
1. detects the local integration surfaces;
|
|
49
53
|
2. writes supported config and passive instructions;
|
|
50
54
|
3. creates a harmless test decision;
|
|
51
55
|
4. closes its outcome;
|
|
52
|
-
5.
|
|
53
|
-
6.
|
|
56
|
+
5. sends the exact self-test decision ID to Marrow for server-side verification;
|
|
57
|
+
6. reads agent status and the one-call runtime;
|
|
58
|
+
7. returns an activation receipt with capture, intervention, closure, first-value state, and the exact next action.
|
|
54
59
|
|
|
55
|
-
Healthy output confirms
|
|
60
|
+
Healthy output confirms the exact decision outcome exists under the authenticated account and agent. A local file write or client-supplied `verified: true` value cannot produce an active receipt.
|
|
56
61
|
|
|
57
62
|
## Govern TUI
|
|
58
63
|
|
|
@@ -120,6 +125,14 @@ The fleet view shows live agents, active workflows, risky actions waiting for pr
|
|
|
120
125
|
|
|
121
126
|
These are integration surfaces for one Marrow product, not separate products.
|
|
122
127
|
|
|
128
|
+
## Always-On Lifecycle
|
|
129
|
+
|
|
130
|
+
Supported integrations capture a compact lifecycle without storing raw prompts, completions, command output, tool output, or credentials. Marrow recognizes prompt, goal, pre-action, tool/command result, verification evidence, workflow/session, subagent, handoff, proof-pack, and outcome events.
|
|
131
|
+
|
|
132
|
+
Meaningful work opens an outcome-closure item. A tool exit or workflow completion does not silently count as a successful business outcome; an explicit outcome receipt closes it. Transient delivery failures are held in an owner-only local spool and retried with the same event ID so retries do not create duplicate lifecycle records.
|
|
133
|
+
|
|
134
|
+
Owners can inspect pending outcomes in Fleet Operations. Agents can retrieve a tenant-scoped causal trace for a decision to see the prior failure, lesson, gate, proof, workflow, and outcome path that changed the action.
|
|
135
|
+
|
|
123
136
|
## Passive Token and Value Proof
|
|
124
137
|
|
|
125
138
|
When the installer writes `.marrow/passive-runtime.mjs` and the harness exposes usage metadata, Marrow can capture compact provider/model, token, latency, and optional cost counts. It does not require raw prompts, completions, command output, tool output, or plaintext secrets.
|
package/package.json
CHANGED
package/src/installer.js
CHANGED
|
@@ -48,12 +48,19 @@ function parseArgs(argv) {
|
|
|
48
48
|
baseUrl: process.env.MARROW_BASE_URL || DEFAULT_BASE_URL,
|
|
49
49
|
agentId: process.env.MARROW_FLEET_AGENT_ID || process.env.MARROW_AGENT_ID || '',
|
|
50
50
|
selfTest: true,
|
|
51
|
+
selfTestExplicitlyDisabled: false,
|
|
51
52
|
json: false,
|
|
53
|
+
activate: false,
|
|
52
54
|
};
|
|
53
55
|
|
|
54
56
|
for (let i = 0; i < argv.length; i += 1) {
|
|
55
57
|
const arg = argv[i];
|
|
56
|
-
if (arg === '
|
|
58
|
+
if (arg === 'activate' || arg === '--activate') {
|
|
59
|
+
options.activate = true;
|
|
60
|
+
options.yes = true;
|
|
61
|
+
options.selfTest = true;
|
|
62
|
+
}
|
|
63
|
+
else if (arg === '--yes' || arg === '-y') options.yes = true;
|
|
57
64
|
else if (arg === '--repair' || arg === 'repair') {
|
|
58
65
|
options.repair = true;
|
|
59
66
|
options.yes = true;
|
|
@@ -61,7 +68,10 @@ function parseArgs(argv) {
|
|
|
61
68
|
else if (arg === '--dry-run') options.dryRun = true;
|
|
62
69
|
else if (arg === '--doctor' || arg === 'doctor' || arg === 'check') options.doctor = true;
|
|
63
70
|
else if (arg === '--json') options.json = true;
|
|
64
|
-
else if (arg === '--no-self-test')
|
|
71
|
+
else if (arg === '--no-self-test') {
|
|
72
|
+
options.selfTest = false;
|
|
73
|
+
options.selfTestExplicitlyDisabled = true;
|
|
74
|
+
}
|
|
65
75
|
else if (arg === '--self-test') options.selfTest = true;
|
|
66
76
|
else if (arg === '--cwd') options.cwd = path.resolve(argv[++i] || options.cwd);
|
|
67
77
|
else if (arg === '--mode') options.mode = argv[++i] || options.mode;
|
|
@@ -85,6 +95,12 @@ function parseArgs(argv) {
|
|
|
85
95
|
if (!['auto', 'mcp', 'sdk', 'both', 'md'].includes(options.mode)) {
|
|
86
96
|
throw new Error('--mode must be one of auto, mcp, sdk, both, md');
|
|
87
97
|
}
|
|
98
|
+
if (options.activate && options.selfTestExplicitlyDisabled) {
|
|
99
|
+
throw new Error('activate cannot be combined with --no-self-test because server verification is required');
|
|
100
|
+
}
|
|
101
|
+
if (options.activate && options.dryRun) {
|
|
102
|
+
throw new Error('activate cannot be combined with --dry-run; use --dry-run without activate to preview changes');
|
|
103
|
+
}
|
|
88
104
|
|
|
89
105
|
return options;
|
|
90
106
|
}
|
|
@@ -92,6 +108,7 @@ function parseArgs(argv) {
|
|
|
92
108
|
function usage() {
|
|
93
109
|
return `Usage:
|
|
94
110
|
npx @getmarrow/install --dry-run
|
|
111
|
+
npx @getmarrow/install activate
|
|
95
112
|
npx @getmarrow/install --yes
|
|
96
113
|
npx @getmarrow/install --repair
|
|
97
114
|
npx @getmarrow/install doctor
|
|
@@ -99,6 +116,7 @@ function usage() {
|
|
|
99
116
|
npx @getmarrow/install --sdk --yes
|
|
100
117
|
|
|
101
118
|
Options:
|
|
119
|
+
activate Detect, install, self-test, and return a server-confirmed activation receipt
|
|
102
120
|
--dry-run Print planned changes without writing
|
|
103
121
|
--doctor Check install health without writing
|
|
104
122
|
--repair Write missing hooks/config, then run self-test and status check
|
|
@@ -111,6 +129,20 @@ Options:
|
|
|
111
129
|
`;
|
|
112
130
|
}
|
|
113
131
|
|
|
132
|
+
function stableAgentId(root, client = sourceClient()) {
|
|
133
|
+
const identity = `${path.resolve(root)}:${os.hostname()}:${client}`;
|
|
134
|
+
return `${client}-${crypto.createHash('sha256').update(identity).digest('hex').slice(0, 12)}`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function detectedClient(detection) {
|
|
138
|
+
if (sourceClient() !== 'custom') return sourceClient();
|
|
139
|
+
if (detection.openclaw) return 'openclaw';
|
|
140
|
+
if (detection.claudeCode) return 'claude-code';
|
|
141
|
+
if (detection.cursor) return 'cursor';
|
|
142
|
+
if (detection.codex) return 'codex';
|
|
143
|
+
return 'custom';
|
|
144
|
+
}
|
|
145
|
+
|
|
114
146
|
function exists(filePath) {
|
|
115
147
|
return fs.existsSync(filePath);
|
|
116
148
|
}
|
|
@@ -556,8 +588,15 @@ function applyPlan(plan, options) {
|
|
|
556
588
|
}
|
|
557
589
|
|
|
558
590
|
const changed = before !== after;
|
|
559
|
-
|
|
560
|
-
|
|
591
|
+
const writeApplied = Boolean(options.yes && !options.dryRun && !options.doctor);
|
|
592
|
+
changes.push({
|
|
593
|
+
path: write.path,
|
|
594
|
+
label: write.label,
|
|
595
|
+
changed,
|
|
596
|
+
applied: changed && writeApplied,
|
|
597
|
+
already_present: !changed,
|
|
598
|
+
});
|
|
599
|
+
if (changed && writeApplied) {
|
|
561
600
|
fs.mkdirSync(path.dirname(write.path), { recursive: true });
|
|
562
601
|
fs.writeFileSync(write.path, after);
|
|
563
602
|
}
|
|
@@ -581,6 +620,22 @@ async function requestJson(url, options) {
|
|
|
581
620
|
return json.data || json;
|
|
582
621
|
}
|
|
583
622
|
|
|
623
|
+
function isCanonicalTimestamp(value) {
|
|
624
|
+
if (typeof value !== 'string' || value.length === 0) return false;
|
|
625
|
+
const parsed = Date.parse(value);
|
|
626
|
+
return Number.isFinite(parsed) && new Date(parsed).toISOString() === value;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function runtimeGateVerified(runtime) {
|
|
630
|
+
if (!runtime || typeof runtime !== 'object') return false;
|
|
631
|
+
if (runtime.ok === true) return true;
|
|
632
|
+
const gate = runtime.risk_gate;
|
|
633
|
+
if (!gate || typeof gate !== 'object') return false;
|
|
634
|
+
if (typeof gate.allow === 'boolean' || typeof gate.allowed === 'boolean') return true;
|
|
635
|
+
const decision = typeof gate.decision === 'string' ? gate.decision.toLowerCase() : '';
|
|
636
|
+
return ['allow', 'warn', 'review_required', 'block'].includes(decision);
|
|
637
|
+
}
|
|
638
|
+
|
|
584
639
|
async function runSelfTest(options) {
|
|
585
640
|
if (!options.selfTest) return { skipped: true, reason: 'disabled' };
|
|
586
641
|
if (!options.apiKey) {
|
|
@@ -595,7 +650,7 @@ async function runSelfTest(options) {
|
|
|
595
650
|
authorization: `Bearer ${options.apiKey}`,
|
|
596
651
|
'content-type': 'application/json',
|
|
597
652
|
'x-marrow-session-id': `install-${Date.now()}`,
|
|
598
|
-
'x-marrow-client': sourceClient(),
|
|
653
|
+
'x-marrow-client': options.client || sourceClient(),
|
|
599
654
|
};
|
|
600
655
|
if (options.agentId) headers['x-marrow-agent-id'] = options.agentId;
|
|
601
656
|
|
|
@@ -608,7 +663,7 @@ async function runSelfTest(options) {
|
|
|
608
663
|
action: 'Marrow passive install self-test: verify SDK/MCP hooks can record a harmless setup event',
|
|
609
664
|
source_meta: {
|
|
610
665
|
channel: 'cli',
|
|
611
|
-
client: sourceClient(),
|
|
666
|
+
client: options.client || sourceClient(),
|
|
612
667
|
user_intent: 'operate',
|
|
613
668
|
},
|
|
614
669
|
}),
|
|
@@ -641,10 +696,7 @@ async function runSelfTest(options) {
|
|
|
641
696
|
outcome: 'self-test outcome committed',
|
|
642
697
|
},
|
|
643
698
|
}),
|
|
644
|
-
})
|
|
645
|
-
ok: false,
|
|
646
|
-
error: error instanceof Error ? error.message : String(error),
|
|
647
|
-
}));
|
|
699
|
+
});
|
|
648
700
|
const performance = await requestJson(`${baseUrl}/v1/analytics/agent-performance?period=7`, { headers })
|
|
649
701
|
.catch((error) => ({
|
|
650
702
|
ok: false,
|
|
@@ -662,11 +714,15 @@ async function runSelfTest(options) {
|
|
|
662
714
|
checks: ['installer first-value self-test'],
|
|
663
715
|
outcome: 'first-value endpoint reached',
|
|
664
716
|
},
|
|
717
|
+
decision_id: decisionId,
|
|
718
|
+
agent_id: options.agentId,
|
|
719
|
+
activation: options.activation ? {
|
|
720
|
+
...options.activation,
|
|
721
|
+
intervention_verified: runtimeGateVerified(runtime),
|
|
722
|
+
closure_verified: true,
|
|
723
|
+
} : undefined,
|
|
665
724
|
}),
|
|
666
|
-
})
|
|
667
|
-
ok: false,
|
|
668
|
-
error: error instanceof Error ? error.message : String(error),
|
|
669
|
-
}));
|
|
725
|
+
});
|
|
670
726
|
const valueProof = await requestJson(`${baseUrl}/v1/agent/value/proof?period_days=30`, { headers })
|
|
671
727
|
.catch((error) => ({
|
|
672
728
|
ok: false,
|
|
@@ -675,6 +731,30 @@ async function runSelfTest(options) {
|
|
|
675
731
|
const tokenValueProof = buildTokenValueProof(valueProof);
|
|
676
732
|
const firstValueSignal = buildFirstValueSignal(status, runtime, performance, firstValue, tokenValueProof);
|
|
677
733
|
const installValueMoment = buildInstallValueMoment(firstValueSignal, status, runtime, performance, firstValue, tokenValueProof);
|
|
734
|
+
let activationReceipt = null;
|
|
735
|
+
let activationVerified = false;
|
|
736
|
+
if (options.activation) {
|
|
737
|
+
activationReceipt = firstValue && firstValue.activation_receipt;
|
|
738
|
+
const receiptValid = activationReceipt
|
|
739
|
+
&& typeof activationReceipt === 'object'
|
|
740
|
+
&& typeof activationReceipt.id === 'string'
|
|
741
|
+
&& activationReceipt.id.length > 0
|
|
742
|
+
&& activationReceipt.decision_id === decisionId
|
|
743
|
+
&& activationReceipt.agent_id === options.agentId
|
|
744
|
+
&& activationReceipt.outcome_success === true
|
|
745
|
+
&& isCanonicalTimestamp(activationReceipt.outcome_recorded_at)
|
|
746
|
+
&& activationReceipt.server_confirmed === true
|
|
747
|
+
&& activationReceipt.capture_verified === true
|
|
748
|
+
&& activationReceipt.intervention_verified === true
|
|
749
|
+
&& activationReceipt.closure_verified === true;
|
|
750
|
+
if (!receiptValid) {
|
|
751
|
+
throw new Error('activation receipt did not verify the exact self-test decision, agent, runtime gate, and closed successful outcome');
|
|
752
|
+
}
|
|
753
|
+
activationVerified = Boolean(firstValue.active && runtimeGateVerified(runtime) && (status.enabled ?? status.ok));
|
|
754
|
+
if (!activationVerified) {
|
|
755
|
+
throw new Error('activation prerequisites were not all verified by the server');
|
|
756
|
+
}
|
|
757
|
+
}
|
|
678
758
|
return {
|
|
679
759
|
skipped: false,
|
|
680
760
|
decision_id: decisionId,
|
|
@@ -684,9 +764,11 @@ async function runSelfTest(options) {
|
|
|
684
764
|
recommended_fix: status.recommended_fix || null,
|
|
685
765
|
next_action: status.next_action || null,
|
|
686
766
|
auto_outcome_closure: status.auto_outcome_closure || null,
|
|
687
|
-
runtime_active:
|
|
767
|
+
runtime_active: runtimeGateVerified(runtime),
|
|
688
768
|
runtime_exact_next_action: runtime.exact_next_action || null,
|
|
689
769
|
runtime_before_you_act: runtime.before_you_act || null,
|
|
770
|
+
activation_verified: activationVerified,
|
|
771
|
+
activation_receipt: activationReceipt,
|
|
690
772
|
first_value: firstValue && firstValue.ok !== false ? firstValue : null,
|
|
691
773
|
first_value_signal: firstValueSignal,
|
|
692
774
|
install_value_moment: installValueMoment,
|
|
@@ -821,6 +903,12 @@ function printReport(report) {
|
|
|
821
903
|
process.stdout.write(`Mode: ${report.mode}\n`);
|
|
822
904
|
process.stdout.write(`Write mode: ${report.writeMode}\n\n`);
|
|
823
905
|
|
|
906
|
+
if (report.activation?.requested) {
|
|
907
|
+
process.stdout.write('Activation:\n');
|
|
908
|
+
process.stdout.write(`- agent: ${report.activation.agent_id}\n`);
|
|
909
|
+
process.stdout.write(`- server confirmed: ${report.activation.server_confirmed ? 'yes' : 'no'}\n\n`);
|
|
910
|
+
}
|
|
911
|
+
|
|
824
912
|
process.stdout.write('Detected:\n');
|
|
825
913
|
for (const [key, value] of Object.entries(report.detected)) {
|
|
826
914
|
process.stdout.write(`- ${key}: ${value ? 'yes' : 'no'}\n`);
|
|
@@ -841,6 +929,7 @@ function printReport(report) {
|
|
|
841
929
|
process.stdout.write(`- decision_id: ${report.selfTest.decision_id}\n`);
|
|
842
930
|
process.stdout.write(`- health: ${report.selfTest.health || 'unknown'}\n`);
|
|
843
931
|
process.stdout.write(`- one-call runtime: ${report.selfTest.runtime_active ? 'active' : 'not verified'}\n`);
|
|
932
|
+
if (report.selfTest.error) process.stdout.write(`- error: ${report.selfTest.error}\n`);
|
|
844
933
|
if (report.selfTest.next_action) process.stdout.write(`- next action: ${report.selfTest.next_action}\n`);
|
|
845
934
|
if (report.selfTest.first_value_signal) {
|
|
846
935
|
process.stdout.write('\nFirst value:\n');
|
|
@@ -930,10 +1019,32 @@ function printReport(report) {
|
|
|
930
1019
|
}
|
|
931
1020
|
|
|
932
1021
|
async function install(options) {
|
|
1022
|
+
if (options.activate && (options.yes !== true || options.dryRun || options.doctor)) {
|
|
1023
|
+
throw new Error('activate requires write mode (--yes) because hooks must be installed during this run');
|
|
1024
|
+
}
|
|
1025
|
+
if (options.activate && options.selfTest === false) {
|
|
1026
|
+
throw new Error('activate requires the server self-test');
|
|
1027
|
+
}
|
|
933
1028
|
const detection = detectEnvironment(options.cwd);
|
|
934
1029
|
const plan = buildPlan(detection, options);
|
|
935
1030
|
const writeMode = options.doctor ? 'doctor' : options.dryRun ? 'dry-run' : options.repair ? 'repair' : options.yes ? 'write' : 'dry-run';
|
|
936
1031
|
const changes = applyPlan(plan, options);
|
|
1032
|
+
const client = detectedClient(detection);
|
|
1033
|
+
options.client = client;
|
|
1034
|
+
if (!options.agentId) options.agentId = stableAgentId(detection.root, client);
|
|
1035
|
+
options.activation = options.activate ? {
|
|
1036
|
+
harness: client,
|
|
1037
|
+
agent_id: options.agentId,
|
|
1038
|
+
install_surface: plan.mode,
|
|
1039
|
+
mode: options.governanceMode || 'passive',
|
|
1040
|
+
hooks_installed: changes
|
|
1041
|
+
.filter((change) => change.changed && change.applied && /hook|runtime|rule|instruction|config/i.test(change.label))
|
|
1042
|
+
.map((change) => change.label)
|
|
1043
|
+
.slice(0, 20),
|
|
1044
|
+
capture_verified: changes.every((change) => change.applied),
|
|
1045
|
+
intervention_verified: false,
|
|
1046
|
+
closure_verified: false,
|
|
1047
|
+
} : null;
|
|
937
1048
|
const configInspection = inspectNpmTokenConfig();
|
|
938
1049
|
const sdkDependency = inspectSdkDependency(detection);
|
|
939
1050
|
const configDiagnostics = configInspection.safe;
|
|
@@ -941,11 +1052,17 @@ async function install(options) {
|
|
|
941
1052
|
? repairConfigDiagnostics(configDiagnostics)
|
|
942
1053
|
: [];
|
|
943
1054
|
const envHints = options.apiKey ? [] : findLikelyEnvFiles(detection);
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
1055
|
+
let selfTest;
|
|
1056
|
+
try {
|
|
1057
|
+
selfTest = await runSelfTest(options);
|
|
1058
|
+
} catch (error) {
|
|
1059
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1060
|
+
if (options.activate) throw new Error(`Marrow activation failed: ${message}`);
|
|
1061
|
+
selfTest = { skipped: false, active: false, error: message };
|
|
1062
|
+
}
|
|
1063
|
+
if (options.activate && !selfTest.activation_verified) {
|
|
1064
|
+
throw new Error('Marrow activation failed: server confirmation was not returned');
|
|
1065
|
+
}
|
|
949
1066
|
const changedConfig = changes.some((change) => change.changed) || configRepairs.some((repair) => repair.changed);
|
|
950
1067
|
const selfTestPassed = Boolean(!selfTest.skipped && selfTest.active && !selfTest.error);
|
|
951
1068
|
const remediation = options.repair
|
|
@@ -976,6 +1093,12 @@ async function install(options) {
|
|
|
976
1093
|
openclaw: detection.openclaw,
|
|
977
1094
|
mcpConfig: detection.mcpConfig,
|
|
978
1095
|
},
|
|
1096
|
+
activation: {
|
|
1097
|
+
requested: options.activate,
|
|
1098
|
+
agent_id: options.agentId,
|
|
1099
|
+
server_confirmed: Boolean(selfTest.activation_verified),
|
|
1100
|
+
receipt: selfTest.activation_receipt || null,
|
|
1101
|
+
},
|
|
979
1102
|
changes,
|
|
980
1103
|
doctor: {
|
|
981
1104
|
active: Boolean(!selfTest.skipped && selfTest.active),
|
|
@@ -1026,4 +1149,5 @@ module.exports = {
|
|
|
1026
1149
|
inspectSdkDependency,
|
|
1027
1150
|
buildInstallValueMoment,
|
|
1028
1151
|
buildTokenValueProof,
|
|
1152
|
+
stableAgentId,
|
|
1029
1153
|
};
|