clawd-models 1.0.2 → 1.0.4

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/README.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  CLI tool to manage OpenClaw model configurations.
4
4
 
5
+ ## Quick Start Flow
6
+
7
+ ![Setup Flow](docs/setup-flow.svg)
8
+
5
9
  ## Prerequisite
6
10
  Install and Setup `openclaw` firstly, refer to https://github.com/openclaw/openclaw
7
11
  ```
@@ -22,10 +26,26 @@ npm i -g clawd-models
22
26
  ## Usage
23
27
 
24
28
  ```bash
25
- clawd-models <command>
29
+ clawd-models <command> [--bot <openclaw|clawdbot|moltbot>]
26
30
  # clawd-models help
27
31
  ```
28
32
 
33
+ **Global Options:**
34
+ - `--bot <bot-id>` - Target bot (openclaw, clawdbot, moltbot). Also saves as default for future commands.
35
+ - `--clear-bot` - Clear the default bot setting
36
+
37
+ **Bot Priority (when --bot not specified):**
38
+ 1. Saved preference (if set via previous `--bot` use)
39
+ 2. Auto-detect: openclaw → clawdbot → moltbot
40
+
41
+ **Config Locations:**
42
+ - OpenClaw: `~/.openclaw/openclaw.json`
43
+ - ClawdBot: `~/.clawdbot/clawdbot.json`
44
+ - MoltBot: `~/.moltbot/moltbot.json`
45
+ - Preferred bot: `~/.clawd-models/bot`
46
+
47
+ **Default Bot Priority:** `openclaw` → `clawdbot` → `moltbot` (auto-selected if not specified via `--bot`)
48
+
29
49
  ## Commands
30
50
 
31
51
  ### Core
@@ -43,13 +63,15 @@ clawd-models <command>
43
63
  | `providers:add -n <name> -u <url> [-k <api-key>]` | Add a new model provider |
44
64
  | `providers:remove -n <name>` | Remove a model provider |
45
65
  | `providers:list` | List all configured providers |
66
+ Should add a provider firstly, then add a model (refer to next section).
46
67
 
47
68
  ### Model Management
48
69
  | Command | Description |
49
70
  |---------|-------------|
50
71
  | `models:add -p <provider> -i <model-id> --name <name>` | Add a model to a provider |
51
72
  | `models:remove -p <provider> -i <model-id>` | Remove a model from a provider |
52
- | `model:list [--provider <name>]` | List all configured models |
73
+ | `models:list [--provider <name>]` | List all configured models |
74
+ | `models:test` | Test the default model configuration by sending a test message |
53
75
 
54
76
  Use `agents:set-default` in next section or refer to `openclaw models set` to set a default model for 'main' and all agents.
55
77
 
@@ -79,4 +101,8 @@ Refer to `openclaw gateway status` for more.
79
101
 
80
102
  ## Configuration Location
81
103
 
82
- `~/.openclaw/openclaw.json`
104
+ `~/.openclaw/openclaw.json`
105
+ or
106
+ `~/.clawdbot/clawdbot.json`
107
+ or
108
+ `~/.moltbot/moltbot.json`
@@ -6,14 +6,210 @@ const path = require('path');
6
6
  const os = require('os');
7
7
 
8
8
  const OPENCLAW_CONFIG_PATH = path.join(os.homedir(), '.openclaw', 'openclaw.json');
9
+ const CLAWD_MODELS_DIR = path.join(os.homedir(), '.clawd-models');
10
+ const CLAWD_MODELS_BOT_FILE = path.join(CLAWD_MODELS_DIR, 'bot.json');
9
11
 
10
12
  const CURRENT_VERSION = '2026.2.1';
11
13
 
14
+ // Supported bot configurations
15
+ const BOT_CONFIGS = [
16
+ { id: 'openclaw', name: 'OpenClaw', path: '.openclaw/openclaw.json' },
17
+ { id: 'clawdbot', name: 'ClawdBot', path: '.clawdbot/clawdbot.json' },
18
+ { id: 'moltbot', name: 'MoltBot', path: '.moltbot/moltbot.json' }
19
+ ];
20
+
21
+ function getBotConfigPath(botId) {
22
+ const bot = BOT_CONFIGS.find(b => b.id === botId);
23
+ if (!bot) return null;
24
+ return path.join(os.homedir(), bot.path);
25
+ }
26
+
27
+ function loadPreferredBot() {
28
+ if (fs.existsSync(CLAWD_MODELS_BOT_FILE)) {
29
+ try {
30
+ const content = JSON.parse(fs.readFileSync(CLAWD_MODELS_BOT_FILE, 'utf8'));
31
+ const botId = content.bot;
32
+ // Validate it's a known bot
33
+ if (botId && BOT_CONFIGS.some(b => b.id === botId)) {
34
+ return botId;
35
+ }
36
+ } catch (e) {
37
+ return null;
38
+ }
39
+ }
40
+ return null;
41
+ }
42
+
43
+ function savePreferredBot(botId) {
44
+ // Validate bot ID
45
+ if (!BOT_CONFIGS.some(b => b.id === botId)) {
46
+ console.error(`Unknown bot: ${botId}. Valid options: ${BOT_CONFIGS.map(b => b.id).join(', ')}`);
47
+ process.exit(1);
48
+ }
49
+ fs.ensureDirSync(CLAWD_MODELS_DIR);
50
+ fs.writeFileSync(CLAWD_MODELS_BOT_FILE, JSON.stringify({ bot: botId }, null, 2));
51
+ }
52
+
53
+ function clearPreferredBot() {
54
+ if (fs.existsSync(CLAWD_MODELS_BOT_FILE)) {
55
+ fs.unlinkSync(CLAWD_MODELS_BOT_FILE);
56
+ }
57
+ }
58
+
59
+ async function promptBotSelection() {
60
+ const available = [];
61
+ for (const bot of BOT_CONFIGS) {
62
+ const configPath = getBotConfigPath(bot.id);
63
+ if (fs.existsSync(configPath)) {
64
+ available.push(bot);
65
+ }
66
+ }
67
+
68
+ if (available.length === 0) {
69
+ console.log('No bot configurations found.');
70
+ console.log('Expected locations:');
71
+ for (const bot of BOT_CONFIGS) {
72
+ console.log(` ${bot.name}: ${getBotConfigPath(bot.id)}`);
73
+ }
74
+ return null;
75
+ }
76
+
77
+ if (available.length === 1) {
78
+ return available[0];
79
+ }
80
+
81
+ console.log('Multiple bot configurations found. Select one:');
82
+ for (let i = 0; i < available.length; i++) {
83
+ console.log(` ${i + 1}. ${available[i].name} (${getBotConfigPath(available[i].id)})`);
84
+ }
85
+
86
+ const { promisify } = require('util');
87
+ const readline = require('readline');
88
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
89
+ const question = promisify(rl.question).bind(rl);
90
+
91
+ while (true) {
92
+ const answer = await question('\nEnter number (1-' + available.length + '): ');
93
+ const idx = parseInt(answer) - 1;
94
+ if (idx >= 0 && idx < available.length) {
95
+ rl.close();
96
+ return available[idx];
97
+ }
98
+ console.log('Invalid selection. Please try again.');
99
+ }
100
+ }
101
+
102
+ async function resolveConfig(botOption) {
103
+ if (botOption) {
104
+ const bot = BOT_CONFIGS.find(b => b.id === botOption);
105
+ if (!bot) {
106
+ throw new Error(`Unknown bot: ${botOption}. Available: ${BOT_CONFIGS.map(b => b.id).join(', ')}`);
107
+ }
108
+ const configPath = getBotConfigPath(bot.id);
109
+ if (!fs.existsSync(configPath)) {
110
+ throw new Error(`${bot.name} config not found at ~/.${bot.name}/${bot.name}.json`);
111
+ }
112
+ return { bot, configPath };
113
+ }
114
+
115
+ // No bot specified, prompt or auto-detect
116
+ const bot = await promptBotSelection();
117
+ if (!bot) {
118
+ throw new Error('No bot configurations available');
119
+ }
120
+ return { bot, configPath: getBotConfigPath(bot.id) };
121
+ }
122
+
123
+ function loadBotConfig(botOption) {
124
+ const { configPath } = resolveConfigSync(botOption);
125
+ const content = fs.readFileSync(configPath, 'utf8');
126
+ return JSON.parse(content);
127
+ }
128
+
129
+ function resolveConfigSync(botOption) {
130
+ // If botOption is provided, validate and use it
131
+ if (botOption) {
132
+ const bot = BOT_CONFIGS.find(b => b.id === botOption);
133
+ if (!bot) {
134
+ throw new Error(`Unknown bot: ${botOption}. Available: ${BOT_CONFIGS.map(b => b.id).join(', ')}`);
135
+ }
136
+ const configPath = getBotConfigPath(bot.id);
137
+ if (!fs.existsSync(configPath)) {
138
+ throw new Error(`${bot.name} config not found at ${configPath}`);
139
+ }
140
+ return { bot, configPath };
141
+ }
142
+
143
+ // Auto-detect: try saved preference first, then priority order
144
+ const savedBot = loadPreferredBot();
145
+ if (savedBot) {
146
+ const bot = BOT_CONFIGS.find(b => b.id === savedBot);
147
+ const configPath = getBotConfigPath(savedBot);
148
+ if (bot && fs.existsSync(configPath)) {
149
+ return { bot, configPath };
150
+ }
151
+ }
152
+
153
+ // Try bots in priority order: openclaw -> clawdbot -> moltbot
154
+ const priorityOrder = ['openclaw', 'clawdbot', 'moltbot'];
155
+ for (const botId of priorityOrder) {
156
+ const configPath = getBotConfigPath(botId);
157
+ if (fs.existsSync(configPath)) {
158
+ const bot = BOT_CONFIGS.find(b => b.id === botId);
159
+ // Auto-detected and valid, save as preference
160
+ savePreferredBot(bot.id);
161
+ console.log(`Auto-detected bot: ${bot.id}`);
162
+ return { bot, configPath };
163
+ }
164
+ }
165
+
166
+ throw new Error('No bot configurations found');
167
+ }
168
+
169
+ async function resolveConfig(botOption) {
170
+ return resolveConfigSync(botOption);
171
+ }
172
+
173
+ function getBotFromArgs() {
174
+ const botIdx = process.argv.indexOf('--bot');
175
+ if (botIdx !== -1 && botIdx + 1 < process.argv.length) {
176
+ const botId = process.argv[botIdx + 1];
177
+ // Validate bot ID
178
+ const bot = BOT_CONFIGS.find(b => b.id === botId);
179
+ if (!bot) {
180
+ console.error(`Unknown bot: ${botId}. Valid options: ${BOT_CONFIGS.map(b => b.id).join(', ')}`);
181
+ process.exit(1);
182
+ }
183
+ // Verify config file exists
184
+ const configPath = getBotConfigPath(botId);
185
+ if (!fs.existsSync(configPath)) {
186
+ console.error(`${bot.name} config not found at ${configPath}`);
187
+ process.exit(1);
188
+ }
189
+ // Save to bot.json
190
+ fs.ensureDirSync(CLAWD_MODELS_DIR);
191
+ fs.writeFileSync(CLAWD_MODELS_BOT_FILE, JSON.stringify({ bot: botId }, null, 2));
192
+ console.log(`Default bot set to: ${botId}`);
193
+ return botId;
194
+ }
195
+ return null;
196
+ }
197
+
12
198
  program
13
199
  .name('clawd-models')
14
200
  .description('CLI tool to manage OpenClaw model configurations')
15
- .version('1.0.2')
16
- .showHelpAfterError();
201
+ .version('1.0.4')
202
+ .showHelpAfterError()
203
+ .option('--bot <bot-id>', 'Target bot: openclaw, clawdbot, moltbot (also sets as default)')
204
+ .option('--clear-bot', 'Clear the default bot setting');
205
+
206
+ // Handle --clear-bot before normal command parsing
207
+ const clearBotIdx = process.argv.indexOf('--clear-bot');
208
+ if (clearBotIdx !== -1) {
209
+ clearPreferredBot();
210
+ console.log('Default bot cleared.');
211
+ process.exit(0);
212
+ }
17
213
 
18
214
  // ============ Core Commands ============
19
215
 
@@ -84,25 +280,29 @@ program
84
280
  program
85
281
  .command('view')
86
282
  .description('View current configuration')
87
- .action(() => {
88
- const config = loadConfig();
283
+ .action((cmdOptions) => {
284
+ const botOption = getBotFromArgs();
285
+ const { bot, configPath } = resolveConfigSync(botOption);
286
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
89
287
  console.log(JSON.stringify(config, null, 2));
90
- console.log(`\n${OPENCLAW_CONFIG_PATH}`);
288
+ console.log(`\n${bot.name}: ${configPath}`);
91
289
  });
92
290
 
93
291
  program
94
292
  .command('edit')
95
293
  .description('Edit configuration in default editor')
96
294
  .action(() => {
97
- const config = loadConfig();
295
+ const botOption = getBotFromArgs();
296
+ const { bot, configPath } = resolveConfigSync(botOption);
297
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
98
298
  config.meta.lastTouchedVersion = CURRENT_VERSION;
99
299
  config.meta.lastTouchedAt = new Date().toISOString();
100
- saveConfig(config);
300
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
101
301
 
102
302
  const editor = process.env.EDITOR || 'vi';
103
- require('child_process').execSync(`${editor} "${OPENCLAW_CONFIG_PATH}"`, { stdio: 'inherit' });
303
+ require('child_process').execSync(`${editor} "${configPath}"`, { stdio: 'inherit' });
104
304
 
105
- console.log(`Configuration updated at ${OPENCLAW_CONFIG_PATH}`);
305
+ console.log(`Configuration updated at ${bot.name}: ${configPath}`);
106
306
  });
107
307
 
108
308
  // ============ Provider Commands ============
@@ -116,7 +316,9 @@ program
116
316
  .option('--api <api-type>', 'API type (e.g., openai-completions, anthropic-messages)', 'openai-completions')
117
317
  .option('--auth <auth-type>', 'Auth method (e.g., api-key, bearer)', 'api-key')
118
318
  .action((options) => {
119
- const config = loadConfig();
319
+ const botOption = getBotFromArgs();
320
+ const { bot, configPath } = resolveConfigSync(botOption);
321
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
120
322
  config.models.providers = config.models.providers || {};
121
323
 
122
324
  const provider = {
@@ -133,8 +335,8 @@ program
133
335
  config.models.providers[options.name] = provider;
134
336
 
135
337
  config.meta.lastTouchedAt = new Date().toISOString();
136
- saveConfig(config);
137
- console.log(`Provider "${options.name}" added. Use "clawd-models models:add" to add models.`);
338
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
339
+ console.log(`Provider "${options.name}" added to ${bot.name}.`);
138
340
  });
139
341
 
140
342
  program
@@ -142,12 +344,14 @@ program
142
344
  .description('Remove a model provider')
143
345
  .requiredOption('-n, --name <name>', 'Provider name')
144
346
  .action((options) => {
145
- const config = loadConfig();
347
+ const botOption = getBotFromArgs();
348
+ const { bot, configPath } = resolveConfigSync(botOption);
349
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
146
350
  if (config.models.providers && config.models.providers[options.name]) {
147
351
  delete config.models.providers[options.name];
148
352
  config.meta.lastTouchedAt = new Date().toISOString();
149
- saveConfig(config);
150
- console.log(`Provider "${options.name}" removed.`);
353
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
354
+ console.log(`Provider "${options.name}" removed from ${bot.name}.`);
151
355
  } else {
152
356
  console.log(`Provider "${options.name}" not found.`);
153
357
  }
@@ -156,8 +360,10 @@ program
156
360
  program
157
361
  .command('providers:list')
158
362
  .description('List all configured providers')
159
- .action(() => {
160
- const config = loadConfig();
363
+ .action((options) => {
364
+ const botOption = getBotFromArgs();
365
+ const { configPath } = resolveConfigSync(botOption);
366
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
161
367
  const providers = config.models?.providers || {};
162
368
 
163
369
  if (Object.keys(providers).length === 0) {
@@ -193,7 +399,9 @@ program
193
399
  .option('--context <tokens>', 'Context window size', '200000')
194
400
  .option('--max-tokens <tokens>', 'Max output tokens', '8192')
195
401
  .action((options) => {
196
- const config = loadConfig();
402
+ const botOption = getBotFromArgs();
403
+ const { bot, configPath } = resolveConfigSync(botOption);
404
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
197
405
 
198
406
  if (!config.models.providers || !config.models.providers[options.provider]) {
199
407
  console.log(`Provider "${options.provider}" not found. Add it first with "clawd-models providers:add".`);
@@ -227,8 +435,8 @@ program
227
435
 
228
436
  provider.models.push(model);
229
437
  config.meta.lastTouchedAt = new Date().toISOString();
230
- saveConfig(config);
231
- console.log(`Model "${options.id}" added to provider "${options.provider}".`);
438
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
439
+ console.log(`Model "${options.id}" added to provider "${options.provider}" in ${bot.name}.`);
232
440
  });
233
441
 
234
442
  program
@@ -237,7 +445,9 @@ program
237
445
  .requiredOption('-p, --provider <provider>', 'Provider name')
238
446
  .requiredOption('-i, --id <id>', 'Model ID')
239
447
  .action((options) => {
240
- const config = loadConfig();
448
+ const botOption = getBotFromArgs();
449
+ const { bot, configPath } = resolveConfigSync(botOption);
450
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
241
451
 
242
452
  if (!config.models.providers || !config.models.providers[options.provider]) {
243
453
  console.log(`Provider "${options.provider}" not found.`);
@@ -254,8 +464,8 @@ program
254
464
 
255
465
  provider.models.splice(idx, 1);
256
466
  config.meta.lastTouchedAt = new Date().toISOString();
257
- saveConfig(config);
258
- console.log(`Model "${options.id}" removed from provider "${options.provider}".`);
467
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
468
+ console.log(`Model "${options.id}" removed from provider "${options.provider}" in ${bot.name}.`);
259
469
  });
260
470
 
261
471
  program
@@ -263,7 +473,9 @@ program
263
473
  .description('List all configured models')
264
474
  .option('--provider <provider>', 'Filter by provider')
265
475
  .action((options) => {
266
- const config = loadConfig();
476
+ const botOption = getBotFromArgs();
477
+ const { configPath } = resolveConfigSync(botOption);
478
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
267
479
  const providers = config.models?.providers || {};
268
480
 
269
481
  if (options.provider) {
@@ -288,6 +500,146 @@ program
288
500
  }
289
501
  });
290
502
 
503
+ program
504
+ .command('models:test')
505
+ .description('Test the default model configuration by sending a test message')
506
+ .action(async () => {
507
+ const botOption = getBotFromArgs();
508
+ const { bot, configPath } = await resolveConfig(botOption);
509
+ console.log(`\nUsing ${bot.name} configuration`);
510
+
511
+ let config;
512
+ try {
513
+ config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
514
+ } catch (error) {
515
+ console.error(`Error loading configuration: ${error.message}`);
516
+ return;
517
+ }
518
+
519
+ const defaultModel = config.agents?.defaults?.model?.primary;
520
+
521
+ if (!defaultModel) {
522
+ console.log('No default model configured. Use "clawd-models agents:set-default" to set one.');
523
+ return;
524
+ }
525
+
526
+ // Parse provider/model from default model
527
+ const [providerName, ...modelPath] = defaultModel.split('/');
528
+ const modelId = modelPath.join('/');
529
+
530
+ const provider = config.models?.providers?.[providerName];
531
+ if (!provider) {
532
+ console.log(`Provider "${providerName}" not found in configuration.`);
533
+ return;
534
+ }
535
+
536
+ console.log(`Testing model: ${defaultModel}`);
537
+ console.log(`Provider: ${providerName}`);
538
+ console.log(`Base URL: ${provider.baseUrl}`);
539
+ console.log(`API: ${provider.api}`);
540
+
541
+ const apiKey = provider.apiKey;
542
+ if (!apiKey) {
543
+ console.log('\nWarning: No API key configured for this provider.');
544
+ }
545
+
546
+ // Build the endpoint URL based on API type
547
+ let endpoint;
548
+ if (provider.api === 'openai-completions') {
549
+ endpoint = `${provider.baseUrl.replace(/\/$/, '')}/chat/completions`;
550
+ } else if (provider.api === 'anthropic-messages') {
551
+ endpoint = `${provider.baseUrl.replace(/\/$/, '')}/v1/messages`;
552
+ } else {
553
+ console.log(`Unsupported API type: ${provider.api}`);
554
+ return;
555
+ }
556
+
557
+ console.log(`\nEndpoint: ${endpoint}`);
558
+
559
+ // Prepare the request body
560
+ let body;
561
+ let headers = {};
562
+
563
+ if (apiKey) {
564
+ if (provider.auth === 'bearer') {
565
+ headers['Authorization'] = `Bearer ${apiKey}`;
566
+ } else {
567
+ headers['Authorization'] = `Bearer ${apiKey}`;
568
+ headers['X-API-Key'] = apiKey;
569
+ }
570
+ }
571
+
572
+ if (provider.api === 'openai-completions') {
573
+ body = JSON.stringify({
574
+ model: modelId,
575
+ messages: [{ role: 'user', content: 'hi, there' }],
576
+ max_tokens: 10
577
+ });
578
+ headers['Content-Type'] = 'application/json';
579
+ } else if (provider.api === 'anthropic-messages') {
580
+ body = JSON.stringify({
581
+ model: modelId,
582
+ messages: [{ role: 'user', content: 'hi, there' }],
583
+ max_tokens: 10
584
+ });
585
+ headers['Content-Type'] = 'application/json';
586
+ headers['anthropic-version'] = '2023-06-01';
587
+ }
588
+
589
+ console.log('\n--- Request Headers ---');
590
+ for (const [key, value] of Object.entries(headers)) {
591
+ // Mask API key for display - hide last 32 chars
592
+ const displayValue = key.toLowerCase().includes('api') || key.toLowerCase().includes('authorization')
593
+ ? value.length > 32 ? value.slice(0, -32) + '********************************' : '********************************'
594
+ : value;
595
+ console.log(`${key}: ${displayValue}`);
596
+ }
597
+
598
+ try {
599
+ const response = await fetch(endpoint, {
600
+ method: 'POST',
601
+ headers,
602
+ body
603
+ });
604
+
605
+ const status = response.status;
606
+ const contentType = response.headers.get('content-type');
607
+
608
+ console.log('\n--- Response Headers ---');
609
+ for (const [key, value] of response.headers.entries()) {
610
+ console.log(`${key}: ${value}`);
611
+ }
612
+
613
+ let responseBody;
614
+
615
+ if (contentType && contentType.includes('application/json')) {
616
+ responseBody = await response.json();
617
+ } else {
618
+ responseBody = await response.text();
619
+ }
620
+
621
+ console.log(`\nStatus: ${status}`);
622
+ console.log('Response body:');
623
+ console.log(JSON.stringify(responseBody, null, 2));
624
+
625
+ // Provide helpful advice on 404 errors
626
+ if (status === 404) {
627
+ console.log('\n--- Troubleshooting ---');
628
+ if (provider.api === 'openai-completions') {
629
+ console.log('For OpenAI-compatible APIs, ensure your base URL ends with /v1');
630
+ console.log('Expected format: <schema>://<hostname>[:port]/v1');
631
+ console.log('Example: https://api.example.com/v1');
632
+ } else if (provider.api === 'anthropic-messages') {
633
+ console.log('For Anthropic Messages APIs, ensure your base URL ends with /v1');
634
+ console.log('Expected format: <schema>://<hostname>[:port]/v1');
635
+ console.log('Example: https://api.anthropic.com/v1');
636
+ }
637
+ }
638
+ } catch (error) {
639
+ console.error(`\nRequest failed: ${error.message}`);
640
+ }
641
+ });
642
+
291
643
  // ============ Agent Commands ============
292
644
 
293
645
  program
@@ -299,7 +651,9 @@ program
299
651
  .option('--workspace <path>', 'Workspace directory')
300
652
  .option('--agent-dir <path>', 'Agent directory')
301
653
  .action((options) => {
302
- const config = loadConfig();
654
+ const botOption = getBotFromArgs();
655
+ const { bot, configPath } = resolveConfigSync(botOption);
656
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
303
657
  config.agents = config.agents || { defaults: {}, list: [] };
304
658
 
305
659
  const existing = config.agents.list.find(a => a.id === options.id);
@@ -316,8 +670,8 @@ program
316
670
 
317
671
  config.agents.list.push(agent);
318
672
  config.meta.lastTouchedAt = new Date().toISOString();
319
- saveConfig(config);
320
- console.log(`Agent "${options.id}" added.`);
673
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
674
+ console.log(`Agent "${options.id}" added to ${bot.name}.`);
321
675
  });
322
676
 
323
677
  program
@@ -325,7 +679,9 @@ program
325
679
  .description('Remove an agent')
326
680
  .requiredOption('-i, --id <id>', 'Agent ID')
327
681
  .action((options) => {
328
- const config = loadConfig();
682
+ const botOption = getBotFromArgs();
683
+ const { configPath } = resolveConfigSync(botOption);
684
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
329
685
  if (!config.agents?.list) {
330
686
  console.log('No agents configured.');
331
687
  return;
@@ -339,15 +695,17 @@ program
339
695
 
340
696
  config.agents.list.splice(idx, 1);
341
697
  config.meta.lastTouchedAt = new Date().toISOString();
342
- saveConfig(config);
698
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
343
699
  console.log(`Agent "${options.id}" removed.`);
344
700
  });
345
701
 
346
702
  program
347
703
  .command('agents:list')
348
704
  .description('List all configured agents')
349
- .action(() => {
350
- const config = loadConfig();
705
+ .action((options) => {
706
+ const botOption = getBotFromArgs();
707
+ const { configPath } = resolveConfigSync(botOption);
708
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
351
709
  const agents = config.agents?.list || [];
352
710
  const defaults = config.agents?.defaults || {};
353
711
 
@@ -385,15 +743,17 @@ program
385
743
  .requiredOption('-a, --agent <agent>', 'Agent type (e.g., main, code)')
386
744
  .requiredOption('-m, --model <model>', 'Model ID')
387
745
  .action((options) => {
388
- const config = loadConfig();
746
+ const botOption = getBotFromArgs();
747
+ const { bot, configPath } = resolveConfigSync(botOption);
748
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
389
749
  config.agents = config.agents || { defaults: {}, list: [] };
390
750
  config.agents.defaults = config.agents.defaults || {};
391
751
  config.agents.defaults.model = config.agents.defaults.model || {};
392
752
  config.agents.defaults.model.primary = options.model;
393
753
 
394
754
  config.meta.lastTouchedAt = new Date().toISOString();
395
- saveConfig(config);
396
- console.log(`Default model for agent "${options.agent}" set to "${options.model}".`);
755
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
756
+ console.log(`Default model for agent "${options.agent}" set to "${options.model}" in ${bot.name}.`);
397
757
  });
398
758
 
399
759
  // ============ Gateway Commands ============
@@ -401,8 +761,10 @@ program
401
761
  program
402
762
  .command('gateway:view')
403
763
  .description('View gateway configuration')
404
- .action(() => {
405
- const config = loadConfig();
764
+ .action((options) => {
765
+ const botOption = getBotFromArgs();
766
+ const { configPath } = resolveConfigSync(botOption);
767
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
406
768
  const gw = config.gateway || {};
407
769
  console.log(`Port: ${gw.port || 18789}`);
408
770
  console.log(`Mode: ${gw.mode || 'local'}`);
@@ -416,13 +778,15 @@ program
416
778
  program
417
779
  .command('gateway:refresh-token')
418
780
  .description('Refresh gateway auth token')
419
- .action(() => {
420
- const config = loadConfig();
781
+ .action((options) => {
782
+ const botOption = getBotFromArgs();
783
+ const { bot, configPath } = resolveConfigSync(botOption);
784
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
421
785
  config.gateway = config.gateway || {};
422
786
  config.gateway.auth = config.gateway.auth || { mode: 'token' };
423
787
  config.gateway.auth.token = generateToken();
424
788
  config.meta.lastTouchedAt = new Date().toISOString();
425
- saveConfig(config);
789
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
426
790
  console.log('Gateway auth token refreshed.');
427
791
  });
428
792
 
@@ -431,8 +795,10 @@ program
431
795
  program
432
796
  .command('auth:profiles')
433
797
  .description('List all auth profiles (configured and supported)')
434
- .action(() => {
435
- const config = loadConfig();
798
+ .action((options) => {
799
+ const botOption = getBotFromArgs();
800
+ const { configPath } = resolveConfigSync(botOption);
801
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
436
802
  const providers = config.models?.providers || {};
437
803
  const profiles = config.auth?.profiles || {};
438
804
 
@@ -467,7 +833,9 @@ program
467
833
  .requiredOption('-p, --provider <provider>', 'Auth provider')
468
834
  .requiredOption('-m, --mode <mode>', 'Auth mode (api_key, bearer)')
469
835
  .action((options) => {
470
- const config = loadConfig();
836
+ const botOption = getBotFromArgs();
837
+ const { bot, configPath } = resolveConfigSync(botOption);
838
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
471
839
  config.auth = config.auth || { profiles: {} };
472
840
 
473
841
  config.auth.profiles[options.name] = {
@@ -476,8 +844,8 @@ program
476
844
  };
477
845
 
478
846
  config.meta.lastTouchedAt = new Date().toISOString();
479
- saveConfig(config);
480
- console.log(`Auth profile "${options.name}" added.`);
847
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
848
+ console.log(`Auth profile "${options.name}" added to ${bot.name}.`);
481
849
  });
482
850
 
483
851
  // ============ Import/Export ============
@@ -487,6 +855,8 @@ program
487
855
  .description('Import configuration from a JSON file')
488
856
  .requiredOption('-f, --file <file>', 'Path to JSON file')
489
857
  .action((options) => {
858
+ const botOption = getBotFromArgs();
859
+ const { bot, configPath } = resolveConfigSync(botOption);
490
860
  if (!fs.existsSync(options.file)) {
491
861
  console.error(`File not found: ${options.file}`);
492
862
  return;
@@ -500,44 +870,22 @@ program
500
870
  lastTouchedAt: new Date().toISOString()
501
871
  };
502
872
 
503
- saveConfig(config);
504
- console.log(`Configuration imported from ${options.file}`);
873
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
874
+ console.log(`Configuration imported from ${options.file} to ${bot.name}.`);
505
875
  });
506
876
 
507
877
  program
508
878
  .command('export')
509
879
  .description('Export configuration to stdout')
510
- .action(() => {
511
- const config = loadConfig();
880
+ .action((options) => {
881
+ const botOption = getBotFromArgs();
882
+ const { configPath } = resolveConfigSync(botOption);
883
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
512
884
  console.log(JSON.stringify(config, null, 2));
513
885
  });
514
886
 
515
887
  // ============ Helpers ============
516
888
 
517
- function loadConfig() {
518
- try {
519
- if (fs.existsSync(OPENCLAW_CONFIG_PATH)) {
520
- const content = fs.readFileSync(OPENCLAW_CONFIG_PATH, 'utf8');
521
- return JSON.parse(content);
522
- }
523
- return {};
524
- } catch (error) {
525
- console.error(`Error loading configuration: ${error.message}`);
526
- return {};
527
- }
528
- }
529
-
530
- function saveConfig(config) {
531
- try {
532
- const dir = path.dirname(OPENCLAW_CONFIG_PATH);
533
- fs.ensureDirSync(dir);
534
- fs.writeFileSync(OPENCLAW_CONFIG_PATH, JSON.stringify(config, null, 2));
535
- } catch (error) {
536
- console.error(`Error saving configuration: ${error.message}`);
537
- process.exit(1);
538
- }
539
- }
540
-
541
889
  function generateToken() {
542
890
  const chars = 'abcdef0123456789';
543
891
  let token = '';
@@ -0,0 +1,86 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 380">
2
+ <defs>
3
+ <marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
4
+ <polygon points="0 0, 10 3.5, 0 7" fill="#333"/>
5
+ </marker>
6
+ <filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
7
+ <feDropShadow dx="2" dy="2" stdDeviation="3" flood-opacity="0.2"/>
8
+ </filter>
9
+ </defs>
10
+
11
+ <!-- Background -->
12
+ <rect width="800" height="380" fill="#fafafa"/>
13
+
14
+ <!-- Title -->
15
+ <text x="400" y="30" text-anchor="middle" font-size="20" font-weight="bold" fill="#333">Setup Models for OpenClaw with clawd-models</text>
16
+
17
+ <!-- Step 1: Install OpenClaw -->
18
+ <rect x="20" y="55" width="150" height="90" rx="8" fill="#e3f2fd" stroke="#1976d2" stroke-width="2" filter="url(#shadow)"/>
19
+ <text x="95" y="80" text-anchor="middle" font-size="13" font-weight="bold" fill="#1976d2">1. Install &amp;</text>
20
+ <text x="95" y="98" text-anchor="middle" font-size="13" font-weight="bold" fill="#1976d2">Init OpenClaw</text>
21
+ <text x="95" y="118" text-anchor="middle" font-size="9" fill="#555">npm i -g openclaw@latest</text>
22
+ <text x="95" y="128" text-anchor="middle" font-size="9" fill="#555">openclaw setup</text>
23
+
24
+ <!-- Arrow 1 -->
25
+ <path d="M175 90 L210 90" stroke="#333" stroke-width="2" marker-end="url(#arrowhead)"/>
26
+
27
+ <!-- Step 2: Install clawd-models -->
28
+ <rect x="220" y="55" width="150" height="70" rx="8" fill="#e8f5e9" stroke="#388e3c" stroke-width="2" filter="url(#shadow)"/>
29
+ <text x="295" y="80" text-anchor="middle" font-size="13" font-weight="bold" fill="#388e3c">2. Install</text>
30
+ <text x="295" y="98" text-anchor="middle" font-size="13" font-weight="bold" fill="#388e3c">clawd-models</text>
31
+ <text x="295" y="118" text-anchor="middle" font-size="9" fill="#555">npm i -g clawd-models</text>
32
+
33
+ <!-- Arrow 2 -->
34
+ <path d="M375 90 L410 90" stroke="#333" stroke-width="2" marker-end="url(#arrowhead)"/>
35
+
36
+ <!-- Step 3: Add Providers -->
37
+ <rect x="420" y="55" width="150" height="70" rx="8" fill="#fff3e0" stroke="#f57c00" stroke-width="2" filter="url(#shadow)"/>
38
+ <text x="495" y="80" text-anchor="middle" font-size="13" font-weight="bold" fill="#f57c00">3. Add Providers</text>
39
+ <text x="495" y="100" text-anchor="middle" font-size="10" fill="#555">providers:add</text>
40
+ <text x="495" y="115" text-anchor="middle" font-size="9" fill="#555">-n &lt;name&gt; -u &lt;url&gt;</text>
41
+
42
+ <!-- Arrow 3 -->
43
+ <path d="M575 90 L610 90" stroke="#333" stroke-width="2" marker-end="url(#arrowhead)"/>
44
+
45
+ <!-- Step 4: Add Models -->
46
+ <rect x="620" y="55" width="150" height="70" rx="8" fill="#fff3e0" stroke="#f57c00" stroke-width="2" filter="url(#shadow)"/>
47
+ <text x="695" y="80" text-anchor="middle" font-size="13" font-weight="bold" fill="#f57c00">4. Add Models</text>
48
+ <text x="695" y="100" text-anchor="middle" font-size="10" fill="#555">models:add</text>
49
+ <text x="695" y="115" text-anchor="middle" font-size="9" fill="#555">-p &lt;provider&gt; -i &lt;model-id&gt;</text>
50
+
51
+ <!-- Row 2: Test & loop -->
52
+ <rect x="100" y="180" width="160" height="60" rx="8" fill="#e8f5e9" stroke="#388e3c" stroke-width="2" filter="url(#shadow)"/>
53
+ <text x="180" y="205" text-anchor="middle" font-size="13" font-weight="bold" fill="#388e3c">5. Test Models</text>
54
+ <text x="180" y="225" text-anchor="middle" font-size="9" fill="#555">models:test [--bot &lt;name&gt;]</text>
55
+
56
+ <!-- Arrow from Add Models to Test -->
57
+ <path d="M695 125 L695 160 L210 160 L210 180" stroke="#333" stroke-width="1.5" fill="none" marker-end="url(#arrowhead)"/>
58
+
59
+ <!-- Alternative: Direct OpenClaw Commands -->
60
+ <rect x="550" y="180" width="220" height="90" rx="8" fill="#fafafa" stroke="#999" stroke-width="1" stroke-dasharray="5,5"/>
61
+ <text x="660" y="200" text-anchor="middle" font-size="11" font-weight="bold" fill="#333">Native Commands</text>
62
+ <text x="660" y="212" text-anchor="middle" font-size="9" fill="#555">openclaw models list</text>
63
+ <text x="660" y="224" text-anchor="middle" font-size="9" fill="#555">openclaw models set &lt;model-id&gt;</text>
64
+ <text x="660" y="236" text-anchor="middle" font-size="9" fill="#555">openclaw agent --agent main -m "hi, there"</text>
65
+
66
+ <!-- Legend -->
67
+ <rect x="20" y="280" width="100" height="80" rx="6" fill="#fff" stroke="#ccc" stroke-width="1"/>
68
+ <text x="70" y="298" text-anchor="middle" font-size="10" font-weight="bold" fill="#333">Legend</text>
69
+ <circle cx="35" cy="320" r="5" fill="#1976d2"/>
70
+ <text x="48" y="324" font-size="9" fill="#555" text-anchor="start">OpenClaw</text>
71
+ <circle cx="35" cy="338" r="5" fill="#f57c00"/>
72
+ <text x="48" y="342" font-size="9" fill="#555" text-anchor="start">Config</text>
73
+ <circle cx="35" cy="354" r="5" fill="#388e3c"/>
74
+ <text x="48" y="358" font-size="9" fill="#555" text-anchor="start">Test</text>
75
+
76
+ <!-- Default Bot Priority -->
77
+ <rect x="350" y="190" width="140" height="50" rx="6" fill="#f5f5f5" stroke="#ddd" stroke-width="1"/>
78
+ <text x="420" y="210" text-anchor="middle" font-size="10" font-weight="bold" fill="#333">Default Bot</text>
79
+ <text x="420" y="228" text-anchor="middle" font-size="9" fill="#555">openclaw → clawdbot → moltbot</text>
80
+
81
+ <!-- Dashed line from Step 5 to Default Bot -->
82
+ <path d="M260 210 L350 210" stroke="#777" stroke-width="1" stroke-dasharray="4,4" fill="none"/>
83
+
84
+ <!-- Dashed line from Default Bot to Step 3 -->
85
+ <path d="M440 190 L440 150 L495 150 L495 125" stroke="#777" stroke-width="1" stroke-dasharray="4,4" fill="none" marker-end="url(#arrowhead)"/>
86
+ </svg>
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "clawd-models",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "CLI tool to manage OpenClaw model configurations",
5
5
  "main": "bin/clawd-models.js",
6
6
  "bin": {
7
- "clawd-models": "./bin/clawd-models.js"
7
+ "clawd-models": "bin/clawd-models.js"
8
8
  },
9
9
  "scripts": {
10
10
  "test": "echo \"Error: no test specified\" && exit 1"