storyforge 0.4.19 → 0.4.21

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.
@@ -75,6 +75,47 @@ function listJsonlFiles(rootDir) {
75
75
  }
76
76
  return out;
77
77
  }
78
+ function parseClaudeFileDirect(file, seen) {
79
+ let content;
80
+ try {
81
+ content = fs.readFileSync(file, "utf-8");
82
+ } catch {
83
+ return [];
84
+ }
85
+ const out = [];
86
+ for (const raw of content.split(/\r?\n/)) {
87
+ if (!raw.trim()) continue;
88
+ let obj;
89
+ try {
90
+ obj = JSON.parse(raw);
91
+ } catch {
92
+ continue;
93
+ }
94
+ const usage = obj?.message?.usage;
95
+ if (!usage) continue;
96
+ const messageId = obj?.message?.id;
97
+ if (messageId) {
98
+ if (seen.has(messageId)) continue;
99
+ seen.add(messageId);
100
+ }
101
+ const model = String(obj?.message?.model ?? "").toLowerCase();
102
+ if (!model) continue;
103
+ const tsRaw = obj?.timestamp ?? obj?.ts;
104
+ const ts = tsRaw ? new Date(tsRaw).getTime() : Date.now();
105
+ if (!Number.isFinite(ts)) continue;
106
+ out.push({
107
+ ts,
108
+ model,
109
+ usage: {
110
+ input: usage.input_tokens ?? 0,
111
+ cacheRead: usage.cache_read_input_tokens ?? 0,
112
+ cacheCreate: usage.cache_creation_input_tokens ?? 0,
113
+ output: usage.output_tokens ?? 0
114
+ }
115
+ });
116
+ }
117
+ return out;
118
+ }
78
119
  function parseCodexFile(file) {
79
120
  let content;
80
121
  try {
@@ -115,13 +156,17 @@ function parseCodexFile(file) {
115
156
  return out;
116
157
  }
117
158
  async function gatherCliUsage() {
159
+ const CCUSAGE_TIMEOUT_MS = 8e3;
118
160
  const claudeEntries = [];
119
161
  let claudeFileCount = 0;
162
+ let claudeError;
120
163
  try {
121
- const blocks = await loadSessionBlockData(
122
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
123
- { offline: true }
124
- );
164
+ const blocks = await Promise.race([
165
+ loadSessionBlockData(),
166
+ new Promise(
167
+ (_, reject) => setTimeout(() => reject(new Error(`ccusage timed out after ${CCUSAGE_TIMEOUT_MS}ms (LiteLLM pricing fetch likely blocked)`)), CCUSAGE_TIMEOUT_MS)
168
+ )
169
+ ]);
125
170
  for (const block of blocks) {
126
171
  const entries = block.entries ?? [];
127
172
  for (const e of entries) {
@@ -144,7 +189,15 @@ async function gatherCliUsage() {
144
189
  }
145
190
  claudeFileCount = listJsonlFiles(path.join(os.homedir(), ".claude", "projects")).length;
146
191
  } catch (err) {
147
- void err;
192
+ claudeError = `ccusage unavailable, using fallback parser (${err.message?.slice(0, 80) ?? "unknown"})`;
193
+ console.error("[cli-usage] ccusage loader failed, falling back:", claudeError);
194
+ const claudeDir = path.join(os.homedir(), ".claude", "projects");
195
+ const claudeFiles = listJsonlFiles(claudeDir);
196
+ claudeFileCount = claudeFiles.length;
197
+ const seen = /* @__PURE__ */ new Set();
198
+ for (const f of claudeFiles) {
199
+ claudeEntries.push(...parseClaudeFileDirect(f, seen));
200
+ }
148
201
  }
149
202
  const codexDir = path.join(os.homedir(), ".codex", "sessions");
150
203
  const codexFiles = listJsonlFiles(codexDir);
@@ -214,7 +267,8 @@ async function gatherCliUsage() {
214
267
  sources: {
215
268
  claudeFiles: claudeFileCount,
216
269
  codexFiles: codexFiles.length,
217
- skipped: 0
270
+ skipped: 0,
271
+ claudeError
218
272
  }
219
273
  };
220
274
  }
package/dist/index.js CHANGED
@@ -839,6 +839,17 @@ async function devCommand(options) {
839
839
  if (env.loaded.length > 0) {
840
840
  log.info(`Loaded env: ${env.loaded.join(", ")} from ${env.sources.join(", ")}`);
841
841
  }
842
+ void (async () => {
843
+ try {
844
+ const { gatherCliUsage, CLI_USAGE_PARSER_VERSION } = await import("./cli-usage-P3LK7WEE.js");
845
+ const report = await gatherCliUsage();
846
+ const g = global;
847
+ g.__forgeCliUsageCache = { at: Date.now(), ver: CLI_USAGE_PARSER_VERSION, data: report };
848
+ log.info(`[cli-usage] warmed cache \xB7 ${report.sources.claudeFiles} claude, ${report.sources.codexFiles} codex sessions${report.sources.claudeError ? ` (claude error: ${report.sources.claudeError.slice(0, 80)})` : ""}`);
849
+ } catch (err) {
850
+ log.warn(`[cli-usage] warm-up failed: ${err.message}`);
851
+ }
852
+ })();
842
853
  const counts = scanAssets(dir);
843
854
  const total = Object.values(counts).reduce((a, b) => a + b, 0);
844
855
  if (total > 0) {
@@ -866,7 +877,7 @@ async function devCommand(options) {
866
877
  const pathname = url.pathname;
867
878
  if (pathname === "/api/cli-usage") {
868
879
  try {
869
- const { gatherCliUsage, CLI_USAGE_PARSER_VERSION } = await import("./cli-usage-NX33FYQ6.js");
880
+ const { gatherCliUsage, CLI_USAGE_PARSER_VERSION } = await import("./cli-usage-P3LK7WEE.js");
870
881
  const g = global;
871
882
  const now = Date.now();
872
883
  if (g.__forgeCliUsageCache && g.__forgeCliUsageCache.ver === CLI_USAGE_PARSER_VERSION && now - g.__forgeCliUsageCache.at < 3e4) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "storyforge",
3
- "version": "0.4.19",
3
+ "version": "0.4.21",
4
4
  "description": "StoryForge — local bridge for the Forge video production web app. Zero runtime dependencies.",
5
5
  "type": "module",
6
6
  "bin": {