@bbyx0011/ghostcode 0.1.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/bin/ghostcode-mcp +11 -0
- package/bin/ghostcode-mcp-darwin-arm64 +0 -0
- package/bin/ghostcode-mcp-darwin-x64 +0 -0
- package/bin/ghostcode-mcp-linux-x64 +0 -0
- package/bin/ghostcode-wrapper +11 -0
- package/bin/ghostcode-wrapper-darwin-arm64 +0 -0
- package/bin/ghostcode-wrapper-darwin-x64 +0 -0
- package/bin/ghostcode-wrapper-linux-x64 +0 -0
- package/bin/ghostcoded-darwin-arm64 +0 -0
- package/bin/ghostcoded-darwin-x64 +0 -0
- package/bin/ghostcoded-linux-x64 +0 -0
- package/dist/cli.d.ts +21 -0
- package/dist/cli.js +97 -0
- package/dist/daemon.d.ts +72 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +35 -0
- package/dist/install.d.ts +61 -0
- package/dist/ipc.d.ts +121 -0
- package/dist/postinstall.d.ts +42 -0
- package/dist/session-lease.d.ts +110 -0
- package/dist/web.d.ts +51 -0
- package/hooks/hooks.json +101 -0
- package/package.json +29 -0
- package/prompts/codex-analyzer.md +64 -0
- package/prompts/codex-reviewer.md +73 -0
- package/prompts/gemini-analyzer.md +82 -0
- package/prompts/gemini-reviewer.md +91 -0
- package/scripts/hook-pre-compact.mjs +128 -0
- package/scripts/hook-pre-tool-use.mjs +225 -0
- package/scripts/hook-session-end.mjs +108 -0
- package/scripts/hook-session-start.mjs +243 -0
- package/scripts/hook-stop.mjs +143 -0
- package/scripts/hook-subagent-start.mjs +231 -0
- package/scripts/hook-subagent-stop.mjs +168 -0
- package/scripts/hook-user-prompt-submit.mjs +134 -0
- package/scripts/lib/daemon-client.mjs +165 -0
- package/scripts/lib/stdin.mjs +88 -0
- package/scripts/run.mjs +98 -0
- package/skills/execute/SKILL.md +481 -0
- package/skills/plan/SKILL.md +318 -0
- package/skills/research/SKILL.md +267 -0
- package/skills/review/SKILL.md +238 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file scripts/lib/daemon-client.mjs
|
|
3
|
+
* @description Daemon Unix Socket IPC 客户端
|
|
4
|
+
* 供所有 Hook 脚本共用的 Daemon 通信工具库。
|
|
5
|
+
*
|
|
6
|
+
* 通信协议:
|
|
7
|
+
* 1. 读取 ~/.ghostcode/daemon/ghostcoded.addr.json 获取 socket 路径
|
|
8
|
+
* 2. 发送 NDJSON 请求:{"v":1,"op":"<op>","args":{...}}\n
|
|
9
|
+
* 3. 读取一行 JSON 响应
|
|
10
|
+
* 4. 超时 2000ms,addr.json 不存在时返回 null
|
|
11
|
+
*
|
|
12
|
+
* 参考: ghostcode-types/src/ipc.rs - DaemonRequest/DaemonResponse 协议定义
|
|
13
|
+
* @author Atlas.oi
|
|
14
|
+
* @date 2026-03-06
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
18
|
+
import { join } from "node:path";
|
|
19
|
+
import { homedir } from "node:os";
|
|
20
|
+
import { createConnection } from "node:net";
|
|
21
|
+
|
|
22
|
+
// ============================================
|
|
23
|
+
// 常量配置
|
|
24
|
+
// ============================================
|
|
25
|
+
|
|
26
|
+
// GhostCode 主目录,支持环境变量覆盖(主要用于测试隔离)
|
|
27
|
+
const GHOSTCODE_HOME = process.env.GHOSTCODE_HOME || join(homedir(), ".ghostcode");
|
|
28
|
+
|
|
29
|
+
// Daemon 地址文件路径(Daemon 启动时写入,包含 socket 路径)
|
|
30
|
+
const ADDR_FILE = join(GHOSTCODE_HOME, "daemon", "ghostcoded.addr.json");
|
|
31
|
+
|
|
32
|
+
// IPC 请求超时时间(毫秒)
|
|
33
|
+
// 设为 2000ms,与 stdin.mjs 的超时对齐,确保在 Hook 外层超时(5s)前完成
|
|
34
|
+
const IPC_TIMEOUT_MS = 2000;
|
|
35
|
+
|
|
36
|
+
// ============================================
|
|
37
|
+
// 核心 IPC 函数
|
|
38
|
+
// ============================================
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 向 Daemon 发送 IPC 请求并等待响应
|
|
42
|
+
*
|
|
43
|
+
* 业务逻辑说明:
|
|
44
|
+
* 1. 读取 addr.json 获取 Unix Socket 路径
|
|
45
|
+
* 2. 建立 Socket 连接
|
|
46
|
+
* 3. 发送 NDJSON 格式请求(一行 JSON + 换行符)
|
|
47
|
+
* 4. 读取一行 JSON 响应
|
|
48
|
+
* 5. 解析并返回 DaemonResponse
|
|
49
|
+
*
|
|
50
|
+
* addr.json 不存在或 Socket 连接失败时返回 null(Daemon 未运行)
|
|
51
|
+
* Hook 脚本调用方应检查返回值是否为 null
|
|
52
|
+
*
|
|
53
|
+
* @param {string} op - 操作名称(如 "actor_start", "actor_stop", "send", "ping")
|
|
54
|
+
* @param {object} args - 操作参数(任意 JSON 对象)
|
|
55
|
+
* @returns {Promise<object|null>} DaemonResponse 对象({v, ok, result, error}),或 null(Daemon 不可达)
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* const resp = await callDaemon("actor_start", { group_id: "g-xxx", actor_id: "agent-1" });
|
|
59
|
+
* if (resp?.ok) {
|
|
60
|
+
* console.log("Actor 注册成功");
|
|
61
|
+
* }
|
|
62
|
+
*/
|
|
63
|
+
export async function callDaemon(op, args = {}) {
|
|
64
|
+
// ============================================
|
|
65
|
+
// 第一步:读取 Daemon 地址文件
|
|
66
|
+
// addr.json 由 Daemon 启动时写入,包含 socket 路径
|
|
67
|
+
// 不存在 = Daemon 未运行,返回 null
|
|
68
|
+
// ============================================
|
|
69
|
+
let socketPath;
|
|
70
|
+
try {
|
|
71
|
+
if (!existsSync(ADDR_FILE)) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
const addrData = JSON.parse(readFileSync(ADDR_FILE, "utf-8"));
|
|
75
|
+
socketPath = addrData.path;
|
|
76
|
+
if (!socketPath) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
} catch {
|
|
80
|
+
// addr.json 读取/解析失败,视为 Daemon 不可达
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ============================================
|
|
85
|
+
// 第二步:构造 NDJSON 请求
|
|
86
|
+
// 协议格式:{"v":1,"op":"<op>","args":{...}}\n
|
|
87
|
+
// 参考: ghostcode-types/src/ipc.rs:21 - DaemonRequest 结构
|
|
88
|
+
// ============================================
|
|
89
|
+
const request = JSON.stringify({ v: 1, op, args }) + "\n";
|
|
90
|
+
|
|
91
|
+
// ============================================
|
|
92
|
+
// 第三步:通过 Unix Socket 发送请求并接收响应
|
|
93
|
+
// 使用 Promise 包裹 net.createConnection,带超时保护
|
|
94
|
+
// ============================================
|
|
95
|
+
return new Promise((resolve) => {
|
|
96
|
+
let settled = false;
|
|
97
|
+
let responseData = "";
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 安全 resolve:防止多次 resolve(超时 + 正常响应竞争)
|
|
101
|
+
*
|
|
102
|
+
* @param {object|null} value - 要 resolve 的值
|
|
103
|
+
*/
|
|
104
|
+
function safeResolve(value) {
|
|
105
|
+
if (settled) return;
|
|
106
|
+
settled = true;
|
|
107
|
+
clearTimeout(timer);
|
|
108
|
+
socket.destroy();
|
|
109
|
+
resolve(value);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 创建 Unix Socket 连接
|
|
113
|
+
const socket = createConnection({ path: socketPath }, () => {
|
|
114
|
+
// 连接建立后发送请求
|
|
115
|
+
socket.write(request);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
socket.setEncoding("utf-8");
|
|
119
|
+
|
|
120
|
+
// 累积响应数据,遇到换行符表示完整响应到达
|
|
121
|
+
socket.on("data", (chunk) => {
|
|
122
|
+
responseData += chunk;
|
|
123
|
+
|
|
124
|
+
// NDJSON 协议:一行 = 一个完整响应
|
|
125
|
+
const newlineIdx = responseData.indexOf("\n");
|
|
126
|
+
if (newlineIdx !== -1) {
|
|
127
|
+
const line = responseData.slice(0, newlineIdx).trim();
|
|
128
|
+
try {
|
|
129
|
+
const parsed = JSON.parse(line);
|
|
130
|
+
safeResolve(parsed);
|
|
131
|
+
} catch {
|
|
132
|
+
// 响应解析失败,返回 null
|
|
133
|
+
safeResolve(null);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// 连接错误(Daemon 已停止、socket 文件残留等)
|
|
139
|
+
socket.on("error", () => {
|
|
140
|
+
safeResolve(null);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// 连接关闭但未收到完整响应
|
|
144
|
+
socket.on("end", () => {
|
|
145
|
+
if (!settled) {
|
|
146
|
+
// 尝试解析已收到的数据(可能没有换行符结尾)
|
|
147
|
+
const trimmed = responseData.trim();
|
|
148
|
+
if (trimmed) {
|
|
149
|
+
try {
|
|
150
|
+
safeResolve(JSON.parse(trimmed));
|
|
151
|
+
return;
|
|
152
|
+
} catch {
|
|
153
|
+
// 解析失败,返回 null
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
safeResolve(null);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// 超时保护:2000ms 后若仍未收到响应,返回 null
|
|
161
|
+
const timer = setTimeout(() => {
|
|
162
|
+
safeResolve(null);
|
|
163
|
+
}, IPC_TIMEOUT_MS);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file scripts/lib/stdin.mjs
|
|
3
|
+
* @description stdin 读取共享工具库
|
|
4
|
+
* 提供带超时防抖的 readStdin 函数,供所有 Hook 脚本共用。
|
|
5
|
+
*
|
|
6
|
+
* 设计要点:
|
|
7
|
+
* 1. settled 标志防止 Promise 被多次 resolve(end 事件 + 超时竞争)
|
|
8
|
+
* 2. 超时设为 2000ms(小于 hooks.json 的 5s),留出余量
|
|
9
|
+
* 3. end 触发后立即 clearTimeout,避免超时误触发
|
|
10
|
+
* 4. 出错时 reject,调用方应 try-catch
|
|
11
|
+
* @author Atlas.oi
|
|
12
|
+
* @date 2026-03-05
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 从 process.stdin 读取完整输入,带超时保护和防抖机制
|
|
17
|
+
*
|
|
18
|
+
* 业务逻辑说明:
|
|
19
|
+
* 1. 监听 data 事件,累积所有分块数据
|
|
20
|
+
* 2. 监听 end 事件,触发 resolve 并清除超时定时器
|
|
21
|
+
* 3. 监听 error 事件,触发 reject
|
|
22
|
+
* 4. 2000ms 超时兜底:即使 end 未到达,也返回已收到的数据
|
|
23
|
+
* 5. settled 标志确保 Promise 只 resolve/reject 一次(防止竞争条件)
|
|
24
|
+
*
|
|
25
|
+
* @returns {Promise<string>} stdin 的完整文本内容
|
|
26
|
+
*/
|
|
27
|
+
export function readStdin() {
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
// 已结算标志:防止 end 事件和超时同时 resolve,导致回调执行两次
|
|
30
|
+
let settled = false;
|
|
31
|
+
|
|
32
|
+
// 累积的输入数据缓冲区
|
|
33
|
+
let data = "";
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 安全 resolve:检查 settled 标志后再 resolve
|
|
37
|
+
* 同时清除超时定时器,防止后续误触发
|
|
38
|
+
*
|
|
39
|
+
* @param {string} value - 要 resolve 的值
|
|
40
|
+
*/
|
|
41
|
+
function safeResolve(value) {
|
|
42
|
+
if (settled) return;
|
|
43
|
+
settled = true;
|
|
44
|
+
clearTimeout(timer);
|
|
45
|
+
resolve(value);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 安全 reject:检查 settled 标志后再 reject
|
|
50
|
+
*
|
|
51
|
+
* @param {Error} err - 要 reject 的错误
|
|
52
|
+
*/
|
|
53
|
+
function safeReject(err) {
|
|
54
|
+
if (settled) return;
|
|
55
|
+
settled = true;
|
|
56
|
+
clearTimeout(timer);
|
|
57
|
+
reject(err);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ============================================
|
|
61
|
+
// 设置字符编码为 UTF-8,确保中文等多字节字符正确处理
|
|
62
|
+
// ============================================
|
|
63
|
+
process.stdin.setEncoding("utf-8");
|
|
64
|
+
|
|
65
|
+
// 监听数据块:累积到 data 缓冲区
|
|
66
|
+
process.stdin.on("data", (chunk) => {
|
|
67
|
+
data += chunk;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// 监听 end 事件:stdin 关闭,返回完整数据
|
|
71
|
+
process.stdin.on("end", () => {
|
|
72
|
+
safeResolve(data);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// 监听 error 事件:stdin 读取出错,reject Promise
|
|
76
|
+
process.stdin.on("error", (err) => {
|
|
77
|
+
safeReject(err);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// ============================================
|
|
81
|
+
// 超时兜底:2000ms 后若 Promise 仍未结算,返回已收到的数据
|
|
82
|
+
// 设为 2000ms 而非 hooks.json 的 5s,确保在外层超时前完成
|
|
83
|
+
// ============================================
|
|
84
|
+
const timer = setTimeout(() => {
|
|
85
|
+
safeResolve(data);
|
|
86
|
+
}, 2000);
|
|
87
|
+
});
|
|
88
|
+
}
|
package/scripts/run.mjs
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file scripts/run.mjs
|
|
3
|
+
* @description GhostCode Hook 统一脚本运行器
|
|
4
|
+
* 接收目标脚本路径参数,统一处理环境变量注入、路径解析、错误处理和超时保护。
|
|
5
|
+
* 由 hooks.json 中的命令调用,格式:
|
|
6
|
+
* node "${CLAUDE_PLUGIN_ROOT}/scripts/run.mjs" "${CLAUDE_PLUGIN_ROOT}/scripts/hook-xxx.mjs"
|
|
7
|
+
*
|
|
8
|
+
* 参考: oh-my-claudecode/scripts/run.cjs - 跨平台 Hook 运行器
|
|
9
|
+
* @author Atlas.oi
|
|
10
|
+
* @date 2026-03-05
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { spawnSync } from "node:child_process";
|
|
14
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
15
|
+
import { dirname, resolve } from "node:path";
|
|
16
|
+
import { homedir } from "node:os";
|
|
17
|
+
import { fileURLToPath } from "node:url";
|
|
18
|
+
|
|
19
|
+
// 当前脚本所在目录(ESM 中没有 __dirname,需要手动计算)
|
|
20
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
21
|
+
const __dirname = dirname(__filename);
|
|
22
|
+
|
|
23
|
+
// 脚本超时时间(毫秒):防止 Hook 脚本挂起阻塞 Claude Code
|
|
24
|
+
const SCRIPT_TIMEOUT_MS = 30_000;
|
|
25
|
+
|
|
26
|
+
// ============================================
|
|
27
|
+
// 第一步:获取目标脚本路径
|
|
28
|
+
// ============================================
|
|
29
|
+
const target = process.argv[2];
|
|
30
|
+
if (!target) {
|
|
31
|
+
// 无参数时静默退出,不阻断 Claude Code hooks
|
|
32
|
+
process.exit(0);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ============================================
|
|
36
|
+
// 第二步:解析目标脚本路径
|
|
37
|
+
// 处理 symlink 和路径不存在的边界情况
|
|
38
|
+
// ============================================
|
|
39
|
+
function resolveTarget(targetPath) {
|
|
40
|
+
// 快速路径:目标直接存在
|
|
41
|
+
if (existsSync(targetPath)) return targetPath;
|
|
42
|
+
|
|
43
|
+
// 尝试 symlink 解析
|
|
44
|
+
try {
|
|
45
|
+
const resolved = realpathSync(targetPath);
|
|
46
|
+
if (existsSync(resolved)) return resolved;
|
|
47
|
+
} catch {
|
|
48
|
+
// realpathSync 在路径不存在时抛异常,预期行为
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const resolved = resolveTarget(target);
|
|
55
|
+
if (!resolved) {
|
|
56
|
+
// 目标脚本不存在,静默退出不阻断 hooks
|
|
57
|
+
process.exit(0);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ============================================
|
|
61
|
+
// 第三步:注入环境变量
|
|
62
|
+
// ============================================
|
|
63
|
+
const env = { ...process.env };
|
|
64
|
+
|
|
65
|
+
// CLAUDE_PLUGIN_ROOT:从当前脚本路径推导(scripts/ 的父目录)
|
|
66
|
+
if (!env.CLAUDE_PLUGIN_ROOT) {
|
|
67
|
+
env.CLAUDE_PLUGIN_ROOT = resolve(__dirname, "..");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// GHOSTCODE_HOME:GhostCode 数据目录,默认 ~/.ghostcode
|
|
71
|
+
if (!env.GHOSTCODE_HOME) {
|
|
72
|
+
env.GHOSTCODE_HOME = resolve(homedir(), ".ghostcode");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ============================================
|
|
76
|
+
// 第四步:执行目标脚本
|
|
77
|
+
// 使用 spawnSync 同步执行,继承 stdio 流
|
|
78
|
+
// ============================================
|
|
79
|
+
// stdio 使用数组形式显式指定三个流,确保子进程能读取父进程 stdin
|
|
80
|
+
// - stdin (fd 0): "inherit" — 父进程 stdin 透传给子进程,使 hook 脚本能通过 readStdin 读取事件 JSON
|
|
81
|
+
// - stdout (fd 1): "inherit" — 子进程输出直接写入父进程 stdout,供 Claude Code 读取 Hook 响应
|
|
82
|
+
// - stderr (fd 2): "inherit" — 子进程错误直接写入父进程 stderr,便于调试
|
|
83
|
+
const result = spawnSync(
|
|
84
|
+
process.execPath,
|
|
85
|
+
[resolved, ...process.argv.slice(3)],
|
|
86
|
+
{
|
|
87
|
+
stdio: ["inherit", "inherit", "inherit"],
|
|
88
|
+
env,
|
|
89
|
+
timeout: SCRIPT_TIMEOUT_MS,
|
|
90
|
+
windowsHide: true,
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
// ============================================
|
|
95
|
+
// 第五步:传播退出码
|
|
96
|
+
// null(超时或信号终止)时返回 0,避免阻断 hooks
|
|
97
|
+
// ============================================
|
|
98
|
+
process.exit(result.status ?? 0);
|