claudish 1.8.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -186,7 +186,14 @@ async function runClaudeWithProxy(config, proxyUrl) {
186
186
  if (config.jsonOutput) {
187
187
  claudeArgs.push("--output-format", "json");
188
188
  }
189
- claudeArgs.push(...config.claudeArgs);
189
+ if (config.agent && config.claudeArgs.length > 0) {
190
+ const modifiedArgs = [...config.claudeArgs];
191
+ const agentId = config.agent.startsWith("@agent-") ? config.agent : `@agent-${config.agent}`;
192
+ modifiedArgs[0] = `Use the ${agentId} agent to: ${modifiedArgs[0]}`;
193
+ claudeArgs.push(...modifiedArgs);
194
+ } else {
195
+ claudeArgs.push(...config.claudeArgs);
196
+ }
190
197
  }
191
198
  const env = {
192
199
  ...process.env,
@@ -331,14 +338,14 @@ function getAvailableModels() {
331
338
  }
332
339
 
333
340
  // src/cli.ts
334
- import { readFileSync as readFileSync2 } from "node:fs";
341
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync3, existsSync as existsSync2, mkdirSync, copyFileSync } from "node:fs";
335
342
  import { fileURLToPath as fileURLToPath2 } from "node:url";
336
343
  import { dirname as dirname2, join as join3 } from "node:path";
337
344
  var __filename3 = fileURLToPath2(import.meta.url);
338
345
  var __dirname3 = dirname2(__filename3);
339
346
  var packageJson = JSON.parse(readFileSync2(join3(__dirname3, "../package.json"), "utf-8"));
340
347
  var VERSION = packageJson.version;
