@aiwayds/dsh-tui-pi 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/LICENSE +21 -0
- package/README.md +324 -0
- package/bin/dsh-tui-pi +5 -0
- package/cordis.patch.yml +9 -0
- package/lib/append-system.d.ts +66 -0
- package/lib/append-system.js +161 -0
- package/lib/append-system.js.map +1 -0
- package/lib/commands.d.ts +53 -0
- package/lib/commands.js +167 -0
- package/lib/commands.js.map +1 -0
- package/lib/dsh-events.d.ts +106 -0
- package/lib/dsh-events.js +30 -0
- package/lib/dsh-events.js.map +1 -0
- package/lib/editor.d.ts +28 -0
- package/lib/editor.js +70 -0
- package/lib/editor.js.map +1 -0
- package/lib/footer.d.ts +36 -0
- package/lib/footer.js +112 -0
- package/lib/footer.js.map +1 -0
- package/lib/frame.d.ts +35 -0
- package/lib/frame.js +75 -0
- package/lib/frame.js.map +1 -0
- package/lib/git.d.ts +17 -0
- package/lib/git.js +51 -0
- package/lib/git.js.map +1 -0
- package/lib/index.d.ts +15 -0
- package/lib/index.js +781 -0
- package/lib/index.js.map +1 -0
- package/lib/instructions.d.ts +29 -0
- package/lib/instructions.js +67 -0
- package/lib/instructions.js.map +1 -0
- package/lib/live-widgets.d.ts +85 -0
- package/lib/live-widgets.js +218 -0
- package/lib/live-widgets.js.map +1 -0
- package/lib/messages.d.ts +277 -0
- package/lib/messages.js +734 -0
- package/lib/messages.js.map +1 -0
- package/lib/permission.d.ts +27 -0
- package/lib/permission.js +48 -0
- package/lib/permission.js.map +1 -0
- package/lib/provider-catalog.d.ts +114 -0
- package/lib/provider-catalog.js +124 -0
- package/lib/provider-catalog.js.map +1 -0
- package/lib/quotes.d.ts +28 -0
- package/lib/quotes.js +144 -0
- package/lib/quotes.js.map +1 -0
- package/lib/reload.d.ts +23 -0
- package/lib/reload.js +171 -0
- package/lib/reload.js.map +1 -0
- package/lib/selectors.d.ts +48 -0
- package/lib/selectors.js +261 -0
- package/lib/selectors.js.map +1 -0
- package/lib/session.d.ts +157 -0
- package/lib/session.js +555 -0
- package/lib/session.js.map +1 -0
- package/lib/sessions.d.ts +73 -0
- package/lib/sessions.js +253 -0
- package/lib/sessions.js.map +1 -0
- package/lib/settings.d.ts +180 -0
- package/lib/settings.js +1328 -0
- package/lib/settings.js.map +1 -0
- package/lib/text.d.ts +22 -0
- package/lib/text.js +45 -0
- package/lib/text.js.map +1 -0
- package/lib/theme/index.d.ts +79 -0
- package/lib/theme/index.js +121 -0
- package/lib/theme/index.js.map +1 -0
- package/lib/theme/palette.d.ts +56 -0
- package/lib/theme/palette.js +154 -0
- package/lib/theme/palette.js.map +1 -0
- package/lib/theme-settings.d.ts +68 -0
- package/lib/theme-settings.js +223 -0
- package/lib/theme-settings.js.map +1 -0
- package/lib/tui.d.ts +70 -0
- package/lib/tui.js +206 -0
- package/lib/tui.js.map +1 -0
- package/lib/welcome.d.ts +91 -0
- package/lib/welcome.js +281 -0
- package/lib/welcome.js.map +1 -0
- package/package.json +50 -0
- package/patches/@earendil-works__pi-tui.patch +72 -0
- package/pnpm-workspace.yaml +2 -0
- package/templates/APPEND_SYSTEM.md +34 -0
package/lib/commands.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slash-command integration with dsh's own command registry.
|
|
3
|
+
*
|
|
4
|
+
* dsh-tui-pi never re-implements a command: autocomplete lists
|
|
5
|
+
* `ctx.commands.list(agent)` and submission routes through
|
|
6
|
+
* `ctx.commands.execute(agent, line, signal)`. Anything that is not a
|
|
7
|
+
* resolvable command falls through to the model as an ordinary prompt, so
|
|
8
|
+
* every command registered by dsh packages (plan, compact, feedback, export,
|
|
9
|
+
* permission, goal, …) works here unchanged and future registrations appear
|
|
10
|
+
* automatically.
|
|
11
|
+
*
|
|
12
|
+
* One exemption: TUI-owned commands that never touch the receiving agent
|
|
13
|
+
* (model pickers, settings browser, session info/resume) are dispatched
|
|
14
|
+
* locally when no live agent exists — dsh's execute path addresses an agent
|
|
15
|
+
* and would mint a throwaway session just to run them.
|
|
16
|
+
*/
|
|
17
|
+
import { parseCommand } from '@deepseek-ai/dsh-commands';
|
|
18
|
+
/**
|
|
19
|
+
* Token ending at the cursor on one editor line, for command detection.
|
|
20
|
+
* Returns the leading slash token when the caret sits inside one.
|
|
21
|
+
*/
|
|
22
|
+
function tokenAtCursor(line, cursorCol) {
|
|
23
|
+
const upto = line.slice(0, cursorCol);
|
|
24
|
+
const match = /(^|\s)\/([a-z][a-z0-9_-]*)?$/u.exec(upto);
|
|
25
|
+
if (match === null)
|
|
26
|
+
return undefined;
|
|
27
|
+
const start = match.index + (match[1] ?? '').length;
|
|
28
|
+
return { token: upto.slice(start), start };
|
|
29
|
+
}
|
|
30
|
+
export class CommandService {
|
|
31
|
+
ctx;
|
|
32
|
+
bridge;
|
|
33
|
+
/** TUI-owned command bodies, dispatchable without a live agent. */
|
|
34
|
+
local = new Map();
|
|
35
|
+
constructor(ctx, bridge) {
|
|
36
|
+
this.ctx = ctx;
|
|
37
|
+
this.bridge = bridge;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Register a local command body (the same handler the ctx.commands
|
|
41
|
+
* registration wraps). It is dispatched directly when no live agent
|
|
42
|
+
* exists, avoiding a throwaway session for agentless commands.
|
|
43
|
+
*/
|
|
44
|
+
registerLocal(name, handler) {
|
|
45
|
+
this.local.set(name, handler);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Try to run `line` as a dsh slash command.
|
|
49
|
+
* @returns `handled: true` when the line was admitted as a command (the
|
|
50
|
+
* caller must not forward it to the model), with an optional `error` or
|
|
51
|
+
* success `text` to surface; `handled: false` when it is not a command.
|
|
52
|
+
*/
|
|
53
|
+
async tryExecute(line, signal) {
|
|
54
|
+
const parsed = parseCommand(line);
|
|
55
|
+
if (parsed === undefined)
|
|
56
|
+
return { handled: false };
|
|
57
|
+
const commands = this.ctx.get('commands');
|
|
58
|
+
if (commands === undefined)
|
|
59
|
+
return { handled: false };
|
|
60
|
+
// Agentless local commands never warm the session — /resume or /session
|
|
61
|
+
// on a fresh TUI must not mint a session just to run.
|
|
62
|
+
const localHandler = this.local.get(parsed.name);
|
|
63
|
+
if (localHandler !== undefined && this.bridge.getAgent() === undefined) {
|
|
64
|
+
try {
|
|
65
|
+
const result = await localHandler(parsed.rawInput, signal);
|
|
66
|
+
if (result.kind === 'error')
|
|
67
|
+
return { handled: true, error: result.text };
|
|
68
|
+
return { handled: true, ...result.text === undefined ? {} : { text: result.text } };
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
72
|
+
return { handled: true, error: message };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Commands address a live agent; warm the session when it does not exist.
|
|
76
|
+
let agent;
|
|
77
|
+
try {
|
|
78
|
+
agent = await this.bridge.ensureAgent();
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
// Warm-up failed (e.g. provider not configured) — surface instead of
|
|
82
|
+
// rejecting into an unhandled promise rejection.
|
|
83
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
84
|
+
return { handled: true, error: message };
|
|
85
|
+
}
|
|
86
|
+
if (commands.find(agent, parsed.name) === undefined) {
|
|
87
|
+
// Syntactically a command but unregistered → treat as ordinary text so
|
|
88
|
+
// the model still sees it (matches "unknown command" falling through).
|
|
89
|
+
return { handled: false };
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
const execution = await commands.execute(agent, line, signal);
|
|
93
|
+
if (execution === undefined)
|
|
94
|
+
return { handled: false };
|
|
95
|
+
if (execution.result.kind === 'error')
|
|
96
|
+
return { handled: true, error: execution.result.text };
|
|
97
|
+
return { handled: true, ...execution.result.text === undefined ? {} : { text: execution.result.text } };
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
101
|
+
return { handled: true, error: message };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Effective command descriptors for the current agent. Warms the session on
|
|
106
|
+
* first use so `/` autocompletes before any prompt was sent.
|
|
107
|
+
*/
|
|
108
|
+
async list() {
|
|
109
|
+
const commands = this.ctx.get('commands');
|
|
110
|
+
if (commands === undefined)
|
|
111
|
+
return [];
|
|
112
|
+
try {
|
|
113
|
+
const agent = await this.bridge.ensureAgent();
|
|
114
|
+
return commands.list(agent);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// Session warm-up failed (e.g. provider not configured) — no completions.
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/** pi-tui autocomplete provider over the live command registry. */
|
|
122
|
+
autocompleteProvider() {
|
|
123
|
+
return {
|
|
124
|
+
triggerCharacters: ['/'],
|
|
125
|
+
getSuggestions: async (lines, cursorLine, cursorCol, options) => {
|
|
126
|
+
const line = lines[cursorLine] ?? '';
|
|
127
|
+
const at = tokenAtCursor(line, cursorCol);
|
|
128
|
+
if (at === undefined)
|
|
129
|
+
return null;
|
|
130
|
+
// Only complete the command name itself (leading token, no arguments yet).
|
|
131
|
+
if (at.start !== 0 && line.slice(0, at.start).trim() !== '')
|
|
132
|
+
return null;
|
|
133
|
+
const descriptors = await this.list();
|
|
134
|
+
if (descriptors.length === 0)
|
|
135
|
+
return null;
|
|
136
|
+
const query = at.token.slice(1).toLowerCase();
|
|
137
|
+
const items = descriptors
|
|
138
|
+
.filter(d => query === '' || d.name.toLowerCase().startsWith(query))
|
|
139
|
+
.map(d => ({
|
|
140
|
+
value: `/${d.name}`,
|
|
141
|
+
label: `/${d.name}`,
|
|
142
|
+
description: d.description,
|
|
143
|
+
}));
|
|
144
|
+
if (items.length === 0)
|
|
145
|
+
return null;
|
|
146
|
+
return { items, prefix: at.token };
|
|
147
|
+
},
|
|
148
|
+
applyCompletion: (lines, cursorLine, cursorCol, item, prefix) => {
|
|
149
|
+
const line = lines[cursorLine] ?? '';
|
|
150
|
+
const at = tokenAtCursor(line, cursorCol);
|
|
151
|
+
if (at === undefined)
|
|
152
|
+
return { lines, cursorLine, cursorCol };
|
|
153
|
+
const before = line.slice(0, at.start);
|
|
154
|
+
const after = line.slice(cursorCol);
|
|
155
|
+
const completed = `${before}${item.value} ${after}`;
|
|
156
|
+
const nextLines = lines.slice();
|
|
157
|
+
nextLines[cursorLine] = completed;
|
|
158
|
+
return {
|
|
159
|
+
lines: nextLines,
|
|
160
|
+
cursorLine,
|
|
161
|
+
cursorCol: before.length + item.value.length + 1,
|
|
162
|
+
};
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=commands.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.js","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local structural types for session events whose declaring packages
|
|
3
|
+
* (`@deepseek-ai/dsh-tool-workflow`, `@deepseek-ai/dsh-subagent`,
|
|
4
|
+
* `@deepseek-ai/dsh-llm-retry`) are not installed in this plugin. The TUI
|
|
5
|
+
* receives these events on the `session/event` firehose regardless (the
|
|
6
|
+
* firehose is not scope-filtered — see dsh-scope's generated subject
|
|
7
|
+
* resolvers), so the bridge folds them through these minimal, dependency-free
|
|
8
|
+
* shapes instead of importing the augmenting packages.
|
|
9
|
+
*
|
|
10
|
+
* `SessionEvent` is a closed discriminated union over the core event map, so
|
|
11
|
+
* `switch (event.type)` falls through these types; the guards narrow them
|
|
12
|
+
* explicitly.
|
|
13
|
+
*/
|
|
14
|
+
import type { SessionEvent } from '@deepseek-ai/dsh-session';
|
|
15
|
+
declare module '@deepseek-ai/dsh-session' {
|
|
16
|
+
interface SessionEventMap {
|
|
17
|
+
'tool-workflow/agent-start': ToolWorkflowAgentStartData;
|
|
18
|
+
'tool-workflow/agent-end': ToolWorkflowAgentEndData;
|
|
19
|
+
'subagent/descriptor': SubagentDescriptorData;
|
|
20
|
+
'llm/retry': LlmRetryData;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/** `tool-workflow/agent-start`: one workflow member, after its child Session is published. */
|
|
24
|
+
export interface ToolWorkflowAgentStartData {
|
|
25
|
+
readonly runId: string;
|
|
26
|
+
readonly seq: number;
|
|
27
|
+
readonly label: string;
|
|
28
|
+
readonly phase?: string;
|
|
29
|
+
readonly childId: string;
|
|
30
|
+
}
|
|
31
|
+
/** `tool-workflow/agent-end`: one workflow member settlement. */
|
|
32
|
+
export interface ToolWorkflowAgentEndData {
|
|
33
|
+
readonly runId: string;
|
|
34
|
+
readonly seq: number;
|
|
35
|
+
readonly outcome: 'completed' | 'failed' | 'cancelled';
|
|
36
|
+
}
|
|
37
|
+
/** `subagent/descriptor`: durable identity of a session-backed subagent child. */
|
|
38
|
+
export interface SubagentDescriptorData {
|
|
39
|
+
readonly version: number;
|
|
40
|
+
readonly mode: 'one-shot' | 'continuable';
|
|
41
|
+
readonly provider: string;
|
|
42
|
+
readonly label?: string;
|
|
43
|
+
}
|
|
44
|
+
/** `llm/retry`: one provider-routed retry scheduled after a failed request attempt. */
|
|
45
|
+
export interface LlmRetryData {
|
|
46
|
+
readonly retry: number;
|
|
47
|
+
/** Absent for the `mode: 'always'` arm of the real event. */
|
|
48
|
+
readonly maxRetries?: number;
|
|
49
|
+
}
|
|
50
|
+
/** Type guard for `tool-workflow/agent-start` events. */
|
|
51
|
+
export declare function isAgentStart(event: SessionEvent): event is SessionEvent & {
|
|
52
|
+
type: 'tool-workflow/agent-start';
|
|
53
|
+
data: ToolWorkflowAgentStartData;
|
|
54
|
+
};
|
|
55
|
+
/** Type guard for `tool-workflow/agent-end` events. */
|
|
56
|
+
export declare function isAgentEnd(event: SessionEvent): event is SessionEvent & {
|
|
57
|
+
type: 'tool-workflow/agent-end';
|
|
58
|
+
data: ToolWorkflowAgentEndData;
|
|
59
|
+
};
|
|
60
|
+
/** Type guard for `subagent/descriptor` events (written to the CHILD session's log). */
|
|
61
|
+
export declare function isSubagentDescriptor(event: SessionEvent): event is SessionEvent & {
|
|
62
|
+
type: 'subagent/descriptor';
|
|
63
|
+
data: SubagentDescriptorData;
|
|
64
|
+
};
|
|
65
|
+
/** Type guard for `llm/retry` events. */
|
|
66
|
+
export declare function isLlmRetry(event: SessionEvent): event is SessionEvent & {
|
|
67
|
+
type: 'llm/retry';
|
|
68
|
+
data: LlmRetryData;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* One live subagent row the TUI renders: the bridge's per-child fold of
|
|
72
|
+
* workflow events (parent log) and the child's own session events. Children
|
|
73
|
+
* are keyed by their session id — discovered either from
|
|
74
|
+
* `tool-workflow/agent-start` or, when the deployment never emits workflow
|
|
75
|
+
* events, from the child session's header (`origin: 'subagent'` +
|
|
76
|
+
* `parentSession`). All fields are O(1)-maintained — never a session-log
|
|
77
|
+
* scan on the render path.
|
|
78
|
+
*/
|
|
79
|
+
export interface AgentView {
|
|
80
|
+
/** The child session id (raw string) — the stable identity key. */
|
|
81
|
+
readonly childId: string;
|
|
82
|
+
/** Subagent type name from the child's `subagent/descriptor.provider`, when known. */
|
|
83
|
+
readonly provider?: string;
|
|
84
|
+
/** Delegation label from `tool-workflow/agent-start` or the child's descriptor. */
|
|
85
|
+
readonly label: string;
|
|
86
|
+
/** Unix epoch ms when the child was first observed — the elapsed baseline. */
|
|
87
|
+
readonly startedAt: number;
|
|
88
|
+
/**
|
|
89
|
+
* Settled marker: set by `tool-workflow/agent-end` (real outcome) or,
|
|
90
|
+
* for header-discovered children, best-effort on the child's `turn/end`.
|
|
91
|
+
* A settled child drops off the live board (clear-when-done).
|
|
92
|
+
*/
|
|
93
|
+
readonly outcome?: 'completed' | 'failed' | 'cancelled';
|
|
94
|
+
/** Unix epoch ms of the settle event, when settled. */
|
|
95
|
+
readonly endedAt?: number;
|
|
96
|
+
/** Cumulative tokens: input + output + cacheRead + cacheWrite. */
|
|
97
|
+
readonly tokens: number;
|
|
98
|
+
/** Latest `llm/retry` attempt number (0 = none retried). */
|
|
99
|
+
readonly retries: number;
|
|
100
|
+
/** Latest `llm/retry` maxRetries, when the policy reported one. */
|
|
101
|
+
readonly maxRetries?: number;
|
|
102
|
+
/** Last tool name the child invoked (the activity line), when any. */
|
|
103
|
+
readonly lastTool?: string;
|
|
104
|
+
/** Context window from the child's `request/context`, for the % column. */
|
|
105
|
+
readonly contextWindow?: number;
|
|
106
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local structural types for session events whose declaring packages
|
|
3
|
+
* (`@deepseek-ai/dsh-tool-workflow`, `@deepseek-ai/dsh-subagent`,
|
|
4
|
+
* `@deepseek-ai/dsh-llm-retry`) are not installed in this plugin. The TUI
|
|
5
|
+
* receives these events on the `session/event` firehose regardless (the
|
|
6
|
+
* firehose is not scope-filtered — see dsh-scope's generated subject
|
|
7
|
+
* resolvers), so the bridge folds them through these minimal, dependency-free
|
|
8
|
+
* shapes instead of importing the augmenting packages.
|
|
9
|
+
*
|
|
10
|
+
* `SessionEvent` is a closed discriminated union over the core event map, so
|
|
11
|
+
* `switch (event.type)` falls through these types; the guards narrow them
|
|
12
|
+
* explicitly.
|
|
13
|
+
*/
|
|
14
|
+
/** Type guard for `tool-workflow/agent-start` events. */
|
|
15
|
+
export function isAgentStart(event) {
|
|
16
|
+
return event.type === 'tool-workflow/agent-start';
|
|
17
|
+
}
|
|
18
|
+
/** Type guard for `tool-workflow/agent-end` events. */
|
|
19
|
+
export function isAgentEnd(event) {
|
|
20
|
+
return event.type === 'tool-workflow/agent-end';
|
|
21
|
+
}
|
|
22
|
+
/** Type guard for `subagent/descriptor` events (written to the CHILD session's log). */
|
|
23
|
+
export function isSubagentDescriptor(event) {
|
|
24
|
+
return event.type === 'subagent/descriptor';
|
|
25
|
+
}
|
|
26
|
+
/** Type guard for `llm/retry` events. */
|
|
27
|
+
export function isLlmRetry(event) {
|
|
28
|
+
return event.type === 'llm/retry';
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=dsh-events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dsh-events.js","sourceRoot":"","sources":["../src/dsh-events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAqDH,yDAAyD;AACzD,MAAM,UAAU,YAAY,CAAC,KAAmB;IAC9C,OAAO,KAAK,CAAC,IAAI,KAAK,2BAA2B,CAAA;AACnD,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,UAAU,CAAC,KAAmB;IAC5C,OAAO,KAAK,CAAC,IAAI,KAAK,yBAAyB,CAAA;AACjD,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,oBAAoB,CAAC,KAAmB;IACtD,OAAO,KAAK,CAAC,IAAI,KAAK,qBAAqB,CAAA;AAC7C,CAAC;AAED,yCAAyC;AACzC,MAAM,UAAU,UAAU,CAAC,KAAmB;IAC5C,OAAO,KAAK,CAAC,IAAI,KAAK,WAAW,CAAA;AACnC,CAAC"}
|
package/lib/editor.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom editor that replaces the input's TOP BORDER row with a plain-text
|
|
3
|
+
* info line: cwd + permission badge + git branch (separator "│"), in border
|
|
4
|
+
* color only (no powerline background segments). Ported from
|
|
5
|
+
* pi-powerline-footer's CwdBorderEditor; every other editor row is left
|
|
6
|
+
* untouched.
|
|
7
|
+
*/
|
|
8
|
+
import { Editor, type EditorTheme, type TUI } from '@earendil-works/pi-tui';
|
|
9
|
+
/** Shorten the cwd by replacing the HOME prefix with `~`. */
|
|
10
|
+
export declare function formatCwd(cwd: string): string;
|
|
11
|
+
export declare class CwdBorderEditor extends Editor {
|
|
12
|
+
private readonly sessionCwd;
|
|
13
|
+
private readonly infoColor;
|
|
14
|
+
private branchProvider;
|
|
15
|
+
private permissionProvider;
|
|
16
|
+
constructor(tui: TUI, theme: EditorTheme, sessionCwd: string, options?: {
|
|
17
|
+
paddingX?: number;
|
|
18
|
+
infoColor?: (str: string) => string;
|
|
19
|
+
});
|
|
20
|
+
/** Live git-branch source (polled outside; the editor only reads it). */
|
|
21
|
+
setBranchProvider(provider: () => string | undefined): void;
|
|
22
|
+
/**
|
|
23
|
+
* Live permission-preset display-name source (e.g. "Full access") — the
|
|
24
|
+
* badge shown right after the cwd; polled outside, the editor only reads it.
|
|
25
|
+
*/
|
|
26
|
+
setPermissionProvider(provider: () => string | undefined): void;
|
|
27
|
+
render(width: number): string[];
|
|
28
|
+
}
|
package/lib/editor.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom editor that replaces the input's TOP BORDER row with a plain-text
|
|
3
|
+
* info line: cwd + permission badge + git branch (separator "│"), in border
|
|
4
|
+
* color only (no powerline background segments). Ported from
|
|
5
|
+
* pi-powerline-footer's CwdBorderEditor; every other editor row is left
|
|
6
|
+
* untouched.
|
|
7
|
+
*/
|
|
8
|
+
import { Editor, truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';
|
|
9
|
+
/** Shorten the cwd by replacing the HOME prefix with `~`. */
|
|
10
|
+
export function formatCwd(cwd) {
|
|
11
|
+
const home = process.env.HOME;
|
|
12
|
+
if (home !== undefined && cwd.startsWith(home)) {
|
|
13
|
+
return `~${cwd.slice(home.length)}`;
|
|
14
|
+
}
|
|
15
|
+
return cwd;
|
|
16
|
+
}
|
|
17
|
+
export class CwdBorderEditor extends Editor {
|
|
18
|
+
sessionCwd;
|
|
19
|
+
infoColor;
|
|
20
|
+
branchProvider = () => undefined;
|
|
21
|
+
permissionProvider = () => undefined;
|
|
22
|
+
constructor(tui, theme, sessionCwd, options) {
|
|
23
|
+
super(tui, theme, { paddingX: options?.paddingX ?? 0 });
|
|
24
|
+
this.sessionCwd = sessionCwd;
|
|
25
|
+
// The border dashes stay border-colored, but the cwd/branch info needs a
|
|
26
|
+
// readable foreground (border color is near-invisible on light themes).
|
|
27
|
+
this.infoColor = options?.infoColor ?? theme.borderColor;
|
|
28
|
+
}
|
|
29
|
+
/** Live git-branch source (polled outside; the editor only reads it). */
|
|
30
|
+
setBranchProvider(provider) {
|
|
31
|
+
this.branchProvider = provider;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Live permission-preset display-name source (e.g. "Full access") — the
|
|
35
|
+
* badge shown right after the cwd; polled outside, the editor only reads it.
|
|
36
|
+
*/
|
|
37
|
+
setPermissionProvider(provider) {
|
|
38
|
+
this.permissionProvider = provider;
|
|
39
|
+
}
|
|
40
|
+
render(width) {
|
|
41
|
+
const lines = super.render(width);
|
|
42
|
+
if (lines.length < 2 || width < 3)
|
|
43
|
+
return lines;
|
|
44
|
+
// When scrolled, the built-in top border row is a scroll indicator
|
|
45
|
+
// ("─── ↑ N more ─…"); preserve that feedback by appending ↑ N.
|
|
46
|
+
const firstLine = lines[0] ?? '';
|
|
47
|
+
const scrollMatch = /↑\s*(\d+)/u.exec(firstLine);
|
|
48
|
+
const scrollInfo = scrollMatch === null ? '' : ` ↑ ${scrollMatch[1]}`;
|
|
49
|
+
const permission = this.permissionProvider();
|
|
50
|
+
const parts = [
|
|
51
|
+
`📁 ${formatCwd(this.sessionCwd)}` +
|
|
52
|
+
(permission !== undefined && permission !== '' ? ` (${permission})` : ''),
|
|
53
|
+
];
|
|
54
|
+
const branch = this.branchProvider();
|
|
55
|
+
if (branch !== undefined && branch !== '')
|
|
56
|
+
parts.push(`⎇ ${branch}`);
|
|
57
|
+
const content = parts.join(' │ ') + scrollInfo;
|
|
58
|
+
// Reserve 3 fixed columns: "─ " prefix (2) + " " suffix (1).
|
|
59
|
+
const maxContent = Math.max(0, width - 3);
|
|
60
|
+
const contentText = truncateToWidth(content, maxContent, '…');
|
|
61
|
+
const fill = Math.max(0, width - 3 - visibleWidth(contentText));
|
|
62
|
+
lines[0] =
|
|
63
|
+
this.borderColor('─ ') +
|
|
64
|
+
this.infoColor(contentText) +
|
|
65
|
+
this.borderColor(' ') +
|
|
66
|
+
this.borderColor('─'.repeat(fill));
|
|
67
|
+
return lines;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=editor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"editor.js","sourceRoot":"","sources":["../src/editor.ts"],"names":[],"mappings":"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"}
|
package/lib/footer.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Powerline-style status footer, ported from pi-powerline-footer.
|
|
3
|
+
*
|
|
4
|
+
* Segments (left → right): fixed brand "dsh" · provider · model+thinking ·
|
|
5
|
+
* context usage · cache-hit rate · message count · tool count, then a
|
|
6
|
+
* right-aligned 24h clock. Segment backgrounds are the pi-powerline-footer
|
|
7
|
+
* palette; separators are U+E0B0 powerline arrows tinted by the neighbouring
|
|
8
|
+
* segment colours.
|
|
9
|
+
*
|
|
10
|
+
* Render cost is O(segments): every statistic is read from the bridge's
|
|
11
|
+
* incrementally maintained counters (never a session-log scan). The 1s clock
|
|
12
|
+
* tick only re-renders this component's single line.
|
|
13
|
+
*/
|
|
14
|
+
import { type Component } from '@earendil-works/pi-tui';
|
|
15
|
+
import type { BridgeStats } from './session.ts';
|
|
16
|
+
export interface FooterDataSource {
|
|
17
|
+
/** O(1) incremental counters. */
|
|
18
|
+
getStats(): BridgeStats;
|
|
19
|
+
/** Current model selection (provider, model, reasoning effort). */
|
|
20
|
+
getSelection(): {
|
|
21
|
+
provider: string;
|
|
22
|
+
model: string;
|
|
23
|
+
reasoningEffort?: string;
|
|
24
|
+
} | undefined;
|
|
25
|
+
/** Model context window in tokens, when known. */
|
|
26
|
+
getContextWindow(): number | undefined;
|
|
27
|
+
/** Git branch of the session cwd, when known. */
|
|
28
|
+
getBranch(): string | undefined;
|
|
29
|
+
}
|
|
30
|
+
export declare function fmtNum(n: number): string;
|
|
31
|
+
export declare class PowerlineFooter implements Component {
|
|
32
|
+
private readonly source;
|
|
33
|
+
constructor(source: FooterDataSource);
|
|
34
|
+
invalidate(): void;
|
|
35
|
+
render(width: number): string[];
|
|
36
|
+
}
|
package/lib/footer.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Powerline-style status footer, ported from pi-powerline-footer.
|
|
3
|
+
*
|
|
4
|
+
* Segments (left → right): fixed brand "dsh" · provider · model+thinking ·
|
|
5
|
+
* context usage · cache-hit rate · message count · tool count, then a
|
|
6
|
+
* right-aligned 24h clock. Segment backgrounds are the pi-powerline-footer
|
|
7
|
+
* palette; separators are U+E0B0 powerline arrows tinted by the neighbouring
|
|
8
|
+
* segment colours.
|
|
9
|
+
*
|
|
10
|
+
* Render cost is O(segments): every statistic is read from the bridge's
|
|
11
|
+
* incrementally maintained counters (never a session-log scan). The 1s clock
|
|
12
|
+
* tick only re-renders this component's single line.
|
|
13
|
+
*/
|
|
14
|
+
import { truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';
|
|
15
|
+
import { ansiBg, ansiFg, BOLD, POWERLINE, RESET } from "./theme/index.js";
|
|
16
|
+
import { clipToWidth } from "./text.js";
|
|
17
|
+
const ARROW_RIGHT = '\uE0B0';
|
|
18
|
+
const WHITE = ansiFg('#FFFFFF');
|
|
19
|
+
export function fmtNum(n) {
|
|
20
|
+
if (n >= 1_000_000)
|
|
21
|
+
return `${(n / 1_000_000).toFixed(1)}M`;
|
|
22
|
+
if (n >= 1000)
|
|
23
|
+
return `${(n / 1000).toFixed(1)}k`;
|
|
24
|
+
return `${n}`;
|
|
25
|
+
}
|
|
26
|
+
function buildSegments(segs) {
|
|
27
|
+
if (segs.length === 0)
|
|
28
|
+
return '';
|
|
29
|
+
let out = '';
|
|
30
|
+
for (let i = 0; i < segs.length; i++) {
|
|
31
|
+
const s = segs[i];
|
|
32
|
+
out += `${ansiBg(s.bgHex)}${BOLD}${WHITE} ${s.label} `;
|
|
33
|
+
if (i + 1 < segs.length) {
|
|
34
|
+
out += `${ansiBg(segs[i + 1].bgHex)}${ansiFg(s.bgHex)}${ARROW_RIGHT}`;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
out += RESET + ansiFg(s.bgHex) + ARROW_RIGHT + RESET;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
/** Thinking-level → segment background (pi-powerline-footer mapping). */
|
|
43
|
+
function thinkingBg(level) {
|
|
44
|
+
const key = (level ?? 'off');
|
|
45
|
+
return POWERLINE.thinking[key] ?? POWERLINE.thinking.off;
|
|
46
|
+
}
|
|
47
|
+
function thinkingIcon(level) {
|
|
48
|
+
switch (level ?? 'off') {
|
|
49
|
+
case 'minimal': return '◔';
|
|
50
|
+
case 'low': return '◑';
|
|
51
|
+
case 'medium': return '◕';
|
|
52
|
+
case 'high': return '●';
|
|
53
|
+
case 'xhigh': return '◉';
|
|
54
|
+
case 'max': return '★';
|
|
55
|
+
default: return '○';
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function contextBg(percent) {
|
|
59
|
+
if (percent >= 90)
|
|
60
|
+
return POWERLINE.contextDanger;
|
|
61
|
+
if (percent >= 70)
|
|
62
|
+
return POWERLINE.contextOrange;
|
|
63
|
+
if (percent >= 50)
|
|
64
|
+
return POWERLINE.contextWarn;
|
|
65
|
+
return POWERLINE.contextOk;
|
|
66
|
+
}
|
|
67
|
+
export class PowerlineFooter {
|
|
68
|
+
source;
|
|
69
|
+
constructor(source) {
|
|
70
|
+
this.source = source;
|
|
71
|
+
}
|
|
72
|
+
invalidate() { }
|
|
73
|
+
render(width) {
|
|
74
|
+
const stats = this.source.getStats();
|
|
75
|
+
const selection = this.source.getSelection();
|
|
76
|
+
const segs = [{ label: 'dsh', bgHex: POWERLINE.brand }];
|
|
77
|
+
if (selection !== undefined) {
|
|
78
|
+
segs.push({ label: `☁️ ${selection.provider}`, bgHex: POWERLINE.provider });
|
|
79
|
+
const model = clipToWidth(selection.model.split('/').pop(), 24);
|
|
80
|
+
const effort = selection.reasoningEffort ?? 'off';
|
|
81
|
+
segs.push({
|
|
82
|
+
label: `🤖 ${model} ${thinkingIcon(effort)} ${effort}`,
|
|
83
|
+
bgHex: thinkingBg(effort),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
// Context usage: latest request's input tokens vs. the model's window.
|
|
87
|
+
const window = this.source.getContextWindow();
|
|
88
|
+
const used = stats.inputTokens;
|
|
89
|
+
if (window !== undefined && window > 0) {
|
|
90
|
+
const percent = (used / window) * 100;
|
|
91
|
+
segs.push({
|
|
92
|
+
label: `🧠 ${fmtNum(used)}/${fmtNum(window)}(${percent.toFixed(1)}%)`,
|
|
93
|
+
bgHex: contextBg(percent),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
segs.push({ label: `🧠 ${fmtNum(used)}`, bgHex: POWERLINE.contextOk });
|
|
98
|
+
}
|
|
99
|
+
if ((stats.cacheReadTokens > 0 || stats.cacheWriteTokens > 0) && stats.cacheHitRate !== undefined) {
|
|
100
|
+
segs.push({ label: `⚡ CH${stats.cacheHitRate.toFixed(1)}%`, bgHex: POWERLINE.cache });
|
|
101
|
+
}
|
|
102
|
+
segs.push({ label: `💬 ${stats.msgCount} msgs`, bgHex: POWERLINE.messages });
|
|
103
|
+
segs.push({ label: `🔧 ${stats.toolCallCount} tools`, bgHex: POWERLINE.tools });
|
|
104
|
+
const left = buildSegments(segs);
|
|
105
|
+
const leftWidth = visibleWidth(left);
|
|
106
|
+
const clock = new Date().toLocaleTimeString('en-GB', { hour12: false });
|
|
107
|
+
const clockWidth = visibleWidth(clock);
|
|
108
|
+
const pad = Math.max(1, width - leftWidth - clockWidth);
|
|
109
|
+
return [truncateToWidth(left + ' '.repeat(pad) + clock, width)];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=footer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"footer.js","sourceRoot":"","sources":["../src/footer.ts"],"names":[],"mappings":"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"}
|
package/lib/frame.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framed overlay chrome — the shared full box (`┌─┐`) every pi SelectList /
|
|
3
|
+
* SettingsList overlay gets, so a popup reads as one bounded surface instead
|
|
4
|
+
* of panel rows floating directly on the chat canvas.
|
|
5
|
+
*
|
|
6
|
+
* Replaces pi agent's DynamicBorder (bare `─` lines) with a complete box:
|
|
7
|
+
* `┌───┐` top, `│ │` side-bordered content rows, `└───┘` bottom. Width
|
|
8
|
+
* passed to `showOverlay` is unchanged — the box is self-contained within it
|
|
9
|
+
* (child renders at width − 2).
|
|
10
|
+
*
|
|
11
|
+
* The frame also paints the panel backdrop: every row (borders, spacers, and
|
|
12
|
+
* each child line — including the raw separator/search rows pi's SettingsList
|
|
13
|
+
* pushes without any theme call) is laid on a full-width canvasSubtle
|
|
14
|
+
* background, so the popup reads as one solid rectangle instead of stripes
|
|
15
|
+
* of text-width highlight.
|
|
16
|
+
*/
|
|
17
|
+
import type { Component } from '@earendil-works/pi-tui';
|
|
18
|
+
import { type TuiTheme } from './theme/index.ts';
|
|
19
|
+
/**
|
|
20
|
+
* Full-box wrapper for one overlay component: `┌─┐` top border, `│`-bordered
|
|
21
|
+
* content rows, `└─┘` bottom border — each with a blank spacer row inside
|
|
22
|
+
* the box for breathing room, colored `palette.borderDefault` on the panel's
|
|
23
|
+
* canvasSubtle backdrop. Every row spans the full overlay width on that
|
|
24
|
+
* backdrop (see fillLine); the child renders at width − 2.
|
|
25
|
+
*/
|
|
26
|
+
export declare class FramedOverlay implements Component {
|
|
27
|
+
private readonly theme;
|
|
28
|
+
private readonly child;
|
|
29
|
+
constructor(theme: TuiTheme, child: Component);
|
|
30
|
+
invalidate(): void;
|
|
31
|
+
render(width: number): string[];
|
|
32
|
+
handleInput(data: string): void;
|
|
33
|
+
}
|
|
34
|
+
/** Build the framed wrapper for `child` (see FramedOverlay). */
|
|
35
|
+
export declare function wrapFramedOverlay(theme: TuiTheme, child: Component): Component;
|