oh-my-githubcopilot 1.4.1 → 1.8.0-alpha.021bf87

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 (123) hide show
  1. package/.claude-plugin/plugin.json +36 -6
  2. package/.mcp.json +17 -0
  3. package/AGENTS.md +78 -9
  4. package/CHANGELOG.md +216 -1
  5. package/README.de.md +112 -26
  6. package/README.es.md +115 -29
  7. package/README.fr.md +114 -28
  8. package/README.it.md +114 -28
  9. package/README.ja.md +112 -26
  10. package/README.ko.md +112 -26
  11. package/README.md +96 -95
  12. package/README.pt.md +116 -30
  13. package/README.ru.md +116 -30
  14. package/README.tr.md +115 -29
  15. package/README.vi.md +116 -30
  16. package/README.zh.md +112 -26
  17. package/agents/analyst.agent.md +27 -0
  18. package/agents/architect.agent.md +24 -0
  19. package/agents/code-reviewer.agent.md +24 -0
  20. package/agents/critic.agent.md +24 -0
  21. package/agents/debugger.agent.md +24 -0
  22. package/agents/designer.agent.md +24 -0
  23. package/agents/document-specialist.agent.md +24 -0
  24. package/agents/executor.agent.md +27 -0
  25. package/agents/explorer.agent.md +23 -0
  26. package/agents/git-master.agent.md +24 -0
  27. package/agents/orchestrator.agent.md +26 -0
  28. package/agents/planner.agent.md +24 -0
  29. package/agents/qa-tester.agent.md +24 -0
  30. package/agents/researcher.agent.md +18 -0
  31. package/agents/reviewer.agent.md +23 -0
  32. package/agents/scientist.agent.md +20 -0
  33. package/agents/security-reviewer.agent.md +20 -0
  34. package/agents/simplifier.agent.md +20 -0
  35. package/agents/test-engineer.agent.md +20 -0
  36. package/agents/tester.agent.md +20 -0
  37. package/agents/tracer.agent.md +24 -0
  38. package/agents/verifier.agent.md +19 -0
  39. package/agents/writer.agent.md +24 -0
  40. package/bin/omp-statusline.mjs +179 -0
  41. package/bin/omp-statusline.mjs.map +7 -0
  42. package/bin/omp-statusline.sh +21 -0
  43. package/bin/omp.mjs +709 -16
  44. package/bin/omp.mjs.map +4 -4
  45. package/dist/hooks/hud-emitter.mjs +268 -82
  46. package/dist/hooks/hud-emitter.mjs.map +4 -4
  47. package/dist/hooks/keyword-detector.mjs +100 -23
  48. package/dist/hooks/keyword-detector.mjs.map +2 -2
  49. package/dist/hooks/model-router.mjs +1 -1
  50. package/dist/hooks/model-router.mjs.map +1 -1
  51. package/dist/hooks/stop-continuation.mjs +1 -1
  52. package/dist/hooks/stop-continuation.mjs.map +1 -1
  53. package/dist/hooks/token-tracker.mjs +2 -1
  54. package/dist/hooks/token-tracker.mjs.map +2 -2
  55. package/dist/mcp/server.mjs +85 -53
  56. package/dist/mcp/server.mjs.map +4 -4
  57. package/dist/skills/setup.mjs +39 -27
  58. package/dist/skills/setup.mjs.map +4 -4
  59. package/hooks/hooks.json +39 -45
  60. package/package.json +9 -4
  61. package/plugin.json +71 -0
  62. package/skills/ai-slop-cleaner/SKILL.md +137 -0
  63. package/skills/autopilot/SKILL.md +6 -0
  64. package/skills/configure-notifications/SKILL.md +6 -0
  65. package/skills/deep-interview/SKILL.md +6 -0
  66. package/skills/doctor/SKILL.md +188 -0
  67. package/skills/ecomode/SKILL.md +6 -0
  68. package/skills/graph-context/SKILL.md +119 -0
  69. package/skills/graph-provider/SKILL.md +6 -0
  70. package/skills/graphify/SKILL.md +6 -0
  71. package/skills/graphwiki/SKILL.md +6 -0
  72. package/skills/hud/SKILL.md +6 -0
  73. package/skills/improve-codebase-architecture/SKILL.md +214 -0
  74. package/skills/interactive-menu/SKILL.md +102 -0
  75. package/skills/interview/SKILL.md +203 -0
  76. package/skills/learner/SKILL.md +6 -0
  77. package/skills/mcp-setup/SKILL.md +6 -0
  78. package/skills/note/SKILL.md +6 -0
  79. package/skills/notifications/SKILL.md +190 -0
  80. package/skills/omp-doctor/SKILL.md +146 -0
  81. package/skills/omp-plan/SKILL.md +219 -2
  82. package/skills/omp-reference/SKILL.md +174 -0
  83. package/skills/omp-setup/SKILL.md +15 -1
  84. package/skills/pipeline/SKILL.md +6 -0
  85. package/skills/psm/SKILL.md +6 -0
  86. package/skills/ralph/SKILL.md +6 -0
  87. package/skills/ralplan/SKILL.md +148 -0
  88. package/skills/release/SKILL.md +6 -0
  89. package/skills/research/SKILL.md +149 -0
  90. package/skills/session/SKILL.md +220 -0
  91. package/skills/setup/SKILL.md +6 -0
  92. package/skills/skillify/SKILL.md +66 -0
  93. package/skills/spending/SKILL.md +6 -0
  94. package/skills/swarm/SKILL.md +6 -0
  95. package/skills/swe-bench/SKILL.md +6 -0
  96. package/skills/tdd/SKILL.md +246 -0
  97. package/skills/team/SKILL.md +6 -0
  98. package/skills/trace/SKILL.md +6 -0
  99. package/skills/ultrawork/SKILL.md +6 -0
  100. package/skills/wiki/SKILL.md +6 -0
  101. package/src/agents/analyst.md +0 -103
  102. package/src/agents/architect.md +0 -169
  103. package/src/agents/code-reviewer.md +0 -135
  104. package/src/agents/critic.md +0 -196
  105. package/src/agents/debugger.md +0 -132
  106. package/src/agents/designer.md +0 -103
  107. package/src/agents/document-specialist.md +0 -111
  108. package/src/agents/executor.md +0 -120
  109. package/src/agents/explorer.md +0 -98
  110. package/src/agents/git-master.md +0 -92
  111. package/src/agents/orchestrator.md +0 -125
  112. package/src/agents/planner.md +0 -106
  113. package/src/agents/qa-tester.md +0 -129
  114. package/src/agents/researcher.md +0 -102
  115. package/src/agents/reviewer.md +0 -100
  116. package/src/agents/scientist.md +0 -150
  117. package/src/agents/security-reviewer.md +0 -132
  118. package/src/agents/simplifier.md +0 -109
  119. package/src/agents/test-engineer.md +0 -124
  120. package/src/agents/tester.md +0 -102
  121. package/src/agents/tracer.md +0 -160
  122. package/src/agents/verifier.md +0 -100
  123. package/src/agents/writer.md +0 -96
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../../src/hooks/hud-emitter.mts"],
4
- "sourcesContent": ["/**\n * hud-emitter hook\n * Trigger: post-cycle (PostToolUse + SessionStart)\n * Priority: 60\n *\n * Writes HUD statusline to ~/.omp/hud.line after every tool call.\n * Initializes session state on SessionStart.\n */\n\nimport { readFileSync, writeFileSync, mkdirSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { join } from \"path\";\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 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}\n\nfunction getStatePath(sessionId?: string): string {\n const base = join(homedir(), \".omp\", \"state\");\n if (sessionId) {\n return join(base, \"sessions\", sessionId, \"session.json\");\n }\n return join(base, \"session.json\");\n}\n\nfunction getHudLinePath(): string {\n return join(homedir(), \".omp\", \"hud.line\");\n}\n\nfunction ensureDir(path: string): void {\n mkdirSync(path.substring(0, path.lastIndexOf(\"/\")), { recursive: true });\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 buildHudLine(state: SessionState): string {\n const age = formatAge(state.started_at);\n const tokens = formatTokens(state.tokens_estimated);\n const ctx = state.context_pct;\n const tools = state.tools_used.length;\n const skills = state.skills_used.length;\n const agents = state.agents_used.length;\n const mode = state.active_mode || \"-\";\n const model = state.model || \"sonnet\";\n\n return `OMP v${state.version} | ${model} | tkn: ${tokens}/${state.token_budget} | ctx: ${ctx}% | session: ${age} | tools: ${tools} | skills: ${skills} | agents: ${agents} | mode: ${mode}`;\n}\n\nfunction processSessionStart(input: HookInput): HookOutput {\n const start = Date.now();\n const log: string[] = [];\n const sessionId = input.session_id || \"default\";\n\n const state: SessionState = {\n version: \"1.0.0\",\n session_id: sessionId,\n started_at: Date.now(),\n model: input.model || \"claude-sonnet-4.5\",\n tokens_estimated: 0,\n token_budget: 200_000,\n context_pct: 0,\n tools_used: [],\n skills_used: [],\n agents_used: [],\n active_mode: null,\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 hudLine = buildHudLine(state);\n const hudPath = getHudLinePath();\n ensureDir(hudPath);\n writeFileSync(hudPath, hudLine, \"utf-8\");\n log.push(`HUD line written: ${hudLine}`);\n\n return {\n status: \"ok\",\n latencyMs: Date.now() - start,\n mutations: [\n {\n type: \"emit_hud\",\n hudEmit: {\n sessionId,\n activeMode: null,\n contextPct: 0,\n tokensUsed: 0,\n tokensTotal: 200_000,\n agentsActive: [],\n lastAgent: \"-\",\n lastOutput: \"\",\n taskProgress: 0,\n },\n },\n ],\n log,\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 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 };\n } catch {\n // Fall back to session start behavior if no state\n return processSessionStart(input);\n }\n\n // Update tools used\n if (input.tool_name && !state.tools_used.includes(input.tool_name)) {\n state.tools_used.push(input.tool_name);\n }\n\n // Recalculate HUD line\n const hudLine = buildHudLine(state);\n const hudPath = getHudLinePath();\n ensureDir(hudPath);\n writeFileSync(hudPath, hudLine, \"utf-8\");\n log.push(`HUD updated: ${hudLine}`);\n\n // Write updated state\n writeFileSync(statePath, JSON.stringify(state), \"utf-8\");\n\n return {\n status: \"ok\",\n latencyMs: Date.now() - start,\n mutations: [\n {\n type: \"emit_hud\",\n hudEmit: {\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: \"\",\n taskProgress: 0,\n },\n },\n ],\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)\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"],
5
- "mappings": "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",
6
- "names": []
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"]
7
7
  }
