@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.
Files changed (34) hide show
  1. package/README.md +26 -0
  2. package/dist/commands/agent-config.d.ts +29 -0
  3. package/dist/commands/agent-config.js +193 -0
  4. package/dist/commands/{code-doctor.d.ts → agent-doctor.d.ts} +2 -2
  5. package/dist/commands/{code-doctor.js → agent-doctor.js} +27 -22
  6. package/dist/commands/{code-invite.d.ts → agent-invite.d.ts} +18 -18
  7. package/dist/commands/{code-invite.js → agent-invite.js} +30 -30
  8. package/dist/commands/agent-session.d.ts +68 -0
  9. package/dist/commands/agent-session.js +114 -0
  10. package/dist/commands/{code-setup.d.ts → agent-setup.d.ts} +13 -13
  11. package/dist/commands/{code-setup.js → agent-setup.js} +46 -42
  12. package/dist/commands/{code.d.ts → agent.d.ts} +50 -14
  13. package/dist/commands/{code.js → agent.js} +144 -86
  14. package/dist/commands/agents.d.ts +2 -2
  15. package/dist/commands/agents.js +5 -5
  16. package/dist/commands/onboard.js +1 -1
  17. package/dist/commands/wrap.js +1 -1
  18. package/dist/index.js +59 -51
  19. package/dist/lib/{code.d.ts → agent.d.ts} +52 -10
  20. package/dist/lib/{code.js → agent.js} +94 -15
  21. package/dist/lib/api.d.ts +4 -4
  22. package/dist/lib/api.js +9 -9
  23. package/dist/lib/auth.js +21 -1
  24. package/dist/lib/{code-config.d.ts → launcher-config.d.ts} +25 -23
  25. package/dist/lib/{code-config.js → launcher-config.js} +50 -31
  26. package/dist/lib/oauth.d.ts +26 -4
  27. package/dist/lib/oauth.js +98 -29
  28. package/dist/lib/version-gate.js +1 -1
  29. package/dist/rc-proxy.mjs +309 -136
  30. package/package.json +7 -3
  31. package/dist/commands/code-config.d.ts +0 -17
  32. package/dist/commands/code-config.js +0 -147
  33. package/dist/commands/code-session.d.ts +0 -52
  34. package/dist/commands/code-session.js +0 -78
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `mnemom code <scenario>` — the CLI's first-class governed-coding-agent launcher.
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 MNEMOM_CODE_GATEWAY / --gateway still wins, never silently prod.
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/code.json store → interactive
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, loadCodeConfig, writeCodeConfig } from "../lib/code-config.js";
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 "./code-session.js";
38
- import { defaultAgentSlug, ensureGovernedAgent, ensureSessionAndBalance, isQuietPreambleExit, resolveDefaultSlug, } from "./code-setup.js";
39
- import { assembleContract, buildCustomHeaders, isProdGateway, launchPreferenceExplicit, resolveAnthropicDoor, resolveConversationId, resolveGatewayHost, resolveLaunchShape, sanitizeAgentName, } from "../lib/code.js";
40
- /** The CLI's own provider-key store (zero 1Password): ~/.mnemom/code.json, 0600. */
41
- const CODE_STORE = join(MNEMOM_DIR, "code.json");
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 code [scenario]`. Throws on fatal misconfig. */
44
- export async function codeCommand(scenarioArg, options = {}) {
45
- // ── session name + goal ── (a bare `mnemom code` in a terminal asks for both,
46
- // explaining goal alignment; no goal ⇒ no contract header ⇒ goal alignment off)
47
- const session = await resolveSessionSetup({ scenario: scenarioArg, goal: options.goal, interactive: isInteractive() }, { askInput: (q) => askInput(q), askYesNo: (q, d) => askYesNo(q, d) });
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 `code-<handle>`, not a shared
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 code: --agent sanitised to an empty string — pick a value with at least one letter or digit.");
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 code: launching terminal-only (Remote Control unavailable: ${rc.missing.join("; ")}).`);
102
- say("mnemom code: once those are in place, `mnemom code <scenario> --remote-control` (or `config set launch remote-control`) gives you a session steerable from claude.ai and the Claude app too.");
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 code config set cli <path>`)",
110
- name: basename((options.cli || env.MNEMOM_CODE_CLI || "claude").trim()),
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 code runs governed and on µ, so before we seal a contract or launch we
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
- // "code-user" fallback). Only when the caller didn't name an agent.
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 code: agent setup did not complete (${msg}) — launching with the unclaimed identity.`));
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 !== null));
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: MNEMOM_CODE_ANTHROPIC_KEY, else ANTHROPIC_API_KEY
214
- * 2. the CLI's own store: ~/.mnemom/code.json (`anthropic_key`)
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.MNEMOM_CODE_ANTHROPIC_KEY || env.ANTHROPIC_API_KEY || "").trim();
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 code: key_source is 'env' but no Anthropic key is set.\n" +
229
- " Set one: export MNEMOM_CODE_ANTHROPIC_KEY=sk-ant-... (or ANTHROPIC_API_KEY)");
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 "store", "prompt", or undefined here — "env" threw above.)
263
+ // (source can only be "prompt" or undefined here — "env" and "store" threw above.)
236
264
  if (opts.allowPrompt && isInteractive()) {
237
- say("mnemom code: no Anthropic key in env (MNEMOM_CODE_ANTHROPIC_KEY / ANTHROPIC_API_KEY) or ~/.mnemom/code.json.");
238
- const entered = (await askInput("Enter your Anthropic API key (sk-ant-…):", true)).trim();
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/code.json (0600) for next time?", true)) {
268
+ if (await askYesNo("Save it to ~/.mnemom/launcher.json (0600) for next time?", true)) {
241
269
  storeKey(entered);
242
- say("mnemom code: saved to ~/.mnemom/code.json.");
270
+ say("mnemom agent: saved to ~/.mnemom/launcher.json.");
243
271
  }
244
272
  return entered;
245
273
  }
