@mnemom/mnemom 0.17.0-next.0 → 0.17.0-next.2
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/dist/commands/code-setup.d.ts +8 -4
- package/dist/commands/code-setup.js +25 -22
- package/dist/commands/code.d.ts +1 -1
- package/dist/commands/code.js +87 -17
- package/package.json +1 -1
|
@@ -5,12 +5,16 @@
|
|
|
5
5
|
* If a customer names no agent, `mnemom code` launches under a per-customer
|
|
6
6
|
* governed identity `code-<handle>`. This module makes that identity REAL in
|
|
7
7
|
* their Mnemom org: it births the agent through the gateway, claims it (so the
|
|
8
|
-
* customer owns it), and publishes a sane default coding posture
|
|
9
|
-
*
|
|
8
|
+
* customer owns it), and publishes a sane default coding posture. The CARDS are
|
|
9
|
+
* observe-only (visibility, no active intervention); the goal contract is what
|
|
10
|
+
* nudges (re-anchor on drift). Specifically:
|
|
11
|
+
* - alignment card: autonomy_mode + integrity_mode = observe,
|
|
10
12
|
* principal.relationship = delegated_authority, coding bounded/forbidden
|
|
11
13
|
* actions, escalate-on-irreversible;
|
|
12
|
-
* - protection card: mode =
|
|
13
|
-
* screen_surfaces on.
|
|
14
|
+
* - protection card: mode = observe, thresholds 0.60/0.80/0.95, all four
|
|
15
|
+
* screen_surfaces on (screened for observation).
|
|
16
|
+
* - goal-alignment: nudge — sealed per-conversation via the goal contract
|
|
17
|
+
* (--goal …), graded and re-anchored by the gateway.
|
|
14
18
|
*
|
|
15
19
|
* All account writes are gated: interactive runs show the plan and confirm;
|
|
16
20
|
* non-interactive runs provision only with an explicit `--setup`, and otherwise
|
|
@@ -5,12 +5,16 @@
|
|
|
5
5
|
* If a customer names no agent, `mnemom code` launches under a per-customer
|
|
6
6
|
* governed identity `code-<handle>`. This module makes that identity REAL in
|
|
7
7
|
* their Mnemom org: it births the agent through the gateway, claims it (so the
|
|
8
|
-
* customer owns it), and publishes a sane default coding posture
|
|
9
|
-
*
|
|
8
|
+
* customer owns it), and publishes a sane default coding posture. The CARDS are
|
|
9
|
+
* observe-only (visibility, no active intervention); the goal contract is what
|
|
10
|
+
* nudges (re-anchor on drift). Specifically:
|
|
11
|
+
* - alignment card: autonomy_mode + integrity_mode = observe,
|
|
10
12
|
* principal.relationship = delegated_authority, coding bounded/forbidden
|
|
11
13
|
* actions, escalate-on-irreversible;
|
|
12
|
-
* - protection card: mode =
|
|
13
|
-
* screen_surfaces on.
|
|
14
|
+
* - protection card: mode = observe, thresholds 0.60/0.80/0.95, all four
|
|
15
|
+
* screen_surfaces on (screened for observation).
|
|
16
|
+
* - goal-alignment: nudge — sealed per-conversation via the goal contract
|
|
17
|
+
* (--goal …), graded and re-anchored by the gateway.
|
|
14
18
|
*
|
|
15
19
|
* All account writes are gated: interactive runs show the plan and confirm;
|
|
16
20
|
* non-interactive runs provision only with an explicit `--setup`, and otherwise
|
|
@@ -83,21 +87,21 @@ export async function ensureGovernedAgent(opts) {
|
|
|
83
87
|
say(fmt.section("Set up a governed coding agent"));
|
|
84
88
|
say(fmt.label(" Agent:", slug));
|
|
85
89
|
say(fmt.label(" Org:", orgLabel));
|
|
86
|
-
say(fmt.label(" Posture:", "
|
|
90
|
+
say(fmt.label(" Posture:", "observe (alignment + protection observe-only, all surfaces screened); goal contracts nudge"));
|
|
87
91
|
say(fmt.dim(" Births the agent through the gateway, claims it to your org, and publishes its cards."));
|
|
88
92
|
if (!(await askYesNo("Provision and claim this agent now?", true))) {
|
|
89
93
|
say(fmt.dim(" Skipped — launching under the unclaimed identity. Run `mnemom code setup` anytime."));
|
|
90
94
|
return { slug, provisioned: false };
|
|
91
95
|
}
|
|
92
96
|
}
|
|
93
|
-
// 5. Birth → claim → publish
|
|
97
|
+
// 5. Birth → claim → publish observe-only cards. (Key never logged.)
|
|
94
98
|
const gatewayUrl = opts.gatewayHost.replace(/\/+$/, "");
|
|
95
99
|
const agentId = await birthThroughGateway(gatewayUrl, "anthropic", slug, opts.anthropicKey);
|
|
96
100
|
const hashProof = deriveHashProof(opts.anthropicKey, slug);
|
|
97
101
|
await claimAgent(agentId, { hashProof, orgId: org?.org_id });
|
|
98
102
|
await putAlignmentCard(agentId, JSON.stringify(buildCodeAlignmentCard(agentId)), "application/json");
|
|
99
103
|
await putProtectionCard(agentId, JSON.stringify(buildCodeProtectionCard(agentId)), "application/json");
|
|
100
|
-
say(fmt.success(`mnemom code: provisioned + claimed '${slug}' (${agentId}) in ${orgLabel} —
|
|
104
|
+
say(fmt.success(`mnemom code: provisioned + claimed '${slug}' (${agentId}) in ${orgLabel} — observe posture published (goal contracts nudge).`));
|
|
101
105
|
// 6. Remember the slug so future launches skip straight to usable.
|
|
102
106
|
persistAgentSlug(slug);
|
|
103
107
|
return { slug, provisioned: true };
|
|
@@ -125,7 +129,7 @@ export async function codeSetupCommand(opts = {}) {
|
|
|
125
129
|
}
|
|
126
130
|
function warnUnclaimed(slug) {
|
|
127
131
|
say(fmt.warn(`mnemom code: agent '${slug}' is not set up in your org — launching with an UNCLAIMED identity (governance is advisory/fail-open).`));
|
|
128
|
-
say(fmt.dim(" Provision it (creates + claims the agent, publishes
|
|
132
|
+
say(fmt.dim(" Provision it (creates + claims the agent, publishes observe cards): mnemom code setup"));
|
|
129
133
|
}
|
|
130
134
|
function persistAgentSlug(slug) {
|
|
131
135
|
try {
|
|
@@ -145,7 +149,7 @@ function persistAgentSlug(slug) {
|
|
|
145
149
|
export class MuInsufficientError extends Error {
|
|
146
150
|
orgLabel;
|
|
147
151
|
constructor(orgLabel) {
|
|
148
|
-
super("insufficient
|
|
152
|
+
super("insufficient µ balance");
|
|
149
153
|
this.orgLabel = orgLabel;
|
|
150
154
|
this.name = "MuInsufficientError";
|
|
151
155
|
}
|
|
@@ -176,7 +180,7 @@ export async function ensureMnemomSession(opts = {}) {
|
|
|
176
180
|
return false;
|
|
177
181
|
say(fmt.section("Sign in to Mnemom"));
|
|
178
182
|
say(fmt.dim(" mnemom code runs your coding session governed and under your Mnemom account,"));
|
|
179
|
-
say(fmt.dim(" so it shows up in your traces and
|
|
183
|
+
say(fmt.dim(" so it shows up in your traces and draws on your org's µ balance."));
|
|
180
184
|
say(fmt.dim(` New to Mnemom? You can create a free account in the same flow (or at ${getWebsiteUrl()}/signup).`));
|
|
181
185
|
if (!(await askYesNo("Sign in or create your account now?", true)))
|
|
182
186
|
return false;
|
|
@@ -237,21 +241,20 @@ function safeBigInt(v) {
|
|
|
237
241
|
}
|
|
238
242
|
}
|
|
239
243
|
/**
|
|
240
|
-
* The "you need
|
|
244
|
+
* The "you need µ" message (no free tier). Voiced like the portal's
|
|
241
245
|
* enforcement-paused banner — safety features can't run without µ — but never
|
|
242
246
|
* says "exhausted" (that assumes a prior balance; a new org may simply have none).
|
|
243
247
|
*/
|
|
244
248
|
function printTopUp(orgLabel, balanceMu) {
|
|
245
249
|
say();
|
|
246
|
-
say(fmt.warn(`No
|
|
247
|
-
say(fmt.dim(" mnemom code runs on
|
|
248
|
-
say(fmt.dim("
|
|
249
|
-
say(fmt.dim(" a governed session:"));
|
|
250
|
+
say(fmt.warn(`No µ balance — ${orgLabel} is at ${balanceMu} µ.`));
|
|
251
|
+
say(fmt.dim(" mnemom code runs on µ, and there's no free tier. Without µ, Mnemom's safety"));
|
|
252
|
+
say(fmt.dim(" features can't run, so the launch is blocked. Top up to launch a governed session:"));
|
|
250
253
|
say();
|
|
251
254
|
say(` Top up µ: ${getWebsiteUrl()}/settings/billing`);
|
|
252
255
|
say(fmt.dim(" If you don't manage billing for this org, ask an admin to top up."));
|
|
253
256
|
say();
|
|
254
|
-
say(fmt.dim(" Your balance updates the moment a top-up completes — then re-run `mnemom code`."));
|
|
257
|
+
say(fmt.dim(" Your µ balance updates the moment a top-up completes — then re-run `mnemom code`."));
|
|
255
258
|
}
|
|
256
259
|
/**
|
|
257
260
|
* The shared pre-launch preamble for `mnemom code` (and `mnemom code setup`):
|
|
@@ -264,9 +267,9 @@ export async function ensureSessionAndBalance(opts) {
|
|
|
264
267
|
const interactive = opts.interactive ?? isInteractive();
|
|
265
268
|
const haveSession = await ensureMnemomSession({ interactive });
|
|
266
269
|
if (!haveSession) {
|
|
267
|
-
throw new Error("mnemom code needs a Mnemom account — it runs your coding session governed and on
|
|
270
|
+
throw new Error("mnemom code needs a Mnemom account — it runs your coding session governed and on your org's µ balance.\n" +
|
|
268
271
|
" Sign in: mnemom login (or set MNEMOM_API_KEY)\n" +
|
|
269
|
-
` New here? create a free account, then
|
|
272
|
+
` New here? create a free account, then top up µ: ${getWebsiteUrl()}/settings/billing`);
|
|
270
273
|
}
|
|
271
274
|
const active = getActiveOrg();
|
|
272
275
|
const orgId = active?.org_id ?? (await getMyPersonalOrg()).org_id;
|
|
@@ -274,13 +277,13 @@ export async function ensureSessionAndBalance(opts) {
|
|
|
274
277
|
await assertMuBalance(orgId, orgLabel);
|
|
275
278
|
return { orgId, orgLabel };
|
|
276
279
|
}
|
|
277
|
-
// ── The default coding posture (nudge
|
|
280
|
+
// ── The default coding posture (cards observe-only; goal contracts nudge) ────
|
|
278
281
|
function buildCodeAlignmentCard(agentId) {
|
|
279
282
|
return {
|
|
280
283
|
card_version: "unified/2026-04-26",
|
|
281
284
|
agent_id: agentId,
|
|
282
|
-
autonomy_mode: "
|
|
283
|
-
integrity_mode: "
|
|
285
|
+
autonomy_mode: "observe",
|
|
286
|
+
integrity_mode: "observe",
|
|
284
287
|
principal: { type: "agent", identifier: agentId, relationship: "delegated_authority" },
|
|
285
288
|
values: {
|
|
286
289
|
declared: ["transparency", "safety", "honesty", "user_control", "harm_prevention"],
|
|
@@ -317,7 +320,7 @@ function buildCodeProtectionCard(agentId) {
|
|
|
317
320
|
return {
|
|
318
321
|
card_version: "protection/2026-04-26",
|
|
319
322
|
agent_id: agentId,
|
|
320
|
-
mode: "
|
|
323
|
+
mode: "observe",
|
|
321
324
|
thresholds: { warn: 0.6, quarantine: 0.8, block: 0.95 },
|
|
322
325
|
screen_surfaces: {
|
|
323
326
|
incoming: true,
|
package/dist/commands/code.d.ts
CHANGED
|
@@ -100,7 +100,7 @@ export interface ResolvedCli {
|
|
|
100
100
|
* a non-claude executable is launched through it with a warning.
|
|
101
101
|
*/
|
|
102
102
|
export declare function resolveCliBin(cliOpt: string | undefined, env: NodeJS.ProcessEnv): ResolvedCli;
|
|
103
|
-
export declare function
|
|
103
|
+
export declare function promptForCliPath(cliOpt: string | undefined, env: NodeJS.ProcessEnv, origErr: Error | undefined): Promise<ResolvedCli>;
|
|
104
104
|
/**
|
|
105
105
|
* Does the resolved coding-agent CLI advertise the `--remote-control` startup
|
|
106
106
|
* flag (Claude Code >= 2.1.269)? Determined by scanning its `--help`. Best-effort:
|
package/dist/commands/code.js
CHANGED
|
@@ -31,6 +31,7 @@ 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
35
|
import { fmt } from "../lib/format.js";
|
|
35
36
|
import { askInput, askYesNo, isInteractive } from "../lib/prompt.js";
|
|
36
37
|
import { defaultAgentSlug, ensureGovernedAgent, ensureSessionAndBalance, MuInsufficientError, } from "./code-setup.js";
|
|
@@ -72,14 +73,25 @@ export async function codeCommand(scenario, options = {}) {
|
|
|
72
73
|
const cellNote = isProdGateway(env) ? "us-2/prod" : `custom (${gatewayHost})`;
|
|
73
74
|
const yolo = options.yolo !== false; // default ON
|
|
74
75
|
const contextHint = options.contextHint !== false; // default ON
|
|
75
|
-
// ── which CLI executable (name or full path) ──
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
76
|
+
// ── which CLI executable (name or full path) ── resolve GRACEFULLY: if it's not
|
|
77
|
+
// found we don't crash with a technical error — we ask for the path at launch.
|
|
78
|
+
let cli;
|
|
79
|
+
let cliError;
|
|
80
|
+
try {
|
|
81
|
+
cli = resolveCliBin(options.cli, env);
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
cliError = err instanceof Error ? err : new Error(String(err));
|
|
85
|
+
}
|
|
86
|
+
// Default launch shape: prefer Remote-Control-in-terminal (the good default —
|
|
87
|
+
// one session steerable from this terminal AND claude.ai/the app) when the
|
|
88
|
+
// machine can do it; otherwise terminal-only with a clear notice. An explicit
|
|
89
|
+
// choice (flag / env / config launch=) is always respected. Needs a resolved
|
|
90
|
+
// CLI to test RC capability, so it's applied once the CLI is known.
|
|
91
|
+
const applyAutoShape = (resolved) => {
|
|
92
|
+
if (shapeExplicit || shape !== "terminal")
|
|
93
|
+
return;
|
|
94
|
+
const rc = checkRcCapability(resolved);
|
|
83
95
|
if (rc.ok) {
|
|
84
96
|
shape = "remote-control";
|
|
85
97
|
}
|
|
@@ -87,22 +99,35 @@ export async function codeCommand(scenario, options = {}) {
|
|
|
87
99
|
say(`mnemom code: launching terminal-only (Remote Control unavailable: ${rc.missing.join("; ")}).`);
|
|
88
100
|
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.");
|
|
89
101
|
}
|
|
90
|
-
}
|
|
102
|
+
};
|
|
103
|
+
if (cli)
|
|
104
|
+
applyAutoShape(cli);
|
|
91
105
|
if (options.dryRun) {
|
|
92
|
-
|
|
106
|
+
const planCli = cli ?? {
|
|
107
|
+
bin: "(not found — pass --cli <path> or `mnemom code config set cli <path>`)",
|
|
108
|
+
name: basename((options.cli || env.MNEMOM_CODE_CLI || "claude").trim()),
|
|
109
|
+
};
|
|
110
|
+
printPlan({ scenario, shape, agent, conversationId, door, cellNote, envelope, yolo, cli: planCli });
|
|
93
111
|
return;
|
|
94
112
|
}
|
|
113
|
+
// Real launch: make sure we actually have a CLI to run. If we couldn't find it,
|
|
114
|
+
// ask for the path (a good experience beats a hard technical error). A
|
|
115
|
+
// non-interactive session still gets the clear, actionable error.
|
|
116
|
+
if (!cli) {
|
|
117
|
+
cli = await promptForCliPath(options.cli, env, cliError);
|
|
118
|
+
applyAutoShape(cli);
|
|
119
|
+
}
|
|
95
120
|
// ── resolve the Anthropic key (zero 1Password; never logged) ──
|
|
96
121
|
const anthropicKey = await resolveAnthropicKey(env, {
|
|
97
122
|
allowPrompt: true,
|
|
98
123
|
keySource: options.keySource,
|
|
99
124
|
});
|
|
100
|
-
// ── account +
|
|
101
|
-
// mnemom code runs governed and on
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
//
|
|
125
|
+
// ── account + µ-balance gate (no free tier) ─────────────────────────────────
|
|
126
|
+
// mnemom code runs governed and on µ, so before we seal a contract or launch we
|
|
127
|
+
// (1) ensure a Mnemom session — offering the try-me-style sign-in / sign-up when
|
|
128
|
+
// there isn't one — and (2) enforce a positive µ balance on the org the agent
|
|
129
|
+
// lives in (default: the user's personal org). No µ ⇒ blocked with a
|
|
130
|
+
// portal-style top-up message. Skipped only on --dry-run (handled above).
|
|
106
131
|
try {
|
|
107
132
|
await ensureSessionAndBalance({ interactive: isInteractive() });
|
|
108
133
|
}
|
|
@@ -295,7 +320,52 @@ export function resolveCliBin(cliOpt, env) {
|
|
|
295
320
|
(name === "claude" ? " and in the fnm/nvm/asdf node dirs" : "") +
|
|
296
321
|
".\n Pass --cli <name|/full/path> or set MNEMOM_CODE_CLI.");
|
|
297
322
|
}
|
|
298
|
-
export
|
|
323
|
+
export /**
|
|
324
|
+
* Recover when the coding-agent CLI can't be found — a good experience beats a
|
|
325
|
+
* hard technical error. In an interactive session, tell the customer we couldn't
|
|
326
|
+
* find it and let them paste the full path (offering to save it as their default
|
|
327
|
+
* so they never retype it). A non-interactive session re-throws the clear,
|
|
328
|
+
* actionable error rather than hanging on a prompt.
|
|
329
|
+
*/ async function promptForCliPath(cliOpt, env, origErr) {
|
|
330
|
+
const requested = (cliOpt || env.MNEMOM_CODE_CLI || "claude").trim();
|
|
331
|
+
if (!isInteractive()) {
|
|
332
|
+
throw origErr ?? new Error(`mnemom code: coding-agent CLI '${requested}' not found.`);
|
|
333
|
+
}
|
|
334
|
+
say(`mnemom code: couldn't find the '${requested}' CLI on your PATH.`);
|
|
335
|
+
if (requested === "claude") {
|
|
336
|
+
say(" If Claude Code is installed, paste its full path (find it with: which claude).");
|
|
337
|
+
say(" Not installed yet? Get it at https://claude.com/claude-code, then re-run.");
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
say(" Paste its full path, or install it and re-run.");
|
|
341
|
+
}
|
|
342
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
343
|
+
const entered = (await askInput("Path to your coding-agent CLI (Enter to cancel):")).trim();
|
|
344
|
+
if (!entered) {
|
|
345
|
+
throw new Error("mnemom code: no coding-agent CLI available.\n" +
|
|
346
|
+
" Install Claude Code (https://claude.com/claude-code), or pass --cli <path> " +
|
|
347
|
+
"(or save it: mnemom code config set cli <path>).");
|
|
348
|
+
}
|
|
349
|
+
if (!existsSync(entered)) {
|
|
350
|
+
say(` '${entered}' doesn't exist — try again.`);
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
if (await askYesNo("Save this as your default CLI so you don't retype it?", true)) {
|
|
354
|
+
try {
|
|
355
|
+
const cfg = loadCodeConfig();
|
|
356
|
+
configKeySpec("cli").set(cfg, entered);
|
|
357
|
+
writeCodeConfig(cfg);
|
|
358
|
+
say(" Saved — future launches will use it.");
|
|
359
|
+
}
|
|
360
|
+
catch {
|
|
361
|
+
// best effort — the launch proceeds with the entered path either way
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return { bin: entered, name: basename(entered) };
|
|
365
|
+
}
|
|
366
|
+
throw new Error("mnemom code: couldn't resolve a coding-agent CLI path — pass --cli <path>.");
|
|
367
|
+
}
|
|
368
|
+
function commandExists(cmd) {
|
|
299
369
|
const res = spawnSync("sh", ["-c", `command -v "$1" >/dev/null 2>&1`, "sh", cmd], {
|
|
300
370
|
stdio: "ignore",
|
|
301
371
|
});
|