982fb4cd5a6ef3ddcdcbed467b95a35dbc2e0d51

Author
TheEdgeOfRage <git@theedgeofrage.com>
Committer
TheEdgeOfRage <git@theedgeofrage.com>
Date

Message

Add entrypoint

Diff

This diff is truncated to protect this page.

  1diff --git a/src/api.ts b/src/api.ts
  2index 6d3c28b2ad7dda5e2bd24b612018ceb47d5685b6..e4ab27852f7174fb20d70d890d49f186a16018cd 100644
  3--- a/src/api.ts
  4+++ b/src/api.ts
  5@@ -7,11 +7,8 @@ export function setCapturedApiKey(key: string) {
  6 }
  7 
  8 function resolveApiKey(): string {
  9-  const key =
 10-    process.env.ANTHROPIC_API_KEY ??
 11-    capturedApiKey
 12-  if (!key) throw new Error("No Anthropic API key available")
 13-  return key
 14+  if (!capturedApiKey) throw new Error("No Anthropic API key available")
 15+  return capturedApiKey
 16 }
 17 
 18 function resolveModel(): string {
 19diff --git a/src/index.ts b/src/index.ts
 20new file mode 100644
 21index 0000000000000000000000000000000000000000..032c333cc9e562d6078dbcb7e5025fd9c8d06642
 22--- /dev/null
 23+++ b/src/index.ts
 24@@ -0,0 +1,196 @@
 25+import type { Plugin, PluginModule } from "@opencode-ai/plugin"
 26+import type { Permission } from "@opencode-ai/sdk"
 27+import { checkDeterministic } from "./deterministic"
 28+import { normalizeRequest, cacheKey } from "./normalizer"
 29+import { lookupCache, writeCache } from "./cache"
 30+import { callHaiku } from "./haiku"
 31+import { setCapturedApiKey, callClaude } from "./api"
 32+import { addPermissions, getPermissions } from "./permissions"
 33+import { logDecision } from "./logger"
 34+import { PERMISSION_KEYWORDS, PERMISSION_EXTRACTION_PROMPT } from "./rules"
 35+import type { Decision } from "./types"
 36+
 37+// v2 PermissionRequest shape (event properties)
 38+type PermissionAskedEvent = {
 39+  type: "permission.asked"
 40+  properties: {
 41+    id: string
 42+    sessionID: string
 43+    permission: string
 44+    patterns: string[]
 45+    metadata: Record<string, unknown>
 46+    always: string[]
 47+    tool?: { messageID: string; callID: string }
 48+  }
 49+}
 50+
 51+function buildToolInput(
 52+  toolType: string,
 53+  patterns: string[],
 54+  metadata: Record<string, unknown>,
 55+): Record<string, unknown> {
 56+  const pattern = patterns.join(" ")
 57+  switch (toolType) {
 58+    case "bash":
 59+      return { command: pattern }
 60+    case "webfetch":
 61+      return { url: patterns[0] ?? "" }
 62+    default:
 63+      return { ...metadata, pattern }
 64+  }
 65+}
 66+
 67+async function extractPermissions(text: string): Promise<string[]> {
 68+  if (text.length < 10) return []
 69+  const lower = text.toLowerCase()
 70+  if (!PERMISSION_KEYWORDS.some((kw) => lower.includes(kw))) return []
 71+
 72+  try {
 73+    const prompt = PERMISSION_EXTRACTION_PROMPT.replace("{user_prompt}", text)
 74+    const raw = await callClaude([{ role: "user", content: prompt }], 256)
 75+
 76+    let arr: unknown
 77+    try {
 78+      arr = JSON.parse(raw)
 79+    } catch {
 80+      const m = raw.match(/```(?:json)?\s*(\[.*?\])\s*```/s)
 81+      if (m) arr = JSON.parse(m[1])
 82+      else {
 83+        const m2 = raw.match(/\[.*\]/s)
 84+        if (m2) arr = JSON.parse(m2[0])
 85+      }
 86+    }
 87+    if (Array.isArray(arr)) return arr.filter((s): s is string => typeof s === "string")
 88+  } catch {
 89+    // non-fatal
 90+  }
 91+  return []
 92+}
 93+
 94+async function runPipeline(
 95+  toolType: string,
 96+  toolInput: Record<string, unknown>,
 97+  sessionId: string,
 98+): Promise<{ decision: Decision; source: string; rawResponse?: string; error?: string }> {
 99+  const start = performance.now()
100+
101+  // Stage 1: deterministic
102+  const det = checkDeterministic(toolType, toolInput)
103+  if (det) {
104+    logDecision({
105+      toolType,
106+      normalized: normalizeRequest(toolType, toolInput),
107+      decision: det,
108+      source: "deterministic",
109+      timingMs: performance.now() - start,
110+      sessionId,
111+    })
112+    return { decision: det, source: "deterministic" }
113+  }
114+
115+  // Stage 2: cache
116+  const normalized = normalizeRequest(toolType, toolInput)
117+  const key = cacheKey(normalized)
118+  const cached = lookupCache(key)
119+  if (cached) {
120+    logDecision({
121+      toolType,
122+      normalized,
123+      decision: cached,