@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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@socialseal/cli",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "SocialSeal CLI (non-interactive)",
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
- { name: 'brand-group-management', category: 'brand', description: 'Manage brand groups, aliases, competitors, and rule configuration.' },
33
- { name: 'enqueue-brand-metrics-backfill', category: 'brand', description: 'Queue backfill jobs for brand metrics refreshes.' },
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
- { name: 'export_tracking_data', category: 'export', description: 'Stream tracking exports as CSV for a group or tracking item.' },
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
- { name: 'group-management', category: 'tracking', description: 'Create, update, list, and delete tracking groups and memberships.' },
43
- { name: 'tracking', category: 'tracking', description: 'Create, update, list, refresh, and delete tracking items.' },
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 method = normalizeMethod(opts.method);
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: useGateway ? `/cli/tools/${opts.function}` : `/functions/v1/${opts.function}`,
1937
+ path,
1004
1938
  method,
1005
- body: payload,
1006
- workspaceId: resolvedWorkspaceId,
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: 'This registry is shipped with the CLI for stable discovery. It is not live backend enumeration.',
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
- process.stdout.write(`- ${tool.name}: ${tool.description}\n`);
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,