storymode-cli 1.3.7 → 1.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "storymode-cli",
3
- "version": "1.3.7",
3
+ "version": "1.4.0",
4
4
  "description": "Play AI-animated pixel art characters in your terminal",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.mjs CHANGED
@@ -203,10 +203,11 @@ export async function run(args) {
203
203
  // but iTerm2's \x1b[2J is intercepted by tmux). So for iTerm2 we use native
204
204
  // splits (AppleScript) instead of tmux, keeping the companion process direct.
205
205
  const renderMode = detectGraphicsProtocol();
206
- // iTerm2 inside tmux: can't clear inline images (\x1b[2J intercepted by tmux).
207
- // Kitty/Ghostty work fine in tmux (a=d,d=a deletes placements).
208
- const useHd = renderMode.protocol === 'kitty'
209
- || (renderMode.protocol === 'iterm2' && !renderMode.inTmux);
206
+ // HD only works with Kitty protocol (Kitty/Ghostty/WezTerm) they have
207
+ // a=d,d=a to delete previous image placements between frames.
208
+ // iTerm2 inline images are permanent (can't be replaced or cleared),
209
+ // so animation is impossible — fall back to ANSI.
210
+ const useHd = renderMode.protocol === 'kitty';
210
211
  const paneWidth = useHd && size === 'full' ? 80 : size === 'full' ? 62 : size === 'tiny' ? 14 : 22;
211
212
 
212
213
  // If we're already the companion pane (spawned by tmux split), play inline
@@ -250,35 +251,6 @@ export async function run(args) {
250
251
  const modelFlag = model ? ` --model=${model}` : '';
251
252
  const companionCmd = `STORYMODE_COMPANION=1 npx --yes storymode-cli play ${id} --size=${size}${sextantFlag}${reactiveFlag}${noAiFlag}${modelFlag}`;
252
253
 
253
- // iTerm2 (not in tmux): use native split to keep HD working.
254
- // iTerm2 inline images can't be cleared through tmux passthrough,
255
- // so we avoid tmux entirely and use AppleScript to split the pane.
256
- // Use "write text" instead of "command" — the latter uses execve directly
257
- // (no shell, no PATH, no env vars). "write text" types into the new
258
- // pane's shell, which inherits everything.
259
- if (renderMode.protocol === 'iterm2' && !renderMode.inTmux) {
260
- try {
261
- execSync(`osascript -e '
262
- tell application "iTerm2"
263
- tell current session of current tab of current window
264
- set newSession to split vertically with default profile
265
- tell newSession
266
- write text "${companionCmd.replace(/"/g, '\\"')}"
267
- end tell
268
- end tell
269
- end tell
270
- '`, { stdio: 'ignore' });
271
- process.stderr.write(` Companion opened in iTerm2 pane (${size} HD/${renderMode.protocol}).\n`);
272
- process.stderr.write(' Close the pane or press q in it to stop.\n');
273
- } catch (err) {
274
- console.error(` iTerm2 split failed: ${err.message}`);
275
- console.error(' Falling back to tmux...');
276
- // Fall through to tmux path below
277
- renderMode.protocol = null;
278
- }
279
- if (renderMode.protocol) break;
280
- }
281
-
282
254
  // Open tmux side pane — install tmux if needed
283
255
  if (!await ensureTmux()) process.exit(1);
284
256
  const inTmux = !!process.env.TMUX;
package/src/player.mjs CHANGED
@@ -439,19 +439,18 @@ export async function playReactiveCompanion(animMap, opts = {}) {
439
439
  const delSeq = `\x1b_Ga=d,d=a\x1b\\`;
440
440
  stdout.write(hdInTmux ? wrapForTmux(delSeq) : delSeq);
441
441
  writeKitty(currentPngFrames[idx], null, imageRows || undefined, hdInTmux);
442
- } else {
443
- // iTerm2: clear to release previous images from memory
444
- stdout.write(fullscreen ? '\x1b[2J\x1b[H' : '\x1b8');
445
- if (!fullscreen) {
446
- // Clear the image area to release old images
447
- for (let i = 0; i < maxLines; i++) stdout.write('\x1b[K\n');
448
- stdout.write(fullscreen ? '\x1b[H' : '\x1b8');
442
+ // Kitty needs explicit cursor positioning after image
443
+ if (imageRows > 0) {
444
+ stdout.write(`\x1b[${imageRows + 1};1H`);
449
445
  }
450
- writeIterm2(currentPngFrames[idx], null, imageRows || undefined, hdInTmux);
451
- }
452
- // Move cursor below the image area for text rendering
453
- if (imageRows > 0) {
454
- stdout.write(`\x1b[${imageRows}B`);
446
+ } else {
447
+ // iTerm2: clear screen to release previous inline images from memory,
448
+ // then render new image. Cursor advances past the image automatically.
449
+ stdout.write('\x1b[2J\x1b[H');
450
+ // Use width=pane columns, let preserveAspectRatio size the height
451
+ const paneCols = process.stdout.columns || 80;
452
+ writeIterm2(currentPngFrames[idx], paneCols, null, hdInTmux);
453
+ stdout.write('\n'); // ensure cursor is on next line after image
455
454
  }
456
455
  } else {
457
456
  // --- ANSI fallback path ---