@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
package/README.md
CHANGED
|
@@ -44,6 +44,32 @@ That's it. `mnemom init` detects your configured AI provider API keys (Anthropic
|
|
|
44
44
|
| `mnemom card publish <file>` | Publish alignment card from JSON file |
|
|
45
45
|
| `mnemom card validate <file>` | Validate card JSON locally |
|
|
46
46
|
| `mnemom usage --org <id>` | Show per-person token and request consumption |
|
|
47
|
+
| `mnemom agent [name]` | Launch a governed coding agent through the gateway |
|
|
48
|
+
|
|
49
|
+
### `mnemom agent` — goal alignment
|
|
50
|
+
|
|
51
|
+
`mnemom agent` launches your coding-agent CLI (Claude Code today) with its model traffic
|
|
52
|
+
routed through the Mnemom gateway under a governed identity. Goal alignment — the judge
|
|
53
|
+
nudging the agent back when it drifts from your goal — comes in three modes; a bare
|
|
54
|
+
`mnemom agent` in a terminal asks which one you want (Enter = off, for now):
|
|
55
|
+
|
|
56
|
+
| Mode | How | What is sent |
|
|
57
|
+
| ------------ | -------------------------------------------------------------------------------------------------------------- | ------------------------------------------- |
|
|
58
|
+
| **Implicit** | `--implicit` — the gateway infers the goal from your own messages and revises it as you steer; nothing to type | `x-mnemom-goal-mode: implicit`, no contract |
|
|
59
|
+
| **Sealed** | `--goal "<statement>"` (+ `--requirement/--allow/--forbid/--goal-id`) — sealed as a contract on the first turn | `x-mnemom-contract` |
|
|
60
|
+
| **Off** | neither | neither header |
|
|
61
|
+
|
|
62
|
+
`--implicit` cannot be combined with the sealed-contract flags: a sealed contract always
|
|
63
|
+
wins on the gateway and would silently switch implicit mode off, so the launch refuses the
|
|
64
|
+
mix. The guardrail ceilings (`--budget`, `--max-turns`, `--stall`, or `guardrails.*` in
|
|
65
|
+
`~/.mnemom/launcher.toml`) apply in both modes — in implicit mode they ride on the mode
|
|
66
|
+
header. In implicit mode the goal lags your steering by one request: a response to a new
|
|
67
|
+
message carries `x-mnemom-goal-steer: queued` and the revision the agent acted under;
|
|
68
|
+
the next request reads the new revision (`x-mnemom-goal-revision`).
|
|
69
|
+
|
|
70
|
+
Make one mode the standing default with `mnemom agent config set goal_mode implicit`
|
|
71
|
+
(or `off`); `--goal` still seals for that launch and `--no-implicit` turns it off for one
|
|
72
|
+
launch. A sealed contract needs a per-launch statement, so it cannot be the default.
|
|
47
73
|
|
|
48
74
|
## How It Works
|
|
49
75
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mnemom agent config …` — read and write the persistent per-customer settings
|
|
3
|
+
* for `mnemom agent` (see lib/code-config.ts). A sub-verb of `mnemom agent`, so a
|
|
4
|
+
* customer configures the launcher once and never retypes their flags:
|
|
5
|
+
*
|
|
6
|
+
* mnemom agent config list # show the current settings
|
|
7
|
+
* mnemom agent config get <key> # print one value
|
|
8
|
+
* mnemom agent config set <key> <value> # validate + persist one value
|
|
9
|
+
* mnemom agent config unset <key> # remove one value
|
|
10
|
+
* mnemom agent config edit # open ~/.mnemom/launcher.toml in $EDITOR
|
|
11
|
+
* mnemom agent config path # print the settings file path
|
|
12
|
+
*
|
|
13
|
+
* Settable keys are the single source of truth in lib/code-config.ts CONFIG_KEYS.
|
|
14
|
+
* The secret Anthropic key is NOT a config key — it lives in ~/.mnemom/launcher.json.
|
|
15
|
+
*/
|
|
16
|
+
/** Entry point for `mnemom agent config <verb> [args...]`. Throws on misuse. */
|
|
17
|
+
export declare function agentConfigCommand(argv: string[]): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* `$VISUAL` / `$EDITOR` is a program plus optional plain flags (`code --wait`, `emacsclient -t`),
|
|
20
|
+
* never shell syntax: the value is split on whitespace and every token must be a bare
|
|
21
|
+
* program name, path, or flag. Anything else (quotes, `;`, `$(`, backticks, redirections, …) is
|
|
22
|
+
* refused with a remediation rather than handed to spawnSync — the editor is launched WITHOUT a
|
|
23
|
+
* shell, so such a value could only ever fail confusingly or, via a lookalike binary on PATH, run
|
|
24
|
+
* something the user did not mean. Exported for the tests.
|
|
25
|
+
*/
|
|
26
|
+
export declare function parseEditorSpec(spec: string): {
|
|
27
|
+
bin: string;
|
|
28
|
+
args: string[];
|
|
29
|
+
};
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mnemom agent config …` — read and write the persistent per-customer settings
|
|
3
|
+
* for `mnemom agent` (see lib/code-config.ts). A sub-verb of `mnemom agent`, so a
|
|
4
|
+
* customer configures the launcher once and never retypes their flags:
|
|
5
|
+
*
|
|
6
|
+
* mnemom agent config list # show the current settings
|
|
7
|
+
* mnemom agent config get <key> # print one value
|
|
8
|
+
* mnemom agent config set <key> <value> # validate + persist one value
|
|
9
|
+
* mnemom agent config unset <key> # remove one value
|
|
10
|
+
* mnemom agent config edit # open ~/.mnemom/launcher.toml in $EDITOR
|
|
11
|
+
* mnemom agent config path # print the settings file path
|
|
12
|
+
*
|
|
13
|
+
* Settable keys are the single source of truth in lib/code-config.ts CONFIG_KEYS.
|
|
14
|
+
* The secret Anthropic key is NOT a config key — it lives in ~/.mnemom/launcher.json.
|
|
15
|
+
*/
|
|
16
|
+
import { spawnSync } from "node:child_process";
|
|
17
|
+
import { existsSync } from "node:fs";
|
|
18
|
+
import { LAUNCHER_CONFIG_PATH, CONFIG_KEYS, configKeySpec, loadLauncherConfig, writeLauncherConfig, } from "../lib/launcher-config.js";
|
|
19
|
+
const out = (line = "") => console.log(line);
|
|
20
|
+
const err = (line = "") => console.error(line);
|
|
21
|
+
/** Entry point for `mnemom agent config <verb> [args...]`. Throws on misuse. */
|
|
22
|
+
export async function agentConfigCommand(argv) {
|
|
23
|
+
const [verb, ...rest] = argv;
|
|
24
|
+
switch (verb) {
|
|
25
|
+
case undefined:
|
|
26
|
+
case "list":
|
|
27
|
+
return listConfig();
|
|
28
|
+
case "path":
|
|
29
|
+
out(LAUNCHER_CONFIG_PATH);
|
|
30
|
+
return;
|
|
31
|
+
case "get":
|
|
32
|
+
return getConfig(rest[0]);
|
|
33
|
+
case "set":
|
|
34
|
+
return setConfig(rest[0], rest[1]);
|
|
35
|
+
case "unset":
|
|
36
|
+
return unsetConfig(rest[0]);
|
|
37
|
+
case "edit":
|
|
38
|
+
return editConfig();
|
|
39
|
+
default:
|
|
40
|
+
throw new Error(`mnemom agent config: unknown verb '${verb}'. Use list | get | set | unset | edit | path.`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function listConfig() {
|
|
44
|
+
const cfg = loadLauncherConfig();
|
|
45
|
+
const rows = [];
|
|
46
|
+
for (const spec of CONFIG_KEYS) {
|
|
47
|
+
const v = spec.get(cfg);
|
|
48
|
+
if (v !== undefined)
|
|
49
|
+
rows.push([spec.key, String(v)]);
|
|
50
|
+
}
|
|
51
|
+
if (!rows.length) {
|
|
52
|
+
out(`No settings yet at ${LAUNCHER_CONFIG_PATH}.`);
|
|
53
|
+
out("Set one with: mnemom agent config set <key> <value>");
|
|
54
|
+
out("");
|
|
55
|
+
out("Settable keys:");
|
|
56
|
+
for (const spec of CONFIG_KEYS)
|
|
57
|
+
out(` ${spec.key.padEnd(22)} ${spec.describe}`);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const width = Math.max(...rows.map(([k]) => k.length));
|
|
61
|
+
out(`# ${LAUNCHER_CONFIG_PATH}`);
|
|
62
|
+
for (const [k, v] of rows)
|
|
63
|
+
out(`${k.padEnd(width)} ${v}`);
|
|
64
|
+
}
|
|
65
|
+
function getConfig(key) {
|
|
66
|
+
if (!key)
|
|
67
|
+
throw new Error("mnemom agent config get: a <key> is required.");
|
|
68
|
+
const spec = requireSpec(key);
|
|
69
|
+
const value = spec.get(loadLauncherConfig());
|
|
70
|
+
if (value === undefined) {
|
|
71
|
+
err(`mnemom agent config: '${key}' is not set.`);
|
|
72
|
+
process.exitCode = 1;
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
out(String(value));
|
|
76
|
+
}
|
|
77
|
+
function setConfig(key, value) {
|
|
78
|
+
if (!key)
|
|
79
|
+
throw new Error("mnemom agent config set: a <key> is required.");
|
|
80
|
+
if (value === undefined)
|
|
81
|
+
throw new Error(`mnemom agent config set: a value for '${key}' is required.`);
|
|
82
|
+
const spec = requireSpec(key);
|
|
83
|
+
const cfg = loadLauncherConfig();
|
|
84
|
+
spec.set(cfg, value); // validates + coerces; throws on invalid
|
|
85
|
+
writeLauncherConfig(cfg);
|
|
86
|
+
out(`${key} = ${String(spec.get(cfg))} (saved to ${LAUNCHER_CONFIG_PATH})`);
|
|
87
|
+
}
|
|
88
|
+
function unsetConfig(key) {
|
|
89
|
+
if (!key)
|
|
90
|
+
throw new Error("mnemom agent config unset: a <key> is required.");
|
|
91
|
+
requireSpec(key);
|
|
92
|
+
const cfg = loadLauncherConfig();
|
|
93
|
+
deleteKey(cfg, key);
|
|
94
|
+
writeLauncherConfig(cfg);
|
|
95
|
+
out(`unset ${key} (saved to ${LAUNCHER_CONFIG_PATH})`);
|
|
96
|
+
}
|
|
97
|
+
function editConfig() {
|
|
98
|
+
// Seed a header-only file so $EDITOR opens something with guidance, not a blank.
|
|
99
|
+
if (!existsSync(LAUNCHER_CONFIG_PATH))
|
|
100
|
+
writeLauncherConfig(loadLauncherConfig());
|
|
101
|
+
const { bin: editor, args: editorArgs } = parseEditorSpec(process.env.VISUAL || process.env.EDITOR || fallbackEditor());
|
|
102
|
+
const res = spawnSync(editor, [...editorArgs, LAUNCHER_CONFIG_PATH], { stdio: "inherit" });
|
|
103
|
+
if (res.error) {
|
|
104
|
+
throw new Error(`mnemom agent config edit: could not launch editor '${editor}': ${res.error.message}\n` +
|
|
105
|
+
` Set $EDITOR, or edit ${LAUNCHER_CONFIG_PATH} directly.`);
|
|
106
|
+
}
|
|
107
|
+
// Re-parse so a typo surfaces now, not at the next launch.
|
|
108
|
+
loadLauncherConfig();
|
|
109
|
+
out(`Saved ${LAUNCHER_CONFIG_PATH}.`);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* `$VISUAL` / `$EDITOR` is a program plus optional plain flags (`code --wait`, `emacsclient -t`),
|
|
113
|
+
* never shell syntax: the value is split on whitespace and every token must be a bare
|
|
114
|
+
* program name, path, or flag. Anything else (quotes, `;`, `$(`, backticks, redirections, …) is
|
|
115
|
+
* refused with a remediation rather than handed to spawnSync — the editor is launched WITHOUT a
|
|
116
|
+
* shell, so such a value could only ever fail confusingly or, via a lookalike binary on PATH, run
|
|
117
|
+
* something the user did not mean. Exported for the tests.
|
|
118
|
+
*/
|
|
119
|
+
export function parseEditorSpec(spec) {
|
|
120
|
+
const tokens = spec.trim().split(/\s+/).filter(Boolean);
|
|
121
|
+
const SAFE = /^[A-Za-z0-9_./+=:@,-]+$/;
|
|
122
|
+
if (tokens.length === 0 || !tokens.every((t) => SAFE.test(t))) {
|
|
123
|
+
throw new Error(`mnemom agent config edit: refusing to run $VISUAL/$EDITOR value '${spec}' — only a program name or path plus plain flags is accepted (no shell syntax).\n` +
|
|
124
|
+
` Set $EDITOR to e.g. 'code --wait', or edit ${LAUNCHER_CONFIG_PATH} directly.`);
|
|
125
|
+
}
|
|
126
|
+
return { bin: tokens[0], args: tokens.slice(1) };
|
|
127
|
+
}
|
|
128
|
+
function fallbackEditor() {
|
|
129
|
+
for (const cand of ["nano", "vim", "vi"]) {
|
|
130
|
+
const r = spawnSync("sh", ["-c", `command -v "$1" >/dev/null 2>&1`, "sh", cand], {
|
|
131
|
+
stdio: "ignore",
|
|
132
|
+
});
|
|
133
|
+
if (r.status === 0)
|
|
134
|
+
return cand;
|
|
135
|
+
}
|
|
136
|
+
return "vi";
|
|
137
|
+
}
|
|
138
|
+
function requireSpec(key) {
|
|
139
|
+
const spec = configKeySpec(key);
|
|
140
|
+
if (!spec) {
|
|
141
|
+
throw new Error(`mnemom agent config: unknown key '${key}'.\n Known keys: ${CONFIG_KEYS.map((k) => k.key).join(", ")}`);
|
|
142
|
+
}
|
|
143
|
+
return spec;
|
|
144
|
+
}
|
|
145
|
+
/** Remove a (possibly dotted) key from a config object, pruning an emptied table. */
|
|
146
|
+
function deleteKey(cfg, key) {
|
|
147
|
+
if (key.startsWith("guardrails.")) {
|
|
148
|
+
const g = cfg.guardrails;
|
|
149
|
+
if (!g)
|
|
150
|
+
return;
|
|
151
|
+
const field = key.slice("guardrails.".length);
|
|
152
|
+
if (field === "max_turns")
|
|
153
|
+
delete g.maxTurns;
|
|
154
|
+
if (field === "budget_usd")
|
|
155
|
+
delete g.budgetUsd;
|
|
156
|
+
if (field === "stall_turns")
|
|
157
|
+
delete g.stallTurns;
|
|
158
|
+
if (g.maxTurns === undefined && g.budgetUsd === undefined && g.stallTurns === undefined) {
|
|
159
|
+
delete cfg.guardrails;
|
|
160
|
+
}
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
// Static, exhaustive: every top-level key the schema knows, so a typo can never
|
|
164
|
+
// delete an unrelated property (and the lint rule on dynamic delete stays honest).
|
|
165
|
+
switch (key) {
|
|
166
|
+
case "gateway":
|
|
167
|
+
delete cfg.gateway;
|
|
168
|
+
break;
|
|
169
|
+
case "model":
|
|
170
|
+
delete cfg.model;
|
|
171
|
+
break;
|
|
172
|
+
case "agent":
|
|
173
|
+
delete cfg.agent;
|
|
174
|
+
break;
|
|
175
|
+
case "effort":
|
|
176
|
+
delete cfg.effort;
|
|
177
|
+
break;
|
|
178
|
+
case "goal_mode":
|
|
179
|
+
delete cfg.goalMode;
|
|
180
|
+
break;
|
|
181
|
+
case "cli":
|
|
182
|
+
delete cfg.cli;
|
|
183
|
+
break;
|
|
184
|
+
case "launch":
|
|
185
|
+
delete cfg.launch;
|
|
186
|
+
break;
|
|
187
|
+
case "key_source":
|
|
188
|
+
delete cfg.keySource;
|
|
189
|
+
break;
|
|
190
|
+
default:
|
|
191
|
+
requireSpec(key); // throws the known-keys message for anything unknown
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `mnemom
|
|
2
|
+
* `mnemom agent doctor` — a read-only preflight for `mnemom agent`. It checks the
|
|
3
3
|
* things a governed launch needs and prints a checklist with clear, actionable
|
|
4
4
|
* remediation, so a customer can see at a glance why a launch would fail before
|
|
5
5
|
* it does. Writes nothing and reads no secret VALUE (only whether a key is
|
|
@@ -16,7 +16,7 @@ export interface DoctorOptions {
|
|
|
16
16
|
cli?: string;
|
|
17
17
|
}
|
|
18
18
|
/** Run the preflight and print the checklist. Sets process.exitCode on failure. */
|
|
19
|
-
export declare function
|
|
19
|
+
export declare function agentDoctorCommand(options?: DoctorOptions): Promise<void>;
|
|
20
20
|
export interface CodeAccessProbe {
|
|
21
21
|
mark: Mark;
|
|
22
22
|
text: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `mnemom
|
|
2
|
+
* `mnemom agent doctor` — a read-only preflight for `mnemom agent`. It checks the
|
|
3
3
|
* things a governed launch needs and prints a checklist with clear, actionable
|
|
4
4
|
* remediation, so a customer can see at a glance why a launch would fail before
|
|
5
5
|
* it does. Writes nothing and reads no secret VALUE (only whether a key is
|
|
@@ -13,27 +13,27 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { spawnSync } from "node:child_process";
|
|
15
15
|
import { existsSync } from "node:fs";
|
|
16
|
-
import { anthropicKeySource, checkRcCapability, resolveCliBin
|
|
17
|
-
import { isProdGateway, resolveAnthropicDoor, resolveGatewayHost
|
|
18
|
-
import {
|
|
16
|
+
import { anthropicKeySource, checkRcCapability, resolveCliBin } from "./agent.js";
|
|
17
|
+
import { isProdGateway, resolveAnthropicDoor, resolveGatewayHost } from "../lib/agent.js";
|
|
18
|
+
import { LAUNCHER_CONFIG_PATH } from "../lib/launcher-config.js";
|
|
19
19
|
import { getCodeEntitlement, MnemomApiError } from "../lib/api.js";
|
|
20
20
|
import { isLoggedIn } from "../lib/auth.js";
|
|
21
21
|
import { getActiveOrg } from "../lib/cli-config.js";
|
|
22
|
-
import { isCodeNotEnabled, sourceLabel } from "./
|
|
22
|
+
import { isCodeNotEnabled, sourceLabel } from "./agent-invite.js";
|
|
23
23
|
const MARK = { ok: "[ ok ]", warn: "[warn]", miss: "[MISS]", info: "[info]" };
|
|
24
24
|
const out = (line = "") => console.log(line);
|
|
25
25
|
/** Run the preflight and print the checklist. Sets process.exitCode on failure. */
|
|
26
|
-
export async function
|
|
26
|
+
export async function agentDoctorCommand(options = {}) {
|
|
27
27
|
const env = process.env;
|
|
28
28
|
const lines = [];
|
|
29
29
|
const add = (mark, text, hint) => {
|
|
30
30
|
lines.push({ mark, text, hint });
|
|
31
31
|
};
|
|
32
32
|
// ── settings file ──
|
|
33
|
-
if (existsSync(
|
|
34
|
-
add("ok", `settings: ${
|
|
33
|
+
if (existsSync(LAUNCHER_CONFIG_PATH))
|
|
34
|
+
add("ok", `settings: ${LAUNCHER_CONFIG_PATH}`);
|
|
35
35
|
else
|
|
36
|
-
add("info", `settings: none yet (${
|
|
36
|
+
add("info", `settings: none yet (${LAUNCHER_CONFIG_PATH}) — using built-in defaults`, "save your defaults with `mnemom agent config set <key> <value>`");
|
|
37
37
|
// ── coding-agent CLI ──
|
|
38
38
|
let cli;
|
|
39
39
|
try {
|
|
@@ -41,16 +41,16 @@ export async function codeDoctorCommand(options = {}) {
|
|
|
41
41
|
add("ok", `coding-agent CLI: ${cli.name} (${cli.bin})`);
|
|
42
42
|
}
|
|
43
43
|
catch (err) {
|
|
44
|
-
add("miss", `coding-agent CLI: ${err instanceof Error ? err.message.split("\n")[0] : err}`, "install Claude Code, or pass --cli <name|/full/path> / set it with `mnemom
|
|
44
|
+
add("miss", `coding-agent CLI: ${err instanceof Error ? err.message.split("\n")[0] : err}`, "install Claude Code, or pass --cli <name|/full/path> / set it with `mnemom agent config set cli <path>`");
|
|
45
45
|
}
|
|
46
46
|
// ── Anthropic key ──
|
|
47
47
|
const keySrc = anthropicKeySource(env);
|
|
48
48
|
if (keySrc === "env")
|
|
49
49
|
add("ok", "Anthropic key: found in the environment");
|
|
50
50
|
else if (keySrc === "store")
|
|
51
|
-
add("ok", "Anthropic key: found in ~/.mnemom/
|
|
51
|
+
add("ok", "Anthropic key: found in ~/.mnemom/launcher.json");
|
|
52
52
|
else
|
|
53
|
-
add("miss", "Anthropic key: none found", "export
|
|
53
|
+
add("miss", "Anthropic key: none found", "export MNEMOM_AGENT_ANTHROPIC_KEY=sk-ant-… (or ANTHROPIC_API_KEY), or run `mnemom agent` once and accept the save prompt");
|
|
54
54
|
// ── gateway health ──
|
|
55
55
|
const host = resolveGatewayHost(env);
|
|
56
56
|
const cell = isProdGateway(env) ? "us-2/prod" : "custom";
|
|
@@ -62,7 +62,7 @@ export async function codeDoctorCommand(options = {}) {
|
|
|
62
62
|
add("warn", `gateway: ${host} [${cell}] responded ${health.status} but without an x-mnemom-verdict header`, "the cell is up but the governance pipeline may not be wired — traces/nudges may not appear");
|
|
63
63
|
}
|
|
64
64
|
else {
|
|
65
|
-
add("warn", `gateway: ${host} [${cell}] unreachable (${health.detail})`, "check the host (
|
|
65
|
+
add("warn", `gateway: ${host} [${cell}] unreachable (${health.detail})`, "check the host (MNEMOM_AGENT_GATEWAY / `mnemom agent config set gateway <url>`) and your network");
|
|
66
66
|
}
|
|
67
67
|
// ── Remote Control capability ──
|
|
68
68
|
if (cli) {
|
|
@@ -82,12 +82,12 @@ export async function codeDoctorCommand(options = {}) {
|
|
|
82
82
|
add("info", "claude.ai login: could not determine");
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
|
-
// ── Mnemom
|
|
85
|
+
// ── Mnemom Agent access (invited cohort gate) ──
|
|
86
86
|
const gate = await probeCodeAccess();
|
|
87
87
|
add(gate.mark, gate.text, gate.hint);
|
|
88
88
|
// ── render ──
|
|
89
89
|
const door = resolveAnthropicDoor(env);
|
|
90
|
-
out("mnemom
|
|
90
|
+
out("mnemom agent doctor");
|
|
91
91
|
out(` door: ${door}`);
|
|
92
92
|
out("");
|
|
93
93
|
for (const l of lines) {
|
|
@@ -98,7 +98,7 @@ export async function codeDoctorCommand(options = {}) {
|
|
|
98
98
|
out("");
|
|
99
99
|
const launchReady = !lines.some((l) => l.mark === "miss");
|
|
100
100
|
if (launchReady) {
|
|
101
|
-
out(" Ready to launch: mnemom
|
|
101
|
+
out(" Ready to launch: mnemom agent");
|
|
102
102
|
}
|
|
103
103
|
else {
|
|
104
104
|
out(" Not launch-ready — resolve the [MISS] items above.");
|
|
@@ -153,26 +153,31 @@ export async function probeCodeAccess() {
|
|
|
153
153
|
if (!(await isLoggedIn())) {
|
|
154
154
|
return {
|
|
155
155
|
mark: "info",
|
|
156
|
-
text: "Mnemom
|
|
157
|
-
hint: "run `mnemom login`; @mnemom.ai accounts are enabled by default, others need an invitation (mnemom
|
|
156
|
+
text: "Mnemom Agent access: sign in to check",
|
|
157
|
+
hint: "run `mnemom login`; @mnemom.ai accounts are enabled by default, others need an invitation (mnemom agent --invite <token>)",
|
|
158
158
|
};
|
|
159
159
|
}
|
|
160
160
|
const org = getActiveOrg();
|
|
161
161
|
const orgLabel = org ? `${org.name} (${org.slug})` : "your personal org";
|
|
162
162
|
try {
|
|
163
163
|
const ent = await getCodeEntitlement(org?.org_id);
|
|
164
|
-
return {
|
|
164
|
+
return {
|
|
165
|
+
mark: "ok",
|
|
166
|
+
text: `Mnemom Agent access: enabled for ${orgLabel} (${sourceLabel(ent.source)})`,
|
|
167
|
+
};
|
|
165
168
|
}
|
|
166
169
|
catch (err) {
|
|
167
170
|
if (isCodeNotEnabled(err)) {
|
|
168
171
|
const hint = err.details?.hint;
|
|
169
172
|
return {
|
|
170
173
|
mark: "miss",
|
|
171
|
-
text: `Mnemom
|
|
172
|
-
hint: typeof hint === "string" && hint
|
|
174
|
+
text: `Mnemom Agent access: not enabled for ${orgLabel}`,
|
|
175
|
+
hint: typeof hint === "string" && hint
|
|
176
|
+
? hint
|
|
177
|
+
: "redeem an invitation: mnemom agent --invite <token>",
|
|
173
178
|
};
|
|
174
179
|
}
|
|
175
180
|
const detail = err instanceof MnemomApiError || err instanceof Error ? err.message : String(err);
|
|
176
|
-
return { mark: "info", text: `Mnemom
|
|
181
|
+
return { mark: "info", text: `Mnemom Agent access: could not determine (${detail})` };
|
|
177
182
|
}
|
|
178
183
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Mnemom
|
|
2
|
+
* Mnemom Agent — the invited-cohort gate + `mnemom agent --invite <token>`.
|
|
3
3
|
*
|
|
4
|
-
* `mnemom
|
|
4
|
+
* `mnemom agent` is open by default to Mnemom's own people (@mnemom.ai) and to
|
|
5
5
|
* customers who redeemed an invitation. The api decides
|
|
6
6
|
* (`GET /v1/code/entitlement`); this module is the CLI side of that:
|
|
7
7
|
*
|
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
* `code_not_enabled` BLOCKS with the api's own hint; anything inconclusive
|
|
11
11
|
* (network, 5xx, a membership 403) fails OPEN — the gate is a friendly
|
|
12
12
|
* pre-check, the api/gateway remain the real enforcement.
|
|
13
|
-
* - `
|
|
13
|
+
* - `agentInviteCommand(token)` redeems a `pinv_…` invitation
|
|
14
14
|
* (`POST /v1/invites/accept`): signs the customer in first (creating a free
|
|
15
|
-
* account in the same flow if needed), unlocks Mnemom
|
|
16
|
-
* reports whether the starter µ landed. Backs both `mnemom
|
|
17
|
-
* <token> [scenario]` and the `mnemom
|
|
15
|
+
* account in the same flow if needed), unlocks Mnemom Agent for them, and
|
|
16
|
+
* reports whether the starter µ landed. Backs both `mnemom agent --invite
|
|
17
|
+
* <token> [scenario]` and the `mnemom agent invite <token>` sub-verb.
|
|
18
18
|
*
|
|
19
|
-
* All narration goes to stderr (the `mnemom
|
|
19
|
+
* All narration goes to stderr (the `mnemom agent` convention — stdout is for
|
|
20
20
|
* the dry-run plan only). Copy says "µ", never "MU".
|
|
21
21
|
*/
|
|
22
22
|
import { MnemomApiError, type AcceptProductInviteResult, type CodeEntitlement } from "../lib/api.js";
|
|
@@ -37,7 +37,7 @@ export declare function sourceLabel(source: CodeEntitlement["source"]): string;
|
|
|
37
37
|
/** The api's `code_not_enabled` 403 — the ONE case that blocks. */
|
|
38
38
|
export declare function isCodeNotEnabled(err: unknown): err is MnemomApiError;
|
|
39
39
|
/**
|
|
40
|
-
* Enforce the Mnemom
|
|
40
|
+
* Enforce the Mnemom Agent gate for the org the launch targets. Blocks (prints
|
|
41
41
|
* guidance + throws CodeNotEnabledError) ONLY on a determined `code_not_enabled`;
|
|
42
42
|
* every other failure is inconclusive and fails open with a dim note.
|
|
43
43
|
*/
|
|
@@ -51,15 +51,15 @@ export declare function explainAcceptError(err: unknown): string | null;
|
|
|
51
51
|
/** Print the outcome of a successful accept. */
|
|
52
52
|
export declare function printAccepted(res: AcceptProductInviteResult): void;
|
|
53
53
|
/**
|
|
54
|
-
* `mnemom
|
|
54
|
+
* `mnemom agent --invite <token>` / `mnemom agent invite <token>`: sign in, redeem,
|
|
55
55
|
* report. Throws with a clear message on a bad token, no session, or an accept
|
|
56
56
|
* failure the customer can act on; rethrows anything else.
|
|
57
57
|
*/
|
|
58
|
-
export declare function
|
|
58
|
+
export declare function agentInviteCommand(token: string | undefined, opts?: {
|
|
59
59
|
interactive?: boolean;
|
|
60
60
|
ensureSession?: EnsureSession;
|
|
61
61
|
}): Promise<AcceptProductInviteResult>;
|
|
62
|
-
export interface
|
|
62
|
+
export interface AgentInvocation {
|
|
63
63
|
/** The positional `[scenario]` (may be a reserved sub-verb such as "invite"). */
|
|
64
64
|
scenario?: string;
|
|
65
65
|
/** Everything after the scenario. */
|
|
@@ -67,7 +67,7 @@ export interface CodeInvocation {
|
|
|
67
67
|
/** `--invite <token>`. */
|
|
68
68
|
inviteFlag?: string;
|
|
69
69
|
}
|
|
70
|
-
export interface
|
|
70
|
+
export interface AgentInvocationDeps {
|
|
71
71
|
redeem: (token: string | undefined) => Promise<unknown>;
|
|
72
72
|
/** The launch half; `undefined` = no session name given (the launcher prompts in a terminal). */
|
|
73
73
|
launch: (scenario: string | undefined) => Promise<void>;
|
|
@@ -75,10 +75,10 @@ export interface CodeInvocationDeps {
|
|
|
75
75
|
/**
|
|
76
76
|
* How the `code` command's positional + `--invite` compose — kept OUT of
|
|
77
77
|
* index.ts so it is unit-testable:
|
|
78
|
-
* - `mnemom
|
|
79
|
-
* - `mnemom
|
|
80
|
-
* - `mnemom
|
|
81
|
-
* - `mnemom
|
|
82
|
-
* - `mnemom
|
|
78
|
+
* - `mnemom agent invite <token>` → redeem, done (sub-verb; "invite" is reserved)
|
|
79
|
+
* - `mnemom agent --invite <token>` → redeem, done
|
|
80
|
+
* - `mnemom agent --invite <token> <scen>` → redeem, then launch
|
|
81
|
+
* - `mnemom agent <scen>` → launch
|
|
82
|
+
* - `mnemom agent` → launch (the launcher asks for a name + goal)
|
|
83
83
|
*/
|
|
84
|
-
export declare function
|
|
84
|
+
export declare function runAgentInvocation(input: AgentInvocation, deps: AgentInvocationDeps): Promise<void>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Mnemom
|
|
2
|
+
* Mnemom Agent — the invited-cohort gate + `mnemom agent --invite <token>`.
|
|
3
3
|
*
|
|
4
|
-
* `mnemom
|
|
4
|
+
* `mnemom agent` is open by default to Mnemom's own people (@mnemom.ai) and to
|
|
5
5
|
* customers who redeemed an invitation. The api decides
|
|
6
6
|
* (`GET /v1/code/entitlement`); this module is the CLI side of that:
|
|
7
7
|
*
|
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
* `code_not_enabled` BLOCKS with the api's own hint; anything inconclusive
|
|
11
11
|
* (network, 5xx, a membership 403) fails OPEN — the gate is a friendly
|
|
12
12
|
* pre-check, the api/gateway remain the real enforcement.
|
|
13
|
-
* - `
|
|
13
|
+
* - `agentInviteCommand(token)` redeems a `pinv_…` invitation
|
|
14
14
|
* (`POST /v1/invites/accept`): signs the customer in first (creating a free
|
|
15
|
-
* account in the same flow if needed), unlocks Mnemom
|
|
16
|
-
* reports whether the starter µ landed. Backs both `mnemom
|
|
17
|
-
* <token> [scenario]` and the `mnemom
|
|
15
|
+
* account in the same flow if needed), unlocks Mnemom Agent for them, and
|
|
16
|
+
* reports whether the starter µ landed. Backs both `mnemom agent --invite
|
|
17
|
+
* <token> [scenario]` and the `mnemom agent invite <token>` sub-verb.
|
|
18
18
|
*
|
|
19
|
-
* All narration goes to stderr (the `mnemom
|
|
19
|
+
* All narration goes to stderr (the `mnemom agent` convention — stdout is for
|
|
20
20
|
* the dry-run plan only). Copy says "µ", never "MU".
|
|
21
21
|
*/
|
|
22
22
|
import { acceptProductInvite, CODE_NOT_ENABLED, getCodeEntitlement, MnemomApiError, } from "../lib/api.js";
|
|
@@ -28,7 +28,7 @@ const say = (line = "") => console.error(line);
|
|
|
28
28
|
export class CodeNotEnabledError extends Error {
|
|
29
29
|
orgLabel;
|
|
30
30
|
constructor(orgLabel) {
|
|
31
|
-
super("Mnemom
|
|
31
|
+
super("Mnemom Agent is not enabled for this organization");
|
|
32
32
|
this.orgLabel = orgLabel;
|
|
33
33
|
this.name = "CodeNotEnabledError";
|
|
34
34
|
}
|
|
@@ -38,7 +38,7 @@ export const INVITE_TOKEN_RE = /^pinv_[A-Za-z0-9_-]{8,}$/;
|
|
|
38
38
|
export function looksLikeInviteToken(value) {
|
|
39
39
|
return typeof value === "string" && INVITE_TOKEN_RE.test(value.trim());
|
|
40
40
|
}
|
|
41
|
-
const FALLBACK_HINT = "Ask your Mnemom contact for a Mnemom
|
|
41
|
+
const FALLBACK_HINT = "Ask your Mnemom contact for a Mnemom Agent invitation, then run: mnemom agent --invite <token>";
|
|
42
42
|
/** Drop control characters (ANSI escapes included) from an api-supplied string before
|
|
43
43
|
* it reaches the terminal — defense in depth; the value is first-party but printed raw. */
|
|
44
44
|
export function sanitizeTerminalText(s) {
|
|
@@ -70,16 +70,16 @@ function hintFrom(err) {
|
|
|
70
70
|
}
|
|
71
71
|
function printNotEnabled(orgLabel, hint) {
|
|
72
72
|
say();
|
|
73
|
-
say(fmt.warn(`Mnemom
|
|
74
|
-
say(fmt.dim(" mnemom
|
|
73
|
+
say(fmt.warn(`Mnemom Agent isn't enabled for ${orgLabel} yet.`));
|
|
74
|
+
say(fmt.dim(" mnemom agent is rolling out to an invited cohort. If you have an invitation token"));
|
|
75
75
|
say(fmt.dim(" (pinv_…), redeem it and you're in:"));
|
|
76
76
|
say();
|
|
77
|
-
say(" mnemom
|
|
77
|
+
say(" mnemom agent --invite <token>");
|
|
78
78
|
say();
|
|
79
79
|
say(fmt.dim(` ${hint}`));
|
|
80
80
|
}
|
|
81
81
|
/**
|
|
82
|
-
* Enforce the Mnemom
|
|
82
|
+
* Enforce the Mnemom Agent gate for the org the launch targets. Blocks (prints
|
|
83
83
|
* guidance + throws CodeNotEnabledError) ONLY on a determined `code_not_enabled`;
|
|
84
84
|
* every other failure is inconclusive and fails open with a dim note.
|
|
85
85
|
*/
|
|
@@ -92,13 +92,13 @@ export async function assertCodeEnabled(orgId, orgLabel) {
|
|
|
92
92
|
printNotEnabled(orgLabel, hintFrom(err));
|
|
93
93
|
throw new CodeNotEnabledError(orgLabel);
|
|
94
94
|
}
|
|
95
|
-
say(fmt.dim(` (Could not verify Mnemom
|
|
95
|
+
say(fmt.dim(` (Could not verify Mnemom Agent access — ${err instanceof Error ? err.message : String(err)}. Continuing.)`));
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
async function defaultEnsureSession(opts) {
|
|
99
99
|
// Lazy import: code-setup imports assertCodeEnabled from THIS module, so a static
|
|
100
100
|
// import here would be a cycle.
|
|
101
|
-
const { ensureMnemomSession } = await import("./
|
|
101
|
+
const { ensureMnemomSession } = await import("./agent-setup.js");
|
|
102
102
|
return ensureMnemomSession(opts);
|
|
103
103
|
}
|
|
104
104
|
/** Map an accept failure to the sentence the customer needs. Null = not ours to explain. */
|
|
@@ -121,10 +121,10 @@ export function explainAcceptError(err) {
|
|
|
121
121
|
/** Print the outcome of a successful accept. */
|
|
122
122
|
export function printAccepted(res) {
|
|
123
123
|
if (res.already_accepted) {
|
|
124
|
-
say(fmt.success("This invitation was already accepted — Mnemom
|
|
124
|
+
say(fmt.success("This invitation was already accepted — Mnemom Agent is unlocked for you."));
|
|
125
125
|
}
|
|
126
126
|
else {
|
|
127
|
-
say(fmt.success("Mnemom
|
|
127
|
+
say(fmt.success("Mnemom Agent is unlocked for your account."));
|
|
128
128
|
}
|
|
129
129
|
if (res.grant === "fired") {
|
|
130
130
|
say(fmt.dim(" Your starter µ has been credited."));
|
|
@@ -132,27 +132,27 @@ export function printAccepted(res) {
|
|
|
132
132
|
else if (res.grant === "deferred") {
|
|
133
133
|
say(fmt.dim(" Your starter µ credit is pending — you can start now; if it hasn't landed shortly, contact support@mnemom.ai."));
|
|
134
134
|
}
|
|
135
|
-
say(fmt.dim(" Start a governed session: mnemom
|
|
135
|
+
say(fmt.dim(" Start a governed session: mnemom agent"));
|
|
136
136
|
}
|
|
137
137
|
/**
|
|
138
|
-
* `mnemom
|
|
138
|
+
* `mnemom agent --invite <token>` / `mnemom agent invite <token>`: sign in, redeem,
|
|
139
139
|
* report. Throws with a clear message on a bad token, no session, or an accept
|
|
140
140
|
* failure the customer can act on; rethrows anything else.
|
|
141
141
|
*/
|
|
142
|
-
export async function
|
|
142
|
+
export async function agentInviteCommand(token, opts = {}) {
|
|
143
143
|
const trimmed = (token ?? "").trim();
|
|
144
144
|
if (!looksLikeInviteToken(trimmed)) {
|
|
145
|
-
throw new Error("mnemom
|
|
145
|
+
throw new Error("mnemom agent --invite: expected a Mnemom Agent invitation token (pinv_…) — copy it from your invitation email.");
|
|
146
146
|
}
|
|
147
147
|
const interactive = opts.interactive ?? isInteractive();
|
|
148
148
|
const ensureSession = opts.ensureSession ?? defaultEnsureSession;
|
|
149
149
|
const haveSession = await ensureSession({ interactive });
|
|
150
150
|
if (!haveSession) {
|
|
151
|
-
throw new Error("mnemom
|
|
151
|
+
throw new Error("mnemom agent --invite: redeeming needs a Mnemom account.\n" +
|
|
152
152
|
" Sign in: mnemom login\n" +
|
|
153
153
|
" Then run the same --invite command again.");
|
|
154
154
|
}
|
|
155
|
-
say(fmt.dim(" Redeeming your Mnemom
|
|
155
|
+
say(fmt.dim(" Redeeming your Mnemom Agent invitation…"));
|
|
156
156
|
let result;
|
|
157
157
|
try {
|
|
158
158
|
result = await acceptProductInvite(trimmed);
|
|
@@ -160,7 +160,7 @@ export async function codeInviteCommand(token, opts = {}) {
|
|
|
160
160
|
catch (err) {
|
|
161
161
|
const explained = explainAcceptError(err);
|
|
162
162
|
if (explained)
|
|
163
|
-
throw new Error(`mnemom
|
|
163
|
+
throw new Error(`mnemom agent --invite: ${explained}`, { cause: err });
|
|
164
164
|
throw err;
|
|
165
165
|
}
|
|
166
166
|
printAccepted(result);
|
|
@@ -169,13 +169,13 @@ export async function codeInviteCommand(token, opts = {}) {
|
|
|
169
169
|
/**
|
|
170
170
|
* How the `code` command's positional + `--invite` compose — kept OUT of
|
|
171
171
|
* index.ts so it is unit-testable:
|
|
172
|
-
* - `mnemom
|
|
173
|
-
* - `mnemom
|
|
174
|
-
* - `mnemom
|
|
175
|
-
* - `mnemom
|
|
176
|
-
* - `mnemom
|
|
172
|
+
* - `mnemom agent invite <token>` → redeem, done (sub-verb; "invite" is reserved)
|
|
173
|
+
* - `mnemom agent --invite <token>` → redeem, done
|
|
174
|
+
* - `mnemom agent --invite <token> <scen>` → redeem, then launch
|
|
175
|
+
* - `mnemom agent <scen>` → launch
|
|
176
|
+
* - `mnemom agent` → launch (the launcher asks for a name + goal)
|
|
177
177
|
*/
|
|
178
|
-
export async function
|
|
178
|
+
export async function runAgentInvocation(input, deps) {
|
|
179
179
|
if (input.scenario === "invite") {
|
|
180
180
|
await deps.redeem(input.passthrough[0]);
|
|
181
181
|
return;
|