@socialseal/cli 0.1.10 → 0.1.11
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/CHANGELOG.md +4 -0
- package/package.json +1 -1
- package/src/index.js +181 -13
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.1.11 - 2026-06-12
|
|
6
|
+
|
|
7
|
+
- Add ad hoc public video URL analysis parity for queue and extract workflows, including `--url` and `--allow-untracked` support.
|
|
8
|
+
|
|
5
9
|
## 0.1.10 - 2026-06-10
|
|
6
10
|
|
|
7
11
|
- Clarify ranked search exports now include publish/observed dates and scoped tracked-search resurfacing history fields when the backend export template is deployed.
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -144,7 +144,7 @@ const KNOWN_TOOLS = [
|
|
|
144
144
|
transport: 'post_edge_function',
|
|
145
145
|
workspaceScoped: true,
|
|
146
146
|
knownLocalDevState: 'enabled',
|
|
147
|
-
notes: 'Accepts videoId/videoUid/platformVideoId/searchResultId items; videoId means video_uid or platform-native video id, not a tracking item id.',
|
|
147
|
+
notes: 'Accepts videoId/videoUid/platformVideoId/searchResultId items and public URL items with allowUntracked=true; videoId means video_uid or platform-native video id, not a tracking item id.',
|
|
148
148
|
},
|
|
149
149
|
{ name: 'douyin-geo-api', category: 'search', description: 'Query Douyin search and geo data.' },
|
|
150
150
|
{
|
|
@@ -258,6 +258,23 @@ const TOOL_SCHEMA_HINTS = {
|
|
|
258
258
|
jobId: '11111111-1111-4111-8111-111111111111',
|
|
259
259
|
},
|
|
260
260
|
},
|
|
261
|
+
{
|
|
262
|
+
action: 'status',
|
|
263
|
+
required: ['action=status', 'workspaceId or --workspace-id', 'items[] with videoUid or platformVideoId'],
|
|
264
|
+
optional: ['includeRawAnalysis'],
|
|
265
|
+
notes: 'Status polling is read-only and does not accept URL items; use videoUid or platformVideoId returned by the initial URL extraction response.',
|
|
266
|
+
example: {
|
|
267
|
+
action: 'status',
|
|
268
|
+
workspaceId: '00000000-0000-4000-8000-000000000000',
|
|
269
|
+
items: [
|
|
270
|
+
{
|
|
271
|
+
videoUid: '11111111-1111-4111-8111-111111111111',
|
|
272
|
+
},
|
|
273
|
+
],
|
|
274
|
+
includeAssets: false,
|
|
275
|
+
includeSourceVideo: false,
|
|
276
|
+
},
|
|
277
|
+
},
|
|
261
278
|
],
|
|
262
279
|
cliExamples: [
|
|
263
280
|
'socialseal tools call --function agent-tool-jobs --body \'{"action":"start","toolName":"search_videos","payload":{"query":"best africa safari itinerary","platform":"tiktok","region":"IN"}}\'',
|
|
@@ -376,6 +393,64 @@ const TOOL_SCHEMA_HINTS = {
|
|
|
376
393
|
'socialseal tools call --function get-google-ai-search-results --body \'{"runId":6809,"includeCitations":true,"limit":10}\'',
|
|
377
394
|
],
|
|
378
395
|
},
|
|
396
|
+
'tracked-video-extract': {
|
|
397
|
+
summary: 'Extract assets and queue/read analysis for tracked identifiers or ad hoc public video URLs.',
|
|
398
|
+
operations: [
|
|
399
|
+
{
|
|
400
|
+
action: 'extract',
|
|
401
|
+
required: ['workspaceId or --workspace-id', 'items[] with exactly one selector'],
|
|
402
|
+
optional: [
|
|
403
|
+
'items[].searchResultId',
|
|
404
|
+
'items[].videoId',
|
|
405
|
+
'items[].videoUid',
|
|
406
|
+
'items[].platformVideoId',
|
|
407
|
+
'items[].url',
|
|
408
|
+
'allowUntracked',
|
|
409
|
+
'ensureAnalysis',
|
|
410
|
+
'includeAssets',
|
|
411
|
+
'includeSourceVideo',
|
|
412
|
+
'frameStrategy',
|
|
413
|
+
'frameCount',
|
|
414
|
+
'signedUrlSeconds',
|
|
415
|
+
],
|
|
416
|
+
notes: 'URL items require request-level allowUntracked:true. Supported selectors are url, searchResultId, videoId, videoUid, or platformVideoId.',
|
|
417
|
+
example: {
|
|
418
|
+
workspaceId: '00000000-0000-4000-8000-000000000000',
|
|
419
|
+
allowUntracked: true,
|
|
420
|
+
ensureAnalysis: true,
|
|
421
|
+
items: [
|
|
422
|
+
{
|
|
423
|
+
url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
|
|
424
|
+
},
|
|
425
|
+
],
|
|
426
|
+
},
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
action: 'queue-analysis',
|
|
430
|
+
required: ['workspaceId or --workspace-id', 'items[] with exactly one selector'],
|
|
431
|
+
optional: ['allowUntracked for URL items', 'queueOnly', 'includeRawAnalysis'],
|
|
432
|
+
notes: 'Set ensureAnalysis:true and queueOnly:true to enqueue analysis without asset URL generation.',
|
|
433
|
+
example: {
|
|
434
|
+
workspaceId: '00000000-0000-4000-8000-000000000000',
|
|
435
|
+
allowUntracked: true,
|
|
436
|
+
ensureAnalysis: true,
|
|
437
|
+
queueOnly: true,
|
|
438
|
+
includeAssets: false,
|
|
439
|
+
items: [
|
|
440
|
+
{
|
|
441
|
+
url: 'https://www.tiktok.com/@creator/video/7348293840000000000',
|
|
442
|
+
},
|
|
443
|
+
],
|
|
444
|
+
},
|
|
445
|
+
},
|
|
446
|
+
],
|
|
447
|
+
cliExamples: [
|
|
448
|
+
'socialseal video extract --url https://www.youtube.com/watch?v=dQw4w9WgXcQ --allow-untracked --wait --out-dir ./video-assets --workspace-id <workspace-uuid>',
|
|
449
|
+
'socialseal video queue-analysis --url https://www.tiktok.com/@creator/video/7348293840000000000 --allow-untracked --wait --workspace-id <workspace-uuid>',
|
|
450
|
+
'socialseal video extract --video-uid <video-uuid> --wait --workspace-id <workspace-uuid>',
|
|
451
|
+
'socialseal tools call --function tracked-video-extract --workspace-id <workspace-uuid> --body \'{"allowUntracked":true,"items":[{"url":"https://www.instagram.com/reel/SHORTCODE/"}]}\'',
|
|
452
|
+
],
|
|
453
|
+
},
|
|
379
454
|
'group-management': {
|
|
380
455
|
summary: 'Manage single-platform tracking groups and memberships.',
|
|
381
456
|
operations: [
|
|
@@ -751,6 +826,7 @@ function inferExtension(urlValue, contentType, fallback = '.bin') {
|
|
|
751
826
|
function normalizeVideoExtractBody(body) {
|
|
752
827
|
const normalized = { ...body };
|
|
753
828
|
const hasInlineIdentifier =
|
|
829
|
+
normalized.url !== undefined ||
|
|
754
830
|
normalized.videoId !== undefined ||
|
|
755
831
|
normalized.searchResultId !== undefined ||
|
|
756
832
|
normalized.videoUid !== undefined ||
|
|
@@ -758,12 +834,14 @@ function normalizeVideoExtractBody(body) {
|
|
|
758
834
|
|
|
759
835
|
if (!Array.isArray(normalized.items) && hasInlineIdentifier) {
|
|
760
836
|
normalized.items = [{
|
|
837
|
+
url: normalized.url,
|
|
761
838
|
videoId: normalized.videoId,
|
|
762
839
|
searchResultId: normalized.searchResultId,
|
|
763
840
|
videoUid: normalized.videoUid,
|
|
764
841
|
platformVideoId: normalized.platformVideoId,
|
|
765
842
|
platformId: normalized.platformId,
|
|
766
843
|
}];
|
|
844
|
+
delete normalized.url;
|
|
767
845
|
delete normalized.videoId;
|
|
768
846
|
delete normalized.searchResultId;
|
|
769
847
|
delete normalized.videoUid;
|
|
@@ -774,6 +852,11 @@ function normalizeVideoExtractBody(body) {
|
|
|
774
852
|
return normalized;
|
|
775
853
|
}
|
|
776
854
|
|
|
855
|
+
function hasUrlVideoItems(body) {
|
|
856
|
+
const items = Array.isArray(body?.items) ? body.items : [];
|
|
857
|
+
return items.some((item) => typeof item?.url === 'string' && item.url.trim().length > 0);
|
|
858
|
+
}
|
|
859
|
+
|
|
777
860
|
function buildVideoExtractBody(opts, workspaceId) {
|
|
778
861
|
const parsed = opts.body
|
|
779
862
|
? ensureJsonObject(parseJsonInput(opts.body, { label: 'body' }), 'body')
|
|
@@ -782,6 +865,7 @@ function buildVideoExtractBody(opts, workspaceId) {
|
|
|
782
865
|
|
|
783
866
|
if (!Array.isArray(normalized.items) || normalized.items.length === 0) {
|
|
784
867
|
const inlineItem = stripUndefinedEntries({
|
|
868
|
+
url: trimString(opts.url) || undefined,
|
|
785
869
|
videoId: trimString(opts.videoId) || undefined,
|
|
786
870
|
searchResultId: opts.searchResultId !== undefined
|
|
787
871
|
? coercePositiveInteger(opts.searchResultId, 'searchResultId')
|
|
@@ -791,10 +875,10 @@ function buildVideoExtractBody(opts, workspaceId) {
|
|
|
791
875
|
});
|
|
792
876
|
|
|
793
877
|
if (Object.keys(inlineItem).length === 0) {
|
|
794
|
-
throw new CliError('Provide --body or one of --video-id, --video-uid, --platform-video-id, or --search-result-id.', {
|
|
878
|
+
throw new CliError('Provide --body or one of --url, --video-id, --video-uid, --platform-video-id, or --search-result-id.', {
|
|
795
879
|
code: 'MISSING_ARGUMENT',
|
|
796
880
|
exitCode: EXIT_CODES.USAGE,
|
|
797
|
-
hint: '--video-id accepts a video_uid or platform video id
|
|
881
|
+
hint: '--url requires --allow-untracked. --video-id accepts a video_uid or platform video id; it does not accept tracking item ids.',
|
|
798
882
|
});
|
|
799
883
|
}
|
|
800
884
|
|
|
@@ -811,6 +895,17 @@ function buildVideoExtractBody(opts, workspaceId) {
|
|
|
811
895
|
}
|
|
812
896
|
|
|
813
897
|
const nextBody = { ...bodyWithWorkspace };
|
|
898
|
+
if (opts.allowUntracked === true) {
|
|
899
|
+
nextBody.allowUntracked = true;
|
|
900
|
+
}
|
|
901
|
+
if (hasUrlVideoItems(nextBody) && nextBody.allowUntracked !== true) {
|
|
902
|
+
throw new CliError('URL video analysis requires --allow-untracked or allowUntracked:true in --body.', {
|
|
903
|
+
code: 'ALLOW_UNTRACKED_REQUIRED',
|
|
904
|
+
exitCode: EXIT_CODES.USAGE,
|
|
905
|
+
hint: 'Pass --allow-untracked for ad hoc public URL analysis. Existing tracked identifier flows do not need this flag.',
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
|
|
814
909
|
if (opts.wait) {
|
|
815
910
|
nextBody.ensureAnalysis = true;
|
|
816
911
|
} else if (opts.ensureAnalysis === true) {
|
|
@@ -868,11 +963,60 @@ function buildVideoQueueBody(opts, workspaceId) {
|
|
|
868
963
|
function hasPendingVideoExtractResults(payload) {
|
|
869
964
|
const results = Array.isArray(payload?.results) ? payload.results : [];
|
|
870
965
|
return results.some((result) => {
|
|
871
|
-
const
|
|
872
|
-
|
|
966
|
+
const itemStatus = String(result?.status || '').trim().toLowerCase();
|
|
967
|
+
const analysisStatus = String(
|
|
968
|
+
result?.analysis?.normalizedStatus || result?.analysis?.status || '',
|
|
969
|
+
).trim().toLowerCase();
|
|
970
|
+
const status = itemStatus || analysisStatus;
|
|
971
|
+
return ACTIVE_STATUS_VALUES.has(status);
|
|
873
972
|
});
|
|
874
973
|
}
|
|
875
974
|
|
|
975
|
+
function buildVideoExtractStatusPollBody(originalBody, payload) {
|
|
976
|
+
const results = Array.isArray(payload?.results) ? payload.results : [];
|
|
977
|
+
const items = results
|
|
978
|
+
.map((result) => {
|
|
979
|
+
const resolved = isJsonObject(result?.resolvedVideo)
|
|
980
|
+
? result.resolvedVideo
|
|
981
|
+
: (isJsonObject(result?.resolved) ? result.resolved : {});
|
|
982
|
+
const videoUid = trimString(resolved.videoUid || resolved.video_uid);
|
|
983
|
+
if (videoUid) return { videoUid };
|
|
984
|
+
const platformVideoId = trimString(
|
|
985
|
+
resolved.platformVideoId || resolved.platform_video_id,
|
|
986
|
+
);
|
|
987
|
+
if (platformVideoId) {
|
|
988
|
+
const item = { platformVideoId };
|
|
989
|
+
if (Number.isInteger(resolved.platformId)) {
|
|
990
|
+
item.platformId = resolved.platformId;
|
|
991
|
+
} else if (Number.isInteger(resolved.platform_id)) {
|
|
992
|
+
item.platformId = resolved.platform_id;
|
|
993
|
+
}
|
|
994
|
+
return item;
|
|
995
|
+
}
|
|
996
|
+
const request = isJsonObject(result?.request) ? result.request : null;
|
|
997
|
+
if (!request || request.url) return null;
|
|
998
|
+
return request;
|
|
999
|
+
})
|
|
1000
|
+
.filter(Boolean);
|
|
1001
|
+
|
|
1002
|
+
if (items.length === 0) {
|
|
1003
|
+
const originalHasUrl = Array.isArray(originalBody.items) &&
|
|
1004
|
+
originalBody.items.some((item) => Boolean(item?.url));
|
|
1005
|
+
if (originalHasUrl) return null;
|
|
1006
|
+
return originalBody;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
return {
|
|
1010
|
+
workspaceId: originalBody.workspaceId,
|
|
1011
|
+
action: 'status',
|
|
1012
|
+
items,
|
|
1013
|
+
includeRawAnalysis: originalBody.includeRawAnalysis === true,
|
|
1014
|
+
includeAssets: false,
|
|
1015
|
+
includeSourceVideo: false,
|
|
1016
|
+
ensureAnalysis: false,
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
|
|
876
1020
|
async function downloadAssetToFile({ url, outDir, stem, timeoutMs }) {
|
|
877
1021
|
const response = await fetchWithTimeout(url, {
|
|
878
1022
|
method: 'GET',
|
|
@@ -4448,13 +4592,13 @@ async function handleVideoExtract(opts) {
|
|
|
4448
4592
|
method: 'POST',
|
|
4449
4593
|
});
|
|
4450
4594
|
|
|
4451
|
-
const requestOnce = async (remainingTimeoutMs) => {
|
|
4595
|
+
const requestOnce = async (remainingTimeoutMs, requestBody = body) => {
|
|
4452
4596
|
const res = await callApi({
|
|
4453
4597
|
apiBase: useGateway ? resolvedApiBase : legacyUrl,
|
|
4454
4598
|
apiKey,
|
|
4455
4599
|
path,
|
|
4456
4600
|
method: 'POST',
|
|
4457
|
-
body,
|
|
4601
|
+
body: requestBody,
|
|
4458
4602
|
workspaceId: effectiveWorkspaceId,
|
|
4459
4603
|
timeoutMs: remainingTimeoutMs,
|
|
4460
4604
|
});
|
|
@@ -4479,6 +4623,7 @@ async function handleVideoExtract(opts) {
|
|
|
4479
4623
|
};
|
|
4480
4624
|
|
|
4481
4625
|
let payload = await requestOnce(timeoutMs);
|
|
4626
|
+
let pollBody = buildVideoExtractStatusPollBody(body, payload);
|
|
4482
4627
|
|
|
4483
4628
|
if (opts.wait) {
|
|
4484
4629
|
const pollIntervalMs = resolvePollIntervalMs(opts);
|
|
@@ -4497,7 +4642,16 @@ async function handleVideoExtract(opts) {
|
|
|
4497
4642
|
|
|
4498
4643
|
emitInfo(opts, 'tracked-video-extract pending; polling for completion.');
|
|
4499
4644
|
await sleep(Math.min(pollIntervalMs, remainingMs));
|
|
4500
|
-
|
|
4645
|
+
if (!pollBody) {
|
|
4646
|
+
throw new CliError('Cannot poll URL analysis without a resolved video identifier.', {
|
|
4647
|
+
code: 'MISSING_RESOLVED_VIDEO_ID',
|
|
4648
|
+
exitCode: EXIT_CODES.SERVER,
|
|
4649
|
+
hint: 'Retry without --wait, then poll with the returned videoUid or platformVideoId.',
|
|
4650
|
+
details: truncateDetails(payload),
|
|
4651
|
+
});
|
|
4652
|
+
}
|
|
4653
|
+
payload = await requestOnce(Math.max(1000, deadline - Date.now()), pollBody);
|
|
4654
|
+
pollBody = buildVideoExtractStatusPollBody(body, payload);
|
|
4501
4655
|
}
|
|
4502
4656
|
}
|
|
4503
4657
|
|
|
@@ -4536,13 +4690,13 @@ async function handleVideoQueueAnalysis(opts) {
|
|
|
4536
4690
|
method: 'POST',
|
|
4537
4691
|
});
|
|
4538
4692
|
|
|
4539
|
-
const requestOnce = async (remainingTimeoutMs) => {
|
|
4693
|
+
const requestOnce = async (remainingTimeoutMs, requestBody = body) => {
|
|
4540
4694
|
const res = await callApi({
|
|
4541
4695
|
apiBase: useGateway ? resolvedApiBase : legacyUrl,
|
|
4542
4696
|
apiKey,
|
|
4543
4697
|
path,
|
|
4544
4698
|
method: 'POST',
|
|
4545
|
-
body,
|
|
4699
|
+
body: requestBody,
|
|
4546
4700
|
workspaceId: effectiveWorkspaceId,
|
|
4547
4701
|
timeoutMs: remainingTimeoutMs,
|
|
4548
4702
|
});
|
|
@@ -4567,6 +4721,7 @@ async function handleVideoQueueAnalysis(opts) {
|
|
|
4567
4721
|
};
|
|
4568
4722
|
|
|
4569
4723
|
let payload = await requestOnce(timeoutMs);
|
|
4724
|
+
let pollBody = buildVideoExtractStatusPollBody(body, payload);
|
|
4570
4725
|
|
|
4571
4726
|
if (opts.wait) {
|
|
4572
4727
|
const pollIntervalMs = resolvePollIntervalMs(opts);
|
|
@@ -4585,7 +4740,16 @@ async function handleVideoQueueAnalysis(opts) {
|
|
|
4585
4740
|
|
|
4586
4741
|
emitInfo(opts, 'tracked-video queue-analysis pending; polling for completion.');
|
|
4587
4742
|
await sleep(Math.min(pollIntervalMs, remainingMs));
|
|
4588
|
-
|
|
4743
|
+
if (!pollBody) {
|
|
4744
|
+
throw new CliError('Cannot poll URL analysis without a resolved video identifier.', {
|
|
4745
|
+
code: 'MISSING_RESOLVED_VIDEO_ID',
|
|
4746
|
+
exitCode: EXIT_CODES.SERVER,
|
|
4747
|
+
hint: 'Retry without --wait, then poll with the returned videoUid or platformVideoId.',
|
|
4748
|
+
details: truncateDetails(payload),
|
|
4749
|
+
});
|
|
4750
|
+
}
|
|
4751
|
+
payload = await requestOnce(Math.max(1000, deadline - Date.now()), pollBody);
|
|
4752
|
+
pollBody = buildVideoExtractStatusPollBody(body, payload);
|
|
4589
4753
|
}
|
|
4590
4754
|
}
|
|
4591
4755
|
|
|
@@ -4967,12 +5131,14 @@ const video = program.command('video').description('Tracked video extraction wor
|
|
|
4967
5131
|
|
|
4968
5132
|
video
|
|
4969
5133
|
.command('queue-analysis')
|
|
4970
|
-
.description('Queue video analysis for tracked videos
|
|
5134
|
+
.description('Queue video analysis for tracked videos, tracked search results, or ad hoc public URLs')
|
|
5135
|
+
.option('--url <url>', 'Public TikTok, Instagram, or YouTube video URL (requires --allow-untracked)')
|
|
4971
5136
|
.option('--video-id <id>', 'Tracked video identifier (video_uid first, then platform video id; not a tracking item id)')
|
|
4972
5137
|
.option('--search-result-id <id>', 'Tracked search result id for a ranked result row')
|
|
4973
5138
|
.option('--video-uid <id>', 'Canonical tracked video_uid')
|
|
4974
5139
|
.option('--platform-video-id <id>', 'Platform-native video id')
|
|
4975
5140
|
.option('--body <jsonOrFile>', 'JSON body or @payload.json for batch queueing')
|
|
5141
|
+
.option('--allow-untracked', 'Allow ad hoc public URL analysis for videos not already tracked')
|
|
4976
5142
|
.option('--wait', 'Poll until queued/completing analyses settle')
|
|
4977
5143
|
.option('--poll-interval <ms>', 'Polling interval in milliseconds when --wait is enabled')
|
|
4978
5144
|
.option('--api-base <url>', 'API base URL (default https://api.socialseal.co)')
|
|
@@ -4986,12 +5152,14 @@ video
|
|
|
4986
5152
|
|
|
4987
5153
|
video
|
|
4988
5154
|
.command('extract')
|
|
4989
|
-
.description('Resolve tracked videos/results into structured analysis plus reference assets')
|
|
5155
|
+
.description('Resolve tracked videos/results or ad hoc public URLs into structured analysis plus reference assets')
|
|
5156
|
+
.option('--url <url>', 'Public TikTok, Instagram, or YouTube video URL (requires --allow-untracked)')
|
|
4990
5157
|
.option('--video-id <id>', 'Tracked video identifier (video_uid first, then platform video id; not a tracking item id)')
|
|
4991
5158
|
.option('--search-result-id <id>', 'Tracked search result id for a ranked result row')
|
|
4992
5159
|
.option('--video-uid <id>', 'Canonical tracked video_uid')
|
|
4993
5160
|
.option('--platform-video-id <id>', 'Platform-native video id')
|
|
4994
5161
|
.option('--body <jsonOrFile>', 'JSON body or @payload.json for batch extraction')
|
|
5162
|
+
.option('--allow-untracked', 'Allow ad hoc public URL analysis for videos not already tracked')
|
|
4995
5163
|
.option('--ensure-analysis', 'Queue analysis when it is missing')
|
|
4996
5164
|
.option('--wait', 'Poll until queued/completing analyses settle')
|
|
4997
5165
|
.option('--poll-interval <ms>', 'Polling interval in milliseconds when --wait is enabled')
|