makaron-cli 0.14.10 โ†’ 0.15.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "makaron-cli",
3
- "version": "0.14.9",
3
+ "version": "0.15.0",
4
4
  "description": "Give Claude Code a creative agent. Pass complete creative requests and source media to Makaron Chat.",
5
5
  "author": {
6
6
  "name": "Versa AI",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "makaron-cli",
3
- "version": "0.14.9",
3
+ "version": "0.15.0",
4
4
  "description": "Give Codex a creative agent. Pass complete creative requests and source media to Makaron Chat.",
5
5
  "author": {
6
6
  "name": "Versa AI",
package/README.md CHANGED
@@ -54,6 +54,15 @@ npx makaron-cli credits
54
54
  npx makaron-cli credits --json
55
55
  ```
56
56
 
57
+ Every `chat` prints the credits it used when it finishes (on stderr, e.g.
58
+ `๐Ÿ’ณ 43 credits used (agent 24 ยท generate_image 19) ยท balance 1157`), and
59
+ `--json` results carry the same numbers in a `usage` object. To look it up later:
60
+ ```bash
61
+ npx makaron-cli responses get <runId> --pick credits_used # net credits of one run
62
+ npx makaron-cli usage --run <runId> # per-tool breakdown of one run
63
+ npx makaron-cli usage --limit 20 # recent usage rows (all sources)
64
+ ```
65
+
57
66
  ### Let a human claim your account
58
67
 
59
68
  After registering, generate a link for a human to link your API key to their account:
@@ -219,6 +228,12 @@ and supports up to 20 media items for one Makaron task. Batch planning remains
219
228
  the upstream orchestrator's responsibility: convert each plan into one manifest
220
229
  and start one independent Makaron task.
221
230
 
231
+ The server inspects imported image bytes, including extensionless Scene URLs.
232
+ HEIC/HEIF images are converted to durable JPEGs before entering the Media List;
233
+ use the returned media URL for generation and rendering. Compatible image URLs
234
+ (including transparent PNGs) and video source ranges are preserved. Conversion
235
+ or storage failure rejects the image import instead of saving an unusable URL.
236
+
222
237
  ### Export editable Remotion compositions
223
238
 
224
239
  Animated Remotion compositions are saved as editable timeline/code artifacts first. To materialize one into an MP4 that CLI, V, or another service can read, call the backend export worker:
@@ -342,6 +357,8 @@ npx makaron-cli responses get <runId> --pick project_url
342
357
  npx makaron-cli responses get <runId> --pick text # agent's text reply
343
358
  npx makaron-cli responses get <runId> --pick output # full output array
344
359
  npx makaron-cli responses get <runId> --pick status
360
+ npx makaron-cli responses get <runId> --pick credits_used # net credits this run cost
361
+ npx makaron-cli responses get <runId> --pick usage # per-tool credit breakdown
345
362
  ```
346
363
 
347
364
  ## Fallback: Direct tool calls (no project context)
@@ -439,6 +456,17 @@ type MakaronRunResponse = {
439
456
  project_url: string
440
457
  next_poll_after_ms?: number // suggested poll interval
441
458
  output: MakaronOutput[]
459
+ usage?: MakaronRunUsage // credits this run cost; present once the agent has stopped
460
+ }
461
+
462
+ type MakaronRunUsage = {
463
+ credits_charged: number // sum of debits
464
+ credits_refunded: number // e.g. a failed video reservation
465
+ credits_net: number // what the run actually cost (1 credit = $0.01)
466
+ input_tokens: number
467
+ output_tokens: number
468
+ entries: { tool_name: string; model: string | null; calls: number; credits: number }[]
469
+ balance?: number // account balance after this run
442
470
  }
443
471
 
444
472
  type MakaronOutput =
package/bin/makaron.mjs CHANGED
@@ -304,11 +304,14 @@ async function login() {
304
304
  console.error(` Token saved to ${AUTH_FILE}`);
305
305
  }
306
306
 
307
+ // Identifies makaron-cli to the API so credit usage is recorded with source=cli.
308
+ const CLIENT_HEADER = { 'X-Makaron-Client': `makaron-cli/${getCliVersion()}` };
309
+
307
310
  function getAuth() {
308
311
  const apiKey = process.env.MAKARON_API_KEY;
309
312
  if (apiKey) {
310
313
  return {
311
- headers: { 'Authorization': `Bearer ${apiKey}` },
314
+ headers: { ...CLIENT_HEADER, 'Authorization': `Bearer ${apiKey}` },
312
315
  baseUrl: process.env.MAKARON_URL || DEFAULT_URL,
313
316
  };
314
317
  }
@@ -322,16 +325,59 @@ function getAuth() {
322
325
  // Registered via `register --verify` (saved as _apiKey)
323
326
  if (auth._apiKey) {
324
327
  return {
325
- headers: { 'Authorization': `Bearer ${auth._apiKey}` },
328
+ headers: { ...CLIENT_HEADER, 'Authorization': `Bearer ${auth._apiKey}` },
326
329
  baseUrl: process.env.MAKARON_URL || auth._baseUrl || BASE_URL,
327
330
  };
328
331
  }
329
332
  return {
330
- headers: { 'Cookie': buildCookie(auth) },
333
+ headers: { ...CLIENT_HEADER, 'Cookie': buildCookie(auth) },
331
334
  baseUrl: process.env.MAKARON_URL || auth._baseUrl || BASE_URL,
332
335
  };
333
336
  }
334
337
 
338
+ // โ”€โ”€โ”€ Credit usage โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
339
+
340
+ /** One-line credit summary for a run's `usage` object (null when nothing was charged). */
341
+ function formatRunUsage(usage) {
342
+ if (!usage || typeof usage !== 'object') return null;
343
+ const entries = Array.isArray(usage.entries) ? usage.entries : [];
344
+ const net = Number(usage.credits_net ?? 0);
345
+ const refunded = Number(usage.credits_refunded ?? 0);
346
+ if (net === 0 && refunded === 0 && entries.length === 0) return null;
347
+ const parts = entries.filter(e => Number(e.credits) !== 0).map(e => `${e.tool_name} ${e.credits}`);
348
+ let line = `${net} credits used`;
349
+ if (parts.length) line += ` (${parts.join(' ยท ')})`;
350
+ if (refunded > 0) line += ` ยท ${refunded} refunded`;
351
+ if (typeof usage.balance === 'number') line += ` ยท balance ${usage.balance}`;
352
+ return line;
353
+ }
354
+
355
+ function printRunUsage(usage) {
356
+ const line = formatRunUsage(usage);
357
+ if (line) process.stderr.write(`๐Ÿ’ณ ${line}\n`);
358
+ }
359
+
360
+ /** Fetch the usage summary of a run (used after legacy --stream chats). */
361
+ async function fetchRunUsage(baseUrl, headers, runId) {
362
+ if (!runId) return null;
363
+ try {
364
+ const res = await fetch(`${baseUrl}/api/agent/run/${runId}?usage=true`, { headers });
365
+ if (!res.ok) return null;
366
+ const data = await res.json();
367
+ return data?.usage || null;
368
+ } catch {
369
+ return null;
370
+ }
371
+ }
372
+
373
+ /** Print credits charged by a direct MCP tool call (edit / video / music / analyze). */
374
+ function printMcpCharge(res) {
375
+ const charged = Number(res.headers.get('X-Credits-Charged'));
376
+ if (!Number.isFinite(charged) || charged <= 0) return;
377
+ const remaining = Number(res.headers.get('X-Credits-Remaining'));
378
+ process.stderr.write(`๐Ÿ’ณ ${charged} credits used${Number.isFinite(remaining) ? ` ยท balance ${remaining}` : ''}\n`);
379
+ }
380
+
335
381
  function normalizeRunResponse(data) {
336
382
  const projectId = data.projectId || data.project_id;
337
383
  if (projectId) {
@@ -467,7 +513,7 @@ Options:
467
513
  gpt-5.6-*-codex-subscription or
468
514
  grok-4.6-grok-subscription personal-plan route.
469
515
  --background, -b Submit and print a runId.
470
- --json Output structured JSON.
516
+ --json Output structured JSON (includes per-run "usage" credits).
471
517
  --stream Legacy live SSE stream.
472
518
  --help, -h Show this help.
473
519
 
@@ -811,6 +857,7 @@ async function pollRun(baseUrl, headers, runId, opts = {}) {
811
857
  if (data.result.error) process.stderr.write(`โŒ ${data.result.error}\n`);
812
858
  }
813
859
  process.stderr.write(`๐Ÿ”— ${APP_URL}/projects/${data.projectId}\n`);
860
+ printRunUsage(data.usage);
814
861
  }
815
862
 
816
863
  if (data.status === 'failed' || data.status === 'aborted') process.exit(1);
@@ -844,6 +891,8 @@ function applyPick(data, field) {
844
891
  case 'studio_run': return [...(data.output || [])].reverse().find(o => o.type === 'studio_run') || null;
845
892
  case 'studio_recipe': return [...(data.output || [])].reverse().find(o => o.type === 'studio_run')?.recipe || null;
846
893
  case 'project_url': return data.project_url || data.projectUrl || null;
894
+ case 'usage': return data.usage || null;
895
+ case 'credits_used': return typeof data.usage?.credits_net === 'number' ? data.usage.credits_net : null;
847
896
  case 'output': return data.output || [];
848
897
  case 'text': return data.output?.find(o => o.type === 'text')?.content || null;
849
898
  case 'status': return data.status;
@@ -895,7 +944,8 @@ async function watchRun(baseUrl, headers, runId, opts = {}) {
895
944
 
896
945
  // Check terminal status
897
946
  if (!data.incomplete && (data.status === 'completed' || data.status === 'failed' || data.status === 'aborted')) {
898
- if (jsonl) console.log(JSON.stringify({ event: 'done', status: data.status }));
947
+ if (jsonl) console.log(JSON.stringify({ event: 'done', status: data.status, ...(data.usage ? { usage: data.usage } : {}) }));
948
+ else printRunUsage(data.usage);
899
949
  if (data.status === 'failed' || data.status === 'aborted') process.exit(1);
900
950
  process.exit(0);
901
951
  }
@@ -1473,6 +1523,7 @@ async function callMcpTool(baseUrl, headers, toolName, args) {
1473
1523
  console.error('MCP tool failed:', data.result.content?.filter(c => c.type === 'text').map(c => c.text).join('\n') || 'Unknown tool error');
1474
1524
  process.exit(1);
1475
1525
  }
1526
+ printMcpCharge(res);
1476
1527
  return data.result;
1477
1528
  }
1478
1529
 
@@ -1982,6 +2033,7 @@ Commands:
1982
2033
  claim Get claim URL for human to link account
1983
2034
  login Log in to Makaron (human interactive)
1984
2035
  credits Show current credit balance
2036
+ usage [--run <id>] [--project <id>] Credit usage history (chat prints per-run usage)
1985
2037
  list (ls) List all projects
1986
2038
  project media <projectId> --json List timeline media for a project
1987
2039
  project media add <projectId> --type image --source-url <url>
@@ -2097,7 +2149,7 @@ function printHelp(topic, subtopic) {
2097
2149
  else console.log(`Responses commands:
2098
2150
  responses get <runId> Get status and output (JSON)
2099
2151
  responses get <runId> --wait Poll until completed
2100
- responses get <runId> --pick <field> Extract: first_image_url, first_video_url, project_url, output
2152
+ responses get <runId> --pick <field> Extract: first_image_url, first_video_url, project_url, output, usage, credits_used
2101
2153
  responses get <runId> --export-compositions --wait --pick first_video_url
2102
2154
  Export animated compositions before picking video URL
2103
2155
  responses get <runId> --materialize --wait --pick first_video_url
@@ -2109,6 +2161,16 @@ function printHelp(topic, subtopic) {
2109
2161
  console.log('Usage: makaron list');
2110
2162
  } else if (topic === 'credits' || topic === 'credit' || topic === 'balance') {
2111
2163
  console.log('Usage: makaron credits [--json]');
2164
+ } else if (topic === 'usage') {
2165
+ console.log(`Usage: makaron usage [--run <runId>] [--project <projectId>] [--limit <n>] [--offset <n>] [--json]
2166
+
2167
+ Lists credit usage rows for the current account (newest first).
2168
+ --run <runId> Only rows charged by one Agent run, with a summary total
2169
+ --project <id> Only rows charged inside one project
2170
+ --json Raw rows plus summary
2171
+
2172
+ Every makaron chat prints its own credit usage when it finishes; the same data
2173
+ is available as responses get <runId> --pick usage (or --pick credits_used).`);
2112
2174
  } else if (topic === 'project' || topic === 'projects') {
2113
2175
  if (subtopic === 'media') console.log(`Usage: makaron project media <projectId> [--json]
2114
2176
  makaron project media add <projectId> --type image --source-url <url> [--description <text>] [--json]
@@ -2244,6 +2306,50 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2244
2306
  console.log('Subscription: none');
2245
2307
  }
2246
2308
  }
2309
+ } else if (command === 'usage') {
2310
+ const { headers, baseUrl } = getAuth();
2311
+ const params = new URLSearchParams();
2312
+ let jsonOutput = false;
2313
+ for (let i = 1; i < args.length; i++) {
2314
+ if (args[i] === '--json') jsonOutput = true;
2315
+ else if (args[i] === '--run' && args[i + 1]) params.set('run_id', args[++i]);
2316
+ else if (args[i] === '--project' && args[i + 1]) params.set('project_id', args[++i]);
2317
+ else if (args[i] === '--limit' && args[i + 1]) params.set('limit', args[++i]);
2318
+ else if (args[i] === '--offset' && args[i + 1]) params.set('offset', args[++i]);
2319
+ else {
2320
+ process.stderr.write(`Unknown option: ${args[i]}\n`);
2321
+ printHelp('usage');
2322
+ process.exit(1);
2323
+ }
2324
+ }
2325
+ const res = await fetch(`${baseUrl}/api/billing/usage${params.size ? `?${params}` : ''}`, { headers });
2326
+ const data = await res.json().catch(() => null);
2327
+ if (!res.ok || !data) {
2328
+ const message = data?.error || data?.message || (res.ok ? 'Invalid response' : `HTTP ${res.status}`);
2329
+ process.stderr.write(`Failed to get usage: ${message}\n`);
2330
+ process.exit(1);
2331
+ }
2332
+ if (jsonOutput) {
2333
+ console.log(JSON.stringify(data));
2334
+ } else {
2335
+ const rows = data.usage || [];
2336
+ if (!rows.length) {
2337
+ console.log('No usage yet.');
2338
+ } else {
2339
+ const pad = (v, n, right = false) => { const t = String(v ?? ''); return right ? t.padStart(n) : t.padEnd(n); };
2340
+ console.log(`${pad('Date', 16)} ${pad('Credits', 8, true)} ${pad('Tool', 24)} ${pad('Model', 28)} ${pad('Source', 6)} Run`);
2341
+ for (const row of rows) {
2342
+ const when = row.created_at ? new Date(row.created_at).toISOString().slice(0, 16).replace('T', ' ') : '';
2343
+ const run = row.run_id ? String(row.run_id).slice(0, 8) : '';
2344
+ console.log(`${pad(when, 16)} ${pad(row.credits_charged, 8, true)} ${pad(row.tool_name, 24)} ${pad(row.model_used || '', 28)} ${pad(row.source || '', 6)} ${run}`);
2345
+ }
2346
+ }
2347
+ const total = formatRunUsage(data.summary);
2348
+ if (total) console.log(`\nTotal: ${total}`);
2349
+ if (data.attribution_available === false) {
2350
+ process.stderr.write('Per-run attribution is not enabled on this server yet.\n');
2351
+ }
2352
+ }
2247
2353
  } else if (command === 'create') {
2248
2354
  const { headers, baseUrl } = getAuth();
2249
2355
  const opts = { images: [], imageUrls: [] };
@@ -2544,7 +2650,7 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2544
2650
 
2545
2651
  if (useStream) {
2546
2652
  // Legacy SSE mode
2547
- const { results } = await streamAgent(baseUrl, headers, projectId, finalPrompt, {
2653
+ const { runId: streamRunId, results } = await streamAgent(baseUrl, headers, projectId, finalPrompt, {
2548
2654
  agentModel,
2549
2655
  uploadedVideoCount: uploadedTurnVideoCount,
2550
2656
  turnMediaCount: uploadedTurnMediaCount,
@@ -2553,6 +2659,7 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2553
2659
  for (const img of results.images) process.stderr.write(`๐Ÿ–ผ๏ธ Image: ${img.imageUrl}\n`);
2554
2660
  for (const d of results.designs) process.stderr.write(`๐ŸŽจ ${d.desc}\n`);
2555
2661
  process.stderr.write(`๐Ÿ”— ${APP_URL}/projects/${projectId}\n`);
2662
+ printRunUsage(await fetchRunUsage(baseUrl, headers, streamRunId));
2556
2663
  for (const task of results.animationTasks) await pollVideo(baseUrl, headers, task.taskId, task.snapshotId);
2557
2664
  for (const task of results.musicTasks) await pollMusic(baseUrl, headers, task.taskId);
2558
2665
  } else {
@@ -2673,7 +2780,7 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2673
2780
  console.log(`Responses commands:
2674
2781
  responses get <runId> Get status and output (JSON)
2675
2782
  responses get <runId> --wait Poll until completed
2676
- responses get <runId> --pick <field> Extract: first_image_url, first_video_url, project_url, output
2783
+ responses get <runId> --pick <field> Extract: first_image_url, first_video_url, project_url, output, usage, credits_used
2677
2784
  responses get <runId> --export-compositions --wait --pick first_video_url
2678
2785
  Export animated compositions before picking video URL
2679
2786
  responses get <runId> --materialize --wait --pick first_video_url
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "makaron-cli",
3
- "version": "0.14.10",
3
+ "version": "0.15.0",
4
4
  "description": "Talk to Makaron Agent from the terminal โ€” create projects, edit images, generate videos",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -50,6 +50,15 @@ npx makaron-cli credits
50
50
  npx makaron-cli credits --json
51
51
  ```
52
52
 
53
+ Every `chat` prints the credits it used when it finishes (on stderr, e.g.
54
+ `๐Ÿ’ณ 43 credits used (agent 24 ยท generate_image 19) ยท balance 1157`), and
55
+ `--json` results carry the same numbers in a `usage` object. To look it up later:
56
+ ```bash
57
+ npx makaron-cli responses get <runId> --pick credits_used # net credits of one run
58
+ npx makaron-cli usage --run <runId> # per-tool breakdown of one run
59
+ npx makaron-cli usage --limit 20 # recent usage rows (all sources)
60
+ ```
61
+
53
62
  ## Core Workflow
54
63
 
55
64
  ```bash
@@ -279,6 +288,8 @@ npx makaron-cli responses get <runId> --pick project_url
279
288
  npx makaron-cli responses get <runId> --pick text # agent's text reply
280
289
  npx makaron-cli responses get <runId> --pick output # full output array
281
290
  npx makaron-cli responses get <runId> --pick status
291
+ npx makaron-cli responses get <runId> --pick credits_used # net credits this run cost
292
+ npx makaron-cli responses get <runId> --pick usage # per-tool credit breakdown
282
293
  ```
283
294
 
284
295
  ## Fallback: Direct tool calls (no project context)
@@ -371,6 +382,17 @@ type MakaronRunResponse = {
371
382
  project_url: string
372
383
  next_poll_after_ms?: number // suggested poll interval
373
384
  output: MakaronOutput[]
385
+ usage?: MakaronRunUsage // credits this run cost; present once the agent has stopped
386
+ }
387
+
388
+ type MakaronRunUsage = {
389
+ credits_charged: number // sum of debits
390
+ credits_refunded: number // e.g. a failed video reservation
391
+ credits_net: number // what the run actually cost (1 credit = $0.01)
392
+ input_tokens: number
393
+ output_tokens: number
394
+ entries: { tool_name: string; model: string | null; calls: number; credits: number }[]
395
+ balance?: number // account balance after this run
374
396
  }
375
397
 
376
398
  type MakaronOutput =