clawzempic 1.0.5 → 1.0.7
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/bin/clawzempic.js +506 -160
- package/package.json +1 -1
package/bin/clawzempic.js
CHANGED
|
@@ -12,6 +12,11 @@
|
|
|
12
12
|
* npx clawzempic test Verify your connection works
|
|
13
13
|
* npx clawzempic status Check usage and savings
|
|
14
14
|
* npx clawzempic doctor Diagnose common issues
|
|
15
|
+
*
|
|
16
|
+
* Flags:
|
|
17
|
+
* --key <key> Provide API key (skip interactive prompt)
|
|
18
|
+
* --yes, -y Auto-confirm all prompts (non-interactive)
|
|
19
|
+
* --no-test Skip connection test after init
|
|
15
20
|
*/
|
|
16
21
|
|
|
17
22
|
import { createInterface } from 'node:readline/promises';
|
|
@@ -22,7 +27,8 @@ import { join, basename } from 'node:path';
|
|
|
22
27
|
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
23
28
|
|
|
24
29
|
const API_BASE = 'https://api.clawzempic.ai';
|
|
25
|
-
const VERSION = '1.0.
|
|
30
|
+
const VERSION = '1.0.7';
|
|
31
|
+
const OPENCLAW_DIR_NAME = '.openclaw';
|
|
26
32
|
|
|
27
33
|
// ── ANSI Colors (no dependencies) ─────────────────────────────────────────────
|
|
28
34
|
|
|
@@ -191,6 +197,8 @@ async function ask(question, opts = {}) {
|
|
|
191
197
|
}
|
|
192
198
|
|
|
193
199
|
async function confirm(question, defaultYes = true) {
|
|
200
|
+
// --yes flag auto-confirms everything
|
|
201
|
+
if (globalArgs?.yes) return true;
|
|
194
202
|
const hint = defaultYes ? 'Y/n' : 'y/N';
|
|
195
203
|
const answer = await ask(`${question} ${c.dim}(${hint})${c.reset}`, { required: false });
|
|
196
204
|
if (!answer) return defaultYes;
|
|
@@ -280,7 +288,7 @@ const FRAMEWORKS = [
|
|
|
280
288
|
return false;
|
|
281
289
|
},
|
|
282
290
|
envFile: '.env',
|
|
283
|
-
envVars: {
|
|
291
|
+
envVars: { LLM_API_KEY: null, LLM_BASE_URL: API_BASE },
|
|
284
292
|
},
|
|
285
293
|
{
|
|
286
294
|
id: 'nanobot',
|
|
@@ -296,11 +304,11 @@ const FRAMEWORKS = [
|
|
|
296
304
|
return false;
|
|
297
305
|
},
|
|
298
306
|
envFile: '.env',
|
|
299
|
-
envVars: {
|
|
307
|
+
envVars: { LLM_API_KEY: null, LLM_BASE_URL: API_BASE },
|
|
300
308
|
},
|
|
301
309
|
{
|
|
302
310
|
id: 'python',
|
|
303
|
-
name: 'Python (Anthropic SDK)',
|
|
311
|
+
name: 'Python (Anthropic/OpenRouter SDK)',
|
|
304
312
|
detect: (dir) => {
|
|
305
313
|
for (const f of ['requirements.txt', 'pyproject.toml', 'Pipfile']) {
|
|
306
314
|
if (existsSync(join(dir, f))) {
|
|
@@ -313,7 +321,7 @@ const FRAMEWORKS = [
|
|
|
313
321
|
return false;
|
|
314
322
|
},
|
|
315
323
|
envFile: '.env',
|
|
316
|
-
envVars: {
|
|
324
|
+
envVars: { LLM_API_KEY: null, LLM_BASE_URL: API_BASE },
|
|
317
325
|
},
|
|
318
326
|
{
|
|
319
327
|
id: 'langchain',
|
|
@@ -330,11 +338,11 @@ const FRAMEWORKS = [
|
|
|
330
338
|
return false;
|
|
331
339
|
},
|
|
332
340
|
envFile: '.env',
|
|
333
|
-
envVars: {
|
|
341
|
+
envVars: { LLM_API_KEY: null, LLM_BASE_URL: API_BASE },
|
|
334
342
|
},
|
|
335
343
|
{
|
|
336
344
|
id: 'node',
|
|
337
|
-
name: 'Node.js (Anthropic SDK)',
|
|
345
|
+
name: 'Node.js (Anthropic/OpenRouter SDK)',
|
|
338
346
|
detect: (dir) => {
|
|
339
347
|
if (existsSync(join(dir, 'package.json'))) {
|
|
340
348
|
try {
|
|
@@ -345,7 +353,7 @@ const FRAMEWORKS = [
|
|
|
345
353
|
return false;
|
|
346
354
|
},
|
|
347
355
|
envFile: '.env',
|
|
348
|
-
envVars: {
|
|
356
|
+
envVars: { LLM_API_KEY: null, LLM_BASE_URL: API_BASE },
|
|
349
357
|
},
|
|
350
358
|
];
|
|
351
359
|
|
|
@@ -357,17 +365,26 @@ function detectFramework(dir) {
|
|
|
357
365
|
}
|
|
358
366
|
|
|
359
367
|
function detectExistingConfig(dir) {
|
|
360
|
-
// Check
|
|
368
|
+
// Check OpenClaw auth-profiles.json first
|
|
369
|
+
const openclawDir = findOpenClawDir(dir);
|
|
370
|
+
if (openclawDir) {
|
|
371
|
+
const key = findKeyInAuthProfiles(openclawDir);
|
|
372
|
+
if (key) {
|
|
373
|
+
return { configured: true, envPath: join(openclawDir, 'auth-profiles.json'), isOpenClaw: true };
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Check .env file
|
|
361
378
|
const envPath = join(dir, '.env');
|
|
362
379
|
if (existsSync(envPath)) {
|
|
363
380
|
try {
|
|
364
381
|
const content = readFileSync(envPath, 'utf8');
|
|
365
382
|
if (content.includes('clawzempic.ai') || content.includes('sk-clwz-') || content.includes('CLAWZEMPIC_API_KEY')) {
|
|
366
|
-
return { configured: true, envPath };
|
|
383
|
+
return { configured: true, envPath, isOpenClaw: false };
|
|
367
384
|
}
|
|
368
385
|
} catch {}
|
|
369
386
|
}
|
|
370
|
-
return { configured: false, envPath };
|
|
387
|
+
return { configured: false, envPath, isOpenClaw: false };
|
|
371
388
|
}
|
|
372
389
|
|
|
373
390
|
// ── Config Patching ───────────────────────────────────────────────────────────
|
|
@@ -412,6 +429,175 @@ function patchEnvFile(envPath, vars) {
|
|
|
412
429
|
return changes;
|
|
413
430
|
}
|
|
414
431
|
|
|
432
|
+
// ── OpenClaw v2026.2+ Helpers ─────────────────────────────────────────────────
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Find the .openclaw directory. Checks cwd first, then home dir.
|
|
436
|
+
*/
|
|
437
|
+
function findOpenClawDir(dir) {
|
|
438
|
+
const local = join(dir, OPENCLAW_DIR_NAME);
|
|
439
|
+
if (existsSync(local)) return local;
|
|
440
|
+
const home = join(process.env.HOME || process.env.USERPROFILE || '', OPENCLAW_DIR_NAME);
|
|
441
|
+
if (existsSync(home)) return home;
|
|
442
|
+
return null;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Check if this is OpenClaw v2026.2+ (has auth-profiles.json).
|
|
447
|
+
*/
|
|
448
|
+
function isModernOpenClaw(openclawDir) {
|
|
449
|
+
return existsSync(join(openclawDir, 'auth-profiles.json'));
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Read and patch auth-profiles.json to add/update a clawzempic provider profile.
|
|
454
|
+
*/
|
|
455
|
+
function patchAuthProfiles(openclawDir, apiKey) {
|
|
456
|
+
const profilesPath = join(openclawDir, 'auth-profiles.json');
|
|
457
|
+
let profiles = { version: 1, profiles: {} };
|
|
458
|
+
|
|
459
|
+
if (existsSync(profilesPath)) {
|
|
460
|
+
// Back up
|
|
461
|
+
const backupPath = profilesPath + '.bak.clawzempic';
|
|
462
|
+
if (!existsSync(backupPath)) {
|
|
463
|
+
copyFileSync(profilesPath, backupPath);
|
|
464
|
+
bullet(info, `Backed up ${c.dim}auth-profiles.json${c.reset} ${arrow} ${c.dim}auth-profiles.json.bak.clawzempic${c.reset}`);
|
|
465
|
+
}
|
|
466
|
+
try {
|
|
467
|
+
profiles = JSON.parse(readFileSync(profilesPath, 'utf8'));
|
|
468
|
+
} catch {
|
|
469
|
+
bullet(warn, 'Could not parse auth-profiles.json, creating fresh one.');
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const hadClawzempic = !!profiles.profiles?.clawzempic;
|
|
474
|
+
|
|
475
|
+
if (!profiles.profiles) profiles.profiles = {};
|
|
476
|
+
profiles.profiles.clawzempic = {
|
|
477
|
+
type: 'api_key',
|
|
478
|
+
provider: 'anthropic',
|
|
479
|
+
key: apiKey,
|
|
480
|
+
baseUrl: API_BASE,
|
|
481
|
+
createdAt: new Date().toISOString(),
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
writeFileSync(profilesPath, JSON.stringify(profiles, null, 2) + '\n', 'utf8');
|
|
485
|
+
return { action: hadClawzempic ? 'updated' : 'added', profilesPath };
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Read and patch openclaw.json to set model to use clawzempic provider.
|
|
490
|
+
*/
|
|
491
|
+
function patchOpenClawConfig(openclawDir) {
|
|
492
|
+
const configPath = join(openclawDir, 'openclaw.json');
|
|
493
|
+
if (!existsSync(configPath)) return { patched: false, reason: 'no openclaw.json' };
|
|
494
|
+
|
|
495
|
+
let config;
|
|
496
|
+
try {
|
|
497
|
+
config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
498
|
+
} catch {
|
|
499
|
+
return { patched: false, reason: 'could not parse openclaw.json' };
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Back up
|
|
503
|
+
const backupPath = configPath + '.bak.clawzempic';
|
|
504
|
+
if (!existsSync(backupPath)) {
|
|
505
|
+
copyFileSync(configPath, backupPath);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Update model to use clawzempic provider
|
|
509
|
+
if (!config.agents) config.agents = {};
|
|
510
|
+
if (!config.agents.defaults) config.agents.defaults = {};
|
|
511
|
+
if (!config.agents.defaults.model) config.agents.defaults.model = {};
|
|
512
|
+
|
|
513
|
+
const oldModel = config.agents.defaults.model.primary || 'none';
|
|
514
|
+
config.agents.defaults.model.primary = 'clawzempic/claude-sonnet-4-5-20250929';
|
|
515
|
+
|
|
516
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
|
517
|
+
return { patched: true, oldModel, configPath };
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Check OpenClaw gateway auth status.
|
|
522
|
+
*/
|
|
523
|
+
function checkGatewayAuth(openclawDir) {
|
|
524
|
+
// Check openclaw.json for gateway.auth
|
|
525
|
+
const configPath = join(openclawDir, 'openclaw.json');
|
|
526
|
+
if (!existsSync(configPath)) return { hasAuth: false, mode: 'unknown' };
|
|
527
|
+
|
|
528
|
+
try {
|
|
529
|
+
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
530
|
+
const auth = config.gateway?.auth;
|
|
531
|
+
if (!auth || !auth.mode || auth.mode === 'none') {
|
|
532
|
+
return { hasAuth: false, mode: auth?.mode || 'none' };
|
|
533
|
+
}
|
|
534
|
+
return { hasAuth: true, mode: auth.mode };
|
|
535
|
+
} catch {
|
|
536
|
+
return { hasAuth: false, mode: 'unknown' };
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Find a Clawzempic API key from OpenClaw auth-profiles.json.
|
|
542
|
+
*/
|
|
543
|
+
function findKeyInAuthProfiles(openclawDir) {
|
|
544
|
+
if (!openclawDir) return null;
|
|
545
|
+
const profilesPath = join(openclawDir, 'auth-profiles.json');
|
|
546
|
+
if (!existsSync(profilesPath)) return null;
|
|
547
|
+
|
|
548
|
+
try {
|
|
549
|
+
const profiles = JSON.parse(readFileSync(profilesPath, 'utf8'));
|
|
550
|
+
// Check clawzempic profile first
|
|
551
|
+
if (profiles.profiles?.clawzempic?.key) {
|
|
552
|
+
return profiles.profiles.clawzempic.key;
|
|
553
|
+
}
|
|
554
|
+
// Check any profile that points to clawzempic.ai
|
|
555
|
+
for (const [, profile] of Object.entries(profiles.profiles || {})) {
|
|
556
|
+
if (profile.baseUrl?.includes('clawzempic.ai') && profile.key) {
|
|
557
|
+
return profile.key;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
} catch {}
|
|
561
|
+
return null;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Find API key from all possible locations.
|
|
566
|
+
*/
|
|
567
|
+
function findApiKey(dir) {
|
|
568
|
+
// 1. Environment variables (check new names first, fall back to old)
|
|
569
|
+
const envKey = process.env.CLAWZEMPIC_API_KEY || process.env.LLM_API_KEY || process.env.ANTHROPIC_API_KEY;
|
|
570
|
+
if (envKey && (envKey.startsWith('sk-clwz-') || envKey.startsWith('sr_'))) {
|
|
571
|
+
return { key: envKey, source: 'environment variable' };
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// 2. OpenClaw auth-profiles.json
|
|
575
|
+
const openclawDir = findOpenClawDir(dir);
|
|
576
|
+
if (openclawDir) {
|
|
577
|
+
const authKey = findKeyInAuthProfiles(openclawDir);
|
|
578
|
+
if (authKey && (authKey.startsWith('sk-clwz-') || authKey.startsWith('sr_'))) {
|
|
579
|
+
return { key: authKey, source: 'OpenClaw auth-profiles.json' };
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// 3. .env file in cwd
|
|
584
|
+
const envPath = join(dir, '.env');
|
|
585
|
+
if (existsSync(envPath)) {
|
|
586
|
+
try {
|
|
587
|
+
const content = readFileSync(envPath, 'utf8');
|
|
588
|
+
const match = content.match(/^(?:CLAWZEMPIC_API_KEY|LLM_API_KEY|ANTHROPIC_API_KEY)\s*=\s*(.+)$/m);
|
|
589
|
+
if (match) {
|
|
590
|
+
const val = match[1].trim();
|
|
591
|
+
if (val.startsWith('sk-clwz-') || val.startsWith('sr_')) {
|
|
592
|
+
return { key: val, source: '.env file' };
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
} catch {}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
return null;
|
|
599
|
+
}
|
|
600
|
+
|
|
415
601
|
// ── Commands ──────────────────────────────────────────────────────────────────
|
|
416
602
|
|
|
417
603
|
async function cmdInit(args) {
|
|
@@ -536,86 +722,160 @@ async function cmdInit(args) {
|
|
|
536
722
|
|
|
537
723
|
heading('Configuration');
|
|
538
724
|
|
|
539
|
-
|
|
540
|
-
const
|
|
541
|
-
|
|
542
|
-
// Build env vars: proxy key + base URL. Keep provider key separate.
|
|
543
|
-
const envVars = {
|
|
544
|
-
CLAWZEMPIC_API_KEY: clawzempicKey,
|
|
545
|
-
ANTHROPIC_BASE_URL: API_BASE,
|
|
546
|
-
};
|
|
725
|
+
// OpenClaw v2026.2+ gets special treatment: patch auth-profiles.json + openclaw.json
|
|
726
|
+
const openclawDir = findOpenClawDir(dir);
|
|
727
|
+
const useOpenClawPath = framework?.id === 'openclaw' && openclawDir && isModernOpenClaw(openclawDir);
|
|
547
728
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
const display = key.includes('KEY') ? value.substring(0, 12) + '...' : value;
|
|
552
|
-
console.log(` ${c.green}+${c.reset} ${key}=${display}`);
|
|
553
|
-
}
|
|
554
|
-
blank();
|
|
729
|
+
if (useOpenClawPath) {
|
|
730
|
+
bullet(ok, `OpenClaw v2026.2+ detected at ${c.dim}${openclawDir}${c.reset}`);
|
|
731
|
+
blank();
|
|
555
732
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
733
|
+
// Show what we'll do
|
|
734
|
+
bullet(info, `Will update ${c.bold}auth-profiles.json${c.reset}:`);
|
|
735
|
+
console.log(` ${c.green}+${c.reset} Add "clawzempic" provider (baseUrl: ${API_BASE})`);
|
|
736
|
+
console.log(` ${c.green}+${c.reset} API key: ${clawzempicKey.substring(0, 12)}...`);
|
|
737
|
+
blank();
|
|
738
|
+
bullet(info, `Will update ${c.bold}openclaw.json${c.reset}:`);
|
|
739
|
+
console.log(` ${c.green}+${c.reset} Set default model to clawzempic/claude-sonnet-4-5-20250929`);
|
|
559
740
|
blank();
|
|
741
|
+
|
|
742
|
+
const applyChanges = await confirm('Apply these changes?');
|
|
743
|
+
if (!applyChanges) {
|
|
744
|
+
bullet(info, 'No changes made. You can configure manually:');
|
|
745
|
+
blank();
|
|
746
|
+
bullet(info, `Add to ${c.bold}auth-profiles.json${c.reset} → profiles.clawzempic:`);
|
|
747
|
+
console.log(` { "type": "api_key", "provider": "anthropic", "key": "${clawzempicKey}", "baseUrl": "${API_BASE}" }`);
|
|
748
|
+
blank();
|
|
749
|
+
bullet(info, `Set in ${c.bold}openclaw.json${c.reset} → agents.defaults.model.primary:`);
|
|
750
|
+
console.log(` "clawzempic/claude-sonnet-4-5-20250929"`);
|
|
751
|
+
blank();
|
|
752
|
+
return cleanup();
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// Patch auth profiles
|
|
756
|
+
const profileResult = patchAuthProfiles(openclawDir, clawzempicKey);
|
|
757
|
+
bullet(ok, `${profileResult.action === 'updated' ? 'Updated' : 'Added'} clawzempic profile in ${c.bold}auth-profiles.json${c.reset}`);
|
|
758
|
+
|
|
759
|
+
// Patch openclaw.json model config
|
|
760
|
+
const configResult = patchOpenClawConfig(openclawDir);
|
|
761
|
+
if (configResult.patched) {
|
|
762
|
+
bullet(ok, `Set default model to ${c.bold}clawzempic/claude-sonnet-4-5-20250929${c.reset}`);
|
|
763
|
+
if (configResult.oldModel !== 'none') {
|
|
764
|
+
bullet(info, `Previous model: ${c.dim}${configResult.oldModel}${c.reset}`);
|
|
765
|
+
}
|
|
766
|
+
} else {
|
|
767
|
+
bullet(warn, `Could not update openclaw.json: ${configResult.reason}`);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// Check gateway auth
|
|
771
|
+
const gwAuth = checkGatewayAuth(openclawDir);
|
|
772
|
+
if (!gwAuth.hasAuth) {
|
|
773
|
+
blank();
|
|
774
|
+
bullet(warn, `Gateway auth is ${c.bold}${gwAuth.mode}${c.reset} — v2026.2+ requires a password or token.`);
|
|
775
|
+
bullet(info, `Set gateway.auth in openclaw.json to "password" or "token" mode.`);
|
|
776
|
+
bullet(info, `Example: ${c.dim}{ "gateway": { "auth": { "mode": "password" } } }${c.reset}`);
|
|
777
|
+
} else {
|
|
778
|
+
bullet(ok, `Gateway auth: ${c.bold}${gwAuth.mode}${c.reset}`);
|
|
779
|
+
}
|
|
780
|
+
} else {
|
|
781
|
+
// Generic .env path (non-OpenClaw frameworks)
|
|
782
|
+
const envFile = framework?.envFile || '.env';
|
|
783
|
+
const envPath = join(dir, envFile);
|
|
784
|
+
|
|
785
|
+
// Build env vars: proxy key + base URL. Keep provider key separate.
|
|
786
|
+
const envVars = {
|
|
787
|
+
CLAWZEMPIC_API_KEY: clawzempicKey,
|
|
788
|
+
LLM_BASE_URL: API_BASE,
|
|
789
|
+
};
|
|
790
|
+
|
|
791
|
+
// Show what we'll do
|
|
792
|
+
bullet(info, `Will update ${c.bold}${envFile}${c.reset}:`);
|
|
560
793
|
for (const [key, value] of Object.entries(envVars)) {
|
|
561
|
-
|
|
794
|
+
const display = key.includes('KEY') ? value.substring(0, 12) + '...' : value;
|
|
795
|
+
console.log(` ${c.green}+${c.reset} ${key}=${display}`);
|
|
562
796
|
}
|
|
563
797
|
blank();
|
|
564
|
-
return cleanup();
|
|
565
|
-
}
|
|
566
798
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
const badVal = badMatch[1].trim();
|
|
574
|
-
if (badVal.startsWith('sr_') || badVal.startsWith('sk-clwz-')) {
|
|
575
|
-
const cleaned = existing.replace(/^ANTHROPIC_API_KEY\s*=\s*(?:sr_|sk-clwz-).+\n?/m, '');
|
|
576
|
-
writeFileSync(envPath, cleaned, 'utf8');
|
|
577
|
-
bullet(warn, `Removed stale ANTHROPIC_API_KEY (had a Clawzempic key from v1.0.3 bug)`);
|
|
799
|
+
const applyChanges = await confirm('Apply these changes?');
|
|
800
|
+
if (!applyChanges) {
|
|
801
|
+
bullet(info, 'No changes made. You can set these manually:');
|
|
802
|
+
blank();
|
|
803
|
+
for (const [key, value] of Object.entries(envVars)) {
|
|
804
|
+
console.log(` ${key}=${value}`);
|
|
578
805
|
}
|
|
806
|
+
blank();
|
|
807
|
+
return cleanup();
|
|
579
808
|
}
|
|
580
|
-
}
|
|
581
809
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
810
|
+
// Clean up legacy: remove Clawzempic keys from ANTHROPIC_API_KEY (v1.0.3 bug + old naming)
|
|
811
|
+
if (existsSync(envPath)) {
|
|
812
|
+
const existing = readFileSync(envPath, 'utf8');
|
|
813
|
+
const badMatch = existing.match(/^ANTHROPIC_API_KEY\s*=\s*(.+)$/m);
|
|
814
|
+
if (badMatch) {
|
|
815
|
+
const badVal = badMatch[1].trim();
|
|
816
|
+
if (badVal.startsWith('sr_') || badVal.startsWith('sk-clwz-')) {
|
|
817
|
+
const cleaned = existing.replace(/^ANTHROPIC_API_KEY\s*=\s*(?:sr_|sk-clwz-).+\n?/m, '');
|
|
818
|
+
writeFileSync(envPath, cleaned, 'utf8');
|
|
819
|
+
bullet(warn, `Removed stale ANTHROPIC_API_KEY (had a Clawzempic key — use CLAWZEMPIC_API_KEY instead)`);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
// Also clean up old ANTHROPIC_BASE_URL pointing to clawzempic
|
|
823
|
+
const oldBaseMatch = existing.match(/^ANTHROPIC_BASE_URL\s*=\s*(.+)$/m);
|
|
824
|
+
if (oldBaseMatch && oldBaseMatch[1].includes('clawzempic.ai')) {
|
|
825
|
+
const cleaned2 = existing.replace(/^ANTHROPIC_BASE_URL\s*=\s*.+clawzempic\.ai.+\n?/m, '');
|
|
826
|
+
writeFileSync(envPath, cleaned2, 'utf8');
|
|
827
|
+
bullet(warn, `Migrated ANTHROPIC_BASE_URL → LLM_BASE_URL`);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// Apply
|
|
832
|
+
const changes = patchEnvFile(envPath, envVars);
|
|
833
|
+
for (const change of changes) {
|
|
834
|
+
if (change.action === 'updated') {
|
|
835
|
+
bullet(ok, `Updated ${c.bold}${change.key}${c.reset}`);
|
|
836
|
+
} else {
|
|
837
|
+
bullet(ok, `Added ${c.bold}${change.key}${c.reset}`);
|
|
838
|
+
}
|
|
589
839
|
}
|
|
590
840
|
}
|
|
591
841
|
|
|
592
842
|
// ── Test Connection ──────────────────────────────────────────────────────
|
|
593
843
|
|
|
594
844
|
blank();
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
845
|
+
let testPassed = true;
|
|
846
|
+
if (args.noTest) {
|
|
847
|
+
bullet(info, 'Skipping connection test (--no-test)');
|
|
848
|
+
} else {
|
|
849
|
+
const runTest = await confirm('Test the connection?');
|
|
850
|
+
testPassed = !runTest; // skip = assume ok
|
|
851
|
+
if (runTest) {
|
|
852
|
+
testPassed = await runConnectionTest(clawzempicKey);
|
|
853
|
+
}
|
|
599
854
|
}
|
|
600
855
|
|
|
601
856
|
// ── Done ─────────────────────────────────────────────────────────────────
|
|
602
857
|
|
|
603
858
|
blank();
|
|
604
859
|
if (testPassed) {
|
|
605
|
-
|
|
860
|
+
const doneLines = [
|
|
606
861
|
`${c.green}${c.bold}Setup complete!${c.reset}`,
|
|
607
862
|
'',
|
|
608
863
|
`${c.dim}Dashboard:${c.reset} ${API_BASE}/v1/auth/portal`,
|
|
609
864
|
`${c.dim}Insights:${c.reset} ${API_BASE}/v1/insights`,
|
|
610
865
|
`${c.dim}Docs:${c.reset} ${API_BASE}/docs`,
|
|
611
866
|
'',
|
|
612
|
-
|
|
613
|
-
|
|
867
|
+
];
|
|
868
|
+
if (useOpenClawPath) {
|
|
869
|
+
doneLines.push(`${c.yellow}Restart your gateway:${c.reset} ${c.cyan}openclaw gateway restart${c.reset}`);
|
|
870
|
+
} else {
|
|
871
|
+
doneLines.push(`${c.yellow}Restart your app to start saving.${c.reset}`);
|
|
872
|
+
}
|
|
873
|
+
box(doneLines);
|
|
614
874
|
} else {
|
|
615
875
|
box([
|
|
616
876
|
`${c.red}${c.bold}Setup incomplete${c.reset} — connection test failed.`,
|
|
617
877
|
'',
|
|
618
|
-
`${c.dim}Config was written
|
|
878
|
+
`${c.dim}Config was written, but your key didn't authenticate.${c.reset}`,
|
|
619
879
|
`${c.dim}Check your key and run:${c.reset} ${c.cyan}npx clawzempic test${c.reset}`,
|
|
620
880
|
]);
|
|
621
881
|
}
|
|
@@ -656,19 +916,14 @@ async function runConnectionTest(apiKey) {
|
|
|
656
916
|
async function cmdTest(args) {
|
|
657
917
|
banner();
|
|
658
918
|
|
|
659
|
-
// Find API key: arg > env > .env
|
|
919
|
+
// Find API key: arg > env > auth-profiles > .env
|
|
660
920
|
let apiKey = args.key;
|
|
661
921
|
|
|
662
922
|
if (!apiKey) {
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
const envPath = join(process.cwd(), '.env');
|
|
668
|
-
if (existsSync(envPath)) {
|
|
669
|
-
const content = readFileSync(envPath, 'utf8');
|
|
670
|
-
const match = content.match(/^(?:CLAWZEMPIC_API_KEY|ANTHROPIC_API_KEY)\s*=\s*(.+)$/m);
|
|
671
|
-
if (match) apiKey = match[1].trim();
|
|
923
|
+
const found = findApiKey(process.cwd());
|
|
924
|
+
if (found) {
|
|
925
|
+
apiKey = found.key;
|
|
926
|
+
bullet(info, `Found key in ${c.dim}${found.source}${c.reset}`);
|
|
672
927
|
}
|
|
673
928
|
}
|
|
674
929
|
|
|
@@ -694,15 +949,8 @@ async function cmdStatus(args) {
|
|
|
694
949
|
// Find API key
|
|
695
950
|
let apiKey = args.key;
|
|
696
951
|
if (!apiKey) {
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
if (!apiKey) {
|
|
700
|
-
const envPath = join(process.cwd(), '.env');
|
|
701
|
-
if (existsSync(envPath)) {
|
|
702
|
-
const content = readFileSync(envPath, 'utf8');
|
|
703
|
-
const match = content.match(/^(?:CLAWZEMPIC_API_KEY|ANTHROPIC_API_KEY)\s*=\s*(.+)$/m);
|
|
704
|
-
if (match) apiKey = match[1].trim();
|
|
705
|
-
}
|
|
952
|
+
const found = findApiKey(process.cwd());
|
|
953
|
+
if (found) apiKey = found.key;
|
|
706
954
|
}
|
|
707
955
|
|
|
708
956
|
if (!apiKey || (!apiKey.startsWith('sk-clwz-') && !apiKey.startsWith('sr_'))) {
|
|
@@ -766,66 +1014,152 @@ async function cmdDoctor() {
|
|
|
766
1014
|
|
|
767
1015
|
const dir = process.cwd();
|
|
768
1016
|
|
|
769
|
-
//
|
|
770
|
-
const
|
|
771
|
-
|
|
772
|
-
bullet(ok, `.env file found`);
|
|
773
|
-
} else {
|
|
774
|
-
bullet(fail, `No .env file in current directory`);
|
|
775
|
-
bullet(info, 'Run `npx clawzempic` to set up.');
|
|
776
|
-
return cleanup(1);
|
|
777
|
-
}
|
|
1017
|
+
// Check if OpenClaw v2026.2+
|
|
1018
|
+
const openclawDir = findOpenClawDir(dir);
|
|
1019
|
+
const fw = detectFramework(dir);
|
|
778
1020
|
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
if (keyMatch && (keyMatch[1].startsWith('sk-clwz-') || keyMatch[1].startsWith('sr_'))) {
|
|
783
|
-
bullet(ok, `Clawzempic API key found`);
|
|
784
|
-
} else if (keyMatch) {
|
|
785
|
-
bullet(warn, `API key found but does not look like a Clawzempic key (expected sk-clwz-... or sr_...)`);
|
|
786
|
-
bullet(info, 'You may be using a direct provider key. Run `npx clawzempic` to set up proxy routing.');
|
|
787
|
-
} else {
|
|
788
|
-
bullet(fail, 'No API key found in .env');
|
|
789
|
-
return cleanup(1);
|
|
790
|
-
}
|
|
1021
|
+
if (openclawDir && isModernOpenClaw(openclawDir)) {
|
|
1022
|
+
// ── OpenClaw v2026.2+ Doctor ──────────────────────────────────────────
|
|
1023
|
+
bullet(ok, `OpenClaw v2026.2+ detected at ${c.dim}${openclawDir}${c.reset}`);
|
|
791
1024
|
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
1025
|
+
// Check auth-profiles.json for clawzempic profile
|
|
1026
|
+
const profilesPath = join(openclawDir, 'auth-profiles.json');
|
|
1027
|
+
let profiles;
|
|
1028
|
+
try {
|
|
1029
|
+
profiles = JSON.parse(readFileSync(profilesPath, 'utf8'));
|
|
1030
|
+
} catch {
|
|
1031
|
+
bullet(fail, 'Could not parse auth-profiles.json');
|
|
1032
|
+
return cleanup(1);
|
|
1033
|
+
}
|
|
801
1034
|
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
1035
|
+
const clawzempicProfile = profiles.profiles?.clawzempic;
|
|
1036
|
+
if (clawzempicProfile) {
|
|
1037
|
+
bullet(ok, `Clawzempic provider profile found`);
|
|
1038
|
+
if (clawzempicProfile.baseUrl?.includes('clawzempic.ai')) {
|
|
1039
|
+
bullet(ok, `Base URL: ${c.dim}${clawzempicProfile.baseUrl}${c.reset}`);
|
|
1040
|
+
} else {
|
|
1041
|
+
bullet(fail, `Base URL is ${c.dim}${clawzempicProfile.baseUrl}${c.reset} — should be ${API_BASE}`);
|
|
1042
|
+
}
|
|
1043
|
+
} else {
|
|
1044
|
+
// Check if any profile points to clawzempic
|
|
1045
|
+
let foundClawzempic = false;
|
|
1046
|
+
for (const [name, profile] of Object.entries(profiles.profiles || {})) {
|
|
1047
|
+
if (profile.baseUrl?.includes('clawzempic.ai')) {
|
|
1048
|
+
bullet(ok, `Clawzempic configured in "${name}" profile (baseUrl: ${c.dim}${profile.baseUrl}${c.reset})`);
|
|
1049
|
+
foundClawzempic = true;
|
|
1050
|
+
break;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
if (!foundClawzempic) {
|
|
1054
|
+
bullet(fail, 'No Clawzempic provider in auth-profiles.json');
|
|
1055
|
+
bullet(info, 'Run `npx clawzempic` to add it.');
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
809
1058
|
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
if (existsSync(
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
1059
|
+
// Check openclaw.json model config
|
|
1060
|
+
const configPath = join(openclawDir, 'openclaw.json');
|
|
1061
|
+
if (existsSync(configPath)) {
|
|
1062
|
+
try {
|
|
1063
|
+
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
1064
|
+
const model = config.agents?.defaults?.model?.primary || 'not set';
|
|
1065
|
+
if (model.startsWith('clawzempic/')) {
|
|
1066
|
+
bullet(ok, `Default model: ${c.bold}${model}${c.reset}`);
|
|
1067
|
+
} else {
|
|
1068
|
+
bullet(info, `Default model: ${c.dim}${model}${c.reset} (not using clawzempic provider)`);
|
|
1069
|
+
}
|
|
1070
|
+
} catch {
|
|
1071
|
+
bullet(warn, 'Could not parse openclaw.json');
|
|
818
1072
|
}
|
|
819
1073
|
}
|
|
820
|
-
}
|
|
821
1074
|
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
1075
|
+
// Check gateway auth
|
|
1076
|
+
const gwAuth = checkGatewayAuth(openclawDir);
|
|
1077
|
+
if (gwAuth.hasAuth) {
|
|
1078
|
+
bullet(ok, `Gateway auth: ${c.bold}${gwAuth.mode}${c.reset}`);
|
|
1079
|
+
} else {
|
|
1080
|
+
bullet(warn, `Gateway auth is ${c.bold}${gwAuth.mode}${c.reset} — v2026.2+ requires a password or token`);
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
// Test connection
|
|
1084
|
+
const apiKeyResult = findApiKey(dir);
|
|
1085
|
+
if (apiKeyResult) {
|
|
1086
|
+
blank();
|
|
1087
|
+
const success = await runConnectionTest(apiKeyResult.key);
|
|
1088
|
+
if (success) {
|
|
1089
|
+
blank();
|
|
1090
|
+
bullet(ok, `${c.green}Everything looks good!${c.reset}`);
|
|
1091
|
+
}
|
|
1092
|
+
} else {
|
|
827
1093
|
blank();
|
|
828
|
-
bullet(
|
|
1094
|
+
bullet(warn, 'No Clawzempic API key found to test connection.');
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
} else {
|
|
1098
|
+
// ── Generic Doctor ────────────────────────────────────────────────────
|
|
1099
|
+
|
|
1100
|
+
// 1. Check .env exists
|
|
1101
|
+
const envPath = join(dir, '.env');
|
|
1102
|
+
if (existsSync(envPath)) {
|
|
1103
|
+
bullet(ok, `.env file found`);
|
|
1104
|
+
} else {
|
|
1105
|
+
bullet(fail, `No .env file in current directory`);
|
|
1106
|
+
if (openclawDir) {
|
|
1107
|
+
bullet(info, `OpenClaw detected but no auth-profiles.json — may be an older version.`);
|
|
1108
|
+
}
|
|
1109
|
+
bullet(info, 'Run `npx clawzempic` to set up.');
|
|
1110
|
+
return cleanup(1);
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
// 2. Check for Clawzempic config
|
|
1114
|
+
const content = readFileSync(envPath, 'utf8');
|
|
1115
|
+
const keyMatch = content.match(/^(?:CLAWZEMPIC_API_KEY|LLM_API_KEY|ANTHROPIC_API_KEY)\s*=\s*(.+)$/m);
|
|
1116
|
+
if (keyMatch && (keyMatch[1].startsWith('sk-clwz-') || keyMatch[1].startsWith('sr_'))) {
|
|
1117
|
+
bullet(ok, `Clawzempic API key found`);
|
|
1118
|
+
} else if (keyMatch) {
|
|
1119
|
+
bullet(warn, `API key found but does not look like a Clawzempic key (expected sk-clwz-... or sr_...)`);
|
|
1120
|
+
bullet(info, 'You may be using a direct provider key. Run `npx clawzempic` to set up proxy routing.');
|
|
1121
|
+
} else {
|
|
1122
|
+
bullet(fail, 'No API key found in .env');
|
|
1123
|
+
return cleanup(1);
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
// 3. Check base URL (check LLM_BASE_URL first, fall back to ANTHROPIC_BASE_URL)
|
|
1127
|
+
const baseUrlMatch = content.match(/^(?:LLM_BASE_URL|ANTHROPIC_BASE_URL)\s*=\s*(.+)$/m);
|
|
1128
|
+
if (baseUrlMatch && baseUrlMatch[1].includes('clawzempic.ai')) {
|
|
1129
|
+
bullet(ok, `Base URL pointing to Clawzempic`);
|
|
1130
|
+
} else if (baseUrlMatch) {
|
|
1131
|
+
bullet(warn, `Base URL set to ${baseUrlMatch[1]} — not pointing to Clawzempic`);
|
|
1132
|
+
} else {
|
|
1133
|
+
bullet(fail, `LLM_BASE_URL not set — requests go directly to your provider, bypassing Clawzempic`);
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
// 4. Check framework
|
|
1137
|
+
if (fw) {
|
|
1138
|
+
bullet(ok, `Framework detected: ${fw.name}`);
|
|
1139
|
+
} else {
|
|
1140
|
+
bullet(info, 'No specific framework detected (generic setup)');
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
// 5. Check docker-compose for OpenClaw
|
|
1144
|
+
for (const f of ['docker-compose.yml', 'docker-compose.yaml']) {
|
|
1145
|
+
if (existsSync(join(dir, f))) {
|
|
1146
|
+
const dcContent = readFileSync(join(dir, f), 'utf8');
|
|
1147
|
+
if (dcContent.includes('env_file') && dcContent.includes('.env')) {
|
|
1148
|
+
bullet(ok, `docker-compose uses .env file`);
|
|
1149
|
+
} else if (dcContent.includes('ANTHROPIC_API_KEY')) {
|
|
1150
|
+
bullet(warn, `docker-compose has hardcoded ANTHROPIC_API_KEY — should use env_file instead`);
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
// 6. Test connection
|
|
1156
|
+
blank();
|
|
1157
|
+
if (keyMatch && (keyMatch[1].startsWith('sk-clwz-') || keyMatch[1].startsWith('sr_'))) {
|
|
1158
|
+
const success = await runConnectionTest(keyMatch[1]);
|
|
1159
|
+
if (success) {
|
|
1160
|
+
blank();
|
|
1161
|
+
bullet(ok, `${c.green}Everything looks good!${c.reset}`);
|
|
1162
|
+
}
|
|
829
1163
|
}
|
|
830
1164
|
}
|
|
831
1165
|
|
|
@@ -874,13 +1208,15 @@ function cmdHelp() {
|
|
|
874
1208
|
// ── Arg Parsing ───────────────────────────────────────────────────────────────
|
|
875
1209
|
|
|
876
1210
|
function parseArgs(argv) {
|
|
877
|
-
const args = { command: null, key: null, help: false, version: false };
|
|
1211
|
+
const args = { command: null, key: null, help: false, version: false, yes: false, noTest: false };
|
|
878
1212
|
const positional = [];
|
|
879
1213
|
|
|
880
1214
|
for (let i = 2; i < argv.length; i++) {
|
|
881
1215
|
const arg = argv[i];
|
|
882
1216
|
if (arg === '--help' || arg === '-h') args.help = true;
|
|
883
1217
|
else if (arg === '--version' || arg === '-v') args.version = true;
|
|
1218
|
+
else if (arg === '--yes' || arg === '-y') args.yes = true;
|
|
1219
|
+
else if (arg === '--no-test') args.noTest = true;
|
|
884
1220
|
else if (arg === '--key' && argv[i + 1]) { args.key = argv[++i]; }
|
|
885
1221
|
else if (!arg.startsWith('-')) positional.push(arg);
|
|
886
1222
|
}
|
|
@@ -907,38 +1243,48 @@ process.on('SIGINT', () => {
|
|
|
907
1243
|
|
|
908
1244
|
// ── Main ──────────────────────────────────────────────────────────────────────
|
|
909
1245
|
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
if (args.version) {
|
|
913
|
-
console.log(`clawzempic ${VERSION}`);
|
|
914
|
-
process.exit(0);
|
|
915
|
-
}
|
|
1246
|
+
let globalArgs = null;
|
|
916
1247
|
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
}
|
|
1248
|
+
async function main() {
|
|
1249
|
+
const args = parseArgs(process.argv);
|
|
1250
|
+
globalArgs = args;
|
|
921
1251
|
|
|
922
|
-
|
|
1252
|
+
if (args.version) {
|
|
1253
|
+
console.log(`clawzempic ${VERSION}`);
|
|
1254
|
+
process.exit(0);
|
|
1255
|
+
}
|
|
923
1256
|
|
|
924
|
-
|
|
925
|
-
case 'init':
|
|
926
|
-
await cmdInit(args);
|
|
927
|
-
break;
|
|
928
|
-
case 'test':
|
|
929
|
-
await cmdTest(args);
|
|
930
|
-
break;
|
|
931
|
-
case 'status':
|
|
932
|
-
await cmdStatus(args);
|
|
933
|
-
break;
|
|
934
|
-
case 'doctor':
|
|
935
|
-
await cmdDoctor();
|
|
936
|
-
break;
|
|
937
|
-
case 'help':
|
|
1257
|
+
if (args.help && !args.command) {
|
|
938
1258
|
cmdHelp();
|
|
939
1259
|
process.exit(0);
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
const command = args.command || 'init';
|
|
1263
|
+
|
|
1264
|
+
switch (command) {
|
|
1265
|
+
case 'init':
|
|
1266
|
+
await cmdInit(args);
|
|
1267
|
+
break;
|
|
1268
|
+
case 'test':
|
|
1269
|
+
await cmdTest(args);
|
|
1270
|
+
break;
|
|
1271
|
+
case 'status':
|
|
1272
|
+
await cmdStatus(args);
|
|
1273
|
+
break;
|
|
1274
|
+
case 'doctor':
|
|
1275
|
+
await cmdDoctor();
|
|
1276
|
+
break;
|
|
1277
|
+
case 'help':
|
|
1278
|
+
cmdHelp();
|
|
1279
|
+
process.exit(0);
|
|
1280
|
+
break;
|
|
1281
|
+
default:
|
|
1282
|
+
console.error(`Unknown command: ${command}. Run 'npx clawzempic --help' for usage.`);
|
|
1283
|
+
process.exit(1);
|
|
1284
|
+
}
|
|
944
1285
|
}
|
|
1286
|
+
|
|
1287
|
+
main().catch(err => {
|
|
1288
|
+
console.error(`\n ${c.red}Fatal:${c.reset} ${err.message}\n`);
|
|
1289
|
+
process.exit(1);
|
|
1290
|
+
});
|
package/package.json
CHANGED