@clipit-ai/cli 0.2.7 → 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 +116 -20
- 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',
|
|
@@ -472,6 +472,7 @@ function usage() {
|
|
|
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,14 +1240,17 @@ 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);
|
|
@@ -1663,14 +1667,26 @@ async function contextCommand(config, options, action) {
|
|
|
1663
1667
|
throw Object.assign(new Error(`Unknown context command: ${action || ''}`), { exitCode: EXIT.USAGE });
|
|
1664
1668
|
}
|
|
1665
1669
|
|
|
1666
|
-
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) {
|
|
1667
1678
|
const canMutateParameters = parameters && typeof parameters === 'object' && !Array.isArray(parameters);
|
|
1668
1679
|
const hadExplicitVideoId = canMutateParameters && parameters.videoId !== undefined;
|
|
1669
1680
|
for (const field of ['videoId', 'clipId', 'projectId', 'sequenceId']) {
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
if (
|
|
1673
|
-
|
|
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;
|
|
1674
1690
|
}
|
|
1675
1691
|
}
|
|
1676
1692
|
}
|
|
@@ -1710,6 +1726,13 @@ function normalizeAgentExecuteResult(result) {
|
|
|
1710
1726
|
|
|
1711
1727
|
async function runTool(config, options, functionName) {
|
|
1712
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 });
|
|
1713
1736
|
const parameters = await readJsonOption(options.params || options['params-json']);
|
|
1714
1737
|
const context = await buildContext(config, options);
|
|
1715
1738
|
const payload = {
|
|
@@ -1726,16 +1749,10 @@ async function runTool(config, options, functionName) {
|
|
|
1726
1749
|
if (options[flag]) {
|
|
1727
1750
|
const value = String(options[flag]);
|
|
1728
1751
|
payload[field] = value;
|
|
1729
|
-
if (parameters && typeof parameters === 'object' && !Array.isArray(parameters) && parameters[field] === undefined) {
|
|
1730
|
-
parameters[field] = value;
|
|
1731
|
-
}
|
|
1732
1752
|
}
|
|
1733
1753
|
}
|
|
1734
|
-
applyContextToAgentPayload(payload, parameters, context);
|
|
1735
|
-
|
|
1736
|
-
confirmPaid(options, RUN_CONFIRMATION_LABELS[functionName]);
|
|
1737
|
-
}
|
|
1738
|
-
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);
|
|
1739
1756
|
const result = normalizeAgentExecuteResult(await apiFetch(config, options, 'POST', '/api/v1/agent/execute', payload));
|
|
1740
1757
|
const outputResult = estimate && result && typeof result === 'object' && !Array.isArray(result) && result.estimate === undefined
|
|
1741
1758
|
? { ...result, estimate }
|
|
@@ -2952,7 +2969,6 @@ function defaultExportStartBody(clipId) {
|
|
|
2952
2969
|
audioSampleRate: 48000,
|
|
2953
2970
|
},
|
|
2954
2971
|
format: 'mp4',
|
|
2955
|
-
includeAudio: true,
|
|
2956
2972
|
};
|
|
2957
2973
|
}
|
|
2958
2974
|
|
|
@@ -3046,6 +3062,43 @@ async function exportsCommand(config, options, action, args) {
|
|
|
3046
3062
|
throw Object.assign(new Error(`Unknown exports command: ${action || ''}`), { exitCode: EXIT.USAGE });
|
|
3047
3063
|
}
|
|
3048
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
|
+
|
|
3049
3102
|
async function putSignedUpload(uploadUrl, filePath, contentType, size, options, requiredHeaders) {
|
|
3050
3103
|
const headers = requiredHeaders && typeof requiredHeaders === 'object'
|
|
3051
3104
|
? Object.fromEntries(Object.entries(requiredHeaders).map(([key, value]) => [key, String(value)]))
|
|
@@ -3292,6 +3345,41 @@ function socialAccountIdPins(value) {
|
|
|
3292
3345
|
}));
|
|
3293
3346
|
}
|
|
3294
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
|
+
|
|
3295
3383
|
async function socialPostBody(config, options, scheduled) {
|
|
3296
3384
|
const clipId = requiredString(options['clip-id'], '--clip-id');
|
|
3297
3385
|
const platforms = socialPlatformList(options.platforms);
|
|
@@ -3308,6 +3396,12 @@ async function socialPostBody(config, options, scheduled) {
|
|
|
3308
3396
|
requestedExportId,
|
|
3309
3397
|
requireReadyToPublish: true,
|
|
3310
3398
|
});
|
|
3399
|
+
const enterpriseDelivery = await resolveEnterpriseSelectedDelivery(
|
|
3400
|
+
config,
|
|
3401
|
+
options,
|
|
3402
|
+
clipId,
|
|
3403
|
+
selectedExport.exportId,
|
|
3404
|
+
);
|
|
3311
3405
|
const accountsResponse = await apiFetch(config, options, 'GET', '/api/v1/social/accounts');
|
|
3312
3406
|
const connectedAccounts = Array.isArray(accountsResponse?.accounts)
|
|
3313
3407
|
? accountsResponse.accounts.filter((account) => account?.connected && typeof account?.accountId === 'string')
|
|
@@ -3343,6 +3437,7 @@ async function socialPostBody(config, options, scheduled) {
|
|
|
3343
3437
|
expectedSnapshotId: editorState.snapshotId,
|
|
3344
3438
|
expectedOutputObjectFingerprint: selectedExport.outputObjectFingerprint,
|
|
3345
3439
|
expectedAccountIds,
|
|
3440
|
+
enterpriseDeliverableId: enterpriseDelivery?.id,
|
|
3346
3441
|
publishExactCurrentArtifact: true,
|
|
3347
3442
|
});
|
|
3348
3443
|
if (scheduled) body.scheduledFor = requiredString(options.at, '--at');
|
|
@@ -3800,7 +3895,7 @@ async function handleMcpRequest(config, options, message) {
|
|
|
3800
3895
|
const result = await handleLocalMcpBillingTool(config, options, name, parameters);
|
|
3801
3896
|
return { jsonrpc: '2.0', id, result: mcpTextResult(result, Boolean(result?.error)) };
|
|
3802
3897
|
}
|
|
3803
|
-
applyContextToAgentPayload(payload, parameters, await buildContext(config, options));
|
|
3898
|
+
applyContextToAgentPayload(payload, parameters, await buildContext(config, options), tool);
|
|
3804
3899
|
if (!confirmed && mcpToolRequiresConfirmation(tool, name)) {
|
|
3805
3900
|
return {
|
|
3806
3901
|
jsonrpc: '2.0',
|
|
@@ -4374,6 +4469,7 @@ async function main() {
|
|
|
4374
4469
|
if (command === 'billing') return billing(config, options, subcommand, rest);
|
|
4375
4470
|
if (command === 'analytics') return analytics(config, options, subcommand, rest);
|
|
4376
4471
|
if (command === 'exports') return exportsCommand(config, options, subcommand, rest);
|
|
4472
|
+
if (command === 'deliverables') return deliverables(config, options, subcommand);
|
|
4377
4473
|
if (command === 'assets') return assets(config, options, subcommand, rest);
|
|
4378
4474
|
if (command === 'thumbnails') return thumbnails(config, options, subcommand, rest);
|
|
4379
4475
|
if (command === 'broll') return broll(config, options, subcommand, rest);
|