@socialseal/cli 0.1.7 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/README.md +7 -0
- package/package.json +1 -1
- package/src/index.js +892 -13
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.1.8 - 2026-04-13
|
|
6
|
+
- Increase default CLI timeout from 30s to 5m to reduce false timeout failures on heavy tool/export workflows.
|
|
7
|
+
- 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
|
+
- Fix async `search-journey-run` polling workspace propagation to always reuse the resolved effective workspace scope.
|
|
9
|
+
|
|
5
10
|
## 0.1.7 - 2026-03-20
|
|
6
11
|
- Add `socialseal data export-search-results` for CLI-first enriched ranked-search exports, including direct CSV download handling.
|
|
7
12
|
- Add `search_results_enriched` as an alias on `socialseal data export-report` to map to the ranked-search export template.
|
package/README.md
CHANGED
|
@@ -42,12 +42,17 @@ Optional config file:
|
|
|
42
42
|
- Tools list (built-in registry):
|
|
43
43
|
- `socialseal tools list`
|
|
44
44
|
- `socialseal tools list --json`
|
|
45
|
+
- `socialseal tools schema --function search-journey-run`
|
|
45
46
|
|
|
46
47
|
- Tools (direct edge function call):
|
|
47
48
|
- `socialseal tools call --function <tool> --body @payload.json --api-base https://api.socialseal.co --api-key <key>`
|
|
48
49
|
- `socialseal tools call --function <tool> --body @payload.json --json`
|
|
49
50
|
- `socialseal tools call --function search-journey-run --body @payload.json --async --workspace-id <uuid>`
|
|
50
51
|
- `socialseal tools call --function search-journey-run --body @payload.json --async --no-poll --workspace-id <uuid>`
|
|
52
|
+
- `socialseal tools status 6809 --kind google_ai_run`
|
|
53
|
+
- `socialseal tools status <job-uuid> --kind agent_job`
|
|
54
|
+
- `socialseal tools status <run-uuid> --kind journey_run --workspace-id <uuid>`
|
|
55
|
+
- `socialseal tools status 6809 --kind google_ai_run --wait --include-results`
|
|
51
56
|
|
|
52
57
|
- Tracked video extraction:
|
|
53
58
|
- `socialseal video queue-analysis --video-id 734829384 --workspace-id <uuid>`
|
|
@@ -65,12 +70,14 @@ Optional config file:
|
|
|
65
70
|
## Notes
|
|
66
71
|
- `export-report`, `export_tracking_data`, and `export-data`-backed exports are provisional until CLI export specs are finalized.
|
|
67
72
|
- `tools list` ships a stable built-in registry of supported direct-call function targets. It is not live backend enumeration.
|
|
73
|
+
- `tools schema --function <name>` prints static required/optional payload fields and example bodies for high-friction tools.
|
|
68
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.
|
|
69
75
|
- `data export-report --report-type search_results_enriched` is a compatibility alias to the same `export-data` template flow.
|
|
70
76
|
- `data export-report` now validates report types locally and shows the allowed list immediately; run `socialseal data export-options` when choosing between export flows.
|
|
71
77
|
- 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.
|
|
72
78
|
- `--timeout <ms>` controls HTTP request timeouts. Agent runs default to a 5-minute WebSocket inactivity timeout unless you set `--idle-timeout <ms>` (or the matching env/config value).
|
|
73
79
|
- `search-journey-run` supports CLI-managed async polling: `--async` starts backend async mode, polling is on by default, `--no-poll` returns the initial `runId`, and `--poll-interval <ms>` controls the status polling cadence.
|
|
80
|
+
- `tools status <id>` is the unified read path for numeric Google AI run ids and UUID job/run ids. `--kind auto` detects numeric Google runs and UUID agent jobs, and falls back to journey status when `--workspace-id` is provided.
|
|
74
81
|
- `video queue-analysis` wraps the tracked-video extraction backend in queue-only mode so you can queue one or many tracked videos without downloading assets first.
|
|
75
82
|
- `video extract` wraps the same backend in extraction mode and returns a normalized JSON payload with resolved tracking context, structured analysis, thumbnail/frame assets, and optional local downloads under `--out-dir`.
|
|
76
83
|
- `--video-id` is the primary ergonomic selector for video workflows. The backend tries it as `video_uid` first, then as platform video id. It does not accept tracking item ids. `--search-result-id` remains available when you are starting from a specific tracked rank row.
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -10,14 +10,16 @@ const DEFAULT_CONFIG_PATH = path.join(os.homedir(), '.config', 'socialseal', 'co
|
|
|
10
10
|
const DEFAULT_API_BASE = 'https://api.socialseal.co';
|
|
11
11
|
const CLI_KEY_HEADER = 'X-CLI-Key';
|
|
12
12
|
const WORKSPACE_HEADER = 'X-Workspace-Id';
|
|
13
|
-
const DEFAULT_TIMEOUT_MS =
|
|
13
|
+
const DEFAULT_TIMEOUT_MS = 300000;
|
|
14
14
|
const DEFAULT_AGENT_IDLE_TIMEOUT_MS = 300000;
|
|
15
15
|
const DEFAULT_POLL_INTERVAL_MS = 2000;
|
|
16
|
+
const DEFAULT_STATUS_RESULTS_LIMIT = 10;
|
|
16
17
|
const DEFAULT_FRAME_COUNT = 3;
|
|
17
18
|
const MAX_TIMEOUT_MS = 900000;
|
|
18
19
|
const LEGACY_ENABLED = process.env.SOCIALSEAL_ENABLE_LEGACY === '1';
|
|
19
20
|
const CLI_VERSION = loadRuntimeVersion();
|
|
20
21
|
const STATIC_TOOL_REGISTRY_NOTE = 'This registry is shipped with the CLI for stable discovery. It is not live backend enumeration, so environment-specific availability can drift.';
|
|
22
|
+
const STATIC_TOOL_SCHEMA_NOTE = 'Schema hints are static CLI docs for discoverability. Backend contracts can still evolve.';
|
|
21
23
|
const EXIT_CODES = {
|
|
22
24
|
OK: 0,
|
|
23
25
|
UNKNOWN: 1,
|
|
@@ -27,6 +29,8 @@ const EXIT_CODES = {
|
|
|
27
29
|
SERVER: 5,
|
|
28
30
|
};
|
|
29
31
|
const HTTP_METHODS = new Set(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']);
|
|
32
|
+
const ACTIVE_STATUS_VALUES = new Set(['queued', 'pending', 'processing', 'in_progress', 'running']);
|
|
33
|
+
const TOOL_STATUS_KINDS = new Set(['auto', 'agent_job', 'google_ai_run', 'journey_run']);
|
|
30
34
|
const REPORT_TYPE_SEARCH_RESULTS_ENRICHED = 'search_results_enriched';
|
|
31
35
|
const EXPORT_DATA_TEMPLATE_TRACKING_RANKED_VIDEOS_RAW = 'tracking_ranked_videos_raw';
|
|
32
36
|
const SUPPORTED_EXPORT_REPORT_TYPES = [
|
|
@@ -66,7 +70,17 @@ const EXPORT_OPTIONS = [
|
|
|
66
70
|
},
|
|
67
71
|
];
|
|
68
72
|
const KNOWN_TOOLS = [
|
|
69
|
-
{
|
|
73
|
+
{
|
|
74
|
+
name: 'agent-tool-jobs',
|
|
75
|
+
category: 'agent',
|
|
76
|
+
description: 'Queue/poll agent-backed tool jobs.',
|
|
77
|
+
objectType: 'agent_tool_job',
|
|
78
|
+
transport: 'post_edge_function',
|
|
79
|
+
workspaceScoped: true,
|
|
80
|
+
knownLocalDevState: 'enabled',
|
|
81
|
+
actionAliases: ['start', 'status'],
|
|
82
|
+
notes: 'Use action=start to queue and action=status to read a UUID job id.',
|
|
83
|
+
},
|
|
70
84
|
{ name: 'deep-exploration-runs', category: 'agent', description: 'Read or persist deep exploration render runs.' },
|
|
71
85
|
{ name: 'workspace-notes', category: 'agent', description: 'Search, create, update, and pin workspace note memory.' },
|
|
72
86
|
{ name: 'workspace-onboarding', category: 'agent', description: 'Read or update workspace onboarding metadata used by the agent.' },
|
|
@@ -123,7 +137,36 @@ const KNOWN_TOOLS = [
|
|
|
123
137
|
notes: 'Accepts videoId/videoUid/platformVideoId/searchResultId items; videoId means video_uid or platform-native video id, not a tracking item id.',
|
|
124
138
|
},
|
|
125
139
|
{ name: 'douyin-geo-api', category: 'search', description: 'Query Douyin search and geo data.' },
|
|
126
|
-
{
|
|
140
|
+
{
|
|
141
|
+
name: 'google-ai-search',
|
|
142
|
+
category: 'search',
|
|
143
|
+
description: 'Queue Google AI search runs.',
|
|
144
|
+
objectType: 'google_ai_run',
|
|
145
|
+
transport: 'post_edge_function',
|
|
146
|
+
workspaceScoped: true,
|
|
147
|
+
knownLocalDevState: 'enabled',
|
|
148
|
+
notes: 'Returns numeric runId. Read status/results via get-google-ai-search-runs/get-google-ai-search-results or socialseal tools status <runId>.',
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
name: 'get-google-ai-search-runs',
|
|
152
|
+
category: 'search',
|
|
153
|
+
description: 'Read Google AI run queue/progress by numeric run id.',
|
|
154
|
+
objectType: 'google_ai_run',
|
|
155
|
+
transport: 'post_edge_function',
|
|
156
|
+
workspaceScoped: true,
|
|
157
|
+
knownLocalDevState: 'enabled',
|
|
158
|
+
notes: 'Primary status endpoint for numeric Google AI run ids.',
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
name: 'get-google-ai-search-results',
|
|
162
|
+
category: 'search',
|
|
163
|
+
description: 'Read Google AI summaries/citations by numeric run id.',
|
|
164
|
+
objectType: 'google_ai_summary',
|
|
165
|
+
transport: 'post_edge_function',
|
|
166
|
+
workspaceScoped: true,
|
|
167
|
+
knownLocalDevState: 'enabled',
|
|
168
|
+
notes: 'Use this after a run reaches succeeded/partial/failed to inspect summary-level output.',
|
|
169
|
+
},
|
|
127
170
|
{ name: 'instagram-geo-api', category: 'search', description: 'Query Instagram search and geo data.' },
|
|
128
171
|
{ name: 'tiktok-geo-api', category: 'search', description: 'Query TikTok search and geo data.' },
|
|
129
172
|
{ name: 'xhs-geo-api', category: 'search', description: 'Query Xiaohongshu search and geo data.' },
|
|
@@ -152,7 +195,16 @@ const KNOWN_TOOLS = [
|
|
|
152
195
|
},
|
|
153
196
|
{ name: 'journey-feedback', category: 'vnext', description: 'Record acceptance or rejection feedback for opportunity bundles.' },
|
|
154
197
|
{ name: 'opportunity-bundle-approve', category: 'vnext', description: 'Approve an opportunity bundle and create tracking coverage.' },
|
|
155
|
-
{
|
|
198
|
+
{
|
|
199
|
+
name: 'search-journey-run',
|
|
200
|
+
category: 'vnext',
|
|
201
|
+
description: 'Run or poll a search journey for a subject across supported platforms.',
|
|
202
|
+
objectType: 'search_journey_run',
|
|
203
|
+
transport: 'post_edge_function',
|
|
204
|
+
workspaceScoped: true,
|
|
205
|
+
knownLocalDevState: 'enabled',
|
|
206
|
+
notes: 'Async start returns runId; poll with action=status or socialseal tools status <runId> --kind journey_run --workspace-id <workspace-id>.',
|
|
207
|
+
},
|
|
156
208
|
{ name: 'vnext-blueprints-create', category: 'vnext', description: 'Create a vNext blueprint from grounded evidence.' },
|
|
157
209
|
{ name: 'vnext-blueprints-generate', category: 'vnext', description: 'Generate a vNext blueprint from workspace opportunity data.' },
|
|
158
210
|
{ name: 'vnext-blueprints-read', category: 'vnext', description: 'Read vNext blueprint history and specific versions.' },
|
|
@@ -168,6 +220,183 @@ const KNOWN_TOOLS = [
|
|
|
168
220
|
{ name: 'vnext-topics-auto-tag', category: 'vnext', description: 'Auto-tag keyword and topic assignments with Gemini-assisted review.' },
|
|
169
221
|
];
|
|
170
222
|
|
|
223
|
+
const TOOL_SCHEMA_HINTS = {
|
|
224
|
+
'agent-tool-jobs': {
|
|
225
|
+
summary: 'Queue agent-backed jobs and read UUID job status.',
|
|
226
|
+
operations: [
|
|
227
|
+
{
|
|
228
|
+
action: 'start',
|
|
229
|
+
required: ['action=start', 'toolName', 'payload'],
|
|
230
|
+
optional: [],
|
|
231
|
+
example: {
|
|
232
|
+
action: 'start',
|
|
233
|
+
toolName: 'search_videos',
|
|
234
|
+
payload: {
|
|
235
|
+
query: 'best africa safari itinerary',
|
|
236
|
+
platform: 'tiktok',
|
|
237
|
+
region: 'IN',
|
|
238
|
+
limit: 20,
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
action: 'status',
|
|
244
|
+
required: ['action=status', 'jobId (uuid)'],
|
|
245
|
+
optional: [],
|
|
246
|
+
example: {
|
|
247
|
+
action: 'status',
|
|
248
|
+
jobId: '11111111-1111-4111-8111-111111111111',
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
],
|
|
252
|
+
cliExamples: [
|
|
253
|
+
'socialseal tools call --function agent-tool-jobs --body \'{"action":"start","toolName":"search_videos","payload":{"query":"best africa safari itinerary","platform":"tiktok","region":"IN"}}\'',
|
|
254
|
+
'socialseal tools status 11111111-1111-4111-8111-111111111111 --kind agent_job',
|
|
255
|
+
],
|
|
256
|
+
},
|
|
257
|
+
'search-journey-run': {
|
|
258
|
+
summary: 'Start/poll journey keyword expansion runs.',
|
|
259
|
+
operations: [
|
|
260
|
+
{
|
|
261
|
+
action: 'start',
|
|
262
|
+
required: ['subject', 'subjectType', 'region', 'workspaceId'],
|
|
263
|
+
optional: [
|
|
264
|
+
'locale',
|
|
265
|
+
'platformKeys',
|
|
266
|
+
'seedKeywords',
|
|
267
|
+
'contentPillars',
|
|
268
|
+
'contentPillarIds',
|
|
269
|
+
'maxKeywords',
|
|
270
|
+
'maxKeywordsPerStage',
|
|
271
|
+
'includeRejected',
|
|
272
|
+
'skipCache',
|
|
273
|
+
'executionMode',
|
|
274
|
+
],
|
|
275
|
+
example: {
|
|
276
|
+
subject: 'Como Hotels',
|
|
277
|
+
subjectType: 'brand',
|
|
278
|
+
region: 'IN',
|
|
279
|
+
locale: 'en-IN',
|
|
280
|
+
workspaceId: '00000000-0000-4000-8000-000000000000',
|
|
281
|
+
executionMode: 'async',
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
action: 'status',
|
|
286
|
+
required: ['action=status', 'workspaceId', 'runId (uuid)'],
|
|
287
|
+
optional: [],
|
|
288
|
+
example: {
|
|
289
|
+
action: 'status',
|
|
290
|
+
workspaceId: '00000000-0000-4000-8000-000000000000',
|
|
291
|
+
runId: '11111111-1111-4111-8111-111111111111',
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
],
|
|
295
|
+
cliExamples: [
|
|
296
|
+
'socialseal tools call --function search-journey-run --body @journey.json --async --workspace-id <workspace-uuid>',
|
|
297
|
+
'socialseal tools status 11111111-1111-4111-8111-111111111111 --kind journey_run --workspace-id <workspace-uuid>',
|
|
298
|
+
],
|
|
299
|
+
},
|
|
300
|
+
'google-ai-search': {
|
|
301
|
+
summary: 'Queue Google AI runs (returns numeric runId).',
|
|
302
|
+
operations: [
|
|
303
|
+
{
|
|
304
|
+
action: 'start',
|
|
305
|
+
required: ['queries'],
|
|
306
|
+
optional: [
|
|
307
|
+
'workspaceId',
|
|
308
|
+
'trackingItemId',
|
|
309
|
+
'countryCode',
|
|
310
|
+
'searchLanguage',
|
|
311
|
+
'brandId',
|
|
312
|
+
'competitorBrandIds',
|
|
313
|
+
'brandDomains',
|
|
314
|
+
'competitorDomains',
|
|
315
|
+
'aiMode',
|
|
316
|
+
],
|
|
317
|
+
notes: 'region is commonly used in workflows, but the canonical field is countryCode.',
|
|
318
|
+
example: {
|
|
319
|
+
queries: ['east africa itinerary', 'kenya tanzania itinerary'],
|
|
320
|
+
countryCode: 'in',
|
|
321
|
+
searchLanguage: 'en',
|
|
322
|
+
workspaceId: '00000000-0000-4000-8000-000000000000',
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
],
|
|
326
|
+
cliExamples: [
|
|
327
|
+
'socialseal tools call --function google-ai-search --body @google-ai-search.json --workspace-id <workspace-uuid>',
|
|
328
|
+
'socialseal tools status 6809 --kind google_ai_run',
|
|
329
|
+
],
|
|
330
|
+
},
|
|
331
|
+
'get-google-ai-search-runs': {
|
|
332
|
+
summary: 'Read Google AI run status/progress.',
|
|
333
|
+
operations: [
|
|
334
|
+
{
|
|
335
|
+
action: 'read',
|
|
336
|
+
required: [],
|
|
337
|
+
optional: ['runId', 'trackingItemId', 'limit', 'offset', 'skipCache'],
|
|
338
|
+
example: {
|
|
339
|
+
runId: 6809,
|
|
340
|
+
limit: 1,
|
|
341
|
+
offset: 0,
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
],
|
|
345
|
+
cliExamples: [
|
|
346
|
+
'socialseal tools call --function get-google-ai-search-runs --body \'{"runId":6809,"limit":1}\'',
|
|
347
|
+
'socialseal tools status 6809 --kind google_ai_run',
|
|
348
|
+
],
|
|
349
|
+
},
|
|
350
|
+
'get-google-ai-search-results': {
|
|
351
|
+
summary: 'Read Google AI summary/citation rows.',
|
|
352
|
+
operations: [
|
|
353
|
+
{
|
|
354
|
+
action: 'read',
|
|
355
|
+
required: [],
|
|
356
|
+
optional: ['runId', 'query', 'trackingItemId', 'includeCitations', 'limit', 'offset', 'skipCache'],
|
|
357
|
+
example: {
|
|
358
|
+
runId: 6809,
|
|
359
|
+
includeCitations: true,
|
|
360
|
+
limit: 10,
|
|
361
|
+
offset: 0,
|
|
362
|
+
},
|
|
363
|
+
},
|
|
364
|
+
],
|
|
365
|
+
cliExamples: [
|
|
366
|
+
'socialseal tools call --function get-google-ai-search-results --body \'{"runId":6809,"includeCitations":true,"limit":10}\'',
|
|
367
|
+
],
|
|
368
|
+
},
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
function getToolSchemaHint(functionName) {
|
|
372
|
+
if (!functionName) return null;
|
|
373
|
+
return TOOL_SCHEMA_HINTS[functionName] || null;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function getKnownTool(functionName) {
|
|
377
|
+
return KNOWN_TOOLS.find((tool) => tool.name === functionName) || null;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function buildSchemaAvailabilitySummary(schema) {
|
|
381
|
+
const firstOperation = Array.isArray(schema?.operations) ? schema.operations[0] : null;
|
|
382
|
+
if (!firstOperation || !Array.isArray(firstOperation.required) || firstOperation.required.length === 0) {
|
|
383
|
+
return 'optional body fields vary by read filter';
|
|
384
|
+
}
|
|
385
|
+
return `required: ${firstOperation.required.join(', ')}`;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function buildToolRegistry() {
|
|
389
|
+
return KNOWN_TOOLS.map((tool) => {
|
|
390
|
+
const schema = getToolSchemaHint(tool.name);
|
|
391
|
+
if (!schema) return tool;
|
|
392
|
+
return {
|
|
393
|
+
...tool,
|
|
394
|
+
schemaAvailable: true,
|
|
395
|
+
schemaSummary: buildSchemaAvailabilitySummary(schema),
|
|
396
|
+
};
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
|
|
171
400
|
function getConfigPath() {
|
|
172
401
|
return process.env.SOCIALSEAL_CONFIG || DEFAULT_CONFIG_PATH;
|
|
173
402
|
}
|
|
@@ -723,6 +952,60 @@ function isUuidLike(value) {
|
|
|
723
952
|
return typeof value === 'string' && /^[0-9a-f]{8}-[0-9a-f-]{27}$/i.test(value.trim());
|
|
724
953
|
}
|
|
725
954
|
|
|
955
|
+
function isPositiveIntegerString(value) {
|
|
956
|
+
return typeof value === 'string' && /^[1-9]\d*$/.test(value.trim());
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
function normalizeStatusIdentifier(rawId) {
|
|
960
|
+
const id = trimString(rawId);
|
|
961
|
+
if (!id) {
|
|
962
|
+
throw new CliError('Missing status identifier.', {
|
|
963
|
+
code: 'MISSING_ARGUMENT',
|
|
964
|
+
exitCode: EXIT_CODES.USAGE,
|
|
965
|
+
hint: 'Pass a numeric Google AI runId or a UUID job/run id.',
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
if (isPositiveIntegerString(id)) {
|
|
970
|
+
const parsed = Number(id);
|
|
971
|
+
if (Number.isSafeInteger(parsed) && parsed > 0) {
|
|
972
|
+
return { rawId: id, numericId: parsed, uuidId: null };
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
if (isUuidLike(id)) {
|
|
977
|
+
return { rawId: id, numericId: null, uuidId: id };
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
throw new CliError(`Unsupported status identifier: ${id}`, {
|
|
981
|
+
code: 'INVALID_ARGUMENT',
|
|
982
|
+
exitCode: EXIT_CODES.USAGE,
|
|
983
|
+
hint: 'Use a numeric Google AI runId (for example 6809) or UUID job/run id.',
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
function parseToolStatusKind(rawKind) {
|
|
988
|
+
const normalized = trimString(rawKind || 'auto').toLowerCase();
|
|
989
|
+
if (TOOL_STATUS_KINDS.has(normalized)) {
|
|
990
|
+
return normalized;
|
|
991
|
+
}
|
|
992
|
+
throw new CliError(`Unsupported tools status kind: ${rawKind}`, {
|
|
993
|
+
code: 'INVALID_ARGUMENT',
|
|
994
|
+
exitCode: EXIT_CODES.USAGE,
|
|
995
|
+
hint: 'Use --kind auto|agent_job|google_ai_run|journey_run.',
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
function normalizeStatusValue(value) {
|
|
1000
|
+
return trimString(value).toLowerCase();
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
function isTerminalStatusValue(value) {
|
|
1004
|
+
const normalized = normalizeStatusValue(value);
|
|
1005
|
+
if (!normalized) return true;
|
|
1006
|
+
return !ACTIVE_STATUS_VALUES.has(normalized);
|
|
1007
|
+
}
|
|
1008
|
+
|
|
726
1009
|
function coercePositiveInteger(value, label) {
|
|
727
1010
|
if (value === undefined || value === null || value === '') return undefined;
|
|
728
1011
|
const parsed = Number(value);
|
|
@@ -1799,14 +2082,17 @@ async function pollSearchJourneyRun({
|
|
|
1799
2082
|
|
|
1800
2083
|
const data = await res.json();
|
|
1801
2084
|
const status = isJsonObject(data) && typeof data.status === 'string' ? data.status : null;
|
|
2085
|
+
const normalizedStatus = normalizeStatusValue(status);
|
|
1802
2086
|
if (status && status !== lastStatus) {
|
|
1803
2087
|
emitInfo(opts, `search-journey-run status: ${status}`);
|
|
1804
2088
|
lastStatus = status;
|
|
1805
2089
|
}
|
|
1806
2090
|
|
|
1807
|
-
if (
|
|
1808
|
-
if (
|
|
1809
|
-
|
|
2091
|
+
if (normalizedStatus === 'completed' || normalizedStatus === 'succeeded') return data;
|
|
2092
|
+
if (normalizedStatus === 'failed' || normalizedStatus === 'error') {
|
|
2093
|
+
throw buildSearchJourneyRunFailure(data);
|
|
2094
|
+
}
|
|
2095
|
+
if (normalizedStatus && ACTIVE_STATUS_VALUES.has(normalizedStatus)) continue;
|
|
1810
2096
|
|
|
1811
2097
|
throw new CliError('search-journey-run status poll returned an unexpected payload.', {
|
|
1812
2098
|
code: 'INVALID_STATUS_RESPONSE',
|
|
@@ -1885,6 +2171,428 @@ async function buildHttpError(res, context = {}) {
|
|
|
1885
2171
|
});
|
|
1886
2172
|
}
|
|
1887
2173
|
|
|
2174
|
+
function resolveStatusResultsLimit(rawLimit) {
|
|
2175
|
+
if (rawLimit == null || rawLimit === '') return DEFAULT_STATUS_RESULTS_LIMIT;
|
|
2176
|
+
const parsed = Number(rawLimit);
|
|
2177
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
2178
|
+
throw new CliError('Invalid results limit. Use a positive integer.', {
|
|
2179
|
+
code: 'INVALID_ARGUMENT',
|
|
2180
|
+
exitCode: EXIT_CODES.USAGE,
|
|
2181
|
+
hint: 'Use --results-limit <n> where n is between 1 and 50.',
|
|
2182
|
+
});
|
|
2183
|
+
}
|
|
2184
|
+
return Math.min(parsed, 50);
|
|
2185
|
+
}
|
|
2186
|
+
|
|
2187
|
+
async function callToolJson({
|
|
2188
|
+
apiBase,
|
|
2189
|
+
apiKey,
|
|
2190
|
+
useGateway,
|
|
2191
|
+
legacyUrl,
|
|
2192
|
+
functionName,
|
|
2193
|
+
body,
|
|
2194
|
+
workspaceId,
|
|
2195
|
+
timeoutMs,
|
|
2196
|
+
label,
|
|
2197
|
+
}) {
|
|
2198
|
+
const path = useGateway ? `/cli/tools/${functionName}` : `/functions/v1/${functionName}`;
|
|
2199
|
+
const res = await callApi({
|
|
2200
|
+
apiBase: useGateway ? apiBase : legacyUrl,
|
|
2201
|
+
apiKey,
|
|
2202
|
+
path,
|
|
2203
|
+
method: 'POST',
|
|
2204
|
+
body,
|
|
2205
|
+
workspaceId,
|
|
2206
|
+
timeoutMs,
|
|
2207
|
+
});
|
|
2208
|
+
|
|
2209
|
+
if (res.status === 404) {
|
|
2210
|
+
return { notFound: true, data: null };
|
|
2211
|
+
}
|
|
2212
|
+
if (!res.ok) {
|
|
2213
|
+
throw await buildHttpError(res, {
|
|
2214
|
+
label,
|
|
2215
|
+
functionName,
|
|
2216
|
+
method: 'POST',
|
|
2217
|
+
});
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
const contentType = res.headers.get('content-type') || '';
|
|
2221
|
+
if (!contentType.includes('application/json')) {
|
|
2222
|
+
throw new CliError(`${label} returned a non-JSON response.`, {
|
|
2223
|
+
code: 'INVALID_RESPONSE',
|
|
2224
|
+
exitCode: EXIT_CODES.SERVER,
|
|
2225
|
+
});
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2228
|
+
return { notFound: false, data: await res.json() };
|
|
2229
|
+
}
|
|
2230
|
+
|
|
2231
|
+
async function readAgentToolJobStatus({
|
|
2232
|
+
apiBase,
|
|
2233
|
+
apiKey,
|
|
2234
|
+
useGateway,
|
|
2235
|
+
legacyUrl,
|
|
2236
|
+
timeoutMs,
|
|
2237
|
+
jobId,
|
|
2238
|
+
}) {
|
|
2239
|
+
const response = await callToolJson({
|
|
2240
|
+
apiBase,
|
|
2241
|
+
apiKey,
|
|
2242
|
+
useGateway,
|
|
2243
|
+
legacyUrl,
|
|
2244
|
+
functionName: 'agent-tool-jobs',
|
|
2245
|
+
body: { action: 'status', jobId },
|
|
2246
|
+
workspaceId: null,
|
|
2247
|
+
timeoutMs,
|
|
2248
|
+
label: 'Agent tool job status',
|
|
2249
|
+
});
|
|
2250
|
+
if (response.notFound) return null;
|
|
2251
|
+
|
|
2252
|
+
const data = response.data;
|
|
2253
|
+
return {
|
|
2254
|
+
kind: 'agent_tool_job',
|
|
2255
|
+
id: jobId,
|
|
2256
|
+
status: isJsonObject(data) ? trimString(data.status || '') : '',
|
|
2257
|
+
workspaceId: isJsonObject(data) ? (data.workspaceId ?? null) : null,
|
|
2258
|
+
toolName: isJsonObject(data) ? (data.toolName ?? null) : null,
|
|
2259
|
+
result: isJsonObject(data) ? (data.result ?? null) : null,
|
|
2260
|
+
error: isJsonObject(data) ? (data.error ?? null) : null,
|
|
2261
|
+
raw: data,
|
|
2262
|
+
};
|
|
2263
|
+
}
|
|
2264
|
+
|
|
2265
|
+
async function readSearchJourneyRunStatus({
|
|
2266
|
+
apiBase,
|
|
2267
|
+
apiKey,
|
|
2268
|
+
useGateway,
|
|
2269
|
+
legacyUrl,
|
|
2270
|
+
timeoutMs,
|
|
2271
|
+
workspaceId,
|
|
2272
|
+
runId,
|
|
2273
|
+
}) {
|
|
2274
|
+
if (!workspaceId) {
|
|
2275
|
+
throw new CliError('Search journey run status requires a workspace id.', {
|
|
2276
|
+
code: 'WORKSPACE_REQUIRED',
|
|
2277
|
+
exitCode: EXIT_CODES.USAGE,
|
|
2278
|
+
hint: 'Pass --workspace-id, set SOCIALSEAL_WORKSPACE_ID, or configure a default workspace.',
|
|
2279
|
+
});
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
const response = await callToolJson({
|
|
2283
|
+
apiBase,
|
|
2284
|
+
apiKey,
|
|
2285
|
+
useGateway,
|
|
2286
|
+
legacyUrl,
|
|
2287
|
+
functionName: 'search-journey-run',
|
|
2288
|
+
body: { action: 'status', workspaceId, runId },
|
|
2289
|
+
workspaceId,
|
|
2290
|
+
timeoutMs,
|
|
2291
|
+
label: 'Search journey status',
|
|
2292
|
+
});
|
|
2293
|
+
if (response.notFound) return null;
|
|
2294
|
+
|
|
2295
|
+
const data = response.data;
|
|
2296
|
+
return {
|
|
2297
|
+
kind: 'search_journey_run',
|
|
2298
|
+
id: runId,
|
|
2299
|
+
status: isJsonObject(data) ? trimString(data.status || '') : '',
|
|
2300
|
+
workspaceId,
|
|
2301
|
+
journeyId: isJsonObject(data) ? (data.journeyId ?? null) : null,
|
|
2302
|
+
stagedKeywordsCount:
|
|
2303
|
+
isJsonObject(data) && Array.isArray(data.stagedKeywords) ? data.stagedKeywords.length : 0,
|
|
2304
|
+
error: isJsonObject(data) ? (data.error ?? null) : null,
|
|
2305
|
+
raw: data,
|
|
2306
|
+
};
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2309
|
+
async function readGoogleAiRunStatus({
|
|
2310
|
+
apiBase,
|
|
2311
|
+
apiKey,
|
|
2312
|
+
useGateway,
|
|
2313
|
+
legacyUrl,
|
|
2314
|
+
timeoutMs,
|
|
2315
|
+
runId,
|
|
2316
|
+
includeResults,
|
|
2317
|
+
resultsLimit,
|
|
2318
|
+
}) {
|
|
2319
|
+
const runsResponse = await callToolJson({
|
|
2320
|
+
apiBase,
|
|
2321
|
+
apiKey,
|
|
2322
|
+
useGateway,
|
|
2323
|
+
legacyUrl,
|
|
2324
|
+
functionName: 'get-google-ai-search-runs',
|
|
2325
|
+
body: {
|
|
2326
|
+
runId,
|
|
2327
|
+
limit: 1,
|
|
2328
|
+
offset: 0,
|
|
2329
|
+
},
|
|
2330
|
+
workspaceId: null,
|
|
2331
|
+
timeoutMs,
|
|
2332
|
+
label: 'Google AI run status',
|
|
2333
|
+
});
|
|
2334
|
+
if (runsResponse.notFound) return null;
|
|
2335
|
+
|
|
2336
|
+
const runsPayload = runsResponse.data;
|
|
2337
|
+
const runItem = isJsonObject(runsPayload) && Array.isArray(runsPayload.items)
|
|
2338
|
+
? runsPayload.items.find((item) => isJsonObject(item) && Number(item.id) === runId) || null
|
|
2339
|
+
: null;
|
|
2340
|
+
|
|
2341
|
+
if (!runItem) return null;
|
|
2342
|
+
|
|
2343
|
+
let resultsPayload = null;
|
|
2344
|
+
if (includeResults) {
|
|
2345
|
+
const resultsResponse = await callToolJson({
|
|
2346
|
+
apiBase,
|
|
2347
|
+
apiKey,
|
|
2348
|
+
useGateway,
|
|
2349
|
+
legacyUrl,
|
|
2350
|
+
functionName: 'get-google-ai-search-results',
|
|
2351
|
+
body: {
|
|
2352
|
+
runId,
|
|
2353
|
+
includeCitations: true,
|
|
2354
|
+
limit: resultsLimit,
|
|
2355
|
+
offset: 0,
|
|
2356
|
+
},
|
|
2357
|
+
workspaceId: null,
|
|
2358
|
+
timeoutMs,
|
|
2359
|
+
label: 'Google AI run results',
|
|
2360
|
+
});
|
|
2361
|
+
resultsPayload = resultsResponse.notFound ? null : resultsResponse.data;
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
return {
|
|
2365
|
+
kind: 'google_ai_run',
|
|
2366
|
+
id: runId,
|
|
2367
|
+
status: trimString(runItem.status || ''),
|
|
2368
|
+
progress: {
|
|
2369
|
+
completedQueries: typeof runItem.completedQueries === 'number' ? runItem.completedQueries : null,
|
|
2370
|
+
totalQueries: typeof runItem.totalQueries === 'number' ? runItem.totalQueries : null,
|
|
2371
|
+
progressPercent: typeof runItem.progressPercent === 'number' ? runItem.progressPercent : null,
|
|
2372
|
+
},
|
|
2373
|
+
lastErrorMessage: runItem.lastErrorMessage ?? null,
|
|
2374
|
+
run: runItem,
|
|
2375
|
+
results: resultsPayload,
|
|
2376
|
+
};
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
function buildToolStatusNotFoundError(identifier, kind, workspaceId) {
|
|
2380
|
+
if (kind === 'google_ai_run') {
|
|
2381
|
+
return new CliError(`Google AI run not found: ${identifier.rawId}`, {
|
|
2382
|
+
code: 'STATUS_NOT_FOUND',
|
|
2383
|
+
exitCode: EXIT_CODES.NOT_FOUND,
|
|
2384
|
+
hint: 'Verify the run id and key scope, then retry with --kind google_ai_run.',
|
|
2385
|
+
});
|
|
2386
|
+
}
|
|
2387
|
+
if (kind === 'agent_job') {
|
|
2388
|
+
return new CliError(`Agent tool job not found: ${identifier.rawId}`, {
|
|
2389
|
+
code: 'STATUS_NOT_FOUND',
|
|
2390
|
+
exitCode: EXIT_CODES.NOT_FOUND,
|
|
2391
|
+
hint: 'Verify the UUID job id, then retry with --kind agent_job.',
|
|
2392
|
+
});
|
|
2393
|
+
}
|
|
2394
|
+
if (kind === 'journey_run') {
|
|
2395
|
+
return new CliError(`Search journey run not found: ${identifier.rawId}`, {
|
|
2396
|
+
code: 'STATUS_NOT_FOUND',
|
|
2397
|
+
exitCode: EXIT_CODES.NOT_FOUND,
|
|
2398
|
+
hint: 'Verify --workspace-id and the journey run UUID, then retry.',
|
|
2399
|
+
});
|
|
2400
|
+
}
|
|
2401
|
+
return new CliError(`No matching status record found for ${identifier.rawId}.`, {
|
|
2402
|
+
code: 'STATUS_NOT_FOUND',
|
|
2403
|
+
exitCode: EXIT_CODES.NOT_FOUND,
|
|
2404
|
+
hint: workspaceId
|
|
2405
|
+
? 'Try --kind agent_job or --kind journey_run explicitly.'
|
|
2406
|
+
: 'Try --kind agent_job or provide --workspace-id to also check journey runs.',
|
|
2407
|
+
});
|
|
2408
|
+
}
|
|
2409
|
+
|
|
2410
|
+
async function resolveUnifiedToolStatus({
|
|
2411
|
+
apiBase,
|
|
2412
|
+
apiKey,
|
|
2413
|
+
useGateway,
|
|
2414
|
+
legacyUrl,
|
|
2415
|
+
timeoutMs,
|
|
2416
|
+
identifier,
|
|
2417
|
+
kind,
|
|
2418
|
+
workspaceId,
|
|
2419
|
+
includeResults,
|
|
2420
|
+
resultsLimit,
|
|
2421
|
+
}) {
|
|
2422
|
+
if (kind === 'google_ai_run') {
|
|
2423
|
+
if (identifier.numericId == null) {
|
|
2424
|
+
throw new CliError('google_ai_run status expects a numeric run id.', {
|
|
2425
|
+
code: 'INVALID_ARGUMENT',
|
|
2426
|
+
exitCode: EXIT_CODES.USAGE,
|
|
2427
|
+
});
|
|
2428
|
+
}
|
|
2429
|
+
const result = await readGoogleAiRunStatus({
|
|
2430
|
+
apiBase,
|
|
2431
|
+
apiKey,
|
|
2432
|
+
useGateway,
|
|
2433
|
+
legacyUrl,
|
|
2434
|
+
timeoutMs,
|
|
2435
|
+
runId: identifier.numericId,
|
|
2436
|
+
includeResults,
|
|
2437
|
+
resultsLimit,
|
|
2438
|
+
});
|
|
2439
|
+
if (result) return result;
|
|
2440
|
+
throw buildToolStatusNotFoundError(identifier, kind, workspaceId);
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2443
|
+
if (kind === 'agent_job') {
|
|
2444
|
+
if (!identifier.uuidId) {
|
|
2445
|
+
throw new CliError('agent_job status expects a UUID id.', {
|
|
2446
|
+
code: 'INVALID_ARGUMENT',
|
|
2447
|
+
exitCode: EXIT_CODES.USAGE,
|
|
2448
|
+
});
|
|
2449
|
+
}
|
|
2450
|
+
const result = await readAgentToolJobStatus({
|
|
2451
|
+
apiBase,
|
|
2452
|
+
apiKey,
|
|
2453
|
+
useGateway,
|
|
2454
|
+
legacyUrl,
|
|
2455
|
+
timeoutMs,
|
|
2456
|
+
jobId: identifier.uuidId,
|
|
2457
|
+
});
|
|
2458
|
+
if (result) return result;
|
|
2459
|
+
throw buildToolStatusNotFoundError(identifier, kind, workspaceId);
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
if (kind === 'journey_run') {
|
|
2463
|
+
if (!identifier.uuidId) {
|
|
2464
|
+
throw new CliError('journey_run status expects a UUID run id.', {
|
|
2465
|
+
code: 'INVALID_ARGUMENT',
|
|
2466
|
+
exitCode: EXIT_CODES.USAGE,
|
|
2467
|
+
});
|
|
2468
|
+
}
|
|
2469
|
+
const result = await readSearchJourneyRunStatus({
|
|
2470
|
+
apiBase,
|
|
2471
|
+
apiKey,
|
|
2472
|
+
useGateway,
|
|
2473
|
+
legacyUrl,
|
|
2474
|
+
timeoutMs,
|
|
2475
|
+
workspaceId,
|
|
2476
|
+
runId: identifier.uuidId,
|
|
2477
|
+
});
|
|
2478
|
+
if (result) return result;
|
|
2479
|
+
throw buildToolStatusNotFoundError(identifier, kind, workspaceId);
|
|
2480
|
+
}
|
|
2481
|
+
|
|
2482
|
+
if (identifier.numericId != null) {
|
|
2483
|
+
const result = await readGoogleAiRunStatus({
|
|
2484
|
+
apiBase,
|
|
2485
|
+
apiKey,
|
|
2486
|
+
useGateway,
|
|
2487
|
+
legacyUrl,
|
|
2488
|
+
timeoutMs,
|
|
2489
|
+
runId: identifier.numericId,
|
|
2490
|
+
includeResults,
|
|
2491
|
+
resultsLimit,
|
|
2492
|
+
});
|
|
2493
|
+
if (result) return result;
|
|
2494
|
+
throw buildToolStatusNotFoundError(identifier, 'google_ai_run', workspaceId);
|
|
2495
|
+
}
|
|
2496
|
+
|
|
2497
|
+
const agentJob = await readAgentToolJobStatus({
|
|
2498
|
+
apiBase,
|
|
2499
|
+
apiKey,
|
|
2500
|
+
useGateway,
|
|
2501
|
+
legacyUrl,
|
|
2502
|
+
timeoutMs,
|
|
2503
|
+
jobId: identifier.uuidId,
|
|
2504
|
+
});
|
|
2505
|
+
if (agentJob) return agentJob;
|
|
2506
|
+
|
|
2507
|
+
if (workspaceId) {
|
|
2508
|
+
const journeyRun = await readSearchJourneyRunStatus({
|
|
2509
|
+
apiBase,
|
|
2510
|
+
apiKey,
|
|
2511
|
+
useGateway,
|
|
2512
|
+
legacyUrl,
|
|
2513
|
+
timeoutMs,
|
|
2514
|
+
workspaceId,
|
|
2515
|
+
runId: identifier.uuidId,
|
|
2516
|
+
});
|
|
2517
|
+
if (journeyRun) return journeyRun;
|
|
2518
|
+
}
|
|
2519
|
+
|
|
2520
|
+
throw buildToolStatusNotFoundError(identifier, 'auto', workspaceId);
|
|
2521
|
+
}
|
|
2522
|
+
|
|
2523
|
+
function buildStatusCommandHint(result, workspaceId) {
|
|
2524
|
+
if (!result || !result.kind) return null;
|
|
2525
|
+
if (result.kind === 'google_ai_run') {
|
|
2526
|
+
return `socialseal tools status ${result.id} --kind google_ai_run`;
|
|
2527
|
+
}
|
|
2528
|
+
if (result.kind === 'agent_tool_job') {
|
|
2529
|
+
return `socialseal tools status ${result.id} --kind agent_job`;
|
|
2530
|
+
}
|
|
2531
|
+
if (result.kind === 'search_journey_run') {
|
|
2532
|
+
const scopedWorkspace = workspaceId || result.workspaceId;
|
|
2533
|
+
if (!scopedWorkspace) return null;
|
|
2534
|
+
return `socialseal tools status ${result.id} --kind journey_run --workspace-id ${scopedWorkspace}`;
|
|
2535
|
+
}
|
|
2536
|
+
return null;
|
|
2537
|
+
}
|
|
2538
|
+
|
|
2539
|
+
function maybeEmitFollowupStatusHint({ functionName, data, workspaceId }) {
|
|
2540
|
+
if (!isJsonObject(data)) return;
|
|
2541
|
+
if (functionName === 'google-ai-search' && Number.isInteger(data.runId)) {
|
|
2542
|
+
process.stderr.write(
|
|
2543
|
+
`[socialseal] Google AI run queued: ${data.runId}. Use: socialseal tools status ${data.runId} --kind google_ai_run\n`,
|
|
2544
|
+
);
|
|
2545
|
+
return;
|
|
2546
|
+
}
|
|
2547
|
+
if (functionName === 'agent-tool-jobs' && typeof data.jobId === 'string' && isUuidLike(data.jobId)) {
|
|
2548
|
+
process.stderr.write(
|
|
2549
|
+
`[socialseal] Agent tool job queued: ${data.jobId}. Use: socialseal tools status ${data.jobId} --kind agent_job\n`,
|
|
2550
|
+
);
|
|
2551
|
+
return;
|
|
2552
|
+
}
|
|
2553
|
+
if (functionName === 'search-journey-run' && typeof data.runId === 'string' && isUuidLike(data.runId)) {
|
|
2554
|
+
const scopedWorkspace = trimString(workspaceId || data.workspaceId || '');
|
|
2555
|
+
const workspaceFlag = scopedWorkspace ? ` --workspace-id ${scopedWorkspace}` : '';
|
|
2556
|
+
process.stderr.write(
|
|
2557
|
+
`[socialseal] Search journey run id: ${data.runId}. Use: socialseal tools status ${data.runId} --kind journey_run${workspaceFlag}\n`,
|
|
2558
|
+
);
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
|
|
2562
|
+
async function pollUnifiedStatus({
|
|
2563
|
+
loader,
|
|
2564
|
+
timeoutMs,
|
|
2565
|
+
pollIntervalMs,
|
|
2566
|
+
opts,
|
|
2567
|
+
}) {
|
|
2568
|
+
const deadline = Date.now() + timeoutMs;
|
|
2569
|
+
let current = await loader();
|
|
2570
|
+
let lastStatus = normalizeStatusValue(current?.status);
|
|
2571
|
+
|
|
2572
|
+
while (!isTerminalStatusValue(current?.status)) {
|
|
2573
|
+
const remainingMs = deadline - Date.now();
|
|
2574
|
+
if (remainingMs <= 0) {
|
|
2575
|
+
throw new CliError('Timed out waiting for status completion.', {
|
|
2576
|
+
code: 'ASYNC_WAIT_TIMEOUT',
|
|
2577
|
+
exitCode: EXIT_CODES.SERVER,
|
|
2578
|
+
hint: 'Increase --timeout <ms> or omit --wait to return current status immediately.',
|
|
2579
|
+
details: truncateDetails(current),
|
|
2580
|
+
});
|
|
2581
|
+
}
|
|
2582
|
+
|
|
2583
|
+
emitInfo(opts, `status: ${current.status}`);
|
|
2584
|
+
await sleep(Math.min(pollIntervalMs, remainingMs));
|
|
2585
|
+
current = await loader();
|
|
2586
|
+
const normalized = normalizeStatusValue(current?.status);
|
|
2587
|
+
if (normalized && normalized !== lastStatus) {
|
|
2588
|
+
emitInfo(opts, `status: ${current.status}`);
|
|
2589
|
+
lastStatus = normalized;
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2593
|
+
return current;
|
|
2594
|
+
}
|
|
2595
|
+
|
|
1888
2596
|
function emitError(err, opts = {}) {
|
|
1889
2597
|
const showDetails = opts.json || opts.verbose;
|
|
1890
2598
|
const payload = {
|
|
@@ -2503,6 +3211,11 @@ async function handleToolsCall(opts) {
|
|
|
2503
3211
|
const data = await res.json();
|
|
2504
3212
|
const shouldPoll = shouldHandleSearchJourneyRunAsync(opts.function, method, payload, opts) && opts.poll !== false;
|
|
2505
3213
|
if (!shouldPoll) {
|
|
3214
|
+
maybeEmitFollowupStatusHint({
|
|
3215
|
+
functionName: opts.function,
|
|
3216
|
+
data,
|
|
3217
|
+
workspaceId: effectiveWorkspaceId,
|
|
3218
|
+
});
|
|
2506
3219
|
emitJsonOutput(data, opts.pretty);
|
|
2507
3220
|
return;
|
|
2508
3221
|
}
|
|
@@ -2530,9 +3243,7 @@ async function handleToolsCall(opts) {
|
|
|
2530
3243
|
apiBase: useGateway ? resolvedApiBase : legacyUrl,
|
|
2531
3244
|
apiKey,
|
|
2532
3245
|
path: useGateway ? `/cli/tools/${opts.function}` : `/functions/v1/${opts.function}`,
|
|
2533
|
-
workspaceId:
|
|
2534
|
-
? payload.workspaceId
|
|
2535
|
-
: resolvedWorkspaceId,
|
|
3246
|
+
workspaceId: effectiveWorkspaceId,
|
|
2536
3247
|
timeoutMs,
|
|
2537
3248
|
pollIntervalMs: resolvePollIntervalMs(opts),
|
|
2538
3249
|
runId,
|
|
@@ -2548,10 +3259,12 @@ async function handleToolsCall(opts) {
|
|
|
2548
3259
|
}
|
|
2549
3260
|
|
|
2550
3261
|
function handleToolsList(opts) {
|
|
3262
|
+
const tools = buildToolRegistry();
|
|
2551
3263
|
const payload = {
|
|
2552
3264
|
discovery: 'built_in_registry',
|
|
2553
|
-
tools
|
|
3265
|
+
tools,
|
|
2554
3266
|
note: STATIC_TOOL_REGISTRY_NOTE,
|
|
3267
|
+
schemaNote: STATIC_TOOL_SCHEMA_NOTE,
|
|
2555
3268
|
};
|
|
2556
3269
|
|
|
2557
3270
|
if (opts.json) {
|
|
@@ -2561,9 +3274,10 @@ function handleToolsList(opts) {
|
|
|
2561
3274
|
|
|
2562
3275
|
process.stdout.write('[socialseal] Built-in tool registry\n');
|
|
2563
3276
|
process.stdout.write(`[socialseal] ${payload.note}\n`);
|
|
3277
|
+
process.stdout.write(`[socialseal] ${payload.schemaNote}\n`);
|
|
2564
3278
|
|
|
2565
3279
|
let currentCategory = null;
|
|
2566
|
-
for (const tool of
|
|
3280
|
+
for (const tool of tools) {
|
|
2567
3281
|
if (tool.category !== currentCategory) {
|
|
2568
3282
|
currentCategory = tool.category;
|
|
2569
3283
|
process.stdout.write(`\n${currentCategory}\n`);
|
|
@@ -2578,9 +3292,148 @@ function handleToolsList(opts) {
|
|
|
2578
3292
|
if (tool.notes) {
|
|
2579
3293
|
process.stdout.write(` note: ${tool.notes}\n`);
|
|
2580
3294
|
}
|
|
3295
|
+
if (tool.schemaAvailable) {
|
|
3296
|
+
process.stdout.write(` schema: ${tool.schemaSummary}\n`);
|
|
3297
|
+
process.stdout.write(` schema command: socialseal tools schema --function ${tool.name}\n`);
|
|
3298
|
+
}
|
|
2581
3299
|
}
|
|
2582
3300
|
|
|
2583
3301
|
process.stdout.write('\n[socialseal] Call a tool with: socialseal tools call --function <name> --body @payload.json\n');
|
|
3302
|
+
process.stdout.write('[socialseal] Inspect schema examples with: socialseal tools schema --function <name>\n');
|
|
3303
|
+
}
|
|
3304
|
+
|
|
3305
|
+
function handleToolsSchema(opts) {
|
|
3306
|
+
const tools = buildToolRegistry();
|
|
3307
|
+
const functionName = trimString(opts.function || '');
|
|
3308
|
+
|
|
3309
|
+
if (!functionName) {
|
|
3310
|
+
const payload = {
|
|
3311
|
+
note: STATIC_TOOL_SCHEMA_NOTE,
|
|
3312
|
+
schemas: tools
|
|
3313
|
+
.filter((tool) => tool.schemaAvailable)
|
|
3314
|
+
.map((tool) => ({
|
|
3315
|
+
function: tool.name,
|
|
3316
|
+
summary: tool.schemaSummary,
|
|
3317
|
+
details: getToolSchemaHint(tool.name),
|
|
3318
|
+
})),
|
|
3319
|
+
};
|
|
3320
|
+
|
|
3321
|
+
if (opts.json) {
|
|
3322
|
+
emitJsonOutput(payload, opts.pretty);
|
|
3323
|
+
return;
|
|
3324
|
+
}
|
|
3325
|
+
|
|
3326
|
+
process.stdout.write('[socialseal] Tool schema hints\n');
|
|
3327
|
+
process.stdout.write(`[socialseal] ${payload.note}\n`);
|
|
3328
|
+
for (const schemaEntry of payload.schemas) {
|
|
3329
|
+
process.stdout.write(`- ${schemaEntry.function}: ${schemaEntry.summary}\n`);
|
|
3330
|
+
process.stdout.write(` command: socialseal tools schema --function ${schemaEntry.function}\n`);
|
|
3331
|
+
}
|
|
3332
|
+
return;
|
|
3333
|
+
}
|
|
3334
|
+
|
|
3335
|
+
const knownTool = getKnownTool(functionName);
|
|
3336
|
+
if (!knownTool) {
|
|
3337
|
+
throw new CliError(`Unknown tool: ${functionName}`, {
|
|
3338
|
+
code: 'INVALID_ARGUMENT',
|
|
3339
|
+
exitCode: EXIT_CODES.USAGE,
|
|
3340
|
+
hint: 'Run `socialseal tools list` to discover available tool names.',
|
|
3341
|
+
});
|
|
3342
|
+
}
|
|
3343
|
+
|
|
3344
|
+
const schema = getToolSchemaHint(functionName);
|
|
3345
|
+
if (!schema) {
|
|
3346
|
+
throw new CliError(`No schema hint is bundled for ${functionName}.`, {
|
|
3347
|
+
code: 'SCHEMA_NOT_AVAILABLE',
|
|
3348
|
+
exitCode: EXIT_CODES.NOT_FOUND,
|
|
3349
|
+
hint: 'Use `socialseal tools call --function <tool> --body @payload.json` and inspect backend validation errors for this tool.',
|
|
3350
|
+
});
|
|
3351
|
+
}
|
|
3352
|
+
|
|
3353
|
+
const payload = {
|
|
3354
|
+
function: functionName,
|
|
3355
|
+
note: STATIC_TOOL_SCHEMA_NOTE,
|
|
3356
|
+
schema,
|
|
3357
|
+
};
|
|
3358
|
+
|
|
3359
|
+
if (opts.json) {
|
|
3360
|
+
emitJsonOutput(payload, opts.pretty);
|
|
3361
|
+
return;
|
|
3362
|
+
}
|
|
3363
|
+
|
|
3364
|
+
process.stdout.write(`[socialseal] Tool schema: ${functionName}\n`);
|
|
3365
|
+
process.stdout.write(`[socialseal] ${payload.note}\n`);
|
|
3366
|
+
process.stdout.write(`summary: ${schema.summary}\n`);
|
|
3367
|
+
if (Array.isArray(schema.operations) && schema.operations.length > 0) {
|
|
3368
|
+
process.stdout.write('operations:\n');
|
|
3369
|
+
for (const operation of schema.operations) {
|
|
3370
|
+
process.stdout.write(`- ${operation.action}\n`);
|
|
3371
|
+
if (Array.isArray(operation.required) && operation.required.length > 0) {
|
|
3372
|
+
process.stdout.write(` required: ${operation.required.join(', ')}\n`);
|
|
3373
|
+
}
|
|
3374
|
+
if (Array.isArray(operation.optional) && operation.optional.length > 0) {
|
|
3375
|
+
process.stdout.write(` optional: ${operation.optional.join(', ')}\n`);
|
|
3376
|
+
}
|
|
3377
|
+
if (operation.notes) {
|
|
3378
|
+
process.stdout.write(` note: ${operation.notes}\n`);
|
|
3379
|
+
}
|
|
3380
|
+
if (operation.example) {
|
|
3381
|
+
process.stdout.write(` example body: ${JSON.stringify(operation.example)}\n`);
|
|
3382
|
+
}
|
|
3383
|
+
}
|
|
3384
|
+
}
|
|
3385
|
+
if (Array.isArray(schema.cliExamples) && schema.cliExamples.length > 0) {
|
|
3386
|
+
process.stdout.write('cli examples:\n');
|
|
3387
|
+
for (const example of schema.cliExamples) {
|
|
3388
|
+
process.stdout.write(`- ${example}\n`);
|
|
3389
|
+
}
|
|
3390
|
+
}
|
|
3391
|
+
}
|
|
3392
|
+
|
|
3393
|
+
async function handleToolsStatus(opts) {
|
|
3394
|
+
const config = loadConfig();
|
|
3395
|
+
const apiKey = requireApiKey(opts, config);
|
|
3396
|
+
const apiBase = resolveApiBase(opts, config);
|
|
3397
|
+
const supabaseUrl = resolveLegacyUrl(resolveSupabaseUrl(opts, config), 'SOCIALSEAL_SUPABASE_URL');
|
|
3398
|
+
const { resolvedApiBase, legacyUrl, useGateway } = resolveApiTarget({ apiBase, legacyUrl: supabaseUrl });
|
|
3399
|
+
const timeoutMs = resolveTimeoutMs(opts, config);
|
|
3400
|
+
const pollIntervalMs = resolvePollIntervalMs(opts);
|
|
3401
|
+
const includeResults = opts.includeResults === true;
|
|
3402
|
+
const resultsLimit = resolveStatusResultsLimit(opts.resultsLimit);
|
|
3403
|
+
const kind = parseToolStatusKind(opts.kind);
|
|
3404
|
+
const identifier = normalizeStatusIdentifier(opts.id);
|
|
3405
|
+
const { workspaceId } = resolveWorkspaceSelection(opts, config);
|
|
3406
|
+
|
|
3407
|
+
const loadStatus = async () =>
|
|
3408
|
+
await resolveUnifiedToolStatus({
|
|
3409
|
+
apiBase: resolvedApiBase,
|
|
3410
|
+
apiKey,
|
|
3411
|
+
useGateway,
|
|
3412
|
+
legacyUrl,
|
|
3413
|
+
timeoutMs,
|
|
3414
|
+
identifier,
|
|
3415
|
+
kind,
|
|
3416
|
+
workspaceId,
|
|
3417
|
+
includeResults,
|
|
3418
|
+
resultsLimit,
|
|
3419
|
+
});
|
|
3420
|
+
|
|
3421
|
+
let result = await loadStatus();
|
|
3422
|
+
if (opts.wait) {
|
|
3423
|
+
result = await pollUnifiedStatus({
|
|
3424
|
+
loader: loadStatus,
|
|
3425
|
+
timeoutMs,
|
|
3426
|
+
pollIntervalMs,
|
|
3427
|
+
opts,
|
|
3428
|
+
});
|
|
3429
|
+
}
|
|
3430
|
+
|
|
3431
|
+
const commandHint = buildStatusCommandHint(result, workspaceId);
|
|
3432
|
+
const payload = {
|
|
3433
|
+
...result,
|
|
3434
|
+
hint: commandHint,
|
|
3435
|
+
};
|
|
3436
|
+
emitJsonOutput(payload, opts.pretty);
|
|
2584
3437
|
}
|
|
2585
3438
|
|
|
2586
3439
|
async function handleDataExportTracking(opts) {
|
|
@@ -3200,7 +4053,7 @@ if (typeof program.showHelpAfterError === 'function') {
|
|
|
3200
4053
|
if (typeof program.showSuggestionAfterError === 'function') {
|
|
3201
4054
|
program.showSuggestionAfterError(true);
|
|
3202
4055
|
}
|
|
3203
|
-
program.addHelpText('after', `\nExamples:\n socialseal workspace list\n socialseal workspace use <workspace-id>\n socialseal agent run --message
|
|
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`);
|
|
3204
4057
|
|
|
3205
4058
|
program
|
|
3206
4059
|
.command('agent')
|
|
@@ -3273,6 +4126,15 @@ tools
|
|
|
3273
4126
|
.option('--verbose', 'Show error details')
|
|
3274
4127
|
.action((opts) => runCommand(handleToolsList, opts));
|
|
3275
4128
|
|
|
4129
|
+
tools
|
|
4130
|
+
.command('schema')
|
|
4131
|
+
.description('Show static payload schema hints and examples for a tool')
|
|
4132
|
+
.option('--function <name>', 'Tool name (omit to list all schema hints)')
|
|
4133
|
+
.option('--json', 'Emit machine-readable output')
|
|
4134
|
+
.option('--pretty', 'Pretty-print JSON')
|
|
4135
|
+
.option('--verbose', 'Show error details')
|
|
4136
|
+
.action((opts) => runCommand(handleToolsSchema, opts));
|
|
4137
|
+
|
|
3276
4138
|
tools
|
|
3277
4139
|
.command('call')
|
|
3278
4140
|
.requiredOption('--function <name>', 'Tool name (see official docs)')
|
|
@@ -3290,6 +4152,23 @@ tools
|
|
|
3290
4152
|
.option('--verbose', 'Show error details')
|
|
3291
4153
|
.action((opts) => runCommand(handleToolsCall, opts));
|
|
3292
4154
|
|
|
4155
|
+
tools
|
|
4156
|
+
.command('status <id>')
|
|
4157
|
+
.description('Read unified status for UUID jobs, journey run UUIDs, or numeric Google AI run ids')
|
|
4158
|
+
.option('--kind <kind>', 'auto|agent_job|google_ai_run|journey_run', 'auto')
|
|
4159
|
+
.option('--wait', 'Poll until status reaches a terminal state')
|
|
4160
|
+
.option('--poll-interval <ms>', 'Polling interval in milliseconds when --wait is enabled')
|
|
4161
|
+
.option('--include-results', 'Include Google AI summary/citation rows when reading numeric run ids')
|
|
4162
|
+
.option('--results-limit <n>', 'Max Google AI summary rows to include when --include-results is set')
|
|
4163
|
+
.option('--api-base <url>', 'API base URL (default https://api.socialseal.co)')
|
|
4164
|
+
.option('--api-key <key>', 'CLI API key')
|
|
4165
|
+
.option('--workspace-id <id>', 'Workspace id (required for journey_run status)')
|
|
4166
|
+
.option('--pretty', 'Pretty-print JSON')
|
|
4167
|
+
.option('--json', 'Emit machine-readable errors')
|
|
4168
|
+
.option('--timeout <ms>', 'Request timeout in milliseconds')
|
|
4169
|
+
.option('--verbose', 'Show error details')
|
|
4170
|
+
.action((id, opts) => runCommand(handleToolsStatus, { ...opts, id }));
|
|
4171
|
+
|
|
3293
4172
|
const data = program.command('data').description('Data exports (provisional)');
|
|
3294
4173
|
|
|
3295
4174
|
data
|