@socialseal/cli 0.1.9 → 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 +19 -0
- package/README.md +17 -1
- package/package.json +2 -2
- package/src/index.js +184 -16
package/CHANGELOG.md
CHANGED
|
@@ -2,24 +2,37 @@
|
|
|
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
|
+
|
|
9
|
+
## 0.1.10 - 2026-06-10
|
|
10
|
+
|
|
11
|
+
- Clarify ranked search exports now include publish/observed dates and scoped tracked-search resurfacing history fields when the backend export template is deployed.
|
|
12
|
+
- Update `data export-options` metadata to distinguish search capture, metrics capture, platform publish, and SocialSeal observed-history timestamps.
|
|
13
|
+
|
|
5
14
|
## 0.1.9 - 2026-05-13
|
|
15
|
+
|
|
6
16
|
- Fail `group-management add_items` on backend partial failures, expected-count mismatches, and failed backend verification.
|
|
7
17
|
- Add `data export-group-evidence` to route social ranked evidence and Google AI evidence to the right raw export template with metadata.
|
|
8
18
|
- Add `data group-completeness` with backend-first completeness checks, manifest fallback, and refresh status visibility.
|
|
9
19
|
- Harden `tools status --wait` terminal failure handling and empty group refresh guardrails.
|
|
10
20
|
|
|
11
21
|
## 0.1.8 - 2026-04-13
|
|
22
|
+
|
|
12
23
|
- Increase default CLI timeout from 30s to 5m to reduce false timeout failures on heavy tool/export workflows.
|
|
13
24
|
- Harden async `search-journey-run` polling to accept additional active/terminal status labels (`queued`, `in_progress`, `running`, `succeeded`, `error`) instead of failing on unexpected variants.
|
|
14
25
|
- Fix async `search-journey-run` polling workspace propagation to always reuse the resolved effective workspace scope.
|
|
15
26
|
|
|
16
27
|
## 0.1.7 - 2026-03-20
|
|
28
|
+
|
|
17
29
|
- Add `socialseal data export-search-results` for CLI-first enriched ranked-search exports, including direct CSV download handling.
|
|
18
30
|
- Add `search_results_enriched` as an alias on `socialseal data export-report` to map to the ranked-search export template.
|
|
19
31
|
- Add `socialseal data export-options` to make available export workflows discoverable from the CLI.
|
|
20
32
|
- Improve export ergonomics with local report-type validation and instructive failure guidance for processing, failed, and expired-download states.
|
|
21
33
|
|
|
22
34
|
## 0.1.6 - 2026-03-19
|
|
35
|
+
|
|
23
36
|
- Fix runtime version reporting so `socialseal --version` reads from package metadata instead of a hardcoded source string.
|
|
24
37
|
- Fix `tracking` create request translation so `--workspace-id` is sent on the REST query path the backend uses for workspace binding.
|
|
25
38
|
- Improve tracked-video extraction failure messages by avoiding `[object Object]` item errors and returning explicit guidance when `videoId` is actually a search-result id or tracking item id.
|
|
@@ -28,28 +41,34 @@
|
|
|
28
41
|
- Clarify in workspace discovery output and docs that `workspace_id` and `brand_id` are different identifiers.
|
|
29
42
|
|
|
30
43
|
## 0.1.5 - 2026-03-19
|
|
44
|
+
|
|
31
45
|
- Add first-class tracked-video workflows with `video queue-analysis` and `video extract`.
|
|
32
46
|
- Make `--video-id` the primary ergonomic selector for tracked-video analysis and asset extraction, while keeping `--search-result-id` as a fallback selector.
|
|
33
47
|
- Support batch queueing/extraction payloads plus optional asset downloads for thumbnails, source video, and extracted key frames.
|
|
34
48
|
|
|
35
49
|
## 0.1.4 - 2026-03-19
|
|
50
|
+
|
|
36
51
|
- Add explicit `group_add_item` / `group_add_items` CLI aliases for tracking-group membership workflows.
|
|
37
52
|
- Add `tracking resolve` / `get_by_value` so existing tracked searches can be resolved by value using the same duplicate-detection semantics as create.
|
|
38
53
|
- Return operational duplicate metadata for tracking conflicts, including `existing_item_id`, `member_of_group_ids`, platform, region, workspace, and active state.
|
|
39
54
|
|
|
40
55
|
## 0.1.3 - 2026-03-19
|
|
56
|
+
|
|
41
57
|
- Republish the current CLI release line after the successful `0.1.2` npm publish, keeping the internal and OSS package versions aligned.
|
|
42
58
|
|
|
43
59
|
## 0.1.2 - 2026-03-18
|
|
60
|
+
|
|
44
61
|
- Add `search-journey-run` async CLI ergonomics: `--async` starts the backend async mode, polling is on by default, and `--no-poll` returns the initial `runId` immediately.
|
|
45
62
|
- Add `--poll-interval <ms>` for async `search-journey-run` status polling.
|
|
46
63
|
- Treat terminal async `search-journey-run` failures as non-zero CLI exits instead of silent `200` JSON output.
|
|
47
64
|
|
|
48
65
|
## 0.1.1 - 2026-03-13
|
|
66
|
+
|
|
49
67
|
- Document public base URL and CLI error output.
|
|
50
68
|
- Add request timeouts, verbose error output, and OSS-safe tool discovery behavior.
|
|
51
69
|
- Ship a stable built-in tool registry for `tools list` instead of the hard-disabled discovery message.
|
|
52
70
|
- Fail fast on agent WebSocket `error` events and surface session/tool progress diagnostics in `--verbose` mode.
|
|
53
71
|
|
|
54
72
|
## 0.1.0
|
|
73
|
+
|
|
55
74
|
- Initial CLI with agent streaming, tools calls, and provisional data exports.
|
package/README.md
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
# SocialSeal CLI
|
|
2
2
|
|
|
3
3
|
## Setup
|
|
4
|
+
|
|
4
5
|
- Requires Node 18+
|
|
5
6
|
- Global install: `npm install -g @socialseal/cli`
|
|
6
7
|
- Dev install: `npm install`
|
|
7
8
|
|
|
8
9
|
## Configuration
|
|
10
|
+
|
|
9
11
|
Environment variables:
|
|
12
|
+
|
|
10
13
|
- `SOCIALSEAL_API_KEY`
|
|
11
14
|
- `SOCIALSEAL_API_BASE` (default `https://api.socialseal.co`)
|
|
12
15
|
- `SOCIALSEAL_WORKSPACE_ID` (optional workspace override; takes precedence over config)
|
|
@@ -14,6 +17,7 @@ Environment variables:
|
|
|
14
17
|
- `SOCIALSEAL_AGENT_IDLE_TIMEOUT_MS` (optional agent WebSocket inactivity timeout override; default 300000)
|
|
15
18
|
|
|
16
19
|
Optional config file:
|
|
20
|
+
|
|
17
21
|
- `~/.config/socialseal/config.json`
|
|
18
22
|
|
|
19
23
|
```json
|
|
@@ -27,6 +31,7 @@ Optional config file:
|
|
|
27
31
|
```
|
|
28
32
|
|
|
29
33
|
## Commands
|
|
34
|
+
|
|
30
35
|
- Workspace discovery/defaults:
|
|
31
36
|
- `socialseal workspace list`
|
|
32
37
|
- `socialseal workspace current`
|
|
@@ -68,10 +73,17 @@ Optional config file:
|
|
|
68
73
|
- `socialseal data export-report --report-type keyword_universe --format csv --payload @payload.json --out out.csv`
|
|
69
74
|
|
|
70
75
|
## Notes
|
|
76
|
+
|
|
71
77
|
- `export-report`, `export_tracking_data`, and `export-data`-backed exports are provisional until CLI export specs are finalized.
|
|
72
78
|
- `tools list` ships a stable built-in registry of supported direct-call function targets. It is not live backend enumeration.
|
|
73
79
|
- `tools schema --function <name>` prints static required/optional payload fields and example bodies for high-friction tools.
|
|
74
|
-
- `data export-search-results` maps to `export-data` template `tracking_ranked_videos_raw` and returns enriched ranked-search rows (search fields + video metadata + latest metrics + analysis). It downloads the signed CSV artifact when available.
|
|
80
|
+
- `data export-search-results` maps to `export-data` template `tracking_ranked_videos_raw` and returns enriched ranked-search rows (search fields + video metadata + publish/observed dates + latest metrics + scoped tracked-search history + analysis). It downloads the signed CSV artifact when available.
|
|
81
|
+
- Timestamp meanings in ranked search exports:
|
|
82
|
+
- `search_timestamp`: when SocialSeal captured that ranked search row.
|
|
83
|
+
- `latest_metrics_ts`: when the latest exported engagement metrics snapshot was captured.
|
|
84
|
+
- `published_at`: platform publish/upload time when available from the video record; blank if unavailable.
|
|
85
|
+
- `observed_at`: when SocialSeal first observed/ingested the video record when available; blank if unavailable.
|
|
86
|
+
- `first_seen_at` / `last_seen_at`: earliest/latest `search_timestamp` for the video within the exported tracking-group/search-row scope. Use these for “resurfacing in tracked search” language, not platform-age claims when `published_at` is absent.
|
|
75
87
|
- `data export-report --report-type search_results_enriched` is a compatibility alias to the same `export-data` template flow.
|
|
76
88
|
- `data export-report` now validates report types locally and shows the allowed list immediately; run `socialseal data export-options` when choosing between export flows.
|
|
77
89
|
- If an export returns metadata without a file URL (for example status `processing`), the CLI prints an explicit retry hint and returns the metadata JSON so automation can branch on status.
|
|
@@ -91,11 +103,13 @@ Optional config file:
|
|
|
91
103
|
- If a scoped CLI key cannot safely infer a workspace, `agent run` now fails closed and tells you to set `--workspace-id` or configure a local default first.
|
|
92
104
|
|
|
93
105
|
## Errors and exit codes
|
|
106
|
+
|
|
94
107
|
- Exit codes: `2` (usage), `3` (auth), `4` (not found), `5` (server), `1` (unknown)
|
|
95
108
|
- Add `--json` to `tools call` or `data` commands to emit machine-readable errors.
|
|
96
109
|
- Add `--verbose` to print error details plus agent session/tool progress diagnostics.
|
|
97
110
|
|
|
98
111
|
## Troubleshooting
|
|
112
|
+
|
|
99
113
|
- `SUPABASE_ANON_KEY not configured`
|
|
100
114
|
- This comes from the CLI gateway, not the local CLI install.
|
|
101
115
|
- The deployed gateway is missing its `SUPABASE_ANON_KEY` secret, so `/cli/tools/*` cannot proxy to Supabase Edge Functions.
|
|
@@ -106,8 +120,10 @@ Optional config file:
|
|
|
106
120
|
- If this reproduces from a supported Google AI region, treat it as an infrastructure/runtime issue. Practical workarounds are to run the agent from a worker placement/egress region that Google accepts, or switch the agent runtime to Vertex AI for server-side calls.
|
|
107
121
|
|
|
108
122
|
## Smoke Test (manual)
|
|
123
|
+
|
|
109
124
|
1. `SOCIALSEAL_API_KEY=... socialseal agent run --message "ping"`
|
|
110
125
|
2. `SOCIALSEAL_API_KEY=... socialseal tools call --function <tool> --body @payload.json`
|
|
111
126
|
|
|
112
127
|
## Maintainers
|
|
128
|
+
|
|
113
129
|
- The public CLI base (`api.socialseal.co`) must route to the CLI gateway service.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@socialseal/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "SocialSeal CLI (non-interactive)",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"license": "Apache-2.0",
|
|
14
14
|
"repository": {
|
|
15
15
|
"type": "git",
|
|
16
|
-
"url": "https://github.com/OpenSealAI/socialseal-cli.git"
|
|
16
|
+
"url": "git+https://github.com/OpenSealAI/socialseal-cli.git"
|
|
17
17
|
},
|
|
18
18
|
"bugs": {
|
|
19
19
|
"url": "https://github.com/OpenSealAI/socialseal-cli/issues"
|
package/src/index.js
CHANGED
|
@@ -55,10 +55,10 @@ const EXPORT_OPTIONS = [
|
|
|
55
55
|
{
|
|
56
56
|
id: 'search_results_enriched',
|
|
57
57
|
command: 'socialseal data export-search-results --group-ids <id,id,...>',
|
|
58
|
-
summary: 'Enriched ranked search rows (search
|
|
58
|
+
summary: 'Enriched ranked search rows (search capture + video publish/observed dates + latest metrics + scoped resurfacing history + analysis).',
|
|
59
59
|
formats: ['csv'],
|
|
60
60
|
required: ['workspace id', '--group-ids'],
|
|
61
|
-
bestFor: 'SQL-like ranked-search datasets
|
|
61
|
+
bestFor: 'SQL-like ranked-search datasets and deck evidence that must distinguish capture, metrics, publish, and tracked-search resurfacing timestamps.',
|
|
62
62
|
alias: 'socialseal data export-report --report-type search_results_enriched --format csv --payload @payload.json',
|
|
63
63
|
},
|
|
64
64
|
{
|
|
@@ -124,7 +124,7 @@ const KNOWN_TOOLS = [
|
|
|
124
124
|
transport: 'post_edge_function',
|
|
125
125
|
workspaceScoped: true,
|
|
126
126
|
knownLocalDevState: 'disabled_by_default',
|
|
127
|
-
notes: 'Includes template `tracking_ranked_videos_raw` for ranked search results with video
|
|
127
|
+
notes: 'Includes template `tracking_ranked_videos_raw` for ranked search results with video publish/observed dates, latest metrics, scoped first/last seen, and analysis enrichment.',
|
|
128
128
|
},
|
|
129
129
|
{
|
|
130
130
|
name: 'export_tracking_data',
|
|
@@ -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')
|