SLOPSHOPPER

dkod

Build and deploy apps through your company's DKOD: policy, templates, Deploy questions and Deliver, with a DKOD band above the prompt and the DKOD rules on…

newbandguardtoaststatusprompt
v0.4.0MITupdated 2026-10-09dkod-ai/dkod-plugin
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · dkod
› fix the failing auth test and add an audit log call ╭────────────────────────────────────────────╮ │ dkod │ ⏺ Read(src/auth.ts) │ DKOD is not connected. Type /mcp and │ ⎿ Read 6 lines │ connect dkod. │ ⏺ Update(src/auth.ts) ╰────────────────────────────────────────────╯ ⎿ Added 2 lines, removed 1 line ⏺ Bash(bun test) ⎿ 3 pass, 1 fail ● Done. refresh now rejects expired claims and logs an audit event. ✻ Worked for 42s · done 4:20 PM DKOD ◇ Dkoder checked push ⟨Claude Code's own drawing⟩ ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Band
DKOD ◇ Dkoder checked push ⟨Claude Code's own drawing⟩
README

DKOD plugin for Claude Code

Build and deploy apps through your company's DKOD from Claude Code.

It adds three things:

  • MCP server dkod: your org's policy, templates, Deploy questions, Deploy, build answers, Deliver and Remove.
  • Skill dkod: tells Claude how to use them, in the right order.
  • Dkoder band and rules: a band above the prompt shows whether you are signed in to DKOD. DKOD's floor checks every command and file write, and once you sign in, so do your organization's Guard rules.

Install

/plugin marketplace add dkod-ai/dkod-plugin
/plugin install dkod@dkod-ai

Then run /mcp, pick dkod and choose Authenticate. You sign in with your company's own sign-in. DKOD never sees a password. Until you sign in, the band above the prompt is yellow.

Dkoder and Guard

DkoderGuard
WhatThis plugin, in your agentYour organization's policy, as rules
RulesThe floor, the same for every organizationYour organization's own, from its governance repository
NeedsNothing. Works signed out; the band asks you to sign inSign-in through /mcp, or your company's device install

What the rules check

The rules need to know your org: which GitHub owners are yours, which repository is the governance repository, and the org's own blocked commands. Dkoder gets those facts from DKOD when you sign in through /mcp, if your organization turned Guard on. Your company can also install Dkoder on its devices from the DKOD dashboard; that copy cannot be turned off. Then, in the org's repositories, the floor refuses:

  • git push, gh pr merge and GitHub API writes to an app repository. App code ships through Deliver.
  • A secret value (private keys, cloud keys, vendor tokens) written into a file.
  • Skipped git hooks (--no-verify, core.hooksPath).

Guard adds your organization's own rules on top, such as blocked commands. A refusal says which one refused: the Dkoder floor, or a Guard rule of your organization.

Before you sign in, nothing names your org, so a repository with a remote is left alone. A folder with no remote still gets the secret check. This plugin sends no events and needs no install file.

Limits

The rules keep Claude on the Deliver path. They are not a security boundary against a person.

To stop direct pushes for real, protect the app repository on GitHub (branch protection or rulesets). DKOD's Deliver does not need a person to push.

Source

This repository is generated from DKOD's source. Do not edit it here: changes are overwritten by the next release.

Docs

https://dkod.ai/docs

License

MIT

