@clipit-ai/cli 0.2.10 → 0.3.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/LICENSE +0 -0
- package/README.md +23 -6
- package/bin/clipit.mjs +497 -42
- package/package.json +1 -1
package/LICENSE
CHANGED
|
File without changes
|
package/README.md
CHANGED
|
@@ -59,6 +59,10 @@ clipit auth open-settings
|
|
|
59
59
|
clipit auth profiles --json
|
|
60
60
|
clipit auth use <profile>
|
|
61
61
|
clipit skills list --json
|
|
62
|
+
clipit skills describe <skill-or-bundle-id> --json
|
|
63
|
+
clipit skills manifest --json
|
|
64
|
+
clipit media-guides list --json
|
|
65
|
+
clipit media-guides describe <guide-id> --json
|
|
62
66
|
clipit tools list --json
|
|
63
67
|
clipit tools describe <functionName> --json
|
|
64
68
|
clipit run <functionName> --params @params.json --max-credits 25 --json
|
|
@@ -110,7 +114,7 @@ clipit exports wait <jobId> --stream
|
|
|
110
114
|
clipit exports download <jobId> --open
|
|
111
115
|
clipit exports cancel <jobId> --confirm --json
|
|
112
116
|
|
|
113
|
-
clipit deliverables create --export-id <exportId> --
|
|
117
|
+
clipit deliverables create --export-id <exportId> --confirm --json
|
|
114
118
|
clipit deliverables list --status selected --json
|
|
115
119
|
|
|
116
120
|
clipit assets list --json
|
|
@@ -204,7 +208,14 @@ clipit workflow approve <jobId> --approval-id <approvalId> --decision cheaper
|
|
|
204
208
|
|
|
205
209
|
## MCP Stdio Bridge
|
|
206
210
|
|
|
207
|
-
Use `clipit mcp stdio` from MCP-compatible clients that can launch a local command. It speaks standard MCP `Content-Length` framed JSON-RPC over stdio
|
|
211
|
+
Use `clipit mcp stdio` from MCP-compatible clients that can launch a local command. It speaks standard MCP `Content-Length` framed JSON-RPC over stdio and exposes both executable ClipIt tools and read-only capability resources through the authenticated CLI profile. Tool calls delegate to `/api/v1/agent/execute`; capability resources are resolved from the live permission-scoped agent registry.
|
|
212
|
+
|
|
213
|
+
Supported MCP methods include `initialize`, `tools/list`, `tools/call`, `resources/list`, and `resources/read`. The resource catalog uses stable URIs:
|
|
214
|
+
|
|
215
|
+
- `clipit://instructions` — the installed agent operating contract
|
|
216
|
+
- `clipit://manifest` — versions, checksums, available capabilities, and the current model estate
|
|
217
|
+
- `clipit://skills/<id>` — one full skill or workflow-bundle guide
|
|
218
|
+
- `clipit://media-guides/<id>` — one provider-aware media prompting guide
|
|
208
219
|
|
|
209
220
|
Paid generation, publishing, deletion, and other metered tools still require an explicit confirmation step. If a tool call may spend credits or mutate user-visible ClipIt state, the MCP bridge returns a `requiresConfirmation` result instead of executing it; retry only after the user approves and include `confirmed: true` in the tool arguments.
|
|
210
221
|
|
|
@@ -218,7 +229,11 @@ clipit links clip <clipId> --download --json
|
|
|
218
229
|
|
|
219
230
|
## Agent Skills
|
|
220
231
|
|
|
221
|
-
Install credential-free instructions for a shell-capable agent. The CLI fetches server-rendered instructions from `/api/v1/agent/instructions?target=<agent>&format=json` when authenticated.
|
|
232
|
+
Install credential-free instructions for a shell-capable agent. The CLI fetches server-rendered instructions from `/api/v1/agent/instructions?target=<agent>&format=json` when authenticated. A server-rendered install contains a concise root `SKILL.md` plus permission-scoped `references/` for editorial workflow, state/time/trust rules, the capability manifest, relevant skills and workflows, and media prompting guides. The agent loads those references progressively as the edit requires them instead of carrying the entire platform manual in every turn.
|
|
233
|
+
|
|
234
|
+
The root skill establishes one execution owner for mutating work and a repeatable edit loop: inspect, plan, edit, review, render, and deliver. Live tool schemas, the capability manifest, provider contracts, and server estimates remain authoritative over copied examples or remembered model behavior.
|
|
235
|
+
|
|
236
|
+
If the fetch fails or the CLI is offline, the installer writes a generic fallback skill and records the source in `SKILL.meta.json` next to `SKILL.md`.
|
|
222
237
|
|
|
223
238
|
```bash
|
|
224
239
|
clipit agent install codex
|
|
@@ -227,7 +242,7 @@ clipit agent install hermes
|
|
|
227
242
|
clipit agent install generic
|
|
228
243
|
```
|
|
229
244
|
|
|
230
|
-
Inspect or remove generated skills:
|
|
245
|
+
Inspect, refresh, or remove generated skills:
|
|
231
246
|
|
|
232
247
|
```bash
|
|
233
248
|
clipit agent doctor codex --json
|
|
@@ -237,6 +252,8 @@ clipit agent uninstall codex
|
|
|
237
252
|
clipit agent print-skill generic
|
|
238
253
|
```
|
|
239
254
|
|
|
255
|
+
`agent doctor` validates the generated root skill and its reference files against the sidecar hashes. Install/update also rejects a live pack whose declared minimum CLI version is newer than the running CLI and tells the operator to install `@clipit-ai/cli@latest`. After reconnecting, run `agent update` to replace an offline fallback or refresh a stale capability-manifest version.
|
|
256
|
+
|
|
240
257
|
## Profiles And Environment
|
|
241
258
|
|
|
242
259
|
The default profile is `default`.
|
|
@@ -274,7 +291,7 @@ Only use `--allow-custom-host` for hosts you control or trust. It permits the CL
|
|
|
274
291
|
| Area | Commands | Notes |
|
|
275
292
|
| --- | --- | --- |
|
|
276
293
|
| Auth and setup | `login`, `setup`, `logout`, `doctor`, `auth status`, `auth set-key`, `auth open-settings`, `auth profiles`, `auth use` | Browser-link login, manual key fallback, and fail-closed named workspace profiles. |
|
|
277
|
-
| Agent tools | `skills list`, `
|
|
294
|
+
| Agent tools | `skills list/describe/manifest`, `media-guides list/describe`, `tools list/describe`, `run`, `ask`, `workflow status/wait/approve`, `mcp stdio` | Permission-scoped discovery, provider-aware prompt guidance, direct execution, Clippy workflows, and MCP tools/resources. |
|
|
278
295
|
| Context and navigation | `context use/show/clear/build`, `open`, `links`, `examples` | Persist active IDs and open ClipIt review surfaces. |
|
|
279
296
|
| Videos and clips | `videos list/get/upload/abort-upload/import-url/transcribe/transcript/suggest-clips/delete`, `clips list/get/create/update/render/download/delete`, `jobs get/wait` | Core media and render job helpers. |
|
|
280
297
|
| Credits | `credits balance`, `credits usage`, `credits estimate` | Estimate accepts `--metrics @file.json`. |
|
|
@@ -284,4 +301,4 @@ Only use `--allow-custom-host` for hosts you control or trust. It permits the CL
|
|
|
284
301
|
| Assets | `assets list/upload/delete` | Upload signs, PUTs the file to object storage, then finalizes the library asset. Delete requires `--confirm`. |
|
|
285
302
|
| Thumbnails and B-Roll | `thumbnails generate/get/list`, `broll plan/generate/list/get` | Paid generation commands require `--confirm`. |
|
|
286
303
|
| Social | `social accounts/post/schedule/posts/get/cancel` | Publish/schedule/cancel require `--confirm`; `x` maps to the server `twitter` platform. |
|
|
287
|
-
| Agent skills | `agent install/update/uninstall/doctor/print-skill/list` |
|
|
304
|
+
| Agent skills | `agent install/update/uninstall/doctor/print-skill/list` | Installs a progressive multi-file editor skill; `doctor` validates source, root content, references, and capability-manifest freshness. |
|
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.
|
|
12
|
+
const VERSION = '0.3.0';
|
|
13
13
|
const DEFAULT_BASE_URL = 'https://clipit.dev';
|
|
14
14
|
const DEFAULT_SCOPES = [
|
|
15
15
|
'clippy_agent',
|
|
@@ -53,17 +53,17 @@ const API_REQUEST_TIMEOUT_MS = readPositiveIntegerEnv(
|
|
|
53
53
|
const MAX_CREDITS_ESTIMATE_MAP = Object.freeze({
|
|
54
54
|
'exports start': [{ operationType: 'lambda_render', provider: 'aws_lambda', modelId: 'remotion-4.0', metrics: 'remotion-render' }],
|
|
55
55
|
'thumbnails generate': [{ operationType: 'thumbnail_generation', provider: 'replicate', modelId: 'openai/gpt-image-2', metrics: 'one-generation' }],
|
|
56
|
-
'broll plan': [{ operationType: 'ai_chat', provider: 'openrouter', modelId: '
|
|
56
|
+
'broll plan': [{ operationType: 'ai_chat', provider: 'openrouter', modelId: 'openai/gpt-5.6-terra-pro', metrics: 'broll-plan' }],
|
|
57
57
|
'broll generate': [
|
|
58
58
|
{ operationType: 'image_generation', provider: 'replicate', modelId: 'openai/gpt-image-2', metrics: 'broll-images' },
|
|
59
|
-
{ operationType: 'video_generation', provider: '
|
|
59
|
+
{ operationType: 'video_generation', provider: 'fal', modelId: 'minimax/h3-max/image-to-video', metrics: 'broll-video' },
|
|
60
60
|
],
|
|
61
61
|
'clips create': [{ operationType: 'clip', provider: 'deepgram', metrics: 'clip-create' }],
|
|
62
62
|
'clips render': [{ operationType: 'lambda_render', provider: 'aws_lambda', modelId: 'remotion-4.0', metrics: 'remotion-render' }],
|
|
63
63
|
'videos upload': [{ operationType: 'video_storage', provider: 'railway_s3', metrics: 'video-upload-storage' }],
|
|
64
64
|
'videos import-url': [{ operationType: null, provider: null, metrics: 'url-import' }],
|
|
65
65
|
'videos transcribe': [{ operationType: 'transcription', provider: 'deepgram', modelId: 'nova-3', metrics: 'transcription-video' }],
|
|
66
|
-
'videos suggest-clips': [{ operationType: 'ai_chat', provider: 'openrouter', modelId: '
|
|
66
|
+
'videos suggest-clips': [{ operationType: 'ai_chat', provider: 'openrouter', modelId: 'openai/gpt-5.6-sol-pro', metrics: 'suggest-clips' }],
|
|
67
67
|
'social post': [{ operationType: 'social_post', provider: 'zernio', metrics: 'social-platforms' }],
|
|
68
68
|
'social schedule': [{ operationType: 'social_post', provider: 'zernio', metrics: 'social-platforms' }],
|
|
69
69
|
});
|
|
@@ -77,9 +77,9 @@ const RUN_MAX_CREDITS_ESTIMATE_MAP = Object.freeze({
|
|
|
77
77
|
generateImage: [{ operationType: 'image_generation', provider: 'replicate', modelId: 'openai/gpt-image-2', metrics: 'one-generation' }],
|
|
78
78
|
generateBRoll: MAX_CREDITS_ESTIMATE_MAP['broll generate'],
|
|
79
79
|
generateVoiceover: [{ operationType: 'tts_generation', provider: 'replicate', modelId: 'google/gemini-3.1-flash-tts', metrics: 'tts-provider-cost' }],
|
|
80
|
-
generateMusicBed: [{ operationType: 'music_generation', provider: 'replicate', modelId: 'minimax/music-2.
|
|
81
|
-
generateVideoAlter: [{ operationType: 'video_alter_generation', provider: '
|
|
82
|
-
generatePlatformCaption: [{ operationType: 'ai_chat', provider: 'openrouter', modelId: '
|
|
80
|
+
generateMusicBed: [{ operationType: 'music_generation', provider: 'replicate', modelId: 'minimax/music-2.6', metrics: 'one-generation' }],
|
|
81
|
+
generateVideoAlter: [{ operationType: 'video_alter_generation', provider: 'fal', modelId: 'google/gemini-omni-flash/v1.1/edit', metrics: 'video-alter-provider-cost' }],
|
|
82
|
+
generatePlatformCaption: [{ operationType: 'ai_chat', provider: 'openrouter', modelId: 'openai/gpt-5.6-terra-pro', metrics: 'platform-caption' }],
|
|
83
83
|
applyHookToFront: REMOTION_RUN_ESTIMATE,
|
|
84
84
|
addLibraryAssetToClip: REMOTION_RUN_ESTIMATE,
|
|
85
85
|
renderClipWithRemotion: REMOTION_RUN_ESTIMATE,
|
|
@@ -441,6 +441,29 @@ function hashText(value) {
|
|
|
441
441
|
return createHash('sha256').update(String(value)).digest('hex');
|
|
442
442
|
}
|
|
443
443
|
|
|
444
|
+
function compareSemver(left, right) {
|
|
445
|
+
const parse = (value) => {
|
|
446
|
+
const match = String(value || '').match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/);
|
|
447
|
+
if (!match) return null;
|
|
448
|
+
return {
|
|
449
|
+
numbers: match.slice(1, 4).map(Number),
|
|
450
|
+
prerelease: match[4] || null,
|
|
451
|
+
};
|
|
452
|
+
};
|
|
453
|
+
const current = parse(left);
|
|
454
|
+
const minimum = parse(right);
|
|
455
|
+
if (!current || !minimum) return null;
|
|
456
|
+
for (let index = 0; index < current.numbers.length; index += 1) {
|
|
457
|
+
if (current.numbers[index] !== minimum.numbers[index]) {
|
|
458
|
+
return current.numbers[index] > minimum.numbers[index] ? 1 : -1;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
if (current.prerelease === minimum.prerelease) return 0;
|
|
462
|
+
if (!current.prerelease) return 1;
|
|
463
|
+
if (!minimum.prerelease) return -1;
|
|
464
|
+
return current.prerelease.localeCompare(minimum.prerelease, undefined, { numeric: true });
|
|
465
|
+
}
|
|
466
|
+
|
|
444
467
|
function usage() {
|
|
445
468
|
return [
|
|
446
469
|
'ClipIt CLI',
|
|
@@ -458,6 +481,9 @@ function usage() {
|
|
|
458
481
|
' clipit context use [--video-id id] [--clip-id id] [--project-id id] [--sequence-id id]',
|
|
459
482
|
' clipit context show|clear|build [--json]',
|
|
460
483
|
' clipit skills list [--json]',
|
|
484
|
+
' clipit skills describe <skill-or-workflow> [--json]',
|
|
485
|
+
' clipit skills manifest [--json]',
|
|
486
|
+
' clipit media-guides list|describe [guide-id] [--json]',
|
|
461
487
|
' clipit tools list [--skill clip] [--json]',
|
|
462
488
|
' clipit tools describe <functionName>',
|
|
463
489
|
' clipit ask "<prompt>" [--video-id id] [--clip-id id] [--conversation-id id] [--quick] [--auto-confirm-costly] [--stream] [--json]',
|
|
@@ -465,6 +491,7 @@ function usage() {
|
|
|
465
491
|
' clipit workflow approve <jobId> --approval-id id [--decision approved|cheaper|cancelled]',
|
|
466
492
|
' clipit mcp [stdio]',
|
|
467
493
|
' clipit run <functionName> [--params @file.json] [--clip-id id] [--video-id id] [--confirm] [--max-credits n]',
|
|
494
|
+
' clipit run setCaptionStyle --clip-id id --params @caption-style.json # exact style or bounded captionDirective; no render',
|
|
468
495
|
' clipit videos list|get|upload|abort-upload|import-url|transcribe|transcript|suggest-clips|delete ...',
|
|
469
496
|
' clipit clips list|get|delivery-state|create|update|initialize-snapshot|render|download|delete ...',
|
|
470
497
|
' clipit jobs get|wait <jobId>',
|
|
@@ -485,6 +512,19 @@ function usage() {
|
|
|
485
512
|
}
|
|
486
513
|
|
|
487
514
|
function commandUsage(command, subcommand) {
|
|
515
|
+
if (command === 'run' && subcommand === 'setCaptionStyle') {
|
|
516
|
+
return [
|
|
517
|
+
'Usage:',
|
|
518
|
+
' clipit run setCaptionStyle --clip-id <id> --params <json|@file> --json',
|
|
519
|
+
'',
|
|
520
|
+
'The params object must pin expectedEditorVersion, expectedEditorStateHash, and expectedClipSettingsRevision,',
|
|
521
|
+
'then supply exactly one of captionStyle or captionDirective.',
|
|
522
|
+
'',
|
|
523
|
+
'captionDirective accepts the strict form: caption size <50-300>% <auto|single|single-line|stacked>.',
|
|
524
|
+
'Example: "caption size 200% single" changes only fontSizeScale to 2 and captionLineMode to single-line;',
|
|
525
|
+
'it preserves wordsPerLine and every other current caption-style field.',
|
|
526
|
+
].join('\n');
|
|
527
|
+
}
|
|
488
528
|
if (command === 'clips' && subcommand === 'initialize-snapshot') {
|
|
489
529
|
return [
|
|
490
530
|
'Usage:',
|
|
@@ -499,7 +539,7 @@ function commandUsage(command, subcommand) {
|
|
|
499
539
|
' --quality <quality> standard, high, or 4k',
|
|
500
540
|
' --captions <true|false> Explicitly enable or disable captions',
|
|
501
541
|
' --caption-style <style> bold, minimal, neon, or classic',
|
|
502
|
-
' --params <json|@file> Supply the full initializer request, including captionPresetId',
|
|
542
|
+
' --params <json|@file> Supply the full initializer request, including captionPresetId or a strict rich captionStyle object',
|
|
503
543
|
' --profile <name> Named ClipIt credential profile',
|
|
504
544
|
' --json Emit machine-readable JSON',
|
|
505
545
|
'',
|
|
@@ -551,19 +591,17 @@ function commandUsage(command, subcommand) {
|
|
|
551
591
|
if (command === 'deliverables' && subcommand === 'create') {
|
|
552
592
|
return [
|
|
553
593
|
'Usage:',
|
|
554
|
-
' clipit deliverables create --export-id <id> --
|
|
594
|
+
' clipit deliverables create --export-id <id> --confirm',
|
|
555
595
|
'',
|
|
556
596
|
'Required:',
|
|
557
597
|
' --export-id <id> Exact completed export to deliver',
|
|
558
|
-
' --title <text> Client-facing deliverable title',
|
|
559
598
|
' --confirm Confirm the delivery mutation',
|
|
560
599
|
'',
|
|
561
600
|
'Optional:',
|
|
562
|
-
' --note <text> Client-facing note',
|
|
563
601
|
' --profile <name> Named ClipIt credential profile',
|
|
564
602
|
' --json Emit machine-readable JSON',
|
|
565
603
|
'',
|
|
566
|
-
'The
|
|
604
|
+
'The CLI pins the export execution, snapshot, caption-style hash, object fingerprint, outro policy, and verified artifact duration. ClipIt derives the title and note from canonical metadata.',
|
|
567
605
|
].join('\n');
|
|
568
606
|
}
|
|
569
607
|
return null;
|
|
@@ -1040,8 +1078,9 @@ function normalizeCreditEstimateRequest(request) {
|
|
|
1040
1078
|
}
|
|
1041
1079
|
|
|
1042
1080
|
function brollVideoProviderCostUsd(durationSeconds, resolution) {
|
|
1043
|
-
const
|
|
1044
|
-
|
|
1081
|
+
const billableDuration = Math.max(5, Math.min(15, Math.round(durationSeconds)));
|
|
1082
|
+
const perSecondUsd = resolution === '480p' ? 0.05 : 0.08;
|
|
1083
|
+
return billableDuration * perSecondUsd;
|
|
1045
1084
|
}
|
|
1046
1085
|
|
|
1047
1086
|
function brollImageProviderCostUsd(quality) {
|
|
@@ -1059,10 +1098,8 @@ function ttsProviderCostUsd(text, prompt) {
|
|
|
1059
1098
|
}
|
|
1060
1099
|
|
|
1061
1100
|
function videoAlterProviderCostUsd(durationSeconds, mode) {
|
|
1062
|
-
const perSecond = mode === 'pro'
|
|
1063
|
-
|
|
1064
|
-
: Number(process.env.KLING_OMNI_STANDARD_COST_PER_SECOND_USD ?? '0.28');
|
|
1065
|
-
return Math.max(0, durationSeconds * (Number.isFinite(perSecond) ? perSecond : 0.28));
|
|
1101
|
+
const perSecond = mode === 'pro' ? 0.15 : 0.10;
|
|
1102
|
+
return Math.max(0, durationSeconds * perSecond);
|
|
1066
1103
|
}
|
|
1067
1104
|
|
|
1068
1105
|
function durationFromClip(clip, body = {}) {
|
|
@@ -1136,7 +1173,7 @@ async function buildMaxCreditsEstimateRequest(config, options, spec, context = {
|
|
|
1136
1173
|
|
|
1137
1174
|
if (spec.metrics === 'broll-images') {
|
|
1138
1175
|
const mode = context.body?.mode || 'single_image';
|
|
1139
|
-
const generationCount = mode === 'start_end_frame' ?
|
|
1176
|
+
const generationCount = mode === 'start_end_frame' ? 2 : 1;
|
|
1140
1177
|
return {
|
|
1141
1178
|
...spec,
|
|
1142
1179
|
metrics: {
|
|
@@ -1148,7 +1185,7 @@ async function buildMaxCreditsEstimateRequest(config, options, spec, context = {
|
|
|
1148
1185
|
|
|
1149
1186
|
if (spec.metrics === 'broll-video') {
|
|
1150
1187
|
const durationSeconds = Number(context.body?.durationSeconds ?? context.body?.duration ?? 6);
|
|
1151
|
-
const resolution = context.body?.resolution || '
|
|
1188
|
+
const resolution = context.body?.resolution || '768p';
|
|
1152
1189
|
if (!Number.isFinite(durationSeconds) || durationSeconds <= 0) return null;
|
|
1153
1190
|
return {
|
|
1154
1191
|
...spec,
|
|
@@ -1198,7 +1235,7 @@ async function buildMaxCreditsEstimateRequest(config, options, spec, context = {
|
|
|
1198
1235
|
return null;
|
|
1199
1236
|
}
|
|
1200
1237
|
|
|
1201
|
-
async function buildStaticRunEstimates(config, options, functionName, parameters, payload) {
|
|
1238
|
+
async function buildStaticRunEstimates(config, options, functionName, parameters, payload, maxCredits = null) {
|
|
1202
1239
|
const specs = RUN_MAX_CREDITS_ESTIMATE_MAP[functionName];
|
|
1203
1240
|
if (!specs) return null;
|
|
1204
1241
|
|
|
@@ -1221,12 +1258,19 @@ async function buildStaticRunEstimates(config, options, functionName, parameters
|
|
|
1221
1258
|
|
|
1222
1259
|
const estimates = [];
|
|
1223
1260
|
for (const request of requests) {
|
|
1224
|
-
const estimate = await apiFetch(config, options, 'POST', '/api/v1/credits/
|
|
1261
|
+
const estimate = await apiFetch(config, options, 'POST', '/api/v1/credits/preflight', compactObject({
|
|
1262
|
+
...request,
|
|
1263
|
+
maxCredits,
|
|
1264
|
+
}));
|
|
1225
1265
|
estimates.push({
|
|
1226
1266
|
operationType: request.operationType,
|
|
1227
1267
|
provider: request.provider,
|
|
1228
1268
|
modelId: request.modelId,
|
|
1229
|
-
estimatedCostClip: Number(estimate?.
|
|
1269
|
+
estimatedCostClip: Number(estimate?.internalEstimatedUsageClip ?? 0),
|
|
1270
|
+
internalEstimatedUsageClip: Number(estimate?.internalEstimatedUsageClip ?? 0),
|
|
1271
|
+
clientCreditChargeClip: Number(estimate?.clientCreditChargeClip ?? 0),
|
|
1272
|
+
settlementMode: estimate?.settlementMode ?? 'direct',
|
|
1273
|
+
withinApprovalCap: estimate?.withinApprovalCap !== false,
|
|
1230
1274
|
affordable: estimate?.affordable,
|
|
1231
1275
|
spendLimitViolation: estimate?.spendLimitViolation ?? null,
|
|
1232
1276
|
});
|
|
@@ -1257,7 +1301,7 @@ function throwMaxCreditsEstimateUnavailable(commandKey) {
|
|
|
1257
1301
|
|
|
1258
1302
|
function estimatedClipCostFromData(data) {
|
|
1259
1303
|
if (!data || typeof data !== 'object') return null;
|
|
1260
|
-
for (const key of ['estimatedCostClip', 'totalEstimatedCostClip', 'costClip', 'totalCostClip']) {
|
|
1304
|
+
for (const key of ['internalEstimatedUsageClip', 'estimatedCostClip', 'totalEstimatedCostClip', 'costClip', 'totalCostClip']) {
|
|
1261
1305
|
const value = Number(data[key]);
|
|
1262
1306
|
if (Number.isFinite(value)) return value;
|
|
1263
1307
|
}
|
|
@@ -1293,12 +1337,19 @@ async function enforceMaxCredits(config, options, commandKey, context = {}) {
|
|
|
1293
1337
|
|
|
1294
1338
|
const estimates = [];
|
|
1295
1339
|
for (const request of requests) {
|
|
1296
|
-
const estimate = await apiFetch(config, options, 'POST', '/api/v1/credits/
|
|
1340
|
+
const estimate = await apiFetch(config, options, 'POST', '/api/v1/credits/preflight', {
|
|
1341
|
+
...request,
|
|
1342
|
+
maxCredits: limit,
|
|
1343
|
+
});
|
|
1297
1344
|
estimates.push({
|
|
1298
1345
|
operationType: request.operationType,
|
|
1299
1346
|
provider: request.provider,
|
|
1300
1347
|
modelId: request.modelId,
|
|
1301
|
-
estimatedCostClip: Number(estimate?.
|
|
1348
|
+
estimatedCostClip: Number(estimate?.internalEstimatedUsageClip ?? 0),
|
|
1349
|
+
internalEstimatedUsageClip: Number(estimate?.internalEstimatedUsageClip ?? 0),
|
|
1350
|
+
clientCreditChargeClip: Number(estimate?.clientCreditChargeClip ?? 0),
|
|
1351
|
+
settlementMode: estimate?.settlementMode ?? 'direct',
|
|
1352
|
+
withinApprovalCap: estimate?.withinApprovalCap !== false,
|
|
1302
1353
|
affordable: estimate?.affordable,
|
|
1303
1354
|
spendLimitViolation: estimate?.spendLimitViolation ?? null,
|
|
1304
1355
|
});
|
|
@@ -1309,15 +1360,18 @@ async function enforceMaxCredits(config, options, commandKey, context = {}) {
|
|
|
1309
1360
|
warnIfEstimateUnaffordable(estimates);
|
|
1310
1361
|
|
|
1311
1362
|
const estimatedCostClip = estimates.reduce((sum, estimate) => sum + estimate.estimatedCostClip, 0);
|
|
1312
|
-
|
|
1363
|
+
const clientCreditChargeClip = estimates.reduce((sum, estimate) => sum + estimate.clientCreditChargeClip, 0);
|
|
1364
|
+
if (estimatedCostClip > limit || estimates.some((estimate) => !estimate.withinApprovalCap)) {
|
|
1313
1365
|
throw Object.assign(
|
|
1314
|
-
new Error(`
|
|
1366
|
+
new Error(`Internal estimated usage ${clipCostLabel(estimatedCostClip)} exceeds --max-credits ${clipCostLabel(limit)} for ${commandKey}; client credit charge is ${clipCostLabel(clientCreditChargeClip)}.`),
|
|
1315
1367
|
{
|
|
1316
1368
|
exitCode: EXIT.CONFIRMATION,
|
|
1317
1369
|
data: {
|
|
1318
1370
|
command: commandKey,
|
|
1319
1371
|
maxCredits: limit,
|
|
1320
1372
|
estimatedCostClip,
|
|
1373
|
+
internalEstimatedUsageClip: estimatedCostClip,
|
|
1374
|
+
clientCreditChargeClip,
|
|
1321
1375
|
estimates,
|
|
1322
1376
|
},
|
|
1323
1377
|
},
|
|
@@ -1349,7 +1403,7 @@ async function enforceRunMaxCredits(config, options, functionName, parameters =
|
|
|
1349
1403
|
|
|
1350
1404
|
let staticEstimates = null;
|
|
1351
1405
|
if (!runEstimate && (limit !== null || needsConfirmationPreflight)) {
|
|
1352
|
-
staticEstimates = await buildStaticRunEstimates(config, options, functionName, parameters, payload);
|
|
1406
|
+
staticEstimates = await buildStaticRunEstimates(config, options, functionName, parameters, payload, limit);
|
|
1353
1407
|
}
|
|
1354
1408
|
|
|
1355
1409
|
if (limit === null) {
|
|
@@ -1387,7 +1441,7 @@ async function enforceRunMaxCredits(config, options, functionName, parameters =
|
|
|
1387
1441
|
warnIfEstimateUnaffordable(staticEstimates);
|
|
1388
1442
|
|
|
1389
1443
|
const estimatedCostClip = staticEstimates.reduce((sum, estimate) => sum + estimate.estimatedCostClip, 0);
|
|
1390
|
-
if (estimatedCostClip > limit) {
|
|
1444
|
+
if (estimatedCostClip > limit || staticEstimates.some((estimate) => !estimate.withinApprovalCap)) {
|
|
1391
1445
|
throw Object.assign(
|
|
1392
1446
|
new Error(`Estimated cost ${clipCostLabel(estimatedCostClip)} exceeds --max-credits ${clipCostLabel(limit)} for run ${functionName}.`),
|
|
1393
1447
|
{
|
|
@@ -1665,6 +1719,28 @@ async function listSkills(config, options) {
|
|
|
1665
1719
|
output(await apiFetch(config, options, 'GET', '/api/v1/agent/skills'), options);
|
|
1666
1720
|
}
|
|
1667
1721
|
|
|
1722
|
+
async function describeSkill(config, options, id) {
|
|
1723
|
+
if (!id) throw Object.assign(new Error('Skill or workflow ID is required.'), { exitCode: EXIT.USAGE });
|
|
1724
|
+
output(await apiFetch(config, options, 'GET', `/api/v1/agent/skills/${encodeURIComponent(id)}`), options);
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
async function capabilityManifest(config, options) {
|
|
1728
|
+
output(await apiFetch(config, options, 'GET', '/api/v1/agent/capability-manifest'), options);
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
async function mediaGuides(config, options, action, id) {
|
|
1732
|
+
if (action === 'list') {
|
|
1733
|
+
output(await apiFetch(config, options, 'GET', '/api/v1/agent/media-guides'), options);
|
|
1734
|
+
return;
|
|
1735
|
+
}
|
|
1736
|
+
if (action === 'describe') {
|
|
1737
|
+
if (!id) throw Object.assign(new Error('Media guide ID is required.'), { exitCode: EXIT.USAGE });
|
|
1738
|
+
output(await apiFetch(config, options, 'GET', `/api/v1/agent/media-guides/${encodeURIComponent(id)}`), options);
|
|
1739
|
+
return;
|
|
1740
|
+
}
|
|
1741
|
+
throw Object.assign(new Error(`Unknown media-guides command: ${action || ''}`), { exitCode: EXIT.USAGE });
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1668
1744
|
async function listTools(config, options) {
|
|
1669
1745
|
const params = new URLSearchParams();
|
|
1670
1746
|
if (options.skill) params.set('skill', String(options.skill));
|
|
@@ -1809,6 +1885,57 @@ function normalizeAgentExecuteResult(result) {
|
|
|
1809
1885
|
});
|
|
1810
1886
|
}
|
|
1811
1887
|
|
|
1888
|
+
function resolveToolResultContext(result, fallback = {}) {
|
|
1889
|
+
if (!result || typeof result !== 'object' || Array.isArray(result)) return null;
|
|
1890
|
+
const data = result.result && typeof result.result === 'object' && !Array.isArray(result.result)
|
|
1891
|
+
? result.result
|
|
1892
|
+
: result;
|
|
1893
|
+
const libraryClip = data.libraryClip && typeof data.libraryClip === 'object' && !Array.isArray(data.libraryClip)
|
|
1894
|
+
? data.libraryClip
|
|
1895
|
+
: null;
|
|
1896
|
+
const batchItems = [
|
|
1897
|
+
...(Array.isArray(data.created) ? data.created : []),
|
|
1898
|
+
...(Array.isArray(data.skipped) ? data.skipped : []),
|
|
1899
|
+
].filter((item) => item && typeof item === 'object' && !Array.isArray(item));
|
|
1900
|
+
const selectedClipIds = batchItems
|
|
1901
|
+
.map((item) => item.libraryClipId || item.clipId || item.id)
|
|
1902
|
+
.filter(Boolean)
|
|
1903
|
+
.map(String);
|
|
1904
|
+
const batchVideoIds = [...new Set(batchItems
|
|
1905
|
+
.map((item) => (
|
|
1906
|
+
item.libraryClip && typeof item.libraryClip === 'object' && !Array.isArray(item.libraryClip)
|
|
1907
|
+
? item.libraryClip.videoId
|
|
1908
|
+
: item.videoId
|
|
1909
|
+
))
|
|
1910
|
+
.filter(Boolean)
|
|
1911
|
+
.map(String))];
|
|
1912
|
+
const clipId = data.libraryClipId
|
|
1913
|
+
|| libraryClip?.id
|
|
1914
|
+
|| data.clipId
|
|
1915
|
+
|| (selectedClipIds.length === 1 ? selectedClipIds[0] : null);
|
|
1916
|
+
const resultVideoId = libraryClip?.videoId
|
|
1917
|
+
|| data.videoId
|
|
1918
|
+
|| (batchVideoIds.length === 1 ? batchVideoIds[0] : null);
|
|
1919
|
+
if (!clipId && selectedClipIds.length === 0 && !resultVideoId) return null;
|
|
1920
|
+
const nextClipId = clipId ? String(clipId) : fallback.clipId;
|
|
1921
|
+
const selectedTargetsChanged = selectedClipIds.length > 0
|
|
1922
|
+
&& JSON.stringify(selectedClipIds) !== JSON.stringify(fallback.selectedClipIds || []);
|
|
1923
|
+
const clipTargetChanged = Boolean(
|
|
1924
|
+
nextClipId
|
|
1925
|
+
&& fallback.clipId
|
|
1926
|
+
&& String(nextClipId) !== String(fallback.clipId),
|
|
1927
|
+
);
|
|
1928
|
+
const videoId = resultVideoId
|
|
1929
|
+
? String(resultVideoId)
|
|
1930
|
+
: clipTargetChanged || selectedTargetsChanged ? undefined : fallback.videoId;
|
|
1931
|
+
return compactObject({
|
|
1932
|
+
...fallback,
|
|
1933
|
+
videoId,
|
|
1934
|
+
clipId: nextClipId,
|
|
1935
|
+
selectedClipIds: selectedClipIds.length ? selectedClipIds : fallback.selectedClipIds,
|
|
1936
|
+
});
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1812
1939
|
async function runTool(config, options, functionName) {
|
|
1813
1940
|
if (!functionName) throw Object.assign(new Error('Function name is required.'), { exitCode: EXIT.USAGE });
|
|
1814
1941
|
if (RUN_CONFIRMATION_LABELS[functionName]) {
|
|
@@ -1847,6 +1974,19 @@ async function runTool(config, options, functionName) {
|
|
|
1847
1974
|
process.exitCode = EXIT.CONFIRMATION;
|
|
1848
1975
|
return;
|
|
1849
1976
|
}
|
|
1977
|
+
if (result?.success !== false) {
|
|
1978
|
+
const nextContext = resolveToolResultContext(result, context);
|
|
1979
|
+
if (nextContext) {
|
|
1980
|
+
const recent = [
|
|
1981
|
+
{ type: 'video', id: nextContext.videoId },
|
|
1982
|
+
{ type: 'clip', id: nextContext.clipId },
|
|
1983
|
+
...(Array.isArray(nextContext.selectedClipIds)
|
|
1984
|
+
? nextContext.selectedClipIds.map((id) => ({ type: 'clip', id }))
|
|
1985
|
+
: []),
|
|
1986
|
+
].filter((entry) => entry.id);
|
|
1987
|
+
await persistActiveContext(config, options, nextContext, recent);
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1850
1990
|
output(outputResult, options);
|
|
1851
1991
|
if (result?.success === false) {
|
|
1852
1992
|
process.exitCode = EXIT.SERVER;
|
|
@@ -3150,7 +3290,7 @@ async function exportsCommand(config, options, action, args) {
|
|
|
3150
3290
|
throw Object.assign(new Error(`Unknown exports command: ${action || ''}`), { exitCode: EXIT.USAGE });
|
|
3151
3291
|
}
|
|
3152
3292
|
|
|
3153
|
-
function enterpriseClientSelectionUrl(config, options, selectionPath = '/enterprise?tab=delivered') {
|
|
3293
|
+
function enterpriseClientSelectionUrl(config, options, selectionPath = '/enterprise/workspace?tab=delivered') {
|
|
3154
3294
|
return new URL(selectionPath, getBaseUrl(config, options)).toString();
|
|
3155
3295
|
}
|
|
3156
3296
|
|
|
@@ -3170,16 +3310,55 @@ async function deliverables(config, options, action) {
|
|
|
3170
3310
|
}
|
|
3171
3311
|
if (action === 'create') {
|
|
3172
3312
|
const exportId = requiredString(options['export-id'], '--export-id');
|
|
3173
|
-
const title = requiredString(options.title, '--title');
|
|
3174
3313
|
requireConfirm(options, 'Delivering an export to the enterprise client');
|
|
3314
|
+
const exactExport = await apiFetch(
|
|
3315
|
+
config,
|
|
3316
|
+
options,
|
|
3317
|
+
'GET',
|
|
3318
|
+
`/api/v1/exports/${encodeURIComponent(exportId)}`,
|
|
3319
|
+
);
|
|
3320
|
+
const expectations = {
|
|
3321
|
+
enterpriseExecutionId: exactExport?.enterpriseExecutionId,
|
|
3322
|
+
snapshotId: exactExport?.snapshotId,
|
|
3323
|
+
editorVersion: exactExport?.editorVersion,
|
|
3324
|
+
editorStateHash: exactExport?.editorStateHash,
|
|
3325
|
+
outputObjectFingerprint: exactExport?.outputObjectFingerprint,
|
|
3326
|
+
captionStyleHash: exactExport?.captionStyleHash,
|
|
3327
|
+
includeOutro: exactExport?.includeOutro,
|
|
3328
|
+
artifactDuration: exactExport?.approvedArtifactDuration,
|
|
3329
|
+
};
|
|
3330
|
+
const actualArtifactDuration = exactExport?.artifactDuration;
|
|
3331
|
+
const exactIdentityReady = exactExport?.status === 'completed'
|
|
3332
|
+
&& typeof expectations.enterpriseExecutionId === 'string'
|
|
3333
|
+
&& typeof expectations.snapshotId === 'string'
|
|
3334
|
+
&& Number.isSafeInteger(expectations.editorVersion)
|
|
3335
|
+
&& typeof expectations.editorStateHash === 'string'
|
|
3336
|
+
&& /^[a-f0-9]{64}$/i.test(expectations.editorStateHash)
|
|
3337
|
+
&& typeof expectations.outputObjectFingerprint === 'string'
|
|
3338
|
+
&& /^[a-f0-9]{64}$/i.test(expectations.outputObjectFingerprint)
|
|
3339
|
+
&& typeof expectations.captionStyleHash === 'string'
|
|
3340
|
+
&& /^[a-f0-9]{64}$/i.test(expectations.captionStyleHash)
|
|
3341
|
+
&& typeof expectations.includeOutro === 'boolean'
|
|
3342
|
+
&& typeof expectations.artifactDuration === 'number'
|
|
3343
|
+
&& Number.isFinite(expectations.artifactDuration)
|
|
3344
|
+
&& expectations.artifactDuration > 0
|
|
3345
|
+
&& typeof actualArtifactDuration === 'number'
|
|
3346
|
+
&& Number.isFinite(actualArtifactDuration)
|
|
3347
|
+
&& actualArtifactDuration > 0
|
|
3348
|
+
&& Math.abs(actualArtifactDuration - expectations.artifactDuration) <= 0.15;
|
|
3349
|
+
if (!exactIdentityReady) {
|
|
3350
|
+
throw Object.assign(
|
|
3351
|
+
new Error('The export is not a completed, exact enterprise artifact with verified snapshot, caption-style, object, outro, and duration lineage.'),
|
|
3352
|
+
{ exitCode: EXIT.SERVER, data: exactExport },
|
|
3353
|
+
);
|
|
3354
|
+
}
|
|
3175
3355
|
const result = await apiFetch(config, options, 'POST', '/api/v1/deliverables', {
|
|
3176
3356
|
exportId,
|
|
3177
|
-
|
|
3178
|
-
note: options.note,
|
|
3357
|
+
expectations,
|
|
3179
3358
|
});
|
|
3180
3359
|
output({
|
|
3181
3360
|
...result,
|
|
3182
|
-
clientSelectionPath: '/enterprise?tab=delivered',
|
|
3361
|
+
clientSelectionPath: '/enterprise/workspace?tab=delivered',
|
|
3183
3362
|
clientSelectionUrl: enterpriseClientSelectionUrl(config, options),
|
|
3184
3363
|
}, options);
|
|
3185
3364
|
return;
|
|
@@ -3451,7 +3630,7 @@ async function resolveEnterpriseSelectedDelivery(config, options, clipId, export
|
|
|
3451
3630
|
const current = deliveries.find((delivery) => (
|
|
3452
3631
|
delivery?.clipId === clipId && delivery?.exportId === exportId
|
|
3453
3632
|
)) ?? null;
|
|
3454
|
-
const clientSelectionPath = result?.clientSelectionPath || '/enterprise?tab=delivered';
|
|
3633
|
+
const clientSelectionPath = result?.clientSelectionPath || '/enterprise/workspace?tab=delivered';
|
|
3455
3634
|
throw Object.assign(
|
|
3456
3635
|
new Error('The client has not selected this delivered export for publishing.'),
|
|
3457
3636
|
{
|
|
@@ -3904,6 +4083,91 @@ async function handleLocalMcpBillingTool(config, options, name, parameters = {})
|
|
|
3904
4083
|
throw Object.assign(new Error(`Unknown local billing tool: ${name}`), { exitCode: EXIT.USAGE });
|
|
3905
4084
|
}
|
|
3906
4085
|
|
|
4086
|
+
function mcpCapabilityResource(capability) {
|
|
4087
|
+
return {
|
|
4088
|
+
uri: `clipit://skills/${encodeURIComponent(capability.id)}`,
|
|
4089
|
+
name: capability.name || capability.id,
|
|
4090
|
+
description: `${capability.type === 'bundle' ? 'Workflow' : 'Skill'}: ${capability.description || ''}`.trim(),
|
|
4091
|
+
mimeType: 'application/json',
|
|
4092
|
+
};
|
|
4093
|
+
}
|
|
4094
|
+
|
|
4095
|
+
function mcpMediaGuideResource(guide) {
|
|
4096
|
+
return {
|
|
4097
|
+
uri: `clipit://media-guides/${encodeURIComponent(guide.id)}`,
|
|
4098
|
+
name: guide.name || guide.id,
|
|
4099
|
+
description: `${guide.purpose} guide${guide.providerModelId ? ` for ${guide.providerModelId}` : ''}`,
|
|
4100
|
+
mimeType: 'text/markdown',
|
|
4101
|
+
};
|
|
4102
|
+
}
|
|
4103
|
+
|
|
4104
|
+
async function fetchMcpResources(config, options) {
|
|
4105
|
+
const [skills, mediaGuides] = await Promise.all([
|
|
4106
|
+
apiFetch(config, options, 'GET', '/api/v1/agent/skills'),
|
|
4107
|
+
apiFetch(config, options, 'GET', '/api/v1/agent/media-guides'),
|
|
4108
|
+
]);
|
|
4109
|
+
return [
|
|
4110
|
+
{
|
|
4111
|
+
uri: 'clipit://instructions',
|
|
4112
|
+
name: 'ClipIt agent instructions',
|
|
4113
|
+
description: 'Permission-scoped operating instructions for the authenticated ClipIt profile.',
|
|
4114
|
+
mimeType: 'text/markdown',
|
|
4115
|
+
},
|
|
4116
|
+
{
|
|
4117
|
+
uri: 'clipit://manifest',
|
|
4118
|
+
name: 'ClipIt capability manifest',
|
|
4119
|
+
description: 'Version, parity checksums, model estate, and live capability counts.',
|
|
4120
|
+
mimeType: 'application/json',
|
|
4121
|
+
},
|
|
4122
|
+
...(Array.isArray(skills?.capabilities) ? skills.capabilities.map(mcpCapabilityResource) : []),
|
|
4123
|
+
...(Array.isArray(mediaGuides?.guides) ? mediaGuides.guides.map(mcpMediaGuideResource) : []),
|
|
4124
|
+
];
|
|
4125
|
+
}
|
|
4126
|
+
|
|
4127
|
+
function parseClipItResourceUri(value) {
|
|
4128
|
+
if (typeof value !== 'string') return null;
|
|
4129
|
+
let parsed;
|
|
4130
|
+
try {
|
|
4131
|
+
parsed = new URL(value);
|
|
4132
|
+
} catch {
|
|
4133
|
+
return null;
|
|
4134
|
+
}
|
|
4135
|
+
if (parsed.protocol !== 'clipit:') return null;
|
|
4136
|
+
const id = parsed.pathname.replace(/^\/+/, '');
|
|
4137
|
+
try {
|
|
4138
|
+
return { kind: parsed.hostname, id: id ? decodeURIComponent(id) : null };
|
|
4139
|
+
} catch {
|
|
4140
|
+
return null;
|
|
4141
|
+
}
|
|
4142
|
+
}
|
|
4143
|
+
|
|
4144
|
+
async function readMcpResource(config, options, uri) {
|
|
4145
|
+
const parsed = parseClipItResourceUri(uri);
|
|
4146
|
+
if (!parsed) return null;
|
|
4147
|
+
|
|
4148
|
+
if (parsed.kind === 'instructions' && !parsed.id) {
|
|
4149
|
+
const response = await apiFetch(config, options, 'GET', '/api/v1/agent/instructions?target=generic&format=json');
|
|
4150
|
+
return { uri, mimeType: 'text/markdown', text: response.markdown };
|
|
4151
|
+
}
|
|
4152
|
+
if (parsed.kind === 'manifest' && !parsed.id) {
|
|
4153
|
+
const response = await apiFetch(config, options, 'GET', '/api/v1/agent/capability-manifest');
|
|
4154
|
+
return { uri, mimeType: 'application/json', text: JSON.stringify(redactDeep(response), null, 2) };
|
|
4155
|
+
}
|
|
4156
|
+
if (parsed.kind === 'skills' && parsed.id) {
|
|
4157
|
+
const response = await apiFetch(config, options, 'GET', `/api/v1/agent/skills/${encodeURIComponent(parsed.id)}`);
|
|
4158
|
+
return { uri, mimeType: 'application/json', text: JSON.stringify(redactDeep(response), null, 2) };
|
|
4159
|
+
}
|
|
4160
|
+
if (parsed.kind === 'media-guides' && parsed.id) {
|
|
4161
|
+
const response = await apiFetch(config, options, 'GET', `/api/v1/agent/media-guides/${encodeURIComponent(parsed.id)}`);
|
|
4162
|
+
return {
|
|
4163
|
+
uri,
|
|
4164
|
+
mimeType: 'text/markdown',
|
|
4165
|
+
text: [response.contract, response.guide].filter(Boolean).join('\n\n---\n\n'),
|
|
4166
|
+
};
|
|
4167
|
+
}
|
|
4168
|
+
return null;
|
|
4169
|
+
}
|
|
4170
|
+
|
|
3907
4171
|
async function handleMcpRequest(config, options, message) {
|
|
3908
4172
|
if (!message || typeof message !== 'object' || Array.isArray(message)) {
|
|
3909
4173
|
return { jsonrpc: '2.0', id: null, error: { code: -32600, message: 'Invalid JSON-RPC request.' } };
|
|
@@ -3920,7 +4184,10 @@ async function handleMcpRequest(config, options, message) {
|
|
|
3920
4184
|
id,
|
|
3921
4185
|
result: {
|
|
3922
4186
|
protocolVersion: message.params?.protocolVersion || '2024-11-05',
|
|
3923
|
-
capabilities: {
|
|
4187
|
+
capabilities: {
|
|
4188
|
+
tools: {},
|
|
4189
|
+
resources: { subscribe: false, listChanged: false },
|
|
4190
|
+
},
|
|
3924
4191
|
serverInfo: { name: 'clipit', version: VERSION },
|
|
3925
4192
|
},
|
|
3926
4193
|
};
|
|
@@ -3943,6 +4210,30 @@ async function handleMcpRequest(config, options, message) {
|
|
|
3943
4210
|
};
|
|
3944
4211
|
}
|
|
3945
4212
|
|
|
4213
|
+
if (method === 'resources/list') {
|
|
4214
|
+
return {
|
|
4215
|
+
jsonrpc: '2.0',
|
|
4216
|
+
id,
|
|
4217
|
+
result: { resources: await fetchMcpResources(config, options) },
|
|
4218
|
+
};
|
|
4219
|
+
}
|
|
4220
|
+
|
|
4221
|
+
if (method === 'resources/read') {
|
|
4222
|
+
const uri = typeof message.params?.uri === 'string' ? message.params.uri : null;
|
|
4223
|
+
if (!uri) {
|
|
4224
|
+
return { jsonrpc: '2.0', id, error: { code: -32602, message: 'resources/read requires params.uri.' } };
|
|
4225
|
+
}
|
|
4226
|
+
const resource = await readMcpResource(config, options, uri);
|
|
4227
|
+
if (!resource) {
|
|
4228
|
+
return { jsonrpc: '2.0', id, error: { code: -32602, message: `Unknown ClipIt resource: ${uri}` } };
|
|
4229
|
+
}
|
|
4230
|
+
return {
|
|
4231
|
+
jsonrpc: '2.0',
|
|
4232
|
+
id,
|
|
4233
|
+
result: { contents: [resource] },
|
|
4234
|
+
};
|
|
4235
|
+
}
|
|
4236
|
+
|
|
3946
4237
|
if (method === 'tools/call') {
|
|
3947
4238
|
const name = typeof message.params?.name === 'string' ? message.params.name : null;
|
|
3948
4239
|
if (!name) {
|
|
@@ -4193,6 +4484,8 @@ async function examples(options) {
|
|
|
4193
4484
|
thumbnail: 'clipit thumbnails generate --clip-id <clipId> --prompt "Expressive high-contrast thumbnail" --confirm --json',
|
|
4194
4485
|
brollPlan: 'clipit broll plan <clipId> --count 3 --confirm --json',
|
|
4195
4486
|
socialPost: 'clipit social post --clip-id <clipId> --platforms x,tiktok --account-ids x=<xAccountId>,tiktok=<tiktokAccountId> --caption "New clip" --confirm --json',
|
|
4487
|
+
setExactCaptionStyle: 'clipit run setCaptionStyle --clip-id <clipId> --params @caption-style.json --json',
|
|
4488
|
+
setCaptionDirective: 'clipit run setCaptionStyle --clip-id <clipId> --params \'{"expectedEditorVersion":1,"expectedEditorStateHash":"<sha256>","expectedClipSettingsRevision":1,"captionDirective":"caption size 200% single"}\' --json',
|
|
4196
4489
|
runTool: 'clipit run <functionName> --clip-id <clipId> --params @params.json --json',
|
|
4197
4490
|
reviewLink: 'clipit links clip <clipId> --json',
|
|
4198
4491
|
};
|
|
@@ -4204,7 +4497,14 @@ async function examples(options) {
|
|
|
4204
4497
|
}
|
|
4205
4498
|
|
|
4206
4499
|
function skillMarkdown(agent) {
|
|
4207
|
-
return
|
|
4500
|
+
return `---
|
|
4501
|
+
name: clipit-cli
|
|
4502
|
+
description: Edit and create video with ClipIt through its live CLI or MCP tools, including review-first workflows, generated media, audio, QA, export, and publishing.
|
|
4503
|
+
metadata:
|
|
4504
|
+
pack-version: "offline-${VERSION}"
|
|
4505
|
+
---
|
|
4506
|
+
|
|
4507
|
+
# ClipIt CLI
|
|
4208
4508
|
|
|
4209
4509
|
Use the ClipIt CLI to operate ClipIt through the user's authenticated account. This is the generic offline fallback skill; when possible, run \`clipit agent update ${agent}\` after login to fetch server-rendered instructions scoped to the current API key.
|
|
4210
4510
|
|
|
@@ -4212,12 +4512,18 @@ Rules:
|
|
|
4212
4512
|
- Run \`clipit auth status --json\` before assuming ClipIt is connected.
|
|
4213
4513
|
- If not connected, ask the user to run \`clipit login\` and approve the browser link.
|
|
4214
4514
|
- Never ask the user to paste API keys into chat.
|
|
4215
|
-
- Use \`clipit skills list --json\` and \`clipit tools list --json\`
|
|
4216
|
-
- Use \`clipit
|
|
4515
|
+
- Use \`clipit skills list --json\`, \`clipit skills describe <id> --json\`, and \`clipit skills manifest --json\` for progressive discovery. Use \`clipit tools list --json\` and \`clipit tools describe <name> --json\` for the current execution schema.
|
|
4516
|
+
- Use \`clipit media-guides list --json\` and \`clipit media-guides describe <id> --json\` before generated image, video, Alter, voiceover, or music work.
|
|
4517
|
+
- Use \`clipit mcp stdio\` when an MCP-compatible client can launch local stdio servers; it exposes tools plus \`clipit://instructions\`, \`clipit://manifest\`, skill/workflow, and media-guide resources.
|
|
4518
|
+
- Keep one execution owner per task: orchestrate direct CLI/MCP calls or delegate the workflow through \`clipit ask\`, never both independently.
|
|
4519
|
+
- Establish the audience, platform, target duration, communication goal, hook, story arc, exact text, references, preservation invariants, source-audio authority, and definition of done before creative mutation.
|
|
4520
|
+
- Work in passes: inspect; propose lightweight candidates; obtain selection; refine story, pacing, timeline, framing, captions, media, and audio; run QA; then export or publish.
|
|
4521
|
+
- Preserve real-person identity and performance. Provider completion is not creative acceptance; inspect generated media before applying it and run final delivery QA.
|
|
4217
4522
|
- Prefer friendly commands such as \`clipit videos list --json\`, \`clipit clips list --json\`, \`clipit credits balance --json\`, and \`clipit jobs wait <jobId> --json\`.
|
|
4218
4523
|
- Use \`clipit ask "..."\` for natural-language Clippy workflows, and \`clipit workflow wait <jobId> --json\` or \`clipit workflow approve <jobId> --approval-id <id>\` for workflow follow-through.
|
|
4219
4524
|
- Use \`clipit ask "..." --auto-confirm-costly\` only when the human has asked you to work autonomously on ClipIt-only cost-spending tasks. It never authorizes social publishing/scheduling or destructive actions.
|
|
4220
4525
|
- Use \`clipit run <functionName> --params @file.json --json\` for exact Clippy tools.
|
|
4526
|
+
- For exact captions, run \`clipit run setCaptionStyle --clip-id <clipId> --params @caption-style.json --json\`; the params file pins expectedEditorVersion, expectedEditorStateHash, and expectedClipSettingsRevision and supplies exactly one of a strict \`captionStyle\` object or \`captionDirective\`. The literal \`caption size 200% single\` maps to scale 2 plus \`single-line\` without changing wordsPerLine or other current style fields. Verify the normalized style/hash/capability response before approving any render.
|
|
4221
4527
|
- Treat paid generation, publishing, deletion, broad mutation, and \`requiresConfirmation\` responses as user approval checkpoints.
|
|
4222
4528
|
- Treat exit code 13 as insufficient credits or an API key spend-limit block; top up billing or adjust the key's spend limit in ClipIt Settings.
|
|
4223
4529
|
- When credits are insufficient, discover payment rails with \`clipit billing capabilities --json\` and \`clipit billing catalog --json\`.
|
|
@@ -4233,6 +4539,9 @@ Useful commands:
|
|
|
4233
4539
|
clipit doctor --json
|
|
4234
4540
|
clipit auth status --json
|
|
4235
4541
|
clipit skills list --json
|
|
4542
|
+
clipit skills describe broll --json
|
|
4543
|
+
clipit skills manifest --json
|
|
4544
|
+
clipit media-guides list --json
|
|
4236
4545
|
clipit tools list --json
|
|
4237
4546
|
clipit tools describe <functionName> --json
|
|
4238
4547
|
clipit mcp stdio
|
|
@@ -4260,6 +4569,7 @@ clipit analytics overview --days 30 --json
|
|
|
4260
4569
|
clipit exports start --clip-id <clipId> --confirm --json
|
|
4261
4570
|
clipit thumbnails generate --clip-id <clipId> --prompt "High contrast thumbnail" --confirm --json
|
|
4262
4571
|
clipit social post --clip-id <clipId> --platforms x,tiktok --account-ids x=<xAccountId>,tiktok=<tiktokAccountId> --caption "New clip" --confirm --json
|
|
4572
|
+
clipit run setCaptionStyle --clip-id <clipId> --params @caption-style.json --json
|
|
4263
4573
|
clipit run renderClipWithRemotion --clip-id <clipId> --params @params.json --confirm --json
|
|
4264
4574
|
\`\`\`
|
|
4265
4575
|
|
|
@@ -4279,6 +4589,7 @@ function mcpSkillAddendum() {
|
|
|
4279
4589
|
'## MCP Stdio Bridge',
|
|
4280
4590
|
'- If your agent runtime supports MCP stdio servers, prefer launching `clipit mcp stdio` from the user\'s machine instead of manually shelling every command.',
|
|
4281
4591
|
'- The bridge uses the authenticated CLI profile, speaks standard `Content-Length` framed JSON-RPC over stdio, and exposes the same live tools as `clipit tools list --json` / `GET /api/v1/agent/tools`.',
|
|
4592
|
+
'- Use `resources/list` and `resources/read` for `clipit://instructions`, `clipit://manifest`, `clipit://skills/<id>`, and `clipit://media-guides/<id>` so only the relevant guide enters context.',
|
|
4282
4593
|
'- `tools/call` delegates to `/api/v1/agent/execute`, inherits the active CLI context from `clipit context use`, and preserves confirmation gates for paid generation, publishing, deletion, export, and other mutating calls.',
|
|
4283
4594
|
'- The bridge also exposes local billing discovery tools: `getPaymentCapabilities`, `getBillingCatalog`, `createPaymentAttempt`, `getPaymentAttempt`, `getPaymentReceipt`, and `getBillingSubscription`.',
|
|
4284
4595
|
'- `createPaymentAttempt` returns a payment URL for direct x402, Stripe-managed x402, or Stripe MPP/Link SPT settlement and requires `confirmed:true`; do not retry it until the human approves the product, amount, rail, and budget policy.',
|
|
@@ -4300,6 +4611,7 @@ function fallbackSkillResult(target, reason) {
|
|
|
4300
4611
|
const generatedAt = new Date().toISOString();
|
|
4301
4612
|
return {
|
|
4302
4613
|
markdown: skillMarkdown(target),
|
|
4614
|
+
references: [],
|
|
4303
4615
|
source: 'fallback',
|
|
4304
4616
|
fallbackReason: reason,
|
|
4305
4617
|
meta: {
|
|
@@ -4345,9 +4657,27 @@ async function resolveAgentSkill(config, options, target) {
|
|
|
4345
4657
|
});
|
|
4346
4658
|
}
|
|
4347
4659
|
|
|
4660
|
+
if (response.meta.minimumCliVersion !== undefined) {
|
|
4661
|
+
const compatibility = compareSemver(VERSION, response.meta.minimumCliVersion);
|
|
4662
|
+
if (compatibility === null) {
|
|
4663
|
+
throw Object.assign(new Error('Instructions endpoint returned an invalid minimum CLI version.'), {
|
|
4664
|
+
exitCode: EXIT.SERVER,
|
|
4665
|
+
data: { minimumCliVersion: response.meta.minimumCliVersion },
|
|
4666
|
+
});
|
|
4667
|
+
}
|
|
4668
|
+
if (compatibility < 0) {
|
|
4669
|
+
throw Object.assign(new Error(
|
|
4670
|
+
`ClipIt CLI ${VERSION} is too old for agent pack ${response.meta.packVersion || 'current'}; install @clipit-ai/cli@latest (minimum ${response.meta.minimumCliVersion}).`,
|
|
4671
|
+
), { exitCode: EXIT.SERVER });
|
|
4672
|
+
}
|
|
4673
|
+
}
|
|
4674
|
+
|
|
4675
|
+
const references = validateAgentInstructionReferences(response.references);
|
|
4676
|
+
|
|
4348
4677
|
const augmented = withLocalCliSkillAddenda(response.markdown);
|
|
4349
4678
|
return {
|
|
4350
4679
|
markdown: augmented.markdown,
|
|
4680
|
+
references,
|
|
4351
4681
|
source: 'server',
|
|
4352
4682
|
meta: compactObject({
|
|
4353
4683
|
...response.meta,
|
|
@@ -4368,12 +4698,83 @@ async function readAgentSkillMeta(baseDir) {
|
|
|
4368
4698
|
}
|
|
4369
4699
|
}
|
|
4370
4700
|
|
|
4701
|
+
function safeAgentReferencePath(value) {
|
|
4702
|
+
if (typeof value !== 'string') return null;
|
|
4703
|
+
if (value.includes('\0')) return null;
|
|
4704
|
+
const normalized = path.posix.normalize(value.replace(/\\/g, '/'));
|
|
4705
|
+
if (
|
|
4706
|
+
!normalized.startsWith('references/')
|
|
4707
|
+
|| normalized === 'references/'
|
|
4708
|
+
|| normalized.endsWith('/')
|
|
4709
|
+
|| normalized.includes('../')
|
|
4710
|
+
|| path.posix.isAbsolute(normalized)
|
|
4711
|
+
) {
|
|
4712
|
+
return null;
|
|
4713
|
+
}
|
|
4714
|
+
return normalized;
|
|
4715
|
+
}
|
|
4716
|
+
|
|
4717
|
+
function validateAgentInstructionReferences(value) {
|
|
4718
|
+
if (value === undefined) return [];
|
|
4719
|
+
if (!Array.isArray(value) || value.length > 256) {
|
|
4720
|
+
throw Object.assign(new Error('Instructions endpoint returned an invalid references collection.'), {
|
|
4721
|
+
exitCode: EXIT.SERVER,
|
|
4722
|
+
});
|
|
4723
|
+
}
|
|
4724
|
+
|
|
4725
|
+
const references = [];
|
|
4726
|
+
const paths = new Set();
|
|
4727
|
+
let totalBytes = 0;
|
|
4728
|
+
for (const entry of value) {
|
|
4729
|
+
const relativePath = safeAgentReferencePath(entry?.path);
|
|
4730
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry) || !relativePath || typeof entry.content !== 'string') {
|
|
4731
|
+
throw Object.assign(new Error('Instructions endpoint returned an invalid or unsafe generated reference.'), {
|
|
4732
|
+
exitCode: EXIT.SERVER,
|
|
4733
|
+
});
|
|
4734
|
+
}
|
|
4735
|
+
if (paths.has(relativePath)) {
|
|
4736
|
+
throw Object.assign(new Error(`Instructions endpoint returned duplicate reference path: ${relativePath}`), {
|
|
4737
|
+
exitCode: EXIT.SERVER,
|
|
4738
|
+
});
|
|
4739
|
+
}
|
|
4740
|
+
paths.add(relativePath);
|
|
4741
|
+
totalBytes += Buffer.byteLength(entry.content, 'utf8');
|
|
4742
|
+
if (totalBytes > 10 * 1024 * 1024) {
|
|
4743
|
+
throw Object.assign(new Error('Instructions endpoint references exceed the 10 MiB install limit.'), {
|
|
4744
|
+
exitCode: EXIT.SERVER,
|
|
4745
|
+
});
|
|
4746
|
+
}
|
|
4747
|
+
|
|
4748
|
+
const actualSha256 = hashText(entry.content);
|
|
4749
|
+
if (entry.sha256 !== undefined && (typeof entry.sha256 !== 'string' || entry.sha256.toLowerCase() !== actualSha256)) {
|
|
4750
|
+
throw Object.assign(new Error(`Instructions endpoint reference checksum mismatch: ${relativePath}`), {
|
|
4751
|
+
exitCode: EXIT.SERVER,
|
|
4752
|
+
});
|
|
4753
|
+
}
|
|
4754
|
+
references.push({ ...entry, path: relativePath, sha256: actualSha256 });
|
|
4755
|
+
}
|
|
4756
|
+
return references;
|
|
4757
|
+
}
|
|
4758
|
+
|
|
4759
|
+
function agentSkillContentHash(markdown, references = []) {
|
|
4760
|
+
const referenceContent = references
|
|
4761
|
+
.map((entry) => ({ path: safeAgentReferencePath(entry.path), content: String(entry.content || '') }))
|
|
4762
|
+
.filter((entry) => entry.path)
|
|
4763
|
+
.sort((left, right) => left.path.localeCompare(right.path));
|
|
4764
|
+
return hashText(JSON.stringify({ markdown: String(markdown || ''), references: referenceContent }));
|
|
4765
|
+
}
|
|
4766
|
+
|
|
4371
4767
|
function agentSkillSidecar(target, skill) {
|
|
4372
4768
|
return compactObject({
|
|
4373
4769
|
target,
|
|
4374
4770
|
source: skill.source,
|
|
4375
4771
|
generatedAt: skill.meta?.generatedAt,
|
|
4376
4772
|
markdownHash: hashText(skill.markdown),
|
|
4773
|
+
contentHash: agentSkillContentHash(skill.markdown, skill.references),
|
|
4774
|
+
references: skill.references?.map((entry) => compactObject({
|
|
4775
|
+
path: safeAgentReferencePath(entry.path),
|
|
4776
|
+
sha256: entry.sha256 || hashText(entry.content),
|
|
4777
|
+
})).filter((entry) => entry.path),
|
|
4377
4778
|
serverMeta: skill.source === 'server' ? skill.meta : undefined,
|
|
4378
4779
|
fallbackReason: skill.fallbackReason,
|
|
4379
4780
|
});
|
|
@@ -4403,6 +4804,19 @@ async function installedAgentStatus(target, options) {
|
|
|
4403
4804
|
fs.readFile(skillPath, 'utf8'),
|
|
4404
4805
|
]);
|
|
4405
4806
|
const sidecar = await readAgentSkillMeta(baseDir);
|
|
4807
|
+
const references = Array.isArray(sidecar?.references)
|
|
4808
|
+
? await Promise.all(sidecar.references.map(async (entry) => {
|
|
4809
|
+
const relativePath = safeAgentReferencePath(entry?.path);
|
|
4810
|
+
if (!relativePath) return null;
|
|
4811
|
+
try {
|
|
4812
|
+
return { path: relativePath, content: await fs.readFile(path.join(baseDir, relativePath), 'utf8') };
|
|
4813
|
+
} catch {
|
|
4814
|
+
return { path: relativePath, content: null };
|
|
4815
|
+
}
|
|
4816
|
+
}))
|
|
4817
|
+
: [];
|
|
4818
|
+
const missingReferences = references.filter((entry) => entry && entry.content === null).map((entry) => entry.path);
|
|
4819
|
+
const installedReferences = references.filter((entry) => entry && typeof entry.content === 'string');
|
|
4406
4820
|
return {
|
|
4407
4821
|
target,
|
|
4408
4822
|
installed: true,
|
|
@@ -4412,6 +4826,11 @@ async function installedAgentStatus(target, options) {
|
|
|
4412
4826
|
generatedAt: sidecar?.generatedAt || null,
|
|
4413
4827
|
fallbackReason: sidecar?.fallbackReason || null,
|
|
4414
4828
|
markdownHash: sidecar?.markdownHash || hashText(markdown),
|
|
4829
|
+
contentHash: missingReferences.length === 0
|
|
4830
|
+
? agentSkillContentHash(markdown, installedReferences)
|
|
4831
|
+
: null,
|
|
4832
|
+
referenceCount: references.length,
|
|
4833
|
+
missingReferences,
|
|
4415
4834
|
serverMeta: sidecar?.serverMeta || null,
|
|
4416
4835
|
};
|
|
4417
4836
|
} catch {
|
|
@@ -4424,6 +4843,14 @@ function agentFreshnessReason(status, currentSkill) {
|
|
|
4424
4843
|
if (status.skillSource !== currentSkill.source) {
|
|
4425
4844
|
return `installed source ${status.skillSource || 'unknown'} differs from current source ${currentSkill.source}`;
|
|
4426
4845
|
}
|
|
4846
|
+
if (status.missingReferences?.length) {
|
|
4847
|
+
return `installed skill is missing ${status.missingReferences.length} generated reference file(s)`;
|
|
4848
|
+
}
|
|
4849
|
+
if (status.serverMeta?.manifestHash
|
|
4850
|
+
&& currentMeta?.manifestHash
|
|
4851
|
+
&& status.serverMeta.manifestHash !== currentMeta.manifestHash) {
|
|
4852
|
+
return 'installed capability manifest differs from the live registry';
|
|
4853
|
+
}
|
|
4427
4854
|
if (status.serverMeta?.toolCount !== undefined
|
|
4428
4855
|
&& currentMeta?.toolCount !== undefined
|
|
4429
4856
|
&& status.serverMeta.toolCount !== currentMeta.toolCount) {
|
|
@@ -4437,6 +4864,28 @@ function agentFreshnessReason(status, currentSkill) {
|
|
|
4437
4864
|
return 'installed skill content differs from current generated instructions';
|
|
4438
4865
|
}
|
|
4439
4866
|
|
|
4867
|
+
async function writeAgentReferences(baseDir, references, previousMeta) {
|
|
4868
|
+
const currentPaths = new Set();
|
|
4869
|
+
for (const entry of references || []) {
|
|
4870
|
+
const relativePath = safeAgentReferencePath(entry.path);
|
|
4871
|
+
if (!relativePath) {
|
|
4872
|
+
throw Object.assign(new Error(`Instructions endpoint returned an unsafe reference path: ${entry.path}`), {
|
|
4873
|
+
exitCode: EXIT.SERVER,
|
|
4874
|
+
});
|
|
4875
|
+
}
|
|
4876
|
+
currentPaths.add(relativePath);
|
|
4877
|
+
const destination = path.join(baseDir, relativePath);
|
|
4878
|
+
await fs.mkdir(path.dirname(destination), { recursive: true });
|
|
4879
|
+
await fs.writeFile(destination, entry.content, 'utf8');
|
|
4880
|
+
}
|
|
4881
|
+
|
|
4882
|
+
for (const previous of previousMeta?.references || []) {
|
|
4883
|
+
const relativePath = safeAgentReferencePath(previous?.path);
|
|
4884
|
+
if (!relativePath || currentPaths.has(relativePath)) continue;
|
|
4885
|
+
await fs.rm(path.join(baseDir, relativePath), { force: true });
|
|
4886
|
+
}
|
|
4887
|
+
}
|
|
4888
|
+
|
|
4440
4889
|
async function agent(config, options, action, args) {
|
|
4441
4890
|
const target = args[0] || 'generic';
|
|
4442
4891
|
const baseDir = agentInstallDir(target, options);
|
|
@@ -4478,8 +4927,8 @@ async function agent(config, options, action, args) {
|
|
|
4478
4927
|
status.stale = null;
|
|
4479
4928
|
status.freshnessError = `Cannot verify installed skill freshness: ${currentSkill.fallbackReason || 'current instructions unavailable'}`;
|
|
4480
4929
|
} else {
|
|
4481
|
-
const currentHash =
|
|
4482
|
-
status.upToDate = status.
|
|
4930
|
+
const currentHash = agentSkillContentHash(currentSkill.markdown, currentSkill.references);
|
|
4931
|
+
status.upToDate = status.contentHash === currentHash;
|
|
4483
4932
|
status.stale = !status.upToDate;
|
|
4484
4933
|
if (status.stale) {
|
|
4485
4934
|
status.staleReason = agentFreshnessReason(status, currentSkill);
|
|
@@ -4495,7 +4944,9 @@ async function agent(config, options, action, args) {
|
|
|
4495
4944
|
const skill = await resolveAgentSkill(config, options, target);
|
|
4496
4945
|
printFallbackNotice(skill, true);
|
|
4497
4946
|
await fs.mkdir(baseDir, { recursive: true });
|
|
4947
|
+
const previousMeta = await readAgentSkillMeta(baseDir);
|
|
4498
4948
|
await fs.writeFile(path.join(baseDir, 'SKILL.md'), skill.markdown, 'utf8');
|
|
4949
|
+
await writeAgentReferences(baseDir, skill.references, previousMeta);
|
|
4499
4950
|
await fs.writeFile(agentSkillMetaPath(baseDir), `${JSON.stringify(agentSkillSidecar(target, skill), null, 2)}\n`, 'utf8');
|
|
4500
4951
|
output({
|
|
4501
4952
|
success: true,
|
|
@@ -4504,6 +4955,7 @@ async function agent(config, options, action, args) {
|
|
|
4504
4955
|
path: baseDir,
|
|
4505
4956
|
skillSource: skill.source,
|
|
4506
4957
|
generatedAt: skill.meta?.generatedAt || null,
|
|
4958
|
+
referenceCount: skill.references?.length || 0,
|
|
4507
4959
|
fallback: skill.source === 'fallback',
|
|
4508
4960
|
}, options);
|
|
4509
4961
|
return;
|
|
@@ -4544,6 +4996,9 @@ async function main() {
|
|
|
4544
4996
|
if (command === 'auth' && subcommand === 'use') return useProfile(config, options, rest[0]);
|
|
4545
4997
|
if (command === 'context') return contextCommand(config, options, subcommand);
|
|
4546
4998
|
if (command === 'skills' && subcommand === 'list') return listSkills(config, options);
|
|
4999
|
+
if (command === 'skills' && subcommand === 'describe') return describeSkill(config, options, rest[0]);
|
|
5000
|
+
if (command === 'skills' && subcommand === 'manifest') return capabilityManifest(config, options);
|
|
5001
|
+
if (command === 'media-guides') return mediaGuides(config, options, subcommand, rest[0]);
|
|
4547
5002
|
if (command === 'tools' && subcommand === 'list') return listTools(config, options);
|
|
4548
5003
|
if (command === 'tools' && subcommand === 'describe') return describeTool(config, options, rest[0]);
|
|
4549
5004
|
if (command === 'ask') return askWorkflow(config, options, [subcommand, ...rest]);
|