@socialseal/cli 0.1.11 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/package.json +1 -1
- package/src/index.js +814 -7
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.1.12 - 2026-06-23
|
|
6
|
+
|
|
7
|
+
- Expose Asset Studio and video production tool surfaces through CLI discovery/schema output.
|
|
8
|
+
- Add category-filtered tool discovery for users and agents.
|
|
9
|
+
|
|
5
10
|
## 0.1.11 - 2026-06-12
|
|
6
11
|
|
|
7
12
|
- Add ad hoc public video URL analysis parity for queue and extract workflows, including `--url` and `--allow-untracked` support.
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander';
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
3
4
|
import fs from 'node:fs';
|
|
4
5
|
import os from 'node:os';
|
|
5
6
|
import path from 'node:path';
|
|
@@ -8,6 +9,7 @@ import WebSocket from 'ws';
|
|
|
8
9
|
|
|
9
10
|
const DEFAULT_CONFIG_PATH = path.join(os.homedir(), '.config', 'socialseal', 'config.json');
|
|
10
11
|
const DEFAULT_API_BASE = 'https://api.socialseal.co';
|
|
12
|
+
const DEFAULT_WEB_BASE = 'https://app.socialseal.co';
|
|
11
13
|
const CLI_KEY_HEADER = 'X-CLI-Key';
|
|
12
14
|
const WORKSPACE_HEADER = 'X-Workspace-Id';
|
|
13
15
|
const DEFAULT_TIMEOUT_MS = 300000;
|
|
@@ -146,6 +148,94 @@ const KNOWN_TOOLS = [
|
|
|
146
148
|
knownLocalDevState: 'enabled',
|
|
147
149
|
notes: 'Accepts videoId/videoUid/platformVideoId/searchResultId items and public URL items with allowUntracked=true; videoId means video_uid or platform-native video id, not a tracking item id.',
|
|
148
150
|
},
|
|
151
|
+
{
|
|
152
|
+
name: 'vnext-clips-read',
|
|
153
|
+
category: 'asset-studio',
|
|
154
|
+
description: 'List workspace clip-library items and optionally sign selected source videos.',
|
|
155
|
+
objectType: 'workspace_clip',
|
|
156
|
+
transport: 'post_edge_function',
|
|
157
|
+
workspaceScoped: true,
|
|
158
|
+
knownLocalDevState: 'enabled',
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
name: 'vnext-clips-create',
|
|
162
|
+
category: 'asset-studio',
|
|
163
|
+
description: 'Create signed clip upload targets and finalize uploaded clip metadata.',
|
|
164
|
+
objectType: 'workspace_clip',
|
|
165
|
+
transport: 'post_edge_function',
|
|
166
|
+
workspaceScoped: true,
|
|
167
|
+
knownLocalDevState: 'enabled',
|
|
168
|
+
actionAliases: ['create', 'finalize'],
|
|
169
|
+
notes: 'The create action returns signed upload URLs; upload bytes to storage before calling finalize.',
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
name: 'vnext-clip-shot-mappings-read',
|
|
173
|
+
category: 'asset-studio',
|
|
174
|
+
description: 'Read clip-to-blueprint shot mappings for Asset Studio.',
|
|
175
|
+
objectType: 'clip_shot_mapping',
|
|
176
|
+
transport: 'post_edge_function',
|
|
177
|
+
workspaceScoped: true,
|
|
178
|
+
knownLocalDevState: 'enabled',
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
name: 'vnext-clip-shot-mappings-write',
|
|
182
|
+
category: 'asset-studio',
|
|
183
|
+
description: 'Upsert or delete clip-to-blueprint shot mappings for Asset Studio.',
|
|
184
|
+
objectType: 'clip_shot_mapping',
|
|
185
|
+
transport: 'post_edge_function',
|
|
186
|
+
workspaceScoped: true,
|
|
187
|
+
knownLocalDevState: 'enabled',
|
|
188
|
+
actionAliases: ['upsert', 'delete'],
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
name: 'vnext-generated-assets-read',
|
|
192
|
+
category: 'asset-studio',
|
|
193
|
+
description: 'List generated rough cuts for a blueprint or read one generated asset.',
|
|
194
|
+
objectType: 'generated_asset',
|
|
195
|
+
transport: 'post_edge_function',
|
|
196
|
+
workspaceScoped: true,
|
|
197
|
+
knownLocalDevState: 'enabled',
|
|
198
|
+
actionAliases: ['list', 'detail'],
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
name: 'vnext-generated-asset-create',
|
|
202
|
+
category: 'asset-studio',
|
|
203
|
+
description: 'Create a generated rough-cut asset from an edit spec.',
|
|
204
|
+
objectType: 'generated_asset',
|
|
205
|
+
transport: 'post_edge_function',
|
|
206
|
+
workspaceScoped: true,
|
|
207
|
+
knownLocalDevState: 'enabled',
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
name: 'vnext-generated-asset-optimize',
|
|
211
|
+
category: 'asset-studio',
|
|
212
|
+
description: 'Optimize a generated asset or create a new revision.',
|
|
213
|
+
objectType: 'generated_asset_revision',
|
|
214
|
+
transport: 'post_edge_function',
|
|
215
|
+
workspaceScoped: true,
|
|
216
|
+
knownLocalDevState: 'enabled',
|
|
217
|
+
actionAliases: ['optimize', 'create-revision'],
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
name: 'vnext-generated-asset-export',
|
|
221
|
+
category: 'asset-studio',
|
|
222
|
+
description: 'Export a generated rough cut as FCPXML.',
|
|
223
|
+
objectType: 'generated_asset_export',
|
|
224
|
+
transport: 'post_edge_function',
|
|
225
|
+
workspaceScoped: true,
|
|
226
|
+
knownLocalDevState: 'enabled',
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
name: 'vnext-generated-asset-share',
|
|
230
|
+
category: 'asset-studio',
|
|
231
|
+
description: 'Create, read, or revoke generated-asset share links.',
|
|
232
|
+
objectType: 'generated_asset_share',
|
|
233
|
+
transport: 'post_edge_function',
|
|
234
|
+
workspaceScoped: false,
|
|
235
|
+
knownLocalDevState: 'enabled',
|
|
236
|
+
actionAliases: ['create', 'read', 'revoke'],
|
|
237
|
+
notes: 'create/revoke require workspaceId in the body; read uses shareToken and does not require workspace scope.',
|
|
238
|
+
},
|
|
149
239
|
{ name: 'douyin-geo-api', category: 'search', description: 'Query Douyin search and geo data.' },
|
|
150
240
|
{
|
|
151
241
|
name: 'google-ai-search',
|
|
@@ -218,7 +308,34 @@ const KNOWN_TOOLS = [
|
|
|
218
308
|
{ name: 'vnext-blueprints-create', category: 'vnext', description: 'Create a vNext blueprint from grounded evidence.' },
|
|
219
309
|
{ name: 'vnext-blueprints-generate', category: 'vnext', description: 'Generate a vNext blueprint from workspace opportunity data.' },
|
|
220
310
|
{ name: 'vnext-blueprints-read', category: 'vnext', description: 'Read vNext blueprint history and specific versions.' },
|
|
311
|
+
{
|
|
312
|
+
name: 'vnext-blueprints-shots-read',
|
|
313
|
+
category: 'video-production',
|
|
314
|
+
description: 'Read shot-lift and pinned shot assets for a blueprint.',
|
|
315
|
+
objectType: 'blueprint_shot_asset',
|
|
316
|
+
transport: 'post_edge_function',
|
|
317
|
+
workspaceScoped: true,
|
|
318
|
+
knownLocalDevState: 'enabled',
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
name: 'vnext-blueprints-shots-refresh',
|
|
322
|
+
category: 'video-production',
|
|
323
|
+
description: 'Queue a refresh for blueprint shot assets.',
|
|
324
|
+
objectType: 'blueprint_shots_job',
|
|
325
|
+
transport: 'post_edge_function',
|
|
326
|
+
workspaceScoped: true,
|
|
327
|
+
knownLocalDevState: 'enabled',
|
|
328
|
+
},
|
|
221
329
|
{ name: 'vnext-briefs-create', category: 'vnext', description: 'Create a vNext brief record.' },
|
|
330
|
+
{
|
|
331
|
+
name: 'vnext-briefs-export',
|
|
332
|
+
category: 'video-production',
|
|
333
|
+
description: 'Export a generated vNext brief as markdown.',
|
|
334
|
+
objectType: 'vnext_brief_export',
|
|
335
|
+
transport: 'post_edge_function',
|
|
336
|
+
workspaceScoped: true,
|
|
337
|
+
knownLocalDevState: 'enabled',
|
|
338
|
+
},
|
|
222
339
|
{ name: 'vnext-briefs-generate', category: 'vnext', description: 'Generate a vNext brief from a blueprint or opportunity.' },
|
|
223
340
|
{ name: 'vnext-briefs-read', category: 'vnext', description: 'Read generated vNext briefs and version history.' },
|
|
224
341
|
{ name: 'vnext-intents', category: 'vnext', description: 'List, create, update, or delete vNext intents.' },
|
|
@@ -451,6 +568,311 @@ const TOOL_SCHEMA_HINTS = {
|
|
|
451
568
|
'socialseal tools call --function tracked-video-extract --workspace-id <workspace-uuid> --body \'{"allowUntracked":true,"items":[{"url":"https://www.instagram.com/reel/SHORTCODE/"}]}\'',
|
|
452
569
|
],
|
|
453
570
|
},
|
|
571
|
+
'vnext-clips-read': {
|
|
572
|
+
summary: 'List Asset Studio clip-library items and optionally sign selected video URLs.',
|
|
573
|
+
operations: [
|
|
574
|
+
{
|
|
575
|
+
action: 'list',
|
|
576
|
+
required: ['workspaceId or --workspace-id'],
|
|
577
|
+
optional: ['videoClipIds[] to include signed source video URLs'],
|
|
578
|
+
example: {
|
|
579
|
+
workspaceId: '00000000-0000-4000-8000-000000000000',
|
|
580
|
+
videoClipIds: ['11111111-1111-4111-8111-111111111111'],
|
|
581
|
+
},
|
|
582
|
+
},
|
|
583
|
+
],
|
|
584
|
+
cliExamples: [
|
|
585
|
+
'socialseal tools call --function vnext-clips-read --workspace-id <workspace-uuid> --body \'{"videoClipIds":["<clip-uuid>"]}\'',
|
|
586
|
+
],
|
|
587
|
+
},
|
|
588
|
+
'vnext-clips-create': {
|
|
589
|
+
summary: 'Create signed upload targets for clips and finalize uploaded clip metadata.',
|
|
590
|
+
operations: [
|
|
591
|
+
{
|
|
592
|
+
action: 'create',
|
|
593
|
+
required: ['action=create', 'workspaceId or --workspace-id', 'fileName', 'mimeType'],
|
|
594
|
+
optional: [],
|
|
595
|
+
example: {
|
|
596
|
+
action: 'create',
|
|
597
|
+
workspaceId: '00000000-0000-4000-8000-000000000000',
|
|
598
|
+
fileName: 'hero-shot.mp4',
|
|
599
|
+
mimeType: 'video/mp4',
|
|
600
|
+
},
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
action: 'finalize',
|
|
604
|
+
required: ['action=finalize', 'workspaceId or --workspace-id', 'clipId', 'fileName', 'storagePath', 'mimeType', 'sizeBytes', 'rightsAttested=true'],
|
|
605
|
+
optional: ['durationSeconds', 'width', 'height', 'posterPath'],
|
|
606
|
+
example: {
|
|
607
|
+
action: 'finalize',
|
|
608
|
+
workspaceId: '00000000-0000-4000-8000-000000000000',
|
|
609
|
+
clipId: '11111111-1111-4111-8111-111111111111',
|
|
610
|
+
fileName: 'hero-shot.mp4',
|
|
611
|
+
storagePath: 'workspace-00000000-0000-4000-8000-000000000000/11111111-1111-4111-8111-111111111111.mp4',
|
|
612
|
+
mimeType: 'video/mp4',
|
|
613
|
+
sizeBytes: 1048576,
|
|
614
|
+
rightsAttested: true,
|
|
615
|
+
},
|
|
616
|
+
},
|
|
617
|
+
],
|
|
618
|
+
cliExamples: [
|
|
619
|
+
'socialseal tools call --function vnext-clips-create --workspace-id <workspace-uuid> --body \'{"action":"create","fileName":"hero-shot.mp4","mimeType":"video/mp4"}\'',
|
|
620
|
+
'socialseal tools call --function vnext-clips-create --workspace-id <workspace-uuid> --body @clip-finalize.json',
|
|
621
|
+
],
|
|
622
|
+
},
|
|
623
|
+
'vnext-clip-shot-mappings-read': {
|
|
624
|
+
summary: 'Read Asset Studio clip-to-shot mappings for a blueprint.',
|
|
625
|
+
operations: [
|
|
626
|
+
{
|
|
627
|
+
action: 'read',
|
|
628
|
+
required: ['workspaceId or --workspace-id', 'blueprintId'],
|
|
629
|
+
optional: [],
|
|
630
|
+
example: {
|
|
631
|
+
workspaceId: '00000000-0000-4000-8000-000000000000',
|
|
632
|
+
blueprintId: '22222222-2222-4222-8222-222222222222',
|
|
633
|
+
},
|
|
634
|
+
},
|
|
635
|
+
],
|
|
636
|
+
cliExamples: [
|
|
637
|
+
'socialseal tools call --function vnext-clip-shot-mappings-read --workspace-id <workspace-uuid> --body \'{"blueprintId":"<blueprint-uuid>"}\'',
|
|
638
|
+
],
|
|
639
|
+
},
|
|
640
|
+
'vnext-clip-shot-mappings-write': {
|
|
641
|
+
summary: 'Upsert or delete Asset Studio clip-to-shot mappings.',
|
|
642
|
+
operations: [
|
|
643
|
+
{
|
|
644
|
+
action: 'upsert',
|
|
645
|
+
required: ['action=upsert', 'workspaceId or --workspace-id', 'blueprintId', 'panelId', 'clipId'],
|
|
646
|
+
optional: ['source (suggested|override)', 'score'],
|
|
647
|
+
example: {
|
|
648
|
+
action: 'upsert',
|
|
649
|
+
workspaceId: '00000000-0000-4000-8000-000000000000',
|
|
650
|
+
blueprintId: '22222222-2222-4222-8222-222222222222',
|
|
651
|
+
panelId: 'panel-1',
|
|
652
|
+
clipId: '11111111-1111-4111-8111-111111111111',
|
|
653
|
+
source: 'override',
|
|
654
|
+
},
|
|
655
|
+
},
|
|
656
|
+
{
|
|
657
|
+
action: 'delete',
|
|
658
|
+
required: ['action=delete', 'workspaceId or --workspace-id', 'blueprintId', 'panelId'],
|
|
659
|
+
optional: [],
|
|
660
|
+
example: {
|
|
661
|
+
action: 'delete',
|
|
662
|
+
workspaceId: '00000000-0000-4000-8000-000000000000',
|
|
663
|
+
blueprintId: '22222222-2222-4222-8222-222222222222',
|
|
664
|
+
panelId: 'panel-1',
|
|
665
|
+
},
|
|
666
|
+
},
|
|
667
|
+
],
|
|
668
|
+
cliExamples: [
|
|
669
|
+
'socialseal tools call --function vnext-clip-shot-mappings-write --workspace-id <workspace-uuid> --body \'{"action":"upsert","blueprintId":"<blueprint-uuid>","panelId":"panel-1","clipId":"<clip-uuid>"}\'',
|
|
670
|
+
],
|
|
671
|
+
},
|
|
672
|
+
'vnext-generated-assets-read': {
|
|
673
|
+
summary: 'List generated rough cuts for a blueprint or read one generated asset.',
|
|
674
|
+
operations: [
|
|
675
|
+
{
|
|
676
|
+
action: 'list',
|
|
677
|
+
required: ['action=list', 'workspaceId or --workspace-id', 'blueprintId'],
|
|
678
|
+
optional: [],
|
|
679
|
+
example: {
|
|
680
|
+
action: 'list',
|
|
681
|
+
workspaceId: '00000000-0000-4000-8000-000000000000',
|
|
682
|
+
blueprintId: '22222222-2222-4222-8222-222222222222',
|
|
683
|
+
},
|
|
684
|
+
},
|
|
685
|
+
{
|
|
686
|
+
action: 'detail',
|
|
687
|
+
required: ['action=detail', 'workspaceId or --workspace-id', 'assetId'],
|
|
688
|
+
optional: [],
|
|
689
|
+
example: {
|
|
690
|
+
action: 'detail',
|
|
691
|
+
workspaceId: '00000000-0000-4000-8000-000000000000',
|
|
692
|
+
assetId: '33333333-3333-4333-8333-333333333333',
|
|
693
|
+
},
|
|
694
|
+
},
|
|
695
|
+
],
|
|
696
|
+
cliExamples: [
|
|
697
|
+
'socialseal tools call --function vnext-generated-assets-read --workspace-id <workspace-uuid> --body \'{"action":"list","blueprintId":"<blueprint-uuid>"}\'',
|
|
698
|
+
'socialseal tools call --function vnext-generated-assets-read --workspace-id <workspace-uuid> --body \'{"action":"detail","assetId":"<asset-uuid>"}\'',
|
|
699
|
+
],
|
|
700
|
+
},
|
|
701
|
+
'vnext-generated-asset-create': {
|
|
702
|
+
summary: 'Create a generated rough cut from an Asset Studio edit spec.',
|
|
703
|
+
operations: [
|
|
704
|
+
{
|
|
705
|
+
action: 'create',
|
|
706
|
+
required: ['workspaceId or --workspace-id', 'blueprintId', 'title', 'editSpec'],
|
|
707
|
+
optional: [],
|
|
708
|
+
example: {
|
|
709
|
+
workspaceId: '00000000-0000-4000-8000-000000000000',
|
|
710
|
+
blueprintId: '22222222-2222-4222-8222-222222222222',
|
|
711
|
+
title: 'Homepage rough cut',
|
|
712
|
+
editSpec: {
|
|
713
|
+
version: 1,
|
|
714
|
+
fps: 30,
|
|
715
|
+
width: 1080,
|
|
716
|
+
height: 1920,
|
|
717
|
+
totalDurationSeconds: 3,
|
|
718
|
+
shots: [
|
|
719
|
+
{
|
|
720
|
+
panelId: 'panel-1',
|
|
721
|
+
clipId: '11111111-1111-4111-8111-111111111111',
|
|
722
|
+
title: 'Opening hook',
|
|
723
|
+
kind: 'hook',
|
|
724
|
+
shotLabel: 'Hero exterior',
|
|
725
|
+
sourceStartSeconds: 0,
|
|
726
|
+
durationSeconds: 3,
|
|
727
|
+
evidenceIds: [],
|
|
728
|
+
},
|
|
729
|
+
],
|
|
730
|
+
},
|
|
731
|
+
},
|
|
732
|
+
},
|
|
733
|
+
],
|
|
734
|
+
cliExamples: [
|
|
735
|
+
'socialseal tools call --function vnext-generated-asset-create --workspace-id <workspace-uuid> --body @edit-spec.json',
|
|
736
|
+
],
|
|
737
|
+
},
|
|
738
|
+
'vnext-generated-asset-optimize': {
|
|
739
|
+
summary: 'Optimize a generated asset or create a new revision.',
|
|
740
|
+
operations: [
|
|
741
|
+
{
|
|
742
|
+
action: 'optimize',
|
|
743
|
+
required: ['action=optimize', 'workspaceId or --workspace-id', 'assetId'],
|
|
744
|
+
optional: [],
|
|
745
|
+
example: {
|
|
746
|
+
action: 'optimize',
|
|
747
|
+
workspaceId: '00000000-0000-4000-8000-000000000000',
|
|
748
|
+
assetId: '33333333-3333-4333-8333-333333333333',
|
|
749
|
+
},
|
|
750
|
+
},
|
|
751
|
+
{
|
|
752
|
+
action: 'create-revision',
|
|
753
|
+
required: ['action=create-revision', 'workspaceId or --workspace-id', 'assetId'],
|
|
754
|
+
optional: [],
|
|
755
|
+
example: {
|
|
756
|
+
action: 'create-revision',
|
|
757
|
+
workspaceId: '00000000-0000-4000-8000-000000000000',
|
|
758
|
+
assetId: '33333333-3333-4333-8333-333333333333',
|
|
759
|
+
},
|
|
760
|
+
},
|
|
761
|
+
],
|
|
762
|
+
cliExamples: [
|
|
763
|
+
'socialseal tools call --function vnext-generated-asset-optimize --workspace-id <workspace-uuid> --body \'{"action":"optimize","assetId":"<asset-uuid>"}\'',
|
|
764
|
+
],
|
|
765
|
+
},
|
|
766
|
+
'vnext-generated-asset-export': {
|
|
767
|
+
summary: 'Export a generated rough cut as FCPXML.',
|
|
768
|
+
operations: [
|
|
769
|
+
{
|
|
770
|
+
action: 'export',
|
|
771
|
+
required: ['workspaceId or --workspace-id', 'assetId'],
|
|
772
|
+
optional: ['format=fcpxml'],
|
|
773
|
+
example: {
|
|
774
|
+
workspaceId: '00000000-0000-4000-8000-000000000000',
|
|
775
|
+
assetId: '33333333-3333-4333-8333-333333333333',
|
|
776
|
+
format: 'fcpxml',
|
|
777
|
+
},
|
|
778
|
+
},
|
|
779
|
+
],
|
|
780
|
+
cliExamples: [
|
|
781
|
+
'socialseal tools call --function vnext-generated-asset-export --workspace-id <workspace-uuid> --body \'{"assetId":"<asset-uuid>","format":"fcpxml"}\'',
|
|
782
|
+
],
|
|
783
|
+
},
|
|
784
|
+
'vnext-generated-asset-share': {
|
|
785
|
+
summary: 'Create, read, or revoke generated rough-cut share links.',
|
|
786
|
+
operations: [
|
|
787
|
+
{
|
|
788
|
+
action: 'create',
|
|
789
|
+
required: ['action=create', 'workspaceId', 'assetId'],
|
|
790
|
+
optional: ['ttlSeconds', 'shareBaseUrl'],
|
|
791
|
+
example: {
|
|
792
|
+
action: 'create',
|
|
793
|
+
workspaceId: '00000000-0000-4000-8000-000000000000',
|
|
794
|
+
assetId: '33333333-3333-4333-8333-333333333333',
|
|
795
|
+
ttlSeconds: 604800,
|
|
796
|
+
},
|
|
797
|
+
},
|
|
798
|
+
{
|
|
799
|
+
action: 'read',
|
|
800
|
+
required: ['action=read', 'shareToken'],
|
|
801
|
+
optional: [],
|
|
802
|
+
example: {
|
|
803
|
+
action: 'read',
|
|
804
|
+
shareToken: '0123456789abcdef0123456789abcdef',
|
|
805
|
+
},
|
|
806
|
+
},
|
|
807
|
+
{
|
|
808
|
+
action: 'revoke',
|
|
809
|
+
required: ['action=revoke', 'workspaceId', 'shareLinkId'],
|
|
810
|
+
optional: [],
|
|
811
|
+
example: {
|
|
812
|
+
action: 'revoke',
|
|
813
|
+
workspaceId: '00000000-0000-4000-8000-000000000000',
|
|
814
|
+
shareLinkId: '44444444-4444-4444-8444-444444444444',
|
|
815
|
+
},
|
|
816
|
+
},
|
|
817
|
+
],
|
|
818
|
+
cliExamples: [
|
|
819
|
+
'socialseal tools call --function vnext-generated-asset-share --body \'{"action":"create","workspaceId":"<workspace-uuid>","assetId":"<asset-uuid>"}\'',
|
|
820
|
+
'socialseal tools call --function vnext-generated-asset-share --body \'{"action":"read","shareToken":"<share-token>"}\'',
|
|
821
|
+
],
|
|
822
|
+
},
|
|
823
|
+
'vnext-blueprints-shots-read': {
|
|
824
|
+
summary: 'Read blueprint shot-lift rows and pinned shot assets with signed URLs.',
|
|
825
|
+
operations: [
|
|
826
|
+
{
|
|
827
|
+
action: 'read',
|
|
828
|
+
required: ['workspaceId or --workspace-id', 'blueprintId'],
|
|
829
|
+
optional: ['signedUrlSeconds'],
|
|
830
|
+
example: {
|
|
831
|
+
workspaceId: '00000000-0000-4000-8000-000000000000',
|
|
832
|
+
blueprintId: '22222222-2222-4222-8222-222222222222',
|
|
833
|
+
signedUrlSeconds: 3600,
|
|
834
|
+
},
|
|
835
|
+
},
|
|
836
|
+
],
|
|
837
|
+
cliExamples: [
|
|
838
|
+
'socialseal tools call --function vnext-blueprints-shots-read --workspace-id <workspace-uuid> --body \'{"blueprintId":"<blueprint-uuid>"}\'',
|
|
839
|
+
],
|
|
840
|
+
},
|
|
841
|
+
'vnext-blueprints-shots-refresh': {
|
|
842
|
+
summary: 'Queue a refresh for blueprint shot assets.',
|
|
843
|
+
operations: [
|
|
844
|
+
{
|
|
845
|
+
action: 'refresh',
|
|
846
|
+
required: ['workspaceId or --workspace-id', 'blueprintId'],
|
|
847
|
+
optional: [],
|
|
848
|
+
example: {
|
|
849
|
+
workspaceId: '00000000-0000-4000-8000-000000000000',
|
|
850
|
+
blueprintId: '22222222-2222-4222-8222-222222222222',
|
|
851
|
+
},
|
|
852
|
+
},
|
|
853
|
+
],
|
|
854
|
+
cliExamples: [
|
|
855
|
+
'socialseal tools call --function vnext-blueprints-shots-refresh --workspace-id <workspace-uuid> --body \'{"blueprintId":"<blueprint-uuid>"}\'',
|
|
856
|
+
],
|
|
857
|
+
},
|
|
858
|
+
'vnext-briefs-export': {
|
|
859
|
+
summary: 'Export the latest or selected generated vNext brief as markdown.',
|
|
860
|
+
operations: [
|
|
861
|
+
{
|
|
862
|
+
action: 'export',
|
|
863
|
+
required: ['workspaceId or --workspace-id', 'opportunityKey'],
|
|
864
|
+
optional: ['version'],
|
|
865
|
+
example: {
|
|
866
|
+
workspaceId: '00000000-0000-4000-8000-000000000000',
|
|
867
|
+
opportunityKey: 'opportunity-key',
|
|
868
|
+
version: 1,
|
|
869
|
+
},
|
|
870
|
+
},
|
|
871
|
+
],
|
|
872
|
+
cliExamples: [
|
|
873
|
+
'socialseal tools call --function vnext-briefs-export --workspace-id <workspace-uuid> --body \'{"opportunityKey":"<opportunity-key>"}\'',
|
|
874
|
+
],
|
|
875
|
+
},
|
|
454
876
|
'group-management': {
|
|
455
877
|
summary: 'Manage single-platform tracking groups and memberships.',
|
|
456
878
|
operations: [
|
|
@@ -544,6 +966,18 @@ function buildToolRegistry() {
|
|
|
544
966
|
});
|
|
545
967
|
}
|
|
546
968
|
|
|
969
|
+
function filterToolRegistry(tools, category) {
|
|
970
|
+
const normalizedCategory = trimString(category).toLowerCase();
|
|
971
|
+
const filtered = normalizedCategory
|
|
972
|
+
? tools.filter((tool) => trimString(tool.category).toLowerCase() === normalizedCategory)
|
|
973
|
+
: tools;
|
|
974
|
+
return [...filtered].sort((a, b) => {
|
|
975
|
+
const categoryCompare = trimString(a.category).localeCompare(trimString(b.category));
|
|
976
|
+
if (categoryCompare !== 0) return categoryCompare;
|
|
977
|
+
return trimString(a.name).localeCompare(trimString(b.name));
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
|
|
547
981
|
function getConfigPath() {
|
|
548
982
|
return process.env.SOCIALSEAL_CONFIG || DEFAULT_CONFIG_PATH;
|
|
549
983
|
}
|
|
@@ -586,9 +1020,36 @@ function saveConfig(config) {
|
|
|
586
1020
|
Object.entries(config || {}).filter(([, value]) => value !== undefined),
|
|
587
1021
|
);
|
|
588
1022
|
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
589
|
-
fs.writeFileSync(configPath, `${JSON.stringify(normalizedConfig, null, 2)}\n
|
|
1023
|
+
fs.writeFileSync(configPath, `${JSON.stringify(normalizedConfig, null, 2)}\n`, {
|
|
1024
|
+
mode: 0o600,
|
|
1025
|
+
});
|
|
1026
|
+
fs.chmodSync(configPath, 0o600);
|
|
590
1027
|
}
|
|
591
1028
|
|
|
1029
|
+
function assertConfigWritable() {
|
|
1030
|
+
const configPath = getConfigPath();
|
|
1031
|
+
const configDir = path.dirname(configPath);
|
|
1032
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
1033
|
+
const probePath = path.join(configDir, `.socialseal-write-test-${process.pid}-${Date.now()}`);
|
|
1034
|
+
try {
|
|
1035
|
+
fs.writeFileSync(probePath, '', { mode: 0o600 });
|
|
1036
|
+
fs.unlinkSync(probePath);
|
|
1037
|
+
} catch (error) {
|
|
1038
|
+
try {
|
|
1039
|
+
if (fs.existsSync(probePath)) fs.unlinkSync(probePath);
|
|
1040
|
+
} catch {
|
|
1041
|
+
// best effort cleanup only
|
|
1042
|
+
}
|
|
1043
|
+
throw new CliError(`Cannot write SocialSeal config at ${configPath}.`, {
|
|
1044
|
+
code: 'CONFIG_NOT_WRITABLE',
|
|
1045
|
+
exitCode: EXIT_CODES.USAGE,
|
|
1046
|
+
hint: 'Set SOCIALSEAL_CONFIG to a writable path, or set SOCIALSEAL_API_KEY manually.',
|
|
1047
|
+
details: error?.message || String(error),
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
|
|
592
1053
|
function resolveApiKey(opts, config) {
|
|
593
1054
|
return opts.apiKey || process.env.SOCIALSEAL_API_KEY || config.apiKey;
|
|
594
1055
|
}
|
|
@@ -605,6 +1066,10 @@ function resolveSupabaseUrl(opts, config) {
|
|
|
605
1066
|
return opts.supabaseUrl || process.env.SOCIALSEAL_SUPABASE_URL || config.supabaseUrl;
|
|
606
1067
|
}
|
|
607
1068
|
|
|
1069
|
+
function resolveWebBase(opts = {}, config = {}) {
|
|
1070
|
+
return opts.webBase || process.env.SOCIALSEAL_WEB_BASE || config.webBase || DEFAULT_WEB_BASE;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
608
1073
|
function resolveWorkspaceSelection(opts, config) {
|
|
609
1074
|
if (typeof opts.workspaceId === 'string' && opts.workspaceId.trim().length > 0) {
|
|
610
1075
|
return { workspaceId: opts.workspaceId.trim(), source: 'flag' };
|
|
@@ -1164,6 +1629,23 @@ function resolvePayloadWorkspaceId(payload, fallbackWorkspaceId) {
|
|
|
1164
1629
|
return fallbackWorkspaceId || null;
|
|
1165
1630
|
}
|
|
1166
1631
|
|
|
1632
|
+
function isGeneratedAssetShareScopedAction(functionName, payload) {
|
|
1633
|
+
if (functionName !== 'vnext-generated-asset-share' || !isJsonObject(payload)) {
|
|
1634
|
+
return false;
|
|
1635
|
+
}
|
|
1636
|
+
const action = trimString(payload.action).toLowerCase();
|
|
1637
|
+
return action === 'create' || action === 'revoke';
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
function shouldRequireToolWorkspace(functionName, payload) {
|
|
1641
|
+
const tool = getKnownTool(functionName);
|
|
1642
|
+
const category = trimString(tool?.category).toLowerCase();
|
|
1643
|
+
return (
|
|
1644
|
+
Boolean(tool?.workspaceScoped) &&
|
|
1645
|
+
(category === 'asset-studio' || category === 'video-production')
|
|
1646
|
+
) || isGeneratedAssetShareScopedAction(functionName, payload);
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1167
1649
|
function isUuidLike(value) {
|
|
1168
1650
|
return typeof value === 'string' && /^[0-9a-f]{8}-[0-9a-f-]{27}$/i.test(value.trim());
|
|
1169
1651
|
}
|
|
@@ -2700,7 +3182,9 @@ function buildStatusHint(status, context = {}) {
|
|
|
2700
3182
|
switch (status) {
|
|
2701
3183
|
case 401:
|
|
2702
3184
|
case 403:
|
|
2703
|
-
return '
|
|
3185
|
+
return 'Authentication failed. Run `socialseal login`, or check your CLI key and workspace access.';
|
|
3186
|
+
case 402:
|
|
3187
|
+
return 'Your free credits or quota may be exhausted. Run `socialseal billing` to open billing and credits options.';
|
|
2704
3188
|
case 404:
|
|
2705
3189
|
if (context.functionName) {
|
|
2706
3190
|
if (isLocallyDisabledByDefaultFunction(context.functionName)) {
|
|
@@ -2714,6 +3198,9 @@ function buildStatusHint(status, context = {}) {
|
|
|
2714
3198
|
case 422:
|
|
2715
3199
|
return 'Validation error. Review the JSON payload schema. For tracking/group tools, prefer the CLI action aliases or the documented REST semantics.';
|
|
2716
3200
|
default:
|
|
3201
|
+
if (context.billingRelated) {
|
|
3202
|
+
return 'Run `socialseal billing` to open billing and credits options.';
|
|
3203
|
+
}
|
|
2717
3204
|
return null;
|
|
2718
3205
|
}
|
|
2719
3206
|
}
|
|
@@ -2740,7 +3227,9 @@ async function buildHttpError(res, context = {}) {
|
|
|
2740
3227
|
|
|
2741
3228
|
const label = context.label || 'Request';
|
|
2742
3229
|
const statusText = res.statusText ? ` ${res.statusText}` : '';
|
|
2743
|
-
const
|
|
3230
|
+
const serializedDetails = typeof details === 'string' ? details : JSON.stringify(details);
|
|
3231
|
+
const billingRelated = /\b(credit|credits|quota|billing|entitlement|payment|plan)\b/i.test(serializedDetails || '');
|
|
3232
|
+
const hint = context.hint || buildStatusHint(status, { ...context, billingRelated });
|
|
2744
3233
|
|
|
2745
3234
|
return new CliError(`${label} failed: ${status}${statusText}`.trim(), {
|
|
2746
3235
|
code: 'HTTP_ERROR',
|
|
@@ -3224,9 +3713,10 @@ function coerceCliError(err, fallbackMessage = 'Command failed') {
|
|
|
3224
3713
|
function requireApiKey(opts, config) {
|
|
3225
3714
|
const apiKey = resolveApiKey(opts, config);
|
|
3226
3715
|
if (!apiKey) {
|
|
3227
|
-
throw new CliError('Missing API key.
|
|
3716
|
+
throw new CliError('Missing API key. Run `socialseal login` to connect this CLI.', {
|
|
3228
3717
|
code: 'MISSING_API_KEY',
|
|
3229
|
-
exitCode: EXIT_CODES.
|
|
3718
|
+
exitCode: EXIT_CODES.AUTH,
|
|
3719
|
+
hint: 'Run `socialseal login`, or set SOCIALSEAL_API_KEY if you already have a key.',
|
|
3230
3720
|
});
|
|
3231
3721
|
}
|
|
3232
3722
|
return apiKey;
|
|
@@ -3756,6 +4246,26 @@ async function handleToolsCall(opts) {
|
|
|
3756
4246
|
});
|
|
3757
4247
|
}
|
|
3758
4248
|
|
|
4249
|
+
const scopedPayload = isJsonObject(translated.normalizedPayload)
|
|
4250
|
+
? translated.normalizedPayload
|
|
4251
|
+
: (isJsonObject(translated.body) ? translated.body : payload);
|
|
4252
|
+
const hasSpecialWorkspaceHandling = new Set([
|
|
4253
|
+
'group-management',
|
|
4254
|
+
'export_tracking_data',
|
|
4255
|
+
'tracked-video-extract',
|
|
4256
|
+
]).has(opts.function);
|
|
4257
|
+
if (!hasSpecialWorkspaceHandling && shouldRequireToolWorkspace(opts.function, scopedPayload)) {
|
|
4258
|
+
requireWorkspaceSelection(effectiveWorkspaceId, {
|
|
4259
|
+
label: opts.function,
|
|
4260
|
+
hint: 'Pass --workspace-id, set SOCIALSEAL_WORKSPACE_ID, include workspaceId in the body, or configure a default workspace.',
|
|
4261
|
+
});
|
|
4262
|
+
emitWorkspaceSelectionNotice(opts, {
|
|
4263
|
+
workspaceId: effectiveWorkspaceId,
|
|
4264
|
+
source: effectiveWorkspaceSource,
|
|
4265
|
+
label: opts.function,
|
|
4266
|
+
});
|
|
4267
|
+
}
|
|
4268
|
+
|
|
3759
4269
|
emitTrackingCreateScopeWarning(
|
|
3760
4270
|
isJsonObject(translated.normalizedPayload) ? trimString(translated.normalizedPayload.action).toLowerCase() : '',
|
|
3761
4271
|
effectiveWorkspaceId,
|
|
@@ -3854,9 +4364,10 @@ async function handleToolsCall(opts) {
|
|
|
3854
4364
|
}
|
|
3855
4365
|
|
|
3856
4366
|
function handleToolsList(opts) {
|
|
3857
|
-
const tools = buildToolRegistry();
|
|
4367
|
+
const tools = filterToolRegistry(buildToolRegistry(), opts.category);
|
|
3858
4368
|
const payload = {
|
|
3859
4369
|
discovery: 'built_in_registry',
|
|
4370
|
+
category: trimString(opts.category) || null,
|
|
3860
4371
|
tools,
|
|
3861
4372
|
note: STATIC_TOOL_REGISTRY_NOTE,
|
|
3862
4373
|
schemaNote: STATIC_TOOL_SCHEMA_NOTE,
|
|
@@ -3868,6 +4379,9 @@ function handleToolsList(opts) {
|
|
|
3868
4379
|
}
|
|
3869
4380
|
|
|
3870
4381
|
process.stdout.write('[socialseal] Built-in tool registry\n');
|
|
4382
|
+
if (payload.category) {
|
|
4383
|
+
process.stdout.write(`[socialseal] Category filter: ${payload.category}\n`);
|
|
4384
|
+
}
|
|
3871
4385
|
process.stdout.write(`[socialseal] ${payload.note}\n`);
|
|
3872
4386
|
process.stdout.write(`[socialseal] ${payload.schemaNote}\n`);
|
|
3873
4387
|
|
|
@@ -4756,6 +5270,258 @@ async function handleVideoQueueAnalysis(opts) {
|
|
|
4756
5270
|
emitJsonOutput(payload, opts.pretty);
|
|
4757
5271
|
}
|
|
4758
5272
|
|
|
5273
|
+
function maskApiKey(apiKey) {
|
|
5274
|
+
const key = typeof apiKey === 'string' ? apiKey.trim() : '';
|
|
5275
|
+
if (!key) return null;
|
|
5276
|
+
return `…${key.slice(-6)}`;
|
|
5277
|
+
}
|
|
5278
|
+
|
|
5279
|
+
function openBrowser(url, onError) {
|
|
5280
|
+
const platform = process.platform;
|
|
5281
|
+
const command = platform === 'darwin'
|
|
5282
|
+
? 'open'
|
|
5283
|
+
: platform === 'win32'
|
|
5284
|
+
? 'cmd'
|
|
5285
|
+
: 'xdg-open';
|
|
5286
|
+
const args = platform === 'win32' ? ['/c', 'start', '', url] : [url];
|
|
5287
|
+
const child = spawn(command, args, {
|
|
5288
|
+
detached: true,
|
|
5289
|
+
stdio: 'ignore',
|
|
5290
|
+
});
|
|
5291
|
+
child.on('error', (error) => {
|
|
5292
|
+
if (typeof onError === 'function') onError(error);
|
|
5293
|
+
});
|
|
5294
|
+
child.unref();
|
|
5295
|
+
}
|
|
5296
|
+
|
|
5297
|
+
async function callPublicApi({ apiBase, path: requestPath, method = 'POST', body, timeoutMs }) {
|
|
5298
|
+
if (!apiBase) {
|
|
5299
|
+
throw new CliError('Missing API base. Set SOCIALSEAL_API_BASE or --api-base.', {
|
|
5300
|
+
code: 'MISSING_API_BASE',
|
|
5301
|
+
exitCode: EXIT_CODES.USAGE,
|
|
5302
|
+
});
|
|
5303
|
+
}
|
|
5304
|
+
const normalizedMethod = normalizeMethod(method);
|
|
5305
|
+
const url = `${apiBase.replace(/\/$/, '')}${requestPath.startsWith('/') ? requestPath : `/${requestPath}`}`;
|
|
5306
|
+
const hasBody = body !== undefined && normalizedMethod !== 'GET' && normalizedMethod !== 'HEAD';
|
|
5307
|
+
return fetchWithTimeout(url, {
|
|
5308
|
+
method: normalizedMethod,
|
|
5309
|
+
headers: {
|
|
5310
|
+
Accept: 'application/json',
|
|
5311
|
+
...(hasBody ? { 'Content-Type': 'application/json' } : {}),
|
|
5312
|
+
},
|
|
5313
|
+
body: hasBody ? JSON.stringify(body ?? {}) : undefined,
|
|
5314
|
+
}, timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
5315
|
+
}
|
|
5316
|
+
|
|
5317
|
+
async function readJsonResponse(res, label) {
|
|
5318
|
+
const contentType = res.headers.get('content-type') || '';
|
|
5319
|
+
if (!contentType.includes('application/json')) {
|
|
5320
|
+
throw new CliError(`${label} returned a non-JSON response.`, {
|
|
5321
|
+
code: 'INVALID_RESPONSE',
|
|
5322
|
+
exitCode: EXIT_CODES.SERVER,
|
|
5323
|
+
});
|
|
5324
|
+
}
|
|
5325
|
+
return res.json();
|
|
5326
|
+
}
|
|
5327
|
+
|
|
5328
|
+
async function handleLogin(opts) {
|
|
5329
|
+
const config = loadConfig();
|
|
5330
|
+
const apiBase = resolveApiBase(opts, config) || DEFAULT_API_BASE;
|
|
5331
|
+
const timeoutMs = resolveTimeoutMs(opts, config);
|
|
5332
|
+
assertConfigWritable();
|
|
5333
|
+
const authorizeRes = await callPublicApi({
|
|
5334
|
+
apiBase,
|
|
5335
|
+
path: '/cli/device/authorize',
|
|
5336
|
+
body: {
|
|
5337
|
+
clientId: '@socialseal/cli',
|
|
5338
|
+
clientName: 'SocialSeal CLI',
|
|
5339
|
+
scopes: { cli: true },
|
|
5340
|
+
},
|
|
5341
|
+
timeoutMs,
|
|
5342
|
+
});
|
|
5343
|
+
|
|
5344
|
+
if (!authorizeRes.ok) {
|
|
5345
|
+
throw await buildHttpError(authorizeRes, { label: 'Device authorization start' });
|
|
5346
|
+
}
|
|
5347
|
+
|
|
5348
|
+
const authorizePayload = await readJsonResponse(authorizeRes, 'Device authorization start');
|
|
5349
|
+
const verificationUrl = authorizePayload.verification_uri_complete || authorizePayload.verification_uri;
|
|
5350
|
+
const deviceCode = authorizePayload.device_code;
|
|
5351
|
+
const userCode = authorizePayload.user_code;
|
|
5352
|
+
if (!verificationUrl || !deviceCode || !userCode) {
|
|
5353
|
+
throw new CliError('Device authorization start returned an incomplete response.', {
|
|
5354
|
+
code: 'INVALID_RESPONSE',
|
|
5355
|
+
exitCode: EXIT_CODES.SERVER,
|
|
5356
|
+
});
|
|
5357
|
+
}
|
|
5358
|
+
|
|
5359
|
+
if (!opts.json) {
|
|
5360
|
+
process.stdout.write(`[socialseal] Open this URL to approve login: ${verificationUrl}\n`);
|
|
5361
|
+
process.stdout.write(`[socialseal] Confirm code: ${userCode}\n`);
|
|
5362
|
+
}
|
|
5363
|
+
|
|
5364
|
+
if (opts.open !== false) {
|
|
5365
|
+
openBrowser(String(verificationUrl), (error) => {
|
|
5366
|
+
if (opts.verbose) {
|
|
5367
|
+
process.stderr.write(`[socialseal] Could not open browser automatically: ${error.message || error}\n`);
|
|
5368
|
+
}
|
|
5369
|
+
});
|
|
5370
|
+
}
|
|
5371
|
+
|
|
5372
|
+
const startedAt = Date.now();
|
|
5373
|
+
let intervalMs = Math.max(1000, Number(authorizePayload.interval || 5) * 1000);
|
|
5374
|
+
if (opts.pollInterval) {
|
|
5375
|
+
intervalMs = parseTimeoutMs(opts.pollInterval, { defaultValue: intervalMs, label: 'poll interval' });
|
|
5376
|
+
}
|
|
5377
|
+
|
|
5378
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
5379
|
+
await sleep(intervalMs);
|
|
5380
|
+
const tokenRes = await callPublicApi({
|
|
5381
|
+
apiBase,
|
|
5382
|
+
path: '/cli/device/token',
|
|
5383
|
+
body: { device_code: deviceCode },
|
|
5384
|
+
timeoutMs: Math.max(1000, timeoutMs - (Date.now() - startedAt)),
|
|
5385
|
+
});
|
|
5386
|
+
const tokenPayload = await readJsonResponse(tokenRes, 'Device token poll');
|
|
5387
|
+
|
|
5388
|
+
if (tokenRes.ok) {
|
|
5389
|
+
const apiKey = typeof tokenPayload.api_key === 'string' ? tokenPayload.api_key : '';
|
|
5390
|
+
if (!apiKey) {
|
|
5391
|
+
throw new CliError('Device token poll returned no API key.', {
|
|
5392
|
+
code: 'INVALID_RESPONSE',
|
|
5393
|
+
exitCode: EXIT_CODES.SERVER,
|
|
5394
|
+
});
|
|
5395
|
+
}
|
|
5396
|
+
|
|
5397
|
+
const workspaceId = typeof tokenPayload.workspace_id === 'string' ? tokenPayload.workspace_id : config.workspaceId;
|
|
5398
|
+
saveConfig({
|
|
5399
|
+
...config,
|
|
5400
|
+
apiBase,
|
|
5401
|
+
apiKey,
|
|
5402
|
+
workspaceId,
|
|
5403
|
+
});
|
|
5404
|
+
|
|
5405
|
+
const payload = {
|
|
5406
|
+
success: true,
|
|
5407
|
+
apiBase,
|
|
5408
|
+
keySuffix: apiKey.slice(-6),
|
|
5409
|
+
key: maskApiKey(apiKey),
|
|
5410
|
+
workspaceId: workspaceId || null,
|
|
5411
|
+
configPath: getConfigPath(),
|
|
5412
|
+
};
|
|
5413
|
+
if (opts.json) {
|
|
5414
|
+
process.stdout.write(JSON.stringify(payload, null, opts.pretty ? 2 : 0) + '\n');
|
|
5415
|
+
return;
|
|
5416
|
+
}
|
|
5417
|
+
|
|
5418
|
+
process.stdout.write(`[socialseal] Login complete. Stored key ${maskApiKey(apiKey)} in ${getConfigPath()}\n`);
|
|
5419
|
+
if (workspaceId) {
|
|
5420
|
+
process.stdout.write(`[socialseal] Default workspace set to ${workspaceId}\n`);
|
|
5421
|
+
}
|
|
5422
|
+
return;
|
|
5423
|
+
}
|
|
5424
|
+
|
|
5425
|
+
if (tokenPayload?.error === 'authorization_pending') {
|
|
5426
|
+
if (!opts.json) process.stdout.write('[socialseal] Waiting for browser approval…\n');
|
|
5427
|
+
continue;
|
|
5428
|
+
}
|
|
5429
|
+
if (tokenPayload?.error === 'slow_down') {
|
|
5430
|
+
intervalMs = Math.min(intervalMs + 5000, 60000);
|
|
5431
|
+
continue;
|
|
5432
|
+
}
|
|
5433
|
+
|
|
5434
|
+
throw await buildHttpError(new Response(JSON.stringify(tokenPayload), {
|
|
5435
|
+
status: tokenRes.status,
|
|
5436
|
+
statusText: tokenRes.statusText,
|
|
5437
|
+
headers: { 'Content-Type': 'application/json' },
|
|
5438
|
+
}), { label: 'Device token poll' });
|
|
5439
|
+
}
|
|
5440
|
+
|
|
5441
|
+
throw new CliError('Timed out waiting for browser approval.', {
|
|
5442
|
+
code: 'DEVICE_LOGIN_TIMEOUT',
|
|
5443
|
+
exitCode: EXIT_CODES.AUTH,
|
|
5444
|
+
hint: 'Run `socialseal login` again when you are ready to approve in the browser.',
|
|
5445
|
+
});
|
|
5446
|
+
}
|
|
5447
|
+
|
|
5448
|
+
function handleLogout(opts) {
|
|
5449
|
+
const config = loadConfig();
|
|
5450
|
+
const hadApiKey = Boolean(resolveApiKey({}, config));
|
|
5451
|
+
const nextConfig = { ...config };
|
|
5452
|
+
delete nextConfig.apiKey;
|
|
5453
|
+
saveConfig(nextConfig);
|
|
5454
|
+
|
|
5455
|
+
const payload = {
|
|
5456
|
+
success: true,
|
|
5457
|
+
removedLocalKey: hadApiKey,
|
|
5458
|
+
configPath: getConfigPath(),
|
|
5459
|
+
};
|
|
5460
|
+
if (opts.json) {
|
|
5461
|
+
process.stdout.write(JSON.stringify(payload, null, opts.pretty ? 2 : 0) + '\n');
|
|
5462
|
+
return;
|
|
5463
|
+
}
|
|
5464
|
+
process.stdout.write('[socialseal] Logged out locally. Any server-side key remains revocable from SocialSeal settings.\n');
|
|
5465
|
+
}
|
|
5466
|
+
|
|
5467
|
+
async function handleWhoami(opts) {
|
|
5468
|
+
const config = loadConfig();
|
|
5469
|
+
const apiKey = requireApiKey(opts, config);
|
|
5470
|
+
const apiBase = resolveApiBase(opts, config);
|
|
5471
|
+
const { resolvedApiBase } = resolveApiTarget({ apiBase, legacyUrl: null });
|
|
5472
|
+
const timeoutMs = resolveTimeoutMs(opts, config);
|
|
5473
|
+
const directory = await fetchWorkspaceDirectory({
|
|
5474
|
+
apiBase: resolvedApiBase,
|
|
5475
|
+
apiKey,
|
|
5476
|
+
timeoutMs,
|
|
5477
|
+
});
|
|
5478
|
+
const selection = resolveWorkspaceSelection({}, config);
|
|
5479
|
+
const workspaces = Array.isArray(directory.workspaces) ? directory.workspaces : [];
|
|
5480
|
+
const workspace = selection.workspaceId
|
|
5481
|
+
? workspaces.find((entry) => entry.id === selection.workspaceId) || null
|
|
5482
|
+
: null;
|
|
5483
|
+
const payload = {
|
|
5484
|
+
authenticated: true,
|
|
5485
|
+
apiBase: resolvedApiBase,
|
|
5486
|
+
key: maskApiKey(apiKey),
|
|
5487
|
+
keySuffix: apiKey.slice(-6),
|
|
5488
|
+
effectiveWorkspaceId: selection.workspaceId,
|
|
5489
|
+
effectiveWorkspaceSource: selection.source,
|
|
5490
|
+
workspace,
|
|
5491
|
+
workspaceCount: workspaces.length,
|
|
5492
|
+
};
|
|
5493
|
+
|
|
5494
|
+
if (opts.json) {
|
|
5495
|
+
process.stdout.write(JSON.stringify(payload, null, opts.pretty ? 2 : 0) + '\n');
|
|
5496
|
+
return;
|
|
5497
|
+
}
|
|
5498
|
+
|
|
5499
|
+
process.stdout.write(`[socialseal] Authenticated with key ${maskApiKey(apiKey)}\n`);
|
|
5500
|
+
if (workspace) {
|
|
5501
|
+
process.stdout.write(`[socialseal] Workspace: ${workspace.name} (${workspace.id})\n`);
|
|
5502
|
+
} else if (directory.defaultWorkspaceId) {
|
|
5503
|
+
process.stdout.write(`[socialseal] Suggested workspace: ${directory.defaultWorkspaceId}\n`);
|
|
5504
|
+
}
|
|
5505
|
+
}
|
|
5506
|
+
|
|
5507
|
+
function handleBilling(opts) {
|
|
5508
|
+
const config = loadConfig();
|
|
5509
|
+
const webBase = resolveWebBase(opts, config);
|
|
5510
|
+
const billingUrl = `${webBase.replace(/\/$/, '')}/settings/billing`;
|
|
5511
|
+
const payload = {
|
|
5512
|
+
billingUrl,
|
|
5513
|
+
note: 'SocialSeal starts on the free tier. Use billing only when credits or quotas are exhausted.',
|
|
5514
|
+
};
|
|
5515
|
+
|
|
5516
|
+
if (opts.json) {
|
|
5517
|
+
process.stdout.write(JSON.stringify(payload, null, opts.pretty ? 2 : 0) + '\n');
|
|
5518
|
+
return;
|
|
5519
|
+
}
|
|
5520
|
+
|
|
5521
|
+
process.stdout.write(`[socialseal] Billing and credits: ${billingUrl}\n`);
|
|
5522
|
+
process.stdout.write('[socialseal] SocialSeal starts on the free tier. Add billing only when you need more capacity.\n');
|
|
5523
|
+
}
|
|
5524
|
+
|
|
4759
5525
|
async function handleWorkspaceList(opts) {
|
|
4760
5526
|
const config = loadConfig();
|
|
4761
5527
|
const apiKey = requireApiKey(opts, config);
|
|
@@ -4915,7 +5681,47 @@ if (typeof program.showHelpAfterError === 'function') {
|
|
|
4915
5681
|
if (typeof program.showSuggestionAfterError === 'function') {
|
|
4916
5682
|
program.showSuggestionAfterError(true);
|
|
4917
5683
|
}
|
|
4918
|
-
program.addHelpText('after', `\nExamples:\n socialseal workspace list\n socialseal workspace use <workspace-id>\n socialseal agent run --message "ping"\n socialseal tools list\n socialseal tools schema --function search-journey-run\n socialseal tools call --function <tool> --body @payload.json\n socialseal tools status 6809 --kind google_ai_run\n socialseal tools status <run-uuid> --kind journey_run --workspace-id <uuid>\n socialseal video queue-analysis --video-id 734829384 --workspace-id <uuid>\n socialseal video extract --video-id 734829384 --wait --out-dir ./video-assets\n socialseal data export-options\n socialseal data export-tracking --group-id 123 --time-period 30d\n socialseal data export-search-results --group-ids 123,124 --workspace-id <uuid> --out ranked.csv\n socialseal data export-group-evidence --group-id 123 --workspace-id <uuid> --out evidence.csv\n`);
|
|
5684
|
+
program.addHelpText('after', `\nExamples:\n socialseal login\n socialseal whoami\n socialseal workspace list\n socialseal workspace use <workspace-id>\n socialseal agent run --message "ping"\n socialseal tools list\n socialseal tools schema --function search-journey-run\n socialseal tools call --function <tool> --body @payload.json\n socialseal tools status 6809 --kind google_ai_run\n socialseal tools status <run-uuid> --kind journey_run --workspace-id <uuid>\n socialseal video queue-analysis --video-id 734829384 --workspace-id <uuid>\n socialseal video extract --video-id 734829384 --wait --out-dir ./video-assets\n socialseal data export-options\n socialseal data export-tracking --group-id 123 --time-period 30d\n socialseal data export-search-results --group-ids 123,124 --workspace-id <uuid> --out ranked.csv\n socialseal data export-group-evidence --group-id 123 --workspace-id <uuid> --out evidence.csv\n`);
|
|
5685
|
+
|
|
5686
|
+
program
|
|
5687
|
+
.command('login')
|
|
5688
|
+
.description('Start browser-based device login and store a local CLI key')
|
|
5689
|
+
.option('--api-base <url>', 'API base URL (default https://api.socialseal.co)')
|
|
5690
|
+
.option('--no-open', 'Print the approval URL without opening a browser')
|
|
5691
|
+
.option('--json', 'Emit machine-readable output')
|
|
5692
|
+
.option('--pretty', 'Pretty-print JSON')
|
|
5693
|
+
.option('--timeout <ms>', 'Overall login timeout in milliseconds')
|
|
5694
|
+
.option('--poll-interval <ms>', 'Polling interval in milliseconds')
|
|
5695
|
+
.option('--verbose', 'Show error details')
|
|
5696
|
+
.action((opts) => runCommand(handleLogin, opts));
|
|
5697
|
+
|
|
5698
|
+
program
|
|
5699
|
+
.command('logout')
|
|
5700
|
+
.description('Remove the locally stored SocialSeal CLI key')
|
|
5701
|
+
.option('--json', 'Emit machine-readable output')
|
|
5702
|
+
.option('--pretty', 'Pretty-print JSON')
|
|
5703
|
+
.option('--verbose', 'Show error details')
|
|
5704
|
+
.action((opts) => runCommand(handleLogout, opts));
|
|
5705
|
+
|
|
5706
|
+
program
|
|
5707
|
+
.command('whoami')
|
|
5708
|
+
.description('Show the current SocialSeal CLI authentication and workspace')
|
|
5709
|
+
.option('--api-base <url>', 'API base URL (default https://api.socialseal.co)')
|
|
5710
|
+
.option('--api-key <key>', 'CLI API key')
|
|
5711
|
+
.option('--json', 'Emit machine-readable output')
|
|
5712
|
+
.option('--pretty', 'Pretty-print JSON')
|
|
5713
|
+
.option('--timeout <ms>', 'Request timeout in milliseconds')
|
|
5714
|
+
.option('--verbose', 'Show error details')
|
|
5715
|
+
.action((opts) => runCommand(handleWhoami, opts));
|
|
5716
|
+
|
|
5717
|
+
program
|
|
5718
|
+
.command('billing')
|
|
5719
|
+
.description('Show where to manage SocialSeal billing and credits')
|
|
5720
|
+
.option('--web-base <url>', 'Web app base URL')
|
|
5721
|
+
.option('--json', 'Emit machine-readable output')
|
|
5722
|
+
.option('--pretty', 'Pretty-print JSON')
|
|
5723
|
+
.option('--verbose', 'Show error details')
|
|
5724
|
+
.action((opts) => runCommand(handleBilling, opts));
|
|
4919
5725
|
|
|
4920
5726
|
program
|
|
4921
5727
|
.command('agent')
|
|
@@ -4983,6 +5789,7 @@ const tools = program.command('tools').description('Call edge functions directly
|
|
|
4983
5789
|
tools
|
|
4984
5790
|
.command('list')
|
|
4985
5791
|
.description('List built-in tool registry entries')
|
|
5792
|
+
.option('--category <name>', 'Filter tools by category')
|
|
4986
5793
|
.option('--json', 'Emit machine-readable output')
|
|
4987
5794
|
.option('--pretty', 'Pretty-print JSON')
|
|
4988
5795
|
.option('--verbose', 'Show error details')
|