drexler 0.2.20 → 0.2.21

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "drexler",
3
- "version": "0.2.20",
3
+ "version": "0.2.21",
4
4
  "description": "CLI chat with Drexler, a corporate-executive AI persona built on OpenRouter Gemma 4 31B.",
5
5
  "license": "MIT",
6
6
  "author": "showOS",
package/src/ui/App.tsx CHANGED
@@ -910,6 +910,21 @@ export function App({
910
910
  return;
911
911
  }
912
912
  if (isSlash(line)) {
913
+ // Bare /theme, /model, /startup, /retry, /export — repopulate the
914
+ // input with "<cmd> " so the palette catches the argument chooser
915
+ // and the user picks via ↑↓ + Enter. Avoids the "print current
916
+ // value and dead-end" feeling of dispatching the base command.
917
+ const lower = line.toLowerCase();
918
+ if (isArgumentParentCommand(lower)) {
919
+ const filled = `${lower} `;
920
+ updateDraft({ value: filled, cursor: graphemeLength(filled) });
921
+ setPaletteIdx(0);
922
+ addItem(
923
+ "system",
924
+ `Pick a ${lower.slice(1)} option below — ↑↓ to choose, Enter to apply, Esc to cancel.`,
925
+ );
926
+ return;
927
+ }
913
928
  await handleSlashWithMutation(line);
914
929
  return;
915
930
  }
@@ -918,7 +933,14 @@ export function App({
918
933
  setMsgCount(conversation.length);
919
934
  await runLLM();
920
935
  },
921
- [addItem, conversation, handleSlashWithMutation, runLLM],
936
+ [
937
+ addItem,
938
+ conversation,
939
+ handleSlashWithMutation,
940
+ runLLM,
941
+ setPaletteIdx,
942
+ updateDraft,
943
+ ],
922
944
  );
923
945
 
924
946
  const reportSubmitError = useCallback(
@@ -171,7 +171,7 @@ const RIGHT_COLUMN_PAD_RIGHT = 1;
171
171
  const LEFT_PANEL_MIN_COPY = 24;
172
172
  const PET_STATS_MIN_WIDTH = 24;
173
173
  const PET_STATS_MAX_WIDTH = 58;
174
- const PET_SPLIT_DIVIDER_HEIGHT = 17;
174
+ const PET_SPLIT_DIVIDER_HEIGHT = 19;
175
175
  const PET_SPLIT_DIVIDER_ROWS: number[] = Array.from(
176
176
  { length: PET_SPLIT_DIVIDER_HEIGHT },
177
177
  (_, i) => i,
@@ -18,7 +18,7 @@ export type Environment = "office" | "home" | "outdoors";
18
18
 
19
19
  const PANEL_BORDER_COLUMNS = 2;
20
20
  const PANEL_PADDING_COLUMNS = 2;
21
- const SCENE_ROWS = 16;
21
+ const SCENE_ROWS = 18;
22
22
  const R_WALL = 0;
23
23
  const R_WINDOW_TOP = 1;
24
24
  const R_WINDOW_BOTTOM = 4;
@@ -26,7 +26,9 @@ const R_ACTIVITY = 5;
26
26
  const R_MASCOT_START = 6;
27
27
  const R_DESK_SURFACE = R_MASCOT_START + BRIEFCASE_FINAL.length;
28
28
  const R_DESK_FRONT = R_DESK_SURFACE + 1;
29
- const R_FLOOR = R_DESK_FRONT + 1;
29
+ const R_DESK_DRAWERS = R_DESK_FRONT + 1;
30
+ const R_DESK_BOTTOM = R_DESK_DRAWERS + 1;
31
+ const R_FLOOR = R_DESK_BOTTOM + 1;
30
32
 
31
33
  export const PET_SCENE_WIDTH = 52;
32
34
 
@@ -37,6 +39,14 @@ function place(base: string, text: string, x: number): string {
37
39
  return base.slice(0, x) + fit + base.slice(end);
38
40
  }
39
41
 
42
+ function placeSprite(rows: string[], row: number, x: number, sprite: readonly string[]): void {
43
+ for (let i = 0; i < sprite.length; i++) {
44
+ const targetRow = row + i;
45
+ if (targetRow < 0 || targetRow >= rows.length) continue;
46
+ rows[targetRow] = place(rows[targetRow] ?? "", sprite[i] ?? "", x);
47
+ }
48
+ }
49
+
40
50
  function blankRow(width: number): string {
41
51
  return " ".repeat(width);
42
52
  }
@@ -47,6 +57,13 @@ function padDisplayText(input: string, width: number): string {
47
57
  return `${fitted}${" ".repeat(Math.max(0, safeWidth - displayWidth(fitted)))}`;
48
58
  }
49
59
 
60
+ function centerPadDisplayText(input: string, width: number): string {
61
+ const safeWidth = Math.max(1, width);
62
+ const fitted = fitDisplayText(input, safeWidth);
63
+ const left = Math.max(0, Math.floor((safeWidth - displayWidth(fitted)) / 2));
64
+ return `${" ".repeat(left)}${fitted}${" ".repeat(Math.max(0, safeWidth - left - displayWidth(fitted)))}`;
65
+ }
66
+
50
67
  function overlayFitted(row: string, text: string, x: number, width: number): string {
51
68
  return place(row, padDisplayText(text, width), x);
52
69
  }
@@ -98,9 +115,9 @@ function placeBoxLines(
98
115
  }
99
116
 
100
117
  function cupForEnergy(energy: number): string {
101
- if (energy > 60) return "[c~]";
102
- if (energy > 30) return "[c-]";
103
- return "[c_]";
118
+ if (energy > 60) return "c~";
119
+ if (energy > 30) return "c-";
120
+ return "c_";
104
121
  }
105
122
 
106
123
  function progressTicker(frame: number): string {
@@ -207,17 +224,25 @@ function drawOfficeBackground(rows: string[], width: number, frame: number, stat
207
224
  Math.max(0, width - displayWidth(`pipe ${dealPct}`) - 1),
208
225
  );
209
226
 
210
- const windowWidth = Math.min(22, Math.max(16, Math.floor(width * 0.36)));
211
- const boardWidth = Math.min(28, Math.max(20, width - windowWidth - 6));
212
- const boardX = Math.max(windowWidth + 4, width - boardWidth - 2);
227
+ const compact = width < 62;
228
+ const windowWidth = compact
229
+ ? 18
230
+ : Math.min(30, Math.max(20, Math.floor(width * 0.32)));
231
+ const boardWidth = compact
232
+ ? Math.min(26, Math.max(20, width - windowWidth - 5))
233
+ : Math.min(36, Math.max(26, Math.floor(width * 0.36)));
234
+ const boardX = Math.max(windowWidth + 3, width - boardWidth - 2);
235
+ const windowRight = 1 + windowWidth;
236
+ const gapWidth = boardX - windowRight;
213
237
  const cloud = frame % 6 < 3 ? "(~~)" : " (~~)";
214
238
  const sun = frame % 12 < 6 ? "\\o/" : "-o-";
239
+ const city = frame % 10 < 5 ? "▂▄▆ city" : "▃▅▇ city";
215
240
  const tape = frame % 8 < 4 ? "▁▃▅▇" : "▂▄▆█";
216
241
  const cursor = frame % 4 < 2 ? ">" : "*";
217
242
 
218
243
  placeBoxLines(rows, R_WINDOW_TOP, 1, windowWidth, "Window", [
219
- `${sun} ${cloud}`,
220
- "║║ ║║ skyline",
244
+ `╔╤╤╗ ${sun} ${cloud}`,
245
+ `║▥▥║ ${city}`,
221
246
  ]);
222
247
  placeBoxLines(
223
248
  rows,
@@ -226,11 +251,21 @@ function drawOfficeBackground(rows: string[], width: number, frame: number, stat
226
251
  Math.min(boardWidth, width - boardX),
227
252
  "Deal Board",
228
253
  [
229
- `DL:${dealPct} fees:${Math.round(stats.happiness)}%`,
230
- `PIPE ${tape} ${cursor}`,
254
+ `DL ${dealPct} FEE ${Math.round(stats.happiness).toString().padStart(3)}%`,
255
+ `PIPE ${tape} $ ${cursor}`,
231
256
  ],
232
257
  );
233
258
 
259
+ if (gapWidth >= 7) {
260
+ const clockX = windowRight + Math.floor((gapWidth - 5) / 2);
261
+ const hour = frame % 8 < 4 ? "09" : "10";
262
+ placeSprite(rows, R_WINDOW_TOP, clockX, [
263
+ "╭──╮",
264
+ `│${hour}│`,
265
+ "╰──╯",
266
+ ]);
267
+ }
268
+
234
269
  rows[R_ACTIVITY] = centerText(
235
270
  "─".repeat(width),
236
271
  buildActivityLine("idle", frame),
@@ -238,25 +273,30 @@ function drawOfficeBackground(rows: string[], width: number, frame: number, stat
238
273
  }
239
274
 
240
275
  function drawOfficeFurniture(rows: string[], width: number, frame: number): void {
241
- const lampX = 2;
242
- const fileX = Math.max(1, width - 10);
243
- const lampLight = frame % 8 < 4 ? "\\|/" : " | ";
244
- const plantLeaves = frame % 6 < 3 ? "\\|/" : "v|v";
245
-
246
- rows[R_MASCOT_START] = place(rows[R_MASCOT_START], ` ${lampLight} `, lampX);
247
- rows[R_MASCOT_START + 1] = place(rows[R_MASCOT_START + 1], " /_\\ ", lampX);
248
- rows[R_MASCOT_START + 2] = place(rows[R_MASCOT_START + 2], " /___\\", lampX);
249
- rows[R_MASCOT_START + 3] = place(rows[R_MASCOT_START + 3], " │ ", lampX);
250
- rows[R_MASCOT_START + 4] = place(rows[R_MASCOT_START + 4], " ╭IN╮ ", lampX);
251
- rows[R_MASCOT_START + 5] = place(rows[R_MASCOT_START + 5], " ╰──╯ ", lampX);
252
-
253
- rows[R_MASCOT_START] = place(rows[R_MASCOT_START], ` ${plantLeaves} `, fileX);
254
- rows[R_MASCOT_START + 1] = place(rows[R_MASCOT_START + 1], " \\|/ ", fileX);
255
- rows[R_MASCOT_START + 2] = place(rows[R_MASCOT_START + 2], " ╰┬╯ ", fileX);
256
- rows[R_MASCOT_START + 3] = place(rows[R_MASCOT_START + 3], "╭FILE╮", fileX - 1);
257
- rows[R_MASCOT_START + 4] = place(rows[R_MASCOT_START + 4], "│▤▤▤│", fileX - 1);
258
- rows[R_MASCOT_START + 5] = place(rows[R_MASCOT_START + 5], "│▤▤▤│", fileX - 1);
259
- rows[R_MASCOT_START + 6] = place(rows[R_MASCOT_START + 6], "╰────╯", fileX - 1);
276
+ const lampX = 1;
277
+ const cabinetX = Math.max(1, width - 8);
278
+ const shade = frame % 8 < 4 ? "╭░░░░╮" : "╭▒▒▒▒╮";
279
+ const plantTop = frame % 6 < 3 ? " ╲│╱ " : " ╱│╲ ";
280
+
281
+ placeSprite(rows, R_MASCOT_START, lampX, [
282
+ ` ${shade} `,
283
+ " ╱▒▒▒▒╲",
284
+ " ╰─┬──╯",
285
+ "",
286
+ " ╭─┴─╮ ",
287
+ " │IN │ ",
288
+ " ╰───╯ ",
289
+ ]);
290
+
291
+ placeSprite(rows, R_MASCOT_START, cabinetX, [
292
+ plantTop,
293
+ " ╲│╱ ",
294
+ " ╰┬╯ ",
295
+ "╭FILE╮",
296
+ "│▤▤▤│",
297
+ "├────┤",
298
+ "│▤▤▤│",
299
+ ]);
260
300
  }
261
301
 
262
302
  function drawActivityAccents(
@@ -272,16 +312,16 @@ function drawActivityAccents(
272
312
  );
273
313
 
274
314
  const mascotRight = mascotX + MASCOT_WIDTH;
275
- const leftAccentX = Math.max(1, mascotX - 10);
276
- const fileX = Math.max(1, width - 10);
277
- const rightAccentX = Math.min(fileX - 5, mascotRight + 2);
315
+ const leftAccentX = Math.max(1, mascotX - 3);
316
+ const fileX = Math.max(1, width - 8);
317
+ const rightAccentX = Math.min(fileX - 6, mascotRight + 2);
278
318
 
279
319
  switch (activity) {
280
320
  case "eating":
281
321
  rows[R_MASCOT_START + 5] = place(
282
322
  rows[R_MASCOT_START + 5],
283
- "[$] memo",
284
- Math.max(1, Math.min(fileX - 8, rightAccentX)),
323
+ "╭$╮",
324
+ Math.max(1, Math.min(fileX - 5, rightAccentX + 1)),
285
325
  );
286
326
  break;
287
327
  case "playing":
@@ -330,33 +370,36 @@ function drawDesktopObjects(
330
370
  ): void {
331
371
  const mascotX = Math.max(0, Math.floor((width - MASCOT_WIDTH) / 2));
332
372
  const mascotRight = mascotX + MASCOT_WIDTH;
333
- const fileX = Math.max(1, width - 10);
334
- const laptopX = Math.max(8, mascotX - 10);
335
- const papersX = Math.min(width - 5, mascotX + MASCOT_WIDTH + 1);
336
- const coffeeX = fileX - 13;
373
+ const cabinetX = Math.max(1, width - 8);
374
+ const laptopX = Math.max(8, mascotX - 9);
375
+ const papersX = Math.min(cabinetX - 10, mascotRight + 2);
376
+ const mugX = Math.min(cabinetX - 5, mascotRight + 6);
337
377
  const cursor = frame % 2 === 0 ? "_" : " ";
338
378
  const screen =
339
379
  activity === "working"
340
- ? `$>${cursor} DL`
380
+ ? `$>${cursor}DL`
341
381
  : activity === "sleeping"
342
382
  ? "zzz..."
343
383
  : "DREX";
344
384
  const steam = stats.energy > 30
345
385
  ? frame % 4 < 2 ? " ((" : " ))"
346
386
  : " ";
347
- const paperFace = frame % 6 < 3 ? "////" : "\\\\\\\\";
387
+ const paperFace = frame % 6 < 3 ? "▱▱▱" : "▰▱▱";
348
388
 
349
- rows[R_MASCOT_START + 4] = place(rows[R_MASCOT_START + 4], "╭laptop─╮", laptopX);
350
- rows[R_MASCOT_START + 5] = place(
351
- rows[R_MASCOT_START + 5],
352
- `│${padDisplayText(screen, 7)}│`,
353
- laptopX,
354
- );
355
- rows[R_MASCOT_START + 6] = place(rows[R_MASCOT_START + 6], "╰─┬──┬─╯", laptopX);
356
- rows[R_MASCOT_START + 6] = place(rows[R_MASCOT_START + 6], paperFace, papersX);
357
- if (coffeeX > mascotRight + 1) {
358
- rows[R_MASCOT_START + 4] = place(rows[R_MASCOT_START + 4], steam, coffeeX + 1);
359
- rows[R_MASCOT_START + 5] = place(rows[R_MASCOT_START + 5], `coffee ${cupForEnergy(stats.energy)}`, coffeeX);
389
+ placeSprite(rows, R_MASCOT_START + 4, laptopX, [
390
+ "╭──────╮",
391
+ `│${padDisplayText(screen, 6)}│`,
392
+ "╰─┬──┬─╯",
393
+ ]);
394
+
395
+ if (papersX > mascotRight) {
396
+ rows[R_MASCOT_START + 6] = place(rows[R_MASCOT_START + 6], paperFace, papersX);
397
+ }
398
+
399
+ if (mugX > mascotRight + 1) {
400
+ rows[R_MASCOT_START + 4] = place(rows[R_MASCOT_START + 4], steam, mugX + 1);
401
+ rows[R_MASCOT_START + 5] = place(rows[R_MASCOT_START + 5], "╭─╮", mugX);
402
+ rows[R_MASCOT_START + 6] = place(rows[R_MASCOT_START + 6], `╰${cupForEnergy(stats.energy)}╯`, mugX);
360
403
  }
361
404
  }
362
405
 
@@ -364,9 +407,11 @@ function drawDesk(rows: string[], width: number, stats: PetStats): void {
364
407
  const deskX = width > PET_SCENE_WIDTH ? 2 : 1;
365
408
  const deskWidth = Math.max(4, width - deskX * 2);
366
409
  const deskInner = Math.max(1, deskWidth - 2);
367
- const surface = `papers [///] coffee ${cupForEnergy(stats.energy)} covenants OK`;
368
- const centerLabel = `DREXLER DEAL DESK pipeline ${Math.round(stats.deals)}%`;
369
- const front = `[IN] ${centerLabel} [OUT]`;
410
+ const surface = `▱▱▱ [${cupForEnergy(stats.energy)}] ▬▬▬▬▬ COV OK`;
411
+ const front = `[IN] ║ DREXLER DEAL DESK ║ PIPE ${Math.round(stats.deals)}% ║ [OUT]`;
412
+ const drawers = width < 68
413
+ ? "╭────╮ ╭────╮ ╭────╮"
414
+ : "╭────╮ ╭────╮ ╭────╮ ╭────╮";
370
415
 
371
416
  rows[R_DESK_SURFACE] = place(
372
417
  rows[R_DESK_SURFACE],
@@ -378,11 +423,20 @@ function drawDesk(rows: string[], width: number, stats: PetStats): void {
378
423
  `│${padDisplayText(front, deskInner)}│`,
379
424
  deskX,
380
425
  );
381
- rows[R_FLOOR] = place(
382
- rows[R_FLOOR],
426
+ rows[R_DESK_DRAWERS] = place(
427
+ rows[R_DESK_DRAWERS],
428
+ `│${centerPadDisplayText(drawers, deskInner)}│`,
429
+ deskX,
430
+ );
431
+ rows[R_DESK_BOTTOM] = place(
432
+ rows[R_DESK_BOTTOM],
383
433
  `╰${"─".repeat(Math.max(0, deskInner))}╯`,
384
434
  deskX,
385
435
  );
436
+ rows[R_FLOOR] = centerText(
437
+ rows[R_FLOOR],
438
+ fitDisplayText("░░░░░░░ deal-room carpet shadow ░░░░░░░", width),
439
+ );
386
440
  }
387
441
 
388
442
  function buildScene(
@@ -421,7 +475,13 @@ function rowColor(i: number, activity: PetActivity, frame: number, t: Theme): st
421
475
  if (activity === "playing") return t.primaryLight;
422
476
  return t.primaryLight;
423
477
  }
424
- if (i === R_DESK_SURFACE || i === R_DESK_FRONT) return t.primaryDim;
478
+ if (
479
+ i === R_DESK_SURFACE ||
480
+ i === R_DESK_FRONT ||
481
+ i === R_DESK_DRAWERS ||
482
+ i === R_DESK_BOTTOM ||
483
+ i === R_FLOOR
484
+ ) return t.primaryDim;
425
485
  if (i === R_WALL) return t.dim;
426
486
  if (i >= R_WINDOW_TOP && i <= R_WINDOW_BOTTOM) return t.primaryDim;
427
487
  return t.primaryDim;