@clipit-ai/cli 0.2.6 → 0.2.8
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/README.md +6 -0
- package/bin/clipit.mjs +150 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -110,6 +110,9 @@ clipit exports wait <jobId> --stream
|
|
|
110
110
|
clipit exports download <jobId> --open
|
|
111
111
|
clipit exports cancel <jobId> --confirm --json
|
|
112
112
|
|
|
113
|
+
clipit deliverables create --export-id <exportId> --title "Client-ready clip" --confirm --json
|
|
114
|
+
clipit deliverables list --status selected --json
|
|
115
|
+
|
|
113
116
|
clipit assets list --json
|
|
114
117
|
clipit assets upload ./brand-logo.png --kind image --confirm --json
|
|
115
118
|
clipit assets delete <assetId> --confirm --json
|
|
@@ -131,6 +134,8 @@ clipit social get <postId> --json
|
|
|
131
134
|
clipit social cancel <postId> --confirm --json
|
|
132
135
|
```
|
|
133
136
|
|
|
137
|
+
For enterprise workspace profiles, create a ready delivery after the exact export completes, then send the returned `clientSelectionUrl` to the client. Publishing and scheduling remain blocked until that client selects the delivery. `clipit deliverables list --export-id <exportId> --json` shows the current delivery state; after selection, the social commands automatically pin the exact delivery ID and granted social account.
|
|
138
|
+
|
|
134
139
|
## Reliability And Spend Guards
|
|
135
140
|
|
|
136
141
|
The CLI retries only idempotent `GET` requests, with at most two retries for network failures, `429`, `502`, `503`, and `504`. `Retry-After` is honored up to 10 seconds. `POST`, `PATCH`, and `DELETE` requests are never retried. Pass `--no-retry` to disable GET retries.
|
|
@@ -275,6 +280,7 @@ Only use `--allow-custom-host` for hosts you control or trust. It permits the CL
|
|
|
275
280
|
| Credits | `credits balance`, `credits usage`, `credits estimate` | Estimate accepts `--metrics @file.json`. |
|
|
276
281
|
| Analytics | `analytics overview`, `analytics top-clips`, `analytics post` | `overview` returns aggregate and by-platform metrics. |
|
|
277
282
|
| Exports | `exports start/list/get/wait/download/cancel` | Export jobs poll through `/api/v1/exports/:jobId`; paid start requires `--confirm`. |
|
|
283
|
+
| Enterprise deliveries | `deliverables list/create` | Workspace profiles can deliver exact exports and inspect client selection; only the client can select. |
|
|
278
284
|
| Assets | `assets list/upload/delete` | Upload signs, PUTs the file to object storage, then finalizes the library asset. Delete requires `--confirm`. |
|
|
279
285
|
| Thumbnails and B-Roll | `thumbnails generate/get/list`, `broll plan/generate/list/get` | Paid generation commands require `--confirm`. |
|
|
280
286
|
| Social | `social accounts/post/schedule/posts/get/cancel` | Publish/schedule/cancel require `--confirm`; `x` maps to the server `twitter` platform. |
|
package/bin/clipit.mjs
CHANGED
|
@@ -9,7 +9,7 @@ import path from 'node:path';
|
|
|
9
9
|
import process from 'node:process';
|
|
10
10
|
import { fileURLToPath } from 'node:url';
|
|
11
11
|
|
|
12
|
-
const VERSION = '0.2.
|
|
12
|
+
const VERSION = '0.2.8';
|
|
13
13
|
const DEFAULT_BASE_URL = 'https://clipit.dev';
|
|
14
14
|
const DEFAULT_SCOPES = [
|
|
15
15
|
'clippy_agent',
|
|
@@ -466,12 +466,13 @@ function usage() {
|
|
|
466
466
|
' clipit mcp [stdio]',
|
|
467
467
|
' clipit run <functionName> [--params @file.json] [--clip-id id] [--video-id id] [--confirm] [--max-credits n]',
|
|
468
468
|
' clipit videos list|get|upload|abort-upload|import-url|transcribe|transcript|suggest-clips|delete ...',
|
|
469
|
-
' clipit clips list|get|delivery-state|create|update|render|download|delete ...',
|
|
469
|
+
' clipit clips list|get|delivery-state|create|update|initialize-snapshot|render|download|delete ...',
|
|
470
470
|
' clipit jobs get|wait <jobId>',
|
|
471
471
|
' clipit credits balance|usage|estimate ...',
|
|
472
472
|
' clipit billing capabilities|catalog|create-attempt|attempt|receipt|subscription ...',
|
|
473
473
|
' clipit analytics overview|top-clips|post ...',
|
|
474
474
|
' clipit exports start|list|get|wait|download|cancel ...',
|
|
475
|
+
' clipit deliverables list|create ...',
|
|
475
476
|
' clipit assets list|upload|delete ...',
|
|
476
477
|
' clipit thumbnails generate|get|list ...',
|
|
477
478
|
' clipit broll plan|generate|list|get ...',
|
|
@@ -1239,18 +1240,28 @@ async function enforceMaxCredits(config, options, commandKey, context = {}) {
|
|
|
1239
1240
|
}
|
|
1240
1241
|
}
|
|
1241
1242
|
|
|
1242
|
-
async function enforceRunMaxCredits(config, options, functionName, parameters = {}, payload = {}) {
|
|
1243
|
+
async function enforceRunMaxCredits(config, options, functionName, parameters = {}, payload = {}, catalogTool = null) {
|
|
1243
1244
|
const limit = maxCreditsLimit(options);
|
|
1244
1245
|
const needsConfirmationPreflight = !boolOption(options.confirm);
|
|
1245
1246
|
if (limit === null && !needsConfirmationPreflight) return null;
|
|
1246
1247
|
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1248
|
+
let tool = catalogTool;
|
|
1249
|
+
if (!tool) {
|
|
1250
|
+
const catalog = await apiFetch(config, options, 'GET', '/api/v1/agent/tools');
|
|
1251
|
+
const tools = Array.isArray(catalog?.tools) ? catalog.tools : Array.isArray(catalog) ? catalog : [];
|
|
1252
|
+
tool = tools.find((item) => item.name === functionName);
|
|
1253
|
+
}
|
|
1250
1254
|
const runEstimate = tool?.estimate ?? tool?.confirmation?.estimate ?? tool?.confirmation?.costEstimate ?? null;
|
|
1251
1255
|
const isMetered = Boolean(tool?.costBand && tool.costBand !== 'free');
|
|
1252
1256
|
const isMeteredExempt = RUN_METERED_CONFIRMATION_EXEMPTIONS.has(functionName);
|
|
1253
1257
|
|
|
1258
|
+
if (tool?.costBand === 'free') {
|
|
1259
|
+
if (tool?.confirmation?.required) {
|
|
1260
|
+
requireConfirm(options, `Running confirmation-gated tool ${functionName}`);
|
|
1261
|
+
}
|
|
1262
|
+
return null;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1254
1265
|
let staticEstimates = null;
|
|
1255
1266
|
if (!runEstimate && (limit !== null || needsConfirmationPreflight)) {
|
|
1256
1267
|
staticEstimates = await buildStaticRunEstimates(config, options, functionName, parameters, payload);
|
|
@@ -1656,14 +1667,26 @@ async function contextCommand(config, options, action) {
|
|
|
1656
1667
|
throw Object.assign(new Error(`Unknown context command: ${action || ''}`), { exitCode: EXIT.USAGE });
|
|
1657
1668
|
}
|
|
1658
1669
|
|
|
1659
|
-
function
|
|
1670
|
+
function toolAcceptsContextField(tool, field) {
|
|
1671
|
+
return Boolean(
|
|
1672
|
+
tool?.contextRequirements?.[field]
|
|
1673
|
+
|| tool?.parameters?.properties?.[field],
|
|
1674
|
+
);
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
function applyContextToAgentPayload(payload, parameters, context, tool) {
|
|
1660
1678
|
const canMutateParameters = parameters && typeof parameters === 'object' && !Array.isArray(parameters);
|
|
1661
1679
|
const hadExplicitVideoId = canMutateParameters && parameters.videoId !== undefined;
|
|
1662
1680
|
for (const field of ['videoId', 'clipId', 'projectId', 'sequenceId']) {
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
if (
|
|
1666
|
-
|
|
1681
|
+
const value = payload[field] || context[field];
|
|
1682
|
+
if (value) {
|
|
1683
|
+
if (!payload[field]) payload[field] = value;
|
|
1684
|
+
if (
|
|
1685
|
+
canMutateParameters
|
|
1686
|
+
&& parameters[field] === undefined
|
|
1687
|
+
&& toolAcceptsContextField(tool, field)
|
|
1688
|
+
) {
|
|
1689
|
+
parameters[field] = value;
|
|
1667
1690
|
}
|
|
1668
1691
|
}
|
|
1669
1692
|
}
|
|
@@ -1703,6 +1726,13 @@ function normalizeAgentExecuteResult(result) {
|
|
|
1703
1726
|
|
|
1704
1727
|
async function runTool(config, options, functionName) {
|
|
1705
1728
|
if (!functionName) throw Object.assign(new Error('Function name is required.'), { exitCode: EXIT.USAGE });
|
|
1729
|
+
if (RUN_CONFIRMATION_LABELS[functionName]) {
|
|
1730
|
+
confirmPaid(options, RUN_CONFIRMATION_LABELS[functionName]);
|
|
1731
|
+
}
|
|
1732
|
+
const catalog = await apiFetch(config, options, 'GET', '/api/v1/agent/tools');
|
|
1733
|
+
const tools = Array.isArray(catalog?.tools) ? catalog.tools : Array.isArray(catalog) ? catalog : [];
|
|
1734
|
+
const tool = tools.find((item) => item.name === functionName);
|
|
1735
|
+
if (!tool) throw Object.assign(new Error(`Tool not found: ${functionName}`), { exitCode: EXIT.USAGE });
|
|
1706
1736
|
const parameters = await readJsonOption(options.params || options['params-json']);
|
|
1707
1737
|
const context = await buildContext(config, options);
|
|
1708
1738
|
const payload = {
|
|
@@ -1719,16 +1749,10 @@ async function runTool(config, options, functionName) {
|
|
|
1719
1749
|
if (options[flag]) {
|
|
1720
1750
|
const value = String(options[flag]);
|
|
1721
1751
|
payload[field] = value;
|
|
1722
|
-
if (parameters && typeof parameters === 'object' && !Array.isArray(parameters) && parameters[field] === undefined) {
|
|
1723
|
-
parameters[field] = value;
|
|
1724
|
-
}
|
|
1725
1752
|
}
|
|
1726
1753
|
}
|
|
1727
|
-
applyContextToAgentPayload(payload, parameters, context);
|
|
1728
|
-
|
|
1729
|
-
confirmPaid(options, RUN_CONFIRMATION_LABELS[functionName]);
|
|
1730
|
-
}
|
|
1731
|
-
const estimate = await enforceRunMaxCredits(config, options, functionName, parameters, payload);
|
|
1754
|
+
applyContextToAgentPayload(payload, parameters, context, tool);
|
|
1755
|
+
const estimate = await enforceRunMaxCredits(config, options, functionName, parameters, payload, tool);
|
|
1732
1756
|
const result = normalizeAgentExecuteResult(await apiFetch(config, options, 'POST', '/api/v1/agent/execute', payload));
|
|
1733
1757
|
const outputResult = estimate && result && typeof result === 'object' && !Array.isArray(result) && result.estimate === undefined
|
|
1734
1758
|
? { ...result, estimate }
|
|
@@ -2718,6 +2742,12 @@ async function clips(config, options, action, args) {
|
|
|
2718
2742
|
}
|
|
2719
2743
|
if (action === 'update') {
|
|
2720
2744
|
if (!args[0]) throw Object.assign(new Error('Clip id is required.'), { exitCode: EXIT.USAGE });
|
|
2745
|
+
if (options.aspect !== undefined || options['aspect-ratio'] !== undefined) {
|
|
2746
|
+
throw Object.assign(
|
|
2747
|
+
new Error('clips update does not accept --aspect-ratio. Use clipit run setClipAspectRatio --clip-id <id> --params \'{"aspectRatio":"4:5"}\', then start an explicit render.'),
|
|
2748
|
+
{ exitCode: EXIT.USAGE },
|
|
2749
|
+
);
|
|
2750
|
+
}
|
|
2721
2751
|
const body = options.params
|
|
2722
2752
|
? await readJsonOption(String(options.params))
|
|
2723
2753
|
: {
|
|
@@ -2732,6 +2762,26 @@ async function clips(config, options, action, args) {
|
|
|
2732
2762
|
output(await apiFetch(config, options, 'PATCH', `/api/v1/clips/${encodeURIComponent(args[0])}`, body), options);
|
|
2733
2763
|
return;
|
|
2734
2764
|
}
|
|
2765
|
+
if (action === 'initialize-snapshot') {
|
|
2766
|
+
if (!args[0]) throw Object.assign(new Error('Clip id is required.'), { exitCode: EXIT.USAGE });
|
|
2767
|
+
const body = options.params
|
|
2768
|
+
? await readJsonOption(String(options.params))
|
|
2769
|
+
: compactObject({
|
|
2770
|
+
aspectRatio: options.aspect || options['aspect-ratio'],
|
|
2771
|
+
fitBackground: options['fit-background'],
|
|
2772
|
+
quality: options.quality,
|
|
2773
|
+
includeCaptions: options.captions === undefined ? undefined : boolOption(options.captions),
|
|
2774
|
+
captionStyle: options['caption-style'],
|
|
2775
|
+
});
|
|
2776
|
+
output(await apiFetch(
|
|
2777
|
+
config,
|
|
2778
|
+
options,
|
|
2779
|
+
'POST',
|
|
2780
|
+
`/api/v1/clips/${encodeURIComponent(args[0])}/editor-snapshot/initialize`,
|
|
2781
|
+
body,
|
|
2782
|
+
), options);
|
|
2783
|
+
return;
|
|
2784
|
+
}
|
|
2735
2785
|
if (action === 'render') {
|
|
2736
2786
|
if (!args[0]) throw Object.assign(new Error('Clip id is required.'), { exitCode: EXIT.USAGE });
|
|
2737
2787
|
const body = options.params
|
|
@@ -2919,7 +2969,6 @@ function defaultExportStartBody(clipId) {
|
|
|
2919
2969
|
audioSampleRate: 48000,
|
|
2920
2970
|
},
|
|
2921
2971
|
format: 'mp4',
|
|
2922
|
-
includeAudio: true,
|
|
2923
2972
|
};
|
|
2924
2973
|
}
|
|
2925
2974
|
|
|
@@ -3013,6 +3062,43 @@ async function exportsCommand(config, options, action, args) {
|
|
|
3013
3062
|
throw Object.assign(new Error(`Unknown exports command: ${action || ''}`), { exitCode: EXIT.USAGE });
|
|
3014
3063
|
}
|
|
3015
3064
|
|
|
3065
|
+
function enterpriseClientSelectionUrl(config, options, selectionPath = '/enterprise?tab=delivered') {
|
|
3066
|
+
return new URL(selectionPath, getBaseUrl(config, options)).toString();
|
|
3067
|
+
}
|
|
3068
|
+
|
|
3069
|
+
async function deliverables(config, options, action) {
|
|
3070
|
+
if (action === 'list') {
|
|
3071
|
+
const result = await apiFetch(config, options, 'GET', `/api/v1/deliverables${queryString({
|
|
3072
|
+
status: options.status,
|
|
3073
|
+
exportId: options['export-id'],
|
|
3074
|
+
limit: options.limit,
|
|
3075
|
+
offset: options.offset,
|
|
3076
|
+
})}`);
|
|
3077
|
+
output({
|
|
3078
|
+
...result,
|
|
3079
|
+
clientSelectionUrl: enterpriseClientSelectionUrl(config, options, result.clientSelectionPath),
|
|
3080
|
+
}, options);
|
|
3081
|
+
return;
|
|
3082
|
+
}
|
|
3083
|
+
if (action === 'create') {
|
|
3084
|
+
const exportId = requiredString(options['export-id'], '--export-id');
|
|
3085
|
+
const title = requiredString(options.title, '--title');
|
|
3086
|
+
requireConfirm(options, 'Delivering an export to the enterprise client');
|
|
3087
|
+
const result = await apiFetch(config, options, 'POST', '/api/v1/deliverables', {
|
|
3088
|
+
exportId,
|
|
3089
|
+
title,
|
|
3090
|
+
note: options.note,
|
|
3091
|
+
});
|
|
3092
|
+
output({
|
|
3093
|
+
...result,
|
|
3094
|
+
clientSelectionPath: '/enterprise?tab=delivered',
|
|
3095
|
+
clientSelectionUrl: enterpriseClientSelectionUrl(config, options),
|
|
3096
|
+
}, options);
|
|
3097
|
+
return;
|
|
3098
|
+
}
|
|
3099
|
+
throw Object.assign(new Error(`Unknown deliverables command: ${action || ''}`), { exitCode: EXIT.USAGE });
|
|
3100
|
+
}
|
|
3101
|
+
|
|
3016
3102
|
async function putSignedUpload(uploadUrl, filePath, contentType, size, options, requiredHeaders) {
|
|
3017
3103
|
const headers = requiredHeaders && typeof requiredHeaders === 'object'
|
|
3018
3104
|
? Object.fromEntries(Object.entries(requiredHeaders).map(([key, value]) => [key, String(value)]))
|
|
@@ -3259,6 +3345,41 @@ function socialAccountIdPins(value) {
|
|
|
3259
3345
|
}));
|
|
3260
3346
|
}
|
|
3261
3347
|
|
|
3348
|
+
async function resolveEnterpriseSelectedDelivery(config, options, clipId, exportId) {
|
|
3349
|
+
if (profileData(config, options).scope?.enterprise !== true) return null;
|
|
3350
|
+
const result = await apiFetch(
|
|
3351
|
+
config,
|
|
3352
|
+
options,
|
|
3353
|
+
'GET',
|
|
3354
|
+
`/api/v1/deliverables${queryString({ exportId, limit: 100, offset: 0 })}`,
|
|
3355
|
+
);
|
|
3356
|
+
const deliveries = Array.isArray(result?.deliverables) ? result.deliverables : [];
|
|
3357
|
+
const selected = deliveries.find((delivery) => (
|
|
3358
|
+
delivery?.clipId === clipId
|
|
3359
|
+
&& delivery?.exportId === exportId
|
|
3360
|
+
&& delivery?.status === 'selected'
|
|
3361
|
+
));
|
|
3362
|
+
if (selected?.id) return selected;
|
|
3363
|
+
const current = deliveries.find((delivery) => (
|
|
3364
|
+
delivery?.clipId === clipId && delivery?.exportId === exportId
|
|
3365
|
+
)) ?? null;
|
|
3366
|
+
const clientSelectionPath = result?.clientSelectionPath || '/enterprise?tab=delivered';
|
|
3367
|
+
throw Object.assign(
|
|
3368
|
+
new Error('The client has not selected this delivered export for publishing.'),
|
|
3369
|
+
{
|
|
3370
|
+
exitCode: EXIT.USAGE,
|
|
3371
|
+
data: {
|
|
3372
|
+
clipId,
|
|
3373
|
+
exportId,
|
|
3374
|
+
currentDelivery: current,
|
|
3375
|
+
clientSelectionPath,
|
|
3376
|
+
clientSelectionUrl: enterpriseClientSelectionUrl(config, options, clientSelectionPath),
|
|
3377
|
+
nextCommand: `clipit deliverables list --export-id ${shellQuote(exportId)} --json`,
|
|
3378
|
+
},
|
|
3379
|
+
},
|
|
3380
|
+
);
|
|
3381
|
+
}
|
|
3382
|
+
|
|
3262
3383
|
async function socialPostBody(config, options, scheduled) {
|
|
3263
3384
|
const clipId = requiredString(options['clip-id'], '--clip-id');
|
|
3264
3385
|
const platforms = socialPlatformList(options.platforms);
|
|
@@ -3275,6 +3396,12 @@ async function socialPostBody(config, options, scheduled) {
|
|
|
3275
3396
|
requestedExportId,
|
|
3276
3397
|
requireReadyToPublish: true,
|
|
3277
3398
|
});
|
|
3399
|
+
const enterpriseDelivery = await resolveEnterpriseSelectedDelivery(
|
|
3400
|
+
config,
|
|
3401
|
+
options,
|
|
3402
|
+
clipId,
|
|
3403
|
+
selectedExport.exportId,
|
|
3404
|
+
);
|
|
3278
3405
|
const accountsResponse = await apiFetch(config, options, 'GET', '/api/v1/social/accounts');
|
|
3279
3406
|
const connectedAccounts = Array.isArray(accountsResponse?.accounts)
|
|
3280
3407
|
? accountsResponse.accounts.filter((account) => account?.connected && typeof account?.accountId === 'string')
|
|
@@ -3310,6 +3437,7 @@ async function socialPostBody(config, options, scheduled) {
|
|
|
3310
3437
|
expectedSnapshotId: editorState.snapshotId,
|
|
3311
3438
|
expectedOutputObjectFingerprint: selectedExport.outputObjectFingerprint,
|
|
3312
3439
|
expectedAccountIds,
|
|
3440
|
+
enterpriseDeliverableId: enterpriseDelivery?.id,
|
|
3313
3441
|
publishExactCurrentArtifact: true,
|
|
3314
3442
|
});
|
|
3315
3443
|
if (scheduled) body.scheduledFor = requiredString(options.at, '--at');
|
|
@@ -3767,7 +3895,7 @@ async function handleMcpRequest(config, options, message) {
|
|
|
3767
3895
|
const result = await handleLocalMcpBillingTool(config, options, name, parameters);
|
|
3768
3896
|
return { jsonrpc: '2.0', id, result: mcpTextResult(result, Boolean(result?.error)) };
|
|
3769
3897
|
}
|
|
3770
|
-
applyContextToAgentPayload(payload, parameters, await buildContext(config, options));
|
|
3898
|
+
applyContextToAgentPayload(payload, parameters, await buildContext(config, options), tool);
|
|
3771
3899
|
if (!confirmed && mcpToolRequiresConfirmation(tool, name)) {
|
|
3772
3900
|
return {
|
|
3773
3901
|
jsonrpc: '2.0',
|
|
@@ -4341,6 +4469,7 @@ async function main() {
|
|
|
4341
4469
|
if (command === 'billing') return billing(config, options, subcommand, rest);
|
|
4342
4470
|
if (command === 'analytics') return analytics(config, options, subcommand, rest);
|
|
4343
4471
|
if (command === 'exports') return exportsCommand(config, options, subcommand, rest);
|
|
4472
|
+
if (command === 'deliverables') return deliverables(config, options, subcommand);
|
|
4344
4473
|
if (command === 'assets') return assets(config, options, subcommand, rest);
|
|
4345
4474
|
if (command === 'thumbnails') return thumbnails(config, options, subcommand, rest);
|
|
4346
4475
|
if (command === 'broll') return broll(config, options, subcommand, rest);
|