selftune 0.2.21 → 0.2.22

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.
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Universal hook types for multi-agent abstraction.
3
+ * All platform adapters normalize their native events to these types.
4
+ */
5
+
6
+ /** Supported agent platforms */
7
+ export type HookPlatform = "claude-code" | "codex" | "opencode" | "cline" | "pi";
8
+
9
+ /** Normalized event types across all platforms */
10
+ export type HookEventType = "pre_tool_use" | "post_tool_use" | "prompt_submit" | "session_end";
11
+
12
+ /**
13
+ * Platform-agnostic hook event. Each adapter normalizes its native payload to this shape.
14
+ * Fields are optional because not all platforms provide all data.
15
+ */
16
+ export interface UnifiedHookEvent {
17
+ platform: HookPlatform;
18
+ event_type: HookEventType;
19
+ session_id: string;
20
+ cwd?: string;
21
+ transcript_path?: string;
22
+
23
+ // Tool-related (pre_tool_use / post_tool_use)
24
+ tool_name?: string;
25
+ tool_input?: Record<string, unknown>;
26
+ tool_output?: Record<string, unknown>;
27
+
28
+ // Prompt-related (prompt_submit)
29
+ prompt?: string;
30
+
31
+ // Session-related (session_end)
32
+ last_message?: string;
33
+
34
+ /** Original platform-specific payload, preserved for platform-specific logic */
35
+ raw_payload?: unknown;
36
+ }
37
+
38
+ /**
39
+ * Hook response returned to the host agent.
40
+ * Adapters translate this back to platform-specific format.
41
+ */
42
+ export interface HookResponse {
43
+ /** Whether the hook modified the input */
44
+ modified: boolean;
45
+ /** Decision for PreToolUse guards */
46
+ decision?: "allow" | "block" | "skip";
47
+ /** Modified tool input (for pre_tool_use hooks that modify commands) */
48
+ updated_input?: Record<string, unknown>;
49
+ /** Advisory message (stderr suggestions) */
50
+ message?: string;
51
+ /** Additional context to inject (stdout JSON for Claude Code) */
52
+ context?: string;
53
+ }
54
+
55
+ /** Generic session state for dedup/tracking across hook invocations */
56
+ export interface SessionState<T extends Record<string, unknown> = Record<string, unknown>> {
57
+ session_id: string;
58
+ created_at: string;
59
+ data: T;
60
+ }
61
+
62
+ /** Platform event mapping reference */
63
+ export const PLATFORM_EVENT_MAP: Record<HookPlatform, Partial<Record<HookEventType, string>>> = {
64
+ "claude-code": {
65
+ pre_tool_use: "PreToolUse",
66
+ post_tool_use: "PostToolUse",
67
+ prompt_submit: "UserPromptSubmit",
68
+ session_end: "Stop",
69
+ },
70
+ codex: {
71
+ pre_tool_use: "PreToolUse",
72
+ post_tool_use: "PostToolUse",
73
+ prompt_submit: "SessionStart",
74
+ session_end: "Stop",
75
+ },
76
+ opencode: {
77
+ pre_tool_use: "tool.execute.before",
78
+ post_tool_use: "tool.execute.after",
79
+ session_end: "session.idle",
80
+ },
81
+ cline: {
82
+ post_tool_use: "PostToolUse",
83
+ session_end: "TaskComplete",
84
+ },
85
+ pi: {
86
+ pre_tool_use: "tool_call",
87
+ post_tool_use: "tool_result",
88
+ session_end: "session_shutdown",
89
+ },
90
+ };
@@ -30,6 +30,9 @@
30
30
  * selftune telemetry — Manage anonymous usage analytics (status, enable, disable)
31
31
  * selftune alpha <subcommand> — Alpha program management (upload)
32
32
  * selftune hook <name> — Run a hook by name (prompt-log, session-stop, etc.)
33
+ * selftune codex <subcommand> — Codex platform hooks (hook, install)
34
+ * selftune opencode <sub> — OpenCode platform hooks (hook, install)
35
+ * selftune cline <subcommand> — Cline platform hooks (hook, install)
33
36
  */
