toolcraft 0.0.162 → 0.0.164

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/composition.json CHANGED
@@ -113,7 +113,7 @@
113
113
  },
114
114
  {
115
115
  "name": "toolcraft",
116
- "version": "0.0.162",
116
+ "version": "0.0.164",
117
117
  "license": "MIT"
118
118
  },
119
119
  {
@@ -123,7 +123,7 @@
123
123
  },
124
124
  {
125
125
  "name": "toolcraft-schema",
126
- "version": "0.0.162",
126
+ "version": "0.0.164",
127
127
  "license": "MIT"
128
128
  },
129
129
  {
@@ -113,7 +113,7 @@
113
113
  },
114
114
  {
115
115
  "name": "toolcraft",
116
- "version": "0.0.162",
116
+ "version": "0.0.164",
117
117
  "license": "MIT"
118
118
  },
119
119
  {
@@ -123,7 +123,7 @@
123
123
  },
124
124
  {
125
125
  "name": "toolcraft-schema",
126
- "version": "0.0.162",
126
+ "version": "0.0.164",
127
127
  "license": "MIT"
128
128
  },
129
129
  {
@@ -8,7 +8,7 @@
8
8
  },
9
9
  {
10
10
  "name": "toolcraft-schema",
11
- "version": "0.0.162",
11
+ "version": "0.0.164",
12
12
  "license": "MIT"
13
13
  }
14
14
  ]
@@ -14,6 +14,7 @@ export type Effect = {
14
14
  resumeWith: (value: unknown) => ExplorerEvent;
15
15
  } | {
16
16
  type: "persistOrder";
17
+ movedId: string;
17
18
  orderedIds: string[];
18
19
  };
19
20
  export type ExplorerEvent = {
@@ -718,7 +718,7 @@ function reorder(state, delta) {
718
718
  };
719
719
  return {
720
720
  state: next,
721
- effects: [{ type: "persistOrder", orderedIds: rows.map((row) => row.id) }]
721
+ effects: [{ type: "persistOrder", movedId: current.id, orderedIds: rows.map((row) => row.id) }]
722
722
  };
723
723
  }
724
724
  function paletteInput(state, key) {
@@ -157,7 +157,7 @@ class ExplorerRuntime {
157
157
  continue;
158
158
  }
159
159
  if (effect.type === "persistOrder") {
160
- this.track(this.persistOrder(effect.orderedIds, previousState.rows, ++this.reorderToken));
160
+ this.track(this.persistOrder(effect.movedId, effect.orderedIds, previousState.rows, ++this.reorderToken));
161
161
  continue;
162
162
  }
163
163
  if (effect.type === "suspend") {
@@ -248,9 +248,10 @@ class ExplorerRuntime {
248
248
  });
249
249
  this.dispatch({ type: "detailLoaded", rowId, token, items: preparedItems });
250
250
  }
