@knightcodeai/cli 0.18.4 → 0.20.0

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 (73) hide show
  1. package/CHANGELOG.md +90 -0
  2. package/dist/bundle/chunks/anthropic-messages-5ZM7Z2XQ.js +6 -0
  3. package/dist/bundle/chunks/azure-openai-responses-OUS4VJNA.js +2 -0
  4. package/dist/bundle/chunks/bedrock-converse-stream.js +3 -3
  5. package/dist/bundle/chunks/chunk-5ZES34QN.js +2 -0
  6. package/dist/bundle/chunks/{chunk-3VVG5HD5.js → chunk-6GRVIFTN.js} +2 -2
  7. package/dist/bundle/chunks/{chunk-FG6M5B3F.js → chunk-6KWPS3LA.js} +159 -124
  8. package/dist/bundle/chunks/chunk-6X7IXVQH.js +30 -0
  9. package/dist/bundle/chunks/{chunk-T6M3FJYR.js → chunk-B7A4GIBK.js} +1 -1
  10. package/dist/bundle/chunks/{chunk-3B66IKOZ.js → chunk-BWGDAXFH.js} +1 -1
  11. package/dist/bundle/chunks/{chunk-MXIWLMNT.js → chunk-CCP7VAJU.js} +1 -1
  12. package/dist/bundle/chunks/{chunk-L2XRQUGK.js → chunk-DFRYMT3C.js} +4 -4
  13. package/dist/bundle/chunks/{chunk-NGWSYXD5.js → chunk-IIWY7U5S.js} +3 -3
  14. package/dist/bundle/chunks/{chunk-SLWTIEJK.js → chunk-J63X3FRT.js} +1 -1
  15. package/dist/bundle/chunks/{chunk-HRZGVTRM.js → chunk-JMJS643J.js} +1 -1
  16. package/dist/bundle/chunks/{chunk-GCXF7ZNR.js → chunk-L6QGGIZG.js} +4 -4
  17. package/dist/bundle/chunks/chunk-MZ5UVOTP.js +2 -0
  18. package/dist/bundle/chunks/chunk-N5M3XNPN.js +2 -0
  19. package/dist/bundle/chunks/{chunk-XILUO467.js → chunk-RKNFSQUG.js} +1 -1
  20. package/dist/bundle/chunks/chunk-UU4A4NV5.js +2 -0
  21. package/dist/bundle/chunks/{chunk-2QXPCKUQ.js → chunk-VQBEVSZH.js} +1 -1
  22. package/dist/bundle/chunks/{cli-AMSWLH7A.js → cli-72YA573P.js} +1 -1
  23. package/dist/bundle/chunks/cloudflare-workers-ai-system-one-AWHNPEAT.js +2 -0
  24. package/dist/bundle/chunks/{easter-egg-3d-VH6SGOZS.js → easter-egg-3d-XXPCDCZV.js} +1 -1
  25. package/dist/bundle/chunks/{execute-4UQWU4SD.js → execute-KWIUMPDE.js} +1 -1
  26. package/dist/bundle/chunks/github-copilot.js +1 -1
  27. package/dist/bundle/chunks/google-generative-ai-HPLZ2ABU.js +2 -0
  28. package/dist/bundle/chunks/google-vertex-E3UTTTIG.js +2 -0
  29. package/dist/bundle/chunks/knightcode-messages-57CV36UV.js +8 -0
  30. package/dist/bundle/chunks/{llama-cpp-classify-HRPRKOZA.js → llama-cpp-classify-FEWC5ZIH.js} +2 -2
  31. package/dist/bundle/chunks/{mistral-conversations-NUSSE6NX.js → mistral-conversations-KT4C4XTZ.js} +1 -1
  32. package/dist/bundle/chunks/openai-codex-responses-BCLSSOJ6.js +10 -0
  33. package/dist/bundle/chunks/openai-completions-GTNAEXX6.js +7 -0
  34. package/dist/bundle/chunks/{openai-decisions-ENPSYANO.js → openai-decisions-CSQTW3S4.js} +1 -1
  35. package/dist/bundle/chunks/openai-responses-ILFXLMJW.js +4 -0
  36. package/dist/bundle/chunks/openrouter-images-J66HXAMB.js +2 -0
  37. package/dist/bundle/chunks/{runtime-IU2Z5WWF.js → runtime-OW5JKWBE.js} +1 -1
  38. package/dist/bundle/chunks/typesafe-system-one-QA5VXBEZ.js +2 -0
  39. package/dist/bundle/chunks/{virtual-modules-AK4BYDR6.js → virtual-modules-5DLTLPP7.js} +1 -1
  40. package/dist/bundle/cli-runtime.js +1 -1
  41. package/dist/core/export-html/template.css +9 -0
  42. package/dist/core/export-html/template.js +165 -45
  43. package/docs/app-server.md +350 -12
  44. package/docs/app-server.schema.json +11493 -2563
  45. package/docs/containerization.md +2 -2
  46. package/docs/declarative-views.md +118 -0
  47. package/docs/desktop.md +45 -4
  48. package/docs/docs.json +4 -0
  49. package/docs/extensions.md +4 -2
  50. package/docs/models-configuration.md +9 -0
  51. package/docs/models.md +8 -4
  52. package/docs/settings.md +53 -0
  53. package/examples/extensions/README.md +1 -0
  54. package/examples/extensions/desktop-view.ts +72 -0
  55. package/examples/extensions/gondolin/index.ts +8 -10
  56. package/examples/extensions/gondolin/package-lock.json +20 -30
  57. package/examples/extensions/gondolin/package.json +1 -1
  58. package/package.json +1 -1
  59. package/dist/bundle/chunks/anthropic-messages-VD2GN7C4.js +0 -6
  60. package/dist/bundle/chunks/azure-openai-responses-6IJ5JZ3Y.js +0 -2
  61. package/dist/bundle/chunks/chunk-3PSTVJEH.js +0 -2
  62. package/dist/bundle/chunks/chunk-4DOSUUX3.js +0 -30
  63. package/dist/bundle/chunks/chunk-CRLO4X52.js +0 -2
  64. package/dist/bundle/chunks/chunk-HHWP56PR.js +0 -2
  65. package/dist/bundle/chunks/cloudflare-workers-ai-system-one-JBMTTHVI.js +0 -2
  66. package/dist/bundle/chunks/google-generative-ai-QBWYWMCA.js +0 -2
  67. package/dist/bundle/chunks/google-vertex-4KVZWEAI.js +0 -2
  68. package/dist/bundle/chunks/knightcode-messages-3GNOTMSI.js +0 -8
  69. package/dist/bundle/chunks/openai-codex-responses-66UQNGWB.js +0 -10
  70. package/dist/bundle/chunks/openai-completions-QTYKORY2.js +0 -7
  71. package/dist/bundle/chunks/openai-responses-JSX72BKV.js +0 -3
  72. package/dist/bundle/chunks/openrouter-images-ZI7LLZ4Y.js +0 -2
  73. package/dist/bundle/chunks/typesafe-system-one-ETHWOYKC.js +0 -2
