@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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@socialseal/cli",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "SocialSeal CLI (non-interactive)",
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 = 30000;
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
- { name: 'agent-tool-jobs', category: 'agent', description: 'Poll queued agent-backed tool jobs and fetch their results.' },
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
- { name: 'google-ai-search', category: 'search', description: 'Run Google AI search queries and fetch result snapshots.' },
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
- { name: 'search-journey-run', category: 'vnext', description: 'Run a search journey for a subject across supported platforms.' },
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 (status === 'completed') return data;
1808
- if (status === 'failed') throw buildSearchJourneyRunFailure(data);
1809
- if (status === 'pending' || status === 'processing') continue;
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: isJsonObject(payload) && typeof payload.workspaceId === 'string'
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: KNOWN_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 KNOWN_TOOLS) {
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 \"ping\"\n socialseal tools list\n socialseal tools call --function <tool> --body @payload.json\n socialseal tools call --function search-journey-run --body @payload.json --async --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`);
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