@knightcodeai/cli 0.20.0 → 0.21.1
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.
- package/CHANGELOG.md +70 -0
- package/dist/bundle/chunks/anthropic-messages-EY2CGNFR.js +6 -0
- package/dist/bundle/chunks/azure-openai-responses-KGI5F27P.js +2 -0
- package/dist/bundle/chunks/bedrock-converse-stream.js +8 -5
- package/dist/bundle/chunks/chunk-424HQSAU.js +10 -0
- package/dist/bundle/chunks/{chunk-DFRYMT3C.js → chunk-5O4LFJS7.js} +3 -3
- package/dist/bundle/chunks/chunk-BLHRVYHG.js +9 -0
- package/dist/bundle/chunks/{chunk-B7A4GIBK.js → chunk-EJS73Z3L.js} +1 -1
- package/dist/bundle/chunks/chunk-F22TYE3W.js +7 -0
- package/dist/bundle/chunks/{chunk-L6QGGIZG.js → chunk-FSAHEI7P.js} +4 -4
- package/dist/bundle/chunks/chunk-K6AEXKBA.js +30 -0
- package/dist/bundle/chunks/chunk-O2HKRIDR.js +16 -0
- package/dist/bundle/chunks/{chunk-JMJS643J.js → chunk-OLW2IS5G.js} +2 -2
- package/dist/bundle/chunks/chunk-QEVQ7EIS.js +2 -0
- package/dist/bundle/chunks/chunk-RRNUW65U.js +4 -0
- package/dist/bundle/chunks/{chunk-MZ5UVOTP.js → chunk-TOEFTX4C.js} +1 -1
- package/dist/bundle/chunks/{chunk-6KWPS3LA.js → chunk-TOGJI36I.js} +141 -129
- package/dist/bundle/chunks/{chunk-N5M3XNPN.js → chunk-VLP4ZJF6.js} +1 -1
- package/dist/bundle/chunks/chunk-XDEOVQPJ.js +108 -0
- package/dist/bundle/chunks/chunk-YDO6TXKD.js +2 -0
- package/dist/bundle/chunks/{cli-72YA573P.js → cli-CIRIEG66.js} +1 -1
- package/dist/bundle/chunks/{cloudflare-workers-ai-system-one-AWHNPEAT.js → cloudflare-workers-ai-system-one-H3D4RYQQ.js} +1 -1
- package/dist/bundle/chunks/{easter-egg-3d-XXPCDCZV.js → easter-egg-3d-YLN6ACWS.js} +1 -1
- package/dist/bundle/chunks/{execute-KWIUMPDE.js → execute-P6HIB6ET.js} +1 -1
- package/dist/bundle/chunks/{google-generative-ai-HPLZ2ABU.js → google-generative-ai-C3FLXY47.js} +1 -1
- package/dist/bundle/chunks/{google-vertex-E3UTTTIG.js → google-vertex-AXIPR47C.js} +1 -1
- package/dist/bundle/chunks/knightcode-messages-LTLKDRGW.js +8 -0
- package/dist/bundle/chunks/{llama-cpp-classify-FEWC5ZIH.js → llama-cpp-classify-J2EJVDIZ.js} +1 -1
- package/dist/bundle/chunks/mistral-conversations-N2UBIKKR.js +5 -0
- package/dist/bundle/chunks/{openai-codex-responses-BCLSSOJ6.js → openai-codex-responses-REOHLPAA.js} +1 -1
- package/dist/bundle/chunks/{openai-completions-GTNAEXX6.js → openai-completions-XONAGXPC.js} +1 -1
- package/dist/bundle/chunks/{openai-decisions-CSQTW3S4.js → openai-decisions-ACB7F6SS.js} +1 -1
- package/dist/bundle/chunks/{openai-responses-ILFXLMJW.js → openai-responses-OHJS7C73.js} +1 -1
- package/dist/bundle/chunks/{openrouter-images-J66HXAMB.js → openrouter-images-SMNBEOPV.js} +1 -1
- package/dist/bundle/chunks/{runtime-OW5JKWBE.js → runtime-WNZGA67H.js} +1 -1
- package/dist/bundle/chunks/{typesafe-system-one-QA5VXBEZ.js → typesafe-system-one-KB2F2YGV.js} +1 -1
- package/dist/bundle/chunks/{virtual-modules-5DLTLPP7.js → virtual-modules-EU3BUTGN.js} +1 -1
- package/dist/bundle/cli-runtime.js +1 -1
- package/docs/app-server.md +102 -6
- package/docs/app-server.schema.json +493 -0
- package/docs/cli.md +3 -3
- package/docs/custom-provider.md +2 -0
- package/docs/desktop.md +83 -4
- package/docs/environment-variables.md +1 -0
- package/docs/extensions.md +3 -1
- package/docs/keybindings.md +1 -0
- package/docs/mcp.md +16 -2
- package/docs/message-types.md +1 -1
- package/docs/packages.md +2 -0
- package/docs/plan-mode.md +4 -0
- package/docs/settings.md +3 -3
- package/docs/slash-commands.md +2 -0
- package/docs/usage.md +23 -0
- package/examples/extensions/custom-provider-anthropic/index.ts +27 -31
- package/examples/extensions/todo.ts +3 -0
- package/examples/mcp-app/server.mjs +177 -0
- package/package.json +1 -1
- package/dist/bundle/chunks/anthropic-messages-5ZM7Z2XQ.js +0 -6
- package/dist/bundle/chunks/azure-openai-responses-OUS4VJNA.js +0 -2
- package/dist/bundle/chunks/chunk-4CRI7BMY.js +0 -2
- package/dist/bundle/chunks/chunk-6GRVIFTN.js +0 -11
- package/dist/bundle/chunks/chunk-6X7IXVQH.js +0 -30
- package/dist/bundle/chunks/chunk-BWGDAXFH.js +0 -108
- package/dist/bundle/chunks/chunk-FA32X2UJ.js +0 -7
- package/dist/bundle/chunks/chunk-IIWY7U5S.js +0 -7
- package/dist/bundle/chunks/chunk-J63X3FRT.js +0 -16
- package/dist/bundle/chunks/chunk-RKNFSQUG.js +0 -2
- package/dist/bundle/chunks/chunk-UU4A4NV5.js +0 -2
- package/dist/bundle/chunks/knightcode-messages-57CV36UV.js +0 -8
- package/dist/bundle/chunks/mistral-conversations-KT4C4XTZ.js +0 -5
package/docs/desktop.md
CHANGED
|
@@ -7,8 +7,40 @@ configuration lives in the files the terminal reads (`settings.json`, `mcp.json`
|
|
|
7
7
|
`tools.json`); the app's Settings edit those same files, so a change made in either place
|
|
8
8
|
shows in both.
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
Installers come from the GitHub releases tagged `desktop-v<version>`; none is published yet,
|
|
11
|
+
so for now the app runs from a source checkout.
|
|
12
|
+
|
|
13
|
+
## Installing
|
|
14
|
+
|
|
15
|
+
Each installer carries its own `knightcode` binary as the app server, so nothing needs to be
|
|
16
|
+
on `PATH`. None is signed by a recognised publisher yet:
|
|
17
|
+
|
|
18
|
+
- Windows: run `KnightCode-<version>.Setup.exe`. It installs for your user only, into
|
|
19
|
+
`%LOCALAPPDATA%\KnightCodeDesktop`, with Start menu and desktop shortcuts; remove it from
|
|
20
|
+
Settings › Apps. SmartScreen says "Windows protected your PC" the first time: choose
|
|
21
|
+
More info, then Run anyway.
|
|
22
|
+
- macOS: open the `.dmg` for your Mac (`arm64` for Apple silicon, `x64` for Intel) and drag
|
|
23
|
+
KnightCode to Applications. The app is signed ad hoc, not notarized, so the first launch
|
|
24
|
+
is refused: open System Settings › Privacy & Security and choose Open Anyway, or run
|
|
25
|
+
`xattr -dr com.apple.quarantine /Applications/KnightCode.app` once.
|
|
26
|
+
- Linux: install the `.deb` (`sudo apt install ./knightcode-desktop_<version>_amd64.deb`) or
|
|
27
|
+
the `.rpm` (`sudo dnf install ./knightcode-desktop-<version>-1.x86_64.rpm`).
|
|
28
|
+
|
|
29
|
+
Check a download against the release's `SHA256SUMS`.
|
|
30
|
+
|
|
31
|
+
## Updates
|
|
32
|
+
|
|
33
|
+
- Windows: the app checks at launch and every 4 hours, and downloads a newer version in
|
|
34
|
+
the background. An Update button then shows at the right of the title bar, and Settings ›
|
|
35
|
+
About offers Restart to update; if threads are running it asks first, since quitting
|
|
36
|
+
interrupts them. Help › Check for Updates… checks now.
|
|
37
|
+
- macOS: the app checks the same way and shows Update when a newer version exists, which
|
|
38
|
+
opens the release page to download it (it cannot install itself while it is signed ad
|
|
39
|
+
hoc). KnightCode › Check for Updates… checks now.
|
|
40
|
+
- Linux: install the newer `.deb` or `.rpm` over the old one.
|
|
41
|
+
|
|
42
|
+
Settings › About shows the app's and the app server's versions and when the app last
|
|
43
|
+
checked.
|
|
12
44
|
|
|
13
45
|
## Running from source
|
|
14
46
|
|
|
@@ -25,7 +57,22 @@ quoting, so a path containing spaces (`C:\Program Files\...`) cannot be named; p
|
|
|
25
57
|
on `PATH` and give the bare name. A packaged build ignores it and runs only its bundled binary.
|
|
26
58
|
|
|
27
59
|
Server output goes to `app-server.log` in the app's logs folder (`%APPDATA%\KnightCode\logs`
|
|
28
|
-
on Windows, `~/Library/Logs/KnightCode` on macOS, `~/.config/KnightCode/logs` on Linux).
|
|
60
|
+
on Windows, `~/Library/Logs/KnightCode` on macOS, `~/.config/KnightCode/logs` on Linux). At
|
|
61
|
+
5 MiB it moves to `app-server.log.1`, the older ones to `.2` up to `.4`, so at most five files
|
|
62
|
+
are kept. Settings › About › Show logs opens the folder.
|
|
63
|
+
|
|
64
|
+
To build the installers for this machine, stage the app server and make them; the
|
|
65
|
+
installers land in `apps/desktop/out/make/`:
|
|
66
|
+
|
|
67
|
+
```sh
|
|
68
|
+
bun run apps/desktop/scripts/stage-server.ts # builds knightcode for this platform
|
|
69
|
+
cd apps/desktop && bun run make
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The end-to-end tests launch the production build that packaging leaves in
|
|
73
|
+
`apps/desktop/.vite/`, against an app server whose model is scripted
|
|
74
|
+
(`packages/cli/test/fixtures/faux-app-server.ts`): run `bun run package`, then
|
|
75
|
+
`bun run test:e2e`.
|
|
29
76
|
|
|
30
77
|
## What works
|
|
31
78
|
|
|
@@ -64,6 +111,32 @@ on Windows, `~/Library/Logs/KnightCode` on macOS, `~/.config/KnightCode/logs` on
|
|
|
64
111
|
added, as `knightcode config` does. Built-in extensions are always on and not listed.
|
|
65
112
|
|
|
66
113
|
Changes apply to threads that start or resume afterwards.
|
|
114
|
+
- Side panel: Changes shows the thread's diff; the separators between hunks expand to
|
|
115
|
+
the unchanged lines around them. A file named in a tool row or in Changes opens in a
|
|
116
|
+
read-only tab beside it, at the line the tool read or changed. A tab opened in passing
|
|
117
|
+
is italic and the next one replaces it; double-click it to keep it. Each thread keeps
|
|
118
|
+
its own tabs, at most 12 files.
|
|
119
|
+
- Open in: a file tab's Open button sends the file, at its line, to an editor, terminal
|
|
120
|
+
or IDE found on your machine, or to the system's default app or file manager. The app
|
|
121
|
+
remembers the last choice, per project and overall.
|
|
122
|
+
- Terminal: Ctrl+\` opens a terminal in the thread's folder, in a region under the chat and
|
|
123
|
+
the side panel; Ctrl+J shows or hides it, and its top edge resizes it. The + menu offers
|
|
124
|
+
the shells found (PowerShell, Windows PowerShell, Command Prompt, Git Bash and WSL on
|
|
125
|
+
Windows; your login shell elsewhere), and the last one picked is the default. Terminals
|
|
126
|
+
belong to their thread: they keep running when the window reloads or another thread
|
|
127
|
+
opens, and end when you close them, archive or delete the thread, or quit. Copy needs a
|
|
128
|
+
selection (Ctrl+C, Ctrl+Shift+C or Ctrl+Insert); paste with Ctrl+V, Ctrl+Shift+V or
|
|
129
|
+
Shift+Insert (on macOS, Cmd+C and Cmd+V). Links in the output open in your browser.
|
|
130
|
+
- Preview: the side panel's + (Ctrl+T) opens a page in a preview tab, http and https only
|
|
131
|
+
(`localhost:3000` is enough). A link to a local server in a message opens there too.
|
|
132
|
+
Each thread keeps its own cookies and storage, cleared when the thread is deleted; pages
|
|
133
|
+
get no permissions, and downloads ask where to save. Annotate (Ctrl+Shift+.) lets you
|
|
134
|
+
drag over part of the page to attach it to the message you are writing, with the page's
|
|
135
|
+
address.
|
|
136
|
+
- MCP Apps: a tool from an [MCP server](mcp.md#mcp-apps) that has an app shows it in the
|
|
137
|
+
thread, under the turn's work. The app can call its own server's tools (asking you
|
|
138
|
+
first for one that may change things), send a follow-up message once you confirm it,
|
|
139
|
+
and open full screen in the side panel.
|
|
67
140
|
- Archived threads: search, unarchive, or delete for good.
|
|
68
141
|
- Command palette (Ctrl+K): actions and threads in one search.
|
|
69
142
|
- Quitting while threads run asks first.
|
|
@@ -105,10 +178,16 @@ once. The protocol side is in [app-server.md](app-server.md#dialogs-and-extensio
|
|
|
105
178
|
| Toggle the sidebar | Ctrl+B |
|
|
106
179
|
| Back / forward | Alt+Left / Alt+Right |
|
|
107
180
|
| Archive the thread | Ctrl+Shift+A |
|
|
181
|
+
| Close the side panel's file tab | Ctrl+Shift+W |
|
|
182
|
+
| Open a terminal / show or hide them | Ctrl+\` / Ctrl+J |
|
|
183
|
+
| Next / previous terminal | Ctrl+PageDown / Ctrl+PageUp |
|
|
184
|
+
| Open a preview / focus its address bar | Ctrl+T / Ctrl+L |
|
|
185
|
+
| Annotate the preview | Ctrl+Shift+. |
|
|
108
186
|
| Send | Enter |
|
|
109
187
|
| New line | Shift+Enter |
|
|
110
188
|
| Steer the running turn | Ctrl+Enter |
|
|
111
189
|
| Stop the running turn | Escape (empty composer) |
|
|
112
190
|
| Allow / deny an approval | Enter / Escape (card focused) |
|
|
113
191
|
|
|
114
|
-
On macOS, Ctrl is Cmd
|
|
192
|
+
On macOS, Ctrl is Cmd, except in Ctrl+\` and Ctrl+PageDown/PageUp, which stay Control.
|
|
193
|
+
Settings › Keyboard shortcuts lists every binding.
|
|
@@ -90,6 +90,7 @@ These variables are read by KnightCode itself:
|
|
|
90
90
|
| `KNIGHTCODE_CACHE_RETENTION` | Set to `long` for extended provider prompt caching where supported |
|
|
91
91
|
| `KNIGHTCODE_SHARE_VIEWER_URL` | Override the base URL used by `/share` |
|
|
92
92
|
| `KNIGHTCODE_BUG_ENDPOINT` | Override the origin `/bug` uploads go to; default is `https://remote.knightcode.dev` |
|
|
93
|
+
| `KNIGHTCODE_CLEAR_ON_SHRINK` | Set to `1` to clear empty rows when rendered content shrinks; the `terminal.clearOnShrink` setting takes precedence. See [Settings](settings.md) |
|
|
93
94
|
| `KNIGHTCODE_HARDWARE_CURSOR` | Set to `1` to show the hardware cursor; see [Terminal setup](terminal-setup.md) |
|
|
94
95
|
| `KNIGHTCODE_CLIPBOARD_OSC52` | Set to `1` to also send copied text to the terminal with OSC 52 after the local clipboard write, as KnightCode does over SSH. Use it when a remote client reaches KnightCode without SSH variables, such as a multiplexer attached from another machine or a remote IDE daemon |
|
|
95
96
|
| `KNIGHTCODE_HYPERLINKS` | Override OSC 8 hyperlink detection with `1`, `0`, or `auto` |
|
package/docs/extensions.md
CHANGED
|
@@ -262,6 +262,8 @@ A tool can run other tools with `ctx.executeTool(name, args, { signal, onUpdate
|
|
|
262
262
|
|
|
263
263
|
See [`hello.ts`](../examples/extensions/hello.ts), [`todo.ts`](../examples/extensions/todo.ts), [`dynamic-tools.ts`](../examples/extensions/dynamic-tools.ts), and [`truncated-tool.ts`](../examples/extensions/truncated-tool.ts).
|
|
264
264
|
|
|
265
|
+
A tool registered under a built-in name replaces the built-in tool. Re-registering a copy of `createBashToolDefinition(cwd)` or `createPowerShellToolDefinition(cwd)` with object spread, for example to change its rendering, keeps [background commands](usage.md#background-commands): the session runs them with its own background manager as long as the copy keeps the built-in `execute` and `parameters`. A copy that replaces `execute`, such as one that routes commands to a sandbox or a remote host, runs commands in the foreground only, and `task_stop` is not declared for it.
|
|
266
|
+
|
|
265
267
|
### Tool exposure
|
|
266
268
|
|
|
267
269
|
`exposure` controls how the model reaches a tool. "Callable" means callable from other tools through `ctx.executeTool()` (`ctx.tools`), as the `codemode` tool's scripts do:
|
|
@@ -315,7 +317,7 @@ knightcode.unregisterMcpServer("jira");
|
|
|
315
317
|
|
|
316
318
|
Servers registered while the extension loads connect when the session starts, together with the `mcp.json` servers; servers registered later connect right away, and `knightcode.unregisterMcpServer()` closes the connection and makes the server's tools unreachable. Registrations are not saved: register again on every load, for example based on the extension's own settings. A server in `mcp.json` with the same name takes precedence, and `/mcp` shows the override. Registering the same name again replaces the extension's earlier registration; names registered by another extension, invalid names, and invalid configs throw.
|
|
317
319
|
|
|
318
|
-
The built-in MCP support connects registered servers. When nothing does, because another extension replaced it (see [MCP](mcp.md#
|
|
320
|
+
The built-in MCP support connects registered servers. When nothing does, because another extension replaced it (see [MCP](mcp.md#replace-the-built-in-mcp-support)), each registration is reported as an extension error. Other MCP extensions can connect registered servers too: read them with `knightcode.getMcpServers()` on `session_start` and handle the `mcp_servers_change` event for later changes.
|
|
319
321
|
|
|
320
322
|
<a id="extensioncontext"></a>
|
|
321
323
|
<a id="extensioncommandcontext"></a>
|
package/docs/keybindings.md
CHANGED
|
@@ -164,6 +164,7 @@ On native Windows, `app.suspend` has no default because Windows terminals do not
|
|
|
164
164
|
| Keybinding id | Default | Description |
|
|
165
165
|
|--------|---------|-------------|
|
|
166
166
|
| `app.tools.expand` | `ctrl+o` | Collapse or expand tool output |
|
|
167
|
+
| `app.tools.background` | `ctrl+b` | Move running shell commands to the [background](usage.md#background-commands). Only while a command runs; otherwise the key reaches the editor, where `ctrl+b` moves the cursor left |
|
|
167
168
|
| `app.message.copy` | `ctrl+x` | Copy the selected message in `/tree`; otherwise copy the active fullscreen text selection when `fullscreenCopyOnSelect` is disabled, falling back to the last assistant message. On OAuth sign-in screens, copy the sign-in URL |
|
|
168
169
|
| `app.message.followUp` | `alt+enter` (`ctrl+q` on Windows and WSL) | Queue follow-up message |
|
|
169
170
|
| `app.message.dequeue` | `alt+up` (`alt+q` on Windows and WSL) | Restore queued messages to editor |
|
package/docs/mcp.md
CHANGED
|
@@ -243,13 +243,27 @@ When a connected server offers [resources](https://modelcontextprotocol.io/speci
|
|
|
243
243
|
|
|
244
244
|
These tools reach every enabled, non-hidden server with resources. Their exposure is the widest exposure among those servers: `direct`, then `codemode` or `deferred`. Resource links in tool results identify `read_mcp_resource` and the server.
|
|
245
245
|
|
|
246
|
-
Resources for MCP Apps, identified by `ui://` URIs or `text/html;profile=mcp-app`, are omitted
|
|
246
|
+
Resources for MCP Apps, identified by `ui://` URIs or `text/html;profile=mcp-app`, are omitted from these tools: the desktop app draws them (see [MCP Apps](#mcp-apps)). Resource icons are also omitted.
|
|
247
247
|
|
|
248
248
|
Reading and listing resources is retried once after a transient HTTP error (408, 429, or 5xx). Tool calls are not retried because the server may already have performed them.
|
|
249
249
|
|
|
250
|
+
## MCP Apps
|
|
251
|
+
|
|
252
|
+
A server can give a tool an interactive view with the MCP Apps extension (`io.modelcontextprotocol/ui`, Stable 2026-01-26): the tool names a `ui://` resource in `_meta.ui.resourceUri`, and the resource is an HTML document of type `text/html;profile=mcp-app`. The [desktop app](desktop.md) draws it in the tool's row. The terminal draws no apps, so KnightCode tells servers it supports them only in the desktop, and elsewhere servers answer with their text fallback.
|
|
253
|
+
|
|
254
|
+
In the desktop:
|
|
255
|
+
|
|
256
|
+
- A tool with an app is declared to the model whatever the server's exposure, and it cannot be called from codemode scripts: a call drawn as an app must be the model's own, which the session keeps. A tool you configured as `hidden` stays hidden and draws no app.
|
|
257
|
+
- A tool whose `_meta.ui.visibility` leaves out `"model"` is not declared to the model, whatever its exposure and whether or not it has an app of its own. An app can still call it.
|
|
258
|
+
- An app calls only tools of its own server whose visibility includes `"app"`. In Full access its calls run. In Ask and Auto-edit, a call to a tool not annotated `readOnlyHint` asks you first, naming the server and the tool. An app's calls do not go through the session's tool pipeline, so extension `tool_call` and `tool_result` handlers, permission gates included, do not see them.
|
|
259
|
+
- An app can send a follow-up message to the thread after you confirm it, open http and https links in your browser, and ask to be shown full screen in the side panel. It cannot update the model's context.
|
|
260
|
+
- The app runs in a sandboxed frame on its own origin, with no permissions (no camera, microphone, location or clipboard). It reaches the network only through the domains its resource declares in `_meta.ui.csp`; a declaration that is not a list of host sources is refused and the app does not load.
|
|
261
|
+
|
|
262
|
+
[`examples/mcp-app/server.mjs`](../examples/mcp-app/server.mjs) is a small stdio server with a counter app: one tool that shows the app, one only the app may call, and one read-only tool.
|
|
263
|
+
|
|
250
264
|
## Permissions
|
|
251
265
|
|
|
252
|
-
Every MCP call passes through KnightCode's tool pipeline. Extension `tool_call` and `tool_result` handlers, including permission gates, therefore apply to MCP tools. Calls made from codemode scripts carry the codemode call ID as `parentToolCallId`.
|
|
266
|
+
Every MCP call the model makes passes through KnightCode's tool pipeline. Extension `tool_call` and `tool_result` handlers, including permission gates, therefore apply to MCP tools; an [MCP App](#mcp-apps)'s own calls are the exception. Calls made from codemode scripts carry the codemode call ID as `parentToolCallId`.
|
|
253
267
|
|
|
254
268
|
`knightcode.getAllTools()` reports the annotations declared by each server: `readOnlyHint`, `destructiveHint`, `idempotentHint`, and `openWorldHint`. Permission extensions can use these hints to decide which calls require confirmation (see [Tool exposure](extensions.md#tool-exposure)). Resource tools are marked read-only.
|
|
255
269
|
|
package/docs/message-types.md
CHANGED
|
@@ -140,7 +140,7 @@ interface AssistantMessage {
|
|
|
140
140
|
}
|
|
141
141
|
```
|
|
142
142
|
|
|
143
|
-
`responseModel` records
|
|
143
|
+
`responseModel` records the concrete model reported by the provider, when the provider reports one. It may equal the requested model. `responseId`, `providerThinkingLevel`, `thinkingLevel`, `diagnostics`, and `rawStopReason` preserve provider or runtime details.
|
|
144
144
|
|
|
145
145
|
`"pending"` is used for a partial assistant message while it streams. The completed message in `message_end` has a terminal stop reason, and KnightCode does not persist `"pending"` assistant messages in session JSONL.
|
|
146
146
|
|
package/docs/packages.md
CHANGED
|
@@ -16,6 +16,8 @@ knightcode install ./local-package
|
|
|
16
16
|
|
|
17
17
|
`knightcode list` shows configured packages. Use `knightcode remove <source>` to remove one and `knightcode update --extensions` to reconcile package installations. See [Command Line](cli.md#package-commands) for every package command and option.
|
|
18
18
|
|
|
19
|
+
If a configured package fails to install at startup, for example because npm cannot reach the registry, KnightCode warns about it, skips that package's resources, and keeps running. KnightCode tries to install it again on the next start or `/reload`. `knightcode install` and `knightcode update --extensions` still report the failure and exit non-zero.
|
|
20
|
+
|
|
19
21
|
Personal installs are written to `~/.knightcode/agent/settings.json`. Add `--local` or `-l` to write the package declaration to `.knightcode/settings.json`. KnightCode reads declarations from that file only after project trust is granted.
|
|
20
22
|
|
|
21
23
|
Project packages are installed and loaded only after project trust is resolved. Packages can execute extension code and can include skills that instruct the model to run programs. Review third-party package source before installing it. Review project package declarations before granting project trust.
|
package/docs/plan-mode.md
CHANGED
|
@@ -69,6 +69,10 @@ to the highlighted choice; on an implement choice it is sent with the
|
|
|
69
69
|
approval, and on Keep planning it is sent as feedback. Escape keeps planning. Review does not open automatically on resume. Session replacement, tree navigation, or a new run cancels an open
|
|
70
70
|
picker. An action rechecks the shown revision before changing state.
|
|
71
71
|
|
|
72
|
+
The desktop app opens review by itself too, asking "Implement this plan?":
|
|
73
|
+
Yes, implement this plan; Implement in a new thread; or Exit planning.
|
|
74
|
+
A fresh session is named after the plan's first line of prose.
|
|
75
|
+
|
|
72
76
|
RPC reports a submission notification; `/plan` opens its review selection.
|
|
73
77
|
JSON mode adds no extra output:
|
|
74
78
|
the submission tool-call events contain the Markdown.
|
package/docs/settings.md
CHANGED
|
@@ -107,7 +107,7 @@ See [Compaction Reference](compaction.md) for trigger, summarization, and valida
|
|
|
107
107
|
| `showHardwareCursor` | boolean | `false` | Use the terminal cursor instead of KnightCode's drawn cursor. KnightCode still positions it for input methods. |
|
|
108
108
|
| `terminal.showImages` | boolean | `true` | Display inline images when supported. |
|
|
109
109
|
| `terminal.imageWidthCells` | number | `60` | Preferred inline image width in terminal cells. |
|
|
110
|
-
| `terminal.clearOnShrink` | boolean | `false` | Clear empty rows when rendered content shrinks. |
|
|
110
|
+
| `terminal.clearOnShrink` | boolean | `false` | Clear empty rows when rendered content shrinks. When unset, `KNIGHTCODE_CLEAR_ON_SHRINK=1` enables it. |
|
|
111
111
|
| `terminal.showTerminalProgress` | boolean | `false` | Show OSC 9;4 progress in the terminal tab. |
|
|
112
112
|
| `terminal.hyperlinks` | `boolean \| "auto"` | `"auto"` | Override OSC 8 hyperlink detection. |
|
|
113
113
|
| `terminal.images` | `"kitty" \| "iterm2" \| "auto" \| false` | `"auto"` | Override inline-image protocol detection. |
|
|
@@ -178,8 +178,8 @@ handed off by accident, and nothing is created through a symlink or junction.
|
|
|
178
178
|
|---|---|---|---|
|
|
179
179
|
| `transport` | `"auto" \| "sse" \| "websocket" \| "websocket-cached"` | `"auto"` | Preferred transport for AI providers that support multiple transports. |
|
|
180
180
|
| `httpProxy` | string | None | Proxy URL applied as `HTTP_PROXY` and `HTTPS_PROXY` for KnightCode-managed HTTP clients. **Can only be set in agent-directory settings.** |
|
|
181
|
-
| `httpIdleTimeoutMs` | number | `300000` | HTTP header and body idle timeout in milliseconds. Set to `0` to disable. |
|
|
182
|
-
| `websocketConnectTimeoutMs` | number | `15000` | WebSocket connection timeout in milliseconds. Set to `0` to disable. |
|
|
181
|
+
| `httpIdleTimeoutMs` | number \| `"disabled"` | `300000` | HTTP header and body idle timeout in milliseconds. Set to `0` or `"disabled"` to disable. |
|
|
182
|
+
| `websocketConnectTimeoutMs` | number \| `"disabled"` | `15000` | WebSocket connection timeout in milliseconds. Set to `0` or `"disabled"` to disable. |
|
|
183
183
|
| `retry.enabled` | boolean | `true` | Enable automatic agent-level retry for transient failures. |
|
|
184
184
|
| `retry.maxRetries` | number | `3` | Maximum agent-level retry attempts. |
|
|
185
185
|
| `retry.baseDelayMs` | number | `2000` | Initial exponential-backoff delay in milliseconds. |
|
package/docs/slash-commands.md
CHANGED
|
@@ -64,8 +64,10 @@ Review a session before exporting or sharing it. Sessions can contain prompts, t
|
|
|
64
64
|
| Command | Description |
|
|
65
65
|
|---|---|
|
|
66
66
|
| `/trust` | Save a project trust decision for future KnightCode processes |
|
|
67
|
+
| `/mcp` | Manage MCP servers: sign in, reconnect, enable or disable, and change exposure |
|
|
67
68
|
| `/reload` | Reload keybindings, extensions, skills, templates, themes, and context files |
|
|
68
69
|
| `/hotkeys` | Show active keyboard shortcuts |
|
|
70
|
+
| `/tasks` | List [background commands](usage.md#background-commands), show their output, and stop them |
|
|
69
71
|
| `/changelog` | Show changelog entries |
|
|
70
72
|
| `/quit` | Quit KnightCode |
|
|
71
73
|
|
package/docs/usage.md
CHANGED
|
@@ -41,6 +41,7 @@ You can send more input while KnightCode is working:
|
|
|
41
41
|
| Adjust the current task | Type a message and press `Enter` |
|
|
42
42
|
| Add work after the current task | Type a message and press `Alt+Enter` |
|
|
43
43
|
| Return queued messages to the editor | Press `Alt+Up` |
|
|
44
|
+
| Let a long command run on while KnightCode continues | Press `Ctrl+B` (see [Background commands](#background-commands)) |
|
|
44
45
|
| Stop the current task | Press `Escape` |
|
|
45
46
|
|
|
46
47
|
A message sent with `Enter` waits until the current response and its tool calls finish, then guides the next response. A follow-up sent with `Alt+Enter` waits until KnightCode finishes the current task. Aborting returns queued messages to the editor.
|
|
@@ -83,6 +84,28 @@ Prefix a command with `!` to run it and include its output in the conversation:
|
|
|
83
84
|
|
|
84
85
|
Use `!!` when you want to run a command without sending its output to the model.
|
|
85
86
|
|
|
87
|
+
## Background commands
|
|
88
|
+
|
|
89
|
+
KnightCode can start a command in the background, and decides per command whether to. It does so for work that takes long or never exits, such as a dev server, a file watcher, or a long build or test run. The tool call returns at once and the command keeps running while KnightCode works, across turns. When the command exits, KnightCode is told how it ended, with the last lines of its output, and carries on from there; you see the same notice in the transcript.
|
|
90
|
+
|
|
91
|
+
Press `Ctrl+B` while a command runs in the foreground to move it to the background. The same process keeps running; nothing starts again. The key works only while a command runs; otherwise `Ctrl+B` moves the cursor as usual.
|
|
92
|
+
|
|
93
|
+
While commands run, a pill above the footer counts them: `1 shell`, `3 shells`. Press `Down` in an empty editor to select it and `Enter` to open the **Background tasks** list, or click it; `Esc` or `Up` returns to the editor. `/tasks` opens the same list. With a single command it opens on that command's details.
|
|
94
|
+
|
|
95
|
+
| Key | In the list | In a command's details |
|
|
96
|
+
|-----|-------------|------------------------|
|
|
97
|
+
| `Up`/`Down` | Choose a command | |
|
|
98
|
+
| `Enter` | Show its details | Close |
|
|
99
|
+
| `x` | Stop the chosen command | Stop it |
|
|
100
|
+
| `Left` | Close | Back to the list |
|
|
101
|
+
| `Esc` | Close | Close |
|
|
102
|
+
|
|
103
|
+
A command's details show its status and exit code, runtime, command, and the last 10 lines of its output as it arrives. Its output is also written to a file, and KnightCode reads that file when it needs the output sooner. If writing that file fails, for example on a full disk, the file is incomplete and KnightCode is told so. KnightCode stops a command with the `task_stop` tool, which is on whenever `bash` or `powershell` is.
|
|
104
|
+
|
|
105
|
+
A background command ends when it exits, when KnightCode or you stop it, or when the session ends: quitting, `/new`, `/resume`, and `/fork` stop it, while `/reload` keeps it. Resuming or forking a session does not start its commands again; with your next message KnightCode is told that they were stopped. A `timeout` given with the command still stops it in the background. A command that writes more than 1 GiB of output is stopped, and at most 32 run at once.
|
|
106
|
+
|
|
107
|
+
A background command has no input: one that asks a question reads end-of-file. If you press `Escape` to stop the current task, a command that ends afterwards is recorded in the conversation without starting KnightCode again; the next message you send re-arms that. In print and JSON modes KnightCode does not exit while a command it started in the background is running, so it can act on how the command ended.
|
|
108
|
+
|
|
86
109
|
## Web Tools
|
|
87
110
|
|
|
88
111
|
Two tools give the agent read access to the web. Both ship disabled; turn them on with `/tools`.
|
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
type TextContent,
|
|
43
43
|
type ThinkingContent,
|
|
44
44
|
type Tool,
|
|
45
|
-
|
|
45
|
+
ToolCallArgumentsStream,
|
|
46
46
|
type ToolResultMessage,
|
|
47
47
|
type TranscriptContext,
|
|
48
48
|
} from "@knightcode/ai";
|
|
@@ -454,8 +454,9 @@ function streamCustomAnthropic(
|
|
|
454
454
|
const anthropicStream = client.messages.stream({ ...params }, { signal: options?.signal });
|
|
455
455
|
stream.push({ type: "start", partial: output });
|
|
456
456
|
|
|
457
|
-
|
|
458
|
-
|
|
457
|
+
// Stream state per Anthropic block index, kept off the content blocks so it never reaches partial messages.
|
|
458
|
+
// ToolCallArgumentsStream reads each argument delta once and shows the arguments parsed so far.
|
|
459
|
+
const openBlocks = new Map<number, { contentIndex: number; arguments?: ToolCallArgumentsStream }>();
|
|
459
460
|
|
|
460
461
|
for await (const event of anthropicStream) {
|
|
461
462
|
if (event.type === "message_start") {
|
|
@@ -468,15 +469,12 @@ function streamCustomAnthropic(
|
|
|
468
469
|
calculateCost(model, output.usage);
|
|
469
470
|
} else if (event.type === "content_block_start") {
|
|
470
471
|
if (event.content_block.type === "text") {
|
|
471
|
-
output.content.push({ type: "text", text: ""
|
|
472
|
+
output.content.push({ type: "text", text: "" });
|
|
473
|
+
openBlocks.set(event.index, { contentIndex: output.content.length - 1 });
|
|
472
474
|
stream.push({ type: "text_start", contentIndex: output.content.length - 1, partial: output });
|
|
473
475
|
} else if (event.content_block.type === "thinking") {
|
|
474
|
-
output.content.push({
|
|
475
|
-
|
|
476
|
-
thinking: "",
|
|
477
|
-
thinkingSignature: "",
|
|
478
|
-
index: event.index,
|
|
479
|
-
} as any);
|
|
476
|
+
output.content.push({ type: "thinking", thinking: "", thinkingSignature: "" });
|
|
477
|
+
openBlocks.set(event.index, { contentIndex: output.content.length - 1 });
|
|
480
478
|
stream.push({ type: "thinking_start", contentIndex: output.content.length - 1, partial: output });
|
|
481
479
|
} else if (event.content_block.type === "tool_use") {
|
|
482
480
|
output.content.push({
|
|
@@ -484,15 +482,18 @@ function streamCustomAnthropic(
|
|
|
484
482
|
id: event.content_block.id,
|
|
485
483
|
name: isOAuth ? fromClaudeCodeName(event.content_block.name, tools) : event.content_block.name,
|
|
486
484
|
arguments: {},
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
485
|
+
});
|
|
486
|
+
openBlocks.set(event.index, {
|
|
487
|
+
contentIndex: output.content.length - 1,
|
|
488
|
+
arguments: new ToolCallArgumentsStream(),
|
|
489
|
+
});
|
|
490
490
|
stream.push({ type: "toolcall_start", contentIndex: output.content.length - 1, partial: output });
|
|
491
491
|
}
|
|
492
492
|
} else if (event.type === "content_block_delta") {
|
|
493
|
-
const
|
|
494
|
-
|
|
495
|
-
|
|
493
|
+
const open = openBlocks.get(event.index);
|
|
494
|
+
if (!open) continue;
|
|
495
|
+
const index = open.contentIndex;
|
|
496
|
+
const block = output.content[index]!;
|
|
496
497
|
|
|
497
498
|
if (event.delta.type === "text_delta" && block.type === "text") {
|
|
498
499
|
block.text += event.delta.text;
|
|
@@ -505,11 +506,8 @@ function streamCustomAnthropic(
|
|
|
505
506
|
delta: event.delta.thinking,
|
|
506
507
|
partial: output,
|
|
507
508
|
});
|
|
508
|
-
} else if (event.delta.type === "input_json_delta" && block.type === "toolCall") {
|
|
509
|
-
|
|
510
|
-
try {
|
|
511
|
-
block.arguments = JSON.parse((block as any).partialJson);
|
|
512
|
-
} catch {}
|
|
509
|
+
} else if (event.delta.type === "input_json_delta" && block.type === "toolCall" && open.arguments) {
|
|
510
|
+
block.arguments = open.arguments.append(event.delta.partial_json);
|
|
513
511
|
stream.push({
|
|
514
512
|
type: "toolcall_delta",
|
|
515
513
|
contentIndex: index,
|
|
@@ -520,20 +518,19 @@ function streamCustomAnthropic(
|
|
|
520
518
|
block.thinkingSignature = (block.thinkingSignature || "") + (event.delta as any).signature;
|
|
521
519
|
}
|
|
522
520
|
} else if (event.type === "content_block_stop") {
|
|
523
|
-
const
|
|
524
|
-
|
|
525
|
-
|
|
521
|
+
const open = openBlocks.get(event.index);
|
|
522
|
+
if (!open) continue;
|
|
523
|
+
openBlocks.delete(event.index);
|
|
524
|
+
const index = open.contentIndex;
|
|
525
|
+
const block = output.content[index]!;
|
|
526
526
|
|
|
527
|
-
delete (block as any).index;
|
|
528
527
|
if (block.type === "text") {
|
|
529
528
|
stream.push({ type: "text_end", contentIndex: index, content: block.text, partial: output });
|
|
530
529
|
} else if (block.type === "thinking") {
|
|
531
530
|
stream.push({ type: "thinking_end", contentIndex: index, content: block.thinking, partial: output });
|
|
532
|
-
} else if (block.type === "toolCall") {
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
} catch {}
|
|
536
|
-
delete (block as any).partialJson;
|
|
531
|
+
} else if (block.type === "toolCall" && open.arguments) {
|
|
532
|
+
// One full parse of the complete text; malformed arguments are reported in `argumentsError`.
|
|
533
|
+
Object.assign(block, open.arguments.finish());
|
|
537
534
|
stream.push({ type: "toolcall_end", contentIndex: index, toolCall: block, partial: output });
|
|
538
535
|
}
|
|
539
536
|
} else if (event.type === "message_delta") {
|
|
@@ -563,7 +560,6 @@ function streamCustomAnthropic(
|
|
|
563
560
|
stream.push({ type: "done", reason: output.stopReason, message: output });
|
|
564
561
|
stream.end();
|
|
565
562
|
} catch (error) {
|
|
566
|
-
for (const block of output.content) delete (block as any).index;
|
|
567
563
|
output.stopReason = options?.signal?.aborted ? "aborted" : "error";
|
|
568
564
|
output.errorMessage = error instanceof Error ? error.message : JSON.stringify(error);
|
|
569
565
|
stream.push({ type: "error", reason: output.stopReason, error: output });
|
|
@@ -273,6 +273,9 @@ export default function (knightcode: ExtensionAPI) {
|
|
|
273
273
|
|
|
274
274
|
case "clear":
|
|
275
275
|
return new Text(theme.fg("success", "✓ ") + theme.fg("muted", "Cleared all todos"), 0, 0);
|
|
276
|
+
|
|
277
|
+
default:
|
|
278
|
+
return new Text(result.content[0]?.type === "text" ? theme.fg("error", result.content[0].text) : "", 0, 0);
|
|
276
279
|
}
|
|
277
280
|
},
|
|
278
281
|
});
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* A stdio MCP server with one MCP App: a counter. It needs no dependencies (newline-delimited JSON-RPC over stdio).
|
|
4
|
+
*
|
|
5
|
+
* - `show_counter` (model and app): shows the counter; its `_meta.ui.resourceUri` names the app, `ui://counter/app.html`.
|
|
6
|
+
* - `increment` (app only, `_meta.ui.visibility: ["app"]`): changes the count, so outside Full access the desktop
|
|
7
|
+
* asks the user before it runs.
|
|
8
|
+
* - `read_count` (read-only): what the count is; an app may call it without asking.
|
|
9
|
+
*
|
|
10
|
+
* Add it to `~/.knightcode/agent/mcp.json`:
|
|
11
|
+
*
|
|
12
|
+
* { "mcpServers": { "counter": { "command": "node", "args": ["/path/to/server.mjs"], "exposure": "direct" } } }
|
|
13
|
+
*
|
|
14
|
+
* In the desktop app, ask KnightCode to show the counter: the tool's row draws the app. In the terminal, which draws
|
|
15
|
+
* no apps, the tool answers with its text.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { createInterface } from "node:readline";
|
|
19
|
+
|
|
20
|
+
const APP_URI = "ui://counter/app.html";
|
|
21
|
+
const APP_MIME = "text/html;profile=mcp-app";
|
|
22
|
+
let count = 0;
|
|
23
|
+
|
|
24
|
+
const APP_HTML = `<!doctype html>
|
|
25
|
+
<meta charset="utf-8">
|
|
26
|
+
<style>
|
|
27
|
+
body { margin: 0; padding: 12px 14px; font: 14px var(--font-sans, system-ui); color: var(--color-text-primary, #111);
|
|
28
|
+
background: var(--color-background-primary, transparent); }
|
|
29
|
+
.row { display: flex; align-items: center; gap: 10px; }
|
|
30
|
+
output { font: 600 22px var(--font-mono, monospace); min-width: 3ch; }
|
|
31
|
+
button { font: inherit; padding: 4px 12px; border-radius: var(--border-radius-md, 8px);
|
|
32
|
+
border: 1px solid var(--color-border-primary, #ccc); background: var(--color-background-secondary, #f4f4f4);
|
|
33
|
+
color: inherit; cursor: pointer; }
|
|
34
|
+
#status { margin-top: 8px; color: var(--color-text-secondary, #666); font-size: 12px; }
|
|
35
|
+
</style>
|
|
36
|
+
<div class="row">
|
|
37
|
+
<span>Count</span><output id="count">…</output>
|
|
38
|
+
<button id="add">Add one</button>
|
|
39
|
+
<button id="tell">Tell KnightCode</button>
|
|
40
|
+
</div>
|
|
41
|
+
<div id="status"></div>
|
|
42
|
+
<script>
|
|
43
|
+
const pending = new Map();
|
|
44
|
+
let next = 0;
|
|
45
|
+
const request = (method, params) =>
|
|
46
|
+
new Promise((resolve, reject) => {
|
|
47
|
+
const id = ++next;
|
|
48
|
+
pending.set(id, { resolve, reject });
|
|
49
|
+
parent.postMessage({ jsonrpc: "2.0", id, method, params }, "*");
|
|
50
|
+
});
|
|
51
|
+
const notify = (method, params) => parent.postMessage({ jsonrpc: "2.0", method, params }, "*");
|
|
52
|
+
const show = (n) => (document.getElementById("count").textContent = String(n));
|
|
53
|
+
const status = (text) => (document.getElementById("status").textContent = text);
|
|
54
|
+
const theme = (context) => {
|
|
55
|
+
for (const [name, value] of Object.entries(context?.styles?.variables ?? {}))
|
|
56
|
+
if (value) document.documentElement.style.setProperty(name, value);
|
|
57
|
+
};
|
|
58
|
+
window.addEventListener("message", (event) => {
|
|
59
|
+
const message = event.data;
|
|
60
|
+
if (!message || message.jsonrpc !== "2.0") return;
|
|
61
|
+
if (message.id !== undefined && !message.method) {
|
|
62
|
+
const waiter = pending.get(message.id);
|
|
63
|
+
pending.delete(message.id);
|
|
64
|
+
if (message.error) waiter?.reject(new Error(message.error.message));
|
|
65
|
+
else waiter?.resolve(message.result);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (message.method === "ui/notifications/tool-result") show(message.params?.structuredContent?.count ?? "?");
|
|
69
|
+
else if (message.method === "ui/notifications/tool-cancelled") status("The call was cancelled");
|
|
70
|
+
else if (message.method === "ui/notifications/host-context-changed") theme(message.params);
|
|
71
|
+
else if (message.method === "ui/resource-teardown") parent.postMessage({ jsonrpc: "2.0", id: message.id, result: {} }, "*");
|
|
72
|
+
else if (message.method === "ping") parent.postMessage({ jsonrpc: "2.0", id: message.id, result: {} }, "*");
|
|
73
|
+
});
|
|
74
|
+
document.getElementById("add").onclick = async () => {
|
|
75
|
+
try {
|
|
76
|
+
const result = await request("tools/call", { name: "increment", arguments: { by: 1 } });
|
|
77
|
+
show(result.structuredContent.count);
|
|
78
|
+
status("");
|
|
79
|
+
} catch (error) {
|
|
80
|
+
status(error.message);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
document.getElementById("tell").onclick = async () => {
|
|
84
|
+
const now = document.getElementById("count").textContent;
|
|
85
|
+
try {
|
|
86
|
+
await request("ui/message", { role: "user", content: [{ type: "text", text: "The counter app shows " + now + "." }] });
|
|
87
|
+
status("Sent");
|
|
88
|
+
} catch (error) {
|
|
89
|
+
status(error.message);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
(async () => {
|
|
93
|
+
const init = await request("ui/initialize", {
|
|
94
|
+
protocolVersion: "2026-01-26",
|
|
95
|
+
appInfo: { name: "counter", version: "1.0.0" },
|
|
96
|
+
appCapabilities: { availableDisplayModes: ["inline"] },
|
|
97
|
+
});
|
|
98
|
+
theme(init.hostContext);
|
|
99
|
+
notify("ui/notifications/initialized", {});
|
|
100
|
+
notify("ui/notifications/size-changed", { height: document.documentElement.scrollHeight });
|
|
101
|
+
})();
|
|
102
|
+
</script>`;
|
|
103
|
+
|
|
104
|
+
const tools = [
|
|
105
|
+
{
|
|
106
|
+
name: "show_counter",
|
|
107
|
+
description: "Show the counter app with the current count.",
|
|
108
|
+
inputSchema: { type: "object", properties: {} },
|
|
109
|
+
annotations: { readOnlyHint: true },
|
|
110
|
+
_meta: { ui: { resourceUri: APP_URI } },
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: "increment",
|
|
114
|
+
description: "Add to the counter.",
|
|
115
|
+
inputSchema: { type: "object", properties: { by: { type: "number" } } },
|
|
116
|
+
_meta: { ui: { visibility: ["app"] } },
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: "read_count",
|
|
120
|
+
description: "Read the counter.",
|
|
121
|
+
inputSchema: { type: "object", properties: {} },
|
|
122
|
+
annotations: { readOnlyHint: true },
|
|
123
|
+
},
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
const countResult = (text) => ({ content: [{ type: "text", text }], structuredContent: { count } });
|
|
127
|
+
|
|
128
|
+
function answer(method, params) {
|
|
129
|
+
switch (method) {
|
|
130
|
+
case "initialize":
|
|
131
|
+
return {
|
|
132
|
+
protocolVersion: params?.protocolVersion ?? "2025-11-25",
|
|
133
|
+
capabilities: { tools: {}, resources: {} },
|
|
134
|
+
serverInfo: { name: "counter-app", version: "1.0.0" },
|
|
135
|
+
};
|
|
136
|
+
case "ping":
|
|
137
|
+
return {};
|
|
138
|
+
case "tools/list":
|
|
139
|
+
return { tools };
|
|
140
|
+
case "tools/call":
|
|
141
|
+
if (params?.name === "show_counter") return countResult(`The counter is at ${count}.`);
|
|
142
|
+
if (params?.name === "increment") {
|
|
143
|
+
count += Number(params.arguments?.by ?? 1);
|
|
144
|
+
return countResult(`The counter is now ${count}.`);
|
|
145
|
+
}
|
|
146
|
+
if (params?.name === "read_count") return countResult(String(count));
|
|
147
|
+
throw Object.assign(new Error(`no tool ${params?.name}`), { code: -32602 });
|
|
148
|
+
case "resources/list":
|
|
149
|
+
return { resources: [{ uri: APP_URI, name: "Counter app", mimeType: APP_MIME }] };
|
|
150
|
+
case "resources/read":
|
|
151
|
+
if (params?.uri !== APP_URI) throw Object.assign(new Error(`no resource ${params?.uri}`), { code: -32002 });
|
|
152
|
+
return {
|
|
153
|
+
contents: [
|
|
154
|
+
{ uri: APP_URI, mimeType: APP_MIME, text: APP_HTML, _meta: { ui: { prefersBorder: true, csp: {} } } },
|
|
155
|
+
],
|
|
156
|
+
};
|
|
157
|
+
default:
|
|
158
|
+
throw Object.assign(new Error(`method not found: ${method}`), { code: -32601 });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const write = (message) => process.stdout.write(`${JSON.stringify(message)}\n`);
|
|
163
|
+
createInterface({ input: process.stdin }).on("line", (line) => {
|
|
164
|
+
let message;
|
|
165
|
+
try {
|
|
166
|
+
message = JSON.parse(line);
|
|
167
|
+
} catch {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
// Notifications (no id) need no answer.
|
|
171
|
+
if (message.id === undefined || message.id === null) return;
|
|
172
|
+
try {
|
|
173
|
+
write({ jsonrpc: "2.0", id: message.id, result: answer(message.method, message.params) });
|
|
174
|
+
} catch (error) {
|
|
175
|
+
write({ jsonrpc: "2.0", id: message.id, error: { code: error.code ?? -32603, message: error.message } });
|
|
176
|
+
}
|
|
177
|
+
});
|