251
- async persistOrder(orderedIds, previousRows, token) {
251
+ async persistOrder(movedId, orderedIds, previousRows, token) {
252
252
  try {
253
253
  await this.config.reorder?.onReorder(orderedIds, {
254
+ movedId,
254
255
  refresh: this.runtimeHandles.refresh,
255
256
  toast: this.runtimeHandles.toast
256
257
  });
@@ -102,6 +102,7 @@ export interface PaneRuntimeState {
102
102
  filter: string;
103
103
  }
104
104
  export interface ReorderContext {
105
+ movedId: string;
105
106
  refresh: () => Promise<void>;
106
107
  toast: (msg: string, tone?: Tone) => void;
107
108
  }
@@ -24,6 +24,8 @@ export interface PromptOptions<Value> {
24
24
  }
25
25
  type InputStream = NodeJS.ReadableStream & {
26
26
  isTTY?: boolean;
27
+ destroyed?: boolean;
28
+ readableEnded?: boolean;
27
29
  setRawMode?: (enabled: boolean) => void;
28
30
  unpipe?: () => void;
29
31
  };
@@ -41,7 +43,6 @@ export declare class Prompt<Value> extends EventEmitter {
41
43
  private readonly trackValue;
42
44
  private previousFrame;
43
45
  private readlineInterface?;
44
- private abortListener?;
45
46
  private closed;
46
47
  constructor(opts: PromptOptions<Value>, trackValue?: boolean);
47
48
  get cursor(): number;
@@ -52,6 +53,7 @@ export declare class Prompt<Value> extends EventEmitter {
52
53
  protected setError(message: string): void;
53
54
  protected setUserInput(value: string): void;
54
55
  protected clearUserInput(): void;
56
+ private readonly onCancel;
55
57
  private readonly onKeypress;
56
58
  private updateTrackedInput;
57
59
  protected readonly render: () => void;
@@ -1,5 +1,6 @@
1
1
  import { EventEmitter } from "node:events";
2
2
  import * as readline from "node:readline";
3
+ import { graphemes } from "../../dashboard/terminal-width.js";
3
4
  import { CANCEL } from "./cancel-symbol.js";
4
5
  import { mapKey } from "./keys.js";
5
6
  import { wrapFrame } from "./wrap.js";
@@ -50,7 +51,6 @@ export class Prompt extends EventEmitter {
50
51
  trackValue;
51
52
  previousFrame = "";
52
53
  readlineInterface;
53
- abortListener;
54
54
  closed = false;
55
55
  constructor(opts, trackValue = true) {
56
56
  super();
@@ -75,18 +75,16 @@ export class Prompt extends EventEmitter {
75
75
  if (this.input.isTTY !== true) {
76
76
  return this.promptNonTty();
77
77
  }
78
+ if (this.input.destroyed || this.input.readableEnded) {
79
+ this.state = "cancel";
80
+ return Promise.resolve(CANCEL);
81
+ }
78
82
  return new Promise((resolve) => {
79
83
  const onSubmit = (value) => resolve(value);
80
84
  const onCancel = () => resolve(CANCEL);
81
85
  this.once("submit", onSubmit);
82
86
  this.once("cancel", onCancel);
83
- this.abortListener = () => {
84
- this.state = "cancel";
85
- this.emit("finalize");
86
- this.render();
87
- this.close();
88
- };
89
- this.signal?.addEventListener("abort", this.abortListener, { once: true });
87
+ this.signal?.addEventListener("abort", this.onCancel, { once: true });
90
88
  this.readlineInterface = readline.createInterface({
91
89
  input: this.input,
92
90
  output: undefined,
@@ -95,6 +93,8 @@ export class Prompt extends EventEmitter {
95
93
  escapeCodeTimeout: 50,
96
94
  terminal: true
97
95
  });
96
+ this.readlineInterface.once("close", this.onCancel);
97
+ this.input.once("close", this.onCancel);
98
98
  readline.emitKeypressEvents(this.input, this.readlineInterface);
99
99
  this.readlineInterface.prompt();
100
100
  this.input.on("keypress", this.onKeypress);
@@ -134,6 +134,15 @@ export class Prompt extends EventEmitter {
134
134
  setUserInput(value) {
135
135
  this.userInput = value;
136
136
  this._cursor = Math.min(this._cursor, this.userInput.length);
137
+ if (this.trackValue) {
138
+ let boundary = 0;
139
+ for (const segment of graphemes(value)) {
140
+ if (boundary >= this._cursor)
141
+ break;
142
+ boundary += segment.length;
143
+ }
144
+ this._cursor = boundary;
145
+ }
137
146
  this.emit("userInput", this.userInput);
138
147
  }
139
148
  clearUserInput() {
@@ -141,7 +150,19 @@ export class Prompt extends EventEmitter {
141
150
  this._cursor = 0;
142
151
  this.emit("userInput", this.userInput);
143
152
  }
153
+ onCancel = () => {
154
+ if (this.closed || this.state === "submit" || this.state === "cancel") {
155
+ return;
156
+ }
157
+ this.state = "cancel";
158
+ this.emit("finalize");
159
+ this.render();
160
+ this.close();
161
+ };
144
162
  onKeypress = (char, key = {}) => {
163
+ if (this.closed) {
164
+ return;
165
+ }
145
166
  let action = mapKey(key.name, char);
146
167
  if (this.trackValue && char && char >= " " && key.name !== "return" && key.name !== "enter" && key.name !== "escape") {
147
168
  action = undefined;
@@ -187,6 +208,14 @@ export class Prompt extends EventEmitter {
187
208
  }
188
209
  };
189
210
  updateTrackedInput(char, key, action) {
211
+ if (key.name === "home") {
212
+ this._cursor = 0;
213
+ return;
214
+ }
215
+ if (key.name === "end") {
216
+ this._cursor = this.userInput.length;
217
+ return;
218
+ }
190
219
  if (key.ctrl) {
191
220
  if (key.name === "a") {
192
221
  this._cursor = 0;
@@ -197,23 +226,24 @@ export class Prompt extends EventEmitter {
197
226
  return;
198
227
  }
199
228
  if (key.name === "u") {
200
- this.userInput = this.userInput.slice(this._cursor);
229
+ const remaining = this.userInput.slice(this._cursor);
201
230
  this._cursor = 0;
202
- this.emit("userInput", this.userInput);
231
+ this.setUserInput(remaining);
203
232
  return;
204
233
  }
205
234
  if (key.name === "k") {
206
- this.userInput = this.userInput.slice(0, this._cursor);
207
- this.emit("userInput", this.userInput);
235
+ this.setUserInput(this.userInput.slice(0, this._cursor));
208
236
  return;
209
237
  }
210
238
  }
239
+ const before = this.userInput.slice(0, this._cursor);
240
+ const after = this.userInput.slice(this._cursor);
211
241
  if (action === "left") {
212
- this._cursor = Math.max(0, this._cursor - 1);
242
+ this._cursor -= graphemes(before).at(-1)?.length ?? 0;
213
243
  return;
214
244
  }
215
245
  if (action === "right") {
216
- this._cursor = Math.min(this.userInput.length, this._cursor + 1);
246
+ this._cursor += graphemes(after)[0]?.length ?? 0;
217
247
  return;
218
248
  }
219
249
  if (action === "cancel" || action === "up" || action === "down" || action === "space") {
@@ -221,25 +251,22 @@ export class Prompt extends EventEmitter {
221
251
  }
222
252
  if (key.name === "backspace" || char === "\b" || char === "\x7f") {
223
253
  if (this._cursor > 0) {
224
- this.userInput = `${this.userInput.slice(0, this._cursor - 1)}${this.userInput.slice(this._cursor)}`;
225
- this._cursor -= 1;
226
- this.emit("userInput", this.userInput);
254
+ this._cursor -= graphemes(before).at(-1)?.length ?? 0;
255
+ this.setUserInput(`${before.slice(0, this._cursor)}${after}`);
227
256
  }
228
257
  return;
229
258
  }
230
259
  if (key.name === "delete") {
231
260
  if (this._cursor < this.userInput.length) {
232
- this.userInput = `${this.userInput.slice(0, this._cursor)}${this.userInput.slice(this._cursor + 1)}`;
233
- this.emit("userInput", this.userInput);
261
+ this.setUserInput(`${before}${after.slice(graphemes(after)[0]?.length ?? 0)}`);
234
262
  }
235
263
  return;
236
264
  }
237
265
  if (!char || char < " " || key.ctrl) {
238
266
  return;
239
267
  }
240
- this.userInput = `${this.userInput.slice(0, this._cursor)}${char}${this.userInput.slice(this._cursor)}`;
241
268
  this._cursor += char.length;
242
- this.emit("userInput", this.userInput);
269
+ this.setUserInput(`${before}${char}${after}`);
243
270
  }
244
271
  render = () => {
245
272
  if (this.closed) {
@@ -267,10 +294,10 @@ export class Prompt extends EventEmitter {
267
294
  }
268
295
  this.closed = true;
269
296
  this.input.removeListener("keypress", this.onKeypress);
297
+ this.input.removeListener("close", this.onCancel);
270
298
  this.output.removeListener("resize", this.render);
271
- if (this.abortListener) {
272
- this.signal?.removeEventListener("abort", this.abortListener);
273
- }
299
+ this.signal?.removeEventListener("abort", this.onCancel);
300
+ this.readlineInterface?.removeListener("close", this.onCancel);
274
301
  this.output.write(`${cursor.show}\n`);
275
302
  if (!process.platform.startsWith("win") && this.input.setRawMode) {
276
303
  this.input.setRawMode(false);
@@ -2,6 +2,7 @@ import { color } from "../../components/color.js";
2
2
  import { GLYPHS, symbol } from "./glyphs.js";
3
3
  import { Prompt } from "./core.js";
4
4
  import { limitOptions } from "./pagination.js";
5
+ import { wrapTextWithPrefix } from "./wrap.js";
5
6
  import { findNonDisabled } from "./select.js";
6
7
  class MultiselectPrompt extends Prompt {
7
8
  options;
@@ -111,7 +112,7 @@ function renderMultiselectPrompt(prompt, opts) {
111
112
  .map((option) => prompt.state === "submit" ? color.dim(option.label) : color.dim.strikethrough(option.label))
112
113
  .join(", ");
113
114
  const end = prompt.state === "submit" ? color.green(GLYPHS.barEnd) : color.red(GLYPHS.barEnd);
114
- return `${color.gray(GLYPHS.barStart)} ${symbol(prompt.state)} ${opts.message}\n${color.gray(GLYPHS.bar)} ${labels}\n${end}`;
115
+ return `${color.gray(GLYPHS.barStart)} ${symbol(prompt.state)} ${opts.message}\n${wrapTextWithPrefix(opts.output ?? process.stdout, labels, `${color.gray(GLYPHS.bar)} `)}\n${end}`;
115
116
  }
116
117
  const lines = limitOptions({
117
118
  cursor: prompt.cursor,
@@ -120,7 +121,7 @@ function renderMultiselectPrompt(prompt, opts) {
120
121
  maxItems: opts.maxItems,
121
122
  columnPadding: 3,
122
123
  style: (option, active) => renderOption(option, prompt.value, active, false, false)
123
- }).map((line) => `${prompt.state === "error" ? color.yellow(GLYPHS.bar) : color.cyan(GLYPHS.bar)} ${line}`);
124
+ }).flatMap((line) => line.split("\n").map((physicalLine) => `${prompt.state === "error" ? color.yellow(GLYPHS.bar) : color.cyan(GLYPHS.bar)} ${physicalLine}`));
124
125
  const body = [`${color.gray(GLYPHS.barStart)} ${symbol(prompt.state)} ${opts.message}`, ...lines];
125
126
  if (prompt.state === "error") {
126
127
  body.push(`${color.yellow(GLYPHS.barEnd)} ${color.yellow(prompt.error)}`);
@@ -5,24 +5,6 @@ import { wrapAnsi } from "fast-wrap-ansi";
5
5
  function countLines(values) {
6
6
  return values.reduce((sum, value) => sum + value.split("\n").length, 0);
7
7
  }
8
- function trimToRows(values, cursorOffset, rows, hasTop, hasBottom) {
9
- const output = [...values];
10
- while (countLines(output) > rows && output.length > 1) {
11
- const removeFromTop = hasTop && cursorOffset > 0;
12
- const removeFromBottom = hasBottom && cursorOffset < output.length - 1;
13
- if (removeFromTop) {
14
- output.shift();
15
- cursorOffset -= 1;
16
- }
17
- else if (removeFromBottom) {
18
- output.pop();
19
- }
20
- else {
21
- output.pop();
22
- }
23
- }
24
- return output;
25
- }
26
8
  export function limitOptions(opts) {
27
9
  const { cursor, options, style, output, maxItems = Number.POSITIVE_INFINITY, columnPadding = 0, rowPadding = 4 } = opts;
28
10
  if (options.length === 0) {
@@ -36,17 +18,29 @@ export function limitOptions(opts) {
36
18
  if (cursor >= cappedVisibleCount - 3) {
37
19
  start = Math.max(Math.min(cursor - cappedVisibleCount + 3, options.length - cappedVisibleCount), 0);
38
20
  }
39
- const hasTopMarker = cappedVisibleCount < options.length && start > 0;
40
- const hasBottomMarker = cappedVisibleCount < options.length && start + cappedVisibleCount < options.length;
21
+ let end = start + cappedVisibleCount;
41
22
  const visible = options
42
- .slice(start, start + cappedVisibleCount)
23
+ .slice(start, end)
43
24
  .map((option, index) => wrapAnsi(style(option, start + index === cursor), columns, { hard: true, trim: false }));
44
- const trimmed = trimToRows(visible, Math.max(cursor - start, 0), Math.max(rowBudget - Number(hasTopMarker) - Number(hasBottomMarker), 1), hasTopMarker, hasBottomMarker);
45
- if (hasTopMarker) {
46
- trimmed.unshift(color.dim(GLYPHS.ellipsis));
25
+ const marker = wrapAnsi(color.dim(GLYPHS.ellipsis), columns, { hard: true, trim: false });
26
+ const markerRows = marker.split("\n").length;
27
+ while (visible.length > 1 && countLines(visible) + markerRows * (Number(start > 0) + Number(end < options.length)) > rowBudget) {
28
+ if (start < cursor) {
29
+ visible.shift();
30
+ start += 1;
31
+ }
32
+ else {
33
+ visible.pop();
34
+ end -= 1;
35
+ }
36
+ }
37
+ let remainingRows = rowBudget - countLines(visible);
38
+ if (start > 0 && remainingRows >= markerRows) {
39
+ visible.unshift(marker);
40
+ remainingRows -= markerRows;
47
41
  }
48
- if (hasBottomMarker) {
49
- trimmed.push(color.dim(GLYPHS.ellipsis));
42
+ if (end < options.length && remainingRows >= markerRows) {
43
+ visible.push(marker);
50
44
  }
51
- return trimmed;
45
+ return visible;
52
46
  }
@@ -1,6 +1,8 @@
1
1
  import { color } from "../../components/color.js";
2
+ import { graphemes } from "../../dashboard/terminal-width.js";
2
3
  import { GLYPHS, symbol, symbolBar } from "./glyphs.js";
3
4
  import { Prompt } from "./core.js";
5
+ import { wrapTextWithPrefix } from "./wrap.js";
4
6
  class PasswordPrompt extends Prompt {
5
7
  mask;
6
8
  constructor(opts) {
@@ -15,16 +17,17 @@ class PasswordPrompt extends Prompt {
15
17
  this.on("userInput", (value) => this.setValue(value));
16
18
  }
17
19
  get masked() {
18
- return this.mask.repeat(this.userInput.length);
20
+ return this.mask.repeat(graphemes(this.userInput).length);
19
21
  }
20
22
  get userInputWithCursor() {
21
23
  if (this.state === "submit") {
22
24
  return this.masked;
23
25
  }
24
26
  const masked = this.masked;
25
- const before = masked.slice(0, this.cursor);
26
- const current = masked[this.cursor];
27
- const after = masked.slice(this.cursor + 1);
27
+ const maskCursor = graphemes(this.userInput.slice(0, this.cursor)).length * this.mask.length;
28
+ const before = masked.slice(0, maskCursor);
29
+ const current = masked.slice(maskCursor, maskCursor + this.mask.length);
30
+ const after = masked.slice(maskCursor + this.mask.length);
28
31
  if (current) {
29
32
  return `${before}${color.inverse(current)}${after}`;
30
33
  }
@@ -40,15 +43,15 @@ function renderHeader(prompt, message) {
40
43
  function renderPasswordPrompt(prompt, opts) {
41
44
  const value = prompt.masked;
42
45
  if (prompt.state === "submit") {
43
- return `${renderHeader(prompt, opts.message)}\n${color.gray(GLYPHS.bar)} ${color.dim(value)}\n${color.green(GLYPHS.barEnd)}`;
46
+ return `${renderHeader(prompt, opts.message)}\n${wrapTextWithPrefix(opts.output ?? process.stdout, color.dim(value), `${color.gray(GLYPHS.bar)} `)}\n${color.green(GLYPHS.barEnd)}`;
44
47
  }
45
48
  if (prompt.state === "cancel") {
46
- return `${renderHeader(prompt, opts.message)}\n${color.gray(GLYPHS.bar)} ${color.dim.strikethrough(value)}\n${color.red(GLYPHS.barEnd)}`;
49
+ return `${renderHeader(prompt, opts.message)}\n${wrapTextWithPrefix(opts.output ?? process.stdout, color.dim.strikethrough(value), `${color.gray(GLYPHS.bar)} `)}\n${color.red(GLYPHS.barEnd)}`;
47
50
  }
48
51
  if (prompt.state === "error") {
49
- return `${renderHeader(prompt, opts.message)}\n${symbolBar(prompt.state)} ${prompt.userInputWithCursor}\n${color.yellow(GLYPHS.barEnd)} ${color.yellow(prompt.error)}`;
52
+ return `${renderHeader(prompt, opts.message)}\n${wrapTextWithPrefix(opts.output ?? process.stdout, prompt.userInputWithCursor, `${symbolBar(prompt.state)} `)}\n${color.yellow(GLYPHS.barEnd)} ${color.yellow(prompt.error)}`;
50
53
  }
51
- return `${renderHeader(prompt, opts.message)}\n${symbolBar(prompt.state)} ${prompt.userInputWithCursor}\n${color.cyan(GLYPHS.barEnd)}`;
54
+ return `${renderHeader(prompt, opts.message)}\n${wrapTextWithPrefix(opts.output ?? process.stdout, prompt.userInputWithCursor, `${symbolBar(prompt.state)} `)}\n${color.cyan(GLYPHS.barEnd)}`;
52
55
  }
53
56
  export function passwordPrompt(opts) {
54
57
  return new PasswordPrompt(opts).prompt();
@@ -2,6 +2,7 @@ import { color } from "../../components/color.js";
2
2
  import { GLYPHS, symbol } from "./glyphs.js";
3
3
  import { Prompt } from "./core.js";
4
4
  import { limitOptions } from "./pagination.js";
5
+ import { wrapTextWithPrefix } from "./wrap.js";
5
6
  class SelectPrompt extends Prompt {
6
7
  options;
7
8
  constructor(opts) {
@@ -72,7 +73,7 @@ function renderSelectPrompt(prompt, opts) {
72
73
  const option = prompt.visibleOptions[prompt.cursor];
73
74
  const rendered = option ? renderOption(option, false, prompt.state === "submit", prompt.state === "cancel") : "";
74
75
  const end = prompt.state === "submit" ? color.green(GLYPHS.barEnd) : color.red(GLYPHS.barEnd);
75
- return `${color.gray(GLYPHS.barStart)} ${symbol(prompt.state)} ${opts.message}\n${color.gray(GLYPHS.bar)} ${rendered}\n${end}`;
76
+ return `${color.gray(GLYPHS.barStart)} ${symbol(prompt.state)} ${opts.message}\n${wrapTextWithPrefix(opts.output ?? process.stdout, rendered, `${color.gray(GLYPHS.bar)} `)}\n${end}`;
76
77
  }
77
78
  const lines = limitOptions({
78
79
  cursor: prompt.cursor,
@@ -81,7 +82,7 @@ function renderSelectPrompt(prompt, opts) {
81
82
  maxItems: opts.maxItems,
82
83
  columnPadding: 3,
83
84
  style: (option, active) => renderOption(option, active, false, false)
84
- }).map((line) => `${color.cyan(GLYPHS.bar)} ${line}`);
85
+ }).flatMap((line) => line.split("\n").map((physicalLine) => `${color.cyan(GLYPHS.bar)} ${physicalLine}`));
85
86
  return `${color.gray(GLYPHS.barStart)} ${symbol(prompt.state)} ${opts.message}\n${lines.join("\n")}\n${color.cyan(GLYPHS.barEnd)}`;
86
87
  }
87
88
  export function selectPrompt(opts) {
@@ -1,6 +1,8 @@
1
1
  import { color } from "../../components/color.js";
2
+ import { graphemes } from "../../dashboard/terminal-width.js";
2
3
  import { GLYPHS, symbol, symbolBar } from "./glyphs.js";
3
4
  import { Prompt } from "./core.js";
5
+ import { wrapTextWithPrefix } from "./wrap.js";
4
6
  class TextPrompt extends Prompt {
5
7
  constructor(opts) {
6
8
  const initialUserInput = opts.initialValue ?? "";
@@ -9,7 +11,7 @@ class TextPrompt extends Prompt {
9
11
  initialValue: initialUserInput,
10
12
  initialUserInput,
11
13
  render: (prompt) => renderTextPrompt(prompt, opts),
12
- validate: opts.validate
14
+ validate: (value) => opts.validate?.(value || opts.defaultValue || "")
13
15
  });
14
16
  this.on("userInput", (value) => this.setValue(value));
15
17
  this.on("finalize", () => {
@@ -23,8 +25,8 @@ class TextPrompt extends Prompt {
23
25
  return this.userInput;
24
26
  }
25
27
  const before = this.userInput.slice(0, this.cursor);
26
- const current = this.userInput[this.cursor];
27
- const after = this.userInput.slice(this.cursor + 1);
28
+ const current = graphemes(this.userInput.slice(this.cursor))[0];
29
+ const after = this.userInput.slice(this.cursor + (current?.length ?? 0));
28
30
  if (current) {
29
31
  return `${before}${color.inverse(current)}${after}`;
30
32
  }
@@ -40,20 +42,21 @@ function renderHeader(prompt, message) {
40
42
  function renderTextPrompt(prompt, opts) {
41
43
  const value = prompt.value ?? "";
42
44
  if (prompt.state === "submit") {
43
- return `${renderHeader(prompt, opts.message)}\n${color.gray(GLYPHS.bar)} ${color.dim(value)}\n${color.green(GLYPHS.barEnd)}`;
45
+ return `${renderHeader(prompt, opts.message)}\n${wrapTextWithPrefix(opts.output ?? process.stdout, color.dim(value), `${color.gray(GLYPHS.bar)} `)}\n${color.green(GLYPHS.barEnd)}`;
44
46
  }
45
47
  if (prompt.state === "cancel") {
46
- return `${renderHeader(prompt, opts.message)}\n${color.gray(GLYPHS.bar)} ${color.dim.strikethrough(value)}\n${color.red(GLYPHS.barEnd)}`;
48
+ return `${renderHeader(prompt, opts.message)}\n${wrapTextWithPrefix(opts.output ?? process.stdout, color.dim.strikethrough(value), `${color.gray(GLYPHS.bar)} `)}\n${color.red(GLYPHS.barEnd)}`;
47
49
  }
50
+ const [placeholder, ...placeholderRest] = graphemes(opts.placeholder ?? "");
48
51
  const input = prompt.userInput.length > 0
49
52
  ? prompt.userInputWithCursor
50
- : opts.placeholder
51
- ? `${color.inverse(opts.placeholder[0] ?? " ")}${color.dim(opts.placeholder.slice(1))}`
53
+ : placeholder
54
+ ? `${color.inverse(placeholder)}${color.dim(placeholderRest.join(""))}`
52
55
  : color.inverse("_");
53
56
  if (prompt.state === "error") {
54
- return `${renderHeader(prompt, opts.message)}\n${symbolBar(prompt.state)} ${input}\n${color.yellow(GLYPHS.barEnd)} ${color.yellow(prompt.error)}`;
57
+ return `${renderHeader(prompt, opts.message)}\n${wrapTextWithPrefix(opts.output ?? process.stdout, input, `${symbolBar(prompt.state)} `)}\n${color.yellow(GLYPHS.barEnd)} ${color.yellow(prompt.error)}`;
55
58
  }
56
- return `${renderHeader(prompt, opts.message)}\n${symbolBar(prompt.state)} ${input}\n${color.cyan(GLYPHS.barEnd)}`;
59
+ return `${renderHeader(prompt, opts.message)}\n${wrapTextWithPrefix(opts.output ?? process.stdout, input, `${symbolBar(prompt.state)} `)}\n${color.cyan(GLYPHS.barEnd)}`;
57
60
  }
58
61
  export function textPrompt(opts) {
59
62
  return new TextPrompt(opts).prompt();
@@ -145,8 +145,13 @@ function parseCsi(sequence) {
145
145
  const name = navigationName(final) ?? ({ "5": "pageup", "6": "pagedown" }[sequence.slice(2, -1)]);
146
146
  if (name === undefined)
147
147
  return undefined;
148
- const modifier = sequence.includes(";5") ? { ctrl: true } : {};
149
- return key(name, modifier);
148
+ const parameters = sequence.slice(2, -1).split(";");
149
+ const modifier = parameters.length > 1 ? Number(parameters.at(-1)) : 1;
150
+ return key(name, {
151
+ shift: modifier === 2 || modifier === 4 || modifier === 6 || modifier === 8,
152
+ alt: modifier === 3 || modifier === 4 || modifier === 7 || modifier === 8,
153
+ ctrl: modifier === 5 || modifier === 6 || modifier === 7 || modifier === 8
154
+ });
150
155
  }
151
156
  function navigationName(final) {
152
157
  return { A: "up", B: "down", C: "right", D: "left", H: "home", F: "end" }[final];
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolcraft-schema",
3
- "version": "0.0.162",
3
+ "version": "0.0.164",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolcraft",
3
- "version": "0.0.162",
3
+ "version": "0.0.164",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -158,7 +158,7 @@
158
158
  "yaml"
159
159
  ],
160
160
  "optionalDependencies": {
161
- "toolcraft-schema": "0.0.162",
161
+ "toolcraft-schema": "0.0.164",
162
162
  "toolcraft-design": "*",
163
163
  "@poe-code/frontmatter": "*",
164
164
  "@poe-code/agent-mcp-config": "*",