storymode-cli 1.3.0 → 1.3.2
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 +1 -1
- package/src/api.mjs +3 -1
- package/src/browse.mjs +2 -1
- package/src/cli.mjs +40 -4
- package/src/mcp.mjs +2 -1
- package/src/player.mjs +4 -4
package/package.json
CHANGED
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
|
-
|
|
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
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 {
|
|
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
|
-
|
|
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 ---
|
|
@@ -167,8 +199,12 @@ export async function run(args) {
|
|
|
167
199
|
const reactive = !flags['no-reactive'];
|
|
168
200
|
|
|
169
201
|
// Auto-detect graphics protocol for HD rendering
|
|
202
|
+
// iTerm2 inside tmux can't clear previous inline images (no equivalent of
|
|
203
|
+
// Kitty's a=d,d=a — \x1b[2J is intercepted by tmux, not forwarded to iTerm2).
|
|
204
|
+
// So HD only works for: Kitty/Ghostty (any context) or iTerm2 (direct, no tmux).
|
|
170
205
|
const renderMode = detectGraphicsProtocol();
|
|
171
|
-
const useHd =
|
|
206
|
+
const useHd = renderMode.protocol === 'kitty'
|
|
207
|
+
|| (renderMode.protocol === 'iterm2' && !renderMode.inTmux);
|
|
172
208
|
const paneWidth = useHd && size === 'full' ? 80 : size === 'full' ? 62 : size === 'tiny' ? 14 : 22;
|
|
173
209
|
|
|
174
210
|
// If we're already the companion pane (spawned by tmux split), play inline
|
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
|
|
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
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
754
|
+
} catch { /* tmux command failed — fall 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 };
|