oh-my-githubcopilot 1.8.1 → 2.0.0-alpha.1cd01f4

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 (60) hide show
  1. package/.claude-plugin/plugin.json +23 -3
  2. package/AGENTS.md +29 -25
  3. package/CHANGELOG.md +49 -336
  4. package/README.md +7 -2
  5. package/agents/code-reviewer.agent.md +5 -0
  6. package/agents/{simplifier.agent.md → code-simplifier.agent.md} +2 -2
  7. package/agents/debugger.agent.md +1 -1
  8. package/agents/document-specialist.agent.md +6 -1
  9. package/agents/{explorer.agent.md → explore.agent.md} +2 -2
  10. package/agents/security-reviewer.agent.md +1 -1
  11. package/agents/test-engineer.agent.md +3 -1
  12. package/bin/omp-statusline.mjs +12 -11
  13. package/bin/omp-statusline.mjs.map +2 -2
  14. package/bin/omp.mjs +885 -42
  15. package/bin/omp.mjs.map +4 -4
  16. package/dist/hooks/delegation-enforcer.mjs +65 -13
  17. package/dist/hooks/delegation-enforcer.mjs.map +4 -4
  18. package/dist/hooks/hud-emitter.mjs +80 -28
  19. package/dist/hooks/hud-emitter.mjs.map +4 -4
  20. package/dist/hooks/keyword-detector.mjs +117 -11
  21. package/dist/hooks/keyword-detector.mjs.map +4 -4
  22. package/dist/hooks/model-router.mjs +64 -12
  23. package/dist/hooks/model-router.mjs.map +4 -4
  24. package/dist/hooks/stop-continuation.mjs +66 -14
  25. package/dist/hooks/stop-continuation.mjs.map +4 -4
  26. package/dist/hooks/token-tracker.mjs +81 -19
  27. package/dist/hooks/token-tracker.mjs.map +4 -4
  28. package/dist/mcp/server.mjs +17 -16
  29. package/dist/mcp/server.mjs.map +2 -2
  30. package/extension/extension.mjs +659 -0
  31. package/hooks/hooks.json +6 -6
  32. package/package.json +3 -2
  33. package/plugin.json +23 -3
  34. package/skills/build-fix/SKILL.md +35 -0
  35. package/skills/cancel/SKILL.md +33 -0
  36. package/skills/ccg/SKILL.md +37 -0
  37. package/skills/code-review/SKILL.md +33 -0
  38. package/skills/deep-dive/SKILL.md +33 -0
  39. package/skills/deepinit/SKILL.md +33 -0
  40. package/skills/deepsearch/SKILL.md +33 -0
  41. package/skills/design/SKILL.md +37 -0
  42. package/skills/external-context/SKILL.md +33 -0
  43. package/skills/help/SKILL.md +33 -0
  44. package/skills/omp-doctor/SKILL.md +23 -1
  45. package/skills/omp-reference/SKILL.md +20 -24
  46. package/skills/remember/SKILL.md +39 -0
  47. package/skills/research/SKILL.md +1 -1
  48. package/skills/sciomc/SKILL.md +35 -0
  49. package/skills/security-review/SKILL.md +33 -0
  50. package/skills/self-improve/SKILL.md +35 -0
  51. package/skills/ultragoal/SKILL.md +33 -0
  52. package/skills/ultraqa/SKILL.md +33 -0
  53. package/skills/verify/SKILL.md +33 -0
  54. package/skills/visual-verdict/SKILL.md +35 -0
  55. package/skills/web-clone/SKILL.md +35 -0
  56. package/skills/writer-memory/SKILL.md +37 -0
  57. package/agents/orchestrator.agent.md +0 -26
  58. package/agents/researcher.agent.md +0 -18
  59. package/agents/reviewer.agent.md +0 -23
  60. package/agents/tester.agent.md +0 -20
@@ -1,8 +1,8 @@
1
1
  // src/hooks/hud-emitter.mts
2
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
2
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
3
3
  import { createRequire } from "module";
4
- import { homedir as homedir2 } from "os";
5
- import { join as join2 } from "path";
4
+ import { homedir as homedir3 } from "os";
5
+ import { join as join3 } from "path";
6
6
 
7
7
  // src/hud/statusline.mts