Source 2 files
hooks/register.js 2529 lines
1// Built by packages/dkoder-plugin/scripts/build-claude.ts from dkod-app. Do not edit: run `bun run --cwd packages/dkoder-plugin build:claude`.
2var MAX_DEPTH = 4;
3var MAX_LINE = 64 * 1024;
4var OPERATORS = ["&&", "||", ";;", "|&", ";", "|", "&", "(", ")", `
5`];
6function tokenize(line, subs) {
7  const out = [];
8  let word = "";
9  let inWord = false;
10  const flush = () => {
11    if (inWord)
12      out.push({ kind: "word", text: word });
13    word = "";
14    inWord = false;
15  };
16  let i = 0;
17  const n = Math.min(line.length, MAX_LINE);
18  const readParen = (start) => {
19    let depth = 1;
20    let j = start;
21    while (j < n && depth > 0) {
22      const c = line[j];
23      if (c === "\\") {
24        j += 2;
25        continue;
26      }
27      if (c === "'") {
28        const end = line.indexOf("'", j + 1);
29        j = end < 0 ? n : end + 1;
30        continue;
31      }
32      if (c === '"') {
33        j += 1;
34        while (j < n && line[j] !== '"')
35          j += line[j] === "\\" ? 2 : 1;
36        j += 1;
37        continue;
38      }
39      if (c === "(")
40        depth += 1;
41      else if (c === ")")
42        depth -= 1;
43      j += 1;
44    }
45    return j;
46  };
47  const readBacktick = (start) => {
48    let j = start;
49    while (j < n && line[j] !== "`")
50      j += line[j] === "\\" ? 2 : 1;
51    return j;
52  };
53  while (i < n) {
54    const c = line[i];
55    if (c === "\\") {
56      if (line[i + 1] === `
57`) {
58        i += 2;
59        continue;
60      }
61      word += line[i + 1] ?? "";
62      inWord = true;
63      i += 2;
64      continue;
65    }
66    if (c === "'") {
67      const end = line.indexOf("'", i + 1);
68      const stop = end < 0 ? n : end;
69      word += line.slice(i + 1, stop);
70      inWord = true;
71      i = stop + 1;
72      continue;
73    }
74    if (c === "$" && line[i + 1] === "'") {
75      let j = i + 2;
76      while (j < n && line[j] !== "'") {
77        if (line[j] === "\\" && j + 1 < n) {
78          const e = line[j + 1];
79          word += e === "n" ? `
80` : e === "t" ? "\t" : e;
81          j += 2;
82        } else {
83          word += line[j];
84          j += 1;
85        }
86      }
87      inWord = true;
88      i = j + 1;
89      continue;
90    }
91    if (c === '"') {
92      let j = i + 1;
93      while (j < n && line[j] !== '"') {
94        const d = line[j];
95        if (d === "\\" && j + 1 < n && '"\\$`\n'.includes(line[j + 1])) {
96          if (line[j + 1] !== `
97`)
98            word += line[j + 1];
99          j += 2;
100          continue;
101        }
102        if (d === "$" && line[j + 1] === "(") {
103          const end = readParen(j + 2);
104          subs.push(line.slice(j + 2, end - 1));
105          word += line.slice(j, end);
106          j = end;
107          continue;
108        }
109        if (d === "`") {
110          const end = readBacktick(j + 1);
111          subs.push(line.slice(j + 1, end));
112          word += line.slice(j, end + 1);
113          j = end + 1;
114          continue;
115        }
116        word += d;
117        j += 1;
118      }
119      inWord = true;
120      i = j + 1;
121      continue;
122    }
123    if (c === "$" && line[i + 1] === "(") {
124      const end = readParen(i + 2);
125      subs.push(line.slice(i + 2, end - 1));
126      word += line.slice(i, end);
127      inWord = true;
128      i = end;
129      continue;
130    }
131    if (c === "`") {
132      const end = readBacktick(i + 1);
133      subs.push(line.slice(i + 1, end));
134      word += line.slice(i, end + 1);
135      inWord = true;
136      i = end + 1;
137      continue;
138    }
139    if (c === "#" && !inWord) {
140      const end = line.indexOf(`
141`, i);
142      i = end < 0 ? n : end;
143      continue;
144    }
145    if (c === " " || c === "\t" || c === "\r") {
146      flush();
147      i += 1;
148      continue;
149    }
150    const op = OPERATORS.find((o) => line.startsWith(o, i));
151    if (op !== undefined) {
152      flush();
153      out.push({ kind: "op", text: op });
154      i += op.length;
155      continue;
156    }
157    if (c === ">" || c === "<") {
158      if (inWord && /^\d+$/.test(word)) {
159        word = "";
160        inWord = false;
161      }
162      flush();
163      if (line.startsWith("<<", i) && line[i + 2] !== "<") {
164        const m = /^<<(-?)[ \t]*(?:'([^'\n]*)'|"([^"\n]*)"|\\?([A-Za-z0-9_.-]+))/.exec(line.slice(i, i + 200));
165        if (m) {
166          const delim = m[2] ?? m[3] ?? m[4] ?? "";
167          const nl = line.indexOf(`
168`, i);
169          let bodyEnd = n;
170          if (nl >= 0) {
171            let k = nl + 1;
172            while (k < n) {
173              const e = line.indexOf(`
174`, k);
175              const stop = e < 0 ? n : e;
176              const text = m[1] === "-" ? line.slice(k, stop).replace(/^\t+/, "") : line.slice(k, stop);
177              if (text === delim) {
178                bodyEnd = stop;
179                break;
180              }
181              k = stop + 1;
182            }
183            const rest = line.slice(i + m[0].length, nl);
184            out.push(...tokenize(rest, subs));
185            out.push({ kind: "op", text: `
186` });
187          }
188          i = bodyEnd;
189          continue;
190        }
191      }
192      let j = i;
193      while (j < n && (line[j] === ">" || line[j] === "<" || line[j] === "&" || line[j] === "|" || line[j] === "-"))
194        j += 1;
195      const op = line.slice(i, j);
196      out.push({ kind: "op", text: "redirect", write: op.includes(">") && !op.endsWith("&") });
197      i = j;
198      continue;
199    }
200    word += c;
201    inWord = true;
202    i += 1;
203  }
204  flush();
205  return out;
206}
207var ASSIGNMENT = /^[A-Za-z_][A-Za-z0-9_]*=/;
208function programName(word) {
209  const slash = word.lastIndexOf("/");
210  return slash < 0 ? word : word.slice(slash + 1);
211}
212function joinCd(current, target) {
213  if (target.startsWith("/") || target.startsWith("~"))
214    return target;
215  if (current === "" || current === ".")
216    return target;
217  return `${current.replace(/\/$/, "")}/${target}`;
218}
219var KEYWORDS = new Set(["!", "if", "elif", "then", "else", "while", "until", "do", "time", "coproc"]);
220var SHELLS = new Set(["bash", "sh", "zsh", "dash", "ksh"]);
221function unwrap(argv, env) {
222  let a = argv;
223  for (let guard = 0;guard < 8 && a.length > 0; guard += 1) {
224    const prog = programName(a[0]);
225    if (prog === "env") {
226      let i = 1;
227      while (i < a.length && (a[i].startsWith("-") || ASSIGNMENT.test(a[i]))) {
228        if (ASSIGNMENT.test(a[i]))
229          env = [...env, a[i]];
230        if (a[i] === "-u" || a[i] === "-C" || a[i] === "-S")
231          i += 1;
232        i += 1;
233      }
234      a = a.slice(i);
235      continue;
236    }
237    if (prog === "command" || prog === "builtin" || prog === "exec" || prog === "nohup" || prog === "time" || prog === "nice") {
238      let i = 1;
239      while (i < a.length && a[i].startsWith("-"))
240        i += a[i] === "-n" && prog === "nice" ? 2 : 1;
241      a = a.slice(i);
242      continue;
243    }
244    if (prog === "sudo" || prog === "doas") {
245      let i = 1;
246      while (i < a.length && a[i].startsWith("-"))
247        i += ["-u", "-g", "-C", "-D", "-h", "-p", "-U"].includes(a[i]) ? 2 : 1;
248      a = a.slice(i);
249      continue;
250    }
251    if (prog === "timeout") {
252      let i = 1;
253      while (i < a.length && a[i].startsWith("-"))
254        i += a[i] === "-s" || a[i] === "-k" ? 2 : 1;
255      a = a.slice(i + 1);
256      continue;
257    }
258    if (prog === "xargs") {
259      let i = 1;
260      while (i < a.length && a[i].startsWith("-"))
261        i += ["-I", "-n", "-P", "-L", "-s", "-d", "-E"].includes(a[i]) ? 2 : 1;
262      a = a.slice(i);
263      continue;
264    }
265    if (SHELLS.has(prog)) {
266      const c = a.findIndex((w, i) => i > 0 && /^-[a-z]*c[a-z]*$/.test(w));
267      if (c > 0 && a[c + 1] !== undefined)
268        return { argv: a, env, script: a[c + 1] };
269      return { argv: a, env, script: null };
270    }
271    if (prog === "eval")
272      return { argv: a, env, script: a.slice(1).join(" ") };
273    break;
274  }
275  return { argv: a, env, script: null };
276}
277function parseCommands(line, depth = 0) {
278  if (depth > MAX_DEPTH)
279    return [];
280  const subs = [];
281  const tokens = tokenize(line, subs);
282  const out = [];
283  let cd = "";
284  let words = [];
285  let skipNext = false;
286  let writeNext = false;
287  let writes = [];
288  const end = () => {
289    const targets = writes;
290    writes = [];
291    if (words.length === 0)
292      return;
293    let i = 0;
294    const env = [];
295    while (i < words.length && ASSIGNMENT.test(words[i]))
296      env.push(words[i++]);
297    const raw = words.slice(i);
298    words = [];
299    if (raw.length === 0)
300      return;
301    const { argv, env: allEnv, script } = unwrap(raw, env);
302    if (argv.length === 0)
303      return;
304    if (programName(argv[0]) === "cd" || programName(argv[0]) === "pushd") {
305      const target = argv.slice(1).find((w) => !w.startsWith("-"));
306      cd = target === undefined ? "~" : joinCd(cd, target);
307      return;
308    }
309    out.push(targets.length > 0 ? { argv, env: allEnv, cd, writes: targets } : { argv, env: allEnv, cd });
310    if (script !== null) {
311      for (const inner of parseCommands(script, depth + 1))
312        out.push({ ...inner, cd: inner.cd === "" ? cd : joinCd(cd, inner.cd) });
313    }
314  };
315  for (const t of tokens) {
316    if (t.kind === "op") {
317      if (t.text === "redirect") {
318        skipNext = true;
319        writeNext = t.write === true;
320        continue;
321      }
322      end();
323      continue;
324    }
325    if (skipNext) {
326      skipNext = false;
327      if (writeNext)
328        writes.push(t.text);
329      continue;
330    }
331    if (t.text === "{" || t.text === "}") {
332      end();
333      continue;
334    }
335    if (words.length === 0 && KEYWORDS.has(t.text))
336      continue;
337    words.push(t.text);
338  }
339  end();
340  for (const body of subs)
341    for (const inner of parseCommands(body, depth + 1))
342      out.push(inner);
343  return out;
344}
345
346var join = (a, b) => b === "" ? a : a === "" ? b : b.startsWith("/") || b.startsWith("~") ? b : `${a.replace(/\/$/, "")}/${b}`;
347var GIT_GLOBAL_VALUE = new Set(["-C", "-c", "--git-dir", "--work-tree", "--namespace", "--exec-path", "--config-env", "--super-prefix", "--list-cmds", "--attr-source"]);
348var GIT_GLOBAL_HARMLESS = new Set(["-C", "-P", "--no-pager", "--paginate", "-p", "--no-optional-locks", "--literal-pathspecs", "--glob-pathspecs", "--noglob-pathspecs", "--icase-pathspecs", "--no-replace-objects", "--no-lazy-fetch", "--no-advice", "--bare"]);
349var PUSH_VALUE = new Set(["-o", "--push-option", "--receive-pack", "--exec", "--repo", "--signed"]);
350var COMMIT_VALUE = new Set(["-m", "--message", "-F", "--file", "-C", "--reuse-message", "-c", "--reedit-message", "--author", "--date", "-t", "--template", "--fixup", "--squash", "--cleanup", "--trailer", "--pathspec-from-file"]);
351var GIT_BUILTINS = new Set("add am annotate apply archive bisect blame branch bundle cat-file check-attr check-ignore checkout cherry cherry-pick citool clean clone column commit commit-graph commit-tree config count-objects credential describe diff diff-files diff-index diff-tree difftool fetch for-each-ref format-patch fsck gc grep hash-object help init interpret-trailers log ls-files ls-remote ls-tree maintenance merge merge-base merge-file merge-tree mergetool mktag mktree mv name-rev notes pack-objects prune pull push range-diff read-tree rebase reflog remote repack replace request-pull rerere reset restore rev-list rev-parse revert rm send-pack shortlog show show-branch show-ref sparse-checkout stash status submodule switch symbolic-ref tag update-index update-ref var verify-commit verify-pack verify-tag version whatchanged worktree write-tree lfs".split(" "));
352var GH_GROUPS = new Set("alias api attestation auth browse cache co codespace completion config copilot extension gist gpg-key help issue label org pr preview project release repo ruleset run search secret ssh-key status variable workflow".split(" "));
353var HOOKS_PATH = /hookspath/i;
354var HARMLESS_GIT_ENV = /^GIT_(SSH_COMMAND|SSH|TERMINAL_PROMPT|ASKPASS|PAGER|EDITOR|SEQUENCE_EDITOR|TRACE[A-Z_]*|AUTHOR_[A-Z]+|COMMITTER_[A-Z]+|PROGRESS_DELAY|REDACT_COOKIES|CURL_VERBOSE|HTTP_LOW_SPEED_[A-Z]+|OPTIONAL_LOCKS|FLUSH)=/;
355var RISKY_ALIAS = /(^|\s)(push|send-pack|merge|api)(\s|$)|^!/;
356function pushAction(cmd, dir, rest, opaque, out) {
357  let force = false;
358  let remote = null;
359  let sawRemote = false;
360  for (let j = 0;j < rest.length; j += 1) {
361    const w = rest[j];
362    if (w === "--")
363      continue;
364    if (w.startsWith("--repo=")) {
365      remote = w.slice("--repo=".length);
366      sawRemote = true;
367      continue;
368    }
369    if (w === "--repo") {
370      remote = rest[j + 1] ?? null;
371      sawRemote = true;
372      j += 1;
373      continue;
374    }
375    if (w === "--no-verify") {
376      out.push({ kind: "hook-bypass", cmd, dir, how: "git push --no-verify" });
377      continue;
378    }
379    if (w === "--force" || w.startsWith("--force-with-lease") || w === "--force-if-includes" || w === "--mirror" || w === "--delete") {
380      force = true;
381      continue;
382    }
383    if (w.startsWith("--receive-pack") || w.startsWith("--exec"))
384      opaque = opaque ?? "a custom receive-pack";
385    if (/^-[A-Za-z]+$/.test(w)) {
386      if (w.includes("f") || w.includes("d"))
387        force = true;
388      if (PUSH_VALUE.has(w))
389        j += 1;
390      continue;
391    }
392    if (w.startsWith("-")) {
393      if (PUSH_VALUE.has(w))
394        j += 1;
395      continue;
396    }
397    if (!sawRemote) {
398      remote = w;
399      sawRemote = true;
400      continue;
401    }
402    if (w.startsWith("+") || w.startsWith(":"))
403      force = true;
404  }
405  out.push({ kind: "push", cmd, dir, remote, force, opaque });
406}
407var remoteKey = (dir, name) => `${dir.replace(/^\.\/?/, "").replace(/\/+$/, "")}\x00${name}`;
408var CLONE_VALUE = new Set(["-b", "--branch", "-o", "--origin", "--depth", "-c", "--config", "--reference", "--reference-if-able", "--template", "-u", "--upload-pack", "--separate-git-dir", "--filter", "-j", "--jobs", "--shallow-since", "--shallow-exclude", "--bundle-uri"]);
409function gitActions(cmd, depth, made = new Map) {
410  const out = [];
411  let dir = cmd.cd;
412  const direct = programName(cmd.argv[0] ?? "").slice("git-".length);
413  const a = direct === "" ? cmd.argv : ["git", direct, ...cmd.argv.slice(1)];
414  let opaque = null;
415  for (const e of cmd.env) {
416    if (/^GIT_CONFIG/.test(e) && HOOKS_PATH.test(e))
417      out.push({ kind: "hook-bypass", cmd, dir, how: "a hooks path set in the environment" });
418    if (/^GIT_/.test(e) && !HARMLESS_GIT_ENV.test(e))
419      opaque = opaque ?? `${e.split("=")[0]} in the environment`;
420  }
421  const local = new Map;
422  const prefix = ["git"];
423  let i = 1;
424  while (i < a.length && a[i].startsWith("-")) {
425    const opt = a[i];
426    const eq = opt.indexOf("=");
427    const name = eq < 0 ? opt : opt.slice(0, eq);
428    const value = eq < 0 ? a[i + 1] : opt.slice(eq + 1);
429    const width = GIT_GLOBAL_VALUE.has(name) && eq < 0 ? 2 : 1;
430    if (name === "-C" && value !== undefined)
431      dir = join(dir, value);
432    else
433      prefix.push(...a.slice(i, i + width));
434    if ((name === "-c" || name === "--config-env") && value !== undefined) {
435      if (HOOKS_PATH.test(value))
436        out.push({ kind: "hook-bypass", cmd, dir, how: "git -c core.hooksPath" });
437      const alias = /^alias\.([^=]+)=(.*)$/s.exec(value);
438      if (alias)
439        local.set(alias[1].toLowerCase(), alias[2]);
440    }
441    if (!GIT_GLOBAL_HARMLESS.has(name))
442      opaque = opaque ?? `git ${name}`;
443    i += width;
444  }
445  const sub = a[i];
446  const rest = a.slice(i + 1);
447  if (sub === undefined)
448    return out;
449  const inline = local.get(sub.toLowerCase());
450  if (inline !== undefined && !GIT_BUILTINS.has(sub)) {
451    out.push(...expandAlias("git", inline, prefix, rest, { ...cmd, cd: dir }, depth));
452    return out;
453  }
454  if (sub === "clone") {
455    let origin = "origin";
456    const pos = [];
457    for (let j = 0;j < rest.length; j += 1) {
458      const w = rest[j];
459      const eq = w.indexOf("=");
460      if (w === "-o" || w === "--origin")
461        origin = rest[j + 1] ?? origin;
462      else if (w.startsWith("--origin="))
463        origin = w.slice(eq + 1);
464      if (w.startsWith("-")) {
465        if (eq < 0 && CLONE_VALUE.has(w))
466          j += 1;
467        continue;
468      }
469      pos.push(w);
470    }
471    const [url, target] = pos;
472    if (url !== undefined) {
473      const base = target ?? url.replace(/\/+$/, "").replace(/\.git$/, "").split(/[/:]/).pop() ?? "";
474      if (base !== "")
475        made.set(remoteKey(join(dir, base), origin), url);
476    }
477    if (made.size === 0)
478      made.set("", "");
479    return out;
480  }
481  if (sub === "remote" && (rest[0] === "add" || rest[0] === "set-url")) {
482    const pos = rest.slice(1).filter((w) => !w.startsWith("-"));
483    made.set(remoteKey(dir, pos[0] ?? ""), pos[pos.length - 1] ?? "");
484    return out;
485  }
486  if (sub === "push") {
487    pushAction(cmd, dir, rest, opaque, out);
488    const p = out[out.length - 1];
489    if (p.kind === "push" && made.size > 0) {
490      p.lineRemote = true;
491      const url = made.get(remoteKey(dir, p.remote ?? "origin"));
492      if (url !== undefined)
493        p.also = [url];
494    }
495    return out;
496  }
497  if (sub === "send-pack") {
498    const remote = rest.find((w) => !w.startsWith("-")) ?? null;
499    out.push({ kind: "push", cmd, dir, remote, force: rest.includes("--force"), opaque });
500    return out;
501  }
502  if (sub === "commit") {
503    let all = false;
504    for (let j = 0;j < rest.length; j += 1) {
505      const w = rest[j];
506      if (w === "--no-verify") {
507        out.push({ kind: "hook-bypass", cmd, dir, how: "git commit --no-verify" });
508        continue;
509      }
510      if (w === "--all" || w === "--include" || w === "--only") {
511        all = true;
512        continue;
513      }
514      if (w === "--") {
515        if (j + 1 < rest.length)
516          all = true;
517        break;
518      }
519      if (/^-[A-Za-z]+$/.test(w)) {
520        for (const ch of w.slice(1)) {
521          if (ch === "n")
522            out.push({ kind: "hook-bypass", cmd, dir, how: "git commit -n" });
523          if (ch === "a" || ch === "i" || ch === "o")
524            all = true;
525          if (COMMIT_VALUE.has(`-${ch}`)) {
526            if (w.endsWith(ch))
527              j += 1;
528            break;
529          }
530        }
531        continue;
532      }
533      if (w.startsWith("-")) {
534        if (!w.includes("=") && COMMIT_VALUE.has(w))
535          j += 1;
536        continue;
537      }
538      all = true;
539    }
540    out.push({ kind: "commit", cmd, dir, all });
541    return out;
542  }
543  if (sub === "config") {
544    if (rest.some((w) => HOOKS_PATH.test(w)))
545      out.push({ kind: "hook-bypass", cmd, dir, how: "git config core.hooksPath" });
546    const key = rest.findIndex((w) => /^alias\./i.test(w));
547    if (key >= 0) {
548      const value = rest.slice(key + 1).join(" ");
549      if (RISKY_ALIAS.test(value.trim()))
550        out.push({ kind: "hook-bypass", cmd, dir, how: `a git alias for "${value.slice(0, 40)}"` });
551    }
552    return out;
553  }
554  if (["merge", "rebase", "am", "cherry-pick", "revert", "pull"].includes(sub) && rest.includes("--no-verify")) {
555    out.push({ kind: "hook-bypass", cmd, dir, how: `git ${sub} --no-verify` });
556    return out;
557  }
558  if (!GIT_BUILTINS.has(sub))
559    out.push({ kind: "alias", tool: "git", name: sub, prefix, args: rest, cmd, dir });
560  return out;
561}
562var MAX_ALIAS_DEPTH = 3;
563function expandAlias(tool, value, prefix, args, cmd, depth = 0) {
564  if (depth >= MAX_ALIAS_DEPTH)
565    return [{ kind: "hook-bypass", cmd, dir: cmd.cd, how: `a ${tool} alias nested too deep to read` }];
566  const quoted = args.map((w) => `'${w.replace(/'/g, `'\\''`)}'`).join(" ");
567  if (value.startsWith("!")) {
568    const inner = parseCommands(`${value.slice(1)} ${quoted}`).map((c) => ({ ...c, cd: join(cmd.cd, c.cd) }));
569    return actionsOf(inner, depth + 1);
570  }
571  const words = parseCommands(value)[0]?.argv ?? [];
572  const argv = [...prefix, ...words, ...args];
573  return actionsOf([{ argv, env: cmd.env, cd: cmd.cd }], depth + 1);
574}
575var REPO_ARG = /^[A-Za-z0-9][A-Za-z0-9-]{0,38}\/[A-Za-z0-9._-]{1,100}$/;
576function repoFromRef(ref) {
577  const url = /^https?:\/\/[^/]+\/(?:api\/v3\/)?(?:repos\/)?([^/\s]+)\/([^/\s#?]+)/.exec(ref);
578  if (url)
579    return `${url[1]}/${url[2].replace(/\.git$/, "")}`;
580  const host = /^(?:[^/\s]+\/)?([A-Za-z0-9][A-Za-z0-9-]{0,38}\/[A-Za-z0-9._-]{1,100})$/.exec(ref);
581  if (host && REPO_ARG.test(host[1]))
582    return host[1];
583  return null;
584}
585function rawPath(endpoint) {
586  let p = endpoint.trim().replace(/^[a-z][a-z0-9+.-]*:\/\/[^/]*/i, "");
587  if (!endpoint.includes("://"))
588    p = p.replace(/^api\.github\.com(?::\d+)?/i, "");
589  try {
590    p = decodeURIComponent(p);
591  } catch {}
592  return p.replace(/[?#].*$/, "").replace(/\/{2,}/g, "/").replace(/^\//, "");
593}
594function apiPath(endpoint) {
595  return rawPath(endpoint).replace(/^api\/v3\//i, "");
596}
597function repoFromApiPath(endpoint) {
598  const m = /^repos\/([^/]+)\/([^/]+)/i.exec(apiPath(endpoint));
599  if (!m)
600    return null;
601  return m[1] === "{owner}" || m[2] === "{repo}" || m[1] === ":owner" || m[2] === ":repo" ? "current" : `${m[1]}/${m[2]}`;
602}
603var WRITE_METHODS = new Set(["POST", "PUT", "PATCH", "DELETE"]);
604var GH_REPO_WRITES = new Set(["delete", "edit", "rename", "archive", "unarchive", "sync", "set-default"]);
605var isDynamic = (w) => /[$`]/.test(w);
606function ghActions(cmd) {
607  const a = cmd.argv;
608  const dir = cmd.cd;
609  const envRepo = cmd.env.filter((e) => e.startsWith("GH_REPO=")).pop()?.slice("GH_REPO=".length) ?? null;
610  let repoFlag = envRepo !== null && envRepo !== "" ? envRepo : null;
611  const words = [];
612  for (let i = 1;i < a.length; i += 1) {
613    const w = a[i];
614    if (w === "-R" || w === "--repo") {
615      repoFlag = a[i + 1] ?? null;
616      i += 1;
617      continue;
618    }
619    if (w.startsWith("--repo=")) {
620      repoFlag = w.slice("--repo=".length);
621      continue;
622    }
623    if (/^-R./.test(w)) {
624      repoFlag = w.slice(w[2] === "=" ? 3 : 2);
625      continue;
626    }
627    words.push(w);
628  }
629  const repo = repoFlag === null ? null : isDynamic(repoFlag) ? "unknown" : repoFromRef(repoFlag) ?? repoFlag;
630  const targetRepo = (target) => target === undefined ? repo : isDynamic(target) ? "unknown" : repoFromRef(target) ?? repo;
631  const [group, verb] = words;
632  if (group === undefined)
633    return [];
634  if (group === "pr" && verb === "merge") {
635    const target = words.slice(2).find((w) => !w.startsWith("-"));
636    if (target !== undefined && isDynamic(target))
637      return [{ kind: "merge", cmd, dir, repo: "unknown" }];
638    const fromUrl = target !== undefined && target.includes("://") ? repoFromRef(target) : null;
639    return [{ kind: "merge", cmd, dir, repo: fromUrl ?? repo }];
640  }
641  if (group === "repo" && verb !== undefined && GH_REPO_WRITES.has(verb)) {
642    return [{ kind: "repo-write", cmd, dir, repo: targetRepo(words.slice(2).find((w) => !w.startsWith("-"))) }];
643  }
644  if (group === "repo" && verb === "create" && words.includes("--push")) {
645    return [{ kind: "repo-write", cmd, dir, repo: targetRepo(words.slice(2).find((w) => !w.startsWith("-"))) }];
646  }
647  if (group === "alias" && (verb === "set" || verb === "import")) {
648    const value = words.slice(3).filter((w) => !w.startsWith("-")).join(" ");
649    if (verb === "import" || RISKY_ALIAS.test(value.trim()) || words.includes("--shell") || words.includes("-s")) {
650      return [{ kind: "hook-bypass", cmd, dir, how: verb === "import" ? "gh alias import" : `a gh alias for "${value.slice(0, 40)}"` }];
651    }
652    return [];
653  }
654  if (group === "api") {
655    let method = null;
656    let hasFields = false;
657    let endpoint = null;
658    let mutation = false;
659    for (let i = 1;i < words.length; i += 1) {
660      const w = words[i];
661      if (w === "-X" || w === "--method") {
662        method = (words[i + 1] ?? "").toUpperCase();
663        i += 1;
664        continue;
665      }
666      if (w.startsWith("--method=")) {
667        method = w.slice("--method=".length).toUpperCase();
668        continue;
669      }
670      if (/^-X[A-Za-z]+$/.test(w)) {
671        method = w.slice(2).toUpperCase();
672        continue;
673      }
674      if (w === "-f" || w === "-F" || w === "--field" || w === "--raw-field" || w === "--input") {
675        hasFields = true;
676        if (/^query=\s*mutation\b/.test(words[i + 1] ?? ""))
677          mutation = true;
678        i += 1;
679        continue;
680      }
681      if (/^--(?:raw-)?field=/.test(w) || w.startsWith("--input=")) {
682        hasFields = true;
683        if (/=query=\s*mutation\b/.test(w))
684          mutation = true;
685        continue;
686      }
687      if (w === "-H" || w === "--header" || w === "-q" || w === "--jq" || w === "-t" || w === "--template" || w === "--cache" || w === "-p" || w === "--preview" || w === "--hostname") {
688        i += 1;
689        continue;
690      }
691      if (w.startsWith("-"))
692        continue;
693      if (endpoint === null)
694        endpoint = w;
695    }
696    const isWrite = method !== null ? WRITE_METHODS.has(method) : hasFields;
697    if (!isWrite || endpoint === null)
698      return [];
699    if (isDynamic(endpoint))
700      return [{ kind: "api-write", cmd, dir, repo: "unknown" }];
701    if (/^graphql\/?$/i.test(apiPath(endpoint)))
702      return mutation || hasFields ? [{ kind: "api-write", cmd, dir, repo }] : [];
703    const named = repoFromApiPath(endpoint);
704    if (named === null)
705      return /^[a-z]/i.test(apiPath(endpoint)) && !/^repos\b/i.test(apiPath(endpoint)) ? [] : [{ kind: "api-write", cmd, dir, repo: "unknown" }];
706    return [{ kind: "api-write", cmd, dir, repo: named === "current" ? repo : named }];
707  }
708  if (!GH_GROUPS.has(group))
709    return [{ kind: "alias", tool: "gh", name: group, prefix: ["gh", ...repoFlag !== null ? ["-R", repoFlag] : []], args: words.slice(1), cmd, dir }];
710  return [];
711}
712var HTTP_TOOLS = new Set(["curl", "wget", "http", "https", "xh", "xhs"]);
713function isGitHubApi(w) {
714  const m = /^(?:[a-z][a-z0-9+.-]*:\/\/)?(?:[^@/]*@)?([^/:?#]+)/i.exec(w.trim());
715  const host = (m?.[1] ?? "").toLowerCase().replace(/\.$/, "");
716  if (host === "api.github.com")
717    return true;
718  return /^[a-z][a-z0-9+.-]*:\/\//i.test(w.trim()) && /^api\/v3(\/|$)/i.test(rawPath(w));
719}
720var CURL_VALUE = new Set(["-H", "--header", "-o", "--output", "-u", "--user", "-A", "--user-agent", "-e", "--referer", "-b", "--cookie", "-c", "--cookie-jar", "-w", "--write-out", "-m", "--max-time", "--connect-timeout", "-x", "--proxy", "--retry", "-r", "--range", "-E", "--cert", "--cacert", "--key", "--resolve", "--connect-to", "--oauth2-bearer"]);
721var CURL_SHORT_VALUE = "HouAebcwmxrEK";
722function httpActions(cmd) {
723  const prog = programName(cmd.argv[0] ?? "");
724  const args = cmd.argv.slice(1);
725  let write = false;
726  let opaque = false;
727  const urls = [];
728  for (let i = 0;i < args.length; i += 1) {
729    const w = args[i];
730    if (prog === "curl") {
731      if (w === "--url") {
732        urls.push(args[i + 1] ?? "");
733        i += 1;
734        continue;
735      }
736      if (w.startsWith("--url=")) {
737        urls.push(w.slice(6));
738        continue;
739      }
740      if (w === "-K" || w === "--config") {
741        opaque = true;
742        i += 1;
743        continue;
744      }
745      if (w.startsWith("--config=")) {
746        opaque = true;
747        continue;
748      }
749      if (w === "--request" || w === "-X") {
750        write = write || WRITE_METHODS.has((args[i + 1] ?? "").toUpperCase());
751        i += 1;
752        continue;
753      }
754      if (w.startsWith("--request=")) {
755        write = write || WRITE_METHODS.has(w.slice(10).toUpperCase());
756        continue;
757      }
758      if (/^--(data|json|form|upload-file)/.test(w)) {
759        write = true;
760        if (!w.includes("="))
761          i += 1;
762        continue;
763      }
764      if (CURL_VALUE.has(w)) {
765        i += 1;
766        continue;
767      }
768      if (/^-[A-Za-z]/.test(w)) {
769        const body = w.slice(1);
770        for (let k = 0;k < body.length; k += 1) {
771          const ch = body[k];
772          const tail = body.slice(k + 1);
773          const value = tail !== "" ? tail : args[i + 1] ?? "";
774          if (ch === "X") {
775            write = write || WRITE_METHODS.has(value.toUpperCase());
776            if (tail === "")
777              i += 1;
778            break;
779          }
780          if (ch === "d" || ch === "F" || ch === "T") {
781            write = true;
782            if (tail === "")
783              i += 1;
784            break;
785          }
786          if (ch === "K") {
787            opaque = true;
788            if (tail === "")
789              i += 1;
790            break;
791          }
792          if (CURL_SHORT_VALUE.includes(ch)) {
793            if (tail === "")
794              i += 1;
795            break;
796          }
797        }
798        continue;
799      }
800      if (w.startsWith("-"))
801        continue;
802      urls.push(w);
803    } else if (prog === "wget") {
804      if (/^--method=/i.test(w))
805        write = write || WRITE_METHODS.has(w.slice(9).toUpperCase());
806      else if (w === "--method") {
807        write = write || WRITE_METHODS.has((args[i + 1] ?? "").toUpperCase());
808        i += 1;
809      } else if (/^--(post|body)-(data|file)/.test(w))
810        write = true;
811      else if (!w.startsWith("-"))
812        urls.push(w);
813    } else {
814      if (/^[A-Za-z]+$/.test(w) && WRITE_METHODS.has(w.toUpperCase())) {
815        write = true;
816        continue;
817      }
818      if (w.startsWith("-"))
819        continue;
820      if (isGitHubApi(w) || /^[a-z][a-z0-9+.-]*:\/\//i.test(w)) {
821        urls.push(w);
822        continue;
823      }
824      if (/^[^=:@]+(:=|==|=|@)/.test(w))
825        write = true;
826    }
827  }
828  if (opaque)
829    return [{ kind: "api-write", cmd, dir: cmd.cd, repo: "unknown" }];
830  const api = urls.filter(isGitHubApi);
831  if (!write || api.length === 0)
832    return [];
833  const out = [];
834  for (const u of api) {
835    const path = apiPath(u);
836    const repo = repoFromApiPath(u);
837    if (repo !== null && repo !== "current")
838      out.push({ kind: "api-write", cmd, dir: cmd.cd, repo });
839    else if (/^graphql\/?$/i.test(path) || !/^[a-z]/i.test(path) || /^repos\b/i.test(path))
840      out.push({ kind: "api-write", cmd, dir: cmd.cd, repo: "unknown" });
841  }
842  return out;
843}
844var EXPORTERS = new Set(["export", "declare", "typeset", "readonly", "local", "set"]);
845function actionsOf(cmds, depth = 0) {
846  const out = [];
847  const made = new Map;
848  const lineEnv = [];
849  for (const cmd of cmds) {
850    const prog = programName(cmd.argv[0] ?? "");
851    lineEnv.push(...cmd.env.filter((e) => e.startsWith("GH_REPO=")).filter(() => cmd.argv.length === 0));
852    if (EXPORTERS.has(prog))
853      lineEnv.push(...cmd.argv.slice(1).filter((w) => w.startsWith("GH_REPO=")));
854    if (prog === "git" || prog.startsWith("git-") && GIT_BUILTINS.has(prog.slice(4)))
855      out.push(...gitActions(cmd, depth, made));
856    else if (prog === "gh")
857      out.push(...ghActions(lineEnv.length > 0 ? { ...cmd, env: [...lineEnv, ...cmd.env] } : cmd));
858    else if (HTTP_TOOLS.has(prog))
859      out.push(...httpActions(cmd));
860  }
861  return out;
862}
863function repoFromRemoteUrl(url) {
864  const u = url.trim();
865  const full = /^(?:https?|ssh|git):\/\/(?:[^@/]+@)?[^/:]+(?::\d+)?\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/.exec(u);
866  if (full)
867    return `${full[1]}/${full[2]}`;
868  const scp = /^(?:[^@/:]+@)?[^/:]+:\/?([^/]+)\/([^/]+?)(?:\.git)?\/?$/.exec(u);
869  if (scp && !u.startsWith("/") && !u.startsWith("."))
870    return `${scp[1]}/${scp[2]}`;
871  return null;
872}
873
874var TICK_MS = 120;
875var CYCLE_TICKS = 34;
876var PULSE_TICKS = 12;
877var PULSE_TEXT_MS = 8000;
878var WORD = ["D", "K", "O", "D"];
879var COLORS = {
880  cyan: "#22d3ee",
881  cyanLight: "#cffafe",
882  yellow: "#facc15",
883  yellowLight: "#fef9c3",
884  green: "#4ade80",
885  coral: "#fb7185",
886  dim: "#94a3b8"
887};
888function signedIn(auth) {
889  return auth === "ok" || auth === "checking";
890}
891function wordmark(frame, auth) {
892  const base = signedIn(auth) ? COLORS.cyan : COLORS.yellow;
893  const light = signedIn(auth) ? COLORS.cyanLight : COLORS.yellowLight;
894  return WORD.map((ch, i) => i === frame ? { ch, color: light, bold: true } : { ch, color: base, bold: true });
895}
896function wordmarkFrame(tick) {
897  const at = (tick % CYCLE_TICKS + CYCLE_TICKS) % CYCLE_TICKS;
898  return at < WORD.length ? at : -1;
899}
900var PULSE_GLYPHS = ["◇", "◈", "◆", "◈"];
901function icon(pulse, pulseFrame) {
902  if (pulse === null || pulseFrame < 0 || pulseFrame >= PULSE_TICKS)
903    return { glyph: "◈", color: COLORS.cyan };
904  const color = pulse.decision === "allowed" ? COLORS.green : pulse.decision === "blocked" ? COLORS.coral : COLORS.yellow;
905  return { glyph: PULSE_GLYPHS[pulseFrame % PULSE_GLYPHS.length], color };
906}
907function message(auth, pulse, now, status) {
908  if (pulse !== null && now - pulse.at < PULSE_TEXT_MS) {
909    const color = pulse.decision === "allowed" ? COLORS.green : pulse.decision === "blocked" ? COLORS.coral : COLORS.yellow;
910    return { text: pulse.text, color };
911  }
912  if (auth === "signin")
913    return { text: "Sign in to DKOD for Deliver and your organization's Guard rules: type /mcp, pick dkod, then Authenticate", color: COLORS.yellow };
914  if (auth === "device")
915    return { text: "This device is not enrolled in your organization's Guard. Ask your DKOD admin.", color: COLORS.yellow };
916  if (auth === "offline")
917    return { text: "DKOD is not connected. Type /mcp and connect dkod.", color: COLORS.yellow };
918  return { text: status, color: COLORS.dim };
919}
920function pulseText(decision, action, repo) {
921  const verb = decision === "allowed" ? "checked" : decision === "blocked" ? "blocked" : "declined";
922  const what = action === "blocked-command" ? "a command" : action.replace(/-/g, " ");
923  return repo === null ? `${verb} ${what}` : `${verb} ${what} · ${repo}`;
924}
925function bandTree(el, view) {
926  const letters = wordmark(view.frame, view.auth).map((l) => h(el.Text, { color: l.color, bold: l.bold }, l.ch));
927  const mark = icon(view.pulse, view.pulseFrame);
928  const words = message(view.auth, view.pulse, view.now, view.status);
929  return h(el.Box, { key: "dkod-band", flexDirection: "row" }, h(el.Text, null, ...letters), h(el.Text, { color: mark.color, bold: true }, `  ${mark.glyph} `), h(el.Text, { color: COLORS.dim }, "Dkoder  "), h(el.Text, { color: words.color, wrap: "truncate-end" }, words.text));
930}
931function withBelow(el, ours, below) {
932  if (below === null || below === undefined || below === false)
933    return ours;
934  return h(el.Box, { key: "dkod-stack", flexDirection: "column" }, ours, below);
935}
936
937var IGNORED_BASENAMES = new Set(["readme.md", "license", "license.md", "licence", "licence.md"]);
938var MAX_ASSET_BYTES = 512 * 1024;
939
940var NAMESPACES = new Set(["app", "policy", "answers", "dkod"]);
941
942var INPUT_TYPES = new Set(["text", "number", "choice", "boolean", "list"]);
943var MAX_REPO_PATH = 256;
944function safeRepoPath(p) {
945  if (typeof p !== "string")
946    return false;
947  if (p.trim() === "")
948    return false;
949  if (p.length > MAX_REPO_PATH)
950    return false;
951  if (p.startsWith("/"))
952    return false;
953  if (p.includes("\\"))
954    return false;
955  if (/[\n\r\u2028\u2029\0]/.test(p))
956    return false;
957  return !p.split("/").some((segment) => segment === "..");
958}
959
960var REMOVED_SECRET = "DKOD_REMOVED_SECRET";
961var ASSIGNMENT_RE = /^[\s\-*]*(?:(?:export|ENV|ARG|const|let|var|readonly|private|public|static|final)\s+)*["']?([A-Za-z_][A-Za-z0-9_.-]*)["']?[ \t]*(?::[ \t]*[A-Za-z_][A-Za-z0-9_<>[\]|]*[ \t]*)?[:=](.*)$/;
962var SECRET_KEY_RE = /^(.*_)?(secret|password|passwd|token|api_key|apikey|private_key)$/i;
963function normaliseKey(key) {
964  return key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[.-]/g, "_").toLowerCase();
965}
966function looksLikeReference(value) {
967  const v = value.trim().replace(/^["'`]/, "").replace(/["'`],?;?$/, "").trim();
968  if (v === "" || v === "null" || v === "~" || v === "true" || v === "false" || v === "{}" || v === "[]")
969    return true;
970  if (v.includes(REMOVED_SECRET))
971    return true;
972  if (v === "|" || v === ">" || v === "|-" || v === ">-")
973    return true;
974  if (/^\{\{[^}]*\}\}$/.test(v))
975    return true;
976  if (/\$\{[^}]*\}/.test(v) || /^\$[A-Za-z_][A-Za-z0-9_]*$/.test(v) || v.startsWith("$("))
977    return true;
978  if (/^%\([^)]*\)s$/.test(v))
979    return true;
980  if (/(process|Bun|Deno|import\.meta)\.env\b/.test(v) || v.includes("env[") || /\bos\.environ\b/.test(v) || /\bgetenv\b/i.test(v))
981    return true;
982  if (/\bsecretKeyRef\b|\bvalueFrom\b|\bexternalSecret\b/.test(v))
983    return true;
984  if (/^<[^<>]+>$/.test(v))
985    return true;
986  return false;
987}
988var CODE_FILE_RE = /\.(?:[cm]?[jt]sx?|py|go|java|kt|kts|rb|rs|cs|php|swift|scala|dart)$/;
989function looksLikeCodeExpression(path, value) {
990  if (isConfigFile(path) || !CODE_FILE_RE.test(basename(path)))
991    return false;
992  const v = value.trim().replace(/[,;]$/, "").trim();
993  if (!v.includes("(") && !v.includes(".") && !/^(?:await|new)[ \t]/.test(v))
994    return false;
995  return /^(?:await[ \t]+|new[ \t]+)?[A-Za-z_$][\w$]*(?:\??\.[A-Za-z_$][\w$]*)*[ \t]*(?:[!?]?\(.*)?$/.test(v);
996}
997function nameWords(name) {
998  return name.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z])([A-Z][a-z])/g, "$1_$2").toLowerCase().split(/[_.-]/).filter((w) => w !== "");
999}
1000var SECRET_LAST_WORDS = new Set(["secret", "token", "apikey", "credential", "credentials"]);
1001var PASSWORD_WORDS = new Set(["password", "passwd", "pwd", "pass", "passphrase"]);
1002var KEY_QUALIFIERS = new Set(["api", "private", "secret", "access", "signing", "encryption", "master", "client"]);
1003var DESCRIBING_LAST_WORDS = new Set([
1004  "id",
1005  "ids",
1006  "hash",
1007  "ref",
1008  "refs",
1009  "name",
1010  "names",
1011  "path",
1012  "paths",
1013  "url",
1014  "uri",
1015  "file",
1016  "dir",
1017  "enc",
1018  "encrypted",
1019  "count",
1020  "kind",
1021  "type",
1022  "header",
1023  "headers",
1024  "state",
1025  "scope",
1026  "scopes",
1027  "label",
1028  "prefix",
1029  "length",
1030  "len",
1031  "env",
1032  "var",
1033  "field",
1034  "tag",
1035  "version",
1036  "digest",
1037  "ttl",
1038  "expiry",
1039  "expires",
1040  "at"
1041]);
1042function secretNameKind(name) {
1043  const words = nameWords(name);
1044  const last = words.at(-1);
1045  if (last === undefined || DESCRIBING_LAST_WORDS.has(last))
1046    return null;
1047  if (words.join("_").endsWith("key_name"))
1048    return null;
1049  if (PASSWORD_WORDS.has(last))
1050    return "password";
1051  if (SECRET_LAST_WORDS.has(last))
1052    return "secret";
1053  if (last === "base" && words.at(-2) === "key" && words.at(-3) === "secret")
1054    return "secret";
1055  if (last !== "key" || words.length < 2)
1056    return null;
1057  return KEY_QUALIFIERS.has(words.at(-2)) ? "secret" : "any_key";
1058}
1059function isSecretName(name) {
1060  return secretNameKind(name) !== null;
1061}
1062function isFakeValue(value) {
1063  return /example/i.test(value) || /^(.)\1*$/.test(value) || /x{8}/i.test(value);
1064}
1065function entropy(value) {
1066  const counts = new Map;
1067  for (const ch of value)
1068    counts.set(ch, (counts.get(ch) ?? 0) + 1);
1069  let bits = 0;
1070  for (const n of counts.values()) {
1071    const p = n / value.length;
1072    bits -= p * Math.log2(p);
1073  }
1074  return bits;
1075}
1076function charClasses(value) {
1077  return [/[a-z]/, /[A-Z]/, /[0-9]/, /[^A-Za-z0-9]/].filter((re) => re.test(value)).length;
1078}
1079var TIER_B_PLACEHOLDER = /test|example|sample|placeholder|dummy|fake|changeme|change_me|your|xxx|redacted|secret-value|todo|<|>|\.\.\./i;
1080var DEFAULT_PASSWORD = /^(password|passwd|secret|admin|root|postgres|mysql|redis|guest|default|changeit)$/i;
1081function looksRandom(value) {
1082  if (/^[0-9a-fA-F]+$/.test(value))
1083    return value.length >= 16 && entropy(value) >= 3;
1084  return entropy(value) >= 3.5 && charClasses(value) >= 2;
1085}
1086function notSecretShape(value) {
1087  if (value.includes(REMOVED_SECRET) || looksLikeReference(value))
1088    return true;
1089  if (/^(\/|\.\/|\.\.\/|~\/)/.test(value) || /\.[a-z0-9]{1,5}$/.test(value))
1090    return true;
1091  if (value.includes("://"))
1092    return true;
1093  if (/\d\.\d/.test(value) || /sha256:/i.test(value))
1094    return true;
1095  if (/\$\{|\$\(|\{\{|process\.env|os\.environ|import\.meta\.env|%/.test(value))
1096    return true;
1097  return TIER_B_PLACEHOLDER.test(value);
1098}
1099var IDENTIFIER_PART = "(?:[a-z]+(?:[0-9]+[a-z]*)?|[0-9]+[a-z]*)";
1100var IDENTIFIER_VALUE = new RegExp(`^${IDENTIFIER_PART}(?:[._-]${IDENTIFIER_PART})*$`);
1101function looksLikeSecretLiteral(value, kind) {
1102  const password = kind === "password";
1103  if (value.length < (password ? 8 : 16) || /\s/.test(value))
1104    return false;
1105  if (notSecretShape(value))
1106    return false;
1107  if (password)
1108    return !DEFAULT_PASSWORD.test(value);
1109  if (/^[A-Z][A-Z0-9_]+$/.test(value))
1110    return false;
1111  if (IDENTIFIER_VALUE.test(value))
1112    return false;
1113  return looksRandom(value);
1114}
1115var TEST_OR_DOC_DIR = /(^|\/)(test|tests|__tests__|spec|specs|fixtures?|testdata|examples?|docs?)\//i;
1116var TEST_OR_DOC_FILE = /\.(test|spec)\.[a-z]+$|_test\.(go|py|rs)$|\.(md|mdx|txt|rst)$/i;
1117function isTestOrDocPath(path) {
1118  return TEST_OR_DOC_DIR.test(path) || TEST_OR_DOC_FILE.test(path);
1119}
1120var PEM_LABEL = "PRIV" + "ATE KEY";
1121var PEM_BEGIN_RE = new RegExp(`-----BEGIN ([A-Z0-9 ]{0,40})${PEM_LABEL}( BLOCK)?-----`, "g");
1122var PEM_BODY_RE = /(?:[A-Za-z0-9+/=\s]|\\[nr]){40,}/y;
1123var hasDigit = (v) => /\d/.test(v);
1124var VALUE_PATTERNS = [
1125  { rule: "aws_access_key_id", re: /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g },
1126  { rule: "github_token", re: /\bgh[pousr]_[A-Za-z0-9]{36,255}/g },
1127  { rule: "github_token", re: /\bgithub_pat_[A-Za-z0-9_]{50,255}/g },
1128  { rule: "gitlab_token", re: /\bglpat-[A-Za-z0-9_-]{20,}/g },
1129  { rule: "anthropic_key", re: /\bsk-ant-[A-Za-z0-9_-]{20,}/g },
1130  { rule: "openrouter_key", re: /\bsk-or-[A-Za-z0-9_-]{20,}/g },
1131  { rule: "openai_key", re: /\bsk-proj-[A-Za-z0-9_-]{20,}/g },
1132  { rule: "openai_key", re: /\bsk-[A-Za-z0-9_-]{20,}/g, accept: hasDigit },
1133  { rule: "xai_key", re: /\bxai-[A-Za-z0-9]{20,}/g },
1134  { rule: "groq_key", re: /\bgsk_[A-Za-z0-9]{20,}/g },
1135  { rule: "huggingface_token", re: /\bhf_[A-Za-z0-9]{30,}/g },
1136  { rule: "npm_token", re: /\bnpm_[A-Za-z0-9]{36}(?![A-Za-z0-9])/g },
1137  { rule: "pypi_token", re: /\bpypi-[A-Za-z0-9_-]{50,}/g },
1138  { rule: "sendgrid_key", re: /\bSG\.[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{16,}/g },
1139  { rule: "shopify_token", re: /\bshp(?:at|ss|ca|pa)_[a-fA-F0-9]{32}(?![a-fA-F0-9])/g },
1140  { rule: "aws_secret_access_key", re: /aws_secret_access_key["']?[ \t]*[:=][ \t]*["']?([A-Za-z0-9/+=]{40})(?![A-Za-z0-9/+=])/dgi, group: 1 },
1141  { rule: "stripe_key", re: /\b(?:sk|rk)_live_[A-Za-z0-9]{16,}/g },
1142  { rule: "slack_token", re: /\bxox[abposr]-[A-Za-z0-9-]{10,}/g },
1143  { rule: "google_api_key", re: /\bAIza[0-9A-Za-z_-]{35}/g },
1144  { rule: "jwt", re: /\beyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g },
1145  { rule: "registry_auth", re: /:_(?:authToken|auth|password)[ \t]*=[ \t]*"?([^\s"#;]+)/dg, group: 1 },
1146  { rule: "db_url_password", re: /:\/\/[^\s:@/'"`]+:([^\s@/'"`]+)@/dg, group: 1 }
1147];
1148var AWS_SECRET_RE = /(?<![A-Za-z0-9/+=])[A-Za-z0-9/+=]{40}(?![A-Za-z0-9/+=])/g;
1149var AWS_PAIR_WINDOW = 400;
1150var awsSecretShaped = (v) => /^[A-Za-z0-9/+=]{40}$/.test(v) && /[a-z]/.test(v) && /[A-Z]/.test(v) && /[0-9/+]/.test(v);
1151var DOCKER_AUTH_RE = /"auth"[ \t]*:[ \t]*"([A-Za-z0-9+/=]{8,})"/dg;
1152var INLINE_ASSIGNMENT_RE = /(?<![A-Za-z0-9_.$-])["']?([A-Za-z_][A-Za-z0-9_.-]*)["']?[ \t]*(?::[ \t]*[A-Za-z_][A-Za-z0-9_<>[\]|]*[ \t]*)?[:=][ \t]*/g;
1153var LINE_START_PREFIX = /^[\s\-*]*(?:(?:export|ENV|ARG)\s+)*$/;
1154var BEARER_RE = /\bBearer[ \t]+([A-Za-z0-9._~+/-]{20,}=*)/dg;
1155var QUOTED = {
1156  '"': /^"((?:\\.|[^"\\\n])*)"/,
1157  "'": /^'((?:\\.|[^'\\\n])*)'/,
1158  "`": /^`((?:\\.|[^`\\\n])*)`/
1159};
1160var ASSIGNMENT_LINE_CAP = 20 * 1024;
1161function basename(path) {
1162  return (path.split("/").pop() ?? path).toLowerCase();
1163}
1164function isConfigFile(path) {
1165  const base = basename(path);
1166  if (base === "dockerfile" || base.startsWith("dockerfile.") || base.startsWith(".env") || base.endsWith(".env"))
1167    return true;
1168  if ([".npmrc", ".yarnrc", ".yarnrc.yml", ".pypirc", ".netrc", "_netrc", ".dev.vars", ".flaskenv", ".pgpass", ".my.cnf"].includes(base))
1169    return true;
1170  return /\.(ya?ml|properties|ini|cfg|conf|toml|env|envrc|example|sample|template|tfvars|vars)$/.test(base);
1171}
1172function lineStarts(content) {
1173  const starts = [0];
1174  for (let i = 0;i < content.length; i += 1)
1175    if (content.charCodeAt(i) === 10)
1176      starts.push(i + 1);
1177  return starts;
1178}
1179function lineOf(starts, offset) {
1180  let lo = 0;
1181  let hi = starts.length - 1;
1182  while (lo < hi) {
1183    const mid = lo + hi + 1 >> 1;
1184    if (starts[mid] <= offset)
1185      lo = mid;
1186    else
1187      hi = mid - 1;
1188  }
1189  return lo + 1;
1190}
1191function pemHits(content) {
1192  const out = [];
1193  PEM_BEGIN_RE.lastIndex = 0;
1194  for (let m = PEM_BEGIN_RE.exec(content);m; m = PEM_BEGIN_RE.exec(content)) {
1195    const start = m.index;
1196    const after = start + m[0].length;
1197    const endMarker = `-----END ${m[1] ?? ""}${PEM_LABEL}${m[2] ?? ""}-----`;
1198    const endAt = content.indexOf(endMarker, after);
1199    if (endAt >= 0) {
1200      out.push({ start, end: endAt + endMarker.length });
types/index.d.ts 15 lines
1// DKOD Guard's band values (packages/dkoder-plugin/src/claude-band.ts), held by the host for the session.
2export type GuardPulse = { decision: 'allowed' | 'blocked' | 'declined'; text: string; at: number }
3
4declare module 'claude-code' {
5  interface PluginState {
6    'dkod': {
7      auth: 'checking' | 'ok' | 'signin' | 'offline' | 'device'
8      frame: number
9      pulse: GuardPulse | null
10      pulseFrame: number
11      status: string
12    }
13  }
14}
15