@lgcyaxi/oh-my-claude 1.0.0 → 1.1.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.
Files changed (49) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +43 -0
  2. package/CHANGELOG.md +53 -0
  3. package/CLAUDE.md +60 -0
  4. package/README.md +97 -12
  5. package/README.zh-CN.md +58 -11
  6. package/bin/oh-my-claude.js +4 -2
  7. package/changelog/v1.0.0.md +28 -0
  8. package/changelog/v1.0.1.md +28 -0
  9. package/changelog/v1.1.0.md +20 -0
  10. package/changelog/v1.1.1.md +71 -0
  11. package/dist/cli.js +213 -4
  12. package/dist/hooks/comment-checker.js +1 -1
  13. package/dist/hooks/task-notification.js +124 -0
  14. package/dist/hooks/task-tracker.js +144 -0
  15. package/dist/index-1dv6t98k.js +7654 -0
  16. package/dist/index-5ars1tn4.js +7348 -0
  17. package/dist/index-d79fk9ah.js +7350 -0
  18. package/dist/index-hzm01rkh.js +7654 -0
  19. package/dist/index-qrbfj4cd.js +7664 -0
  20. package/dist/index-ypyx3ye0.js +7349 -0
  21. package/dist/index.js +24 -1
  22. package/dist/mcp/server.js +202 -45
  23. package/dist/statusline/statusline.js +146 -0
  24. package/package.json +6 -5
  25. package/src/agents/document-writer.ts +5 -0
  26. package/src/agents/explore.ts +5 -0
  27. package/src/agents/frontend-ui-ux.ts +5 -0
  28. package/src/agents/librarian.ts +5 -0
  29. package/src/agents/oracle.ts +5 -0
  30. package/src/agents/types.ts +11 -0
  31. package/src/cli.ts +261 -2
  32. package/src/commands/index.ts +8 -1
  33. package/src/commands/omc-status.md +71 -0
  34. package/src/commands/omcx-issue.md +175 -0
  35. package/src/commands/ulw.md +144 -0
  36. package/src/config/loader.ts +73 -0
  37. package/src/config/schema.ts +25 -2
  38. package/src/generators/agent-generator.ts +17 -1
  39. package/src/hooks/comment-checker.ts +2 -2
  40. package/src/hooks/task-notification.ts +206 -0
  41. package/src/hooks/task-tracker.ts +252 -0
  42. package/src/installer/index.ts +55 -4
  43. package/src/installer/settings-merger.ts +86 -0
  44. package/src/installer/statusline-merger.ts +169 -0
  45. package/src/mcp/background-agent-server/server.ts +11 -2
  46. package/src/mcp/background-agent-server/task-manager.ts +83 -4
  47. package/src/providers/router.ts +28 -0
  48. package/src/statusline/formatter.ts +164 -0
  49. package/src/statusline/statusline.ts +103 -0
