@mnemom/mnemom 0.7.1 → 0.8.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/lib/auth.js CHANGED
@@ -1,7 +1,76 @@
1
+ /**
2
+ * Auth credential management.
3
+ *
4
+ * Stores auth tokens in ~/.mnemom/auth.json (UC-9: no more config.json).
5
+ * License JWTs are stored alongside auth tokens.
6
+ */
7
+ import * as fs from "node:fs";
8
+ import * as path from "node:path";
1
9
  import * as http from "node:http";
2
10
  import * as crypto from "node:crypto";
3
11
  import { exec } from "node:child_process";
4
- import { getApiUrl, getWebsiteUrl, getAuthInfo, saveAuthTokens, loadConfig } from "./config.js";
12
+ import { getApiUrl, getWebsiteUrl, MNEMOM_DIR } from "./config.js";
13
+ // ============================================================================
14
+ // Auth Store (persisted to ~/.mnemom/auth.json)
15
+ // ============================================================================
16
+ const AUTH_FILE = path.join(MNEMOM_DIR, "auth.json");
17
+ function loadAuthStore() {
18
+ try {
19
+ if (!fs.existsSync(AUTH_FILE))
20
+ return null;
21
+ const content = fs.readFileSync(AUTH_FILE, "utf-8");
22
+ return JSON.parse(content);
23
+ }
24
+ catch {
25
+ return null;
26
+ }
27
+ }
28
+ function saveAuthStore(store) {
29
+ if (!fs.existsSync(MNEMOM_DIR)) {
30
+ fs.mkdirSync(MNEMOM_DIR, { recursive: true });
31
+ }
32
+ const resolvedPath = path.resolve(AUTH_FILE);
33
+ const sanitized = JSON.parse(JSON.stringify(store));
34
+ const tmpFile = `${resolvedPath}.${process.pid}.tmp`;
35
+ fs.writeFileSync(tmpFile, JSON.stringify(sanitized, null, 2));
36
+ fs.renameSync(tmpFile, resolvedPath);
37
+ }
38
+ // ============================================================================
39
+ // Auth token helpers
40
+ // ============================================================================
41
+ export function saveAuthTokens(tokens) {
42
+ const store = loadAuthStore() ?? {};
43
+ store.auth = tokens;
44
+ saveAuthStore(store);
45
+ }
46
+ export function clearAuthTokens() {
47
+ const store = loadAuthStore();
48
+ if (!store)
49
+ return;
50
+ delete store.auth;
51
+ saveAuthStore(store);
52
+ }
53
+ export function getAuthInfo() {
54
+ return loadAuthStore()?.auth ?? null;
55
+ }
56
+ // ============================================================================
57
+ // License JWT helpers
58
+ // ============================================================================
59
+ export function saveLicenseJwt(jwt) {
60
+ const store = loadAuthStore() ?? {};
61
+ store.licenseJwt = jwt;
62
+ saveAuthStore(store);
63
+ }
64
+ export function clearLicenseJwt() {
65
+ const store = loadAuthStore();
66
+ if (!store)
67
+ return;
68
+ delete store.licenseJwt;
69
+ saveAuthStore(store);
70
+ }
71
+ export function getLicenseJwt() {
72
+ return loadAuthStore()?.licenseJwt ?? null;
73
+ }
5
74
  /** Sanitize file-sourced data before use in outbound HTTP requests. */
