odai-dsh-agent 0.2.1 → 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 (51) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +138 -138
  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/governance.mjs +128 -128
  9. package/preset/odai/runtime/index.mjs +2461 -2263
  10. package/preset/odai/runtime/output-config.mjs +360 -304
  11. package/preset/odai/runtime/research-packet.mjs +150 -150
  12. package/preset/odai/runtime/responsibility-scope.mjs +35 -2
  13. package/preset/odai/runtime/role-overlays.mjs +27 -27
  14. package/preset/odai/runtime/route-card.mjs +168 -152
  15. package/preset/odai/runtime/router.mjs +683 -651
  16. package/preset/odai/runtime/routing-config.mjs +480 -436
  17. package/preset/odai/runtime/routing-context.mjs +321 -321
  18. package/preset/odai/runtime/semantic-memory-store.mjs +452 -452
  19. package/preset/odai/runtime/semantic-memory.mjs +842 -842
  20. package/preset/odai/runtime/session-compat.mjs +389 -389
  21. package/preset/odai/runtime/skill-bundle.mjs +318 -318
  22. package/preset/odai/runtime/skill-evolution.mjs +1413 -1413
  23. package/preset/odai/skills/odai/agents/openai.yaml +4 -4
  24. package/preset/odai/skills/odai/assets/claude-agents/agent.md +11 -11
  25. package/preset/odai/skills/odai/assets/codex-agents/config.toml +11 -11
  26. package/preset/odai/skills/odai/assets/codex-agents/role.toml +5 -5
  27. package/preset/odai/skills/odai/assets/copilot-agents/agent.md +13 -13
  28. package/preset/odai/skills/odai/assets/hooks-policy.example.json +24 -24
  29. package/preset/odai/skills/odai/assets/routing-roles/controller.md +15 -15
  30. package/preset/odai/skills/odai/assets/routing-roles/executor.md +11 -11
  31. package/preset/odai/skills/odai/assets/routing-roles/frontend.md +7 -7
  32. package/preset/odai/skills/odai/assets/routing-roles/planner.md +29 -29
  33. package/preset/odai/skills/odai/assets/routing-roles/researcher.md +6 -6
  34. package/preset/odai/skills/odai/assets/routing-roles/reviewer.md +11 -11
  35. package/preset/odai/skills/odai/assets/task-state.md +14 -14
  36. package/preset/odai/skills/odai/manifest.json +37 -37
  37. package/preset/odai/skills/odai/references/craft.md +54 -54
  38. package/preset/odai/skills/odai/references/dao.md +30 -30
  39. package/preset/odai/skills/odai/references/leverage.md +53 -53
  40. package/preset/odai/skills/odai/references/planning.md +82 -82
  41. package/preset/odai/skills/odai/references/support.md +36 -36
  42. package/preset/odai/skills/odai/references/verification.md +23 -23
  43. package/preset/odai/skills/odai/scripts/build-hooks.mjs +273 -273
  44. package/preset/odai/skills/odai/scripts/build-routing.mjs +274 -274
  45. package/preset/odai/skills/odai/scripts/install-routing.mjs +397 -397
  46. package/preset/odai/skills/odai/scripts/odai-hook.mjs +457 -457
  47. package/preset/odai/skills/odai/scripts/run-role.mjs +336 -336
  48. package/preset/odai/skills/odai/scripts/run-routing.mjs +1154 -1154
  49. package/preset/odai/skills/odai/scripts/verify-routing.mjs +103 -103
  50. package/scripts/verify-agent-load.mjs +257 -257
  51. 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
+ }