246
274
  }
247
- throw new Error("mnemom code: no Anthropic key found — refusing to launch un-routed.\n" +
248
- " Set one: export MNEMOM_CODE_ANTHROPIC_KEY=sk-ant-... (or ANTHROPIC_API_KEY)\n" +
249
- " Or save it: run `mnemom code` interactively and accept the save prompt\n" +
250
- " (stored 0600 at ~/.mnemom/code.json).");
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" (MNEMOM_CODE_ANTHROPIC_KEY / ANTHROPIC_API_KEY), "store" (~/.mnemom/code.json),
255
- * or null (would fall to the interactive prompt). For `mnemom code doctor`.
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.MNEMOM_CODE_ANTHROPIC_KEY || env.ANTHROPIC_API_KEY || "").trim())
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.trim() : "";
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/code.json at 0600 (atomic). Best-effort. */
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 MNEMOM_CODE_CLI) is a
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.MNEMOM_CODE_CLI || "claude").trim();
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 code: --cli path '${requested}' not found.`);
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 code: coding-agent CLI '${requested}' not found.\n` +
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 MNEMOM_CODE_CLI.");
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.MNEMOM_CODE_CLI || "claude").trim();
377
+ const requested = (cliOpt || env.MNEMOM_AGENT_CLI || "claude").trim();
350
378
  if (!isInteractive()) {
351
- throw origErr ?? new Error(`mnemom code: coding-agent CLI '${requested}' not found.`);
379
+ throw origErr ?? new Error(`mnemom agent: coding-agent CLI '${requested}' not found.`);
352
380
  }
353
- say(`mnemom code: couldn't find the '${requested}' CLI on your PATH.`);
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 code: no coding-agent CLI available.\n" +
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 code config set cli <path>).");
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 = loadCodeConfig();
402
+ const cfg = loadLauncherConfig();
375
403
  configKeySpec("cli").set(cfg, entered);
376
- writeCodeConfig(cfg);
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 code: couldn't resolve a coding-agent CLI path — pass --cli <path>.");
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 code doctor`
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 code: only the Claude Code adapter is wired in v1 — launching '${cli.name}' with the Anthropic gateway env. codex/cursor/github adapters are coming.`);
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 code: failed to launch ${ctx.cli.name}: ${err.message}`);
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 code: --remote-control needs '${tool}' on PATH (the local proxy is a Node script that mints an ephemeral CA with openssl).`);
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.MNEMOM_CODE_RC_PROXY_LOG || join(rcDir, "proxy.log");
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
- MNEMOM_CODE_RC_DOOR: ctx.door,
489
- MNEMOM_CODE_RC_AGENT: ctx.agent,
490
- MNEMOM_CODE_RC_CONVERSATION_ID: ctx.conversationId,
491
- MNEMOM_CODE_RC_CONTRACT_B64: ctx.envelope?.base64 ?? "",
492
- MNEMOM_CODE_RC_PROXY_LOG: proxyLog,
493
- MNEMOM_CODE_RC_PARENT_PID: String(process.pid),
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 code: malformed READY line from the Remote Control proxy.");
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 code: the Remote Control proxy exited before it was ready — refusing to launch un-routed.");
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 code: the Remote Control proxy did not report READY within 15s.");
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 code: failed to launch the Remote Control proxy: ${err.message}`);
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 code: 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.`);
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 code: 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.`);
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 code: failed to launch ${ctx.cli.name} under Remote Control: ${err.message}`);
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 code: --effort is not applied under --server (set it from the session instead).");
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 code: --trim-mcp is not applied under --server (sessions load the full MCP set).");
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 code: yolo (--dangerously-skip-permissions) is not applied under --server; set permissions from the driven sessions.");
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 code: the Remote Control proxy is missing (expected ${cand}). Reinstall the CLI.`);
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
- function logLaunch(ctx) {
724
- say(`mnemom code: routing ${ctx.cli.name} through ${ctx.door} [${ctx.cellNote}] as '${ctx.agent}' (conversation ${ctx.conversationId}).`);
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 code: contract statement: ${ctx.envelope.contract.statement}`);
727
- say(`mnemom code: contract sha256[0:16]: ${ctx.envelope.sha}`);
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 code — launch plan (dry run)");
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 agents claim <id-or-name> [--org <slug>] (--key <k> | --hash-proof <h>)`
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 agents move <id-or-name> --to <slug|id>`
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
@@ -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 agents claim <id-or-name> [--org <slug>] (--key <k> | --hash-proof <h>)`
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 agents claim mnm-... --key <key> [--name ${idOrName}]`) + "\n");
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 agents claim ${agentId} --key <key> --name <provisioned-name>`) + "\n");
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 agents move <id-or-name> --to <slug|id>`
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 agents claim <id> --org <slug> --key <key>.") + "\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 agent claim <id> --org <slug> --key <key>.") + "\n");
404
404
  process.exit(1);
405
405
  return;
406
406
  }
@@ -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 agents claim ${agentId} --key <key> --name <name>\`.`, json, result, steps);
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);
@@ -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 agents claim ${agentId}`,
326
+ ` mnemom agent claim ${agentId}`,
327
327
  "",
328
328
  fmt.dim(" After claiming, traces appear in `mnemom logs`."),
329
329
  ];