6
75
  function sanitizeForHttp(data) {
7
76
  return String(data).trim();
@@ -10,15 +79,13 @@ function sanitizeForHttp(data) {
10
79
  * Get a valid access token, or null if not authenticated.
11
80
  *
12
81
  * Resolution order:
13
- * 1. SMOLTBOT_TOKEN environment variable (CI / non-interactive)
14
- * 2. Stored token from config (auto-refreshes if expired)
82
+ * 1. MNEMOM_TOKEN environment variable (CI / non-interactive)
83
+ * 2. Stored token from auth store (auto-refreshes if expired)
15
84
  */
16
85
  export async function getAccessToken() {
17
- // 1. Env var override (CI / non-interactive)
18
- const envToken = process.env.SMOLTBOT_TOKEN;
86
+ const envToken = process.env.MNEMOM_TOKEN;
19
87
  if (envToken)
20
88
  return envToken;
21
- // 2. Stored token
22
89
  const auth = getAuthInfo();
23
90
  if (!auth)
24
91
  return null;
@@ -27,7 +94,7 @@ export async function getAccessToken() {
27
94
  if (auth.expiresAt > now + 60) {
28
95
  return auth.accessToken;
29
96
  }
30
- // 3. Auto-refresh
97
+ // Auto-refresh
31
98
  const refreshed = await refreshAccessToken(auth.refreshToken);
32
99
  if (refreshed)
33
100
  return refreshed.accessToken;
@@ -39,31 +106,23 @@ export async function getAccessToken() {
39
106
  export async function requireAccessToken() {
40
107
  const token = await getAccessToken();
41
108
  if (!token) {
42
- console.error("Authentication required. Run `smoltbot login` first.");
109
+ console.error("Authentication required. Run `mnemom login` first.");
43
110
  process.exit(1);
44
111
  }
45
112
  return token;
46
113
  }
47
114
  /**
48
- * Get the Mnemom API key from env var or config.
49
- *
50
- * Resolution order:
51
- * 1. MNEMOM_API_KEY environment variable
52
- * 2. Stored mnemomApiKey from config
115
+ * Get the Mnemom API key from env var.
53
116
  */
54
117
  export function getMnemomApiKey() {
55
- const envKey = process.env.MNEMOM_API_KEY;
56
- if (envKey)
57
- return envKey;
58
- const config = loadConfig();
59
- return config?.mnemomApiKey ?? null;
118
+ return process.env.MNEMOM_API_KEY ?? null;
60
119
  }
61
120
  /**
62
121
  * Resolve the best available auth credential.
63
122
  *
64
123
  * Resolution order:
65
- * 1. JWT (SMOLTBOT_TOKEN env or stored token with auto-refresh)
66
- * 2. API key (MNEMOM_API_KEY env or config mnemomApiKey)
124
+ * 1. JWT (MNEMOM_TOKEN env or stored token with auto-refresh)
125
+ * 2. API key (MNEMOM_API_KEY env)
67
126
  * 3. None
68
127
  */
69
128
  export async function resolveAuth() {
@@ -81,20 +140,21 @@ export async function resolveAuth() {
81
140
  export async function requireAuth() {
82
141
  const cred = await resolveAuth();
83
142
  if (cred.type === "none") {
84
- console.error("Authentication required. Run `smoltbot login` or set MNEMOM_API_KEY.");
143
+ console.error("Authentication required. Run `mnemom login` or set MNEMOM_API_KEY.");
85
144
  process.exit(1);
86
145
  }
87
146
  return cred;
88
147
  }
89
148
  /**
90
- * Authenticate via browser-based login flow.
91
- *
92
- * 1. Start a local HTTP server on a random port
93
- * 2. Generate a random `state` nonce for CSRF protection
94
- * 3. Open the browser to the API's CLI login page
95
- * 4. Wait for the login page to POST tokens back to localhost
96
- * 5. Verify state, store tokens, and close the server
149
+ * Check if the user is logged in (has any credential).
97
150
  */
151
+ export async function isLoggedIn() {
152
+ const cred = await resolveAuth();
153
+ return cred.type !== "none";
154
+ }
155
+ // ============================================================================
156
+ // Browser login flow
157
+ // ============================================================================
98
158
  export async function loginWithBrowser() {
99
159
  const state = crypto.randomBytes(16).toString("hex");
100
160
  const { port, tokenPromise, close } = await startCallbackServer(state);
@@ -113,10 +173,6 @@ export async function loginWithBrowser() {
113
173
  close();
114
174
  }
115
175
  }
116
- /**
117
- * Start a local HTTP server that listens for the auth callback POST.
118
- * Returns the assigned port, a promise that resolves with tokens, and a close function.
119
- */
120
176
  async function startCallbackServer(expectedState) {
121
177
  let resolveTokens;
122
178
  let rejectTokens;
@@ -125,7 +181,6 @@ async function startCallbackServer(expectedState) {
125
181
  rejectTokens = reject;
126
182
  });
127
183
  const server = http.createServer((req, res) => {
128
- // Handle CORS preflight for the POST from the browser page
129
184
  if (req.method === "OPTIONS") {
130
185
  res.writeHead(200, {
131
186
  "Access-Control-Allow-Origin": "*",
@@ -143,7 +198,6 @@ async function startCallbackServer(expectedState) {
143
198
  let body = "";
144
199
  req.on("data", (chunk) => {
145
200
  body += chunk.toString();
146
- // Limit body size to prevent abuse
147
201
  if (body.length > 1_000_000) {
148
202
  req.destroy();
149
203
  rejectTokens(new Error("Callback body too large"));
@@ -157,7 +211,7 @@ async function startCallbackServer(expectedState) {
157
211
  "Content-Type": "text/html",
158
212
  "Access-Control-Allow-Origin": "*",
159
213
  });
160
- res.end("<html><body><h2>Authentication failed</h2><p>State mismatch. Please try again.</p></body></html>");
214
+ res.end("<html><body><h2>Authentication failed</h2><p>State mismatch.</p></body></html>");
161
215
  rejectTokens(new Error("State mismatch — possible CSRF attack"));
162
216
  return;
163
217
  }
@@ -188,13 +242,11 @@ async function startCallbackServer(expectedState) {
188
242
  }
189
243
  });
190
244
  });
191
- // Listen on port 0, wait for the server to be ready before reading the address
192
245
  const port = await new Promise((resolve) => {
193
246
  server.listen(0, "127.0.0.1", () => {
194
247
  resolve(server.address().port);
195
248
  });
196
249
  });
197
- // Auto-timeout after 5 minutes
198
250
  const timeout = setTimeout(() => {
199
251
  rejectTokens(new Error("Login timed out. Please try again."));
200
252
  server.close();
@@ -208,9 +260,6 @@ async function startCallbackServer(expectedState) {
208
260
  },
209
261
  };
210
262
  }
211
- /**
212
- * Open a URL in the user's default browser.
213
- */
214
263
  function openBrowser(url) {
215
264
  const cmd = process.platform === "darwin"
216
265
  ? "open"
@@ -219,10 +268,9 @@ function openBrowser(url) {
219
268
  : "xdg-open";
220
269
  exec(`${cmd} ${JSON.stringify(url)}`);
221
270
  }
222
- /**
223
- * Authenticate with email + password via the API auth proxy.
224
- * Used by --no-browser fallback.
225
- */
271
+ // ============================================================================
272
+ // Password login
273
+ // ============================================================================
226
274
  export async function loginWithPassword(email, password) {
227
275
  const url = `${getApiUrl()}/v1/auth/login`;
228
276
  const res = await fetch(url, {
@@ -245,10 +293,9 @@ export async function loginWithPassword(email, password) {
245
293
  saveAuthTokens(tokens);
246
294
  return tokens;
247
295
  }
248
- /**
249
- * Refresh an expired access token.
250
- * Returns new tokens on success, null on failure.
251
- */
296
+ // ============================================================================
297
+ // Token refresh
298
+ // ============================================================================
252
299
  async function refreshAccessToken(refreshToken) {
253
300
  if (!refreshToken || typeof refreshToken !== "string") {
254
301
  return null;
@@ -263,7 +310,6 @@ async function refreshAccessToken(refreshToken) {
263
310
  if (!res.ok)
264
311
  return null;
265
312
  const data = (await res.json());
266
- // Preserve existing user info from stored auth
267
313
  const existing = getAuthInfo();
268
314
  const tokens = {
269
315
  accessToken: data.access_token,
@@ -1,105 +1,18 @@
1
- export declare const CONFIG_DIR: string;
2
- export declare const CONFIG_FILE: string;
3
- export type Environment = "production" | "staging" | "local";
4
1
  /**
5
- * Resolve the active environment.
2
+ * Environment resolution and URL constants.
6
3
  *
7
- * Resolution order:
8
- * 1. `SMOLTBOT_ENV` environment variable
9
- * 2. Defaults to `production`
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
+ */
8
+ /** Base directory for mnemom CLI state (auth tokens, caches). */
9
+ export declare const MNEMOM_DIR: string;
10
+ export type Environment = "production" | "staging" | "local";
11
+ /**
12
+ * Resolve the active environment from MNEMOM_ENV.
13
+ * Defaults to production.
10
14
  */
11
15
  export declare function getEnvironment(): Environment;
12
16
  export declare function getApiUrl(): string;
13
17
  export declare function getGatewayUrl(): string;
14
18
  export declare function getWebsiteUrl(): string;
15
- export interface ConfigV1 {
16
- agentId: string;
17
- email?: string;
18
- gateway?: string;
19
- openclawConfigured?: boolean;
20
- providers?: string[];
21
- mnemomApiKey?: string;
22
- licenseJwt?: string;
23
- configuredAt?: string;
24
- }
25
- export interface AgentConfig {
26
- agentId: string;
27
- openclawConfigured?: boolean;
28
- providers?: string[];
29
- configuredAt?: string;
30
- }
31
- export interface AuthTokens {
32
- accessToken: string;
33
- refreshToken: string;
34
- expiresAt: number;
35
- userId: string;
36
- email: string;
37
- }
38
- export interface ConfigV2 {
39
- version: 2;
40
- defaultAgent: string;
41
- gateway: string;
42
- mnemomApiKey?: string;
43
- licenseJwt?: string;
44
- agents: Record<string, AgentConfig>;
45
- auth?: AuthTokens;
46
- }
47
- /** Backward-compatible alias so existing imports keep working. */
48
- export type Config = ConfigV2;
49
- /**
50
- * Migrate a v1 config into v2 format.
51
- * All agent-specific fields move into `agents.default`.
52
- */
53
- export declare function migrateConfig(raw: ConfigV1): ConfigV2;
54
- export declare function configExists(): boolean;
55
- /**
56
- * Load the config file.
57
- * If the file is v1 (no `version` field), it is automatically migrated to v2
58
- * and written back to disk before returning.
59
- */
60
- export declare function loadConfig(): ConfigV2 | null;
61
- export declare function saveConfig(config: ConfigV2): void;
62
- /**
63
- * Resolve the active agent config.
64
- *
65
- * Resolution order:
66
- * 1. Explicit `agentName` parameter (--agent flag)
67
- * 2. `SMOLTBOT_AGENT` environment variable
68
- *
69
- * Returns `null` if no agent is specified or the agent is not found.
70
- * Callers must require --agent or SMOLTBOT_AGENT for agent-scoped commands.
71
- */
72
- export declare function getActiveAgent(agentName?: string): AgentConfig | null;
73
- /**
74
- * Require an explicit agent selection. Exits with a helpful error if
75
- * no agent was specified via --agent or SMOLTBOT_AGENT.
76
- *
77
- * Falls back to API lookup if the agent is not in local config:
78
- * - smolt-XXXXXXXX IDs: public endpoint, no auth required
79
- * - Names: authenticated account listing (requires `smoltbot login`)
80
- */
81
- export declare function requireAgent(agentName?: string): Promise<AgentConfig>;
82
- export declare function generateAgentId(): string;
83
- /**
84
- * Compute the 16-char agent_hash for a given API key and optional agent name.
85
- * Matches the gateway's hashApiKey() and the POST /v1/agents/:id/rekey expected format.
86
- *
87
- * Unnamed agent: SHA256(apiKey).slice(0, 16)
88
- * Named agent: SHA256(apiKey + '|' + name).slice(0, 16)
89
- */
90
- export declare function computeAgentHash(apiKey: string, name?: string | null): string;
91
- /**
92
- * Derive agent ID deterministically from an API key.
93
- * Uses SHA-256 to match the gateway's hashApiKey (Web Crypto SHA-256, first 16 hex chars).
94
- * The agent ID is "smolt-" + first 8 hex chars of the SHA-256 digest.
95
- */
96
- export declare function deriveAgentId(apiKey: string): string;
97
- /**
98
- * Derive agent ID deterministically from an API key *and* a name.
99
- * Allows multiple named agents to share one API key with distinct IDs.
100
- * Uses SHA-256 to match the gateway's hashApiKey(apiKey + '|' + name).
101
- */
102
- export declare function deriveAgentIdWithName(apiKey: string, name: string): string;
103
- export declare function saveAuthTokens(tokens: AuthTokens): void;
104
- export declare function clearAuthTokens(): void;
105
- export declare function getAuthInfo(): AuthTokens | null;
@@ -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.1",
4
- "description": "Transparent AI agent tracing - AAP compliant",
3
+ "version": "0.8.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>;