@pi-extensions/pi-pacman 0.1.0 → 0.1.1

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.
Files changed (3) hide show
  1. package/README.md +64 -45
  2. package/index.ts +150 -48
  3. package/package.json +8 -5
package/README.md CHANGED
@@ -1,68 +1,87 @@
1
- # pi-pacman
1
+ # @pi-extensions/pi-pacman
2
2
 
3
- Pac-Man working indicator for [pi](https://github.com/earendil-works/pi).
3
+ Pac-Man **working indicator** for [pi](https://github.com/earendil-works/pi): replaces the streaming spinner with pellet runs, ghost chases, arcade tunnels, and fruit bonuses.
4
4
 
5
- Replaces the streaming spinner with pellet runs, ghost chases, arcade tunnels, and fruit bonuses.
5
+ Part of the [pi-extensions](https://github.com/smarzban/pi-extensions) monorepo.
6
6
 
7
- ## Install
7
+ ## Highlights
8
+
9
+ - **Drop-in working indicator**: uses `setWorkingIndicator` / `setWorkingMessage` (normal streaming only)
10
+ - **Five looks**: full-width `classic` & `chase`, plus fixed-width `mini`, `arcade`, `fruit` (default 10 cells, configurable)
11
+ - **Rotate mode**: cycle short looks every agent message (`/pacman rotate`)
12
+ - **Random working blurbs**: arcade + AI/token-flavored lines each run (or lock your own)
13
+ - **Remembers your choice**: look, rotate, cells, and custom message in `~/.pi/agent/pacman-thinking.json`
14
+
15
+ ## Quickstart
8
16
 
9
17
  ```bash
10
- # from npm (after publish)
11
18
  pi install npm:@pi-extensions/pi-pacman
19
+ ```
20
+
21
+ Restart pi (or start a new session), send a message, and you should see a yellow `ᗧ` chomping pellets next to the working line:
12
22
 
13
- # local path (this monorepo)
14
- pi install /absolute/path/to/pi-extentions/packages/pi-pacman
23
+ ```text
24
+ ᗧ······ waka waka...
15
25
  ```
16
26
 
17
- ### Publish to npm
27
+ Default look is **classic** (full-width pellet run). Footer status shows `ᗧ classic` while the extension is active.
18
28
 
19
- Releases are **tag-driven** (OIDC trusted publishing) see [docs/releases.md](../../docs/releases.md).
29
+ The indicator only appears while the agent is **streaming a normal response**, not during compaction/retry loaders.
20
30
 
21
- ```bash
22
- # after version bump on main:
23
- git tag v0.1.0 && git push origin v0.1.0
24
- ```
31
+ ## Install
25
32
 
26
- One-time: reservation publish with OTP + Trusted Publisher on npmjs.com (same doc).
33
+ | Method | Loads | Command |
34
+ |--------|-------|---------|
35
+ | **npm** (recommended) | This package only | `pi install npm:@pi-extensions/pi-pacman` |
36
+ | **local path** | This package only | `pi install /absolute/path/to/pi-extensions/packages/pi-pacman` |
37
+ | **git** (whole monorepo) | All packages in the repo | `pi install git:github.com/smarzban/pi-extensions` |
27
38
 
28
- Then anyone can:
39
+ More detail: [docs/install](../../docs/install/README.md) (one package vs whole monorepo).
29
40
 
30
- ```bash
31
- pi install npm:@pi-extensions/pi-pacman
32
- pi install npm:@pi-extensions/pi-pacman@0.1.0
33
- ```
41
+ ## Usage
34
42
 
35
- ## Commands
43
+ ```text
44
+ /pacman list
45
+ /pacman chase
46
+ /pacman rotate
47
+ /pacman cells 12
48
+ /pacman message chomping tokens...
49
+ /pacman off
50
+ ```
36
51
 
37
- | Command | Effect |
52
+ | Command | Result |
38
53
  |---------|--------|
39
- | `/pacman` | Show current look |
40
- | `/pacman list` | Catalog of looks |
41
- | `/pacman <look>` | Lock to that look (stops rotate) |
42
- | `/pacman rotate` | Cycle a different look every message |
43
- | `/pacman off` | Hide the indicator (stops rotate) |
44
- | `/pacman message <text>` | Custom working message |
45
- | `/pacman message` | Reset message to look default |
46
- | `/pacman clear` | Hide the look list widget |
47
-
48
- ### Looks
49
-
50
- | Look | Vibe |
51
- |------|------|
54
+ | `/pacman` | Current look, rotate, cells, message, strip width |
55
+ | `/pacman list` | Catalog under the editor |
56
+ | `/pacman <look>` | Lock a look (stops rotate) |
57
+ | `/pacman rotate` | Cycle short looks every message |
58
+ | `/pacman cells [n]` | Fixed-look width (default 10, range 4–40) |
59
+ | `/pacman message …` | Lock custom working text (empty = auto-random) |
60
+ | `/pacman off` | Hide indicator |
61
+
62
+ Full reference: [docs/usage/commands.md](../../docs/usage/commands.md).
63
+
64
+ ## Looks
65
+
66
+ | Look | Notes |
67
+ |------|--------|
52
68
  | `classic` | Full-width pellet run (default) |
53
- | `chase` | Full-width Blinky hunt → revenge |
54
- | `mini` | 7-cell pellet run |
55
- | `arcade` | 7-cell maze tunnel |
56
- | `fruit` | 7-cell cherry bonus |
69
+ | `chase` | Full-width Blinky hunt → power pellet → revenge |
70
+ | `mini` / `arcade` / `fruit` | Fixed-width animations (**in rotate**); width via `cells` config |
57
71
 
58
- Speeds: full-width **80ms**/frame · fixed **110ms**/frame.
72
+ Details: [docs/usage/looks.md](../../docs/usage/looks.md). Persistence: [docs/usage/persistence.md](../../docs/usage/persistence.md).
59
73
 
60
- ## Persistence
74
+ ## Documentation
61
75
 
62
- Look, rotate on/off, rotate index, and custom message save to `~/.pi/agent/pacman-thinking.json`.
76
+ | Doc | What |
77
+ |-----|------|
78
+ | [Quickstart](../../docs/quickstart.md) | Fastest path to first chomp |
79
+ | [Commands](../../docs/usage/commands.md) | Every `/pacman` subcommand |
80
+ | [Looks](../../docs/usage/looks.md) | Frame / width notes |
81
+ | [Persistence](../../docs/usage/persistence.md) | State file fields |
82
+ | [Architecture](../../docs/architecture.md) | How the extension hooks pi |
83
+ | [Releases](../../docs/releases.md) | Maintainer publish flow |
63
84
 
64
- ## One-shot (without install)
85
+ ## License
65
86
 
66
- ```bash
67
- pi --extension ./index.ts
68
- ```
87
+ [MIT](LICENSE)
package/index.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * Replaces pi's streaming working indicator with a Pac-Man animation.
5
5
  *
6
6
  * Install:
7
- * pi install /path/to/pi-extentions/packages/pi-pacman
7
+ * pi install /path/to/pi-extensions/packages/pi-pacman
8
8
  * pi --extension ./index.ts
9
9
  *
10
10
  * Commands:
@@ -33,16 +33,19 @@ const PELLET = "\x1b[38;2;255;184;174m";
33
33
  const POWER = "\x1b[38;2;255;184;255m";
34
34
  const WALL = "\x1b[38;2;33;33;255m";
35
35
  const CHERRY = "\x1b[38;2;255;0;0m";
36
- const SCORE = "\x1b[38;2;255;255;255m";
37
36
  const GHOSTS = {
38
37
  blinky: "\x1b[38;2;255;0;0m",
39
38
  scared: "\x1b[38;2;33;33;255m",
40
39
  } as const;
41
40
 
42
- /** Full-width looks (classic / chase) snappier chomp. */
41
+ /** Full-width looks (classic / chase): snappier chomp. */
43
42
  const FRAME_MS_FULL = 80;
44
- /** Fixed 7-cell looks (mini / arcade / fruit) a bit slower. */
43
+ /** Fixed-width looks (mini / arcade / fruit): a bit slower. */
45
44
  const FRAME_MS_FIXED = 110;
45
+ /** Default strip length for mini / arcade / fruit (cells). Overridable via config. */
46
+ const DEFAULT_CELLS = 10;
47
+ const CELLS_MIN = 4;
48
+ const CELLS_MAX = 40;
46
49
 
47
50
  type Facing = "right" | "left";
48
51
 
@@ -127,13 +130,15 @@ function runTrack(track: number): string[] {
127
130
 
128
131
  // ── looks ────────────────────────────────────────────────────────────
129
132
 
130
- /** Fixed strip length for mini / arcade / fruit (cells). */
131
- const FIXED_CELLS = 7;
133
+ function clampCells(n: number): number {
134
+ if (!Number.isFinite(n)) return DEFAULT_CELLS;
135
+ return Math.max(CELLS_MIN, Math.min(CELLS_MAX, Math.round(n)));
136
+ }
132
137
 
133
138
  const LOOKS: Look[] = [
134
139
  {
135
140
  id: "classic",
136
- blurb: "Full-width pellet run dots span the terminal",
141
+ blurb: "Full-width pellet run, dots span the terminal",
137
142
  message: "waka waka...",
138
143
  fullWidth: true,
139
144
  frames: (track) => runTrack(track),
@@ -193,16 +198,16 @@ const LOOKS: Look[] = [
193
198
  },
194
199
  {
195
200
  id: "mini",
196
- blurb: "7-cell pellet run",
201
+ blurb: "fixed-width pellet run",
197
202
  message: "chomp chomp...",
198
- frames: () => runTrack(FIXED_CELLS),
203
+ frames: (track) => runTrack(track),
199
204
  },
200
205
  {
201
206
  id: "arcade",
202
- blurb: "7-cell maze tunnel with blue walls",
207
+ blurb: "fixed-width maze tunnel with blue walls",
203
208
  message: "insert coin...",
204
- frames: () => {
205
- const last = FIXED_CELLS - 1;
209
+ frames: (track) => {
210
+ const last = track - 1;
206
211
  const frames: string[] = [];
207
212
  const wall = paint(WALL, "│");
208
213
  for (let i = 0; i <= last; i++) {
@@ -216,7 +221,7 @@ const LOOKS: Look[] = [
216
221
  frames.push(`${wall}${mid}${wall}`);
217
222
  }
218
223
  }
219
- frames.push(`${wall}${paint(WALL, "≈".repeat(FIXED_CELLS))}${wall}`);
224
+ frames.push(`${wall}${paint(WALL, "≈".repeat(track))}${wall}`);
220
225
  for (let i = last; i >= 0; i--) {
221
226
  for (const open of [true, false]) {
222
227
  let mid = "";
@@ -233,10 +238,10 @@ const LOOKS: Look[] = [
233
238
  },
234
239
  {
235
240
  id: "fruit",
236
- blurb: "7-cell cherry bonus run",
241
+ blurb: "fixed-width cherry bonus run",
237
242
  message: "fruit bonus...",
238
- frames: () => {
239
- const last = FIXED_CELLS - 1;
243
+ frames: (track) => {
244
+ const last = track - 1;
240
245
  const frames: string[] = [];
241
246
 
242
247
  // Rightward: cherry waits at the far end
@@ -253,8 +258,6 @@ const LOOKS: Look[] = [
253
258
  frames.push(row);
254
259
  }
255
260
  }
256
- frames.push(`${" ".repeat(Math.max(0, FIXED_CELLS - 3))}${paint(SCORE, "100")}`);
257
- frames.push(`${" ".repeat(Math.max(0, FIXED_CELLS - 3))}${paint(SCORE, "100")}`);
258
261
 
259
262
  // Leftward: cherry waits at the start
260
263
  for (let i = last; i >= 0; i--) {
@@ -270,26 +273,66 @@ const LOOKS: Look[] = [
270
273
  frames.push(row);
271
274
  }
272
275
  }
273
- frames.push(`${paint(SCORE, "300")}${" ".repeat(Math.max(0, FIXED_CELLS - 3))}`);
274
- frames.push(`${paint(SCORE, "300")}${" ".repeat(Math.max(0, FIXED_CELLS - 3))}`);
275
276
  return frames;
276
277
  },
277
278
  },
278
279
  ];
279
280
 
280
281
  const LOOK_BY_ID = new Map(LOOKS.map((l) => [l.id, l]));
281
- /** Looks eligible for rotate/random (excludes off/default). */
282
- const ROTATE_LOOKS = LOOKS.map((l) => l.id);
282
+ /** Rotate only cycles short looks; full-width classic/chase stay opt-in via /pacman <look>. */
283
+ const ROTATE_LOOKS = LOOKS.filter((l) => !l.fullWidth).map((l) => l.id);
284
+
285
+ /** Fun working-line blurbs; one is picked at random each agent turn (unless custom message). */
286
+ const MESSAGE_POOL = [
287
+ "waka waka...",
288
+ "chomp chomp...",
289
+ "nom nom nom...",
290
+ "pellet run...",
291
+ "extra life...",
292
+ "maze clear...",
293
+ "level up...",
294
+ "thinking in 8-bit...",
295
+ "chomping tokens...",
296
+ "munching context...",
297
+ "dot duty...",
298
+ "blinky inbound...",
299
+ "still hungry...",
300
+ "just one more pellet...",
301
+ "pac-ing thoughts...",
302
+ "busy in the maze...",
303
+ "sampling next token...",
304
+ "warming up the weights...",
305
+ "attention is all you need...",
306
+ "prompt cache go brrr...",
307
+ "context window snacking...",
308
+ "tool call pending...",
309
+ "reasoning tokens burning...",
310
+ "embedding the maze...",
311
+ "softmaxing vibes...",
312
+ "gradient descending...",
313
+ "hallucinating less... hopefully...",
314
+ "compacting the snack tray...",
315
+ "waiting on the model...",
316
+ "streaming tokens...",
317
+ "fine-tuning my appetite...",
318
+ "loading more context...",
319
+ "thinking tokens go chomp...",
320
+ "RAG-ing the pellets...",
321
+ "temperature set to spicy...",
322
+ "beam search for snacks...",
323
+ ];
283
324
 
284
325
  type Mode = string;
285
326
 
286
327
  interface PersistedState {
287
328
  mode?: string;
288
- /** @deprecated old field migrated to `rotate` */
329
+ /** @deprecated old field, migrated to `rotate` */
289
330
  mix?: string;
290
331
  rotate?: boolean;
291
332
  rotateIndex?: number;
292
333
  customMessage?: string;
334
+ /** Fixed strip length for mini / arcade / fruit (cells). Default 10. */
335
+ cells?: number;
293
336
  }
294
337
 
295
338
  function statePath(): string {
@@ -312,20 +355,24 @@ function saveState(state: PersistedState): void {
312
355
  mkdirSync(dirname(path), { recursive: true });
313
356
  writeFileSync(path, `${JSON.stringify(state, null, "\t")}\n`, "utf-8");
314
357
  } catch {
315
- // Non-fatal indicator still works without persistence
358
+ // Non-fatal: indicator still works without persistence
316
359
  }
317
360
  }
318
361
 
319
- function resolveTrack(look: Look, message: string): number {
362
+ function resolveTrack(look: Look, message: string, cells: number): number {
320
363
  if (look.fullWidth) return indicatorWidth(message);
321
- return FIXED_CELLS;
364
+ return cells;
322
365
  }
323
366
 
324
- function getIndicator(mode: Mode, message: string): WorkingIndicatorOptions | undefined {
367
+ function getIndicator(
368
+ mode: Mode,
369
+ message: string,
370
+ cells: number,
371
+ ): WorkingIndicatorOptions | undefined {
325
372
  if (mode === "off") return { frames: [] };
326
373
  const look = LOOK_BY_ID.get(mode);
327
374
  if (!look) return undefined;
328
- const track = resolveTrack(look, message);
375
+ const track = resolveTrack(look, message, cells);
329
376
  const intervalMs = look.fullWidth ? FRAME_MS_FULL : FRAME_MS_FIXED;
330
377
  return { frames: look.frames(track), intervalMs };
331
378
  }
@@ -333,26 +380,31 @@ function getIndicator(mode: Mode, message: string): WorkingIndicatorOptions | un
333
380
  function describeMode(mode: Mode): string {
334
381
  if (mode === "off") return "hidden";
335
382
  const look = LOOK_BY_ID.get(mode);
336
- return look ? `${look.id} ${look.blurb}` : mode;
383
+ return look ? `${look.id}: ${look.blurb}` : mode;
337
384
  }
338
385
 
339
- function defaultMessageFor(mode: Mode): string | undefined {
340
- if (mode === "off") return " ";
341
- return LOOK_BY_ID.get(mode)?.message;
386
+ function pickRandomMessage(previous?: string): string {
387
+ const pool =
388
+ previous && MESSAGE_POOL.length > 1
389
+ ? MESSAGE_POOL.filter((m) => m !== previous)
390
+ : MESSAGE_POOL;
391
+ return pool[Math.floor(Math.random() * pool.length)]!;
342
392
  }
343
393
 
344
394
  function listLooks(): string {
345
395
  const lines = LOOKS.map((l) => {
346
396
  const tag = l.fullWidth ? " (full width)" : "";
347
- return ` ${l.id.padEnd(8)} ${l.blurb}${tag}`;
397
+ const rot = !l.fullWidth ? " · in rotate" : "";
398
+ return ` ${l.id.padEnd(8)} ${l.blurb}${tag}${rot}`;
348
399
  });
349
400
  return [
350
401
  "Pac-Man looks:",
351
402
  ...lines,
352
403
  "",
353
- " rotate Different look each message (cycle)",
404
+ " rotate Cycle short looks each message (mini/arcade/fruit)",
354
405
  " off Hide indicator",
355
406
  "",
407
+ "Working text is random each run (unless /pacman message …).",
356
408
  "Pick a look name to lock it (stops rotate).",
357
409
  ].join("\n");
358
410
  }
@@ -376,6 +428,10 @@ export default function (pi: ExtensionAPI) {
376
428
  typeof saved.customMessage === "string" && saved.customMessage.length > 0
377
429
  ? saved.customMessage
378
430
  : undefined;
431
+ let cells =
432
+ typeof saved.cells === "number" ? clampCells(saved.cells) : DEFAULT_CELLS;
433
+ /** Last auto-picked blurb (not persisted); used to avoid immediate repeats. */
434
+ let autoMessage: string = pickRandomMessage();
379
435
  let lastUi: ExtensionUIContext | undefined;
380
436
 
381
437
  const persist = () => {
@@ -384,6 +440,7 @@ export default function (pi: ExtensionAPI) {
384
440
  rotate,
385
441
  rotateIndex,
386
442
  customMessage,
443
+ cells,
387
444
  });
388
445
  };
389
446
 
@@ -392,11 +449,22 @@ export default function (pi: ExtensionAPI) {
392
449
  return `ᗧ ${mode}${rotate ? " ↻" : ""}`;
393
450
  };
394
451
 
452
+ const workingMessage = (): string => {
453
+ if (mode === "off") return " ";
454
+ if (customMessage) return customMessage;
455
+ return autoMessage;
456
+ };
457
+
458
+ const refreshAutoMessage = () => {
459
+ if (customMessage) return;
460
+ autoMessage = pickRandomMessage(autoMessage);
461
+ };
462
+
395
463
  const apply = (ctx: ExtensionContext) => {
396
464
  lastUi = ctx.ui;
397
- const message = customMessage ?? defaultMessageFor(mode) ?? "Working...";
398
- ctx.ui.setWorkingIndicator(getIndicator(mode, message));
399
- ctx.ui.setWorkingMessage(mode === "off" ? " " : message);
465
+ const message = workingMessage();
466
+ ctx.ui.setWorkingIndicator(getIndicator(mode, message, cells));
467
+ ctx.ui.setWorkingMessage(message);
400
468
  ctx.ui.setStatus("pacman-thinking", ctx.ui.theme.fg("dim", statusLabel()));
401
469
  persist();
402
470
  };
@@ -404,30 +472,40 @@ export default function (pi: ExtensionAPI) {
404
472
  const isFullWidthMode = () => LOOK_BY_ID.get(mode)?.fullWidth === true;
405
473
 
406
474
  const pickNextRotatedLook = () => {
475
+ if (ROTATE_LOOKS.length === 0) return;
407
476
  mode = ROTATE_LOOKS[rotateIndex % ROTATE_LOOKS.length]!;
408
477
  rotateIndex++;
409
478
  };
410
479
 
411
480
  const reapplyIfFullWidth = () => {
412
481
  if (!lastUi || !isFullWidthMode()) return;
413
- const message = customMessage ?? defaultMessageFor(mode) ?? "Working...";
414
- lastUi.setWorkingIndicator(getIndicator(mode, message));
482
+ const message = workingMessage();
483
+ lastUi.setWorkingIndicator(getIndicator(mode, message, cells));
415
484
  };
416
485
 
417
486
  process.stdout.on?.("resize", reapplyIfFullWidth);
418
487
 
419
488
  pi.on("session_start", async (_event, ctx) => {
420
- // Don't advance rotate here agent_start picks per message.
489
+ // Don't advance rotate here; agent_start picks per message.
490
+ refreshAutoMessage();
421
491
  apply(ctx);
422
492
  });
423
493
 
424
494
  pi.on("agent_start", async (_event, ctx) => {
495
+ // Fresh random blurb every run (unless user set /pacman message)
496
+ refreshAutoMessage();
425
497
  if (rotate) {
426
498
  pickNextRotatedLook();
427
499
  apply(ctx);
428
500
  return;
429
501
  }
430
- if (isFullWidthMode()) apply(ctx);
502
+ // Always re-apply so the new message (and full-width width) stick
503
+ apply(ctx);
504
+ });
505
+
506
+ pi.on("session_shutdown", async () => {
507
+ // Drop the resize listener so reloads don't stack duplicates.
508
+ process.stdout.off?.("resize", reapplyIfFullWidth);
431
509
  });
432
510
 
433
511
  pi.registerCommand("pacman", {
@@ -435,15 +513,16 @@ export default function (pi: ExtensionAPI) {
435
513
  handler: async (args, ctx) => {
436
514
  const raw = args.trim();
437
515
  if (!raw) {
438
- const msg = customMessage ?? defaultMessageFor(mode) ?? "Working...";
516
+ const msg = workingMessage();
439
517
  const extra = isFullWidthMode()
440
518
  ? ` · width ${indicatorWidth(msg)} cells`
441
519
  : LOOK_BY_ID.has(mode)
442
- ? ` · width ${FIXED_CELLS} cells`
520
+ ? ` · width ${cells} cells`
443
521
  : "";
444
- const rotInfo = rotate ? " · rotate on" : "";
522
+ const rotInfo = rotate ? " · rotate on (short looks)" : "";
523
+ const msgMode = customMessage ? "custom" : "auto";
445
524
  ctx.ui.notify(
446
- `Pac-Man: ${describeMode(mode)}${rotInfo} · message: "${msg}"${extra}`,
525
+ `Pac-Man: ${describeMode(mode)}${rotInfo} · cells ${cells} · message (${msgMode}): "${msg}"${extra}`,
447
526
  "info",
448
527
  );
449
528
  return;
@@ -470,24 +549,47 @@ export default function (pi: ExtensionAPI) {
470
549
  if (head === "message") {
471
550
  const text = rest.join(" ").trim();
472
551
  customMessage = text.length > 0 ? text : undefined;
552
+ if (!customMessage) refreshAutoMessage();
473
553
  apply(ctx);
474
554
  ctx.ui.notify(
475
555
  customMessage
476
- ? `Working message set to: ${customMessage}`
477
- : "Working message reset to mode default",
556
+ ? `Working message locked to: ${customMessage}`
557
+ : `Working message is auto again (now: "${autoMessage}")`,
478
558
  "info",
479
559
  );
480
560
  return;
481
561
  }
482
562
 
563
+ if (head === "cells" || head === "width") {
564
+ const rawN = rest[0]?.trim();
565
+ if (!rawN) {
566
+ ctx.ui.notify(
567
+ `Pac-Man cells: ${cells} (default ${DEFAULT_CELLS}, range ${CELLS_MIN}–${CELLS_MAX}). Set with /pacman cells <n> or in ~/.pi/agent/pacman-thinking.json`,
568
+ "info",
569
+ );
570
+ return;
571
+ }
572
+ const n = Number(rawN);
573
+ if (!Number.isFinite(n)) {
574
+ ctx.ui.notify(`Invalid cells "${rawN}". Use a number ${CELLS_MIN}–${CELLS_MAX}.`, "error");
575
+ return;
576
+ }
577
+ cells = clampCells(n);
578
+ apply(ctx);
579
+ ctx.ui.notify(`Pac-Man short-look width: ${cells} cells`, "info");
580
+ return;
581
+ }
582
+
483
583
  if (head === "rotate" || head === "cycle") {
484
584
  rotate = true;
585
+ // Only short looks participate; jump into that cycle cleanly
485
586
  const idx = ROTATE_LOOKS.indexOf(mode);
486
587
  rotateIndex = idx >= 0 ? idx : 0;
487
588
  pickNextRotatedLook();
589
+ refreshAutoMessage();
488
590
  apply(ctx);
489
591
  ctx.ui.notify(
490
- `Pac-Man rotate on cycles each message (now: ${mode}). Pick a look to lock it.`,
592
+ `Pac-Man rotate on. Short looks only: ${ROTATE_LOOKS.join(", ")} (now: ${mode}).`,
491
593
  "info",
492
594
  );
493
595
  return;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pi-extensions/pi-pacman",
3
- "version": "0.1.0",
4
- "description": "Pac-Man working indicator for pi replaces the streaming spinner with pellet runs, ghost chases, and more",
3
+ "version": "0.1.1",
4
+ "description": "Pac-Man working indicator for pi: replaces the streaming spinner with pellet runs, ghost chases, and more",
5
5
  "type": "module",
6
6
  "keywords": [
7
7
  "pi-package",
@@ -14,13 +14,13 @@
14
14
  "license": "MIT",
15
15
  "repository": {
16
16
  "type": "git",
17
- "url": "git+https://github.com/smarzban/pi-extentions.git",
17
+ "url": "git+https://github.com/smarzban/pi-extensions.git",
18
18
  "directory": "packages/pi-pacman"
19
19
  },
20
20
  "bugs": {
21
- "url": "https://github.com/smarzban/pi-extentions/issues"
21
+ "url": "https://github.com/smarzban/pi-extensions/issues"
22
22
  },
23
- "homepage": "https://github.com/smarzban/pi-extentions/tree/main/packages/pi-pacman#readme",
23
+ "homepage": "https://github.com/smarzban/pi-extensions/tree/main/packages/pi-pacman#readme",
24
24
  "engines": {
25
25
  "node": ">=18.0.0"
26
26
  },
@@ -29,6 +29,9 @@
29
29
  "./index.ts"
30
30
  ]
31
31
  },
32
+ "peerDependencies": {
33
+ "@earendil-works/pi-coding-agent": "*"
34
+ },
32
35
  "files": [
33
36
  "index.ts",
34
37
  "README.md",