odai-dsh-agent 0.2.21 → 0.2.23

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 (82) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +155 -157
  3. package/build/bin/odai-dsh-agent.mjs +18 -36
  4. package/build/bin/odai-dsh-agent.mjs.map +1 -1
  5. package/build/src/installer.d.mts +1 -6
  6. package/build/src/installer.d.mts.map +1 -1
  7. package/build/src/installer.mjs +3 -83
  8. package/build/src/installer.mjs.map +1 -1
  9. package/client/client.js +36 -58
  10. package/control-center.cordis.patch.yml +3 -3
  11. package/package.json +90 -90
  12. package/preset/odai/agent.cordis.yml +260 -260
  13. package/preset/odai/preset.yml +3 -3
  14. package/preset/odai/runtime/control-center-runtime.d.mts +1 -0
  15. package/preset/odai/runtime/control-center-runtime.d.mts.map +1 -1
  16. package/preset/odai/runtime/control-center-runtime.mjs +20 -0
  17. package/preset/odai/runtime/control-center-runtime.mjs.map +1 -1
  18. package/preset/odai/runtime/index.d.mts.map +1 -1
  19. package/preset/odai/runtime/index.mjs +9 -7
  20. package/preset/odai/runtime/index.mjs.map +1 -1
  21. package/preset/odai/runtime/{session-compat.d.mts → legacy-session-repair.d.mts} +7 -7
  22. package/preset/odai/runtime/legacy-session-repair.d.mts.map +1 -0
  23. package/preset/odai/runtime/{session-compat.mjs → legacy-session-repair.mjs} +1 -1
  24. package/preset/odai/runtime/legacy-session-repair.mjs.map +1 -0
  25. package/preset/odai/runtime/lifecycle-runtime.d.mts.map +1 -1
  26. package/preset/odai/runtime/lifecycle-runtime.mjs +10 -2
  27. package/preset/odai/runtime/lifecycle-runtime.mjs.map +1 -1
  28. package/preset/odai/runtime/responsibility-gap.d.mts +19 -1
  29. package/preset/odai/runtime/responsibility-gap.d.mts.map +1 -1
  30. package/preset/odai/runtime/responsibility-gap.mjs +180 -16
  31. package/preset/odai/runtime/responsibility-gap.mjs.map +1 -1
  32. package/preset/odai/runtime/role-overlays.mjs +9 -9
  33. package/preset/odai/runtime/role-overlays.mjs.map +1 -1
  34. package/preset/odai/runtime/router.d.mts +1 -0
  35. package/preset/odai/runtime/router.d.mts.map +1 -1
  36. package/preset/odai/runtime/router.mjs +5 -1
  37. package/preset/odai/runtime/router.mjs.map +1 -1
  38. package/preset/odai/runtime/routing-context.d.mts +9 -2
  39. package/preset/odai/runtime/routing-context.d.mts.map +1 -1
  40. package/preset/odai/runtime/routing-context.mjs +30 -9
  41. package/preset/odai/runtime/routing-context.mjs.map +1 -1
  42. package/preset/odai/runtime/runtime-support.d.mts +0 -1
  43. package/preset/odai/runtime/runtime-support.d.mts.map +1 -1
  44. package/preset/odai/runtime/runtime-support.mjs.map +1 -1
  45. package/preset/odai/runtime/runtime-types.d.mts +1 -3
  46. package/preset/odai/runtime/runtime-types.d.mts.map +1 -1
  47. package/preset/odai/runtime/runtime-types.mjs +1 -5
  48. package/preset/odai/runtime/runtime-types.mjs.map +1 -1
  49. package/preset/odai/runtime/session-evidence.d.mts.map +1 -1
  50. package/preset/odai/runtime/session-evidence.mjs +1 -3
  51. package/preset/odai/runtime/session-evidence.mjs.map +1 -1
  52. package/preset/odai/runtime/tool-runtime.d.mts.map +1 -1
  53. package/preset/odai/runtime/tool-runtime.mjs +23 -3
  54. package/preset/odai/runtime/tool-runtime.mjs.map +1 -1
  55. package/preset/odai/skills/odai/SKILL.md +68 -68
  56. package/preset/odai/skills/odai/agents/openai.yaml +4 -4
  57. package/preset/odai/skills/odai/assets/claude-agents/agent.md +11 -11
  58. package/preset/odai/skills/odai/assets/codex-agents/config.toml +11 -11
  59. package/preset/odai/skills/odai/assets/codex-agents/role.toml +5 -5
  60. package/preset/odai/skills/odai/assets/copilot-agents/agent.md +13 -13
  61. package/preset/odai/skills/odai/assets/hooks-policy.example.json +24 -24
  62. package/preset/odai/skills/odai/assets/routing-roles/controller.md +15 -15
  63. package/preset/odai/skills/odai/assets/routing-roles/frontend.md +7 -7
  64. package/preset/odai/skills/odai/assets/routing-roles/planner.md +28 -28
  65. package/preset/odai/skills/odai/assets/routing-roles/researcher.md +6 -6
  66. package/preset/odai/skills/odai/assets/routing-roles/reviewer.md +11 -11
  67. package/preset/odai/skills/odai/assets/task-state.md +14 -14
  68. package/preset/odai/skills/odai/manifest.json +52 -52
  69. package/preset/odai/skills/odai/references/craft.md +54 -54
  70. package/preset/odai/skills/odai/references/dao.md +31 -31
  71. package/preset/odai/skills/odai/references/leverage.md +50 -50
  72. package/preset/odai/skills/odai/references/planning.md +83 -83
  73. package/preset/odai/skills/odai/references/support.md +30 -30
  74. package/preset/odai/skills/odai/references/verification.md +24 -24
  75. package/preset/odai/skills/odai/scripts/build-hooks.mjs +273 -273
  76. package/preset/odai/skills/odai/scripts/build-routing.mjs +315 -315
  77. package/preset/odai/skills/odai/scripts/install-routing.mjs +393 -393
  78. package/preset/odai/skills/odai/scripts/odai-hook.mjs +457 -457
  79. package/preset/odai/skills/odai/scripts/run-role.mjs +285 -285
  80. package/preset/odai/skills/odai/scripts/verify-routing.mjs +103 -103
  81. package/preset/odai/runtime/session-compat.d.mts.map +0 -1
  82. package/preset/odai/runtime/session-compat.mjs.map +0 -1
