@getmarrow/install 0.1.27 → 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 +100 -198
- package/package.json +3 -3
- package/src/installer.js +144 -20
package/README.md
CHANGED
|
@@ -1,93 +1,86 @@
|
|
|
1
1
|
# @getmarrow/install
|
|
2
2
|
|
|
3
|
-
Universal installer for Marrow agent
|
|
3
|
+
> Universal installer and governed runner for Marrow agent fleets.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Marrow is the runtime control and proof layer for teams running AI agents. It applies policy and prior lessons before consequential actions, then records the evidence and outcome afterward.
|
|
6
|
+
|
|
7
|
+
Use `@getmarrow/install` as the default entry point. It detects supported agent and project surfaces, writes the appropriate passive configuration, runs a harmless end-to-end self-test, and reports whether policy, proof, attribution, and outcome capture are active.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
6
10
|
|
|
7
11
|
```bash
|
|
8
|
-
npx @getmarrow/install --dry-run
|
|
9
12
|
npx @getmarrow/install --yes
|
|
10
|
-
npx @getmarrow/install --repair
|
|
11
|
-
npx @getmarrow/install doctor
|
|
12
|
-
npx @getmarrow/install govern
|
|
13
|
-
npx @getmarrow/install fleet
|
|
14
|
-
npx @getmarrow/install integrations
|
|
15
|
-
npx @getmarrow/install hermes
|
|
16
|
-
npx @getmarrow/install openclaw
|
|
17
13
|
```
|
|
18
14
|
|
|
19
|
-
|
|
15
|
+
Required secret:
|
|
20
16
|
|
|
21
|
-
|
|
17
|
+
```bash
|
|
18
|
+
export MARROW_API_KEY=mrw_live_...
|
|
19
|
+
```
|
|
22
20
|
|
|
23
|
-
|
|
24
|
-
- `GET /v1/agent/data-quality` lets agents verify attribution coverage after install, including agent ID, source metadata, harness/client, workflow type, outcome closure, and exact repair guidance.
|
|
25
|
-
- `GET /v1/agent/integrations/{client}` gives supported harnesses a per-client setup guide with install command, capture points, source metadata, runtime/commit/batch mapping, and exact next action.
|
|
26
|
-
- `npx @getmarrow/install govern` detects Codex, Claude Code, Cursor, Cursor Composer, Windsurf, Cline, OpenCode, Hermes, OpenClaw, Gemini, Grok, DeepSeek, Qwen, Kimi, MiniMax, GLM, MCP clients, CI scripts, and custom commands.
|
|
27
|
-
- Hermes Agent and OpenClaw remain first-class add-ons.
|
|
28
|
-
- Codex, Claude Code, Cursor, Cursor Composer, Windsurf, Cline, OpenCode, Gemini, Grok, DeepSeek, Qwen, Kimi, MiniMax, GLM, MCP clients, CI scripts, and custom shell/API harnesses can use MCP/SDK hooks, the governed runner, or `POST /v1/agent/integrations/events`.
|
|
29
|
-
- Custom harness events should include `harness`, `event_type`, `agent_id`, and `action`, so Marrow can return the right runtime, commit, or batch-ingest next step without storing unsupported or under-attributed raw payloads.
|
|
21
|
+
## What's New in v0.1.29
|
|
30
22
|
|
|
31
|
-
|
|
23
|
+
v0.1.29 makes first-run activation server-verifiable instead of relying on local setup output:
|
|
32
24
|
|
|
33
|
-
|
|
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.
|
|
34
29
|
|
|
35
|
-
|
|
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.
|
|
36
31
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
- New backend surfaces include governance control-plane proof, completion-contract evaluation, fleet governance timeline, and buyer-grade value proof.
|
|
32
|
+
```bash
|
|
33
|
+
npx @getmarrow/install activate
|
|
34
|
+
```
|
|
41
35
|
|
|
42
|
-
## What
|
|
36
|
+
## What It Detects
|
|
43
37
|
|
|
44
|
-
|
|
38
|
+
The installer detects supported configuration and project signals for:
|
|
45
39
|
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
-
|
|
49
|
-
-
|
|
50
|
-
-
|
|
40
|
+
- Codex, Claude Code, Cursor, Cursor Composer, Windsurf, Cline, OpenCode, Hermes, and OpenClaw;
|
|
41
|
+
- Gemini, Grok, DeepSeek, Qwen, Kimi, MiniMax, and GLM command-line or custom harness paths;
|
|
42
|
+
- MCP client configuration;
|
|
43
|
+
- Node.js and Python projects;
|
|
44
|
+
- CI, deploy, publish, merge, migration, and custom shell workflows.
|
|
51
45
|
|
|
52
|
-
|
|
46
|
+
Marrow does not replace these models or harnesses. It adds a common business control, proof, and outcome layer around the actions they perform.
|
|
53
47
|
|
|
54
|
-
##
|
|
48
|
+
## First-Run Activation
|
|
55
49
|
|
|
56
|
-
|
|
50
|
+
With a valid key, `activate`:
|
|
57
51
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
52
|
+
1. detects the local integration surfaces;
|
|
53
|
+
2. writes supported config and passive instructions;
|
|
54
|
+
3. creates a harmless test decision;
|
|
55
|
+
4. closes its outcome;
|
|
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.
|
|
64
59
|
|
|
65
|
-
|
|
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.
|
|
66
61
|
|
|
67
|
-
|
|
62
|
+
## Govern TUI
|
|
68
63
|
|
|
69
|
-
|
|
64
|
+
Open the interactive setup panel:
|
|
70
65
|
|
|
71
66
|
```bash
|
|
72
67
|
npx @getmarrow/install govern
|
|
73
68
|
```
|
|
74
69
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
```bash
|
|
78
|
-
MARROW_API_KEY=<your_marrow_key> npx @getmarrow/install run --agent codex-prod --profile production -- node -e "process.exit(0)"
|
|
79
|
-
```
|
|
70
|
+
The TUI shows detected harnesses and project risks, recommends passive, pilot, or enforce mode with reasons, lets the owner accept or override the recommendation, runs the self-test, and confirms the active controls. Use `Ctrl+C` to exit.
|
|
80
71
|
|
|
81
|
-
|
|
72
|
+
For non-interactive environments:
|
|
82
73
|
|
|
83
74
|
```bash
|
|
84
|
-
|
|
75
|
+
npx @getmarrow/install govern --no-interactive
|
|
85
76
|
```
|
|
86
77
|
|
|
87
|
-
|
|
78
|
+
## Governed Runner
|
|
79
|
+
|
|
80
|
+
Place Marrow around an existing command without replacing the agent harness:
|
|
88
81
|
|
|
89
82
|
```bash
|
|
90
|
-
|
|
83
|
+
npx @getmarrow/install run \
|
|
91
84
|
--agent deploy-agent \
|
|
92
85
|
--type deploy \
|
|
93
86
|
--profile production \
|
|
@@ -95,180 +88,89 @@ MARROW_API_KEY=<your_marrow_key> npx @getmarrow/install run \
|
|
|
95
88
|
-- wrangler deploy
|
|
96
89
|
```
|
|
97
90
|
|
|
98
|
-
|
|
91
|
+
The runner:
|
|
99
92
|
|
|
100
|
-
|
|
93
|
+
1. requests the Marrow runtime gate;
|
|
94
|
+
2. prints the decision, relevant lesson, owner-approval state, and required proof;
|
|
95
|
+
3. blocks when policy requires it;
|
|
96
|
+
4. runs the original command when allowed;
|
|
97
|
+
5. records success or failure and attaches a redacted proof pack.
|
|
101
98
|
|
|
102
|
-
|
|
99
|
+
Useful commands:
|
|
103
100
|
|
|
104
101
|
```bash
|
|
105
|
-
npx @getmarrow/install --
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
Use the lower-level packages only when you need direct control:
|
|
111
|
-
|
|
112
|
-
- **SDK:** use `@getmarrow/sdk` when you are building a custom Node/TypeScript agent integration or wrapping your own tools, commands, deploys, and publishes in code.
|
|
113
|
-
- **MCP:** use `@getmarrow/mcp` when you want manual MCP server/hook setup for Claude Code, Claude Desktop, Cursor, or another MCP-compatible client.
|
|
114
|
-
|
|
115
|
-
The three packages are not three competing onboarding paths. `@getmarrow/install` is the front door; SDK and MCP are the implementation paths underneath it.
|
|
116
|
-
|
|
117
|
-
For cleaner attribution, set an agent id and optional client label before install:
|
|
118
|
-
|
|
119
|
-
```bash
|
|
120
|
-
export MARROW_FLEET_AGENT_ID=codex-deploy-agent
|
|
121
|
-
export MARROW_CLIENT=codex
|
|
122
|
-
npx @getmarrow/install --yes
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
## Agent Value Proof Quickstart
|
|
126
|
-
|
|
127
|
-
One command should prove Marrow is active and useful:
|
|
128
|
-
|
|
129
|
-
```bash
|
|
130
|
-
MARROW_API_KEY=<your_marrow_key> npx @getmarrow/install --yes
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
Expected result:
|
|
134
|
-
|
|
135
|
-
- Marrow writes the safest detected MCP/SDK/agent config.
|
|
136
|
-
- A harmless setup decision is created and its outcome is committed.
|
|
137
|
-
- `/v1/agent/status` confirms capture health and missing hooks.
|
|
138
|
-
- `/v1/agent/data-quality` confirms whether attribution is clean enough for per-agent, per-harness, and per-workflow value proof.
|
|
139
|
-
- `/v1/agent/runtime` verifies the one-call runtime gate and returns the before-action intervention contract.
|
|
140
|
-
- `/v1/agent/first-value` returns the five-minute proof payload used by installer, SDK, and MCP clients.
|
|
141
|
-
- `/v1/agent/value/proof` returns token value proof from passive model-usage capture.
|
|
142
|
-
- The installer prints: "Your agent is no longer starting from zero."
|
|
143
|
-
- The installer prints: "Token value proof" with capture status, observed model calls, token totals, estimated savings, confidence, and exact next action.
|
|
144
|
-
- Fresh accounts get a first useful action to try immediately.
|
|
145
|
-
- Accounts with history get proof such as avoided mistakes, reused winning decisions, prevented risky actions, or estimated time/token savings.
|
|
146
|
-
|
|
147
|
-
## First Five-Minute Proof
|
|
148
|
-
|
|
149
|
-
After install, ask the agent:
|
|
150
|
-
|
|
151
|
-
```text
|
|
152
|
-
I am about to deploy to production. What should I check first?
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
Marrow should answer with `proceed`, `warn`, `block`, or `owner_approval_required`, plus required proof and matching fleet lessons/playbooks before the agent acts. This is the first product moment: not just "hooks installed", but "the agent is being warned before risky work."
|
|
156
|
-
|
|
157
|
-
## What It Detects
|
|
158
|
-
|
|
159
|
-
- OpenClaw-style workspaces
|
|
160
|
-
- Hermes Agent config files
|
|
161
|
-
- Codex/agent instruction files such as `AGENTS.md`
|
|
162
|
-
- Claude Code settings and hooks
|
|
163
|
-
- Cursor project folders
|
|
164
|
-
- Cursor Composer, Windsurf, Cline, and OpenCode project markers
|
|
165
|
-
- Gemini, Grok, DeepSeek, Qwen, Kimi, MiniMax, and GLM command/profile markers
|
|
166
|
-
- MCP config files
|
|
167
|
-
- Node projects
|
|
168
|
-
- Python projects
|
|
169
|
-
|
|
170
|
-
## Install Modes
|
|
171
|
-
|
|
172
|
-
```bash
|
|
173
|
-
npx @getmarrow/install --mcp --dry-run
|
|
174
|
-
npx @getmarrow/install --sdk --dry-run
|
|
175
|
-
npx @getmarrow/install --both --dry-run
|
|
176
|
-
npx @getmarrow/install --md --dry-run
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
`--dry-run` is the default unless `--yes` is passed.
|
|
180
|
-
|
|
181
|
-
## What It Writes
|
|
182
|
-
|
|
183
|
-
- `.claude/settings.json` passive MCP hooks for tool outcomes and prompt context.
|
|
184
|
-
- `.mcp.json` with the Marrow MCP server entry.
|
|
185
|
-
- `.marrow/passive-runtime.mjs` for SDK passive runtime preload in Node processes.
|
|
186
|
-
- `.marrow/env.example` with required environment variables.
|
|
187
|
-
- `AGENTS.md` instructions for agents that rely on markdown/skills.
|
|
188
|
-
- `.cursor/rules/marrow.mdc` when a Cursor project is detected.
|
|
189
|
-
|
|
190
|
-
## Self-Test
|
|
191
|
-
|
|
192
|
-
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, reads `/v1/agent/value/proof`, and prints the first useful Marrow signal plus token value proof.
|
|
193
|
-
|
|
194
|
-
```bash
|
|
195
|
-
MARROW_API_KEY=<your_marrow_key> npx @getmarrow/install --yes
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
Skip self-test:
|
|
199
|
-
|
|
200
|
-
```bash
|
|
201
|
-
npx @getmarrow/install --yes --no-self-test
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
Doctor check:
|
|
205
|
-
|
|
206
|
-
```bash
|
|
207
|
-
MARROW_API_KEY=<your_marrow_key> npx @getmarrow/install doctor
|
|
102
|
+
npx @getmarrow/install gate --agent deploy-agent --type deploy --action "deploy production"
|
|
103
|
+
npx @getmarrow/install status
|
|
104
|
+
npx @getmarrow/install doctor
|
|
105
|
+
npx @getmarrow/install --repair
|
|
208
106
|
```
|
|
209
107
|
|
|
210
|
-
|
|
108
|
+
## Fleet Operator TUI
|
|
211
109
|
|
|
212
110
|
```bash
|
|
213
|
-
|
|
111
|
+
npx @getmarrow/install fleet
|
|
214
112
|
```
|
|
215
113
|
|
|
216
|
-
|
|
114
|
+
The fleet view shows live agents, active workflows, risky actions waiting for proof, stale or failed outcomes, capture health, recent decisions, and exact repair commands. It is an operator surface for the authenticated account, not a public status dashboard.
|
|
217
115
|
|
|
218
|
-
|
|
116
|
+
## Integration Paths
|
|
219
117
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
118
|
+
| Path | Use it when | Owner effort |
|
|
119
|
+
| --- | --- | --- |
|
|
120
|
+
| Universal installer | You want Marrow to detect and wire the safest supported integration | Lowest |
|
|
121
|
+
| Governed runner | You need control around existing shell, CI, deploy, publish, merge, or migration commands | Low |
|
|
122
|
+
| MCP package | The agent client supports MCP and should use Marrow tools and hooks natively | Low |
|
|
123
|
+
| SDK | You own the Node.js/TypeScript runtime and need programmatic control | Advanced |
|
|
124
|
+
| Event contract | You have a custom harness that must map its lifecycle into Marrow | Advanced |
|
|
223
125
|
|
|
224
|
-
|
|
126
|
+
These are integration surfaces for one Marrow product, not separate products.
|
|
225
127
|
|
|
226
|
-
##
|
|
128
|
+
## Always-On Lifecycle
|
|
227
129
|
|
|
228
|
-
|
|
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.
|
|
229
131
|
|
|
230
|
-
|
|
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.
|
|
231
133
|
|
|
232
|
-
|
|
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.
|
|
233
135
|
|
|
234
|
-
|
|
235
|
-
- input, output, cached, and total token counts
|
|
236
|
-
- optional cost and latency estimates
|
|
237
|
-
- agent, workflow, session, and decision linkage
|
|
238
|
-
- Marrow intervention type, such as `runtime_gate`, `proof_pack`, or `before_you_act`
|
|
136
|
+
## Passive Token and Value Proof
|
|
239
137
|
|
|
240
|
-
Marrow
|
|
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.
|
|
241
139
|
|
|
242
|
-
|
|
140
|
+
After meaningful work, supported runtime and commit responses can return observed usage, trend direction, evidence confidence, and the next capture improvement. Savings are only reported when the available evidence supports them.
|
|
243
141
|
|
|
244
|
-
|
|
245
|
-
MARROW_PASSIVE_TOKEN_USAGE=false
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
Day-one behavior:
|
|
142
|
+
## Trust and Data Boundaries
|
|
249
143
|
|
|
250
|
-
-
|
|
251
|
-
-
|
|
252
|
-
-
|
|
144
|
+
- Private account, fleet, workflow, proof, and agent data remains tenant-scoped by default.
|
|
145
|
+
- Agent-bound keys can be restricted to an allowed identity and permission set.
|
|
146
|
+
- Sanitized aggregate contribution is optional and never means sharing raw prompts, code, secrets, proof packs, account identifiers, agent identifiers, or customer identities.
|
|
147
|
+
- The installer diagnoses key locations without printing secret values.
|
|
148
|
+
- Marrow returns guidance and policy data. Agents must not execute returned text as shell input.
|
|
253
149
|
|
|
150
|
+
See the [Trust Center](https://getmarrow.ai/trust/) for implemented controls, current limits, and roadmap status.
|
|
254
151
|
|
|
255
|
-
##
|
|
152
|
+
## Environment
|
|
256
153
|
|
|
257
|
-
|
|
154
|
+
| Variable | Required | Purpose |
|
|
155
|
+
| --- | --- | --- |
|
|
156
|
+
| `MARROW_API_KEY` | Yes for live verification | Account or agent-bound API key |
|
|
157
|
+
| `MARROW_BASE_URL` | No | API base override |
|
|
158
|
+
| `MARROW_FLEET_AGENT_ID` | No | Default agent identity |
|
|
258
159
|
|
|
259
|
-
|
|
160
|
+
Use the host's secret manager first. The shared resolver can also check documented Marrow and project env files for owned development environments. Run `doctor` when a key or hook cannot be found.
|
|
260
161
|
|
|
261
|
-
|
|
162
|
+
## Documentation
|
|
262
163
|
|
|
263
|
-
|
|
164
|
+
- [Source-of-truth docs](https://getmarrow.ai/docs/)
|
|
165
|
+
- [Trust Center](https://getmarrow.ai/trust/)
|
|
166
|
+
- [Status](https://getmarrow.ai/status/)
|
|
167
|
+
- [GitHub](https://github.com/getmarrow/marrow-install)
|
|
264
168
|
|
|
265
|
-
|
|
169
|
+
## License
|
|
266
170
|
|
|
267
|
-
|
|
171
|
+
MIT
|
|
268
172
|
|
|
269
173
|
## Related Packages
|
|
270
174
|
|
|
271
|
-
-
|
|
272
|
-
-
|
|
273
|
-
|
|
274
|
-
**Docs:** [https://getmarrow.ai/docs](https://getmarrow.ai/docs)
|
|
175
|
+
- [@getmarrow/sdk](https://www.npmjs.com/package/@getmarrow/sdk) - Node.js and TypeScript integration for owned agent runtimes
|
|
176
|
+
- [@getmarrow/mcp](https://www.npmjs.com/package/@getmarrow/mcp) - MCP-native integration for compatible agent clients
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getmarrow/install",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Universal installer for Marrow agent
|
|
3
|
+
"version": "0.1.29",
|
|
4
|
+
"description": "Universal installer and governed runner for Marrow agent fleets.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"marrow-install": "bin/marrow-install.js"
|
|
7
7
|
},
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
],
|
|
23
23
|
"repository": {
|
|
24
24
|
"type": "git",
|
|
25
|
-
"url": "https://github.com/getmarrow/marrow-install.git"
|
|
25
|
+
"url": "git+https://github.com/getmarrow/marrow-install.git"
|
|
26
26
|
},
|
|
27
27
|
"license": "MIT",
|
|
28
28
|
"files": [
|
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
|
};
|