34
37
 
35
38
  import { CLIError, handleCLIError } from "./utils/cli-error.js";
@@ -73,20 +76,26 @@ Commands:
73
76
  alpha <subcommand> Alpha program management (upload)
74
77
  telemetry Manage anonymous usage analytics (status, enable, disable)
75
78
  hook <name> Run a hook by name (prompt-log, session-stop, etc.)
79
+ codex <sub> Codex platform hooks (hook, install)
80
+ opencode <sub> OpenCode platform hooks (hook, install)
81
+ cline <sub> Cline platform hooks (hook, install)
76
82
 
77
83
  Run 'selftune <command> --help' for command-specific options.`);
78
84
  process.exit(0);
79
85
  }
80
86
 
81
- // Track command usage (lazy importavoids loading crypto/os on --help or no-op paths)
82
- if (command && command !== "--help" && command !== "-h") {
87
+ // Fast-path commands (real-time hooks)skip analytics and auto-update to minimize latency
88
+ const FAST_COMMANDS: ReadonlySet<string> = new Set(["hook", "codex", "opencode", "cline"]);
89
+
90
+ // Track command usage (lazy import — skip for hooks and --help to avoid loading crypto/os)
91
+ if (command && !FAST_COMMANDS.has(command) && command !== "--help" && command !== "-h") {
83
92
  import("./analytics.js")
84
93
  .then(({ trackEvent }) => trackEvent("command_run", { command }))
85
94
  .catch(() => {});
86
95
  }
87
96
 
88
- // Auto-update check (skip for hooks — they must be fast — and --help)
89
- if (command && command !== "hook" && command !== "--help" && command !== "-h") {
97
+ // Auto-update check (skip for hooks and platform hook commands — they must be fast — and --help)
98
+ if (command && !FAST_COMMANDS.has(command) && command !== "--help" && command !== "-h") {
90
99
  const { autoUpdate } = await import("./auto-update.js");
91
100
  await autoUpdate();
92
101
  }
@@ -815,6 +824,49 @@ Output:
815
824
  process.exit(result.status ?? 1);
816
825
  break;
817
826
  }
827
+ // ── Platform hook adapters ─────────────────────────────────────────
828
+
829
+ case "codex":
830
+ case "opencode":
831
+ case "cline": {
832
+ const platform = command;
833
+ const displayName = { codex: "Codex", opencode: "OpenCode", cline: "Cline" }[platform];
834
+ const sub = process.argv[2];
835
+ if (!sub || sub === "--help" || sub === "-h") {
836
+ console.log(`selftune ${platform} — ${displayName} platform hooks
837
+
838
+ Usage:
839
+ selftune ${platform} <subcommand> [options]
840
+
841
+ Subcommands:
842
+ hook Handle a real-time hook event from ${displayName}
843
+ install Install or remove selftune hooks in ${displayName} config
844
+
845
+ Run 'selftune ${platform} <subcommand> --help' for subcommand-specific options.`);
846
+ process.exit(0);
847
+ }
848
+ process.argv = [process.argv[0], process.argv[1], ...process.argv.slice(3)];
849
+ switch (sub) {
850
+ case "hook": {
851
+ const { cliMain } = await import(`./adapters/${platform}/hook.js`);
852
+ await cliMain();
853
+ break;
854
+ }
855
+ case "install": {
856
+ const { cliMain } = await import(`./adapters/${platform}/install.js`);
857
+ await cliMain();
858
+ break;
859
+ }
860
+ default:
861
+ throw new CLIError(
862
+ `Unknown ${platform} subcommand: ${sub}`,
863
+ "UNKNOWN_COMMAND",
864
+ `selftune ${platform} --help`,
865
+ );
866
+ }
867
+ break;
868
+ }
869
+
818
870
  default:
819
871
  throw new CLIError(`Unknown command: ${command}`, "UNKNOWN_COMMAND", "selftune --help");
820
872
  }
@@ -6,9 +6,9 @@
6
6
  * modules can reuse the same calling logic.
7
7
  */
8
8
 
9
- import { readFileSync, writeFileSync } from "node:fs";
9
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
10
10
  import { tmpdir } from "node:os";
11
- import { join } from "node:path";
11
+ import { dirname, join, resolve } from "node:path";
12
12
 
13
13
  import { AGENT_CANDIDATES } from "../constants.js";
14
14
  import { createLogger } from "./logging.js";
@@ -33,6 +33,40 @@ function resolveModelFlag(flag: string): string {
33
33
  return CLAUDE_MODEL_ALIASES[flag] ?? flag;
34
34
  }
35
35
 
36
+ /**
37
+ * Map selftune model aliases to OpenCode provider/model format.
38
+ * OpenCode uses "provider/model" syntax (e.g. "anthropic/claude-sonnet-4-20250514").
39
+ */
40
+ const OPENCODE_MODEL_MAP: Record<string, string> = {
41
+ haiku: "anthropic/claude-haiku-4-5-20251001",
42
+ sonnet: "anthropic/claude-sonnet-4-20250514",
43
+ opus: "anthropic/claude-opus-4-20250514",
44
+ };
45
+
46
+ /** Resolve a model alias to OpenCode's provider/model format. */
47
+ function resolveOpenCodeModel(flag: string): string {
48
+ return OPENCODE_MODEL_MAP[flag] ?? flag;
49
+ }
50
+
51
+ // ---------------------------------------------------------------------------
52
+ // Bundled agent file loading (for codex inline prompt injection)
53
+ // ---------------------------------------------------------------------------
54
+
55
+ const BUNDLED_AGENT_DIR = resolve(dirname(import.meta.path), "..", "..", "..", "skill", "agents");
56
+
57
+ /**
58
+ * Read the bundled agent markdown file and return its body (without frontmatter).
59
+ * Used by codex path to inline agent instructions into the prompt since codex
60
+ * has no --agent flag.
61
+ */
62
+ function loadAgentInstructions(agentName: string): string | null {
63
+ const filePath = join(BUNDLED_AGENT_DIR, `${agentName}.md`);
64
+ if (!existsSync(filePath)) return null;
65
+ const content = readFileSync(filePath, "utf-8");
66
+ // Strip YAML frontmatter
67
+ return content.replace(/^---\n[\s\S]*?\n---\n*/, "").trim();
68
+ }
69
+
36
70
  // ---------------------------------------------------------------------------
37
71
  // Agent detection
38
72
  // ---------------------------------------------------------------------------
@@ -155,7 +189,11 @@ export async function callViaAgent(
155
189
  } else if (agent === "codex") {
156
190
  cmd = ["codex", "exec", "--skip-git-repo-check", promptContent];
157
191
  } else if (agent === "opencode") {
158
- cmd = ["opencode", "-p", promptContent, "-f", "text", "-q"];
192
+ cmd = ["opencode", "run"];
193
+ if (modelFlag) {
194
+ cmd.push("--model", resolveOpenCodeModel(modelFlag));
195
+ }
196
+ cmd.push(promptContent);
159
197
  } else {
160
198
  throw new Error(`Unknown agent: ${agent}`);
161
199
  }
@@ -222,9 +260,9 @@ export async function callViaAgent(
222
260
  // Call LLM via named subagent (multi-turn, agentic)
223
261
  // ---------------------------------------------------------------------------
224
262
 
225
- /** Options for calling a named Claude Code subagent. */
263
+ /** Options for calling a named subagent (Claude Code or OpenCode). */
226
264
  export interface SubagentCallOptions {
227
- /** Name of the subagent (synced into ~/.claude/agents/ by selftune init/update). */
265
+ /** Name of the subagent (synced into ~/.claude/agents/ or opencode.json by selftune init/update). */
228
266
  agentName: string;
229
267
  /** The task prompt for the subagent. */
230
268
  prompt: string;
@@ -243,13 +281,13 @@ export interface SubagentCallOptions {
243
281
  }
244
282
 
245
283
  /**
246
- * Call a named Claude Code subagent in print mode. The subagent runs its
247
- * multi-turn workflow (reading files, running commands, etc.) and returns
248
- * the final text output.
284
+ * Call a named subagent in print mode. The subagent runs its multi-turn
285
+ * workflow (reading files, running commands, etc.) and returns the final
286
+ * text output.
249
287
  *
250
- * Unlike callViaAgent(), this does NOT use --bare (agents need discovery)
251
- * and passes --agent + --max-turns for agentic multi-turn behavior.
252
- * Only supports the claude CLI.
288
+ * Supports Claude Code (`claude --agent`), OpenCode (`opencode run --agent`),
289
+ * and Codex (`codex exec` with agent instructions inlined into the prompt).
290
+ * Auto-detects the available agent CLI.
253
291
  */
254
292
  export async function callViaSubagent(options: SubagentCallOptions): Promise<string> {
255
293
  const {
@@ -263,31 +301,58 @@ export async function callViaSubagent(options: SubagentCallOptions): Promise<str
263
301
  allowedTools,
264
302
  } = options;
265
303
 
266
- const cmd: string[] = [
267
- "claude",
268
- "-p",
269
- prompt,
270
- "--agent",
271
- agentName,
272
- "--max-turns",
273
- String(maxTurns),
274
- ];
275
-
276
- if (appendSystemPrompt) {
277
- cmd.push("--append-system-prompt", appendSystemPrompt);
278
- }
279
- if (modelFlag) {
280
- const resolved = resolveModelFlag(modelFlag);
281
- cmd.push("--model", resolved);
304
+ const agent = detectAgent();
305
+ if (!agent || (agent !== "claude" && agent !== "opencode" && agent !== "codex")) {
306
+ throw new Error(
307
+ `Subagent calls require 'claude', 'opencode', or 'codex' CLI in PATH (detected: ${agent ?? "none"})`,
308
+ );
282
309
  }
283
- if (effort) {
284
- cmd.push("--effort", effort);
285
- }
286
- if (allowedTools && allowedTools.length > 0) {
287
- cmd.push("--allowedTools", ...allowedTools);
310
+
311
+ let cmd: string[];
312
+
313
+ if (agent === "opencode") {
314
+ // OpenCode supports --agent and --model but not allowedTools, appendSystemPrompt, or maxTurns
315
+ if (allowedTools?.length || appendSystemPrompt) {
316
+ logger.warn(
317
+ `Subagent '${agentName}' on opencode: allowedTools and appendSystemPrompt are not supported and will be ignored`,
318
+ );
319
+ }
320
+ cmd = ["opencode", "run", "--agent", agentName];
321
+ if (modelFlag) {
322
+ cmd.push("--model", resolveOpenCodeModel(modelFlag));
323
+ }
324
+ cmd.push(prompt);
325
+ } else if (agent === "codex") {
326
+ // Codex has no --agent flag; inline the agent instructions into the prompt.
327
+ // allowedTools, appendSystemPrompt, maxTurns, and effort are not supported.
328
+ if (allowedTools?.length || appendSystemPrompt) {
329
+ logger.warn(
330
+ `Subagent '${agentName}' on codex: allowedTools and appendSystemPrompt are not supported and will be ignored`,
331
+ );
332
+ }
333
+ const agentInstructions = loadAgentInstructions(agentName);
334
+ const fullPrompt = agentInstructions ? `${agentInstructions}\n\n---\n\n${prompt}` : prompt;
335
+ cmd = ["codex", "exec", "--skip-git-repo-check", fullPrompt];
336
+ } else {
337
+ // Claude Code
338
+ cmd = ["claude", "-p", prompt, "--agent", agentName, "--max-turns", String(maxTurns)];
339
+
340
+ if (appendSystemPrompt) {
341
+ cmd.push("--append-system-prompt", appendSystemPrompt);
342
+ }
343
+ if (modelFlag) {
344
+ const resolved = resolveModelFlag(modelFlag);
345
+ cmd.push("--model", resolved);
346
+ }
347
+ if (effort) {
348
+ cmd.push("--effort", effort);
349
+ }
350
+ if (allowedTools && allowedTools.length > 0) {
351
+ cmd.push("--allowedTools", ...allowedTools);
352
+ }
353
+ // Skip permissions since this runs non-interactively in a pipeline
354
+ cmd.push("--dangerously-skip-permissions");
288
355
  }
289
- // Skip permissions since this runs non-interactively in a pipeline
290
- cmd.push("--dangerously-skip-permissions");
291
356
 
292
357
  const maxRetries = retryOpts?.maxRetries ?? DEFAULT_MAX_RETRIES;
293
358
  const initialBackoffMs = retryOpts?.initialBackoffMs ?? DEFAULT_INITIAL_BACKOFF_MS;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "selftune",
3
- "version": "0.2.21",
3
+ "version": "0.2.22",
4
4
  "description": "Self-improving skills CLI for AI agents",
5
5
  "keywords": [
6
6
  "agent",
package/skill/SKILL.md CHANGED
@@ -125,6 +125,14 @@ selftune uninstall [--dry-run] [--keep-logs] [--npm-uninstall]
125
125
  # Hook dispatch (for debugging/manual invocation)
126
126
  selftune hook <name> # prompt-log | session-stop | skill-eval | auto-activate | skill-change-guard | evolution-guard
127
127
 
128
+ # Platform hooks (non-Claude-Code agents)
129
+ selftune codex hook
130
+ selftune codex install [--dry-run] [--uninstall]
131
+ selftune opencode hook
132
+ selftune opencode install [--dry-run] [--uninstall]
133
+ selftune cline hook
134
+ selftune cline install [--dry-run] [--uninstall]
135
+
128
136
  # Alpha enrollment (device-code flow — browser opens automatically)
129
137
  selftune init --alpha --alpha-email <email>
130
138
  selftune alpha upload [--dry-run]
@@ -169,6 +177,7 @@ selftune status # shows c
169
177
  | repair, rebuild usage, fix skill usage, trustworthy usage, repair-skill-usage | RepairSkillUsage | Workflows/RepairSkillUsage.md |
170
178
  | export canonical, canonical export, canonical telemetry, push payload | ExportCanonical | Workflows/ExportCanonical.md |
171
179
  | hook, run hook, invoke hook, manual hook, debug hook | Hook | Workflows/Hook.md |
180
+ | codex hooks, codex install, codex setup, opencode hooks, opencode install, opencode setup, cline hooks, cline install, cline setup, multi-platform, platform hooks, non-claude hooks, multiple agents, multi-agent | PlatformHooks | Workflows/PlatformHooks.md |
172
181
  | export, dump, jsonl, export sqlite, debug export | Export | _(direct command — no workflow file)_ |
173
182
  | status, health summary, skill health, how are skills, skills doing, run selftune | Status | _(direct command — no workflow file)_ |
174
183
  | last, last session, recent session, what happened, what changed | Last | _(direct command — no workflow file)_ |
@@ -357,6 +366,7 @@ accomplish a task _using_ a skill, route to that skill instead.
357
366
  | `Workflows/CreatorContributions.md` | Manage bundled `selftune.contribute.json` configs | When preparing a skill package for creator contributions |
358
367
  | `Workflows/ExportCanonical.md` | Export canonical telemetry for downstream use | When exporting data for external consumption |
359
368
  | `Workflows/Hook.md` | Manual hook invocation for debugging | When debugging or testing hooks manually |
369
+ | `Workflows/PlatformHooks.md` | Non-Claude-Code platform hook install/config | When setting up Codex, OpenCode, or Cline hooks |
360
370
  | `references/logs.md` | Log file formats (telemetry, usage, queries, audit) | When parsing or debugging log files |
361
371
  | `references/grading-methodology.md` | 3-tier grading model, evidence standards | When grading sessions or interpreting grades |
362
372
  | `references/invocation-taxonomy.md` | 4 invocation types, coverage analysis | When analyzing trigger coverage |
@@ -7,6 +7,7 @@ Bootstrap selftune for first-time use or after changing environments.
7
7
  - The user asks to set up selftune, configure selftune, or initialize selftune
8
8
  - The agent detects `~/.selftune/config.json` does not exist
9
9
  - The user has switched agent platforms (Claude Code, Codex, OpenCode)
10
+ - The user wants to add hooks for additional platforms (multi-agent setup)
10
11
 
11
12
  ## Default Command
12
13
 
@@ -136,15 +137,49 @@ Code subagent calls stay up to date.
136
137
  | `PostToolUse` (Bash) | `hooks/commit-track.ts` | Track git commits for session traceability | Fast-path: skips non-git Bash commands |
137
138
  | `Stop` | `hooks/session-stop.ts` | Capture session telemetry | Runs async (non-blocking), 60s timeout |
138
139
 
139
- **Codex agents:**
140
+ ### 4b. Multi-Platform Hooks
140
141
 
141
- - Use `selftune ingest wrap-codex` for real-time telemetry capture (see `Workflows/Ingest.md`)
142
- - Or batch-ingest existing sessions with `selftune ingest codex`
142
+ After Claude Code hooks are installed, check whether the user has **other** agent
143
+ CLIs available. Run these checks:
143
144
 
144
- **OpenCode agents:**
145
+ ```bash
146
+ which codex 2>/dev/null && echo "codex available"
147
+ which opencode 2>/dev/null && echo "opencode available"
148
+ ls ~/Documents/Cline/Hooks/ 2>/dev/null && echo "cline available"
149
+ ```
150
+
151
+ If **any** additional platforms are detected, use `AskUserQuestion` listing only
152
+ the platforms that were actually found:
153
+
154
+ > I detected these agent platforms in addition to your primary one:
155
+ > - [list only detected platforms, e.g. "Codex", "OpenCode"]
156
+ >
157
+ > Would you like to install selftune hooks for any of them? This enables
158
+ > real-time skill tracking across all your agents.
159
+
160
+ Options:
161
+ - `Yes — install hooks for all detected platforms`
162
+ - `Let me pick — show me the list` (then present only the detected platforms)
163
+ - `No — skip for now` (they can always run `selftune <platform> install` later)
164
+
165
+ For each platform the user selects, run the install command:
166
+
167
+ ```bash
168
+ selftune codex install # writes hooks.json entries
169
+ selftune opencode install # writes shell shim + config entries
170
+ selftune cline install # creates hook scripts
171
+ ```
172
+
173
+ Use `--dry-run` first if the user wants to preview. See `Workflows/PlatformHooks.md`
174
+ for platform-specific details.
175
+
176
+ **Batch ingest** fallback for platforms without real-time hooks or to backfill history:
145
177
 
146
- - Use `selftune ingest opencode` to import sessions from the SQLite database
147
- - See `Workflows/Ingest.md` for details
178
+ ```bash
179
+ selftune ingest codex # import Codex rollout sessions
180
+ selftune ingest opencode # import OpenCode sessions from SQLite
181
+ selftune ingest openclaw # import OpenClaw sessions
182
+ ```
148
183
 
149
184
  ### 5. Initialize Memory Directory
150
185
 
@@ -387,6 +422,13 @@ retrying with `selftune init --alpha --alpha-email <email> --force`.
387
422
  > and optional display name in chat, then run `selftune init --alpha --alpha-email ...`.
388
423
  > The browser opens automatically for approval. No manual key management needed.
389
424
 
425
+ **User uses multiple agents (Claude Code + Codex, etc.)**
426
+
427
+ > Run `selftune init` for the primary agent, then offer to install hooks for
428
+ > additional detected platforms. Run `selftune codex install`, `selftune opencode install`,
429
+ > or `selftune cline install` as needed. All platforms write to the same shared
430
+ > log schema — no extra config required.
431
+
390
432
  **Hooks not capturing data**
391
433
 
392
434
  > Run `selftune doctor` to check hook installation. Parse the JSON output
@@ -0,0 +1,93 @@
1
+ # Platform Hooks Workflow
2
+
3
+ ## Purpose
4
+
5
+ Install and configure selftune hooks for non-Claude-Code platforms (Codex, OpenCode, Cline).
6
+
7
+ ## When to Use
8
+
9
+ - User wants selftune on Codex, OpenCode, or Cline
10
+ - User asks about multi-platform support
11
+ - User wants real-time skill tracking on a non-Claude-Code agent
12
+
13
+ ## Commands
14
+
15
+ ### Install hooks for a platform
16
+
17
+ ```bash
18
+ selftune <platform> install [--dry-run] [--uninstall]
19
+ ```
20
+
21
+ Supported platforms: `codex`, `opencode`, `cline`
22
+
23
+ | Flag | Description |
24
+ | ------------- | ---------------------------------------------- |
25
+ | `--dry-run` | Preview what would be installed without writing |
26
+ | `--uninstall` | Remove selftune hooks from the platform |
27
+ | `--help, -h` | Show usage help |
28
+
29
+ ### Hook handler (called by the agent, not the user)
30
+
31
+ ```bash
32
+ selftune <platform> hook
33
+ ```
34
+
35
+ This is called automatically by the agent's hook system. Users don't run this directly.
36
+
37
+ ## Platform Details
38
+
39
+ ### Codex
40
+
41
+ - Config: `~/.codex/hooks.json`
42
+ - Events: SessionStart, PreToolUse, PostToolUse, Stop
43
+ - Install creates hooks.json entries that prefer `$SELFTUNE_CLI_PATH codex hook`, otherwise `npx -y selftune@latest codex hook`
44
+
45
+ ### OpenCode
46
+
47
+ - Config: `./opencode.json` or `~/.config/opencode/opencode.json`
48
+ - Plugin dir: `~/.config/opencode/plugins/` (global) or `./.opencode/plugins/` (project)
49
+ - Events: tool.execute.before, tool.execute.after, session.idle (via event handler)
50
+ - Install writes a TypeScript plugin file (`selftune-opencode-plugin.ts`) into the plugins directory (auto-discovered by OpenCode at startup)
51
+ - Agents are registered in the `agent` config key (identified by `[selftune]` description prefix)
52
+
53
+ ### Cline
54
+
55
+ - Config: `~/Documents/Cline/Hooks/`
56
+ - Events: PostToolUse, TaskComplete, TaskCancel
57
+ - Install creates executable shell scripts in the hooks directory
58
+
59
+ ## Examples
60
+
61
+ ### Codex
62
+
63
+ ```bash
64
+ selftune codex install # Install hooks into ~/.codex/hooks.json
65
+ selftune codex install --dry-run # Preview changes without writing
66
+ selftune codex install --uninstall # Remove selftune hooks
67
+ ```
68
+
69
+ ### OpenCode
70
+
71
+ ```bash
72
+ selftune opencode install # Install plugin (selftune-opencode-plugin.ts) + config entries
73
+ selftune opencode install --dry-run # Preview changes without writing
74
+ selftune opencode install --uninstall # Remove selftune plugin and config entries
75
+ ```
76
+
77
+ ### Cline
78
+
79
+ ```bash
80
+ selftune cline install # Create hook scripts in ~/Documents/Cline/Hooks/
81
+ selftune cline install --dry-run # Preview what would be created
82
+ selftune cline install --uninstall # Remove selftune hook scripts
83
+ ```
84
+
85
+ ### Hook handler (agent-only, not user-facing)
86
+
87
+ The hook subcommand is called automatically by the agent. Users do not run it directly:
88
+
89
+ ```bash
90
+ printf '%s\n' "$PAYLOAD" | selftune codex hook
91
+ printf '%s\n' "$PAYLOAD" | selftune opencode hook
92
+ printf '%s\n' "$PAYLOAD" | selftune cline hook
93
+ ```