@geohar/unbien-cli 0.1.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 +72 -0
- package/dist/client.d.ts +89 -0
- package/dist/client.js +210 -0
- package/dist/client.js.map +1 -0
- package/dist/commands.d.ts +59 -0
- package/dist/commands.js +273 -0
- package/dist/commands.js.map +1 -0
- package/dist/connect.d.ts +2 -0
- package/dist/connect.js +475 -0
- package/dist/connect.js.map +1 -0
- package/dist/identity.d.ts +2 -0
- package/dist/identity.js +30 -0
- package/dist/identity.js.map +1 -0
- package/dist/invite.d.ts +10 -0
- package/dist/invite.js +28 -0
- package/dist/invite.js.map +1 -0
- package/dist/main.d.ts +3 -0
- package/dist/main.js +20 -0
- package/dist/main.js.map +1 -0
- package/dist/panels.d.ts +40 -0
- package/dist/panels.js +155 -0
- package/dist/panels.js.map +1 -0
- package/dist/picker.d.ts +6 -0
- package/dist/picker.js +95 -0
- package/dist/picker.js.map +1 -0
- package/dist/plan.d.ts +30 -0
- package/dist/plan.js +132 -0
- package/dist/plan.js.map +1 -0
- package/dist/reduce.d.ts +45 -0
- package/dist/reduce.js +123 -0
- package/dist/reduce.js.map +1 -0
- package/dist/render.d.ts +13 -0
- package/dist/render.js +91 -0
- package/dist/render.js.map +1 -0
- package/dist/replay.d.ts +2 -0
- package/dist/replay.js +57 -0
- package/dist/replay.js.map +1 -0
- package/dist/settings.d.ts +27 -0
- package/dist/settings.js +31 -0
- package/dist/settings.js.map +1 -0
- package/dist/store.d.ts +13 -0
- package/dist/store.js +20 -0
- package/dist/store.js.map +1 -0
- package/dist/theme.d.ts +7 -0
- package/dist/theme.js +110 -0
- package/dist/theme.js.map +1 -0
- package/dist/tui.d.ts +78 -0
- package/dist/tui.js +265 -0
- package/dist/tui.js.map +1 -0
- package/package.json +58 -0
package/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/georgeharker/un-bien/main/app/icons/un-bien-macos-1024.png" alt="Un Bien" width="96" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# unbien-cli
|
|
6
|
+
|
|
7
|
+
A terminal client for **remote [Pi coding agent](https://github.com/earendil-works/pi)
|
|
8
|
+
sessions**. Attach to a session running on another machine over an
|
|
9
|
+
[un-bien](https://github.com/georgeharker/un-bien) relay and drive it from your
|
|
10
|
+
shell — the same sessions the iOS/macOS app talks to.
|
|
11
|
+
|
|
12
|
+
It renders with **pi's own interactive components**, so a remote session looks
|
|
13
|
+
like a local one: CommonMark + syntax highlighting, native edit-diff / read /
|
|
14
|
+
bash tool cards, thinking blocks, and your configured pi theme (including
|
|
15
|
+
package-contributed ones such as `tokyo-night`).
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install -g @geohar/unbien-cli
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Use
|
|
22
|
+
|
|
23
|
+
Pair once per machine — run `/unbien pair` in a Pi session and copy the invite:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
unbien connect 'unbien://pair?t=…&epk=…'
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Pairing is machine-level, so later runs need no token:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
unbien connect --list # sessions on that machine
|
|
33
|
+
unbien connect # pick one interactively
|
|
34
|
+
unbien connect --session <id>
|
|
35
|
+
unbien connect --session-name un-bien
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`--relay` defaults to `$UNBIEN_RELAY`; a remembered machine uses the relay it
|
|
39
|
+
was paired on. `--theme <name>` overrides your pi theme, and `--list-themes`
|
|
40
|
+
shows what's available.
|
|
41
|
+
|
|
42
|
+
Type to prompt. Slash commands:
|
|
43
|
+
|
|
44
|
+
| | |
|
|
45
|
+
| -------------------------------------------------------------- | -------------------------------------------------------------------- |
|
|
46
|
+
| `/help` | list commands |
|
|
47
|
+
| `/tree` | browse turns — `f` forks a new session, `b` branches in place |
|
|
48
|
+
| `/fork`, `/branch` | same, by entry id |
|
|
49
|
+
| `/plan`, `/subagents` | pin a panel above the composer (`toggle`/`expand`/`collapse`/`hide`) |
|
|
50
|
+
| `/plan filter done`, `/plan filter context`, `/plan lines <n>` | what the plan shows |
|
|
51
|
+
| `/model`, `/thinking`, `/compact`, `/abort` | pi's own session verbs |
|
|
52
|
+
| `/set` | client settings (thinking stream, panel modes) |
|
|
53
|
+
| `/quit` | detach; the remote session keeps running |
|
|
54
|
+
|
|
55
|
+
Ctrl-C exits. Settings live in `~/.config/unbien-cli/settings.json`.
|
|
56
|
+
|
|
57
|
+
## Offline rendering
|
|
58
|
+
|
|
59
|
+
`unbien replay <capture.jsonl>` renders a captured rpc-envelope stream — useful
|
|
60
|
+
for looking at a transcript, or for checking rendering without a live session.
|
|
61
|
+
|
|
62
|
+
## How it relates to the app
|
|
63
|
+
|
|
64
|
+
The un-bien wire carries **byte-faithful pi rpc frames**, so this is a second
|
|
65
|
+
renderer over the stream the app already reads, not a second protocol. History
|
|
66
|
+
(`get_entries`) replays through the _same_ reducer as live frames.
|
|
67
|
+
|
|
68
|
+
Requires a paired Pi running the [`@geohar/un-bien`](https://www.npmjs.com/package/@geohar/un-bien)
|
|
69
|
+
extension and a relay you host. Machine administration (launcher daemon,
|
|
70
|
+
devices, revocation) lives in that package's `unbien-admin` CLI.
|
|
71
|
+
|
|
72
|
+
MIT © George Harker.
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
|
+
import { type EnvelopeMessage, type Ed25519Keypair } from "@geohar/un-bien/client";
|
|
3
|
+
import type { PairingInvite } from "./invite.js";
|
|
4
|
+
/** One session's relay room, as announced by the pi that owns it. */
|
|
5
|
+
export interface RoomInfo {
|
|
6
|
+
room_id: string;
|
|
7
|
+
name?: string;
|
|
8
|
+
cwd?: string;
|
|
9
|
+
started_at?: number;
|
|
10
|
+
parent?: string;
|
|
11
|
+
sessionId?: string;
|
|
12
|
+
caps?: string[];
|
|
13
|
+
}
|
|
14
|
+
export interface SessionClientEvents {
|
|
15
|
+
/** An inner envelope frame ({rpc|evt|ub}) from the pi. */
|
|
16
|
+
envelope: [EnvelopeMessage];
|
|
17
|
+
/** A pre-attach stock frame — only `pair_ok` / `pair_error` arrive this way. */
|
|
18
|
+
control: [Record<string, unknown>];
|
|
19
|
+
/** A relay control frame (rooms / presence) — not routed to any pi. */
|
|
20
|
+
relayControl: [Record<string, unknown>];
|
|
21
|
+
close: [];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The owner end of a session channel: relay auth, pairing, then the envelope
|
|
25
|
+
* plane. The pi is addressed by its Ed25519 public key (`invite.epk`) — an
|
|
26
|
+
* opaque routing key that is echoed verbatim, never parsed.
|
|
27
|
+
*/
|
|
28
|
+
export declare class SessionClient extends EventEmitter<SessionClientEvents> {
|
|
29
|
+
private readonly invite;
|
|
30
|
+
private readonly relay;
|
|
31
|
+
/** The room being driven. Mutable: one pairing reaches every room the pi
|
|
32
|
+
* owns, so picking a session is a routing change, not a re-pair. */
|
|
33
|
+
room: string;
|
|
34
|
+
constructor(relayUrl: string, keypair: Ed25519Keypair, invite: PairingInvite);
|
|
35
|
+
/** Snapshot the pi's currently-open rooms (one per live session). */
|
|
36
|
+
listRooms(timeoutMs?: number): Promise<RoomInfo[]>;
|
|
37
|
+
connect(): Promise<void>;
|
|
38
|
+
/** The one non-envelope frame a client ever sends — nothing exists before it. */
|
|
39
|
+
pair(deviceName: string): void;
|
|
40
|
+
/** Ask the extension to replay panels + pending UI for this session. */
|
|
41
|
+
requestSync(): void;
|
|
42
|
+
/**
|
|
43
|
+
* Pull the session's persisted transcript. This is pi's native history verb —
|
|
44
|
+
* `session_sync` covers panels and pending UI only, never the transcript.
|
|
45
|
+
*/
|
|
46
|
+
/**
|
|
47
|
+
* Walk the whole transcript. `get_entries` is PAGED: each reply carries a
|
|
48
|
+
* page plus the `leafId` to continue from, and the walk ends on an empty
|
|
49
|
+
* page. A single call returns only the first page, which silently looks like
|
|
50
|
+
* a short history.
|
|
51
|
+
*
|
|
52
|
+
* Mirrors the app's walk, including its repeated-leaf circuit breaker: a
|
|
53
|
+
* non-empty page whose leaf did not advance would otherwise page forever.
|
|
54
|
+
*/
|
|
55
|
+
getEntries(maxPages?: number): Promise<unknown[]>;
|
|
56
|
+
/**
|
|
57
|
+
* Issue a pi rpc command and await its correlated `response`. pi's verbs are
|
|
58
|
+
* used directly — an un-bien hop would only be a second name for the same
|
|
59
|
+
* thing.
|
|
60
|
+
*/
|
|
61
|
+
request<T>(type: string, params?: Record<string, unknown>, timeoutMs?: number): Promise<T | undefined>;
|
|
62
|
+
/**
|
|
63
|
+
* un-bien's own plane, for the things pi has no same-process verb for.
|
|
64
|
+
*
|
|
65
|
+
* SAFETY: the exported `UbFrame` union is narrower than what the extension
|
|
66
|
+
* actually routes — `session_fork` / `session_navigate` are dispatched from
|
|
67
|
+
* `index.ts` but absent from the published type, which casts them the same
|
|
68
|
+
* way. Typing this parameter to the union would reject frames the receiver
|
|
69
|
+
* handles today.
|
|
70
|
+
*/
|
|
71
|
+
sendUb(type: string, params?: Record<string, unknown>): void;
|
|
72
|
+
/** Drive the agent. `prompt` is pi's own rpc verb — no invented hop. */
|
|
73
|
+
prompt(message: string): void;
|
|
74
|
+
/** Interrupt a running turn with a steering message. */
|
|
75
|
+
steer(message: string): void;
|
|
76
|
+
abort(): void;
|
|
77
|
+
close(): void;
|
|
78
|
+
/**
|
|
79
|
+
* The single outbound choke, mirroring the extension's: stamp the plane's
|
|
80
|
+
* REAL wrapper kind plus a timestamp.
|
|
81
|
+
*
|
|
82
|
+
* The kind is not decoration. The extension's pre-attach listener drops any
|
|
83
|
+
* frame whose top-level `type` is not a string (relay_lifecycle.ts), so an
|
|
84
|
+
* unstamped envelope is silently ignored — it routes, arrives, and vanishes.
|
|
85
|
+
*/
|
|
86
|
+
sendEnvelope(env: EnvelopeMessage): void;
|
|
87
|
+
private sendInner;
|
|
88
|
+
private onLine;
|
|
89
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { EVT_KIND, RPC_KIND, UB_KIND, RelayClient, } from "@geohar/un-bien/client";
|
|
4
|
+
/** Config stores relay URLs as http(s); the socket needs ws(s). */
|
|
5
|
+
function toWsUrl(url) {
|
|
6
|
+
if (url.startsWith("http://"))
|
|
7
|
+
return `ws://${url.slice(7)}`;
|
|
8
|
+
if (url.startsWith("https://"))
|
|
9
|
+
return `wss://${url.slice(8)}`;
|
|
10
|
+
return url;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The owner end of a session channel: relay auth, pairing, then the envelope
|
|
14
|
+
* plane. The pi is addressed by its Ed25519 public key (`invite.epk`) — an
|
|
15
|
+
* opaque routing key that is echoed verbatim, never parsed.
|
|
16
|
+
*/
|
|
17
|
+
export class SessionClient extends EventEmitter {
|
|
18
|
+
invite;
|
|
19
|
+
relay;
|
|
20
|
+
/** The room being driven. Mutable: one pairing reaches every room the pi
|
|
21
|
+
* owns, so picking a session is a routing change, not a re-pair. */
|
|
22
|
+
room;
|
|
23
|
+
constructor(relayUrl, keypair, invite) {
|
|
24
|
+
super();
|
|
25
|
+
this.invite = invite;
|
|
26
|
+
this.room = invite.roomId;
|
|
27
|
+
this.relay = new RelayClient(toWsUrl(relayUrl), keypair);
|
|
28
|
+
}
|
|
29
|
+
/** Snapshot the pi's currently-open rooms (one per live session). */
|
|
30
|
+
listRooms(timeoutMs = 5_000) {
|
|
31
|
+
return new Promise((resolve) => {
|
|
32
|
+
const timer = setTimeout(() => {
|
|
33
|
+
this.off("relayControl", onControl);
|
|
34
|
+
resolve([]);
|
|
35
|
+
}, timeoutMs);
|
|
36
|
+
const onControl = (frame) => {
|
|
37
|
+
if (frame.type !== "rooms" || frame.peer !== this.invite.epk)
|
|
38
|
+
return;
|
|
39
|
+
clearTimeout(timer);
|
|
40
|
+
this.off("relayControl", onControl);
|
|
41
|
+
resolve(Array.isArray(frame.rooms) ? frame.rooms : []);
|
|
42
|
+
};
|
|
43
|
+
this.on("relayControl", onControl);
|
|
44
|
+
this.relay.send(JSON.stringify({ type: "rooms_check", peers: [this.invite.epk] }));
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
async connect() {
|
|
48
|
+
this.relay.on("message", (line) => this.onLine(line));
|
|
49
|
+
this.relay.on("close", () => this.emit("close"));
|
|
50
|
+
// No room_id: an owner subscribes to rooms, it does not own one.
|
|
51
|
+
await this.relay.connect();
|
|
52
|
+
}
|
|
53
|
+
/** The one non-envelope frame a client ever sends — nothing exists before it. */
|
|
54
|
+
pair(deviceName) {
|
|
55
|
+
this.sendInner({
|
|
56
|
+
type: "pair_request",
|
|
57
|
+
id: randomUUID(),
|
|
58
|
+
token: this.invite.token,
|
|
59
|
+
device_name: deviceName,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/** Ask the extension to replay panels + pending UI for this session. */
|
|
63
|
+
requestSync() {
|
|
64
|
+
this.sendEnvelope({ ub: { type: "session_sync", id: randomUUID() } });
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Pull the session's persisted transcript. This is pi's native history verb —
|
|
68
|
+
* `session_sync` covers panels and pending UI only, never the transcript.
|
|
69
|
+
*/
|
|
70
|
+
/**
|
|
71
|
+
* Walk the whole transcript. `get_entries` is PAGED: each reply carries a
|
|
72
|
+
* page plus the `leafId` to continue from, and the walk ends on an empty
|
|
73
|
+
* page. A single call returns only the first page, which silently looks like
|
|
74
|
+
* a short history.
|
|
75
|
+
*
|
|
76
|
+
* Mirrors the app's walk, including its repeated-leaf circuit breaker: a
|
|
77
|
+
* non-empty page whose leaf did not advance would otherwise page forever.
|
|
78
|
+
*/
|
|
79
|
+
async getEntries(maxPages = 200) {
|
|
80
|
+
const all = [];
|
|
81
|
+
let since;
|
|
82
|
+
for (let page = 0; page < maxPages; page++) {
|
|
83
|
+
let data;
|
|
84
|
+
try {
|
|
85
|
+
data = await this.request("get_entries", since ? { since } : {});
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
break; // timeout or error: keep whatever we already walked
|
|
89
|
+
}
|
|
90
|
+
const entries = data?.entries ?? [];
|
|
91
|
+
const leaf = data?.leafId;
|
|
92
|
+
if (entries.length === 0 || !leaf)
|
|
93
|
+
break;
|
|
94
|
+
all.push(...entries);
|
|
95
|
+
if (leaf === since)
|
|
96
|
+
break; // leaf did not advance — the walk is spinning
|
|
97
|
+
since = leaf;
|
|
98
|
+
}
|
|
99
|
+
return all;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Issue a pi rpc command and await its correlated `response`. pi's verbs are
|
|
103
|
+
* used directly — an un-bien hop would only be a second name for the same
|
|
104
|
+
* thing.
|
|
105
|
+
*/
|
|
106
|
+
request(type, params = {}, timeoutMs = 15_000) {
|
|
107
|
+
const id = randomUUID();
|
|
108
|
+
return new Promise((resolve, reject) => {
|
|
109
|
+
const timer = setTimeout(() => {
|
|
110
|
+
this.off("envelope", onEnvelope);
|
|
111
|
+
reject(new Error(`${type}: timed out`));
|
|
112
|
+
}, timeoutMs);
|
|
113
|
+
const onEnvelope = (env) => {
|
|
114
|
+
const rpc = env.rpc;
|
|
115
|
+
if (typeof rpc !== "object" || rpc === null)
|
|
116
|
+
return;
|
|
117
|
+
const frame = rpc;
|
|
118
|
+
if (frame.type !== "response" || frame.id !== id)
|
|
119
|
+
return;
|
|
120
|
+
clearTimeout(timer);
|
|
121
|
+
this.off("envelope", onEnvelope);
|
|
122
|
+
if (frame.success === false) {
|
|
123
|
+
reject(new Error(String(frame.error ?? `${type} failed`)));
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
resolve(frame.data);
|
|
127
|
+
};
|
|
128
|
+
this.on("envelope", onEnvelope);
|
|
129
|
+
this.sendEnvelope({ rpc: { type, id, ...params } });
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* un-bien's own plane, for the things pi has no same-process verb for.
|
|
134
|
+
*
|
|
135
|
+
* SAFETY: the exported `UbFrame` union is narrower than what the extension
|
|
136
|
+
* actually routes — `session_fork` / `session_navigate` are dispatched from
|
|
137
|
+
* `index.ts` but absent from the published type, which casts them the same
|
|
138
|
+
* way. Typing this parameter to the union would reject frames the receiver
|
|
139
|
+
* handles today.
|
|
140
|
+
*/
|
|
141
|
+
sendUb(type, params = {}) {
|
|
142
|
+
const frame = { type, id: randomUUID(), ...params };
|
|
143
|
+
this.sendEnvelope({ ub: frame });
|
|
144
|
+
}
|
|
145
|
+
/** Drive the agent. `prompt` is pi's own rpc verb — no invented hop. */
|
|
146
|
+
prompt(message) {
|
|
147
|
+
this.sendEnvelope({
|
|
148
|
+
rpc: { type: "prompt", id: randomUUID(), message },
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
/** Interrupt a running turn with a steering message. */
|
|
152
|
+
steer(message) {
|
|
153
|
+
this.sendEnvelope({ rpc: { type: "steer", id: randomUUID(), message } });
|
|
154
|
+
}
|
|
155
|
+
abort() {
|
|
156
|
+
this.sendEnvelope({ rpc: { type: "abort", id: randomUUID() } });
|
|
157
|
+
}
|
|
158
|
+
close() {
|
|
159
|
+
this.relay.close();
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* The single outbound choke, mirroring the extension's: stamp the plane's
|
|
163
|
+
* REAL wrapper kind plus a timestamp.
|
|
164
|
+
*
|
|
165
|
+
* The kind is not decoration. The extension's pre-attach listener drops any
|
|
166
|
+
* frame whose top-level `type` is not a string (relay_lifecycle.ts), so an
|
|
167
|
+
* unstamped envelope is silently ignored — it routes, arrives, and vanishes.
|
|
168
|
+
*/
|
|
169
|
+
sendEnvelope(env) {
|
|
170
|
+
const kind = env.rpc ? RPC_KIND : env.evt ? EVT_KIND : UB_KIND;
|
|
171
|
+
this.sendInner({ type: kind, ...env, ts: Date.now() });
|
|
172
|
+
}
|
|
173
|
+
sendInner(inner) {
|
|
174
|
+
const outer = {
|
|
175
|
+
peer: this.invite.epk,
|
|
176
|
+
room: this.room,
|
|
177
|
+
ct: Buffer.from(JSON.stringify(inner)).toString("base64"),
|
|
178
|
+
};
|
|
179
|
+
this.relay.send(JSON.stringify(outer));
|
|
180
|
+
}
|
|
181
|
+
onLine(line) {
|
|
182
|
+
let frame;
|
|
183
|
+
try {
|
|
184
|
+
frame = JSON.parse(line);
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
// Relay control frames (rooms/presence) carry no `ct` — they are addressed
|
|
190
|
+
// to us, not routed through a pi.
|
|
191
|
+
const ct = frame.ct;
|
|
192
|
+
if (typeof ct !== "string") {
|
|
193
|
+
this.emit("relayControl", frame);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
let inner;
|
|
197
|
+
try {
|
|
198
|
+
inner = JSON.parse(Buffer.from(ct, "base64").toString("utf8"));
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if ("rpc" in inner || "evt" in inner || "ub" in inner) {
|
|
204
|
+
this.emit("envelope", inner);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
this.emit("control", inner);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slash commands. Where pi has a first-class rpc verb we issue that verb and pi
|
|
3
|
+
* acts; only fork/branch go over un-bien's own plane, because they need a pi
|
|
4
|
+
* command context that the rpc dispatch path doesn't carry.
|
|
5
|
+
*/
|
|
6
|
+
import { SelectList, type SelectItem } from "@earendil-works/pi-tui";
|
|
7
|
+
import type { SessionClient } from "./client.js";
|
|
8
|
+
import type { CliSettings } from "./settings.js";
|
|
9
|
+
import type { PanelStore } from "./panels.js";
|
|
10
|
+
/** A session log entry, as `get_entries` returns it. */
|
|
11
|
+
export interface SessionEntry {
|
|
12
|
+
id?: string;
|
|
13
|
+
parentId?: string | null;
|
|
14
|
+
type?: string;
|
|
15
|
+
message?: {
|
|
16
|
+
role?: string;
|
|
17
|
+
content?: Array<{
|
|
18
|
+
type?: string;
|
|
19
|
+
text?: string;
|
|
20
|
+
}>;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export interface CommandContext {
|
|
24
|
+
client: SessionClient;
|
|
25
|
+
panels: PanelStore;
|
|
26
|
+
/**
|
|
27
|
+
* The walked session log. The extension bridges only a subset of pi's rpc
|
|
28
|
+
* verbs (`rpc_inbound.ts`) — `get_tree` and `get_fork_messages` are NOT
|
|
29
|
+
* among them — but entries carry `id`/`parentId`, so the tree and the fork
|
|
30
|
+
* points are derived here instead of asking for them.
|
|
31
|
+
*/
|
|
32
|
+
entries: () => readonly SessionEntry[];
|
|
33
|
+
settings: CliSettings;
|
|
34
|
+
saveSettings: () => void;
|
|
35
|
+
settingsPath: string;
|
|
36
|
+
/** Leave the client. The remote session keeps running. */
|
|
37
|
+
quit: () => void;
|
|
38
|
+
/** Print lines into the transcript area. */
|
|
39
|
+
print: (lines: string[]) => void;
|
|
40
|
+
/** Offer a highlighted chooser; resolves to the picked value or null. */
|
|
41
|
+
choose: (title: string, items: SelectItem[]) => Promise<SelectItem | null> | SelectItem | null;
|
|
42
|
+
/** Chooser whose rows carry per-key actions (fork vs branch). */
|
|
43
|
+
chooseAction: (title: string, items: SelectItem[], hint: string, keys: readonly string[]) => Promise<{
|
|
44
|
+
item: SelectItem;
|
|
45
|
+
action: string;
|
|
46
|
+
} | null>;
|
|
47
|
+
}
|
|
48
|
+
export interface Command {
|
|
49
|
+
name: string;
|
|
50
|
+
summary: string;
|
|
51
|
+
run: (ctx: CommandContext, args: string) => Promise<void> | void;
|
|
52
|
+
}
|
|
53
|
+
export declare const COMMANDS: Command[];
|
|
54
|
+
export declare function findCommand(input: string): {
|
|
55
|
+
command: Command;
|
|
56
|
+
args: string;
|
|
57
|
+
} | null;
|
|
58
|
+
/** Chooser used when no TUI is available: prints a numbered list instead. */
|
|
59
|
+
export declare function selectListFor(items: SelectItem[], maxVisible?: number): SelectList;
|