drexler 0.2.18 → 0.2.20

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.18",
3
+ "version": "0.2.20",
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",
@@ -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 = 15;
174
+ const PET_SPLIT_DIVIDER_HEIGHT = 17;
175
175
  const PET_SPLIT_DIVIDER_ROWS: number[] = Array.from(
176
176
  { length: PET_SPLIT_DIVIDER_HEIGHT },
177
177
  (_, i) => i,
@@ -18,14 +18,15 @@ 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 = 14;
21
+ const SCENE_ROWS = 16;
22
22
  const R_WALL = 0;
23
23
  const R_WINDOW_TOP = 1;
24
- const R_WINDOW_BOTTOM = 3;
25
- const R_ACTIVITY = 4;
26
- const R_MASCOT_START = 5;
24
+ const R_WINDOW_BOTTOM = 4;
25
+ const R_ACTIVITY = 5;
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
30
 
30
31
  export const PET_SCENE_WIDTH = 52;
31
32
 
@@ -73,23 +74,33 @@ function sceneBoxBottom(width: number): string {
73
74
  return `╰${"─".repeat(safeWidth - 2)}╯`;
74
75
  }
75
76
 
76
- function placeSceneBox(
77
+ function placeBoxLines(
77
78
  rows: string[],
78
79
  row: number,
79
80
  x: number,
80
81
  width: number,
81
82
  title: string,
82
- body: string,
83
+ body: readonly string[],
83
84
  ): void {
84
85
  rows[row] = place(rows[row] ?? "", sceneBoxTop(title, width), x);
85
- rows[row + 1] = place(rows[row + 1] ?? "", sceneBoxBody(body, width), x);
86
- rows[row + 2] = place(rows[row + 2] ?? "", sceneBoxBottom(width), x);
86
+ for (let i = 0; i < body.length; i++) {
87
+ rows[row + i + 1] = place(
88
+ rows[row + i + 1] ?? "",
89
+ sceneBoxBody(body[i] ?? "", width),
90
+ x,
91
+ );
92
+ }
93
+ rows[row + body.length + 1] = place(
94
+ rows[row + body.length + 1] ?? "",
95
+ sceneBoxBottom(width),
96
+ x,
97
+ );
87
98
  }
88
99
 
89
100
  function cupForEnergy(energy: number): string {
90
- if (energy > 60) return "coffee [c~]";
91
- if (energy > 30) return "coffee [c-]";
92
- return "coffee [c_]";
101
+ if (energy > 60) return "[c~]";
102
+ if (energy > 30) return "[c-]";
103
+ return "[c_]";
93
104
  }
94
105
 
95
106
  function progressTicker(frame: number): string {
@@ -199,16 +210,25 @@ function drawOfficeBackground(rows: string[], width: number, frame: number, stat
199
210
  const windowWidth = Math.min(22, Math.max(16, Math.floor(width * 0.36)));
200
211
  const boardWidth = Math.min(28, Math.max(20, width - windowWidth - 6));
201
212
  const boardX = Math.max(windowWidth + 4, width - boardWidth - 2);
202
- const marketPulse = frame % 10 < 5 ? "market tape + + +" : "market tape + +";
203
-
204
- placeSceneBox(rows, R_WINDOW_TOP, 1, windowWidth, "Market Window", marketPulse);
205
- placeSceneBox(
213
+ const cloud = frame % 6 < 3 ? "(~~)" : " (~~)";
214
+ const sun = frame % 12 < 6 ? "\\o/" : "-o-";
215
+ const tape = frame % 8 < 4 ? "▁▃▅▇" : "▂▄▆█";
216
+ const cursor = frame % 4 < 2 ? ">" : "*";
217
+
218
+ placeBoxLines(rows, R_WINDOW_TOP, 1, windowWidth, "Window", [
219
+ `${sun} ${cloud}`,
220
+ "║║ ║║ skyline",
221
+ ]);
222
+ placeBoxLines(
206
223
  rows,
207
224
  R_WINDOW_TOP,
208
225
  boardX,
209
226
  Math.min(boardWidth, width - boardX),
210
227
  "Deal Board",
211
- `DL:${dealPct} fees:${Math.round(stats.happiness)}%`,
228
+ [
229
+ `DL:${dealPct} fees:${Math.round(stats.happiness)}%`,
230
+ `PIPE ${tape} ${cursor}`,
231
+ ],
212
232
  );
213
233
 
214
234
  rows[R_ACTIVITY] = centerText(
@@ -217,6 +237,28 @@ function drawOfficeBackground(rows: string[], width: number, frame: number, stat
217
237
  );
218
238
  }
219
239
 
240
+ 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);
260
+ }
261
+
220
262
  function drawActivityAccents(
221
263
  rows: string[],
222
264
  width: number,
@@ -231,34 +273,37 @@ function drawActivityAccents(
231
273
 
232
274
  const mascotRight = mascotX + MASCOT_WIDTH;
233
275
  const leftAccentX = Math.max(1, mascotX - 10);
234
- const rightAccentX = Math.min(width - 10, mascotRight + 2);
276
+ const fileX = Math.max(1, width - 10);
277
+ const rightAccentX = Math.min(fileX - 5, mascotRight + 2);
235
278
 
236
279
  switch (activity) {
237
280
  case "eating":
238
- rows[R_DESK_SURFACE] = place(rows[R_DESK_SURFACE], "[$ memo]", rightAccentX);
281
+ rows[R_MASCOT_START + 5] = place(
282
+ rows[R_MASCOT_START + 5],
283
+ "[$] memo",
284
+ Math.max(1, Math.min(fileX - 8, rightAccentX)),
285
+ );
239
286
  break;
240
287
  case "playing":
241
288
  rows[R_MASCOT_START + 2] = place(rows[R_MASCOT_START + 2], "*", leftAccentX);
242
- rows[R_MASCOT_START + 2] = place(rows[R_MASCOT_START + 2], "*", rightAccentX + 6);
289
+ rows[R_MASCOT_START + 2] = place(rows[R_MASCOT_START + 2], "*", Math.min(fileX - 2, rightAccentX + 6));
243
290
  break;
244
291
  case "working":
245
292
  rows[R_MASCOT_START + 1] = place(rows[R_MASCOT_START + 1], "$", leftAccentX);
246
- rows[R_MASCOT_START + 3] = place(rows[R_MASCOT_START + 3], "$", rightAccentX + 6);
293
+ rows[R_MASCOT_START + 3] = place(rows[R_MASCOT_START + 3], "$", Math.min(fileX - 2, rightAccentX + 6));
247
294
  break;
248
295
  case "sleeping":
249
296
  rows[R_MASCOT_START] = place(rows[R_MASCOT_START], "z z Z", rightAccentX);
250
297
  break;
251
298
  case "praised":
252
299
  rows[R_MASCOT_START + 1] = place(rows[R_MASCOT_START + 1], "* *", leftAccentX);
253
- rows[R_MASCOT_START + 1] = place(rows[R_MASCOT_START + 1], "* *", rightAccentX + 4);
300
+ rows[R_MASCOT_START + 1] = place(rows[R_MASCOT_START + 1], "* *", Math.min(fileX - 4, rightAccentX + 4));
254
301
  break;
255
302
  case "vibing":
256
303
  rows[R_MASCOT_START + 3] = place(rows[R_MASCOT_START + 3], "~ ~", leftAccentX);
257
- rows[R_MASCOT_START + 3] = place(rows[R_MASCOT_START + 3], "~ ~", rightAccentX + 4);
304
+ rows[R_MASCOT_START + 3] = place(rows[R_MASCOT_START + 3], "~ ~", Math.min(fileX - 4, rightAccentX + 4));
258
305
  break;
259
306
  default:
260
- rows[R_MASCOT_START + 2] = place(rows[R_MASCOT_START + 2], "[IN]", 2);
261
- rows[R_MASCOT_START + 2] = place(rows[R_MASCOT_START + 2], "[OUT]", Math.max(2, width - 8));
262
307
  break;
263
308
  }
264
309
  }
@@ -276,12 +321,52 @@ function drawMascot(rows: string[], width: number, activity: PetActivity, frame:
276
321
  return mascotX;
277
322
  }
278
323
 
324
+ function drawDesktopObjects(
325
+ rows: string[],
326
+ width: number,
327
+ activity: PetActivity,
328
+ frame: number,
329
+ stats: PetStats,
330
+ ): void {
331
+ const mascotX = Math.max(0, Math.floor((width - MASCOT_WIDTH) / 2));
332
+ 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;
337
+ const cursor = frame % 2 === 0 ? "_" : " ";
338
+ const screen =
339
+ activity === "working"
340
+ ? `$>${cursor} DL`
341
+ : activity === "sleeping"
342
+ ? "zzz..."
343
+ : "DREX";
344
+ const steam = stats.energy > 30
345
+ ? frame % 4 < 2 ? " ((" : " ))"
346
+ : " ";
347
+ const paperFace = frame % 6 < 3 ? "////" : "\\\\\\\\";
348
+
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);
360
+ }
361
+ }
362
+
279
363
  function drawDesk(rows: string[], width: number, stats: PetStats): void {
280
364
  const deskX = width > PET_SCENE_WIDTH ? 2 : 1;
281
365
  const deskWidth = Math.max(4, width - deskX * 2);
282
366
  const deskInner = Math.max(1, deskWidth - 2);
283
- const surface = `laptop [▣] papers [///] ${cupForEnergy(stats.energy)}`;
284
- const front = `DESK pipeline ${Math.round(stats.deals)}% covenants OK`;
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]`;
285
370
 
286
371
  rows[R_DESK_SURFACE] = place(
287
372
  rows[R_DESK_SURFACE],
@@ -290,7 +375,12 @@ function drawDesk(rows: string[], width: number, stats: PetStats): void {
290
375
  );
291
376
  rows[R_DESK_FRONT] = place(
292
377
  rows[R_DESK_FRONT],
293
- `╰${padDisplayText(front, deskInner)}╯`,
378
+ `│${padDisplayText(front, deskInner)}│`,
379
+ deskX,
380
+ );
381
+ rows[R_FLOOR] = place(
382
+ rows[R_FLOOR],
383
+ `╰${"─".repeat(Math.max(0, deskInner))}╯`,
294
384
  deskX,
295
385
  );
296
386
  }
@@ -305,8 +395,10 @@ function buildScene(
305
395
  const rows: string[] = Array.from({ length: SCENE_ROWS }, () => blankRow(sceneWidth));
306
396
 
307
397
  drawOfficeBackground(rows, sceneWidth, frame, stats);
308
- drawDesk(rows, sceneWidth, stats);
398
+ drawOfficeFurniture(rows, sceneWidth, frame);
309
399
  const mascotX = drawMascot(rows, sceneWidth, activity, frame);
400
+ drawDesktopObjects(rows, sceneWidth, activity, frame, stats);
401
+ drawDesk(rows, sceneWidth, stats);
310
402
  drawActivityAccents(rows, sceneWidth, activity, frame, mascotX);
311
403
  return rows.map((row) => overlayFitted(blankRow(sceneWidth), row, 0, sceneWidth));
312
404
  }