@socialseal/cli 0.1.8 → 0.1.10
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 +21 -0
- package/README.md +17 -1
- package/package.json +2 -2
- package/src/index.js +777 -45
package/CHANGELOG.md
CHANGED
|
@@ -2,18 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.1.10 - 2026-06-10
|
|
6
|
+
|
|
7
|
+
- Clarify ranked search exports now include publish/observed dates and scoped tracked-search resurfacing history fields when the backend export template is deployed.
|
|
8
|
+
- Update `data export-options` metadata to distinguish search capture, metrics capture, platform publish, and SocialSeal observed-history timestamps.
|
|
9
|
+
|
|
10
|
+
## 0.1.9 - 2026-05-13
|
|
11
|
+
|
|
12
|
+
- Fail `group-management add_items` on backend partial failures, expected-count mismatches, and failed backend verification.
|
|
13
|
+
- Add `data export-group-evidence` to route social ranked evidence and Google AI evidence to the right raw export template with metadata.
|
|
14
|
+
- Add `data group-completeness` with backend-first completeness checks, manifest fallback, and refresh status visibility.
|
|
15
|
+
- Harden `tools status --wait` terminal failure handling and empty group refresh guardrails.
|
|
16
|
+
|
|
5
17
|
## 0.1.8 - 2026-04-13
|
|
18
|
+
|
|
6
19
|
- Increase default CLI timeout from 30s to 5m to reduce false timeout failures on heavy tool/export workflows.
|
|
7
20
|
- 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.
|
|
8
21
|
- Fix async `search-journey-run` polling workspace propagation to always reuse the resolved effective workspace scope.
|
|
9
22
|
|
|
10
23
|
## 0.1.7 - 2026-03-20
|
|
24
|
+
|
|
11
25
|
- Add `socialseal data export-search-results` for CLI-first enriched ranked-search exports, including direct CSV download handling.
|
|
12
26
|
- Add `search_results_enriched` as an alias on `socialseal data export-report` to map to the ranked-search export template.
|
|
13
27
|
- Add `socialseal data export-options` to make available export workflows discoverable from the CLI.
|
|
14
28
|
- Improve export ergonomics with local report-type validation and instructive failure guidance for processing, failed, and expired-download states.
|
|
15
29
|
|
|
16
30
|
## 0.1.6 - 2026-03-19
|
|
31
|
+
|
|
17
32
|
- Fix runtime version reporting so `socialseal --version` reads from package metadata instead of a hardcoded source string.
|
|
18
33
|
- Fix `tracking` create request translation so `--workspace-id` is sent on the REST query path the backend uses for workspace binding.
|
|
19
34
|
- 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.
|
|
@@ -22,28 +37,34 @@
|
|
|
22
37
|
- Clarify in workspace discovery output and docs that `workspace_id` and `brand_id` are different identifiers.
|
|
23
38
|
|
|
24
39
|
## 0.1.5 - 2026-03-19
|
|
40
|
+
|
|
25
41
|
- Add first-class tracked-video workflows with `video queue-analysis` and `video extract`.
|
|
26
42
|
- Make `--video-id` the primary ergonomic selector for tracked-video analysis and asset extraction, while keeping `--search-result-id` as a fallback selector.
|
|
27
43
|
- Support batch queueing/extraction payloads plus optional asset downloads for thumbnails, source video, and extracted key frames.
|
|
28
44
|
|
|
29
45
|
## 0.1.4 - 2026-03-19
|
|
46
|
+
|
|
30
47
|
- Add explicit `group_add_item` / `group_add_items` CLI aliases for tracking-group membership workflows.
|
|
31
48
|
- Add `tracking resolve` / `get_by_value` so existing tracked searches can be resolved by value using the same duplicate-detection semantics as create.
|
|
32
49
|
- Return operational duplicate metadata for tracking conflicts, including `existing_item_id`, `member_of_group_ids`, platform, region, workspace, and active state.
|
|
33
50
|
|
|
34
51
|
## 0.1.3 - 2026-03-19
|
|
52
|
+
|
|
35
53
|
- Republish the current CLI release line after the successful `0.1.2` npm publish, keeping the internal and OSS package versions aligned.
|
|
36
54
|
|
|
37
55
|
## 0.1.2 - 2026-03-18
|
|
56
|
+
|
|
38
57
|
- 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.
|
|
39
58
|
- Add `--poll-interval <ms>` for async `search-journey-run` status polling.
|
|
40
59
|
- Treat terminal async `search-journey-run` failures as non-zero CLI exits instead of silent `200` JSON output.
|
|
41
60
|
|
|
42
61
|
## 0.1.1 - 2026-03-13
|
|
62
|
+
|
|
43
63
|
- Document public base URL and CLI error output.
|
|
44
64
|
- Add request timeouts, verbose error output, and OSS-safe tool discovery behavior.
|
|
45
65
|
- Ship a stable built-in tool registry for `tools list` instead of the hard-disabled discovery message.
|
|
46
66
|
- Fail fast on agent WebSocket `error` events and surface session/tool progress diagnostics in `--verbose` mode.
|
|
47
67
|
|
|
48
68
|
## 0.1.0
|
|
69
|
+
|
|
49
70
|
- 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.10",
|
|
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
|
@@ -33,6 +33,8 @@ const ACTIVE_STATUS_VALUES = new Set(['queued', 'pending', 'processing', 'in_pro
|
|
|
33
33
|
const TOOL_STATUS_KINDS = new Set(['auto', 'agent_job', 'google_ai_run', 'journey_run']);
|
|
34
34
|
const REPORT_TYPE_SEARCH_RESULTS_ENRICHED = 'search_results_enriched';
|
|
35
35
|
const EXPORT_DATA_TEMPLATE_TRACKING_RANKED_VIDEOS_RAW = 'tracking_ranked_videos_raw';
|
|
36
|
+
const EXPORT_DATA_TEMPLATE_GOOGLE_AI_SEARCH_SUMMARIES_RAW = 'google_ai_search_summaries_raw';
|
|
37
|
+
const PLATFORM_ID_GOOGLE_AI = 11;
|
|
36
38
|
const SUPPORTED_EXPORT_REPORT_TYPES = [
|
|
37
39
|
'keyword_universe',
|
|
38
40
|
'cluster_insights',
|
|
@@ -53,12 +55,20 @@ const EXPORT_OPTIONS = [
|
|
|
53
55
|
{
|
|
54
56
|
id: 'search_results_enriched',
|
|
55
57
|
command: 'socialseal data export-search-results --group-ids <id,id,...>',
|
|
56
|
-
summary: 'Enriched ranked search rows (search
|
|
58
|
+
summary: 'Enriched ranked search rows (search capture + video publish/observed dates + latest metrics + scoped resurfacing history + analysis).',
|
|
57
59
|
formats: ['csv'],
|
|
58
60
|
required: ['workspace id', '--group-ids'],
|
|
59
|
-
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.',
|
|
60
62
|
alias: 'socialseal data export-report --report-type search_results_enriched --format csv --payload @payload.json',
|
|
61
63
|
},
|
|
64
|
+
{
|
|
65
|
+
id: 'group_evidence',
|
|
66
|
+
command: 'socialseal data export-group-evidence --group-id <id> --workspace-id <uuid>',
|
|
67
|
+
summary: 'Unified group evidence export that routes social groups and Google AI groups to the right raw export template.',
|
|
68
|
+
formats: ['csv'],
|
|
69
|
+
required: ['workspace id', '--group-id'],
|
|
70
|
+
bestFor: 'Automation that needs usable evidence without knowing whether the group is social ranked search or Google AI.',
|
|
71
|
+
},
|
|
62
72
|
{
|
|
63
73
|
id: 'report_templates',
|
|
64
74
|
command: 'socialseal data export-report --report-type <type> --format <format> --payload @payload.json',
|
|
@@ -114,7 +124,7 @@ const KNOWN_TOOLS = [
|
|
|
114
124
|
transport: 'post_edge_function',
|
|
115
125
|
workspaceScoped: true,
|
|
116
126
|
knownLocalDevState: 'disabled_by_default',
|
|
117
|
-
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.',
|
|
118
128
|
},
|
|
119
129
|
{
|
|
120
130
|
name: 'export_tracking_data',
|
|
@@ -180,7 +190,7 @@ const KNOWN_TOOLS = [
|
|
|
180
190
|
workspaceScoped: true,
|
|
181
191
|
knownLocalDevState: 'disabled_by_default',
|
|
182
192
|
actionAliases: ['list', 'get', 'create', 'update', 'delete', 'refresh', 'list_items', 'add_item', 'group_add_item', 'add_items', 'group_add_items', 'remove_item', 'group_remove_item'],
|
|
183
|
-
notes: 'REST-style surface under /groups. `add_item`/`group_add_item` accepts an existing `item_id`; `add_items`/`group_add_items` accepts `item_ids` or item payloads for bulk membership adds. Always pass a workspace id or configure a default workspace so the backend does not fall back to the personal workspace.',
|
|
193
|
+
notes: 'REST-style surface under /groups. `add_item`/`group_add_item` accepts an existing `item_id`; `add_items`/`group_add_items` accepts `item_ids` or item payloads for bulk membership adds. `completeness` checks expected memberships and refresh queue visibility. Always pass a workspace id or configure a default workspace so the backend does not fall back to the personal workspace.',
|
|
184
194
|
},
|
|
185
195
|
{
|
|
186
196
|
name: 'tracking',
|
|
@@ -366,6 +376,68 @@ const TOOL_SCHEMA_HINTS = {
|
|
|
366
376
|
'socialseal tools call --function get-google-ai-search-results --body \'{"runId":6809,"includeCitations":true,"limit":10}\'',
|
|
367
377
|
],
|
|
368
378
|
},
|
|
379
|
+
'group-management': {
|
|
380
|
+
summary: 'Manage single-platform tracking groups and memberships.',
|
|
381
|
+
operations: [
|
|
382
|
+
{
|
|
383
|
+
action: 'create',
|
|
384
|
+
required: ['action=create', 'name', 'workspaceId or --workspace-id'],
|
|
385
|
+
optional: [
|
|
386
|
+
'platform (defaults to tiktok)',
|
|
387
|
+
'description',
|
|
388
|
+
'refresh_frequency',
|
|
389
|
+
'next_refresh_at',
|
|
390
|
+
'brand_id',
|
|
391
|
+
],
|
|
392
|
+
notes: 'Supported platform values: tiktok, instagram, youtube, ig_reels, yt_shorts, douyin, xhs, google_ai.',
|
|
393
|
+
example: {
|
|
394
|
+
action: 'create',
|
|
395
|
+
name: 'YouTube competitor searches',
|
|
396
|
+
platform: 'youtube',
|
|
397
|
+
},
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
action: 'add_items',
|
|
401
|
+
required: ['action=add_items', 'group_id', 'workspaceId or --workspace-id'],
|
|
402
|
+
optional: ['item_ids', 'items', 'platform/groupPlatform for item payload defaults'],
|
|
403
|
+
notes: 'When adding item payloads, omit item platform to inherit the group platform, or pass platform explicitly.',
|
|
404
|
+
example: {
|
|
405
|
+
action: 'add_items',
|
|
406
|
+
group_id: 123,
|
|
407
|
+
items: [
|
|
408
|
+
{
|
|
409
|
+
name: 'best kenya safari',
|
|
410
|
+
type: 'keyword',
|
|
411
|
+
value: 'best kenya safari',
|
|
412
|
+
region: 'US',
|
|
413
|
+
},
|
|
414
|
+
],
|
|
415
|
+
},
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
action: 'completeness',
|
|
419
|
+
required: ['action=completeness', 'group_id', 'items or expected_items', 'workspaceId or --workspace-id'],
|
|
420
|
+
optional: ['include_refresh_status'],
|
|
421
|
+
notes: 'Returns durable setup completeness and aggregate refresh queue status for the group.',
|
|
422
|
+
example: {
|
|
423
|
+
action: 'completeness',
|
|
424
|
+
group_id: 123,
|
|
425
|
+
expected_items: [
|
|
426
|
+
{
|
|
427
|
+
track_type: 'search',
|
|
428
|
+
track_value: 'best kenya safari',
|
|
429
|
+
region: 'US',
|
|
430
|
+
},
|
|
431
|
+
],
|
|
432
|
+
},
|
|
433
|
+
},
|
|
434
|
+
],
|
|
435
|
+
cliExamples: [
|
|
436
|
+
'socialseal tools call --function group-management --workspace-id <workspace-uuid> --body \'{"action":"create","name":"YouTube group","platform":"youtube"}\'',
|
|
437
|
+
'socialseal tools call --function group-management --workspace-id <workspace-uuid> --body \'{"action":"create","name":"Instagram group","platform":"instagram"}\'',
|
|
438
|
+
'socialseal tools call --function group-management --workspace-id <workspace-uuid> --body \'{"action":"add_items","group_id":123,"items":[{"name":"best kenya safari","type":"keyword","value":"best kenya safari","region":"US"}]}\'',
|
|
439
|
+
],
|
|
440
|
+
},
|
|
369
441
|
};
|
|
370
442
|
|
|
371
443
|
function getToolSchemaHint(functionName) {
|
|
@@ -1006,6 +1078,11 @@ function isTerminalStatusValue(value) {
|
|
|
1006
1078
|
return !ACTIVE_STATUS_VALUES.has(normalized);
|
|
1007
1079
|
}
|
|
1008
1080
|
|
|
1081
|
+
function isFailedStatusValue(value) {
|
|
1082
|
+
const normalized = normalizeStatusValue(value);
|
|
1083
|
+
return normalized === 'failed' || normalized === 'error';
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1009
1086
|
function coercePositiveInteger(value, label) {
|
|
1010
1087
|
if (value === undefined || value === null || value === '') return undefined;
|
|
1011
1088
|
const parsed = Number(value);
|
|
@@ -1104,6 +1181,200 @@ function buildSearchResultsEnrichedExportPayload(rawPayload, workspaceId) {
|
|
|
1104
1181
|
});
|
|
1105
1182
|
}
|
|
1106
1183
|
|
|
1184
|
+
function buildGoogleAiSearchSummariesExportPayload(rawPayload, workspaceId) {
|
|
1185
|
+
const payload = isJsonObject(rawPayload) ? rawPayload : {};
|
|
1186
|
+
const groupId = coercePositiveInteger(firstDefined(payload, ['groupId', 'group_id']), 'groupId');
|
|
1187
|
+
if (!groupId) {
|
|
1188
|
+
throw new CliError('Google AI evidence export requires a group id.', {
|
|
1189
|
+
code: 'MISSING_ARGUMENT',
|
|
1190
|
+
exitCode: EXIT_CODES.USAGE,
|
|
1191
|
+
hint: 'Provide --group-id or groupId in the payload.',
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
const trackingItemIds = normalizePositiveIntegerList(
|
|
1196
|
+
firstDefined(payload, ['trackingItemIds', 'tracking_item_ids']),
|
|
1197
|
+
'trackingItemIds',
|
|
1198
|
+
{ max: 1000 },
|
|
1199
|
+
);
|
|
1200
|
+
const filename = trimString(firstDefined(payload, ['filename'])) || undefined;
|
|
1201
|
+
|
|
1202
|
+
return stripUndefinedEntries({
|
|
1203
|
+
workspaceId,
|
|
1204
|
+
groupId,
|
|
1205
|
+
trackingItemIds: trackingItemIds.length > 0 ? trackingItemIds : undefined,
|
|
1206
|
+
filename,
|
|
1207
|
+
});
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
function normalizeEvidenceSurface(value) {
|
|
1211
|
+
const normalized = trimString(value || 'auto').toLowerCase().replace(/-/g, '_').replace(/\s+/g, '_');
|
|
1212
|
+
if (['auto', 'social', 'ranked', 'ranked_search', 'google_ai', 'google'].includes(normalized)) {
|
|
1213
|
+
if (normalized === 'ranked' || normalized === 'ranked_search') return 'social';
|
|
1214
|
+
if (normalized === 'google') return 'google_ai';
|
|
1215
|
+
return normalized;
|
|
1216
|
+
}
|
|
1217
|
+
throw new CliError(`Unsupported evidence surface: ${value}`, {
|
|
1218
|
+
code: 'INVALID_ARGUMENT',
|
|
1219
|
+
exitCode: EXIT_CODES.USAGE,
|
|
1220
|
+
hint: 'Use --surface auto|social|google_ai.',
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
function normalizePlatformKey(value) {
|
|
1225
|
+
const normalized = trimString(value).toLowerCase().replace(/-/g, '_').replace(/\s+/g, '_');
|
|
1226
|
+
if (!normalized) return null;
|
|
1227
|
+
if (['google', 'google_ai', 'google_ai_overview', 'ai_search'].includes(normalized)) return 'google_ai';
|
|
1228
|
+
return normalized;
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
function resolveEvidenceSurfaceFromGroup(group) {
|
|
1232
|
+
if (!isJsonObject(group)) return 'social';
|
|
1233
|
+
const platformId = firstDefined(group, ['platform_id', 'platformId']);
|
|
1234
|
+
if (Number(platformId) === PLATFORM_ID_GOOGLE_AI) return 'google_ai';
|
|
1235
|
+
const platform = normalizePlatformKey(firstDefined(group, ['platform', 'platform_key', 'platformKey']));
|
|
1236
|
+
return platform === 'google_ai' ? 'google_ai' : 'social';
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
function buildGroupEvidenceMetadata({ surface, groupId, workspaceId, responseJson }) {
|
|
1240
|
+
const metadata = isJsonObject(responseJson) && isJsonObject(responseJson.metadata)
|
|
1241
|
+
? responseJson.metadata
|
|
1242
|
+
: {};
|
|
1243
|
+
const rowCount = Number.isFinite(metadata.row_count) ? metadata.row_count : null;
|
|
1244
|
+
return {
|
|
1245
|
+
group_id: groupId,
|
|
1246
|
+
workspace_id: workspaceId,
|
|
1247
|
+
platform: surface === 'google_ai' ? 'google_ai' : 'social',
|
|
1248
|
+
surface,
|
|
1249
|
+
row_count: rowCount,
|
|
1250
|
+
generated_at: new Date().toISOString(),
|
|
1251
|
+
header_only: rowCount === 0,
|
|
1252
|
+
template: metadata.template ?? (surface === 'google_ai'
|
|
1253
|
+
? EXPORT_DATA_TEMPLATE_GOOGLE_AI_SEARCH_SUMMARIES_RAW
|
|
1254
|
+
: EXPORT_DATA_TEMPLATE_TRACKING_RANKED_VIDEOS_RAW),
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
function normalizeCompletenessPlatform(rawItem) {
|
|
1259
|
+
const platformId = firstDefined(rawItem, ['platform_id', 'platformId']);
|
|
1260
|
+
if (Number.isFinite(Number(platformId))) return `id:${Number(platformId)}`;
|
|
1261
|
+
const platform = normalizePlatformKey(firstDefined(rawItem, ['platform', 'platform_key', 'platformKey']));
|
|
1262
|
+
return platform ? `key:${platform}` : '';
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
function normalizeCompletenessItem(rawItem, index, source) {
|
|
1266
|
+
if (!isJsonObject(rawItem)) {
|
|
1267
|
+
throw new CliError(`Invalid ${source} item at index ${index}: expected an object.`, {
|
|
1268
|
+
code: 'INVALID_MANIFEST',
|
|
1269
|
+
exitCode: EXIT_CODES.USAGE,
|
|
1270
|
+
});
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
const id = coercePositiveInteger(firstDefined(rawItem, ['item_id', 'itemId', 'id']), `${source}[${index}].item_id`);
|
|
1274
|
+
const rawValue = firstDefined(rawItem, ['track_value', 'trackValue', 'value', 'name']);
|
|
1275
|
+
const value = trimString(rawValue).toLowerCase();
|
|
1276
|
+
const rawType = firstDefined(rawItem, ['track_type', 'trackType', 'type']);
|
|
1277
|
+
const type = normalizeTrackingType(rawType) || trimString(rawType).toLowerCase() || '';
|
|
1278
|
+
const region = trimString(firstDefined(rawItem, ['region'])).toUpperCase();
|
|
1279
|
+
const platform = normalizeCompletenessPlatform(rawItem);
|
|
1280
|
+
const hasSemanticKey = Boolean(value && type);
|
|
1281
|
+
const key = hasSemanticKey ? `${type}|${value}|${region}|${platform}` : `id:${id}`;
|
|
1282
|
+
const looseKey = hasSemanticKey ? `${type}|${value}|${region}` : `id:${id}`;
|
|
1283
|
+
|
|
1284
|
+
if (!id && (!value || !type)) {
|
|
1285
|
+
throw new CliError(`Invalid ${source} item at index ${index}: expected item_id or track_type + track_value.`, {
|
|
1286
|
+
code: 'INVALID_MANIFEST',
|
|
1287
|
+
exitCode: EXIT_CODES.USAGE,
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
return {
|
|
1292
|
+
key,
|
|
1293
|
+
looseKey,
|
|
1294
|
+
item_id: id ?? null,
|
|
1295
|
+
track_type: type || null,
|
|
1296
|
+
track_value: value || null,
|
|
1297
|
+
region: region || null,
|
|
1298
|
+
platform: platform || null,
|
|
1299
|
+
raw: rawItem,
|
|
1300
|
+
};
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
function extractExpectedManifestItems(manifest) {
|
|
1304
|
+
if (Array.isArray(manifest)) return manifest;
|
|
1305
|
+
if (!isJsonObject(manifest)) {
|
|
1306
|
+
throw new CliError('Invalid completeness manifest: expected an array or object with items.', {
|
|
1307
|
+
code: 'INVALID_MANIFEST',
|
|
1308
|
+
exitCode: EXIT_CODES.USAGE,
|
|
1309
|
+
});
|
|
1310
|
+
}
|
|
1311
|
+
const items = firstDefined(manifest, ['items', 'expectedItems', 'expected_items']);
|
|
1312
|
+
if (!Array.isArray(items)) {
|
|
1313
|
+
throw new CliError('Invalid completeness manifest: expected items, expectedItems, or expected_items array.', {
|
|
1314
|
+
code: 'INVALID_MANIFEST',
|
|
1315
|
+
exitCode: EXIT_CODES.USAGE,
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
return items;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
function normalizeActualGroupItem(rawItem, index) {
|
|
1322
|
+
const tracking = isJsonObject(rawItem?.user_tracking) ? rawItem.user_tracking : rawItem;
|
|
1323
|
+
return normalizeCompletenessItem(tracking, index, 'actual_items');
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
function buildCompletenessDiff({ expectedItems, actualItems, groupId, workspaceId }) {
|
|
1327
|
+
const expected = expectedItems.map((item, index) =>
|
|
1328
|
+
normalizeCompletenessItem(item, index, 'expected_items')
|
|
1329
|
+
);
|
|
1330
|
+
const actual = actualItems.map((item, index) => normalizeActualGroupItem(item, index));
|
|
1331
|
+
const actualByKey = new Map(actual.map((item) => [item.key, item]));
|
|
1332
|
+
const expectedByKey = new Map(expected.map((item) => [item.key, item]));
|
|
1333
|
+
const actualByLooseKey = new Map(actual.map((item) => [item.looseKey, item]));
|
|
1334
|
+
const expectedByLooseKey = new Map(expected.map((item) => [item.looseKey, item]));
|
|
1335
|
+
|
|
1336
|
+
const missing = [];
|
|
1337
|
+
const platformMismatches = [];
|
|
1338
|
+
for (const expectedItem of expected) {
|
|
1339
|
+
if (actualByKey.has(expectedItem.key)) continue;
|
|
1340
|
+
const looseMatch = actualByLooseKey.get(expectedItem.looseKey);
|
|
1341
|
+
if (looseMatch && expectedItem.platform !== looseMatch.platform) {
|
|
1342
|
+
platformMismatches.push({
|
|
1343
|
+
expected: expectedItem.raw,
|
|
1344
|
+
actual: looseMatch.raw,
|
|
1345
|
+
expected_platform: expectedItem.platform,
|
|
1346
|
+
actual_platform: looseMatch.platform,
|
|
1347
|
+
});
|
|
1348
|
+
continue;
|
|
1349
|
+
}
|
|
1350
|
+
missing.push(expectedItem.raw);
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
const extra = [];
|
|
1354
|
+
for (const actualItem of actual) {
|
|
1355
|
+
if (expectedByKey.has(actualItem.key)) continue;
|
|
1356
|
+
const looseMatch = expectedByLooseKey.get(actualItem.looseKey);
|
|
1357
|
+
if (looseMatch && looseMatch.platform !== actualItem.platform) continue;
|
|
1358
|
+
extra.push(actualItem.raw);
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
const complete = missing.length === 0 && extra.length === 0 && platformMismatches.length === 0;
|
|
1362
|
+
return {
|
|
1363
|
+
success: complete,
|
|
1364
|
+
complete,
|
|
1365
|
+
group_id: groupId,
|
|
1366
|
+
workspace_id: workspaceId,
|
|
1367
|
+
expected_count: expected.length,
|
|
1368
|
+
actual_count: actual.length,
|
|
1369
|
+
missing_count: missing.length,
|
|
1370
|
+
extra_count: extra.length,
|
|
1371
|
+
platform_mismatch_count: platformMismatches.length,
|
|
1372
|
+
missing,
|
|
1373
|
+
extra,
|
|
1374
|
+
platform_mismatches: platformMismatches,
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1107
1378
|
function buildPathWithQuery(basePath, query) {
|
|
1108
1379
|
const params = new URLSearchParams();
|
|
1109
1380
|
for (const [key, rawValue] of Object.entries(query || {})) {
|
|
@@ -1174,8 +1445,10 @@ function normalizeGroupManagementPayload(payload, fallbackWorkspaceId) {
|
|
|
1174
1445
|
const itemId = firstDefined(payload, ['item_id', 'itemId']);
|
|
1175
1446
|
const itemIds = firstDefined(payload, ['item_ids', 'itemIds']);
|
|
1176
1447
|
const items = firstDefined(payload, ['items']);
|
|
1448
|
+
const expectedItems = firstDefined(payload, ['expected_items', 'expectedItems']);
|
|
1177
1449
|
const limit = firstDefined(payload, ['limit']);
|
|
1178
1450
|
const page = firstDefined(payload, ['page']);
|
|
1451
|
+
const force = firstDefined(payload, ['force']);
|
|
1179
1452
|
return stripUndefinedEntries({
|
|
1180
1453
|
action: trimString(firstDefined(payload, ['action'])) || undefined,
|
|
1181
1454
|
workspaceId: resolvePayloadWorkspaceId(payload, fallbackWorkspaceId),
|
|
@@ -1194,6 +1467,7 @@ function normalizeGroupManagementPayload(payload, fallbackWorkspaceId) {
|
|
|
1194
1467
|
})
|
|
1195
1468
|
: undefined,
|
|
1196
1469
|
items: Array.isArray(items) ? items : undefined,
|
|
1470
|
+
expected_items: Array.isArray(expectedItems) ? expectedItems : undefined,
|
|
1197
1471
|
name: trimString(firstDefined(payload, ['name'])) || undefined,
|
|
1198
1472
|
description: firstDefined(payload, ['description']),
|
|
1199
1473
|
platform: trimString(firstDefined(payload, ['platform', 'groupPlatform'])) || undefined,
|
|
@@ -1207,6 +1481,7 @@ function normalizeGroupManagementPayload(payload, fallbackWorkspaceId) {
|
|
|
1207
1481
|
: firstDefined(payload, ['region']),
|
|
1208
1482
|
limit: limit !== undefined ? Number(limit) : undefined,
|
|
1209
1483
|
page: page !== undefined ? Number(page) : undefined,
|
|
1484
|
+
force: force === true || trimString(force).toLowerCase() === 'true' ? true : undefined,
|
|
1210
1485
|
});
|
|
1211
1486
|
}
|
|
1212
1487
|
|
|
@@ -1696,6 +1971,28 @@ function translateGroupManagementAction(payload, workspaceId, originalMethod) {
|
|
|
1696
1971
|
};
|
|
1697
1972
|
}
|
|
1698
1973
|
|
|
1974
|
+
if (action === 'completeness' || action === 'group_completeness') {
|
|
1975
|
+
const groupId = coercePositiveInteger(payload.group_id, 'group_id');
|
|
1976
|
+
if (!groupId) {
|
|
1977
|
+
throw new CliError('group_id is required for group completeness.', {
|
|
1978
|
+
code: 'MISSING_ARGUMENT',
|
|
1979
|
+
exitCode: EXIT_CODES.USAGE,
|
|
1980
|
+
});
|
|
1981
|
+
}
|
|
1982
|
+
const expectedItems = Array.isArray(payload.expected_items)
|
|
1983
|
+
? payload.expected_items
|
|
1984
|
+
: (Array.isArray(payload.items) ? payload.items : undefined);
|
|
1985
|
+
return {
|
|
1986
|
+
method: 'POST',
|
|
1987
|
+
pathSuffix: buildPathWithQuery(`/groups/${groupId}/completeness`, { workspace_id: workspaceId || undefined }),
|
|
1988
|
+
body: stripUndefinedEntries({
|
|
1989
|
+
expected_items: expectedItems,
|
|
1990
|
+
include_refresh_status: firstDefined(payload, ['include_refresh_status', 'includeRefreshStatus']),
|
|
1991
|
+
}),
|
|
1992
|
+
workspaceId,
|
|
1993
|
+
};
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1699
1996
|
if (action === 'remove_item' || action === 'group_remove_item') {
|
|
1700
1997
|
const groupId = coercePositiveInteger(payload.group_id, 'group_id');
|
|
1701
1998
|
const itemId = coercePositiveInteger(payload.item_id, 'item_id');
|
|
@@ -1716,7 +2013,7 @@ function translateGroupManagementAction(payload, workspaceId, originalMethod) {
|
|
|
1716
2013
|
throw new CliError(`Unsupported group-management action: ${payload.action}`, {
|
|
1717
2014
|
code: 'INVALID_ARGUMENT',
|
|
1718
2015
|
exitCode: EXIT_CODES.USAGE,
|
|
1719
|
-
hint: 'Supported group-management actions: list, get, create, update, delete, refresh, list_items, add_item, add_items, remove_item.',
|
|
2016
|
+
hint: 'Supported group-management actions: list, get, create, update, delete, refresh, list_items, add_item, add_items, completeness, remove_item.',
|
|
1720
2017
|
});
|
|
1721
2018
|
}
|
|
1722
2019
|
|
|
@@ -2005,6 +2302,145 @@ function emitJsonOutput(value, pretty) {
|
|
|
2005
2302
|
process.stdout.write(formatJsonOutput(value, pretty) + '\n');
|
|
2006
2303
|
}
|
|
2007
2304
|
|
|
2305
|
+
function isGroupManagementBulkAddInvocation(functionName, translated) {
|
|
2306
|
+
if (functionName !== 'group-management') return false;
|
|
2307
|
+
const action = isJsonObject(translated.normalizedPayload)
|
|
2308
|
+
? trimString(translated.normalizedPayload.action).toLowerCase()
|
|
2309
|
+
: '';
|
|
2310
|
+
return action === 'add_items' || action === 'group_add_items';
|
|
2311
|
+
}
|
|
2312
|
+
|
|
2313
|
+
function getBulkAddExpectedCount(translated) {
|
|
2314
|
+
return Array.isArray(translated.body) ? translated.body.length : null;
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
function maybeThrowGroupManagementBulkAddPartialFailure(data, translated) {
|
|
2318
|
+
if (!isJsonObject(data)) return;
|
|
2319
|
+
|
|
2320
|
+
const errors = Array.isArray(data.errors) ? data.errors : [];
|
|
2321
|
+
const expected = getBulkAddExpectedCount(translated);
|
|
2322
|
+
const processed = Number.isFinite(data.items_processed) ? data.items_processed : null;
|
|
2323
|
+
const added = Number.isFinite(data.items_added) ? data.items_added : 0;
|
|
2324
|
+
const linked = Number.isFinite(data.items_linked) ? data.items_linked : 0;
|
|
2325
|
+
const alreadyLinked = Number.isFinite(data.items_already_linked) ? data.items_already_linked : 0;
|
|
2326
|
+
const successful = added + linked + alreadyLinked;
|
|
2327
|
+
const failed = errors.length;
|
|
2328
|
+
const hasErrors = failed > 0;
|
|
2329
|
+
const hasProcessedMismatch = expected !== null && processed !== null && processed !== expected;
|
|
2330
|
+
const hasSuccessMismatch = expected !== null && successful !== expected;
|
|
2331
|
+
const hasVerificationFailure = data.verified === false;
|
|
2332
|
+
|
|
2333
|
+
if (!hasErrors && !hasProcessedMismatch && !hasSuccessMismatch && !hasVerificationFailure) return;
|
|
2334
|
+
|
|
2335
|
+
throw new CliError('group-management add_items partially failed.', {
|
|
2336
|
+
code: 'PARTIAL_FAILURE',
|
|
2337
|
+
exitCode: EXIT_CODES.SERVER,
|
|
2338
|
+
hint: 'Inspect errors[] and re-run add_items after fixing failed items; automation should treat this as an unsuccessful setup.',
|
|
2339
|
+
details: truncateDetails({
|
|
2340
|
+
expected,
|
|
2341
|
+
processed,
|
|
2342
|
+
added,
|
|
2343
|
+
linked,
|
|
2344
|
+
alreadyLinked,
|
|
2345
|
+
successful,
|
|
2346
|
+
failed,
|
|
2347
|
+
verified: data.verified,
|
|
2348
|
+
finalGroupItemCount: data.final_group_item_count,
|
|
2349
|
+
expectedFinalGroupItemCount: data.expected_final_group_item_count,
|
|
2350
|
+
errors,
|
|
2351
|
+
response: data,
|
|
2352
|
+
}),
|
|
2353
|
+
});
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2356
|
+
function getGroupManagementAction(translated) {
|
|
2357
|
+
return isJsonObject(translated.normalizedPayload)
|
|
2358
|
+
? trimString(translated.normalizedPayload.action).toLowerCase()
|
|
2359
|
+
: '';
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2362
|
+
function isGroupManagementRefreshInvocation(functionName, translated) {
|
|
2363
|
+
if (functionName !== 'group-management') return false;
|
|
2364
|
+
const action = getGroupManagementAction(translated);
|
|
2365
|
+
return action === 'refresh' || action === 'group_refresh';
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
function getGroupManagementGroupId(translated) {
|
|
2369
|
+
return isJsonObject(translated.normalizedPayload)
|
|
2370
|
+
? coercePositiveInteger(translated.normalizedPayload.group_id, 'group_id')
|
|
2371
|
+
: undefined;
|
|
2372
|
+
}
|
|
2373
|
+
|
|
2374
|
+
function getGroupItemsCount(data) {
|
|
2375
|
+
if (Array.isArray(data)) return data.length;
|
|
2376
|
+
if (!isJsonObject(data)) return null;
|
|
2377
|
+
if (Number.isFinite(data.total)) return data.total;
|
|
2378
|
+
if (Array.isArray(data.items)) return data.items.length;
|
|
2379
|
+
if (Array.isArray(data.data)) return data.data.length;
|
|
2380
|
+
return null;
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2383
|
+
async function preflightGroupRefreshNotEmpty({
|
|
2384
|
+
opts,
|
|
2385
|
+
translated,
|
|
2386
|
+
apiBase,
|
|
2387
|
+
apiKey,
|
|
2388
|
+
pathPrefix,
|
|
2389
|
+
workspaceId,
|
|
2390
|
+
timeoutMs,
|
|
2391
|
+
}) {
|
|
2392
|
+
if (!isGroupManagementRefreshInvocation(opts.function, translated)) return;
|
|
2393
|
+
if (translated.normalizedPayload?.force === true) return;
|
|
2394
|
+
|
|
2395
|
+
const groupId = getGroupManagementGroupId(translated);
|
|
2396
|
+
if (!groupId) return;
|
|
2397
|
+
|
|
2398
|
+
const res = await callApi({
|
|
2399
|
+
apiBase,
|
|
2400
|
+
apiKey,
|
|
2401
|
+
path: `${pathPrefix}/groups/${groupId}/items?${new URLSearchParams({
|
|
2402
|
+
workspace_id: workspaceId,
|
|
2403
|
+
page: '1',
|
|
2404
|
+
limit: '1',
|
|
2405
|
+
}).toString()}`,
|
|
2406
|
+
method: 'GET',
|
|
2407
|
+
workspaceId,
|
|
2408
|
+
timeoutMs,
|
|
2409
|
+
});
|
|
2410
|
+
|
|
2411
|
+
if (!res.ok) {
|
|
2412
|
+
throw await buildHttpError(res, {
|
|
2413
|
+
label: 'Group refresh preflight',
|
|
2414
|
+
functionName: 'group-management',
|
|
2415
|
+
method: 'GET',
|
|
2416
|
+
});
|
|
2417
|
+
}
|
|
2418
|
+
|
|
2419
|
+
const contentType = res.headers.get('content-type') || '';
|
|
2420
|
+
if (!contentType.includes('application/json')) {
|
|
2421
|
+
throw new CliError('Group refresh preflight returned a non-JSON response.', {
|
|
2422
|
+
code: 'INVALID_RESPONSE',
|
|
2423
|
+
exitCode: EXIT_CODES.SERVER,
|
|
2424
|
+
});
|
|
2425
|
+
}
|
|
2426
|
+
|
|
2427
|
+
const data = await res.json();
|
|
2428
|
+
const itemCount = getGroupItemsCount(data);
|
|
2429
|
+
if (itemCount === 0) {
|
|
2430
|
+
throw new CliError('Refusing to refresh an empty tracking group.', {
|
|
2431
|
+
code: 'EMPTY_GROUP_REFRESH',
|
|
2432
|
+
exitCode: EXIT_CODES.USAGE,
|
|
2433
|
+
hint: 'Add items to the group first, or pass force:true in the group-management payload to override.',
|
|
2434
|
+
details: truncateDetails({
|
|
2435
|
+
groupId,
|
|
2436
|
+
workspaceId,
|
|
2437
|
+
itemCount,
|
|
2438
|
+
response: data,
|
|
2439
|
+
}),
|
|
2440
|
+
});
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2008
2444
|
function buildSearchJourneyRunFailure(data) {
|
|
2009
2445
|
const message = isJsonObject(data) && typeof data.error === 'string' && data.error.trim().length > 0
|
|
2010
2446
|
? data.error
|
|
@@ -3188,6 +3624,18 @@ async function handleToolsCall(opts) {
|
|
|
3188
3624
|
method,
|
|
3189
3625
|
});
|
|
3190
3626
|
|
|
3627
|
+
if (isGroupManagementRefreshInvocation(opts.function, translated)) {
|
|
3628
|
+
await preflightGroupRefreshNotEmpty({
|
|
3629
|
+
opts,
|
|
3630
|
+
translated,
|
|
3631
|
+
apiBase: useGateway ? resolvedApiBase : legacyUrl,
|
|
3632
|
+
apiKey,
|
|
3633
|
+
pathPrefix: useGateway ? '/cli/tools/group-management' : '/functions/v1/group-management',
|
|
3634
|
+
workspaceId: effectiveWorkspaceId,
|
|
3635
|
+
timeoutMs,
|
|
3636
|
+
});
|
|
3637
|
+
}
|
|
3638
|
+
|
|
3191
3639
|
const res = await callApi({
|
|
3192
3640
|
apiBase: useGateway ? resolvedApiBase : legacyUrl,
|
|
3193
3641
|
apiKey,
|
|
@@ -3211,6 +3659,9 @@ async function handleToolsCall(opts) {
|
|
|
3211
3659
|
const data = await res.json();
|
|
3212
3660
|
const shouldPoll = shouldHandleSearchJourneyRunAsync(opts.function, method, payload, opts) && opts.poll !== false;
|
|
3213
3661
|
if (!shouldPoll) {
|
|
3662
|
+
if (isGroupManagementBulkAddInvocation(opts.function, translated)) {
|
|
3663
|
+
maybeThrowGroupManagementBulkAddPartialFailure(data, translated);
|
|
3664
|
+
}
|
|
3214
3665
|
maybeEmitFollowupStatusHint({
|
|
3215
3666
|
functionName: opts.function,
|
|
3216
3667
|
data,
|
|
@@ -3433,6 +3884,14 @@ async function handleToolsStatus(opts) {
|
|
|
3433
3884
|
...result,
|
|
3434
3885
|
hint: commandHint,
|
|
3435
3886
|
};
|
|
3887
|
+
if (opts.wait && isFailedStatusValue(result.status)) {
|
|
3888
|
+
throw new CliError(`${result.kind} reached terminal ${result.status} status.`, {
|
|
3889
|
+
code: 'STATUS_FAILED',
|
|
3890
|
+
exitCode: EXIT_CODES.SERVER,
|
|
3891
|
+
hint: commandHint,
|
|
3892
|
+
details: truncateDetails(payload),
|
|
3893
|
+
});
|
|
3894
|
+
}
|
|
3436
3895
|
emitJsonOutput(payload, opts.pretty);
|
|
3437
3896
|
}
|
|
3438
3897
|
|
|
@@ -3586,46 +4045,27 @@ async function handleDataExportReport(opts) {
|
|
|
3586
4045
|
process.stdout.write(JSON.stringify(json, null, opts.pretty ? 2 : 0) + '\n');
|
|
3587
4046
|
}
|
|
3588
4047
|
|
|
3589
|
-
async function
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
filename: opts.filename,
|
|
3604
|
-
});
|
|
3605
|
-
|
|
3606
|
-
const payloadWorkspaceId = resolvePayloadWorkspaceId(rawPayload, null);
|
|
3607
|
-
const effectiveWorkspaceId = requireWorkspaceSelection(payloadWorkspaceId || resolvedWorkspaceId, {
|
|
3608
|
-
label: 'Search results enriched export',
|
|
3609
|
-
hint: 'Pass --workspace-id, set SOCIALSEAL_WORKSPACE_ID, or configure a default workspace before exporting.',
|
|
3610
|
-
});
|
|
3611
|
-
const effectiveWorkspaceSource = payloadWorkspaceId ? 'body' : workspaceSource;
|
|
3612
|
-
emitWorkspaceSelectionNotice(opts, {
|
|
3613
|
-
workspaceId: effectiveWorkspaceId,
|
|
3614
|
-
source: effectiveWorkspaceSource,
|
|
3615
|
-
label: 'search_results_enriched export',
|
|
3616
|
-
});
|
|
3617
|
-
|
|
3618
|
-
const normalizedPayload = buildSearchResultsEnrichedExportPayload(rawPayload, effectiveWorkspaceId);
|
|
3619
|
-
const requestedFilename = trimString(normalizedPayload.filename) || undefined;
|
|
3620
|
-
delete normalizedPayload.filename;
|
|
3621
|
-
|
|
4048
|
+
async function runExportDataCsv({
|
|
4049
|
+
opts,
|
|
4050
|
+
label,
|
|
4051
|
+
template,
|
|
4052
|
+
normalizedPayload,
|
|
4053
|
+
requestedFilename,
|
|
4054
|
+
effectiveWorkspaceId,
|
|
4055
|
+
apiBase,
|
|
4056
|
+
apiKey,
|
|
4057
|
+
useGateway,
|
|
4058
|
+
legacyUrl,
|
|
4059
|
+
timeoutMs,
|
|
4060
|
+
decorateResponse,
|
|
4061
|
+
}) {
|
|
3622
4062
|
const exportResponse = await callApi({
|
|
3623
|
-
apiBase: useGateway ?
|
|
4063
|
+
apiBase: useGateway ? apiBase : legacyUrl,
|
|
3624
4064
|
apiKey,
|
|
3625
4065
|
path: useGateway ? '/cli/tools/export-data' : '/functions/v1/export-data',
|
|
3626
4066
|
method: 'POST',
|
|
3627
4067
|
body: {
|
|
3628
|
-
template
|
|
4068
|
+
template,
|
|
3629
4069
|
format: 'csv',
|
|
3630
4070
|
payload: normalizedPayload,
|
|
3631
4071
|
filename: requestedFilename,
|
|
@@ -3636,13 +4076,16 @@ async function handleDataExportSearchResults(opts) {
|
|
|
3636
4076
|
|
|
3637
4077
|
if (!exportResponse.ok) {
|
|
3638
4078
|
throw await buildHttpError(exportResponse, {
|
|
3639
|
-
label
|
|
4079
|
+
label,
|
|
3640
4080
|
functionName: 'export-data',
|
|
3641
4081
|
method: 'POST',
|
|
3642
4082
|
});
|
|
3643
4083
|
}
|
|
3644
4084
|
|
|
3645
4085
|
const responseJson = await exportResponse.json();
|
|
4086
|
+
const decoratedResponse = typeof decorateResponse === 'function'
|
|
4087
|
+
? decorateResponse(responseJson)
|
|
4088
|
+
: responseJson;
|
|
3646
4089
|
const metadata = isJsonObject(responseJson) && isJsonObject(responseJson.metadata)
|
|
3647
4090
|
? responseJson.metadata
|
|
3648
4091
|
: null;
|
|
@@ -3657,7 +4100,7 @@ async function handleDataExportSearchResults(opts) {
|
|
|
3657
4100
|
} else {
|
|
3658
4101
|
process.stderr.write('[socialseal] Export did not include a file URL yet. Inspect the JSON metadata and retry if needed.\n');
|
|
3659
4102
|
}
|
|
3660
|
-
emitJsonOutput(
|
|
4103
|
+
emitJsonOutput(decoratedResponse, opts.pretty);
|
|
3661
4104
|
return;
|
|
3662
4105
|
}
|
|
3663
4106
|
|
|
@@ -3668,7 +4111,7 @@ async function handleDataExportSearchResults(opts) {
|
|
|
3668
4111
|
|
|
3669
4112
|
if (!artifactResponse.ok) {
|
|
3670
4113
|
throw await buildHttpError(artifactResponse, {
|
|
3671
|
-
label:
|
|
4114
|
+
label: `${label} artifact download`,
|
|
3672
4115
|
method: 'GET',
|
|
3673
4116
|
hint: 'The signed file URL may be expired or inaccessible. Re-run the export command to mint a fresh URL.',
|
|
3674
4117
|
});
|
|
@@ -3683,7 +4126,7 @@ async function handleDataExportSearchResults(opts) {
|
|
|
3683
4126
|
|
|
3684
4127
|
const outPath = opts.stdout
|
|
3685
4128
|
? null
|
|
3686
|
-
: (opts.out || trimString(metadata?.filename || '') ||
|
|
4129
|
+
: (opts.out || trimString(metadata?.filename || '') || `${template}.csv`);
|
|
3687
4130
|
if (outPath) {
|
|
3688
4131
|
await pipeline(artifactResponse.body, fs.createWriteStream(outPath));
|
|
3689
4132
|
process.stderr.write(`[socialseal] Export written to ${outPath}\n`);
|
|
@@ -3692,6 +4135,261 @@ async function handleDataExportSearchResults(opts) {
|
|
|
3692
4135
|
}
|
|
3693
4136
|
}
|
|
3694
4137
|
|
|
4138
|
+
async function handleDataExportSearchResults(opts) {
|
|
4139
|
+
const config = loadConfig();
|
|
4140
|
+
const apiKey = requireApiKey(opts, config);
|
|
4141
|
+
const apiBase = resolveApiBase(opts, config);
|
|
4142
|
+
const supabaseUrl = resolveLegacyUrl(resolveSupabaseUrl(opts, config), 'SOCIALSEAL_SUPABASE_URL');
|
|
4143
|
+
const { resolvedApiBase, legacyUrl, useGateway } = resolveApiTarget({ apiBase, legacyUrl: supabaseUrl });
|
|
4144
|
+
const timeoutMs = resolveTimeoutMs(opts, config);
|
|
4145
|
+
const { workspaceId: resolvedWorkspaceId, source: workspaceSource } = resolveWorkspaceSelection(opts, config);
|
|
4146
|
+
|
|
4147
|
+
const rawPayload = opts.__rawPayload ?? stripUndefinedEntries({
|
|
4148
|
+
groupIds: normalizePositiveIntegerList(opts.groupIds, 'groupIds', { max: 100 }),
|
|
4149
|
+
trackingItemIds: normalizePositiveIntegerList(opts.trackingItemIds, 'trackingItemIds', { max: 1000 }),
|
|
4150
|
+
dateFrom: opts.dateFrom,
|
|
4151
|
+
dateTo: opts.dateTo,
|
|
4152
|
+
filename: opts.filename,
|
|
4153
|
+
});
|
|
4154
|
+
|
|
4155
|
+
const payloadWorkspaceId = resolvePayloadWorkspaceId(rawPayload, null);
|
|
4156
|
+
const effectiveWorkspaceId = requireWorkspaceSelection(payloadWorkspaceId || resolvedWorkspaceId, {
|
|
4157
|
+
label: 'Search results enriched export',
|
|
4158
|
+
hint: 'Pass --workspace-id, set SOCIALSEAL_WORKSPACE_ID, or configure a default workspace before exporting.',
|
|
4159
|
+
});
|
|
4160
|
+
const effectiveWorkspaceSource = payloadWorkspaceId ? 'body' : workspaceSource;
|
|
4161
|
+
emitWorkspaceSelectionNotice(opts, {
|
|
4162
|
+
workspaceId: effectiveWorkspaceId,
|
|
4163
|
+
source: effectiveWorkspaceSource,
|
|
4164
|
+
label: 'search_results_enriched export',
|
|
4165
|
+
});
|
|
4166
|
+
|
|
4167
|
+
const normalizedPayload = buildSearchResultsEnrichedExportPayload(rawPayload, effectiveWorkspaceId);
|
|
4168
|
+
const requestedFilename = trimString(normalizedPayload.filename) || undefined;
|
|
4169
|
+
delete normalizedPayload.filename;
|
|
4170
|
+
|
|
4171
|
+
await runExportDataCsv({
|
|
4172
|
+
opts,
|
|
4173
|
+
label: 'Search results enriched export',
|
|
4174
|
+
template: EXPORT_DATA_TEMPLATE_TRACKING_RANKED_VIDEOS_RAW,
|
|
4175
|
+
normalizedPayload,
|
|
4176
|
+
requestedFilename,
|
|
4177
|
+
effectiveWorkspaceId,
|
|
4178
|
+
apiBase: resolvedApiBase,
|
|
4179
|
+
apiKey,
|
|
4180
|
+
useGateway,
|
|
4181
|
+
legacyUrl,
|
|
4182
|
+
timeoutMs,
|
|
4183
|
+
});
|
|
4184
|
+
}
|
|
4185
|
+
|
|
4186
|
+
async function handleDataExportGroupEvidence(opts) {
|
|
4187
|
+
const config = loadConfig();
|
|
4188
|
+
const apiKey = requireApiKey(opts, config);
|
|
4189
|
+
const apiBase = resolveApiBase(opts, config);
|
|
4190
|
+
const supabaseUrl = resolveLegacyUrl(resolveSupabaseUrl(opts, config), 'SOCIALSEAL_SUPABASE_URL');
|
|
4191
|
+
const { resolvedApiBase, legacyUrl, useGateway } = resolveApiTarget({ apiBase, legacyUrl: supabaseUrl });
|
|
4192
|
+
const timeoutMs = resolveTimeoutMs(opts, config);
|
|
4193
|
+
const { workspaceId: resolvedWorkspaceId, source: workspaceSource } = resolveWorkspaceSelection(opts, config);
|
|
4194
|
+
|
|
4195
|
+
const groupId = coercePositiveInteger(opts.groupId, 'group_id');
|
|
4196
|
+
const effectiveWorkspaceId = requireWorkspaceSelection(resolvedWorkspaceId, {
|
|
4197
|
+
label: 'Group evidence export',
|
|
4198
|
+
hint: 'Pass --workspace-id, set SOCIALSEAL_WORKSPACE_ID, or configure a default workspace before exporting group evidence.',
|
|
4199
|
+
});
|
|
4200
|
+
emitWorkspaceSelectionNotice(opts, {
|
|
4201
|
+
workspaceId: effectiveWorkspaceId,
|
|
4202
|
+
source: workspaceSource,
|
|
4203
|
+
label: 'group evidence export',
|
|
4204
|
+
});
|
|
4205
|
+
|
|
4206
|
+
const requestedSurface = normalizeEvidenceSurface(opts.surface);
|
|
4207
|
+
let surface = requestedSurface;
|
|
4208
|
+
if (requestedSurface === 'auto') {
|
|
4209
|
+
const groupResponse = await callApi({
|
|
4210
|
+
apiBase: useGateway ? resolvedApiBase : legacyUrl,
|
|
4211
|
+
apiKey,
|
|
4212
|
+
path: useGateway
|
|
4213
|
+
? `/cli/tools/group-management/groups/${groupId}?workspace_id=${encodeURIComponent(effectiveWorkspaceId)}`
|
|
4214
|
+
: `/functions/v1/group-management/groups/${groupId}?workspace_id=${encodeURIComponent(effectiveWorkspaceId)}`,
|
|
4215
|
+
method: 'GET',
|
|
4216
|
+
workspaceId: effectiveWorkspaceId,
|
|
4217
|
+
timeoutMs,
|
|
4218
|
+
});
|
|
4219
|
+
|
|
4220
|
+
if (!groupResponse.ok) {
|
|
4221
|
+
throw await buildHttpError(groupResponse, {
|
|
4222
|
+
label: 'Group evidence platform detection',
|
|
4223
|
+
functionName: 'group-management',
|
|
4224
|
+
method: 'GET',
|
|
4225
|
+
});
|
|
4226
|
+
}
|
|
4227
|
+
|
|
4228
|
+
const groupData = await groupResponse.json();
|
|
4229
|
+
surface = resolveEvidenceSurfaceFromGroup(groupData);
|
|
4230
|
+
}
|
|
4231
|
+
|
|
4232
|
+
const rawPayload = stripUndefinedEntries({
|
|
4233
|
+
groupId,
|
|
4234
|
+
groupIds: surface === 'social' ? [groupId] : undefined,
|
|
4235
|
+
trackingItemIds: normalizePositiveIntegerList(opts.trackingItemIds, 'trackingItemIds', { max: 1000 }),
|
|
4236
|
+
dateFrom: opts.dateFrom,
|
|
4237
|
+
dateTo: opts.dateTo,
|
|
4238
|
+
filename: opts.filename,
|
|
4239
|
+
});
|
|
4240
|
+
const normalizedPayload = surface === 'google_ai'
|
|
4241
|
+
? buildGoogleAiSearchSummariesExportPayload(rawPayload, effectiveWorkspaceId)
|
|
4242
|
+
: buildSearchResultsEnrichedExportPayload(rawPayload, effectiveWorkspaceId);
|
|
4243
|
+
const requestedFilename = trimString(normalizedPayload.filename) || undefined;
|
|
4244
|
+
delete normalizedPayload.filename;
|
|
4245
|
+
const template = surface === 'google_ai'
|
|
4246
|
+
? EXPORT_DATA_TEMPLATE_GOOGLE_AI_SEARCH_SUMMARIES_RAW
|
|
4247
|
+
: EXPORT_DATA_TEMPLATE_TRACKING_RANKED_VIDEOS_RAW;
|
|
4248
|
+
|
|
4249
|
+
await runExportDataCsv({
|
|
4250
|
+
opts,
|
|
4251
|
+
label: 'Group evidence export',
|
|
4252
|
+
template,
|
|
4253
|
+
normalizedPayload,
|
|
4254
|
+
requestedFilename,
|
|
4255
|
+
effectiveWorkspaceId,
|
|
4256
|
+
apiBase: resolvedApiBase,
|
|
4257
|
+
apiKey,
|
|
4258
|
+
useGateway,
|
|
4259
|
+
legacyUrl,
|
|
4260
|
+
timeoutMs,
|
|
4261
|
+
decorateResponse: (responseJson) => ({
|
|
4262
|
+
...responseJson,
|
|
4263
|
+
evidence: buildGroupEvidenceMetadata({
|
|
4264
|
+
surface,
|
|
4265
|
+
groupId,
|
|
4266
|
+
workspaceId: effectiveWorkspaceId,
|
|
4267
|
+
responseJson,
|
|
4268
|
+
}),
|
|
4269
|
+
}),
|
|
4270
|
+
});
|
|
4271
|
+
}
|
|
4272
|
+
|
|
4273
|
+
async function handleDataGroupCompleteness(opts) {
|
|
4274
|
+
const config = loadConfig();
|
|
4275
|
+
const apiKey = requireApiKey(opts, config);
|
|
4276
|
+
const apiBase = resolveApiBase(opts, config);
|
|
4277
|
+
const supabaseUrl = resolveLegacyUrl(resolveSupabaseUrl(opts, config), 'SOCIALSEAL_SUPABASE_URL');
|
|
4278
|
+
const { resolvedApiBase, legacyUrl, useGateway } = resolveApiTarget({ apiBase, legacyUrl: supabaseUrl });
|
|
4279
|
+
const timeoutMs = resolveTimeoutMs(opts, config);
|
|
4280
|
+
const { workspaceId: resolvedWorkspaceId, source: workspaceSource } = resolveWorkspaceSelection(opts, config);
|
|
4281
|
+
|
|
4282
|
+
const groupId = coercePositiveInteger(opts.groupId, 'group_id');
|
|
4283
|
+
const effectiveWorkspaceId = requireWorkspaceSelection(resolvedWorkspaceId, {
|
|
4284
|
+
label: 'Group completeness check',
|
|
4285
|
+
hint: 'Pass --workspace-id, set SOCIALSEAL_WORKSPACE_ID, or configure a default workspace before checking group completeness.',
|
|
4286
|
+
});
|
|
4287
|
+
emitWorkspaceSelectionNotice(opts, {
|
|
4288
|
+
workspaceId: effectiveWorkspaceId,
|
|
4289
|
+
source: workspaceSource,
|
|
4290
|
+
label: 'group completeness check',
|
|
4291
|
+
});
|
|
4292
|
+
|
|
4293
|
+
const manifest = parseJsonInput(opts.manifest, { label: 'manifest' });
|
|
4294
|
+
const expectedItems = extractExpectedManifestItems(manifest);
|
|
4295
|
+
const backendPath = useGateway
|
|
4296
|
+
? `/cli/tools/group-management/groups/${groupId}/completeness?${new URLSearchParams({
|
|
4297
|
+
workspace_id: effectiveWorkspaceId,
|
|
4298
|
+
}).toString()}`
|
|
4299
|
+
: `/functions/v1/group-management/groups/${groupId}/completeness?${new URLSearchParams({
|
|
4300
|
+
workspace_id: effectiveWorkspaceId,
|
|
4301
|
+
}).toString()}`;
|
|
4302
|
+
|
|
4303
|
+
const backendRes = await callApi({
|
|
4304
|
+
apiBase: useGateway ? resolvedApiBase : legacyUrl,
|
|
4305
|
+
apiKey,
|
|
4306
|
+
path: backendPath,
|
|
4307
|
+
method: 'POST',
|
|
4308
|
+
body: {
|
|
4309
|
+
expected_items: expectedItems,
|
|
4310
|
+
include_refresh_status: true,
|
|
4311
|
+
},
|
|
4312
|
+
workspaceId: effectiveWorkspaceId,
|
|
4313
|
+
timeoutMs,
|
|
4314
|
+
});
|
|
4315
|
+
|
|
4316
|
+
if (backendRes.ok) {
|
|
4317
|
+
const contentType = backendRes.headers.get('content-type') || '';
|
|
4318
|
+
if (!contentType.includes('application/json')) {
|
|
4319
|
+
throw new CliError('Group completeness check returned a non-JSON response.', {
|
|
4320
|
+
code: 'INVALID_RESPONSE',
|
|
4321
|
+
exitCode: EXIT_CODES.SERVER,
|
|
4322
|
+
});
|
|
4323
|
+
}
|
|
4324
|
+
const backendData = await backendRes.json();
|
|
4325
|
+
emitJsonOutput(backendData, opts.pretty);
|
|
4326
|
+
if (isJsonObject(backendData) && backendData.complete === false) {
|
|
4327
|
+
process.exitCode = EXIT_CODES.SERVER;
|
|
4328
|
+
}
|
|
4329
|
+
return;
|
|
4330
|
+
}
|
|
4331
|
+
|
|
4332
|
+
if (backendRes.status !== 404) {
|
|
4333
|
+
throw await buildHttpError(backendRes, {
|
|
4334
|
+
label: 'Group completeness check',
|
|
4335
|
+
functionName: 'group-management',
|
|
4336
|
+
method: 'POST',
|
|
4337
|
+
});
|
|
4338
|
+
}
|
|
4339
|
+
|
|
4340
|
+
const res = await callApi({
|
|
4341
|
+
apiBase: useGateway ? resolvedApiBase : legacyUrl,
|
|
4342
|
+
apiKey,
|
|
4343
|
+
path: useGateway
|
|
4344
|
+
? `/cli/tools/group-management/groups/${groupId}/items?${new URLSearchParams({
|
|
4345
|
+
workspace_id: effectiveWorkspaceId,
|
|
4346
|
+
page: '1',
|
|
4347
|
+
limit: String(Math.max(expectedItems.length + 100, 1000)),
|
|
4348
|
+
}).toString()}`
|
|
4349
|
+
: `/functions/v1/group-management/groups/${groupId}/items?${new URLSearchParams({
|
|
4350
|
+
workspace_id: effectiveWorkspaceId,
|
|
4351
|
+
page: '1',
|
|
4352
|
+
limit: String(Math.max(expectedItems.length + 100, 1000)),
|
|
4353
|
+
}).toString()}`,
|
|
4354
|
+
method: 'GET',
|
|
4355
|
+
workspaceId: effectiveWorkspaceId,
|
|
4356
|
+
timeoutMs,
|
|
4357
|
+
});
|
|
4358
|
+
|
|
4359
|
+
if (!res.ok) {
|
|
4360
|
+
throw await buildHttpError(res, {
|
|
4361
|
+
label: 'Group completeness check',
|
|
4362
|
+
functionName: 'group-management',
|
|
4363
|
+
method: 'GET',
|
|
4364
|
+
});
|
|
4365
|
+
}
|
|
4366
|
+
|
|
4367
|
+
const contentType = res.headers.get('content-type') || '';
|
|
4368
|
+
if (!contentType.includes('application/json')) {
|
|
4369
|
+
throw new CliError('Group completeness check returned a non-JSON response.', {
|
|
4370
|
+
code: 'INVALID_RESPONSE',
|
|
4371
|
+
exitCode: EXIT_CODES.SERVER,
|
|
4372
|
+
});
|
|
4373
|
+
}
|
|
4374
|
+
|
|
4375
|
+
const data = await res.json();
|
|
4376
|
+
const actualItems = isJsonObject(data) && Array.isArray(data.items)
|
|
4377
|
+
? data.items
|
|
4378
|
+
: (Array.isArray(data) ? data : []);
|
|
4379
|
+
const diff = buildCompletenessDiff({
|
|
4380
|
+
expectedItems,
|
|
4381
|
+
actualItems,
|
|
4382
|
+
groupId,
|
|
4383
|
+
workspaceId: effectiveWorkspaceId,
|
|
4384
|
+
});
|
|
4385
|
+
diff.source = 'manifest_fallback';
|
|
4386
|
+
|
|
4387
|
+
emitJsonOutput(diff, opts.pretty);
|
|
4388
|
+
if (!diff.complete) {
|
|
4389
|
+
process.exitCode = EXIT_CODES.SERVER;
|
|
4390
|
+
}
|
|
4391
|
+
}
|
|
4392
|
+
|
|
3695
4393
|
function handleDataExportOptions(opts) {
|
|
3696
4394
|
const payload = {
|
|
3697
4395
|
exports: EXPORT_OPTIONS,
|
|
@@ -4053,7 +4751,7 @@ if (typeof program.showHelpAfterError === 'function') {
|
|
|
4053
4751
|
if (typeof program.showSuggestionAfterError === 'function') {
|
|
4054
4752
|
program.showSuggestionAfterError(true);
|
|
4055
4753
|
}
|
|
4056
|
-
program.addHelpText('after', `\nExamples:\n socialseal workspace list\n socialseal workspace use <workspace-id>\n socialseal agent run --message "ping"\n socialseal tools list\n socialseal tools schema --function search-journey-run\n socialseal tools call --function <tool> --body @payload.json\n socialseal tools status 6809 --kind google_ai_run\n socialseal tools status <run-uuid> --kind journey_run --workspace-id <uuid>\n socialseal video queue-analysis --video-id 734829384 --workspace-id <uuid>\n socialseal video extract --video-id 734829384 --wait --out-dir ./video-assets\n socialseal data export-options\n socialseal data export-tracking --group-id 123 --time-period 30d\n socialseal data export-search-results --group-ids 123,124 --workspace-id <uuid> --out ranked.csv\n`);
|
|
4754
|
+
program.addHelpText('after', `\nExamples:\n socialseal workspace list\n socialseal workspace use <workspace-id>\n socialseal agent run --message "ping"\n socialseal tools list\n socialseal tools schema --function search-journey-run\n socialseal tools call --function <tool> --body @payload.json\n socialseal tools status 6809 --kind google_ai_run\n socialseal tools status <run-uuid> --kind journey_run --workspace-id <uuid>\n socialseal video queue-analysis --video-id 734829384 --workspace-id <uuid>\n socialseal video extract --video-id 734829384 --wait --out-dir ./video-assets\n socialseal data export-options\n socialseal data export-tracking --group-id 123 --time-period 30d\n socialseal data export-search-results --group-ids 123,124 --workspace-id <uuid> --out ranked.csv\n socialseal data export-group-evidence --group-id 123 --workspace-id <uuid> --out evidence.csv\n`);
|
|
4057
4755
|
|
|
4058
4756
|
program
|
|
4059
4757
|
.command('agent')
|
|
@@ -4214,6 +4912,40 @@ data
|
|
|
4214
4912
|
.option('--verbose', 'Show error details')
|
|
4215
4913
|
.action((opts) => runCommand(handleDataExportSearchResults, opts));
|
|
4216
4914
|
|
|
4915
|
+
data
|
|
4916
|
+
.command('export-group-evidence')
|
|
4917
|
+
.description('Export usable group evidence, routing social groups and Google AI groups to the correct CSV export')
|
|
4918
|
+
.requiredOption('--group-id <id>', 'Tracking group id')
|
|
4919
|
+
.option('--surface <surface>', 'auto|social|google_ai', 'auto')
|
|
4920
|
+
.option('--tracking-item-ids <ids>', 'Optional comma-separated tracking item ids')
|
|
4921
|
+
.option('--date-from <iso>', 'Optional ISO datetime lower bound for social ranked exports')
|
|
4922
|
+
.option('--date-to <iso>', 'Optional ISO datetime upper bound for social ranked exports')
|
|
4923
|
+
.option('--filename <name>', 'Optional export filename stem (without extension)')
|
|
4924
|
+
.option('--out <path>', 'Output file path')
|
|
4925
|
+
.option('--stdout', 'Write to stdout')
|
|
4926
|
+
.option('--api-base <url>', 'API base URL (default https://api.socialseal.co)')
|
|
4927
|
+
.option('--api-key <key>', 'CLI API key')
|
|
4928
|
+
.option('--workspace-id <id>', 'Workspace id (for scoped keys)')
|
|
4929
|
+
.option('--pretty', 'Pretty-print JSON metadata when no file is ready')
|
|
4930
|
+
.option('--json', 'Emit machine-readable errors')
|
|
4931
|
+
.option('--timeout <ms>', 'Request timeout in milliseconds')
|
|
4932
|
+
.option('--verbose', 'Show error details')
|
|
4933
|
+
.action((opts) => runCommand(handleDataExportGroupEvidence, opts));
|
|
4934
|
+
|
|
4935
|
+
data
|
|
4936
|
+
.command('group-completeness')
|
|
4937
|
+
.description('Compare an expected tracking-item manifest against current group items')
|
|
4938
|
+
.requiredOption('--group-id <id>', 'Tracking group id')
|
|
4939
|
+
.requiredOption('--manifest <jsonOrFile>', 'Expected items manifest JSON or @file.json')
|
|
4940
|
+
.option('--api-base <url>', 'API base URL (default https://api.socialseal.co)')
|
|
4941
|
+
.option('--api-key <key>', 'CLI API key')
|
|
4942
|
+
.option('--workspace-id <id>', 'Workspace id (for scoped keys)')
|
|
4943
|
+
.option('--pretty', 'Pretty-print JSON')
|
|
4944
|
+
.option('--json', 'Emit machine-readable errors')
|
|
4945
|
+
.option('--timeout <ms>', 'Request timeout in milliseconds')
|
|
4946
|
+
.option('--verbose', 'Show error details')
|
|
4947
|
+
.action((opts) => runCommand(handleDataGroupCompleteness, opts));
|
|
4948
|
+
|
|
4217
4949
|
data
|
|
4218
4950
|
.command('export-report')
|
|
4219
4951
|
.description('Export report data via export-report (provisional)')
|