makaron-cli 0.14.11 โ 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.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +28 -0
- package/bin/makaron.mjs +115 -8
- package/package.json +1 -1
- package/skills/makaron/SKILL.md +22 -0
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
package/skills/makaron/SKILL.md
CHANGED
|
@@ -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 =
|