1 import Base 2 import bend-net-json@0.3.0.0/json.bend as Json 3 import ../json.bend as J 4 import ./type.bend as Pl 5 6 # Claude Code's part of a plugin, as the keys it adds to .claude-plugin/plugin.json. 7 8 # ---- userConfig ---- 9 def kind_keys(k: Pl.Input) -> List<&2, Sigma<&2, &2, String, _ => Json.Val>>: 10 match k: 11 case Pl.Text{sensitive, d}: 12 List.append(&2, Sigma<&2, &2, String, _ => Json.Val>, [("type", Json.Str{"string"})], 13 List.append(&2, Sigma<&2, &2, String, _ => Json.Val>, J.when_true("sensitive", sensitive), J.opt("default", J.text(d)))) 14 case Pl.Choice{os, d}: 15 List.append(&2, Sigma<&2, &2, String, _ => Json.Val>, [("type", Json.Str{"string"}), ("options", J.texts(os))], J.opt("default", J.text(d))) 16 case Pl.Texts{d}: 17 [("type", Json.Str{"string"}), ("multiple", Json.Flag{True{}}), ("default", J.texts(d))] 18 case Pl.Number{lo, hi, d}: 19 List.concat(&2, Sigma<&2, &2, String, _ => Json.Val>, [[("type", Json.Str{"number"})], J.opt("min", J.number(lo)), J.opt("max", J.number(hi)), J.opt("default", J.number(d))]) 20 case Pl.Boolean{d}: 21 List.append(&2, Sigma<&2, &2, String, _ => Json.Val>, [("type", Json.Str{"boolean"})], J.opt("default", J.flag(d))) 22 case Pl.Folder{d}: 23 List.append(&2, Sigma<&2, &2, String, _ => Json.Val>, [("type", Json.Str{"directory"})], J.opt("default", J.text(d))) 24 case Pl.Path{d}: 25 List.append(&2, Sigma<&2, &2, String, _ => Json.Val>, [("type", Json.Str{"file"})], J.opt("default", J.text(d))) 26 27 def field(f: Pl.Field) -> Sigma<&2, &2, String, _ => Json.Val>: 28 match f: 29 case Pl.Field{key, title, description, required, k}: 30 (key, J.cat([[("title", Json.Str{title}), ("description", Json.Str{description})], J.when_true("required", required), kind_keys(k)])) 31 32 def fields(fs: List<&2, Pl.Field>) -> List<&2, Sigma<&2, &2, String, _ => Json.Val>>: 33 match fs: 34 case []: 35 [] 36 case f <> rest: 37 field(f) <> fields(rest) 38 39 # ---- hooks ---- 40 def event_name(e: Pl.HookEvent) -> String: 41 match e: 42 case Pl.SessionStart{}: 43 "SessionStart" 44 case Pl.Setup{}: 45 "Setup" 46 case Pl.UserPromptSubmit{}: 47 "UserPromptSubmit" 48 case Pl.UserPromptExpansion{}: 49 "UserPromptExpansion" 50 case Pl.PreToolUse{}: 51 "PreToolUse" 52 case Pl.PermissionRequest{}: 53 "PermissionRequest" 54 case Pl.PermissionDenied{}: 55 "PermissionDenied" 56 case Pl.PostToolUse{}: 57 "PostToolUse" 58 case Pl.PostToolUseFailure{}: 59 "PostToolUseFailure" 60 case Pl.PostToolBatch{}: 61 "PostToolBatch" 62 case Pl.Notification{}: 63 "Notification" 64 case Pl.MessageDisplay{}: 65 "MessageDisplay" 66 case Pl.SubagentStart{}: 67 "SubagentStart" 68 case Pl.SubagentStop{}: 69 "SubagentStop" 70 case Pl.TaskCreated{}: 71 "TaskCreated" 72 case Pl.TaskCompleted{}: 73 "TaskCompleted" 74 case Pl.Stop{}: 75 "Stop" 76 case Pl.StopFailure{}: 77 "StopFailure" 78 case Pl.TeammateIdle{}: 79 "TeammateIdle" 80 case Pl.InstructionsLoaded{}: 81 "InstructionsLoaded" 82 case Pl.ConfigChange{}: 83 "ConfigChange" 84 case Pl.CwdChanged{}: 85 "CwdChanged" 86 case Pl.DirectoryAdded{}: 87 "DirectoryAdded" 88 case Pl.FileChanged{}: 89 "FileChanged" 90 case Pl.WorktreeCreate{}: 91 "WorktreeCreate" 92 case Pl.WorktreeRemove{}: 93 "WorktreeRemove" 94 case Pl.PreCompact{}: 95 "PreCompact" 96 case Pl.PostCompact{}: 97 "PostCompact" 98 case Pl.PreModelSwitch{}: 99 "PreModelSwitch" 100 case Pl.PostModelSwitch{}: 101 "PostModelSwitch" 102 case Pl.Elicitation{}: 103 "Elicitation" 104 case Pl.ElicitationResult{}: 105 "ElicitationResult" 106 case Pl.SessionEnd{}: 107 "SessionEnd" 108 109 def shell_name(m: Maybe<&2, Pl.Shell>) -> Maybe<&2, Json.Val>: 110 match m: 111 case Some{Pl.Bash{}}: 112 Some{Json.Str{"bash"}} 113 case Some{Pl.PowerShell{}}: 114 Some{Json.Str{"powershell"}} 115 case None{}: 116 None{} 117 118 def form_keys(f: Pl.Form) -> List<&2, Sigma<&2, &2, String, _ => Json.Val>>: 119 match f: 120 case Pl.Exec{command, args}: 121 [("command", Json.Str{command}), ("args", J.texts(args))] 122 case Pl.ShellForm{command, sh}: 123 List.append(&2, Sigma<&2, &2, String, _ => Json.Val>, [("command", Json.Str{command})], J.opt("shell", shell_name(sh))) 124 125 def run_keys(r: Pl.Run) -> List<&2, Sigma<&2, &2, String, _ => Json.Val>>: 126 match r: 127 case Pl.Blocking{}: 128 [] 129 case Pl.Async{}: 130 [("async", Json.Flag{True{}})] 131 case Pl.AsyncRewake{}: 132 [("asyncRewake", Json.Flag{True{}})] 133 134 def handler(h: Pl.Handler) -> Json.Val: 135 match h: 136 case Pl.Command{f, r, w, t, s}: 137 J.cat([[("type", Json.Str{"command"})], form_keys(f), run_keys(r), J.opt("if", J.text(w)), J.opt("timeout", J.number(t)), J.opt("statusMessage", J.text(s))]) 138 139 def handlers(hs: List<&2, Pl.Handler>) -> List<&2, Json.Val>: 140 match hs: 141 case []: 142 [] 143 case h <> rest: 144 handler(h) <> handlers(rest) 145 146 def group(g: Pl.Group) -> Json.Val: 147 match g: 148 case Pl.Group{m, hs}: 149 J.cat([J.opt("matcher", J.text(m)), [("hooks", Json.Arr{handlers(hs)})]]) 150 151 def groups(gs: List<&2, Pl.Group>) -> List<&2, Json.Val>: 152 match gs: 153 case []: 154 [] 155 case g <> rest: 156 group(g) <> groups(rest) 157 158 def hooks(hs: List<&2, Pl.Hook>) -> List<&2, Sigma<&2, &2, String, _ => Json.Val>>: 159 match hs: 160 case []: 161 [] 162 case Pl.Hook{e, gs} <> rest: 163 (event_name(e), Json.Arr{groups(gs)}) <> hooks(rest) 164 165 # ---- Claude Code's part of .claude-plugin/plugin.json ---- 166 def config(fs: List<&2, Pl.Field>) -> List<&2, Sigma<&2, &2, String, _ => Json.Val>>: 167 match fs: 168 case []: 169 [] 170 case f <> rest: 171 [("userConfig", J.obj(fields(f <> rest)))] 172 173 def hooks_key(hs: List<&2, Pl.Hook>) -> List<&2, Sigma<&2, &2, String, _ => Json.Val>>: 174 match hs: 175 case []: 176 [] 177 case h <> rest: 178 [("hooks", J.obj(hooks(h <> rest)))] 179 180 # Enabled is Claude Code's default, so only a plugin that starts disabled says so. 181 def enabled_key(b: Bool) -> List<&2, Sigma<&2, &2, String, _ => Json.Val>>: 182 match b: 183 case True{}: 184 [] 185 case False{}: 186 [("defaultEnabled", Json.Flag{False{}})] 187 188 def keys(c: Pl.Claude) -> List<&2, Sigma<&2, &2, String, _ => Json.Val>>: 189 match c: 190 case Pl.Claude{display, enabled, fs, hs}: 191 List.concat(&2, Sigma<&2, &2, String, _ => Json.Val>, [J.opt("displayName", J.text(display)), enabled_key(enabled), config(fs), hooks_key(hs)])