storymode-cli 1.3.0 → 1.3.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "storymode-cli",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "description": "Play AI-animated pixel art characters in your terminal",
5
5
  "type": "module",
6
6
  "bin": {
package/src/api.mjs CHANGED
@@ -25,7 +25,9 @@ function request(url) {
25
25
 
26
26
  export async function fetchGallery() {
27
27
  const { buffer } = await request(`${BASE}/gallery`);
28
- return JSON.parse(buffer.toString('utf-8'));
28
+ const data = JSON.parse(buffer.toString('utf-8'));
29
+ // Handle both new {featured_id, curated_ids, items} and legacy bare array
30
+ return Array.isArray(data) ? { featured_id: null, curated_ids: null, items: data } : data;
29
31
  }
30
32
 
31
33
  export async function fetchFrames(galleryId, { size, mode, animId } = {}) {
package/src/browse.mjs CHANGED
@@ -19,7 +19,8 @@ export async function browse() {
19
19
  const spinner = startSpinner('Fetching gallery...');
20
20
  let items;
21
21
  try {
22
- items = await fetchGallery();
22
+ const gallery = await fetchGallery();
23
+ items = gallery.items;
23
24
  } finally {
24
25
  stopSpinner(spinner);
25
26
  }
package/src/cli.mjs CHANGED
@@ -1,6 +1,9 @@
1
1
  import { execSync } from 'node:child_process';
2
2
  import { createInterface } from 'node:readline';
3
- import { fetchFrames, fetchCharacter, fetchPngFrames } from './api.mjs';
3
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ import { homedir } from 'node:os';
6
+ import { fetchFrames, fetchCharacter, fetchPngFrames, fetchGallery } from './api.mjs';
4
7
  import { playAnimation, playCompanion, playReactiveCompanion, showFrame, playHdAnimation, detectGraphicsProtocol } from './player.mjs';
5
8
  import { browse } from './browse.mjs';
6
9
  import { startMcpServer } from './mcp.mjs';
@@ -111,12 +114,41 @@ function parseFlags(args) {
111
114
  return { flags, positional };
112
115
  }
113
116
 
114
- const DEFAULT_CHARACTER = 3; // Zephyr
117
+ const DEFAULT_CHARACTER = 3; // Zephyr (fallback if server has no featured_id)
118
+ const CONFIG_CACHE_PATH = join(homedir(), '.storymode', 'cache', 'gallery-config.json');
119
+
120
+ /** Get the featured character ID: positional arg > cached config > fetch from server > fallback */
121
+ async function resolveFeaturedId() {
122
+ // Try cached config first
123
+ try {
124
+ if (existsSync(CONFIG_CACHE_PATH)) {
125
+ const cached = JSON.parse(readFileSync(CONFIG_CACHE_PATH, 'utf-8'));
126
+ if (cached.featured_id != null) return cached.featured_id;
127
+ }
128
+ } catch { /* ignore corrupt cache */ }
129
+
130
+ // Fetch from server and cache
131
+ try {
132
+ const gallery = await fetchGallery();
133
+ const cacheDir = join(homedir(), '.storymode', 'cache');
134
+ mkdirSync(cacheDir, { recursive: true });
135
+ writeFileSync(CONFIG_CACHE_PATH, JSON.stringify({
136
+ featured_id: gallery.featured_id,
137
+ curated_ids: gallery.curated_ids,
138
+ }));
139
+ if (gallery.featured_id != null) return gallery.featured_id;
140
+ } catch { /* network failure — fall through */ }
141
+
142
+ return DEFAULT_CHARACTER;
143
+ }
115
144
 
116
145
  export async function run(args) {
117
146
  const cmd = args[0];
118
147
  const { flags, positional } = parseFlags(args.slice(1));
119
- const id = positional[0] || (cmd === 'play' || cmd === 'companion' ? DEFAULT_CHARACTER : undefined);
148
+ let id = positional[0];
149
+ if (!id && (cmd === 'play' || cmd === 'companion')) {
150
+ id = await resolveFeaturedId();
151
+ }
120
152
 
121
153
  switch (cmd) {
122
154
  // --- Main command: reactive companion ---
package/src/mcp.mjs CHANGED
@@ -47,7 +47,8 @@ function findAnimation(animations, query) {
47
47
  async function handleToolCall(name, args) {
48
48
  switch (name) {
49
49
  case 'list_characters': {
50
- const items = await fetchGallery();
50
+ const gallery = await fetchGallery();
51
+ const items = gallery.items;
51
52
  if (!items.length) return 'Gallery is empty.';
52
53
  const chars = await Promise.all(
53
54
  items.map(item => fetchCharacter(item.id).catch(() => null))
package/src/player.mjs CHANGED
@@ -751,10 +751,10 @@ export function detectGraphicsProtocol() {
751
751
  // Map client_termname back to protocol
752
752
  if (clientTerm === 'xterm-ghostty') return { protocol: 'kitty', inTmux };
753
753
  if (clientTerm.includes('kitty')) return { protocol: 'kitty', inTmux };
754
- // For iTerm2, client_termname is usually "xterm-256color"check LC_TERMINAL
755
- const lcTerminal = process.env.LC_TERMINAL || '';
756
- if (lcTerminal === 'iTerm2') return { protocol: 'iterm2', inTmux };
757
- } catch { /* fall through to env-based detection */ }
754
+ } catch { /* tmux command failedfall through */ }
755
+ // LC_TERMINAL survives tmux env overwriting (iTerm2 sets it)
756
+ const lcTerminal = process.env.LC_TERMINAL || '';
757
+ if (lcTerminal === 'iTerm2') return { protocol: 'iterm2', inTmux };
758
758
  }
759
759
 
760
760
  if (kittyId) return { protocol: 'kitty', inTmux };