koppler 0.0.0-stage → 0.0.2

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 (225) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +101 -2
  3. package/bin/run-logged +16 -0
  4. package/bin/secret-list +7 -0
  5. package/bin/secret-psql +41 -0
  6. package/bin/secret-set +17 -0
  7. package/bin/secret-store +82 -0
  8. package/bin/secret-test +10 -0
  9. package/bin/with-secrets +25 -0
  10. package/dist/.built +1 -0
  11. package/dist/cli/agent.js +144 -0
  12. package/dist/cli/agent.js.map +1 -0
  13. package/dist/cli/doctor.js +173 -0
  14. package/dist/cli/doctor.js.map +1 -0
  15. package/dist/cli/mcp.js +204 -0
  16. package/dist/cli/mcp.js.map +1 -0
  17. package/dist/cli/output.js +37 -0
  18. package/dist/cli/output.js.map +1 -0
  19. package/dist/cli/records.js +121 -0
  20. package/dist/cli/records.js.map +1 -0
  21. package/dist/cli/running.js +270 -0
  22. package/dist/cli/running.js.map +1 -0
  23. package/dist/cli/shell.js +43 -0
  24. package/dist/cli/shell.js.map +1 -0
  25. package/dist/cli/wait.js +59 -0
  26. package/dist/cli/wait.js.map +1 -0
  27. package/dist/core/addresses.js +55 -0
  28. package/dist/core/addresses.js.map +1 -0
  29. package/dist/core/arrange.js +329 -0
  30. package/dist/core/arrange.js.map +1 -0
  31. package/dist/core/cables.js +158 -0
  32. package/dist/core/cables.js.map +1 -0
  33. package/dist/core/expressions.js +72 -0
  34. package/dist/core/expressions.js.map +1 -0
  35. package/dist/core/frameworks.js +13 -0
  36. package/dist/core/frameworks.js.map +1 -0
  37. package/dist/core/kinds.js +244 -0
  38. package/dist/core/kinds.js.map +1 -0
  39. package/dist/core/launch.js +181 -0
  40. package/dist/core/launch.js.map +1 -0
  41. package/dist/core/logfmt.js +144 -0
  42. package/dist/core/logfmt.js.map +1 -0
  43. package/dist/core/mock.js +27 -0
  44. package/dist/core/mock.js.map +1 -0
  45. package/dist/core/plan.js +147 -0
  46. package/dist/core/plan.js.map +1 -0
  47. package/dist/core/ports.js +155 -0
  48. package/dist/core/ports.js.map +1 -0
  49. package/dist/core/presets.js +186 -0
  50. package/dist/core/presets.js.map +1 -0
  51. package/dist/core/secret-types.js +3 -0
  52. package/dist/core/secret-types.js.map +1 -0
  53. package/dist/core/sign-in.js +30 -0
  54. package/dist/core/sign-in.js.map +1 -0
  55. package/dist/core/stack-path.js +64 -0
  56. package/dist/core/stack-path.js.map +1 -0
  57. package/dist/core/stack.js +509 -0
  58. package/dist/core/stack.js.map +1 -0
  59. package/dist/core/terraform.js +393 -0
  60. package/dist/core/terraform.js.map +1 -0
  61. package/dist/data.js +37 -0
  62. package/dist/data.js.map +1 -0
  63. package/dist/engine/actions.js +99 -0
  64. package/dist/engine/actions.js.map +1 -0
  65. package/dist/engine/app-commands.js +106 -0
  66. package/dist/engine/app-commands.js.map +1 -0
  67. package/dist/engine/app-start.js +167 -0
  68. package/dist/engine/app-start.js.map +1 -0
  69. package/dist/engine/app-state.js +43 -0
  70. package/dist/engine/app-state.js.map +1 -0
  71. package/dist/engine/boards.js +219 -0
  72. package/dist/engine/boards.js.map +1 -0
  73. package/dist/engine/branch.js +194 -0
  74. package/dist/engine/branch.js.map +1 -0
  75. package/dist/engine/browser-console.js +45 -0
  76. package/dist/engine/browser-console.js.map +1 -0
  77. package/dist/engine/calls.js +97 -0
  78. package/dist/engine/calls.js.map +1 -0
  79. package/dist/engine/checks.js +270 -0
  80. package/dist/engine/checks.js.map +1 -0
  81. package/dist/engine/clones.js +67 -0
  82. package/dist/engine/clones.js.map +1 -0
  83. package/dist/engine/config.js +26 -0
  84. package/dist/engine/config.js.map +1 -0
  85. package/dist/engine/deployed.js +488 -0
  86. package/dist/engine/deployed.js.map +1 -0
  87. package/dist/engine/devices.js +81 -0
  88. package/dist/engine/devices.js.map +1 -0
  89. package/dist/engine/discover.js +236 -0
  90. package/dist/engine/discover.js.map +1 -0
  91. package/dist/engine/down.js +253 -0
  92. package/dist/engine/down.js.map +1 -0
  93. package/dist/engine/history.js +215 -0
  94. package/dist/engine/history.js.map +1 -0
  95. package/dist/engine/investigate.js +278 -0
  96. package/dist/engine/investigate.js.map +1 -0
  97. package/dist/engine/libraries.js +129 -0
  98. package/dist/engine/libraries.js.map +1 -0
  99. package/dist/engine/linked.js +146 -0
  100. package/dist/engine/linked.js.map +1 -0
  101. package/dist/engine/listen.js +223 -0
  102. package/dist/engine/listen.js.map +1 -0
  103. package/dist/engine/locks.js +38 -0
  104. package/dist/engine/locks.js.map +1 -0
  105. package/dist/engine/messages.js +177 -0
  106. package/dist/engine/messages.js.map +1 -0
  107. package/dist/engine/needs.js +363 -0
  108. package/dist/engine/needs.js.map +1 -0
  109. package/dist/engine/processes.js +350 -0
  110. package/dist/engine/processes.js.map +1 -0
  111. package/dist/engine/query.js +241 -0
  112. package/dist/engine/query.js.map +1 -0
  113. package/dist/engine/queues.js +100 -0
  114. package/dist/engine/queues.js.map +1 -0
  115. package/dist/engine/recipes.js +248 -0
  116. package/dist/engine/recipes.js.map +1 -0
  117. package/dist/engine/reload.js +32 -0
  118. package/dist/engine/reload.js.map +1 -0
  119. package/dist/engine/repos.js +214 -0
  120. package/dist/engine/repos.js.map +1 -0
  121. package/dist/engine/research.js +134 -0
  122. package/dist/engine/research.js.map +1 -0
  123. package/dist/engine/run-logs.js +79 -0
  124. package/dist/engine/run-logs.js.map +1 -0
  125. package/dist/engine/run.js +54 -0
  126. package/dist/engine/run.js.map +1 -0
  127. package/dist/engine/runs.js +223 -0
  128. package/dist/engine/runs.js.map +1 -0
  129. package/dist/engine/session.js +130 -0
  130. package/dist/engine/session.js.map +1 -0
  131. package/dist/engine/share.js +124 -0
  132. package/dist/engine/share.js.map +1 -0
  133. package/dist/engine/stack-file.js +206 -0
  134. package/dist/engine/stack-file.js.map +1 -0
  135. package/dist/engine/stack-share.js +86 -0
  136. package/dist/engine/stack-share.js.map +1 -0
  137. package/dist/engine/stacks.js +143 -0
  138. package/dist/engine/stacks.js.map +1 -0
  139. package/dist/engine/sync.js +214 -0
  140. package/dist/engine/sync.js.map +1 -0
  141. package/dist/engine/traces.js +65 -0
  142. package/dist/engine/traces.js.map +1 -0
  143. package/dist/engine/up.js +621 -0
  144. package/dist/engine/up.js.map +1 -0
  145. package/dist/engine/watch.js +71 -0
  146. package/dist/engine/watch.js.map +1 -0
  147. package/dist/engine/wire.js +120 -0
  148. package/dist/engine/wire.js.map +1 -0
  149. package/dist/failure.js +16 -0
  150. package/dist/failure.js.map +1 -0
  151. package/dist/github.js +95 -0
  152. package/dist/github.js.map +1 -0
  153. package/dist/ids.js +6 -0
  154. package/dist/ids.js.map +1 -0
  155. package/dist/json-files.js +36 -0
  156. package/dist/json-files.js.map +1 -0
  157. package/dist/koppler.mjs +840 -0
  158. package/dist/koppler.mjs.map +1 -0
  159. package/dist/lambda-local.mjs +250 -0
  160. package/dist/lambda-local.mjs.map +1 -0
  161. package/dist/lambda-secrets-proxy.go +31 -0
  162. package/dist/mock-server.mjs +23 -0
  163. package/dist/mock-server.mjs.map +1 -0
  164. package/dist/page/assets/JetBrainsMono-cyrillic-D2jMJ_bk.woff2 +0 -0
  165. package/dist/page/assets/JetBrainsMono-greek-D2nYnAec.woff2 +0 -0
  166. package/dist/page/assets/JetBrainsMono-latin-Db4Uuiha.woff2 +0 -0
  167. package/dist/page/assets/JetBrainsMono-latin-ext-Bnd2h4Br.woff2 +0 -0
  168. package/dist/page/assets/JetBrainsMono-vietnamese-CNlsqbWb.woff2 +0 -0
  169. package/dist/page/assets/SchibstedGrotesk-latin-DxefFeRw.woff2 +0 -0
  170. package/dist/page/assets/SchibstedGrotesk-latin-ext-CnpJE18X.woff2 +0 -0
  171. package/dist/page/assets/index-Bkj_pX35.js +42 -0
  172. package/dist/page/assets/index-Dq45jswH.css +1 -0
  173. package/dist/page/index.html +14 -0
  174. package/dist/page/theme.js +8 -0
  175. package/dist/paths.js +19 -0
  176. package/dist/paths.js.map +1 -0
  177. package/dist/safe-env.js +73 -0
  178. package/dist/safe-env.js.map +1 -0
  179. package/dist/sam-local.mjs +135 -0
  180. package/dist/sam-local.mjs.map +1 -0
  181. package/dist/scrub.js +87 -0
  182. package/dist/scrub.js.map +1 -0
  183. package/dist/secret-check.mjs +262 -0
  184. package/dist/secret-check.mjs.map +1 -0
  185. package/dist/server/browser-console.js +117 -0
  186. package/dist/server/browser-console.js.map +1 -0
  187. package/dist/server/cable-test.js +365 -0
  188. package/dist/server/cable-test.js.map +1 -0
  189. package/dist/server/choices.js +51 -0
  190. package/dist/server/choices.js.map +1 -0
  191. package/dist/server/files.js +107 -0
  192. package/dist/server/files.js.map +1 -0
  193. package/dist/server/gate.js +128 -0
  194. package/dist/server/gate.js.map +1 -0
  195. package/dist/server/logs.js +76 -0
  196. package/dist/server/logs.js.map +1 -0
  197. package/dist/server/mobile.js +113 -0
  198. package/dist/server/mobile.js.map +1 -0
  199. package/dist/server/routes.js +326 -0
  200. package/dist/server/routes.js.map +1 -0
  201. package/dist/server/runs.js +111 -0
  202. package/dist/server/runs.js.map +1 -0
  203. package/dist/server/sanitize.js +242 -0
  204. package/dist/server/sanitize.js.map +1 -0
  205. package/dist/server/secrets.js +111 -0
  206. package/dist/server/secrets.js.map +1 -0
  207. package/dist/server/status.js +321 -0
  208. package/dist/server/status.js.map +1 -0
  209. package/dist/server/token.js +38 -0
  210. package/dist/server/token.js.map +1 -0
  211. package/dist/server/waiting.js +79 -0
  212. package/dist/server/waiting.js.map +1 -0
  213. package/dist/status-watch.mjs +63 -0
  214. package/dist/status-watch.mjs.map +1 -0
  215. package/package.json +74 -4
  216. package/skills/koppler-run/SKILL.md +169 -0
  217. package/skills/koppler-run/VERSION +1 -0
  218. package/skills/koppler-run/references/action-log.md +50 -0
  219. package/skills/koppler-run/references/permissions.md +25 -0
  220. package/skills/koppler-run/references/preparing-nodes.md +62 -0
  221. package/skills/koppler-run/references/reporting-bugs.md +76 -0
  222. package/skills/koppler-run/references/research.md +40 -0
  223. package/skills/koppler-run/references/secrets.md +56 -0
  224. package/skills/koppler-run/references/stack.md +197 -0
  225. package/skills/koppler-run/references/status.md +92 -0
