@opencode-cockpit/shell 0.1.4 → 0.2.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.
- package/README.md +100 -10
- package/dist/agent/plugin.js +180 -0
- package/dist/agent/tools/index.js +23 -0
- package/{src/tools/keys.ts → dist/agent/tools/keys.js} +11 -14
- package/dist/agent/tools/list.js +68 -0
- package/dist/agent/tools/read.js +59 -0
- package/dist/agent/tools/restart.js +44 -0
- package/dist/agent/tools/send.js +71 -0
- package/dist/agent/tools/shared.js +94 -0
- package/dist/agent/tools/start.js +140 -0
- package/dist/agent/tools/stop.js +42 -0
- package/dist/agent/tools/wait.js +79 -0
- package/dist/agent/tools/watch-args.js +12 -0
- package/dist/agent/tools/watch.js +74 -0
- package/dist/connect.js +31 -0
- package/dist/core/config.js +97 -0
- package/dist/core/find.js +69 -0
- package/dist/core/format.js +63 -0
- package/dist/core/kind.js +31 -0
- package/dist/server.js +2 -0
- package/dist/tui/components/badge.js +29 -0
- package/dist/tui/components/console.js +759 -0
- package/dist/tui/components/dock.js +270 -0
- package/dist/tui/components/sidebar.js +136 -0
- package/dist/tui/dialogs.js +163 -0
- package/dist/tui/index.js +205 -0
- package/dist/tui/lib/details.js +23 -0
- package/dist/tui/lib/keys.js +43 -0
- package/dist/tui/lib/search.js +39 -0
- package/dist/tui/lib/update.js +58 -0
- package/dist/tui/lib/view.js +191 -0
- package/dist/tui/state/store.js +158 -0
- package/package.json +21 -12
- package/types/agent/plugin.d.ts +12 -0
- package/types/agent/tools/index.d.ts +5 -0
- package/types/agent/tools/keys.d.ts +3 -0
- package/types/agent/tools/list.d.ts +3 -0
- package/types/agent/tools/read.d.ts +3 -0
- package/types/agent/tools/restart.d.ts +3 -0
- package/types/agent/tools/send.d.ts +3 -0
- package/types/agent/tools/shared.d.ts +40 -0
- package/types/agent/tools/start.d.ts +3 -0
- package/types/agent/tools/stop.d.ts +3 -0
- package/types/agent/tools/wait.d.ts +3 -0
- package/types/agent/tools/watch-args.d.ts +10 -0
- package/types/agent/tools/watch.d.ts +3 -0
- package/types/connect.d.ts +9 -0
- package/types/core/config.d.ts +59 -0
- package/types/core/find.d.ts +37 -0
- package/types/core/format.d.ts +8 -0
- package/types/core/kind.d.ts +9 -0
- package/types/server.d.ts +2 -0
- package/types/tui/components/badge.d.ts +9 -0
- package/types/tui/components/console.d.ts +22 -0
- package/types/tui/components/dock.d.ts +14 -0
- package/types/tui/components/sidebar.d.ts +14 -0
- package/types/tui/dialogs.d.ts +10 -0
- package/types/tui/index.d.ts +13 -0
- package/types/tui/lib/details.d.ts +3 -0
- package/types/tui/lib/keys.d.ts +8 -0
- package/types/tui/lib/search.d.ts +7 -0
- package/types/tui/lib/update.d.ts +25 -0
- package/types/tui/lib/view.d.ts +80 -0
- package/types/tui/state/store.d.ts +55 -0
- package/src/connect.ts +0 -24
- package/src/server.ts +0 -156
- package/src/tools/find.ts +0 -80
- package/src/tools/format.ts +0 -78
- package/src/tools/index.ts +0 -467
- package/src/tui/badge.tsx +0 -17
- package/src/tui/console.tsx +0 -438
- package/src/tui/dock.tsx +0 -130
- package/src/tui/index.tsx +0 -286
- package/src/tui/keys.ts +0 -52
- package/src/tui/sidebar.tsx +0 -44
- package/src/tui/store.ts +0 -185
- package/src/tui/view.ts +0 -161
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { createEffect, createMemo, createRoot, createSignal, on, onCleanup } from "solid-js";
|
|
3
|
+
import { createStore, reconcile } from "solid-js/store";
|
|
4
|
+
import { order, partition } from "../lib/view.js";
|
|
5
|
+
export function createShellStore(api, client, options = {}) {
|
|
6
|
+
return createRoot(dispose => {
|
|
7
|
+
const [state, setState] = createStore({
|
|
8
|
+
list: []
|
|
9
|
+
});
|
|
10
|
+
const [connected, setConnected] = createSignal(false);
|
|
11
|
+
const [now, setNow] = createSignal(Date.now());
|
|
12
|
+
const [frame, setFrame] = createSignal(0);
|
|
13
|
+
const [selectedId, setSelectedId] = createSignal();
|
|
14
|
+
const [showAll, setShowAll] = createSignal(api.kv.get("cockpit.shells.showAll", false));
|
|
15
|
+
const project = () => api.state.path.directory;
|
|
16
|
+
const historyMs = (options.historyMinutes ?? 30) * 60_000;
|
|
17
|
+
const refresh = async () => {
|
|
18
|
+
try {
|
|
19
|
+
const list = await client.call("shell.list", {
|
|
20
|
+
owner: {
|
|
21
|
+
project: project()
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
setState("list", reconcile(list, {
|
|
25
|
+
key: "id"
|
|
26
|
+
}));
|
|
27
|
+
} catch {
|
|
28
|
+
// daemon not running yet; the reconnect loop will pick it up
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
const offs = [client.on("shell.started", () => void refresh()), client.on("shell.exited", () => void refresh()), client.on("shell.removed", () => void refresh()), client.onState(s => {
|
|
32
|
+
setConnected(s === "connected");
|
|
33
|
+
if (s === "connected") void refresh();
|
|
34
|
+
})];
|
|
35
|
+
|
|
36
|
+
// Connect only to a daemon that already exists; starting one is left to explicit actions.
|
|
37
|
+
const probe = () => {
|
|
38
|
+
if (!client.connected && existsSync(client.paths.socket)) void client.connect().catch(() => {});
|
|
39
|
+
};
|
|
40
|
+
probe();
|
|
41
|
+
const probeTimer = setInterval(probe, 3000);
|
|
42
|
+
const tick = setInterval(() => setNow(Date.now()), 1000);
|
|
43
|
+
const spin = setInterval(() => {
|
|
44
|
+
if (state.list.some(s => s.status === "running")) setFrame(f => f + 1);
|
|
45
|
+
}, 120);
|
|
46
|
+
onCleanup(() => {
|
|
47
|
+
clearInterval(probeTimer);
|
|
48
|
+
clearInterval(tick);
|
|
49
|
+
clearInterval(spin);
|
|
50
|
+
for (const off of offs) off();
|
|
51
|
+
});
|
|
52
|
+
const pick = createMemo(() => {
|
|
53
|
+
const ordered = order(state.list);
|
|
54
|
+
return ordered.find(s => s.id === selectedId()) ?? ordered[0];
|
|
55
|
+
});
|
|
56
|
+
const folded = createMemo(() => partition(state.list, {
|
|
57
|
+
showAll: showAll(),
|
|
58
|
+
historyMs,
|
|
59
|
+
now: now(),
|
|
60
|
+
keep: pick()?.id
|
|
61
|
+
}));
|
|
62
|
+
return {
|
|
63
|
+
client,
|
|
64
|
+
project,
|
|
65
|
+
shells: () => state.list,
|
|
66
|
+
visible: () => folded().visible,
|
|
67
|
+
hidden: () => folded().hidden,
|
|
68
|
+
showAll,
|
|
69
|
+
toggleAll() {
|
|
70
|
+
const next = !showAll();
|
|
71
|
+
setShowAll(next);
|
|
72
|
+
api.kv.set("cockpit.shells.showAll", next);
|
|
73
|
+
},
|
|
74
|
+
connected,
|
|
75
|
+
now,
|
|
76
|
+
frame,
|
|
77
|
+
selected: pick,
|
|
78
|
+
select: id => setSelectedId(id),
|
|
79
|
+
// Cycles every shell in the project: folding only exists to keep the sidebar short, and a
|
|
80
|
+
// shell you cannot reach from the console would be a trap.
|
|
81
|
+
step(delta) {
|
|
82
|
+
const list = order(state.list);
|
|
83
|
+
if (list.length === 0) return;
|
|
84
|
+
const index = Math.max(0, list.findIndex(s => s.id === pick()?.id));
|
|
85
|
+
const next = list[(index + delta + list.length) % list.length];
|
|
86
|
+
if (next) setSelectedId(next.id);
|
|
87
|
+
},
|
|
88
|
+
refresh,
|
|
89
|
+
async clearFinished() {
|
|
90
|
+
const {
|
|
91
|
+
removed
|
|
92
|
+
} = await client.call("shell.clear", {
|
|
93
|
+
owner: {
|
|
94
|
+
project: project()
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
await refresh();
|
|
98
|
+
return removed.length;
|
|
99
|
+
},
|
|
100
|
+
dispose
|
|
101
|
+
};
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Live terminal view of one shell: attaches for change notifications and re-renders the daemon's
|
|
107
|
+
* emulated screen, throttled. Must be called inside a reactive owner.
|
|
108
|
+
*/
|
|
109
|
+
export function useScreen(store, id) {
|
|
110
|
+
const [screen, setScreen] = createSignal();
|
|
111
|
+
let timer;
|
|
112
|
+
let current;
|
|
113
|
+
const fetch = shellId => {
|
|
114
|
+
timer = undefined;
|
|
115
|
+
void store.client.call("shell.screen", {
|
|
116
|
+
id: shellId
|
|
117
|
+
}).then(s => {
|
|
118
|
+
if (current === shellId) setScreen(s);
|
|
119
|
+
}).catch(() => {});
|
|
120
|
+
};
|
|
121
|
+
const schedule = shellId => {
|
|
122
|
+
timer ??= setTimeout(() => fetch(shellId), 80);
|
|
123
|
+
};
|
|
124
|
+
const offOutput = store.client.on("shell.output", e => {
|
|
125
|
+
if (e.id === current) schedule(e.id);
|
|
126
|
+
});
|
|
127
|
+
const offExit = store.client.on("shell.exited", info => {
|
|
128
|
+
if (info.id === current) schedule(info.id);
|
|
129
|
+
});
|
|
130
|
+
const attach = next => {
|
|
131
|
+
if (next === current) return;
|
|
132
|
+
if (current) void store.client.call("shell.detach", {
|
|
133
|
+
id: current
|
|
134
|
+
}).catch(() => {});
|
|
135
|
+
current = next;
|
|
136
|
+
setScreen(undefined);
|
|
137
|
+
if (!next) return;
|
|
138
|
+
const info = store.shells().find(s => s.id === next);
|
|
139
|
+
void store.client.call("shell.attach", {
|
|
140
|
+
id: next,
|
|
141
|
+
fromOffset: info?.bytes ?? Number.MAX_SAFE_INTEGER
|
|
142
|
+
}).catch(() => {});
|
|
143
|
+
fetch(next);
|
|
144
|
+
};
|
|
145
|
+
createEffect(on(id, next => attach(next)));
|
|
146
|
+
onCleanup(() => {
|
|
147
|
+
clearTimeout(timer);
|
|
148
|
+
offOutput();
|
|
149
|
+
offExit();
|
|
150
|
+
if (current) void store.client.call("shell.detach", {
|
|
151
|
+
id: current
|
|
152
|
+
}).catch(() => {});
|
|
153
|
+
});
|
|
154
|
+
return {
|
|
155
|
+
screen,
|
|
156
|
+
refetch: () => current && fetch(current)
|
|
157
|
+
};
|
|
158
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opencode-cockpit/shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Background shells for OpenCode: the agent starts, waits on and drives PTYs; you watch them in a docked TUI panel",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,16 +24,26 @@
|
|
|
24
24
|
"terminal"
|
|
25
25
|
],
|
|
26
26
|
"exports": {
|
|
27
|
-
"./server":
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
"./server": {
|
|
28
|
+
"types": "./types/server.d.ts",
|
|
29
|
+
"default": "./dist/server.js"
|
|
30
|
+
},
|
|
31
|
+
"./tui": {
|
|
32
|
+
"types": "./types/tui/index.d.ts",
|
|
33
|
+
"default": "./dist/tui/index.js"
|
|
34
|
+
},
|
|
35
|
+
"./connect": {
|
|
36
|
+
"types": "./types/connect.d.ts",
|
|
37
|
+
"default": "./dist/connect.js"
|
|
38
|
+
}
|
|
30
39
|
},
|
|
31
40
|
"engines": {
|
|
32
41
|
"opencode": ">=1.18.0",
|
|
33
42
|
"bun": ">=1.3.5"
|
|
34
43
|
},
|
|
35
44
|
"files": [
|
|
36
|
-
"
|
|
45
|
+
"dist",
|
|
46
|
+
"types",
|
|
37
47
|
"README.md",
|
|
38
48
|
"LICENSE"
|
|
39
49
|
],
|
|
@@ -41,16 +51,15 @@
|
|
|
41
51
|
"access": "public"
|
|
42
52
|
},
|
|
43
53
|
"dependencies": {
|
|
44
|
-
"@opencode-cockpit/client": "0.
|
|
45
|
-
"@opencode-cockpit/daemon": "0.
|
|
46
|
-
"@opencode-cockpit/protocol": "0.
|
|
47
|
-
"@opencode-ai/plugin": "1.18.31"
|
|
54
|
+
"@opencode-cockpit/client": "0.2.0",
|
|
55
|
+
"@opencode-cockpit/daemon": "0.2.0",
|
|
56
|
+
"@opencode-cockpit/protocol": "0.2.0",
|
|
57
|
+
"@opencode-ai/plugin": "1.18.31"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
48
60
|
"@opentui/core": "0.4.5",
|
|
49
61
|
"@opentui/keymap": "0.4.5",
|
|
50
62
|
"@opentui/solid": "0.4.5",
|
|
51
63
|
"solid-js": "1.9.12"
|
|
52
|
-
},
|
|
53
|
-
"devDependencies": {
|
|
54
|
-
"zod": "4.6.5"
|
|
55
64
|
}
|
|
56
65
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Plugin, PluginModule } from "@opencode-ai/plugin";
|
|
2
|
+
export declare const SHELL_PACKAGE = "@opencode-cockpit/shell";
|
|
3
|
+
export interface ShellServerOptions {
|
|
4
|
+
/** Package that loaded Shell, reported when a duplicate copy is skipped. */
|
|
5
|
+
source?: string;
|
|
6
|
+
}
|
|
7
|
+
/** Shell's server half as a factory, so bundles such as `opencode-cockpit` can include it. */
|
|
8
|
+
export declare function createShellServer({ source }?: ShellServerOptions): Plugin;
|
|
9
|
+
declare const plugin: PluginModule & {
|
|
10
|
+
id: string;
|
|
11
|
+
};
|
|
12
|
+
export default plugin;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ToolDefinition } from "@opencode-ai/plugin";
|
|
2
|
+
import { type ToolDeps } from "./shared.ts";
|
|
3
|
+
export type { ToolDeps } from "./shared.ts";
|
|
4
|
+
/** The shell tools an agent sees. One file per tool; `shared.ts` holds what they have in common. */
|
|
5
|
+
export declare function createTools(deps: ToolDeps): Record<string, ToolDefinition>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ToolContext } from "@opencode-ai/plugin";
|
|
2
|
+
import type { CockpitClient } from "@opencode-cockpit/client";
|
|
3
|
+
import type { ShellInfo } from "@opencode-cockpit/protocol/shell";
|
|
4
|
+
import type { CockpitConfig } from "../../core/config.ts";
|
|
5
|
+
export interface ToolDeps {
|
|
6
|
+
client: CockpitClient;
|
|
7
|
+
/** Identifies this OpenCode instance so only it notifies the owning session. */
|
|
8
|
+
instance: string;
|
|
9
|
+
/** Shells whose exit should not message the agent (it stopped them itself, or opted out). */
|
|
10
|
+
quiet: Set<string>;
|
|
11
|
+
shellCommand(command: string): {
|
|
12
|
+
command: string;
|
|
13
|
+
args: string[];
|
|
14
|
+
};
|
|
15
|
+
env(): Record<string, string>;
|
|
16
|
+
/** Settings that shape defaults, kinds and watch presets. */
|
|
17
|
+
config?: CockpitConfig;
|
|
18
|
+
/** Human title of an OpenCode session, for telling agents which session started a shell. */
|
|
19
|
+
sessionTitle?(sessionID: string): Promise<string | undefined>;
|
|
20
|
+
}
|
|
21
|
+
/** What every tool shares: the client, name resolution, permission prompts and abort handling. */
|
|
22
|
+
export interface ToolKit {
|
|
23
|
+
deps: ToolDeps;
|
|
24
|
+
/** Settings from ~/.config/opencode-cockpit/config.json, .cockpit.json and plugin options. */
|
|
25
|
+
config: CockpitConfig;
|
|
26
|
+
client: CockpitClient;
|
|
27
|
+
peek(info: ShellInfo, tail?: number): Promise<string>;
|
|
28
|
+
sessionLabel(shell: ShellInfo, ctx: ToolContext): Promise<string>;
|
|
29
|
+
resolve(args: {
|
|
30
|
+
id?: string | null;
|
|
31
|
+
name?: string | null;
|
|
32
|
+
}, ctx: ToolContext): Promise<{
|
|
33
|
+
id: string;
|
|
34
|
+
note: string;
|
|
35
|
+
}>;
|
|
36
|
+
}
|
|
37
|
+
export declare function createToolKit(deps: ToolDeps): ToolKit;
|
|
38
|
+
export declare function askPermission(ctx: ToolContext, command: string): Promise<void>;
|
|
39
|
+
/** Resolves undefined when the tool call is aborted; the daemon keeps running the shell. */
|
|
40
|
+
export declare function abortable<T>(ctx: ToolContext, promise: Promise<T>): Promise<T | undefined>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { WatchRule } from "@opencode-cockpit/protocol/shell";
|
|
2
|
+
import type { CockpitConfig } from "../../core/config.ts";
|
|
3
|
+
/**
|
|
4
|
+
* Turns a preset name into what `shell.watch` needs. A preset defined in config is sent as an
|
|
5
|
+
* explicit rule, so users can add tools or correct a built-in without touching the daemon.
|
|
6
|
+
*/
|
|
7
|
+
export declare function watchArgs(preset: string, config: CockpitConfig): {
|
|
8
|
+
preset?: string;
|
|
9
|
+
rule?: WatchRule;
|
|
10
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { CockpitClient } from "@opencode-cockpit/client";
|
|
2
|
+
/** Resolves the daemon entry shipped with this package. */
|
|
3
|
+
export declare function daemonEntry(): string;
|
|
4
|
+
/**
|
|
5
|
+
* Inside OpenCode `process.execPath` is the OpenCode binary; the client starts the daemon with
|
|
6
|
+
* BUN_BE_BUN=1 so it runs on OpenCode's embedded Bun (ADR 0001). The expected build lets the
|
|
7
|
+
* client replace a daemon left running from older plugin code.
|
|
8
|
+
*/
|
|
9
|
+
export declare function createClient(name: string): CockpitClient;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { WatchRule } from "@opencode-cockpit/protocol/shell";
|
|
2
|
+
/**
|
|
3
|
+
* Settings, read from one file so they are written once instead of twice (OpenCode keeps agent and
|
|
4
|
+
* TUI plugins in separate configs). Precedence, lowest first:
|
|
5
|
+
*
|
|
6
|
+
* ~/.config/opencode-cockpit/config.json → <project>/.cockpit.json → plugin-entry options
|
|
7
|
+
*
|
|
8
|
+
* Everything is optional, and an unreadable or invalid file is ignored rather than fatal: a typo in
|
|
9
|
+
* a config should never stop shells from working.
|
|
10
|
+
*/
|
|
11
|
+
export interface CockpitConfig {
|
|
12
|
+
/** Health watching (see shell_watch). */
|
|
13
|
+
watch?: {
|
|
14
|
+
/** Attach a matching preset to every new shell without being asked. Off by default. */
|
|
15
|
+
auto?: boolean;
|
|
16
|
+
/** Extra rules, or replacements for built-ins, keyed by preset name. */
|
|
17
|
+
presets?: Record<string, WatchRule>;
|
|
18
|
+
};
|
|
19
|
+
/** Extra shell kinds, or overrides, as name → regular expression matched against the command. */
|
|
20
|
+
kinds?: Record<string, string>;
|
|
21
|
+
/** Applied to every shell the agent starts, unless the call says otherwise. */
|
|
22
|
+
defaults?: {
|
|
23
|
+
watch?: boolean | string;
|
|
24
|
+
logFile?: boolean;
|
|
25
|
+
idleTimeoutSeconds?: number;
|
|
26
|
+
timeoutSeconds?: number;
|
|
27
|
+
notifyOnExit?: boolean;
|
|
28
|
+
};
|
|
29
|
+
/** What is allowed to interrupt the agent. */
|
|
30
|
+
notify?: {
|
|
31
|
+
exit?: boolean;
|
|
32
|
+
watch?: boolean;
|
|
33
|
+
/** Output lines included in an exit message. */
|
|
34
|
+
tailLines?: number;
|
|
35
|
+
};
|
|
36
|
+
/** The system-prompt guidance that teaches the agent to use shells (~120 tokens per request). */
|
|
37
|
+
guidance?: boolean;
|
|
38
|
+
/** Running shells listed in the system prompt each turn; 0 disables (~20 tokens each). */
|
|
39
|
+
listRunningShells?: number;
|
|
40
|
+
/** Interface options; also settable on the tui.json plugin entry. */
|
|
41
|
+
ui?: {
|
|
42
|
+
dockHeight?: number;
|
|
43
|
+
dockOpen?: boolean;
|
|
44
|
+
sidebarRows?: number;
|
|
45
|
+
historyMinutes?: number;
|
|
46
|
+
colors?: boolean;
|
|
47
|
+
defaultView?: "screen" | "log";
|
|
48
|
+
keybinds?: Record<string, string>;
|
|
49
|
+
updateCheck?: boolean;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export declare const CONFIG_FILE = "config.json";
|
|
53
|
+
export declare const PROJECT_FILE = ".cockpit.json";
|
|
54
|
+
export declare function globalConfigPath(env?: Record<string, string | undefined>): string;
|
|
55
|
+
/** Reads and merges every source. `options` is the plugin entry's own options object. */
|
|
56
|
+
export declare function loadConfig(directory: string, options?: unknown, env?: Record<string, string | undefined>): CockpitConfig;
|
|
57
|
+
export declare function readConfigFile(path: string): CockpitConfig;
|
|
58
|
+
/** Section-wise merge: later sources win key by key, and never lose a whole section. */
|
|
59
|
+
export declare function mergeConfig(base: CockpitConfig, over: CockpitConfig): CockpitConfig;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ShellInfo } from "@opencode-cockpit/protocol/shell";
|
|
2
|
+
import { type ShellKind } from "./kind.ts";
|
|
3
|
+
/** The command as written, without the `$SHELL -c` wrapper. */
|
|
4
|
+
export declare function commandOf(s: ShellInfo): string;
|
|
5
|
+
export type StatusFilter = "running" | "failed" | "finished" | "any";
|
|
6
|
+
export type SessionFilter = "this" | "others" | "any";
|
|
7
|
+
export interface ShellFilter {
|
|
8
|
+
/** What the shell is, derived from its command (server, tests, build, watcher, task). */
|
|
9
|
+
kind?: ShellKind | "any";
|
|
10
|
+
/** Case-insensitive text found in the name or the command. */
|
|
11
|
+
query?: string;
|
|
12
|
+
status?: StatusFilter;
|
|
13
|
+
session?: SessionFilter;
|
|
14
|
+
/** The asking agent's session, for `session` filtering. */
|
|
15
|
+
currentSession?: string;
|
|
16
|
+
/** Extra kind patterns from config, so a kind it defines is also filterable. */
|
|
17
|
+
kinds?: Record<string, string>;
|
|
18
|
+
}
|
|
19
|
+
export declare function isFailed(s: ShellInfo): boolean;
|
|
20
|
+
export declare function filterShells(list: readonly ShellInfo[], filter: ShellFilter): ShellInfo[];
|
|
21
|
+
export type NameMatch = {
|
|
22
|
+
kind: "found";
|
|
23
|
+
shell: ShellInfo;
|
|
24
|
+
alsoMatched: ShellInfo[];
|
|
25
|
+
} | {
|
|
26
|
+
kind: "ambiguous";
|
|
27
|
+
candidates: ShellInfo[];
|
|
28
|
+
} | {
|
|
29
|
+
kind: "none";
|
|
30
|
+
available: ShellInfo[];
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Finds the shell a name refers to. Exact names (ignoring case) beat partial matches on name or
|
|
34
|
+
* command. When several match, a single running shell is the obvious intent (earlier finished
|
|
35
|
+
* shells with the same name are history); otherwise the caller must choose.
|
|
36
|
+
*/
|
|
37
|
+
export declare function matchByName(list: readonly ShellInfo[], name: string): NameMatch;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { LogLine, ReadResult, ShellInfo, WaitResult } from "@opencode-cockpit/protocol/shell";
|
|
2
|
+
/** Compact log lines for a model: numbered, long lines cut, consecutive repeats collapsed. */
|
|
3
|
+
export declare function formatLines(lines: LogLine[]): string;
|
|
4
|
+
export declare function describeStatus(info: ShellInfo): string;
|
|
5
|
+
export declare function header(info: ShellInfo): string;
|
|
6
|
+
export declare function formatRead(info: ShellInfo, page: ReadResult, empty?: string): string;
|
|
7
|
+
export declare function formatWait(result: WaitResult, timeoutSeconds: number): string;
|
|
8
|
+
export declare function duration(ms: number): string;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ShellInfo } from "@opencode-cockpit/protocol/shell";
|
|
2
|
+
/** What a shell is, derived from its command — never a label anyone has to maintain by hand. */
|
|
3
|
+
export type ShellKind = "server" | "tests" | "watcher" | "build" | "task" | (string & {});
|
|
4
|
+
/**
|
|
5
|
+
* Order matters: a test runner in watch mode is still about tests, and `vite build` is a build even
|
|
6
|
+
* though `vite` usually serves.
|
|
7
|
+
*/
|
|
8
|
+
export declare function classify(command: string, custom?: Record<string, string>): ShellKind;
|
|
9
|
+
export declare function kindOfShell(s: ShellInfo, custom?: Record<string, string>): ShellKind;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** @jsxImportSource @opentui/solid */
|
|
2
|
+
import type { TuiPluginApi } from "@opencode-ai/plugin/tui";
|
|
3
|
+
import type { ShellInfo } from "@opencode-cockpit/protocol/shell";
|
|
4
|
+
/** Status pill: label on a coloured background, readable in any font and colour scheme. */
|
|
5
|
+
export declare function Badge(props: {
|
|
6
|
+
api: TuiPluginApi;
|
|
7
|
+
shell: ShellInfo;
|
|
8
|
+
frame: number;
|
|
9
|
+
}): import("solid-js").JSX.Element;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** @jsxImportSource @opentui/solid */
|
|
2
|
+
import type { TuiPluginApi } from "@opencode-ai/plugin/tui";
|
|
3
|
+
import { type ShellStore } from "../state/store.ts";
|
|
4
|
+
export interface ConsoleProps {
|
|
5
|
+
api: TuiPluginApi;
|
|
6
|
+
store: ShellStore;
|
|
7
|
+
/** Start in typing mode. */
|
|
8
|
+
typing?: boolean;
|
|
9
|
+
/** Paint the colours programs print (config: ui.colors). */
|
|
10
|
+
colors?: boolean;
|
|
11
|
+
/** Which view a freshly opened console shows (config: ui.defaultView). */
|
|
12
|
+
defaultView?: "screen" | "log";
|
|
13
|
+
onClose: () => void;
|
|
14
|
+
onNewShell: () => void;
|
|
15
|
+
}
|
|
16
|
+
/** Splits a line into plain and matching parts so a filtered log can highlight what matched. */
|
|
17
|
+
/**
|
|
18
|
+
* Keyboard-first shell console in an overlay. Normal mode: single-key actions. Typing mode:
|
|
19
|
+
* every key goes to the program (ctrl+c included); ctrl+] returns to normal mode.
|
|
20
|
+
*/
|
|
21
|
+
export declare function Console(props: ConsoleProps): import("solid-js").JSX.Element;
|
|
22
|
+
/** Everything about a shell, with the full command wrapped rather than cut. */
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** @jsxImportSource @opentui/solid */
|
|
2
|
+
import type { TuiPluginApi } from "@opencode-ai/plugin/tui";
|
|
3
|
+
import type { ShellStore } from "../state/store.ts";
|
|
4
|
+
export interface DockProps {
|
|
5
|
+
api: TuiPluginApi;
|
|
6
|
+
store: ShellStore;
|
|
7
|
+
height: number;
|
|
8
|
+
/** Paint the colours programs print (config: ui.colors). */
|
|
9
|
+
colors?: boolean;
|
|
10
|
+
hint: () => string;
|
|
11
|
+
onOpenConsole: (id: string) => void;
|
|
12
|
+
}
|
|
13
|
+
/** Split pane under the chat: status tabs for shells and the selected shell's live screen. */
|
|
14
|
+
export declare function Dock(props: DockProps): import("solid-js").JSX.Element;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** @jsxImportSource @opentui/solid */
|
|
2
|
+
import type { TuiPluginApi } from "@opencode-ai/plugin/tui";
|
|
3
|
+
import type { ShellStore } from "../state/store.ts";
|
|
4
|
+
export interface SidebarProps {
|
|
5
|
+
api: TuiPluginApi;
|
|
6
|
+
store: ShellStore;
|
|
7
|
+
/** Rows shown before the rest folds away; the sidebar is a narrow, shared column. */
|
|
8
|
+
rows?: number;
|
|
9
|
+
/** Rows shown while expanded, so a hundred shells can never push the sidebar over. */
|
|
10
|
+
expandedRows?: number;
|
|
11
|
+
onOpen: (id: string) => void;
|
|
12
|
+
consoleShortcut: () => string;
|
|
13
|
+
}
|
|
14
|
+
export declare function SidebarShells(props: SidebarProps): import("solid-js").JSX.Element;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** @jsxImportSource @opentui/solid */
|
|
2
|
+
import type { TuiPluginApi, TuiPluginMeta } from "@opencode-ai/plugin/tui";
|
|
3
|
+
import type { ShellStore } from "./state/store.ts";
|
|
4
|
+
/** One quiet registry check a day; a newer release is announced once per version. */
|
|
5
|
+
export declare function announceUpdate(api: TuiPluginApi, meta: TuiPluginMeta, current: string): Promise<void>;
|
|
6
|
+
/** Clears this plugin's cache entry so the next OpenCode start installs the newer release. */
|
|
7
|
+
export declare function offerUpdate(api: TuiPluginApi, meta: TuiPluginMeta, current: string): void;
|
|
8
|
+
export declare function newShell(api: TuiPluginApi, store: ShellStore, open: (id?: string) => void): void;
|
|
9
|
+
export declare function restartDaemon(api: TuiPluginApi, store: ShellStore): void;
|
|
10
|
+
export declare function pickShell(api: TuiPluginApi, store: ShellStore, open: (id?: string) => void): void;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** @jsxImportSource @opentui/solid */
|
|
2
|
+
import { type TuiPlugin, type TuiPluginModule } from "@opencode-ai/plugin/tui";
|
|
3
|
+
import { type CockpitConfig } from "../core/config.ts";
|
|
4
|
+
/** Interface settings; the `ui` section of the config file (see core/config.ts). */
|
|
5
|
+
export type ShellTuiOptions = NonNullable<CockpitConfig["ui"]>;
|
|
6
|
+
/** Shell's TUI half as a factory, so bundles such as `opencode-cockpit` can include it. */
|
|
7
|
+
export declare function createShellTui({ source }?: {
|
|
8
|
+
source?: string;
|
|
9
|
+
}): TuiPlugin;
|
|
10
|
+
declare const plugin: TuiPluginModule & {
|
|
11
|
+
id: string;
|
|
12
|
+
};
|
|
13
|
+
export default plugin;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { KeyEvent } from "@opentui/core";
|
|
2
|
+
/**
|
|
3
|
+
* Re-encodes a parsed key as legacy terminal bytes. The TUI may receive keys via the kitty
|
|
4
|
+
* protocol, whose raw form programs in the PTY would not understand.
|
|
5
|
+
*/
|
|
6
|
+
export declare function keyToBytes(e: KeyEvent): string | undefined;
|
|
7
|
+
/** ctrl+] releases typing mode (the telnet escape), since esc and ctrl+c belong to the program. */
|
|
8
|
+
export declare function isReleaseKey(e: KeyEvent): boolean;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Log search helpers: what matched, and how failures should read. */
|
|
2
|
+
export declare function splitMatches(text: string, query: string): {
|
|
3
|
+
text: string;
|
|
4
|
+
match: boolean;
|
|
5
|
+
}[];
|
|
6
|
+
/** Daemon errors name ids and internal states; say what happened instead. */
|
|
7
|
+
export declare function friendlyError(err: unknown): string;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update notice.
|
|
3
|
+
*
|
|
4
|
+
* OpenCode resolves an unpinned plugin spec (`@latest`) once and then reuses its cached copy, so
|
|
5
|
+
* an install never moves forward on its own. The plugin therefore checks the registry itself, at
|
|
6
|
+
* most once a day, and offers to clear its own cache entry so the next start installs the newer
|
|
7
|
+
* version.
|
|
8
|
+
*/
|
|
9
|
+
export interface UpdateState {
|
|
10
|
+
/** Version currently running. */
|
|
11
|
+
current: string;
|
|
12
|
+
/** Newest version on the registry. */
|
|
13
|
+
latest: string;
|
|
14
|
+
/** Cache entry to remove so OpenCode reinstalls, when this is an npm install. */
|
|
15
|
+
cacheDir?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function shouldCheck(lastCheckedAt: number | undefined, now: number, everyMs?: number): boolean;
|
|
18
|
+
/** True when `latest` is a higher release than `current`. Pre-releases never look newer. */
|
|
19
|
+
export declare function isNewer(latest: string, current: string): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* The directory OpenCode installed this plugin into, given the resolved module path. Removing it
|
|
22
|
+
* makes the next start reinstall from the registry. Undefined for anything but an npm install.
|
|
23
|
+
*/
|
|
24
|
+
export declare function cacheDirFor(target: string | undefined, source: string): string | undefined;
|
|
25
|
+
export declare function fetchLatestVersion(pkg: string, timeoutMs?: number): Promise<string | undefined>;
|