@hmharness/cli 0.4.2 → 0.4.3
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/dist/tui.d.ts +12 -2
- package/dist/tui.js +59 -22
- package/package.json +7 -7
package/dist/tui.d.ts
CHANGED
|
@@ -46,6 +46,8 @@ export declare class TuiRuntime {
|
|
|
46
46
|
private modeTag;
|
|
47
47
|
/** rows for the `/model ` picker (configured providers first, set by driver) */
|
|
48
48
|
private modelChoices;
|
|
49
|
+
/** rows for the `/resume ` session picker (id prefix + first user line) */
|
|
50
|
+
private sessionChoices;
|
|
49
51
|
/** Wheel/click handling: NO mouse reporting by default - select/copy
|
|
50
52
|
* always works and terminals translate the wheel to arrow keys on the
|
|
51
53
|
* alternate screen. Reporting turns on ONLY while a palette is open
|
|
@@ -63,9 +65,16 @@ export declare class TuiRuntime {
|
|
|
63
65
|
name: string;
|
|
64
66
|
desc: string;
|
|
65
67
|
}>): void;
|
|
68
|
+
setSessionChoices(list: Array<{
|
|
69
|
+
name: string;
|
|
70
|
+
desc: string;
|
|
71
|
+
}>): void;
|
|
66
72
|
/** Focus the /model picker (used by the bare `/model` command so the
|
|
67
73
|
* printed list is never a dead end - the live palette opens on it). */
|
|
68
74
|
openModelPicker(): void;
|
|
75
|
+
/** Focus the /resume session picker - same live palette as /model:
|
|
76
|
+
* arrows/wheel navigate, Enter loads the highlighted session. */
|
|
77
|
+
openSessionPicker(): void;
|
|
69
78
|
/** Run the highlighted palette row (shared by Enter and palette clicks);
|
|
70
79
|
* with no palette open it just submits the typed input. */
|
|
71
80
|
private pickHighlighted;
|
|
@@ -89,8 +98,9 @@ export declare class TuiRuntime {
|
|
|
89
98
|
* while it shows, clicks choose its rows and the wheel drives its
|
|
90
99
|
* selection; drag-select resumes the moment it closes. */
|
|
91
100
|
private syncMouseReporting;
|
|
92
|
-
/** The palette data source: `/model ` opens the model picker,
|
|
93
|
-
* slash commands. Rows are {name, desc} so
|
|
101
|
+
/** The palette data source: `/model ` opens the model picker, `/resume `
|
|
102
|
+
* the session picker, otherwise slash commands. Rows are {name, desc} so
|
|
103
|
+
* all three share one renderer, keyboard and click machinery. */
|
|
94
104
|
private panelItems;
|
|
95
105
|
configure(model: string, cwdName: string, skillCount: number, locale: Locale, version?: string): void;
|
|
96
106
|
destroy(): void;
|
package/dist/tui.js
CHANGED
|
@@ -167,6 +167,8 @@ export class TuiRuntime {
|
|
|
167
167
|
modeTag = '';
|
|
168
168
|
/** rows for the `/model ` picker (configured providers first, set by driver) */
|
|
169
169
|
modelChoices = [];
|
|
170
|
+
/** rows for the `/resume ` session picker (id prefix + first user line) */
|
|
171
|
+
sessionChoices = [];
|
|
170
172
|
/** Wheel/click handling: NO mouse reporting by default - select/copy
|
|
171
173
|
* always works and terminals translate the wheel to arrow keys on the
|
|
172
174
|
* alternate screen. Reporting turns on ONLY while a palette is open
|
|
@@ -195,6 +197,10 @@ export class TuiRuntime {
|
|
|
195
197
|
this.modelChoices = list;
|
|
196
198
|
this.dirty = true;
|
|
197
199
|
}
|
|
200
|
+
setSessionChoices(list) {
|
|
201
|
+
this.sessionChoices = list;
|
|
202
|
+
this.dirty = true;
|
|
203
|
+
}
|
|
198
204
|
/** Focus the /model picker (used by the bare `/model` command so the
|
|
199
205
|
* printed list is never a dead end - the live palette opens on it). */
|
|
200
206
|
openModelPicker() {
|
|
@@ -203,13 +209,26 @@ export class TuiRuntime {
|
|
|
203
209
|
this.cmdIdx = 0;
|
|
204
210
|
this.dirty = true;
|
|
205
211
|
}
|
|
212
|
+
/** Focus the /resume session picker - same live palette as /model:
|
|
213
|
+
* arrows/wheel navigate, Enter loads the highlighted session. */
|
|
214
|
+
openSessionPicker() {
|
|
215
|
+
this.input = '/resume ';
|
|
216
|
+
this.caret = this.input.length;
|
|
217
|
+
this.cmdIdx = 0;
|
|
218
|
+
this.dirty = true;
|
|
219
|
+
}
|
|
206
220
|
/** Run the highlighted palette row (shared by Enter and palette clicks);
|
|
207
221
|
* with no palette open it just submits the typed input. */
|
|
208
222
|
pickHighlighted() {
|
|
209
223
|
const hits = this.panelItems(this.input);
|
|
210
224
|
const pick = hits.length ? hits[Math.min(this.cmdIdx, hits.length - 1)].name : '';
|
|
211
225
|
if (pick) {
|
|
212
|
-
|
|
226
|
+
if (this.input.startsWith('/model'))
|
|
227
|
+
this.input = `/model ${pick} `;
|
|
228
|
+
else if (this.input.startsWith('/resume'))
|
|
229
|
+
this.input = `/resume ${pick}`;
|
|
230
|
+
else
|
|
231
|
+
this.input = pick + ' ';
|
|
213
232
|
this.caret = this.input.length;
|
|
214
233
|
this.cmdIdx = 0;
|
|
215
234
|
}
|
|
@@ -271,8 +290,9 @@ export class TuiRuntime {
|
|
|
271
290
|
this.mouseReported = want;
|
|
272
291
|
stdout.write(want ? '\x1b[?1000h\x1b[?1006h' : '\x1b[?1000l\x1b[?1006l');
|
|
273
292
|
}
|
|
274
|
-
/** The palette data source: `/model ` opens the model picker,
|
|
275
|
-
* slash commands. Rows are {name, desc} so
|
|
293
|
+
/** The palette data source: `/model ` opens the model picker, `/resume `
|
|
294
|
+
* the session picker, otherwise slash commands. Rows are {name, desc} so
|
|
295
|
+
* all three share one renderer, keyboard and click machinery. */
|
|
276
296
|
panelItems(input) {
|
|
277
297
|
if (input === '/model' || input.startsWith('/model ')) {
|
|
278
298
|
const q = input.slice(6).trim().toLowerCase();
|
|
@@ -283,6 +303,10 @@ export class TuiRuntime {
|
|
|
283
303
|
const all = [...configured, ...rest];
|
|
284
304
|
return q ? all.filter((i) => i.name.toLowerCase().startsWith(q)) : all;
|
|
285
305
|
}
|
|
306
|
+
if (input === '/resume' || input.startsWith('/resume ')) {
|
|
307
|
+
const q = input.slice(7).trim().toLowerCase();
|
|
308
|
+
return q ? this.sessionChoices.filter((i) => i.name.toLowerCase().startsWith(q)) : this.sessionChoices;
|
|
309
|
+
}
|
|
286
310
|
return matchCommands(input).map((c) => ({ name: c.name, desc: String(this.t[c.key]) }));
|
|
287
311
|
}
|
|
288
312
|
configure(model, cwdName, skillCount, locale, version) {
|
|
@@ -509,6 +533,12 @@ export class TuiRuntime {
|
|
|
509
533
|
this.openModelPicker();
|
|
510
534
|
return;
|
|
511
535
|
}
|
|
536
|
+
// same two-stage rule for /resume: bare command + Enter opens the
|
|
537
|
+
// session picker (focus moves to the list), never loads row 0 blindly
|
|
538
|
+
if (this.input === '/resume') {
|
|
539
|
+
this.openSessionPicker();
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
512
542
|
// palette open: Enter runs the highlighted row (a command, or a
|
|
513
543
|
// /model target), not the raw input; without a palette it submits
|
|
514
544
|
this.pickHighlighted();
|
|
@@ -519,7 +549,9 @@ export class TuiRuntime {
|
|
|
519
549
|
if (hits.length) {
|
|
520
550
|
this.input = this.input.startsWith('/model')
|
|
521
551
|
? `/model ${hits[Math.min(this.cmdIdx, hits.length - 1)].name} `
|
|
522
|
-
:
|
|
552
|
+
: this.input.startsWith('/resume')
|
|
553
|
+
? `/resume ${hits[Math.min(this.cmdIdx, hits.length - 1)].name}`
|
|
554
|
+
: hits[Math.min(this.cmdIdx, hits.length - 1)].name + ' ';
|
|
523
555
|
this.caret = this.input.length;
|
|
524
556
|
this.cmdIdx = 0;
|
|
525
557
|
this.dirty = true;
|
|
@@ -925,29 +957,34 @@ export async function tui(yes, noWeb = false) {
|
|
|
925
957
|
if (line === '/resume' || line.startsWith('/resume ')) {
|
|
926
958
|
const arg = line.slice(8).trim();
|
|
927
959
|
const { latestSession, loadTranscript } = await import('@hmharness/kernel');
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
960
|
+
const { readdir } = await import('node:fs/promises');
|
|
961
|
+
// refresh the picker rows: id prefix + first user line as the preview
|
|
962
|
+
let files = [];
|
|
963
|
+
try {
|
|
964
|
+
files = (await readdir(join(home, 'sessions'))).filter((f) => f.endsWith('.jsonl'));
|
|
965
|
+
}
|
|
966
|
+
catch { /* none */ }
|
|
967
|
+
files.sort();
|
|
968
|
+
const recent = files.slice(-8).reverse();
|
|
969
|
+
const rows = [];
|
|
970
|
+
for (const f of recent) {
|
|
932
971
|
try {
|
|
933
|
-
|
|
972
|
+
const tr = await loadTranscript(join(home, 'sessions', f));
|
|
973
|
+
const firstUser = tr?.messages.find((m) => m.role === 'user')?.content ?? '';
|
|
974
|
+
rows.push({ name: f.slice(0, 18), desc: (firstUser || '(no user line)').replace(/\n/g, ' ').slice(0, 56) });
|
|
934
975
|
}
|
|
935
|
-
catch { /*
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
976
|
+
catch { /* skip unreadable */ }
|
|
977
|
+
}
|
|
978
|
+
rt.setSessionChoices(rows);
|
|
979
|
+
if (!arg) {
|
|
980
|
+
// bare /resume opens the LIVE picker - same arrows/wheel/Enter/click
|
|
981
|
+
// machinery as /model; a printed text list is a dead end (user-
|
|
982
|
+
// reported: "上下键无法选择")
|
|
983
|
+
if (rows.length === 0) {
|
|
939
984
|
rt.addText(t.cmdResumeNone, 'dim');
|
|
940
985
|
return;
|
|
941
986
|
}
|
|
942
|
-
|
|
943
|
-
try {
|
|
944
|
-
const tr = await loadTranscript(join(home, 'sessions', f));
|
|
945
|
-
const firstUser = tr?.messages.find((m) => m.role === 'user')?.content ?? '';
|
|
946
|
-
rt.addText(`${CYAN(f.slice(0, 18))} ${(firstUser || '(no user line)').replace(/\n/g, ' ').slice(0, 60)}`, 'dim');
|
|
947
|
-
}
|
|
948
|
-
catch { /* skip unreadable */ }
|
|
949
|
-
}
|
|
950
|
-
rt.addText(t.cmdResumeHint, 'dim');
|
|
987
|
+
rt.openSessionPicker();
|
|
951
988
|
return;
|
|
952
989
|
}
|
|
953
990
|
const file = await latestSession(home, arg);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hmharness/cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "hmharness command line: one-shot tasks, an interactive REPL, a fullscreen TUI, the web frontend, and direct tool invocation.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/main.js",
|
|
@@ -43,11 +43,11 @@
|
|
|
43
43
|
"build": "tsc -p tsconfig.build.json"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@hmharness/kernel": "0.4.
|
|
47
|
-
"@hmharness/evolution": "0.4.
|
|
48
|
-
"@hmharness/domain-harmony": "0.4.
|
|
49
|
-
"@hmharness/domain-ops": "0.4.
|
|
50
|
-
"@hmharness/agent": "0.4.
|
|
51
|
-
"@hmharness/web": "0.4.
|
|
46
|
+
"@hmharness/kernel": "0.4.3",
|
|
47
|
+
"@hmharness/evolution": "0.4.3",
|
|
48
|
+
"@hmharness/domain-harmony": "0.4.3",
|
|
49
|
+
"@hmharness/domain-ops": "0.4.3",
|
|
50
|
+
"@hmharness/agent": "0.4.3",
|
|
51
|
+
"@hmharness/web": "0.4.3"
|
|
52
52
|
}
|
|
53
53
|
}
|