skyloom 1.15.5 → 1.16.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/dist/cli/command_args.d.ts +74 -0
- package/dist/cli/command_args.d.ts.map +1 -0
- package/dist/cli/command_args.js +129 -0
- package/dist/cli/command_args.js.map +1 -0
- package/dist/cli/loom.d.ts +20 -0
- package/dist/cli/loom.d.ts.map +1 -1
- package/dist/cli/loom.js +202 -24
- package/dist/cli/loom.js.map +1 -1
- package/dist/cli/loom_chat.d.ts.map +1 -1
- package/dist/cli/loom_chat.js +39 -0
- package/dist/cli/loom_chat.js.map +1 -1
- package/dist/core/agent.js +2 -2
- package/dist/core/agent.js.map +1 -1
- package/dist/core/security.d.ts.map +1 -1
- package/dist/core/security.js +1 -0
- package/dist/core/security.js.map +1 -1
- package/dist/core/tool_router.d.ts.map +1 -1
- package/dist/core/tool_router.js +11 -3
- package/dist/core/tool_router.js.map +1 -1
- package/dist/tools/builtin.d.ts.map +1 -1
- package/dist/tools/builtin.js +38 -192
- package/dist/tools/builtin.js.map +1 -1
- package/dist/tools/websearch.d.ts +92 -0
- package/dist/tools/websearch.d.ts.map +1 -0
- package/dist/tools/websearch.js +343 -0
- package/dist/tools/websearch.js.map +1 -0
- package/package.json +1 -1
- package/src/cli/command_args.ts +159 -0
- package/src/cli/loom.ts +155 -17
- package/src/cli/loom_chat.ts +33 -0
- package/src/core/agent.ts +2 -2
- package/src/core/security.ts +1 -0
- package/src/core/tool_router.ts +11 -3
- package/src/tools/builtin.ts +38 -190
- package/src/tools/websearch.ts +368 -0
- package/tests/command_args.test.ts +115 -0
- package/tests/loom.test.ts +74 -0
- package/tests/tool_router.test.ts +15 -0
- package/tests/websearch.test.ts +190 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 斜杠命令向导 · Cascading argument wizard for slash commands.
|
|
3
|
+
*
|
|
4
|
+
* After a slash command that takes structured arguments is chosen in the loom
|
|
5
|
+
* palette, the TUI walks the user through its arguments one level at a time:
|
|
6
|
+
* pick a provider, then paste a key; pick a model; pick a session. Each level is
|
|
7
|
+
* navigable with ↑/↓ and filterable by typing — the same affordance as the
|
|
8
|
+
* command palette itself, extended to arguments.
|
|
9
|
+
*
|
|
10
|
+
* This module is the pure brain of that flow (no I/O, no terminal) so it is
|
|
11
|
+
* fully unit-testable: given a command, the values chosen so far, and a snapshot
|
|
12
|
+
* of runtime context, it returns the next step — or null when the command is
|
|
13
|
+
* complete and ready to submit.
|
|
14
|
+
*/
|
|
15
|
+
export interface ArgChoice {
|
|
16
|
+
/** The value contributed to the final command line. */
|
|
17
|
+
value: string;
|
|
18
|
+
/** Display label in the list. */
|
|
19
|
+
label: string;
|
|
20
|
+
/** Optional dim hint shown after the label. */
|
|
21
|
+
hint?: string;
|
|
22
|
+
/** Optional group heading (e.g. provider name) for sectioned lists. */
|
|
23
|
+
group?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface WizardStep {
|
|
26
|
+
kind: 'choice' | 'freeform';
|
|
27
|
+
/** Heading shown above the list / prompt. */
|
|
28
|
+
title: string;
|
|
29
|
+
/** Choices for a 'choice' step (already ordered). */
|
|
30
|
+
choices: ArgChoice[];
|
|
31
|
+
/** A 'choice' step may also accept a typed value not in the list. */
|
|
32
|
+
allowFreeform: boolean;
|
|
33
|
+
/** Placeholder for a 'freeform' step (or a free-typed choice). */
|
|
34
|
+
placeholder?: string;
|
|
35
|
+
/** Mask typed input (API keys). */
|
|
36
|
+
secret?: boolean;
|
|
37
|
+
}
|
|
38
|
+
export interface WizardProvider {
|
|
39
|
+
id: string;
|
|
40
|
+
label: string;
|
|
41
|
+
configured: boolean;
|
|
42
|
+
envVar?: string;
|
|
43
|
+
}
|
|
44
|
+
export interface WizardModel {
|
|
45
|
+
id: string;
|
|
46
|
+
provider: string;
|
|
47
|
+
label: string;
|
|
48
|
+
hint?: string;
|
|
49
|
+
}
|
|
50
|
+
export interface WizardSession {
|
|
51
|
+
id: string;
|
|
52
|
+
label: string;
|
|
53
|
+
}
|
|
54
|
+
export interface WizardContext {
|
|
55
|
+
providers: WizardProvider[];
|
|
56
|
+
models: WizardModel[];
|
|
57
|
+
sessions: WizardSession[];
|
|
58
|
+
}
|
|
59
|
+
/** Does this base command (with or without leading slash) have a wizard? */
|
|
60
|
+
export declare function hasWizard(command: string): boolean;
|
|
61
|
+
/**
|
|
62
|
+
* The next step for `command` given the values already chosen, or null when the
|
|
63
|
+
* command is complete (ready to submit via {@link buildCommandLine}).
|
|
64
|
+
*/
|
|
65
|
+
export declare function nextWizardStep(command: string, prior: string[], ctx: WizardContext): WizardStep | null;
|
|
66
|
+
/** Assemble the final command line from the base command + chosen values. */
|
|
67
|
+
export declare function buildCommandLine(command: string, values: string[]): string;
|
|
68
|
+
/**
|
|
69
|
+
* Filter + rank choices by a typed query (case-insensitive substring on value,
|
|
70
|
+
* label, and group). Empty query returns the list unchanged. Exact value/label
|
|
71
|
+
* prefix matches sort first so the obvious pick lands at the top.
|
|
72
|
+
*/
|
|
73
|
+
export declare function filterChoices(choices: ArgChoice[], typed: string): ArgChoice[];
|
|
74
|
+
//# sourceMappingURL=command_args.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command_args.d.ts","sourceRoot":"","sources":["../../src/cli/command_args.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,SAAS;IACxB,uDAAuD;IACvD,KAAK,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,QAAQ,GAAG,UAAU,CAAC;IAC5B,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB,qEAAqE;IACrE,aAAa,EAAE,OAAO,CAAC;IACvB,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mCAAmC;IACnC,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAAG,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE;AACnG,MAAM,WAAW,WAAW;IAAG,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE;AAC3F,MAAM,WAAW,aAAa;IAAG,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE;AAE5D,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,QAAQ,EAAE,aAAa,EAAE,CAAC;CAC3B;AAKD,4EAA4E;AAC5E,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAElD;AAmBD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,aAAa,GAAG,UAAU,GAAG,IAAI,CAoCtG;AAED,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAgB1E;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,CAiB9E"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* 斜杠命令向导 · Cascading argument wizard for slash commands.
|
|
4
|
+
*
|
|
5
|
+
* After a slash command that takes structured arguments is chosen in the loom
|
|
6
|
+
* palette, the TUI walks the user through its arguments one level at a time:
|
|
7
|
+
* pick a provider, then paste a key; pick a model; pick a session. Each level is
|
|
8
|
+
* navigable with ↑/↓ and filterable by typing — the same affordance as the
|
|
9
|
+
* command palette itself, extended to arguments.
|
|
10
|
+
*
|
|
11
|
+
* This module is the pure brain of that flow (no I/O, no terminal) so it is
|
|
12
|
+
* fully unit-testable: given a command, the values chosen so far, and a snapshot
|
|
13
|
+
* of runtime context, it returns the next step — or null when the command is
|
|
14
|
+
* complete and ready to submit.
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.hasWizard = hasWizard;
|
|
18
|
+
exports.nextWizardStep = nextWizardStep;
|
|
19
|
+
exports.buildCommandLine = buildCommandLine;
|
|
20
|
+
exports.filterChoices = filterChoices;
|
|
21
|
+
/** Commands that drive a guided wizard (base name without the leading slash). */
|
|
22
|
+
const WIZARD_COMMANDS = new Set(['model', 'apikey', 'connect', 'resume']);
|
|
23
|
+
/** Does this base command (with or without leading slash) have a wizard? */
|
|
24
|
+
function hasWizard(command) {
|
|
25
|
+
return WIZARD_COMMANDS.has(command.replace(/^\//, '').trim().toLowerCase());
|
|
26
|
+
}
|
|
27
|
+
function providerChoices(ctx) {
|
|
28
|
+
return ctx.providers.map((p) => ({
|
|
29
|
+
value: p.id,
|
|
30
|
+
label: p.label,
|
|
31
|
+
hint: p.configured ? '✓ 已配置' : (p.envVar ? `需 ${p.envVar}` : '未配置'),
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
function modelChoices(ctx) {
|
|
35
|
+
return ctx.models.map((m) => ({
|
|
36
|
+
value: m.id,
|
|
37
|
+
label: m.id,
|
|
38
|
+
hint: m.hint,
|
|
39
|
+
group: m.provider,
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The next step for `command` given the values already chosen, or null when the
|
|
44
|
+
* command is complete (ready to submit via {@link buildCommandLine}).
|
|
45
|
+
*/
|
|
46
|
+
function nextWizardStep(command, prior, ctx) {
|
|
47
|
+
const cmd = command.replace(/^\//, '').trim().toLowerCase();
|
|
48
|
+
switch (cmd) {
|
|
49
|
+
case 'model': {
|
|
50
|
+
if (prior.length >= 1)
|
|
51
|
+
return null;
|
|
52
|
+
const choices = [
|
|
53
|
+
{ value: 'reset', label: '↺ reset', hint: '回到统一默认模型' },
|
|
54
|
+
...modelChoices(ctx),
|
|
55
|
+
];
|
|
56
|
+
return { kind: 'choice', title: '选择模型(输入可筛选)', choices, allowFreeform: true, placeholder: '模型 id' };
|
|
57
|
+
}
|
|
58
|
+
case 'connect': {
|
|
59
|
+
if (prior.length >= 1)
|
|
60
|
+
return null;
|
|
61
|
+
return { kind: 'choice', title: '选择 Provider', choices: providerChoices(ctx), allowFreeform: true, placeholder: 'provider' };
|
|
62
|
+
}
|
|
63
|
+
case 'apikey': {
|
|
64
|
+
// step 0: provider · step 1: the key
|
|
65
|
+
if (prior.length === 0) {
|
|
66
|
+
return { kind: 'choice', title: '为哪个 Provider 配置 API Key', choices: providerChoices(ctx), allowFreeform: true, placeholder: 'provider' };
|
|
67
|
+
}
|
|
68
|
+
if (prior.length === 1) {
|
|
69
|
+
return { kind: 'freeform', title: `粘贴 ${prior[0]} 的 API Key`, choices: [], allowFreeform: true, placeholder: 'sk-…(回车保存)', secret: true };
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
case 'resume': {
|
|
74
|
+
if (prior.length >= 1)
|
|
75
|
+
return null;
|
|
76
|
+
const choices = ctx.sessions.map((s, i) => ({ value: String(i + 1), label: `${i + 1}. ${s.label}`, hint: s.id.slice(0, 8) }));
|
|
77
|
+
return { kind: 'choice', title: choices.length ? '选择要恢复的会话' : '暂无历史会话', choices, allowFreeform: true, placeholder: '序号或 id' };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
/** Assemble the final command line from the base command + chosen values. */
|
|
83
|
+
function buildCommandLine(command, values) {
|
|
84
|
+
const cmd = command.replace(/^\//, '').trim().toLowerCase();
|
|
85
|
+
const v = values.filter((x) => x !== undefined && x !== null);
|
|
86
|
+
switch (cmd) {
|
|
87
|
+
case 'apikey':
|
|
88
|
+
// /apikey set <provider> <key>
|
|
89
|
+
return `/apikey set ${v.join(' ')}`.trim();
|
|
90
|
+
case 'model':
|
|
91
|
+
return `/model ${v.join(' ')}`.trim();
|
|
92
|
+
case 'connect':
|
|
93
|
+
return `/connect ${v.join(' ')}`.trim();
|
|
94
|
+
case 'resume':
|
|
95
|
+
return `/resume ${v.join(' ')}`.trim();
|
|
96
|
+
default:
|
|
97
|
+
return `/${cmd} ${v.join(' ')}`.trim();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Filter + rank choices by a typed query (case-insensitive substring on value,
|
|
102
|
+
* label, and group). Empty query returns the list unchanged. Exact value/label
|
|
103
|
+
* prefix matches sort first so the obvious pick lands at the top.
|
|
104
|
+
*/
|
|
105
|
+
function filterChoices(choices, typed) {
|
|
106
|
+
const q = typed.trim().toLowerCase();
|
|
107
|
+
if (!q)
|
|
108
|
+
return choices;
|
|
109
|
+
const scored = [];
|
|
110
|
+
for (const c of choices) {
|
|
111
|
+
const value = c.value.toLowerCase();
|
|
112
|
+
const label = c.label.toLowerCase();
|
|
113
|
+
const group = (c.group || '').toLowerCase();
|
|
114
|
+
let rank = -1;
|
|
115
|
+
if (value === q || label === q)
|
|
116
|
+
rank = 0;
|
|
117
|
+
else if (value.startsWith(q) || label.startsWith(q))
|
|
118
|
+
rank = 1;
|
|
119
|
+
else if (value.includes(q) || label.includes(q))
|
|
120
|
+
rank = 2;
|
|
121
|
+
else if (group.includes(q))
|
|
122
|
+
rank = 3;
|
|
123
|
+
if (rank >= 0)
|
|
124
|
+
scored.push({ c, rank });
|
|
125
|
+
}
|
|
126
|
+
scored.sort((a, b) => a.rank - b.rank); // stable within equal ranks
|
|
127
|
+
return scored.map((s) => s.c);
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=command_args.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command_args.js","sourceRoot":"","sources":["../../src/cli/command_args.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/loom.d.ts
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
*
|
|
26
26
|
* Design rationale: docs/AESTHETIC_DESIGN.md §2.2 (方案三 · 立轴).
|
|
27
27
|
*/
|
|
28
|
+
import { type WizardStep } from "./command_args";
|
|
28
29
|
/** Truncate a styled string to a visual width, keeping ANSI sequences intact. */
|
|
29
30
|
export declare function cutVisual(s: string, maxW: number): string;
|
|
30
31
|
/** Pad a styled string with spaces to an exact visual width (truncates if over). */
|
|
@@ -168,6 +169,14 @@ export declare class LoomUI {
|
|
|
168
169
|
modeBadge: string;
|
|
169
170
|
/** User-defined slash commands shown in the palette ([name, description]). */
|
|
170
171
|
extraCommands: [string, string][];
|
|
172
|
+
/**
|
|
173
|
+
* Cascading argument wizard, active after a structured slash command is
|
|
174
|
+
* chosen (e.g. /apikey → pick provider → paste key). Resolved step-by-step
|
|
175
|
+
* via the wizardStep callback the chat loop wires up.
|
|
176
|
+
*/
|
|
177
|
+
private wizard;
|
|
178
|
+
/** Next-step resolver for the argument wizard (set by the chat loop with runtime context). */
|
|
179
|
+
wizardStep: ((command: string, prior: string[]) => WizardStep | null) | null;
|
|
171
180
|
private keypressHandler;
|
|
172
181
|
private resizeHandler;
|
|
173
182
|
constructor(opts?: LoomOpts);
|
|
@@ -202,6 +211,17 @@ export declare class LoomUI {
|
|
|
202
211
|
private onKey;
|
|
203
212
|
private handleSigint;
|
|
204
213
|
private flashHint;
|
|
214
|
+
/** Submit a turn: clear input, record history, resolve the pending read. */
|
|
215
|
+
private submitText;
|
|
216
|
+
/** Choices for the current wizard step, filtered by what the user has typed. */
|
|
217
|
+
private wizardFiltered;
|
|
218
|
+
/** Open the wizard for `command` (e.g. "/model"). Returns false if it has none. */
|
|
219
|
+
private startWizard;
|
|
220
|
+
/** Accept a value for the current step; advance to the next or submit. */
|
|
221
|
+
private wizardCommit;
|
|
222
|
+
/** Step back one level (or close the wizard when at the first level). */
|
|
223
|
+
private wizardBack;
|
|
224
|
+
private handleWizardKey;
|
|
205
225
|
private paletteMatches;
|
|
206
226
|
private cols;
|
|
207
227
|
private rows;
|
package/dist/cli/loom.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loom.d.ts","sourceRoot":"","sources":["../../src/cli/loom.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;
|
|
1
|
+
{"version":3,"file":"loom.d.ts","sourceRoot":"","sources":["../../src/cli/loom.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/loom.js
CHANGED
|
@@ -74,6 +74,7 @@ const readline = __importStar(require("readline"));
|
|
|
74
74
|
const chalk_1 = __importDefault(require("chalk"));
|
|
75
75
|
const theme_1 = require("../core/theme");
|
|
76
76
|
const tui_1 = require("./tui");
|
|
77
|
+
const command_args_1 = require("./command_args");
|
|
77
78
|
/* ════════════════════════════════════════
|
|
78
79
|
ANSI-aware string helpers (pure, tested)
|
|
79
80
|
════════════════════════════════════════ */
|
|
@@ -417,6 +418,14 @@ class LoomUI {
|
|
|
417
418
|
this.modeBadge = "";
|
|
418
419
|
/** User-defined slash commands shown in the palette ([name, description]). */
|
|
419
420
|
this.extraCommands = [];
|
|
421
|
+
/**
|
|
422
|
+
* Cascading argument wizard, active after a structured slash command is
|
|
423
|
+
* chosen (e.g. /apikey → pick provider → paste key). Resolved step-by-step
|
|
424
|
+
* via the wizardStep callback the chat loop wires up.
|
|
425
|
+
*/
|
|
426
|
+
this.wizard = null;
|
|
427
|
+
/** Next-step resolver for the argument wizard (set by the chat loop with runtime context). */
|
|
428
|
+
this.wizardStep = null;
|
|
420
429
|
this.keypressHandler = null;
|
|
421
430
|
this.resizeHandler = null;
|
|
422
431
|
/* ── streaming ── */
|
|
@@ -651,6 +660,11 @@ class LoomUI {
|
|
|
651
660
|
this.handleSigint();
|
|
652
661
|
return;
|
|
653
662
|
}
|
|
663
|
+
// The argument wizard owns all keys while it is open.
|
|
664
|
+
if (this.wizard && !this.busy) {
|
|
665
|
+
this.handleWizardKey(str, key);
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
654
668
|
if (name === "pageup") {
|
|
655
669
|
this.scrollOff += Math.max(1, this.bodyH() - 2);
|
|
656
670
|
this.clampScroll();
|
|
@@ -668,15 +682,15 @@ class LoomUI {
|
|
|
668
682
|
return; // a reply is being woven; ignore submit
|
|
669
683
|
let text = this.inputGlyphs.join("").trim();
|
|
670
684
|
// Palette open: Enter runs the ↑↓-highlighted command (Claude Code
|
|
671
|
-
// style).
|
|
672
|
-
//
|
|
685
|
+
// style). A command with a guided argument wizard opens the wizard;
|
|
686
|
+
// otherwise an argument-taking command fills the input to wait for input.
|
|
673
687
|
const matches = this.paletteMatches();
|
|
674
688
|
if (matches.length > 0 && text.startsWith("/")) {
|
|
675
689
|
const [cmd] = matches[Math.max(0, Math.min(this.paletteIdx, matches.length - 1))];
|
|
690
|
+
if (this.startWizard(cmd.trim()))
|
|
691
|
+
return;
|
|
676
692
|
if (cmd.endsWith(" ")) {
|
|
677
|
-
// argument-taking command: fill the input and wait
|
|
678
|
-
// (the palette closes once the line contains a space; a second
|
|
679
|
-
// Enter then submits as typed)
|
|
693
|
+
// argument-taking command without a wizard: fill the input and wait.
|
|
680
694
|
this.inputGlyphs = [...cmd];
|
|
681
695
|
this.cursor = this.inputGlyphs.length;
|
|
682
696
|
this.paletteIdx = 0;
|
|
@@ -685,21 +699,7 @@ class LoomUI {
|
|
|
685
699
|
}
|
|
686
700
|
text = cmd.trimEnd();
|
|
687
701
|
}
|
|
688
|
-
this.
|
|
689
|
-
this.cursor = 0;
|
|
690
|
-
this.histIdx = -1;
|
|
691
|
-
this.paletteIdx = 0;
|
|
692
|
-
this.scrollOff = 0; // submitting a turn snaps back to the tail to watch the reply
|
|
693
|
-
if (text) {
|
|
694
|
-
this.history.unshift(text);
|
|
695
|
-
if (this.history.length > 200)
|
|
696
|
-
this.history.pop();
|
|
697
|
-
}
|
|
698
|
-
const r = this.pendingResolve;
|
|
699
|
-
this.pendingResolve = null;
|
|
700
|
-
this.paint();
|
|
701
|
-
if (r)
|
|
702
|
-
r(text);
|
|
702
|
+
this.submitText(text);
|
|
703
703
|
return;
|
|
704
704
|
}
|
|
705
705
|
const paletteOpen = this.paletteMatches().length > 0 && this.inputGlyphs[0] === "/";
|
|
@@ -739,6 +739,8 @@ class LoomUI {
|
|
|
739
739
|
const m = this.paletteMatches();
|
|
740
740
|
const pick = m[Math.min(this.paletteIdx, m.length - 1)];
|
|
741
741
|
if (pick) {
|
|
742
|
+
if (this.startWizard(pick[0].trim()))
|
|
743
|
+
return;
|
|
742
744
|
this.inputGlyphs = [...pick[0].trimEnd()];
|
|
743
745
|
this.cursor = this.inputGlyphs.length;
|
|
744
746
|
}
|
|
@@ -851,6 +853,134 @@ class LoomUI {
|
|
|
851
853
|
this.sigintAt = now;
|
|
852
854
|
this.flash("再按一次 Ctrl-C 退出");
|
|
853
855
|
}
|
|
856
|
+
/* ── argument wizard ── */
|
|
857
|
+
/** Submit a turn: clear input, record history, resolve the pending read. */
|
|
858
|
+
submitText(text) {
|
|
859
|
+
this.wizard = null;
|
|
860
|
+
this.inputGlyphs = [];
|
|
861
|
+
this.cursor = 0;
|
|
862
|
+
this.histIdx = -1;
|
|
863
|
+
this.paletteIdx = 0;
|
|
864
|
+
this.scrollOff = 0; // submitting a turn snaps back to the tail to watch the reply
|
|
865
|
+
if (text) {
|
|
866
|
+
this.history.unshift(text);
|
|
867
|
+
if (this.history.length > 200)
|
|
868
|
+
this.history.pop();
|
|
869
|
+
}
|
|
870
|
+
const r = this.pendingResolve;
|
|
871
|
+
this.pendingResolve = null;
|
|
872
|
+
this.paint();
|
|
873
|
+
if (r)
|
|
874
|
+
r(text);
|
|
875
|
+
}
|
|
876
|
+
/** Choices for the current wizard step, filtered by what the user has typed. */
|
|
877
|
+
wizardFiltered() {
|
|
878
|
+
if (!this.wizard || this.wizard.step.kind !== "choice")
|
|
879
|
+
return [];
|
|
880
|
+
return (0, command_args_1.filterChoices)(this.wizard.step.choices, this.wizard.typed);
|
|
881
|
+
}
|
|
882
|
+
/** Open the wizard for `command` (e.g. "/model"). Returns false if it has none. */
|
|
883
|
+
startWizard(command) {
|
|
884
|
+
if (!this.wizardStep || !(0, command_args_1.hasWizard)(command))
|
|
885
|
+
return false;
|
|
886
|
+
const step = this.wizardStep(command, []);
|
|
887
|
+
if (!step)
|
|
888
|
+
return false;
|
|
889
|
+
this.wizard = { command, values: [], step, typed: "", idx: 0 };
|
|
890
|
+
this.inputGlyphs = [];
|
|
891
|
+
this.cursor = 0;
|
|
892
|
+
this.paletteIdx = 0;
|
|
893
|
+
this.paint();
|
|
894
|
+
return true;
|
|
895
|
+
}
|
|
896
|
+
/** Accept a value for the current step; advance to the next or submit. */
|
|
897
|
+
wizardCommit(value) {
|
|
898
|
+
const w = this.wizard;
|
|
899
|
+
const values = [...w.values, value];
|
|
900
|
+
const next = this.wizardStep ? this.wizardStep(w.command, values) : null;
|
|
901
|
+
if (next) {
|
|
902
|
+
this.wizard = { command: w.command, values, step: next, typed: "", idx: 0 };
|
|
903
|
+
this.paint();
|
|
904
|
+
}
|
|
905
|
+
else {
|
|
906
|
+
this.submitText((0, command_args_1.buildCommandLine)(w.command, values));
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
/** Step back one level (or close the wizard when at the first level). */
|
|
910
|
+
wizardBack() {
|
|
911
|
+
const w = this.wizard;
|
|
912
|
+
if (w.values.length === 0 || !this.wizardStep) {
|
|
913
|
+
this.wizard = null;
|
|
914
|
+
this.paint();
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
const values = w.values.slice(0, -1);
|
|
918
|
+
const step = this.wizardStep(w.command, values);
|
|
919
|
+
if (!step) {
|
|
920
|
+
this.wizard = null;
|
|
921
|
+
this.paint();
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
this.wizard = { command: w.command, values, step, typed: "", idx: 0 };
|
|
925
|
+
this.paint();
|
|
926
|
+
}
|
|
927
|
+
handleWizardKey(str, key) {
|
|
928
|
+
const w = this.wizard;
|
|
929
|
+
const name = key?.name;
|
|
930
|
+
if (name === "escape") {
|
|
931
|
+
this.wizard = null;
|
|
932
|
+
this.paint();
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
if (name === "up") {
|
|
936
|
+
if (w.step.kind === "choice")
|
|
937
|
+
w.idx = Math.max(0, w.idx - 1);
|
|
938
|
+
this.paint();
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
if (name === "down") {
|
|
942
|
+
if (w.step.kind === "choice")
|
|
943
|
+
w.idx = Math.min(Math.max(0, this.wizardFiltered().length - 1), w.idx + 1);
|
|
944
|
+
this.paint();
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
if (name === "return" || name === "tab") {
|
|
948
|
+
if (w.step.kind === "choice") {
|
|
949
|
+
const filtered = this.wizardFiltered();
|
|
950
|
+
const pick = filtered[Math.min(w.idx, filtered.length - 1)];
|
|
951
|
+
if (pick) {
|
|
952
|
+
this.wizardCommit(pick.value);
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
if (w.step.allowFreeform && w.typed.trim()) {
|
|
956
|
+
this.wizardCommit(w.typed.trim());
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
if (w.typed.trim())
|
|
962
|
+
this.wizardCommit(w.typed.trim());
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
if (name === "backspace") {
|
|
966
|
+
if (w.typed.length > 0) {
|
|
967
|
+
w.typed = w.typed.slice(0, -1);
|
|
968
|
+
w.idx = 0;
|
|
969
|
+
this.paint();
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
this.wizardBack();
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
if (str && !key?.ctrl && !key?.meta) {
|
|
976
|
+
const glyphs = [...str].filter((c) => c >= " " || (0, tui_1.charWidth)(c.codePointAt(0)) > 0);
|
|
977
|
+
if (glyphs.length) {
|
|
978
|
+
w.typed += glyphs.join("");
|
|
979
|
+
w.idx = 0;
|
|
980
|
+
this.paint();
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
}
|
|
854
984
|
paletteMatches() {
|
|
855
985
|
const l = this.inputGlyphs.join("");
|
|
856
986
|
if (!l.startsWith("/") || l.includes(" "))
|
|
@@ -1047,6 +1177,14 @@ class LoomUI {
|
|
|
1047
1177
|
content = " " + chalk_1.default.yellow("⚠ ") + cutVisual(this.modal.text, innerW - 14) + chalk_1.default.bold(" 允许? ") + chalk_1.default.dim("[y/N]");
|
|
1048
1178
|
cursorPos = { row: rows - 2, col: Math.min(innerW, (0, tui_1.visualWidth)(content) + 1) };
|
|
1049
1179
|
}
|
|
1180
|
+
else if (this.wizard) {
|
|
1181
|
+
const w = this.wizard;
|
|
1182
|
+
const crumb = [w.command.replace(/^\//, ""), ...w.values].join(" ");
|
|
1183
|
+
const shownTyped = w.step.secret ? "•".repeat([...w.typed].length) : w.typed;
|
|
1184
|
+
const head = chalk_1.default.hex(t.hex)(` ${t.symbol} `) + chalk_1.default.hex(theme_1.PALETTE.inkLight)(crumb + " ▸ ");
|
|
1185
|
+
content = head + cutVisual(shownTyped, innerW - (0, tui_1.visualWidth)(head) - 2);
|
|
1186
|
+
cursorPos = { row: rows - 2, col: Math.min(innerW, (0, tui_1.visualWidth)(content) + 1) };
|
|
1187
|
+
}
|
|
1050
1188
|
else {
|
|
1051
1189
|
const promptStr = chalk_1.default.hex(t.hex)(` ${t.symbol} `) + chalk_1.default.hex(theme_1.PALETTE.inkLight)("❯ ");
|
|
1052
1190
|
const promptW = (0, tui_1.visualWidth)(promptStr);
|
|
@@ -1079,16 +1217,56 @@ class LoomUI {
|
|
|
1079
1217
|
const paletteUp = this.paletteMatches().length > 0 && this.inputGlyphs[0] === "/";
|
|
1080
1218
|
const hint = this.busy
|
|
1081
1219
|
? " Ctrl-C 中断本轮 "
|
|
1082
|
-
:
|
|
1083
|
-
?
|
|
1084
|
-
|
|
1220
|
+
: this.wizard
|
|
1221
|
+
? (this.wizard.step.kind === "choice"
|
|
1222
|
+
? " ↑↓ 选择 · Enter 确认 · 输入筛选 · ⌫ 返回 · Esc 取消 "
|
|
1223
|
+
: " 输入后 Enter 确认 · ⌫ 返回上一步 · Esc 取消 ")
|
|
1224
|
+
: paletteUp
|
|
1225
|
+
? " ↑↓ 选命令 · Enter 执行 · Tab 补全 · Esc 收起 "
|
|
1226
|
+
: " / 命令 · 滚轮/PgUp 回看 · Shift+Tab 切模式 · Ctrl-C 退出 ";
|
|
1085
1227
|
// └─ hint ───…┘ → 2 + w(hint) + fill + 1 = cols
|
|
1086
1228
|
const fill = innerW - (0, tui_1.visualWidth)(hint) - 1;
|
|
1087
1229
|
frame.push(B("└─") + chalk_1.default.dim(hint) + B("─".repeat(Math.max(0, fill)) + "┘"));
|
|
1088
1230
|
}
|
|
1231
|
+
// ── argument wizard: overlay the title + selectable choices ──
|
|
1232
|
+
if (this.wizard && !this.modal) {
|
|
1233
|
+
const w = this.wizard;
|
|
1234
|
+
const overlayRow = (row, s) => {
|
|
1235
|
+
if (row < 1 + SKY_H || row >= 1 + SKY_H + bodyH)
|
|
1236
|
+
return;
|
|
1237
|
+
frame[row] = B("│") + padAnsi(rail[row - 1 - SKY_H] ?? "", RAIL_W) + B("│") + " " + padAnsi(s, this.viewW()) + B("│");
|
|
1238
|
+
};
|
|
1239
|
+
const lines = [];
|
|
1240
|
+
lines.push(chalk_1.default.dim(" " + cutVisual(w.step.title, this.viewW() - 4)));
|
|
1241
|
+
if (w.step.kind === "choice") {
|
|
1242
|
+
const filtered = this.wizardFiltered();
|
|
1243
|
+
const bodyRows = Math.min(7, bodyH - 1);
|
|
1244
|
+
if (!filtered.length) {
|
|
1245
|
+
lines.push(" " + chalk_1.default.dim(w.step.allowFreeform ? `直接输入,回车确认` : `无匹配项`));
|
|
1246
|
+
}
|
|
1247
|
+
else {
|
|
1248
|
+
w.idx = Math.max(0, Math.min(w.idx, filtered.length - 1));
|
|
1249
|
+
const start = Math.max(0, Math.min(w.idx - bodyRows + 1, filtered.length - bodyRows));
|
|
1250
|
+
filtered.slice(start, start + bodyRows).forEach((c, i) => {
|
|
1251
|
+
const sel = start + i === w.idx;
|
|
1252
|
+
const mark = sel ? chalk_1.default.hex(t.hex)(" ▸ ") : " ";
|
|
1253
|
+
const group = c.group ? chalk_1.default.dim(`${c.group}/`) : "";
|
|
1254
|
+
const label = sel ? chalk_1.default.bold.hex(t.hex)(c.label) : chalk_1.default.hex(theme_1.PALETTE.inkLight)(c.label);
|
|
1255
|
+
const hint = c.hint ? chalk_1.default.dim(" " + c.hint) : "";
|
|
1256
|
+
const counter = sel && filtered.length > bodyRows ? chalk_1.default.dim(` ${w.idx + 1}/${filtered.length}`) : "";
|
|
1257
|
+
lines.push(mark + group + cutVisual(label + hint, this.viewW() - 8) + counter);
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
else {
|
|
1262
|
+
lines.push(" " + chalk_1.default.dim(w.step.placeholder || "输入后回车确认"));
|
|
1263
|
+
}
|
|
1264
|
+
const baseRow = 1 + SKY_H + bodyH - lines.length;
|
|
1265
|
+
lines.forEach((s, i) => overlayRow(baseRow + i, s));
|
|
1266
|
+
}
|
|
1089
1267
|
// ── slash palette: overlay onto the rows just above the divider ──
|
|
1090
1268
|
const matches = this.paletteMatches();
|
|
1091
|
-
if (matches.length > 0 && this.inputGlyphs[0] === "/" && !this.modal) {
|
|
1269
|
+
if (!this.wizard && matches.length > 0 && this.inputGlyphs[0] === "/" && !this.modal) {
|
|
1092
1270
|
const maxShow = Math.min(8, bodyH - 1);
|
|
1093
1271
|
this.paletteIdx = Math.max(0, Math.min(this.paletteIdx, matches.length - 1));
|
|
1094
1272
|
// scroll window that keeps the ↑↓ selection visible
|