@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.
- package/LICENSE +191 -0
- package/README.md +119 -0
- package/dist/commands/init.d.ts +5 -0
- package/dist/commands/init.js +235 -0
- package/dist/commands/integrity.d.ts +1 -0
- package/dist/commands/integrity.js +63 -0
- package/dist/commands/logs.d.ts +4 -0
- package/dist/commands/logs.js +86 -0
- package/dist/commands/status.d.ts +1 -0
- package/dist/commands/status.js +299 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +63 -0
- package/dist/lib/api.d.ts +34 -0
- package/dist/lib/api.js +22 -0
- package/dist/lib/config.d.ts +18 -0
- package/dist/lib/config.js +40 -0
- package/dist/lib/models.d.ts +32 -0
- package/dist/lib/models.js +189 -0
- package/dist/lib/openclaw.d.ts +131 -0
- package/dist/lib/openclaw.js +219 -0
- package/dist/lib/prompt.d.ts +11 -0
- package/dist/lib/prompt.js +40 -0
- package/package.json +38 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Known Anthropic model definitions with their specifications.
|
|
3
|
+
* These are used when configuring the smoltbot provider.
|
|
4
|
+
*/
|
|
5
|
+
export const ANTHROPIC_MODELS = {
|
|
6
|
+
// Claude Opus 4.5 (latest flagship)
|
|
7
|
+
"claude-opus-4-5-20251101": {
|
|
8
|
+
id: "claude-opus-4-5-20251101",
|
|
9
|
+
name: "Claude Opus 4.5",
|
|
10
|
+
reasoning: true,
|
|
11
|
+
input: ["text", "image"],
|
|
12
|
+
contextWindow: 200000,
|
|
13
|
+
maxTokens: 64000,
|
|
14
|
+
cost: {
|
|
15
|
+
input: 5,
|
|
16
|
+
output: 25,
|
|
17
|
+
cacheRead: 0.5,
|
|
18
|
+
cacheWrite: 6.25,
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
// Claude Sonnet 4.5
|
|
22
|
+
"claude-sonnet-4-5-20250929": {
|
|
23
|
+
id: "claude-sonnet-4-5-20250929",
|
|
24
|
+
name: "Claude Sonnet 4.5",
|
|
25
|
+
reasoning: true,
|
|
26
|
+
input: ["text", "image"],
|
|
27
|
+
contextWindow: 200000,
|
|
28
|
+
maxTokens: 64000,
|
|
29
|
+
cost: {
|
|
30
|
+
input: 3,
|
|
31
|
+
output: 15,
|
|
32
|
+
cacheRead: 0.3,
|
|
33
|
+
cacheWrite: 3.75,
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
// Claude Haiku 4.5
|
|
37
|
+
"claude-haiku-4-5-20251001": {
|
|
38
|
+
id: "claude-haiku-4-5-20251001",
|
|
39
|
+
name: "Claude Haiku 4.5",
|
|
40
|
+
reasoning: false,
|
|
41
|
+
input: ["text", "image"],
|
|
42
|
+
contextWindow: 200000,
|
|
43
|
+
maxTokens: 64000,
|
|
44
|
+
cost: {
|
|
45
|
+
input: 0.8,
|
|
46
|
+
output: 4,
|
|
47
|
+
cacheRead: 0.08,
|
|
48
|
+
cacheWrite: 1,
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
// Legacy models (Claude 3.5)
|
|
52
|
+
"claude-3-5-sonnet-20241022": {
|
|
53
|
+
id: "claude-3-5-sonnet-20241022",
|
|
54
|
+
name: "Claude 3.5 Sonnet",
|
|
55
|
+
reasoning: false,
|
|
56
|
+
input: ["text", "image"],
|
|
57
|
+
contextWindow: 200000,
|
|
58
|
+
maxTokens: 8192,
|
|
59
|
+
cost: {
|
|
60
|
+
input: 3,
|
|
61
|
+
output: 15,
|
|
62
|
+
cacheRead: 0.3,
|
|
63
|
+
cacheWrite: 3.75,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
"claude-3-5-haiku-20241022": {
|
|
67
|
+
id: "claude-3-5-haiku-20241022",
|
|
68
|
+
name: "Claude 3.5 Haiku",
|
|
69
|
+
reasoning: false,
|
|
70
|
+
input: ["text", "image"],
|
|
71
|
+
contextWindow: 200000,
|
|
72
|
+
maxTokens: 8192,
|
|
73
|
+
cost: {
|
|
74
|
+
input: 0.8,
|
|
75
|
+
output: 4,
|
|
76
|
+
cacheRead: 0.08,
|
|
77
|
+
cacheWrite: 1,
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
// Claude 3 Opus (legacy)
|
|
81
|
+
"claude-3-opus-20240229": {
|
|
82
|
+
id: "claude-3-opus-20240229",
|
|
83
|
+
name: "Claude 3 Opus",
|
|
84
|
+
reasoning: false,
|
|
85
|
+
input: ["text", "image"],
|
|
86
|
+
contextWindow: 200000,
|
|
87
|
+
maxTokens: 4096,
|
|
88
|
+
cost: {
|
|
89
|
+
input: 15,
|
|
90
|
+
output: 75,
|
|
91
|
+
cacheRead: 1.5,
|
|
92
|
+
cacheWrite: 18.75,
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
"claude-3-haiku-20240307": {
|
|
96
|
+
id: "claude-3-haiku-20240307",
|
|
97
|
+
name: "Claude 3 Haiku",
|
|
98
|
+
reasoning: false,
|
|
99
|
+
input: ["text", "image"],
|
|
100
|
+
contextWindow: 200000,
|
|
101
|
+
maxTokens: 4096,
|
|
102
|
+
cost: {
|
|
103
|
+
input: 0.25,
|
|
104
|
+
output: 1.25,
|
|
105
|
+
cacheRead: 0.03,
|
|
106
|
+
cacheWrite: 0.3,
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* Get model definition by ID
|
|
112
|
+
* Returns the definition if known, or creates a basic one if unknown
|
|
113
|
+
*/
|
|
114
|
+
export function getModelDefinition(modelId) {
|
|
115
|
+
const known = ANTHROPIC_MODELS[modelId];
|
|
116
|
+
if (known) {
|
|
117
|
+
return known;
|
|
118
|
+
}
|
|
119
|
+
// Create a basic definition for unknown models
|
|
120
|
+
// This allows smoltbot to work with new models not yet in our registry
|
|
121
|
+
return {
|
|
122
|
+
id: modelId,
|
|
123
|
+
name: formatModelName(modelId),
|
|
124
|
+
reasoning: modelId.includes("opus") || modelId.includes("sonnet"),
|
|
125
|
+
input: ["text", "image"],
|
|
126
|
+
contextWindow: 200000,
|
|
127
|
+
maxTokens: 64000,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Check if a model ID is a known Anthropic model
|
|
132
|
+
*/
|
|
133
|
+
export function isKnownModel(modelId) {
|
|
134
|
+
return modelId in ANTHROPIC_MODELS;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Check if a model ID looks like an Anthropic model
|
|
138
|
+
*/
|
|
139
|
+
export function isAnthropicModel(modelId) {
|
|
140
|
+
return modelId.startsWith("claude-");
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Format a model ID into a human-readable name
|
|
144
|
+
* e.g., "claude-opus-4-5-20251101" -> "Claude Opus 4.5"
|
|
145
|
+
*/
|
|
146
|
+
export function formatModelName(modelId) {
|
|
147
|
+
// First check known models
|
|
148
|
+
const known = ANTHROPIC_MODELS[modelId];
|
|
149
|
+
if (known) {
|
|
150
|
+
return known.name;
|
|
151
|
+
}
|
|
152
|
+
// Try to parse the model ID
|
|
153
|
+
// Pattern: claude-{tier}-{version}-{date}
|
|
154
|
+
// e.g., claude-opus-4-5-20251101
|
|
155
|
+
const parts = modelId.split("-");
|
|
156
|
+
if (parts[0] !== "claude" || parts.length < 3) {
|
|
157
|
+
return modelId; // Return as-is if we can't parse
|
|
158
|
+
}
|
|
159
|
+
const tier = parts[1]; // opus, sonnet, haiku
|
|
160
|
+
const tierCapitalized = tier.charAt(0).toUpperCase() + tier.slice(1);
|
|
161
|
+
// Try to extract version (e.g., "4-5" -> "4.5")
|
|
162
|
+
if (parts.length >= 4 && /^\d+$/.test(parts[2]) && /^\d+$/.test(parts[3])) {
|
|
163
|
+
return `Claude ${tierCapitalized} ${parts[2]}.${parts[3]}`;
|
|
164
|
+
}
|
|
165
|
+
// Fallback for older naming (e.g., claude-3-opus-20240229)
|
|
166
|
+
if (/^\d+$/.test(parts[1])) {
|
|
167
|
+
const version = parts[1];
|
|
168
|
+
const tierName = parts[2];
|
|
169
|
+
const tierCap = tierName.charAt(0).toUpperCase() + tierName.slice(1);
|
|
170
|
+
return `Claude ${version} ${tierCap}`;
|
|
171
|
+
}
|
|
172
|
+
return `Claude ${tierCapitalized}`;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Get all known model IDs
|
|
176
|
+
*/
|
|
177
|
+
export function getAllKnownModelIds() {
|
|
178
|
+
return Object.keys(ANTHROPIC_MODELS);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Get the latest model for each tier
|
|
182
|
+
*/
|
|
183
|
+
export function getLatestModels() {
|
|
184
|
+
return [
|
|
185
|
+
ANTHROPIC_MODELS["claude-opus-4-5-20251101"],
|
|
186
|
+
ANTHROPIC_MODELS["claude-sonnet-4-5-20250929"],
|
|
187
|
+
ANTHROPIC_MODELS["claude-haiku-4-5-20251001"],
|
|
188
|
+
];
|
|
189
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
export declare const OPENCLAW_DIR: string;
|
|
2
|
+
export declare const OPENCLAW_CONFIG_FILE: string;
|
|
3
|
+
export declare const AUTH_PROFILES_FILE: string;
|
|
4
|
+
export interface AuthProfile {
|
|
5
|
+
type: "api_key" | "oauth";
|
|
6
|
+
provider: string;
|
|
7
|
+
key?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface AuthProfilesFile {
|
|
10
|
+
version: number;
|
|
11
|
+
profiles: Record<string, AuthProfile>;
|
|
12
|
+
lastGood?: Record<string, string>;
|
|
13
|
+
usageStats?: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
export interface ModelDefinition {
|
|
16
|
+
id: string;
|
|
17
|
+
name: string;
|
|
18
|
+
reasoning?: boolean;
|
|
19
|
+
input?: string[];
|
|
20
|
+
contextWindow?: number;
|
|
21
|
+
maxTokens?: number;
|
|
22
|
+
cost?: {
|
|
23
|
+
input: number;
|
|
24
|
+
output: number;
|
|
25
|
+
cacheRead?: number;
|
|
26
|
+
cacheWrite?: number;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export interface SmoltbotProvider {
|
|
30
|
+
baseUrl: string;
|
|
31
|
+
apiKey: string;
|
|
32
|
+
api: string;
|
|
33
|
+
models: ModelDefinition[];
|
|
34
|
+
}
|
|
35
|
+
export interface OpenClawConfig {
|
|
36
|
+
meta?: {
|
|
37
|
+
lastTouchedVersion?: string;
|
|
38
|
+
lastTouchedAt?: string;
|
|
39
|
+
};
|
|
40
|
+
wizard?: Record<string, unknown>;
|
|
41
|
+
update?: Record<string, unknown>;
|
|
42
|
+
auth?: {
|
|
43
|
+
profiles?: Record<string, {
|
|
44
|
+
provider: string;
|
|
45
|
+
mode: string;
|
|
46
|
+
}>;
|
|
47
|
+
};
|
|
48
|
+
models?: {
|
|
49
|
+
mode?: string;
|
|
50
|
+
providers?: Record<string, SmoltbotProvider>;
|
|
51
|
+
};
|
|
52
|
+
agents?: {
|
|
53
|
+
defaults?: {
|
|
54
|
+
workspace?: string;
|
|
55
|
+
compaction?: Record<string, unknown>;
|
|
56
|
+
maxConcurrent?: number;
|
|
57
|
+
subagents?: Record<string, unknown>;
|
|
58
|
+
model?: {
|
|
59
|
+
primary?: string;
|
|
60
|
+
};
|
|
61
|
+
models?: Record<string, unknown>;
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
messages?: Record<string, unknown>;
|
|
65
|
+
commands?: Record<string, unknown>;
|
|
66
|
+
gateway?: Record<string, unknown>;
|
|
67
|
+
[key: string]: unknown;
|
|
68
|
+
}
|
|
69
|
+
export interface OpenClawDetectionResult {
|
|
70
|
+
installed: boolean;
|
|
71
|
+
hasApiKey: boolean;
|
|
72
|
+
isOAuth: boolean;
|
|
73
|
+
apiKey?: string;
|
|
74
|
+
currentModel?: string;
|
|
75
|
+
currentModelId?: string;
|
|
76
|
+
currentProvider?: string;
|
|
77
|
+
smoltbotAlreadyConfigured: boolean;
|
|
78
|
+
error?: string;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Check if OpenClaw is installed
|
|
82
|
+
*/
|
|
83
|
+
export declare function openclawExists(): boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Load and parse auth-profiles.json
|
|
86
|
+
*/
|
|
87
|
+
export declare function loadAuthProfiles(): AuthProfilesFile | null;
|
|
88
|
+
/**
|
|
89
|
+
* Get the Anthropic API key from auth-profiles.json
|
|
90
|
+
*/
|
|
91
|
+
export declare function getAnthropicApiKey(): {
|
|
92
|
+
key: string | null;
|
|
93
|
+
isOAuth: boolean;
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Load openclaw.json config
|
|
97
|
+
*/
|
|
98
|
+
export declare function loadOpenClawConfig(): OpenClawConfig | null;
|
|
99
|
+
/**
|
|
100
|
+
* Save openclaw.json config (preserves all existing fields)
|
|
101
|
+
*/
|
|
102
|
+
export declare function saveOpenClawConfig(config: OpenClawConfig): void;
|
|
103
|
+
/**
|
|
104
|
+
* Get the current default model from OpenClaw config
|
|
105
|
+
* Returns both the full model path (provider/model) and parsed parts
|
|
106
|
+
*/
|
|
107
|
+
export declare function getCurrentModel(): {
|
|
108
|
+
fullPath: string | null;
|
|
109
|
+
provider: string | null;
|
|
110
|
+
modelId: string | null;
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* Check if smoltbot provider is already configured
|
|
114
|
+
*/
|
|
115
|
+
export declare function isSmoltbotConfigured(): boolean;
|
|
116
|
+
/**
|
|
117
|
+
* Get the existing smoltbot provider config
|
|
118
|
+
*/
|
|
119
|
+
export declare function getSmoltbotProvider(): SmoltbotProvider | null;
|
|
120
|
+
/**
|
|
121
|
+
* Comprehensive detection of OpenClaw setup
|
|
122
|
+
*/
|
|
123
|
+
export declare function detectOpenClaw(): OpenClawDetectionResult;
|
|
124
|
+
/**
|
|
125
|
+
* Configure the smoltbot provider in OpenClaw config
|
|
126
|
+
*/
|
|
127
|
+
export declare function configureSmoltbotProvider(apiKey: string, models: ModelDefinition[]): void;
|
|
128
|
+
/**
|
|
129
|
+
* Set the default model in OpenClaw config
|
|
130
|
+
*/
|
|
131
|
+
export declare function setDefaultModel(modelPath: string): void;
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
// OpenClaw paths
|
|
5
|
+
export const OPENCLAW_DIR = path.join(os.homedir(), ".openclaw");
|
|
6
|
+
export const OPENCLAW_CONFIG_FILE = path.join(OPENCLAW_DIR, "openclaw.json");
|
|
7
|
+
export const AUTH_PROFILES_FILE = path.join(OPENCLAW_DIR, "agents", "main", "agent", "auth-profiles.json");
|
|
8
|
+
/**
|
|
9
|
+
* Check if OpenClaw is installed
|
|
10
|
+
*/
|
|
11
|
+
export function openclawExists() {
|
|
12
|
+
return fs.existsSync(OPENCLAW_DIR) && fs.existsSync(OPENCLAW_CONFIG_FILE);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Load and parse auth-profiles.json
|
|
16
|
+
*/
|
|
17
|
+
export function loadAuthProfiles() {
|
|
18
|
+
if (!fs.existsSync(AUTH_PROFILES_FILE)) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
const content = fs.readFileSync(AUTH_PROFILES_FILE, "utf-8");
|
|
23
|
+
return JSON.parse(content);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Get the Anthropic API key from auth-profiles.json
|
|
31
|
+
*/
|
|
32
|
+
export function getAnthropicApiKey() {
|
|
33
|
+
const profiles = loadAuthProfiles();
|
|
34
|
+
if (!profiles) {
|
|
35
|
+
return { key: null, isOAuth: false };
|
|
36
|
+
}
|
|
37
|
+
// Look for anthropic:default or any anthropic profile
|
|
38
|
+
const anthropicProfile = profiles.profiles["anthropic:default"] ||
|
|
39
|
+
Object.values(profiles.profiles).find((p) => p.provider === "anthropic");
|
|
40
|
+
if (!anthropicProfile) {
|
|
41
|
+
return { key: null, isOAuth: false };
|
|
42
|
+
}
|
|
43
|
+
if (anthropicProfile.type === "oauth" || !anthropicProfile.key) {
|
|
44
|
+
return { key: null, isOAuth: true };
|
|
45
|
+
}
|
|
46
|
+
return { key: anthropicProfile.key, isOAuth: false };
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Load openclaw.json config
|
|
50
|
+
*/
|
|
51
|
+
export function loadOpenClawConfig() {
|
|
52
|
+
if (!fs.existsSync(OPENCLAW_CONFIG_FILE)) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
const content = fs.readFileSync(OPENCLAW_CONFIG_FILE, "utf-8");
|
|
57
|
+
return JSON.parse(content);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Save openclaw.json config (preserves all existing fields)
|
|
65
|
+
*/
|
|
66
|
+
export function saveOpenClawConfig(config) {
|
|
67
|
+
// Update meta timestamp
|
|
68
|
+
if (!config.meta) {
|
|
69
|
+
config.meta = {};
|
|
70
|
+
}
|
|
71
|
+
config.meta.lastTouchedAt = new Date().toISOString();
|
|
72
|
+
fs.writeFileSync(OPENCLAW_CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Get the current default model from OpenClaw config
|
|
76
|
+
* Returns both the full model path (provider/model) and parsed parts
|
|
77
|
+
*/
|
|
78
|
+
export function getCurrentModel() {
|
|
79
|
+
const config = loadOpenClawConfig();
|
|
80
|
+
if (!config) {
|
|
81
|
+
return { fullPath: null, provider: null, modelId: null };
|
|
82
|
+
}
|
|
83
|
+
const primary = config.agents?.defaults?.model?.primary;
|
|
84
|
+
if (!primary) {
|
|
85
|
+
return { fullPath: null, provider: null, modelId: null };
|
|
86
|
+
}
|
|
87
|
+
// Parse provider/model format (e.g., "anthropic/claude-opus-4-5-20251101")
|
|
88
|
+
const parts = primary.split("/");
|
|
89
|
+
if (parts.length === 2) {
|
|
90
|
+
return {
|
|
91
|
+
fullPath: primary,
|
|
92
|
+
provider: parts[0],
|
|
93
|
+
modelId: parts[1],
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
// No provider prefix, assume it's just the model ID
|
|
97
|
+
return {
|
|
98
|
+
fullPath: primary,
|
|
99
|
+
provider: null,
|
|
100
|
+
modelId: primary,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Check if smoltbot provider is already configured
|
|
105
|
+
*/
|
|
106
|
+
export function isSmoltbotConfigured() {
|
|
107
|
+
const config = loadOpenClawConfig();
|
|
108
|
+
return !!config?.models?.providers?.smoltbot;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Get the existing smoltbot provider config
|
|
112
|
+
*/
|
|
113
|
+
export function getSmoltbotProvider() {
|
|
114
|
+
const config = loadOpenClawConfig();
|
|
115
|
+
return config?.models?.providers?.smoltbot || null;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Comprehensive detection of OpenClaw setup
|
|
119
|
+
*/
|
|
120
|
+
export function detectOpenClaw() {
|
|
121
|
+
// Check if OpenClaw is installed
|
|
122
|
+
if (!openclawExists()) {
|
|
123
|
+
return {
|
|
124
|
+
installed: false,
|
|
125
|
+
hasApiKey: false,
|
|
126
|
+
isOAuth: false,
|
|
127
|
+
smoltbotAlreadyConfigured: false,
|
|
128
|
+
error: "OpenClaw is not installed. Install from https://openclaw.ai",
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
// Check auth profile
|
|
132
|
+
const { key, isOAuth } = getAnthropicApiKey();
|
|
133
|
+
if (isOAuth) {
|
|
134
|
+
return {
|
|
135
|
+
installed: true,
|
|
136
|
+
hasApiKey: false,
|
|
137
|
+
isOAuth: true,
|
|
138
|
+
smoltbotAlreadyConfigured: isSmoltbotConfigured(),
|
|
139
|
+
error: "OAuth authentication detected. smoltbot only supports API key authentication.\n" +
|
|
140
|
+
"To use smoltbot, add an API key to your Anthropic auth profile.",
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
if (!key) {
|
|
144
|
+
return {
|
|
145
|
+
installed: true,
|
|
146
|
+
hasApiKey: false,
|
|
147
|
+
isOAuth: false,
|
|
148
|
+
smoltbotAlreadyConfigured: isSmoltbotConfigured(),
|
|
149
|
+
error: "No Anthropic API key found in auth-profiles.json.\n" +
|
|
150
|
+
"Run `openclaw auth` to configure your API key.",
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
// Get current model
|
|
154
|
+
const { fullPath, provider, modelId } = getCurrentModel();
|
|
155
|
+
return {
|
|
156
|
+
installed: true,
|
|
157
|
+
hasApiKey: true,
|
|
158
|
+
isOAuth: false,
|
|
159
|
+
apiKey: key,
|
|
160
|
+
currentModel: fullPath || undefined,
|
|
161
|
+
currentModelId: modelId || undefined,
|
|
162
|
+
currentProvider: provider || undefined,
|
|
163
|
+
smoltbotAlreadyConfigured: isSmoltbotConfigured(),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Configure the smoltbot provider in OpenClaw config
|
|
168
|
+
*/
|
|
169
|
+
export function configureSmoltbotProvider(apiKey, models) {
|
|
170
|
+
const config = loadOpenClawConfig();
|
|
171
|
+
if (!config) {
|
|
172
|
+
throw new Error("Could not load OpenClaw config");
|
|
173
|
+
}
|
|
174
|
+
// Ensure models section exists
|
|
175
|
+
if (!config.models) {
|
|
176
|
+
config.models = {};
|
|
177
|
+
}
|
|
178
|
+
if (!config.models.providers) {
|
|
179
|
+
config.models.providers = {};
|
|
180
|
+
}
|
|
181
|
+
// Set mode to merge if not set
|
|
182
|
+
if (!config.models.mode) {
|
|
183
|
+
config.models.mode = "merge";
|
|
184
|
+
}
|
|
185
|
+
// Configure smoltbot provider
|
|
186
|
+
config.models.providers.smoltbot = {
|
|
187
|
+
baseUrl: "https://gateway.mnemom.ai/anthropic",
|
|
188
|
+
apiKey: apiKey,
|
|
189
|
+
api: "anthropic-messages",
|
|
190
|
+
models: models,
|
|
191
|
+
};
|
|
192
|
+
saveOpenClawConfig(config);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Set the default model in OpenClaw config
|
|
196
|
+
*/
|
|
197
|
+
export function setDefaultModel(modelPath) {
|
|
198
|
+
const config = loadOpenClawConfig();
|
|
199
|
+
if (!config) {
|
|
200
|
+
throw new Error("Could not load OpenClaw config");
|
|
201
|
+
}
|
|
202
|
+
// Ensure agents.defaults.model section exists
|
|
203
|
+
if (!config.agents) {
|
|
204
|
+
config.agents = {};
|
|
205
|
+
}
|
|
206
|
+
if (!config.agents.defaults) {
|
|
207
|
+
config.agents.defaults = {};
|
|
208
|
+
}
|
|
209
|
+
if (!config.agents.defaults.model) {
|
|
210
|
+
config.agents.defaults.model = {};
|
|
211
|
+
}
|
|
212
|
+
// Also add to models map if not present
|
|
213
|
+
if (!config.agents.defaults.models) {
|
|
214
|
+
config.agents.defaults.models = {};
|
|
215
|
+
}
|
|
216
|
+
config.agents.defaults.model.primary = modelPath;
|
|
217
|
+
config.agents.defaults.models[modelPath] = {};
|
|
218
|
+
saveOpenClawConfig(config);
|
|
219
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ask a yes/no question with a default answer
|
|
3
|
+
* @param question The question to ask
|
|
4
|
+
* @param defaultYes If true, default is Yes (Y/n), otherwise default is No (y/N)
|
|
5
|
+
* @returns Promise<boolean> - true for yes, false for no
|
|
6
|
+
*/
|
|
7
|
+
export declare function askYesNo(question: string, defaultYes?: boolean): Promise<boolean>;
|
|
8
|
+
/**
|
|
9
|
+
* Check if running in non-interactive mode (no TTY)
|
|
10
|
+
*/
|
|
11
|
+
export declare function isInteractive(): boolean;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import * as readline from "node:readline";
|
|
2
|
+
/**
|
|
3
|
+
* Ask a yes/no question with a default answer
|
|
4
|
+
* @param question The question to ask
|
|
5
|
+
* @param defaultYes If true, default is Yes (Y/n), otherwise default is No (y/N)
|
|
6
|
+
* @returns Promise<boolean> - true for yes, false for no
|
|
7
|
+
*/
|
|
8
|
+
export async function askYesNo(question, defaultYes = true) {
|
|
9
|
+
const suffix = defaultYes ? "[Y/n]" : "[y/N]";
|
|
10
|
+
const rl = readline.createInterface({
|
|
11
|
+
input: process.stdin,
|
|
12
|
+
output: process.stdout,
|
|
13
|
+
});
|
|
14
|
+
return new Promise((resolve) => {
|
|
15
|
+
rl.question(`${question} ${suffix} `, (answer) => {
|
|
16
|
+
rl.close();
|
|
17
|
+
const normalized = answer.trim().toLowerCase();
|
|
18
|
+
if (normalized === "") {
|
|
19
|
+
// Use default
|
|
20
|
+
resolve(defaultYes);
|
|
21
|
+
}
|
|
22
|
+
else if (normalized === "y" || normalized === "yes") {
|
|
23
|
+
resolve(true);
|
|
24
|
+
}
|
|
25
|
+
else if (normalized === "n" || normalized === "no") {
|
|
26
|
+
resolve(false);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
// Invalid input, use default
|
|
30
|
+
resolve(defaultYes);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Check if running in non-interactive mode (no TTY)
|
|
37
|
+
*/
|
|
38
|
+
export function isInteractive() {
|
|
39
|
+
return process.stdin.isTTY === true;
|
|
40
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mnemom/smoltbot",
|
|
3
|
+
"version": "2.0.1",
|
|
4
|
+
"description": "Transparent AI agent tracing - AAP compliant",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"smoltbot": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"dev": "tsx src/index.ts",
|
|
12
|
+
"test": "vitest"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"commander": "^12.0.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/node": "^20.10.0",
|
|
19
|
+
"tsx": "^4.7.0",
|
|
20
|
+
"typescript": "^5.3.3",
|
|
21
|
+
"vitest": "^1.2.0"
|
|
22
|
+
},
|
|
23
|
+
"files": ["dist", "!dist/__tests__", "README.md", "LICENSE"],
|
|
24
|
+
"keywords": ["ai", "agent", "transparency", "aap", "alignment", "tracing", "observability"],
|
|
25
|
+
"author": "Mnemom.ai",
|
|
26
|
+
"license": "Apache-2.0",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/mnemom/smoltbot"
|
|
30
|
+
},
|
|
31
|
+
"homepage": "https://mnemom.ai",
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18.0.0"
|
|
37
|
+
}
|
|
38
|
+
}
|