341
- function parseArgs(args) {
348
+ async function parseArgs(args) {
342
349
  const config = {
343
350
  model: undefined,
344
351
  autoApprove: true,
@@ -429,8 +436,16 @@ function parseArgs(args) {
429
436
  } else if (arg === "--help" || arg === "-h") {
430
437
  printHelp();
431
438
  process.exit(0);
439
+ } else if (arg === "--help-ai") {
440
+ printAIAgentGuide();
441
+ process.exit(0);
442
+ } else if (arg === "--init") {
443
+ await initializeClaudishSkill();
444
+ process.exit(0);
432
445
  } else if (arg === "--list-models") {
433
446
  const hasJsonFlag = args.includes("--json");
447
+ const forceUpdate = args.includes("--force-update");
448
+ await checkAndUpdateModelsCache(forceUpdate);
434
449
  if (hasJsonFlag) {
435
450
  printAvailableModelsJSON();
436
451
  } else {
@@ -483,6 +498,164 @@ function parseArgs(args) {
483
498
  }
484
499
  return config;
485
500
  }
501
+ var CACHE_MAX_AGE_DAYS = 2;
502
+ var MODELS_JSON_PATH = join3(__dirname3, "../recommended-models.json");
503
+ function isCacheStale() {
504
+ if (!existsSync2(MODELS_JSON_PATH)) {
505
+ return true;
506
+ }
507
+ try {
508
+ const jsonContent = readFileSync2(MODELS_JSON_PATH, "utf-8");
509
+ const data = JSON.parse(jsonContent);
510
+ if (!data.lastUpdated) {
511
+ return true;
512
+ }
513
+ const lastUpdated = new Date(data.lastUpdated);
514
+ const now = new Date;
515
+ const ageInDays = (now.getTime() - lastUpdated.getTime()) / (1000 * 60 * 60 * 24);
516
+ return ageInDays > CACHE_MAX_AGE_DAYS;
517
+ } catch (error) {
518
+ return true;
519
+ }
520
+ }
521
+ async function updateModelsFromOpenRouter() {
522
+ console.error("\uD83D\uDD04 Updating model recommendations from OpenRouter...");
523
+ try {
524
+ const topWeeklyProgrammingModels = [
525
+ "x-ai/grok-code-fast-1",
526
+ "anthropic/claude-sonnet-4.5",
527
+ "google/gemini-2.5-flash",
528
+ "minimax/minimax-m2",
529
+ "anthropic/claude-sonnet-4",
530
+ "z-ai/glm-4.6",
531
+ "anthropic/claude-haiku-4.5",
532
+ "openai/gpt-5",
533
+ "qwen/qwen3-vl-235b-a22b-instruct",
534
+ "openrouter/polaris-alpha"
535
+ ];
536
+ const apiResponse = await fetch("https://openrouter.ai/api/v1/models");
537
+ if (!apiResponse.ok) {
538
+ throw new Error(`OpenRouter API returned ${apiResponse.status}`);
539
+ }
540
+ const openrouterData = await apiResponse.json();
541
+ const allModels = openrouterData.data;
542
+ const modelMap = new Map;
543
+ for (const model of allModels) {
544
+ modelMap.set(model.id, model);
545
+ }
546
+ const recommendations = [];
547
+ const providers = new Set;
548
+ for (const modelId of topWeeklyProgrammingModels) {
549
+ const provider = modelId.split("/")[0];
550
+ if (provider === "anthropic") {
551
+ continue;
552
+ }
553
+ if (providers.has(provider)) {
554
+ continue;
555
+ }
556
+ const model = modelMap.get(modelId);
557
+ if (!model) {
558
+ console.error(`⚠️ Model ${modelId} not found in OpenRouter API (including with limited metadata)`);
559
+ recommendations.push({
560
+ id: modelId,
561
+ name: modelId.split("/")[1].replace(/-/g, " ").replace(/\b\w/g, (l) => l.toUpperCase()),
562
+ description: `${modelId} (metadata pending - not yet available in API)`,
563
+ provider: provider.charAt(0).toUpperCase() + provider.slice(1),
564
+ category: "programming",
565
+ priority: recommendations.length + 1,
566
+ pricing: {
567
+ input: "N/A",
568
+ output: "N/A",
569
+ average: "N/A"
570
+ },
571
+ context: "N/A",
572
+ maxOutputTokens: null,
573
+ modality: "text->text",
574
+ supportsTools: false,
575
+ supportsReasoning: false,
576
+ supportsVision: false,
577
+ isModerated: false,
578
+ recommended: true
579
+ });
580
+ providers.add(provider);
581
+ continue;
582
+ }
583
+ const name = model.name || modelId;
584
+ const description = model.description || `${name} model`;
585
+ const architecture = model.architecture || {};
586
+ const topProvider = model.top_provider || {};
587
+ const supportedParams = model.supported_parameters || [];
588
+ const promptPrice = parseFloat(model.pricing?.prompt || "0");
589
+ const completionPrice = parseFloat(model.pricing?.completion || "0");
590
+ const inputPrice = promptPrice > 0 ? `$${(promptPrice * 1e6).toFixed(2)}/1M` : "FREE";
591
+ const outputPrice = completionPrice > 0 ? `$${(completionPrice * 1e6).toFixed(2)}/1M` : "FREE";
592
+ const avgPrice = promptPrice > 0 || completionPrice > 0 ? `$${((promptPrice + completionPrice) / 2 * 1e6).toFixed(2)}/1M` : "FREE";
593
+ let category = "programming";
594
+ const lowerDesc = description.toLowerCase() + " " + name.toLowerCase();
595
+ if (lowerDesc.includes("vision") || lowerDesc.includes("vl-") || lowerDesc.includes("multimodal")) {
596
+ category = "vision";
597
+ } else if (lowerDesc.includes("reason")) {
598
+ category = "reasoning";
599
+ }
600
+ recommendations.push({
601
+ id: modelId,
602
+ name,
603
+ description,
604
+ provider: provider.charAt(0).toUpperCase() + provider.slice(1),
605
+ category,
606
+ priority: recommendations.length + 1,
607
+ pricing: {
608
+ input: inputPrice,
609
+ output: outputPrice,
610
+ average: avgPrice
611
+ },
612
+ context: topProvider.context_length ? `${Math.floor(topProvider.context_length / 1000)}K` : "N/A",
613
+ maxOutputTokens: topProvider.max_completion_tokens || null,
614
+ modality: architecture.modality || "text->text",
615
+ supportsTools: supportedParams.includes("tools") || supportedParams.includes("tool_choice"),
616
+ supportsReasoning: supportedParams.includes("reasoning") || supportedParams.includes("include_reasoning"),
617
+ supportsVision: (architecture.input_modalities || []).includes("image") || (architecture.input_modalities || []).includes("video"),
618
+ isModerated: topProvider.is_moderated || false,
619
+ recommended: true
620
+ });
621
+ providers.add(provider);
622
+ }
623
+ let version = "1.1.5";
624
+ if (existsSync2(MODELS_JSON_PATH)) {
625
+ try {
626
+ const existing = JSON.parse(readFileSync2(MODELS_JSON_PATH, "utf-8"));
627
+ version = existing.version || version;
628
+ } catch {}
629
+ }
630
+ const updatedData = {
631
+ version,
632
+ lastUpdated: new Date().toISOString().split("T")[0],
633
+ source: "https://openrouter.ai/models?categories=programming&fmt=cards&order=top-weekly",
634
+ models: recommendations
635
+ };
636
+ writeFileSync3(MODELS_JSON_PATH, JSON.stringify(updatedData, null, 2), "utf-8");
637
+ console.error(`✅ Updated ${recommendations.length} models (last updated: ${updatedData.lastUpdated})`);
638
+ } catch (error) {
639
+ console.error(`❌ Failed to update models: ${error instanceof Error ? error.message : String(error)}`);
640
+ console.error(" Using cached models (if available)");
641
+ }
642
+ }
643
+ async function checkAndUpdateModelsCache(forceUpdate = false) {
644
+ if (forceUpdate) {
645
+ console.error("\uD83D\uDD04 Force update requested...");
646
+ await updateModelsFromOpenRouter();
647
+ return;
648
+ }
649
+ if (isCacheStale()) {
650
+ console.error("⚠️ Model cache is stale (>2 days old), updating...");
651
+ await updateModelsFromOpenRouter();
652
+ } else {
653
+ try {
654
+ const data = JSON.parse(readFileSync2(MODELS_JSON_PATH, "utf-8"));
655
+ console.error(`✓ Using cached models (last updated: ${data.lastUpdated})`);
656
+ } catch {}
657
+ }
658
+ }
486
659
  function printVersion() {
487
660
  console.log(`claudish version ${VERSION}`);
488
661
  }
@@ -510,10 +683,17 @@ OPTIONS:
510
683
  --cost-tracker Enable cost tracking for API usage (NB!)
511
684
  --audit-costs Show cost analysis report
512
685
  --reset-costs Reset accumulated cost statistics
513
- --list-models List available OpenRouter models
686
+ --list-models List available OpenRouter models (auto-updates if stale >2 days)
514
687
  --list-models --json Output model list in JSON format
688
+ --force-update Force refresh model cache from OpenRouter API
515
689
  --version Show version information
516
690
  -h, --help Show this help message
691
+ --help-ai Show AI agent usage guide (file-based patterns, sub-agents)
692
+ --init Install Claudish skill in current project (.claude/skills/)
693
+
694
+ CUSTOM MODELS:
695
+ Claudish accepts ANY valid OpenRouter model ID, even if not in --list-models
696
+ Example: claudish --model your_provider/custom-model-123 "task"
517
697
 
518
698
  MODES:
519
699
  • Interactive mode (default): Shows model selector, starts persistent session
@@ -574,29 +754,156 @@ EXAMPLES:
574
754
  claudish --verbose "analyze code structure"
575
755
 
576
756
  AVAILABLE MODELS:
577
- Run: claudish --list-models
757
+ List models: claudish --list-models
578
758
  JSON output: claudish --list-models --json
759
+ Force update: claudish --list-models --force-update
760
+ (Cache auto-updates every 2 days)
579
761
 
580
762
  MORE INFO:
581
763
  GitHub: https://github.com/MadAppGang/claude-code
582
764
  OpenRouter: https://openrouter.ai
583
765
  `);
584
766
  }
767
+ function printAIAgentGuide() {
768
+ try {
769
+ const guidePath = join3(__dirname3, "../AI_AGENT_GUIDE.md");
770
+ const guideContent = readFileSync2(guidePath, "utf-8");
771
+ console.log(guideContent);
772
+ } catch (error) {
773
+ console.error("Error reading AI Agent Guide:");
774
+ console.error(error instanceof Error ? error.message : String(error));
775
+ console.error(`
776
+ The guide should be located at: AI_AGENT_GUIDE.md`);
777
+ console.error("You can also view it online at:");
778
+ console.error("https://github.com/MadAppGang/claude-code/blob/main/mcp/claudish/AI_AGENT_GUIDE.md");
779
+ process.exit(1);
780
+ }
781
+ }
782
+ async function initializeClaudishSkill() {
783
+ console.log(`\uD83D\uDD27 Initializing Claudish skill in current project...
784
+ `);
785
+ const cwd = process.cwd();
786
+ const claudeDir = join3(cwd, ".claude");
787
+ const skillsDir = join3(claudeDir, "skills");
788
+ const claudishSkillDir = join3(skillsDir, "claudish-usage");
789
+ const skillFile = join3(claudishSkillDir, "SKILL.md");
790
+ if (existsSync2(skillFile)) {
791
+ console.log("✅ Claudish skill already installed at:");
792
+ console.log(` ${skillFile}
793
+ `);
794
+ console.log("\uD83D\uDCA1 To reinstall, delete the file and run 'claudish --init' again.");
795
+ return;
796
+ }
797
+ const sourceSkillPath = join3(__dirname3, "../skills/claudish-usage/SKILL.md");
798
+ if (!existsSync2(sourceSkillPath)) {
799
+ console.error("❌ Error: Claudish skill file not found in installation.");
800
+ console.error(` Expected at: ${sourceSkillPath}`);
801
+ console.error(`
802
+ \uD83D\uDCA1 Try reinstalling Claudish:`);
803
+ console.error(" npm install -g claudish@latest");
804
+ process.exit(1);
805
+ }
806
+ try {
807
+ if (!existsSync2(claudeDir)) {
808
+ mkdirSync(claudeDir, { recursive: true });
809
+ console.log("\uD83D\uDCC1 Created .claude/ directory");
810
+ }
811
+ if (!existsSync2(skillsDir)) {
812
+ mkdirSync(skillsDir, { recursive: true });
813
+ console.log("\uD83D\uDCC1 Created .claude/skills/ directory");
814
+ }
815
+ if (!existsSync2(claudishSkillDir)) {
816
+ mkdirSync(claudishSkillDir, { recursive: true });
817
+ console.log("\uD83D\uDCC1 Created .claude/skills/claudish-usage/ directory");
818
+ }
819
+ copyFileSync(sourceSkillPath, skillFile);
820
+ console.log("✅ Installed Claudish skill at:");
821
+ console.log(` ${skillFile}
822
+ `);
823
+ console.log("━".repeat(60));
824
+ console.log(`
825
+ \uD83C\uDF89 Claudish skill installed successfully!
826
+ `);
827
+ console.log(`\uD83D\uDCCB Next steps:
828
+ `);
829
+ console.log("1. Reload Claude Code to discover the skill");
830
+ console.log(" - Restart Claude Code, or");
831
+ console.log(` - Re-open your project
832
+ `);
833
+ console.log("2. Use Claudish with external models:");
834
+ console.log(' - User: "use Grok to implement feature X"');
835
+ console.log(` - Claude will automatically use the skill
836
+ `);
837
+ console.log("\uD83D\uDCA1 The skill enforces best practices:");
838
+ console.log(" ✅ Mandatory sub-agent delegation");
839
+ console.log(" ✅ File-based instruction patterns");
840
+ console.log(` ✅ Context window protection
841
+ `);
842
+ console.log(`\uD83D\uDCD6 For more info: claudish --help-ai
843
+ `);
844
+ console.log("━".repeat(60));
845
+ } catch (error) {
846
+ console.error(`
847
+ ❌ Error installing Claudish skill:`);
848
+ console.error(error instanceof Error ? error.message : String(error));
849
+ console.error(`
850
+ \uD83D\uDCA1 Make sure you have write permissions in the current directory.`);
851
+ process.exit(1);
852
+ }
853
+ }
585
854
  function printAvailableModels() {
855
+ let lastUpdated = "unknown";
856
+ let models = [];
857
+ try {
858
+ if (existsSync2(MODELS_JSON_PATH)) {
859
+ const data = JSON.parse(readFileSync2(MODELS_JSON_PATH, "utf-8"));
860
+ lastUpdated = data.lastUpdated || "unknown";
861
+ models = data.models || [];
862
+ }
863
+ } catch {
864
+ const basicModels = getAvailableModels();
865
+ const modelInfo = loadModelInfo();
866
+ for (const model of basicModels) {
867
+ const info = modelInfo[model];
868
+ console.log(` ${model}`);
869
+ console.log(` ${info.name} - ${info.description}`);
870
+ console.log("");
871
+ }
872
+ return;
873
+ }
586
874
  console.log(`
587
- Available OpenRouter Models (in priority order):
875
+ Available OpenRouter Models (last updated: ${lastUpdated}):
588
876
  `);
589
- const models = getAvailableModels();
590
- const modelInfo = loadModelInfo();
877
+ console.log(" Model Provider Pricing Context Capabilities");
878
+ console.log(" " + "─".repeat(86));
591
879
  for (const model of models) {
592
- const info = modelInfo[model];
593
- console.log(` ${model}`);
594
- console.log(` ${info.name} - ${info.description}`);
595
- console.log("");
596
- }
880
+ const modelId = model.id.length > 30 ? model.id.substring(0, 27) + "..." : model.id;
881
+ const modelIdPadded = modelId.padEnd(30);
882
+ const provider = model.provider.length > 10 ? model.provider.substring(0, 7) + "..." : model.provider;
883
+ const providerPadded = provider.padEnd(10);
884
+ let pricing = model.pricing?.average || "N/A";
885
+ if (pricing.includes("-1000000")) {
886
+ pricing = "varies";
887
+ } else if (pricing === "$0.00/1M" || pricing === "FREE") {
888
+ pricing = "FREE";
889
+ }
890
+ const pricingPadded = pricing.padEnd(10);
891
+ const context = model.context || "N/A";
892
+ const contextPadded = context.padEnd(7);
893
+ const tools = model.supportsTools ? "\uD83D\uDD27" : " ";
894
+ const reasoning = model.supportsReasoning ? "\uD83E\uDDE0" : " ";
895
+ const vision = model.supportsVision ? "\uD83D\uDC41️ " : " ";
896
+ const capabilities = `${tools} ${reasoning} ${vision}`;
897
+ console.log(` ${modelIdPadded} ${providerPadded} ${pricingPadded} ${contextPadded} ${capabilities}`);
898
+ }
899
+ console.log("");
900
+ console.log(" Capabilities: \uD83D\uDD27 Tools \uD83E\uDDE0 Reasoning \uD83D\uDC41️ Vision");
901
+ console.log("");
597
902
  console.log("Set default with: export CLAUDISH_MODEL=<model>");
598
903
  console.log(" or: export ANTHROPIC_MODEL=<model>");
599
- console.log(`Or use: claudish --model <model> ...
904
+ console.log("Or use: claudish --model <model> ...");
905
+ console.log(`
906
+ Force update: claudish --list-models --force-update
600
907
  `);
601
908
  }
602
909
  function printAvailableModelsJSON() {
@@ -776,7 +1083,7 @@ async function selectModelInteractively() {
776
1083
  }
777
1084
 
778
1085
  // src/logger.ts
779
- import { writeFileSync as writeFileSync2, appendFile, existsSync as existsSync2, mkdirSync } from "fs";
1086
+ import { writeFileSync as writeFileSync4, appendFile, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
780
1087
  import { join as join4 } from "path";
781
1088
  var logFilePath = null;
782
1089
  var logLevel = "info";
@@ -807,7 +1114,7 @@ function scheduleFlush() {
807
1114
  flushTimer = null;
808
1115
  }
809
1116
  if (logFilePath && logBuffer.length > 0) {
810
- writeFileSync2(logFilePath, logBuffer.join(""), { flag: "a" });
1117
+ writeFileSync4(logFilePath, logBuffer.join(""), { flag: "a" });
811
1118
  logBuffer = [];
812
1119
  }
813
1120
  });
@@ -823,12 +1130,12 @@ function initLogger(debugMode, level = "info") {
823
1130
  }
824
1131
  logLevel = level;
825
1132
  const logsDir = join4(process.cwd(), "logs");
826
- if (!existsSync2(logsDir)) {
827
- mkdirSync(logsDir, { recursive: true });
1133
+ if (!existsSync3(logsDir)) {
1134
+ mkdirSync2(logsDir, { recursive: true });
828
1135
  }
829
1136
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-").split("T").join("_").slice(0, -5);
830
1137
  logFilePath = join4(logsDir, `claudish_${timestamp}.log`);
831
- writeFileSync2(logFilePath, `Claudish Debug Log - ${new Date().toISOString()}
1138
+ writeFileSync4(logFilePath, `Claudish Debug Log - ${new Date().toISOString()}
832
1139
  Log Level: ${level}
833
1140
  ${"=".repeat(80)}
834
1141
 
@@ -3063,7 +3370,7 @@ var serve = (options, listeningListener) => {
3063
3370
  };
3064
3371
 
3065
3372
  // src/proxy-server.ts
3066
- import { writeFileSync as writeFileSync3 } from "node:fs";
3373
+ import { writeFileSync as writeFileSync5 } from "node:fs";
3067
3374
 
3068
3375
  // src/transform.ts
3069
3376
  function removeUriFormat(schema) {
@@ -3851,7 +4158,7 @@ data: ${JSON.stringify(data)}
3851
4158
  total_tokens: cumulativeInputTokens + cumulativeOutputTokens,
3852
4159
  updated_at: Date.now()
3853
4160
  };
3854
- writeFileSync3(tokenFilePath, JSON.stringify(tokenData), "utf-8");
4161
+ writeFileSync5(tokenFilePath, JSON.stringify(tokenData), "utf-8");
3855
4162
  } catch (error) {
3856
4163
  if (isLoggingEnabled()) {
3857
4164
  log(`[Proxy] Failed to write token file: ${error}`);
@@ -4330,7 +4637,7 @@ async function readStdin() {
4330
4637
  }
4331
4638
  async function main() {
4332
4639
  try {
4333
- const config = parseArgs(process.argv.slice(2));
4640
+ const config = await parseArgs(process.argv.slice(2));
4334
4641
  initLogger(config.debug, config.logLevel);
4335
4642
  if (config.debug && !config.quiet) {
4336
4643
  const logFile = getLogFilePath();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudish",
3
- "version": "1.8.0",
3
+ "version": "2.2.0",
4
4
  "description": "CLI tool to run Claude Code with any OpenRouter model (Grok, GPT-5, MiniMax, etc.) via local Anthropic API-compatible proxy",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -35,7 +35,10 @@
35
35
  },
36
36
  "files": [
37
37
  "dist/",
38
- "scripts/"
38
+ "scripts/",
39
+ "skills/",
40
+ "AI_AGENT_GUIDE.md",
41
+ "recommended-models.json"
39
42
  ],
40
43
  "engines": {
41
44
  "node": ">=18.0.0",