@mnemom/mnemom 0.16.2 → 0.17.0-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/README.md +1 -0
- package/dist/commands/agents.d.ts +14 -0
- package/dist/commands/agents.js +100 -2
- package/dist/commands/card.d.ts +43 -0
- package/dist/commands/card.js +153 -102
- package/dist/commands/code-config.d.ts +17 -0
- package/dist/commands/code-config.js +147 -0
- package/dist/commands/code-doctor.d.ts +18 -0
- package/dist/commands/code-doctor.js +138 -0
- package/dist/commands/code-setup.d.ts +97 -0
- package/dist/commands/code-setup.js +330 -0
- package/dist/commands/code.d.ts +133 -0
- package/dist/commands/code.js +661 -0
- package/dist/commands/logs.js +11 -1
- package/dist/commands/onboard.d.ts +59 -0
- package/dist/commands/onboard.js +395 -0
- package/dist/commands/org.d.ts +13 -0
- package/dist/commands/org.js +63 -2
- package/dist/commands/protection.d.ts +10 -0
- package/dist/commands/protection.js +109 -0
- package/dist/commands/status.js +5 -0
- package/dist/commands/try-me.js +9 -0
- package/dist/commands/usage.d.ts +35 -0
- package/dist/commands/usage.js +265 -0
- package/dist/commands/wrap.d.ts +28 -0
- package/dist/commands/wrap.js +331 -0
- package/dist/index.js +315 -7
- package/dist/lib/agent-config.d.ts +27 -0
- package/dist/lib/agent-config.js +86 -0
- package/dist/lib/api.d.ts +139 -1
- package/dist/lib/api.js +132 -183
- package/dist/lib/cli-config.d.ts +33 -0
- package/dist/lib/cli-config.js +70 -0
- package/dist/lib/code-config.d.ts +78 -0
- package/dist/lib/code-config.js +281 -0
- package/dist/lib/code.d.ts +154 -0
- package/dist/lib/code.js +252 -0
- package/dist/lib/config.d.ts +10 -0
- package/dist/lib/config.js +39 -3
- package/dist/lib/keyed-identity.d.ts +35 -0
- package/dist/lib/keyed-identity.js +363 -0
- package/dist/lib/protection-drift.d.ts +117 -0
- package/dist/lib/protection-drift.js +180 -0
- package/dist/lib/skills.js +25 -12
- package/dist/lib/version-gate.d.ts +37 -0
- package/dist/lib/version-gate.js +84 -0
- package/dist/rc-proxy.mjs +341 -0
- package/package.json +9 -7
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { program } from "commander";
|
|
2
|
+
import { program, Option } from "commander";
|
|
3
3
|
import { isEntrypoint } from "./lib/entrypoint.js";
|
|
4
4
|
import { CLI_VERSION } from "./version.js";
|
|
5
5
|
import { statusCommand } from "./commands/status.js";
|
|
@@ -8,9 +8,9 @@ import { logsCommand } from "./commands/logs.js";
|
|
|
8
8
|
import { licenseActivateCommand, licenseStatusCommand, licenseDeactivateCommand, } from "./commands/license.js";
|
|
9
9
|
import { cardShowCommand, cardPublishCommand, cardValidateCommand, cardEditCommand, cardEvaluateCommand, } from "./commands/card.js";
|
|
10
10
|
import { policyInitCommand, policyValidateCommand, policyPublishCommand, policyListCommand, policyTestCommand, policyEvaluateCommand, } from "./commands/policy.js";
|
|
11
|
-
import { protectionShowCommand, protectionPublishCommand, protectionValidateCommand, protectionEditCommand, } from "./commands/protection.js";
|
|
12
|
-
import { agentsListCommand, agentsClaimCommand } from "./commands/agents.js";
|
|
13
|
-
import { orgListCommand, orgShowCommand } from "./commands/org.js";
|
|
11
|
+
import { protectionShowCommand, protectionPublishCommand, protectionValidateCommand, protectionEditCommand, protectionDriftCommand, } from "./commands/protection.js";
|
|
12
|
+
import { agentsListCommand, agentsClaimCommand, agentsMoveCommand } from "./commands/agents.js";
|
|
13
|
+
import { orgListCommand, orgShowCommand, orgUseCommand } from "./commands/org.js";
|
|
14
14
|
import { teamListCommand, teamShowCommand, teamTemplateCommand, teamPreviewComposeCommand, teamAdminGrantCommand, teamAdminRevokeCommand, teamAdminListCommand, teamCoverageCommand, } from "./commands/team.js";
|
|
15
15
|
import { advisoriesListCommand, advisoriesShowCommand } from "./commands/advisories.js";
|
|
16
16
|
import { postureListCommand, postureShowCommand, postureCreateCommand, postureUpdateCommand, postureCloneCommand, postureRevisionsCommand, postureDiffCommand, postureAssignCommand, postureUnassignCommand, posturePreviewComposeCommand, postureDeleteCommand, } from "./commands/posture.js";
|
|
@@ -20,7 +20,15 @@ import { apiKeyListCommand, apiKeyCreateCommand, apiKeyRotateCommand, apiKeyRevo
|
|
|
20
20
|
import { webhooksListCommand, webhooksGetCommand, webhooksCreateCommand, webhooksUpdateCommand, webhooksDeleteCommand, webhooksRotateSecretCommand, webhooksTriggerCommand, webhooksListDeliveriesCommand, webhooksRedeliverCommand, webhooksReplayCommand, } from "./commands/webhooks.js";
|
|
21
21
|
import { listenCommand } from "./commands/listen.js";
|
|
22
22
|
import { tryMeCommand } from "./commands/try-me.js";
|
|
23
|
+
import { codeCommand } from "./commands/code.js";
|
|
24
|
+
import { codeConfigCommand } from "./commands/code-config.js";
|
|
25
|
+
import { codeDoctorCommand } from "./commands/code-doctor.js";
|
|
26
|
+
import { codeSetupCommand } from "./commands/code-setup.js";
|
|
27
|
+
import { applyConfigToEnv, loadCodeConfig } from "./lib/code-config.js";
|
|
28
|
+
import { wrapCommand } from "./commands/wrap.js";
|
|
29
|
+
import { onboardCommand } from "./commands/onboard.js";
|
|
23
30
|
import { skillsListCommand, skillsDescribeCommand } from "./commands/skills.js";
|
|
31
|
+
import { usageCommand, parseNumericFlag, parseDaysFlag } from "./commands/usage.js";
|
|
24
32
|
program
|
|
25
33
|
.name("mnemom")
|
|
26
34
|
.description("Transparent AI agent tracing")
|
|
@@ -71,6 +79,195 @@ program
|
|
|
71
79
|
process.exit(1);
|
|
72
80
|
}
|
|
73
81
|
});
|
|
82
|
+
// ── code — first-class governed coding-agent launcher ────────────────────────
|
|
83
|
+
//
|
|
84
|
+
// `mnemom code <scenario>` launches your coding-agent CLI (Claude Code in v1) with
|
|
85
|
+
// its dev-time model traffic routed through the Mnemom gateway's /anthropic door
|
|
86
|
+
// for observability + governance, under a governed agent identity (the
|
|
87
|
+
// program-level `--agent`, default "code-agent") and an optional per-conversation
|
|
88
|
+
// CONTRACT the gateway seals on the first turn. Three launch shapes — terminal
|
|
89
|
+
// (default), --remote-control (interactive RC in this terminal), --server
|
|
90
|
+
// (headless RC dispatcher) — with yolo on by default. `--cli` selects the
|
|
91
|
+
// executable (name or full path; default claude). The us-2/prod door is the
|
|
92
|
+
// DEFAULT; set MNEMOM_CODE_GATEWAY (or --gateway) to point elsewhere. The
|
|
93
|
+
// Anthropic key is resolved with zero 1Password dependency (env → ~/.mnemom store
|
|
94
|
+
// → interactive prompt) inside the launched process and never logged. See
|
|
95
|
+
// commands/code.ts.
|
|
96
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
97
|
+
const collectRepeatable = (value, previous) => {
|
|
98
|
+
previous.push(value);
|
|
99
|
+
return previous;
|
|
100
|
+
};
|
|
101
|
+
program
|
|
102
|
+
.command("code <scenario> [passthrough...]")
|
|
103
|
+
.description("Launch a governed coding agent through the Mnemom gateway (governed identity + optional sealed contract). " +
|
|
104
|
+
"Sub-verbs: `mnemom code setup` provisions your governed agent; `mnemom code doctor` preflights prerequisites; `mnemom code config …` manages persistent settings (~/.mnemom/code.toml).")
|
|
105
|
+
.option("--goal <statement>", "Contract goal statement (required if any contract flag is given)")
|
|
106
|
+
.option("--requirement <text>", "Contract requirement (repeatable)", collectRepeatable, [])
|
|
107
|
+
.option("--allow <glob>", "Allowed path/glob (repeatable)", collectRepeatable, [])
|
|
108
|
+
.option("--forbid <glob>", "Forbidden path/glob (repeatable)", collectRepeatable, [])
|
|
109
|
+
.option("--goal-id <id>", "Stable goal id to correlate this contract across sessions")
|
|
110
|
+
.option("--budget <usd>", "Guardrail: spend ceiling in USD (positive number)")
|
|
111
|
+
.option("--max-turns <n>", "Guardrail: turn ceiling (positive integer)")
|
|
112
|
+
.option("--stall <n>", "Guardrail: consecutive no-write turns (positive integer)")
|
|
113
|
+
.option("--conversation-id <id>", "x-mnemom-conversation-id (default: auto-generated)")
|
|
114
|
+
.option("--cli <name|path>", "Coding-agent CLI to launch: a name or full path (default: claude)")
|
|
115
|
+
.option("--gateway <url>", "Gateway host override (default: https://gateway.mnemom.ai; or MNEMOM_CODE_GATEWAY)")
|
|
116
|
+
.option("--remote-control", "Interactive Remote Control session in THIS terminal (behind a local proxy)")
|
|
117
|
+
.option("--server", "Headless `claude remote-control` dispatcher (behind a local proxy)")
|
|
118
|
+
.option("--model <alias|id>", "Model to launch (CLI alias or concrete id)")
|
|
119
|
+
.addOption(new Option("--effort <level>", "Reasoning effort").choices([
|
|
120
|
+
"low",
|
|
121
|
+
"medium",
|
|
122
|
+
"high",
|
|
123
|
+
"xhigh",
|
|
124
|
+
"max",
|
|
125
|
+
]))
|
|
126
|
+
.option("--trim-mcp", "Launch the CLI with no MCP servers (--strict-mcp-config + empty --mcp-config)")
|
|
127
|
+
.option("--setup", "Provision + claim the governed agent before launch (non-interactive opt-in)")
|
|
128
|
+
.option("--no-setup", "Skip governed-agent provisioning entirely for this launch")
|
|
129
|
+
.option("--no-yolo", "Do NOT pass --dangerously-skip-permissions (yolo is on by default)")
|
|
130
|
+
.option("--no-context-hint", "Do NOT seed Claude Code's context-hint feature flag before launch")
|
|
131
|
+
.option("--dry-run", "Print the resolved launch plan and exit — no key read, no spawn")
|
|
132
|
+
.action(async (scenario, passthrough, opts) => {
|
|
133
|
+
try {
|
|
134
|
+
// Sub-verb: `mnemom code config …` manages persistent settings and never
|
|
135
|
+
// launches. (A scenario literally named "config" is reserved for this.)
|
|
136
|
+
if (scenario === "config") {
|
|
137
|
+
await codeConfigCommand(passthrough);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (opts.gateway)
|
|
141
|
+
process.env.MNEMOM_CODE_GATEWAY = opts.gateway;
|
|
142
|
+
// Persistent settings (~/.mnemom/code.toml) sit UNDER env and flags:
|
|
143
|
+
// precedence is flag > env > code.toml > built-in. Env-backed settings are
|
|
144
|
+
// folded into env only when unset (so env/flags still win); the resolvers
|
|
145
|
+
// in lib/code.ts keep the built-in default as the final fallback.
|
|
146
|
+
const cfg = loadCodeConfig();
|
|
147
|
+
applyConfigToEnv(cfg, process.env);
|
|
148
|
+
// Sub-verb: `mnemom code doctor` runs a read-only preflight and never
|
|
149
|
+
// launches. Placed after config load so it reports the effective settings.
|
|
150
|
+
if (scenario === "doctor") {
|
|
151
|
+
await codeDoctorCommand({ cli: opts.cli });
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
// Sub-verb: `mnemom code setup` provisions/claims the governed agent and
|
|
155
|
+
// never launches. The agent slug follows the same precedence as a launch.
|
|
156
|
+
if (scenario === "setup") {
|
|
157
|
+
await codeSetupCommand({ agent: program.opts().agent ?? cfg.agent });
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const numToStr = (n) => n === undefined ? undefined : String(n);
|
|
161
|
+
// Config guardrails apply only when a contract is actually intended (a
|
|
162
|
+
// --goal is present), so a saved ceiling never forces an unrequested
|
|
163
|
+
// contract (which would then demand --goal and fail the launch).
|
|
164
|
+
const contractIntended = Boolean(opts.goal && opts.goal.trim());
|
|
165
|
+
// The governed identity rides on the program-level `--agent` (which
|
|
166
|
+
// shadows a subcommand `--agent` under commander@12 — the MNE-238 class),
|
|
167
|
+
// exactly as status/logs/onboard read it.
|
|
168
|
+
const parentOpts = program.opts();
|
|
169
|
+
await codeCommand(scenario, {
|
|
170
|
+
goal: opts.goal,
|
|
171
|
+
requirement: opts.requirement,
|
|
172
|
+
allow: opts.allow,
|
|
173
|
+
forbid: opts.forbid,
|
|
174
|
+
goalId: opts.goalId,
|
|
175
|
+
budget: opts.budget ?? (contractIntended ? numToStr(cfg.guardrails?.budgetUsd) : undefined),
|
|
176
|
+
maxTurns: opts.maxTurns ?? (contractIntended ? numToStr(cfg.guardrails?.maxTurns) : undefined),
|
|
177
|
+
stall: opts.stall ?? (contractIntended ? numToStr(cfg.guardrails?.stallTurns) : undefined),
|
|
178
|
+
agent: parentOpts.agent ?? cfg.agent,
|
|
179
|
+
conversationId: opts.conversationId,
|
|
180
|
+
cli: opts.cli,
|
|
181
|
+
remoteControl: opts.remoteControl,
|
|
182
|
+
server: opts.server,
|
|
183
|
+
model: opts.model ?? cfg.model,
|
|
184
|
+
effort: opts.effort ?? cfg.effort,
|
|
185
|
+
trimMcp: opts.trimMcp,
|
|
186
|
+
yolo: opts.yolo,
|
|
187
|
+
contextHint: opts.contextHint,
|
|
188
|
+
setup: opts.setup,
|
|
189
|
+
keySource: cfg.keySource,
|
|
190
|
+
passthrough,
|
|
191
|
+
dryRun: opts.dryRun,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
console.error("Error:", error instanceof Error ? error.message : error);
|
|
196
|
+
process.exit(1);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
// ── wrap (MNE-935, A4) — instrument an existing agent through the gateway ────
|
|
200
|
+
//
|
|
201
|
+
// `mnemom wrap` is the "bring your production agent" skill: it asks for
|
|
202
|
+
// provider + framework + agent name, births the agent through the gateway,
|
|
203
|
+
// seeds starter alignment + protection cards, and emits a drop-in code snippet.
|
|
204
|
+
// Extends the skill-runner contract from A1 (MNE-932). See commands/wrap.ts.
|
|
205
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
206
|
+
program
|
|
207
|
+
.command("wrap")
|
|
208
|
+
.description("Instrument an existing agent through the Mnemom gateway (born → cards → snippet)")
|
|
209
|
+
.option("--provider <provider>", "AI provider: anthropic, openai, gemini (default: prompt)")
|
|
210
|
+
.option("--framework <framework>", "Language/framework: python, node (default: prompt)")
|
|
211
|
+
.option("--name <name>", "Agent name used as the x-mnemom-agent header value")
|
|
212
|
+
.option("--provider-key <key>", "Provider API key for the one-time birth call (default: reads env)")
|
|
213
|
+
.option("-y, --yes", "Non-interactive: accept defaults, skip all prompts")
|
|
214
|
+
.option("--json", "Emit machine-readable result (skill / verdict / agent_id / steps)")
|
|
215
|
+
.action(async (opts) => {
|
|
216
|
+
try {
|
|
217
|
+
await wrapCommand({
|
|
218
|
+
provider: opts.provider,
|
|
219
|
+
framework: opts.framework,
|
|
220
|
+
name: opts.name,
|
|
221
|
+
providerKey: opts.providerKey,
|
|
222
|
+
yes: opts.yes,
|
|
223
|
+
json: opts.json,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
console.error("Error:", error instanceof Error ? error.message : error);
|
|
228
|
+
process.exit(1);
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
// ── onboard (MNE-933, A2) — self-onboard the calling agent end-to-end ────────
|
|
232
|
+
//
|
|
233
|
+
// `mnemom onboard` runs the sovereignty path for the CALLING agent itself:
|
|
234
|
+
// scan its trust posture → claim its identity → declare an alignment card →
|
|
235
|
+
// surface its Trust Rating → hand back a public badge URL. One command, no
|
|
236
|
+
// manifest. Additive + human-in-the-loop preserving: the only mutation is the
|
|
237
|
+
// agent's own alignment-card declaration (its standing scoped token). The agent
|
|
238
|
+
// authenticates as its OWN device-grant principal (MNE-944). The Trust Rating
|
|
239
|
+
// is PROVISIONAL until the observer pipeline generates traces — the signed
|
|
240
|
+
// rating + rendered badge are computed server-side, post-hoc. See
|
|
241
|
+
// commands/onboard.ts for the auth model.
|
|
242
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
243
|
+
program
|
|
244
|
+
.command("onboard")
|
|
245
|
+
.description("Self-onboard the calling agent (scan → claim → declare → rating → badge); the Trust Rating is provisional until the observer pipeline generates traces")
|
|
246
|
+
.option("--json", "Emit machine-readable step outcomes (implies non-interactive)")
|
|
247
|
+
.option("-y, --yes", "Non-interactive: accept defaults, skip all prompts")
|
|
248
|
+
.option("--key <key>", "The agent's provider API key — the CLI derives the claim hash proof")
|
|
249
|
+
.option("--hash-proof <hex>", "A pre-computed 64-hex hash proof (advanced/CI; alternative to --key)")
|
|
250
|
+
.option("--no-open", "Never auto-open the badge URL in a browser — just print it")
|
|
251
|
+
.action(async (opts) => {
|
|
252
|
+
try {
|
|
253
|
+
// The calling agent is selected via the program-level `--agent` (which
|
|
254
|
+
// shadows any subcommand `--agent` under commander@12 — the MNE-238
|
|
255
|
+
// class), mirroring how `status`/`logs` read it.
|
|
256
|
+
const parentOpts = program.opts();
|
|
257
|
+
await onboardCommand({
|
|
258
|
+
json: opts.json,
|
|
259
|
+
yes: opts.yes,
|
|
260
|
+
key: opts.key,
|
|
261
|
+
hashProof: opts.hashProof,
|
|
262
|
+
open: opts.open,
|
|
263
|
+
agent: parentOpts.agent,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
267
|
+
console.error("Error:", error instanceof Error ? error.message : error);
|
|
268
|
+
process.exit(1);
|
|
269
|
+
}
|
|
270
|
+
});
|
|
74
271
|
// ── skills (MNE-1324) — discover the zero-install skill surface ──────────────
|
|
75
272
|
const skills = program
|
|
76
273
|
.command("skills")
|
|
@@ -346,6 +543,25 @@ protectionCmd
|
|
|
346
543
|
process.exit(1);
|
|
347
544
|
}
|
|
348
545
|
});
|
|
546
|
+
protectionCmd
|
|
547
|
+
.command("drift")
|
|
548
|
+
.argument("<file>", "Path to a committed protection-card snapshot (YAML or JSON)")
|
|
549
|
+
.description("Compare a committed snapshot against the live canonical card (read-only)")
|
|
550
|
+
.option("--strict", "Also fail on live fields the snapshot does not record")
|
|
551
|
+
.option("--json", "Emit the drift result as JSON")
|
|
552
|
+
.action(async (file, subOpts) => {
|
|
553
|
+
try {
|
|
554
|
+
const opts = program.opts();
|
|
555
|
+
await protectionDriftCommand(file, opts.agent, {
|
|
556
|
+
strict: subOpts.strict,
|
|
557
|
+
json: subOpts.json,
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
catch (error) {
|
|
561
|
+
console.error("Error:", error instanceof Error ? error.message : error);
|
|
562
|
+
process.exit(1);
|
|
563
|
+
}
|
|
564
|
+
});
|
|
349
565
|
// ============================================================================
|
|
350
566
|
// Policy commands (removed — stubs with migration guidance)
|
|
351
567
|
// ============================================================================
|
|
@@ -418,15 +634,27 @@ const agentsCmd = program
|
|
|
418
634
|
agentsCmd
|
|
419
635
|
.command("claim <id-or-name>")
|
|
420
636
|
.description("Claim an agent into an org (ADR-062 claim-to-org)")
|
|
421
|
-
.option("--org <slug>", "Org slug or id to claim into (default: your personal org)")
|
|
637
|
+
.option("--org <slug>", "Org slug or id to claim into (default: your ACTIVE org from `mnemom org use`, else your personal org)")
|
|
422
638
|
.option("--key <key>", "The agent's provider API key — the CLI derives the hash proof from it")
|
|
423
639
|
.option("--hash-proof <hex>", "A pre-computed 64-hex SHA-256 proof (advanced/CI; alternative to --key)")
|
|
424
640
|
.option("--name <name>", "Provisioned agent name for proof derivation — auto-resolved from the agent id when possible; pass only to override")
|
|
425
641
|
.option("--json", "Output JSON instead of human-readable text")
|
|
426
642
|
.action(async (idOrName, opts) => {
|
|
427
643
|
try {
|
|
644
|
+
// `agentsCmd` (the parent `agents` command) ALSO defines `--org` (for
|
|
645
|
+
// `mnemom agents --org <id>` list-scoping). Commander@12 binds a flag
|
|
646
|
+
// shared by a parent and a subcommand to whichever of the two owns it
|
|
647
|
+
// and runs first in the parse chain — here that's the parent — so
|
|
648
|
+
// `agents claim <id> --org <slug>` had its --org silently swallowed by
|
|
649
|
+
// `agentsCmd` before this subcommand's own `--org` option ever saw it
|
|
650
|
+
// (MNE-2496: confirmed live, the agent always landed in the caller's
|
|
651
|
+
// personal org regardless of --org). Same shadow class as the global
|
|
652
|
+
// `--agent` fix at MNE-238 (see the `advisories list`/`show` actions
|
|
653
|
+
// below) — resolve the local value first, falling back to the
|
|
654
|
+
// parent's captured value so either parse order still works.
|
|
655
|
+
const org = opts.org ?? agentsCmd.opts().org;
|
|
428
656
|
await agentsClaimCommand(idOrName, {
|
|
429
|
-
org
|
|
657
|
+
org,
|
|
430
658
|
key: opts.key,
|
|
431
659
|
hashProof: opts.hashProof,
|
|
432
660
|
name: opts.name,
|
|
@@ -438,6 +666,21 @@ agentsCmd
|
|
|
438
666
|
process.exit(1);
|
|
439
667
|
}
|
|
440
668
|
});
|
|
669
|
+
agentsCmd
|
|
670
|
+
.command("move <id-or-name>")
|
|
671
|
+
.description("Move an agent to another org — requires owner/admin in BOTH the current and destination org " +
|
|
672
|
+
"(role-based; no agent key needed, unlike re-claim)")
|
|
673
|
+
.option("--to <slug-or-id>", "Destination org (required; never defaults to the active org)")
|
|
674
|
+
.option("--json", "Output JSON instead of human-readable text")
|
|
675
|
+
.action(async (idOrName, opts) => {
|
|
676
|
+
try {
|
|
677
|
+
await agentsMoveCommand(idOrName, { to: opts.to, json: opts.json });
|
|
678
|
+
}
|
|
679
|
+
catch (error) {
|
|
680
|
+
console.error("Error:", error instanceof Error ? error.message : error);
|
|
681
|
+
process.exit(1);
|
|
682
|
+
}
|
|
683
|
+
});
|
|
441
684
|
// ============================================================================
|
|
442
685
|
// Organization management (ADR-044, Piece 1 of T1-3.1)
|
|
443
686
|
// ============================================================================
|
|
@@ -457,6 +700,21 @@ orgCmd
|
|
|
457
700
|
process.exit(1);
|
|
458
701
|
}
|
|
459
702
|
});
|
|
703
|
+
orgCmd
|
|
704
|
+
.command("use [slug-or-id]")
|
|
705
|
+
.description("Set the ACTIVE org — the default for org-scoped commands like `agents claim` " +
|
|
706
|
+
"(login binds no org; without this, claims land in your personal org). " +
|
|
707
|
+
"No argument shows the current setting.")
|
|
708
|
+
.option("--clear", "Forget the active org (org-scoped commands revert to your personal org)")
|
|
709
|
+
.action(async (slugOrId, options) => {
|
|
710
|
+
try {
|
|
711
|
+
await orgUseCommand(slugOrId, { clear: options.clear });
|
|
712
|
+
}
|
|
713
|
+
catch (error) {
|
|
714
|
+
console.error("Error:", error instanceof Error ? error.message : error);
|
|
715
|
+
process.exit(1);
|
|
716
|
+
}
|
|
717
|
+
});
|
|
460
718
|
orgCmd
|
|
461
719
|
.command("show [org_id]")
|
|
462
720
|
.description("Show details of an org (default: your personal org if --personal, else single membership)")
|
|
@@ -1060,7 +1318,8 @@ postureCmd
|
|
|
1060
1318
|
// ============================================================================
|
|
1061
1319
|
program
|
|
1062
1320
|
.command("login")
|
|
1063
|
-
.description("Authenticate with your Mnemom account"
|
|
1321
|
+
.description("Authenticate with your Mnemom account (note: login binds no org — set a default " +
|
|
1322
|
+
"for org-scoped commands with `mnemom org use <slug>`)")
|
|
1064
1323
|
.option("--no-browser", "Use the device code flow instead of opening a browser")
|
|
1065
1324
|
.action(async (options) => {
|
|
1066
1325
|
try {
|
|
@@ -1443,6 +1702,55 @@ program
|
|
|
1443
1702
|
process.exit(1);
|
|
1444
1703
|
}
|
|
1445
1704
|
});
|
|
1705
|
+
// ============================================================================
|
|
1706
|
+
// Usage attribution (MNE-3215 W6 / issue #1221)
|
|
1707
|
+
//
|
|
1708
|
+
// `mnemom usage --org <id>` shows per-person token and request consumption
|
|
1709
|
+
// for an org window. Gated by USAGE_ATTRIBUTION_API_ENABLED on the API side
|
|
1710
|
+
// (defaults off in every environment). Reports consumption only — no currency,
|
|
1711
|
+
// no spend, no cost.
|
|
1712
|
+
//
|
|
1713
|
+
// Every flag below maps 1:1 onto a query parameter the endpoint documents in
|
|
1714
|
+
// the API's `openapi.json` (`days`, `person_id`, `provider`, `model`, `limit`,
|
|
1715
|
+
// `cursor`). It is deliberately not a friendlier invented vocabulary: the first
|
|
1716
|
+
// version of this command offered `--period` and `--page`, neither of which the
|
|
1717
|
+
// endpoint accepts, so the server silently ignored both and returned an
|
|
1718
|
+
// unfiltered first page.
|
|
1719
|
+
// ============================================================================
|
|
1720
|
+
program
|
|
1721
|
+
.command("usage")
|
|
1722
|
+
.description("Show per-person token and request consumption for an org")
|
|
1723
|
+
.requiredOption("--org <id>", "Org ID to show consumption for")
|
|
1724
|
+
.option("--days <7|30|90>", "Reporting window in days (default: 30)")
|
|
1725
|
+
.option("--person <id>", "Filter to one person's consumption")
|
|
1726
|
+
.option("--provider <name>", "Filter to one provider")
|
|
1727
|
+
.option("--model <name>", "Filter to one model")
|
|
1728
|
+
.option("--limit <n>", "Max rows per page (default: 100, max 200)")
|
|
1729
|
+
.option("--cursor <cursor>", "Continue from a previous page's cursor")
|
|
1730
|
+
.option("--json", "Emit raw JSON instead of rendered output")
|
|
1731
|
+
.action(async (options) => {
|
|
1732
|
+
try {
|
|
1733
|
+
// A miskeyed numeric flag (e.g. `--limit foo`, `--limit 2abc`,
|
|
1734
|
+
// `--limit 0`, `--days 45`) warns and falls back to the default rather
|
|
1735
|
+
// than silently acting on a value the user did not type. Both live in
|
|
1736
|
+
// commands/usage.ts so their reject-and-warn arms are unit-testable
|
|
1737
|
+
// without driving Commander.
|
|
1738
|
+
await usageCommand({
|
|
1739
|
+
org: options.org,
|
|
1740
|
+
days: parseDaysFlag(options.days),
|
|
1741
|
+
person: options.person,
|
|
1742
|
+
provider: options.provider,
|
|
1743
|
+
model: options.model,
|
|
1744
|
+
limit: parseNumericFlag("--limit", options.limit),
|
|
1745
|
+
cursor: options.cursor,
|
|
1746
|
+
json: options.json,
|
|
1747
|
+
});
|
|
1748
|
+
}
|
|
1749
|
+
catch (error) {
|
|
1750
|
+
console.error("Error:", error instanceof Error ? error.message : error);
|
|
1751
|
+
process.exit(1);
|
|
1752
|
+
}
|
|
1753
|
+
});
|
|
1446
1754
|
// Export the fully-assembled commander program so tooling (e.g. the
|
|
1447
1755
|
// command-tree snapshot generator in scripts/gen-command-tree.mjs) can
|
|
1448
1756
|
// statically introspect the command surface WITHOUT executing the CLI.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent identity config store — ~/.mnemom/agent.json.
|
|
3
|
+
*
|
|
4
|
+
* Persists the calling agent's resolved identity so skill verbs (try-me,
|
|
5
|
+
* onboard, wrap) write on first success and read thereafter — no re-prompt for
|
|
6
|
+
* identity on subsequent invocations (MNE-937).
|
|
7
|
+
*
|
|
8
|
+
* Co-located with auth.ts → auth.json. Separate by design: auth.json holds
|
|
9
|
+
* bearer credentials (wiped on logout); agent.json holds identity metadata
|
|
10
|
+
* that survives re-login and provider key rotation.
|
|
11
|
+
*/
|
|
12
|
+
export interface AgentConfig {
|
|
13
|
+
agent_id?: string;
|
|
14
|
+
agent_name?: string;
|
|
15
|
+
org_id?: string;
|
|
16
|
+
gateway_url?: string;
|
|
17
|
+
}
|
|
18
|
+
/** Load the agent config; a missing or corrupt file returns `{}`. */
|
|
19
|
+
export declare function loadAgentConfig(): AgentConfig;
|
|
20
|
+
/** Persist the full agent config (replaces the file). */
|
|
21
|
+
export declare function saveAgentConfig(config: AgentConfig): void;
|
|
22
|
+
/** Shallow-merge `partial` into the existing config and persist. */
|
|
23
|
+
export declare function mergeAgentConfig(partial: Partial<AgentConfig>): void;
|
|
24
|
+
export declare function getAgentId(): string | undefined;
|
|
25
|
+
export declare function getAgentName(): string | undefined;
|
|
26
|
+
export declare function getOrgId(): string | undefined;
|
|
27
|
+
export declare function getGatewayUrl(): string | undefined;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent identity config store — ~/.mnemom/agent.json.
|
|
3
|
+
*
|
|
4
|
+
* Persists the calling agent's resolved identity so skill verbs (try-me,
|
|
5
|
+
* onboard, wrap) write on first success and read thereafter — no re-prompt for
|
|
6
|
+
* identity on subsequent invocations (MNE-937).
|
|
7
|
+
*
|
|
8
|
+
* Co-located with auth.ts → auth.json. Separate by design: auth.json holds
|
|
9
|
+
* bearer credentials (wiped on logout); agent.json holds identity metadata
|
|
10
|
+
* that survives re-login and provider key rotation.
|
|
11
|
+
*/
|
|
12
|
+
import * as fs from "node:fs";
|
|
13
|
+
import * as path from "node:path";
|
|
14
|
+
import { MNEMOM_DIR } from "./config.js";
|
|
15
|
+
function agentFile() {
|
|
16
|
+
return path.join(MNEMOM_DIR, "agent.json");
|
|
17
|
+
}
|
|
18
|
+
/** Load the agent config; a missing or corrupt file returns `{}`. */
|
|
19
|
+
export function loadAgentConfig() {
|
|
20
|
+
try {
|
|
21
|
+
if (!fs.existsSync(agentFile()))
|
|
22
|
+
return {};
|
|
23
|
+
const parsed = JSON.parse(fs.readFileSync(agentFile(), "utf-8"));
|
|
24
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return {};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function writeAgentConfig(config) {
|
|
31
|
+
if (!fs.existsSync(MNEMOM_DIR)) {
|
|
32
|
+
// 0700 to match the auth store — the directory holds credentials.
|
|
33
|
+
fs.mkdirSync(MNEMOM_DIR, { recursive: true, mode: 0o700 });
|
|
34
|
+
}
|
|
35
|
+
const resolvedPath = path.resolve(agentFile());
|
|
36
|
+
const tmpFile = `${resolvedPath}.${process.pid}.tmp`;
|
|
37
|
+
// Write-then-rename for atomicity; 0600 owner-only to match the auth store posture.
|
|
38
|
+
try {
|
|
39
|
+
fs.writeFileSync(tmpFile, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
40
|
+
try {
|
|
41
|
+
fs.chmodSync(tmpFile, 0o600);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
/* best effort on platforms without POSIX perms */
|
|
45
|
+
}
|
|
46
|
+
fs.renameSync(tmpFile, resolvedPath);
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
// Don't leave a partially-written `${pid}.tmp` sibling behind on a failed
|
|
50
|
+
// write/rename — best-effort unlink, then re-throw the original error.
|
|
51
|
+
try {
|
|
52
|
+
fs.unlinkSync(tmpFile);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
/* the tmp file may not have been created — nothing to clean up */
|
|
56
|
+
}
|
|
57
|
+
throw err;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/** Persist the full agent config (replaces the file). */
|
|
61
|
+
export function saveAgentConfig(config) {
|
|
62
|
+
writeAgentConfig(config);
|
|
63
|
+
}
|
|
64
|
+
/** Shallow-merge `partial` into the existing config and persist. */
|
|
65
|
+
export function mergeAgentConfig(partial) {
|
|
66
|
+
const current = loadAgentConfig();
|
|
67
|
+
const next = { ...current };
|
|
68
|
+
for (const key of Object.keys(partial)) {
|
|
69
|
+
if (partial[key] !== undefined) {
|
|
70
|
+
next[key] = partial[key];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
writeAgentConfig(next);
|
|
74
|
+
}
|
|
75
|
+
export function getAgentId() {
|
|
76
|
+
return loadAgentConfig().agent_id;
|
|
77
|
+
}
|
|
78
|
+
export function getAgentName() {
|
|
79
|
+
return loadAgentConfig().agent_name;
|
|
80
|
+
}
|
|
81
|
+
export function getOrgId() {
|
|
82
|
+
return loadAgentConfig().org_id;
|
|
83
|
+
}
|
|
84
|
+
export function getGatewayUrl() {
|
|
85
|
+
return loadAgentConfig().gateway_url;
|
|
86
|
+
}
|