@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/sessions.js
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session TUI overlays: the `/session` info panel and the `/resume`
|
|
3
|
+
* persisted-session picker — terminal counterparts of the web surface's
|
|
4
|
+
* session management. Both follow the pi SelectList overlay pattern
|
|
5
|
+
* (`showOverlay` + `restoreFocus` on close); the info panel is a one-shot
|
|
6
|
+
* read-only component in the style of settings' ReadOnlyViewer.
|
|
7
|
+
*/
|
|
8
|
+
import { getKeybindings, SelectList, } from '@earendil-works/pi-tui';
|
|
9
|
+
import { basename } from 'node:path';
|
|
10
|
+
import { wrapFramedOverlay } from "./frame.js";
|
|
11
|
+
import { ansiFg, BOLD, RESET } from "./theme/index.js";
|
|
12
|
+
import { clipToWidth, visibleWidth } from "./text.js";
|
|
13
|
+
/** Label column width for the key-value rows (longest: "cache read/write"). */
|
|
14
|
+
const LABEL_WIDTH = 12;
|
|
15
|
+
/** One-shot read-only panel; Esc/Enter hides the overlay and resolves. */
|
|
16
|
+
class SessionInfoPanel {
|
|
17
|
+
theme;
|
|
18
|
+
data;
|
|
19
|
+
onClose;
|
|
20
|
+
constructor(theme, data, onClose) {
|
|
21
|
+
this.theme = theme;
|
|
22
|
+
this.data = data;
|
|
23
|
+
this.onClose = onClose;
|
|
24
|
+
}
|
|
25
|
+
invalidate() { }
|
|
26
|
+
render(width) {
|
|
27
|
+
const fg = (hex) => (text) => ansiFg(hex) + text + RESET;
|
|
28
|
+
const p = this.theme.palette;
|
|
29
|
+
const lines = [
|
|
30
|
+
fg(p.accent)(BOLD + 'ⓘ session' + RESET),
|
|
31
|
+
'',
|
|
32
|
+
];
|
|
33
|
+
const id = this.data.id;
|
|
34
|
+
if (id === undefined) {
|
|
35
|
+
lines.push(fg(p.fgDefault)(' no active session — send a prompt or /resume one'));
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
const max = Math.max(2, width - LABEL_WIDTH - 4);
|
|
39
|
+
const clip = (text) => clipToWidth(text, max);
|
|
40
|
+
const row = (label, value) => {
|
|
41
|
+
lines.push(fg(p.fgMuted)(label.padEnd(LABEL_WIDTH)) + fg(p.fgDefault)(clip(value)));
|
|
42
|
+
};
|
|
43
|
+
const shortId = clipToWidth(id, 8);
|
|
44
|
+
row('session', shortId);
|
|
45
|
+
if (visibleWidth(id) > 8) {
|
|
46
|
+
lines.push(' ' + ' '.repeat(LABEL_WIDTH) + fg(p.fgSubtle)(clip(id)));
|
|
47
|
+
}
|
|
48
|
+
row('cwd', this.data.cwd ?? '—');
|
|
49
|
+
row('created', this.data.createdAt === undefined ? '—' : new Date(this.data.createdAt).toLocaleString());
|
|
50
|
+
row('model', this.data.model ?? '—');
|
|
51
|
+
row('think', this.data.effort ?? '—');
|
|
52
|
+
row('status', this.data.status === 'running' ? fg(p.accent)(this.data.status) : this.data.status);
|
|
53
|
+
row('messages', String(this.data.msgCount));
|
|
54
|
+
row('tool calls', String(this.data.toolCallCount));
|
|
55
|
+
row('tokens in', String(this.data.inputTokens));
|
|
56
|
+
row('tokens out', String(this.data.outputTokens));
|
|
57
|
+
row('cache read', String(this.data.cacheReadTokens));
|
|
58
|
+
row('cache write', String(this.data.cacheWriteTokens));
|
|
59
|
+
row('events', this.data.eventCount === undefined ? '—' : String(this.data.eventCount));
|
|
60
|
+
const parent = this.data.parentSession;
|
|
61
|
+
row('parent', parent === undefined ? '—' : clipToWidth(parent, 8));
|
|
62
|
+
}
|
|
63
|
+
lines.push('');
|
|
64
|
+
lines.push(fg(p.fgSubtle)(' Esc to close'));
|
|
65
|
+
return lines;
|
|
66
|
+
}
|
|
67
|
+
handleInput(data) {
|
|
68
|
+
if (getKeybindings().matches(data, 'tui.select.cancel') || getKeybindings().matches(data, 'tui.select.confirm')) {
|
|
69
|
+
this.onClose();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Open the `/session` info panel; resolves when the user closes it. Focus
|
|
75
|
+
* returns to `restoreFocus` on close — it must re-focus the CURRENT editor
|
|
76
|
+
* instance, which may have been rebuilt under a theme hot-swap while the
|
|
77
|
+
* panel was open (pi-tui's hide would otherwise restore focus to the stale
|
|
78
|
+
* pre-overlay editor and swallow subsequent input).
|
|
79
|
+
*/
|
|
80
|
+
export async function showSessionInfo(tui, theme, data, restoreFocus) {
|
|
81
|
+
await new Promise(resolve => {
|
|
82
|
+
const panel = new SessionInfoPanel(theme, data, () => {
|
|
83
|
+
overlay.hide();
|
|
84
|
+
restoreFocus();
|
|
85
|
+
resolve();
|
|
86
|
+
});
|
|
87
|
+
// maxHeight only ever slices (never stretches), and the framed overlay
|
|
88
|
+
// needs 4 extra rows for its borders: cap high so the bottom border
|
|
89
|
+
// survives on small terminals (24 rows: 19 panel rows + 4 frame rows).
|
|
90
|
+
const overlay = tui.showOverlay(wrapFramedOverlay(theme, panel), { width: '70%', maxHeight: '100%' });
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* How many of the most recent candidates get a first-message preview before
|
|
95
|
+
* the picker opens. Older sessions fall back to the header label, so the
|
|
96
|
+
* picker stays fast even with a long history.
|
|
97
|
+
*/
|
|
98
|
+
const PREVIEW_SESSION_CAP = 30;
|
|
99
|
+
/** Concurrent `inspect` calls while enriching previews. */
|
|
100
|
+
const PREVIEW_CONCURRENCY = 6;
|
|
101
|
+
/** Hard character cap for a preview string; the list column clips it further. */
|
|
102
|
+
const PREVIEW_MAX_CHARS = 140;
|
|
103
|
+
/** Join the text blocks of a message's content into one trimmed string. */
|
|
104
|
+
function textOfContent(content) {
|
|
105
|
+
if (typeof content === 'string')
|
|
106
|
+
return content.trim();
|
|
107
|
+
if (!Array.isArray(content))
|
|
108
|
+
return '';
|
|
109
|
+
let text = '';
|
|
110
|
+
for (const block of content) {
|
|
111
|
+
if (block && typeof block === 'object' && block.type === 'text') {
|
|
112
|
+
const blockText = block.text;
|
|
113
|
+
if (typeof blockText === 'string')
|
|
114
|
+
text += blockText + ' ';
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return text.trim();
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* The session's "first sentence": the first direct human prompt, falling
|
|
121
|
+
* back to the first non-injected user-role text (goal/cron/recall prompts),
|
|
122
|
+
* then the first assistant message. Synthetic context injects (workspace
|
|
123
|
+
* instructions, runtime reminders) are skipped entirely — they precede the
|
|
124
|
+
* first real prompt and read as noise.
|
|
125
|
+
*/
|
|
126
|
+
export function previewOfEvents(events) {
|
|
127
|
+
let fallbackUser;
|
|
128
|
+
let fallbackAssistant;
|
|
129
|
+
for (const event of events) {
|
|
130
|
+
if (event.type === 'user/message') {
|
|
131
|
+
const message = event.data;
|
|
132
|
+
const text = textOfContent(message?.content);
|
|
133
|
+
if (!text)
|
|
134
|
+
continue;
|
|
135
|
+
const kind = message?.source?.kind;
|
|
136
|
+
if (kind === 'user')
|
|
137
|
+
return text;
|
|
138
|
+
if (kind === 'tool' || kind === 'plugin' || kind === 'agent-instructions')
|
|
139
|
+
continue;
|
|
140
|
+
if (fallbackUser === undefined)
|
|
141
|
+
fallbackUser = text;
|
|
142
|
+
}
|
|
143
|
+
else if (event.type === 'assistant/message') {
|
|
144
|
+
const message = event.data.message;
|
|
145
|
+
const text = textOfContent(message?.content);
|
|
146
|
+
if (text && fallbackAssistant === undefined)
|
|
147
|
+
fallbackAssistant = text;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return fallbackUser ?? fallbackAssistant;
|
|
151
|
+
}
|
|
152
|
+
/** Collapse control chars/whitespace and clip a raw message into a one-line preview. */
|
|
153
|
+
export function normalizePreview(text, maxChars = PREVIEW_MAX_CHARS) {
|
|
154
|
+
const oneLine = text.replace(/[\x00-\x1f\x7f]+/g, ' ').replace(/\s+/g, ' ').trim();
|
|
155
|
+
return oneLine.length <= maxChars ? oneLine : `${oneLine.slice(0, maxChars)}…`;
|
|
156
|
+
}
|
|
157
|
+
/** Best-effort first-message preview per session id; failures map to undefined. */
|
|
158
|
+
async function loadSessionPreviews(persistence, ids) {
|
|
159
|
+
const previews = new Map();
|
|
160
|
+
let cursor = 0;
|
|
161
|
+
const workers = Array.from({ length: Math.min(PREVIEW_CONCURRENCY, ids.length) }, async () => {
|
|
162
|
+
while (cursor < ids.length) {
|
|
163
|
+
const index = cursor++;
|
|
164
|
+
const id = ids[index];
|
|
165
|
+
let preview;
|
|
166
|
+
try {
|
|
167
|
+
const { events } = await persistence.inspect(id);
|
|
168
|
+
const text = previewOfEvents(events);
|
|
169
|
+
if (text)
|
|
170
|
+
preview = normalizePreview(text);
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
preview = undefined;
|
|
174
|
+
}
|
|
175
|
+
previews.set(String(id), preview);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
await Promise.all(workers);
|
|
179
|
+
return previews;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Validate one persisted session's log without publishing it — the /resume
|
|
183
|
+
* pre-check: a corrupt target must be rejected BEFORE the current live agent
|
|
184
|
+
* is torn down. Throws when persistence is missing or the log fails to load.
|
|
185
|
+
*/
|
|
186
|
+
export async function inspectPersistedSession(ctx, id) {
|
|
187
|
+
const persistence = ctx.get('sessionPersistence');
|
|
188
|
+
if (persistence === undefined) {
|
|
189
|
+
throw new Error('Session persistence is not configured in this profile.');
|
|
190
|
+
}
|
|
191
|
+
return await persistence.inspect(id);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Open the persisted-session picker. Resolves with the picked session,
|
|
195
|
+
* `cancelled` when dismissed, or `empty` when no other session exists.
|
|
196
|
+
* Throws when the profile has no persistence backend. Focus returns to
|
|
197
|
+
* `restoreFocus` on close.
|
|
198
|
+
*/
|
|
199
|
+
export async function pickPersistedSession(ctx, tui, theme, excludeSessionId, restoreFocus) {
|
|
200
|
+
const persistence = ctx.get('sessionPersistence');
|
|
201
|
+
if (persistence === undefined) {
|
|
202
|
+
throw new Error('Session persistence is not configured in this profile.');
|
|
203
|
+
}
|
|
204
|
+
let headers;
|
|
205
|
+
try {
|
|
206
|
+
headers = await persistence.list();
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
210
|
+
throw new Error(`Failed to list persisted sessions: ${message}`);
|
|
211
|
+
}
|
|
212
|
+
const candidates = headers
|
|
213
|
+
// Subagent children carry a persisted delegation depth — resuming one as
|
|
214
|
+
// this TUI's main conversation would misplace it in the recursion budget.
|
|
215
|
+
.filter(header => header.origin !== 'subagent')
|
|
216
|
+
.filter(header => String(header.id) !== excludeSessionId)
|
|
217
|
+
.sort((a, b) => b.createdAt - a.createdAt);
|
|
218
|
+
if (candidates.length === 0)
|
|
219
|
+
return { kind: 'empty' };
|
|
220
|
+
// Enrich the most recent candidates with their first-message preview so the
|
|
221
|
+
// rows are distinguishable at a glance; older ones fall back to the header
|
|
222
|
+
// label (cwd + short id).
|
|
223
|
+
const previews = await loadSessionPreviews(persistence, candidates.slice(0, PREVIEW_SESSION_CAP).map(header => header.id));
|
|
224
|
+
const items = candidates.map(header => {
|
|
225
|
+
const id = String(header.id);
|
|
226
|
+
const preview = previews.get(id);
|
|
227
|
+
return {
|
|
228
|
+
value: id,
|
|
229
|
+
label: preview ?? `${basename(header.cwd ?? '?')} · ${clipToWidth(id, 8)}`,
|
|
230
|
+
description: `${new Date(header.createdAt).toLocaleString()} · ${clipToWidth(id, 8)} · ${header.cwd ?? 'no cwd'}`,
|
|
231
|
+
};
|
|
232
|
+
});
|
|
233
|
+
return new Promise(resolve => {
|
|
234
|
+
// Wide primary column so previews (often CJK) get room before the time/cwd
|
|
235
|
+
// description column starts.
|
|
236
|
+
const list = new SelectList(items, 12, theme.selectList, { minPrimaryColumnWidth: 24, maxPrimaryColumnWidth: 60 });
|
|
237
|
+
list.setSelectedIndex(0);
|
|
238
|
+
// Framed overlay: 13 list rows + 4 frame rows fit inside 75% of 24 rows.
|
|
239
|
+
const overlay = tui.showOverlay(wrapFramedOverlay(theme, list), { width: '80%', maxHeight: '75%' });
|
|
240
|
+
const finish = (picked) => {
|
|
241
|
+
overlay.hide();
|
|
242
|
+
restoreFocus();
|
|
243
|
+
resolve(picked === undefined
|
|
244
|
+
? { kind: 'cancelled' }
|
|
245
|
+
: { kind: 'picked', id: picked.id, header: picked });
|
|
246
|
+
};
|
|
247
|
+
list.onSelect = item => {
|
|
248
|
+
finish(candidates.find(header => String(header.id) === item.value));
|
|
249
|
+
};
|
|
250
|
+
list.onCancel = () => finish(undefined);
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
//# sourceMappingURL=sessions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.js","sourceRoot":"","sources":["../src/sessions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text-based settings browser — the terminal counterpart of the web GUI's
|
|
3
|
+
* settings surface (schema-form driven there, pi-tui overlays here).
|
|
4
|
+
*
|
|
5
|
+
* Walks `ctx.settings.describe()` (registered namespaces → serialized
|
|
6
|
+
* schemastery schemas → resolved values) and renders it as nested
|
|
7
|
+
* SettingsList overlays, one level per schema depth:
|
|
8
|
+
*
|
|
9
|
+
* level 0 category list (searchable): general / models / plugins / agent,
|
|
10
|
+
* then `other` for unmapped namespaces. Namespace→category comes
|
|
11
|
+
* from a static mapping (categorizeNamespaces) mirroring the web
|
|
12
|
+
* settings page: the web client slots namespaces into categories
|
|
13
|
+
* client-side and the data plane carries no category field, so
|
|
14
|
+
* the mapping is maintained here by hand; `other` is hidden when
|
|
15
|
+
* empty.
|
|
16
|
+
* level 1 namespace list for the chosen category (searchable);
|
|
17
|
+
* description shows applies timing. The Models category is
|
|
18
|
+
* special-cased: it lists configured llm-pi-ai providers (label,
|
|
19
|
+
* model summary, API-key state) instead of the raw namespace —
|
|
20
|
+
* the original schema surface is hidden — plus dedicated
|
|
21
|
+
* llm-deepseek / agent-default-model rows and an add-provider
|
|
22
|
+
* flow (built-in directory → one API key → write)
|
|
23
|
+
* level 2+ schema walk, dispatched on node type:
|
|
24
|
+
* - object/dict → drill in (nested list; dicts get an add-key row)
|
|
25
|
+
* - boolean / all-literal union → Enter cycles the value
|
|
26
|
+
* - string/number/mixed union → inline Input editor
|
|
27
|
+
* - array / literal / const / unknown → read-only viewer
|
|
28
|
+
* - role('secret') fields are masked and edited without prefill
|
|
29
|
+
* - every group row resets its subtree to defaults (Esc confirms)
|
|
30
|
+
*
|
|
31
|
+
* Writes go through `settings.mutate(ns, pathOps, expectedRevision)` so the
|
|
32
|
+
* user layer is edited by path without restating sections the UI never saw;
|
|
33
|
+
* the descriptor (and with it the revision) is re-read after every committed
|
|
34
|
+
* write, and failed writes revert the on-screen row and surface the error.
|
|
35
|
+
*/
|
|
36
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
37
|
+
import { type SchemaNode } from '@deepseek-ai/dsh-client-schema-form';
|
|
38
|
+
import { type Component, type TUI } from '@earendil-works/pi-tui';
|
|
39
|
+
import { type TuiTheme } from './theme/index.ts';
|
|
40
|
+
export interface SettingsCategory {
|
|
41
|
+
id: string;
|
|
42
|
+
label: string;
|
|
43
|
+
namespaces: readonly string[];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Static namespace→category mapping, mirroring the web settings page's
|
|
47
|
+
* client-side slot structure (see the module header): the data plane carries
|
|
48
|
+
* no category field, so the slots are maintained here by hand. Namespaces not
|
|
49
|
+
* listed anywhere fall into the trailing `other` category.
|
|
50
|
+
*
|
|
51
|
+
* Labels are English-only: pi-tui's SettingsList search matches the label
|
|
52
|
+
* text, so English queries (model, shell, permission, …) hit directly.
|
|
53
|
+
*/
|
|
54
|
+
export declare const CATEGORY_MAP: readonly SettingsCategory[];
|
|
55
|
+
/** Cap for a category row's member-name description line. */
|
|
56
|
+
export declare const CATEGORY_DESC_MAX = 60;
|
|
57
|
+
/**
|
|
58
|
+
* Group a describe() namespace list into ordered categories — general, models,
|
|
59
|
+
* plugins, agent, then `other` for everything unmapped. Categories with no
|
|
60
|
+
* members are dropped, including `other` when nothing falls into it.
|
|
61
|
+
*
|
|
62
|
+
* Defensive: duplicate namespaces in the input count once (a namespace
|
|
63
|
+
* registered twice must not be listed twice), and the `mapped` guard resolves
|
|
64
|
+
* CATEGORY_MAP overlap — a namespace listed in several categories goes to the
|
|
65
|
+
* first one, by category order.
|
|
66
|
+
*/
|
|
67
|
+
export declare function categorizeNamespaces(nses: string[]): SettingsCategory[];
|
|
68
|
+
/**
|
|
69
|
+
* Description line for a category row: member names joined with ", ", capped
|
|
70
|
+
* at `max` columns. Namespace names are ASCII, so visible width equals char
|
|
71
|
+
* count here; the width-aware clip is used anyway so the cap semantics stay
|
|
72
|
+
* uniform with every other truncation in the TUI (an exactly-fitting string
|
|
73
|
+
* is kept whole, an ellipsis appears only when a column is free).
|
|
74
|
+
*/
|
|
75
|
+
export declare function categoryDescription(namespaces: readonly string[], max?: number): string;
|
|
76
|
+
/** Human display for a resolved value (row value column; never re-scans anything). */
|
|
77
|
+
export declare function formatValue(value: unknown): string;
|
|
78
|
+
/** Display string for one union literal (the SettingsList cycle vocabulary). */
|
|
79
|
+
export declare function displayValue(value: unknown): string;
|
|
80
|
+
export interface UnionLiterals {
|
|
81
|
+
/** Raw literal values (strings, numbers, booleans, …) in schema order. */
|
|
82
|
+
values: unknown[];
|
|
83
|
+
/** Whether every union branch is a plain literal (cycle-eligible). */
|
|
84
|
+
all: boolean;
|
|
85
|
+
}
|
|
86
|
+
/** Extract literal branches of a union node (`literal`/`const` members). */
|
|
87
|
+
export declare function unionLiterals(node: SchemaNode): UnionLiterals;
|
|
88
|
+
/** One parsed Input outcome: commit a value, unset (reset), or reject. */
|
|
89
|
+
export type ParseOutcome = {
|
|
90
|
+
kind: 'value';
|
|
91
|
+
value: unknown;
|
|
92
|
+
} | {
|
|
93
|
+
kind: 'unset';
|
|
94
|
+
} | {
|
|
95
|
+
kind: 'keep';
|
|
96
|
+
} | {
|
|
97
|
+
kind: 'error';
|
|
98
|
+
error: string;
|
|
99
|
+
};
|
|
100
|
+
export declare function parseNumberInput(text: string): ParseOutcome;
|
|
101
|
+
export declare function parseStringInput(text: string): ParseOutcome;
|
|
102
|
+
/** Mixed (non-literal) union editing: JSON values, else a raw string branch. */
|
|
103
|
+
export declare function parseUnionInput(text: string, node: SchemaNode): ParseOutcome;
|
|
104
|
+
/** Seed value for a newly added dict key, from the inner schema. */
|
|
105
|
+
export declare function defaultValueFor(node: SchemaNode): unknown;
|
|
106
|
+
/** Description line for one field row (static — built once per row). */
|
|
107
|
+
export declare function fieldDescription(node: SchemaNode, userOverride: boolean): string;
|
|
108
|
+
/**
|
|
109
|
+
* Outcome of one commit write: an `error` (shown with the ✘ marker and
|
|
110
|
+
* danger color), a `notice` (shown as a plain hint — the write succeeded but
|
|
111
|
+
* needs a follow-up, e.g. "provider added, key must come from the
|
|
112
|
+
* environment"), or `undefined` (clean success → the editor closes). Exactly
|
|
113
|
+
* one of `error`/`notice` is set.
|
|
114
|
+
*/
|
|
115
|
+
export interface CommitResult {
|
|
116
|
+
error?: string;
|
|
117
|
+
notice?: string;
|
|
118
|
+
}
|
|
119
|
+
interface EditOptions {
|
|
120
|
+
title: string;
|
|
121
|
+
subtitle: string;
|
|
122
|
+
initial: string;
|
|
123
|
+
parse: (text: string) => ParseOutcome;
|
|
124
|
+
onCommit: (outcome: ParseOutcome) => Promise<CommitResult | undefined>;
|
|
125
|
+
onDone: () => void;
|
|
126
|
+
/** Error sink for writes that fail after the editor already closed (Esc). */
|
|
127
|
+
onError?: (message: string) => void;
|
|
128
|
+
/**
|
|
129
|
+
* Masked rendering: the value is shown as a dot row instead of plaintext
|
|
130
|
+
* (secrets never echo). Editing semantics are unaffected — the internal
|
|
131
|
+
* Input still owns input/delete/paste/submit.
|
|
132
|
+
*/
|
|
133
|
+
secret?: boolean;
|
|
134
|
+
}
|
|
135
|
+
/** Inline value editor: title, current-value line, error/notice line, Input. */
|
|
136
|
+
export declare class EditField implements Component {
|
|
137
|
+
private readonly tui;
|
|
138
|
+
private readonly options;
|
|
139
|
+
private readonly input;
|
|
140
|
+
private error;
|
|
141
|
+
/** Non-error commit message (success with a follow-up hint, see CommitResult). */
|
|
142
|
+
private notice;
|
|
143
|
+
/** Set while a commit write is in flight; extra Enter presses are ignored. */
|
|
144
|
+
private pending;
|
|
145
|
+
/** Guards onDone: exactly one terminal transition (submit success/keep/escape). */
|
|
146
|
+
private done;
|
|
147
|
+
private readonly fg;
|
|
148
|
+
private readonly fgMuted;
|
|
149
|
+
private readonly fgDanger;
|
|
150
|
+
/** Popup-surface background for the secret mask line. */
|
|
151
|
+
private readonly maskBg;
|
|
152
|
+
constructor(tui: TUI, options: EditOptions, theme: TuiTheme);
|
|
153
|
+
/** Terminal transition — runs the caller's onDone exactly once. */
|
|
154
|
+
private finish;
|
|
155
|
+
invalidate(): void;
|
|
156
|
+
render(width: number): string[];
|
|
157
|
+
/**
|
|
158
|
+
* Masked input line for secret fields: a dot row (one dot per visible
|
|
159
|
+
* column of the value, capped to the popup width) with a `▎` marker at the
|
|
160
|
+
* cursor position. The real cursor is not rendered — the marker only hints
|
|
161
|
+
* at the editing position; input semantics live in the internal Input.
|
|
162
|
+
*/
|
|
163
|
+
private maskLine;
|
|
164
|
+
handleInput(data: string): void;
|
|
165
|
+
}
|
|
166
|
+
export interface OpenSettingsBrowserOptions {
|
|
167
|
+
ctx: Context;
|
|
168
|
+
tui: TUI;
|
|
169
|
+
theme: TuiTheme;
|
|
170
|
+
/** Focus target to restore when the browser closes (usually the editor). */
|
|
171
|
+
restoreFocus: () => void;
|
|
172
|
+
/** Error sink for writes that fail outside an inline editor (transcript). */
|
|
173
|
+
onError: (message: string) => void;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Open the modal settings browser. Resolves when it closes with the number of
|
|
177
|
+
* committed writes, or -1 when no namespace is registered (nothing to show).
|
|
178
|
+
*/
|
|
179
|
+
export declare function openSettingsBrowser(options: OpenSettingsBrowserOptions): Promise<number>;
|
|
180
|
+
export {};
|