8
8
  import { mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
@@ -32,7 +32,7 @@ function renderPlain(state) {
32
32
  const model = state.activeModel || "sonnet";
33
33
  const reqWarningPlain = state.warningActive ? " !!" : "";
34
34
  const reqStrPlain = `req:${state.premiumRequests ?? 0}/${state.premiumRequestsTotal ?? 1500}${reqWarningPlain}`;
35
- return `[OMP v${state.version}] ${mode} | ${model} | ctx:${ctx}% | tok:~${tokens}/${state.tokensTotal} | ${reqStrPlain} | ${age} | tools:${state.toolsUsed?.size || 0}/${state.toolsTotal ?? 13} | skills:${state.skillsUsed?.size || 0}/${state.skillsTotal ?? 25} | agents:${state.cumulativeAgentsUsed}/${state.agentsTotal ?? 23} | ${state.status}`;
35
+ return `[OMP v${state.version}] ${mode} | ${model} | ctx:${ctx}% | tok:~${tokens}/${state.tokensTotal} | ${reqStrPlain} | ${age} | tools:${state.toolsUsed?.size || 0}/${state.toolsTotal ?? 13} | skills:${state.skillsUsed?.size || 0}/${state.skillsTotal ?? 25} | agents:${state.cumulativeAgentsUsed}/${state.agentsTotal ?? 19} | ${state.status}`;
36
36
  }
37
37
 
38
38
  // src/hud/statusline.mts
@@ -98,8 +98,8 @@ function deserializeHudState(raw) {
98
98
  toolsUsed,
99
99
  skillsUsed,
100
100
  toolsTotal: typeof value.toolsTotal === "number" ? value.toolsTotal : 13,
101
- skillsTotal: typeof value.skillsTotal === "number" ? value.skillsTotal : 25,
102
- agentsTotal: typeof value.agentsTotal === "number" ? value.agentsTotal : 23,
101
+ skillsTotal: typeof value.skillsTotal === "number" ? value.skillsTotal : 59,
102
+ agentsTotal: typeof value.agentsTotal === "number" ? value.agentsTotal : 19,
103
103
  premiumRequests: typeof value.premiumRequests === "number" ? value.premiumRequests : 0,
104
104
  premiumRequestsTotal: typeof value.premiumRequestsTotal === "number" ? value.premiumRequestsTotal : DEFAULT_PREMIUM_REQUESTS_TOTAL,
105
105
  warningActive: typeof value.warningActive === "boolean" ? value.warningActive : false
@@ -131,8 +131,8 @@ function buildHudState(snapshot, now = Date.now()) {
131
131
  toolsUsed,
132
132
  skillsUsed,
133
133
  toolsTotal: 13,
134
- skillsTotal: 25,
135
- agentsTotal: 23,
134
+ skillsTotal: 59,
135
+ agentsTotal: 19,
136
136
  premiumRequests: snapshot.premium_requests ?? 0,
137
137
  premiumRequestsTotal: snapshot.premium_requests_total ?? DEFAULT_PREMIUM_REQUESTS_TOTAL,
138
138
  warningActive: snapshot.warning_active ?? false
@@ -153,17 +153,17 @@ function writeHudArtifacts(snapshot, paths = getStatuslinePaths()) {
153
153
  return { line, state, paths };
154
154
  }
155
155
  function readStatusline(paths = getStatuslinePaths()) {
156
- try {
157
- const line = readFileSync(paths.displayPath, "utf-8").trim();
158
- if (line) return line;
159
- } catch {
160
- }
161
156
  try {
162
157
  const parsed = JSON.parse(readFileSync(paths.statusJsonPath, "utf-8"));
163
158
  const state = deserializeHudState(parsed);
164
159
  if (state) return renderPlain(state);
165
160
  } catch {
166
161
  }
162
+ try {
163
+ const line = readFileSync(paths.displayPath, "utf-8").trim();
164
+ if (line) return line;
165
+ } catch {
166
+ }
167
167
  try {
168
168
  const line = readFileSync(paths.legacyLinePath, "utf-8").trim();
169
169
  if (line) return line;
@@ -171,23 +171,84 @@ function readStatusline(paths = getStatuslinePaths()) {
171
171
  }
172
172
  return DEFAULT_STATUSLINE;
173
173
  }
174
- if (process.argv[1] === fileURLToPath(import.meta.url)) {
174
+ if (process.argv[1] === fileURLToPath(import.meta.url) && (process.argv[1].endsWith("omp-statusline.mjs") || process.argv[1].endsWith("statusline.mts"))) {
175
175
  console.log(readStatusline());
176
176
  }
177
177
 
178
178
  // src/hooks/hud-emitter.mts
179
179
  import { fileURLToPath as fileURLToPath2 } from "url";
180
+
181
+ // src/hooks/runner.mts
182
+ import { appendFileSync, mkdirSync as mkdirSync2 } from "fs";
183
+ import { homedir as homedir2 } from "os";
184
+ import { join as join2 } from "path";
185
+ async function readStdin() {
186
+ const readStdinActual = async () => {
187
+ const chunks = [];
188
+ for await (const chunk of process.stdin) {
189
+ chunks.push(String(chunk));
190
+ }
191
+ return chunks.join("");
192
+ };
193
+ const stdinTimeout = new Promise(
194
+ (resolve) => setTimeout(
195
+ () => resolve(""),
196
+ parseInt(process.env.OMP_HOOK_STDIN_TIMEOUT_MS ?? "500") || 500
197
+ )
198
+ );
199
+ return Promise.race([readStdinActual(), stdinTimeout]);
200
+ }
201
+ function logHookFailure(hook, reason) {
202
+ try {
203
+ process.stderr.write(`[omp hook fail-open] ${hook}: ${reason}
204
+ `);
205
+ } catch {
206
+ }
207
+ try {
208
+ const logsDir = join2(homedir2(), ".omp", "logs");
209
+ mkdirSync2(logsDir, { recursive: true });
210
+ const record = JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), hook, reason });
211
+ appendFileSync(join2(logsDir, "hook-failures.jsonl"), record + "\n", "utf-8");
212
+ } catch {
213
+ }
214
+ }
215
+ async function runHookMain(processHook2, options = {}) {
216
+ let outputJson;
217
+ try {
218
+ const input = JSON.parse(await readStdin());
219
+ const serialized = JSON.stringify(processHook2(input));
220
+ if (typeof serialized !== "string") {
221
+ throw new Error("hook produced no serializable output");
222
+ }
223
+ outputJson = serialized;
224
+ } catch (err) {
225
+ const reason = err instanceof Error ? err.message : String(err);
226
+ logHookFailure(options.hookName ?? "unknown", reason);
227
+ const failOpen = {
228
+ ...options.failOpenDecision ? { decision: "allow" } : {},
229
+ status: "error",
230
+ latencyMs: 0,
231
+ mutations: [],
232
+ log: [`fail-open: ${reason}`]
233
+ };
234
+ outputJson = JSON.stringify(failOpen);
235
+ }
236
+ console.log(outputJson);
237
+ process.exitCode = 0;
238
+ }
239
+
240
+ // src/hooks/hud-emitter.mts
180
241
  var _require = createRequire(import.meta.url);
181
242
  var { version: PKG_VERSION } = _require("../../package.json");
182
243
  function getStatePath(sessionId) {
183
- const base = join2(process.env["HOME"] || homedir2(), ".omp", "state");
244
+ const base = join3(process.env["HOME"] || homedir3(), ".omp", "state");
184
245
  if (sessionId) {
185
- return join2(base, "sessions", sessionId, "session.json");
246
+ return join3(base, "sessions", sessionId, "session.json");
186
247
  }
187
- return join2(base, "session.json");
248
+ return join3(base, "session.json");
188
249
  }
189
250
  function ensureDir(path) {
190
- mkdirSync2(path.substring(0, path.lastIndexOf("/")), { recursive: true });
251
+ mkdirSync3(path.substring(0, path.lastIndexOf("/")), { recursive: true });
191
252
  }
192
253
  function stringifyOutput(value) {
193
254
  if (typeof value === "string") {
@@ -336,16 +397,7 @@ function processHook(input) {
336
397
  };
337
398
  }
338
399
  if (process.argv[1] === fileURLToPath2(import.meta.url)) {
339
- const input = JSON.parse(await readStdin());
340
- const output = processHook(input);
341
- console.log(JSON.stringify(output));
342
- }
343
- async function readStdin() {
344
- const chunks = [];
345
- for await (const chunk of process.stdin) {
346
- chunks.push(chunk);
347
- }
348
- return chunks.join("");
400
+ await runHookMain(processHook, { hookName: "hud-emitter" });
349
401
  }
350
402
  export {
351
403
  processHook
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../../src/hooks/hud-emitter.mts", "../../src/hud/statusline.mts", "../../src/hud/renderer.mts"],
4
- "sourcesContent": ["/**\n * hud-emitter hook\n * Trigger: post-cycle (PostToolUse + SessionStart)\n * Priority: 60\n *\n * Writes HUD artifacts after every tool call and initializes session state.\n */\n\nimport { mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { createRequire } from \"module\";\nimport { homedir } from \"os\";\nimport { join } from \"path\";\nimport { writeHudArtifacts } from \"../hud/statusline.mts\";\n\nconst _require = createRequire(import.meta.url);\nconst { version: PKG_VERSION } = _require(\"../../package.json\") as { version: string };\n\nexport interface HookInput {\n hook_type: \"SessionStart\" | \"PostToolUse\";\n tool_name?: string;\n tool_input?: unknown;\n tool_output?: unknown;\n session_id?: string;\n model?: string;\n}\n\nexport interface HookOutput {\n status: \"ok\" | \"skip\" | \"error\";\n latencyMs: number;\n mutations: Array<{ type: \"emit_hud\"; hudEmit: HudEmit } | { type: \"log\"; level: \"info\"; message: string }>;\n log: string[];\n}\n\nexport interface HudEmit {\n sessionId: string;\n activeMode: string | null;\n contextPct: number;\n tokensUsed: number;\n tokensTotal: number;\n agentsActive: string[];\n lastAgent: string;\n lastOutput: string;\n taskProgress: number;\n}\n\ninterface SessionState {\n version: string;\n session_id: string;\n started_at: number;\n updated_at: number;\n model: string;\n tokens_estimated: number;\n token_budget: number;\n context_pct: number;\n tools_used: string[];\n skills_used: string[];\n agents_used: string[];\n active_mode: string | null;\n last_output: string;\n task_progress: number;\n status: \"idle\" | \"running\" | \"waiting\" | \"complete\" | \"error\" | \"eco\";\n premium_requests: number;\n premium_requests_total: number;\n warning_active: boolean;\n}\n\nfunction getStatePath(sessionId?: string): string {\n const base = join(process.env[\"HOME\"] || homedir(), \".omp\", \"state\");\n if (sessionId) {\n return join(base, \"sessions\", sessionId, \"session.json\");\n }\n return join(base, \"session.json\");\n}\n\nfunction ensureDir(path: string): void {\n mkdirSync(path.substring(0, path.lastIndexOf(\"/\")), { recursive: true });\n}\n\nfunction stringifyOutput(value: unknown): string {\n if (typeof value === \"string\") {\n return value.trim().slice(0, 200);\n }\n if (value === undefined || value === null) {\n return \"\";\n }\n try {\n return JSON.stringify(value).slice(0, 200);\n } catch {\n return String(value).slice(0, 200);\n }\n}\n\nfunction buildEmit(state: SessionState): HudEmit {\n return {\n sessionId: state.session_id,\n activeMode: state.active_mode,\n contextPct: state.context_pct,\n tokensUsed: state.tokens_estimated,\n tokensTotal: state.token_budget,\n agentsActive: state.agents_used,\n lastAgent: state.agents_used[state.agents_used.length - 1] || \"-\",\n lastOutput: state.last_output,\n taskProgress: state.task_progress,\n };\n}\n\nconst MODEL_CONTEXTS: Record<string, number> = {\n \"claude-sonnet-4.5\": 200_000,\n \"claude-sonnet-4\": 200_000,\n \"claude-sonnet-4.6\": 200_000,\n \"claude-opus-4.6\": 200_000,\n \"gpt-5\": 128_000,\n \"gpt-5.4-mini\": 128_000,\n \"gemini-3-pro\": 128_000,\n default: 200_000,\n};\n\nfunction resolveTokenBudget(model: string): number {\n return MODEL_CONTEXTS[model] ?? MODEL_CONTEXTS[\"default\"] ?? 200_000;\n}\n\nfunction resolvePremiumRequestsTotal(): number {\n const env = process.env[\"OMP_PREMIUM_REQUESTS_TOTAL\"];\n if (env) {\n const parsed = parseInt(env, 10);\n if (!isNaN(parsed) && parsed > 0) return parsed;\n }\n return 1500;\n}\n\nfunction processSessionStart(input: HookInput): HookOutput {\n const start = Date.now();\n const log: string[] = [];\n const sessionId = input.session_id || \"default\";\n const now = Date.now();\n const model = input.model || \"claude-sonnet-4.6\";\n\n const state: SessionState = {\n version: PKG_VERSION,\n session_id: sessionId,\n started_at: now,\n updated_at: now,\n model,\n tokens_estimated: 0,\n token_budget: resolveTokenBudget(model),\n context_pct: 0,\n tools_used: [],\n skills_used: [],\n agents_used: [],\n active_mode: null,\n last_output: \"\",\n task_progress: 0,\n status: \"idle\",\n premium_requests: 0,\n premium_requests_total: resolvePremiumRequestsTotal(),\n warning_active: false,\n };\n\n const statePath = getStatePath(sessionId);\n ensureDir(statePath);\n writeFileSync(statePath, JSON.stringify(state), \"utf-8\");\n log.push(`Session initialized: ${sessionId}`);\n\n const { line, state: hudState } = writeHudArtifacts(state);\n log.push(`HUD artifacts written: ${line}`);\n\n return {\n status: \"ok\",\n latencyMs: Date.now() - start,\n mutations: [{ type: \"emit_hud\", hudEmit: buildEmit(state) }],\n log: [...log, `HUD state version: ${hudState.version}`],\n };\n}\n\nfunction processPostToolUse(input: HookInput): HookOutput {\n const start = Date.now();\n const log: string[] = [];\n\n const statePath = getStatePath(input.session_id);\n let state: SessionState;\n\n try {\n const raw = JSON.parse(readFileSync(statePath, \"utf-8\"));\n state = {\n ...raw,\n version: typeof raw.version === \"string\" ? raw.version : PKG_VERSION,\n session_id: typeof raw.session_id === \"string\" ? raw.session_id : input.session_id || \"default\",\n started_at: typeof raw.started_at === \"number\" ? raw.started_at : Date.now(),\n updated_at: Date.now(),\n model: typeof raw.model === \"string\" ? raw.model : input.model || \"claude-sonnet-4.6\",\n tokens_estimated: typeof raw.tokens_estimated === \"number\" ? raw.tokens_estimated : 0,\n token_budget: typeof raw.token_budget === \"number\" ? raw.token_budget : resolveTokenBudget(typeof raw.model === \"string\" ? raw.model : input.model || \"claude-sonnet-4.6\"),\n context_pct: typeof raw.context_pct === \"number\" ? raw.context_pct : 0,\n tools_used: Array.isArray(raw.tools_used) ? raw.tools_used : [],\n skills_used: Array.isArray(raw.skills_used) ? raw.skills_used : [],\n agents_used: Array.isArray(raw.agents_used) ? raw.agents_used : [],\n active_mode: typeof raw.active_mode === \"string\" ? raw.active_mode : null,\n last_output: typeof raw.last_output === \"string\" ? raw.last_output : \"\",\n task_progress: typeof raw.task_progress === \"number\" ? raw.task_progress : 0,\n status: raw.status ?? \"running\",\n premium_requests: typeof raw.premium_requests === \"number\" ? raw.premium_requests : 0,\n premium_requests_total: typeof raw.premium_requests_total === \"number\" ? raw.premium_requests_total : resolvePremiumRequestsTotal(),\n warning_active: typeof raw.warning_active === \"boolean\" ? raw.warning_active : false,\n };\n } catch {\n return processSessionStart(input);\n }\n\n if (input.tool_name && !state.tools_used.includes(input.tool_name)) {\n state.tools_used.push(input.tool_name);\n }\n state.status = \"running\";\n state.last_output = stringifyOutput(input.tool_output);\n\n writeFileSync(statePath, JSON.stringify(state), \"utf-8\");\n const { line } = writeHudArtifacts(state);\n log.push(`HUD updated: ${line}`);\n\n return {\n status: \"ok\",\n latencyMs: Date.now() - start,\n mutations: [{ type: \"emit_hud\", hudEmit: buildEmit(state) }],\n log,\n };\n}\n\nexport function processHook(input: HookInput): HookOutput {\n if (input.hook_type === \"SessionStart\") {\n return processSessionStart(input);\n }\n if (input.hook_type === \"PostToolUse\") {\n return processPostToolUse(input);\n }\n return {\n status: \"skip\",\n latencyMs: 0,\n mutations: [],\n log: [\"Unknown hook type\"],\n };\n}\n\nimport { fileURLToPath } from \"url\";\n\nif (process.argv[1] === fileURLToPath(import.meta.url)) {\n const input: HookInput = JSON.parse(await readStdin());\n const output = processHook(input);\n console.log(JSON.stringify(output));\n}\n\nasync function readStdin(): Promise<string> {\n const chunks: string[] = [];\n for await (const chunk of process.stdin) {\n chunks.push(chunk);\n }\n return chunks.join(\"\");\n}\n", "/**\n * HUD statusline helpers and standalone entrypoint.\n *\n * Keeps HUD artifact generation in one place so hooks and shell wrappers\n * can share the same rendering and fallback behavior.\n */\n\nimport { mkdirSync, readFileSync, renameSync, writeFileSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { dirname, join } from \"path\";\nimport { fileURLToPath } from \"url\";\nimport { renderPlain, type HudState, type HudStatus } from \"./renderer.mts\";\n\nconst DEFAULT_VERSION = \"0.0.0\";\nconst DEFAULT_STATUSLINE = \"OMP | hud: no active session\";\nconst DEFAULT_TOKEN_BUDGET = 200_000;\nconst DEFAULT_PREMIUM_REQUESTS_TOTAL = 1500;\n\nexport interface StatuslinePaths {\n legacyLinePath: string;\n hudDir: string;\n statusJsonPath: string;\n displayPath: string;\n tmuxSegmentPath: string;\n}\n\nexport interface HudSnapshot {\n version?: string;\n session_id?: string;\n started_at?: number;\n updated_at?: number;\n model?: string;\n tokens_estimated?: number;\n token_budget?: number;\n context_pct?: number;\n tools_used?: string[];\n skills_used?: string[];\n agents_used?: string[];\n active_mode?: string | null;\n last_output?: string;\n task_progress?: number;\n status?: HudStatus;\n premium_requests?: number;\n premium_requests_total?: number;\n warning_active?: boolean;\n}\n\ninterface SerializedHudState extends Omit<HudState, \"toolsUsed\" | \"skillsUsed\"> {\n toolsUsed: string[];\n skillsUsed: string[];\n}\n\nexport function getStatuslinePaths(home = process.env[\"HOME\"] || homedir()): StatuslinePaths {\n const ompDir = join(home, \".omp\");\n const hudDir = join(ompDir, \"hud\");\n return {\n legacyLinePath: join(ompDir, \"hud.line\"),\n hudDir,\n statusJsonPath: join(hudDir, \"status.json\"),\n displayPath: join(hudDir, \"display.txt\"),\n tmuxSegmentPath: join(hudDir, \"tmux-segment.sh\"),\n };\n}\n\nfunction ensureParent(filePath: string): void {\n mkdirSync(dirname(filePath), { recursive: true });\n}\n\nfunction writeAtomic(filePath: string, content: string, mode?: number): void {\n ensureParent(filePath);\n const tempPath = `${filePath}.tmp`;\n writeFileSync(tempPath, content, mode === undefined ? \"utf-8\" : { encoding: \"utf-8\", mode });\n renameSync(tempPath, filePath);\n}\n\nfunction normalizeStringArray(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n return value.filter((item): item is string => typeof item === \"string\");\n}\n\nfunction serializeHudState(state: HudState): SerializedHudState {\n return {\n ...state,\n toolsUsed: Array.from(state.toolsUsed),\n skillsUsed: Array.from(state.skillsUsed),\n };\n}\n\nfunction deserializeHudState(raw: unknown): HudState | null {\n if (!raw || typeof raw !== \"object\") return null;\n const value = raw as Record<string, unknown>;\n const toolsUsed = new Set(normalizeStringArray(value.toolsUsed));\n const skillsUsed = new Set(normalizeStringArray(value.skillsUsed));\n const agentsActive = normalizeStringArray(value.agentsActive);\n const status = typeof value.status === \"string\" ? (value.status as HudStatus) : \"idle\";\n\n return {\n sessionId: typeof value.sessionId === \"string\" ? value.sessionId : \"default\",\n activeMode: typeof value.activeMode === \"string\" ? value.activeMode : null,\n activeModel: typeof value.activeModel === \"string\" ? value.activeModel : \"sonnet\",\n contextPct: typeof value.contextPct === \"number\" ? value.contextPct : 0,\n tokensUsed: typeof value.tokensUsed === \"number\" ? value.tokensUsed : 0,\n tokensTotal: typeof value.tokensTotal === \"number\" ? value.tokensTotal : DEFAULT_TOKEN_BUDGET,\n agentsActive,\n lastAgent: typeof value.lastAgent === \"string\" ? value.lastAgent : agentsActive.at(-1) ?? \"-\",\n lastOutput: typeof value.lastOutput === \"string\" ? value.lastOutput : \"\",\n taskProgress: typeof value.taskProgress === \"number\" ? value.taskProgress : 0,\n startedAt: typeof value.startedAt === \"number\" ? value.startedAt : Date.now(),\n updatedAt: typeof value.updatedAt === \"number\" ? value.updatedAt : Date.now(),\n version: typeof value.version === \"string\" ? value.version : DEFAULT_VERSION,\n status,\n sessionDurationMs: typeof value.sessionDurationMs === \"number\" ? value.sessionDurationMs : 0,\n cumulativeAgentsUsed: typeof value.cumulativeAgentsUsed === \"number\" ? value.cumulativeAgentsUsed : agentsActive.length,\n toolsUsed,\n skillsUsed,\n toolsTotal: typeof value.toolsTotal === \"number\" ? value.toolsTotal : 13,\n skillsTotal: typeof value.skillsTotal === \"number\" ? value.skillsTotal : 25,\n agentsTotal: typeof value.agentsTotal === \"number\" ? value.agentsTotal : 23,\n premiumRequests: typeof value.premiumRequests === \"number\" ? value.premiumRequests : 0,\n premiumRequestsTotal: typeof value.premiumRequestsTotal === \"number\" ? value.premiumRequestsTotal : DEFAULT_PREMIUM_REQUESTS_TOTAL,\n warningActive: typeof value.warningActive === \"boolean\" ? value.warningActive : false,\n };\n}\n\nexport function buildHudState(snapshot: HudSnapshot, now = Date.now()): HudState {\n const startedAt = snapshot.started_at ?? now;\n const updatedAt = snapshot.updated_at ?? now;\n const toolsUsed = new Set(normalizeStringArray(snapshot.tools_used));\n const skillsUsed = new Set(normalizeStringArray(snapshot.skills_used));\n const agentsActive = normalizeStringArray(snapshot.agents_used);\n\n return {\n sessionId: snapshot.session_id ?? \"default\",\n activeMode: snapshot.active_mode ?? null,\n activeModel: snapshot.model ?? \"sonnet\",\n contextPct: snapshot.context_pct ?? 0,\n tokensUsed: snapshot.tokens_estimated ?? 0,\n tokensTotal: snapshot.token_budget ?? DEFAULT_TOKEN_BUDGET,\n agentsActive,\n lastAgent: agentsActive.at(-1) ?? \"-\",\n lastOutput: snapshot.last_output ?? \"\",\n taskProgress: snapshot.task_progress ?? 0,\n startedAt,\n updatedAt,\n version: snapshot.version ?? DEFAULT_VERSION,\n status: snapshot.status ?? \"idle\",\n sessionDurationMs: Math.max(0, updatedAt - startedAt),\n cumulativeAgentsUsed: agentsActive.length,\n toolsUsed,\n skillsUsed,\n toolsTotal: 13,\n skillsTotal: 25,\n agentsTotal: 23,\n premiumRequests: snapshot.premium_requests ?? 0,\n premiumRequestsTotal: snapshot.premium_requests_total ?? DEFAULT_PREMIUM_REQUESTS_TOTAL,\n warningActive: snapshot.warning_active ?? false,\n };\n}\n\nexport function writeHudArtifacts(snapshot: HudSnapshot, paths = getStatuslinePaths()): { line: string; state: HudState; paths: StatuslinePaths } {\n const state = buildHudState(snapshot);\n const line = renderPlain(state);\n const serializedState = `${JSON.stringify(serializeHudState(state), null, 2)}\\n`;\n\n writeAtomic(paths.statusJsonPath, serializedState);\n writeAtomic(paths.displayPath, `${line}\\n`);\n writeAtomic(paths.tmuxSegmentPath, `${line}\\n`, 0o755);\n writeAtomic(paths.legacyLinePath, `${line}\\n`);\n\n return { line, state, paths };\n}\n\nexport function readStatusline(paths = getStatuslinePaths()): string {\n try {\n const line = readFileSync(paths.displayPath, \"utf-8\").trim();\n if (line) return line;\n } catch {\n // Fall through to other sources.\n }\n\n try {\n const parsed = JSON.parse(readFileSync(paths.statusJsonPath, \"utf-8\"));\n const state = deserializeHudState(parsed);\n if (state) return renderPlain(state);\n } catch {\n // Fall through to legacy file.\n }\n\n try {\n const line = readFileSync(paths.legacyLinePath, \"utf-8\").trim();\n if (line) return line;\n } catch {\n // No HUD artifacts yet.\n }\n\n return DEFAULT_STATUSLINE;\n}\n\nif (process.argv[1] === fileURLToPath(import.meta.url)) {\n console.log(readStatusline());\n}\n", "/**\n * HUD Renderer\n * Formats HudState into ANSI or plain text status lines.\n */\n\n\nexport interface HudState {\n sessionId: string;\n activeMode: string | null;\n activeModel: string;\n contextPct: number;\n tokensUsed: number;\n tokensTotal: number;\n agentsActive: string[];\n lastAgent: string;\n lastOutput: string;\n taskProgress: number;\n startedAt: number;\n updatedAt: number;\n version: string;\n status: HudStatus;\n sessionDurationMs: number;\n cumulativeAgentsUsed: number;\n toolsUsed: Set<string>;\n skillsUsed: Set<string>;\n toolsTotal: number;\n skillsTotal: number;\n agentsTotal: number;\n premiumRequests: number;\n premiumRequestsTotal: number;\n warningActive: boolean;\n}\n\nexport type HudStatus = \"idle\" | \"running\" | \"waiting\" | \"complete\" | \"error\" | \"eco\";\n\nconst STATUS_ICONS: Record<HudStatus, string> = {\n idle: \"\u25CB\",\n running: \"\u25CF\",\n waiting: \"\u25F7\",\n complete: \"\u2713\",\n error: \"\u2717\",\n eco: \"\u26A1\",\n};\n\nfunction formatAge(startedAt: number): string {\n const elapsed = Date.now() - startedAt;\n const mins = Math.floor(elapsed / 60000);\n if (mins < 60) return `${mins}m`;\n const hours = Math.floor(mins / 60);\n const remainingMins = mins % 60;\n return `${hours}h${remainingMins}m`;\n}\n\nfunction formatTokens(tokens: number): string {\n if (tokens >= 1_000_000) return `${(tokens / 1_000_000).toFixed(1)}M`;\n if (tokens >= 1_000) return `${(tokens / 1_000).toFixed(1)}k`;\n return `${tokens}`;\n}\n\nfunction ctxColor(pct: number): string {\n if (pct < 60) return \"\\x1b[32m\"; // green\n if (pct < 85) return \"\\x1b[33m\"; // yellow\n return \"\\x1b[31m\"; // red\n}\n\nfunction reset(): string {\n return \"\\x1b[0m\";\n}\n\n/**\n * Render HUD line with ANSI color codes.\n * Format: [OMP v1.0.0] mode | model | ctx:N% | tok:~Nk/Nk | Nm | tools:N/N | skills:N/N | agents:N/N | N% status\n */\nexport function renderAnsi(state: HudState): string {\n const age = formatAge(state.startedAt);\n const tokens = formatTokens(state.tokensUsed);\n const ctx = state.contextPct;\n const mode = state.activeMode || \"-\";\n const model = state.activeModel || \"sonnet\";\n const icon = STATUS_ICONS[state.status] || \"\u25CF\";\n\n const ctxClr = ctxColor(ctx);\n const ctxStr = `${ctxClr}ctx:${ctx}%${reset()}`;\n const tokenStr = `tok:~${tokens}/${state.tokensTotal}`;\n const modeStr = mode === \"-\" ? \"-\" : `\\x1b[36m${mode}${reset()}`; // cyan for active modes\n\n const reqWarning = state.warningActive ? \" !!\" : \"\";\n const reqStr = `req:${state.premiumRequests ?? 0}/${state.premiumRequestsTotal ?? 1500}${reqWarning}`;\n\n return `[OMP v${state.version}] ${modeStr} | ${model} | ${ctxStr} | ${tokenStr} | ${reqStr} | ${age} | tools:${state.toolsUsed?.size || 0}/${state.toolsTotal ?? 13} | skills:${state.skillsUsed?.size || 0}/${state.skillsTotal ?? 25} | agents:${state.cumulativeAgentsUsed}/${state.agentsTotal ?? 23} | ${icon} ${state.status}`;\n}\n\n/**\n * Render HUD line as plain text (no ANSI codes).\n * Format: [OMP v1.0.0] mode | model | ctx:N% | tok:~Nk/Nk | Nm | tools:N/N | skills:N/N | agents:N/N | N% status\n */\nexport function renderPlain(state: HudState): string {\n const age = formatAge(state.startedAt);\n const tokens = formatTokens(state.tokensUsed);\n const ctx = state.contextPct;\n const mode = state.activeMode || \"-\";\n const model = state.activeModel || \"sonnet\";\n\n const reqWarningPlain = state.warningActive ? \" !!\" : \"\";\n const reqStrPlain = `req:${state.premiumRequests ?? 0}/${state.premiumRequestsTotal ?? 1500}${reqWarningPlain}`;\n\n return `[OMP v${state.version}] ${mode} | ${model} | ctx:${ctx}% | tok:~${tokens}/${state.tokensTotal} | ${reqStrPlain} | ${age} | tools:${state.toolsUsed?.size || 0}/${state.toolsTotal ?? 13} | skills:${state.skillsUsed?.size || 0}/${state.skillsTotal ?? 25} | agents:${state.cumulativeAgentsUsed}/${state.agentsTotal ?? 23} | ${state.status}`;\n}\n"],
5
- "mappings": "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",
6
- "names": ["mkdirSync", "readFileSync", "writeFileSync", "homedir", "join", "fileURLToPath", "join", "homedir", "mkdirSync", "writeFileSync", "readFileSync"]
3
+ "sources": ["../../src/hooks/hud-emitter.mts", "../../src/hud/statusline.mts", "../../src/hud/renderer.mts", "../../src/hooks/runner.mts"],
4
+ "sourcesContent": ["/**\n * hud-emitter hook\n * Trigger: post-cycle (PostToolUse + SessionStart)\n * Priority: 60\n *\n * Writes HUD artifacts after every tool call and initializes session state.\n */\n\nimport { mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { createRequire } from \"module\";\nimport { homedir } from \"os\";\nimport { join } from \"path\";\nimport { writeHudArtifacts } from \"../hud/statusline.mts\";\n\nconst _require = createRequire(import.meta.url);\nconst { version: PKG_VERSION } = _require(\"../../package.json\") as { version: string };\n\nexport interface HookInput {\n hook_type: \"SessionStart\" | \"PostToolUse\";\n tool_name?: string;\n tool_input?: unknown;\n tool_output?: unknown;\n session_id?: string;\n model?: string;\n}\n\nexport interface HookOutput {\n status: \"ok\" | \"skip\" | \"error\";\n latencyMs: number;\n mutations: Array<{ type: \"emit_hud\"; hudEmit: HudEmit } | { type: \"log\"; level: \"info\"; message: string }>;\n log: string[];\n}\n\nexport interface HudEmit {\n sessionId: string;\n activeMode: string | null;\n contextPct: number;\n tokensUsed: number;\n tokensTotal: number;\n agentsActive: string[];\n lastAgent: string;\n lastOutput: string;\n taskProgress: number;\n}\n\ninterface SessionState {\n version: string;\n session_id: string;\n started_at: number;\n updated_at: number;\n model: string;\n tokens_estimated: number;\n token_budget: number;\n context_pct: number;\n tools_used: string[];\n skills_used: string[];\n agents_used: string[];\n active_mode: string | null;\n last_output: string;\n task_progress: number;\n status: \"idle\" | \"running\" | \"waiting\" | \"complete\" | \"error\" | \"eco\";\n premium_requests: number;\n premium_requests_total: number;\n warning_active: boolean;\n}\n\nfunction getStatePath(sessionId?: string): string {\n const base = join(process.env[\"HOME\"] || homedir(), \".omp\", \"state\");\n if (sessionId) {\n return join(base, \"sessions\", sessionId, \"session.json\");\n }\n return join(base, \"session.json\");\n}\n\nfunction ensureDir(path: string): void {\n mkdirSync(path.substring(0, path.lastIndexOf(\"/\")), { recursive: true });\n}\n\nfunction stringifyOutput(value: unknown): string {\n if (typeof value === \"string\") {\n return value.trim().slice(0, 200);\n }\n if (value === undefined || value === null) {\n return \"\";\n }\n try {\n return JSON.stringify(value).slice(0, 200);\n } catch {\n return String(value).slice(0, 200);\n }\n}\n\nfunction buildEmit(state: SessionState): HudEmit {\n return {\n sessionId: state.session_id,\n activeMode: state.active_mode,\n contextPct: state.context_pct,\n tokensUsed: state.tokens_estimated,\n tokensTotal: state.token_budget,\n agentsActive: state.agents_used,\n lastAgent: state.agents_used[state.agents_used.length - 1] || \"-\",\n lastOutput: state.last_output,\n taskProgress: state.task_progress,\n };\n}\n\nconst MODEL_CONTEXTS: Record<string, number> = {\n \"claude-sonnet-4.5\": 200_000,\n \"claude-sonnet-4\": 200_000,\n \"claude-sonnet-4.6\": 200_000,\n \"claude-opus-4.6\": 200_000,\n \"gpt-5\": 128_000,\n \"gpt-5.4-mini\": 128_000,\n \"gemini-3-pro\": 128_000,\n default: 200_000,\n};\n\nfunction resolveTokenBudget(model: string): number {\n return MODEL_CONTEXTS[model] ?? MODEL_CONTEXTS[\"default\"] ?? 200_000;\n}\n\nfunction resolvePremiumRequestsTotal(): number {\n const env = process.env[\"OMP_PREMIUM_REQUESTS_TOTAL\"];\n if (env) {\n const parsed = parseInt(env, 10);\n if (!isNaN(parsed) && parsed > 0) return parsed;\n }\n return 1500;\n}\n\nfunction processSessionStart(input: HookInput): HookOutput {\n const start = Date.now();\n const log: string[] = [];\n const sessionId = input.session_id || \"default\";\n const now = Date.now();\n const model = input.model || \"claude-sonnet-4.6\";\n\n const state: SessionState = {\n version: PKG_VERSION,\n session_id: sessionId,\n started_at: now,\n updated_at: now,\n model,\n tokens_estimated: 0,\n token_budget: resolveTokenBudget(model),\n context_pct: 0,\n tools_used: [],\n skills_used: [],\n agents_used: [],\n active_mode: null,\n last_output: \"\",\n task_progress: 0,\n status: \"idle\",\n premium_requests: 0,\n premium_requests_total: resolvePremiumRequestsTotal(),\n warning_active: false,\n };\n\n const statePath = getStatePath(sessionId);\n ensureDir(statePath);\n writeFileSync(statePath, JSON.stringify(state), \"utf-8\");\n log.push(`Session initialized: ${sessionId}`);\n\n const { line, state: hudState } = writeHudArtifacts(state);\n log.push(`HUD artifacts written: ${line}`);\n\n return {\n status: \"ok\",\n latencyMs: Date.now() - start,\n mutations: [{ type: \"emit_hud\", hudEmit: buildEmit(state) }],\n log: [...log, `HUD state version: ${hudState.version}`],\n };\n}\n\nfunction processPostToolUse(input: HookInput): HookOutput {\n const start = Date.now();\n const log: string[] = [];\n\n const statePath = getStatePath(input.session_id);\n let state: SessionState;\n\n try {\n const raw = JSON.parse(readFileSync(statePath, \"utf-8\"));\n state = {\n ...raw,\n version: typeof raw.version === \"string\" ? raw.version : PKG_VERSION,\n session_id: typeof raw.session_id === \"string\" ? raw.session_id : input.session_id || \"default\",\n started_at: typeof raw.started_at === \"number\" ? raw.started_at : Date.now(),\n updated_at: Date.now(),\n model: typeof raw.model === \"string\" ? raw.model : input.model || \"claude-sonnet-4.6\",\n tokens_estimated: typeof raw.tokens_estimated === \"number\" ? raw.tokens_estimated : 0,\n token_budget: typeof raw.token_budget === \"number\" ? raw.token_budget : resolveTokenBudget(typeof raw.model === \"string\" ? raw.model : input.model || \"claude-sonnet-4.6\"),\n context_pct: typeof raw.context_pct === \"number\" ? raw.context_pct : 0,\n tools_used: Array.isArray(raw.tools_used) ? raw.tools_used : [],\n skills_used: Array.isArray(raw.skills_used) ? raw.skills_used : [],\n agents_used: Array.isArray(raw.agents_used) ? raw.agents_used : [],\n active_mode: typeof raw.active_mode === \"string\" ? raw.active_mode : null,\n last_output: typeof raw.last_output === \"string\" ? raw.last_output : \"\",\n task_progress: typeof raw.task_progress === \"number\" ? raw.task_progress : 0,\n status: raw.status ?? \"running\",\n premium_requests: typeof raw.premium_requests === \"number\" ? raw.premium_requests : 0,\n premium_requests_total: typeof raw.premium_requests_total === \"number\" ? raw.premium_requests_total : resolvePremiumRequestsTotal(),\n warning_active: typeof raw.warning_active === \"boolean\" ? raw.warning_active : false,\n };\n } catch {\n return processSessionStart(input);\n }\n\n if (input.tool_name && !state.tools_used.includes(input.tool_name)) {\n state.tools_used.push(input.tool_name);\n }\n state.status = \"running\";\n state.last_output = stringifyOutput(input.tool_output);\n\n writeFileSync(statePath, JSON.stringify(state), \"utf-8\");\n const { line } = writeHudArtifacts(state);\n log.push(`HUD updated: ${line}`);\n\n return {\n status: \"ok\",\n latencyMs: Date.now() - start,\n mutations: [{ type: \"emit_hud\", hudEmit: buildEmit(state) }],\n log,\n };\n}\n\nexport function processHook(input: HookInput): HookOutput {\n if (input.hook_type === \"SessionStart\") {\n return processSessionStart(input);\n }\n if (input.hook_type === \"PostToolUse\") {\n return processPostToolUse(input);\n }\n return {\n status: \"skip\",\n latencyMs: 0,\n mutations: [],\n log: [\"Unknown hook type\"],\n };\n}\n\n// Main entry point \u2014 only runs when executed directly (not imported).\n// Fail-open: any stdin/parse/processing failure still emits valid JSON and exits 0.\nimport { fileURLToPath } from \"url\";\nimport { runHookMain } from \"./runner.mts\";\n\nif (process.argv[1] === fileURLToPath(import.meta.url)) {\n await runHookMain(processHook, { hookName: \"hud-emitter\" });\n}\n", "/**\n * HUD statusline helpers and standalone entrypoint.\n *\n * Keeps HUD artifact generation in one place so hooks and shell wrappers\n * can share the same rendering and fallback behavior.\n */\n\nimport { mkdirSync, readFileSync, renameSync, writeFileSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { dirname, join } from \"path\";\nimport { fileURLToPath } from \"url\";\nimport { renderPlain, type HudState, type HudStatus } from \"./renderer.mts\";\n\nconst DEFAULT_VERSION = \"0.0.0\";\n/** Fallback line when no HUD artifacts exist. Mirrored in extension/extension.mjs. */\nexport const DEFAULT_STATUSLINE = \"OMP | hud: no active session\";\nconst DEFAULT_TOKEN_BUDGET = 200_000;\nconst DEFAULT_PREMIUM_REQUESTS_TOTAL = 1500;\n\nexport interface StatuslinePaths {\n legacyLinePath: string;\n hudDir: string;\n statusJsonPath: string;\n displayPath: string;\n tmuxSegmentPath: string;\n}\n\nexport interface HudSnapshot {\n version?: string;\n session_id?: string;\n started_at?: number;\n updated_at?: number;\n model?: string;\n tokens_estimated?: number;\n token_budget?: number;\n context_pct?: number;\n tools_used?: string[];\n skills_used?: string[];\n agents_used?: string[];\n active_mode?: string | null;\n last_output?: string;\n task_progress?: number;\n status?: HudStatus;\n premium_requests?: number;\n premium_requests_total?: number;\n warning_active?: boolean;\n}\n\ninterface SerializedHudState extends Omit<HudState, \"toolsUsed\" | \"skillsUsed\"> {\n toolsUsed: string[];\n skillsUsed: string[];\n}\n\nexport function getStatuslinePaths(home = process.env[\"HOME\"] || homedir()): StatuslinePaths {\n const ompDir = join(home, \".omp\");\n const hudDir = join(ompDir, \"hud\");\n return {\n legacyLinePath: join(ompDir, \"hud.line\"),\n hudDir,\n statusJsonPath: join(hudDir, \"status.json\"),\n displayPath: join(hudDir, \"display.txt\"),\n tmuxSegmentPath: join(hudDir, \"tmux-segment.sh\"),\n };\n}\n\nfunction ensureParent(filePath: string): void {\n mkdirSync(dirname(filePath), { recursive: true });\n}\n\nfunction writeAtomic(filePath: string, content: string, mode?: number): void {\n ensureParent(filePath);\n const tempPath = `${filePath}.tmp`;\n writeFileSync(tempPath, content, mode === undefined ? \"utf-8\" : { encoding: \"utf-8\", mode });\n renameSync(tempPath, filePath);\n}\n\nfunction normalizeStringArray(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n return value.filter((item): item is string => typeof item === \"string\");\n}\n\nfunction serializeHudState(state: HudState): SerializedHudState {\n return {\n ...state,\n toolsUsed: Array.from(state.toolsUsed),\n skillsUsed: Array.from(state.skillsUsed),\n };\n}\n\nfunction deserializeHudState(raw: unknown): HudState | null {\n if (!raw || typeof raw !== \"object\") return null;\n const value = raw as Record<string, unknown>;\n const toolsUsed = new Set(normalizeStringArray(value.toolsUsed));\n const skillsUsed = new Set(normalizeStringArray(value.skillsUsed));\n const agentsActive = normalizeStringArray(value.agentsActive);\n const status = typeof value.status === \"string\" ? (value.status as HudStatus) : \"idle\";\n\n return {\n sessionId: typeof value.sessionId === \"string\" ? value.sessionId : \"default\",\n activeMode: typeof value.activeMode === \"string\" ? value.activeMode : null,\n activeModel: typeof value.activeModel === \"string\" ? value.activeModel : \"sonnet\",\n contextPct: typeof value.contextPct === \"number\" ? value.contextPct : 0,\n tokensUsed: typeof value.tokensUsed === \"number\" ? value.tokensUsed : 0,\n tokensTotal: typeof value.tokensTotal === \"number\" ? value.tokensTotal : DEFAULT_TOKEN_BUDGET,\n agentsActive,\n lastAgent: typeof value.lastAgent === \"string\" ? value.lastAgent : agentsActive.at(-1) ?? \"-\",\n lastOutput: typeof value.lastOutput === \"string\" ? value.lastOutput : \"\",\n taskProgress: typeof value.taskProgress === \"number\" ? value.taskProgress : 0,\n startedAt: typeof value.startedAt === \"number\" ? value.startedAt : Date.now(),\n updatedAt: typeof value.updatedAt === \"number\" ? value.updatedAt : Date.now(),\n version: typeof value.version === \"string\" ? value.version : DEFAULT_VERSION,\n status,\n sessionDurationMs: typeof value.sessionDurationMs === \"number\" ? value.sessionDurationMs : 0,\n cumulativeAgentsUsed: typeof value.cumulativeAgentsUsed === \"number\" ? value.cumulativeAgentsUsed : agentsActive.length,\n toolsUsed,\n skillsUsed,\n toolsTotal: typeof value.toolsTotal === \"number\" ? value.toolsTotal : 13,\n skillsTotal: typeof value.skillsTotal === \"number\" ? value.skillsTotal : 59,\n agentsTotal: typeof value.agentsTotal === \"number\" ? value.agentsTotal : 19,\n premiumRequests: typeof value.premiumRequests === \"number\" ? value.premiumRequests : 0,\n premiumRequestsTotal: typeof value.premiumRequestsTotal === \"number\" ? value.premiumRequestsTotal : DEFAULT_PREMIUM_REQUESTS_TOTAL,\n warningActive: typeof value.warningActive === \"boolean\" ? value.warningActive : false,\n };\n}\n\nexport function buildHudState(snapshot: HudSnapshot, now = Date.now()): HudState {\n const startedAt = snapshot.started_at ?? now;\n const updatedAt = snapshot.updated_at ?? now;\n const toolsUsed = new Set(normalizeStringArray(snapshot.tools_used));\n const skillsUsed = new Set(normalizeStringArray(snapshot.skills_used));\n const agentsActive = normalizeStringArray(snapshot.agents_used);\n\n return {\n sessionId: snapshot.session_id ?? \"default\",\n activeMode: snapshot.active_mode ?? null,\n activeModel: snapshot.model ?? \"sonnet\",\n contextPct: snapshot.context_pct ?? 0,\n tokensUsed: snapshot.tokens_estimated ?? 0,\n tokensTotal: snapshot.token_budget ?? DEFAULT_TOKEN_BUDGET,\n agentsActive,\n lastAgent: agentsActive.at(-1) ?? \"-\",\n lastOutput: snapshot.last_output ?? \"\",\n taskProgress: snapshot.task_progress ?? 0,\n startedAt,\n updatedAt,\n version: snapshot.version ?? DEFAULT_VERSION,\n status: snapshot.status ?? \"idle\",\n sessionDurationMs: Math.max(0, updatedAt - startedAt),\n cumulativeAgentsUsed: agentsActive.length,\n toolsUsed,\n skillsUsed,\n toolsTotal: 13,\n skillsTotal: 59,\n agentsTotal: 19,\n premiumRequests: snapshot.premium_requests ?? 0,\n premiumRequestsTotal: snapshot.premium_requests_total ?? DEFAULT_PREMIUM_REQUESTS_TOTAL,\n warningActive: snapshot.warning_active ?? false,\n };\n}\n\nexport function writeHudArtifacts(snapshot: HudSnapshot, paths = getStatuslinePaths()): { line: string; state: HudState; paths: StatuslinePaths } {\n const state = buildHudState(snapshot);\n const line = renderPlain(state);\n const serializedState = `${JSON.stringify(serializeHudState(state), null, 2)}\\n`;\n\n writeAtomic(paths.statusJsonPath, serializedState);\n writeAtomic(paths.displayPath, `${line}\\n`);\n writeAtomic(paths.tmuxSegmentPath, `${line}\\n`, 0o755);\n writeAtomic(paths.legacyLinePath, `${line}\\n`);\n\n return { line, state, paths };\n}\n\nexport function readStatusline(paths = getStatuslinePaths()): string {\n // Try live render from status.json \u2014 formatAge runs at call time, not hook-fire time\n try {\n const parsed = JSON.parse(readFileSync(paths.statusJsonPath, \"utf-8\"));\n const state = deserializeHudState(parsed);\n if (state) return renderPlain(state);\n } catch {\n // Fall through to cached display string.\n }\n\n // Fallback: pre-rendered cached string (written by hud-emitter; used by tmux consumers)\n try {\n const line = readFileSync(paths.displayPath, \"utf-8\").trim();\n if (line) return line;\n } catch {\n // Fall through to legacy file.\n }\n\n try {\n const line = readFileSync(paths.legacyLinePath, \"utf-8\").trim();\n if (line) return line;\n } catch {\n // Fall through to default statusline.\n }\n\n return DEFAULT_STATUSLINE;\n}\n\n// Only emit when executed as the statusline entry itself. The bundle-name\n// check prevents this from firing inside other bundles (e.g. hud-emitter.mjs)\n// that inline this module \u2014 hooks must emit exactly one JSON object on stdout.\nif (\n process.argv[1] === fileURLToPath(import.meta.url) &&\n (process.argv[1].endsWith(\"omp-statusline.mjs\") || process.argv[1].endsWith(\"statusline.mts\"))\n) {\n console.log(readStatusline());\n}\n", "/**\n * HUD Renderer\n * Formats HudState into ANSI or plain text status lines.\n */\n\n\nexport interface HudState {\n sessionId: string;\n activeMode: string | null;\n activeModel: string;\n contextPct: number;\n tokensUsed: number;\n tokensTotal: number;\n agentsActive: string[];\n lastAgent: string;\n lastOutput: string;\n taskProgress: number;\n startedAt: number;\n updatedAt: number;\n version: string;\n status: HudStatus;\n sessionDurationMs: number;\n cumulativeAgentsUsed: number;\n toolsUsed: Set<string>;\n skillsUsed: Set<string>;\n toolsTotal: number;\n skillsTotal: number;\n agentsTotal: number;\n premiumRequests: number;\n premiumRequestsTotal: number;\n warningActive: boolean;\n}\n\nexport type HudStatus = \"idle\" | \"running\" | \"waiting\" | \"complete\" | \"error\" | \"eco\";\n\nconst STATUS_ICONS: Record<HudStatus, string> = {\n idle: \"\u25CB\",\n running: \"\u25CF\",\n waiting: \"\u25F7\",\n complete: \"\u2713\",\n error: \"\u2717\",\n eco: \"\u26A1\",\n};\n\nfunction formatAge(startedAt: number): string {\n const elapsed = Date.now() - startedAt;\n const mins = Math.floor(elapsed / 60000);\n if (mins < 60) return `${mins}m`;\n const hours = Math.floor(mins / 60);\n const remainingMins = mins % 60;\n return `${hours}h${remainingMins}m`;\n}\n\nfunction formatTokens(tokens: number): string {\n if (tokens >= 1_000_000) return `${(tokens / 1_000_000).toFixed(1)}M`;\n if (tokens >= 1_000) return `${(tokens / 1_000).toFixed(1)}k`;\n return `${tokens}`;\n}\n\nfunction ctxColor(pct: number): string {\n if (pct < 60) return \"\\x1b[32m\"; // green\n if (pct < 85) return \"\\x1b[33m\"; // yellow\n return \"\\x1b[31m\"; // red\n}\n\nfunction reset(): string {\n return \"\\x1b[0m\";\n}\n\n/**\n * Render HUD line with ANSI color codes.\n * Format: [OMP v1.0.0] mode | model | ctx:N% | tok:~Nk/Nk | Nm | tools:N/N | skills:N/N | agents:N/N | N% status\n */\nexport function renderAnsi(state: HudState): string {\n const age = formatAge(state.startedAt);\n const tokens = formatTokens(state.tokensUsed);\n const ctx = state.contextPct;\n const mode = state.activeMode || \"-\";\n const model = state.activeModel || \"sonnet\";\n const icon = STATUS_ICONS[state.status] || \"\u25CF\";\n\n const ctxClr = ctxColor(ctx);\n const ctxStr = `${ctxClr}ctx:${ctx}%${reset()}`;\n const tokenStr = `tok:~${tokens}/${state.tokensTotal}`;\n const modeStr = mode === \"-\" ? \"-\" : `\\x1b[36m${mode}${reset()}`; // cyan for active modes\n\n const reqWarning = state.warningActive ? \" !!\" : \"\";\n const reqStr = `req:${state.premiumRequests ?? 0}/${state.premiumRequestsTotal ?? 1500}${reqWarning}`;\n\n return `[OMP v${state.version}] ${modeStr} | ${model} | ${ctxStr} | ${tokenStr} | ${reqStr} | ${age} | tools:${state.toolsUsed?.size || 0}/${state.toolsTotal ?? 13} | skills:${state.skillsUsed?.size || 0}/${state.skillsTotal ?? 25} | agents:${state.cumulativeAgentsUsed}/${state.agentsTotal ?? 19} | ${icon} ${state.status}`;\n}\n\n/**\n * Render HUD line as plain text (no ANSI codes).\n * Format: [OMP v1.0.0] mode | model | ctx:N% | tok:~Nk/Nk | Nm | tools:N/N | skills:N/N | agents:N/N | N% status\n */\nexport function renderPlain(state: HudState): string {\n const age = formatAge(state.startedAt);\n const tokens = formatTokens(state.tokensUsed);\n const ctx = state.contextPct;\n const mode = state.activeMode || \"-\";\n const model = state.activeModel || \"sonnet\";\n\n const reqWarningPlain = state.warningActive ? \" !!\" : \"\";\n const reqStrPlain = `req:${state.premiumRequests ?? 0}/${state.premiumRequestsTotal ?? 1500}${reqWarningPlain}`;\n\n return `[OMP v${state.version}] ${mode} | ${model} | ctx:${ctx}% | tok:~${tokens}/${state.tokensTotal} | ${reqStrPlain} | ${age} | tools:${state.toolsUsed?.size || 0}/${state.toolsTotal ?? 13} | skills:${state.skillsUsed?.size || 0}/${state.skillsTotal ?? 25} | agents:${state.cumulativeAgentsUsed}/${state.agentsTotal ?? 19} | ${state.status}`;\n}\n", "/**\n * Shared hook entry-point runner.\n *\n * Hooks must be FAIL-OPEN: any failure (empty stdin, malformed JSON,\n * unexpected processing error) must still emit a valid HookOutput-shaped\n * JSON object on stdout and exit 0. A non-zero exit or non-JSON stdout\n * causes the Copilot CLI to treat the hook as errored, which denies the\n * tool call for PreToolUse hooks.\n *\n * Fail-open events are persisted (best-effort) as JSONL records to\n * ~/.omp/logs/hook-failures.jsonl and mirrored on stderr so failures\n * remain observable without ever touching the stdout JSON contract.\n */\n\nimport { appendFileSync, mkdirSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { join } from \"path\";\n\nexport interface FailOpenOutput {\n decision?: \"allow\";\n status: \"error\";\n latencyMs: number;\n mutations: never[];\n log: string[];\n}\n\nexport interface RunHookOptions {\n /**\n * When true (hooks whose HookOutput supports a decision field), the\n * fail-open output includes `\"decision\": \"allow\"` so the tool call is\n * explicitly allowed.\n */\n failOpenDecision?: boolean;\n /** Hook id recorded in fail-open log entries (stderr + JSONL). */\n hookName?: string;\n}\n\nexport async function readStdin(): Promise<string> {\n const readStdinActual = async (): Promise<string> => {\n const chunks: string[] = [];\n for await (const chunk of process.stdin) {\n chunks.push(String(chunk));\n }\n return chunks.join(\"\");\n };\n\n const stdinTimeout = new Promise<string>((resolve) =>\n setTimeout(\n () => resolve(\"\"),\n (parseInt(process.env.OMP_HOOK_STDIN_TIMEOUT_MS ?? \"500\") || 500)\n )\n );\n\n return Promise.race([readStdinActual(), stdinTimeout]);\n}\n\n/**\n * Best-effort persistence of a fail-open event. Wrapped in its own\n * try/catch: logging must NEVER break fail-open or the one-JSON-object\n * stdout contract. Writes to stderr and the JSONL log only \u2014 never stdout.\n */\nfunction logHookFailure(hook: string, reason: string): void {\n try {\n process.stderr.write(`[omp hook fail-open] ${hook}: ${reason}\\n`);\n } catch {\n // stderr unavailable \u2014 ignore\n }\n try {\n const logsDir = join(homedir(), \".omp\", \"logs\");\n mkdirSync(logsDir, { recursive: true });\n const record = JSON.stringify({ ts: new Date().toISOString(), hook, reason });\n appendFileSync(join(logsDir, \"hook-failures.jsonl\"), record + \"\\n\", \"utf-8\");\n } catch {\n // best-effort only \u2014 never let logging break fail-open\n }\n}\n\n/**\n * Reads HookInput JSON from stdin, runs the hook, and prints the\n * HookOutput JSON to stdout. Never throws, never exits non-zero,\n * never emits non-JSON to stdout.\n */\nexport async function runHookMain<TInput>(\n processHook: (input: TInput) => unknown,\n options: RunHookOptions = {}\n): Promise<void> {\n let outputJson: string;\n try {\n const input = JSON.parse(await readStdin()) as TInput;\n const serialized = JSON.stringify(processHook(input));\n if (typeof serialized !== \"string\") {\n throw new Error(\"hook produced no serializable output\");\n }\n outputJson = serialized;\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n logHookFailure(options.hookName ?? \"unknown\", reason);\n const failOpen: FailOpenOutput = {\n ...(options.failOpenDecision ? { decision: \"allow\" as const } : {}),\n status: \"error\",\n latencyMs: 0,\n mutations: [],\n log: [`fail-open: ${reason}`],\n };\n outputJson = JSON.stringify(failOpen);\n }\n console.log(outputJson);\n process.exitCode = 0;\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["mkdirSync", "readFileSync", "writeFileSync", "homedir", "join", "fileURLToPath", "mkdirSync", "homedir", "join", "processHook", "join", "homedir", "mkdirSync", "writeFileSync", "readFileSync", "fileURLToPath"]
7
7
  }
@@ -1,3 +1,62 @@
1
+ // src/hooks/runner.mts
2
+ import { appendFileSync, mkdirSync } from "fs";
3
+ import { homedir } from "os";
4
+ import { join } from "path";
5
+ async function readStdin() {
6
+ const readStdinActual = async () => {
7
+ const chunks = [];
8
+ for await (const chunk of process.stdin) {
9
+ chunks.push(String(chunk));
10
+ }
11
+ return chunks.join("");
12
+ };
13
+ const stdinTimeout = new Promise(
14
+ (resolve) => setTimeout(
15
+ () => resolve(""),
16
+ parseInt(process.env.OMP_HOOK_STDIN_TIMEOUT_MS ?? "500") || 500
17
+ )
18
+ );
19
+ return Promise.race([readStdinActual(), stdinTimeout]);
20
+ }
21
+ function logHookFailure(hook, reason) {
22
+ try {
23
+ process.stderr.write(`[omp hook fail-open] ${hook}: ${reason}
24
+ `);
25
+ } catch {
26
+ }
27
+ try {
28
+ const logsDir = join(homedir(), ".omp", "logs");
29
+ mkdirSync(logsDir, { recursive: true });
30
+ const record = JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), hook, reason });
31
+ appendFileSync(join(logsDir, "hook-failures.jsonl"), record + "\n", "utf-8");
32
+ } catch {
33
+ }
34
+ }
35
+ async function runHookMain(processHook2, options = {}) {
36
+ let outputJson;
37
+ try {
38
+ const input = JSON.parse(await readStdin());
39
+ const serialized = JSON.stringify(processHook2(input));
40
+ if (typeof serialized !== "string") {
41
+ throw new Error("hook produced no serializable output");
42
+ }
43
+ outputJson = serialized;
44
+ } catch (err) {
45
+ const reason = err instanceof Error ? err.message : String(err);
46
+ logHookFailure(options.hookName ?? "unknown", reason);
47
+ const failOpen = {
48
+ ...options.failOpenDecision ? { decision: "allow" } : {},
49
+ status: "error",
50
+ latencyMs: 0,
51
+ mutations: [],
52
+ log: [`fail-open: ${reason}`]
53
+ };
54
+ outputJson = JSON.stringify(failOpen);
55
+ }
56
+ console.log(outputJson);
57
+ process.exitCode = 0;
58
+ }
59
+
1
60
  // src/hooks/keyword-detector.mts
2
61
  var KEYWORD_MAP = {
3
62
  "autopilot:": "autopilot",
@@ -99,7 +158,63 @@ var KEYWORD_MAP = {
99
158
  "/omp:notifications": "notifications",
100
159
  "session:": "session",
101
160
  "/session": "session",
102
- "/omp:session": "session"
161
+ "/omp:session": "session",
162
+ "verify:": "verify",
163
+ "/verify": "verify",
164
+ "/omp:verify": "verify",
165
+ "cancel:": "cancel",
166
+ "/omp:cancel": "cancel",
167
+ "help:": "help",
168
+ "/omp:help": "help",
169
+ "code-review:": "code-review",
170
+ "/code-review": "code-review",
171
+ "/omp:code-review": "code-review",
172
+ "security-review:": "security-review",
173
+ "/security-review": "security-review",
174
+ "/omp:security-review": "security-review",
175
+ "ultraqa:": "ultraqa",
176
+ "/ultraqa": "ultraqa",
177
+ "/omp:ultraqa": "ultraqa",
178
+ "ultragoal:": "ultragoal",
179
+ "/ultragoal": "ultragoal",
180
+ "/omp:ultragoal": "ultragoal",
181
+ "deep-dive:": "deep-dive",
182
+ "/deep-dive": "deep-dive",
183
+ "/omp:deep-dive": "deep-dive",
184
+ "external-context:": "external-context",
185
+ "/external-context": "external-context",
186
+ "/omp:external-context": "external-context",
187
+ "deepsearch:": "deepsearch",
188
+ "/deepsearch": "deepsearch",
189
+ "/omp:deepsearch": "deepsearch",
190
+ "sciomc:": "sciomc",
191
+ "/sciomc": "sciomc",
192
+ "/omp:sciomc": "sciomc",
193
+ "remember:": "remember",
194
+ "/omp:remember": "remember",
195
+ "writer-memory:": "writer-memory",
196
+ "/writer-memory": "writer-memory",
197
+ "/omp:writer-memory": "writer-memory",
198
+ "deepinit:": "deepinit",
199
+ "/deepinit": "deepinit",
200
+ "/omp:deepinit": "deepinit",
201
+ "self-improve:": "self-improve",
202
+ "/self-improve": "self-improve",
203
+ "/omp:self-improve": "self-improve",
204
+ "visual-verdict:": "visual-verdict",
205
+ "/visual-verdict": "visual-verdict",
206
+ "/omp:visual-verdict": "visual-verdict",
207
+ "ccg:": "ccg",
208
+ "/ccg": "ccg",
209
+ "/omp:ccg": "ccg",
210
+ "build-fix:": "build-fix",
211
+ "/build-fix": "build-fix",
212
+ "/omp:build-fix": "build-fix",
213
+ "design:": "design",
214
+ "/omp:design": "design",
215
+ "web-clone:": "web-clone",
216
+ "/web-clone": "web-clone",
217
+ "/omp:web-clone": "web-clone"
103
218
  };
104
219
  var KEYWORD_ENTRIES = Object.entries(KEYWORD_MAP).sort(([a], [b]) => b.length - a.length);
105
220
  var CANONICAL_COMMAND_MAP = {
@@ -194,16 +309,7 @@ function processHook(input) {
194
309
  }
195
310
  }
196
311
  if (process.argv[1]?.endsWith("keyword-detector.mjs") || process.argv[1]?.endsWith("keyword-detector.mts")) {
197
- const input = JSON.parse(await readStdin());
198
- const output = processHook(input);
199
- console.log(JSON.stringify(output));
200
- }
201
- async function readStdin() {
202
- const chunks = [];
203
- for await (const chunk of process.stdin) {
204
- chunks.push(chunk);
205
- }
206
- return chunks.join("");
312
+ await runHookMain(processHook, { failOpenDecision: true, hookName: "keyword-detector" });
207
313
  }
208
314
  export {
209
315
  processHook
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../../src/hooks/keyword-detector.mts"],
4
- "sourcesContent": ["/**\n * keyword-detector hook\n * Trigger: pre-cycle (UserPromptSubmitted equivalent)\n * Priority: 100 (runs first)\n *\n * Scans incoming prompts for magic keywords and rewrites them\n * to skill invocation slash commands.\n */\n\nexport interface KeywordMatch {\n keyword: string;\n skillId: string;\n position: number;\n}\n\nconst KEYWORD_MAP: Record<string, string> = {\n \"autopilot:\": \"autopilot\",\n \"/autopilot\": \"autopilot\",\n \"/omp:autopilot\": \"autopilot\",\n \"ralph:\": \"ralph\",\n \"/ralph\": \"ralph\",\n \"/omp:ralph\": \"ralph\",\n \"ulw:\": \"ultrawork\",\n \"ultrawork:\": \"ultrawork\",\n \"/ulw\": \"ultrawork\",\n \"/ultrawork\": \"ultrawork\",\n \"/omp:ulw\": \"ultrawork\",\n \"/omp:ultrawork\": \"ultrawork\",\n \"team:\": \"team\",\n \"/team\": \"team\",\n \"/omp:team\": \"team\",\n \"eco:\": \"ecomode\",\n \"ecomode:\": \"ecomode\",\n \"/eco\": \"ecomode\",\n \"/ecomode\": \"ecomode\",\n \"/omp:eco\": \"ecomode\",\n \"/omp:ecomode\": \"ecomode\",\n \"swarm:\": \"swarm\",\n \"/swarm\": \"swarm\",\n \"/omp:swarm\": \"swarm\",\n \"pipeline:\": \"pipeline\",\n \"/pipeline\": \"pipeline\",\n \"/omp:pipeline\": \"pipeline\",\n \"deep interview:\": \"deep-interview\",\n \"/deep-interview\": \"deep-interview\",\n \"/omp:deep-interview\": \"deep-interview\",\n \"plan:\": \"omp-plan\",\n \"/plan\": \"omp-plan\",\n \"/omp-plan\": \"omp-plan\",\n \"/omp:plan\": \"omp-plan\",\n \"setup:\": \"omp-setup\",\n \"/setup\": \"omp-setup\",\n \"/omp-setup\": \"omp-setup\",\n \"/omp:setup\": \"omp-setup\",\n \"mcp:\": \"mcp-setup\",\n \"mcp-setup:\": \"mcp-setup\",\n \"/mcp\": \"mcp-setup\",\n \"/mcp-setup\": \"mcp-setup\",\n \"/omp:mcp-setup\": \"mcp-setup\",\n \"/hud\": \"hud\",\n \"hud:\": \"hud\",\n \"/omp:hud\": \"hud\",\n \"/wiki\": \"wiki\",\n \"wiki:\": \"wiki\",\n \"/omp:wiki\": \"wiki\",\n \"/learner\": \"learner\",\n \"learner:\": \"learner\",\n \"/omp:learner\": \"learner\",\n \"/note\": \"note\",\n \"note:\": \"note\",\n \"/omp:note\": \"note\",\n \"/trace\": \"trace\",\n \"trace:\": \"trace\",\n \"/omp:trace\": \"trace\",\n \"/release\": \"release\",\n \"release:\": \"release\",\n \"/omp:release\": \"release\",\n \"/configure-notifications\": \"configure-notifications\",\n \"configure-notifications:\": \"configure-notifications\",\n \"/omp:configure-notifications\": \"configure-notifications\",\n \"/psm\": \"psm\",\n \"psm:\": \"psm\",\n \"/omp:psm\": \"psm\",\n \"/swe-bench\": \"swe-bench\",\n \"swe-bench:\": \"swe-bench\",\n \"/omp:swe-bench\": \"swe-bench\",\n \"graphify:\": \"graphify\",\n \"graph build\": \"graphify\",\n \"build graph\": \"graphify\",\n \"graphwiki:\": \"graphwiki\",\n \"graph:\": \"graph-provider\",\n \"spending:\": \"spending\",\n \"/graphify\": \"graphify\",\n \"/omp:graphify\": \"graphify\",\n \"/graphwiki\": \"graphwiki\",\n \"/omp:graphwiki\": \"graphwiki\",\n \"/graph-provider\": \"graph-provider\",\n \"/omp:graph-provider\": \"graph-provider\",\n \"/spending\": \"spending\",\n \"/omp:spending\": \"spending\",\n \"--consensus\": \"omp-plan\",\n \"/omp:omp-doctor\": \"omp-doctor\",\n \"/omp:ralplan\": \"ralplan\",\n \"/omp:research\": \"research\",\n \"doctor:\": \"doctor\",\n \"/doctor\": \"doctor\",\n \"/omp:doctor\": \"doctor\",\n \"interview:\": \"interview\",\n \"/interview\": \"interview\",\n \"/omp:interview\": \"interview\",\n \"notifications:\": \"notifications\",\n \"/notifications\": \"notifications\",\n \"/omp:notifications\": \"notifications\",\n \"session:\": \"session\",\n \"/session\": \"session\",\n \"/omp:session\": \"session\",\n};\n\nconst KEYWORD_ENTRIES = Object.entries(KEYWORD_MAP).sort(([a], [b]) => b.length - a.length);\nconst CANONICAL_COMMAND_MAP: Record<string, string> = {\n \"omp-plan\": \"/omp:plan\",\n \"omp-setup\": \"/setup\",\n \"mcp-setup\": \"/mcp\",\n};\n\nexport interface HookInput {\n hook_type: \"UserPromptSubmitted\";\n prompt: string;\n session_id?: string;\n}\n\nexport interface HookOutput {\n decision?: \"allow\";\n modifiedPrompt?: string;\n additionalContext?: string;\n status: \"ok\" | \"skip\" | \"error\";\n latencyMs: number;\n mutations: Array<{ type: \"set_mode\"; mode: string } | { type: \"log\"; level: \"info\"; message: string }>;\n log: string[];\n}\n\nfunction detectKeyword(prompt: string): KeywordMatch | null {\n const trimmed = prompt.trimStart();\n\n // Prefer the longest literal alias match first so /mcp-setup wins over /mcp.\n for (const [keyword, skillId] of KEYWORD_ENTRIES) {\n if (trimmed.startsWith(keyword)) {\n return {\n keyword,\n skillId,\n position: 0,\n };\n }\n }\n\n // Case-insensitive check for slash forms\n const slashPattern = /^\\/((?:omp:)?[a-zA-Z][a-zA-Z0-9-]*)\\b/;\n const slashMatch = trimmed.match(slashPattern);\n if (slashMatch) {\n const cmd = slashMatch[1].toLowerCase();\n const skillId = KEYWORD_MAP[`/${cmd}`] ?? KEYWORD_MAP[`${cmd}:`];\n if (skillId) {\n return {\n keyword: slashMatch[0],\n skillId,\n position: 0,\n };\n }\n }\n\n // Compatibility: support long namespace aliases like\n // \"oh-my-githubcopilot:ralph\" (or \"/oh-my-githubcopilot:ralph\")\n const longNamespacePattern = /^\\/?oh-my-githubcopilot:([a-zA-Z][a-zA-Z0-9-]*)\\b/i;\n const longNamespaceMatch = trimmed.match(longNamespacePattern);\n if (longNamespaceMatch) {\n const cmd = longNamespaceMatch[1].toLowerCase();\n const skillId =\n KEYWORD_MAP[`/omp:${cmd}`] ??\n KEYWORD_MAP[`/${cmd}`] ??\n KEYWORD_MAP[`${cmd}:`];\n if (skillId) {\n return {\n keyword: longNamespaceMatch[0],\n skillId,\n position: 0,\n };\n }\n }\n\n return null;\n}\n\nfunction getCanonicalCommand(skillId: string): string {\n return CANONICAL_COMMAND_MAP[skillId] ?? `/omp:${skillId}`;\n}\n\nexport function processHook(input: HookInput): HookOutput {\n const start = Date.now();\n const log: string[] = [];\n\n try {\n if (input.hook_type !== \"UserPromptSubmitted\") {\n return {\n status: \"skip\",\n latencyMs: Date.now() - start,\n mutations: [],\n log: [\"Not a UserPromptSubmitted hook\"],\n };\n }\n\n const match = detectKeyword(input.prompt);\n if (!match) {\n return {\n status: \"ok\",\n latencyMs: Date.now() - start,\n mutations: [],\n log: [],\n };\n }\n\n // Rewrite prompt to invoke the skill\n const taskPart = input.prompt.slice(match.position + match.keyword.length).trim();\n const rewritten = `${getCanonicalCommand(match.skillId)}${taskPart ? ` ${taskPart}` : \"\"}`;\n\n log.push(`Keyword detected: \"${match.keyword}\" \u2192 skill: ${match.skillId}`);\n log.push(`Rewritten: \"${rewritten}\"`);\n\n return {\n status: \"ok\",\n latencyMs: Date.now() - start,\n modifiedPrompt: rewritten,\n mutations: [\n { type: \"set_mode\", mode: match.skillId },\n { type: \"log\", level: \"info\", message: `Skill activated: ${match.skillId}` },\n ],\n log,\n };\n } catch (err) {\n return {\n status: \"error\",\n latencyMs: Date.now() - start,\n mutations: [],\n log: [`Error: ${err}`],\n };\n }\n}\n\n// Main entry point \u2014 only runs when executed directly (not imported).\n// The omp CLI imports processHook directly to avoid double-dispatch when bundled.\nif (process.argv[1]?.endsWith(\"keyword-detector.mjs\") || process.argv[1]?.endsWith(\"keyword-detector.mts\")) {\n const input: HookInput = JSON.parse(await readStdin());\n const output = processHook(input);\n console.log(JSON.stringify(output));\n}\n\nasync function readStdin(): Promise<string> {\n const chunks: string[] = [];\n for await (const chunk of process.stdin) {\n chunks.push(chunk);\n }\n return chunks.join(\"\");\n}\n"],
5
- "mappings": "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",
6
- "names": []
3
+ "sources": ["../../src/hooks/runner.mts", "../../src/hooks/keyword-detector.mts"],
4
+ "sourcesContent": ["/**\n * Shared hook entry-point runner.\n *\n * Hooks must be FAIL-OPEN: any failure (empty stdin, malformed JSON,\n * unexpected processing error) must still emit a valid HookOutput-shaped\n * JSON object on stdout and exit 0. A non-zero exit or non-JSON stdout\n * causes the Copilot CLI to treat the hook as errored, which denies the\n * tool call for PreToolUse hooks.\n *\n * Fail-open events are persisted (best-effort) as JSONL records to\n * ~/.omp/logs/hook-failures.jsonl and mirrored on stderr so failures\n * remain observable without ever touching the stdout JSON contract.\n */\n\nimport { appendFileSync, mkdirSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { join } from \"path\";\n\nexport interface FailOpenOutput {\n decision?: \"allow\";\n status: \"error\";\n latencyMs: number;\n mutations: never[];\n log: string[];\n}\n\nexport interface RunHookOptions {\n /**\n * When true (hooks whose HookOutput supports a decision field), the\n * fail-open output includes `\"decision\": \"allow\"` so the tool call is\n * explicitly allowed.\n */\n failOpenDecision?: boolean;\n /** Hook id recorded in fail-open log entries (stderr + JSONL). */\n hookName?: string;\n}\n\nexport async function readStdin(): Promise<string> {\n const readStdinActual = async (): Promise<string> => {\n const chunks: string[] = [];\n for await (const chunk of process.stdin) {\n chunks.push(String(chunk));\n }\n return chunks.join(\"\");\n };\n\n const stdinTimeout = new Promise<string>((resolve) =>\n setTimeout(\n () => resolve(\"\"),\n (parseInt(process.env.OMP_HOOK_STDIN_TIMEOUT_MS ?? \"500\") || 500)\n )\n );\n\n return Promise.race([readStdinActual(), stdinTimeout]);\n}\n\n/**\n * Best-effort persistence of a fail-open event. Wrapped in its own\n * try/catch: logging must NEVER break fail-open or the one-JSON-object\n * stdout contract. Writes to stderr and the JSONL log only \u2014 never stdout.\n */\nfunction logHookFailure(hook: string, reason: string): void {\n try {\n process.stderr.write(`[omp hook fail-open] ${hook}: ${reason}\\n`);\n } catch {\n // stderr unavailable \u2014 ignore\n }\n try {\n const logsDir = join(homedir(), \".omp\", \"logs\");\n mkdirSync(logsDir, { recursive: true });\n const record = JSON.stringify({ ts: new Date().toISOString(), hook, reason });\n appendFileSync(join(logsDir, \"hook-failures.jsonl\"), record + \"\\n\", \"utf-8\");\n } catch {\n // best-effort only \u2014 never let logging break fail-open\n }\n}\n\n/**\n * Reads HookInput JSON from stdin, runs the hook, and prints the\n * HookOutput JSON to stdout. Never throws, never exits non-zero,\n * never emits non-JSON to stdout.\n */\nexport async function runHookMain<TInput>(\n processHook: (input: TInput) => unknown,\n options: RunHookOptions = {}\n): Promise<void> {\n let outputJson: string;\n try {\n const input = JSON.parse(await readStdin()) as TInput;\n const serialized = JSON.stringify(processHook(input));\n if (typeof serialized !== \"string\") {\n throw new Error(\"hook produced no serializable output\");\n }\n outputJson = serialized;\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n logHookFailure(options.hookName ?? \"unknown\", reason);\n const failOpen: FailOpenOutput = {\n ...(options.failOpenDecision ? { decision: \"allow\" as const } : {}),\n status: \"error\",\n latencyMs: 0,\n mutations: [],\n log: [`fail-open: ${reason}`],\n };\n outputJson = JSON.stringify(failOpen);\n }\n console.log(outputJson);\n process.exitCode = 0;\n}\n", "/**\n * keyword-detector hook\n * Trigger: pre-cycle (UserPromptSubmitted equivalent)\n * Priority: 100 (runs first)\n *\n * Scans incoming prompts for magic keywords and rewrites them\n * to skill invocation slash commands.\n */\n\nexport interface KeywordMatch {\n keyword: string;\n skillId: string;\n position: number;\n}\n\nconst KEYWORD_MAP: Record<string, string> = {\n \"autopilot:\": \"autopilot\",\n \"/autopilot\": \"autopilot\",\n \"/omp:autopilot\": \"autopilot\",\n \"ralph:\": \"ralph\",\n \"/ralph\": \"ralph\",\n \"/omp:ralph\": \"ralph\",\n \"ulw:\": \"ultrawork\",\n \"ultrawork:\": \"ultrawork\",\n \"/ulw\": \"ultrawork\",\n \"/ultrawork\": \"ultrawork\",\n \"/omp:ulw\": \"ultrawork\",\n \"/omp:ultrawork\": \"ultrawork\",\n \"team:\": \"team\",\n \"/team\": \"team\",\n \"/omp:team\": \"team\",\n \"eco:\": \"ecomode\",\n \"ecomode:\": \"ecomode\",\n \"/eco\": \"ecomode\",\n \"/ecomode\": \"ecomode\",\n \"/omp:eco\": \"ecomode\",\n \"/omp:ecomode\": \"ecomode\",\n \"swarm:\": \"swarm\",\n \"/swarm\": \"swarm\",\n \"/omp:swarm\": \"swarm\",\n \"pipeline:\": \"pipeline\",\n \"/pipeline\": \"pipeline\",\n \"/omp:pipeline\": \"pipeline\",\n \"deep interview:\": \"deep-interview\",\n \"/deep-interview\": \"deep-interview\",\n \"/omp:deep-interview\": \"deep-interview\",\n \"plan:\": \"omp-plan\",\n \"/plan\": \"omp-plan\",\n \"/omp-plan\": \"omp-plan\",\n \"/omp:plan\": \"omp-plan\",\n \"setup:\": \"omp-setup\",\n \"/setup\": \"omp-setup\",\n \"/omp-setup\": \"omp-setup\",\n \"/omp:setup\": \"omp-setup\",\n \"mcp:\": \"mcp-setup\",\n \"mcp-setup:\": \"mcp-setup\",\n \"/mcp\": \"mcp-setup\",\n \"/mcp-setup\": \"mcp-setup\",\n \"/omp:mcp-setup\": \"mcp-setup\",\n \"/hud\": \"hud\",\n \"hud:\": \"hud\",\n \"/omp:hud\": \"hud\",\n \"/wiki\": \"wiki\",\n \"wiki:\": \"wiki\",\n \"/omp:wiki\": \"wiki\",\n \"/learner\": \"learner\",\n \"learner:\": \"learner\",\n \"/omp:learner\": \"learner\",\n \"/note\": \"note\",\n \"note:\": \"note\",\n \"/omp:note\": \"note\",\n \"/trace\": \"trace\",\n \"trace:\": \"trace\",\n \"/omp:trace\": \"trace\",\n \"/release\": \"release\",\n \"release:\": \"release\",\n \"/omp:release\": \"release\",\n \"/configure-notifications\": \"configure-notifications\",\n \"configure-notifications:\": \"configure-notifications\",\n \"/omp:configure-notifications\": \"configure-notifications\",\n \"/psm\": \"psm\",\n \"psm:\": \"psm\",\n \"/omp:psm\": \"psm\",\n \"/swe-bench\": \"swe-bench\",\n \"swe-bench:\": \"swe-bench\",\n \"/omp:swe-bench\": \"swe-bench\",\n \"graphify:\": \"graphify\",\n \"graph build\": \"graphify\",\n \"build graph\": \"graphify\",\n \"graphwiki:\": \"graphwiki\",\n \"graph:\": \"graph-provider\",\n \"spending:\": \"spending\",\n \"/graphify\": \"graphify\",\n \"/omp:graphify\": \"graphify\",\n \"/graphwiki\": \"graphwiki\",\n \"/omp:graphwiki\": \"graphwiki\",\n \"/graph-provider\": \"graph-provider\",\n \"/omp:graph-provider\": \"graph-provider\",\n \"/spending\": \"spending\",\n \"/omp:spending\": \"spending\",\n \"--consensus\": \"omp-plan\",\n \"/omp:omp-doctor\": \"omp-doctor\",\n \"/omp:ralplan\": \"ralplan\",\n \"/omp:research\": \"research\",\n \"doctor:\": \"doctor\",\n \"/doctor\": \"doctor\",\n \"/omp:doctor\": \"doctor\",\n \"interview:\": \"interview\",\n \"/interview\": \"interview\",\n \"/omp:interview\": \"interview\",\n \"notifications:\": \"notifications\",\n \"/notifications\": \"notifications\",\n \"/omp:notifications\": \"notifications\",\n \"session:\": \"session\",\n \"/session\": \"session\",\n \"/omp:session\": \"session\",\n \"verify:\": \"verify\",\n \"/verify\": \"verify\",\n \"/omp:verify\": \"verify\",\n \"cancel:\": \"cancel\",\n \"/omp:cancel\": \"cancel\",\n \"help:\": \"help\",\n \"/omp:help\": \"help\",\n \"code-review:\": \"code-review\",\n \"/code-review\": \"code-review\",\n \"/omp:code-review\": \"code-review\",\n \"security-review:\": \"security-review\",\n \"/security-review\": \"security-review\",\n \"/omp:security-review\": \"security-review\",\n \"ultraqa:\": \"ultraqa\",\n \"/ultraqa\": \"ultraqa\",\n \"/omp:ultraqa\": \"ultraqa\",\n \"ultragoal:\": \"ultragoal\",\n \"/ultragoal\": \"ultragoal\",\n \"/omp:ultragoal\": \"ultragoal\",\n \"deep-dive:\": \"deep-dive\",\n \"/deep-dive\": \"deep-dive\",\n \"/omp:deep-dive\": \"deep-dive\",\n \"external-context:\": \"external-context\",\n \"/external-context\": \"external-context\",\n \"/omp:external-context\": \"external-context\",\n \"deepsearch:\": \"deepsearch\",\n \"/deepsearch\": \"deepsearch\",\n \"/omp:deepsearch\": \"deepsearch\",\n \"sciomc:\": \"sciomc\",\n \"/sciomc\": \"sciomc\",\n \"/omp:sciomc\": \"sciomc\",\n \"remember:\": \"remember\",\n \"/omp:remember\": \"remember\",\n \"writer-memory:\": \"writer-memory\",\n \"/writer-memory\": \"writer-memory\",\n \"/omp:writer-memory\": \"writer-memory\",\n \"deepinit:\": \"deepinit\",\n \"/deepinit\": \"deepinit\",\n \"/omp:deepinit\": \"deepinit\",\n \"self-improve:\": \"self-improve\",\n \"/self-improve\": \"self-improve\",\n \"/omp:self-improve\": \"self-improve\",\n \"visual-verdict:\": \"visual-verdict\",\n \"/visual-verdict\": \"visual-verdict\",\n \"/omp:visual-verdict\": \"visual-verdict\",\n \"ccg:\": \"ccg\",\n \"/ccg\": \"ccg\",\n \"/omp:ccg\": \"ccg\",\n \"build-fix:\": \"build-fix\",\n \"/build-fix\": \"build-fix\",\n \"/omp:build-fix\": \"build-fix\",\n \"design:\": \"design\",\n \"/omp:design\": \"design\",\n \"web-clone:\": \"web-clone\",\n \"/web-clone\": \"web-clone\",\n \"/omp:web-clone\": \"web-clone\",\n};\n\nconst KEYWORD_ENTRIES = Object.entries(KEYWORD_MAP).sort(([a], [b]) => b.length - a.length);\nconst CANONICAL_COMMAND_MAP: Record<string, string> = {\n \"omp-plan\": \"/omp:plan\",\n \"omp-setup\": \"/setup\",\n \"mcp-setup\": \"/mcp\",\n};\n\nexport interface HookInput {\n hook_type: \"UserPromptSubmitted\";\n prompt: string;\n session_id?: string;\n}\n\nexport interface HookOutput {\n decision?: \"allow\";\n modifiedPrompt?: string;\n additionalContext?: string;\n status: \"ok\" | \"skip\" | \"error\";\n latencyMs: number;\n mutations: Array<{ type: \"set_mode\"; mode: string } | { type: \"log\"; level: \"info\"; message: string }>;\n log: string[];\n}\n\nfunction detectKeyword(prompt: string): KeywordMatch | null {\n const trimmed = prompt.trimStart();\n\n // Prefer the longest literal alias match first so /mcp-setup wins over /mcp.\n for (const [keyword, skillId] of KEYWORD_ENTRIES) {\n if (trimmed.startsWith(keyword)) {\n return {\n keyword,\n skillId,\n position: 0,\n };\n }\n }\n\n // Case-insensitive check for slash forms\n const slashPattern = /^\\/((?:omp:)?[a-zA-Z][a-zA-Z0-9-]*)\\b/;\n const slashMatch = trimmed.match(slashPattern);\n if (slashMatch) {\n const cmd = slashMatch[1].toLowerCase();\n const skillId = KEYWORD_MAP[`/${cmd}`] ?? KEYWORD_MAP[`${cmd}:`];\n if (skillId) {\n return {\n keyword: slashMatch[0],\n skillId,\n position: 0,\n };\n }\n }\n\n // Compatibility: support long namespace aliases like\n // \"oh-my-githubcopilot:ralph\" (or \"/oh-my-githubcopilot:ralph\")\n const longNamespacePattern = /^\\/?oh-my-githubcopilot:([a-zA-Z][a-zA-Z0-9-]*)\\b/i;\n const longNamespaceMatch = trimmed.match(longNamespacePattern);\n if (longNamespaceMatch) {\n const cmd = longNamespaceMatch[1].toLowerCase();\n const skillId =\n KEYWORD_MAP[`/omp:${cmd}`] ??\n KEYWORD_MAP[`/${cmd}`] ??\n KEYWORD_MAP[`${cmd}:`];\n if (skillId) {\n return {\n keyword: longNamespaceMatch[0],\n skillId,\n position: 0,\n };\n }\n }\n\n return null;\n}\n\nfunction getCanonicalCommand(skillId: string): string {\n return CANONICAL_COMMAND_MAP[skillId] ?? `/omp:${skillId}`;\n}\n\nexport function processHook(input: HookInput): HookOutput {\n const start = Date.now();\n const log: string[] = [];\n\n try {\n if (input.hook_type !== \"UserPromptSubmitted\") {\n return {\n status: \"skip\",\n latencyMs: Date.now() - start,\n mutations: [],\n log: [\"Not a UserPromptSubmitted hook\"],\n };\n }\n\n const match = detectKeyword(input.prompt);\n if (!match) {\n return {\n status: \"ok\",\n latencyMs: Date.now() - start,\n mutations: [],\n log: [],\n };\n }\n\n // Rewrite prompt to invoke the skill\n const taskPart = input.prompt.slice(match.position + match.keyword.length).trim();\n const rewritten = `${getCanonicalCommand(match.skillId)}${taskPart ? ` ${taskPart}` : \"\"}`;\n\n log.push(`Keyword detected: \"${match.keyword}\" \u2192 skill: ${match.skillId}`);\n log.push(`Rewritten: \"${rewritten}\"`);\n\n return {\n status: \"ok\",\n latencyMs: Date.now() - start,\n modifiedPrompt: rewritten,\n mutations: [\n { type: \"set_mode\", mode: match.skillId },\n { type: \"log\", level: \"info\", message: `Skill activated: ${match.skillId}` },\n ],\n log,\n };\n } catch (err) {\n return {\n status: \"error\",\n latencyMs: Date.now() - start,\n mutations: [],\n log: [`Error: ${err}`],\n };\n }\n}\n\n// Main entry point \u2014 only runs when executed directly (not imported).\n// The omp CLI imports processHook directly to avoid double-dispatch when bundled.\n// Fail-open: any stdin/parse/processing failure still emits valid JSON and exits 0.\nimport { runHookMain } from \"./runner.mts\";\n\nif (process.argv[1]?.endsWith(\"keyword-detector.mjs\") || process.argv[1]?.endsWith(\"keyword-detector.mts\")) {\n await runHookMain(processHook, { failOpenDecision: true, hookName: \"keyword-detector\" });\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["processHook"]
7
7
  }