@socialseal/cli 0.1.2 → 0.1.4
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 +8 -0
- package/package.json +1 -1
- package/src/index.js +964 -14
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.1.4 - 2026-03-19
|
|
6
|
+
- Add explicit `group_add_item` / `group_add_items` CLI aliases for tracking-group membership workflows.
|
|
7
|
+
- Add `tracking resolve` / `get_by_value` so existing tracked searches can be resolved by value using the same duplicate-detection semantics as create.
|
|
8
|
+
- Return operational duplicate metadata for tracking conflicts, including `existing_item_id`, `member_of_group_ids`, platform, region, workspace, and active state.
|
|
9
|
+
|
|
10
|
+
## 0.1.3 - 2026-03-19
|
|
11
|
+
- Republish the current CLI release line after the successful `0.1.2` npm publish, keeping the internal and OSS package versions aligned.
|
|
12
|
+
|
|
5
13
|
## 0.1.2 - 2026-03-18
|
|
6
14
|
- Add `search-journey-run` async CLI ergonomics: `--async` starts the backend async mode, polling is on by default, and `--no-poll` returns the initial `runId` immediately.
|
|
7
15
|
- Add `--poll-interval <ms>` for async `search-journey-run` status polling.
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -15,6 +15,7 @@ const DEFAULT_AGENT_IDLE_TIMEOUT_MS = 300000;
|
|
|
15
15
|
const DEFAULT_POLL_INTERVAL_MS = 2000;
|
|
16
16
|
const MAX_TIMEOUT_MS = 900000;
|
|
17
17
|
const LEGACY_ENABLED = process.env.SOCIALSEAL_ENABLE_LEGACY === '1';
|
|
18
|
+
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.';
|
|
18
19
|
const EXIT_CODES = {
|
|
19
20
|
OK: 0,
|
|
20
21
|
UNKNOWN: 1,
|
|
@@ -29,18 +30,66 @@ const KNOWN_TOOLS = [
|
|
|
29
30
|
{ name: 'deep-exploration-runs', category: 'agent', description: 'Read or persist deep exploration render runs.' },
|
|
30
31
|
{ name: 'workspace-notes', category: 'agent', description: 'Search, create, update, and pin workspace note memory.' },
|
|
31
32
|
{ name: 'workspace-onboarding', category: 'agent', description: 'Read or update workspace onboarding metadata used by the agent.' },
|
|
32
|
-
{
|
|
33
|
-
|
|
33
|
+
{
|
|
34
|
+
name: 'brand-group-management',
|
|
35
|
+
category: 'brand',
|
|
36
|
+
description: 'Manage brand groups, aliases, competitors, and rule configuration.',
|
|
37
|
+
objectType: 'brand_group',
|
|
38
|
+
transport: 'rest_edge_function',
|
|
39
|
+
workspaceScoped: true,
|
|
40
|
+
knownLocalDevState: 'enabled',
|
|
41
|
+
actionAliases: ['list', 'create', 'update', 'delete', 'add_member', 'remove_member'],
|
|
42
|
+
notes: 'Brand groups are not tracking groups.',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'enqueue-brand-metrics-backfill',
|
|
46
|
+
category: 'brand',
|
|
47
|
+
description: 'Queue backfill jobs for brand metrics refreshes.',
|
|
48
|
+
objectType: 'brand_metrics_backfill_job',
|
|
49
|
+
transport: 'post_edge_function',
|
|
50
|
+
workspaceScoped: true,
|
|
51
|
+
knownLocalDevState: 'enabled',
|
|
52
|
+
notes: 'Refreshes brand metrics for brands/workspaces. It does not refresh a tracking group by UUID.',
|
|
53
|
+
},
|
|
34
54
|
{ name: 'export-report', category: 'export', description: 'Generate report exports (csv/json/markdown/html/excel_data).' },
|
|
35
|
-
{
|
|
55
|
+
{
|
|
56
|
+
name: 'export_tracking_data',
|
|
57
|
+
category: 'export',
|
|
58
|
+
description: 'Stream tracking exports as CSV for a group or tracking item.',
|
|
59
|
+
objectType: 'tracking_export',
|
|
60
|
+
transport: 'post_edge_function',
|
|
61
|
+
workspaceScoped: true,
|
|
62
|
+
knownLocalDevState: 'disabled_by_default',
|
|
63
|
+
notes: 'group_id expects a numeric tracking_group id, not a brand_group UUID.',
|
|
64
|
+
},
|
|
36
65
|
{ name: 'douyin-geo-api', category: 'search', description: 'Query Douyin search and geo data.' },
|
|
37
66
|
{ name: 'google-ai-search', category: 'search', description: 'Run Google AI search queries and fetch result snapshots.' },
|
|
38
67
|
{ name: 'instagram-geo-api', category: 'search', description: 'Query Instagram search and geo data.' },
|
|
39
68
|
{ name: 'tiktok-geo-api', category: 'search', description: 'Query TikTok search and geo data.' },
|
|
40
69
|
{ name: 'xhs-geo-api', category: 'search', description: 'Query Xiaohongshu search and geo data.' },
|
|
41
70
|
{ name: 'youtube-geo-api', category: 'search', description: 'Query YouTube search and geo data.' },
|
|
42
|
-
{
|
|
43
|
-
|
|
71
|
+
{
|
|
72
|
+
name: 'group-management',
|
|
73
|
+
category: 'tracking',
|
|
74
|
+
description: 'Manage tracking groups and memberships.',
|
|
75
|
+
objectType: 'tracking_group',
|
|
76
|
+
transport: 'rest_edge_function',
|
|
77
|
+
workspaceScoped: true,
|
|
78
|
+
knownLocalDevState: 'disabled_by_default',
|
|
79
|
+
actionAliases: ['list', 'get', 'create', 'update', 'delete', 'refresh', 'list_items', 'add_item', 'group_add_item', 'add_items', 'group_add_items', 'remove_item', 'group_remove_item'],
|
|
80
|
+
notes: 'REST-style surface under /groups. `add_item`/`group_add_item` accepts an existing `item_id`; `add_items`/`group_add_items` accepts `item_ids` or item payloads for bulk membership adds.',
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: 'tracking',
|
|
84
|
+
category: 'tracking',
|
|
85
|
+
description: 'Manage tracking items.',
|
|
86
|
+
objectType: 'tracking_item',
|
|
87
|
+
transport: 'rest_edge_function',
|
|
88
|
+
workspaceScoped: true,
|
|
89
|
+
knownLocalDevState: 'enabled',
|
|
90
|
+
actionAliases: ['list', 'get', 'resolve', 'get_by_value', 'create', 'update', 'delete', 'refresh'],
|
|
91
|
+
notes: 'REST-style surface. `resolve`/`get_by_value` uses the same workspace/platform/region duplicate-detection lookup as create and returns inactive matches too.',
|
|
92
|
+
},
|
|
44
93
|
{ name: 'journey-feedback', category: 'vnext', description: 'Record acceptance or rejection feedback for opportunity bundles.' },
|
|
45
94
|
{ name: 'opportunity-bundle-approve', category: 'vnext', description: 'Approve an opportunity bundle and create tracking coverage.' },
|
|
46
95
|
{ name: 'search-journey-run', category: 'vnext', description: 'Run a search journey for a subject across supported platforms.' },
|
|
@@ -290,10 +339,869 @@ function mergeWorkspaceIdIntoPayload(payload, workspaceId) {
|
|
|
290
339
|
return { ...payload, workspaceId };
|
|
291
340
|
}
|
|
292
341
|
|
|
342
|
+
function hasOwn(value, key) {
|
|
343
|
+
return Boolean(value) && Object.prototype.hasOwnProperty.call(value, key);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function firstDefined(source, keys) {
|
|
347
|
+
if (!source || typeof source !== 'object' || Array.isArray(source)) return undefined;
|
|
348
|
+
for (const key of keys) {
|
|
349
|
+
if (hasOwn(source, key) && source[key] !== undefined && source[key] !== null) {
|
|
350
|
+
return source[key];
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return undefined;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function trimString(value) {
|
|
357
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function stripUndefinedEntries(value) {
|
|
361
|
+
return Object.fromEntries(
|
|
362
|
+
Object.entries(value || {}).filter(([, entry]) => entry !== undefined),
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function resolvePayloadWorkspaceId(payload, fallbackWorkspaceId) {
|
|
367
|
+
const workspaceId = firstDefined(payload, ['workspace_id', 'workspaceId']);
|
|
368
|
+
if (typeof workspaceId === 'string' && workspaceId.trim().length > 0) {
|
|
369
|
+
return workspaceId.trim();
|
|
370
|
+
}
|
|
371
|
+
return fallbackWorkspaceId || null;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function isUuidLike(value) {
|
|
375
|
+
return typeof value === 'string' && /^[0-9a-f]{8}-[0-9a-f-]{27}$/i.test(value.trim());
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function coercePositiveInteger(value, label) {
|
|
379
|
+
if (value === undefined || value === null || value === '') return undefined;
|
|
380
|
+
const parsed = Number(value);
|
|
381
|
+
if (Number.isInteger(parsed) && parsed > 0) {
|
|
382
|
+
return parsed;
|
|
383
|
+
}
|
|
384
|
+
throw new CliError(`Invalid ${label}: expected a positive integer.`, {
|
|
385
|
+
code: 'INVALID_ARGUMENT',
|
|
386
|
+
exitCode: EXIT_CODES.USAGE,
|
|
387
|
+
hint: isUuidLike(value)
|
|
388
|
+
? `${label} expects a numeric tracking id. Brand-group ids are UUIDs and use brand-group-management instead.`
|
|
389
|
+
: null,
|
|
390
|
+
details: value,
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function buildPathWithQuery(basePath, query) {
|
|
395
|
+
const params = new URLSearchParams();
|
|
396
|
+
for (const [key, rawValue] of Object.entries(query || {})) {
|
|
397
|
+
if (rawValue === undefined || rawValue === null || rawValue === '') continue;
|
|
398
|
+
if (Array.isArray(rawValue)) {
|
|
399
|
+
for (const entry of rawValue) {
|
|
400
|
+
if (entry !== undefined && entry !== null && entry !== '') {
|
|
401
|
+
params.append(key, String(entry));
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
params.set(key, String(rawValue));
|
|
407
|
+
}
|
|
408
|
+
const queryString = params.toString();
|
|
409
|
+
return queryString ? `${basePath}?${queryString}` : basePath;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function normalizeTrackingType(value) {
|
|
413
|
+
const normalized = trimString(value).toLowerCase();
|
|
414
|
+
if (!normalized) return undefined;
|
|
415
|
+
if (normalized === 'keyword' || normalized === 'search') return 'search';
|
|
416
|
+
if (normalized === 'account' || normalized === 'creator') return 'creator';
|
|
417
|
+
if (normalized === 'hashtag') return 'hashtag';
|
|
418
|
+
throw new CliError(`Invalid tracking type: ${value}`, {
|
|
419
|
+
code: 'INVALID_ARGUMENT',
|
|
420
|
+
exitCode: EXIT_CODES.USAGE,
|
|
421
|
+
hint: 'Use keyword/search, hashtag, or account/creator.',
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function normalizeTrackingPayload(payload, fallbackWorkspaceId) {
|
|
426
|
+
const trackValue = firstDefined(payload, ['track_value', 'trackValue', 'value']);
|
|
427
|
+
const refreshFrequency = firstDefined(payload, ['refresh_frequency', 'refreshFrequency']);
|
|
428
|
+
const nextRefreshAt = firstDefined(payload, ['next_refresh_at', 'nextRefreshAt']);
|
|
429
|
+
const region = firstDefined(payload, ['region']);
|
|
430
|
+
const platform = firstDefined(payload, ['platform']);
|
|
431
|
+
const brandIds = firstDefined(payload, ['brand_ids', 'brandIds']);
|
|
432
|
+
const includeInactive = firstDefined(payload, ['includeInactive', 'include_inactive']);
|
|
433
|
+
const isActive = firstDefined(payload, ['is_active', 'isActive']);
|
|
434
|
+
const limit = firstDefined(payload, ['limit']);
|
|
435
|
+
const page = firstDefined(payload, ['page']);
|
|
436
|
+
const offset = firstDefined(payload, ['offset']);
|
|
437
|
+
const itemId = firstDefined(payload, ['item_id', 'itemId', 'id']);
|
|
438
|
+
|
|
439
|
+
return stripUndefinedEntries({
|
|
440
|
+
action: trimString(firstDefined(payload, ['action'])) || undefined,
|
|
441
|
+
workspaceId: resolvePayloadWorkspaceId(payload, fallbackWorkspaceId),
|
|
442
|
+
item_id: coercePositiveInteger(itemId, 'item_id'),
|
|
443
|
+
name: trimString(firstDefined(payload, ['name'])) || (trimString(trackValue) || undefined),
|
|
444
|
+
track_type: normalizeTrackingType(firstDefined(payload, ['track_type', 'trackType', 'type'])),
|
|
445
|
+
track_value: trimString(trackValue) || undefined,
|
|
446
|
+
refresh_frequency: trimString(refreshFrequency) || undefined,
|
|
447
|
+
next_refresh_at: nextRefreshAt ?? undefined,
|
|
448
|
+
region: typeof region === 'string' ? region.trim() || undefined : region,
|
|
449
|
+
platform: trimString(platform) || undefined,
|
|
450
|
+
brand_ids: Array.isArray(brandIds) ? brandIds : undefined,
|
|
451
|
+
limit: limit !== undefined ? Number(limit) : undefined,
|
|
452
|
+
page: page !== undefined ? Number(page) : undefined,
|
|
453
|
+
offset: offset !== undefined ? Number(offset) : undefined,
|
|
454
|
+
is_active: typeof isActive === 'boolean' ? isActive : undefined,
|
|
455
|
+
include_inactive: typeof includeInactive === 'boolean' ? includeInactive : undefined,
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function normalizeGroupManagementPayload(payload, fallbackWorkspaceId) {
|
|
460
|
+
const groupId = firstDefined(payload, ['group_id', 'groupId', 'id']);
|
|
461
|
+
const itemId = firstDefined(payload, ['item_id', 'itemId']);
|
|
462
|
+
const itemIds = firstDefined(payload, ['item_ids', 'itemIds']);
|
|
463
|
+
const items = firstDefined(payload, ['items']);
|
|
464
|
+
const limit = firstDefined(payload, ['limit']);
|
|
465
|
+
const page = firstDefined(payload, ['page']);
|
|
466
|
+
return stripUndefinedEntries({
|
|
467
|
+
action: trimString(firstDefined(payload, ['action'])) || undefined,
|
|
468
|
+
workspaceId: resolvePayloadWorkspaceId(payload, fallbackWorkspaceId),
|
|
469
|
+
group_id: coercePositiveInteger(groupId, 'group_id'),
|
|
470
|
+
item_id: coercePositiveInteger(itemId, 'item_id'),
|
|
471
|
+
item_ids: Array.isArray(itemIds)
|
|
472
|
+
? itemIds.map((value, index) => {
|
|
473
|
+
const parsed = coercePositiveInteger(value, `item_ids[${index}]`);
|
|
474
|
+
if (!parsed) {
|
|
475
|
+
throw new CliError(`Invalid item_ids[${index}]: expected a positive integer.`, {
|
|
476
|
+
code: 'INVALID_ARGUMENT',
|
|
477
|
+
exitCode: EXIT_CODES.USAGE,
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
return parsed;
|
|
481
|
+
})
|
|
482
|
+
: undefined,
|
|
483
|
+
items: Array.isArray(items) ? items : undefined,
|
|
484
|
+
name: trimString(firstDefined(payload, ['name'])) || undefined,
|
|
485
|
+
description: firstDefined(payload, ['description']),
|
|
486
|
+
platform: trimString(firstDefined(payload, ['platform', 'groupPlatform'])) || undefined,
|
|
487
|
+
refresh_frequency: trimString(firstDefined(payload, ['refresh_frequency', 'refreshFrequency'])) || undefined,
|
|
488
|
+
next_refresh_at: firstDefined(payload, ['next_refresh_at', 'nextRefreshAt']) ?? undefined,
|
|
489
|
+
brand_id: trimString(firstDefined(payload, ['brand_id', 'brandId'])) || undefined,
|
|
490
|
+
track_type: normalizeTrackingType(firstDefined(payload, ['track_type', 'trackType', 'type'])),
|
|
491
|
+
track_value: trimString(firstDefined(payload, ['track_value', 'trackValue', 'value'])) || undefined,
|
|
492
|
+
region: typeof firstDefined(payload, ['region']) === 'string'
|
|
493
|
+
? trimString(firstDefined(payload, ['region'])) || undefined
|
|
494
|
+
: firstDefined(payload, ['region']),
|
|
495
|
+
limit: limit !== undefined ? Number(limit) : undefined,
|
|
496
|
+
page: page !== undefined ? Number(page) : undefined,
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function normalizeBrandGroupPayload(payload, fallbackWorkspaceId) {
|
|
501
|
+
return stripUndefinedEntries({
|
|
502
|
+
action: trimString(firstDefined(payload, ['action'])) || undefined,
|
|
503
|
+
workspaceId: resolvePayloadWorkspaceId(payload, fallbackWorkspaceId),
|
|
504
|
+
workspace_id: resolvePayloadWorkspaceId(payload, fallbackWorkspaceId) || undefined,
|
|
505
|
+
brand_group_id: trimString(firstDefined(payload, ['brand_group_id', 'brandGroupId', 'group_id', 'groupId', 'id'])) || undefined,
|
|
506
|
+
brand_id: trimString(firstDefined(payload, ['brand_id', 'brandId'])) || undefined,
|
|
507
|
+
name: trimString(firstDefined(payload, ['name'])) || undefined,
|
|
508
|
+
description: firstDefined(payload, ['description']),
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function normalizeBackfillPayload(payload, fallbackWorkspaceId) {
|
|
513
|
+
return stripUndefinedEntries({
|
|
514
|
+
workspace_id: resolvePayloadWorkspaceId(payload, fallbackWorkspaceId) || undefined,
|
|
515
|
+
brand_id: trimString(firstDefined(payload, ['brand_id', 'brandId'])) || undefined,
|
|
516
|
+
tracking_group_ids: firstDefined(payload, ['tracking_group_ids', 'trackingGroupIds']),
|
|
517
|
+
backfill_days: firstDefined(payload, ['backfill_days', 'backfillDays']),
|
|
518
|
+
max_tracking_groups: firstDefined(payload, ['max_tracking_groups', 'maxTrackingGroups']),
|
|
519
|
+
max_videos: firstDefined(payload, ['max_videos', 'maxVideos']),
|
|
520
|
+
max_summaries: firstDefined(payload, ['max_summaries', 'maxSummaries']),
|
|
521
|
+
bump_user_revision: firstDefined(payload, ['bump_user_revision', 'bumpUserRevision']),
|
|
522
|
+
bump_workspace_revision: firstDefined(payload, ['bump_workspace_revision', 'bumpWorkspaceRevision']),
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function normalizeTrackingExportPayload(payload, fallbackWorkspaceId) {
|
|
527
|
+
const groupId = firstDefined(payload, ['group_id', 'groupId', 'tracking_group_id', 'trackingGroupId']);
|
|
528
|
+
const itemId = firstDefined(payload, ['tracking_item_id', 'trackingItemId', 'item_id', 'itemId']);
|
|
529
|
+
return stripUndefinedEntries({
|
|
530
|
+
workspace_id: resolvePayloadWorkspaceId(payload, fallbackWorkspaceId) || undefined,
|
|
531
|
+
group_id: coercePositiveInteger(groupId, 'group_id'),
|
|
532
|
+
tracking_item_id: coercePositiveInteger(itemId, 'tracking_item_id'),
|
|
533
|
+
time_period: trimString(firstDefined(payload, ['time_period', 'timePeriod'])) || undefined,
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function buildGroupAddPayloadFromValue(rawValue, payload, label) {
|
|
538
|
+
const value = trimString(rawValue);
|
|
539
|
+
if (!value) {
|
|
540
|
+
throw new CliError(`Invalid ${label}: expected a non-empty tracking value.`, {
|
|
541
|
+
code: 'INVALID_ARGUMENT',
|
|
542
|
+
exitCode: EXIT_CODES.USAGE,
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
if (!payload.track_type) {
|
|
546
|
+
throw new CliError(`${label} requires track_type/type when using raw values.`, {
|
|
547
|
+
code: 'MISSING_ARGUMENT',
|
|
548
|
+
exitCode: EXIT_CODES.USAGE,
|
|
549
|
+
hint: 'Use type=keyword/search, hashtag, or account/creator when adding items by value.',
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
return stripUndefinedEntries({
|
|
553
|
+
name: value,
|
|
554
|
+
track_type: payload.track_type,
|
|
555
|
+
track_value: value,
|
|
556
|
+
refresh_frequency: payload.refresh_frequency,
|
|
557
|
+
next_refresh_at: payload.next_refresh_at,
|
|
558
|
+
region: payload.region,
|
|
559
|
+
platform: payload.platform,
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function buildGroupAddPayloadFromItem(rawItem, payload, label) {
|
|
564
|
+
if (typeof rawItem === 'number') {
|
|
565
|
+
return { item_id: coercePositiveInteger(rawItem, label) };
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
if (typeof rawItem === 'string') {
|
|
569
|
+
const trimmed = rawItem.trim();
|
|
570
|
+
if (/^\d+$/.test(trimmed)) {
|
|
571
|
+
return { item_id: coercePositiveInteger(trimmed, label) };
|
|
572
|
+
}
|
|
573
|
+
return buildGroupAddPayloadFromValue(rawItem, payload, label);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if (!rawItem || typeof rawItem !== 'object' || Array.isArray(rawItem)) {
|
|
577
|
+
throw new CliError(`Invalid ${label}: expected an item id, string value, or object payload.`, {
|
|
578
|
+
code: 'INVALID_ARGUMENT',
|
|
579
|
+
exitCode: EXIT_CODES.USAGE,
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
const itemId = coercePositiveInteger(firstDefined(rawItem, ['item_id', 'itemId', 'id']), `${label}.item_id`);
|
|
584
|
+
if (itemId) {
|
|
585
|
+
return { item_id: itemId };
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
const trackValue = trimString(firstDefined(rawItem, ['track_value', 'trackValue', 'value'])) || undefined;
|
|
589
|
+
const name = trimString(firstDefined(rawItem, ['name'])) || trackValue;
|
|
590
|
+
const trackType = normalizeTrackingType(firstDefined(rawItem, ['track_type', 'trackType', 'type'])) || payload.track_type;
|
|
591
|
+
const region = firstDefined(rawItem, ['region']) ?? payload.region;
|
|
592
|
+
const platform = trimString(firstDefined(rawItem, ['platform'])) || payload.platform;
|
|
593
|
+
const refreshFrequency = trimString(firstDefined(rawItem, ['refresh_frequency', 'refreshFrequency'])) || payload.refresh_frequency;
|
|
594
|
+
const nextRefreshAt = firstDefined(rawItem, ['next_refresh_at', 'nextRefreshAt']) ?? payload.next_refresh_at;
|
|
595
|
+
|
|
596
|
+
if (!name || !trackValue || !trackType) {
|
|
597
|
+
throw new CliError(`${label} requires item_id or name/track_value + track_type.`, {
|
|
598
|
+
code: 'MISSING_ARGUMENT',
|
|
599
|
+
exitCode: EXIT_CODES.USAGE,
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
return stripUndefinedEntries({
|
|
604
|
+
name,
|
|
605
|
+
track_type: trackType,
|
|
606
|
+
track_value: trackValue,
|
|
607
|
+
refresh_frequency: refreshFrequency,
|
|
608
|
+
next_refresh_at: nextRefreshAt,
|
|
609
|
+
region,
|
|
610
|
+
platform,
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
function buildSingleGroupAddBody(payload) {
|
|
615
|
+
if (payload.item_id) {
|
|
616
|
+
return { item_id: payload.item_id };
|
|
617
|
+
}
|
|
618
|
+
if (payload.track_value || payload.name) {
|
|
619
|
+
return buildGroupAddPayloadFromItem({
|
|
620
|
+
name: payload.name,
|
|
621
|
+
track_type: payload.track_type,
|
|
622
|
+
track_value: payload.track_value,
|
|
623
|
+
refresh_frequency: payload.refresh_frequency,
|
|
624
|
+
next_refresh_at: payload.next_refresh_at,
|
|
625
|
+
region: payload.region,
|
|
626
|
+
platform: payload.platform,
|
|
627
|
+
}, payload, 'group add item payload');
|
|
628
|
+
}
|
|
629
|
+
throw new CliError('group add_item requires item_id or a tracking payload.', {
|
|
630
|
+
code: 'MISSING_ARGUMENT',
|
|
631
|
+
exitCode: EXIT_CODES.USAGE,
|
|
632
|
+
hint: 'Provide item_id to attach an existing tracking item, or provide track_type + track_value to create/link by value.',
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
function buildBulkGroupAddBody(payload) {
|
|
637
|
+
if (Array.isArray(payload.item_ids) && payload.item_ids.length > 0) {
|
|
638
|
+
return payload.item_ids.map((item_id) => ({ item_id }));
|
|
639
|
+
}
|
|
640
|
+
if (Array.isArray(payload.items) && payload.items.length > 0) {
|
|
641
|
+
return payload.items.map((item, index) => buildGroupAddPayloadFromItem(item, payload, `items[${index}]`));
|
|
642
|
+
}
|
|
643
|
+
if (payload.item_id || payload.track_value || payload.name) {
|
|
644
|
+
return [buildSingleGroupAddBody(payload)];
|
|
645
|
+
}
|
|
646
|
+
throw new CliError('group add_items requires item_ids, items, or a single item payload.', {
|
|
647
|
+
code: 'MISSING_ARGUMENT',
|
|
648
|
+
exitCode: EXIT_CODES.USAGE,
|
|
649
|
+
hint: 'Use item_ids to bulk attach existing tracking items, or use items with track_type/type for value-based adds.',
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
function translateTrackingAction(payload, workspaceId) {
|
|
654
|
+
const action = payload.action ? payload.action.toLowerCase() : null;
|
|
655
|
+
if (!action) {
|
|
656
|
+
return {
|
|
657
|
+
method: 'POST',
|
|
658
|
+
pathSuffix: '',
|
|
659
|
+
body: stripUndefinedEntries({
|
|
660
|
+
name: payload.name,
|
|
661
|
+
track_type: payload.track_type,
|
|
662
|
+
track_value: payload.track_value,
|
|
663
|
+
refresh_frequency: payload.refresh_frequency,
|
|
664
|
+
next_refresh_at: payload.next_refresh_at,
|
|
665
|
+
region: payload.region,
|
|
666
|
+
platform: payload.platform,
|
|
667
|
+
brand_ids: payload.brand_ids,
|
|
668
|
+
}),
|
|
669
|
+
workspaceId,
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
if (action === 'list' || action === 'item_list') {
|
|
674
|
+
const limit = Number.isFinite(payload.limit) ? payload.limit : 20;
|
|
675
|
+
const page = Number.isFinite(payload.page) ? Math.max(1, payload.page) : 1;
|
|
676
|
+
const offset = Number.isFinite(payload.offset) ? Math.max(0, payload.offset) : ((page - 1) * limit);
|
|
677
|
+
const isActive = typeof payload.is_active === 'boolean'
|
|
678
|
+
? payload.is_active
|
|
679
|
+
: (payload.include_inactive ? undefined : true);
|
|
680
|
+
return {
|
|
681
|
+
method: 'GET',
|
|
682
|
+
pathSuffix: buildPathWithQuery('', {
|
|
683
|
+
workspace_id: workspaceId || undefined,
|
|
684
|
+
limit,
|
|
685
|
+
offset,
|
|
686
|
+
track_type: payload.track_type,
|
|
687
|
+
track_value: payload.track_value,
|
|
688
|
+
platform: payload.platform,
|
|
689
|
+
region: payload.region,
|
|
690
|
+
is_active: isActive,
|
|
691
|
+
}),
|
|
692
|
+
body: undefined,
|
|
693
|
+
workspaceId,
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
if (action === 'get' || action === 'item_get') {
|
|
698
|
+
const itemId = coercePositiveInteger(payload.item_id, 'item_id');
|
|
699
|
+
if (!itemId) {
|
|
700
|
+
throw new CliError('item_id is required for tracking get.', {
|
|
701
|
+
code: 'MISSING_ARGUMENT',
|
|
702
|
+
exitCode: EXIT_CODES.USAGE,
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
return {
|
|
706
|
+
method: 'GET',
|
|
707
|
+
pathSuffix: buildPathWithQuery(`/${itemId}`, { workspace_id: workspaceId || undefined }),
|
|
708
|
+
body: undefined,
|
|
709
|
+
workspaceId,
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
if (action === 'resolve' || action === 'item_resolve' || action === 'get_by_value' || action === 'item_get_by_value') {
|
|
714
|
+
if (!payload.track_type || !payload.track_value) {
|
|
715
|
+
throw new CliError('track_type and track_value are required for tracking resolve.', {
|
|
716
|
+
code: 'MISSING_ARGUMENT',
|
|
717
|
+
exitCode: EXIT_CODES.USAGE,
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
return {
|
|
721
|
+
method: 'GET',
|
|
722
|
+
pathSuffix: buildPathWithQuery('', {
|
|
723
|
+
workspace_id: workspaceId || undefined,
|
|
724
|
+
resolve: 'true',
|
|
725
|
+
track_type: payload.track_type,
|
|
726
|
+
track_value: payload.track_value,
|
|
727
|
+
platform: payload.platform,
|
|
728
|
+
region: payload.region,
|
|
729
|
+
}),
|
|
730
|
+
body: undefined,
|
|
731
|
+
workspaceId,
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
if (action === 'create' || action === 'item_create') {
|
|
736
|
+
return {
|
|
737
|
+
method: 'POST',
|
|
738
|
+
pathSuffix: '',
|
|
739
|
+
body: stripUndefinedEntries({
|
|
740
|
+
name: payload.name,
|
|
741
|
+
track_type: payload.track_type,
|
|
742
|
+
track_value: payload.track_value,
|
|
743
|
+
refresh_frequency: payload.refresh_frequency,
|
|
744
|
+
next_refresh_at: payload.next_refresh_at,
|
|
745
|
+
region: payload.region,
|
|
746
|
+
platform: payload.platform,
|
|
747
|
+
brand_ids: payload.brand_ids,
|
|
748
|
+
}),
|
|
749
|
+
workspaceId,
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
if (action === 'update' || action === 'item_update') {
|
|
754
|
+
const itemId = coercePositiveInteger(payload.item_id, 'item_id');
|
|
755
|
+
if (!itemId) {
|
|
756
|
+
throw new CliError('item_id is required for tracking update.', {
|
|
757
|
+
code: 'MISSING_ARGUMENT',
|
|
758
|
+
exitCode: EXIT_CODES.USAGE,
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
return {
|
|
762
|
+
method: 'PATCH',
|
|
763
|
+
pathSuffix: buildPathWithQuery(`/${itemId}`, { workspace_id: workspaceId || undefined }),
|
|
764
|
+
body: stripUndefinedEntries({
|
|
765
|
+
refresh_frequency: payload.refresh_frequency,
|
|
766
|
+
next_refresh_at: payload.next_refresh_at,
|
|
767
|
+
}),
|
|
768
|
+
workspaceId,
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
if (action === 'delete' || action === 'item_delete') {
|
|
773
|
+
const itemId = coercePositiveInteger(payload.item_id, 'item_id');
|
|
774
|
+
if (!itemId) {
|
|
775
|
+
throw new CliError('item_id is required for tracking delete.', {
|
|
776
|
+
code: 'MISSING_ARGUMENT',
|
|
777
|
+
exitCode: EXIT_CODES.USAGE,
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
return {
|
|
781
|
+
method: 'DELETE',
|
|
782
|
+
pathSuffix: buildPathWithQuery(`/${itemId}`, { workspace_id: workspaceId || undefined }),
|
|
783
|
+
body: undefined,
|
|
784
|
+
workspaceId,
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
if (action === 'refresh' || action === 'item_refresh') {
|
|
789
|
+
const itemId = coercePositiveInteger(payload.item_id, 'item_id');
|
|
790
|
+
if (!itemId) {
|
|
791
|
+
throw new CliError('item_id is required for tracking refresh.', {
|
|
792
|
+
code: 'MISSING_ARGUMENT',
|
|
793
|
+
exitCode: EXIT_CODES.USAGE,
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
return {
|
|
797
|
+
method: 'POST',
|
|
798
|
+
pathSuffix: buildPathWithQuery(`/${itemId}/refresh`, { workspace_id: workspaceId || undefined }),
|
|
799
|
+
body: {},
|
|
800
|
+
workspaceId,
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
throw new CliError(`Unsupported tracking action: ${payload.action}`, {
|
|
805
|
+
code: 'INVALID_ARGUMENT',
|
|
806
|
+
exitCode: EXIT_CODES.USAGE,
|
|
807
|
+
hint: 'Supported tracking actions: list, get, resolve, create, update, delete, refresh.',
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
function translateGroupManagementAction(payload, workspaceId, originalMethod) {
|
|
812
|
+
const action = payload.action ? payload.action.toLowerCase() : null;
|
|
813
|
+
|
|
814
|
+
if (!action && originalMethod === 'GET') {
|
|
815
|
+
return {
|
|
816
|
+
method: 'GET',
|
|
817
|
+
pathSuffix: buildPathWithQuery('/groups', { workspace_id: workspaceId || undefined }),
|
|
818
|
+
body: undefined,
|
|
819
|
+
workspaceId,
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
if (!action) {
|
|
824
|
+
return {
|
|
825
|
+
method: 'POST',
|
|
826
|
+
pathSuffix: buildPathWithQuery('/groups', { workspace_id: workspaceId || undefined }),
|
|
827
|
+
body: stripUndefinedEntries({
|
|
828
|
+
name: payload.name,
|
|
829
|
+
description: payload.description,
|
|
830
|
+
platform: payload.platform,
|
|
831
|
+
refresh_frequency: payload.refresh_frequency,
|
|
832
|
+
next_refresh_at: payload.next_refresh_at,
|
|
833
|
+
brand_id: payload.brand_id,
|
|
834
|
+
}),
|
|
835
|
+
workspaceId,
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
if (action === 'list' || action === 'group_list') {
|
|
840
|
+
return {
|
|
841
|
+
method: 'GET',
|
|
842
|
+
pathSuffix: buildPathWithQuery('/groups', { workspace_id: workspaceId || undefined }),
|
|
843
|
+
body: undefined,
|
|
844
|
+
workspaceId,
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
if (action === 'get' || action === 'group_get') {
|
|
849
|
+
const groupId = coercePositiveInteger(payload.group_id, 'group_id');
|
|
850
|
+
if (!groupId) {
|
|
851
|
+
throw new CliError('group_id is required for group get.', {
|
|
852
|
+
code: 'MISSING_ARGUMENT',
|
|
853
|
+
exitCode: EXIT_CODES.USAGE,
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
return {
|
|
857
|
+
method: 'GET',
|
|
858
|
+
pathSuffix: buildPathWithQuery(`/groups/${groupId}`, { workspace_id: workspaceId || undefined }),
|
|
859
|
+
body: undefined,
|
|
860
|
+
workspaceId,
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
if (action === 'create' || action === 'group_create') {
|
|
865
|
+
return {
|
|
866
|
+
method: 'POST',
|
|
867
|
+
pathSuffix: buildPathWithQuery('/groups', { workspace_id: workspaceId || undefined }),
|
|
868
|
+
body: stripUndefinedEntries({
|
|
869
|
+
name: payload.name,
|
|
870
|
+
description: payload.description,
|
|
871
|
+
platform: payload.platform,
|
|
872
|
+
refresh_frequency: payload.refresh_frequency,
|
|
873
|
+
next_refresh_at: payload.next_refresh_at,
|
|
874
|
+
brand_id: payload.brand_id,
|
|
875
|
+
}),
|
|
876
|
+
workspaceId,
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
if (action === 'update' || action === 'group_update') {
|
|
881
|
+
const groupId = coercePositiveInteger(payload.group_id, 'group_id');
|
|
882
|
+
if (!groupId) {
|
|
883
|
+
throw new CliError('group_id is required for group update.', {
|
|
884
|
+
code: 'MISSING_ARGUMENT',
|
|
885
|
+
exitCode: EXIT_CODES.USAGE,
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
return {
|
|
889
|
+
method: 'PATCH',
|
|
890
|
+
pathSuffix: buildPathWithQuery(`/groups/${groupId}`, { workspace_id: workspaceId || undefined }),
|
|
891
|
+
body: stripUndefinedEntries({
|
|
892
|
+
name: payload.name,
|
|
893
|
+
description: payload.description,
|
|
894
|
+
refresh_frequency: payload.refresh_frequency,
|
|
895
|
+
next_refresh_at: payload.next_refresh_at,
|
|
896
|
+
brand_id: payload.brand_id,
|
|
897
|
+
}),
|
|
898
|
+
workspaceId,
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
if (action === 'delete' || action === 'group_delete') {
|
|
903
|
+
const groupId = coercePositiveInteger(payload.group_id, 'group_id');
|
|
904
|
+
if (!groupId) {
|
|
905
|
+
throw new CliError('group_id is required for group delete.', {
|
|
906
|
+
code: 'MISSING_ARGUMENT',
|
|
907
|
+
exitCode: EXIT_CODES.USAGE,
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
return {
|
|
911
|
+
method: 'DELETE',
|
|
912
|
+
pathSuffix: buildPathWithQuery(`/groups/${groupId}`, { workspace_id: workspaceId || undefined }),
|
|
913
|
+
body: undefined,
|
|
914
|
+
workspaceId,
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
if (action === 'refresh' || action === 'group_refresh') {
|
|
919
|
+
const groupId = coercePositiveInteger(payload.group_id, 'group_id');
|
|
920
|
+
if (!groupId) {
|
|
921
|
+
throw new CliError('group_id is required for group refresh.', {
|
|
922
|
+
code: 'MISSING_ARGUMENT',
|
|
923
|
+
exitCode: EXIT_CODES.USAGE,
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
return {
|
|
927
|
+
method: 'POST',
|
|
928
|
+
pathSuffix: buildPathWithQuery(`/groups/${groupId}/refresh`, { workspace_id: workspaceId || undefined }),
|
|
929
|
+
body: {},
|
|
930
|
+
workspaceId,
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
if (action === 'list_items' || action === 'group_list_items') {
|
|
935
|
+
const groupId = coercePositiveInteger(payload.group_id, 'group_id');
|
|
936
|
+
if (!groupId) {
|
|
937
|
+
throw new CliError('group_id is required for group list_items.', {
|
|
938
|
+
code: 'MISSING_ARGUMENT',
|
|
939
|
+
exitCode: EXIT_CODES.USAGE,
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
return {
|
|
943
|
+
method: 'GET',
|
|
944
|
+
pathSuffix: buildPathWithQuery(`/groups/${groupId}/items`, {
|
|
945
|
+
workspace_id: workspaceId || undefined,
|
|
946
|
+
page: Number.isFinite(payload.page) ? payload.page : undefined,
|
|
947
|
+
limit: Number.isFinite(payload.limit) ? payload.limit : undefined,
|
|
948
|
+
}),
|
|
949
|
+
body: undefined,
|
|
950
|
+
workspaceId,
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
if (action === 'add_item' || action === 'group_add_item') {
|
|
955
|
+
const groupId = coercePositiveInteger(payload.group_id, 'group_id');
|
|
956
|
+
if (!groupId) {
|
|
957
|
+
throw new CliError('group_id is required for group add_item.', {
|
|
958
|
+
code: 'MISSING_ARGUMENT',
|
|
959
|
+
exitCode: EXIT_CODES.USAGE,
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
return {
|
|
963
|
+
method: 'POST',
|
|
964
|
+
pathSuffix: buildPathWithQuery(`/groups/${groupId}/items`, { workspace_id: workspaceId || undefined }),
|
|
965
|
+
body: buildSingleGroupAddBody(payload),
|
|
966
|
+
workspaceId,
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
if (action === 'add_items' || action === 'group_add_items') {
|
|
971
|
+
const groupId = coercePositiveInteger(payload.group_id, 'group_id');
|
|
972
|
+
if (!groupId) {
|
|
973
|
+
throw new CliError('group_id is required for group add_items.', {
|
|
974
|
+
code: 'MISSING_ARGUMENT',
|
|
975
|
+
exitCode: EXIT_CODES.USAGE,
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
return {
|
|
979
|
+
method: 'POST',
|
|
980
|
+
pathSuffix: buildPathWithQuery(`/groups/${groupId}/items/bulk`, { workspace_id: workspaceId || undefined }),
|
|
981
|
+
body: buildBulkGroupAddBody(payload),
|
|
982
|
+
workspaceId,
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
if (action === 'remove_item' || action === 'group_remove_item') {
|
|
987
|
+
const groupId = coercePositiveInteger(payload.group_id, 'group_id');
|
|
988
|
+
const itemId = coercePositiveInteger(payload.item_id, 'item_id');
|
|
989
|
+
if (!groupId || !itemId) {
|
|
990
|
+
throw new CliError('group_id and item_id are required for group remove_item.', {
|
|
991
|
+
code: 'MISSING_ARGUMENT',
|
|
992
|
+
exitCode: EXIT_CODES.USAGE,
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
return {
|
|
996
|
+
method: 'DELETE',
|
|
997
|
+
pathSuffix: buildPathWithQuery(`/groups/${groupId}/items/${itemId}`, { workspace_id: workspaceId || undefined }),
|
|
998
|
+
body: undefined,
|
|
999
|
+
workspaceId,
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
throw new CliError(`Unsupported group-management action: ${payload.action}`, {
|
|
1004
|
+
code: 'INVALID_ARGUMENT',
|
|
1005
|
+
exitCode: EXIT_CODES.USAGE,
|
|
1006
|
+
hint: 'Supported group-management actions: list, get, create, update, delete, refresh, list_items, add_item, add_items, remove_item.',
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
function translateBrandGroupAction(payload, workspaceId) {
|
|
1011
|
+
const action = payload.action ? payload.action.toLowerCase() : null;
|
|
1012
|
+
const brandGroupId = payload.brand_group_id || undefined;
|
|
1013
|
+
const effectiveWorkspaceId = payload.workspace_id || workspaceId || undefined;
|
|
1014
|
+
|
|
1015
|
+
if (!action) {
|
|
1016
|
+
return {
|
|
1017
|
+
method: 'POST',
|
|
1018
|
+
pathSuffix: '',
|
|
1019
|
+
body: stripUndefinedEntries({
|
|
1020
|
+
name: payload.name,
|
|
1021
|
+
description: payload.description,
|
|
1022
|
+
workspace_id: effectiveWorkspaceId,
|
|
1023
|
+
}),
|
|
1024
|
+
workspaceId: workspaceId || null,
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
if (action === 'list') {
|
|
1029
|
+
return {
|
|
1030
|
+
method: 'GET',
|
|
1031
|
+
pathSuffix: buildPathWithQuery('', { workspace_id: effectiveWorkspaceId }),
|
|
1032
|
+
body: undefined,
|
|
1033
|
+
workspaceId: workspaceId || null,
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
if (action === 'create') {
|
|
1038
|
+
return {
|
|
1039
|
+
method: 'POST',
|
|
1040
|
+
pathSuffix: '',
|
|
1041
|
+
body: stripUndefinedEntries({
|
|
1042
|
+
name: payload.name,
|
|
1043
|
+
description: payload.description,
|
|
1044
|
+
workspace_id: effectiveWorkspaceId,
|
|
1045
|
+
}),
|
|
1046
|
+
workspaceId: workspaceId || null,
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
if (action === 'update') {
|
|
1051
|
+
if (!brandGroupId) {
|
|
1052
|
+
throw new CliError('brand_group_id is required for brand-group update.', {
|
|
1053
|
+
code: 'MISSING_ARGUMENT',
|
|
1054
|
+
exitCode: EXIT_CODES.USAGE,
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
return {
|
|
1058
|
+
method: 'PATCH',
|
|
1059
|
+
pathSuffix: `/${brandGroupId}`,
|
|
1060
|
+
body: stripUndefinedEntries({
|
|
1061
|
+
name: payload.name,
|
|
1062
|
+
description: payload.description,
|
|
1063
|
+
}),
|
|
1064
|
+
workspaceId: workspaceId || null,
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
if (action === 'delete') {
|
|
1069
|
+
if (!brandGroupId) {
|
|
1070
|
+
throw new CliError('brand_group_id is required for brand-group delete.', {
|
|
1071
|
+
code: 'MISSING_ARGUMENT',
|
|
1072
|
+
exitCode: EXIT_CODES.USAGE,
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1075
|
+
return {
|
|
1076
|
+
method: 'DELETE',
|
|
1077
|
+
pathSuffix: `/${brandGroupId}`,
|
|
1078
|
+
body: undefined,
|
|
1079
|
+
workspaceId: workspaceId || null,
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
if (action === 'add_member') {
|
|
1084
|
+
if (!brandGroupId || !payload.brand_id) {
|
|
1085
|
+
throw new CliError('brand_group_id and brand_id are required for brand-group add_member.', {
|
|
1086
|
+
code: 'MISSING_ARGUMENT',
|
|
1087
|
+
exitCode: EXIT_CODES.USAGE,
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
return {
|
|
1091
|
+
method: 'POST',
|
|
1092
|
+
pathSuffix: `/${brandGroupId}/members`,
|
|
1093
|
+
body: { brand_id: payload.brand_id },
|
|
1094
|
+
workspaceId: workspaceId || null,
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
if (action === 'remove_member') {
|
|
1099
|
+
if (!brandGroupId || !payload.brand_id) {
|
|
1100
|
+
throw new CliError('brand_group_id and brand_id are required for brand-group remove_member.', {
|
|
1101
|
+
code: 'MISSING_ARGUMENT',
|
|
1102
|
+
exitCode: EXIT_CODES.USAGE,
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
return {
|
|
1106
|
+
method: 'DELETE',
|
|
1107
|
+
pathSuffix: `/${brandGroupId}/members/${payload.brand_id}`,
|
|
1108
|
+
body: undefined,
|
|
1109
|
+
workspaceId: workspaceId || null,
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
throw new CliError(`Unsupported brand-group-management action: ${payload.action}`, {
|
|
1114
|
+
code: 'INVALID_ARGUMENT',
|
|
1115
|
+
exitCode: EXIT_CODES.USAGE,
|
|
1116
|
+
hint: 'Supported brand-group-management actions: list, create, update, delete, add_member, remove_member.',
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
function translateToolInvocation({ functionName, method, payload, resolvedWorkspaceId }) {
|
|
1121
|
+
if (!isJsonObject(payload)) {
|
|
1122
|
+
return {
|
|
1123
|
+
method,
|
|
1124
|
+
pathSuffix: '',
|
|
1125
|
+
body: payload,
|
|
1126
|
+
workspaceId: resolvedWorkspaceId,
|
|
1127
|
+
normalizedPayload: payload,
|
|
1128
|
+
};
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
if (functionName === 'tracking') {
|
|
1132
|
+
const normalizedPayload = normalizeTrackingPayload(payload, resolvedWorkspaceId);
|
|
1133
|
+
const workspaceId = normalizedPayload.workspaceId || resolvedWorkspaceId || null;
|
|
1134
|
+
const translated = translateTrackingAction(normalizedPayload, workspaceId);
|
|
1135
|
+
return { ...translated, normalizedPayload };
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
if (functionName === 'group-management') {
|
|
1139
|
+
const normalizedPayload = normalizeGroupManagementPayload(payload, resolvedWorkspaceId);
|
|
1140
|
+
const workspaceId = normalizedPayload.workspaceId || resolvedWorkspaceId || null;
|
|
1141
|
+
const translated = translateGroupManagementAction(normalizedPayload, workspaceId, method);
|
|
1142
|
+
return { ...translated, normalizedPayload };
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
if (functionName === 'brand-group-management') {
|
|
1146
|
+
const normalizedPayload = normalizeBrandGroupPayload(payload, resolvedWorkspaceId);
|
|
1147
|
+
const workspaceId = normalizedPayload.workspaceId || resolvedWorkspaceId || null;
|
|
1148
|
+
const translated = translateBrandGroupAction(normalizedPayload, workspaceId);
|
|
1149
|
+
return { ...translated, normalizedPayload };
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
if (functionName === 'enqueue-brand-metrics-backfill') {
|
|
1153
|
+
const normalizedPayload = normalizeBackfillPayload(payload, resolvedWorkspaceId);
|
|
1154
|
+
if (!normalizedPayload.brand_id && hasOwn(payload, 'group_id')) {
|
|
1155
|
+
throw new CliError('enqueue-brand-metrics-backfill expects brand_id, not group_id.', {
|
|
1156
|
+
code: 'INVALID_ARGUMENT',
|
|
1157
|
+
exitCode: EXIT_CODES.USAGE,
|
|
1158
|
+
hint: 'Use group-management refresh for tracking groups. Backfill jobs refresh brand metrics for a workspace brand.',
|
|
1159
|
+
});
|
|
1160
|
+
}
|
|
1161
|
+
return {
|
|
1162
|
+
method,
|
|
1163
|
+
pathSuffix: '',
|
|
1164
|
+
body: normalizedPayload,
|
|
1165
|
+
workspaceId: resolvedWorkspaceId,
|
|
1166
|
+
normalizedPayload,
|
|
1167
|
+
};
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
if (functionName === 'export_tracking_data') {
|
|
1171
|
+
const normalizedPayload = normalizeTrackingExportPayload(payload, resolvedWorkspaceId);
|
|
1172
|
+
return {
|
|
1173
|
+
method,
|
|
1174
|
+
pathSuffix: '',
|
|
1175
|
+
body: normalizedPayload,
|
|
1176
|
+
workspaceId: normalizedPayload.workspace_id || resolvedWorkspaceId || null,
|
|
1177
|
+
normalizedPayload,
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
return {
|
|
1182
|
+
method,
|
|
1183
|
+
pathSuffix: '',
|
|
1184
|
+
body: payload,
|
|
1185
|
+
workspaceId: resolvedWorkspaceId,
|
|
1186
|
+
normalizedPayload: payload,
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
|
|
293
1190
|
function isJsonObject(value) {
|
|
294
1191
|
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
295
1192
|
}
|
|
296
1193
|
|
|
1194
|
+
function isStateChangingMethod(method) {
|
|
1195
|
+
return !['GET', 'HEAD', 'OPTIONS'].includes(method);
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
function emitWorkspaceContext(opts, { workspaceId, source, functionName, method }) {
|
|
1199
|
+
if (!workspaceId || !isStateChangingMethod(method)) return;
|
|
1200
|
+
process.stderr.write(
|
|
1201
|
+
`[socialseal] Workspace: ${workspaceId}${source ? ` (${source})` : ''} for ${functionName} ${method}\n`,
|
|
1202
|
+
);
|
|
1203
|
+
}
|
|
1204
|
+
|
|
297
1205
|
function sleep(ms) {
|
|
298
1206
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
299
1207
|
}
|
|
@@ -431,6 +1339,10 @@ function mapStatusToExitCode(status) {
|
|
|
431
1339
|
return EXIT_CODES.UNKNOWN;
|
|
432
1340
|
}
|
|
433
1341
|
|
|
1342
|
+
function isLocallyDisabledByDefaultFunction(functionName) {
|
|
1343
|
+
return functionName === 'group-management' || functionName === 'export_tracking_data';
|
|
1344
|
+
}
|
|
1345
|
+
|
|
434
1346
|
function buildStatusHint(status, context = {}) {
|
|
435
1347
|
switch (status) {
|
|
436
1348
|
case 401:
|
|
@@ -438,13 +1350,16 @@ function buildStatusHint(status, context = {}) {
|
|
|
438
1350
|
return 'Check your CLI key and workspace access.';
|
|
439
1351
|
case 404:
|
|
440
1352
|
if (context.functionName) {
|
|
1353
|
+
if (isLocallyDisabledByDefaultFunction(context.functionName)) {
|
|
1354
|
+
return `Unknown function "${context.functionName}". This tool is listed in the static registry, but it is disabled by default in some local Supabase environments. Check the deployment or enable it in supabase/config.toml.`;
|
|
1355
|
+
}
|
|
441
1356
|
return `Unknown function "${context.functionName}". Double-check the name and API base.`;
|
|
442
1357
|
}
|
|
443
1358
|
return 'Check the API base URL and endpoint path.';
|
|
444
1359
|
case 405:
|
|
445
1360
|
return `Method not allowed. Try --method GET or ensure the endpoint supports ${context.method || 'this method'}.`;
|
|
446
1361
|
case 422:
|
|
447
|
-
return 'Validation error. Review the JSON payload schema.';
|
|
1362
|
+
return 'Validation error. Review the JSON payload schema. For tracking/group tools, prefer the CLI action aliases or the documented REST semantics.';
|
|
448
1363
|
default:
|
|
449
1364
|
return null;
|
|
450
1365
|
}
|
|
@@ -991,19 +1906,38 @@ async function handleToolsCall(opts) {
|
|
|
991
1906
|
const supabaseUrl = resolveLegacyUrl(resolveSupabaseUrl(opts, config), 'SOCIALSEAL_SUPABASE_URL');
|
|
992
1907
|
const { resolvedApiBase, legacyUrl, useGateway } = resolveApiTarget({ apiBase, legacyUrl: supabaseUrl });
|
|
993
1908
|
const timeoutMs = resolveTimeoutMs(opts, config);
|
|
994
|
-
const { workspaceId: resolvedWorkspaceId } = resolveWorkspaceSelection(opts, config);
|
|
1909
|
+
const { workspaceId: resolvedWorkspaceId, source: workspaceSource } = resolveWorkspaceSelection(opts, config);
|
|
995
1910
|
|
|
996
1911
|
const parsedPayload = parseJsonInput(opts.body, { label: 'body' }) ?? {};
|
|
997
1912
|
const mergedPayload = mergeWorkspaceIdIntoPayload(parsedPayload, resolvedWorkspaceId);
|
|
998
|
-
const
|
|
999
|
-
const payload = applySearchJourneyRunAsyncStart(mergedPayload, { ...opts, method });
|
|
1913
|
+
const requestedMethod = normalizeMethod(opts.method);
|
|
1914
|
+
const payload = applySearchJourneyRunAsyncStart(mergedPayload, { ...opts, method: requestedMethod });
|
|
1915
|
+
const translated = translateToolInvocation({
|
|
1916
|
+
functionName: opts.function,
|
|
1917
|
+
method: requestedMethod,
|
|
1918
|
+
payload,
|
|
1919
|
+
resolvedWorkspaceId,
|
|
1920
|
+
});
|
|
1921
|
+
const method = normalizeMethod(translated.method);
|
|
1922
|
+
const effectiveWorkspaceId = translated.workspaceId ?? resolvedWorkspaceId ?? null;
|
|
1923
|
+
const path = useGateway
|
|
1924
|
+
? `/cli/tools/${opts.function}${translated.pathSuffix || ''}`
|
|
1925
|
+
: `/functions/v1/${opts.function}${translated.pathSuffix || ''}`;
|
|
1926
|
+
|
|
1927
|
+
emitWorkspaceContext(opts, {
|
|
1928
|
+
workspaceId: effectiveWorkspaceId,
|
|
1929
|
+
source: effectiveWorkspaceId === resolvedWorkspaceId ? workspaceSource : 'body',
|
|
1930
|
+
functionName: opts.function,
|
|
1931
|
+
method,
|
|
1932
|
+
});
|
|
1933
|
+
|
|
1000
1934
|
const res = await callApi({
|
|
1001
1935
|
apiBase: useGateway ? resolvedApiBase : legacyUrl,
|
|
1002
1936
|
apiKey,
|
|
1003
|
-
path
|
|
1937
|
+
path,
|
|
1004
1938
|
method,
|
|
1005
|
-
body:
|
|
1006
|
-
workspaceId:
|
|
1939
|
+
body: translated.body,
|
|
1940
|
+
workspaceId: effectiveWorkspaceId,
|
|
1007
1941
|
timeoutMs,
|
|
1008
1942
|
});
|
|
1009
1943
|
|
|
@@ -1068,7 +2002,7 @@ function handleToolsList(opts) {
|
|
|
1068
2002
|
const payload = {
|
|
1069
2003
|
discovery: 'built_in_registry',
|
|
1070
2004
|
tools: KNOWN_TOOLS,
|
|
1071
|
-
note:
|
|
2005
|
+
note: STATIC_TOOL_REGISTRY_NOTE,
|
|
1072
2006
|
};
|
|
1073
2007
|
|
|
1074
2008
|
if (opts.json) {
|
|
@@ -1085,7 +2019,16 @@ function handleToolsList(opts) {
|
|
|
1085
2019
|
currentCategory = tool.category;
|
|
1086
2020
|
process.stdout.write(`\n${currentCategory}\n`);
|
|
1087
2021
|
}
|
|
1088
|
-
|
|
2022
|
+
const qualifiers = [
|
|
2023
|
+
tool.objectType ? `object=${tool.objectType}` : null,
|
|
2024
|
+
tool.transport ? `transport=${tool.transport}` : null,
|
|
2025
|
+
tool.knownLocalDevState ? `local=${tool.knownLocalDevState}` : null,
|
|
2026
|
+
].filter(Boolean);
|
|
2027
|
+
const qualifierText = qualifiers.length > 0 ? ` [${qualifiers.join(', ')}]` : '';
|
|
2028
|
+
process.stdout.write(`- ${tool.name}${qualifierText}: ${tool.description}\n`);
|
|
2029
|
+
if (tool.notes) {
|
|
2030
|
+
process.stdout.write(` note: ${tool.notes}\n`);
|
|
2031
|
+
}
|
|
1089
2032
|
}
|
|
1090
2033
|
|
|
1091
2034
|
process.stdout.write('\n[socialseal] Call a tool with: socialseal tools call --function <name> --body @payload.json\n');
|
|
@@ -1107,6 +2050,13 @@ async function handleDataExportTracking(opts) {
|
|
|
1107
2050
|
});
|
|
1108
2051
|
}
|
|
1109
2052
|
|
|
2053
|
+
if (opts.groupId !== undefined) {
|
|
2054
|
+
opts.groupId = coercePositiveInteger(opts.groupId, 'group_id');
|
|
2055
|
+
}
|
|
2056
|
+
if (opts.itemId !== undefined) {
|
|
2057
|
+
opts.itemId = coercePositiveInteger(opts.itemId, 'tracking_item_id');
|
|
2058
|
+
}
|
|
2059
|
+
|
|
1110
2060
|
const payload = {
|
|
1111
2061
|
tracking_item_id: opts.itemId || undefined,
|
|
1112
2062
|
group_id: opts.groupId || undefined,
|