@mnemom/mnemom 0.14.0 → 0.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,14 +1,24 @@
1
1
  import { createHash } from "node:crypto";
2
- import { listOrgAgents, listMyOrgs, getAgentByName, claimAgent, MnemomApiError, } from "../lib/api.js";
2
+ import { listAgents, listOrgAgents, listMyOrgs, getAgentByName, claimAgent, MnemomApiError, } from "../lib/api.js";
3
3
  import { requireAuth } from "../lib/auth.js";
4
4
  import { fmt } from "../lib/format.js";
5
5
  export async function agentsListCommand(options = {}) {
6
- await requireAuth();
6
+ const cred = await requireAuth();
7
7
  console.log(fmt.header("Agents"));
8
8
  console.log();
9
+ // API keys are agent-scoped, not user/org-scoped. --org requires a session to
10
+ // resolve org membership; route the key to the compatible endpoint instead so
11
+ // a valid key is never reported as unauthenticated.
12
+ if (cred.type === "api-key" && options.org) {
13
+ console.log(fmt.warn("API keys can't filter by org — org membership is a session concept.") + "\n");
14
+ console.log(fmt.dim(" To list org-scoped agents, log in with a session: mnemom login") + "\n");
15
+ return;
16
+ }
17
+ // Show the Org column only when a JWT session aggregates across all orgs.
18
+ const showOrg = cred.type === "jwt" && !options.org;
9
19
  let agents;
10
20
  try {
11
- agents = await listOrgAgents(options.org);
21
+ agents = cred.type === "api-key" ? await listAgents() : await listOrgAgents(options.org);
12
22
  }
13
23
  catch (err) {
14
24
  const msg = err instanceof Error ? err.message : String(err);
@@ -19,8 +29,6 @@ export async function agentsListCommand(options = {}) {
19
29
  console.log(options.org ? ` No agents found in org '${options.org}'.\n` : " No agents found.\n");
20
30
  return;
21
31
  }
22
- // Show the Org column only when aggregating across orgs; with --org it's redundant.
23
- const showOrg = !options.org;
24
32
  const nameW = 24;
25
33
  const idW = 40;
26
34
  const seenW = 14;
@@ -1,5 +1,5 @@
1
- import { resolveAgentId, getTraces, MnemomApiError } from "../lib/api.js";
2
- import { getGatewayUrl } from "../lib/config.js";
1
+ import { resolveAgentId, getTraces, MnemomApiError, } from "../lib/api.js";
2
+ import { getGatewayUrl, getWebsiteUrl } from "../lib/config.js";
3
3
  import { fmt } from "../lib/format.js";
4
4
  export async function logsCommand(options = {}) {
5
5
  const agentId = await resolveAgentId(options.agentName);
@@ -20,7 +20,7 @@ export async function logsCommand(options = {}) {
20
20
  displayTrace(trace);
21
21
  }
22
22
  console.log(`\nView more: mnemom logs --limit ${limit + 10}`);
23
- console.log(`Dashboard: https://mnemon.ai/dashboard/${agentId}\n`);
23
+ console.log(`Dashboard: ${getWebsiteUrl()}/agents/${agentId}\n`);
24
24
  }
25
25
  catch (error) {
26
26
  // A 404 means the agent isn't registered yet — treat as the empty state.
@@ -60,9 +60,36 @@ function displayTrace(trace) {
60
60
  console.log(` ${fmt.label("Reason:", ` ${preview}`)}`);
61
61
  }
62
62
  if (trace.verification && trace.verification.violations.length > 0) {
63
- console.log(` ${fmt.label("Issues:", ` ${trace.verification.violations.join(", ")}`)}`);
63
+ const issues = trace.verification.violations.map(formatViolation).join("; ");
64
+ console.log(` ${fmt.label("Issues:", ` ${issues}`)}`);
64
65
  }
65
66
  }
67
+ /**
68
+ * Render a single policy violation as a readable one-line string.
69
+ *
70
+ * The wire shape is an object ({type,tool,severity,reason} — canonical
71
+ * `PolicyViolation`); a naive `.join(", ")` string-coerces each to
72
+ * "[object Object]". We compose only the fields that are present, e.g.
73
+ * "[high] forbidden — write_file: tool not in capability set", and tolerate a
74
+ * bare-string element (legacy/forward compatibility) by returning it verbatim.
75
+ */
76
+ function formatViolation(v) {
77
+ if (typeof v === "string")
78
+ return v;
79
+ let line = "";
80
+ if (v.severity)
81
+ line += `[${v.severity}] `;
82
+ if (v.type)
83
+ line += v.type;
84
+ if (v.tool)
85
+ line += `${v.type ? " — " : ""}${v.tool}`;
86
+ if (v.reason)
87
+ line += `${v.type || v.tool ? ": " : ""}${v.reason}`;
88
+ const out = line.trim();
89
+ // Fall back to a structured dump only if no known field was present, so we
90
+ // never silently emit an empty Issues entry or "[object Object]".
91
+ return out || JSON.stringify(v);
92
+ }
66
93
  function formatTimestamp(iso) {
67
94
  try {
68
95
  const date = new Date(iso);
@@ -1,8 +1,7 @@
1
- import { getGatewayUrl } from "../lib/config.js";
1
+ import { getGatewayUrl, getWebsiteUrl } from "../lib/config.js";
2
2
  import { resolveAgentId, getAgent, getIntegrity, getTraces, MnemomApiError } from "../lib/api.js";
3
3
  import { isLoggedIn, getAuthInfo } from "../lib/auth.js";
4
4
  import { fmt } from "../lib/format.js";
5
- const DASHBOARD_URL = "https://mnemom.ai";
6
5
  export async function statusCommand(agentName) {
7
6
  console.log(fmt.header("mnemom status"));
8
7
  console.log();
@@ -31,7 +30,7 @@ export async function statusCommand(agentName) {
31
30
  console.log();
32
31
  console.log(fmt.label("Agent ID: ", agentId));
33
32
  console.log(fmt.label("Gateway: ", gatewayUrl));
34
- console.log(fmt.label("Dashboard:", ` ${DASHBOARD_URL}/agents/${agentId}`));
33
+ console.log(fmt.label("Dashboard:", ` ${getWebsiteUrl()}/agents/${agentId}`));
35
34
  // Trace summary if API connectivity is healthy
36
35
  if (apiCheck.status === "ok") {
37
36
  await showTraceSummary(agentId);
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { pathToFileURL } from "node:url";
3
2
  import { program } from "commander";
3
+ import { isEntrypoint } from "./lib/entrypoint.js";
4
4
  import { CLI_VERSION } from "./version.js";
5
5
  import { statusCommand } from "./commands/status.js";
6
6
  import { integrityCommand } from "./commands/integrity.js";
@@ -1326,8 +1326,8 @@ program
1326
1326
  // statically introspect the command surface WITHOUT executing the CLI.
1327
1327
  export { program };
1328
1328
  // Parse argv only when invoked as the CLI entrypoint — not when imported.
1329
- // (ESM has no `require.main`; compare this module's URL to argv[1].)
1330
- const invokedDirectly = process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href;
1331
- if (invokedDirectly) {
1329
+ // The symlink-safe guard lives in ./lib/entrypoint so it can be unit-tested
1330
+ // without executing this module's top-level parse().
1331
+ if (isEntrypoint(import.meta.url, process.argv[1])) {
1332
1332
  program.parse();
1333
1333
  }
package/dist/lib/api.d.ts CHANGED
@@ -72,7 +72,19 @@ export interface IntegrityScore {
72
72
  * reasoning, tool_name } — none of which the wire emits. `mnemom logs` rendered
73
73
  * every action as "[object Object]" and flagged EVERY trace as [VIOLATION]
74
74
  * (undefined `verified` is falsy). Field names + nesting now match the wire.
75
+ *
76
+ * `verification.violations` elements are OBJECTS ({type,tool,severity,reason}),
77
+ * not strings — produced by the observer/gateway mappers (canonical
78
+ * `PolicyViolation` in shared/policy-engine). A prior pass corrected `action`'s
79
+ * nesting but left `violations` mistyped, which re-introduced "[object Object]"
80
+ * on the Issues line. All fields are optional here for forward/backward tolerance.
75
81
  */
82
+ export interface TraceViolation {
83
+ type?: string;
84
+ tool?: string;
85
+ severity?: string;
86
+ reason?: string;
87
+ }
76
88
  export interface Trace {
77
89
  trace_id: string;
78
90
  agent_id: string;
@@ -90,7 +102,7 @@ export interface Trace {
90
102
  };
91
103
  verification: {
92
104
  verified: boolean;
93
- violations: string[];
105
+ violations: TraceViolation[];
94
106
  } | null;
95
107
  created_at?: string;
96
108
  }
@@ -208,11 +220,11 @@ export interface AgentListItem {
208
220
  key_prefix?: string | null;
209
221
  }
210
222
  /**
211
- * @deprecated Legacy per-user listing — `GET /v1/agents` is scoped by the
212
- * caller's `claimed_by` rows, which ADR-062 retired as an authorization /
213
- * listing boundary (org_id is now the sole boundary). New code must list via
214
- * {@link listOrgAgents} (org-scoped). Retained only for back-compat callers;
215
- * the `mnemom agents` command and name resolution no longer use it.
223
+ * Key-compatible agent listing — `GET /v1/agents` is scoped by the caller's
224
+ * `claimed_by` rows (the pre-ADR-062 boundary). JWT callers should prefer
225
+ * {@link listOrgAgents} (org-scoped, the ADR-062 canonical boundary).
226
+ * API-key callers use this path because the org-scoped route rejects API keys;
227
+ * routing here avoids the false "Not authenticated" error (MNE-194 F10).
216
228
  */
217
229
  export declare function listAgents(): Promise<AgentListItem[]>;
218
230
  export interface OrgListItem {
package/dist/lib/api.js CHANGED
@@ -206,11 +206,11 @@ export async function getAgent(id) {
206
206
  return fetchApi(`/v1/agents/${id}`);
207
207
  }
208
208
  /**
209
- * @deprecated Legacy per-user listing — `GET /v1/agents` is scoped by the
210
- * caller's `claimed_by` rows, which ADR-062 retired as an authorization /
211
- * listing boundary (org_id is now the sole boundary). New code must list via
212
- * {@link listOrgAgents} (org-scoped). Retained only for back-compat callers;
213
- * the `mnemom agents` command and name resolution no longer use it.
209
+ * Key-compatible agent listing — `GET /v1/agents` is scoped by the caller's
210
+ * `claimed_by` rows (the pre-ADR-062 boundary). JWT callers should prefer
211
+ * {@link listOrgAgents} (org-scoped, the ADR-062 canonical boundary).
212
+ * API-key callers use this path because the org-scoped route rejects API keys;
213
+ * routing here avoids the false "Not authenticated" error (MNE-194 F10).
214
214
  */
215
215
  export async function listAgents() {
216
216
  const url = validateUrl(`${API_BASE}/v1/agents?limit=100`);
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Decide whether the current module is being run as the CLI entrypoint
3
+ * (so it should `program.parse()`) rather than merely imported.
4
+ *
5
+ * ESM has no `require.main`, so we compare this module's URL to argv[1].
6
+ * The catch: npm installs the global bin as a SYMLINK
7
+ * (…/bin/mnemom → …/dist/index.js). When invoked through that symlink Node
8
+ * sets `process.argv[1]` to the symlink path while `import.meta.url` is the
9
+ * real resolved file — so a naive comparison never matches and the CLI
10
+ * silently no-ops. Canonicalize argv[1] with `realpathSync` before comparing
11
+ * so the symlinked global bin and the real path both resolve to the same URL.
12
+ *
13
+ * `realpathSync` is wrapped in try/catch because argv[1] may be missing or
14
+ * point at a path that no longer exists (ENOENT); in that case we return
15
+ * `false` to preserve the safe default of not auto-running.
16
+ */
17
+ export declare function isEntrypoint(moduleUrl: string, argv1: string | undefined): boolean;
@@ -0,0 +1,28 @@
1
+ import { realpathSync } from "node:fs";
2
+ import { pathToFileURL } from "node:url";
3
+ /**
4
+ * Decide whether the current module is being run as the CLI entrypoint
5
+ * (so it should `program.parse()`) rather than merely imported.
6
+ *
7
+ * ESM has no `require.main`, so we compare this module's URL to argv[1].
8
+ * The catch: npm installs the global bin as a SYMLINK
9
+ * (…/bin/mnemom → …/dist/index.js). When invoked through that symlink Node
10
+ * sets `process.argv[1]` to the symlink path while `import.meta.url` is the
11
+ * real resolved file — so a naive comparison never matches and the CLI
12
+ * silently no-ops. Canonicalize argv[1] with `realpathSync` before comparing
13
+ * so the symlinked global bin and the real path both resolve to the same URL.
14
+ *
15
+ * `realpathSync` is wrapped in try/catch because argv[1] may be missing or
16
+ * point at a path that no longer exists (ENOENT); in that case we return
17
+ * `false` to preserve the safe default of not auto-running.
18
+ */
19
+ export function isEntrypoint(moduleUrl, argv1) {
20
+ if (argv1 === undefined)
21
+ return false;
22
+ try {
23
+ return pathToFileURL(realpathSync(argv1)).href === moduleUrl;
24
+ }
25
+ catch {
26
+ return false;
27
+ }
28
+ }
@@ -2,6 +2,9 @@
2
2
  // smoltbot is deprecated — this shim prints a warning then hands off to mnemom
3
3
  process.stderr.write("\n⚠️ The smoltbot command is deprecated. Use mnemom instead.\n" +
4
4
  " Install: npm install -g @mnemom/mnemom\n\n");
5
- // Dynamic import runs index.ts which calls program.parse(process.argv) automatically
6
- await import("./index.js");
5
+ // argv[1] here is the smoltbot bin path, so index.js's entrypoint guard never
6
+ // fires (it resolves to dist/index.js, not this shim). Import the assembled
7
+ // program and call parse() explicitly instead of relying on its auto-parse.
8
+ const { program } = await import("./index.js");
9
+ program.parse(process.argv);
7
10
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mnemom/mnemom",
3
- "version": "0.14.0",
3
+ "version": "0.14.2",
4
4
  "description": "Transparent AI agent tracing",
5
5
  "type": "module",
6
6
  "bin": {