@mnemom/mnemom 0.17.0-next.6 → 0.17.0-next.8
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 +26 -0
- package/dist/commands/agent-config.d.ts +29 -0
- package/dist/commands/agent-config.js +193 -0
- package/dist/commands/{code-doctor.d.ts → agent-doctor.d.ts} +2 -2
- package/dist/commands/{code-doctor.js → agent-doctor.js} +27 -22
- package/dist/commands/{code-invite.d.ts → agent-invite.d.ts} +18 -18
- package/dist/commands/{code-invite.js → agent-invite.js} +30 -30
- package/dist/commands/agent-session.d.ts +68 -0
- package/dist/commands/agent-session.js +114 -0
- package/dist/commands/{code-setup.d.ts → agent-setup.d.ts} +13 -13
- package/dist/commands/{code-setup.js → agent-setup.js} +46 -42
- package/dist/commands/{code.d.ts → agent.d.ts} +50 -14
- package/dist/commands/{code.js → agent.js} +144 -86
- package/dist/commands/agents.d.ts +2 -2
- package/dist/commands/agents.js +5 -5
- package/dist/commands/onboard.js +1 -1
- package/dist/commands/wrap.js +1 -1
- package/dist/index.js +59 -51
- package/dist/lib/{code.d.ts → agent.d.ts} +52 -10
- package/dist/lib/{code.js → agent.js} +94 -15
- package/dist/lib/api.d.ts +4 -4
- package/dist/lib/api.js +9 -9
- package/dist/lib/auth.js +21 -1
- package/dist/lib/{code-config.d.ts → launcher-config.d.ts} +25 -23
- package/dist/lib/{code-config.js → launcher-config.js} +50 -31
- package/dist/lib/oauth.d.ts +26 -4
- package/dist/lib/oauth.js +98 -29
- package/dist/lib/version-gate.js +1 -1
- package/dist/rc-proxy.mjs +309 -136
- package/package.json +7 -3
- package/dist/commands/code-config.d.ts +0 -17
- package/dist/commands/code-config.js +0 -147
- package/dist/commands/code-session.d.ts +0 -52
- package/dist/commands/code-session.js +0 -78
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `mnemom
|
|
2
|
+
* `mnemom agent <scenario>` — the CLI's first-class governed-coding-agent launcher.
|
|
3
3
|
*
|
|
4
4
|
* Launches your coding-agent CLI (Claude Code in v1) with its dev-time model
|
|
5
5
|
* traffic routed through the Mnemom gateway's /anthropic door for observability +
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* no router door / Mnemom key). `--cli` selects the executable to run (name or
|
|
12
12
|
* full path) so codex/cursor/github adapters can be added later.
|
|
13
13
|
* - The us-2/PROD gateway is the DEFAULT door (https://gateway.mnemom.ai); an
|
|
14
|
-
* explicit
|
|
14
|
+
* explicit MNEMOM_AGENT_GATEWAY / --gateway still wins, never silently prod.
|
|
15
15
|
* - Three launch shapes: `terminal` (DEFAULT), `remote-control` (interactive RC
|
|
16
16
|
* in this terminal), `server` (headless RC dispatcher). RC runs the CLI
|
|
17
17
|
* UNTOUCHED behind a bundled local TLS proxy (rc-proxy.mjs) — Claude Code
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* - yolo (`--dangerously-skip-permissions`) is ON by default; `--no-yolo` opts out.
|
|
21
21
|
*
|
|
22
22
|
* The Anthropic key is resolved from a credential source with ZERO 1Password
|
|
23
|
-
* dependency (env → the CLI's own ~/.mnemom/
|
|
23
|
+
* dependency (env → the CLI's own ~/.mnemom/launcher.json store → interactive
|
|
24
24
|
* prompt) and placed ONLY in the launched process (an `ANTHROPIC_CUSTOM_HEADERS`
|
|
25
25
|
* line, or the proxy's stdin under RC). It is never logged, printed, or passed as
|
|
26
26
|
* an argv value.
|
|
@@ -31,31 +31,41 @@ import { tmpdir, homedir } from "node:os";
|
|
|
31
31
|
import { basename, join, dirname } from "node:path";
|
|
32
32
|
import { fileURLToPath } from "node:url";
|
|
33
33
|
import { MNEMOM_DIR } from "../lib/config.js";
|
|
34
|
-
import { configKeySpec,
|
|
34
|
+
import { configKeySpec, loadLauncherConfig, writeLauncherConfig } from "../lib/launcher-config.js";
|
|
35
35
|
import { fmt } from "../lib/format.js";
|
|
36
|
-
import { askInput, askYesNo, isInteractive } from "../lib/prompt.js";
|
|
37
|
-
import { goalAlignmentLine, resolveSessionSetup } from "./
|
|
38
|
-
import { defaultAgentSlug, ensureGovernedAgent, ensureSessionAndBalance, isQuietPreambleExit, resolveDefaultSlug, } from "./
|
|
39
|
-
import { assembleContract, buildCustomHeaders, isProdGateway, launchPreferenceExplicit, resolveAnthropicDoor, resolveConversationId, resolveGatewayHost, resolveLaunchShape, sanitizeAgentName, } from "../lib/
|
|
40
|
-
/** The CLI's own provider-key store (zero 1Password): ~/.mnemom/
|
|
41
|
-
const CODE_STORE = join(MNEMOM_DIR, "
|
|
36
|
+
import { askInput, askSelect, askYesNo, isInteractive } from "../lib/prompt.js";
|
|
37
|
+
import { goalAlignmentLine, resolveSessionSetup } from "./agent-session.js";
|
|
38
|
+
import { defaultAgentSlug, ensureGovernedAgent, ensureSessionAndBalance, isQuietPreambleExit, resolveDefaultSlug, } from "./agent-setup.js";
|
|
39
|
+
import { assembleContract, buildGoalModeHeader, buildCustomHeaders, isProdGateway, launchPreferenceExplicit, resolveAnthropicDoor, resolveConversationId, resolveGatewayHost, resolveLaunchShape, sanitizeAgentName, } from "../lib/agent.js";
|
|
40
|
+
/** The CLI's own provider-key store (zero 1Password): ~/.mnemom/launcher.json, 0600. */
|
|
41
|
+
const CODE_STORE = join(MNEMOM_DIR, "launcher.json");
|
|
42
42
|
const say = (line = "") => console.error(line);
|
|
43
|
-
/** Entry point for `mnemom
|
|
44
|
-
export async function
|
|
45
|
-
// ── session name + goal ── (a bare `mnemom
|
|
46
|
-
// explaining goal alignment
|
|
47
|
-
|
|
43
|
+
/** Entry point for `mnemom agent [scenario]`. Throws on fatal misconfig. */
|
|
44
|
+
export async function agentCommand(scenarioArg, options = {}) {
|
|
45
|
+
// ── session name + goal ── (a bare `mnemom agent` in a terminal asks for both,
|
|
46
|
+
// explaining goal alignment: infer it from your messages (implicit), type a
|
|
47
|
+
// one-line goal (sealed), or off — no contract header and no mode header)
|
|
48
|
+
const session = await resolveSessionSetup({
|
|
49
|
+
scenario: scenarioArg,
|
|
50
|
+
goal: options.goal,
|
|
51
|
+
implicit: options.implicit,
|
|
52
|
+
interactive: isInteractive(),
|
|
53
|
+
}, {
|
|
54
|
+
askInput: (q) => askInput(q),
|
|
55
|
+
askYesNo: (q, d) => askYesNo(q, d),
|
|
56
|
+
askSelect: (q, o) => askSelect(q, o),
|
|
57
|
+
});
|
|
48
58
|
const scenario = session.scenario;
|
|
49
59
|
const env = process.env;
|
|
50
60
|
let shape = resolveLaunchShape({ remoteControl: options.remoteControl, server: options.server }, env);
|
|
51
61
|
const shapeExplicit = launchPreferenceExplicit({ remoteControl: options.remoteControl, server: options.server }, env);
|
|
52
|
-
// ── governed identity ── (default: per-customer `
|
|
62
|
+
// ── governed identity ── (default: per-customer `agent-<handle>`, not a shared
|
|
53
63
|
// "code-agent" — see defaultAgentSlug; provisioning claims exactly this slug.
|
|
54
64
|
// `let` because a brand-new user who signs in during the pre-launch gate below
|
|
55
65
|
// gets their real handle re-derived once the session exists.)
|
|
56
66
|
let agent = sanitizeAgentName(options.agent?.trim() || defaultAgentSlug());
|
|
57
67
|
if (!agent) {
|
|
58
|
-
throw new Error("mnemom
|
|
68
|
+
throw new Error("mnemom agent: --agent sanitised to an empty string — pick a value with at least one letter or digit.");
|
|
59
69
|
}
|
|
60
70
|
const conversationId = resolveConversationId(options.conversationId);
|
|
61
71
|
// ── contract (built only if a contract flag was given; --goal then required) ──
|
|
@@ -68,6 +78,15 @@ export async function codeCommand(scenarioArg, options = {}) {
|
|
|
68
78
|
budget: options.budget,
|
|
69
79
|
maxTurns: options.maxTurns,
|
|
70
80
|
stall: options.stall,
|
|
81
|
+
implicit: options.implicit,
|
|
82
|
+
});
|
|
83
|
+
// ── implicit goal mode (no contract; the gateway infers the goal from the user's
|
|
84
|
+
// messages) — the guardrail ceilings ride on the mode header instead ──
|
|
85
|
+
const goalMode = buildGoalModeHeader({
|
|
86
|
+
implicit: options.implicit,
|
|
87
|
+
budget: options.budget,
|
|
88
|
+
maxTurns: options.maxTurns,
|
|
89
|
+
stall: options.stall,
|
|
71
90
|
});
|
|
72
91
|
// ── door ──
|
|
73
92
|
const gatewayHost = resolveGatewayHost(env);
|
|
@@ -98,16 +117,16 @@ export async function codeCommand(scenarioArg, options = {}) {
|
|
|
98
117
|
shape = "remote-control";
|
|
99
118
|
}
|
|
100
119
|
else {
|
|
101
|
-
say(`mnemom
|
|
102
|
-
say("mnemom
|
|
120
|
+
say(`mnemom agent: launching terminal-only (Remote Control unavailable: ${rc.missing.join("; ")}).`);
|
|
121
|
+
say("mnemom agent: once those are in place, `mnemom agent <scenario> --remote-control` (or `config set launch remote-control`) gives you a session steerable from claude.ai and the Claude app too.");
|
|
103
122
|
}
|
|
104
123
|
};
|
|
105
124
|
if (cli)
|
|
106
125
|
applyAutoShape(cli);
|
|
107
126
|
if (options.dryRun) {
|
|
108
127
|
const planCli = cli ?? {
|
|
109
|
-
bin: "(not found — pass --cli <path> or `mnemom
|
|
110
|
-
name: basename((options.cli || env.
|
|
128
|
+
bin: "(not found — pass --cli <path> or `mnemom agent config set cli <path>`)",
|
|
129
|
+
name: basename((options.cli || env.MNEMOM_AGENT_CLI || "claude").trim()),
|
|
111
130
|
};
|
|
112
131
|
printPlan({
|
|
113
132
|
scenario,
|
|
@@ -117,6 +136,7 @@ export async function codeCommand(scenarioArg, options = {}) {
|
|
|
117
136
|
door,
|
|
118
137
|
cellNote,
|
|
119
138
|
envelope,
|
|
139
|
+
goalMode,
|
|
120
140
|
yolo,
|
|
121
141
|
cli: planCli,
|
|
122
142
|
});
|
|
@@ -135,7 +155,7 @@ export async function codeCommand(scenarioArg, options = {}) {
|
|
|
135
155
|
keySource: options.keySource,
|
|
136
156
|
});
|
|
137
157
|
// ── account + µ-balance gate (no free tier) ─────────────────────────────────
|
|
138
|
-
// mnemom
|
|
158
|
+
// mnemom agent runs governed and on µ, so before we seal a contract or launch we
|
|
139
159
|
// (1) ensure a Mnemom session — offering the try-me-style sign-in / sign-up when
|
|
140
160
|
// there isn't one — and (2) enforce a positive µ balance on the org the agent
|
|
141
161
|
// lives in (default: the user's personal org). No µ ⇒ blocked with a
|
|
@@ -150,7 +170,7 @@ export async function codeCommand(scenarioArg, options = {}) {
|
|
|
150
170
|
}
|
|
151
171
|
// Now that a session exists, a defaulted slug uses the real login handle from
|
|
152
172
|
// /v1/auth/me (the stored token has no email, so the pre-sign-in value was the
|
|
153
|
-
// "
|
|
173
|
+
// "agent-user" fallback). Only when the caller didn't name an agent.
|
|
154
174
|
if (!options.agent?.trim()) {
|
|
155
175
|
const refreshed = sanitizeAgentName(await resolveDefaultSlug());
|
|
156
176
|
if (refreshed)
|
|
@@ -172,11 +192,11 @@ export async function codeCommand(scenarioArg, options = {}) {
|
|
|
172
192
|
}
|
|
173
193
|
catch (err) {
|
|
174
194
|
const msg = err instanceof Error ? err.message : String(err);
|
|
175
|
-
say(fmt.warn(`mnemom
|
|
195
|
+
say(fmt.warn(`mnemom agent: agent setup did not complete (${msg}) — launching with the unclaimed identity.`));
|
|
176
196
|
}
|
|
177
197
|
}
|
|
178
|
-
logLaunch({ shape, door, agent, conversationId, cellNote, envelope, cli });
|
|
179
|
-
say(goalAlignmentLine(envelope
|
|
198
|
+
logLaunch({ shape, door, agent, conversationId, cellNote, envelope, goalMode, cli });
|
|
199
|
+
say(goalAlignmentLine(goalAlignmentMode(envelope, goalMode)));
|
|
180
200
|
if (shape === "terminal") {
|
|
181
201
|
launchTerminal({
|
|
182
202
|
cli,
|
|
@@ -185,6 +205,7 @@ export async function codeCommand(scenarioArg, options = {}) {
|
|
|
185
205
|
agent,
|
|
186
206
|
conversationId,
|
|
187
207
|
envelope,
|
|
208
|
+
goalMode,
|
|
188
209
|
options,
|
|
189
210
|
yolo,
|
|
190
211
|
contextHint,
|
|
@@ -200,6 +221,7 @@ export async function codeCommand(scenarioArg, options = {}) {
|
|
|
200
221
|
conversationId,
|
|
201
222
|
scenario,
|
|
202
223
|
envelope,
|
|
224
|
+
goalMode,
|
|
203
225
|
options,
|
|
204
226
|
yolo,
|
|
205
227
|
});
|
|
@@ -210,8 +232,8 @@ export async function codeCommand(scenarioArg, options = {}) {
|
|
|
210
232
|
/**
|
|
211
233
|
* Resolve the Anthropic key with NO 1Password dependency (built for external
|
|
212
234
|
* customer adoption), in order:
|
|
213
|
-
* 1. env:
|
|
214
|
-
* 2. the CLI's own store: ~/.mnemom/
|
|
235
|
+
* 1. env: MNEMOM_AGENT_ANTHROPIC_KEY, else ANTHROPIC_API_KEY
|
|
236
|
+
* 2. the CLI's own store: ~/.mnemom/launcher.json (`anthropic_key`)
|
|
215
237
|
* 3. interactive prompt (masked), offering to save it to the store
|
|
216
238
|
* Never logged / printed. Throws with guidance when nothing resolves — we refuse
|
|
217
239
|
* to launch un-routed.
|
|
@@ -220,42 +242,48 @@ export async function resolveAnthropicKey(env, opts) {
|
|
|
220
242
|
const source = opts.keySource;
|
|
221
243
|
// "prompt" forces the interactive prompt (skips env + store).
|
|
222
244
|
if (source !== "prompt") {
|
|
223
|
-
const fromEnv = (env.
|
|
245
|
+
const fromEnv = stripCrlf(env.MNEMOM_AGENT_ANTHROPIC_KEY || env.ANTHROPIC_API_KEY || "");
|
|
224
246
|
if (fromEnv)
|
|
225
247
|
return fromEnv;
|
|
226
248
|
// "env" restricts to env only — no store, no prompt.
|
|
227
249
|
if (source === "env") {
|
|
228
|
-
throw new Error("mnemom
|
|
229
|
-
" Set one: export
|
|
250
|
+
throw new Error("mnemom agent: key_source is 'env' but no Anthropic key is set.\n" +
|
|
251
|
+
" Set one: export MNEMOM_AGENT_ANTHROPIC_KEY=sk-ant-... (or ANTHROPIC_API_KEY)");
|
|
230
252
|
}
|
|
231
253
|
const stored = readStoredKey();
|
|
232
254
|
if (stored)
|
|
233
255
|
return stored;
|
|
256
|
+
// "store" restricts to env → store — never the interactive prompt.
|
|
257
|
+
if (source === "store") {
|
|
258
|
+
throw new Error("mnemom agent: key_source is 'store' but no Anthropic key is set in env or ~/.mnemom/launcher.json.\n" +
|
|
259
|
+
" Set one: export MNEMOM_AGENT_ANTHROPIC_KEY=sk-ant-... (or ANTHROPIC_API_KEY)\n" +
|
|
260
|
+
" Or store it: `mnemom agent config set key_source prompt`, run `mnemom agent` once and accept the save.");
|
|
261
|
+
}
|
|
234
262
|
}
|
|
235
|
-
// (source can only be "
|
|
263
|
+
// (source can only be "prompt" or undefined here — "env" and "store" threw above.)
|
|
236
264
|
if (opts.allowPrompt && isInteractive()) {
|
|
237
|
-
say("mnemom
|
|
238
|
-
const entered = (await askInput("Enter your Anthropic API key (sk-ant-…):", true))
|
|
265
|
+
say("mnemom agent: no Anthropic key in env (MNEMOM_AGENT_ANTHROPIC_KEY / ANTHROPIC_API_KEY) or ~/.mnemom/launcher.json.");
|
|
266
|
+
const entered = stripCrlf(await askInput("Enter your Anthropic API key (sk-ant-…):", true));
|
|
239
267
|
if (entered) {
|
|
240
|
-
if (await askYesNo("Save it to ~/.mnemom/
|
|
268
|
+
if (await askYesNo("Save it to ~/.mnemom/launcher.json (0600) for next time?", true)) {
|
|
241
269
|
storeKey(entered);
|
|
242
|
-
say("mnemom
|
|
270
|
+
say("mnemom agent: saved to ~/.mnemom/launcher.json.");
|
|
243
271
|
}
|
|
244
272
|
return entered;
|
|
245
273
|
}
|
|
246
274
|
}
|
|
247
|
-
throw new Error("mnemom
|
|
248
|
-
" Set one: export
|
|
249
|
-
" Or save it: run `mnemom
|
|
250
|
-
" (stored 0600 at ~/.mnemom/
|
|
275
|
+
throw new Error("mnemom agent: no Anthropic key found — refusing to launch un-routed.\n" +
|
|
276
|
+
" Set one: export MNEMOM_AGENT_ANTHROPIC_KEY=sk-ant-... (or ANTHROPIC_API_KEY)\n" +
|
|
277
|
+
" Or save it: run `mnemom agent` interactively and accept the save prompt\n" +
|
|
278
|
+
" (stored 0600 at ~/.mnemom/launcher.json).");
|
|
251
279
|
}
|
|
252
280
|
/**
|
|
253
281
|
* Where an Anthropic key would resolve from RIGHT NOW, without reading its value:
|
|
254
|
-
* "env" (
|
|
255
|
-
* or null (would fall to the interactive prompt). For `mnemom
|
|
282
|
+
* "env" (MNEMOM_AGENT_ANTHROPIC_KEY / ANTHROPIC_API_KEY), "store" (~/.mnemom/launcher.json),
|
|
283
|
+
* or null (would fall to the interactive prompt). For `mnemom agent doctor`.
|
|
256
284
|
*/
|
|
257
285
|
export function anthropicKeySource(env = process.env) {
|
|
258
|
-
if ((env.
|
|
286
|
+
if ((env.MNEMOM_AGENT_ANTHROPIC_KEY || env.ANTHROPIC_API_KEY || "").trim())
|
|
259
287
|
return "env";
|
|
260
288
|
if (readStoredKey())
|
|
261
289
|
return "store";
|
|
@@ -265,14 +293,14 @@ export function anthropicKeySource(env = process.env) {
|
|
|
265
293
|
function readStoredKey() {
|
|
266
294
|
try {
|
|
267
295
|
const data = JSON.parse(readFileSync(CODE_STORE, "utf8"));
|
|
268
|
-
const key = typeof data.anthropic_key === "string" ? data.anthropic_key
|
|
296
|
+
const key = typeof data.anthropic_key === "string" ? stripCrlf(data.anthropic_key) : "";
|
|
269
297
|
return key || undefined;
|
|
270
298
|
}
|
|
271
299
|
catch {
|
|
272
300
|
return undefined;
|
|
273
301
|
}
|
|
274
302
|
}
|
|
275
|
-
/** Persist the provider key to ~/.mnemom/
|
|
303
|
+
/** Persist the provider key to ~/.mnemom/launcher.json at 0600 (atomic). Best-effort. */
|
|
276
304
|
function storeKey(key) {
|
|
277
305
|
try {
|
|
278
306
|
mkdirSync(MNEMOM_DIR, { recursive: true });
|
|
@@ -285,7 +313,7 @@ function storeKey(key) {
|
|
|
285
313
|
}
|
|
286
314
|
}
|
|
287
315
|
/**
|
|
288
|
-
* Resolve which coding-agent CLI to launch. `--cli` (or
|
|
316
|
+
* Resolve which coding-agent CLI to launch. `--cli` (or MNEMOM_AGENT_CLI) is a
|
|
289
317
|
* name or a full path; default "claude". A value with a path separator is used
|
|
290
318
|
* verbatim (must exist); a bare name is resolved on PATH, then — for claude — the
|
|
291
319
|
* fnm/nvm/asdf install dirs (a version-manager shim / shell function is absent in
|
|
@@ -293,12 +321,12 @@ function storeKey(key) {
|
|
|
293
321
|
* a non-claude executable is launched through it with a warning.
|
|
294
322
|
*/
|
|
295
323
|
export function resolveCliBin(cliOpt, env) {
|
|
296
|
-
const requested = (cliOpt || env.
|
|
324
|
+
const requested = (cliOpt || env.MNEMOM_AGENT_CLI || "claude").trim();
|
|
297
325
|
const name = basename(requested);
|
|
298
326
|
if (requested.includes("/")) {
|
|
299
327
|
if (existsSync(requested))
|
|
300
328
|
return { bin: requested, name };
|
|
301
|
-
throw new Error(`mnemom
|
|
329
|
+
throw new Error(`mnemom agent: --cli path '${requested}' not found.`);
|
|
302
330
|
}
|
|
303
331
|
for (const dir of (env.PATH || "").split(":")) {
|
|
304
332
|
// Skip ephemeral cmux CLI shims: they only work inside a cmux session and,
|
|
@@ -334,10 +362,10 @@ export function resolveCliBin(cliOpt, env) {
|
|
|
334
362
|
}
|
|
335
363
|
}
|
|
336
364
|
}
|
|
337
|
-
throw new Error(`mnemom
|
|
365
|
+
throw new Error(`mnemom agent: coding-agent CLI '${requested}' not found.\n` +
|
|
338
366
|
" Looked on $PATH" +
|
|
339
367
|
(name === "claude" ? " and in the fnm/nvm/asdf node dirs" : "") +
|
|
340
|
-
".\n Pass --cli <name|/full/path> or set
|
|
368
|
+
".\n Pass --cli <name|/full/path> or set MNEMOM_AGENT_CLI.");
|
|
341
369
|
}
|
|
342
370
|
export /**
|
|
343
371
|
* Recover when the coding-agent CLI can't be found — a good experience beats a
|
|
@@ -346,11 +374,11 @@ export /**
|
|
|
346
374
|
* so they never retype it). A non-interactive session re-throws the clear,
|
|
347
375
|
* actionable error rather than hanging on a prompt.
|
|
348
376
|
*/ async function promptForCliPath(cliOpt, env, origErr) {
|
|
349
|
-
const requested = (cliOpt || env.
|
|
377
|
+
const requested = (cliOpt || env.MNEMOM_AGENT_CLI || "claude").trim();
|
|
350
378
|
if (!isInteractive()) {
|
|
351
|
-
throw origErr ?? new Error(`mnemom
|
|
379
|
+
throw origErr ?? new Error(`mnemom agent: coding-agent CLI '${requested}' not found.`);
|
|
352
380
|
}
|
|
353
|
-
say(`mnemom
|
|
381
|
+
say(`mnemom agent: couldn't find the '${requested}' CLI on your PATH.`);
|
|
354
382
|
if (requested === "claude") {
|
|
355
383
|
say(" If Claude Code is installed, paste its full path (find it with: which claude).");
|
|
356
384
|
say(" Not installed yet? Get it at https://claude.com/claude-code, then re-run.");
|
|
@@ -361,9 +389,9 @@ export /**
|
|
|
361
389
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
362
390
|
const entered = (await askInput("Path to your coding-agent CLI (Enter to cancel):")).trim();
|
|
363
391
|
if (!entered) {
|
|
364
|
-
throw new Error("mnemom
|
|
392
|
+
throw new Error("mnemom agent: no coding-agent CLI available.\n" +
|
|
365
393
|
" Install Claude Code (https://claude.com/claude-code), or pass --cli <path> " +
|
|
366
|
-
"(or save it: mnemom
|
|
394
|
+
"(or save it: mnemom agent config set cli <path>).");
|
|
367
395
|
}
|
|
368
396
|
if (!existsSync(entered)) {
|
|
369
397
|
say(` '${entered}' doesn't exist — try again.`);
|
|
@@ -371,9 +399,9 @@ export /**
|
|
|
371
399
|
}
|
|
372
400
|
if (await askYesNo("Save this as your default CLI so you don't retype it?", true)) {
|
|
373
401
|
try {
|
|
374
|
-
const cfg =
|
|
402
|
+
const cfg = loadLauncherConfig();
|
|
375
403
|
configKeySpec("cli").set(cfg, entered);
|
|
376
|
-
|
|
404
|
+
writeLauncherConfig(cfg);
|
|
377
405
|
say(" Saved — future launches will use it.");
|
|
378
406
|
}
|
|
379
407
|
catch {
|
|
@@ -382,7 +410,7 @@ export /**
|
|
|
382
410
|
}
|
|
383
411
|
return { bin: entered, name: basename(entered) };
|
|
384
412
|
}
|
|
385
|
-
throw new Error("mnemom
|
|
413
|
+
throw new Error("mnemom agent: couldn't resolve a coding-agent CLI path — pass --cli <path>.");
|
|
386
414
|
}
|
|
387
415
|
function commandExists(cmd) {
|
|
388
416
|
const res = spawnSync("sh", ["-c", `command -v "$1" >/dev/null 2>&1`, "sh", cmd], {
|
|
@@ -408,7 +436,7 @@ export function cliSupportsRemoteControl(bin) {
|
|
|
408
436
|
/**
|
|
409
437
|
* Can we launch the DEFAULT Remote-Control-in-terminal shape on this machine?
|
|
410
438
|
* Needs node + openssl (the local proxy mints an ephemeral CA) and a claude
|
|
411
|
-
* adapter that advertises `--remote-control`. Used both by `mnemom
|
|
439
|
+
* adapter that advertises `--remote-control`. Used both by `mnemom agent doctor`
|
|
412
440
|
* and by the auto-default selection (a bare launch prefers RC when capable).
|
|
413
441
|
*/
|
|
414
442
|
export function checkRcCapability(cli) {
|
|
@@ -429,7 +457,7 @@ export function checkRcCapability(cli) {
|
|
|
429
457
|
/** Warn once when a non-claude executable rides the v1 Claude Code adapter. */
|
|
430
458
|
function warnAdapter(cli) {
|
|
431
459
|
if (cli.name !== "claude") {
|
|
432
|
-
say(`mnemom
|
|
460
|
+
say(`mnemom agent: only the Claude Code adapter is wired in v1 — launching '${cli.name}' with the Anthropic gateway env. codex/cursor/github adapters are coming.`);
|
|
433
461
|
}
|
|
434
462
|
}
|
|
435
463
|
// ───────────────────────────────────────────────────────────────────────────
|
|
@@ -442,6 +470,7 @@ function launchTerminal(ctx) {
|
|
|
442
470
|
agent: ctx.agent,
|
|
443
471
|
conversationId: ctx.conversationId,
|
|
444
472
|
contractB64: ctx.envelope?.base64,
|
|
473
|
+
goalMode: ctx.goalMode ?? undefined,
|
|
445
474
|
});
|
|
446
475
|
const args = buildCliArgs(ctx.options, { yolo: ctx.yolo, allowCliFlags: true });
|
|
447
476
|
// Claude Code logged in via claude.ai (OAuth) PREEMPTS ANTHROPIC_API_KEY, so
|
|
@@ -459,7 +488,7 @@ function launchTerminal(ctx) {
|
|
|
459
488
|
}
|
|
460
489
|
const child = spawn(ctx.cli.bin, args, { stdio: "inherit", env: childEnv });
|
|
461
490
|
child.on("error", (err) => {
|
|
462
|
-
say(`mnemom
|
|
491
|
+
say(`mnemom agent: failed to launch ${ctx.cli.name}: ${err.message}`);
|
|
463
492
|
process.exit(1);
|
|
464
493
|
});
|
|
465
494
|
child.on("exit", (code, signal) => {
|
|
@@ -476,21 +505,22 @@ function launchRemoteControl(ctx) {
|
|
|
476
505
|
warnAdapter(ctx.cli);
|
|
477
506
|
for (const tool of ["node", "openssl"]) {
|
|
478
507
|
if (!commandExists(tool)) {
|
|
479
|
-
throw new Error(`mnemom
|
|
508
|
+
throw new Error(`mnemom agent: --remote-control needs '${tool}' on PATH (the local proxy is a Node script that mints an ephemeral CA with openssl).`);
|
|
480
509
|
}
|
|
481
510
|
}
|
|
482
511
|
const proxyScript = resolveProxyScript();
|
|
483
512
|
const rcDir = mkdtempSync(join(tmpdir(), "mnemom-code-rc."));
|
|
484
|
-
const proxyLog = process.env.
|
|
513
|
+
const proxyLog = process.env.MNEMOM_AGENT_RC_PROXY_LOG || join(rcDir, "proxy.log");
|
|
485
514
|
const proxy = spawn("node", [proxyScript], {
|
|
486
515
|
env: {
|
|
487
516
|
...process.env,
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
517
|
+
MNEMOM_AGENT_RC_DOOR: ctx.door,
|
|
518
|
+
MNEMOM_AGENT_RC_AGENT: ctx.agent,
|
|
519
|
+
MNEMOM_AGENT_RC_CONVERSATION_ID: ctx.conversationId,
|
|
520
|
+
MNEMOM_AGENT_RC_CONTRACT_B64: ctx.envelope?.base64 ?? "",
|
|
521
|
+
MNEMOM_AGENT_RC_GOAL_MODE: ctx.goalMode ?? "",
|
|
522
|
+
MNEMOM_AGENT_RC_PROXY_LOG: proxyLog,
|
|
523
|
+
MNEMOM_AGENT_RC_PARENT_PID: String(process.pid),
|
|
494
524
|
},
|
|
495
525
|
stdio: ["pipe", "pipe", "inherit"],
|
|
496
526
|
});
|
|
@@ -526,7 +556,7 @@ function launchRemoteControl(ctx) {
|
|
|
526
556
|
const port = m[1];
|
|
527
557
|
const ca = m[2].trim();
|
|
528
558
|
if (!existsSync(ca)) {
|
|
529
|
-
say("mnemom
|
|
559
|
+
say("mnemom agent: malformed READY line from the Remote Control proxy.");
|
|
530
560
|
onExit();
|
|
531
561
|
process.exit(1);
|
|
532
562
|
}
|
|
@@ -534,13 +564,13 @@ function launchRemoteControl(ctx) {
|
|
|
534
564
|
return;
|
|
535
565
|
}
|
|
536
566
|
if (proxy.exitCode !== null) {
|
|
537
|
-
say("mnemom
|
|
567
|
+
say("mnemom agent: the Remote Control proxy exited before it was ready — refusing to launch un-routed.");
|
|
538
568
|
redactedProxyLog(proxyLog);
|
|
539
569
|
onExit();
|
|
540
570
|
process.exit(1);
|
|
541
571
|
}
|
|
542
572
|
if (Date.now() - start > 15_000) {
|
|
543
|
-
say("mnemom
|
|
573
|
+
say("mnemom agent: the Remote Control proxy did not report READY within 15s.");
|
|
544
574
|
onExit();
|
|
545
575
|
process.exit(1);
|
|
546
576
|
}
|
|
@@ -550,7 +580,7 @@ function launchRemoteControl(ctx) {
|
|
|
550
580
|
stdoutBuf += chunk.toString("utf8");
|
|
551
581
|
});
|
|
552
582
|
proxy.on("error", (err) => {
|
|
553
|
-
say(`mnemom
|
|
583
|
+
say(`mnemom agent: failed to launch the Remote Control proxy: ${err.message}`);
|
|
554
584
|
onExit();
|
|
555
585
|
process.exit(1);
|
|
556
586
|
});
|
|
@@ -602,7 +632,7 @@ function spawnRcCli(ctx) {
|
|
|
602
632
|
if (ctx.options.model)
|
|
603
633
|
childEnv.ANTHROPIC_MODEL = ctx.options.model;
|
|
604
634
|
args = ["remote-control", "--name", rcName, ...passthrough];
|
|
605
|
-
say(`mnemom
|
|
635
|
+
say(`mnemom agent: remote-control: headless server (session name '${rcName}'); drive the sessions it spawns from claude.ai/code or the Claude app. Stop this command to stop the server.`);
|
|
606
636
|
}
|
|
607
637
|
else {
|
|
608
638
|
// Interactive RC in THIS terminal: the CLI's own `--remote-control <name>`
|
|
@@ -610,11 +640,11 @@ function spawnRcCli(ctx) {
|
|
|
610
640
|
// buildCliArgs already includes the (name-stripped) passthrough.
|
|
611
641
|
const flagArgs = buildCliArgs(scopedOptions, { yolo: ctx.yolo, allowCliFlags: true });
|
|
612
642
|
args = ["--remote-control", rcName, ...flagArgs];
|
|
613
|
-
say(`mnemom
|
|
643
|
+
say(`mnemom agent: remote-control: interactive session in THIS terminal with Remote Control on (session name '${rcName}'); it also appears under Remote Control at claude.ai/code and in the Claude app. Exit the CLI to stop everything.`);
|
|
614
644
|
}
|
|
615
645
|
const child = spawn(ctx.cli.bin, args, { stdio: "inherit", env: childEnv });
|
|
616
646
|
child.on("error", (err) => {
|
|
617
|
-
say(`mnemom
|
|
647
|
+
say(`mnemom agent: failed to launch ${ctx.cli.name} under Remote Control: ${err.message}`);
|
|
618
648
|
ctx.onExit();
|
|
619
649
|
process.exit(1);
|
|
620
650
|
});
|
|
@@ -628,20 +658,20 @@ function spawnRcCli(ctx) {
|
|
|
628
658
|
}
|
|
629
659
|
function warnServerDrops(options, yolo) {
|
|
630
660
|
if (options.effort) {
|
|
631
|
-
say("mnemom
|
|
661
|
+
say("mnemom agent: --effort is not applied under --server (set it from the session instead).");
|
|
632
662
|
}
|
|
633
663
|
if (options.trimMcp) {
|
|
634
|
-
say("mnemom
|
|
664
|
+
say("mnemom agent: --trim-mcp is not applied under --server (sessions load the full MCP set).");
|
|
635
665
|
}
|
|
636
666
|
if (yolo) {
|
|
637
|
-
say("mnemom
|
|
667
|
+
say("mnemom agent: yolo (--dangerously-skip-permissions) is not applied under --server; set permissions from the driven sessions.");
|
|
638
668
|
}
|
|
639
669
|
}
|
|
640
670
|
function resolveProxyScript() {
|
|
641
671
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
642
672
|
const cand = join(here, "..", "rc-proxy.mjs");
|
|
643
673
|
if (!existsSync(cand)) {
|
|
644
|
-
throw new Error(`mnemom
|
|
674
|
+
throw new Error(`mnemom agent: the Remote Control proxy is missing (expected ${cand}). Reinstall the CLI.`);
|
|
645
675
|
}
|
|
646
676
|
return cand;
|
|
647
677
|
}
|
|
@@ -720,16 +750,32 @@ function seedContextHintFlag(childEnv) {
|
|
|
720
750
|
// ───────────────────────────────────────────────────────────────────────────
|
|
721
751
|
// Human-facing logging (never prints the key)
|
|
722
752
|
// ───────────────────────────────────────────────────────────────────────────
|
|
723
|
-
|
|
724
|
-
|
|
753
|
+
/**
|
|
754
|
+
* Which goal-alignment mode the launch status line names: a sealed contract →
|
|
755
|
+
* "on"; `--implicit` (mode header, no contract) → "implicit"; neither → "off".
|
|
756
|
+
* A sealed contract and a mode header never coexist (`assembleContract` refuses
|
|
757
|
+
* the combination), so the first arm winning is not a precedence choice.
|
|
758
|
+
*/
|
|
759
|
+
export function goalAlignmentMode(envelope, goalMode) {
|
|
760
|
+
if (envelope)
|
|
761
|
+
return "on";
|
|
762
|
+
if (goalMode)
|
|
763
|
+
return "implicit";
|
|
764
|
+
return "off";
|
|
765
|
+
}
|
|
766
|
+
export function logLaunch(ctx) {
|
|
767
|
+
say(`mnemom agent: routing ${ctx.cli.name} through ${ctx.door} [${ctx.cellNote}] as '${ctx.agent}' (conversation ${ctx.conversationId}).`);
|
|
725
768
|
if (ctx.envelope) {
|
|
726
|
-
say(`mnemom
|
|
727
|
-
say(`mnemom
|
|
769
|
+
say(`mnemom agent: contract statement: ${ctx.envelope.contract.statement}`);
|
|
770
|
+
say(`mnemom agent: contract sha256[0:16]: ${ctx.envelope.sha}`);
|
|
771
|
+
}
|
|
772
|
+
else if (ctx.goalMode) {
|
|
773
|
+
say(`mnemom agent: goal mode: ${ctx.goalMode} (inferred from your messages; watch x-mnemom-goal-revision / x-mnemom-goal-steer on each response)`);
|
|
728
774
|
}
|
|
729
775
|
}
|
|
730
|
-
function printPlan(ctx) {
|
|
776
|
+
export function printPlan(ctx) {
|
|
731
777
|
const L = [];
|
|
732
|
-
L.push("mnemom
|
|
778
|
+
L.push("mnemom agent — launch plan (dry run)");
|
|
733
779
|
L.push(` scenario: ${ctx.scenario}`);
|
|
734
780
|
L.push(` cli: ${ctx.cli.name} (${ctx.cli.bin})`);
|
|
735
781
|
L.push(` shape: ${ctx.shape}`);
|
|
@@ -741,6 +787,10 @@ function printPlan(ctx) {
|
|
|
741
787
|
L.push(` contract: ${ctx.envelope.canonical}`);
|
|
742
788
|
L.push(` contract sha: ${ctx.envelope.sha}`);
|
|
743
789
|
}
|
|
790
|
+
else if (ctx.goalMode) {
|
|
791
|
+
L.push(" contract: (none — implicit goal mode: the gateway infers the goal from your messages)");
|
|
792
|
+
L.push(` goal mode: ${ctx.goalMode}`);
|
|
793
|
+
}
|
|
744
794
|
else {
|
|
745
795
|
L.push(" contract: (none — no contract flag given)");
|
|
746
796
|
}
|
|
@@ -748,3 +798,11 @@ function printPlan(ctx) {
|
|
|
748
798
|
L.push(" Dry run — no key was read and nothing was launched.");
|
|
749
799
|
process.stdout.write(`${L.join("\n")}\n`);
|
|
750
800
|
}
|
|
801
|
+
/**
|
|
802
|
+
* Header-value hygiene for the Anthropic key: the key is spliced into the newline-joined
|
|
803
|
+
* ANTHROPIC_CUSTOM_HEADERS block (`x-api-key: <key>`), so an embedded CR/LF from a multi-line
|
|
804
|
+
* paste would inject extra header lines. Same rule resolveConversationId applies to its input.
|
|
805
|
+
*/
|
|
806
|
+
function stripCrlf(raw) {
|
|
807
|
+
return raw.replace(/[\r\n]+/g, "").trim();
|
|
808
|
+
}
|
|
@@ -26,7 +26,7 @@ export interface AgentsClaimOptions {
|
|
|
26
26
|
*/
|
|
27
27
|
export declare function deriveHashProof(apiKey: string, agentName?: string): string;
|
|
28
28
|
/**
|
|
29
|
-
* `mnemom
|
|
29
|
+
* `mnemom agent claim <id-or-name> [--org <slug>] (--key <k> | --hash-proof <h>)`
|
|
30
30
|
*
|
|
31
31
|
* Claims an agent into an org (ADR-062). Possession of the agent's key (via
|
|
32
32
|
* `--key`, from which the CLI derives the proof) or a pre-computed
|
|
@@ -42,7 +42,7 @@ export interface AgentsMoveOptions {
|
|
|
42
42
|
json?: boolean;
|
|
43
43
|
}
|
|
44
44
|
/**
|
|
45
|
-
* `mnemom
|
|
45
|
+
* `mnemom agent move <id-or-name> --to <slug|id>`
|
|
46
46
|
*
|
|
47
47
|
* Role-based relocation between orgs (the counterpart to re-claim, which is
|
|
48
48
|
* possession-based). The server requires the caller to be an owner/admin of
|
package/dist/commands/agents.js
CHANGED
|
@@ -121,7 +121,7 @@ function extractClaimableOrgs(details) {
|
|
|
121
121
|
return list.filter((o) => !!o && typeof o === "object" && typeof o.org_id === "string");
|
|
122
122
|
}
|
|
123
123
|
/**
|
|
124
|
-
* `mnemom
|
|
124
|
+
* `mnemom agent claim <id-or-name> [--org <slug>] (--key <k> | --hash-proof <h>)`
|
|
125
125
|
*
|
|
126
126
|
* Claims an agent into an org (ADR-062). Possession of the agent's key (via
|
|
127
127
|
* `--key`, from which the CLI derives the proof) or a pre-computed
|
|
@@ -148,7 +148,7 @@ export async function agentsClaimCommand(idOrName, options = {}) {
|
|
|
148
148
|
// The arg names an agent we can't see yet (unclaimed agents aren't in
|
|
149
149
|
// your fleet), so we have no id to address the claim — guide to the id.
|
|
150
150
|
console.log(fmt.error(`Could not resolve '${idOrName}' to an agent id you can see yet — unclaimed agents are not in your fleet.`));
|
|
151
|
-
console.log(fmt.dim(` Pass the agent id directly: mnemom
|
|
151
|
+
console.log(fmt.dim(` Pass the agent id directly: mnemom agent claim mnm-... --key <key> [--name ${idOrName}]`) + "\n");
|
|
152
152
|
process.exit(1);
|
|
153
153
|
return;
|
|
154
154
|
}
|
|
@@ -286,7 +286,7 @@ export async function agentsClaimCommand(idOrName, options = {}) {
|
|
|
286
286
|
console.log(fmt.error("Failed to claim agent: the --key (and agent name) didn't match the agent's stored proof (invalid hash proof)."));
|
|
287
287
|
console.log(fmt.dim(` Confirm --key is the exact provider key the agent runs with.\n` +
|
|
288
288
|
` If the name couldn't be auto-resolved from the id, pass it explicitly:\n` +
|
|
289
|
-
` mnemom
|
|
289
|
+
` mnemom agent claim ${agentId} --key <key> --name <provisioned-name>`) + "\n");
|
|
290
290
|
process.exit(1);
|
|
291
291
|
return;
|
|
292
292
|
}
|
|
@@ -335,7 +335,7 @@ export async function agentsClaimCommand(idOrName, options = {}) {
|
|
|
335
335
|
console.log();
|
|
336
336
|
}
|
|
337
337
|
/**
|
|
338
|
-
* `mnemom
|
|
338
|
+
* `mnemom agent move <id-or-name> --to <slug|id>`
|
|
339
339
|
*
|
|
340
340
|
* Role-based relocation between orgs (the counterpart to re-claim, which is
|
|
341
341
|
* possession-based). The server requires the caller to be an owner/admin of
|
|
@@ -400,7 +400,7 @@ export async function agentsMoveCommand(idOrName, options = {}) {
|
|
|
400
400
|
catch (err) {
|
|
401
401
|
if (err instanceof MnemomApiError && err.effectiveStatus === 403) {
|
|
402
402
|
console.log(fmt.error("Move denied: you must be an owner/admin of BOTH the agent's current org and the destination org.") + "\n");
|
|
403
|
-
console.log(fmt.dim(" Check your roles with `mnemom org list`. If you hold the agent's key instead, re-claim can move it: mnemom
|
|
403
|
+
console.log(fmt.dim(" Check your roles with `mnemom org list`. If you hold the agent's key instead, re-claim can move it: mnemom agent claim <id> --org <slug> --key <key>.") + "\n");
|
|
404
404
|
process.exit(1);
|
|
405
405
|
return;
|
|
406
406
|
}
|
package/dist/commands/onboard.js
CHANGED
|
@@ -262,7 +262,7 @@ async function claimIdentity(agentId, scannedName, options, result, json, steps,
|
|
|
262
262
|
if (status === 403 &&
|
|
263
263
|
(err.code === "invalid_hash_proof" || /hash[ _]?proof/i.test(err.message))) {
|
|
264
264
|
throw teachingClaimError(agentId, "the --key (and agent name) didn't match the agent's stored proof (invalid hash proof).", "Confirm --key is the exact provider key the agent runs with. If the name couldn't be " +
|
|
265
|
-
`auto-resolved, pass it via \`mnemom
|
|
265
|
+
`auto-resolved, pass it via \`mnemom agent claim ${agentId} --key <key> --name <name>\`.`, json, result, steps);
|
|
266
266
|
}
|
|
267
267
|
if (status === 403 && err.code === "agent_cross_tenant") {
|
|
268
268
|
throw teachingClaimError(agentId, "this agent is already claimed by a different owner.", "Onboard an agent you own, or provision a fresh one with `mnemom wrap`.", json, result, steps);
|
package/dist/commands/wrap.js
CHANGED
|
@@ -323,7 +323,7 @@ function renderClaimInstructions(agentId) {
|
|
|
323
323
|
"",
|
|
324
324
|
fmt.dim(" Claim this agent so you own its identity on the platform:"),
|
|
325
325
|
"",
|
|
326
|
-
` mnemom
|
|
326
|
+
` mnemom agent claim ${agentId}`,
|
|
327
327
|
"",
|
|
328
328
|
fmt.dim(" After claiming, traces appear in `mnemom logs`."),
|
|
329
329
|
];
|