@@ -0,0 +1,144 @@
1
+ # /ulw - Ultrawork Mode
2
+
3
+ **ULTRAWORK MODE ACTIVATED!**
4
+
5
+ You are now operating in **MAXIMUM PERFORMANCE MODE**. All restrictions on effort, thoroughness, and completion are lifted. You will work relentlessly until the task is FULLY complete.
6
+
7
+ ## Core Mandate
8
+
9
+ **ZERO PARTIAL COMPLETION. 100% DELIVERY OR NOTHING.**
10
+
11
+ This is not a suggestion. This is your operating contract:
12
+ - NO scope reduction
13
+ - NO mock versions or placeholders
14
+ - NO skipped requirements
15
+ - NO "good enough" stopping points
16
+ - NO deleting tests to make builds pass
17
+ - NO leaving code in broken state
18
+
19
+ **60-80% completion is FAILURE. Only 100% is acceptable.**
20
+
21
+ ## Agent Utilization (MANDATORY)
22
+
23
+ You MUST leverage ALL available agents to their fullest potential:
24
+
25
+ ### Sync Agents (Task tool)
26
+ | Agent | Use For | When |
27
+ |-------|---------|------|
28
+ | **Explore** | Codebase search, pattern finding | FIRST - before any implementation |
29
+ | **Claude-Reviewer** | Code review, verification | AFTER - every significant change |
30
+ | **Claude-Scout** | Fast exploration, quick checks | PARALLEL - multiple searches |
31
+
32
+ ### Async Agents (MCP background)
33
+ | Agent | Use For | When |
34
+ |-------|---------|------|
35
+ | **Oracle** | Architecture decisions, deep reasoning | When stuck or uncertain |
36
+ | **Librarian** | External docs, library research | Before using unfamiliar APIs |
37
+ | **Frontend-UI-UX** | Visual/UI implementation | All frontend work |
38
+ | **Document-Writer** | Documentation, README | After implementation |
39
+
40
+ **FIRE PARALLEL AGENTS AGGRESSIVELY.** Launch 5-10+ background tasks if needed. Don't wait - collect results when ready.
41
+
42
+ ## Execution Protocol
43
+
44
+ ### 1. Immediate Planning
45
+ ```
46
+ FIRST ACTION: Create comprehensive TodoWrite list
47
+ - Break down EVERY step
48
+ - Include verification steps
49
+ - Include documentation steps
50
+ - NO hidden work - everything tracked
51
+ ```
52
+
53
+ ### 2. Aggressive Parallelization
54
+ ```
55
+ LAUNCH PARALLEL:
56
+ - Explore agents for codebase context
57
+ - Librarian for external documentation
58
+ - Multiple search paths simultaneously
59
+
60
+ DO NOT wait for one search to complete before starting another.
61
+ ```
62
+
63
+ ### 3. Implementation with Verification
64
+ ```
65
+ FOR EACH CHANGE:
66
+ 1. Implement the change
67
+ 2. Run diagnostics immediately
68
+ 3. Run tests if applicable
69
+ 4. Mark todo complete ONLY after verification passes
70
+ ```
71
+
72
+ ### 4. Zero-Tolerance Quality Gates
73
+
74
+ **Before marking ANY task complete:**
75
+ - [ ] Code compiles/transpiles without errors
76
+ - [ ] Linter passes (no suppressions added)
77
+ - [ ] Tests pass (no tests deleted or skipped)
78
+ - [ ] Build succeeds (if applicable)
79
+ - [ ] Functionality verified with actual execution
80
+
81
+ **If verification fails:**
82
+ 1. Fix immediately
83
+ 2. Re-verify
84
+ 3. After 3 failures: STOP, consult Oracle, then ask user
85
+
86
+ ### 5. Completion Criteria
87
+
88
+ The task is NOT complete until:
89
+ - [ ] ALL todo items marked done
90
+ - [ ] ALL verification gates passed
91
+ - [ ] ALL user requirements addressed
92
+ - [ ] Evidence collected for each completion claim
93
+
94
+ ## Anti-Patterns (BLOCKED)
95
+
96
+ | Violation | Consequence |
97
+ |-----------|-------------|
98
+ | Claiming "done" without verification | REJECTED - redo with proof |
99
+ | Reducing scope without user approval | REJECTED - implement full scope |
100
+ | Skipping agent delegation | REJECTED - suboptimal execution |
101
+ | Batch-completing todos | REJECTED - defeats tracking |
102
+ | Leaving broken code | REJECTED - fix before proceeding |
103
+
104
+ ## Work Until Done Protocol
105
+
106
+ **If session ends with incomplete todos:**
107
+ 1. Boulder state persists your progress
108
+ 2. Next session: `/omc-start-work` to resume
109
+ 3. Continue from first unchecked item
110
+ 4. NEVER restart from scratch
111
+
112
+ **If you hit a blocker:**
113
+ 1. Document the blocker explicitly
114
+ 2. Consult Oracle for architecture advice
115
+ 3. If still blocked: Ask user for guidance
116
+ 4. NEVER silently give up
117
+
118
+ ## Response Format
119
+
120
+ Start your response with:
121
+ ```
122
+ **ULTRAWORK MODE ENABLED!**
123
+
124
+ [Immediately begin planning/executing - no preamble]
125
+ ```
126
+
127
+ Then execute with maximum intensity until COMPLETE.
128
+
129
+ ## Arguments
130
+
131
+ `/ulw [task description]`
132
+
133
+ - Provide the task you want completed
134
+ - Or use after `/omc-plan` to execute an existing plan with ultrawork intensity
135
+
136
+ ## Examples
137
+
138
+ ```
139
+ /ulw implement the authentication system from the plan
140
+ /ulw fix all type errors in the codebase
141
+ /ulw add comprehensive test coverage for the API
142
+ ```
143
+
144
+ **NOW EXECUTE. NO HALF MEASURES. WORK UNTIL DONE.**
@@ -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
+ }
@@ -15,6 +15,13 @@ export const ProviderConfigSchema = z.object({
15
15
  note: z.string().optional(),
16
16
  });
