@lgcyaxi/oh-my-claude 1.0.0 → 1.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/dist/index.js CHANGED
@@ -1,9 +1,11 @@
1
1
  import {
2
2
  AgentConfigSchema,
3
+ AgentFallbackConfigSchema,
3
4
  AnthropicCompatibleClient,
4
5
  CategoryConfigSchema,
5
6
  ConcurrencyConfigSchema,
6
7
  DEFAULT_CONFIG,
8
+ FallbackRequiredError,
7
9
  OhMyClaudeConfigSchema,
8
10
  OpenAICompatibleClient,
9
11
  OpenRouterClient,
@@ -25,6 +27,7 @@ import {
25
27
  generateAgentMarkdown,
26
28
  generateAllAgentFiles,
27
29
  getAgent,
30
+ getAgentFallback,
28
31
  getAgentsDirectory,
29
32
  getCommandsDir,
30
33
  getConfigPath,
@@ -38,6 +41,7 @@ import {
38
41
  getProvidersStatus,
39
42
  install,
40
43
  isMcpAgent,
44
+ isProviderConfigured,
41
45
  isTaskAgent,
42
46
  librarianAgent,
43
47
  loadConfig,
@@ -50,10 +54,11 @@ import {
50
54
  resolveProviderForCategory,
51
55
  routeByAgent,
52
56
  routeByCategory,
57
+ shouldUseFallback,
53
58
  sisyphusAgent,
54
59
  taskAgents,
55
60
  uninstall
56
- } from "./index-ejmtq3zc.js";
61
+ } from "./index-5ars1tn4.js";
57
62
  // src/providers/deepseek.ts
58
63
  var DEEPSEEK_BASE_URL = "https://api.deepseek.com/anthropic";
59
64
  var DEEPSEEK_API_KEY_ENV = "DEEPSEEK_API_KEY";
@@ -80,6 +85,7 @@ export {
80
85
  uninstall,
81
86
  taskAgents,
82
87
  sisyphusAgent,
88
+ shouldUseFallback,
83
89
  routeByCategory,
84
90
  routeByAgent,
85
91
  resolveProviderForCategory,
@@ -94,6 +100,7 @@ export {
94
100
  loadConfig,
95
101
  librarianAgent,
96
102
  isTaskAgent,
103
+ isProviderConfigured,
97
104
  isMcpAgent,
98
105
  install,
99
106
  getProvidersStatus,
@@ -107,6 +114,7 @@ export {
107
114
  getConfigPath,
108
115
  getCommandsDir,
109
116
  getAgentsDirectory,
117
+ getAgentFallback,
110
118
  getAgent,
111
119
  generateAllAgentFiles,
112
120
  generateAgentMarkdown,
@@ -131,9 +139,11 @@ export {
131
139
  OpenRouterClient,
132
140
  OpenAICompatibleClient,
133
141
  OhMyClaudeConfigSchema,
142
+ FallbackRequiredError,
134
143
  DEFAULT_CONFIG,
135
144
  ConcurrencyConfigSchema,
136
145
  CategoryConfigSchema,
137
146
  AnthropicCompatibleClient,
147
+ AgentFallbackConfigSchema,
138
148
  AgentConfigSchema
139
149
  };
@@ -16671,6 +16671,11 @@ var ProviderConfigSchema = exports_external.object({
16671
16671
  api_key_env: exports_external.string().optional(),
16672
16672
  note: exports_external.string().optional()
16673
16673
  });
16674
+ var AgentFallbackConfigSchema = exports_external.object({
16675
+ provider: exports_external.string(),
16676
+ model: exports_external.string(),
16677
+ executionMode: exports_external.enum(["task", "mcp"]).optional()
16678
+ });
16674
16679
  var AgentConfigSchema = exports_external.object({
16675
16680
  provider: exports_external.string(),
16676
16681
  model: exports_external.string(),
@@ -16679,7 +16684,8 @@ var AgentConfigSchema = exports_external.object({
16679
16684
  thinking: exports_external.object({
16680
16685
  enabled: exports_external.boolean(),
16681
16686
  budget_tokens: exports_external.number().optional()
16682
- }).optional()
16687
+ }).optional(),
16688
+ fallback: AgentFallbackConfigSchema.optional()
16683
16689
  });
16684
16690
  var CategoryConfigSchema = exports_external.object({
16685
16691
  provider: exports_external.string(),
@@ -16736,19 +16742,32 @@ var OhMyClaudeConfigSchema = exports_external.object({
16736
16742
  oracle: {
16737
16743
  provider: "deepseek",
16738
16744
  model: "deepseek-reasoner",
16739
- temperature: 0.1
16745
+ temperature: 0.1,
16746
+ fallback: { provider: "claude", model: "claude-opus-4-5", executionMode: "task" }
16747
+ },
16748
+ librarian: {
16749
+ provider: "zhipu",
16750
+ model: "glm-4.7",
16751
+ temperature: 0.3,
16752
+ fallback: { provider: "claude", model: "claude-sonnet-4-5", executionMode: "task" }
16753
+ },
16754
+ explore: {
16755
+ provider: "deepseek",
16756
+ model: "deepseek-chat",
16757
+ temperature: 0.1,
16758
+ fallback: { provider: "claude", model: "claude-haiku-4-5", executionMode: "task" }
16740
16759
  },
16741
- librarian: { provider: "zhipu", model: "glm-4.7", temperature: 0.3 },
16742
- explore: { provider: "deepseek", model: "deepseek-chat", temperature: 0.1 },
16743
16760
  "frontend-ui-ux": {
16744
16761
  provider: "zhipu",
16745
16762
  model: "glm-4v-flash",
16746
- temperature: 0.7
16763
+ temperature: 0.7,
16764
+ fallback: { provider: "claude", model: "claude-sonnet-4-5", executionMode: "task" }
16747
16765
  },
16748
16766
  "document-writer": {
16749
16767
  provider: "minimax",
16750
16768
  model: "MiniMax-M2.1",
16751
- temperature: 0.5
16769
+ temperature: 0.5,
16770
+ fallback: { provider: "claude", model: "claude-sonnet-4-5", executionMode: "task" }
16752
16771
  }
16753
16772
  }),
16754
16773
  categories: exports_external.record(exports_external.string(), CategoryConfigSchema).default({
@@ -16850,6 +16869,50 @@ function getProviderDetails(config2, providerName) {
16850
16869
  }
16851
16870
  return null;
16852
16871
  }
16872
+ function isProviderConfigured(config2, providerName) {
16873
+ const providerConfig = config2.providers[providerName];
16874
+ if (!providerConfig) {
16875
+ return false;
16876
+ }
16877
+ if (providerConfig.type === "claude-subscription") {
16878
+ return true;
16879
+ }
16880
+ if (providerConfig.api_key_env) {
16881
+ const apiKey = process.env[providerConfig.api_key_env];
16882
+ return !!apiKey && apiKey.length > 0;
16883
+ }
16884
+ return false;
16885
+ }
16886
+ function getAgentFallback(config2, agentName) {
16887
+ const agentConfig = config2.agents[agentName];
16888
+ if (agentConfig?.fallback) {
16889
+ return {
16890
+ provider: agentConfig.fallback.provider,
16891
+ model: agentConfig.fallback.model,
16892
+ executionMode: agentConfig.fallback.executionMode
16893
+ };
16894
+ }
16895
+ return null;
16896
+ }
16897
+ function shouldUseFallback(config2, agentName) {
16898
+ const agentConfig = config2.agents[agentName];
16899
+ if (!agentConfig) {
16900
+ return { useFallback: false };
16901
+ }
16902
+ if (!isProviderConfigured(config2, agentConfig.provider)) {
16903
+ const fallback = getAgentFallback(config2, agentName);
16904
+ if (fallback) {
16905
+ const providerConfig = config2.providers[agentConfig.provider];
16906
+ const envVar = providerConfig?.api_key_env ?? `${agentConfig.provider.toUpperCase()}_API_KEY`;
16907
+ return {
16908
+ useFallback: true,
16909
+ reason: `${envVar} is not set`,
16910
+ fallback
16911
+ };
16912
+ }
16913
+ }
16914
+ return { useFallback: false };
16915
+ }
16853
16916
  // src/providers/router.ts
16854
16917
  var clientCache = new Map;
16855
16918
  function getProviderClient(providerName, config2) {
@@ -16889,6 +16952,19 @@ function getProviderClient(providerName, config2) {
16889
16952
  clientCache.set(providerName, client);
16890
16953
  return client;
16891
16954
  }
16955
+
16956
+ class FallbackRequiredError extends Error {
16957
+ agentName;
16958
+ fallback;
16959
+ reason;
16960
+ constructor(message, agentName, fallback, reason) {
16961
+ super(message);
16962
+ this.agentName = agentName;
16963
+ this.fallback = fallback;
16964
+ this.reason = reason;
16965
+ this.name = "FallbackRequiredError";
16966
+ }
16967
+ }
16892
16968
  async function routeByAgent(agentName, messages, options) {
16893
16969
  const config2 = loadConfig();
16894
16970
  const agentConfig = resolveProviderForAgent(config2, agentName);
@@ -16899,6 +16975,10 @@ async function routeByAgent(agentName, messages, options) {
16899
16975
  if (providerDetails?.type === "claude-subscription") {
16900
16976
  throw new Error(`Agent "${agentName}" uses Claude subscription. Use Claude Code's Task tool instead of MCP.`);
16901
16977
  }
16978
+ const fallbackCheck = shouldUseFallback(config2, agentName);
16979
+ if (fallbackCheck.useFallback && fallbackCheck.fallback) {
16980
+ throw new FallbackRequiredError(`Agent "${agentName}" requires fallback: ${fallbackCheck.reason}. Use Task tool with ${fallbackCheck.fallback.model} instead.`, agentName, fallbackCheck.fallback, fallbackCheck.reason ?? "Provider not configured");
16981
+ }
16902
16982
  const client = getProviderClient(agentConfig.provider, config2);
16903
16983
  const request = {
16904
16984
  model: agentConfig.model,
@@ -17903,7 +17983,12 @@ var oracleAgent = {
17903
17983
  defaultTemperature: 0.1,
17904
17984
  executionMode: "mcp",
17905
17985
  category: "advisor",
17906
- restrictedTools: ["Edit", "Write", "Task"]
17986
+ restrictedTools: ["Edit", "Write", "Task"],
17987
+ fallback: {
17988
+ provider: "claude",
17989
+ model: "claude-opus-4-5",
17990
+ executionMode: "task"
17991
+ }
17907
17992
  };
17908
17993
  // src/agents/librarian.ts
17909
17994
  var LIBRARIAN_PROMPT = `# THE LIBRARIAN
@@ -18109,7 +18194,12 @@ var librarianAgent = {
18109
18194
  defaultTemperature: 0.3,
18110
18195
  executionMode: "mcp",
18111
18196
  category: "explorer",
18112
- restrictedTools: ["Edit", "Write"]
18197
+ restrictedTools: ["Edit", "Write"],
18198
+ fallback: {
18199
+ provider: "claude",
18200
+ model: "claude-sonnet-4-5",
18201
+ executionMode: "task"
18202
+ }
18113
18203
  };
18114
18204
  // src/agents/explore.ts
18115
18205
  var EXPLORE_PROMPT = `You are a codebase search specialist. Your job: find files and code, return actionable results.
@@ -18212,7 +18302,12 @@ var exploreAgent = {
18212
18302
  defaultTemperature: 0.1,
18213
18303
  executionMode: "mcp",
18214
18304
  category: "explorer",
18215
- restrictedTools: ["Edit", "Write", "Task"]
18305
+ restrictedTools: ["Edit", "Write", "Task"],
18306
+ fallback: {
18307
+ provider: "claude",
18308
+ model: "claude-haiku-4-5",
18309
+ executionMode: "task"
18310
+ }
18216
18311
  };
18217
18312
  // src/agents/frontend-ui-ux.ts
18218
18313
  var FRONTEND_UI_UX_PROMPT = `# Role: Designer-Turned-Developer
@@ -18308,7 +18403,12 @@ var frontendUiUxAgent = {
18308
18403
  defaultModel: "glm-4v-flash",
18309
18404
  defaultTemperature: 0.7,
18310
18405
  executionMode: "mcp",
18311
- category: "specialist"
18406
+ category: "specialist",
18407
+ fallback: {
18408
+ provider: "claude",
18409
+ model: "claude-sonnet-4-5",
18410
+ executionMode: "task"
18411
+ }
18312
18412
  };
18313
18413
  // src/agents/document-writer.ts
18314
18414
  var DOCUMENT_WRITER_PROMPT = `<role>
@@ -18449,7 +18549,12 @@ var documentWriterAgent = {
18449
18549
  defaultModel: "MiniMax-M2.1",
18450
18550
  defaultTemperature: 0.5,
18451
18551
  executionMode: "mcp",
18452
- category: "specialist"
18552
+ category: "specialist",
18553
+ fallback: {
18554
+ provider: "claude",
18555
+ model: "claude-sonnet-4-5",
18556
+ executionMode: "task"
18557
+ }
18453
18558
  };
18454
18559
  // src/agents/index.ts
18455
18560
  var agents = {
@@ -18529,8 +18634,19 @@ async function runTask(task, systemPrompt) {
18529
18634
  task.completedAt = Date.now();
18530
18635
  tasks.set(task.id, task);
18531
18636
  } catch (error2) {
18532
- task.status = "failed";
18533
- task.error = error2 instanceof Error ? error2.message : String(error2);
18637
+ if (error2 instanceof FallbackRequiredError) {
18638
+ task.status = "fallback_required";
18639
+ task.error = error2.message;
18640
+ task.fallback = {
18641
+ provider: error2.fallback.provider,
18642
+ model: error2.fallback.model,
18643
+ executionMode: error2.fallback.executionMode,
18644
+ reason: error2.reason
18645
+ };
18646
+ } else {
18647
+ task.status = "failed";
18648
+ task.error = error2 instanceof Error ? error2.message : String(error2);
18649
+ }
18534
18650
  task.completedAt = Date.now();
18535
18651
  tasks.set(task.id, task);
18536
18652
  }
@@ -18543,7 +18659,8 @@ function pollTask(taskId) {
18543
18659
  return {
18544
18660
  status: task.status,
18545
18661
  result: task.result,
18546
- error: task.error
18662
+ error: task.error,
18663
+ fallback: task.fallback
18547
18664
  };
18548
18665
  }
18549
18666
  function cancelTask(taskId) {
@@ -18643,6 +18760,9 @@ Use this for async operations that should run in parallel without blocking.
18643
18760
  - visual-engineering: UI/UX work
18644
18761
  - writing: Documentation and text
18645
18762
 
18763
+ **Fallback Behavior**:
18764
+ If the provider's API key is not configured, the task will return with status "fallback_required" and include fallback info. When this happens, use Claude Code's Task tool with the specified fallback model instead.
18765
+
18646
18766
  Note: Agents using Claude subscription (sisyphus, claude-reviewer, claude-scout) should use Claude Code's Task tool instead.`,
18647
18767
  inputSchema: {
18648
18768
  type: "object",
@@ -18706,8 +18826,8 @@ Note: Agents using Claude subscription (sisyphus, claude-reviewer, claude-scout)
18706
18826
  properties: {
18707
18827
  status: {
18708
18828
  type: "string",
18709
- enum: ["pending", "running", "completed", "failed", "cancelled"],
18710
- description: "Filter by status"
18829
+ enum: ["pending", "running", "completed", "failed", "cancelled", "fallback_required"],
18830
+ description: "Filter by status. 'fallback_required' means the provider API key is not configured and Claude Task tool should be used instead."
18711
18831
  },
18712
18832
  limit: {
18713
18833
  type: "number",
@@ -18870,7 +18990,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
18870
18990
  ...t.completedAt && {
18871
18991
  completed: new Date(t.completedAt).toISOString()
18872
18992
  },
18873
- ...t.error && { error: t.error }
18993
+ ...t.error && { error: t.error },
18994
+ ...t.fallback && { fallback: t.fallback }
18874
18995
  }))
18875
18996
  })
18876
18997
  }
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@lgcyaxi/oh-my-claude",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Multi-agent orchestration plugin for Claude Code with multi-provider support",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
8
8
  "bin": {
9
- "oh-my-claude": "./bin/oh-my-claude.js"
9
+ "oh-my-claude": "bin/oh-my-claude.js"
10
10
  },
11
11
  "scripts": {
12
12
  "build": "bun build src/index.ts src/cli.ts --outdir dist --target node --splitting",
@@ -43,7 +43,7 @@
43
43
  "license": "MIT",
44
44
  "repository": {
45
45
  "type": "git",
46
- "url": "https://github.com/lgcyaxi/oh-my-claude"
46
+ "url": "git+https://github.com/lgcyaxi/oh-my-claude.git"
47
47
  },
48
48
  "homepage": "https://github.com/lgcyaxi/oh-my-claude#readme",
49
49
  "bugs": {
@@ -146,6 +146,11 @@ export const documentWriterAgent: AgentDefinition = {
146
146
  defaultTemperature: 0.5,
147
147
  executionMode: "mcp",
148
148
  category: "specialist",
149
+ fallback: {
150
+ provider: "claude",
151
+ model: "claude-sonnet-4-5",
152
+ executionMode: "task",
153
+ },
149
154
  };
150
155
 
151
156
  export default documentWriterAgent;
@@ -108,6 +108,11 @@ export const exploreAgent: AgentDefinition = {
108
108
  executionMode: "mcp",
109
109
  category: "explorer",
110
110
  restrictedTools: ["Edit", "Write", "Task"],
111
+ fallback: {
112
+ provider: "claude",
113
+ model: "claude-haiku-4-5",
114
+ executionMode: "task",
115
+ },
111
116
  };
112
117
 
113
118
  export default exploreAgent;
@@ -101,6 +101,11 @@ export const frontendUiUxAgent: AgentDefinition = {
101
101
  defaultTemperature: 0.7,
102
102
  executionMode: "mcp",
103
103
  category: "specialist",
104
+ fallback: {
105
+ provider: "claude",
106
+ model: "claude-sonnet-4-5",
107
+ executionMode: "task",
108
+ },
104
109
  };
105
110
 
106
111
  export default frontendUiUxAgent;
@@ -211,6 +211,11 @@ export const librarianAgent: AgentDefinition = {
211
211
  executionMode: "mcp",
212
212
  category: "explorer",
213
213
  restrictedTools: ["Edit", "Write"],
214
+ fallback: {
215
+ provider: "claude",
216
+ model: "claude-sonnet-4-5",
217
+ executionMode: "task",
218
+ },
214
219
  };
215
220
 
216
221
  export default librarianAgent;
@@ -99,6 +99,11 @@ export const oracleAgent: AgentDefinition = {
99
99
  executionMode: "mcp",
100
100
  category: "advisor",
101
101
  restrictedTools: ["Edit", "Write", "Task"],
102
+ fallback: {
103
+ provider: "claude",
104
+ model: "claude-opus-4-5",
105
+ executionMode: "task",
106
+ },
102
107
  };
103
108
 
104
109
  export default oracleAgent;
@@ -2,6 +2,15 @@
2
2
  * Agent definition types for oh-my-claude
3
3
  */
4
4
 
5
+ export interface AgentFallback {
6
+ /** Fallback provider (usually "claude") */
7
+ provider: string;
8
+ /** Fallback model */
9
+ model: string;
10
+ /** Fallback execution mode (usually "task" for Claude) */
11
+ executionMode: "task" | "mcp";
12
+ }
13
+
5
14
  export interface AgentDefinition {
6
15
  /** Agent identifier (used in config) */
7
16
  name: string;
@@ -21,6 +30,8 @@ export interface AgentDefinition {
21
30
  category: "orchestrator" | "reviewer" | "scout" | "advisor" | "explorer" | "specialist" | "planner";
22
31
  /** Tools this agent should NOT use (restrictions) */
23
32
  restrictedTools?: string[];
33
+ /** Fallback configuration when primary provider API key is not available */
34
+ fallback?: AgentFallback;
24
35
  }
25
36
 
26
37
  export interface AgentMarkdownOptions {
package/src/cli.ts CHANGED
@@ -7,6 +7,7 @@
7
7
  * npx oh-my-claude uninstall # Uninstall oh-my-claude
8
8
  * npx oh-my-claude status # Check installation status
9
9
  * npx oh-my-claude doctor # Diagnose configuration issues
10
+ * npx oh-my-claude update # Update oh-my-claude to latest version
10
11
  * npx oh-my-claude setup-mcp # Install official MCP servers (MiniMax, GLM)
11
12
  */
12
13
 
@@ -18,7 +19,7 @@ import { loadConfig } from "./config";
18
19
  program
19
20
  .name("oh-my-claude")
20
21
  .description("Multi-agent orchestration plugin for Claude Code")
21
- .version("1.0.0");
22
+ .version("1.0.1");
22
23
 
23
24
  // Install command
24
25
  program
@@ -362,6 +363,130 @@ program
362
363
  }
363
364
  });
364
365
 
366
+ // Update command
367
+ program
368
+ .command("update")
369
+ .description("Update oh-my-claude to the latest version")
370
+ .option("--check", "Only check for updates without installing")
371
+ .option("--force", "Force update even if already on latest version")
372
+ .action(async (options) => {
373
+ const { execSync } = require("node:child_process");
374
+ const { readFileSync, existsSync } = require("node:fs");
375
+ const { join } = require("node:path");
376
+ const { homedir } = require("node:os");
377
+
378
+ // Color helpers
379
+ const useColor = process.stdout.isTTY;
380
+ const c = {
381
+ reset: useColor ? "\x1b[0m" : "",
382
+ bold: useColor ? "\x1b[1m" : "",
383
+ dim: useColor ? "\x1b[2m" : "",
384
+ green: useColor ? "\x1b[32m" : "",
385
+ red: useColor ? "\x1b[31m" : "",
386
+ yellow: useColor ? "\x1b[33m" : "",
387
+ cyan: useColor ? "\x1b[36m" : "",
388
+ magenta: useColor ? "\x1b[35m" : "",
389
+ };
390
+
391
+ const ok = (text: string) => `${c.green}✓${c.reset} ${text}`;
392
+ const fail = (text: string) => `${c.red}✗${c.reset} ${text}`;
393
+ const warn = (text: string) => `${c.yellow}⚠${c.reset} ${text}`;
394
+ const header = (text: string) => `${c.cyan}${c.bold}${text}${c.reset}`;
395
+ const dimText = (text: string) => `${c.dim}${text}${c.reset}`;
396
+
397
+ const PACKAGE_NAME = "@lgcyaxi/oh-my-claude";
398
+
399
+ console.log(`${c.bold}${c.magenta}oh-my-claude Update${c.reset}\n`);
400
+
401
+ // Get current version
402
+ let currentVersion = "unknown";
403
+ try {
404
+ // Try to read from package.json in the installed location
405
+ const installDir = join(homedir(), ".claude", "oh-my-claude");
406
+ const localPkgPath = join(installDir, "package.json");
407
+
408
+ if (existsSync(localPkgPath)) {
409
+ const pkg = JSON.parse(readFileSync(localPkgPath, "utf-8"));
410
+ currentVersion = pkg.version;
411
+ } else {
412
+ // Fall back to the version from the running CLI
413
+ currentVersion = program.version() || "unknown";
414
+ }
415
+ } catch (error) {
416
+ // Use the CLI version as fallback
417
+ currentVersion = program.version() || "unknown";
418
+ }
419
+
420
+ console.log(`Current version: ${c.cyan}${currentVersion}${c.reset}`);
421
+
422
+ // Fetch latest version from npm
423
+ let latestVersion = "unknown";
424
+ try {
425
+ console.log(`${dimText("Checking npm registry for latest version...")}`);
426
+ const npmInfo = execSync(`npm view ${PACKAGE_NAME} version 2>/dev/null`, {
427
+ encoding: "utf-8",
428
+ }).trim();
429
+ latestVersion = npmInfo;
430
+ console.log(`Latest version: ${c.cyan}${latestVersion}${c.reset}\n`);
431
+ } catch (error) {
432
+ console.log(`${fail("Failed to fetch latest version from npm")}`);
433
+ console.log(`${dimText("Check your internet connection or try again later")}\n`);
434
+ process.exit(1);
435
+ }
436
+
437
+ // Compare versions
438
+ const isUpToDate = currentVersion === latestVersion;
439
+ const needsUpdate = !isUpToDate || options.force;
440
+
441
+ if (isUpToDate && !options.force) {
442
+ console.log(ok("You are already on the latest version!"));
443
+ process.exit(0);
444
+ }
445
+
446
+ if (options.check) {
447
+ if (needsUpdate) {
448
+ console.log(warn(`Update available: ${currentVersion} → ${latestVersion}`));
449
+ console.log(`\nRun ${c.cyan}npx ${PACKAGE_NAME} update${c.reset} to update.`);
450
+ }
451
+ process.exit(0);
452
+ }
453
+
454
+ // Perform update
455
+ console.log(header("Updating oh-my-claude...\n"));
456
+
457
+ try {
458
+ // Step 1: Clear npx cache for the package
459
+ console.log(`${dimText("Clearing npx cache...")}`);
460
+ try {
461
+ execSync(`npx --yes clear-npx-cache 2>/dev/null || true`, { stdio: "pipe" });
462
+ } catch {
463
+ // Ignore errors - cache clear is optional
464
+ }
465
+
466
+ // Step 2: Install latest version via npx
467
+ console.log(`${dimText("Downloading latest version...")}`);
468
+
469
+ // Use npx with --yes to ensure latest version is fetched
470
+ // The @latest tag forces npm to check for the newest version
471
+ const updateCmd = `npx --yes ${PACKAGE_NAME}@latest install --force`;
472
+ console.log(`${dimText(`Running: ${updateCmd}`)}\n`);
473
+
474
+ execSync(updateCmd, { stdio: "inherit" });
475
+
476
+ console.log(`\n${ok("Update complete!")}`);
477
+ console.log(`Updated from ${c.yellow}${currentVersion}${c.reset} to ${c.green}${latestVersion}${c.reset}`);
478
+
479
+ // Show changelog hint
480
+ console.log(`\n${dimText("View changelog at: https://github.com/lgcyaxi/oh-my-claude/blob/main/CHANGELOG.md")}`);
481
+
482
+ } catch (error) {
483
+ console.log(`\n${fail("Update failed")}`);
484
+ console.log(`${dimText("Try running manually:")}`);
485
+ console.log(` ${c.cyan}npx ${PACKAGE_NAME}@latest install --force${c.reset}`);
486
+ process.exit(1);
487
+ }
488
+ });
489
+
365
490
  // Setup MCP command
366
491
  program
367
492
  .command("setup-mcp")
@@ -103,3 +103,76 @@ export function getProviderDetails(
103
103
  }
104
104
  return null;
105
105
  }
106
+
107
+ /**
108
+ * Check if a provider is configured (has API key set)
109
+ */
110
+ export function isProviderConfigured(
111
+ config: OhMyClaudeConfig,
112
+ providerName: string
113
+ ): boolean {
114
+ const providerConfig = config.providers[providerName];
115
+ if (!providerConfig) {
116
+ return false;
117
+ }
118
+
119
+ // Claude subscription is always "configured"
120
+ if (providerConfig.type === "claude-subscription") {
121
+ return true;
122
+ }
123
+
124
+ // Check if API key environment variable is set
125
+ if (providerConfig.api_key_env) {
126
+ const apiKey = process.env[providerConfig.api_key_env];
127
+ return !!apiKey && apiKey.length > 0;
128
+ }
129
+
130
+ return false;
131
+ }
132
+
133
+ /**
134
+ * Get fallback configuration for an agent
135
+ */
136
+ export function getAgentFallback(
137
+ config: OhMyClaudeConfig,
138
+ agentName: string
139
+ ): { provider: string; model: string; executionMode?: string } | null {
140
+ const agentConfig = config.agents[agentName];
141
+ if (agentConfig?.fallback) {
142
+ return {
143
+ provider: agentConfig.fallback.provider,
144
+ model: agentConfig.fallback.model,
145
+ executionMode: agentConfig.fallback.executionMode,
146
+ };
147
+ }
148
+ return null;
149
+ }
150
+
151
+ /**
152
+ * Check if an agent should use fallback (primary provider not configured)
153
+ */
154
+ export function shouldUseFallback(
155
+ config: OhMyClaudeConfig,
156
+ agentName: string
157
+ ): { useFallback: boolean; reason?: string; fallback?: { provider: string; model: string; executionMode?: string } } {
158
+ const agentConfig = config.agents[agentName];
159
+ if (!agentConfig) {
160
+ return { useFallback: false };
161
+ }
162
+
163
+ // Check if primary provider is configured
164
+ if (!isProviderConfigured(config, agentConfig.provider)) {
165
+ const fallback = getAgentFallback(config, agentName);
166
+ if (fallback) {
167
+ const providerConfig = config.providers[agentConfig.provider];
168
+ const envVar = providerConfig?.api_key_env ?? `${agentConfig.provider.toUpperCase()}_API_KEY`;
169
+ return {
170
+ useFallback: true,
171
+ reason: `${envVar} is not set`,
172
+ fallback,
173
+ };
174
+ }
175
+ }
176
+
177
+ return { useFallback: false };
178
+ }