@lgcyaxi/oh-my-claude 1.0.1 → 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.
- package/.github/ISSUE_TEMPLATE/bug_report.md +43 -0
- package/CHANGELOG.md +22 -0
- package/CLAUDE.md +60 -0
- package/README.md +69 -1
- package/README.zh-CN.md +30 -0
- package/changelog/v1.1.0.md +20 -0
- package/changelog/v1.1.1.md +71 -0
- package/dist/cli.js +121 -4
- package/dist/hooks/comment-checker.js +1 -1
- package/dist/hooks/task-notification.js +124 -0
- package/dist/hooks/task-tracker.js +144 -0
- package/dist/index-1dv6t98k.js +7654 -0
- package/dist/index-d79fk9ah.js +7350 -0
- package/dist/index-hzm01rkh.js +7654 -0
- package/dist/index-qrbfj4cd.js +7664 -0
- package/dist/index-ypyx3ye0.js +7349 -0
- package/dist/index.js +14 -1
- package/dist/mcp/server.js +64 -28
- package/dist/statusline/statusline.js +146 -0
- package/package.json +4 -3
- package/src/cli.ts +136 -2
- package/src/commands/index.ts +8 -1
- package/src/commands/omc-status.md +71 -0
- package/src/commands/omcx-issue.md +175 -0
- package/src/commands/ulw.md +144 -0
- package/src/hooks/comment-checker.ts +2 -2
- package/src/hooks/task-notification.ts +206 -0
- package/src/hooks/task-tracker.ts +252 -0
- package/src/installer/index.ts +55 -4
- package/src/installer/settings-merger.ts +86 -0
- package/src/installer/statusline-merger.ts +169 -0
- package/src/mcp/background-agent-server/server.ts +5 -0
- package/src/mcp/background-agent-server/task-manager.ts +53 -0
- package/src/statusline/formatter.ts +164 -0
- package/src/statusline/statusline.ts +103 -0
package/dist/index.js
CHANGED
|
@@ -39,6 +39,10 @@ import {
|
|
|
39
39
|
getMcpServerPath,
|
|
40
40
|
getProviderDetails,
|
|
41
41
|
getProvidersStatus,
|
|
42
|
+
getStatusLineScriptPath,
|
|
43
|
+
init_agent_generator,
|
|
44
|
+
init_agents,
|
|
45
|
+
init_installer,
|
|
42
46
|
install,
|
|
43
47
|
isMcpAgent,
|
|
44
48
|
isProviderConfigured,
|
|
@@ -58,7 +62,10 @@ import {
|
|
|
58
62
|
sisyphusAgent,
|
|
59
63
|
taskAgents,
|
|
60
64
|
uninstall
|
|
61
|
-
} from "./index-
|
|
65
|
+
} from "./index-qrbfj4cd.js";
|
|
66
|
+
|
|
67
|
+
// src/index.ts
|
|
68
|
+
init_agents();
|
|
62
69
|
// src/providers/deepseek.ts
|
|
63
70
|
var DEEPSEEK_BASE_URL = "https://api.deepseek.com/anthropic";
|
|
64
71
|
var DEEPSEEK_API_KEY_ENV = "DEEPSEEK_API_KEY";
|
|
@@ -80,6 +87,11 @@ function createMiniMaxClient() {
|
|
|
80
87
|
return createAnthropicClientFromEnv("MiniMax", MINIMAX_BASE_URL, MINIMAX_API_KEY_ENV, "MiniMax-M2.1");
|
|
81
88
|
}
|
|
82
89
|
var minimaxClient = createMiniMaxClient();
|
|
90
|
+
// src/generators/index.ts
|
|
91
|
+
init_agent_generator();
|
|
92
|
+
|
|
93
|
+
// src/index.ts
|
|
94
|
+
init_installer();
|
|
83
95
|
export {
|
|
84
96
|
zhipuClient,
|
|
85
97
|
uninstall,
|
|
@@ -103,6 +115,7 @@ export {
|
|
|
103
115
|
isProviderConfigured,
|
|
104
116
|
isMcpAgent,
|
|
105
117
|
install,
|
|
118
|
+
getStatusLineScriptPath,
|
|
106
119
|
getProvidersStatus,
|
|
107
120
|
getProviderDetails,
|
|
108
121
|
getMcpServerPath,
|
package/dist/mcp/server.js
CHANGED
|
@@ -18573,10 +18573,65 @@ function getAgent(name) {
|
|
|
18573
18573
|
}
|
|
18574
18574
|
|
|
18575
18575
|
// src/mcp/background-agent-server/task-manager.ts
|
|
18576
|
+
import { writeFileSync, mkdirSync, existsSync as existsSync2 } from "node:fs";
|
|
18577
|
+
import { join as join2, dirname } from "node:path";
|
|
18578
|
+
import { homedir as homedir2 } from "node:os";
|
|
18579
|
+
|
|
18580
|
+
// src/mcp/background-agent-server/concurrency.ts
|
|
18581
|
+
var activeCounts = new Map;
|
|
18582
|
+
var queues = new Map;
|
|
18583
|
+
function getConcurrencyLimit(provider) {
|
|
18584
|
+
const config2 = loadConfig();
|
|
18585
|
+
const perProvider = config2.concurrency.per_provider?.[provider];
|
|
18586
|
+
if (perProvider !== undefined) {
|
|
18587
|
+
return perProvider;
|
|
18588
|
+
}
|
|
18589
|
+
return config2.concurrency.default;
|
|
18590
|
+
}
|
|
18591
|
+
function getActiveCount(provider) {
|
|
18592
|
+
return activeCounts.get(provider) ?? 0;
|
|
18593
|
+
}
|
|
18594
|
+
function getConcurrencyStatus() {
|
|
18595
|
+
const config2 = loadConfig();
|
|
18596
|
+
const status = {};
|
|
18597
|
+
const providers = Object.keys(config2.providers);
|
|
18598
|
+
for (const provider of providers) {
|
|
18599
|
+
status[provider] = {
|
|
18600
|
+
active: getActiveCount(provider),
|
|
18601
|
+
limit: getConcurrencyLimit(provider),
|
|
18602
|
+
queued: queues.get(provider)?.length ?? 0
|
|
18603
|
+
};
|
|
18604
|
+
}
|
|
18605
|
+
return status;
|
|
18606
|
+
}
|
|
18607
|
+
|
|
18608
|
+
// src/mcp/background-agent-server/task-manager.ts
|
|
18609
|
+
var STATUS_FILE_PATH = join2(homedir2(), ".claude", "oh-my-claude", "status.json");
|
|
18576
18610
|
var tasks = new Map;
|
|
18577
18611
|
function generateTaskId() {
|
|
18578
18612
|
return `task_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
18579
18613
|
}
|
|
18614
|
+
function updateStatusFile() {
|
|
18615
|
+
try {
|
|
18616
|
+
const dir = dirname(STATUS_FILE_PATH);
|
|
18617
|
+
if (!existsSync2(dir)) {
|
|
18618
|
+
mkdirSync(dir, { recursive: true });
|
|
18619
|
+
}
|
|
18620
|
+
const activeTasks = Array.from(tasks.values()).filter((t) => t.status === "running" || t.status === "pending").map((t) => ({
|
|
18621
|
+
agent: t.agentName || t.categoryName || "unknown",
|
|
18622
|
+
startedAt: t.startedAt || t.createdAt
|
|
18623
|
+
}));
|
|
18624
|
+
const providers = getConcurrencyStatus();
|
|
18625
|
+
const status = {
|
|
18626
|
+
activeTasks,
|
|
18627
|
+
providers,
|
|
18628
|
+
updatedAt: new Date().toISOString()
|
|
18629
|
+
};
|
|
18630
|
+
writeFileSync(STATUS_FILE_PATH, JSON.stringify(status, null, 2));
|
|
18631
|
+
} catch (error2) {
|
|
18632
|
+
console.error("Failed to update status file:", error2);
|
|
18633
|
+
}
|
|
18634
|
+
}
|
|
18580
18635
|
async function launchTask(options) {
|
|
18581
18636
|
const { agentName, categoryName, prompt, systemPrompt } = options;
|
|
18582
18637
|
if (!agentName && !categoryName) {
|
|
@@ -18599,6 +18654,7 @@ async function launchTask(options) {
|
|
|
18599
18654
|
createdAt: Date.now()
|
|
18600
18655
|
};
|
|
18601
18656
|
tasks.set(taskId, task);
|
|
18657
|
+
updateStatusFile();
|
|
18602
18658
|
runTask(task, finalSystemPrompt).catch((error2) => {
|
|
18603
18659
|
console.error(`Task ${taskId} failed:`, error2);
|
|
18604
18660
|
});
|
|
@@ -18608,6 +18664,7 @@ async function runTask(task, systemPrompt) {
|
|
|
18608
18664
|
task.status = "running";
|
|
18609
18665
|
task.startedAt = Date.now();
|
|
18610
18666
|
tasks.set(task.id, task);
|
|
18667
|
+
updateStatusFile();
|
|
18611
18668
|
try {
|
|
18612
18669
|
const messages = [];
|
|
18613
18670
|
if (systemPrompt) {
|
|
@@ -18633,6 +18690,7 @@ async function runTask(task, systemPrompt) {
|
|
|
18633
18690
|
task.result = result;
|
|
18634
18691
|
task.completedAt = Date.now();
|
|
18635
18692
|
tasks.set(task.id, task);
|
|
18693
|
+
updateStatusFile();
|
|
18636
18694
|
} catch (error2) {
|
|
18637
18695
|
if (error2 instanceof FallbackRequiredError) {
|
|
18638
18696
|
task.status = "fallback_required";
|
|
@@ -18649,6 +18707,7 @@ async function runTask(task, systemPrompt) {
|
|
|
18649
18707
|
}
|
|
18650
18708
|
task.completedAt = Date.now();
|
|
18651
18709
|
tasks.set(task.id, task);
|
|
18710
|
+
updateStatusFile();
|
|
18652
18711
|
}
|
|
18653
18712
|
}
|
|
18654
18713
|
function pollTask(taskId) {
|
|
@@ -18672,6 +18731,7 @@ function cancelTask(taskId) {
|
|
|
18672
18731
|
task.status = "cancelled";
|
|
18673
18732
|
task.completedAt = Date.now();
|
|
18674
18733
|
tasks.set(taskId, task);
|
|
18734
|
+
updateStatusFile();
|
|
18675
18735
|
return true;
|
|
18676
18736
|
}
|
|
18677
18737
|
return false;
|
|
@@ -18686,6 +18746,9 @@ function cancelAllTasks() {
|
|
|
18686
18746
|
cancelled++;
|
|
18687
18747
|
}
|
|
18688
18748
|
}
|
|
18749
|
+
if (cancelled > 0) {
|
|
18750
|
+
updateStatusFile();
|
|
18751
|
+
}
|
|
18689
18752
|
return cancelled;
|
|
18690
18753
|
}
|
|
18691
18754
|
function listTasks(options) {
|
|
@@ -18711,34 +18774,6 @@ function cleanupTasks(maxAgeMs = 30 * 60 * 1000) {
|
|
|
18711
18774
|
return cleaned;
|
|
18712
18775
|
}
|
|
18713
18776
|
|
|
18714
|
-
// src/mcp/background-agent-server/concurrency.ts
|
|
18715
|
-
var activeCounts = new Map;
|
|
18716
|
-
var queues = new Map;
|
|
18717
|
-
function getConcurrencyLimit(provider) {
|
|
18718
|
-
const config2 = loadConfig();
|
|
18719
|
-
const perProvider = config2.concurrency.per_provider?.[provider];
|
|
18720
|
-
if (perProvider !== undefined) {
|
|
18721
|
-
return perProvider;
|
|
18722
|
-
}
|
|
18723
|
-
return config2.concurrency.default;
|
|
18724
|
-
}
|
|
18725
|
-
function getActiveCount(provider) {
|
|
18726
|
-
return activeCounts.get(provider) ?? 0;
|
|
18727
|
-
}
|
|
18728
|
-
function getConcurrencyStatus() {
|
|
18729
|
-
const config2 = loadConfig();
|
|
18730
|
-
const status = {};
|
|
18731
|
-
const providers = Object.keys(config2.providers);
|
|
18732
|
-
for (const provider of providers) {
|
|
18733
|
-
status[provider] = {
|
|
18734
|
-
active: getActiveCount(provider),
|
|
18735
|
-
limit: getConcurrencyLimit(provider),
|
|
18736
|
-
queued: queues.get(provider)?.length ?? 0
|
|
18737
|
-
};
|
|
18738
|
-
}
|
|
18739
|
-
return status;
|
|
18740
|
-
}
|
|
18741
|
-
|
|
18742
18777
|
// src/mcp/background-agent-server/server.ts
|
|
18743
18778
|
var tools = [
|
|
18744
18779
|
{
|
|
@@ -19032,6 +19067,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
19032
19067
|
async function main() {
|
|
19033
19068
|
const transport = new StdioServerTransport;
|
|
19034
19069
|
await server.connect(transport);
|
|
19070
|
+
updateStatusFile();
|
|
19035
19071
|
console.error("oh-my-claude Background Agent MCP Server running");
|
|
19036
19072
|
}
|
|
19037
19073
|
main().catch((error2) => {
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/statusline/statusline.ts
|
|
4
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
|
|
8
|
+
// src/statusline/formatter.ts
|
|
9
|
+
var AGENT_ABBREV = {
|
|
10
|
+
oracle: "Oracle",
|
|
11
|
+
librarian: "Lib",
|
|
12
|
+
explore: "Exp",
|
|
13
|
+
"frontend-ui-ux": "UI",
|
|
14
|
+
"document-writer": "Doc"
|
|
15
|
+
};
|
|
16
|
+
var TASK_AGENT_ABBREV = {
|
|
17
|
+
Scout: "Scout",
|
|
18
|
+
Planner: "Plan",
|
|
19
|
+
General: "Gen",
|
|
20
|
+
Guide: "Guide",
|
|
21
|
+
Bash: "Bash"
|
|
22
|
+
};
|
|
23
|
+
var PROVIDER_ABBREV = {
|
|
24
|
+
deepseek: "DS",
|
|
25
|
+
zhipu: "ZP",
|
|
26
|
+
minimax: "MM",
|
|
27
|
+
openrouter: "OR"
|
|
28
|
+
};
|
|
29
|
+
function formatDuration(seconds) {
|
|
30
|
+
if (seconds < 60) {
|
|
31
|
+
return `${Math.floor(seconds)}s`;
|
|
32
|
+
}
|
|
33
|
+
const minutes = Math.floor(seconds / 60);
|
|
34
|
+
return `${minutes}m`;
|
|
35
|
+
}
|
|
36
|
+
function getAgentAbbrev(agent) {
|
|
37
|
+
if (agent.startsWith("@")) {
|
|
38
|
+
const taskAgent = agent.slice(1);
|
|
39
|
+
return `@${TASK_AGENT_ABBREV[taskAgent] || taskAgent.slice(0, 4)}`;
|
|
40
|
+
}
|
|
41
|
+
return AGENT_ABBREV[agent.toLowerCase()] || agent.slice(0, 4);
|
|
42
|
+
}
|
|
43
|
+
function getProviderAbbrev(provider) {
|
|
44
|
+
return PROVIDER_ABBREV[provider.toLowerCase()] || provider.slice(0, 2).toUpperCase();
|
|
45
|
+
}
|
|
46
|
+
function formatStatusLine(data) {
|
|
47
|
+
const parts = ["omc"];
|
|
48
|
+
const now = Date.now();
|
|
49
|
+
if (data.activeTasks.length > 0) {
|
|
50
|
+
const tasksToShow = data.activeTasks.slice(0, 3);
|
|
51
|
+
for (const task of tasksToShow) {
|
|
52
|
+
const durationSec = Math.floor((now - task.startedAt) / 1000);
|
|
53
|
+
const agentName = getAgentAbbrev(task.agent);
|
|
54
|
+
const duration = formatDuration(durationSec);
|
|
55
|
+
parts.push(`[${agentName}: ${duration}]`);
|
|
56
|
+
}
|
|
57
|
+
if (data.activeTasks.length > 3) {
|
|
58
|
+
parts.push(`+${data.activeTasks.length - 3}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const providerParts = [];
|
|
62
|
+
const providersToShow = ["deepseek", "zhipu", "minimax"];
|
|
63
|
+
for (const provider of providersToShow) {
|
|
64
|
+
const status = data.providers[provider];
|
|
65
|
+
if (status && (status.active > 0 || data.activeTasks.length > 0)) {
|
|
66
|
+
const abbrev = getProviderAbbrev(provider);
|
|
67
|
+
providerParts.push(`${abbrev}: ${status.active}/${status.limit}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (providerParts.length > 0) {
|
|
71
|
+
parts.push("|");
|
|
72
|
+
parts.push(...providerParts);
|
|
73
|
+
}
|
|
74
|
+
return parts.join(" ");
|
|
75
|
+
}
|
|
76
|
+
function formatEmptyStatusLine() {
|
|
77
|
+
return "omc ready";
|
|
78
|
+
}
|
|
79
|
+
function formatIdleStatusLine(providers) {
|
|
80
|
+
const parts = ["omc ready"];
|
|
81
|
+
const providerParts = [];
|
|
82
|
+
const providersToShow = ["deepseek", "zhipu", "minimax"];
|
|
83
|
+
for (const provider of providersToShow) {
|
|
84
|
+
const status = providers[provider];
|
|
85
|
+
if (status && status.limit > 0) {
|
|
86
|
+
const abbrev = getProviderAbbrev(provider);
|
|
87
|
+
providerParts.push(`${abbrev}: ${status.limit}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (providerParts.length > 0) {
|
|
91
|
+
parts.push("|");
|
|
92
|
+
parts.push(...providerParts);
|
|
93
|
+
}
|
|
94
|
+
return parts.join(" ");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/statusline/statusline.ts
|
|
98
|
+
var STATUS_FILE_PATH = join(homedir(), ".claude", "oh-my-claude", "status.json");
|
|
99
|
+
var TIMEOUT_MS = 100;
|
|
100
|
+
function readStatusFile() {
|
|
101
|
+
try {
|
|
102
|
+
if (!existsSync(STATUS_FILE_PATH)) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
const content = readFileSync(STATUS_FILE_PATH, "utf-8");
|
|
106
|
+
const data = JSON.parse(content);
|
|
107
|
+
if (!data || typeof data !== "object") {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
if (data.updatedAt) {
|
|
111
|
+
const updatedAt = new Date(data.updatedAt).getTime();
|
|
112
|
+
const age = Date.now() - updatedAt;
|
|
113
|
+
if (age > 5 * 60 * 1000) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return data;
|
|
118
|
+
} catch {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async function main() {
|
|
123
|
+
const timeoutId = setTimeout(() => {
|
|
124
|
+
console.log(formatEmptyStatusLine());
|
|
125
|
+
process.exit(0);
|
|
126
|
+
}, TIMEOUT_MS);
|
|
127
|
+
try {
|
|
128
|
+
let _input = "";
|
|
129
|
+
try {
|
|
130
|
+
_input = readFileSync(0, "utf-8");
|
|
131
|
+
} catch {}
|
|
132
|
+
const statusData = readStatusFile();
|
|
133
|
+
if (!statusData) {
|
|
134
|
+
console.log(formatEmptyStatusLine());
|
|
135
|
+
} else if (statusData.activeTasks.length === 0) {
|
|
136
|
+
console.log(formatIdleStatusLine(statusData.providers));
|
|
137
|
+
} else {
|
|
138
|
+
console.log(formatStatusLine(statusData));
|
|
139
|
+
}
|
|
140
|
+
} catch {
|
|
141
|
+
console.log(formatEmptyStatusLine());
|
|
142
|
+
} finally {
|
|
143
|
+
clearTimeout(timeoutId);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
main();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lgcyaxi/oh-my-claude",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.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",
|
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
"scripts": {
|
|
12
12
|
"build": "bun build src/index.ts src/cli.ts --outdir dist --target node --splitting",
|
|
13
13
|
"build:mcp": "bun build src/mcp/background-agent-server/server.ts --outdir dist/mcp --target node",
|
|
14
|
-
"build:hooks": "bun build src/hooks/comment-checker.ts src/hooks/todo-continuation.ts --outdir dist/hooks --target node",
|
|
15
|
-
"build:
|
|
14
|
+
"build:hooks": "bun build src/hooks/comment-checker.ts src/hooks/todo-continuation.ts src/hooks/task-notification.ts src/hooks/task-tracker.ts --outdir dist/hooks --target node",
|
|
15
|
+
"build:statusline": "bun build src/statusline/statusline.ts --outdir dist/statusline --target node",
|
|
16
|
+
"build:all": "bun run build && bun run build:mcp && bun run build:hooks && bun run build:statusline",
|
|
16
17
|
"typecheck": "tsc --noEmit",
|
|
17
18
|
"test": "bun test",
|
|
18
19
|
"dev": "bun run --watch src/index.ts",
|
package/src/cli.ts
CHANGED
|
@@ -19,7 +19,7 @@ import { loadConfig } from "./config";
|
|
|
19
19
|
program
|
|
20
20
|
.name("oh-my-claude")
|
|
21
21
|
.description("Multi-agent orchestration plugin for Claude Code")
|
|
22
|
-
.version("1.
|
|
22
|
+
.version("1.1.1");
|
|
23
23
|
|
|
24
24
|
// Install command
|
|
25
25
|
program
|
|
@@ -206,6 +206,7 @@ program
|
|
|
206
206
|
console.log(` ${status.components.agents ? ok("Agent files generated") : fail("Agent files generated")}`);
|
|
207
207
|
console.log(` ${status.components.hooks ? ok("Hooks configured") : fail("Hooks configured")}`);
|
|
208
208
|
console.log(` ${status.components.mcp ? ok("MCP server configured") : fail("MCP server configured")}`);
|
|
209
|
+
console.log(` ${status.components.statusLine ? ok("StatusLine configured") : warn("StatusLine not configured")}`);
|
|
209
210
|
console.log(` ${status.components.config ? ok("Configuration file exists") : fail("Configuration file exists")}`);
|
|
210
211
|
|
|
211
212
|
|
|
@@ -243,11 +244,13 @@ program
|
|
|
243
244
|
"omc-explore",
|
|
244
245
|
"omc-plan",
|
|
245
246
|
"omc-start-work",
|
|
247
|
+
"omc-status",
|
|
246
248
|
// Quick action commands
|
|
247
249
|
"omcx-commit",
|
|
248
250
|
"omcx-implement",
|
|
249
251
|
"omcx-refactor",
|
|
250
252
|
"omcx-docs",
|
|
253
|
+
"omcx-issue",
|
|
251
254
|
];
|
|
252
255
|
console.log(` ${subheader("Agent commands (omc-):")}`);
|
|
253
256
|
for (const cmd of expectedCommands.filter(c => c.startsWith("omc-"))) {
|
|
@@ -291,12 +294,43 @@ program
|
|
|
291
294
|
// Hooks detail
|
|
292
295
|
console.log(`\n${header("Hooks (detailed):")}`);
|
|
293
296
|
const hooksDir = join(homedir(), ".claude", "oh-my-claude", "hooks");
|
|
294
|
-
const expectedHooks = ["comment-checker.js", "todo-continuation.js"];
|
|
297
|
+
const expectedHooks = ["comment-checker.js", "todo-continuation.js", "task-notification.js"];
|
|
295
298
|
for (const hook of expectedHooks) {
|
|
296
299
|
const hookPath = join(hooksDir, hook);
|
|
297
300
|
const exists = existsSync(hookPath);
|
|
298
301
|
console.log(` ${exists ? ok(hook) : fail(hook)}`);
|
|
299
302
|
}
|
|
303
|
+
|
|
304
|
+
// StatusLine detail
|
|
305
|
+
console.log(`\n${header("StatusLine (detailed):")}`);
|
|
306
|
+
const statusLineDir = join(homedir(), ".claude", "oh-my-claude", "dist", "statusline");
|
|
307
|
+
const statusLineScript = join(statusLineDir, "statusline.js");
|
|
308
|
+
const statusFileExists = existsSync(statusLineScript);
|
|
309
|
+
console.log(` ${statusFileExists ? ok("statusline.js installed") : fail("statusline.js not installed")}`);
|
|
310
|
+
|
|
311
|
+
try {
|
|
312
|
+
const settingsPath = join(homedir(), ".claude", "settings.json");
|
|
313
|
+
if (existsSync(settingsPath)) {
|
|
314
|
+
const settings = JSON.parse(require("node:fs").readFileSync(settingsPath, "utf-8"));
|
|
315
|
+
if (settings.statusLine) {
|
|
316
|
+
const cmd = settings.statusLine.command || "";
|
|
317
|
+
const isOurs = cmd.includes("oh-my-claude");
|
|
318
|
+
const isWrapper = cmd.includes("statusline-wrapper");
|
|
319
|
+
console.log(` ${ok("StatusLine configured in settings.json")}`);
|
|
320
|
+
if (isWrapper) {
|
|
321
|
+
console.log(` Mode: ${c.yellow}Merged (wrapper)${c.reset}`);
|
|
322
|
+
} else if (isOurs) {
|
|
323
|
+
console.log(` Mode: ${c.green}Direct${c.reset}`);
|
|
324
|
+
} else {
|
|
325
|
+
console.log(` Mode: ${c.cyan}External${c.reset}`);
|
|
326
|
+
}
|
|
327
|
+
} else {
|
|
328
|
+
console.log(` ${warn("StatusLine not configured in settings.json")}`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
} catch {
|
|
332
|
+
console.log(` ${fail("Failed to read settings.json")}`);
|
|
333
|
+
}
|
|
300
334
|
}
|
|
301
335
|
|
|
302
336
|
// Check providers
|
|
@@ -487,6 +521,106 @@ program
|
|
|
487
521
|
}
|
|
488
522
|
});
|
|
489
523
|
|
|
524
|
+
// StatusLine command
|
|
525
|
+
program
|
|
526
|
+
.command("statusline")
|
|
527
|
+
.description("Manage statusline integration")
|
|
528
|
+
.option("--enable", "Enable statusline")
|
|
529
|
+
.option("--disable", "Disable statusline")
|
|
530
|
+
.option("--status", "Show current statusline configuration")
|
|
531
|
+
.action((options) => {
|
|
532
|
+
const { readFileSync, existsSync } = require("node:fs");
|
|
533
|
+
const { join } = require("node:path");
|
|
534
|
+
const { homedir } = require("node:os");
|
|
535
|
+
|
|
536
|
+
// Color helpers
|
|
537
|
+
const useColor = process.stdout.isTTY;
|
|
538
|
+
const c = {
|
|
539
|
+
reset: useColor ? "\x1b[0m" : "",
|
|
540
|
+
bold: useColor ? "\x1b[1m" : "",
|
|
541
|
+
dim: useColor ? "\x1b[2m" : "",
|
|
542
|
+
green: useColor ? "\x1b[32m" : "",
|
|
543
|
+
red: useColor ? "\x1b[31m" : "",
|
|
544
|
+
yellow: useColor ? "\x1b[33m" : "",
|
|
545
|
+
cyan: useColor ? "\x1b[36m" : "",
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
const ok = (text: string) => `${c.green}+${c.reset} ${text}`;
|
|
549
|
+
const fail = (text: string) => `${c.red}x${c.reset} ${text}`;
|
|
550
|
+
const warn = (text: string) => `${c.yellow}!${c.reset} ${text}`;
|
|
551
|
+
|
|
552
|
+
const settingsPath = join(homedir(), ".claude", "settings.json");
|
|
553
|
+
|
|
554
|
+
if (options.status || (!options.enable && !options.disable)) {
|
|
555
|
+
// Show status
|
|
556
|
+
console.log(`${c.bold}StatusLine Status${c.reset}\n`);
|
|
557
|
+
|
|
558
|
+
if (!existsSync(settingsPath)) {
|
|
559
|
+
console.log(fail("settings.json not found"));
|
|
560
|
+
process.exit(1);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
try {
|
|
564
|
+
const settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
565
|
+
|
|
566
|
+
if (!settings.statusLine) {
|
|
567
|
+
console.log(fail("StatusLine not configured"));
|
|
568
|
+
console.log(`\nRun ${c.cyan}oh-my-claude statusline --enable${c.reset} to enable.`);
|
|
569
|
+
} else {
|
|
570
|
+
const cmd = settings.statusLine.command || "";
|
|
571
|
+
const isOurs = cmd.includes("oh-my-claude");
|
|
572
|
+
const isWrapper = cmd.includes("statusline-wrapper");
|
|
573
|
+
|
|
574
|
+
console.log(ok("StatusLine configured"));
|
|
575
|
+
console.log(` Command: ${c.dim}${cmd}${c.reset}`);
|
|
576
|
+
|
|
577
|
+
if (isWrapper) {
|
|
578
|
+
console.log(` Mode: ${c.yellow}Merged (wrapper)${c.reset}`);
|
|
579
|
+
} else if (isOurs) {
|
|
580
|
+
console.log(` Mode: ${c.green}Direct${c.reset}`);
|
|
581
|
+
} else {
|
|
582
|
+
console.log(` Mode: ${c.cyan}External${c.reset}`);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
} catch (error) {
|
|
586
|
+
console.log(fail(`Failed to read settings: ${error}`));
|
|
587
|
+
process.exit(1);
|
|
588
|
+
}
|
|
589
|
+
} else if (options.enable) {
|
|
590
|
+
// Enable statusline
|
|
591
|
+
const { installStatusLine } = require("./installer/settings-merger");
|
|
592
|
+
const { getStatusLineScriptPath } = require("./installer");
|
|
593
|
+
|
|
594
|
+
try {
|
|
595
|
+
const result = installStatusLine(getStatusLineScriptPath());
|
|
596
|
+
if (result.installed) {
|
|
597
|
+
console.log(ok("StatusLine enabled"));
|
|
598
|
+
if (result.wrapperCreated) {
|
|
599
|
+
console.log(warn("Wrapper created to merge with existing statusLine"));
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
} catch (error) {
|
|
603
|
+
console.log(fail(`Failed to enable statusline: ${error}`));
|
|
604
|
+
process.exit(1);
|
|
605
|
+
}
|
|
606
|
+
} else if (options.disable) {
|
|
607
|
+
// Disable statusline
|
|
608
|
+
const { uninstallStatusLine } = require("./installer/settings-merger");
|
|
609
|
+
|
|
610
|
+
try {
|
|
611
|
+
const result = uninstallStatusLine();
|
|
612
|
+
if (result) {
|
|
613
|
+
console.log(ok("StatusLine disabled"));
|
|
614
|
+
} else {
|
|
615
|
+
console.log(warn("StatusLine was not configured"));
|
|
616
|
+
}
|
|
617
|
+
} catch (error) {
|
|
618
|
+
console.log(fail(`Failed to disable statusline: ${error}`));
|
|
619
|
+
process.exit(1);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
});
|
|
623
|
+
|
|
490
624
|
// Setup MCP command
|
|
491
625
|
program
|
|
492
626
|
.command("setup-mcp")
|
package/src/commands/index.ts
CHANGED
|
@@ -18,6 +18,7 @@ export const agentCommands = [
|
|
|
18
18
|
"omc-explore",
|
|
19
19
|
"omc-plan",
|
|
20
20
|
"omc-start-work",
|
|
21
|
+
"omc-status",
|
|
21
22
|
] as const;
|
|
22
23
|
|
|
23
24
|
// Quick action commands (omcx- prefix)
|
|
@@ -26,10 +27,16 @@ export const actionCommands = [
|
|
|
26
27
|
"omcx-implement",
|
|
27
28
|
"omcx-refactor",
|
|
28
29
|
"omcx-docs",
|
|
30
|
+
"omcx-issue",
|
|
31
|
+
] as const;
|
|
32
|
+
|
|
33
|
+
// Special mode commands
|
|
34
|
+
export const modeCommands = [
|
|
35
|
+
"ulw", // Ultrawork mode - work until done
|
|
29
36
|
] as const;
|
|
30
37
|
|
|
31
38
|
// All commands
|
|
32
|
-
export const commands = [...agentCommands, ...actionCommands] as const;
|
|
39
|
+
export const commands = [...agentCommands, ...actionCommands, ...modeCommands] as const;
|
|
33
40
|
|
|
34
41
|
export type CommandName = typeof commands[number];
|
|
35
42
|
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# /omc-status
|
|
2
|
+
|
|
3
|
+
Display status dashboard for oh-my-claude MCP background agents.
|
|
4
|
+
|
|
5
|
+
## Instructions
|
|
6
|
+
|
|
7
|
+
The user wants to see the current status of MCP background agents. Display a formatted ASCII dashboard showing:
|
|
8
|
+
|
|
9
|
+
1. **Active Tasks** - Currently running background tasks
|
|
10
|
+
2. **Provider Concurrency** - Usage bars for each provider
|
|
11
|
+
3. **Recent Tasks** - Last 5 completed/failed tasks
|
|
12
|
+
|
|
13
|
+
**Step 1: Get system status**
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
Use mcp__oh-my-claude-background__get_status to get:
|
|
17
|
+
- Provider configuration (which have API keys)
|
|
18
|
+
- Concurrency limits per provider
|
|
19
|
+
- Current active task counts
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Step 2: Get task list**
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
Use mcp__oh-my-claude-background__list_tasks with:
|
|
26
|
+
- limit: 20
|
|
27
|
+
(Gets recent tasks including running, completed, failed, fallback_required)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Step 3: Display dashboard**
|
|
31
|
+
|
|
32
|
+
Format the output as an ASCII dashboard:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
oh-my-claude Status Dashboard
|
|
36
|
+
==============================
|
|
37
|
+
|
|
38
|
+
ACTIVE TASKS (count)
|
|
39
|
+
[agent] task_id... Provider [progress] status duration
|
|
40
|
+
|
|
41
|
+
PROVIDER CONCURRENCY
|
|
42
|
+
Provider: [bar] active/limit active, queued queued
|
|
43
|
+
|
|
44
|
+
RECENT TASKS (last 5)
|
|
45
|
+
[status] task_id agent time_ago
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**ASCII Progress Bar Helper:**
|
|
49
|
+
|
|
50
|
+
For concurrency bars, use:
|
|
51
|
+
- Full blocks: active tasks
|
|
52
|
+
- Empty blocks: remaining capacity
|
|
53
|
+
- 10 character width total
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
Example: 4/10 active -> "4/10 active"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Status indicators:**
|
|
60
|
+
- `running` - Task is executing
|
|
61
|
+
- `completed` - Task finished successfully
|
|
62
|
+
- `failed` - Task encountered an error
|
|
63
|
+
- `fallback_required` - Provider API key not configured
|
|
64
|
+
|
|
65
|
+
**If no tasks:**
|
|
66
|
+
Display a message that no background tasks have been run yet, and suggest using `/omc-oracle` or other agent commands.
|
|
67
|
+
|
|
68
|
+
**If MCP server not available:**
|
|
69
|
+
Display an error message suggesting to run `oh-my-claude doctor` to check configuration.
|
|
70
|
+
|
|
71
|
+
Now gather the status and display the dashboard.
|