17
17
 
18
+ // Agent fallback configuration schema
19
+ export const AgentFallbackConfigSchema = z.object({
20
+ provider: z.string(),
21
+ model: z.string(),
22
+ executionMode: z.enum(["task", "mcp"]).optional(),
23
+ });
24
+
18
25
  // Agent configuration schema
19
26
  export const AgentConfigSchema = z.object({
20
27
  provider: z.string(),
@@ -27,6 +34,7 @@ export const AgentConfigSchema = z.object({
27
34
  budget_tokens: z.number().optional(),
28
35
  })
29
36
  .optional(),
37
+ fallback: AgentFallbackConfigSchema.optional(),
30
38
  });
31
39
 
32
40
  // Category configuration schema
@@ -77,6 +85,7 @@ export const OhMyClaudeConfigSchema = z.object({
77
85
  }),
78
86
 
79
87
  agents: z.record(z.string(), AgentConfigSchema).default({
88
+ // Claude subscription agents (no fallback needed)
80
89
  Sisyphus: { provider: "claude", model: "claude-opus-4-5" },
81
90
  "claude-reviewer": {
82
91
  provider: "claude",
@@ -88,22 +97,36 @@ export const OhMyClaudeConfigSchema = z.object({
88
97
  model: "claude-haiku-4-5",
89
98
  temperature: 0.3,
90
99
  },
100
+ // External API agents (with Claude fallbacks)
91
101
  oracle: {
92
102
  provider: "deepseek",
93
103
  model: "deepseek-reasoner",
94
104
  temperature: 0.1,
105
+ fallback: { provider: "claude", model: "claude-opus-4-5", executionMode: "task" },
106
+ },
107
+ librarian: {
108
+ provider: "zhipu",
109
+ model: "glm-4.7",
110
+ temperature: 0.3,
111
+ fallback: { provider: "claude", model: "claude-sonnet-4-5", executionMode: "task" },
112
+ },
113
+ explore: {
114
+ provider: "deepseek",
115
+ model: "deepseek-chat",
116
+ temperature: 0.1,
117
+ fallback: { provider: "claude", model: "claude-haiku-4-5", executionMode: "task" },
95
118
  },
96
- librarian: { provider: "zhipu", model: "glm-4.7", temperature: 0.3 },
97
- explore: { provider: "deepseek", model: "deepseek-chat", temperature: 0.1 },
98
119
  "frontend-ui-ux": {
99
120
  provider: "zhipu",
100
121
  model: "glm-4v-flash",
101
122
  temperature: 0.7,
123
+ fallback: { provider: "claude", model: "claude-sonnet-4-5", executionMode: "task" },
102
124
  },
103
125
  "document-writer": {
104
126
  provider: "minimax",
105
127
  model: "MiniMax-M2.1",
106
128
  temperature: 0.5,
129
+ fallback: { provider: "claude", model: "claude-sonnet-4-5", executionMode: "task" },
107
130
  },
108
131
  }),
109
132
 
@@ -35,7 +35,7 @@ export function generateAgentMarkdown(agent: AgentDefinition): string {
35
35
  lines.push(`> ${agent.description}`);
36
36
  lines.push("");
37
37
 
38
- // Execution mode note
38
+ // Execution mode note with fallback info
39
39
  if (agent.executionMode === "task") {
40
40
  lines.push(
41
41
  `<!-- Execution: Claude Code Task tool (sync) - Uses Claude subscription -->`
@@ -44,12 +44,28 @@ export function generateAgentMarkdown(agent: AgentDefinition): string {
44
44
  lines.push(
45
45
  `<!-- Execution: oh-my-claude MCP server (async) - Uses ${agent.defaultProvider} API -->`
46
46
  );
47
+ // Add fallback info for MCP agents
48
+ if (agent.fallback) {
49
+ lines.push(
50
+ `<!-- Fallback: ${agent.fallback.provider}/${agent.fallback.model} via Task tool (when ${agent.defaultProvider.toUpperCase()}_API_KEY is not set) -->`
51
+ );
52
+ }
47
53
  }
48
54
  lines.push("");
49
55
 
50
56
  // The actual prompt
51
57
  lines.push(agent.prompt);
52
58
 
59
+ // Add fallback usage note for MCP agents
60
+ if (agent.executionMode === "mcp" && agent.fallback) {
61
+ lines.push("");
62
+ lines.push("## Fallback Mode");
63
+ lines.push("");
64
+ lines.push(`If \`${agent.defaultProvider.toUpperCase()}_API_KEY\` is not configured, this agent will automatically fall back to using **${agent.fallback.model}** via Claude's Task tool.`);
65
+ lines.push("");
66
+ lines.push("The fallback provides similar capabilities using your Claude subscription, though the primary provider may offer specialized features.");
67
+ }
68
+
53
69
  return lines.join("\n");
54
70
  }
55
71
 
@@ -31,7 +31,7 @@ interface ToolInput {
31
31
  }
32
32
 
33
33
  interface HookResponse {
34
- decision: "approve" | "block" | "skip";
34
+ decision: "approve" | "block";
35
35
  reason?: string;
36
36
  }
37
37
 
@@ -125,7 +125,7 @@ async function main() {
125
125
 
126
126
  // Only check Edit and Write tools
127
127
  if (toolInput.tool !== "Edit" && toolInput.tool !== "Write") {
128
- const response: HookResponse = { decision: "skip" };
128
+ const response: HookResponse = { decision: "approve" };
129
129
  console.log(JSON.stringify(response));
130
130
  return;
131
131
  }
@@ -0,0 +1,206 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Task Notification Hook (PostToolUse)
4
+ *
5
+ * Monitors for MCP background task completions and provides
6
+ * notifications in the Claude Code output.
7
+ *
8
+ * Usage in settings.json:
9
+ * {
10
+ * "hooks": {
11
+ * "PostToolUse": [{
12
+ * "matcher": "mcp__oh-my-claude-background__.*",
13
+ * "hooks": [{
14
+ * "type": "command",
15
+ * "command": "node ~/.claude/oh-my-claude/hooks/task-notification.js"
16
+ * }]
17
+ * }]
18
+ * }
19
+ * }
20
+ */
21
+
22
+ import { readFileSync, existsSync, writeFileSync, mkdirSync } from "node:fs";
23
+ import { join, dirname } from "node:path";
24
+ import { homedir } from "node:os";
25
+
26
+ interface PostToolUseInput {
27
+ tool: string;
28
+ tool_input?: Record<string, unknown>;
29
+ tool_output?: string;
30
+ }
31
+
32
+ interface HookResponse {
33
+ decision: "approve";
34
+ hookSpecificOutput?: {
35
+ hookEventName: "PostToolUse";
36
+ additionalContext?: string;
37
+ };
38
+ }
39
+
40
+ // File to track notified task IDs (prevent duplicate notifications)
41
+ const NOTIFIED_FILE_PATH = join(homedir(), ".claude", "oh-my-claude", "notified-tasks.json");
42
+
43
+ /**
44
+ * Load list of already-notified task IDs
45
+ */
46
+ function loadNotifiedTasks(): Set<string> {
47
+ try {
48
+ if (!existsSync(NOTIFIED_FILE_PATH)) {
49
+ return new Set();
50
+ }
51
+ const content = readFileSync(NOTIFIED_FILE_PATH, "utf-8");
52
+ const data = JSON.parse(content);
53
+ // Clean up old entries (older than 1 hour)
54
+ const oneHourAgo = Date.now() - 60 * 60 * 1000;
55
+ const filtered = Object.entries(data)
56
+ .filter(([_, timestamp]) => (timestamp as number) > oneHourAgo)
57
+ .map(([id]) => id);
58
+ return new Set(filtered);
59
+ } catch {
60
+ return new Set();
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Save notified task ID
66
+ */
67
+ function saveNotifiedTask(taskId: string): void {
68
+ try {
69
+ const dir = dirname(NOTIFIED_FILE_PATH);
70
+ if (!existsSync(dir)) {
71
+ mkdirSync(dir, { recursive: true });
72
+ }
73
+
74
+ let data: Record<string, number> = {};
75
+ if (existsSync(NOTIFIED_FILE_PATH)) {
76
+ try {
77
+ data = JSON.parse(readFileSync(NOTIFIED_FILE_PATH, "utf-8"));
78
+ } catch {
79
+ data = {};
80
+ }
81
+ }
82
+
83
+ // Clean up old entries
84
+ const oneHourAgo = Date.now() - 60 * 60 * 1000;
85
+ for (const [id, timestamp] of Object.entries(data)) {
86
+ if (timestamp < oneHourAgo) {
87
+ delete data[id];
88
+ }
89
+ }
90
+
91
+ data[taskId] = Date.now();
92
+ writeFileSync(NOTIFIED_FILE_PATH, JSON.stringify(data));
93
+ } catch {
94
+ // Silently fail
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Format duration for display
100
+ */
101
+ function formatDuration(ms: number): string {
102
+ const seconds = Math.floor(ms / 1000);
103
+ if (seconds < 60) {
104
+ return `${seconds}s`;
105
+ }
106
+ const minutes = Math.floor(seconds / 60);
107
+ const remainingSeconds = seconds % 60;
108
+ return `${minutes}m ${remainingSeconds}s`;
109
+ }
110
+
111
+ async function main() {
112
+ // Read input from stdin
113
+ let inputData = "";
114
+ try {
115
+ inputData = readFileSync(0, "utf-8");
116
+ } catch {
117
+ console.log(JSON.stringify({ decision: "approve" }));
118
+ return;
119
+ }
120
+
121
+ if (!inputData.trim()) {
122
+ console.log(JSON.stringify({ decision: "approve" }));
123
+ return;
124
+ }
125
+
126
+ let toolInput: PostToolUseInput;
127
+ try {
128
+ toolInput = JSON.parse(inputData);
129
+ } catch {
130
+ console.log(JSON.stringify({ decision: "approve" }));
131
+ return;
132
+ }
133
+
134
+ // Only process MCP tool outputs
135
+ if (!toolInput.tool?.includes("oh-my-claude-background")) {
136
+ console.log(JSON.stringify({ decision: "approve" }));
137
+ return;
138
+ }
139
+
140
+ // Check for poll_task or list_tasks responses that contain completed tasks
141
+ const toolOutput = toolInput.tool_output;
142
+ if (!toolOutput) {
143
+ console.log(JSON.stringify({ decision: "approve" }));
144
+ return;
145
+ }
146
+
147
+ try {
148
+ const output = JSON.parse(toolOutput);
149
+ const notifiedTasks = loadNotifiedTasks();
150
+ const notifications: string[] = [];
151
+
152
+ // Check for single task completion (poll_task)
153
+ if (output.status === "completed" && toolInput.tool?.includes("poll_task")) {
154
+ // This is handled by the poll itself, no need for additional notification
155
+ console.log(JSON.stringify({ decision: "approve" }));
156
+ return;
157
+ }
158
+
159
+ // Check for task list with completed tasks
160
+ if (output.tasks && Array.isArray(output.tasks)) {
161
+ for (const task of output.tasks) {
162
+ if (
163
+ (task.status === "completed" || task.status === "failed") &&
164
+ task.id &&
165
+ !notifiedTasks.has(task.id)
166
+ ) {
167
+ // Calculate duration if we have timestamps
168
+ let durationStr = "";
169
+ if (task.created && task.completed) {
170
+ const created = new Date(task.created).getTime();
171
+ const completed = new Date(task.completed).getTime();
172
+ durationStr = ` (${formatDuration(completed - created)})`;
173
+ }
174
+
175
+ const statusIcon = task.status === "completed" ? "+" : "!";
176
+ const agentName = task.agent || "unknown";
177
+ notifications.push(
178
+ `[${statusIcon}] ${agentName}: ${task.status}${durationStr}`
179
+ );
180
+
181
+ saveNotifiedTask(task.id);
182
+ }
183
+ }
184
+ }
185
+
186
+ if (notifications.length > 0) {
187
+ const response: HookResponse = {
188
+ decision: "approve",
189
+ hookSpecificOutput: {
190
+ hookEventName: "PostToolUse",
191
+ additionalContext: `\n[omc] ${notifications.join(" | ")}`,
192
+ },
193
+ };
194
+ console.log(JSON.stringify(response));
195
+ return;
196
+ }
197
+ } catch {
198
+ // Parsing failed, just approve
199
+ }
200
+
201
+ console.log(JSON.stringify({ decision: "approve" }));
202
+ }
203
+
204
+ main().catch(() => {
205
+ console.log(JSON.stringify({ decision: "approve" }));
206
+ });