@@ -112,11 +112,11 @@ OpenShell inference routing can keep raw model credentials outside the sandbox.
112
112
 
113
113
  ## Route tools through Gondolin
114
114
 
115
- [Gondolin](https://github.com/KnightCodeAI/gondolin) is a local Linux micro-VM. Its example extension keeps the KnightCode process and file-based provider credentials on the host while routing the built-in tools and user `!` commands into the VM.
115
+ [Gondolin](https://github.com/earendil-works/gondolin) is a local Linux micro-VM. Its example extension keeps the KnightCode process and file-based provider credentials on the host while routing the built-in tools and user `!` commands into the VM.
116
116
 
117
117
  Commands inside the VM inherit the host process environment. Provider keys supplied through environment variables can therefore be visible inside the VM. Do not use this pattern as a credential boundary unless you remove sensitive variables or change the extension's environment handling.
118
118
 
119
- Gondolin requires Node.js 23.6 or newer and QEMU installed through your operating-system package manager.
119
+ Gondolin requires Node.js 22.19 or newer and QEMU installed through your operating-system package manager.
120
120
 
121
121
  ### Install the extension
122
122
 
@@ -0,0 +1,118 @@
1
+ # Declarative Views
2
+
3
+ A view is a small tree of data that describes what to draw: text, markdown, a
4
+ table, a progress bar, a button. An extension hands one to a widget, a tool
5
+ result or an extension message, and every client draws it with its own
6
+ components: the terminal with TUI components, the [desktop app](desktop.md) with
7
+ its own. A view is data only. It never holds HTML, scripts or URLs that load.
8
+
9
+ ```ts
10
+ import type { View } from "@knightcodeai/cli";
11
+
12
+ const view: View = {
13
+ type: "stack",
14
+ children: [
15
+ { type: "text", text: "2 checks failed", tone: "error" },
16
+ { type: "table", columns: ["check", "result"], rows: [["parser", "failed"], ["lexer", "failed"]] },
17
+ { type: "button", id: "rerun", label: "Run again" },
18
+ ],
19
+ };
20
+ ```
21
+
22
+ ## Nodes
23
+
24
+ | Node | Fields | Example |
25
+ | --- | --- | --- |
26
+ | `text` | `text`, optional `tone`: `muted`, `accent`, `success`, `warning`, `error` | `{type: "text", text: "Saved", tone: "success"}` |
27
+ | `markdown` | `markdown`; raw HTML shows as text, images do not load | `{type: "markdown", markdown: "**3** files"}` |
28
+ | `stack` | `children`, optional `direction`: `vertical` (default) or `horizontal` | `{type: "stack", direction: "horizontal", children: [...]}` |
29
+ | `list` | `items`, each a view, drawn as bullets | `{type: "list", items: [{type: "text", text: "one"}]}` |
30
+ | `table` | `columns` (at least one), `rows` of strings, each row as wide as `columns` | `{type: "table", columns: ["a"], rows: [["1"]]}` |
31
+ | `progress` | `value` (0 or more), `max` (over 0), optional `label`; a value over `max` draws full | `{type: "progress", value: 3, max: 10, label: "tests"}` |
32
+ | `button` | `id` (1 to 128 characters), `label` | `{type: "button", id: "reset", label: "Reset"}` |
33
+ | `field` | `id`, `kind`: `text`, `toggle` or `select`, `label`, `value`, and `options` for a select | `{type: "field", id: "mode", kind: "select", label: "Mode", value: "fast", options: ["fast", "slow"]}` |
34
+
35
+ ## Rules and limits
36
+
37
+ A view that breaks a rule is refused as a whole:
38
+
39
+ - No members other than those above, and no other node types.
40
+ - Stacks and lists nest at most 8 levels.
41
+ - Control ids (`button` and `field`) are unique within the view.
42
+ - A toggle's `value` is a boolean; a text or select field's is a string. A
43
+ select has `options` and its `value` is one of them; a text field or toggle
44
+ has no `options`.
45
+ - Its JSON is at most 64 KiB in a tool result or an extension message, and at
46
+ most 16 KiB in a widget.
47
+
48
+ The view is copied through JSON when it is used, so a getter, a `toJSON` or a
49
+ change you make to the object later does not change what was drawn. Call
50
+ `setWidget` again to change a widget.
51
+
52
+ Terminal escape sequences and control characters other than tab and newline
53
+ are removed from every string of the copy, so colour a view with `tone`, not
54
+ ANSI codes.
55
+
56
+ A thread shows at most 16 widgets and 50 statuses, with keys of 1 to 128
57
+ characters. Status text, the title and the working message are cut to 200
58
+ characters and kept on one line.
59
+
60
+ ## Widgets
61
+
62
+ ```ts
63
+ ctx.ui.setWidget("checks:summary", view, { placement: "aboveEditor" });
64
+ ctx.ui.setWidget("checks:summary", undefined); // remove it
65
+ ```
66
+
67
+ `placement` is `aboveEditor` (the default) or `belowEditor`. Widget and status
68
+ keys are shared by every extension of a thread, so prefix them with your
69
+ extension's name. In the terminal, an invalid view draws one warning line in
70
+ its place; the app server sends nothing and reports it once per key as a
71
+ warning.
72
+
73
+ ## Tool results and extension messages
74
+
75
+ Put a view in `details.view` and clients draw it in place of the text output:
76
+
77
+ ```ts
78
+ return { content: [{ type: "text", text: "parser: failed" }], details: { view } };
79
+
80
+ knightcode.sendMessage({ customType: "checks", content: "2 failed", display: true, details: { view } });
81
+ ```
82
+
83
+ Keep the text content meaningful: the model reads it, not the view, and a client
84
+ that cannot draw the view shows the text. An invalid view is ignored and the
85
+ text shows. In the terminal a tool's own `renderResult`, or a registered message
86
+ renderer, still wins over the view.
87
+
88
+ ## Buttons and fields
89
+
90
+ In a widget on the desktop app, buttons and fields are live. A press or a
91
+ change reaches every handler the thread's extensions registered:
92
+
93
+ ```ts
94
+ ctx.ui.onViewEvent?.((event) => {
95
+ if (event.widgetKey !== "checks:summary") return;
96
+ if (event.id === "rerun") runChecks();
97
+ if (event.id === "verbose" && typeof event.value === "boolean") verbose = event.value;
98
+ ctx.ui.setWidget("checks:summary", render());
99
+ });
100
+ ```
101
+
102
+ `event` is `{widgetKey, id, value?}`: `value` is a string for a text or select
103
+ field, a boolean for a toggle, absent for a button. A field shows the value in
104
+ the view, so answer a change by setting the widget again. `onViewEvent` is
105
+ present only where widgets are interactive; the `?.` keeps the code working
106
+ elsewhere. A handler that throws becomes a warning in the app.
107
+
108
+ ## Where views draw
109
+
110
+ | Client | Widgets | Tool results and messages | Controls |
111
+ | --- | --- | --- | --- |
112
+ | Terminal | yes | yes | drawn as text |
113
+ | Desktop app | yes | yes | live in widgets, disabled elsewhere |
114
+ | RPC, JSON and print modes | no | no | no |
115
+
116
+ [`examples/extensions/desktop-view.ts`](../examples/extensions/desktop-view.ts)
117
+ puts all of it together. For the protocol side see
118
+ [app-server.md](app-server.md#dialogs-and-extension-ui).
package/docs/desktop.md CHANGED
@@ -2,9 +2,10 @@
2
2
 
3
3
  The KnightCode desktop app is an Electron window over the [app server](app-server.md). It
4
4
  runs one `knightcode app-server` process and every window talks to it, so the desktop and
5
- the terminal share sessions, settings, credentials and extensions. Nothing about the agent
6
- is configured in the app itself: models, approval defaults and extensions come from your
7
- `settings.json`, the same file the terminal reads.
5
+ the terminal share sessions, settings, credentials and extensions. The agent's
6
+ configuration lives in the files the terminal reads (`settings.json`, `mcp.json` and
7
+ `tools.json`); the app's Settings edit those same files, so a change made in either place
8
+ shows in both.
8
9
 
9
10
  The app is in development and not yet published. Phase S1 (threads, transcript, composer,
10
11
  approvals) is usable from a source checkout.
@@ -48,11 +49,51 @@ on Windows, `~/Library/Logs/KnightCode` on macOS, `~/.config/KnightCode/logs` on
48
49
  a card with the decisions the server offers; Enter allows once, Escape denies, and Deny
49
50
  can tell KnightCode why. Full access asks once per thread, and Settings can hide it.
50
51
  - Settings: send key, queue or steer, theme (System, Light, Dark), default model, signing
51
- in to and out of providers, keyboard shortcuts, versions and logs.
52
+ in to and out of providers, keyboard shortcuts, versions and logs. Under Coding:
53
+ - Configuration: the agent's `settings.json` keys `/settings` offers (reasoning, compaction,
54
+ steering, images, transport and timeouts, cache warming, project trust, updates), and
55
+ per-model reasoning, for the user or for one trusted project. A row set in the file being
56
+ edited has a reset button. Keys without a row are one click away in the file.
57
+ - Tools: the KnightCode tools `/tools` manages (web fetch, web search with its provider and
58
+ Brave key, ask user, scratchpad, classifier gate with its model), the tools new threads
59
+ start with (`defaultTools`), and codemode.
60
+ - MCP servers: add, remove, turn on or off and set the exposure of the servers in the user's
61
+ or a trusted project's `mcp.json`, as `/mcp` and `knightcode mcp` do. Signing in to a
62
+ server and its live status stay with `/mcp` in a thread.
63
+ - Extensions & skills: load or skip each extension, skill, prompt template and theme you
64
+ added, as `knightcode config` does. Built-in extensions are always on and not listed.
65
+
66
+ Changes apply to threads that start or resume afterwards.
52
67
  - Archived threads: search, unarchive, or delete for good.
53
68
  - Command palette (Ctrl+K): actions and threads in one search.
54
69
  - Quitting while threads run asks first.
55
70
 
71
+ ## Extensions
72
+
73
+ Extensions load in desktop threads as they do in the terminal (`ctx.mode` is
74
+ `"desktop"`). What they show appears in the app:
75
+
76
+ - Dialogs (`select`, `confirm`, `input`, `editor`) and `ask_user`'s questions take
77
+ the composer's place; notices (`notify`) show as toasts.
78
+ - Statuses (`setStatus`) are chips in the thread's header, with the full text on
79
+ hover.
80
+ - `setTitle` names the window while the thread is open; the thread's own name stays.
81
+ - `setWorkingMessage` replaces the "Thinking" text while a turn waits on the model,
82
+ and `setWorkingVisible(false)` hides it.
83
+ - Widgets (`setWidget`) show in a panel above the composer, lines in a monospace
84
+ font, at most 10 per widget. A widget can be a
85
+ [declarative view](declarative-views.md) whose buttons and fields work: a press
86
+ reaches the extension's `ctx.ui.onViewEvent` handler.
87
+ - A tool result or an extension message with a view in `details.view` shows the view
88
+ in its row.
89
+
90
+ Terminal-only UI does not draw: widget factories, `custom()` components, header
91
+ and footer components, raw terminal input, the working indicator and editor access
92
+ (`setEditorText`, `getEditorText`, `pasteToEditor`, custom editors and autocomplete).
93
+ The app server notes each such call once per thread in `app-server.log`. A widget or
94
+ status the server refuses (an invalid view, too large, too many) shows a warning
95
+ once. The protocol side is in [app-server.md](app-server.md#dialogs-and-extension-ui).
96
+
56
97
  ## Keyboard shortcuts
57
98
 
58
99
  | Action | Default |
package/docs/docs.json CHANGED
@@ -121,6 +121,10 @@
121
121
  "title": "Build Extensions",
122
122
  "path": "extensions.md"
123
123
  },
124
+ {
125
+ "title": "Draw Declarative Views",
126
+ "path": "declarative-views.md"
127
+ },
124
128
  {
125
129
  "title": "Add Custom Providers",
126
130
  "path": "custom-provider.md"
@@ -387,8 +387,10 @@ Use `ctx.ui.custom()` only when the interaction needs its own rendering and inpu
387
387
  See [Terminal UI](tui.md) for component, focus, overlay, theme, and performance guidance.
388
388
 
389
389
  Extensions load in interactive, RPC, JSON, print, and desktop modes.
390
- Desktop mode currently has no extension UI (`hasUI` is false); terminal
391
- components remain unavailable even when desktop dialogs are added.
390
+ Desktop mode draws dialogs, notices, statuses, titles, working text and widgets,
391
+ including [declarative views](declarative-views.md) with live buttons and
392
+ fields; terminal components (widget factories, `custom()`, header, footer) do not
393
+ draw there. See [Desktop App](desktop.md#extensions).
392
394
  Interactive mode provides the complete terminal UI. The [DOOM overlay example](../examples/extensions/doom-overlay/) uses a custom overlay component to render a game frame by frame:
393
395
 
394
396
  <p align="center"><img src="images/doom-extension.png" alt="The DOOM overlay example running over a KnightCode session" width="750"></p>
@@ -185,3 +185,12 @@ Also applies to `azure-openai-responses` and `openai-codex-responses`.
185
185
  | Field | Type | Default | Description |
186
186
  |---|---|---|---|
187
187
  | `supportsMidConvoSystemMessages` | boolean | `false` | Whether the exact model accepts system messages after the conversation has started. When false, later system messages are folded into the leading system message. |
188
+
189
+ ### `typesafe-system-one`
190
+
191
+ Also applies to `cloudflare-workers-ai-system-one`.
192
+
193
+ | Field | Type | Default | Description |
194
+ |---|---|---|---|
195
+ | `imageInput` | `"assets" \| "images" \| "state-image-urls"` | | How the service takes classifier images. "assets" sends TypeSafe's assets map (PNG and JPEG) and references the images from the state, as TypeSafe and Jev on Workers AI expect. "images" sends Clef's images list of data URLs (PNG, JPEG, and WebP, at most four), as Clef on Workers AI expects. "state-image-urls" places image_url parts before the state, as OpenRouter expects; OpenRouter drops assets without an error. Auto-detected from the provider, baseUrl, and model ID. Classifier models read it from the provider-level compat; models and modelOverrides entries apply to chat models only. |
196
+ | `sendImageOptInHeader` | boolean | | Whether requests with images send the TypeSafe-Danger-Zone-Images: true header, which TypeSafe's own API requires for its experimental image input. Defaults to true for the typesafe-system-one API unless the model is detected as OpenRouter. |
package/docs/models.md CHANGED
@@ -141,20 +141,20 @@ Compatibility settings should describe verified differences in the endpoint's re
141
141
 
142
142
  ## Use classifier models
143
143
 
144
- Classifier models do not chat. They answer typed questions about JSON state: pick one of several choices, answer yes or no, or give a score, each with probabilities. KnightCode includes TypeSafe's Jev model from these providers, Cloudflare's Clef and Clef Flash models from Workers AI, and OpenAI's GPT-6 Luna through the [Decisions API](https://developers.openai.com/api/docs/guides/decisions):
144
+ Classifier models do not chat. They answer typed questions about JSON state: pick one of several choices, answer yes or no, or give a score, each with probabilities. KnightCode includes TypeSafe's Jev model from these providers, Cloudflare's Clef, Clef Flash, and Clef Omni models from Workers AI and OpenRouter, and OpenAI's GPT-6 Luna through the [Decisions API](https://developers.openai.com/api/docs/guides/decisions):
145
145
 
146
146
  | Provider | Model IDs | Authentication |
147
147
  |---|---|---|
148
148
  | `typesafe` | `jev-latest` | `TYPESAFE_API_KEY` |
149
- | `openrouter` | `typesafe/jev-1.13`, `~typesafe/jev-latest` | `OPENROUTER_API_KEY` or `/login` |
150
- | `cloudflare-workers-ai` | `typesafe/jev`, `@cf/cloudflare/clef`, `@cf/cloudflare/clef-flash` | `CLOUDFLARE_API_KEY` and `CLOUDFLARE_ACCOUNT_ID` |
149
+ | `openrouter` | `typesafe/jev-1.13`, `typesafe/jev-1.14`, `~typesafe/jev-latest`, `cloudflare/clef`, `cloudflare/clef-flash`, `cloudflare/clef-omni` | `OPENROUTER_API_KEY` or `/login` |
150
+ | `cloudflare-workers-ai` | `typesafe/jev`, `typesafe/jev-1.14.0`, `@cf/cloudflare/clef`, `@cf/cloudflare/clef-flash`, `@cf/cloudflare/clef-omni` | `CLOUDFLARE_API_KEY` and `CLOUDFLARE_ACCOUNT_ID` |
151
151
  | `vercel-ai-gateway` | `typesafe-ai/jev` | `AI_GATEWAY_API_KEY` |
152
152
  | `opencode` | `jev-1.13`, `jev-1.13-free` | `OPENCODE_API_KEY` |
153
153
  | `openai` | `gpt-6-luna` | `OPENAI_API_KEY` |
154
154
 
155
155
  A [llama.cpp router](llama-cpp.md#classification) lists decision models as native classifiers, and also lists each chat model as a classifier.
156
156
 
157
- OpenAI's Decisions API needs an API key. ChatGPT sign-in does not work with it, so `gpt-6-luna` is not listed as available while `openai` is logged in through `/login`, even when `OPENAI_API_KEY` is set; log out of `openai` to use the key. GPT-6 Luna also judges images passed in `images` (see [Codemode](codemode.md#classify)); other classifier models return an error for them. The API rejects inputs above 922K tokens, but requests that run longer than about five seconds, currently above roughly 600K input tokens, fail with a gateway timeout.
157
+ OpenAI's Decisions API needs an API key. ChatGPT sign-in does not work with it, so `gpt-6-luna` is not listed as available while `openai` is logged in through `/login`, even when `OPENAI_API_KEY` is set; log out of `openai` to use the key. The API rejects inputs above 922K tokens, but requests that run longer than about five seconds, currently above roughly 600K input tokens, fail with a gateway timeout.
158
158
 
159
159
  Classifier models do not appear in `/model`. The [classifier gate](usage.md#classifier-gate) uses one to screen risky tool calls. The model reaches them through the [`codemode`](cli.md#enable-codemode) tool, which is off unless an MCP server turned it on. Enable it with `"defaultTools": ["+codemode"]` in [settings](settings.md#tools). Scripts then list classifier models with `models.getAvailableOfType("classifier")` and call `models.classify(model, { state, questions })`:
160
160
 
@@ -175,6 +175,10 @@ return result.answers;
175
175
 
176
176
  [Codemode](codemode.md#classify) describes the question and answer types.
177
177
 
178
+ Classifier models whose `input` includes `"image"` also judge images passed in `images`; other classifier models return an error for them. Image input works with GPT-6 Luna, with Jev from `typesafe` and `cloudflare-workers-ai`, with Clef from `cloudflare-workers-ai`, and with the OpenRouter models whose catalog lists image input. Jev takes PNG and JPEG images, and image input is experimental on TypeSafe's own API. Clef takes PNG, JPEG, and WebP images, at most four per request. Jev from `vercel-ai-gateway` and `opencode` takes text only.
179
+
180
+ Each System One service takes images in its own request format, set by the model's `compat.imageInput`. Built-in models set it, so overriding a provider's `baseUrl` keeps it. A custom provider that proxies another service's classifiers sets it in its provider-level `compat` in `models.json` (see [System One compatibility](models-configuration.md#typesafe-system-one)). OpenRouter in particular answers requests in TypeSafe's format without an error but drops the images.
181
+
178
182
  When the service reports token counts, as all System One services do, `result.usage` carries them with their cost. KnightCode adds the usage of a script's classifier calls to the `codemode` tool result, so it counts toward the session cost in the footer and `/session`. The cost uses the model's catalog price; models without one, such as TypeSafe's direct `jev-latest`, report tokens at no cost.
179
183
 
180
184
  Extensions call classifiers through `ctx.modelRegistry.classify()`, without codemode. [Virtual models](virtual-models.md#route-requests) can use them to route requests; see the `jev-router.ts` example.
package/docs/settings.md CHANGED
@@ -119,6 +119,59 @@ See [Compaction Reference](compaction.md) for trigger, summarization, and valida
119
119
 
120
120
  See [Themes](themes.md) and [Terminal Setup](terminal-setup.md) for format and platform details.
121
121
 
122
+ ## Worktrees
123
+
124
+ Worktree settings belong to the user: they are read from the agent-directory
125
+ settings file only, never from a project's.
126
+
127
+ | Setting | Type | Default | Description |
128
+ | --- | --- | --- | --- |
129
+ | `worktrees.root` | string | `<agent dir>/worktrees` | Absolute directory that holds managed worktrees as `<root>/<4 hex digits>/<repository name>`. Worktrees made under an earlier root are no longer managed. |
130
+ | `worktrees.autoDelete` | boolean | `true` | Archiving a thread deletes its worktree, and the least recently used worktrees past `worktrees.keep` are pruned. Each deletion first keeps a snapshot under `refs/knightcode/snapshots/`. |
131
+ | `worktrees.keep` | number | `15` | Managed worktrees kept before pruning starts. |
132
+ | `worktrees.fetchBeforeCreate` | boolean | `false` | Fetch the base's remote before each new worktree. |
133
+ | `worktrees.branchPrefix` | string | `"kc/"` | Prefix of the branch a handoff to the main checkout creates. |
134
+
135
+ ### Environments
136
+
137
+ A project's environments live in `.knightcode/environments.json`, which can be
138
+ committed:
139
+
140
+ ```json
141
+ {
142
+ "version": 1,
143
+ "environments": [
144
+ {
145
+ "name": "node",
146
+ "setup": { "default": "cd \"$KNIGHTCODE_WORKTREE_PATH\" && npm ci" },
147
+ "cleanup": { "default": "rm -rf \"$KNIGHTCODE_WORKTREE_PATH/.cache\"" },
148
+ "actions": [{ "name": "Test", "command": { "default": "npm test" } }]
149
+ }
150
+ ]
151
+ }
152
+ ```
153
+
154
+ Each script has `default`, `macos`, `linux` and `windows` variants; the
155
+ platform's wins. Scripts run in bash on every platform (Git Bash on Windows, or
156
+ the `shellPath` setting), so one `default` usually covers all three; a platform
157
+ variant is for a command that differs. The setup script runs when a worktree is
158
+ created and the cleanup script before one is deleted, both at the project root,
159
+ with
160
+ `KNIGHTCODE_SOURCE_TREE_PATH` (the checkout the worktree came from) and
161
+ `KNIGHTCODE_WORKTREE_PATH` (the worktree) set. Since they start in the main
162
+ checkout, work on the worktree through `KNIGHTCODE_WORKTREE_PATH`, as the
163
+ example does: a bare `npm ci` would install into the main checkout. A failing setup keeps the
164
+ worktree and marks it `setupFailed`; a failing cleanup is a warning. An
165
+ untrusted project's scripts never run. `actions` are for the desktop app's
166
+ toolbar.
167
+
168
+ ### `.worktreeinclude`
169
+
170
+ Ignored files are not copied into a new worktree unless the repository root's
171
+ `.worktreeinclude` (gitignore syntax) lists them, such as `.env`. A file is
172
+ copied only if the worktree ignores it too, so it can never be committed or
173
+ handed off by accident, and nothing is created through a symlink or junction.
174
+
122
175
  ## Network and retries
123
176
 
124
177
  | Setting | Type | Default | Description |
@@ -32,6 +32,7 @@ cp permission-gate.ts ~/.knightcode/agent/extensions/
32
32
  |-----------|-------------|
33
33
  | `todo.ts` | Todo list tool + `/todos` command with custom rendering and state persistence |
34
34
  | `hello.ts` | Minimal custom tool example |
35
+ | `desktop-view.ts` | Declarative views: a widget with a live toggle and button (`ctx.ui.onViewEvent`), and a tool result drawn as a table |
35
36
  | `question.ts` | Demonstrates `ctx.ui.select()` for asking the user questions with custom UI |
36
37
  | `questionnaire.ts` | Multi-question input with tab bar navigation between questions |
37
38
  | `tool-override.ts` | Override built-in tools (e.g., add logging/access control to `read`) |
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Desktop View - declarative views in a widget, answering presses, and in a tool result
3
+ *
4
+ * Shows a widget with a toggle and a button, drawn by the terminal and by the desktop app from the same data
5
+ * (docs/declarative-views.md). In the desktop app the controls are live: a press reaches `ctx.ui.onViewEvent`, and
6
+ * the extension answers by setting the widget again. The `check_summary` tool returns a table view, which both
7
+ * clients draw in place of the tool's text output.
8
+ *
9
+ * Usage:
10
+ * knightcode --extension examples/extensions/desktop-view.ts
11
+ */
12
+
13
+ import { Type } from "@knightcode/ai";
14
+ import { defineTool, type ExtensionAPI, type ExtensionContext, type View } from "@knightcodeai/cli";
15
+
16
+ const WIDGET = "desktop-view:checks";
17
+
18
+ const checkSummary = defineTool({
19
+ name: "check_summary",
20
+ label: "Check summary",
21
+ description: "Summarize the project's checks as a table",
22
+ parameters: Type.Object({}),
23
+ async execute() {
24
+ const view: View = {
25
+ type: "table",
26
+ columns: ["check", "result"],
27
+ rows: [
28
+ ["types", "passed"],
29
+ ["tests", "2 failed"],
30
+ ],
31
+ };
32
+ return { content: [{ type: "text", text: "types: passed\ntests: 2 failed" }], details: { view } };
33
+ },
34
+ });
35
+
36
+ export default function (knightcode: ExtensionAPI) {
37
+ // Per session: the factory runs again for every thread that loads the extension.
38
+ let verbose = false;
39
+ let resets = 0;
40
+
41
+ const show = (ctx: ExtensionContext) => {
42
+ const view: View = {
43
+ type: "stack",
44
+ children: [
45
+ { type: "text", text: `Checks reset ${resets} time${resets === 1 ? "" : "s"}`, tone: "muted" },
46
+ {
47
+ type: "stack",
48
+ direction: "horizontal",
49
+ children: [
50
+ { type: "field", id: "verbose", kind: "toggle", label: "Verbose", value: verbose },
51
+ { type: "button", id: "reset", label: "Reset" },
52
+ ],
53
+ },
54
+ ],
55
+ };
56
+ ctx.ui.setWidget(WIDGET, view);
57
+ };
58
+
59
+ knightcode.registerTool(checkSummary);
60
+
61
+ knightcode.on("session_start", (_event, ctx) => {
62
+ if (!ctx.hasUI) return;
63
+ show(ctx);
64
+ // Only where widgets are interactive (the desktop app); the terminal draws the controls as text.
65
+ ctx.ui.onViewEvent?.((event) => {
66
+ if (event.widgetKey !== WIDGET) return;
67
+ if (event.id === "verbose" && typeof event.value === "boolean") verbose = event.value;
68
+ if (event.id === "reset") resets++;
69
+ show(ctx);
70
+ });
71
+ });
72
+ }
@@ -7,20 +7,20 @@
7
7
  * isolated to the VM.
8
8
  *
9
9
  * Setup:
10
- * cd packages/coding-agent/examples/extensions/gondolin
10
+ * cd packages/cli/examples/extensions/gondolin
11
11
  * npm install --ignore-scripts
12
12
  *
13
13
  * Usage:
14
14
  * cd /path/to/project
15
- * knightcode -e /path/to/knightcode/packages/coding-agent/examples/extensions/gondolin
15
+ * knightcode -e /path/to/knightcode/packages/cli/examples/extensions/gondolin
16
16
  *
17
17
  * Requirements:
18
- * - Node.js >= 23.6.0 for @KnightCodeAI/gondolin
18
+ * - Node.js >= 22.19.0 for @earendil-works/gondolin
19
19
  * - QEMU installed (for example, `brew install qemu` on macOS)
20
20
  */
21
21
 
22
22
  import path from "node:path";
23
- import { RealFSProvider, VM } from "@KnightCodeAI/gondolin";
23
+ import { RealFSProvider, VM } from "@earendil-works/gondolin";
24
24
  import type { ExtensionAPI, ExtensionContext } from "@knightcodeai/cli";
25
25
  import {
26
26
  type BashOperations,
@@ -249,7 +249,6 @@ async function executeGondolinGrep(
249
249
  const contextLines = params.context && params.context > 0 ? params.context : 0;
250
250
  const effectiveLimit = Math.max(1, params.limit ?? DEFAULT_GREP_LIMIT);
251
251
  const outputLines: string[] = [];
252
- const details: GrepToolDetails = {};
253
252
  let matchCount = 0;
254
253
  let matchLimitReached = false;
255
254
  let linesTruncated = false;
@@ -285,12 +284,14 @@ async function executeGondolinGrep(
285
284
  signal,
286
285
  );
287
286
 
288
- if (matchCount === 0) return { content: [{ type: "text", text: "No matches found" }], details: undefined };
287
+ if (matchCount === 0) return { content: [{ type: "text", text: "No matches found" }], details: { matchCount: 0 } };
289
288
 
290
289
  const rawOutput = outputLines.join("\n");
291
290
  const truncation = truncateHead(rawOutput, { maxLines: Number.MAX_SAFE_INTEGER });
292
291
  const notices: string[] = [];
293
292
  let output = truncation.content;
293
+ // The renderer counts output lines when this is missing, and context lines are not matches.
294
+ const details: GrepToolDetails = { matchCount };
294
295
 
295
296
  if (matchLimitReached) {
296
297
  details.matchLimitReached = effectiveLimit;
@@ -306,10 +307,7 @@ async function executeGondolinGrep(
306
307
  }
307
308
  if (notices.length > 0) output += `\n\n[${notices.join(". ")}]`;
308
309
 
309
- return {
310
- content: [{ type: "text", text: output }],
311
- details: Object.keys(details).length > 0 ? details : undefined,
312
- };
310
+ return { content: [{ type: "text", text: output }], details };
313
311
  }
314
312
 
315
313
  function sanitizeEnv(env: NodeJS.ProcessEnv | undefined): Record<string, string> | undefined {
@@ -8,7 +8,7 @@
8
8
  "name": "knightcode-extension-gondolin",
9
9
  "version": "0.84.3",
10
10
  "dependencies": {
11
- "@KnightCodeAI/gondolin": "0.12.0"
11
+ "@earendil-works/gondolin": "0.14.0"
12
12
  }
13
13
  },
14
14
  "node_modules/@cto.af/wtf8": {
@@ -20,32 +20,31 @@
20
20
  "node": ">=20"
21
21
  }
22
22
  },
23
- "node_modules/@KnightCodeAI/gondolin": {
24
- "version": "0.12.0",
25
- "resolved": "https://registry.npmjs.org/@KnightCodeAI/gondolin/-/gondolin-0.12.0.tgz",
26
- "integrity": "sha512-BXbvzQKb5QmxY5NtthRDONJTu7+IDKbzqWGrJyyNXMP7N681Tx0Q9TK8pK1ba8nUvYQTipNJyGZOsJfYiZll1A==",
23
+ "node_modules/@earendil-works/gondolin": {
24
+ "version": "0.14.0",
25
+ "resolved": "https://registry.npmjs.org/@earendil-works/gondolin/-/gondolin-0.14.0.tgz",
26
+ "integrity": "sha512-MSw+EGcqFJeyKHdXEfm+nMRr74ytT4x8DGIohav0oPCHeVQAB9AajquM94cv2xqvIU3VDAaZZVC07IXsOY6OVQ==",
27
27
  "license": "Apache-2.0",
28
28
  "dependencies": {
29
29
  "cbor2": "^2.3.0",
30
- "node-forge": "^1.3.3",
31
30
  "ssh2": "^1.17.0",
32
- "undici": "^6.21.0"
31
+ "undici": "^6.29.0"
33
32
  },
34
33
  "bin": {
35
34
  "gondolin": "dist/bin/gondolin.js"
36
35
  },
37
36
  "engines": {
38
- "node": ">=23.6.0"
37
+ "node": ">=22.19.0"
39
38
  },
40
39
  "optionalDependencies": {
41
- "@KnightCodeAI/gondolin-krun-runner-darwin-arm64": "0.12.0",
42
- "@KnightCodeAI/gondolin-krun-runner-linux-x64": "0.12.0"
40
+ "@earendil-works/gondolin-krun-runner-darwin-arm64": "0.14.0",
41
+ "@earendil-works/gondolin-krun-runner-linux-x64": "0.14.0"
43
42
  }
44
43
  },
45
- "node_modules/@KnightCodeAI/gondolin-krun-runner-darwin-arm64": {
46
- "version": "0.12.0",
47
- "resolved": "https://registry.npmjs.org/@KnightCodeAI/gondolin-krun-runner-darwin-arm64/-/gondolin-krun-runner-darwin-arm64-0.12.0.tgz",
48
- "integrity": "sha512-ftDlusht4PcT7Y3TuPrZIKrCXy3isiBTVMvlXYK0pcud2uXY6uwFTGeunYgP+8ND/60ddb+MImqbfmkcK8B84A==",
44
+ "node_modules/@earendil-works/gondolin-krun-runner-darwin-arm64": {
45
+ "version": "0.14.0",
46
+ "resolved": "https://registry.npmjs.org/@earendil-works/gondolin-krun-runner-darwin-arm64/-/gondolin-krun-runner-darwin-arm64-0.14.0.tgz",
47
+ "integrity": "sha512-fvuhz1CZPaEbmXC0luZoXd1obAb0bjaaGJz33Jo63bhMlosYunAahItakh7cU0dnTlNFzufOSnpGOPqtuYjnpQ==",
49
48
  "cpu": [
50
49
  "arm64"
51
50
  ],
@@ -58,10 +57,10 @@
58
57
  "gondolin-krun-runner": "bin/gondolin-krun-runner"
59
58
  }
60
59
  },
61
- "node_modules/@KnightCodeAI/gondolin-krun-runner-linux-x64": {
62
- "version": "0.12.0",
63
- "resolved": "https://registry.npmjs.org/@KnightCodeAI/gondolin-krun-runner-linux-x64/-/gondolin-krun-runner-linux-x64-0.12.0.tgz",
64
- "integrity": "sha512-RRYsgwe2r5ApKmFNy469QgwnyjAHpAs9XANdWpTd9ol4iUYOY3sX7e0xIooAKxd+ktxGI4N/xRWicwGen3D/Ow==",
60
+ "node_modules/@earendil-works/gondolin-krun-runner-linux-x64": {
61
+ "version": "0.14.0",
62
+ "resolved": "https://registry.npmjs.org/@earendil-works/gondolin-krun-runner-linux-x64/-/gondolin-krun-runner-linux-x64-0.14.0.tgz",
63
+ "integrity": "sha512-91iw4smoOH/yOoAuQS6N2UZOxqSFwRhtk8rTyrokXBoWiWcveAzg4kHvnLcm40ZlNrWwzIDFrere4bkZeGDQcA==",
65
64
  "cpu": [
66
65
  "x64"
67
66
  ],
@@ -134,15 +133,6 @@
134
133
  "license": "MIT",
135
134
  "optional": true
136
135
  },
137
- "node_modules/node-forge": {
138
- "version": "1.4.0",
139
- "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz",
140
- "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==",
141
- "license": "(BSD-3-Clause OR GPL-2.0)",
142
- "engines": {
143
- "node": ">= 6.13.0"
144
- }
145
- },
146
136
  "node_modules/safer-buffer": {
147
137
  "version": "2.1.2",
148
138
  "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
@@ -173,9 +163,9 @@
173
163
  "license": "Unlicense"
174
164
  },
175
165
  "node_modules/undici": {
176
- "version": "6.26.0",
177
- "resolved": "https://registry.npmjs.org/undici/-/undici-6.26.0.tgz",
178
- "integrity": "sha512-4yqz8a3n5HmGTlsbADNtr/dJlhkh/55Rq798G6ibiULcXbDtaLpTl1pvdqcbFfeoj3iSi52lePFM7h9H21cw/A==",
166
+ "version": "6.29.1",
167
+ "resolved": "https://registry.npmjs.org/undici/-/undici-6.29.1.tgz",
168
+ "integrity": "sha512-EvRlUt/QqAEvXdQeRMYUfwOQsfzpDztykrG2owGEE/rBOBoYv9uF39ZzsNDh4lTFHhTr2XOZjKkYgueD12ZmGA==",
179
169
  "license": "MIT",
180
170
  "engines": {
181
171
  "node": ">=18.17"
@@ -14,6 +14,6 @@
14
14
  ]
15
15
  },
16
16
  "dependencies": {
17
- "@KnightCodeAI/gondolin": "0.12.0"
17
+ "@earendil-works/gondolin": "0.14.0"
18
18
  }
19
19
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knightcodeai/cli",
3
- "version": "0.18.4",
3
+ "version": "0.20.0",
4
4
  "description": "KnightCode — a local, BYOK terminal coding agent powered by OpenRouter.",
5
5
  "type": "module",
6
6
  "repository": {