@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.
Files changed (48) hide show
  1. package/README.md +1 -0
  2. package/dist/commands/agents.d.ts +14 -0
  3. package/dist/commands/agents.js +100 -2
  4. package/dist/commands/card.d.ts +43 -0
  5. package/dist/commands/card.js +153 -102
  6. package/dist/commands/code-config.d.ts +17 -0
  7. package/dist/commands/code-config.js +147 -0
  8. package/dist/commands/code-doctor.d.ts +18 -0
  9. package/dist/commands/code-doctor.js +138 -0
  10. package/dist/commands/code-setup.d.ts +97 -0
  11. package/dist/commands/code-setup.js +330 -0
  12. package/dist/commands/code.d.ts +133 -0
  13. package/dist/commands/code.js +661 -0
  14. package/dist/commands/logs.js +11 -1
  15. package/dist/commands/onboard.d.ts +59 -0
  16. package/dist/commands/onboard.js +395 -0
  17. package/dist/commands/org.d.ts +13 -0
  18. package/dist/commands/org.js +63 -2
  19. package/dist/commands/protection.d.ts +10 -0
  20. package/dist/commands/protection.js +109 -0
  21. package/dist/commands/status.js +5 -0
  22. package/dist/commands/try-me.js +9 -0
  23. package/dist/commands/usage.d.ts +35 -0
  24. package/dist/commands/usage.js +265 -0
  25. package/dist/commands/wrap.d.ts +28 -0
  26. package/dist/commands/wrap.js +331 -0
  27. package/dist/index.js +315 -7
  28. package/dist/lib/agent-config.d.ts +27 -0
  29. package/dist/lib/agent-config.js +86 -0
  30. package/dist/lib/api.d.ts +139 -1
  31. package/dist/lib/api.js +132 -183
  32. package/dist/lib/cli-config.d.ts +33 -0
  33. package/dist/lib/cli-config.js +70 -0
  34. package/dist/lib/code-config.d.ts +78 -0
  35. package/dist/lib/code-config.js +281 -0
  36. package/dist/lib/code.d.ts +154 -0
  37. package/dist/lib/code.js +252 -0
  38. package/dist/lib/config.d.ts +10 -0
  39. package/dist/lib/config.js +39 -3
  40. package/dist/lib/keyed-identity.d.ts +35 -0
  41. package/dist/lib/keyed-identity.js +363 -0
  42. package/dist/lib/protection-drift.d.ts +117 -0
  43. package/dist/lib/protection-drift.js +180 -0
  44. package/dist/lib/skills.js +25 -12
  45. package/dist/lib/version-gate.d.ts +37 -0
  46. package/dist/lib/version-gate.js +84 -0
  47. package/dist/rc-proxy.mjs +341 -0
  48. 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: opts.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
+ }