@socialseal/cli 0.1.12 → 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/package.json +1 -1
- package/src/index.js +339 -6
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;
|
|
@@ -1018,9 +1020,36 @@ function saveConfig(config) {
|
|
|
1018
1020
|
Object.entries(config || {}).filter(([, value]) => value !== undefined),
|
|
1019
1021
|
);
|
|
1020
1022
|
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
1021
|
-
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);
|
|
1027
|
+
}
|
|
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
|
+
}
|
|
1022
1050
|
}
|
|
1023
1051
|
|
|
1052
|
+
|
|
1024
1053
|
function resolveApiKey(opts, config) {
|
|
1025
1054
|
return opts.apiKey || process.env.SOCIALSEAL_API_KEY || config.apiKey;
|
|
1026
1055
|
}
|
|
@@ -1037,6 +1066,10 @@ function resolveSupabaseUrl(opts, config) {
|
|
|
1037
1066
|
return opts.supabaseUrl || process.env.SOCIALSEAL_SUPABASE_URL || config.supabaseUrl;
|
|
1038
1067
|
}
|
|
1039
1068
|
|
|
1069
|
+
function resolveWebBase(opts = {}, config = {}) {
|
|
1070
|
+
return opts.webBase || process.env.SOCIALSEAL_WEB_BASE || config.webBase || DEFAULT_WEB_BASE;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1040
1073
|
function resolveWorkspaceSelection(opts, config) {
|
|
1041
1074
|
if (typeof opts.workspaceId === 'string' && opts.workspaceId.trim().length > 0) {
|
|
1042
1075
|
return { workspaceId: opts.workspaceId.trim(), source: 'flag' };
|
|
@@ -3149,7 +3182,9 @@ function buildStatusHint(status, context = {}) {
|
|
|
3149
3182
|
switch (status) {
|
|
3150
3183
|
case 401:
|
|
3151
3184
|
case 403:
|
|
3152
|
-
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.';
|
|
3153
3188
|
case 404:
|
|
3154
3189
|
if (context.functionName) {
|
|
3155
3190
|
if (isLocallyDisabledByDefaultFunction(context.functionName)) {
|
|
@@ -3163,6 +3198,9 @@ function buildStatusHint(status, context = {}) {
|
|
|
3163
3198
|
case 422:
|
|
3164
3199
|
return 'Validation error. Review the JSON payload schema. For tracking/group tools, prefer the CLI action aliases or the documented REST semantics.';
|
|
3165
3200
|
default:
|
|
3201
|
+
if (context.billingRelated) {
|
|
3202
|
+
return 'Run `socialseal billing` to open billing and credits options.';
|
|
3203
|
+
}
|
|
3166
3204
|
return null;
|
|
3167
3205
|
}
|
|
3168
3206
|
}
|
|
@@ -3189,7 +3227,9 @@ async function buildHttpError(res, context = {}) {
|
|
|
3189
3227
|
|
|
3190
3228
|
const label = context.label || 'Request';
|
|
3191
3229
|
const statusText = res.statusText ? ` ${res.statusText}` : '';
|
|
3192
|
-
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 });
|
|
3193
3233
|
|
|
3194
3234
|
return new CliError(`${label} failed: ${status}${statusText}`.trim(), {
|
|
3195
3235
|
code: 'HTTP_ERROR',
|
|
@@ -3673,9 +3713,10 @@ function coerceCliError(err, fallbackMessage = 'Command failed') {
|
|
|
3673
3713
|
function requireApiKey(opts, config) {
|
|
3674
3714
|
const apiKey = resolveApiKey(opts, config);
|
|
3675
3715
|
if (!apiKey) {
|
|
3676
|
-
throw new CliError('Missing API key.
|
|
3716
|
+
throw new CliError('Missing API key. Run `socialseal login` to connect this CLI.', {
|
|
3677
3717
|
code: 'MISSING_API_KEY',
|
|
3678
|
-
exitCode: EXIT_CODES.
|
|
3718
|
+
exitCode: EXIT_CODES.AUTH,
|
|
3719
|
+
hint: 'Run `socialseal login`, or set SOCIALSEAL_API_KEY if you already have a key.',
|
|
3679
3720
|
});
|
|
3680
3721
|
}
|
|
3681
3722
|
return apiKey;
|
|
@@ -5229,6 +5270,258 @@ async function handleVideoQueueAnalysis(opts) {
|
|
|
5229
5270
|
emitJsonOutput(payload, opts.pretty);
|
|
5230
5271
|
}
|
|
5231
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
|
+
|
|
5232
5525
|
async function handleWorkspaceList(opts) {
|
|
5233
5526
|
const config = loadConfig();
|
|
5234
5527
|
const apiKey = requireApiKey(opts, config);
|
|
@@ -5388,7 +5681,47 @@ if (typeof program.showHelpAfterError === 'function') {
|
|
|
5388
5681
|
if (typeof program.showSuggestionAfterError === 'function') {
|
|
5389
5682
|
program.showSuggestionAfterError(true);
|
|
5390
5683
|
}
|
|
5391
|
-
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));
|
|
5392
5725
|
|
|
5393
5726
|
program
|
|
5394
5727
|
.command('agent')
|