opencode-cockpit 0.1.4 → 0.1.5
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 +118 -34
- package/dist/compose.js +38 -0
- package/dist/features.js +23 -0
- package/dist/server.js +17 -0
- package/dist/tui.js +14 -0
- package/package.json +13 -6
- package/types/compose.d.ts +7 -0
- package/types/features.d.ts +17 -0
- package/types/server.d.ts +5 -0
- package/types/tui.d.ts +5 -0
- package/src/compose.ts +0 -43
- package/src/features.ts +0 -34
- package/src/server.ts +0 -16
- package/src/tui.ts +0 -13
package/README.md
CHANGED
|
@@ -1,15 +1,91 @@
|
|
|
1
1
|
# opencode-cockpit
|
|
2
2
|
|
|
3
3
|
[](https://github.com/Codestz/opencode-cockpit/actions/workflows/ci.yml)
|
|
4
|
-
[](https://www.npmjs.com/package/opencode-cockpit)
|
|
4
|
+
[](https://www.npmjs.com/package/opencode-cockpit)
|
|
5
|
+
[](https://www.npmjs.com/package/@opencode-cockpit/shell)
|
|
5
6
|
[](https://github.com/Codestz/opencode-cockpit/blob/main/LICENSE)
|
|
6
7
|
|
|
7
|
-
Superpowers for [OpenCode](https://opencode.ai)
|
|
8
|
+
**Superpowers for [OpenCode](https://opencode.ai) — take all of them, or just the one you need.**
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
Coding agents are stuck in a one-command-at-a-time world: they run something, wait for it to
|
|
11
|
+
finish, and paste the whole log back into their context. Cockpit gives your agent the things a
|
|
12
|
+
developer actually has — long-running terminals, a way to wait for "ready", and output it can read
|
|
13
|
+
without drowning in it — and gives *you* a live view of all of it, inside OpenCode.
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
────────────────────────────────────────────────────────────────────────────────────────
|
|
17
|
+
Shells ⠹ RUN dev server FAIL unit tests ▸ 3 more ctrl+x i console · ctrl+x o hide
|
|
18
|
+
VITE v7.3.1 ready in 431 ms
|
|
19
|
+
➜ Local: http://localhost:5173/
|
|
20
|
+
✓ 142 modules transformed
|
|
21
|
+
2m14s sh_k4tq8b2p · $ npm run dev
|
|
22
|
+
────────────────────────────────────────────────────────────────────────────────────────
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Features
|
|
26
|
+
|
|
27
|
+
| Feature | What your agent gains | Package |
|
|
28
|
+
|---|---|---|
|
|
29
|
+
| **Shell** | Background terminals it starts, waits on, reads and types into — dev servers, watchers, test suites, REPLs | [`@opencode-cockpit/shell`](https://github.com/Codestz/opencode-cockpit/tree/main/packages/shell) |
|
|
30
|
+
| **Agents** *(next)* | A live, keyboard-first view of every subagent, without leaving your chat | `@opencode-cockpit/agents` |
|
|
31
|
+
|
|
32
|
+
## Shell, by example
|
|
33
|
+
|
|
34
|
+
**"Start the dev server and wait until it's actually ready."**
|
|
35
|
+
The agent starts it in a real terminal and blocks on the port opening — not a guess, not a sleep:
|
|
36
|
+
```
|
|
37
|
+
shell_start command="npm run dev" waitFor={ port: 5173 }
|
|
38
|
+
→ condition met: port is accepting connections
|
|
39
|
+
1| VITE v7.3.1 ready in 431 ms
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**"Run the tests, keep working, tell me if they fail."**
|
|
43
|
+
The suite runs in the background. When it exits, the agent is messaged once, with the error line
|
|
44
|
+
already picked out:
|
|
45
|
+
```
|
|
46
|
+
<shell_exited id="sh_9wq2f1ab" title="unit tests">
|
|
47
|
+
exited with code 1 after 48s
|
|
48
|
+
last output: 37| FAIL src/auth.test.ts > refresh token expiry
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**"How is DB Monitoring doing?"**
|
|
52
|
+
Shells are shared across sessions and can be addressed by name:
|
|
53
|
+
```
|
|
54
|
+
shell_read name="DB Monitoring"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Logs that don't eat your context.** Colour codes stripped, progress-bar redraws collapsed to
|
|
58
|
+
their final frame, repeated lines folded to `(×12)`, and every read returns a cursor so the next
|
|
59
|
+
one only brings what's new. For full-screen programs (`vitest --ui`, `htop`, prompts) the agent can
|
|
60
|
+
ask for the *screen* instead of the log.
|
|
61
|
+
|
|
62
|
+
**It can type.** Prompts, REPLs, migration wizards: `shell_send` sends text or named keys
|
|
63
|
+
(`ctrl+c`, `up`, `enter`) and returns whatever the program printed back.
|
|
64
|
+
|
|
65
|
+
### Why not just `bash`?
|
|
66
|
+
|
|
67
|
+
| | Built-in `bash` tool | Cockpit Shell |
|
|
10
68
|
|---|---|---|
|
|
11
|
-
|
|
|
12
|
-
|
|
|
69
|
+
| Long-running processes | Blocks until exit | Runs in the background, survives the turn |
|
|
70
|
+
| Knowing something is ready | Guess, or sleep and poll | Blocks on a port, a pattern, silence or exit |
|
|
71
|
+
| Interactive programs | Not possible (no TTY) | Real PTY: prompts, REPLs, ctrl+c |
|
|
72
|
+
| Reading output | Whole log, every time | Clean lines from a cursor, with grep |
|
|
73
|
+
| Your visibility | None until it finishes | Live panel, console and sidebar |
|
|
74
|
+
| After OpenCode restarts | Gone | Still running |
|
|
75
|
+
|
|
76
|
+
## For you, not just the agent
|
|
77
|
+
|
|
78
|
+
| Key / command | Does |
|
|
79
|
+
|---|---|
|
|
80
|
+
| `ctrl+x o` · `/shells` | Toggle the shells panel under the chat |
|
|
81
|
+
| `ctrl+x i` · `/shell` | Open the shell console |
|
|
82
|
+
| `/shell-new` | Start a shell yourself |
|
|
83
|
+
| `/shells-clear` | Remove finished shells |
|
|
84
|
+
|
|
85
|
+
Status reads the same everywhere — `RUN` (with a spinner), `FAIL`, `STOP`, `DONE` — running shells
|
|
86
|
+
and recent failures stay in view, the rest folds behind `▸ N more`. In the console: `i` types
|
|
87
|
+
straight into the program (`ctrl+]` to stop), `c` sends ctrl+c, `r` restarts, `x` stops, `tab`
|
|
88
|
+
switches between the live screen and the scrollback, `?` shows details.
|
|
13
89
|
|
|
14
90
|
## Install
|
|
15
91
|
|
|
@@ -25,34 +101,24 @@ opencode plugin opencode-cockpit --global
|
|
|
25
101
|
opencode plugin @opencode-cockpit/shell --global
|
|
26
102
|
```
|
|
27
103
|
|
|
28
|
-
|
|
29
|
-
and
|
|
30
|
-
|
|
31
|
-
```json
|
|
32
|
-
{ "plugin": [["opencode-cockpit", { "features": { "shell": false } }]] }
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
Restart OpenCode after installing. Requires OpenCode 1.18 or newer on macOS or Linux.
|
|
36
|
-
|
|
37
|
-
> Install a feature **either** through `opencode-cockpit` **or** on its own, not both. If both are
|
|
38
|
-
> configured, the first one loaded is used and OpenCode shows a warning telling you which entry to
|
|
39
|
-
> remove.
|
|
40
|
-
|
|
41
|
-
## Configuring features
|
|
104
|
+
Restart OpenCode. Requires OpenCode 1.18+ on macOS or Linux. Install a feature either through
|
|
105
|
+
`opencode-cockpit` or on its own — if both are configured, the first one loaded is used and
|
|
106
|
+
OpenCode warns you which entry to remove.
|
|
42
107
|
|
|
43
|
-
|
|
44
|
-
name:
|
|
108
|
+
**Turn features off, or pass them options** (in both `opencode.json` and `tui.json`):
|
|
45
109
|
|
|
46
110
|
```json
|
|
47
111
|
{
|
|
48
112
|
"plugin": [
|
|
49
|
-
["opencode-cockpit", {
|
|
113
|
+
["opencode-cockpit", {
|
|
114
|
+
"features": { "shell": true },
|
|
115
|
+
"shell": { "dockHeight": 16, "historyMinutes": 60 }
|
|
116
|
+
}]
|
|
50
117
|
]
|
|
51
118
|
}
|
|
52
119
|
```
|
|
53
120
|
|
|
54
|
-
|
|
55
|
-
`["@opencode-cockpit/shell", { "dockHeight": 16 }]`.
|
|
121
|
+
Each feature's README documents its own options: [Shell](https://github.com/Codestz/opencode-cockpit/tree/main/packages/shell#configuration).
|
|
56
122
|
|
|
57
123
|
## How it works
|
|
58
124
|
|
|
@@ -62,25 +128,43 @@ OpenCode TUI thread ── feature plugins (tui) ──┐
|
|
|
62
128
|
OpenCode server worker ─ feature plugins (server) ┘
|
|
63
129
|
```
|
|
64
130
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
131
|
+
OpenCode runs its interface and its server in separate threads, so a plugin's two halves can't
|
|
132
|
+
share memory. Both talk to **`cockpitd`**, a small daemon that owns every long-lived process: it
|
|
133
|
+
starts on demand, is shared by every OpenCode window, upgrades itself when a newer plugin connects,
|
|
134
|
+
cleans up processes left by a crash, and exits when idle. That's why shells outlive OpenCode
|
|
135
|
+
restarts, and why one session can look at a shell another session started.
|
|
136
|
+
|
|
137
|
+
Each shell's output feeds three views at once: a normalized **log** for the agent, an emulated
|
|
138
|
+
**screen** for you, and a raw ring buffer so a panel opened late can catch up.
|
|
68
139
|
|
|
69
140
|
| Package | Role |
|
|
70
141
|
|---|---|
|
|
71
|
-
| [`opencode-cockpit`](https://github.com/Codestz/opencode-cockpit/tree/main/packages/opencode) | All features in one plugin
|
|
142
|
+
| [`opencode-cockpit`](https://github.com/Codestz/opencode-cockpit/tree/main/packages/opencode) | All features in one plugin |
|
|
72
143
|
| [`@opencode-cockpit/shell`](https://github.com/Codestz/opencode-cockpit/tree/main/packages/shell) | Shell feature |
|
|
73
144
|
| [`@opencode-cockpit/daemon`](https://github.com/Codestz/opencode-cockpit/tree/main/packages/daemon) | `cockpitd`, the shared process host |
|
|
74
|
-
| [`@opencode-cockpit/client`](https://github.com/Codestz/opencode-cockpit/tree/main/packages/client) | Typed, auto-spawning client and
|
|
145
|
+
| [`@opencode-cockpit/client`](https://github.com/Codestz/opencode-cockpit/tree/main/packages/client) | Typed, auto-spawning client and plugin helpers |
|
|
75
146
|
| [`@opencode-cockpit/protocol`](https://github.com/Codestz/opencode-cockpit/tree/main/packages/protocol) | Wire contracts |
|
|
76
147
|
|
|
77
148
|
## Roadmap
|
|
78
149
|
|
|
79
|
-
- **Watchers**
|
|
80
|
-
|
|
81
|
-
- **
|
|
82
|
-
|
|
83
|
-
- Coloured output in the
|
|
150
|
+
- **Watchers** — `tsc`, `eslint` and `vitest` that report only *state changes*: "ok → 3 errors in
|
|
151
|
+
auth.ts", never a wall of repeated output.
|
|
152
|
+
- **Agents** — live subagent tree with a peek overlay.
|
|
153
|
+
- **Doctor** — one command that checks your setup and tells you how to fix it.
|
|
154
|
+
- Coloured output in the panel and console.
|
|
155
|
+
|
|
156
|
+
## Contributing
|
|
157
|
+
|
|
158
|
+
Issues and pull requests are welcome. [CONTRIBUTING.md](https://github.com/Codestz/opencode-cockpit/blob/main/CONTRIBUTING.md) covers the architecture,
|
|
159
|
+
the invariants worth knowing before changing anything, and how to run your working copy inside
|
|
160
|
+
OpenCode.
|
|
161
|
+
|
|
162
|
+
```sh
|
|
163
|
+
bun install
|
|
164
|
+
bun run check # lint, typecheck, tests (real PTYs, real daemon)
|
|
165
|
+
bun run pack:check # pack, install the tarballs, run a shell through them
|
|
166
|
+
bun run smoke:tui # drive a real OpenCode against the packed plugin
|
|
167
|
+
```
|
|
84
168
|
|
|
85
169
|
## License
|
|
86
170
|
|
package/dist/compose.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merges the server hooks of several features into one plugin. Tool maps are unioned (a name
|
|
3
|
+
* clash is a bug, so it throws); function hooks run in feature order, each awaited, which is how
|
|
4
|
+
* OpenCode itself runs hooks from separate plugins.
|
|
5
|
+
*/
|
|
6
|
+
export function composeHooks(parts) {
|
|
7
|
+
const tools = {};
|
|
8
|
+
const functions = new Map();
|
|
9
|
+
const singles = {};
|
|
10
|
+
for (const part of parts) {
|
|
11
|
+
for (const [key, value] of Object.entries(part)) {
|
|
12
|
+
if (value === undefined) continue;
|
|
13
|
+
if (key === "tool") {
|
|
14
|
+
for (const [id, def] of Object.entries(value)) {
|
|
15
|
+
if (id in tools) throw new Error(`tool "${id}" is registered by more than one cockpit feature`);
|
|
16
|
+
tools[id] = def;
|
|
17
|
+
}
|
|
18
|
+
} else if (typeof value === "function") {
|
|
19
|
+
const list = functions.get(key) ?? [];
|
|
20
|
+
list.push(value);
|
|
21
|
+
functions.set(key, list);
|
|
22
|
+
} else {
|
|
23
|
+
if (key in singles) throw new Error(`hook "${key}" is provided by more than one cockpit feature`);
|
|
24
|
+
singles[key] = value;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const hooks = {
|
|
29
|
+
...singles
|
|
30
|
+
};
|
|
31
|
+
for (const [key, list] of functions) {
|
|
32
|
+
hooks[key] = list.length === 1 ? list[0] : async (...args) => {
|
|
33
|
+
for (const fn of list) await fn(...args);
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (Object.keys(tools).length > 0) hooks.tool = tools;
|
|
37
|
+
return hooks;
|
|
38
|
+
}
|
package/dist/features.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Every feature the bundle can load. Adding one: list it here and wire it in server.ts / tui.ts. */
|
|
2
|
+
export const FEATURES = ["shell"];
|
|
3
|
+
export const BUNDLE = "opencode-cockpit";
|
|
4
|
+
export function isEnabled(options, feature) {
|
|
5
|
+
return options?.features?.[feature] !== false;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Options for one feature. Shell also accepts options at the top level, which is where 0.1.x
|
|
10
|
+
* (when `opencode-cockpit` was Shell) read them from.
|
|
11
|
+
*/
|
|
12
|
+
export function featureOptions(options, feature) {
|
|
13
|
+
const own = options?.[feature] ?? {};
|
|
14
|
+
if (feature !== "shell" || !options) return own;
|
|
15
|
+
const legacy = {};
|
|
16
|
+
for (const [key, value] of Object.entries(options)) {
|
|
17
|
+
if (key !== "features" && !FEATURES.includes(key)) legacy[key] = value;
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
...legacy,
|
|
21
|
+
...own
|
|
22
|
+
};
|
|
23
|
+
}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createShellServer } from "@opencode-cockpit/shell/server";
|
|
2
|
+
import { composeHooks } from "./compose.js";
|
|
3
|
+
import { BUNDLE, featureOptions, isEnabled } from "./features.js";
|
|
4
|
+
const shell = createShellServer({
|
|
5
|
+
source: BUNDLE
|
|
6
|
+
});
|
|
7
|
+
const server = async (input, rawOptions) => {
|
|
8
|
+
const options = rawOptions;
|
|
9
|
+
const parts = [];
|
|
10
|
+
if (isEnabled(options, "shell")) parts.push(await shell(input, featureOptions(options, "shell")));
|
|
11
|
+
return composeHooks(parts);
|
|
12
|
+
};
|
|
13
|
+
const plugin = {
|
|
14
|
+
id: BUNDLE,
|
|
15
|
+
server
|
|
16
|
+
};
|
|
17
|
+
export default plugin;
|
package/dist/tui.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createShellTui } from "@opencode-cockpit/shell/tui";
|
|
2
|
+
import { BUNDLE, featureOptions, isEnabled } from "./features.js";
|
|
3
|
+
const shell = createShellTui({
|
|
4
|
+
source: BUNDLE
|
|
5
|
+
});
|
|
6
|
+
const tui = async (api, rawOptions, meta) => {
|
|
7
|
+
const options = rawOptions;
|
|
8
|
+
if (isEnabled(options, "shell")) await shell(api, featureOptions(options, "shell"), meta);
|
|
9
|
+
};
|
|
10
|
+
const plugin = {
|
|
11
|
+
id: BUNDLE,
|
|
12
|
+
tui
|
|
13
|
+
};
|
|
14
|
+
export default plugin;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-cockpit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "OpenCode superpowers, all in one: installs every opencode-cockpit feature (Shell today), each can be switched off",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,15 +25,22 @@
|
|
|
25
25
|
"agents"
|
|
26
26
|
],
|
|
27
27
|
"exports": {
|
|
28
|
-
"./server":
|
|
29
|
-
|
|
28
|
+
"./server": {
|
|
29
|
+
"types": "./types/server.d.ts",
|
|
30
|
+
"default": "./dist/server.js"
|
|
31
|
+
},
|
|
32
|
+
"./tui": {
|
|
33
|
+
"types": "./types/tui.d.ts",
|
|
34
|
+
"default": "./dist/tui.js"
|
|
35
|
+
}
|
|
30
36
|
},
|
|
31
37
|
"engines": {
|
|
32
38
|
"opencode": ">=1.18.0",
|
|
33
39
|
"bun": ">=1.3.5"
|
|
34
40
|
},
|
|
35
41
|
"files": [
|
|
36
|
-
"
|
|
42
|
+
"dist",
|
|
43
|
+
"types",
|
|
37
44
|
"README.md",
|
|
38
45
|
"LICENSE"
|
|
39
46
|
],
|
|
@@ -41,10 +48,10 @@
|
|
|
41
48
|
"access": "public"
|
|
42
49
|
},
|
|
43
50
|
"dependencies": {
|
|
44
|
-
"@opencode-cockpit/shell": "0.1.
|
|
51
|
+
"@opencode-cockpit/shell": "0.1.5",
|
|
45
52
|
"@opencode-ai/plugin": "1.18.31"
|
|
46
53
|
},
|
|
47
54
|
"devDependencies": {
|
|
48
|
-
"@opencode-cockpit/client": "0.1.
|
|
55
|
+
"@opencode-cockpit/client": "0.1.5"
|
|
49
56
|
}
|
|
50
57
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Hooks } from "@opencode-ai/plugin";
|
|
2
|
+
/**
|
|
3
|
+
* Merges the server hooks of several features into one plugin. Tool maps are unioned (a name
|
|
4
|
+
* clash is a bug, so it throws); function hooks run in feature order, each awaited, which is how
|
|
5
|
+
* OpenCode itself runs hooks from separate plugins.
|
|
6
|
+
*/
|
|
7
|
+
export declare function composeHooks(parts: Hooks[]): Hooks;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Every feature the bundle can load. Adding one: list it here and wire it in server.ts / tui.ts. */
|
|
2
|
+
export declare const FEATURES: readonly ["shell"];
|
|
3
|
+
export type Feature = (typeof FEATURES)[number];
|
|
4
|
+
export interface CockpitOptions {
|
|
5
|
+
/** Switch features off, e.g. `{ "shell": false }`. Everything is on by default. */
|
|
6
|
+
features?: Partial<Record<Feature, boolean>>;
|
|
7
|
+
/** Options passed to a single feature, keyed by feature name. */
|
|
8
|
+
shell?: Record<string, unknown>;
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
}
|
|
11
|
+
export declare const BUNDLE = "opencode-cockpit";
|
|
12
|
+
export declare function isEnabled(options: CockpitOptions | undefined, feature: Feature): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Options for one feature. Shell also accepts options at the top level, which is where 0.1.x
|
|
15
|
+
* (when `opencode-cockpit` was Shell) read them from.
|
|
16
|
+
*/
|
|
17
|
+
export declare function featureOptions(options: CockpitOptions | undefined, feature: Feature): Record<string, unknown>;
|
package/types/tui.d.ts
ADDED
package/src/compose.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import type { Hooks } from "@opencode-ai/plugin"
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Merges the server hooks of several features into one plugin. Tool maps are unioned (a name
|
|
5
|
-
* clash is a bug, so it throws); function hooks run in feature order, each awaited, which is how
|
|
6
|
-
* OpenCode itself runs hooks from separate plugins.
|
|
7
|
-
*/
|
|
8
|
-
export function composeHooks(parts: Hooks[]): Hooks {
|
|
9
|
-
const tools: NonNullable<Hooks["tool"]> = {}
|
|
10
|
-
const functions = new Map<string, ((...args: unknown[]) => unknown)[]>()
|
|
11
|
-
const singles: Record<string, unknown> = {}
|
|
12
|
-
|
|
13
|
-
for (const part of parts) {
|
|
14
|
-
for (const [key, value] of Object.entries(part)) {
|
|
15
|
-
if (value === undefined) continue
|
|
16
|
-
if (key === "tool") {
|
|
17
|
-
for (const [id, def] of Object.entries(value as NonNullable<Hooks["tool"]>)) {
|
|
18
|
-
if (id in tools) throw new Error(`tool "${id}" is registered by more than one cockpit feature`)
|
|
19
|
-
tools[id] = def
|
|
20
|
-
}
|
|
21
|
-
} else if (typeof value === "function") {
|
|
22
|
-
const list = functions.get(key) ?? []
|
|
23
|
-
list.push(value as (...args: unknown[]) => unknown)
|
|
24
|
-
functions.set(key, list)
|
|
25
|
-
} else {
|
|
26
|
-
if (key in singles) throw new Error(`hook "${key}" is provided by more than one cockpit feature`)
|
|
27
|
-
singles[key] = value
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const hooks: Record<string, unknown> = { ...singles }
|
|
33
|
-
for (const [key, list] of functions) {
|
|
34
|
-
hooks[key] =
|
|
35
|
-
list.length === 1
|
|
36
|
-
? list[0]
|
|
37
|
-
: async (...args: unknown[]) => {
|
|
38
|
-
for (const fn of list) await fn(...args)
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
if (Object.keys(tools).length > 0) hooks.tool = tools
|
|
42
|
-
return hooks as Hooks
|
|
43
|
-
}
|
package/src/features.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/** Every feature the bundle can load. Adding one: list it here and wire it in server.ts / tui.ts. */
|
|
2
|
-
export const FEATURES = ["shell"] as const
|
|
3
|
-
export type Feature = (typeof FEATURES)[number]
|
|
4
|
-
|
|
5
|
-
export interface CockpitOptions {
|
|
6
|
-
/** Switch features off, e.g. `{ "shell": false }`. Everything is on by default. */
|
|
7
|
-
features?: Partial<Record<Feature, boolean>>
|
|
8
|
-
/** Options passed to a single feature, keyed by feature name. */
|
|
9
|
-
shell?: Record<string, unknown>
|
|
10
|
-
[key: string]: unknown
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export const BUNDLE = "opencode-cockpit"
|
|
14
|
-
|
|
15
|
-
export function isEnabled(options: CockpitOptions | undefined, feature: Feature): boolean {
|
|
16
|
-
return options?.features?.[feature] !== false
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Options for one feature. Shell also accepts options at the top level, which is where 0.1.x
|
|
21
|
-
* (when `opencode-cockpit` was Shell) read them from.
|
|
22
|
-
*/
|
|
23
|
-
export function featureOptions(
|
|
24
|
-
options: CockpitOptions | undefined,
|
|
25
|
-
feature: Feature,
|
|
26
|
-
): Record<string, unknown> {
|
|
27
|
-
const own = (options?.[feature] as Record<string, unknown> | undefined) ?? {}
|
|
28
|
-
if (feature !== "shell" || !options) return own
|
|
29
|
-
const legacy: Record<string, unknown> = {}
|
|
30
|
-
for (const [key, value] of Object.entries(options)) {
|
|
31
|
-
if (key !== "features" && !(FEATURES as readonly string[]).includes(key)) legacy[key] = value
|
|
32
|
-
}
|
|
33
|
-
return { ...legacy, ...own }
|
|
34
|
-
}
|
package/src/server.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { Hooks, Plugin, PluginModule } from "@opencode-ai/plugin"
|
|
2
|
-
import { createShellServer } from "@opencode-cockpit/shell/server"
|
|
3
|
-
import { composeHooks } from "./compose.ts"
|
|
4
|
-
import { BUNDLE, type CockpitOptions, featureOptions, isEnabled } from "./features.ts"
|
|
5
|
-
|
|
6
|
-
const shell = createShellServer({ source: BUNDLE })
|
|
7
|
-
|
|
8
|
-
const server: Plugin = async (input, rawOptions) => {
|
|
9
|
-
const options = rawOptions as CockpitOptions | undefined
|
|
10
|
-
const parts: Hooks[] = []
|
|
11
|
-
if (isEnabled(options, "shell")) parts.push(await shell(input, featureOptions(options, "shell")))
|
|
12
|
-
return composeHooks(parts)
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const plugin: PluginModule & { id: string } = { id: BUNDLE, server }
|
|
16
|
-
export default plugin
|
package/src/tui.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { TuiPlugin, TuiPluginModule } from "@opencode-ai/plugin/tui"
|
|
2
|
-
import { createShellTui } from "@opencode-cockpit/shell/tui"
|
|
3
|
-
import { BUNDLE, type CockpitOptions, featureOptions, isEnabled } from "./features.ts"
|
|
4
|
-
|
|
5
|
-
const shell = createShellTui({ source: BUNDLE })
|
|
6
|
-
|
|
7
|
-
const tui: TuiPlugin = async (api, rawOptions, meta) => {
|
|
8
|
-
const options = rawOptions as CockpitOptions | undefined
|
|
9
|
-
if (isEnabled(options, "shell")) await shell(api, featureOptions(options, "shell"), meta)
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const plugin: TuiPluginModule & { id: string } = { id: BUNDLE, tui }
|
|
13
|
-
export default plugin
|