@mnemom/smoltbot 2.0.1

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.
@@ -0,0 +1,86 @@
1
+ import { configExists, loadConfig } from "../lib/config.js";
2
+ import { getTraces } from "../lib/api.js";
3
+ export async function logsCommand(options = {}) {
4
+ if (!configExists()) {
5
+ console.log("\nāœ— smoltbot is not initialized\n");
6
+ console.log("Run `smoltbot init` to get started.\n");
7
+ process.exit(1);
8
+ }
9
+ const config = loadConfig();
10
+ if (!config) {
11
+ console.log("\nāœ— Failed to load configuration\n");
12
+ process.exit(1);
13
+ }
14
+ const limit = options.limit || 10;
15
+ console.log("\nšŸ” Fetching traces...\n");
16
+ try {
17
+ const traces = await getTraces(config.agentId, limit);
18
+ if (traces.length === 0) {
19
+ console.log("━".repeat(60));
20
+ console.log("No traces found");
21
+ console.log("━".repeat(60));
22
+ console.log("\nšŸ’” Start using Claude to generate traces.\n");
23
+ console.log("Make sure ANTHROPIC_BASE_URL is set correctly:\n");
24
+ console.log(` export ANTHROPIC_BASE_URL="${config.gateway || "https://gateway.mnemon.ai"}/v1/proxy/${config.agentId}"\n`);
25
+ return;
26
+ }
27
+ console.log("━".repeat(60));
28
+ console.log(`Recent Traces (${traces.length})`);
29
+ console.log("━".repeat(60));
30
+ for (const trace of traces) {
31
+ displayTrace(trace);
32
+ }
33
+ console.log("━".repeat(60));
34
+ console.log(`\nšŸ’” View more: smoltbot logs --limit ${limit + 10}\n`);
35
+ console.log(`šŸ“Š Dashboard: https://mnemon.ai/dashboard/${config.agentId}\n`);
36
+ }
37
+ catch (error) {
38
+ const message = error instanceof Error ? error.message : String(error);
39
+ if (message.includes("404") || message.includes("not found")) {
40
+ console.log("━".repeat(60));
41
+ console.log("No traces found");
42
+ console.log("━".repeat(60));
43
+ console.log("\nšŸ’” Start using Claude to generate traces.\n");
44
+ }
45
+ else {
46
+ console.log(`\nāœ— Failed to fetch traces: ${message}\n`);
47
+ process.exit(1);
48
+ }
49
+ }
50
+ }
51
+ function displayTrace(trace) {
52
+ const timestamp = formatTimestamp(trace.timestamp);
53
+ const status = trace.verified ? "āœ“" : "āœ—";
54
+ const statusColor = trace.verified ? "" : " [VIOLATION]";
55
+ console.log(`\n ${timestamp} ${status}${statusColor}`);
56
+ console.log(` Action: ${trace.action}`);
57
+ if (trace.tool_name) {
58
+ console.log(` Tool: ${trace.tool_name}`);
59
+ }
60
+ if (trace.reasoning) {
61
+ const preview = truncate(trace.reasoning, 60);
62
+ console.log(` Reason: ${preview}`);
63
+ }
64
+ }
65
+ function formatTimestamp(iso) {
66
+ try {
67
+ const date = new Date(iso);
68
+ return date.toLocaleString("en-US", {
69
+ month: "short",
70
+ day: "numeric",
71
+ hour: "2-digit",
72
+ minute: "2-digit",
73
+ hour12: false,
74
+ });
75
+ }
76
+ catch {
77
+ return iso;
78
+ }
79
+ }
80
+ function truncate(text, maxLength) {
81
+ const cleaned = text.replace(/\n/g, " ").trim();
82
+ if (cleaned.length <= maxLength) {
83
+ return cleaned;
84
+ }
85
+ return cleaned.slice(0, maxLength - 3) + "...";
86
+ }
@@ -0,0 +1 @@
1
+ export declare function statusCommand(): Promise<void>;
@@ -0,0 +1,299 @@
1
+ import { configExists, loadConfig } from "../lib/config.js";
2
+ import { getAgent, getIntegrity, getTraces } from "../lib/api.js";
3
+ import { detectOpenClaw, getCurrentModel, } from "../lib/openclaw.js";
4
+ import { formatModelName } from "../lib/models.js";
5
+ const GATEWAY_URL = "https://gateway.mnemom.ai";
6
+ const DASHBOARD_URL = "https://mnemom.ai";
7
+ export async function statusCommand() {
8
+ console.log("\n" + "=".repeat(60));
9
+ console.log(" smoltbot status");
10
+ console.log("=".repeat(60) + "\n");
11
+ const checks = [];
12
+ // 1. Check smoltbot config
13
+ const configCheck = checkSmoltbotConfig();
14
+ checks.push(configCheck);
15
+ if (configCheck.status === "error") {
16
+ printChecks(checks);
17
+ console.log("\nRun `smoltbot init` to get started.\n");
18
+ process.exit(1);
19
+ }
20
+ const config = loadConfig();
21
+ // 2. Check OpenClaw configuration
22
+ const openclawCheck = checkOpenClawConfig();
23
+ checks.push(openclawCheck);
24
+ // 3. Check current model
25
+ const modelCheck = checkCurrentModel();
26
+ checks.push(modelCheck);
27
+ // 4. Test gateway connectivity
28
+ const gatewayCheck = await checkGatewayConnectivity();
29
+ checks.push(gatewayCheck);
30
+ // 5. Test API connectivity
31
+ const apiCheck = await checkApiConnectivity(config.agentId);
32
+ checks.push(apiCheck);
33
+ // Print all checks
34
+ printChecks(checks);
35
+ // Show configuration details
36
+ console.log("\n" + "─".repeat(50));
37
+ console.log("Configuration");
38
+ console.log("─".repeat(50) + "\n");
39
+ console.log(`Agent ID: ${config.agentId}`);
40
+ console.log(`Gateway: ${config.gateway || GATEWAY_URL}`);
41
+ console.log(`Dashboard: ${DASHBOARD_URL}/agents/${config.agentId}`);
42
+ if (config.openclawConfigured) {
43
+ console.log(`Configured: ${config.configuredAt || "yes"}`);
44
+ }
45
+ // Show current model info
46
+ const { fullPath, provider, modelId } = getCurrentModel();
47
+ if (fullPath) {
48
+ console.log(`\nCurrent Model: ${fullPath}`);
49
+ if (modelId) {
50
+ console.log(` (${formatModelName(modelId)})`);
51
+ }
52
+ if (provider === "smoltbot") {
53
+ console.log(" Status: Traced mode ACTIVE");
54
+ }
55
+ else {
56
+ console.log(" Status: Traced mode NOT ACTIVE");
57
+ console.log(`\n To enable: openclaw models set smoltbot/${modelId}`);
58
+ }
59
+ }
60
+ // Show trace summary if available
61
+ if (apiCheck.status === "ok") {
62
+ await showTraceSummary(config.agentId);
63
+ }
64
+ // Show overall status
65
+ const hasErrors = checks.some((c) => c.status === "error");
66
+ const hasWarnings = checks.some((c) => c.status === "warning");
67
+ console.log("\n" + "=".repeat(60));
68
+ if (hasErrors) {
69
+ console.log(" Status: ISSUES DETECTED");
70
+ console.log("\n Fix the errors above to ensure tracing works correctly.");
71
+ }
72
+ else if (hasWarnings) {
73
+ console.log(" Status: OK (with warnings)");
74
+ }
75
+ else {
76
+ console.log(" Status: ALL SYSTEMS GO");
77
+ }
78
+ console.log("=".repeat(60) + "\n");
79
+ }
80
+ function checkSmoltbotConfig() {
81
+ if (!configExists()) {
82
+ return {
83
+ name: "Smoltbot Config",
84
+ status: "error",
85
+ message: "Not initialized",
86
+ details: "Run `smoltbot init` to configure",
87
+ };
88
+ }
89
+ const config = loadConfig();
90
+ if (!config) {
91
+ return {
92
+ name: "Smoltbot Config",
93
+ status: "error",
94
+ message: "Config file corrupted",
95
+ details: "Delete ~/.smoltbot/config.json and run `smoltbot init`",
96
+ };
97
+ }
98
+ return {
99
+ name: "Smoltbot Config",
100
+ status: "ok",
101
+ message: `Agent ID: ${config.agentId}`,
102
+ };
103
+ }
104
+ function checkOpenClawConfig() {
105
+ const detection = detectOpenClaw();
106
+ if (!detection.installed) {
107
+ return {
108
+ name: "OpenClaw",
109
+ status: "error",
110
+ message: "Not installed",
111
+ details: "Install from https://openclaw.ai",
112
+ };
113
+ }
114
+ if (!detection.hasApiKey) {
115
+ if (detection.isOAuth) {
116
+ return {
117
+ name: "OpenClaw",
118
+ status: "error",
119
+ message: "OAuth auth (not supported)",
120
+ details: "smoltbot requires API key authentication",
121
+ };
122
+ }
123
+ return {
124
+ name: "OpenClaw",
125
+ status: "error",
126
+ message: "No API key configured",
127
+ details: "Run `openclaw auth` to add your API key",
128
+ };
129
+ }
130
+ if (!detection.smoltbotAlreadyConfigured) {
131
+ return {
132
+ name: "OpenClaw",
133
+ status: "warning",
134
+ message: "smoltbot provider not configured",
135
+ details: "Run `smoltbot init` to configure",
136
+ };
137
+ }
138
+ return {
139
+ name: "OpenClaw",
140
+ status: "ok",
141
+ message: "smoltbot provider configured",
142
+ };
143
+ }
144
+ function checkCurrentModel() {
145
+ const { fullPath, provider, modelId } = getCurrentModel();
146
+ if (!fullPath) {
147
+ return {
148
+ name: "Current Model",
149
+ status: "warning",
150
+ message: "No default model set",
151
+ details: "Run `openclaw models set smoltbot/<model>`",
152
+ };
153
+ }
154
+ if (provider === "smoltbot") {
155
+ return {
156
+ name: "Current Model",
157
+ status: "ok",
158
+ message: `${modelId} (traced)`,
159
+ };
160
+ }
161
+ return {
162
+ name: "Current Model",
163
+ status: "warning",
164
+ message: `${fullPath} (not traced)`,
165
+ details: `Switch with: openclaw models set smoltbot/${modelId}`,
166
+ };
167
+ }
168
+ async function checkGatewayConnectivity() {
169
+ try {
170
+ const response = await fetch(`${GATEWAY_URL}/health`, {
171
+ signal: AbortSignal.timeout(5000),
172
+ });
173
+ if (response.ok) {
174
+ const data = (await response.json());
175
+ return {
176
+ name: "Gateway",
177
+ status: "ok",
178
+ message: `Connected (v${data.version || "unknown"})`,
179
+ };
180
+ }
181
+ return {
182
+ name: "Gateway",
183
+ status: "error",
184
+ message: `HTTP ${response.status}`,
185
+ details: "Gateway returned an error",
186
+ };
187
+ }
188
+ catch (error) {
189
+ const message = error instanceof Error ? error.message : String(error);
190
+ if (message.includes("timeout") || message.includes("TIMEOUT")) {
191
+ return {
192
+ name: "Gateway",
193
+ status: "error",
194
+ message: "Connection timeout",
195
+ details: "Check your network connection",
196
+ };
197
+ }
198
+ return {
199
+ name: "Gateway",
200
+ status: "error",
201
+ message: "Connection failed",
202
+ details: message,
203
+ };
204
+ }
205
+ }
206
+ async function checkApiConnectivity(agentId) {
207
+ try {
208
+ const agent = await getAgent(agentId);
209
+ if (agent) {
210
+ return {
211
+ name: "API",
212
+ status: "ok",
213
+ message: "Agent registered",
214
+ };
215
+ }
216
+ return {
217
+ name: "API",
218
+ status: "warning",
219
+ message: "Agent not yet registered",
220
+ details: "Will register on first traced API call",
221
+ };
222
+ }
223
+ catch (error) {
224
+ const message = error instanceof Error ? error.message : String(error);
225
+ if (message.includes("404") || message.includes("not found")) {
226
+ return {
227
+ name: "API",
228
+ status: "warning",
229
+ message: "Agent not yet registered",
230
+ details: "Will register on first traced API call",
231
+ };
232
+ }
233
+ if (message.includes("timeout") || message.includes("TIMEOUT")) {
234
+ return {
235
+ name: "API",
236
+ status: "error",
237
+ message: "Connection timeout",
238
+ details: "Check your network connection",
239
+ };
240
+ }
241
+ return {
242
+ name: "API",
243
+ status: "error",
244
+ message: "Connection failed",
245
+ details: message,
246
+ };
247
+ }
248
+ }
249
+ async function showTraceSummary(agentId) {
250
+ try {
251
+ const [integrityResult, tracesResult] = await Promise.allSettled([
252
+ getIntegrity(agentId),
253
+ getTraces(agentId, 1),
254
+ ]);
255
+ console.log("\n" + "─".repeat(50));
256
+ console.log("Trace Summary");
257
+ console.log("─".repeat(50) + "\n");
258
+ if (integrityResult.status === "fulfilled") {
259
+ const integrity = integrityResult.value;
260
+ const score = (integrity.score * 100).toFixed(1);
261
+ console.log(`Integrity Score: ${score}%`);
262
+ console.log(`Total Traces: ${integrity.total_traces}`);
263
+ console.log(`Verified: ${integrity.verified}`);
264
+ if (integrity.violations > 0) {
265
+ console.log(`Violations: ${integrity.violations}`);
266
+ }
267
+ }
268
+ else {
269
+ console.log("Integrity: No data yet");
270
+ }
271
+ if (tracesResult.status === "fulfilled" && tracesResult.value.length > 0) {
272
+ const lastTrace = tracesResult.value[0];
273
+ const lastTime = new Date(lastTrace.timestamp).toLocaleString();
274
+ console.log(`\nLast Activity: ${lastTime}`);
275
+ }
276
+ else {
277
+ console.log("\nLast Activity: None");
278
+ }
279
+ }
280
+ catch {
281
+ // Silently skip if we can't get trace info
282
+ }
283
+ }
284
+ function printChecks(checks) {
285
+ console.log("System Checks");
286
+ console.log("─".repeat(50) + "\n");
287
+ for (const check of checks) {
288
+ const icon = check.status === "ok" ? "āœ“" : check.status === "warning" ? "⚠" : "āœ—";
289
+ const color = check.status === "ok"
290
+ ? ""
291
+ : check.status === "warning"
292
+ ? ""
293
+ : "";
294
+ console.log(`${icon} ${check.name}: ${check.message}`);
295
+ if (check.details) {
296
+ console.log(` ${check.details}`);
297
+ }
298
+ }
299
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env node
2
+ import { program } from "commander";
3
+ import { initCommand } from "./commands/init.js";
4
+ import { statusCommand } from "./commands/status.js";
5
+ import { integrityCommand } from "./commands/integrity.js";
6
+ import { logsCommand } from "./commands/logs.js";
7
+ program
8
+ .name("smoltbot")
9
+ .description("Transparent AI agent tracing - AAP compliant")
10
+ .version("2.0.0");
11
+ program
12
+ .command("init")
13
+ .description("Initialize smoltbot and configure OpenClaw for traced mode")
14
+ .option("-y, --yes", "Skip confirmation prompts (accept defaults)")
15
+ .option("-f, --force", "Force reconfiguration even if already configured")
16
+ .action(async (options) => {
17
+ try {
18
+ await initCommand({ yes: options.yes, force: options.force });
19
+ }
20
+ catch (error) {
21
+ console.error("Error:", error instanceof Error ? error.message : error);
22
+ process.exit(1);
23
+ }
24
+ });
25
+ program
26
+ .command("status")
27
+ .description("Show agent status and connection info")
28
+ .action(async () => {
29
+ try {
30
+ await statusCommand();
31
+ }
32
+ catch (error) {
33
+ console.error("Error:", error instanceof Error ? error.message : error);
34
+ process.exit(1);
35
+ }
36
+ });
37
+ program
38
+ .command("integrity")
39
+ .description("Display integrity score and verification stats")
40
+ .action(async () => {
41
+ try {
42
+ await integrityCommand();
43
+ }
44
+ catch (error) {
45
+ console.error("Error:", error instanceof Error ? error.message : error);
46
+ process.exit(1);
47
+ }
48
+ });
49
+ program
50
+ .command("logs")
51
+ .description("Show recent traces and actions")
52
+ .option("-l, --limit <number>", "Number of traces to show", "10")
53
+ .action(async (options) => {
54
+ try {
55
+ const limit = parseInt(options.limit, 10);
56
+ await logsCommand({ limit: isNaN(limit) ? 10 : limit });
57
+ }
58
+ catch (error) {
59
+ console.error("Error:", error instanceof Error ? error.message : error);
60
+ process.exit(1);
61
+ }
62
+ });
63
+ program.parse();
@@ -0,0 +1,34 @@
1
+ export declare const API_BASE = "https://api.mnemom.ai";
2
+ export interface Agent {
3
+ id: string;
4
+ gateway: string;
5
+ last_seen: string | null;
6
+ claimed: boolean;
7
+ email?: string;
8
+ created_at: string;
9
+ }
10
+ export interface IntegrityScore {
11
+ agent_id: string;
12
+ score: number;
13
+ total_traces: number;
14
+ verified: number;
15
+ violations: number;
16
+ last_updated: string;
17
+ }
18
+ export interface Trace {
19
+ id: string;
20
+ agent_id: string;
21
+ timestamp: string;
22
+ action: string;
23
+ verified: boolean;
24
+ reasoning?: string;
25
+ tool_name?: string;
26
+ tool_input?: Record<string, unknown>;
27
+ }
28
+ export interface ApiError {
29
+ error: string;
30
+ message: string;
31
+ }
32
+ export declare function getAgent(id: string): Promise<Agent>;
33
+ export declare function getIntegrity(id: string): Promise<IntegrityScore>;
34
+ export declare function getTraces(id: string, limit?: number): Promise<Trace[]>;
@@ -0,0 +1,22 @@
1
+ export const API_BASE = "https://api.mnemom.ai";
2
+ async function fetchApi(endpoint) {
3
+ const url = `${API_BASE}${endpoint}`;
4
+ const response = await fetch(url);
5
+ if (!response.ok) {
6
+ const error = (await response.json().catch(() => ({
7
+ error: "unknown",
8
+ message: response.statusText,
9
+ })));
10
+ throw new Error(error.message || `API request failed: ${response.status}`);
11
+ }
12
+ return response.json();
13
+ }
14
+ export async function getAgent(id) {
15
+ return fetchApi(`/v1/agents/${id}`);
16
+ }
17
+ export async function getIntegrity(id) {
18
+ return fetchApi(`/v1/integrity/${id}`);
19
+ }
20
+ export async function getTraces(id, limit = 10) {
21
+ return fetchApi(`/v1/traces?agent_id=${id}&limit=${limit}`);
22
+ }
@@ -0,0 +1,18 @@
1
+ export declare const CONFIG_DIR: string;
2
+ export declare const CONFIG_FILE: string;
3
+ export interface Config {
4
+ agentId: string;
5
+ email?: string;
6
+ gateway?: string;
7
+ openclawConfigured?: boolean;
8
+ configuredAt?: string;
9
+ }
10
+ export declare function configExists(): boolean;
11
+ export declare function loadConfig(): Config | null;
12
+ export declare function saveConfig(config: Config): void;
13
+ export declare function generateAgentId(): string;
14
+ /**
15
+ * Derive agent ID deterministically from an API key.
16
+ * Uses the same SHA-256 hashing as the gateway so IDs match.
17
+ */
18
+ export declare function deriveAgentId(apiKey: string): string;
@@ -0,0 +1,40 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ 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");
7
+ export function configExists() {
8
+ return fs.existsSync(CONFIG_FILE);
9
+ }
10
+ export function loadConfig() {
11
+ if (!configExists()) {
12
+ return null;
13
+ }
14
+ try {
15
+ const content = fs.readFileSync(CONFIG_FILE, "utf-8");
16
+ return JSON.parse(content);
17
+ }
18
+ catch {
19
+ return null;
20
+ }
21
+ }
22
+ export function saveConfig(config) {
23
+ if (!fs.existsSync(CONFIG_DIR)) {
24
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
25
+ }
26
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
27
+ }
28
+ export function generateAgentId() {
29
+ const randomHex = crypto.randomBytes(4).toString("hex");
30
+ return `smolt-${randomHex}`;
31
+ }
32
+ /**
33
+ * Derive agent ID deterministically from an API key.
34
+ * Uses the same SHA-256 hashing as the gateway so IDs match.
35
+ */
36
+ export function deriveAgentId(apiKey) {
37
+ const hash = crypto.createHash("sha256").update(apiKey).digest("hex");
38
+ const agentHash = hash.substring(0, 16);
39
+ return `smolt-${agentHash.slice(0, 8)}`;
40
+ }
@@ -0,0 +1,32 @@
1
+ import type { ModelDefinition } from "./openclaw.js";
2
+ /**
3
+ * Known Anthropic model definitions with their specifications.
4
+ * These are used when configuring the smoltbot provider.
5
+ */
6
+ export declare const ANTHROPIC_MODELS: Record<string, ModelDefinition>;
7
+ /**
8
+ * Get model definition by ID
9
+ * Returns the definition if known, or creates a basic one if unknown
10
+ */
11
+ export declare function getModelDefinition(modelId: string): ModelDefinition;
12
+ /**
13
+ * Check if a model ID is a known Anthropic model
14
+ */
15
+ export declare function isKnownModel(modelId: string): boolean;
16
+ /**
17
+ * Check if a model ID looks like an Anthropic model
18
+ */
19
+ export declare function isAnthropicModel(modelId: string): boolean;
20
+ /**
21
+ * Format a model ID into a human-readable name
22
+ * e.g., "claude-opus-4-5-20251101" -> "Claude Opus 4.5"
23
+ */
24
+ export declare function formatModelName(modelId: string): string;
25
+ /**
26
+ * Get all known model IDs
27
+ */
28
+ export declare function getAllKnownModelIds(): string[];
29
+ /**
30
+ * Get the latest model for each tier
31
+ */
32
+ export declare function getLatestModels(): ModelDefinition[];