@@ -0,0 +1,169 @@
1
+ ---
2
+ name: koppler-run
3
+ description: Use when the user wants to connect this Claude Code session to the Koppler canvas ("/koppler-run connect", "disconnect"), wants to report what went wrong with Koppler ("/koppler-run feedback"), or wants to run several apps or services of their stack locally, wired to each other, while the rest keeps calling a deployed environment, or says "koppler", "local stack", "run koppler with the X board", "run X locally against local Y", "point X at my local Y", or submits a graph from the Koppler canvas. In auto mode it applies the config changes and starts logins, tunnels and apps itself; in manual mode it hands over a per-app checklist. Not for deploying, debugging deployed services, or fetching secrets.
4
+ ---
5
+
6
+ # Koppler
7
+
8
+ ## Talking to the user on the canvas
9
+
10
+ While this session is connected to the canvas (see Connect), anything that needs the user (a decision, something only they can do) and any finished or failed work goes on the canvas too, whether it started from the canvas or from the chat, so it is seen there and not only in the chat.
11
+ When it is about a run, give its id; otherwise leave `run` out:
12
+ `koppler say` with JSON on stdin: `{ "kind": "question" | "result" | "failure", "run"?: "<data.id of the run>", "title", "summary", "sections": [{ "title", "items": [...], "commands": [...] }], "choices": [{ "id", "label", "detail" }] }`.
13
+ Put anything the user has to run in a section's `commands`, never in `items`: the canvas shows each command in a box with a Copy button.
14
+ Short plain sentences; no Markdown, it is shown as text. The same question also goes in your chat reply.
15
+ An answer from the canvas arrives as a `koppler-reply` prompt (`data.message`, `data.choice` and its `label`, or `data.text`): act on it like the user's own reply. If the user answers in the chat instead, run `koppler say --answered <message id>` so the canvas stops asking.
16
+
17
+ ## Connect and disconnect
18
+
19
+ When invoked with `connect` (or the user asks to connect this session to Koppler):
20
+ 1. Make sure the canvas runs: `koppler status`; if `canvas` is not running, `koppler canvas`.
21
+ 2. Start listening for the canvas's requests in this session (runs with a note, checklists, fixes, and what a run left over): `koppler listen take` with run_in_background and `timeout: 7200000`. `take` moves the connection here from any other session, which stops getting them. Its first line names this session (`connected as session <id>`); remember the id.
22
+ 3. Run `koppler plan` and `koppler history`, then tell the user in a few lines: the canvas address (`<state>/canvas-url`), that this session now gets their runs, and any run already waiting (handle it as usual, starting with `koppler plan`).
23
+ 4. **Always listen again.** `koppler listen` exits as soon as it hands over a prompt, and nothing reaches this session until it runs again. Start `koppler listen as <id>` (this session's id, never `take`) again before you end every turn that handled a prompt (a run, a Stop, an answer from the canvas, a "find the facts" request), and also when you stop early (blocked, failed, waiting for the user's answer in chat). Never end a turn without a listener running, unless the user asked to disconnect. If it exits 3, the user connected another session or disconnected this one: tell them this one no longer gets runs, and stop listening (only `connect` brings it back).
24
+ 5. **Every request ends with `koppler history done` and the next listener**, runs or not. A request that is no run (filling in settings, an answer, a fix or facts request) is finished with `koppler history done` (no id) once you have acted on it: otherwise the canvas keeps showing the agent as working, and after ten minutes as quiet.
25
+ 6. What the canvas shows: "Agent connected" while a listener runs; "Agent working, N min" from taking a prompt until `koppler history done` marks it done or failed; "Agent needs you" while a question of yours waits in the inbox; "Agent quiet, N min" once that has lasted over ten minutes; after that, with no listener, "No agent listening, N waiting", and the user's runs and Stops pile up unseen. So finish a run with `koppler history done` and the next listener together.
26
+
27
+ When invoked with `disconnect`: `koppler listen stop`, and say runs keep working without an agent, while notes, checklists and fixes wait until a session connects.
28
+
29
+ Compose a local run from a graph: nodes run locally, an edge points a caller at a local callee, every unwired dependency calls the deployed app of the chosen environment (the stack's `envs`).
30
+ Koppler is a separate app that works without an agent; this skill drives it. It needs the `koppler` command on the PATH (`npm i -g koppler`, or `npm link` in `app/` of a Koppler clone): when `koppler --version` fails, tell the user how to install it and stop.
31
+ A released skill has a `VERSION` file beside this one: when `koppler --version` names another version, tell the user both, and that the older one needs updating (`npm i -g koppler`, or `koppler@next` for the QA channel, and the plugin from the same channel).
32
+ `<app>` below means the Koppler app's folder, `"$(dirname "$(realpath "$(command -v koppler)")")/.."` (with `dist/` and `bin/`), and `<state>` Koppler's state folder, `~/.local/state/koppler/run` (the canvas, the agent and `koppler` share it).
33
+
34
+ ## Operating principles
35
+
36
+ - **Facts come from the shared stack config**: `stack.json` in the stack folder set in `~/.config/koppler/config.json`, copied by `koppler sync` into `<state>/registry.json` (with its `source` path) for the canvas and scripts. Read the stack's `notes` first.
37
+ Top-level app fields are the first env; for another env, each field in `apps[id].envs.<env>` replaces the top-level one (a list such as `prereqs` or `secrets` is replaced whole, never merged). Every sign-in, tunnel and hosts entry the first env has must be in that env's own list; `koppler doctor` reports a missing one as a gap, and Koppler never borrows another env's.
38
+ **Never write `stack.json`, `registry.json` or any other Koppler JSON file yourself, not even one field.** Every change to the stack goes through `koppler stack set <path> <value>` / `koppler stack unset <path>` (`koppler stack get <path>` reads one), or `koppler add`/`remove` for whole apps; each change is checked before it is kept, and the stack reloads. A change no command can make is a Koppler gap: report it, never work around it.
39
+ Never guess a missing fact: read the app's repo and set it with `koppler stack set`; that change is for the user to commit to the team repo, or, when they ask, to propose with `koppler stack propose` (a pull request with only those changes).
40
+ No app is the center of a stack: an empty or missing `consumes` means its calls are not mapped yet, never that it calls nothing. A cable the user draws between apps the stack has no call for is marked `NEW` in the prompt: find in the caller's repo how it picks the callee's address (a setting, an env file, a constant), record it with `koppler stack set apps.<caller>.consumes.<callee> '{"how":...}'`, and ask when it cannot be told. Never state which apps call which from `stack.json` alone; `koppler sync` searches every clone for other apps' deployed hosts and prints `FOUND a -> b` for calls the stack misses (stored as `REGISTRY.found`, cable-able on the canvas), `UNKNOWN` for apps with no deployed address and `SHARED` for hosts it cannot attribute. Treat a found call like `how: unknown`; once confirmed, record it with `koppler stack set`. Apps with a `pending` field have name, kind and slug only; fill in `run`, `url`, `deployedUrl`, `baseProfile`, `project`, `launchSettings`, `prereqs` before use with `koppler stack set`, then `koppler stack unset apps.<id>.pending`.
41
+ - **Mode decides who acts** (a Send's `mode`: `auto`, or `manual` when the user picked "Get a checklist instead" in the Run menu; in chat, ask; default `auto`):
42
+ - `auto`: apply the changes, start AWS logins, tunnels and apps yourself (tunnels and apps through `koppler run`), and verify each app answers with `koppler wait <apps> --healthy` (never a polling loop of your own). Log each change with `koppler actions add` before you make it (`references/action-log.md`). Report what ran.
43
+ - `manual`: change nothing; hand over the per-app checklist and wait.
44
+ - **Always stop and ask, in both modes:** checking out a branch over uncommitted changes when the user has not chosen on the card how to treat them, anything that needs the user in a browser or terminal (SSO approval, sudo for `/etc/hosts`, secrets), and on `prod` before opening a DB tunnel or swapping secrets.
45
+ - **Never approve a stack's commands.** A stack can carry checks, start commands and code changes of its own; Koppler runs none until the user has reviewed them with `koppler doctor --approve` in their own terminal, and again after any of them changes. When a command says so, tell the user exactly that and stop: do not pass `--yes`, do not edit the approvals file, and do not start the app another way.
46
+ - **Never touch secrets.** The agent does not read, fetch, print or source secret values. See `references/secrets.md`.
47
+ - **Real clones, not worktrees**, so the IDE sees the changes. Never commit or discard on the user's behalf; stash only through `koppler switch --set-aside`, when the user chose it on the card.
48
+ - A `koppler-facts` prompt with `data.app` comes from "Ask the agent to fill in" in an app's settings: read that app's repo for each field under `EMPTY`, leave the ones that do not apply, never touch a field under `LOCKED` (a locked field, listed by `koppler stack locks`, refuses every change; only the user unlocks one, in their own terminal, so never run `koppler stack unlock`), propose what you would set and why, and after the user's yes set each with `koppler stack set apps.<app>.<field> <value>`.
49
+ - A `koppler-facts` prompt (or "find the PROD facts for <app>") lists what the stack is missing for an environment (`koppler doctor` reports them as `GAP`): a tunnel another environment has, or how to get a secret there. Read the app's repo (Terraform tfvars per environment, workflow files) and the user's local tunnel aliases (shell rc files); never read a secret value, and never copy another environment's host. Propose the additions (`apps.<id>.envs.<env>.prereqs`, `.secrets` with `fetch` steps for that environment's account) and make them with `koppler stack set` only after the user confirms. Until then `koppler plan` refuses that environment for the app.
50
+ - When the user raises a concern about the canvas ("this looked wrong", "it did X"), read `<state>/debug.jsonl` first: with Settings > Debug log on, it holds every click, change, shortcut, message and page error, each with a summary of the board. If it is empty, ask them to turn it on and repeat what happened.
51
+ - **Report every Koppler bug as a GitHub issue.** When something is wrong with Koppler itself (the command, the canvas, the engine, this skill or its docs), file it on `evan-mcgeek/koppler` with a reproduction, scrubbed logs (`koppler status --debug`) and a possible fix, after the user agrees to the draft. Never work around one silently. Something Koppler cannot do yet that the user wants is a feature request, filed the same way. Each goes on the project board (Bugs or Feature Requests) and moves to In Progress, Testing and Done as it is worked on. See `references/reporting-bugs.md`.
52
+ - Private findings and open questions go in `<state>/NOTES.md` (dated, one per line); facts about the apps go in the stack, through `koppler stack set`.
53
+
54
+ ## Feedback
55
+
56
+ When invoked with `feedback` (or the user asks to report what went wrong with Koppler):
57
+ 1. Gather the session: `koppler history session` prints what happened since the last finished Stop (failed and left-over runs, page errors, messages the user saw, buttons clicked over and over, error lines in app logs) and says where the files are. Add `koppler status --debug` and what you yourself hit in this session: a command that failed, a step you worked around, an instruction here that was wrong or missing.
58
+ 2. Keep only problems with Koppler itself. What belongs to the user's own apps or to the stack's facts is left out: say so in one line and offer to fix the stack with `koppler stack set` as usual.
59
+ 3. Show a table, one row per problem: what went wrong, the evidence (the file and line, or the message), and whether it is reported already (`gh issue list -R evan-mcgeek/koppler --state all --search "<a few words>"`). Ask which rows to report.
60
+ 4. For each row the user picks, write one issue as `references/reporting-bugs.md` says, from scrubbed text only, and show it in full before it is filed. An existing issue gets a comment instead. Without `gh` or a GitHub login, give a prefilled link: `https://github.com/evan-mcgeek/koppler/issues/new?title=<title>&body=<body>`, both URL-encoded.
61
+ 5. Reply with the links, and what the user can do until each is fixed.
62
+
63
+ The gathered files stay on the user's Mac: they have secrets masked but still name the user's apps. Nothing from them goes into an issue as is; an issue never holds a secret value or anything that names the user, their company or their stack.
64
+
65
+ ## Session review (while Koppler itself is worked on)
66
+
67
+ When Koppler runs from its source folder (not an installed plugin), every session is recorded: the canvas debug log is always on, and each app's previous log is kept next to it (`<app>.log.history`) when it restarts.
68
+ A finished Stop and revert ends the session: `koppler history done` gathers it into `<state>/sessions/<stop id>/` (summary, runs, the user's clicks and the messages they saw, the agent's messages, Koppler's start logs, app logs, the canvas log) and queues a `koppler-review` prompt.
69
+ On that prompt:
70
+ 1. Read `summary.md`, then the files it points at. Every problem the user ran into counts: failed or left-over runs, page errors, warnings they saw, buttons clicked over and over, questions you had to ask, error lines in app logs.
71
+ 2. For each one, find the cause in Koppler (engine, canvas, skill) and reproduce it the way the user hit it. File it as an issue (`references/reporting-bugs.md`), then fix it on a branch whose commits name the issue, and verify (tests, and a throwaway canvas for UI). Problems in the user's own app repos or the stack's facts are not fixed silently: list them with what you would change, and ask.
72
+ 3. Report one line per problem: what happened, the issue, the cause, the fix, how it was verified; and what you left for the user.
73
+ 4. Start `koppler listen as <id>` again, as after every prompt.
74
+
75
+ ## Routing
76
+
77
+ | Step | What | Where |
78
+ |---|---|---|
79
+ | 0 | What the stack needs on this Mac: `koppler doctor` (also run by every `koppler sync`, and shown on the canvas). Stop on `MISSING` and give the user the fix it prints. `PENDING` is a check written into the stack that waits for approval: tell the user to run `koppler doctor --approve` in their own terminal; never approve it for them. | inline |
80
+ | 0a | Several stacks: `koppler use` lists every one on this Mac (shared in cloned repos, private in `~/.config/koppler/stacks`); `koppler use <name or folder>` switches, and the canvas has the same switcher next to the name. Each stack keeps its own board, presets, history, live state and action log. Only one is in use at a time | inline |
81
+ | 0a2 | A stack linked from GitHub (`koppler link <url>`, or a link on the canvas) is a local copy in `~/.config/koppler/stacks/`: set the facts you learn with `koppler stack set` as usual. They show as changes not in the shared stack yet (`koppler link`). Opening a pull request with them (`koppler link pr`) pushes to the team's repo, so do it only when the user asks for it; never `discard` without being asked | inline |
82
+ | 0b | Stack and clones: `koppler sync` before every canvas open (reloads the stack, reads each clone's branch and changes; `--rescan` after the user clones something). With no config yet, `koppler init` once: it scans the user's repo folders (any git host) and finds `stack.json` files; if it finds several or none, ask which stack to use and rerun with `--stack <dir>`. Which clones belong to the stack is the user's choice, made on the canvas: `koppler init --pick` (automatic on a stack with no apps) opens the repo picker, and "Add repos" above the Apps list opens it later. Never add an app to `stack.json` that the user did not pick. | inline |
83
+ | 0c | Stack format, adding or fixing an app's facts. `koppler discover` (also the last step of `koppler init` once the stack has apps) first fetches every clone and fast-forwards a clean branch that is only behind (uncommitted or diverged work is reported, never touched; `--no-fetch` skips this), then reports every app's local ports, tunnels, calls, port conflicts and gaps against `stack.json`, with suggestions in `<state>/stack.suggestions.json`; check them in the repo before adding them to `stack.json`. `koppler discover --deployed` drafts each app's deployed address, settings, secret names and calls from the Terraform in its clone (and a SAM app's domains from its `samconfig.toml`) (names and plain addresses only), and the queues and topics between apps (who sends, who reads, what feeds them, which other clones name them) into `<state>/stack.deployed.json` (`queueSuggestions`) | `references/stack.md` |
84
+ | 0c2 | An app the stack does not have yet: `koppler add <repo or folder>` (from the user's clones), then fill in its facts with `koppler discover`; `koppler remove <app>` takes one out. An outside system (SaaS, payment provider, a webhook sender): `koppler add --outside "<name>" --called-by <app,...> --sends-to <app,...> [--url <address>] [--why "<why>"]` | inline |
85
+ | 0d | A stack from a description (words or a diagram): find each named app (`koppler discover --find`), confirm facts from the repos, propose the stack additions and a board, make them with `koppler stack set` after a yes | `references/research.md` |
86
+ | 1 | Get the graph: from chat, or the canvas (below). A saved board or team preset runs in one go: `koppler up --board "<name>"` (`koppler use --boards` lists them) | inline |
87
+ | 2 | Per app: branch, wiring overrides, temporary edits (`koppler recipes --apply` first: remembered changes) | `references/preparing-nodes.md` |
88
+ | 3 | Per app: prerequisites (AWS SSO, tunnels, hosts, dev certs, files) | `references/preparing-nodes.md` |
89
+ | 3b | What the user has to do (AWS sign-in, hosts entries, the HTTPS certificate, secrets, `sudo` steps): `koppler doctor <every local app> --env <env>` checks each one live and words it for someone who never ran the backend. Hand the user exactly its numbered steps (secrets in their own terminal, never with `!`); with `--json`, put them on the cards as `user` steps with `copy` | `references/secrets.md` |
90
+ | 4 | Run (auto) or checklist (manual) | inline |
91
+ | 4a | Something does not answer: `koppler logs <name>` (secrets masked, `-f` follows), and `koppler doctor --flow` tests every connection of what runs, like the canvas's Flow check. To see where one request failed across the apps, `koppler traces start`, restart the apps involved, repeat the request, then `koppler traces open <app>` | inline |
92
+ | 4b | Canvas open (`koppler canvas`): keep each card's checklist current with `koppler status step <app> "<label>" <state>`, so it shows what is done, what waits for the user and what failed | `references/status.md` |
93
+ | 5 | Stop and revert: Koppler does it (`koppler down`); you only finish what it left (canvas button, tag `koppler-stop`, or asked in chat) | `references/action-log.md` |
94
+ | 6 | What a run will do, and marking it done: `koppler plan`, `koppler history done`, `koppler history` | inline, section 1 |
95
+
96
+ ## 1. Canvas
97
+
98
+ Open it with `koppler canvas` (starts it when needed and opens it; the address stays the same across restarts), then `koppler listen as <id>` (run_in_background; it exits with the prompts as JSON lines; start it again after each).
99
+ The user answers on the canvas (its inbox) or in the chat.
100
+
101
+ **Nothing happens that the user has not seen and confirmed.** Before a run or a Stop leaves the canvas, a review lists what it will start, restart, stop and revert, and keep running, compared with what is running now; the confirmed plan comes with the prompt (`data.plan`, `PLAN` lines in the text).
102
+ - A prompt's `waited` is how many minutes it sat in the queue before you got it; only the newest run or Stop is handed over, older ones are already marked replaced. Over 5 minutes, tell the user what it is and how old it is, and ask before applying it.
103
+ - First run `koppler plan`: it works the plan out again against what is really running. If it says `DIFFERS`, things changed since the user confirmed: tell them what differs and ask before doing anything.
104
+ - Do exactly the plan: `STOP AND REVERT` apps are stopped and only their own changes reverted (their action-log entries, and the tunnels nothing else needs); `RESTART` apps are rewired and restarted for the listed reason; `START` apps are started; `KEEP RUNNING` apps are not touched, not even restarted.
105
+ - Anything the plan does not list (another app, another branch, another environment, a restart to fix something) needs the user's yes first.
106
+ - `koppler history done` only after every `START` and `RESTART` line really happened and each app answers; if you skipped or could not do one, it is `koppler history done --failed "<which and why>"`, never plain `koppler history done`.
107
+ - Check before you explain: when a result looks wrong (an empty query, an unexpected value), verify the cause (the data, the currency, the filters) before blaming the tunnel, the environment or the user's setup.
108
+ - When done: `koppler history done` (or `koppler history done --failed "<what went wrong, in plain words>"`). After a Stop and revert: `koppler history done <id>` with the prompt's `data.id`. That board becomes the live state the next plan is compared with, and the run shows as Done or Failed in the canvas History.
109
+ - A Stop whose `data.switchTo` is set comes from switching stacks on the canvas: stop and revert as usual, `koppler history done <id>`, then `koppler use "<switchTo>"`. The canvas then loads that stack by itself. `koppler use` refuses while anything is still running.
110
+ - A newer run replaces one you have not started; if one arrives while you work, finish the step in progress, then run `koppler plan` again.
111
+
112
+ Either returns a `koppler` prompt whose `data` is `{ env, mode, note?, nodes: [{id, branch, run, platform?, tunnels?, changes?, note?}], edges: [{from, to}], deployed: [{from, to}], queues?: {<queue>: 'local'}, layout }`; the canvas server keeps the layout itself. A `koppler-stop` prompt means stop and revert.
113
+ A `koppler-fix` prompt comes from the canvas's Flow check: `data.failures` lists each failed connection (`connection`, `result`, `detail`, and the ids it was tested with). Find the cause of each in the logs, the launch profiles and `koppler doctor`, tell the user what you would change with `koppler say`, and ask before starting, stopping or restarting anything; it confirms no plan by itself.
114
+ `tunnels: false` means the user wants only that app's API: open none of its tunnels, add no hosts entries for them, leave it out of `koppler plan`, pass it to `koppler doctor` with `--no-tunnels <id>`, and start the app anyway.
115
+ `note` is the user's own instruction, for the whole run or for one app (e.g. a feature flag, a test account, an extra env var): apply it like the stack's facts, record any change it causes in the action log so it is reverted too, and ask when it is unclear or conflicts with the stack.
116
+ `run` is `cli`, `ide` or `mock` (Koppler answers the app's `mock` responses from the stack in its place: nothing to prepare for it, and its callers are wired to it like to a local app); a mobile app node with `run: cli` also carries `platform`: `ios` (simulator), `android` (emulator) or `phone` (a connected phone on the same Wi-Fi; its callees run with `--lan`). With `ide` the device is picked in the IDE, so ask which one before wiring the mobile app.
117
+ Apps not cloned are greyed out; `koppler sync` prints the clone command. Ask before cloning.
118
+ Boards: the canvas always shows one board, saved as the user changes it; the selector switches between the user's boards and the team boards (the stack's `presets`). A team board the user changes becomes their own copy. When the user asks for a usual setup, suggest the matching board.
119
+ To change a board ("trim it to the apps in flow X", "add the apps that call Y"), use `koppler board edit "<name>" --add <app,...> --remove <app,...> --cable <caller>:<callee> --uncable <caller>:<callee> --branch <app>=<branch> --note <app>="<note>"`, never `state.json` or a board file: an open canvas makes the change and shows it, and the cards it does not touch keep their places. A team board changed this way becomes the user's own copy. A board that does not exist yet is made with `koppler board new "<name>"`, which takes the same `--add`, `--cable`, `--branch` and `--note`. When the change is your own idea rather than something the user asked for, add `--propose`: nothing changes until the user says yes in the canvas inbox, and their answer comes back as a `koppler-reply` prompt. Add `--arrange` whenever the change adds apps or cables, so the board is laid out as Arrange does instead of the new cards being put beside the others; leave it off only when the user has placed the cards by hand.
120
+ Whenever you make, change, share or run a board from the chat (`koppler board new`, `edit`, `share`, `koppler up --board`) and the user did not open it on the canvas themselves, end with `koppler canvas --board "<name>"`, every time, so they see the result.
121
+ After changing the stack, clones or anything else the canvas shows from outside it, run `koppler refresh`: it syncs, and an open canvas reads the stack and its board again without reloading the page.
122
+
123
+ ## 4. Run or checklist
124
+
125
+ **Koppler starts a confirmed run itself.** On an auto run from the canvas, the canvas server runs `koppler up --run <id>`: stops and restarts from the plan, branches as chosen on the cards (`koppler switch`), remembered changes (`koppler recipes --apply`), each cable's local address passed to its caller at start, tunnels, queues switched to local (an AWS emulator, `references/stack.md`), then apps callees first, each waited for; then it marks the run done.
126
+ You get the run only when Koppler could not do all of it (the prompt says what it did and what is left, `data.koppler`), when it has a note, or when it is a checklist. Then do only what is left: never restart what Koppler already started, and finish with `koppler history done`.
127
+ A cable left for you ("point it at ...") means its caller is not started yet: make the change, log it (`references/action-log.md`), then run the same `koppler up --run <id>` again, which starts the caller and marks the run done.
128
+ To check data (after a fix, for a report), propose `koppler query <app> --env <env> "<one SQL statement>"`: read-only, the stored connection, rows only. On PROD the user runs it in their own terminal and tells you what matters; never run it yourself there, the rows are production data.
129
+ Allowing the safe `koppler` commands without prompts: `references/permissions.md`.
130
+ When the user asks in chat, start with `koppler up <apps> --cable <caller>:<callee>... --env <env>` too: one `--cable` per call that should reach a local app. It is recorded like Run on the canvas and added to what already runs on that environment, so the canvas, the history and Stop all see it. A single `koppler run <app>` is not part of any run; the canvas shows it as running outside a run, so use it only for one-off commands, never to start a board. If you are blocked from starting something (auto mode, PROD), never hand over a list of commands: point the user to press Run on the canvas, or give the single `koppler up <apps> --env <env>` line.
131
+
132
+ ### A board asked for in chat
133
+
134
+ A sentence such as "run koppler on qa with the checkout board" names a board, and often an environment or another task next to it.
135
+ 1. Find the board: `koppler use --boards`, matched loosely on the name (a typo or a part of it is fine). With several matches or none, list them and ask.
136
+ 2. Start it: `koppler up --board "<name>" --env <env>` (without `--env`, the stack's first environment).
137
+ 3. Open it: `koppler canvas --board "<name>"`, every time, also when the canvas already runs. It opens that board in the browser.
138
+ 4. Paste the canvas address (`<state>/canvas-url`) in the chat reply as a link, next to what is running.
139
+
140
+ Do steps 3 and 4 even when step 2 is blocked or fails, so the user can press Run on the board themselves.
141
+
142
+ Order callees before callers (topological over edges); cycles are fine, start either.
143
+ Shared prerequisites first: AWS logins, then tunnels, then apps in order.
144
+ Plan tunnels with `koppler plan <every local app> --env <env>` and follow it: one tunnel per host and port (apps share it); a moved tunnel runs on its new port, and `koppler up` starts its apps with the new address (for an app started from the IDE, its `env` setting goes into the app's `-LOCAL` profile; log it as a profile change); a tunnel on a loopback alias needs the user to run the printed `sudo` commands first.
145
+
146
+ - `auto`: start tunnels and apps with `koppler`. Its processes are detached: they outlive agent background-job limits and Claude Code restarts, log where the canvas reads, and stop by name.
147
+ - Stack apps of any kind: `koppler run <app> [--env <env>] [--device ios|android|phone|<id>]` (each kind has its own rules): .NET runs its `-LOCAL` profile; Flutter builds and launches on the device, keeps its log on the card and closes the app on stop; Node and web front ends run `npm run` of their `script` (else `dev` or `start`), with the framework's default port and, with `--lan`, its own host flag. It loads the app's stored secrets (the only way the agent may start an app with them; never `with-secrets`, which stays denied), first tests each one against its own address (so open tunnels before apps; on a production env add `--test` only when the user agreed to the extra Keychain prompt), and refuses a port that is already held. `--dry-run` prints the command without starting it, e.g. for a manual-mode checklist.
148
+ - Anything else: `koppler run <app or tunnel name> [--cwd <repo>] [--port <p>] [--bind <address>] [--restart] -- <command>`; an arbitrary command never gets secrets. Use `--restart` for tunnels, and `--bind` for one that keeps its port on its own address (`koppler plan` prints it).
149
+ Wait for each app's `url` to answer (or a startup error in its log) before the next. An `ide` node still runs from the CLI unless the user said they start it in the IDE.
150
+ A Flutter app the user starts from the IDE still gets its log on the card: `koppler run <app> --cwd <repo> --restart -- fvm flutter logs -d <device id>` (`references/status.md`).
151
+ When `koppler run` reports a held port: if it is the same app (same repo binary, expected `LAUNCH_PROFILE`/env, e.g. started from Rider), reuse it; if it is anything else, ask before stopping it.
152
+ - `manual`: the checklist below, one block per app.
153
+
154
+ Logins open the browser: run `aws sso login --profile <p>` in the background and poll `aws sts get-caller-identity --profile <p>` until it passes.
155
+ Only short waits stay agent background jobs (`aws sso login`, `koppler listen`, with `timeout: 7200000`); never start a tunnel or app that way.
156
+
157
+ ## Output format
158
+
159
+ Start with one stack line (env, mode, local apps, what stays deployed), then one block per app in start order:
160
+
161
+ ```
162
+ ### <App name> (<repo path>, <branch>)
163
+ - [ ] Changes: <diff or "none"> (<profile>)
164
+ - [ ] Prerequisites: <each login / tunnel / hosts / cert / secret, with status>
165
+ - [ ] Start: <command or IDE profile> → <url>
166
+ - [ ] Check: <url> answers
167
+ ```
168
+
169
+ In `auto`, tick what you did and leave unticked only what needs the user. End with `koppler status` (what is running, ports) and how to stop it (`koppler stop <name>`, or Stop and revert).
@@ -0,0 +1 @@
1
+ 0.0.2
@@ -0,0 +1,50 @@
1
+ # Action log and stop / revert
2
+
3
+ Stop and revert (canvas button, prompt tag `koppler-stop`, or asked in chat) means: **stop every process the agent started, and revert every temporary code change** (including ones the log does not know about).
4
+ The action log lists exactly what was started and changed. It is written through `koppler actions`, never by hand.
5
+
6
+ ## Action log
7
+
8
+ In `auto` mode, log one entry per change with `koppler actions add '<json>'` (or `add --file <json file>` for a list) just before you make the change, so a change interrupted part-way still has its entry. The command checks the entry, wants every `file` as a full path, and refuses a secret value. `koppler actions` lists the log (`--json` for the entries).
9
+ If a command says the log is damaged, stop and tell the user: never start it afresh, since every entry in it is a change Stop must undo. Koppler's own commands refuse the same way and keep a copy (`actions.json.damaged-<time>`).
10
+ Give every entry its `app`, so that when a later run drops one app from the board, only that app's entries are stopped and reverted and removed from the log; a full Stop and revert still empties it.
11
+ The log says how to undo things; which runs and Stops happened, and how they ended, is the run history (`koppler history`, `<state>/history.jsonl`, written by the canvas and `koppler history done`).
12
+
13
+ | `kind` | Fields |
14
+ |---|---|
15
+ | `edit` | `app`, `file`, `before`, `after` (exact strings), `recipe?` (set by `koppler recipes --apply` for a remembered change) |
16
+ | `profile` | `app`, `file` (the full path, like every `file` in this log), `name`, `created` (bool), `previous` (env object, when an existing profile was updated) |
17
+ | `process` | `name` (the `koppler run` name), `command`, `port?`. For the rare process not started through `koppler` (e.g. `flutter run` in the background): `label`, `pid` (the program's own pid, not a shell wrapper: find it by the listening port or the first word of `ps -o command=`, never with a bare `pgrep -f`), `command`, `port?` |
18
+ | `env-file` | `app`, `file` (e.g. `.env.local`), `lines` added, `created` (bool: Koppler created the file) |
19
+ | `file` | `app`, `file`, `before` (the file's text before the run, `null` when Koppler created it), `after` (the text Koppler wrote last): a local library's `pubspec_overrides.yaml`; Stop puts `before` back or removes the file, and leaves a file that no longer reads as `after` for you to sort out |
20
+ | `npm-local` | `app`, `repo`, `packages`: local libraries installed with `npm install --no-save`; Stop runs `npm install` to go back to the published ones, after the branch is back |
21
+ | `checkout` | `app`, `repo`, `from` (a branch, or a commit with `detached`), `to`, `stash?` (its label), `carried?`; written by `koppler switch`, do not add it yourself. It stays until the clone is back and its set-aside changes are too: when those clash, the clone is left clean, they stay in the stash, and the user takes them back with `git stash pop` |
22
+ | `secret` | `app`, `env` (name only) |
23
+
24
+ `koppler history done` copies the code changes in this log (`edit`, `profile`, `env-file`, `checkout`) into the run's history entry, with secrets masked, so the canvas History shows them as diffs even after a revert. Log each change before calling it.
25
+
26
+ ## Koppler does it
27
+
28
+ Stop and revert from the canvas runs `koppler down --run <id>` (by hand: `koppler down`): it stops everything Koppler runs except the canvas, undoes this log in reverse (edits, env files, launch profiles, then `koppler switch --back`), looks for unlogged `koppler:` markers, and marks the Stop done.
29
+ You get the Stop only for what it left (listed in the prompt; those entries stay in the log): handle exactly those by the rules below, then `koppler history done <id>`.
30
+
31
+ ## Stop
32
+
33
+ Stop only processes the agent started, as recorded in the log:
34
+ - `name` entries: `koppler stop <name...>` (it stops the whole process tree, `TERM` then `KILL` after 8 s). `koppler status` lists what Koppler runs, after a Claude Code restart too; something there the log does not know about was started by the user, so leave it.
35
+ - `pid` entries: only if the `pid` still runs the logged `command`; send `TERM`, wait up to 10 s, then `KILL`.
36
+ Check each logged `port` is free afterwards.
37
+ Leave running: anything the user started (IDE sessions, their own terminals, tunnels they opened), AWS SSO logins, and anything not in the log. Name what was left running in the report.
38
+
39
+ ## Revert
40
+
41
+ 1. **Logged edits**, in reverse order: replace `after` with `before`. If `after` is no longer in the file, report it and leave the file alone.
42
+ 2. **Unlogged edits:** search each stack repo for `koppler:` markers (`git -C <repo> grep -nE "koppler:.*do not commit"`). Revert each with the matching `edit` entry's `before`; if there is none, restore that line from `git show HEAD:<file>` when the rest of the file has no other changes, otherwise show the line and ask.
43
+ 3. **Env files:** remove the logged `lines` from `file`, one occurrence each, the last one, so a line of the user's own that reads the same stays; delete the file if Koppler `created` it and nothing else is left in it.
44
+ 4. **Launch profiles:** remove profiles the skill added (logged `created` profiles, plus any profile whose name ends in `-LOCAL`); restore `previous` env for updated ones. Keep the rest of `launchSettings.json` byte-for-byte; if the file then equals `HEAD`, it is clean again.
45
+ 5. **Checkouts:** `koppler switch <app> --back` for each: it switches back and brings set-aside changes back. If it reports new uncommitted changes, tell the user and leave that app on its branch.
46
+ 6. **Secrets:** leave stored values; mention `bin/secret-list` so the user can decide.
47
+
48
+ Finish by checking `git -C <repo> status --porcelain` for each stack repo shows no change the skill made, empty the log with `koppler actions clear` (`--app <app>` when only one app was dropped), and report one line per stopped process and reverted change, plus anything left as is and why.
49
+
50
+ > This command modifies state.
@@ -0,0 +1,25 @@
1
+ # Letting the agent run Koppler without prompts
2
+
3
+ Koppler starts a confirmed run and a Stop and revert itself (`koppler up`, `koppler down`), so the agent mostly checks and finishes leftovers.
4
+ These commands read state or act only on what Koppler already runs, so they are safe to allow without a prompt on QA:
5
+
6
+ ```
7
+ koppler status
8
+ koppler logs
9
+ koppler plan
10
+ koppler history
11
+ koppler doctor
12
+ koppler listen
13
+ koppler say
14
+ koppler actions
15
+ koppler stack get
16
+ koppler stack locks
17
+ ```
18
+
19
+ In Claude Code that is a `permissions.allow` list in `.claude/settings.json`, one `Bash(koppler <command>:*)` entry per line above.
20
+ Other agents have their own allow lists; use the same commands.
21
+
22
+ Leave these asking every time, because they start or stop things, change code, branches or the stack, may touch production, show its data, or touch secrets: `koppler run`, `koppler up`, `koppler down`, `koppler stop`, `koppler switch`, `koppler recipes`, `koppler stack set`, `koppler stack unset`, `koppler query`, `koppler secret`.
23
+ `koppler stack unlock` asks the person at the terminal, like approval.
24
+ `koppler doctor --approve` cannot be allowed at all: it asks the person at the terminal, and refuses anywhere else.
25
+ On PROD the user's Run on PROD on the canvas is the confirmation; `koppler query` stays in the user's own terminal.
@@ -0,0 +1,62 @@
1
+ # Preparing local nodes
2
+
3
+ Facts come from `<state>/registry.json` (a copy of the shared `stack.json`; read its `notes`) and `<state>/local.json` (its `apps`, written by `koppler sync`: clone path, branch, dirty count).
4
+ For an env other than the first, each field in `apps[id].envs.<env>` replaces the top-level one first (lists such as `prereqs` and `secrets` are replaced whole, never merged).
5
+ Work in `LOCAL.apps[id].path`, the user's real clone, never a worktree.
6
+ Every change below is proposed with the exact diff or command and applied only after the user confirms.
7
+
8
+ ## Remembered changes
9
+
10
+ Before working out any temporary edit, run `koppler recipes --apply <every local app>`: it applies the remembered changes (`koppler recipes`) that match the latest Send's environment and cables, logs them for Stop, and says which no longer fit because the code moved.
11
+ For an edit you still have to make, log it first with `koppler actions add`; once the app runs with it, ask the user "Remember this for next time?" and on yes run `koppler recipes --remember <app> --why "<plain reason>" --when <always|calls:<app>|env:<env>>`. Add `--any-os` only when nothing in the edit depends on the operating system. It is shared through stack.json unless it names a path, alias or user only this machine has; then it stays private.
12
+ A recipe that moved: make the edit again by hand, then offer to remember the new one.
13
+
14
+ ## Branches
15
+
16
+ If a node's `branch` is set and differs from `LOCAL.apps[id].branch`, run `koppler switch <id> <branch>`; it logs the switch itself.
17
+ With uncommitted changes the node carries the user's choice from the card, `changes: set-aside` or `take-along`: pass it as `--set-aside` or `--take-along`. Never pick one yourself; without it (a chat request), ask which.
18
+ If it says the changes clash with the branch, tell the user and leave the app on its branch.
19
+
20
+ > This command modifies state.
21
+
22
+ ## Wiring
23
+
24
+ For each edge `from -> to`, run `koppler plan --wire <from> <to> [--device ios|android|phone|<id>]`: it prints the setting or the file and key, and the value to use, worked out for the device a mobile app runs on (simulator `localhost`, Android emulator `10.0.2.2`, a phone the Mac's network address and the callee's http port). Then:
25
+
26
+ - `how: env`: add a launch profile `<baseProfile>-LOCAL` (e.g. `QA-LOCAL`, `PROD-OPS-LOCAL`) to `from`'s `launchSettings`, copied from `baseProfile`, with each listed env var set to the printed value.
27
+ Reuse the profile if it exists and only update its env vars. Keep a `note`'s path requirements (e.g. a callee URL that must keep a path suffix).
28
+ - `how: edit`: make the minimal temporary edit at `file` / `key` with the printed value, marked with a `koppler: do not commit` comment.
29
+ A mobile node's `platform` is the device (`ios`, `android` or `phone`); with `run: ide` there is no `platform`, so ask which device the user will pick.
30
+ - `how: env-file`: add the printed lines to the printed file (`.env.local` by default: git-ignored and read on top of `.env` by Vite, Next.js and Create React App), creating it if needed; log it as `env-file`.
31
+ For a browser front end, also set the printed CORS setting in the callee's `-LOCAL` profile, and pass on any warning (no recorded CORS setting, an https page calling an http API).
32
+ - `how: unknown`: find how the caller picks this address in its repo, apply it, and record the real `how` with `koppler stack set apps.<caller>.consumes.<callee>.how <how>`.
33
+ - When it says the callee must listen on the network (a phone), start the callee with `koppler run <to> --lan`.
34
+
35
+ Overrides named in a prerequisite `note` (e.g. an `AWS_PROFILE`) go into the same `-LOCAL` profile.
36
+ Leave out of the `-LOCAL` profile any variable the app has as a stored secret (its `secrets[].env`): a launch profile's own value wins over the environment, so it would hide the secret, and `koppler run` refuses to start until it is removed.
37
+ A node with no edges and no overrides runs its `baseProfile` unchanged.
38
+
39
+ > This command modifies state.
40
+
41
+ ## Prerequisites
42
+
43
+ Collect every local node's `prereqs`, remove duplicates, check before asking:
44
+
45
+ | Kind | Check | If missing |
46
+ |---|---|---|
47
+ | `sso` | `aws sts get-caller-identity --profile <p>` | user runs `aws sso login --profile <sso>` (a `source_profile` role needs its source logged in) |
48
+ | `tunnel` | `lsof -iTCP:<port> -sTCP:LISTEN` | user starts `alias` in their own terminal |
49
+ | `hosts` | `grep <host> /etc/hosts` | user adds the line (needs sudo) |
50
+ | `devCerts` | `dotnet dev-certs https --check --trust` | `dotnet dev-certs https --clean && dotnet dev-certs https --trust` |
51
+ | `file` | the path exists | follow the entry's `note` or its `secrets` fetch steps |
52
+ | `nuget` | the repo restored before (`obj/project.assets.json`) | README GitHub packages setup with a `read:packages` token |
53
+
54
+ Two tunnels on the same `port` conflict; the stack's `notes` say which one can move and how.
55
+
56
+ For a production env (`prod`): say so up front, and confirm before opening a production DB tunnel or anything that swaps secrets.
57
+
58
+ ## Cleanup, when asked
59
+
60
+ Revert every `koppler: do not commit` edit and remove the `-LOCAL` profiles, unless the user wants to keep them.
61
+
62
+ > This command modifies state.
@@ -0,0 +1,76 @@
1
+ # Reporting a bug or a feature request in Koppler
2
+
3
+ Every problem with Koppler itself goes to a GitHub issue on `evan-mcgeek/koppler`: the `koppler` command, the canvas, the engine, this skill and its references, and the docs.
4
+ That includes a wrong or missing instruction in this skill, a confusing message, and a step you had to work around.
5
+ Something Koppler cannot do yet, that the user asks for or that would have saved them a step, is a feature request: an issue too, labelled `enhancement`, with what they wanted to do and why instead of the logs.
6
+ Problems in the user's own apps or in the stack's facts are not Koppler bugs: tell the user, and fix the stack with `koppler stack set` as usual.
7
+
8
+ Never work around a Koppler bug silently, and never keep it only in `<state>/NOTES.md`.
9
+
10
+ ## Steps
11
+
12
+ 1. **Reproduce it** the way the user hit it, in the smallest setup that still shows it. If it does not reproduce, say so in the issue and describe exactly what happened instead.
13
+ 2. **Find the cause** in Koppler's code or instructions, as far as you can, and a possible fix.
14
+ 3. **Look for an existing issue:** `gh issue list -R evan-mcgeek/koppler --state all --search "<a few words>"`. If one matches, add your evidence to it as a comment instead of opening another.
15
+ 4. **Gather the logs:** `koppler history session` sums up what happened since the last finished Stop and keeps the files on this Mac, for you to read.
16
+ For the issue itself, `koppler status --debug [name...]` prints the versions and the recent canvas debug log, latest run log and each named app or tunnel log, scrubbed. Keep only the lines around the problem.
17
+ 5. **Read the scrubbed text yourself** before it goes anywhere. The scrubbing replaces secrets, the stack's app, repo, host and queue names, paths, user names, emails, ticket ids, account ids and IPs, but it cannot know every name in the user's own code (class names, project names). Replace anything that still names the user, their company, their apps or their data with a neutral word (Orders API, `acme`).
18
+ 6. **Show the user the draft** (title and body) and file it once they agree:
19
+ `gh issue create -R evan-mcgeek/koppler --label bug --title "<title>" --body-file <file>` (`--label enhancement` for a feature request).
20
+ Without `gh` or a GitHub login, give the user the draft to paste at https://github.com/evan-mcgeek/koppler/issues/new.
21
+ 7. **Put it on the project board** when you can write to it (`gh auth status` lists the `project` scope): a bug in **Bugs**, a feature request in **Feature Requests**. See [the board](#the-board).
22
+ 8. **Tell the user** the issue link and what they can do until it is fixed.
23
+
24
+ ## The issue
25
+
26
+ Title: what goes wrong, in plain words ("Stop leaves the Kafka broker running after a failed Send").
27
+
28
+ ```markdown
29
+ ## What happened
30
+ One or two sentences: what the user did, what they expected, what happened instead.
31
+
32
+ ## How to reproduce
33
+ 1. Numbered steps from a clean start, with the commands and the clicks.
34
+ 2. A minimal stack.json excerpt with neutral names, if the stack matters.
35
+
36
+ ## Logs
37
+ <scrubbed lines, only around the problem, in a ```text block>
38
+
39
+ ## Where it goes wrong
40
+ The file and line, or the instruction in the skill, and why it fails.
41
+
42
+ ## Possible fix
43
+ What you would change, and how it could be tested.
44
+
45
+ ## Environment
46
+ The first line of `koppler status --debug`, and the agent used.
47
+ ```
48
+
49
+ Never put in an issue: secret values, the user's stack.json as is, internal hostnames, company or app names, file paths under the user's home, screenshots of their apps.
50
+
51
+ ## The board
52
+
53
+ Every issue is a card on https://github.com/users/evan-mcgeek/projects/2, in one of five columns:
54
+
55
+ | Column | Holds | Option id |
56
+ |---|---|---|
57
+ | Bugs | Something broken, not started | `f75ad846` |
58
+ | Feature Requests | Something new or better, not started | `edf21591` |
59
+ | In Progress | Being fixed or built, on a branch | `47fc9ee4` |
60
+ | Testing | Merged, for the user to try | `394ec429` |
61
+ | Done | The user tried it, or it needed no trying | `98236657` |
62
+
63
+ To add an issue and set its column:
64
+
65
+ ```bash
66
+ item=$(gh project item-add 2 --owner evan-mcgeek --url <issue url> --format json --jq .id)
67
+ gh project item-edit --project-id PVT_kwHOBGpGts4BmUgx --id "$item" \
68
+ --field-id PVTSSF_lAHOBGpGts4BmUgxzhk-BfA --single-select-option-id <option id>
69
+ ```
70
+
71
+ `gh project item-list 2 --owner evan-mcgeek --format json` finds the item id of an issue already on the board.
72
+
73
+ ## While Koppler itself is worked on
74
+
75
+ When Koppler runs from its source folder, the same rule holds: file the issue first, then fix it on a branch whose commits name the issue (`Fixes #12`).
76
+ Move its card as the work goes: **In Progress** when the branch starts, **Testing** once it is merged and the user can try it, **Done** when they have.
@@ -0,0 +1,40 @@
1
+ # A stack from a description
2
+
3
+ The user describes a flow in words ("order updates from the Orders API to the app") or attaches a diagram (a drawing, a screenshot, an Excalidraw export).
4
+ You turn it into a proposed stack and a wired board. The description is a lead, not a fact: every fact comes from the repos.
5
+
6
+ ## 1. Read the description
7
+
8
+ List every named thing and every arrow between them:
9
+ - apps, APIs, lambdas, workers, front ends;
10
+ - databases, caches, queues, topics and event buses;
11
+ - outside systems (Salesforce, Firebase, Kafka, a SaaS).
12
+
13
+ Show the list back to the user in one short message and fix what they correct before searching.
14
+
15
+ ## 2. Find each one
16
+
17
+ `koppler discover --find "<name>,<name>"` searches the stack, then the clones on this Mac, then GitHub in the user's own organisations (through their `gh` login).
18
+ - One match: use it.
19
+ - Several or none: ask which, showing the candidates; never pick one yourself. An archived repo is rarely the one meant; say so.
20
+ - Not cloned: give the clone command and ask before cloning anything.
21
+
22
+ Things that never run locally (a SaaS, an S3 bucket) become apps with an `outside` reason, so the stack still names them.
23
+
24
+ ## 3. Confirm the facts from the repos
25
+
26
+ For every app that is cloned: `koppler discover` (local ports, tunnels, calls, conflicts) and `koppler discover --deployed` (deployed addresses, settings, secret names, calls from the Terraform).
27
+ Its kind, how it runs, its addresses and who it calls come from there, not from the description.
28
+ Where a repo disagrees with the drawing (a call that does not exist, a direction the other way round), say so and ask.
29
+
30
+ ## 4. Propose, then write
31
+
32
+ Show the proposal before writing anything:
33
+ - the `stack.json` additions (apps, their facts, `consumes` for each confirmed call, `outside` systems);
34
+ - a board: the apps and the cables of the flow, as a shared or local board;
35
+ - open questions, one line each.
36
+
37
+ Make them only after the user's yes, with `koppler stack set` (never by writing `stack.json`); the board is built on the canvas, or with `koppler board new` and `koppler board edit`, and it goes to the team with Share with team on the canvas or `koppler board share "<name>"`, which opens a pull request on the stack's repo; open one only when the user asks.
38
+ A shared stack's change is for the user to commit (or `koppler link pr` for a linked one).
39
+
40
+ Queues, topics and event buses go under `queues`, with `publishes` / `reads` on each app (`references/stack.md`); confirm both ends from the repos and the Terraform, as for a call.
@@ -0,0 +1,56 @@
1
+ # Secrets
2
+
3
+ The agent never reads, fetches, prints or sources a secret value: no `aws secretsmanager get-secret-value`, no reading the secrets store, no reading an app's secrets file.
4
+ Back this up with deny rules in the user's `~/.claude/settings.json` (`permissions.deny`):
5
+
6
+ ```json
7
+ "Read(~/.config/koppler/secrets/**)",
8
+ "Read(**/appsettings.secrets.json)",
9
+ "Read(~/.pgpass)",
10
+ "Bash(*pgpass*)",
11
+ "Bash(*koppler/secrets*)",
12
+ "Bash(*with-secrets*)",
13
+ "Bash(*secret-store*)",
14
+ "Bash(*find-generic-password*)",
15
+ "Bash(*koppler secret set*)",
16
+ "Bash(*appsettings.secrets.json*)",
17
+ "Bash(*secretsmanager get-secret-value*)"
18
+ ```
19
+
20
+ An app that loads its own secrets at runtime with the user's AWS login (e.g. from Secrets Manager or SSM) needs nothing here.
21
+
22
+ ## Store
23
+
24
+ Values live in the macOS Keychain, one item per secret (service `koppler:<app>` or `koppler:<app>@<env>`, account = the name); `bin/secret-store` is the only code that touches them, and the other scripts go through it.
25
+ A protected environment's store is created so that every read shows a Keychain prompt; the others are readable without one, by anything running as the user, so only the deny rules above keep an agent from them.
26
+ Which environments are protected is the stack's call (`protected` in `stack.json`), and without it every environment except the first; it is never decided by an environment's name.
27
+ Where there is no Keychain (or when `KOPPLER_SECRETS` names a folder) values live in private files instead: `<folder>/<app>.env`, default `~/.config/koppler/secrets` (dir 700, files 600). `bin/secret-store migrate` moves such files into the Keychain.
28
+ On the canvas, the Credentials panel lists what the apps on the board need for the chosen environment and can Set, Test and Remove a value; it never shows one, and fetching stays a command the user copies into their own terminal.
29
+ Each environment after the stack's first has its own store, named `<app>@<env>` (`koppler secret set orders-api@prod <NAME>`): `koppler run <app> --env <env>` loads only that one and refuses to start while a declared secret is missing from it, so a PROD value never replaces or leaks into QA.
30
+ `koppler doctor --secrets <app> [--env <env>]` tries each stored secret the app declares and prints only the outcome (`connected`, or a plain reason).
31
+ It tests Postgres (needs `psql`), MySQL (needs the `mysql` client) and Redis or Valkey (no client needed), in .NET `Key=Value;` form, as a URL, or as `host:port,password=...` for Redis.
32
+ A secret's optional `type` in `stack.json` (`postgres`, `mysql`, `redis`) says which one it is; without it the value's shape decides, and an ambiguous `Server=...;User Id=...` string asks for the type.
33
+ `koppler run <app>` runs the same test before it starts the app and refuses to start on a failure (`--no-test` starts anyway). A protected environment's store is only tested when asked (`--test`), because each read of it shows a Keychain prompt.
34
+ A value is only ever sent to the host named inside it. API keys and tokens are not tested: that would mean sending them to an address taken from `stack.json`, which anyone editing that file could point elsewhere.
35
+ `koppler secret psql <app or app@env> <NAME>` opens `psql` with a stored connection string, and `... --pgpass` adds its line to `~/.pgpass` (for `localhost` and `127.0.0.1`) so other database tools find the password; both are for the user's own terminal.
36
+ `--pgpass` refuses a protected environment's store: that password stays behind its Keychain prompt and is never copied into a plain file.
37
+ Scripts are in `<app>/bin/`:
38
+
39
+ | Script | Who runs it | What it does |
40
+ |---|---|---|
41
+ | `koppler secret set <app> <ENV_NAME>` | the user, in their own terminal | hidden prompt; prints only the key name and length |
42
+ | `<fetch> \| koppler secret set <app> <ENV_NAME> --stdin` | the user | stores a fetched value without showing it |
43
+ | `koppler secret list <app>[@<env>]` | anyone | key names only |
44
+ | `with-secrets <app> -- <command>` | the user | runs the command with the app's secrets in its environment only |
45
+ | `koppler run <app>` | the agent or the user | starts a stack app of any kind with its secrets, detached and logged; with only an app id it cannot run another command, and `koppler run <name> -- <command>` never gets secrets |
46
+
47
+ ## Asking the user
48
+
49
+ For each local node's `secrets` entry whose `when` applies:
50
+
51
+ - `env` entry: say what the value is and why it is needed, then give both `fetch.cli` and `fetch.ui`, ending in `koppler secret set <app> <NAME>` (piped with `--stdin`, or typed at its prompt).
52
+ Tell the user to run them in their own terminal, not with `!` (its output lands in the conversation).
53
+ The app then runs through `koppler run <app>` (or `with-secrets <app> -- <run command>` in the user's terminal); for a Rider run, the user points the EnvFile plugin at `~/.config/koppler/secrets/<app>.env`.
54
+ - `file` entry: give `fetch.cli` / `fetch.ui` and the `note`; the user places the file. Warn when the path is tracked in git.
55
+
56
+ Confirm with `koppler secret list <app>` (names only) before handing over the run plan.