add zdr and debug status to the sidebar
This commit is contained in:
parent
41fe0788a7
commit
a52efe1f70
29
AGENTS.md
29
AGENTS.md
@ -23,8 +23,8 @@ Run from the repository root.
|
|||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
npm install # dev deps: typescript, @types/node, @ai-sdk/provider
|
npm install # dev deps: typescript, @types/node, @ai-sdk/provider
|
||||||
npm run typecheck # tsc --noEmit
|
npm run typecheck # tsc --noEmit (provider) + tsc -p tsconfig.tui.json (TUI plugin)
|
||||||
npm run build # tsc -> dist/
|
npm run build # tsc -> dist/ (provider only; the TUI plugin is loaded from source)
|
||||||
npm run smoke # live request against CommandCode (needs a key)
|
npm run smoke # live request against CommandCode (needs a key)
|
||||||
npm run sync-models # regenerate provider.commandcode.models from the live catalog
|
npm run sync-models # regenerate provider.commandcode.models from the live catalog
|
||||||
```
|
```
|
||||||
@ -46,7 +46,9 @@ src/usage.ts finish event -> LanguageModelV3Usage; finish-reason unificatio
|
|||||||
src/redact.ts Credential scrubbing for error surfaces.
|
src/redact.ts Credential scrubbing for error surfaces.
|
||||||
src/log.ts Opt-in tracing (COMMANDCODE_DEBUG) to a log file.
|
src/log.ts Opt-in tracing (COMMANDCODE_DEBUG) to a log file.
|
||||||
src/toggles.ts Shared toggle file (~/.config/opencode/commandcode-toggles.json).
|
src/toggles.ts Shared toggle file (~/.config/opencode/commandcode-toggles.json).
|
||||||
src/tui.ts opencode TUI plugin exposing /cc-zdr, /cc-debug, /cc-status.
|
src/tui.tsx opencode TUI plugin: /cc-zdr, /cc-debug, /cc-status + sidebar status panel.
|
||||||
|
src/tui-shims.d.ts Ambient types for the opencode-provided solid-js/@opentui/solid runtime.
|
||||||
|
tsconfig.tui.json Typechecks src/tui.tsx against the shim (no dependencies).
|
||||||
src/constants.ts Defaults, paths, headers, passthrough params, static config block.
|
src/constants.ts Defaults, paths, headers, passthrough params, static config block.
|
||||||
scripts/smoke.mjs Live end-to-end check.
|
scripts/smoke.mjs Live end-to-end check.
|
||||||
scripts/sync-models.mjs Catalog -> provider.commandcode.models generator (with vision probing).
|
scripts/sync-models.mjs Catalog -> provider.commandcode.models generator (with vision probing).
|
||||||
@ -116,13 +118,20 @@ These are load-bearing. Breaking one causes silent failures in opencode.
|
|||||||
`provider.commandcode.models` in `opencode.json` and never asks a custom `file://` provider to
|
`provider.commandcode.models` in `opencode.json` and never asks a custom `file://` provider to
|
||||||
discover models (only internal providers can register `discoverModels`). Refresh the map with
|
discover models (only internal providers can register `discoverModels`). Refresh the map with
|
||||||
`npm run sync-models`; do not expect a discovery hook in `src/` to populate it.
|
`npm run sync-models`; do not expect a discovery hook in `src/` to populate it.
|
||||||
12. **The TUI plugin lives outside the provider entrypoint.** `src/tui.ts` compiles to
|
12. **The TUI plugin lives outside the provider entrypoint.** `src/tui.tsx` is loaded from
|
||||||
`dist/tui.js`, is exposed as `./tui` in `package.json`, and is installed with
|
**source** (not `dist/`) via `opencode plugin <path>`, which writes a `tui.json` `plugin`
|
||||||
`opencode plugin <path>`, which writes a `tui.json` `plugin` entry. Verified on opencode
|
entry; it is exposed as `./tui` in `package.json`. opencode compiles the `.tsx` at load with
|
||||||
1.18.31: the module's **default export must be `{ id, tui }`** (a bare `tui` named export is
|
its Bun/Solid transform and maps `solid-js` / `@opentui/solid` to its internal modules, so the
|
||||||
imported but never invoked), `id` is required, and `tui` is `async (api) => {}`. Commands
|
package stays dependency-free — do not add a build step or real imports of those packages.
|
||||||
register via `api.keymap.registerLayer({ commands })` with `{ name, run, title, desc,
|
`src/tui-shims.d.ts` is the only local stand-in for types; keep it in sync with what the file
|
||||||
category, namespace: "palette", slashName }`.
|
actually imports. Verified on opencode 1.18.31: the module's **default export must be
|
||||||
|
`{ id, tui }`** (a bare `tui` named export is imported but never invoked), `id` is required,
|
||||||
|
and `tui` is `async (api) => {}`. Commands register via
|
||||||
|
`api.keymap.registerLayer({ commands })` with `{ name, run, title, desc, category,
|
||||||
|
namespace: "palette", slashName }`. The sidebar panel registers via
|
||||||
|
`api.slots.register({ order, slots: { sidebar_content } })`; `order: 90` keeps it above the
|
||||||
|
built-in panels (100 context, 200 mcp, 300 lsp, 400 todo, 500 files). The slot renderer
|
||||||
|
returns Solid JSX and is reactive; it relies on `api.theme` and `api.lifecycle.onDispose`.
|
||||||
13. **Slash commands and `Ctrl+P` share one registry.** In opencode 1.x the slash menu queries
|
13. **Slash commands and `Ctrl+P` share one registry.** In opencode 1.x the slash menu queries
|
||||||
the same `namespace: "palette"` commands the palette lists (both filter out `hidden: true`).
|
the same `namespace: "palette"` commands the palette lists (both filter out `hidden: true`).
|
||||||
A slash-only entry is not expressible; registering `/cc-*` also adds them to `Ctrl+P`.
|
A slash-only entry is not expressible; registering `/cc-*` also adds them to `Ctrl+P`.
|
||||||
|
|||||||
24
README.md
24
README.md
@ -188,18 +188,21 @@ reads on every request, so both settings change without restarting opencode. `/c
|
|||||||
the current state. The file is `~/.config/opencode/commandcode-toggles.json` (overridable with
|
the current state. The file is `~/.config/opencode/commandcode-toggles.json` (overridable with
|
||||||
`COMMANDCODE_TOGGLES_FILE`); the environment variables above are used when a key is absent.
|
`COMMANDCODE_TOGGLES_FILE`); the environment variables above are used when a key is absent.
|
||||||
|
|
||||||
These commands come from a small TUI plugin shipped in this package (`src/tui.ts`, built to
|
These commands come from a small TUI plugin shipped in this package (`src/tui.tsx`). It is loaded
|
||||||
`dist/tui.js`). Register it once:
|
from **source** — opencode compiles the TSX and provides the Solid runtime itself — so there is no
|
||||||
|
build step and no runtime dependency for it. Register it once:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
opencode plugin file:///C:/DevTools/pienv/ccprovider/dist/tui.js
|
opencode plugin file:///C:/DevTools/pienv/ccprovider/src/tui.tsx
|
||||||
```
|
```
|
||||||
|
|
||||||
That writes a `tui.json` `plugin` entry (project-local `.opencode/tui.json`, or global with
|
That writes a `tui.json` `plugin` entry (project-local `.opencode/tui.json`, or global with
|
||||||
`--global`). Restart opencode after installing; then type `/cc-` for autocomplete. Note that in
|
`--global`). Restart opencode after installing; then type `/cc-` for autocomplete. The plugin also
|
||||||
opencode 1.x the slash menu and the `Ctrl+P` palette read the same command registry, so these
|
renders a **CommandCode panel in the session sidebar** (`zdr` / `debug`, above the built-in
|
||||||
entries appear in both. If you run opencode against a remote server, the TUI-side file is not
|
panels) that updates live as you toggle. Note that in opencode 1.x the slash menu and the `Ctrl+P`
|
||||||
visible to the provider on the server host — use the env vars or `providerOptions` there.
|
palette read the same command registry, so these entries appear in both. If you run opencode
|
||||||
|
against a remote server, the TUI-side file is not visible to the provider on the server host — use
|
||||||
|
the env vars or `providerOptions` there.
|
||||||
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
@ -221,6 +224,7 @@ visible to the provider on the server host — use the env vars or `providerOpti
|
|||||||
| Retry with backoff | Yes — 429/5xx and network errors, honouring `Retry-After` |
|
| Retry with backoff | Yes — 429/5xx and network errors, honouring `Retry-After` |
|
||||||
| Credential redaction | Yes — error bodies are scrubbed before surfacing |
|
| Credential redaction | Yes — error bodies are scrubbed before surfacing |
|
||||||
| Runtime toggles | Yes — `/cc-zdr` and `/cc-debug` flip the shared toggle file without a restart |
|
| Runtime toggles | Yes — `/cc-zdr` and `/cc-debug` flip the shared toggle file without a restart |
|
||||||
|
| Sidebar status panel | Yes — a `sidebar_content` panel shows `zdr`/`debug` and updates live |
|
||||||
|
|
||||||
### `tool_choice` handling
|
### `tool_choice` handling
|
||||||
|
|
||||||
@ -255,7 +259,7 @@ src/usage.ts finish event -> V3 usage; finish-reason unification.
|
|||||||
src/redact.ts Credential scrubbing for error surfaces.
|
src/redact.ts Credential scrubbing for error surfaces.
|
||||||
src/log.ts Opt-in tracing (COMMANDCODE_DEBUG) to a log file.
|
src/log.ts Opt-in tracing (COMMANDCODE_DEBUG) to a log file.
|
||||||
src/toggles.ts Shared toggle file read by the provider and written by the TUI plugin.
|
src/toggles.ts Shared toggle file read by the provider and written by the TUI plugin.
|
||||||
src/tui.ts opencode TUI plugin registering /cc-zdr, /cc-debug, /cc-status.
|
src/tui.tsx opencode TUI plugin: /cc-zdr, /cc-debug, /cc-status + sidebar status panel.
|
||||||
src/constants.ts Defaults, header names, passthrough params, static config block.
|
src/constants.ts Defaults, header names, passthrough params, static config block.
|
||||||
scripts/smoke.mjs Live end-to-end check against api.commandcode.ai.
|
scripts/smoke.mjs Live end-to-end check against api.commandcode.ai.
|
||||||
scripts/sync-models.mjs Generate provider.commandcode.models from the live catalog.
|
scripts/sync-models.mjs Generate provider.commandcode.models from the live catalog.
|
||||||
@ -283,8 +287,8 @@ scripts/sync-models.mjs Generate provider.commandcode.models from the live catal
|
|||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
npm install # installs typescript, @types/node, @ai-sdk/provider
|
npm install # installs typescript, @types/node, @ai-sdk/provider
|
||||||
npm run typecheck # tsc --noEmit
|
npm run typecheck # tsc --noEmit (provider) + tsc -p tsconfig.tui.json (TUI plugin)
|
||||||
npm run build # tsc -> dist/
|
npm run build # tsc -> dist/ (provider only; the TUI plugin loads from source)
|
||||||
npm run smoke # live request against CommandCode
|
npm run smoke # live request against CommandCode
|
||||||
npm run sync-models # regenerate provider.commandcode.models from the catalog
|
npm run sync-models # regenerate provider.commandcode.models from the catalog
|
||||||
```
|
```
|
||||||
|
|||||||
10
package.json
10
package.json
@ -11,16 +11,18 @@
|
|||||||
"types": "./dist/index.d.ts"
|
"types": "./dist/index.d.ts"
|
||||||
},
|
},
|
||||||
"./tui": {
|
"./tui": {
|
||||||
"import": "./dist/tui.js",
|
"import": "./src/tui.tsx"
|
||||||
"types": "./dist/tui.d.ts"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist",
|
||||||
|
"src/tui.tsx",
|
||||||
|
"src/tui-shims.d.ts",
|
||||||
|
"src/toggles.ts"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit && tsc -p tsconfig.tui.json",
|
||||||
"smoke": "node scripts/smoke.mjs",
|
"smoke": "node scripts/smoke.mjs",
|
||||||
"sync-models": "node scripts/sync-models.mjs"
|
"sync-models": "node scripts/sync-models.mjs"
|
||||||
},
|
},
|
||||||
|
|||||||
19
src/tui-shims.d.ts
vendored
Normal file
19
src/tui-shims.d.ts
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// Ambient shims for the symbols the TUI plugin imports from opencode's bundled runtime.
|
||||||
|
// opencode resolves `solid-js` and `@opentui/solid` to its own internal modules when it
|
||||||
|
// loads the `.tsx` plugin, so this package declares just enough surface for `tsc` without
|
||||||
|
// taking a dependency. The JSX namespace is intentionally permissive: only a handful of
|
||||||
|
// intrinsic elements are used and their props come from opencode's theme at runtime.
|
||||||
|
|
||||||
|
declare module "solid-js" {
|
||||||
|
export function createSignal<Value>(value: Value): [get: () => Value, set: (value: Value) => void];
|
||||||
|
}
|
||||||
|
|
||||||
|
declare namespace JSX {
|
||||||
|
type Element = unknown;
|
||||||
|
interface ElementChildrenAttribute {
|
||||||
|
children: {};
|
||||||
|
}
|
||||||
|
interface IntrinsicElements {
|
||||||
|
[name: string]: any;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,14 +1,21 @@
|
|||||||
// opencode TUI plugin exposing /cc-zdr, /cc-debug and /cc-status. It flips the shared
|
// opencode TUI plugin exposing /cc-zdr, /cc-debug and /cc-status, plus a sidebar panel
|
||||||
// toggle file (see ./toggles.ts) that the provider reads on every request, so a change
|
// showing the current toggle state. It flips the shared toggle file (see ./toggles.ts)
|
||||||
// takes effect without restarting opencode.
|
// that the provider reads on every request, so a change takes effect without restarting
|
||||||
|
// opencode.
|
||||||
//
|
//
|
||||||
// opencode loads this module's default export (`{ id, tui }`) from a `tui.json` plugin
|
// opencode loads this module's default export (`{ id, tui }`) from a `tui.json` plugin
|
||||||
// entry; verified against @opencode-ai/plugin@1.18.31. The local structural types below
|
// entry. It is loaded from source (not dist) so opencode's Bun/Solid transform compiles
|
||||||
// keep this package runtime-dependency-free. Note: in this opencode version slash
|
// the JSX and maps `solid-js` / `@opentui/solid` to its internal modules; that keeps this
|
||||||
// commands and the Ctrl+P palette read the same `namespace: "palette"` command list, so
|
// package free of runtime and build dependencies. The ambient types in ./tui-shims.d.ts
|
||||||
// these entries appear in both; `hidden: true` would remove them from both.
|
// back the JSX and `createSignal` references for `tsc -p tsconfig.tui.json`.
|
||||||
|
//
|
||||||
|
// Note: in this opencode version slash commands and the Ctrl+P palette read the same
|
||||||
|
// `namespace: "palette"` command list, so these entries appear in both; `hidden: true`
|
||||||
|
// would remove them from both.
|
||||||
|
|
||||||
import { readToggles, toggle, type ToggleName } from "./toggles.js";
|
import { createSignal } from "solid-js";
|
||||||
|
|
||||||
|
import { readToggles, toggle, type ToggleName, type Toggles } from "./toggles.js";
|
||||||
|
|
||||||
type ToastVariant = "info" | "success" | "warning" | "error";
|
type ToastVariant = "info" | "success" | "warning" | "error";
|
||||||
|
|
||||||
@ -26,33 +33,66 @@ type TuiCommand = {
|
|||||||
run: () => void | Promise<void>;
|
run: () => void | Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type TuiTheme = { current: { text: unknown; textMuted: unknown; success: unknown } };
|
||||||
|
|
||||||
|
type TuiSlotHandler = (ctx: { theme: TuiTheme }, props: { session_id: string }) => unknown;
|
||||||
|
|
||||||
|
type TuiSlotPlugin = { order?: number; slots: Record<string, TuiSlotHandler> };
|
||||||
|
|
||||||
type TuiApi = {
|
type TuiApi = {
|
||||||
keymap: { registerLayer(layer: { commands?: readonly TuiCommand[] }): () => void };
|
keymap: { registerLayer(layer: { commands?: readonly TuiCommand[] }): () => void };
|
||||||
ui: { toast(input: { title?: string; message: string; variant?: ToastVariant; duration?: number }): void };
|
ui: { toast(input: { title?: string; message: string; variant?: ToastVariant; duration?: number }): void };
|
||||||
|
slots: { register(plugin: TuiSlotPlugin): string };
|
||||||
|
theme: TuiTheme;
|
||||||
|
lifecycle: { onDispose(fn: () => void): () => void };
|
||||||
};
|
};
|
||||||
|
|
||||||
const ID = "commandcode-toggles";
|
const ID = "commandcode-toggles";
|
||||||
const CATEGORY = "CommandCode";
|
const CATEGORY = "CommandCode";
|
||||||
|
const SIDEBAR_ORDER = 90;
|
||||||
|
|
||||||
function state(value: boolean | undefined): string {
|
function state(value: boolean | undefined): string {
|
||||||
return value === true ? "on" : "off";
|
return value === true ? "on" : "off";
|
||||||
}
|
}
|
||||||
|
|
||||||
function summary(): string {
|
function summary(toggles: Toggles = readToggles()): string {
|
||||||
const toggles = readToggles();
|
|
||||||
return `zdr=${state(toggles.zdr)}, debug=${state(toggles.debug)}`;
|
return `zdr=${state(toggles.zdr)}, debug=${state(toggles.debug)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const tui = async (api: TuiApi): Promise<void> => {
|
export const tui = async (api: TuiApi): Promise<void> => {
|
||||||
|
const [toggles, setToggles] = createSignal(readToggles());
|
||||||
|
|
||||||
|
const refresh = (): void => setToggles(readToggles());
|
||||||
|
|
||||||
const flip = (name: ToggleName): void => {
|
const flip = (name: ToggleName): void => {
|
||||||
const next = toggle(name);
|
const next = toggle(name);
|
||||||
|
setToggles(next);
|
||||||
api.ui.toast({
|
api.ui.toast({
|
||||||
title: CATEGORY,
|
title: CATEGORY,
|
||||||
message: `${name} ${state(next[name])} (${summary()})`,
|
message: `${name} ${state(next[name])} (${summary(next)})`,
|
||||||
variant: next[name] === true ? "success" : "info",
|
variant: next[name] === true ? "success" : "info",
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Status = () => (
|
||||||
|
<box flexDirection="column" gap={0}>
|
||||||
|
<text fg={api.theme.current.text}>
|
||||||
|
<b>CommandCode</b>
|
||||||
|
</text>
|
||||||
|
<text fg={api.theme.current.textMuted}>zdr: {state(toggles().zdr)}</text>
|
||||||
|
<text fg={api.theme.current.textMuted}>debug: {state(toggles().debug)}</text>
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
|
||||||
|
api.slots.register({
|
||||||
|
order: SIDEBAR_ORDER,
|
||||||
|
slots: {
|
||||||
|
sidebar_content() {
|
||||||
|
return <Status />;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
api.keymap.registerLayer({
|
api.keymap.registerLayer({
|
||||||
commands: [
|
commands: [
|
||||||
{
|
{
|
||||||
@ -84,6 +124,9 @@ export const tui = async (api: TuiApi): Promise<void> => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const timer = setInterval(refresh, 1500);
|
||||||
|
api.lifecycle.onDispose(() => clearInterval(timer));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const id = ID;
|
export const id = ID;
|
||||||
@ -14,5 +14,6 @@
|
|||||||
"noUncheckedIndexedAccess": true,
|
"noUncheckedIndexedAccess": true,
|
||||||
"verbatimModuleSyntax": true
|
"verbatimModuleSyntax": true
|
||||||
},
|
},
|
||||||
"include": ["src"]
|
"include": ["src"],
|
||||||
|
"exclude": ["src/tui.tsx", "src/tui-shims.d.ts"]
|
||||||
}
|
}
|
||||||
|
|||||||
18
tsconfig.tui.json
Normal file
18
tsconfig.tui.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"types": ["node"],
|
||||||
|
"strict": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"noEmit": true
|
||||||
|
},
|
||||||
|
"include": ["src/tui.tsx", "src/tui-shims.d.ts"]
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user