@mnemom/mnemom 0.7.2 → 0.9.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.
@@ -1,9 +1,14 @@
1
- import * as fs from "node:fs";
1
+ /**
2
+ * Environment resolution and URL constants.
3
+ *
4
+ * UC-9 simplification: this module no longer manages a config file.
5
+ * Auth tokens live in auth.ts → ~/.mnemom/auth.json.
6
+ * Agent resolution is server-side via api.ts → resolveAgentId().
7
+ */
2
8
  import * as path from "node:path";
3
9
  import * as os from "node:os";
4
- import * as crypto from "node:crypto";
5
- export const CONFIG_DIR = path.join(os.homedir(), ".smoltbot");
6
- export const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
10
+ /** Base directory for mnemom CLI state (auth tokens, caches). */
11
+ export const MNEMOM_DIR = path.join(os.homedir(), ".mnemom");
7
12
  const API_URLS = {
8
13
  production: "https://api.mnemom.ai",
9
14
  staging: "https://api-staging.mnemom.ai",
@@ -20,14 +25,11 @@ const WEBSITE_URLS = {
20
25
  local: "http://localhost:5173",
21
26
  };
22
27
  /**
23
- * Resolve the active environment.
24
- *
25
- * Resolution order:
26
- * 1. `SMOLTBOT_ENV` environment variable
27
- * 2. Defaults to `production`
28
+ * Resolve the active environment from MNEMOM_ENV.
29
+ * Defaults to production.
28
30
  */
29
31
  export function getEnvironment() {
30
- const env = process.env.SMOLTBOT_ENV;
32
+ const env = process.env.MNEMOM_ENV;
31
33
  if (env === "staging" || env === "local")
32
34
  return env;
33
35
  return "production";
@@ -41,213 +43,3 @@ export function getGatewayUrl() {
41
43
  export function getWebsiteUrl() {
42
44
  return WEBSITE_URLS[getEnvironment()];
43
45
  }
44
- // ---------------------------------------------------------------------------
45
- // Migration
46
- // ---------------------------------------------------------------------------
47
- /**
48
- * Migrate a v1 config into v2 format.
49
- * All agent-specific fields move into `agents.default`.
50
- */
51
- export function migrateConfig(raw) {
52
- return {
53
- version: 2,
54
- defaultAgent: "default",
55
- gateway: raw.gateway ?? "https://gateway.mnemom.ai",
56
- mnemomApiKey: raw.mnemomApiKey,
57
- licenseJwt: raw.licenseJwt,
58
- agents: {
59
- default: {
60
- agentId: raw.agentId,
61
- openclawConfigured: raw.openclawConfigured,
62
- providers: raw.providers,
63
- configuredAt: raw.configuredAt,
64
- },
65
- },
66
- };
67
- }
68
- // ---------------------------------------------------------------------------
69
- // Persistence
70
- // ---------------------------------------------------------------------------
71
- export function configExists() {
72
- return fs.existsSync(CONFIG_FILE);
73
- }
74
- /**
75
- * Load the config file.
76
- * If the file is v1 (no `version` field), it is automatically migrated to v2
77
- * and written back to disk before returning.
78
- */
79
- export function loadConfig() {
80
- if (!configExists()) {
81
- return null;
82
- }
83
- try {
84
- const content = fs.readFileSync(CONFIG_FILE, "utf-8");
85
- const raw = JSON.parse(content);
86
- // Detect v1: no `version` field present
87
- if (!raw.version) {
88
- const migrated = migrateConfig(raw);
89
- saveConfig(migrated);
90
- return migrated;
91
- }
92
- return raw;
93
- }
94
- catch {
95
- return null;
96
- }
97
- }
98
- export function saveConfig(config) {
99
- if (!fs.existsSync(CONFIG_DIR)) {
100
- fs.mkdirSync(CONFIG_DIR, { recursive: true });
101
- }
102
- // Validate write path stays within expected directory
103
- const resolvedPath = path.resolve(CONFIG_FILE);
104
- if (!resolvedPath.startsWith(path.resolve(CONFIG_DIR))) {
105
- throw new Error("Config file path escapes expected directory");
106
- }
107
- // Re-serialize to sanitize any HTTP-sourced data before writing to disk
108
- const sanitizedConfig = JSON.parse(JSON.stringify(config));
109
- const tmpFile = `${resolvedPath}.${process.pid}.tmp`;
110
- fs.writeFileSync(tmpFile, JSON.stringify(sanitizedConfig, null, 2));
111
- fs.renameSync(tmpFile, resolvedPath);
112
- }
113
- // ---------------------------------------------------------------------------
114
- // Agent resolution
115
- // ---------------------------------------------------------------------------
116
- /**
117
- * Resolve the active agent config.
118
- *
119
- * Resolution order:
120
- * 1. Explicit `agentName` parameter (--agent flag)
121
- * 2. `SMOLTBOT_AGENT` environment variable
122
- *
123
- * Returns `null` if no agent is specified or the agent is not found.
124
- * Callers must require --agent or SMOLTBOT_AGENT for agent-scoped commands.
125
- */
126
- export function getActiveAgent(agentName) {
127
- const config = loadConfig();
128
- if (!config) {
129
- return null;
130
- }
131
- const name = agentName ??
132
- process.env.SMOLTBOT_AGENT;
133
- if (!name) {
134
- return null;
135
- }
136
- return config.agents[name] ?? null;
137
- }
138
- /**
139
- * Require an explicit agent selection. Exits with a helpful error if
140
- * no agent was specified via --agent or SMOLTBOT_AGENT.
141
- *
142
- * Falls back to API lookup if the agent is not in local config:
143
- * - smolt-XXXXXXXX IDs: public endpoint, no auth required
144
- * - Names: authenticated account listing (requires `smoltbot login`)
145
- */
146
- export async function requireAgent(agentName) {
147
- if (!configExists()) {
148
- console.error("\nsmoltbot is not initialized. Run `smoltbot init` first.\n");
149
- process.exit(1);
150
- }
151
- // Fast path: local config hit — no network call needed
152
- const localAgent = getActiveAgent(agentName);
153
- if (localAgent)
154
- return localAgent;
155
- const name = agentName ?? process.env.SMOLTBOT_AGENT;
156
- if (!name) {
157
- console.error("\nAgent required. Use --agent <name> or set SMOLTBOT_AGENT.\n");
158
- console.error("Available agents:");
159
- const config = loadConfig();
160
- if (config) {
161
- for (const [n, a] of Object.entries(config.agents)) {
162
- console.error(` ${n} (${a.agentId})`);
163
- }
164
- }
165
- console.error();
166
- process.exit(1);
167
- }
168
- // API fallback: resolve from account
169
- try {
170
- const { getAgent, getAgentByName } = await import("./api.js");
171
- let found = null;
172
- if (/^smolt-[0-9a-f]{8}$/.test(name)) {
173
- // Public endpoint — no auth needed
174
- found = await getAgent(name);
175
- }
176
- else {
177
- found = await getAgentByName(name);
178
- }
179
- if (found) {
180
- return { agentId: found.id };
181
- }
182
- }
183
- catch (err) {
184
- const msg = err instanceof Error ? err.message : String(err);
185
- if (msg.includes("Not logged in")) {
186
- console.error(`\n${msg}\n`);
187
- process.exit(1);
188
- }
189
- // Other API errors: fall through to "not found" message
190
- }
191
- console.error(`\nAgent not found: ${name}`);
192
- console.error(`Run \`smoltbot agents add ${name}\` to register it locally,`);
193
- console.error(`or check \`smoltbot agents\` to see agents in your account.\n`);
194
- process.exit(1);
195
- }
196
- // ---------------------------------------------------------------------------
197
- // ID generation
198
- // ---------------------------------------------------------------------------
199
- export function generateAgentId() {
200
- // New format per ADR-019 (scale/step-25b): mnm-{uuid_v4} for all new agents.
201
- // crypto.randomUUID() is a Node.js built-in (>=14.17), no new dependencies.
202
- return `mnm-${crypto.randomUUID()}`;
203
- }
204
- /**
205
- * Compute the 16-char agent_hash for a given API key and optional agent name.
206
- * Matches the gateway's hashApiKey() and the POST /v1/agents/:id/rekey expected format.
207
- *
208
- * Unnamed agent: SHA256(apiKey).slice(0, 16)
209
- * Named agent: SHA256(apiKey + '|' + name).slice(0, 16)
210
- */
211
- export function computeAgentHash(apiKey, name) {
212
- const input = name ? `${apiKey}|${name}` : apiKey;
213
- // eslint-disable-next-line -- deterministic ID derivation must match gateway SHA-256
214
- return crypto.createHash("sha256").update(input).digest("hex").slice(0, 16); // lgtm[js/insufficient-password-hash]
215
- }
216
- /**
217
- * Derive agent ID deterministically from an API key.
218
- * Uses SHA-256 to match the gateway's hashApiKey (Web Crypto SHA-256, first 16 hex chars).
219
- * The agent ID is "smolt-" + first 8 hex chars of the SHA-256 digest.
220
- */
221
- export function deriveAgentId(apiKey) {
222
- return `smolt-${computeAgentHash(apiKey).slice(0, 8)}`;
223
- }
224
- /**
225
- * Derive agent ID deterministically from an API key *and* a name.
226
- * Allows multiple named agents to share one API key with distinct IDs.
227
- * Uses SHA-256 to match the gateway's hashApiKey(apiKey + '|' + name).
228
- */
229
- export function deriveAgentIdWithName(apiKey, name) {
230
- return `smolt-${computeAgentHash(apiKey, name).slice(0, 8)}`;
231
- }
232
- // ---------------------------------------------------------------------------
233
- // Auth token helpers
234
- // ---------------------------------------------------------------------------
235
- export function saveAuthTokens(tokens) {
236
- const config = loadConfig();
237
- if (!config) {
238
- throw new Error("Config not initialized. Run `smoltbot init` first.");
239
- }
240
- config.auth = tokens;
241
- saveConfig(config);
242
- }
243
- export function clearAuthTokens() {
244
- const config = loadConfig();
245
- if (!config)
246
- return;
247
- delete config.auth;
248
- saveConfig(config);
249
- }
250
- export function getAuthInfo() {
251
- const config = loadConfig();
252
- return config?.auth ?? null;
253
- }
@@ -1,9 +1,8 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
- import * as os from "node:os";
4
3
  import { MODEL_REGISTRY, getModelDefinition as getStaticModelDefinition } from "./models.js";
5
- const SMOLTBOT_DIR = path.join(os.homedir(), ".smoltbot");
6
- const CACHE_FILE = path.join(SMOLTBOT_DIR, "models-cache.json");
4
+ import { MNEMOM_DIR } from "./config.js";
5
+ const CACHE_FILE = path.join(MNEMOM_DIR, "models-cache.json");
7
6
  const MODELS_URL = "https://gateway.mnemom.ai/models.json";
8
7
  const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
9
8
  /**
@@ -36,12 +35,12 @@ function saveCache(models) {
36
35
  return;
37
36
  }
38
37
  // Ensure directory exists
39
- if (!fs.existsSync(SMOLTBOT_DIR)) {
40
- fs.mkdirSync(SMOLTBOT_DIR, { recursive: true });
38
+ if (!fs.existsSync(MNEMOM_DIR)) {
39
+ fs.mkdirSync(MNEMOM_DIR, { recursive: true });
41
40
  }
42
41
  // Validate write path stays within expected directory
43
42
  const resolvedCachePath = path.resolve(CACHE_FILE);
44
- if (!resolvedCachePath.startsWith(path.resolve(SMOLTBOT_DIR))) {
43
+ if (!resolvedCachePath.startsWith(path.resolve(MNEMOM_DIR))) {
45
44
  throw new Error("Cache file path escapes expected directory");
46
45
  }
47
46
  // Re-serialize HTTP-sourced data to sanitize before writing to disk
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
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
- ' Run: mnemom migrate-config (updates your OpenClaw provider config)\n\n');
4
+ ' Install: npm install -g @mnemom/mnemom\n\n');
5
5
  // Dynamic import runs index.ts which calls program.parse(process.argv) automatically
6
6
  await import('./index.js');
7
7
  export {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mnemom/mnemom",
3
- "version": "0.7.2",
4
- "description": "Transparent AI agent tracing - AAP compliant",
3
+ "version": "0.9.0",
4
+ "description": "Transparent AI agent tracing",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "mnemom": "./dist/index.js",
@@ -1,7 +0,0 @@
1
- export interface InitOptions {
2
- yes?: boolean;
3
- force?: boolean;
4
- openclaw?: boolean;
5
- standalone?: boolean;
6
- }
7
- export declare function initCommand(options?: InitOptions): Promise<void>;