@@ -1,54 +1,115 @@
1
1
  // src/hooks/keyword-detector.mts
2
- import { fileURLToPath } from "url";
3
2
  var KEYWORD_MAP = {
4
3
  "autopilot:": "autopilot",
4
+ "/autopilot": "autopilot",
5
+ "/omp:autopilot": "autopilot",
5
6
  "ralph:": "ralph",
7
+ "/ralph": "ralph",
8
+ "/omp:ralph": "ralph",
6
9
  "ulw:": "ultrawork",
7
- "team:": "team",
8
- "eco:": "ecomode",
9
- "swarm:": "swarm",
10
- "pipeline:": "pipeline",
11
- "plan:": "omp-plan",
12
- // Aliases (shortcut commands)
13
- "setup:": "setup",
14
- "ralplan:": "ralplan",
15
- "ultraqa:": "ultraqa",
16
- "mcp:": "mcp-setup",
17
10
  "ultrawork:": "ultrawork",
18
- "ecomode:": "ecomode",
19
- // Phase 1.1 skill stubs (19 total from plugin.json)
20
- "/autopilot": "autopilot",
21
- "/ralph": "ralph",
22
11
  "/ulw": "ultrawork",
12
+ "/ultrawork": "ultrawork",
13
+ "/omp:ulw": "ultrawork",
14
+ "/omp:ultrawork": "ultrawork",
15
+ "team:": "team",
23
16
  "/team": "team",
17
+ "/omp:team": "team",
18
+ "eco:": "ecomode",
19
+ "ecomode:": "ecomode",
24
20
  "/eco": "ecomode",
21
+ "/ecomode": "ecomode",
22
+ "/omp:eco": "ecomode",
23
+ "/omp:ecomode": "ecomode",
24
+ "swarm:": "swarm",
25
25
  "/swarm": "swarm",
26
+ "/omp:swarm": "swarm",
27
+ "pipeline:": "pipeline",
26
28
  "/pipeline": "pipeline",
29
+ "/omp:pipeline": "pipeline",
30
+ "deep interview:": "deep-interview",
27
31
  "/deep-interview": "deep-interview",
32
+ "/omp:deep-interview": "deep-interview",
33
+ "plan:": "omp-plan",
34
+ "/plan": "omp-plan",
28
35
  "/omp-plan": "omp-plan",
36
+ "/omp:plan": "omp-plan",
37
+ "setup:": "omp-setup",
38
+ "/setup": "omp-setup",
29
39
  "/omp-setup": "omp-setup",
40
+ "/omp:setup": "omp-setup",
41
+ "mcp:": "mcp-setup",
42
+ "mcp-setup:": "mcp-setup",
43
+ "/mcp": "mcp-setup",
44
+ "/mcp-setup": "mcp-setup",
45
+ "/omp:mcp-setup": "mcp-setup",
30
46
  "/hud": "hud",
47
+ "hud:": "hud",
48
+ "/omp:hud": "hud",
31
49
  "/wiki": "wiki",
50
+ "wiki:": "wiki",
51
+ "/omp:wiki": "wiki",
32
52
  "/learner": "learner",
53
+ "learner:": "learner",
54
+ "/omp:learner": "learner",
33
55
  "/note": "note",
56
+ "note:": "note",
57
+ "/omp:note": "note",
34
58
  "/trace": "trace",
59
+ "trace:": "trace",
60
+ "/omp:trace": "trace",
35
61
  "/release": "release",
62
+ "release:": "release",
63
+ "/omp:release": "release",
36
64
  "/configure-notifications": "configure-notifications",
65
+ "configure-notifications:": "configure-notifications",
66
+ "/omp:configure-notifications": "configure-notifications",
37
67
  "/psm": "psm",
68
+ "psm:": "psm",
69
+ "/omp:psm": "psm",
38
70
  "/swe-bench": "swe-bench",
39
- // v1.2 graph provider + spending skills
71
+ "swe-bench:": "swe-bench",
72
+ "/omp:swe-bench": "swe-bench",
40
73
  "graphify:": "graphify",
74
+ "graph build": "graphify",
75
+ "build graph": "graphify",
41
76
  "graphwiki:": "graphwiki",
42
77
  "graph:": "graph-provider",
43
78
  "spending:": "spending",
44
79
  "/graphify": "graphify",
80
+ "/omp:graphify": "graphify",
45
81
  "/graphwiki": "graphwiki",
82
+ "/omp:graphwiki": "graphwiki",
46
83
  "/graph-provider": "graph-provider",
47
- "/spending": "spending"
84
+ "/omp:graph-provider": "graph-provider",
85
+ "/spending": "spending",
86
+ "/omp:spending": "spending",
87
+ "--consensus": "omp-plan",
88
+ "/omp:omp-doctor": "omp-doctor",
89
+ "/omp:ralplan": "ralplan",
90
+ "/omp:research": "research",
91
+ "doctor:": "doctor",
92
+ "/doctor": "doctor",
93
+ "/omp:doctor": "doctor",
94
+ "interview:": "interview",
95
+ "/interview": "interview",
96
+ "/omp:interview": "interview",
97
+ "notifications:": "notifications",
98
+ "/notifications": "notifications",
99
+ "/omp:notifications": "notifications",
100
+ "session:": "session",
101
+ "/session": "session",
102
+ "/omp:session": "session"
103
+ };
104
+ var KEYWORD_ENTRIES = Object.entries(KEYWORD_MAP).sort(([a], [b]) => b.length - a.length);
105
+ var CANONICAL_COMMAND_MAP = {
106
+ "omp-plan": "/omp:plan",
107
+ "omp-setup": "/setup",
108
+ "mcp-setup": "/mcp"
48
109
  };