@@ -1,273 +1,273 @@
1
- #!/usr/bin/env node
2
-
3
- import {
4
- copyFileSync,
5
- existsSync,
6
- mkdirSync,
7
- readdirSync,
8
- writeFileSync,
9
- } from "node:fs";
10
- import path from "node:path";
11
- import { fileURLToPath } from "node:url";
12
-
13
- const skillRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
14
- const runtimeSource = path.join(skillRoot, "scripts", "odai-hook.mjs");
15
- const policySource = path.join(skillRoot, "assets", "hooks-policy.example.json");
16
- const supportedHosts = ["codex", "claude", "copilot", "gemini", "grok", "kimi"];
17
-
18
- const args = parseArgs(process.argv.slice(2));
19
- if (args.help) {
20
- printHelp();
21
- process.exit(0);
22
- }
23
- if (!args.out) fail("缺少 --out <directory>");
24
- if (!args.host) fail("缺少 --host <codex|claude|copilot|gemini|grok|kimi|all>");
25
-
26
- const hosts = args.host === "all" ? supportedHosts : [args.host];
27
- for (const host of hosts) {
28
- if (!supportedHosts.includes(host)) fail(`不支持的 host:${host}`);
29
- }
30
-
31
- const outputRoot = path.resolve(args.out);
32
- mkdirSync(outputRoot, { recursive: true });
33
- for (const host of hosts) {
34
- const target = path.join(outputRoot, host);
35
- assertEmpty(target);
36
- mkdirSync(target, { recursive: true });
37
- buildAdapter(host, target);
38
- console.log(`${host}: ${target}`);
39
- }
40
-
41
- function buildAdapter(host, target) {
42
- const metadata = {
43
- version: 1,
44
- host,
45
- policyPath: "<project>/.odai/hooks.json",
46
- defaultBehavior: "No policy file means no-op.",
47
- generatedFrom: "skills/odai/scripts/build-hooks.mjs",
48
- capabilities: host === "grok" ? ["pre-tool"] : ["pre-tool", "stop-check"],
49
- };
50
-
51
- if (host === "copilot") {
52
- copy(target, ".odai/hooks/odai-hook.mjs", runtimeSource);
53
- copy(target, ".odai/hooks.example.json", policySource);
54
- writeJson(target, ".github/hooks/odai.json", copilotHooks());
55
- metadata.format = "project-overlay";
56
- metadata.install = "Merge this directory into the repository root, review .github/hooks/odai.json, then create .odai/hooks.json from the example.";
57
- } else {
58
- copy(target, "scripts/odai-hook.mjs", runtimeSource);
59
- copy(target, "examples/hooks.json", policySource);
60
-
61
- if (host === "codex") {
62
- writeJson(target, ".codex-plugin/plugin.json", codexPluginManifest());
63
- writeJson(target, "hooks/hooks.json", groupedHooks("${PLUGIN_ROOT}", host, "PreToolUse", "Stop"));
64
- metadata.format = "codex-plugin";
65
- metadata.install = "Install or link this directory as a Codex plugin, review and trust its hooks, then create <project>/.odai/hooks.json.";
66
- } else if (host === "claude") {
67
- writeJson(target, ".claude-plugin/plugin.json", pluginManifest("Claude Code"));
68
- writeJson(target, "hooks/hooks.json", groupedHooks("${CLAUDE_PLUGIN_ROOT}", host, "PreToolUse", "Stop"));
69
- metadata.format = "claude-plugin";
70
- metadata.install = "Install this directory as a Claude Code plugin, then create <project>/.odai/hooks.json.";
71
- } else if (host === "grok") {
72
- writeJson(target, ".claude-plugin/plugin.json", pluginManifest("Grok Build"));
73
- writeJson(target, "hooks/hooks.json", groupedHooks("${GROK_PLUGIN_ROOT}", host, "PreToolUse", null));
74
- metadata.format = "grok-plugin-using-claude-compatible-manifest";
75
- metadata.install = "Install this directory with Grok Build's plugin manager, trust the hook, then create <project>/.odai/hooks.json.";
76
- metadata.note = "Grok Build exposes PreToolUse as the blocking boundary; this adapter intentionally has no Stop verifier.";
77
- } else if (host === "gemini") {
78
- writeJson(target, "gemini-extension.json", {
79
- name: "odai-hooks",
80
- version: "0.1.0",
81
- description: "Optional deterministic boundaries for odai.",
82
- });
83
- writeJson(target, "hooks/hooks.json", groupedHooks("${extensionPath}", host, "BeforeTool", "AfterAgent"));
84
- metadata.format = "gemini-extension";
85
- metadata.install = "Link or install this directory as a Gemini CLI extension, then create <project>/.odai/hooks.json.";
86
- } else if (host === "kimi") {
87
- writeJson(target, "kimi.plugin.json", kimiManifest());
88
- metadata.format = "kimi-plugin";
89
- metadata.install = "Run /plugins install <this-directory> in Kimi Code CLI, enable it, then create <project>/.odai/hooks.json.";
90
- }
91
- }
92
-
93
- writeJson(target, "ADAPTER.json", metadata);
94
- }
95
-
96
- function groupedHooks(rootVariable, host, preEvent, stopEvent) {
97
- const commandRoot = `${rootVariable}/scripts/odai-hook.mjs`;
98
- const preHandler = {
99
- type: "command",
100
- command: `node "${commandRoot}" pre-tool --host ${host}`,
101
- timeout: 5,
102
- };
103
- const stopHandler = {
104
- type: "command",
105
- command: `node "${commandRoot}" stop --host ${host}`,
106
- timeout: 600,
107
- };
108
- if (host === "gemini") {
109
- preHandler.name = "odai-write-boundary";
110
- stopHandler.name = "odai-acceptance-check";
111
- }
112
- const hooks = {
113
- [preEvent]: [
114
- {
115
- matcher: writeMatcher(host),
116
- hooks: [preHandler],
117
- },
118
- ],
119
- };
120
- if (stopEvent) {
121
- hooks[stopEvent] = [
122
- {
123
- matcher: "*",
124
- hooks: [stopHandler],
125
- },
126
- ];
127
- }
128
- const result = { hooks };
129
- if (host !== "gemini") {
130
- result.description = "Optional odai boundaries. Inactive unless the project defines .odai/hooks.json.";
131
- }
132
- return result;
133
- }
134
-
135
- function copilotHooks() {
136
- const command = "node .odai/hooks/odai-hook.mjs";
137
- return {
138
- version: 1,
139
- hooks: {
140
- preToolUse: [
141
- {
142
- type: "command",
143
- matcher: writeMatcher("copilot"),
144
- command: `${command} pre-tool --host copilot`,
145
- cwd: ".",
146
- timeoutSec: 5,
147
- },
148
- ],
149
- agentStop: [
150
- {
151
- type: "command",
152
- command: `${command} stop --host copilot`,
153
- cwd: ".",
154
- timeoutSec: 600,
155
- },
156
- ],
157
- },
158
- };
159
- }
160
-
161
- function kimiManifest() {
162
- return {
163
- name: "odai-hooks",
164
- version: "0.1.0",
165
- description: "Optional deterministic boundaries for odai.",
166
- interface: {
167
- displayName: "odai Hooks",
168
- shortDescription: "Protect explicit read-only paths and run declared acceptance checks.",
169
- },
170
- hooks: [
171
- {
172
- event: "PreToolUse",
173
- matcher: writeMatcher("kimi"),
174
- command: "node ./scripts/odai-hook.mjs pre-tool --host kimi",
175
- timeout: 5,
176
- },
177
- {
178
- event: "Stop",
179
- command: "node ./scripts/odai-hook.mjs stop --host kimi",
180
- timeout: 600,
181
- },
182
- ],
183
- };
184
- }
185
-
186
- function pluginManifest(hostName) {
187
- return {
188
- name: "odai-hooks",
189
- version: "0.1.0",
190
- description: `Optional deterministic odai boundaries for ${hostName}.`,
191
- };
192
- }
193
-
194
- function codexPluginManifest() {
195
- return {
196
- id: "odai-hooks",
197
- name: "odai-hooks",
198
- version: "0.1.0",
199
- description: "Optional deterministic boundaries for odai.",
200
- author: {
201
- name: "orzi",
202
- url: "https://github.com/orziz",
203
- },
204
- homepage: "https://github.com/orziz/odai",
205
- repository: "https://github.com/orziz/odai",
206
- license: "MIT",
207
- keywords: ["odai", "hooks", "guardrails"],
208
- interface: {
209
- displayName: "odai Hooks",
210
- shortDescription: "Protect explicit read-only paths and run declared checks.",
211
- longDescription: "Optional deterministic hook guardrails for projects that use odai.",
212
- developerName: "orzi",
213
- category: "Developer Tools",
214
- capabilities: ["PreToolUse guard", "Stop acceptance check"],
215
- websiteURL: "https://github.com/orziz/odai",
216
- brandColor: "#1F6FEB",
217
- defaultPrompt: "Use odai with the project's explicit hook boundaries.",
218
- },
219
- };
220
- }
221
-
222
- function writeMatcher(host) {
223
- if (host === "copilot") return "create|edit|str_replace_editor|apply_patch";
224
- if (host === "gemini") return "write_file|replace|apply_patch|edit|create";
225
- return "Edit|Write|apply_patch|create|edit|write_file|str_replace_editor";
226
- }
227
-
228
- function writeJson(root, relativePath, value) {
229
- const target = path.join(root, relativePath);
230
- mkdirSync(path.dirname(target), { recursive: true });
231
- writeFileSync(target, `${JSON.stringify(value, null, 2)}\n`, "utf8");
232
- }
233
-
234
- function copy(root, relativePath, source) {
235
- const target = path.join(root, relativePath);
236
- mkdirSync(path.dirname(target), { recursive: true });
237
- copyFileSync(source, target);
238
- }
239
-
240
- function assertEmpty(target) {
241
- if (!existsSync(target)) return;
242
- if (readdirSync(target).length > 0) fail(`输出目录非空,拒绝覆盖:${target}`);
243
- }
244
-
245
- function parseArgs(argv) {
246
- const result = { host: "", out: "", help: false };
247
- for (let index = 0; index < argv.length; index += 1) {
248
- const value = argv[index];
249
- if (value === "--help" || value === "-h") result.help = true;
250
- else if (value === "--host") result.host = argv[++index] || "";
251
- else if (value === "--out") result.out = argv[++index] || "";
252
- else fail(`未知参数:${value}`);
253
- }
254
- return result;
255
- }
256
-
257
- function printHelp() {
258
- console.log(`Build optional odai hook adapters.
259
-
260
- Usage:
261
- node skills/odai/scripts/build-hooks.mjs --host <host|all> --out <directory>
262
-
263
- Hosts:
264
- ${supportedHosts.join(", ")}
265
-
266
- The builder never installs hooks or edits a target project. Each generated
267
- adapter is inactive until that project defines .odai/hooks.json.`);
268
- }
269
-
270
- function fail(message) {
271
- console.error(message);
272
- process.exit(1);
273
- }
1
+ #!/usr/bin/env node
2
+
3
+ import {
4
+ copyFileSync,
5
+ existsSync,
6
+ mkdirSync,
7
+ readdirSync,
8
+ writeFileSync,
9
+ } from "node:fs";
10
+ import path from "node:path";
11
+ import { fileURLToPath } from "node:url";
12
+
13
+ const skillRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
14
+ const runtimeSource = path.join(skillRoot, "scripts", "odai-hook.mjs");
15
+ const policySource = path.join(skillRoot, "assets", "hooks-policy.example.json");
16
+ const supportedHosts = ["codex", "claude", "copilot", "gemini", "grok", "kimi"];
17
+
18
+ const args = parseArgs(process.argv.slice(2));
19
+ if (args.help) {
20
+ printHelp();
21
+ process.exit(0);
22
+ }
23
+ if (!args.out) fail("缺少 --out <directory>");
24
+ if (!args.host) fail("缺少 --host <codex|claude|copilot|gemini|grok|kimi|all>");
25
+
26
+ const hosts = args.host === "all" ? supportedHosts : [args.host];
27
+ for (const host of hosts) {
28
+ if (!supportedHosts.includes(host)) fail(`不支持的 host:${host}`);
29
+ }
30
+
31
+ const outputRoot = path.resolve(args.out);
32
+ mkdirSync(outputRoot, { recursive: true });
33
+ for (const host of hosts) {
34
+ const target = path.join(outputRoot, host);
35
+ assertEmpty(target);
36
+ mkdirSync(target, { recursive: true });
37
+ buildAdapter(host, target);
38
+ console.log(`${host}: ${target}`);
39
+ }
40
+
41
+ function buildAdapter(host, target) {
42
+ const metadata = {
43
+ version: 1,
44
+ host,
45
+ policyPath: "<project>/.odai/hooks.json",
46
+ defaultBehavior: "No policy file means no-op.",
47
+ generatedFrom: "skills/odai/scripts/build-hooks.mjs",
48
+ capabilities: host === "grok" ? ["pre-tool"] : ["pre-tool", "stop-check"],
49
+ };
50
+
51
+ if (host === "copilot") {
52
+ copy(target, ".odai/hooks/odai-hook.mjs", runtimeSource);
53
+ copy(target, ".odai/hooks.example.json", policySource);
54
+ writeJson(target, ".github/hooks/odai.json", copilotHooks());
55
+ metadata.format = "project-overlay";
56
+ metadata.install = "Merge this directory into the repository root, review .github/hooks/odai.json, then create .odai/hooks.json from the example.";
57
+ } else {
58
+ copy(target, "scripts/odai-hook.mjs", runtimeSource);
59
+ copy(target, "examples/hooks.json", policySource);
60
+
61
+ if (host === "codex") {
62
+ writeJson(target, ".codex-plugin/plugin.json", codexPluginManifest());
63
+ writeJson(target, "hooks/hooks.json", groupedHooks("${PLUGIN_ROOT}", host, "PreToolUse", "Stop"));
64
+ metadata.format = "codex-plugin";
65
+ metadata.install = "Install or link this directory as a Codex plugin, review and trust its hooks, then create <project>/.odai/hooks.json.";
66
+ } else if (host === "claude") {
67
+ writeJson(target, ".claude-plugin/plugin.json", pluginManifest("Claude Code"));
68
+ writeJson(target, "hooks/hooks.json", groupedHooks("${CLAUDE_PLUGIN_ROOT}", host, "PreToolUse", "Stop"));
69
+ metadata.format = "claude-plugin";
70
+ metadata.install = "Install this directory as a Claude Code plugin, then create <project>/.odai/hooks.json.";
71
+ } else if (host === "grok") {
72
+ writeJson(target, ".claude-plugin/plugin.json", pluginManifest("Grok Build"));
73
+ writeJson(target, "hooks/hooks.json", groupedHooks("${GROK_PLUGIN_ROOT}", host, "PreToolUse", null));
74
+ metadata.format = "grok-plugin-using-claude-compatible-manifest";
75
+ metadata.install = "Install this directory with Grok Build's plugin manager, trust the hook, then create <project>/.odai/hooks.json.";
76
+ metadata.note = "Grok Build exposes PreToolUse as the blocking boundary; this adapter intentionally has no Stop verifier.";
77
+ } else if (host === "gemini") {
78
+ writeJson(target, "gemini-extension.json", {
79
+ name: "odai-hooks",
80
+ version: "0.1.0",
81
+ description: "Optional deterministic boundaries for odai.",
82
+ });
83
+ writeJson(target, "hooks/hooks.json", groupedHooks("${extensionPath}", host, "BeforeTool", "AfterAgent"));
84
+ metadata.format = "gemini-extension";
85
+ metadata.install = "Link or install this directory as a Gemini CLI extension, then create <project>/.odai/hooks.json.";
86
+ } else if (host === "kimi") {
87
+ writeJson(target, "kimi.plugin.json", kimiManifest());
88
+ metadata.format = "kimi-plugin";
89
+ metadata.install = "Run /plugins install <this-directory> in Kimi Code CLI, enable it, then create <project>/.odai/hooks.json.";
90
+ }
91
+ }
92
+
93
+ writeJson(target, "ADAPTER.json", metadata);
94
+ }
95
+
96
+ function groupedHooks(rootVariable, host, preEvent, stopEvent) {
97
+ const commandRoot = `${rootVariable}/scripts/odai-hook.mjs`;
98
+ const preHandler = {
99
+ type: "command",
100
+ command: `node "${commandRoot}" pre-tool --host ${host}`,
101
+ timeout: 5,
102
+ };
103
+ const stopHandler = {
104
+ type: "command",
105
+ command: `node "${commandRoot}" stop --host ${host}`,
106
+ timeout: 600,
107
+ };
108
+ if (host === "gemini") {
109
+ preHandler.name = "odai-write-boundary";
110
+ stopHandler.name = "odai-acceptance-check";
111
+ }
112
+ const hooks = {
113
+ [preEvent]: [
114
+ {
115
+ matcher: writeMatcher(host),
116
+ hooks: [preHandler],
117
+ },
118
+ ],
119
+ };
120
+ if (stopEvent) {
121
+ hooks[stopEvent] = [
122
+ {
123
+ matcher: "*",
124
+ hooks: [stopHandler],
125
+ },
126
+ ];
127
+ }
128
+ const result = { hooks };
129
+ if (host !== "gemini") {
130
+ result.description = "Optional odai boundaries. Inactive unless the project defines .odai/hooks.json.";
131
+ }
132
+ return result;
133
+ }
134
+
135
+ function copilotHooks() {
136
+ const command = "node .odai/hooks/odai-hook.mjs";
137
+ return {
138
+ version: 1,
139
+ hooks: {
140
+ preToolUse: [
141
+ {
142
+ type: "command",
143
+ matcher: writeMatcher("copilot"),
144
+ command: `${command} pre-tool --host copilot`,
145
+ cwd: ".",
146
+ timeoutSec: 5,
147
+ },
148
+ ],
149
+ agentStop: [
150
+ {
151
+ type: "command",
152
+ command: `${command} stop --host copilot`,
153
+ cwd: ".",
154
+ timeoutSec: 600,
155
+ },
156
+ ],
157
+ },
158
+ };
159
+ }
160
+
161
+ function kimiManifest() {
162
+ return {
163
+ name: "odai-hooks",
164
+ version: "0.1.0",
165
+ description: "Optional deterministic boundaries for odai.",
166
+ interface: {
167
+ displayName: "odai Hooks",
168
+ shortDescription: "Protect explicit read-only paths and run declared acceptance checks.",
169
+ },
170
+ hooks: [
171
+ {
172
+ event: "PreToolUse",
173
+ matcher: writeMatcher("kimi"),
174
+ command: "node ./scripts/odai-hook.mjs pre-tool --host kimi",
175
+ timeout: 5,
176
+ },
177
+ {
178
+ event: "Stop",
179
+ command: "node ./scripts/odai-hook.mjs stop --host kimi",
180
+ timeout: 600,
181
+ },
182
+ ],
183
+ };
184
+ }
185
+
186
+ function pluginManifest(hostName) {
187
+ return {
188
+ name: "odai-hooks",
189
+ version: "0.1.0",
190
+ description: `Optional deterministic odai boundaries for ${hostName}.`,
191
+ };
192
+ }
193
+
194
+ function codexPluginManifest() {
195
+ return {
196
+ id: "odai-hooks",
197
+ name: "odai-hooks",
198
+ version: "0.1.0",
199
+ description: "Optional deterministic boundaries for odai.",
200
+ author: {
201
+ name: "orzi",
202
+ url: "https://github.com/orziz",
203
+ },
204
+ homepage: "https://github.com/orziz/odai",
205
+ repository: "https://github.com/orziz/odai",
206
+ license: "MIT",
207
+ keywords: ["odai", "hooks", "guardrails"],
208
+ interface: {
209
+ displayName: "odai Hooks",
210
+ shortDescription: "Protect explicit read-only paths and run declared checks.",
211
+ longDescription: "Optional deterministic hook guardrails for projects that use odai.",
212
+ developerName: "orzi",
213
+ category: "Developer Tools",
214
+ capabilities: ["PreToolUse guard", "Stop acceptance check"],
215
+ websiteURL: "https://github.com/orziz/odai",
216
+ brandColor: "#1F6FEB",
217
+ defaultPrompt: "Use odai with the project's explicit hook boundaries.",
218
+ },
219
+ };
220
+ }
221
+
222
+ function writeMatcher(host) {
223
+ if (host === "copilot") return "create|edit|str_replace_editor|apply_patch";
224
+ if (host === "gemini") return "write_file|replace|apply_patch|edit|create";
225
+ return "Edit|Write|apply_patch|create|edit|write_file|str_replace_editor";
226
+ }
227
+
228
+ function writeJson(root, relativePath, value) {
229
+ const target = path.join(root, relativePath);
230
+ mkdirSync(path.dirname(target), { recursive: true });
231
+ writeFileSync(target, `${JSON.stringify(value, null, 2)}\n`, "utf8");
232
+ }
233
+
234
+ function copy(root, relativePath, source) {
235
+ const target = path.join(root, relativePath);
236
+ mkdirSync(path.dirname(target), { recursive: true });
237
+ copyFileSync(source, target);
238
+ }
239
+
240
+ function assertEmpty(target) {
241
+ if (!existsSync(target)) return;
242
+ if (readdirSync(target).length > 0) fail(`输出目录非空,拒绝覆盖:${target}`);
243
+ }
244
+
245
+ function parseArgs(argv) {
246
+ const result = { host: "", out: "", help: false };
247
+ for (let index = 0; index < argv.length; index += 1) {
248
+ const value = argv[index];
249
+ if (value === "--help" || value === "-h") result.help = true;
250
+ else if (value === "--host") result.host = argv[++index] || "";
251
+ else if (value === "--out") result.out = argv[++index] || "";
252
+ else fail(`未知参数:${value}`);
253
+ }
254
+ return result;
255
+ }
256
+
257
+ function printHelp() {
258
+ console.log(`Build optional odai hook adapters.
259
+
260
+ Usage:
261
+ node skills/odai/scripts/build-hooks.mjs --host <host|all> --out <directory>
262
+
263
+ Hosts:
264
+ ${supportedHosts.join(", ")}
265
+
266
+ The builder never installs hooks or edits a target project. Each generated
267
+ adapter is inactive until that project defines .odai/hooks.json.`);
268
+ }
269
+
270
+ function fail(message) {
271
+ console.error(message);
272
+ process.exit(1);
273
+ }