odai-dsh-agent 0.2.0 → 0.2.2

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 (61) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +138 -134
  3. package/bin/odai-dsh-agent.mjs +108 -108
  4. package/package.json +69 -69
  5. package/preset/odai/agent.cordis.yml +260 -260
  6. package/preset/odai/preset.yml +3 -3
  7. package/preset/odai/runtime/compaction-config.mjs +388 -388
  8. package/preset/odai/runtime/context-activation.mjs +71 -0
  9. package/preset/odai/runtime/context-capability.mjs +86 -0
  10. package/preset/odai/runtime/governance.mjs +128 -121
  11. package/preset/odai/runtime/human-care.mjs +45 -0
  12. package/preset/odai/runtime/human-safety-continuity.mjs +2 -2
  13. package/preset/odai/runtime/human-safety.mjs +3 -3
  14. package/preset/odai/runtime/index.mjs +2461 -1977
  15. package/preset/odai/runtime/output-config.mjs +360 -304
  16. package/preset/odai/runtime/research-packet.mjs +150 -150
  17. package/preset/odai/runtime/responsibility-gap.mjs +8 -10
  18. package/preset/odai/runtime/responsibility-scope.mjs +199 -0
  19. package/preset/odai/runtime/role-overlays.mjs +27 -27
  20. package/preset/odai/runtime/route-card.mjs +168 -152
  21. package/preset/odai/runtime/router.mjs +683 -651
  22. package/preset/odai/runtime/routing-config.mjs +480 -436
  23. package/preset/odai/runtime/routing-context.mjs +321 -321
  24. package/preset/odai/runtime/semantic-memory-store.mjs +452 -452
  25. package/preset/odai/runtime/semantic-memory.mjs +842 -842
  26. package/preset/odai/runtime/session-compat.mjs +389 -389
  27. package/preset/odai/runtime/skill-bundle.mjs +318 -318
  28. package/preset/odai/runtime/skill-evolution.mjs +1413 -1413
  29. package/preset/odai/skills/odai/SKILL.md +69 -68
  30. package/preset/odai/skills/odai/agents/openai.yaml +4 -4
  31. package/preset/odai/skills/odai/assets/claude-agents/agent.md +11 -11
  32. package/preset/odai/skills/odai/assets/codex-agents/config.toml +11 -11
  33. package/preset/odai/skills/odai/assets/codex-agents/role.toml +5 -5
  34. package/preset/odai/skills/odai/assets/copilot-agents/agent.md +13 -13
  35. package/preset/odai/skills/odai/assets/hooks-policy.example.json +24 -24
  36. package/preset/odai/skills/odai/assets/routing-roles/controller.md +15 -15
  37. package/preset/odai/skills/odai/assets/routing-roles/executor.md +11 -11
  38. package/preset/odai/skills/odai/assets/routing-roles/frontend.md +7 -7
  39. package/preset/odai/skills/odai/assets/routing-roles/planner.md +29 -29
  40. package/preset/odai/skills/odai/assets/routing-roles/researcher.md +6 -6
  41. package/preset/odai/skills/odai/assets/routing-roles/reviewer.md +11 -11
  42. package/preset/odai/skills/odai/assets/task-state.md +14 -14
  43. package/preset/odai/skills/odai/manifest.json +37 -36
  44. package/preset/odai/skills/odai/references/care.md +26 -0
  45. package/preset/odai/skills/odai/references/craft.md +54 -54
  46. package/preset/odai/skills/odai/references/dao.md +30 -30
  47. package/preset/odai/skills/odai/references/human-safety.md +33 -54
  48. package/preset/odai/skills/odai/references/leverage.md +53 -53
  49. package/preset/odai/skills/odai/references/planning.md +82 -82
  50. package/preset/odai/skills/odai/references/support.md +36 -36
  51. package/preset/odai/skills/odai/references/verification.md +23 -23
  52. package/preset/odai/skills/odai/scripts/build-hooks.mjs +273 -273
  53. package/preset/odai/skills/odai/scripts/build-routing.mjs +274 -274
  54. package/preset/odai/skills/odai/scripts/install-routing.mjs +397 -397
  55. package/preset/odai/skills/odai/scripts/odai-hook.mjs +457 -457
  56. package/preset/odai/skills/odai/scripts/run-role.mjs +336 -336
  57. package/preset/odai/skills/odai/scripts/run-routing.mjs +1154 -1154
  58. package/preset/odai/skills/odai/scripts/verify-routing.mjs +103 -103
  59. package/scripts/verify-agent-load.mjs +257 -257
  60. package/src/dsh-version.mjs +6 -2
  61. package/src/installer.mjs +382 -382
@@ -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
+ }