@mnemom/mnemom 0.17.3 → 0.17.4-next.0
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/agent-doctor.js +21 -8
- package/dist/commands/agent.d.ts +5 -0
- package/dist/commands/agent.js +70 -14
- package/dist/index.js +2 -0
- package/dist/lib/agent.d.ts +4 -1
- package/dist/lib/agent.js +2 -1
- package/dist/lib/launcher-config.d.ts +3 -0
- package/dist/lib/launcher-config.js +16 -0
- package/dist/lib/subscription-login.d.ts +53 -0
- package/dist/lib/subscription-login.js +103 -0
- package/package.json +1 -1
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import { spawnSync } from "node:child_process";
|
|
15
15
|
import { existsSync } from "node:fs";
|
|
16
16
|
import { anthropicKeySource, checkRcCapability, resolveCliBin } from "./agent.js";
|
|
17
|
+
import { resolveAgentAuth } from "../lib/subscription-login.js";
|
|
17
18
|
import { isProdGateway, resolveAnthropicDoor, resolveGatewayHost, singleCellError, } from "../lib/agent.js";
|
|
18
19
|
import { getApiUrl, getGatewayUrl } from "../lib/config.js";
|
|
19
20
|
import { LAUNCHER_CONFIG_PATH } from "../lib/launcher-config.js";
|
|
@@ -45,14 +46,26 @@ export async function agentDoctorCommand(options = {}) {
|
|
|
45
46
|
catch (err) {
|
|
46
47
|
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>`");
|
|
47
48
|
}
|
|
48
|
-
// ── Anthropic key ──
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
49
|
+
// ── auth: an Anthropic key, or the claude.ai subscription login (no key needed) ──
|
|
50
|
+
let auth = "api-key";
|
|
51
|
+
try {
|
|
52
|
+
auth = resolveAgentAuth(undefined, env);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
add("miss", `auth: ${err instanceof Error ? err.message : String(err)}`, "`mnemom agent config set auth api-key` (or subscription)");
|
|
56
|
+
}
|
|
57
|
+
if (auth === "subscription") {
|
|
58
|
+
add("ok", "auth: subscription — Claude Code uses your claude.ai login (Team/Enterprise plans only); no Anthropic key needed");
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
const keySrc = anthropicKeySource(env);
|
|
62
|
+
if (keySrc === "env")
|
|
63
|
+
add("ok", "Anthropic key: found in the environment");
|
|
64
|
+
else if (keySrc === "store")
|
|
65
|
+
add("ok", "Anthropic key: found in ~/.mnemom/launcher.json");
|
|
66
|
+
else
|
|
67
|
+
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");
|
|
68
|
+
}
|
|
56
69
|
// ── one cell: the launch door must be the CLI's own cell ──
|
|
57
70
|
const cellError = singleCellError({
|
|
58
71
|
door: resolveAnthropicDoor(env),
|
package/dist/commands/agent.d.ts
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
* line, or the proxy's stdin under RC). It is never logged, printed, or passed as
|
|
26
26
|
* an argv value.
|
|
27
27
|
*/
|
|
28
|
+
import { type MnemomKeyDeps } from "../lib/subscription-login.js";
|
|
28
29
|
import { type ExperimentalId } from "../lib/experimental.js";
|
|
29
30
|
import { type ContextProfile, type LaunchShape, type SealedContractEnvelope } from "../lib/agent.js";
|
|
30
31
|
export interface AgentOptions {
|
|
@@ -48,6 +49,8 @@ export interface AgentOptions {
|
|
|
48
49
|
cli?: string;
|
|
49
50
|
remoteControl?: boolean;
|
|
50
51
|
server?: boolean;
|
|
52
|
+
/** `--auth api-key|subscription` (lib/subscription-login.ts); else MNEMOM_AGENT_AUTH / config. */
|
|
53
|
+
auth?: string;
|
|
51
54
|
model?: string;
|
|
52
55
|
effort?: string;
|
|
53
56
|
trimMcp?: boolean;
|
|
@@ -124,6 +127,8 @@ export declare function recordSessionLaunch(ctx: {
|
|
|
124
127
|
/** The resolved context profile this launch runs under. */
|
|
125
128
|
contextProfile: ContextProfile;
|
|
126
129
|
}): Promise<void>;
|
|
130
|
+
/** The real I/O behind a subscription launch's Mnemom key (lib/subscription-login.ts). */
|
|
131
|
+
export declare function defaultMnemomKeyDeps(storePath?: string): MnemomKeyDeps;
|
|
127
132
|
/**
|
|
128
133
|
* Resolve the Anthropic key with NO 1Password dependency (built for external
|
|
129
134
|
* customer adoption), in order:
|
package/dist/commands/agent.js
CHANGED
|
@@ -28,11 +28,12 @@
|
|
|
28
28
|
import { spawn, spawnSync } from "node:child_process";
|
|
29
29
|
import { existsSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync, renameSync, } from "node:fs";
|
|
30
30
|
import { randomUUID } from "node:crypto";
|
|
31
|
-
import { tmpdir, homedir } from "node:os";
|
|
31
|
+
import { tmpdir, homedir, hostname } from "node:os";
|
|
32
32
|
import { basename, join, dirname } from "node:path";
|
|
33
33
|
import { fileURLToPath } from "node:url";
|
|
34
34
|
import { getApiUrl, getGatewayUrl, MNEMOM_DIR } from "../lib/config.js";
|
|
35
|
-
import { getAgentByName } from "../lib/api.js";
|
|
35
|
+
import { createApiKey, getAgentByName } from "../lib/api.js";
|
|
36
|
+
import { MNEMOM_KEY_STORE_FIELD, resolveAgentAuth, resolveSubscriptionMnemomKey, subscriptionChildEnv, subscriptionShape, } from "../lib/subscription-login.js";
|
|
36
37
|
import { LEDGER_FILE, appendSessionRecord, resolveRetentionDays, toLedgerContract, } from "../lib/agent-ledger.js";
|
|
37
38
|
import { loadCliConfig } from "../lib/cli-config.js";
|
|
38
39
|
import { configKeySpec, loadLauncherConfig, writeLauncherConfig } from "../lib/launcher-config.js";
|
|
@@ -77,6 +78,9 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
77
78
|
const env = process.env;
|
|
78
79
|
let shape = resolveLaunchShape({ remoteControl: options.remoteControl, server: options.server }, env);
|
|
79
80
|
const shapeExplicit = launchPreferenceExplicit({ remoteControl: options.remoteControl, server: options.server }, env);
|
|
81
|
+
// How Claude Code authenticates: an Anthropic key (default) or the user's claude.ai
|
|
82
|
+
// subscription login (Team/Enterprise; lib/subscription-login.ts).
|
|
83
|
+
const auth = resolveAgentAuth(options.auth, env);
|
|
80
84
|
// ── governed identity ── (default: per-customer `agent-<handle>`, not a shared
|
|
81
85
|
// "code-agent" — see defaultAgentSlug; provisioning claims exactly this slug.
|
|
82
86
|
// `let` because a brand-new user who signs in during the pre-launch gate below
|
|
@@ -124,7 +128,12 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
124
128
|
context: options.context,
|
|
125
129
|
env,
|
|
126
130
|
});
|
|
127
|
-
|
|
131
|
+
let contextHandoff = resolveContextHandoff({ contextHandoff: options.contextHandoff, env });
|
|
132
|
+
if (contextHandoff && auth === "subscription") {
|
|
133
|
+
// The handoff tool authenticates with the Anthropic key a subscription launch does not have.
|
|
134
|
+
say("mnemom agent: context handoff is not available with subscription login yet — launching without it.");
|
|
135
|
+
contextHandoff = false;
|
|
136
|
+
}
|
|
128
137
|
const experimental = options.experimental ?? [];
|
|
129
138
|
const sessionConfig = buildSessionConfig({ profile: contextProfile, contextHandoff });
|
|
130
139
|
const sessionConfigB64 = buildSessionConfigB64({
|
|
@@ -175,6 +184,9 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
175
184
|
sessionConfig,
|
|
176
185
|
experimental,
|
|
177
186
|
});
|
|
187
|
+
if (auth === "subscription") {
|
|
188
|
+
say("mnemom agent: auth = subscription — Claude Code would use your claude.ai login (Team/Enterprise plans only) with a Mnemom API key; no Anthropic key, terminal-only.");
|
|
189
|
+
}
|
|
178
190
|
if (expNotice)
|
|
179
191
|
say(expNotice);
|
|
180
192
|
if (previewShape === "remote-control") {
|
|
@@ -193,10 +205,10 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
193
205
|
cli = await promptForCliPath(options.cli, env, cliError);
|
|
194
206
|
}
|
|
195
207
|
// ── resolve the Anthropic key (zero 1Password; never logged) ──
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
keySource: options.keySource
|
|
199
|
-
|
|
208
|
+
// Not on a subscription launch: Claude Code then sends its own claude.ai login instead.
|
|
209
|
+
const anthropicKey = auth === "api-key"
|
|
210
|
+
? await resolveAnthropicKey(env, { allowPrompt: true, keySource: options.keySource })
|
|
211
|
+
: undefined;
|
|
200
212
|
// ── account + µ-balance gate (no free tier) ─────────────────────────────────
|
|
201
213
|
// mnemom agent runs governed and on µ, so before we seal a contract or launch we
|
|
202
214
|
// (1) ensure a Mnemom session — offering the try-me-style sign-in / sign-up when
|
|
@@ -245,6 +257,14 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
245
257
|
rcCapable: rc.ok,
|
|
246
258
|
say,
|
|
247
259
|
});
|
|
260
|
+
// Subscription login: terminal-only for now, and the governed identity anchors on a
|
|
261
|
+
// Mnemom API key (minted once and stored if there is none). Resolved after sign-in so
|
|
262
|
+
// minting can use the session.
|
|
263
|
+
let mnemomKey;
|
|
264
|
+
if (auth === "subscription") {
|
|
265
|
+
shape = subscriptionShape(shape, shapeExplicit, say);
|
|
266
|
+
mnemomKey = await resolveSubscriptionMnemomKey(defaultMnemomKeyDeps());
|
|
267
|
+
}
|
|
248
268
|
// Now that a session exists, a defaulted slug uses the real login handle from
|
|
249
269
|
// /v1/auth/me (the stored token has no email, so the pre-sign-in value was the
|
|
250
270
|
// "agent-user" fallback). Only when the caller didn't name an agent. Under an
|
|
@@ -261,7 +281,12 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
261
281
|
// provisions-with-confirm when interactive; warns and continues otherwise.
|
|
262
282
|
// Skipped entirely by `--no-setup`. A provisioning failure never blocks the
|
|
263
283
|
// launch — it degrades to the unclaimed (fail-open) identity with a warning.
|
|
264
|
-
if (options.setup !== false) {
|
|
284
|
+
if (options.setup !== false && !anthropicKey) {
|
|
285
|
+
// Subscription login: the gateway creates the agent in the Mnemom key's org on the first
|
|
286
|
+
// turn, so there is nothing to birth or claim here.
|
|
287
|
+
say(`mnemom agent: subscription login — '${agent}' is created in your org on the first turn (identity: your Mnemom API key).`);
|
|
288
|
+
}
|
|
289
|
+
else if (options.setup !== false && anthropicKey) {
|
|
265
290
|
try {
|
|
266
291
|
await ensureGovernedAgent({
|
|
267
292
|
slug: agent,
|
|
@@ -343,11 +368,13 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
343
368
|
say(fmt.warn(`mnemom agent: context handoff could not be registered (${err instanceof Error ? err.message : String(err)}) — launching without the handoff tool.`));
|
|
344
369
|
}
|
|
345
370
|
}
|
|
346
|
-
|
|
371
|
+
// A subscription launch (no Anthropic key) is always terminal — see subscriptionShape.
|
|
372
|
+
if (shape === "terminal" || !anthropicKey) {
|
|
347
373
|
launchTerminal({
|
|
348
374
|
cli,
|
|
349
375
|
door,
|
|
350
376
|
anthropicKey,
|
|
377
|
+
mnemomKey,
|
|
351
378
|
agent,
|
|
352
379
|
scenario,
|
|
353
380
|
conversationId,
|
|
@@ -425,6 +452,25 @@ export async function recordSessionLaunch(ctx) {
|
|
|
425
452
|
// ───────────────────────────────────────────────────────────────────────────
|
|
426
453
|
// Credential resolution (zero 1Password)
|
|
427
454
|
// ───────────────────────────────────────────────────────────────────────────
|
|
455
|
+
/** The real I/O behind a subscription launch's Mnemom key (lib/subscription-login.ts). */
|
|
456
|
+
export function defaultMnemomKeyDeps(storePath = CODE_STORE) {
|
|
457
|
+
return {
|
|
458
|
+
env: process.env,
|
|
459
|
+
readStored: () => {
|
|
460
|
+
try {
|
|
461
|
+
const data = JSON.parse(readFileSync(storePath, "utf8"));
|
|
462
|
+
const v = data[MNEMOM_KEY_STORE_FIELD];
|
|
463
|
+
return typeof v === "string" ? stripCrlf(v) : undefined;
|
|
464
|
+
}
|
|
465
|
+
catch {
|
|
466
|
+
return undefined;
|
|
467
|
+
}
|
|
468
|
+
},
|
|
469
|
+
mint: async () => (await createApiKey(`mnemom-agent ${hostname()}`, ["gateway"])).key,
|
|
470
|
+
store: (key) => mergeStore({ [MNEMOM_KEY_STORE_FIELD]: key }, storePath),
|
|
471
|
+
say,
|
|
472
|
+
};
|
|
473
|
+
}
|
|
428
474
|
/**
|
|
429
475
|
* Resolve the Anthropic key with NO 1Password dependency (built for external
|
|
430
476
|
* customer adoption), in order:
|
|
@@ -660,6 +706,7 @@ function launchTerminal(ctx) {
|
|
|
660
706
|
warnAdapter(ctx.cli);
|
|
661
707
|
const headers = buildCustomHeaders({
|
|
662
708
|
anthropicKey: ctx.anthropicKey,
|
|
709
|
+
mnemomKey: ctx.mnemomKey,
|
|
663
710
|
agent: ctx.agent,
|
|
664
711
|
conversationId: ctx.conversationId,
|
|
665
712
|
contractB64: ctx.envelope?.base64,
|
|
@@ -675,11 +722,20 @@ function launchTerminal(ctx) {
|
|
|
675
722
|
// Claude Code logged in via claude.ai (OAuth) PREEMPTS ANTHROPIC_API_KEY, so
|
|
676
723
|
// it is force-unset; the real key rides in ANTHROPIC_CUSTOM_HEADERS and
|
|
677
724
|
// ANTHROPIC_AUTH_TOKEN is a fixed non-secret placeholder.
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
childEnv
|
|
682
|
-
|
|
725
|
+
//
|
|
726
|
+
// A subscription launch (no Anthropic key) sets NO auth token: Claude Code then sends its
|
|
727
|
+
// own claude.ai login to the gateway (lib/subscription-login.ts).
|
|
728
|
+
let childEnv;
|
|
729
|
+
if (ctx.anthropicKey) {
|
|
730
|
+
childEnv = { ...process.env };
|
|
731
|
+
delete childEnv.ANTHROPIC_API_KEY;
|
|
732
|
+
childEnv.ANTHROPIC_BASE_URL = ctx.door;
|
|
733
|
+
childEnv.ANTHROPIC_AUTH_TOKEN = "mnemom-gateway";
|
|
734
|
+
childEnv.ANTHROPIC_CUSTOM_HEADERS = headers;
|
|
735
|
+
}
|
|
736
|
+
else {
|
|
737
|
+
childEnv = subscriptionChildEnv(process.env, ctx.door, headers);
|
|
738
|
+
}
|
|
683
739
|
const sessionName = passthroughName(ctx.options.passthrough ?? []) ?? sessionDisplayName(ctx.agent, ctx.scenario);
|
|
684
740
|
childEnv[SESSION_NAME_ENV] = sessionName;
|
|
685
741
|
if (ctx.options.slack)
|
package/dist/index.js
CHANGED
|
@@ -142,6 +142,7 @@ const agentCmd = program
|
|
|
142
142
|
.option("--gateway <url>", "Gateway host override — must be the same cell as the rest of the CLI (default: that cell's gateway; or MNEMOM_AGENT_GATEWAY)")
|
|
143
143
|
.option("--remote-control", "Interactive Remote Control session in THIS terminal (behind a local proxy)")
|
|
144
144
|
.option("--server", "Headless `claude remote-control` dispatcher (behind a local proxy)")
|
|
145
|
+
.option("--auth <mode>", "How Claude Code authenticates: api-key (default) | subscription (your Claude Team/Enterprise login; terminal-only)")
|
|
145
146
|
.option("--model <alias|id>", "Model to launch (CLI alias or concrete id)")
|
|
146
147
|
.addOption(new Option("--effort <level>", "Reasoning effort").choices([
|
|
147
148
|
"low",
|
|
@@ -278,6 +279,7 @@ const agentCmd = program
|
|
|
278
279
|
conversationId: opts.conversationId,
|
|
279
280
|
cli: opts.cli,
|
|
280
281
|
remoteControl: opts.remoteControl,
|
|
282
|
+
auth: opts.auth,
|
|
281
283
|
server: opts.server,
|
|
282
284
|
model: opts.model ?? cfg.model,
|
|
283
285
|
effort: opts.effort ?? cfg.effort,
|
package/dist/lib/agent.d.ts
CHANGED
|
@@ -283,7 +283,10 @@ export declare function resolveRemoteControlShape(args: {
|
|
|
283
283
|
* callers must place it only in the child's env, never in a log line.
|
|
284
284
|
*/
|
|
285
285
|
export declare function buildCustomHeaders(opts: {
|
|
286
|
-
|
|
286
|
+
/** Absent on a subscription-login launch: Claude Code then sends its own claude.ai login. */
|
|
287
|
+
anthropicKey?: string;
|
|
288
|
+
/** The governed-identity anchor on a subscription-login launch (lib/subscription-login.ts). */
|
|
289
|
+
mnemomKey?: string;
|
|
287
290
|
agent: string;
|
|
288
291
|
conversationId: string;
|
|
289
292
|
contractB64?: string;
|
package/dist/lib/agent.js
CHANGED
|
@@ -466,7 +466,8 @@ export function resolveRemoteControlShape(args) {
|
|
|
466
466
|
*/
|
|
467
467
|
export function buildCustomHeaders(opts) {
|
|
468
468
|
const lines = [
|
|
469
|
-
`x-api-key: ${opts.anthropicKey}
|
|
469
|
+
...(opts.anthropicKey ? [`x-api-key: ${opts.anthropicKey}`] : []),
|
|
470
|
+
...(opts.mnemomKey ? [`x-mnemom-api-key: ${opts.mnemomKey}`] : []),
|
|
470
471
|
`x-mnemom-agent: ${opts.agent}`,
|
|
471
472
|
`x-mnemom-conversation-id: ${opts.conversationId}`,
|
|
472
473
|
];
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* resolvers in lib/code.ts keep the built-in default as the final fallback);
|
|
16
16
|
* the caller folds the plain-value settings under the CLI flags.
|
|
17
17
|
*/
|
|
18
|
+
import { type AgentAuth } from "./subscription-login.js";
|
|
18
19
|
import type { ExperimentalId } from "./experimental.js";
|
|
19
20
|
import { type ContextProfile, type GoalModeDefault, type LaunchShape } from "./agent.js";
|
|
20
21
|
/** The human-editable settings file. Secret key lives elsewhere (launcher.json). */
|
|
@@ -29,6 +30,8 @@ export interface LauncherConfig {
|
|
|
29
30
|
effort?: string;
|
|
30
31
|
cli?: string;
|
|
31
32
|
launch?: LaunchShape;
|
|
33
|
+
/** How Claude Code authenticates (lib/subscription-login.ts); api-key when unset. */
|
|
34
|
+
auth?: AgentAuth;
|
|
32
35
|
keySource?: KeySource;
|
|
33
36
|
/** Standing goal-alignment default when neither --goal nor --implicit is given. */
|
|
34
37
|
goalMode?: GoalModeDefault;
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* resolvers in lib/code.ts keep the built-in default as the final fallback);
|
|
16
16
|
* the caller folds the plain-value settings under the CLI flags.
|
|
17
17
|
*/
|
|
18
|
+
import { AGENT_AUTH_ENV, AGENT_AUTHS, coerceAgentAuth, } from "./subscription-login.js";
|
|
18
19
|
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
19
20
|
import { dirname, join } from "node:path";
|
|
20
21
|
import { parse as parseToml, stringify as stringifyToml, TomlError } from "smol-toml";
|
|
@@ -132,6 +133,14 @@ export const CONFIG_KEYS = [
|
|
|
132
133
|
c.launch = coerceLaunch(v);
|
|
133
134
|
},
|
|
134
135
|
},
|
|
136
|
+
{
|
|
137
|
+
key: "auth",
|
|
138
|
+
describe: `How Claude Code authenticates (${AGENT_AUTHS.join("|")}); subscription = your Claude Team/Enterprise login, terminal-only`,
|
|
139
|
+
get: (c) => c.auth,
|
|
140
|
+
set: (c, v) => {
|
|
141
|
+
c.auth = coerceAgentAuth(v);
|
|
142
|
+
},
|
|
143
|
+
},
|
|
135
144
|
{
|
|
136
145
|
key: "key_source",
|
|
137
146
|
describe: `Where to source the Anthropic key (${KEY_SOURCES.join("|")})`,
|
|
@@ -258,6 +267,8 @@ export function parseLauncherConfig(text) {
|
|
|
258
267
|
cfg.cli = coerceNonEmpty("cli", str(raw.cli) ?? "");
|
|
259
268
|
if (raw.launch !== undefined)
|
|
260
269
|
cfg.launch = coerceLaunch(str(raw.launch) ?? "");
|
|
270
|
+
if (raw.auth !== undefined)
|
|
271
|
+
cfg.auth = coerceAgentAuth(str(raw.auth) ?? "");
|
|
261
272
|
if (raw.key_source !== undefined)
|
|
262
273
|
cfg.keySource = coerceKeySource(str(raw.key_source) ?? "");
|
|
263
274
|
if (raw.goal_mode !== undefined)
|
|
@@ -330,6 +341,8 @@ export function serializeLauncherConfig(cfg) {
|
|
|
330
341
|
doc.cli = cfg.cli;
|
|
331
342
|
if (cfg.launch !== undefined)
|
|
332
343
|
doc.launch = cfg.launch;
|
|
344
|
+
if (cfg.auth !== undefined)
|
|
345
|
+
doc.auth = cfg.auth;
|
|
333
346
|
if (cfg.keySource !== undefined)
|
|
334
347
|
doc.key_source = cfg.keySource;
|
|
335
348
|
if (cfg.goalMode !== undefined)
|
|
@@ -392,6 +405,9 @@ export function applyConfigToEnv(cfg, env = process.env) {
|
|
|
392
405
|
env.MNEMOM_AGENT_REMOTE_CONTROL =
|
|
393
406
|
cfg.launch === "server" ? "server" : cfg.launch === "remote-control" ? "1" : "0";
|
|
394
407
|
}
|
|
408
|
+
if (cfg.auth && !(env[AGENT_AUTH_ENV] ?? "").trim()) {
|
|
409
|
+
env[AGENT_AUTH_ENV] = cfg.auth;
|
|
410
|
+
}
|
|
395
411
|
if (cfg.context !== undefined && !(env.MNEMOM_AGENT_CONTEXT ?? "").trim()) {
|
|
396
412
|
env.MNEMOM_AGENT_CONTEXT = cfg.context ? "1" : "0";
|
|
397
413
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mnemom agent` with a Claude subscription login (`auth = subscription`) — Team and
|
|
3
|
+
* Enterprise plans only; the gateway checks the plan with Anthropic and refuses anything
|
|
4
|
+
* else (gateway/src/subscription-auth.ts).
|
|
5
|
+
*
|
|
6
|
+
* Claude Code then authenticates to the gateway with its own claude.ai OAuth login: the
|
|
7
|
+
* launch sets ANTHROPIC_BASE_URL and leaves ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN unset,
|
|
8
|
+
* so Claude Code sends `Authorization: Bearer <its login>`. No Anthropic key is involved.
|
|
9
|
+
* The gateway anchors the governed identity on a Mnemom API key instead
|
|
10
|
+
* (`x-mnemom-api-key`), which this module resolves — env, then the 0600 store, then a
|
|
11
|
+
* freshly minted gateway-only key — and the agent is created in that key's org on the
|
|
12
|
+
* first turn, so there is no CLI-side birth or claim.
|
|
13
|
+
*
|
|
14
|
+
* v1 is terminal-only: the Remote Control proxy rewrites the OAuth bearer away by design,
|
|
15
|
+
* and the context-handoff tool authenticates with an Anthropic key.
|
|
16
|
+
*
|
|
17
|
+
* Pure apart from the injected deps, so every arm is unit-tested.
|
|
18
|
+
*/
|
|
19
|
+
import type { LaunchShape } from "./agent.js";
|
|
20
|
+
export type AgentAuth = "api-key" | "subscription";
|
|
21
|
+
export declare const AGENT_AUTHS: readonly AgentAuth[];
|
|
22
|
+
/** Env override for the setting (the launcher.toml `auth` key folds into it). */
|
|
23
|
+
export declare const AGENT_AUTH_ENV = "MNEMOM_AGENT_AUTH";
|
|
24
|
+
/** Env override for the Mnemom key a subscription launch sends. */
|
|
25
|
+
export declare const MNEMOM_KEY_ENV = "MNEMOM_AGENT_MNEMOM_KEY";
|
|
26
|
+
/** Field in ~/.mnemom/launcher.json (0600) holding the minted key. */
|
|
27
|
+
export declare const MNEMOM_KEY_STORE_FIELD = "mnemom_api_key";
|
|
28
|
+
export declare function coerceAgentAuth(raw: string): AgentAuth;
|
|
29
|
+
/** `--auth` flag > MNEMOM_AGENT_AUTH (launcher.toml folds in here) > api-key. */
|
|
30
|
+
export declare function resolveAgentAuth(flag: string | undefined, env: Record<string, string | undefined>): AgentAuth;
|
|
31
|
+
export interface MnemomKeyDeps {
|
|
32
|
+
env: Record<string, string | undefined>;
|
|
33
|
+
readStored(): string | undefined;
|
|
34
|
+
/** Mint a gateway-only personal key for this machine; returns the secret. */
|
|
35
|
+
mint(): Promise<string>;
|
|
36
|
+
store(key: string): void;
|
|
37
|
+
say(line: string): void;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The Mnemom key a subscription launch sends: MNEMOM_AGENT_MNEMOM_KEY, else the stored
|
|
41
|
+
* key, else mint one (gateway scope only) and store it. The key is never printed.
|
|
42
|
+
*/
|
|
43
|
+
export declare function resolveSubscriptionMnemomKey(deps: MnemomKeyDeps): Promise<string>;
|
|
44
|
+
/**
|
|
45
|
+
* Subscription login is terminal-only in v1. An explicit Remote Control / server request
|
|
46
|
+
* is refused; an automatic upgrade to Remote Control falls back to terminal with a notice.
|
|
47
|
+
*/
|
|
48
|
+
export declare function subscriptionShape(shape: LaunchShape, shapeExplicit: boolean, say: (line: string) => void): LaunchShape;
|
|
49
|
+
/**
|
|
50
|
+
* The child env for a subscription launch: the gateway door, the Mnemom headers, and
|
|
51
|
+
* NOTHING that would make Claude Code send a key instead of its claude.ai login.
|
|
52
|
+
*/
|
|
53
|
+
export declare function subscriptionChildEnv(base: NodeJS.ProcessEnv, door: string, headers: string): NodeJS.ProcessEnv;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mnemom agent` with a Claude subscription login (`auth = subscription`) — Team and
|
|
3
|
+
* Enterprise plans only; the gateway checks the plan with Anthropic and refuses anything
|
|
4
|
+
* else (gateway/src/subscription-auth.ts).
|
|
5
|
+
*
|
|
6
|
+
* Claude Code then authenticates to the gateway with its own claude.ai OAuth login: the
|
|
7
|
+
* launch sets ANTHROPIC_BASE_URL and leaves ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN unset,
|
|
8
|
+
* so Claude Code sends `Authorization: Bearer <its login>`. No Anthropic key is involved.
|
|
9
|
+
* The gateway anchors the governed identity on a Mnemom API key instead
|
|
10
|
+
* (`x-mnemom-api-key`), which this module resolves — env, then the 0600 store, then a
|
|
11
|
+
* freshly minted gateway-only key — and the agent is created in that key's org on the
|
|
12
|
+
* first turn, so there is no CLI-side birth or claim.
|
|
13
|
+
*
|
|
14
|
+
* v1 is terminal-only: the Remote Control proxy rewrites the OAuth bearer away by design,
|
|
15
|
+
* and the context-handoff tool authenticates with an Anthropic key.
|
|
16
|
+
*
|
|
17
|
+
* Pure apart from the injected deps, so every arm is unit-tested.
|
|
18
|
+
*/
|
|
19
|
+
export const AGENT_AUTHS = ["api-key", "subscription"];
|
|
20
|
+
/** Env override for the setting (the launcher.toml `auth` key folds into it). */
|
|
21
|
+
export const AGENT_AUTH_ENV = "MNEMOM_AGENT_AUTH";
|
|
22
|
+
/** Env override for the Mnemom key a subscription launch sends. */
|
|
23
|
+
export const MNEMOM_KEY_ENV = "MNEMOM_AGENT_MNEMOM_KEY";
|
|
24
|
+
/** Field in ~/.mnemom/launcher.json (0600) holding the minted key. */
|
|
25
|
+
export const MNEMOM_KEY_STORE_FIELD = "mnemom_api_key";
|
|
26
|
+
/**
|
|
27
|
+
* The key is spliced into the newline-joined ANTHROPIC_CUSTOM_HEADERS block, so an embedded
|
|
28
|
+
* CR/LF (a multi-line paste, a bad env value) would inject extra header lines.
|
|
29
|
+
*/
|
|
30
|
+
function headerSafe(raw) {
|
|
31
|
+
return raw.replace(/[\r\n]+/g, "").trim();
|
|
32
|
+
}
|
|
33
|
+
export function coerceAgentAuth(raw) {
|
|
34
|
+
const v = raw.trim().toLowerCase();
|
|
35
|
+
if (AGENT_AUTHS.includes(v))
|
|
36
|
+
return v;
|
|
37
|
+
throw new Error(`auth must be one of ${AGENT_AUTHS.join(", ")} (got '${raw}').`);
|
|
38
|
+
}
|
|
39
|
+
/** `--auth` flag > MNEMOM_AGENT_AUTH (launcher.toml folds in here) > api-key. */
|
|
40
|
+
export function resolveAgentAuth(flag, env) {
|
|
41
|
+
if (flag !== undefined && flag.trim())
|
|
42
|
+
return coerceAgentAuth(flag);
|
|
43
|
+
const fromEnv = (env[AGENT_AUTH_ENV] ?? "").trim();
|
|
44
|
+
return fromEnv ? coerceAgentAuth(fromEnv) : "api-key";
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The Mnemom key a subscription launch sends: MNEMOM_AGENT_MNEMOM_KEY, else the stored
|
|
48
|
+
* key, else mint one (gateway scope only) and store it. The key is never printed.
|
|
49
|
+
*/
|
|
50
|
+
export async function resolveSubscriptionMnemomKey(deps) {
|
|
51
|
+
const fromEnv = headerSafe(deps.env[MNEMOM_KEY_ENV] ?? "");
|
|
52
|
+
if (fromEnv)
|
|
53
|
+
return fromEnv;
|
|
54
|
+
const stored = headerSafe(deps.readStored() ?? "");
|
|
55
|
+
if (stored)
|
|
56
|
+
return stored;
|
|
57
|
+
let minted;
|
|
58
|
+
try {
|
|
59
|
+
minted = headerSafe(await deps.mint());
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
63
|
+
throw new Error(`mnemom agent: subscription login needs a Mnemom API key and minting one failed (${msg}).\n` +
|
|
64
|
+
` Create one with \`mnemom api-key create --name mnemom-agent --scopes gateway\` and set ${MNEMOM_KEY_ENV}.`, { cause: err });
|
|
65
|
+
}
|
|
66
|
+
if (!minted)
|
|
67
|
+
throw new Error("mnemom agent: the API returned an empty Mnemom key.");
|
|
68
|
+
try {
|
|
69
|
+
deps.store(minted);
|
|
70
|
+
deps.say("mnemom agent: minted a gateway-only Mnemom API key for subscription login (saved to ~/.mnemom/launcher.json, 0600).");
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
deps.say("mnemom agent: minted a gateway-only Mnemom API key for this launch but could not save it — the next launch mints another.");
|
|
74
|
+
}
|
|
75
|
+
return minted;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Subscription login is terminal-only in v1. An explicit Remote Control / server request
|
|
79
|
+
* is refused; an automatic upgrade to Remote Control falls back to terminal with a notice.
|
|
80
|
+
*/
|
|
81
|
+
export function subscriptionShape(shape, shapeExplicit, say) {
|
|
82
|
+
if (shape === "terminal")
|
|
83
|
+
return shape;
|
|
84
|
+
if (shapeExplicit) {
|
|
85
|
+
throw new Error(`mnemom agent: subscription login runs terminal-only for now — drop --${shape === "server" ? "server" : "remote-control"} (or \`mnemom agent config set launch terminal\`), or use auth = api-key.`);
|
|
86
|
+
}
|
|
87
|
+
say("mnemom agent: subscription login runs terminal-only for now — launching in this terminal.");
|
|
88
|
+
return "terminal";
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The child env for a subscription launch: the gateway door, the Mnemom headers, and
|
|
92
|
+
* NOTHING that would make Claude Code send a key instead of its claude.ai login.
|
|
93
|
+
*/
|
|
94
|
+
export function subscriptionChildEnv(base, door, headers) {
|
|
95
|
+
const env = { ...base };
|
|
96
|
+
delete env.ANTHROPIC_API_KEY;
|
|
97
|
+
delete env.ANTHROPIC_AUTH_TOKEN;
|
|
98
|
+
// The key already rides in the headers; the child process has no other use for it.
|
|
99
|
+
delete env.MNEMOM_AGENT_MNEMOM_KEY;
|
|
100
|
+
env.ANTHROPIC_BASE_URL = door;
|
|
101
|
+
env.ANTHROPIC_CUSTOM_HEADERS = headers;
|
|
102
|
+
return env;
|
|
103
|
+
}
|