49
110
  function detectKeyword(prompt) {
50
111
  const trimmed = prompt.trimStart();
51
- for (const [keyword, skillId] of Object.entries(KEYWORD_MAP)) {
112
+ for (const [keyword, skillId] of KEYWORD_ENTRIES) {
52
113
  if (trimmed.startsWith(keyword)) {
53
114
  return {
54
115
  keyword,
@@ -57,12 +118,12 @@ function detectKeyword(prompt) {
57
118
  };
58
119
  }
59
120
  }
60
- const slashPattern = /^\/([a-zA-Z]+)\b/;
121
+ const slashPattern = /^\/((?:omp:)?[a-zA-Z][a-zA-Z0-9-]*)\b/;
61
122
  const slashMatch = trimmed.match(slashPattern);
62
123
  if (slashMatch) {
63
124
  const cmd = slashMatch[1].toLowerCase();
64
- const skillId = KEYWORD_MAP[`${cmd}:`] || cmd;
65
- if (skillId !== cmd || Object.values(KEYWORD_MAP).includes(cmd)) {
125
+ const skillId = KEYWORD_MAP[`/${cmd}`] ?? KEYWORD_MAP[`${cmd}:`];
126
+ if (skillId) {
66
127
  return {
67
128
  keyword: slashMatch[0],
68
129
  skillId,
@@ -70,8 +131,24 @@ function detectKeyword(prompt) {
70
131
  };
71
132
  }
72
133
  }
134
+ const longNamespacePattern = /^\/?oh-my-githubcopilot:([a-zA-Z][a-zA-Z0-9-]*)\b/i;
135
+ const longNamespaceMatch = trimmed.match(longNamespacePattern);
136
+ if (longNamespaceMatch) {
137
+ const cmd = longNamespaceMatch[1].toLowerCase();
138
+ const skillId = KEYWORD_MAP[`/omp:${cmd}`] ?? KEYWORD_MAP[`/${cmd}`] ?? KEYWORD_MAP[`${cmd}:`];
139
+ if (skillId) {
140
+ return {
141
+ keyword: longNamespaceMatch[0],
142
+ skillId,
143
+ position: 0
144
+ };
145
+ }
146
+ }
73
147
  return null;
74
148
  }
149
+ function getCanonicalCommand(skillId) {
150
+ return CANONICAL_COMMAND_MAP[skillId] ?? `/omp:${skillId}`;
151
+ }
75
152
  function processHook(input) {
76
153
  const start = Date.now();
77
154
  const log = [];
@@ -94,7 +171,7 @@ function processHook(input) {
94
171
  };
95
172
  }
96
173
  const taskPart = input.prompt.slice(match.position + match.keyword.length).trim();
97
- const rewritten = `/oh-my-githubcopilot:${match.skillId}${taskPart ? ` ${taskPart}` : ""}`;
174
+ const rewritten = `${getCanonicalCommand(match.skillId)}${taskPart ? ` ${taskPart}` : ""}`;
98
175
  log.push(`Keyword detected: "${match.keyword}" \u2192 skill: ${match.skillId}`);
99
176
  log.push(`Rewritten: "${rewritten}"`);
100
177
  return {
@@ -116,7 +193,7 @@ function processHook(input) {
116
193
  };
117
194
  }
118
195
  }
119
- if (process.argv[1] === fileURLToPath(import.meta.url)) {
196
+ if (process.argv[1]?.endsWith("keyword-detector.mjs") || process.argv[1]?.endsWith("keyword-detector.mts")) {
120
197
  const input = JSON.parse(await readStdin());
121
198
  const output = processHook(input);
122
199
  console.log(JSON.stringify(output));
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
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 \"ralph:\": \"ralph\",\n \"ulw:\": \"ultrawork\",\n \"team:\": \"team\",\n \"eco:\": \"ecomode\",\n \"swarm:\": \"swarm\",\n \"pipeline:\": \"pipeline\",\n \"plan:\": \"omp-plan\",\n // Aliases (shortcut commands)\n \"setup:\": \"setup\",\n \"ralplan:\": \"ralplan\",\n \"ultraqa:\": \"ultraqa\",\n \"mcp:\": \"mcp-setup\",\n \"ultrawork:\": \"ultrawork\",\n \"ecomode:\": \"ecomode\",\n // Phase 1.1 skill stubs (19 total from plugin.json)\n \"/autopilot\": \"autopilot\",\n \"/ralph\": \"ralph\",\n \"/ulw\": \"ultrawork\",\n \"/team\": \"team\",\n \"/eco\": \"ecomode\",\n \"/swarm\": \"swarm\",\n \"/pipeline\": \"pipeline\",\n \"/deep-interview\": \"deep-interview\",\n \"/omp-plan\": \"omp-plan\",\n \"/omp-setup\": \"omp-setup\",\n \"/hud\": \"hud\",\n \"/wiki\": \"wiki\",\n \"/learner\": \"learner\",\n \"/note\": \"note\",\n \"/trace\": \"trace\",\n \"/release\": \"release\",\n \"/configure-notifications\": \"configure-notifications\",\n \"/psm\": \"psm\",\n \"/swe-bench\": \"swe-bench\",\n // v1.2 graph provider + spending skills\n \"graphify:\": \"graphify\",\n \"graphwiki:\": \"graphwiki\",\n \"graph:\": \"graph-provider\",\n \"spending:\": \"spending\",\n \"/graphify\": \"graphify\",\n \"/graphwiki\": \"graphwiki\",\n \"/graph-provider\": \"graph-provider\",\n \"/spending\": \"spending\",\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 // Case-sensitive check for : suffixed forms\n for (const [keyword, skillId] of Object.entries(KEYWORD_MAP)) {\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 = /^\\/([a-zA-Z]+)\\b/;\n const slashMatch = trimmed.match(slashPattern);\n if (slashMatch) {\n const cmd = slashMatch[1].toLowerCase();\n // Map /autopilot -> autopilot, /ralph -> ralph, etc.\n const skillId = KEYWORD_MAP[`${cmd}:`] || cmd;\n if (skillId !== cmd || Object.values(KEYWORD_MAP).includes(cmd)) {\n return {\n keyword: slashMatch[0],\n skillId,\n position: 0,\n };\n }\n }\n\n return null;\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 = `/oh-my-githubcopilot:${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)\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"],
5
- "mappings": "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",
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": ";AAeA,IAAM,cAAsC;AAAA,EAC1C,cAAc;AAAA,EACd,cAAc;AAAA,EACd,kBAAkB;AAAA,EAClB,UAAU;AAAA,EACV,UAAU;AAAA,EACV,cAAc;AAAA,EACd,QAAQ;AAAA,EACR,cAAc;AAAA,EACd,QAAQ;AAAA,EACR,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,kBAAkB;AAAA,EAClB,SAAS;AAAA,EACT,SAAS;AAAA,EACT,aAAa;AAAA,EACb,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,UAAU;AAAA,EACV,UAAU;AAAA,EACV,cAAc;AAAA,EACd,aAAa;AAAA,EACb,aAAa;AAAA,EACb,iBAAiB;AAAA,EACjB,mBAAmB;AAAA,EACnB,mBAAmB;AAAA,EACnB,uBAAuB;AAAA,EACvB,SAAS;AAAA,EACT,SAAS;AAAA,EACT,aAAa;AAAA,EACb,aAAa;AAAA,EACb,UAAU;AAAA,EACV,UAAU;AAAA,EACV,cAAc;AAAA,EACd,cAAc;AAAA,EACd,QAAQ;AAAA,EACR,cAAc;AAAA,EACd,QAAQ;AAAA,EACR,cAAc;AAAA,EACd,kBAAkB;AAAA,EAClB,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,SAAS;AAAA,EACT,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,SAAS;AAAA,EACT,SAAS;AAAA,EACT,aAAa;AAAA,EACb,UAAU;AAAA,EACV,UAAU;AAAA,EACV,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,4BAA4B;AAAA,EAC5B,4BAA4B;AAAA,EAC5B,gCAAgC;AAAA,EAChC,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,cAAc;AAAA,EACd,kBAAkB;AAAA,EAClB,aAAa;AAAA,EACb,eAAe;AAAA,EACf,eAAe;AAAA,EACf,cAAc;AAAA,EACd,UAAU;AAAA,EACV,aAAa;AAAA,EACb,aAAa;AAAA,EACb,iBAAiB;AAAA,EACjB,cAAc;AAAA,EACd,kBAAkB;AAAA,EAClB,mBAAmB;AAAA,EACnB,uBAAuB;AAAA,EACvB,aAAa;AAAA,EACb,iBAAiB;AAAA,EACjB,eAAe;AAAA,EACf,mBAAmB;AAAA,EACnB,gBAAgB;AAAA,EAChB,iBAAiB;AAAA,EACjB,WAAW;AAAA,EACX,WAAW;AAAA,EACX,eAAe;AAAA,EACf,cAAc;AAAA,EACd,cAAc;AAAA,EACd,kBAAkB;AAAA,EAClB,kBAAkB;AAAA,EAClB,kBAAkB;AAAA,EAClB,sBAAsB;AAAA,EACtB,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,gBAAgB;AAClB;AAEA,IAAM,kBAAkB,OAAO,QAAQ,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM;AAC1F,IAAM,wBAAgD;AAAA,EACpD,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,aAAa;AACf;AAkBA,SAAS,cAAc,QAAqC;AAC1D,QAAM,UAAU,OAAO,UAAU;AAGjC,aAAW,CAAC,SAAS,OAAO,KAAK,iBAAiB;AAChD,QAAI,QAAQ,WAAW,OAAO,GAAG;AAC/B,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA,UAAU;AAAA,MACZ;AAAA,IACF;AAAA,EACF;AAGA,QAAM,eAAe;AACrB,QAAM,aAAa,QAAQ,MAAM,YAAY;AAC7C,MAAI,YAAY;AACd,UAAM,MAAM,WAAW,CAAC,EAAE,YAAY;AACtC,UAAM,UAAU,YAAY,IAAI,GAAG,EAAE,KAAK,YAAY,GAAG,GAAG,GAAG;AAC/D,QAAI,SAAS;AACX,aAAO;AAAA,QACL,SAAS,WAAW,CAAC;AAAA,QACrB;AAAA,QACA,UAAU;AAAA,MACZ;AAAA,IACF;AAAA,EACF;AAIA,QAAM,uBAAuB;AAC7B,QAAM,qBAAqB,QAAQ,MAAM,oBAAoB;AAC7D,MAAI,oBAAoB;AACtB,UAAM,MAAM,mBAAmB,CAAC,EAAE,YAAY;AAC9C,UAAM,UACJ,YAAY,QAAQ,GAAG,EAAE,KACzB,YAAY,IAAI,GAAG,EAAE,KACrB,YAAY,GAAG,GAAG,GAAG;AACvB,QAAI,SAAS;AACX,aAAO;AAAA,QACL,SAAS,mBAAmB,CAAC;AAAA,QAC7B;AAAA,QACA,UAAU;AAAA,MACZ;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,oBAAoB,SAAyB;AACpD,SAAO,sBAAsB,OAAO,KAAK,QAAQ,OAAO;AAC1D;AAEO,SAAS,YAAY,OAA8B;AACxD,QAAM,QAAQ,KAAK,IAAI;AACvB,QAAM,MAAgB,CAAC;AAEvB,MAAI;AACF,QAAI,MAAM,cAAc,uBAAuB;AAC7C,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,WAAW,KAAK,IAAI,IAAI;AAAA,QACxB,WAAW,CAAC;AAAA,QACZ,KAAK,CAAC,gCAAgC;AAAA,MACxC;AAAA,IACF;AAEA,UAAM,QAAQ,cAAc,MAAM,MAAM;AACxC,QAAI,CAAC,OAAO;AACV,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,WAAW,KAAK,IAAI,IAAI;AAAA,QACxB,WAAW,CAAC;AAAA,QACZ,KAAK,CAAC;AAAA,MACR;AAAA,IACF;AAGA,UAAM,WAAW,MAAM,OAAO,MAAM,MAAM,WAAW,MAAM,QAAQ,MAAM,EAAE,KAAK;AAChF,UAAM,YAAY,GAAG,oBAAoB,MAAM,OAAO,CAAC,GAAG,WAAW,IAAI,QAAQ,KAAK,EAAE;AAExF,QAAI,KAAK,sBAAsB,MAAM,OAAO,mBAAc,MAAM,OAAO,EAAE;AACzE,QAAI,KAAK,eAAe,SAAS,GAAG;AAEpC,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,WAAW,KAAK,IAAI,IAAI;AAAA,MACxB,gBAAgB;AAAA,MAChB,WAAW;AAAA,QACT,EAAE,MAAM,YAAY,MAAM,MAAM,QAAQ;AAAA,QACxC,EAAE,MAAM,OAAO,OAAO,QAAQ,SAAS,oBAAoB,MAAM,OAAO,GAAG;AAAA,MAC7E;AAAA,MACA;AAAA,IACF;AAAA,EACF,SAAS,KAAK;AACZ,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,WAAW,KAAK,IAAI,IAAI;AAAA,MACxB,WAAW,CAAC;AAAA,MACZ,KAAK,CAAC,UAAU,GAAG,EAAE;AAAA,IACvB;AAAA,EACF;AACF;AAIA,IAAI,QAAQ,KAAK,CAAC,GAAG,SAAS,sBAAsB,KAAK,QAAQ,KAAK,CAAC,GAAG,SAAS,sBAAsB,GAAG;AAC1G,QAAM,QAAmB,KAAK,MAAM,MAAM,UAAU,CAAC;AACrD,QAAM,SAAS,YAAY,KAAK;AAChC,UAAQ,IAAI,KAAK,UAAU,MAAM,CAAC;AACpC;AAEA,eAAe,YAA6B;AAC1C,QAAM,SAAmB,CAAC;AAC1B,mBAAiB,SAAS,QAAQ,OAAO;AACvC,WAAO,KAAK,KAAK;AAAA,EACnB;AACA,SAAO,OAAO,KAAK,EAAE;AACvB;",
6
6
  "names": []
7
7
  }
@@ -2,7 +2,7 @@
2
2
  import { fileURLToPath } from "url";
3
3
  var TIER_RECOMMENDATIONS = {
4
4
  high: "model: claude-opus-4.6 or gpt-5 recommended for this task (architecture, security, critical decisions)",
5
- standard: "model: claude-sonnet-4.5 recommended for this task (standard implementation and review)",
5
+ standard: "model: claude-sonnet-4.6 recommended for this task (standard implementation and review)",
6
6
  fast: "model: gpt-5.4-mini or haiku recommended for quick lookups and formatting"
7
7
  };
8
8
  var DEFAULT_TIER = "standard";
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/hooks/model-router.mts"],
4
- "sourcesContent": ["/**\n * model-router hook\n * Trigger: pre-cycle (PreToolUse equivalent)\n * Priority: 80\n *\n * Reads agent frontmatter model_tier and adds advisory additionalContext.\n */\n\nexport interface HookInput {\n hook_type: \"PreToolUse\";\n tool_name?: string;\n agent_id?: string;\n session_id?: string;\n}\n\nexport interface HookOutput {\n decision?: \"allow\";\n additionalContext?: string;\n status: \"ok\" | \"skip\" | \"error\";\n latencyMs: number;\n mutations: Array<{ type: \"set_model\"; model: \"opus\" | \"sonnet\" | \"haiku\" }>;\n log: string[];\n}\n\n// Model tier recommendations \u2014 advisory only\nconst TIER_RECOMMENDATIONS: Record<string, string> = {\n high: \"model: claude-opus-4.6 or gpt-5 recommended for this task (architecture, security, critical decisions)\",\n standard: \"model: claude-sonnet-4.5 recommended for this task (standard implementation and review)\",\n fast: \"model: gpt-5.4-mini or haiku recommended for quick lookups and formatting\",\n};\n\n// Default if agent tier unknown\nconst DEFAULT_TIER = \"standard\";\n\nexport function processHook(input: HookInput): HookOutput {\n const start = Date.now();\n\n try {\n if (input.hook_type !== \"PreToolUse\") {\n return {\n status: \"skip\",\n latencyMs: Date.now() - start,\n mutations: [],\n log: [],\n };\n }\n\n const agentId = input.agent_id;\n if (!agentId) {\n return {\n status: \"ok\",\n latencyMs: Date.now() - start,\n mutations: [],\n log: [],\n };\n }\n\n // Agent tier is determined by agent frontmatter in the agent definition files.\n // This hook reads agent metadata from the session state or agent registry.\n // For now, we use a simple mapping based on known agent tiers.\n const agentTier = getAgentTier(agentId);\n const recommendation = TIER_RECOMMENDATIONS[agentTier] || TIER_RECOMMENDATIONS[DEFAULT_TIER];\n\n const mutations: HookOutput[\"mutations\"] = [\n { type: \"set_model\", model: agentTierToModel(agentTier) },\n ];\n\n return {\n status: \"ok\",\n latencyMs: Date.now() - start,\n additionalContext: recommendation,\n mutations,\n log: [`${agentId} \u2192 tier: ${agentTier} \u2192 ${agentTierToModel(agentTier)}`],\n };\n } catch (err) {\n return {\n status: \"error\",\n latencyMs: Date.now() - start,\n mutations: [],\n log: [`Error: ${err}`],\n };\n }\n}\n\nfunction getAgentTier(agentId: string): string {\n // Tier 1 \u2014 High\n if ([\"orchestrator\", \"architect\", \"planner\", \"reviewer-security\", \"critic\"].includes(agentId)) {\n return \"high\";\n }\n // Tier 3 \u2014 Fast\n if ([\"explorer\", \"writer\"].includes(agentId)) {\n return \"fast\";\n }\n // Tier 2 \u2014 Standard (default)\n return \"standard\";\n}\n\nfunction agentTierToModel(tier: string): \"opus\" | \"sonnet\" | \"haiku\" {\n if (tier === \"high\") return \"opus\";\n if (tier === \"fast\") return \"haiku\";\n return \"sonnet\";\n}\n\n// Main entry point \u2014 only runs when executed directly (not imported)\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"],
4
+ "sourcesContent": ["/**\n * model-router hook\n * Trigger: pre-cycle (PreToolUse equivalent)\n * Priority: 80\n *\n * Reads agent frontmatter model_tier and adds advisory additionalContext.\n */\n\nexport interface HookInput {\n hook_type: \"PreToolUse\";\n tool_name?: string;\n agent_id?: string;\n session_id?: string;\n}\n\nexport interface HookOutput {\n decision?: \"allow\";\n additionalContext?: string;\n status: \"ok\" | \"skip\" | \"error\";\n latencyMs: number;\n mutations: Array<{ type: \"set_model\"; model: \"opus\" | \"sonnet\" | \"haiku\" }>;\n log: string[];\n}\n\n// Model tier recommendations \u2014 advisory only\nconst TIER_RECOMMENDATIONS: Record<string, string> = {\n high: \"model: claude-opus-4.6 or gpt-5 recommended for this task (architecture, security, critical decisions)\",\n standard: \"model: claude-sonnet-4.6 recommended for this task (standard implementation and review)\",\n fast: \"model: gpt-5.4-mini or haiku recommended for quick lookups and formatting\",\n};\n\n// Default if agent tier unknown\nconst DEFAULT_TIER = \"standard\";\n\nexport function processHook(input: HookInput): HookOutput {\n const start = Date.now();\n\n try {\n if (input.hook_type !== \"PreToolUse\") {\n return {\n status: \"skip\",\n latencyMs: Date.now() - start,\n mutations: [],\n log: [],\n };\n }\n\n const agentId = input.agent_id;\n if (!agentId) {\n return {\n status: \"ok\",\n latencyMs: Date.now() - start,\n mutations: [],\n log: [],\n };\n }\n\n // Agent tier is determined by agent frontmatter in the agent definition files.\n // This hook reads agent metadata from the session state or agent registry.\n // For now, we use a simple mapping based on known agent tiers.\n const agentTier = getAgentTier(agentId);\n const recommendation = TIER_RECOMMENDATIONS[agentTier] || TIER_RECOMMENDATIONS[DEFAULT_TIER];\n\n const mutations: HookOutput[\"mutations\"] = [\n { type: \"set_model\", model: agentTierToModel(agentTier) },\n ];\n\n return {\n status: \"ok\",\n latencyMs: Date.now() - start,\n additionalContext: recommendation,\n mutations,\n log: [`${agentId} \u2192 tier: ${agentTier} \u2192 ${agentTierToModel(agentTier)}`],\n };\n } catch (err) {\n return {\n status: \"error\",\n latencyMs: Date.now() - start,\n mutations: [],\n log: [`Error: ${err}`],\n };\n }\n}\n\nfunction getAgentTier(agentId: string): string {\n // Tier 1 \u2014 High\n if ([\"orchestrator\", \"architect\", \"planner\", \"reviewer-security\", \"critic\"].includes(agentId)) {\n return \"high\";\n }\n // Tier 3 \u2014 Fast\n if ([\"explorer\", \"writer\"].includes(agentId)) {\n return \"fast\";\n }\n // Tier 2 \u2014 Standard (default)\n return \"standard\";\n}\n\nfunction agentTierToModel(tier: string): \"opus\" | \"sonnet\" | \"haiku\" {\n if (tier === \"high\") return \"opus\";\n if (tier === \"fast\") return \"haiku\";\n return \"sonnet\";\n}\n\n// Main entry point \u2014 only runs when executed directly (not imported)\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"],
5
5
  "mappings": ";AAwGA,SAAS,qBAAqB;AA/E9B,IAAM,uBAA+C;AAAA,EACnD,MAAM;AAAA,EACN,UAAU;AAAA,EACV,MAAM;AACR;AAGA,IAAM,eAAe;AAEd,SAAS,YAAY,OAA8B;AACxD,QAAM,QAAQ,KAAK,IAAI;AAEvB,MAAI;AACF,QAAI,MAAM,cAAc,cAAc;AACpC,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,WAAW,KAAK,IAAI,IAAI;AAAA,QACxB,WAAW,CAAC;AAAA,QACZ,KAAK,CAAC;AAAA,MACR;AAAA,IACF;AAEA,UAAM,UAAU,MAAM;AACtB,QAAI,CAAC,SAAS;AACZ,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,WAAW,KAAK,IAAI,IAAI;AAAA,QACxB,WAAW,CAAC;AAAA,QACZ,KAAK,CAAC;AAAA,MACR;AAAA,IACF;AAKA,UAAM,YAAY,aAAa,OAAO;AACtC,UAAM,iBAAiB,qBAAqB,SAAS,KAAK,qBAAqB,YAAY;AAE3F,UAAM,YAAqC;AAAA,MACzC,EAAE,MAAM,aAAa,OAAO,iBAAiB,SAAS,EAAE;AAAA,IAC1D;AAEA,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,WAAW,KAAK,IAAI,IAAI;AAAA,MACxB,mBAAmB;AAAA,MACnB;AAAA,MACA,KAAK,CAAC,GAAG,OAAO,iBAAY,SAAS,WAAM,iBAAiB,SAAS,CAAC,EAAE;AAAA,IAC1E;AAAA,EACF,SAAS,KAAK;AACZ,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,WAAW,KAAK,IAAI,IAAI;AAAA,MACxB,WAAW,CAAC;AAAA,MACZ,KAAK,CAAC,UAAU,GAAG,EAAE;AAAA,IACvB;AAAA,EACF;AACF;AAEA,SAAS,aAAa,SAAyB;AAE7C,MAAI,CAAC,gBAAgB,aAAa,WAAW,qBAAqB,QAAQ,EAAE,SAAS,OAAO,GAAG;AAC7F,WAAO;AAAA,EACT;AAEA,MAAI,CAAC,YAAY,QAAQ,EAAE,SAAS,OAAO,GAAG;AAC5C,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAEA,SAAS,iBAAiB,MAA2C;AACnE,MAAI,SAAS,OAAQ,QAAO;AAC5B,MAAI,SAAS,OAAQ,QAAO;AAC5B,SAAO;AACT;AAKA,IAAI,QAAQ,KAAK,CAAC,MAAM,cAAc,YAAY,GAAG,GAAG;AACtD,QAAM,QAAmB,KAAK,MAAM,MAAM,UAAU,CAAC;AACrD,QAAM,SAAS,YAAY,KAAK;AAChC,UAAQ,IAAI,KAAK,UAAU,MAAM,CAAC;AACpC;AAEA,eAAe,YAA6B;AAC1C,QAAM,SAAmB,CAAC;AAC1B,mBAAiB,SAAS,QAAQ,OAAO;AACvC,WAAO,KAAK,KAAK;AAAA,EACnB;AACA,SAAO,OAAO,KAAK,EAAE;AACvB;",
6
6
  "names": []
7
7
  }
@@ -42,7 +42,7 @@ function processHook(input) {
42
42
  mutations: [
43
43
  {
44
44
  type: "stop",
45
- reason: `${reason} Use /oh-my-githubcopilot:cancel to end it, or continue the session to keep going.`
45
+ reason: `${reason} Use /cancel to end it, or continue the session to keep going.`
46
46
  },
47
47
  { type: "log", level: "info", message: reason }
48
48
  ],
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/hooks/stop-continuation.mts"],
4
- "sourcesContent": ["/**\n * stop-continuation hook\n * Trigger: post-message (SessionEnd equivalent)\n * Priority: 50\n *\n * Detects active persistent modes (ralph, ultrawork, team) and\n * returns continue instructions so the user can decide whether\n * to keep going.\n */\n\nimport { readFileSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { join } from \"path\";\n\nexport interface HookInput {\n hook_type: \"SessionEnd\";\n session_id?: string;\n message?: string;\n}\n\nexport interface HookOutput {\n modifiedResult?: unknown;\n status: \"ok\" | \"skip\" | \"error\";\n latencyMs: number;\n mutations: Array<{ type: \"stop\"; reason: string } | { type: \"log\"; level: \"info\"; message: string }>;\n log: string[];\n}\n\ninterface ModeState {\n active?: boolean;\n mode?: string;\n linked_ultrawork?: boolean;\n linked_team?: boolean;\n}\n\nfunction getModeStatePath(mode: string, sessionId?: string): string {\n const base = join(homedir(), \".omp\", \"state\");\n if (sessionId) {\n return join(base, \"sessions\", sessionId, `${mode}-state.json`);\n }\n return join(base, `${mode}-state.json`);\n}\n\nfunction readModeState(mode: string, sessionId?: string): ModeState | null {\n try {\n const path = getModeStatePath(mode, sessionId);\n return JSON.parse(readFileSync(path, \"utf-8\"));\n } catch {\n return null;\n }\n}\n\n// Priority order for checking: team > ralph > ultrawork\nconst PERSISTENT_MODES = [\"team\", \"ralph\", \"ultrawork\"];\n\nexport function processHook(input: HookInput): HookOutput {\n const start = Date.now();\n const log: string[] = [];\n\n try {\n if (input.hook_type !== \"SessionEnd\") {\n return {\n status: \"skip\",\n latencyMs: Date.now() - start,\n mutations: [],\n log: [],\n };\n }\n\n // Check for active persistent modes\n for (const mode of PERSISTENT_MODES) {\n const state = readModeState(mode, input.session_id);\n if (state?.active) {\n const reason = `${mode} mode is still active.`;\n log.push(`Stop continuation: ${reason}`);\n\n return {\n status: \"ok\",\n latencyMs: Date.now() - start,\n mutations: [\n {\n type: \"stop\",\n reason: `${reason} Use /oh-my-githubcopilot:cancel to end it, or continue the session to keep going.`,\n },\n { type: \"log\", level: \"info\", message: reason },\n ],\n log,\n };\n }\n }\n\n return {\n status: \"ok\",\n latencyMs: Date.now() - start,\n mutations: [],\n log: [\"No persistent modes active\"],\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)\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"],
4
+ "sourcesContent": ["/**\n * stop-continuation hook\n * Trigger: post-message (SessionEnd equivalent)\n * Priority: 50\n *\n * Detects active persistent modes (ralph, ultrawork, team) and\n * returns continue instructions so the user can decide whether\n * to keep going.\n */\n\nimport { readFileSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { join } from \"path\";\n\nexport interface HookInput {\n hook_type: \"SessionEnd\";\n session_id?: string;\n message?: string;\n}\n\nexport interface HookOutput {\n modifiedResult?: unknown;\n status: \"ok\" | \"skip\" | \"error\";\n latencyMs: number;\n mutations: Array<{ type: \"stop\"; reason: string } | { type: \"log\"; level: \"info\"; message: string }>;\n log: string[];\n}\n\ninterface ModeState {\n active?: boolean;\n mode?: string;\n linked_ultrawork?: boolean;\n linked_team?: boolean;\n}\n\nfunction getModeStatePath(mode: string, sessionId?: string): string {\n const base = join(homedir(), \".omp\", \"state\");\n if (sessionId) {\n return join(base, \"sessions\", sessionId, `${mode}-state.json`);\n }\n return join(base, `${mode}-state.json`);\n}\n\nfunction readModeState(mode: string, sessionId?: string): ModeState | null {\n try {\n const path = getModeStatePath(mode, sessionId);\n return JSON.parse(readFileSync(path, \"utf-8\"));\n } catch {\n return null;\n }\n}\n\n// Priority order for checking: team > ralph > ultrawork\nconst PERSISTENT_MODES = [\"team\", \"ralph\", \"ultrawork\"];\n\nexport function processHook(input: HookInput): HookOutput {\n const start = Date.now();\n const log: string[] = [];\n\n try {\n if (input.hook_type !== \"SessionEnd\") {\n return {\n status: \"skip\",\n latencyMs: Date.now() - start,\n mutations: [],\n log: [],\n };\n }\n\n // Check for active persistent modes\n for (const mode of PERSISTENT_MODES) {\n const state = readModeState(mode, input.session_id);\n if (state?.active) {\n const reason = `${mode} mode is still active.`;\n log.push(`Stop continuation: ${reason}`);\n\n return {\n status: \"ok\",\n latencyMs: Date.now() - start,\n mutations: [\n {\n type: \"stop\",\n reason: `${reason} Use /cancel to end it, or continue the session to keep going.`,\n },\n { type: \"log\", level: \"info\", message: reason },\n ],\n log,\n };\n }\n }\n\n return {\n status: \"ok\",\n latencyMs: Date.now() - start,\n mutations: [],\n log: [\"No persistent modes active\"],\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)\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"],
5
5
  "mappings": ";AAUA,SAAS,oBAAoB;AAC7B,SAAS,eAAe;AACxB,SAAS,YAAY;AAgGrB,SAAS,qBAAqB;AAzE9B,SAAS,iBAAiB,MAAc,WAA4B;AAClE,QAAM,OAAO,KAAK,QAAQ,GAAG,QAAQ,OAAO;AAC5C,MAAI,WAAW;AACb,WAAO,KAAK,MAAM,YAAY,WAAW,GAAG,IAAI,aAAa;AAAA,EAC/D;AACA,SAAO,KAAK,MAAM,GAAG,IAAI,aAAa;AACxC;AAEA,SAAS,cAAc,MAAc,WAAsC;AACzE,MAAI;AACF,UAAM,OAAO,iBAAiB,MAAM,SAAS;AAC7C,WAAO,KAAK,MAAM,aAAa,MAAM,OAAO,CAAC;AAAA,EAC/C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,IAAM,mBAAmB,CAAC,QAAQ,SAAS,WAAW;AAE/C,SAAS,YAAY,OAA8B;AACxD,QAAM,QAAQ,KAAK,IAAI;AACvB,QAAM,MAAgB,CAAC;AAEvB,MAAI;AACF,QAAI,MAAM,cAAc,cAAc;AACpC,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,WAAW,KAAK,IAAI,IAAI;AAAA,QACxB,WAAW,CAAC;AAAA,QACZ,KAAK,CAAC;AAAA,MACR;AAAA,IACF;AAGA,eAAW,QAAQ,kBAAkB;AACnC,YAAM,QAAQ,cAAc,MAAM,MAAM,UAAU;AAClD,UAAI,OAAO,QAAQ;AACjB,cAAM,SAAS,GAAG,IAAI;AACtB,YAAI,KAAK,sBAAsB,MAAM,EAAE;AAEvC,eAAO;AAAA,UACL,QAAQ;AAAA,UACR,WAAW,KAAK,IAAI,IAAI;AAAA,UACxB,WAAW;AAAA,YACT;AAAA,cACE,MAAM;AAAA,cACN,QAAQ,GAAG,MAAM;AAAA,YACnB;AAAA,YACA,EAAE,MAAM,OAAO,OAAO,QAAQ,SAAS,OAAO;AAAA,UAChD;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,WAAW,KAAK,IAAI,IAAI;AAAA,MACxB,WAAW,CAAC;AAAA,MACZ,KAAK,CAAC,4BAA4B;AAAA,IACpC;AAAA,EACF,SAAS,KAAK;AACZ,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,WAAW,KAAK,IAAI,IAAI;AAAA,MACxB,WAAW,CAAC;AAAA,MACZ,KAAK,CAAC,UAAU,GAAG,EAAE;AAAA,IACvB;AAAA,EACF;AACF;AAKA,IAAI,QAAQ,KAAK,CAAC,MAAM,cAAc,YAAY,GAAG,GAAG;AACtD,QAAM,QAAmB,KAAK,MAAM,MAAM,UAAU,CAAC;AACrD,QAAM,SAAS,YAAY,KAAK;AAChC,UAAQ,IAAI,KAAK,UAAU,MAAM,CAAC;AACpC;AAEA,eAAe,YAA6B;AAC1C,QAAM,SAAmB,CAAC;AAC1B,mBAAiB,SAAS,QAAQ,OAAO;AACvC,WAAO,KAAK,KAAK;AAAA,EACnB;AACA,SAAO,OAAO,KAAK,EAAE;AACvB;",
6
6
  "names": []
7
7
  }
@@ -111,9 +111,10 @@ function processHook(input) {
111
111
  try {
112
112
  state = JSON.parse(readFileSync2(statePath, "utf-8"));
113
113
  } catch {
114
+ const fallbackModel = input.model ?? "default";
114
115
  state = {
115
116
  tokens_estimated: 0,
116
- token_budget: 2e5,
117
+ token_budget: MODEL_CONTEXTS[fallbackModel] ?? MODEL_CONTEXTS["default"] ?? 2e5,
117
118
  context_pct: 0,
118
119
  warnings_issued: /* @__PURE__ */ new Set()
119
120
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/hooks/token-tracker.mts", "../../src/spending/tracker.mts"],
4
- "sourcesContent": ["/**\n * token-tracker hook\n * Trigger: post-message (PostToolUse equivalent)\n * Priority: 70\n *\n * Estimates token usage from character counts (1 token \u2248 4 chars).\n * Accumulates in session state. Warns at 60%, 80%, 90% thresholds.\n */\n\nimport { readFileSync, writeFileSync, mkdirSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { join } from \"path\";\nimport { incrementSpending } from \"../spending/tracker.mjs\";\n\nexport interface HookInput {\n hook_type: \"PostToolUse\";\n tool_name?: string;\n tool_input?: unknown;\n tool_output?: unknown;\n session_id?: string;\n}\n\nexport interface HookOutput {\n modifiedResult?: unknown;\n status: \"ok\" | \"skip\" | \"error\";\n latencyMs: number;\n mutations: Array<{ type: \"set_token_budget\"; budget: number } | { type: \"emit_hud\"; hudEmit: unknown } | { type: \"log\"; level: \"info\" | \"warn\" | \"error\"; message: string }>;\n log: string[];\n}\n\ninterface SessionState {\n tokens_estimated: number;\n token_budget: number;\n context_pct: number;\n warnings_issued: Set<string>;\n}\n\n// Model context windows in tokens (for future model-specific budget lookup)\n// Exported for potential external use\nexport const MODEL_CONTEXTS = {\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\nconst WARNING_THRESHOLDS = [60, 80, 90];\n\nexport function estimateTokens(input: unknown): number {\n if (!input) return 0;\n try {\n const str = typeof input === \"string\" ? input : JSON.stringify(input);\n return Math.ceil(str.length / 4);\n } catch {\n return 0;\n }\n}\n\nfunction getStatePath(sessionId?: string): string {\n const base = join(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\nexport function processHook(input: HookInput): HookOutput {\n const start = Date.now();\n const log: string[] = [];\n\n try {\n if (input.hook_type !== \"PostToolUse\") {\n return {\n status: \"skip\",\n latencyMs: Date.now() - start,\n mutations: [],\n log: [],\n };\n }\n\n const statePath = getStatePath(input.session_id);\n let state: SessionState;\n\n try {\n state = JSON.parse(readFileSync(statePath, \"utf-8\"));\n } catch {\n // Initialize state if not found\n state = {\n tokens_estimated: 0,\n token_budget: 200_000,\n context_pct: 0,\n warnings_issued: new Set(),\n };\n }\n\n const inputTokens = estimateTokens(input.tool_input);\n const outputTokens = estimateTokens(input.tool_output);\n const delta = inputTokens + outputTokens;\n\n state.tokens_estimated += delta;\n state.context_pct = Math.min(100, Math.round((state.tokens_estimated / state.token_budget) * 100));\n\n const mutations: HookOutput[\"mutations\"] = [\n { type: \"set_token_budget\", budget: state.token_budget },\n ];\n\n // Check warning thresholds\n for (const threshold of WARNING_THRESHOLDS) {\n const key = `warn_${threshold}`;\n if (state.context_pct >= threshold && !state.warnings_issued.has(key)) {\n state.warnings_issued.add(key);\n const message =\n threshold >= 90\n ? `CRITICAL: Context at ${state.context_pct}%. Tokens near budget limit.`\n : threshold >= 80\n ? `WARNING: Context at ${state.context_pct}%. Consider enabling ecomode.`\n : `INFO: Context at ${state.context_pct}%.`;\n mutations.push({ type: \"log\", level: threshold >= 80 ? \"warn\" : \"info\", message });\n log.push(message);\n }\n }\n\n // Write state back\n try {\n ensureDir(statePath);\n writeFileSync(statePath, JSON.stringify(state), \"utf-8\");\n } catch (e) {\n log.push(`Failed to write state: ${e}`);\n }\n\n // Track premium request spending\n const sessionId = input.session_id ?? `omp-${Date.now()}`;\n try { incrementSpending(sessionId); } catch { /* non-blocking */ }\n\n return {\n status: \"ok\",\n latencyMs: Date.now() - start,\n mutations,\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)\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 * Spending tracker for OMP.\n * Tracks premium API requests per session and per calendar month.\n * Persists to ~/.omp/state/spending-monthly.json\n *\n * // v1.1 known limitation: no /omp:spending reset command. To reset monthly counter manually: rm ~/.omp/state/spending-monthly.json\n */\n\nimport { readFileSync, writeFileSync, mkdirSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { join, dirname } from \"path\";\nimport type { SpendingState } from \"./types.mjs\";\n\nconst SPENDING_PATH = join(homedir(), \".omp\", \"state\", \"spending-monthly.json\");\n\nfunction currentMonth(): string {\n const now = new Date();\n return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, \"0\")}`;\n}\n\nexport function loadSpending(sessionId: string): SpendingState {\n let raw: SpendingState;\n try {\n raw = JSON.parse(readFileSync(SPENDING_PATH, \"utf-8\")) as SpendingState;\n } catch {\n // Missing or malformed file \u2014 start fresh\n return {\n version: 1,\n sessionId,\n sessionPremiumRequests: 0,\n month: currentMonth(),\n monthlyPremiumRequests: 0,\n };\n }\n\n const month = currentMonth();\n\n // Reset monthly counter when month rolls over\n if (raw.month !== month) {\n return {\n version: 1,\n sessionId,\n sessionPremiumRequests: 0,\n month,\n monthlyPremiumRequests: 0,\n };\n }\n\n // Reset session counter when session changes\n if (raw.sessionId !== sessionId) {\n return {\n version: 1,\n sessionId,\n sessionPremiumRequests: 0,\n month,\n monthlyPremiumRequests: raw.monthlyPremiumRequests,\n };\n }\n\n return { ...raw, version: 1 };\n}\n\nexport function saveSpending(state: SpendingState): void {\n try {\n mkdirSync(dirname(SPENDING_PATH), { recursive: true });\n writeFileSync(SPENDING_PATH, JSON.stringify(state, null, 2), \"utf-8\");\n } catch (e) {\n console.warn(`[OMP] spending: failed to save state: ${e}`);\n }\n}\n\nexport function incrementSpending(sessionId: string): SpendingState {\n const state = loadSpending(sessionId);\n state.sessionPremiumRequests += 1;\n state.monthlyPremiumRequests += 1;\n saveSpending(state);\n return state;\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * token-tracker hook\n * Trigger: post-message (PostToolUse equivalent)\n * Priority: 70\n *\n * Estimates token usage from character counts (1 token \u2248 4 chars).\n * Accumulates in session state. Warns at 60%, 80%, 90% thresholds.\n */\n\nimport { readFileSync, writeFileSync, mkdirSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { join } from \"path\";\nimport { incrementSpending } from \"../spending/tracker.mjs\";\n\nexport interface HookInput {\n hook_type: \"PostToolUse\";\n tool_name?: string;\n tool_input?: unknown;\n tool_output?: unknown;\n session_id?: string;\n}\n\nexport interface HookOutput {\n modifiedResult?: unknown;\n status: \"ok\" | \"skip\" | \"error\";\n latencyMs: number;\n mutations: Array<{ type: \"set_token_budget\"; budget: number } | { type: \"emit_hud\"; hudEmit: unknown } | { type: \"log\"; level: \"info\" | \"warn\" | \"error\"; message: string }>;\n log: string[];\n}\n\ninterface SessionState {\n tokens_estimated: number;\n token_budget: number;\n context_pct: number;\n warnings_issued: Set<string>;\n}\n\n// Model context windows in tokens (for future model-specific budget lookup)\n// Exported for potential external use\nexport const MODEL_CONTEXTS = {\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\nconst WARNING_THRESHOLDS = [60, 80, 90];\n\nexport function estimateTokens(input: unknown): number {\n if (!input) return 0;\n try {\n const str = typeof input === \"string\" ? input : JSON.stringify(input);\n return Math.ceil(str.length / 4);\n } catch {\n return 0;\n }\n}\n\nfunction getStatePath(sessionId?: string): string {\n const base = join(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\nexport function processHook(input: HookInput): HookOutput {\n const start = Date.now();\n const log: string[] = [];\n\n try {\n if (input.hook_type !== \"PostToolUse\") {\n return {\n status: \"skip\",\n latencyMs: Date.now() - start,\n mutations: [],\n log: [],\n };\n }\n\n const statePath = getStatePath(input.session_id);\n let state: SessionState;\n\n try {\n state = JSON.parse(readFileSync(statePath, \"utf-8\"));\n } catch {\n // Initialize state if not found \u2014 budget derived from model when available\n const fallbackModel = (input as { model?: string }).model ?? \"default\";\n state = {\n tokens_estimated: 0,\n token_budget: (MODEL_CONTEXTS as Record<string, number>)[fallbackModel] ?? MODEL_CONTEXTS[\"default\"] ?? 200_000,\n context_pct: 0,\n warnings_issued: new Set(),\n };\n }\n\n const inputTokens = estimateTokens(input.tool_input);\n const outputTokens = estimateTokens(input.tool_output);\n const delta = inputTokens + outputTokens;\n\n state.tokens_estimated += delta;\n state.context_pct = Math.min(100, Math.round((state.tokens_estimated / state.token_budget) * 100));\n\n const mutations: HookOutput[\"mutations\"] = [\n { type: \"set_token_budget\", budget: state.token_budget },\n ];\n\n // Check warning thresholds\n for (const threshold of WARNING_THRESHOLDS) {\n const key = `warn_${threshold}`;\n if (state.context_pct >= threshold && !state.warnings_issued.has(key)) {\n state.warnings_issued.add(key);\n const message =\n threshold >= 90\n ? `CRITICAL: Context at ${state.context_pct}%. Tokens near budget limit.`\n : threshold >= 80\n ? `WARNING: Context at ${state.context_pct}%. Consider enabling ecomode.`\n : `INFO: Context at ${state.context_pct}%.`;\n mutations.push({ type: \"log\", level: threshold >= 80 ? \"warn\" : \"info\", message });\n log.push(message);\n }\n }\n\n // Write state back\n try {\n ensureDir(statePath);\n writeFileSync(statePath, JSON.stringify(state), \"utf-8\");\n } catch (e) {\n log.push(`Failed to write state: ${e}`);\n }\n\n // Track premium request spending\n const sessionId = input.session_id ?? `omp-${Date.now()}`;\n try { incrementSpending(sessionId); } catch { /* non-blocking */ }\n\n return {\n status: \"ok\",\n latencyMs: Date.now() - start,\n mutations,\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)\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 * Spending tracker for OMP.\n * Tracks premium API requests per session and per calendar month.\n * Persists to ~/.omp/state/spending-monthly.json\n *\n * // v1.1 known limitation: no /omp:spending reset command. To reset monthly counter manually: rm ~/.omp/state/spending-monthly.json\n */\n\nimport { readFileSync, writeFileSync, mkdirSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { join, dirname } from \"path\";\nimport type { SpendingState } from \"./types.mjs\";\n\nconst SPENDING_PATH = join(homedir(), \".omp\", \"state\", \"spending-monthly.json\");\n\nfunction currentMonth(): string {\n const now = new Date();\n return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, \"0\")}`;\n}\n\nexport function loadSpending(sessionId: string): SpendingState {\n let raw: SpendingState;\n try {\n raw = JSON.parse(readFileSync(SPENDING_PATH, \"utf-8\")) as SpendingState;\n } catch {\n // Missing or malformed file \u2014 start fresh\n return {\n version: 1,\n sessionId,\n sessionPremiumRequests: 0,\n month: currentMonth(),\n monthlyPremiumRequests: 0,\n };\n }\n\n const month = currentMonth();\n\n // Reset monthly counter when month rolls over\n if (raw.month !== month) {\n return {\n version: 1,\n sessionId,\n sessionPremiumRequests: 0,\n month,\n monthlyPremiumRequests: 0,\n };\n }\n\n // Reset session counter when session changes\n if (raw.sessionId !== sessionId) {\n return {\n version: 1,\n sessionId,\n sessionPremiumRequests: 0,\n month,\n monthlyPremiumRequests: raw.monthlyPremiumRequests,\n };\n }\n\n return { ...raw, version: 1 };\n}\n\nexport function saveSpending(state: SpendingState): void {\n try {\n mkdirSync(dirname(SPENDING_PATH), { recursive: true });\n writeFileSync(SPENDING_PATH, JSON.stringify(state, null, 2), \"utf-8\");\n } catch (e) {\n console.warn(`[OMP] spending: failed to save state: ${e}`);\n }\n}\n\nexport function incrementSpending(sessionId: string): SpendingState {\n const state = loadSpending(sessionId);\n state.sessionPremiumRequests += 1;\n state.monthlyPremiumRequests += 1;\n saveSpending(state);\n return state;\n}\n"],
5
+ "mappings": ";AASA,SAAS,gBAAAA,eAAc,iBAAAC,gBAAe,aAAAC,kBAAiB;AACvD,SAAS,WAAAC,gBAAe;AACxB,SAAS,QAAAC,aAAY;;;ACHrB,SAAS,cAAc,eAAe,iBAAiB;AACvD,SAAS,eAAe;AACxB,SAAS,MAAM,eAAe;AAG9B,IAAM,gBAAgB,KAAK,QAAQ,GAAG,QAAQ,SAAS,uBAAuB;AAE9E,SAAS,eAAuB;AAC9B,QAAM,MAAM,oBAAI,KAAK;AACrB,SAAO,GAAG,IAAI,YAAY,CAAC,IAAI,OAAO,IAAI,SAAS,IAAI,CAAC,EAAE,SAAS,GAAG,GAAG,CAAC;AAC5E;AAEO,SAAS,aAAa,WAAkC;AAC7D,MAAI;AACJ,MAAI;AACF,UAAM,KAAK,MAAM,aAAa,eAAe,OAAO,CAAC;AAAA,EACvD,QAAQ;AAEN,WAAO;AAAA,MACL,SAAS;AAAA,MACT;AAAA,MACA,wBAAwB;AAAA,MACxB,OAAO,aAAa;AAAA,MACpB,wBAAwB;AAAA,IAC1B;AAAA,EACF;AAEA,QAAM,QAAQ,aAAa;AAG3B,MAAI,IAAI,UAAU,OAAO;AACvB,WAAO;AAAA,MACL,SAAS;AAAA,MACT;AAAA,MACA,wBAAwB;AAAA,MACxB;AAAA,MACA,wBAAwB;AAAA,IAC1B;AAAA,EACF;AAGA,MAAI,IAAI,cAAc,WAAW;AAC/B,WAAO;AAAA,MACL,SAAS;AAAA,MACT;AAAA,MACA,wBAAwB;AAAA,MACxB;AAAA,MACA,wBAAwB,IAAI;AAAA,IAC9B;AAAA,EACF;AAEA,SAAO,EAAE,GAAG,KAAK,SAAS,EAAE;AAC9B;AAEO,SAAS,aAAa,OAA4B;AACvD,MAAI;AACF,cAAU,QAAQ,aAAa,GAAG,EAAE,WAAW,KAAK,CAAC;AACrD,kBAAc,eAAe,KAAK,UAAU,OAAO,MAAM,CAAC,GAAG,OAAO;AAAA,EACtE,SAAS,GAAG;AACV,YAAQ,KAAK,yCAAyC,CAAC,EAAE;AAAA,EAC3D;AACF;AAEO,SAAS,kBAAkB,WAAkC;AAClE,QAAM,QAAQ,aAAa,SAAS;AACpC,QAAM,0BAA0B;AAChC,QAAM,0BAA0B;AAChC,eAAa,KAAK;AAClB,SAAO;AACT;;;ADmFA,SAAS,qBAAqB;AAzHvB,IAAM,iBAAiB;AAAA,EAC5B,qBAAqB;AAAA,EACrB,mBAAmB;AAAA,EACnB,qBAAqB;AAAA,EACrB,mBAAmB;AAAA,EACnB,SAAS;AAAA,EACT,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,SAAS;AACX;AAEA,IAAM,qBAAqB,CAAC,IAAI,IAAI,EAAE;AAE/B,SAAS,eAAe,OAAwB;AACrD,MAAI,CAAC,MAAO,QAAO;AACnB,MAAI;AACF,UAAM,MAAM,OAAO,UAAU,WAAW,QAAQ,KAAK,UAAU,KAAK;AACpE,WAAO,KAAK,KAAK,IAAI,SAAS,CAAC;AAAA,EACjC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,aAAa,WAA4B;AAChD,QAAM,OAAOC,MAAKC,SAAQ,GAAG,QAAQ,OAAO;AAC5C,MAAI,WAAW;AACb,WAAOD,MAAK,MAAM,YAAY,WAAW,cAAc;AAAA,EACzD;AACA,SAAOA,MAAK,MAAM,cAAc;AAClC;AAEA,SAAS,UAAU,MAAoB;AACrC,EAAAE,WAAU,KAAK,UAAU,GAAG,KAAK,YAAY,GAAG,CAAC,GAAG,EAAE,WAAW,KAAK,CAAC;AACzE;AAEO,SAAS,YAAY,OAA8B;AACxD,QAAM,QAAQ,KAAK,IAAI;AACvB,QAAM,MAAgB,CAAC;AAEvB,MAAI;AACF,QAAI,MAAM,cAAc,eAAe;AACrC,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,WAAW,KAAK,IAAI,IAAI;AAAA,QACxB,WAAW,CAAC;AAAA,QACZ,KAAK,CAAC;AAAA,MACR;AAAA,IACF;AAEA,UAAM,YAAY,aAAa,MAAM,UAAU;AAC/C,QAAI;AAEJ,QAAI;AACF,cAAQ,KAAK,MAAMC,cAAa,WAAW,OAAO,CAAC;AAAA,IACrD,QAAQ;AAEN,YAAM,gBAAiB,MAA6B,SAAS;AAC7D,cAAQ;AAAA,QACN,kBAAkB;AAAA,QAClB,cAAe,eAA0C,aAAa,KAAK,eAAe,SAAS,KAAK;AAAA,QACxG,aAAa;AAAA,QACb,iBAAiB,oBAAI,IAAI;AAAA,MAC3B;AAAA,IACF;AAEA,UAAM,cAAc,eAAe,MAAM,UAAU;AACnD,UAAM,eAAe,eAAe,MAAM,WAAW;AACrD,UAAM,QAAQ,cAAc;AAE5B,UAAM,oBAAoB;AAC1B,UAAM,cAAc,KAAK,IAAI,KAAK,KAAK,MAAO,MAAM,mBAAmB,MAAM,eAAgB,GAAG,CAAC;AAEjG,UAAM,YAAqC;AAAA,MACzC,EAAE,MAAM,oBAAoB,QAAQ,MAAM,aAAa;AAAA,IACzD;AAGA,eAAW,aAAa,oBAAoB;AAC1C,YAAM,MAAM,QAAQ,SAAS;AAC7B,UAAI,MAAM,eAAe,aAAa,CAAC,MAAM,gBAAgB,IAAI,GAAG,GAAG;AACrE,cAAM,gBAAgB,IAAI,GAAG;AAC7B,cAAM,UACJ,aAAa,KACT,wBAAwB,MAAM,WAAW,iCACzC,aAAa,KACb,uBAAuB,MAAM,WAAW,kCACxC,oBAAoB,MAAM,WAAW;AAC3C,kBAAU,KAAK,EAAE,MAAM,OAAO,OAAO,aAAa,KAAK,SAAS,QAAQ,QAAQ,CAAC;AACjF,YAAI,KAAK,OAAO;AAAA,MAClB;AAAA,IACF;AAGA,QAAI;AACF,gBAAU,SAAS;AACnB,MAAAC,eAAc,WAAW,KAAK,UAAU,KAAK,GAAG,OAAO;AAAA,IACzD,SAAS,GAAG;AACV,UAAI,KAAK,0BAA0B,CAAC,EAAE;AAAA,IACxC;AAGA,UAAM,YAAY,MAAM,cAAc,OAAO,KAAK,IAAI,CAAC;AACvD,QAAI;AAAE,wBAAkB,SAAS;AAAA,IAAG,QAAQ;AAAA,IAAqB;AAEjE,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,WAAW,KAAK,IAAI,IAAI;AAAA,MACxB;AAAA,MACA;AAAA,IACF;AAAA,EACF,SAAS,KAAK;AACZ,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,WAAW,KAAK,IAAI,IAAI;AAAA,MACxB,WAAW,CAAC;AAAA,MACZ,KAAK,CAAC,UAAU,GAAG,EAAE;AAAA,IACvB;AAAA,EACF;AACF;AAKA,IAAI,QAAQ,KAAK,CAAC,MAAM,cAAc,YAAY,GAAG,GAAG;AACtD,QAAM,QAAmB,KAAK,MAAM,MAAM,UAAU,CAAC;AACrD,QAAM,SAAS,YAAY,KAAK;AAChC,UAAQ,IAAI,KAAK,UAAU,MAAM,CAAC;AACpC;AAEA,eAAe,YAA6B;AAC1C,QAAM,SAAmB,CAAC;AAC1B,mBAAiB,SAAS,QAAQ,OAAO;AACvC,WAAO,KAAK,KAAK;AAAA,EACnB;AACA,SAAO,OAAO,KAAK,EAAE;AACvB;",
6
6
  "names": ["readFileSync", "writeFileSync", "mkdirSync", "homedir", "join", "join", "homedir", "mkdirSync", "readFileSync", "writeFileSync"]
7
7
  }