@socialseal/cli 0.1.0 → 0.1.1
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 +4 -0
- package/README.md +8 -5
- package/package.json +1 -1
- package/src/index.js +309 -26
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 0.1.1 - 2026-03-13
|
|
4
6
|
- Document public base URL and CLI error output.
|
|
5
7
|
- Add request timeouts, verbose error output, and OSS-safe tool discovery behavior.
|
|
8
|
+
- Ship a stable built-in tool registry for `tools list` instead of the hard-disabled discovery message.
|
|
9
|
+
- Fail fast on agent WebSocket `error` events and surface session/tool progress diagnostics in `--verbose` mode.
|
|
6
10
|
|
|
7
11
|
## 0.1.0
|
|
8
12
|
- Initial CLI with agent streaming, tools calls, and provisional data exports.
|
package/README.md
CHANGED
|
@@ -10,6 +10,7 @@ Environment variables:
|
|
|
10
10
|
- `SOCIALSEAL_API_KEY`
|
|
11
11
|
- `SOCIALSEAL_API_BASE` (default `https://api.socialseal.co`)
|
|
12
12
|
- `SOCIALSEAL_TIMEOUT_MS` (optional request timeout override)
|
|
13
|
+
- `SOCIALSEAL_AGENT_IDLE_TIMEOUT_MS` (optional agent WebSocket inactivity timeout override; default 300000)
|
|
13
14
|
|
|
14
15
|
Optional config file:
|
|
15
16
|
- `~/.config/socialseal/config.json`
|
|
@@ -18,7 +19,8 @@ Optional config file:
|
|
|
18
19
|
{
|
|
19
20
|
"apiKey": "ss_cli_...",
|
|
20
21
|
"apiBase": "https://api.socialseal.co",
|
|
21
|
-
"timeoutMs": 30000
|
|
22
|
+
"timeoutMs": 30000,
|
|
23
|
+
"agentIdleTimeoutMs": 300000
|
|
22
24
|
}
|
|
23
25
|
```
|
|
24
26
|
|
|
@@ -26,8 +28,9 @@ Optional config file:
|
|
|
26
28
|
- Agent (non-interactive, streaming):
|
|
27
29
|
- `socialseal agent run --message "..." --api-base https://api.socialseal.co --api-key <key> [--workspace-id <uuid>]`
|
|
28
30
|
- `socialseal agent run --message "..." --timeout 60000`
|
|
31
|
+
- `socialseal agent run --message "..." --idle-timeout 300000 --verbose`
|
|
29
32
|
|
|
30
|
-
- Tools list (
|
|
33
|
+
- Tools list (built-in registry):
|
|
31
34
|
- `socialseal tools list`
|
|
32
35
|
- `socialseal tools list --json`
|
|
33
36
|
|
|
@@ -41,13 +44,13 @@ Optional config file:
|
|
|
41
44
|
|
|
42
45
|
## Notes
|
|
43
46
|
- `export-report` and `export_tracking_data` are provisional until CLI export specs are finalized.
|
|
44
|
-
- `tools list`
|
|
45
|
-
-
|
|
47
|
+
- `tools list` ships a stable built-in registry of supported direct-call function targets. It is not live backend enumeration.
|
|
48
|
+
- `--timeout <ms>` controls HTTP request timeouts. Agent runs default to a 5-minute WebSocket inactivity timeout unless you set `--idle-timeout <ms>` (or the matching env/config value).
|
|
46
49
|
|
|
47
50
|
## Errors and exit codes
|
|
48
51
|
- Exit codes: `2` (usage), `3` (auth), `4` (not found), `5` (server), `1` (unknown)
|
|
49
52
|
- Add `--json` to `tools call` or `data` commands to emit machine-readable errors.
|
|
50
|
-
- Add `--verbose` to print error details
|
|
53
|
+
- Add `--verbose` to print error details plus agent session/tool progress diagnostics.
|
|
51
54
|
|
|
52
55
|
## Smoke Test (manual)
|
|
53
56
|
1. `SOCIALSEAL_API_KEY=... socialseal agent run --message "ping"`
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -11,7 +11,8 @@ const DEFAULT_API_BASE = 'https://api.socialseal.co';
|
|
|
11
11
|
const CLI_KEY_HEADER = 'X-CLI-Key';
|
|
12
12
|
const WORKSPACE_HEADER = 'X-Workspace-Id';
|
|
13
13
|
const DEFAULT_TIMEOUT_MS = 30000;
|
|
14
|
-
const
|
|
14
|
+
const DEFAULT_AGENT_IDLE_TIMEOUT_MS = 300000;
|
|
15
|
+
const MAX_TIMEOUT_MS = 900000;
|
|
15
16
|
const LEGACY_ENABLED = process.env.SOCIALSEAL_ENABLE_LEGACY === '1';
|
|
16
17
|
const EXIT_CODES = {
|
|
17
18
|
OK: 0,
|
|
@@ -22,7 +23,40 @@ const EXIT_CODES = {
|
|
|
22
23
|
SERVER: 5,
|
|
23
24
|
};
|
|
24
25
|
const HTTP_METHODS = new Set(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']);
|
|
25
|
-
const KNOWN_TOOLS = [
|
|
26
|
+
const KNOWN_TOOLS = [
|
|
27
|
+
{ name: 'agent-tool-jobs', category: 'agent', description: 'Poll queued agent-backed tool jobs and fetch their results.' },
|
|
28
|
+
{ name: 'deep-exploration-runs', category: 'agent', description: 'Read or persist deep exploration render runs.' },
|
|
29
|
+
{ name: 'workspace-notes', category: 'agent', description: 'Search, create, update, and pin workspace note memory.' },
|
|
30
|
+
{ name: 'workspace-onboarding', category: 'agent', description: 'Read or update workspace onboarding metadata used by the agent.' },
|
|
31
|
+
{ name: 'brand-group-management', category: 'brand', description: 'Manage brand groups, aliases, competitors, and rule configuration.' },
|
|
32
|
+
{ name: 'enqueue-brand-metrics-backfill', category: 'brand', description: 'Queue backfill jobs for brand metrics refreshes.' },
|
|
33
|
+
{ name: 'export-report', category: 'export', description: 'Generate report exports (csv/json/markdown/html/excel_data).' },
|
|
34
|
+
{ name: 'export_tracking_data', category: 'export', description: 'Stream tracking exports as CSV for a group or tracking item.' },
|
|
35
|
+
{ name: 'douyin-geo-api', category: 'search', description: 'Query Douyin search and geo data.' },
|
|
36
|
+
{ name: 'google-ai-search', category: 'search', description: 'Run Google AI search queries and fetch result snapshots.' },
|
|
37
|
+
{ name: 'instagram-geo-api', category: 'search', description: 'Query Instagram search and geo data.' },
|
|
38
|
+
{ name: 'tiktok-geo-api', category: 'search', description: 'Query TikTok search and geo data.' },
|
|
39
|
+
{ name: 'xhs-geo-api', category: 'search', description: 'Query Xiaohongshu search and geo data.' },
|
|
40
|
+
{ name: 'youtube-geo-api', category: 'search', description: 'Query YouTube search and geo data.' },
|
|
41
|
+
{ name: 'group-management', category: 'tracking', description: 'Create, update, list, and delete tracking groups and memberships.' },
|
|
42
|
+
{ name: 'tracking', category: 'tracking', description: 'Create, update, list, refresh, and delete tracking items.' },
|
|
43
|
+
{ name: 'journey-feedback', category: 'vnext', description: 'Record acceptance or rejection feedback for opportunity bundles.' },
|
|
44
|
+
{ name: 'opportunity-bundle-approve', category: 'vnext', description: 'Approve an opportunity bundle and create tracking coverage.' },
|
|
45
|
+
{ name: 'search-journey-run', category: 'vnext', description: 'Run a search journey for a subject across supported platforms.' },
|
|
46
|
+
{ name: 'vnext-blueprints-create', category: 'vnext', description: 'Create a vNext blueprint from grounded evidence.' },
|
|
47
|
+
{ name: 'vnext-blueprints-generate', category: 'vnext', description: 'Generate a vNext blueprint from workspace opportunity data.' },
|
|
48
|
+
{ name: 'vnext-blueprints-read', category: 'vnext', description: 'Read vNext blueprint history and specific versions.' },
|
|
49
|
+
{ name: 'vnext-briefs-create', category: 'vnext', description: 'Create a vNext brief record.' },
|
|
50
|
+
{ name: 'vnext-briefs-generate', category: 'vnext', description: 'Generate a vNext brief from a blueprint or opportunity.' },
|
|
51
|
+
{ name: 'vnext-briefs-read', category: 'vnext', description: 'Read generated vNext briefs and version history.' },
|
|
52
|
+
{ name: 'vnext-intents', category: 'vnext', description: 'List, create, update, or delete vNext intents.' },
|
|
53
|
+
{ name: 'vnext-journeys', category: 'vnext', description: 'List journey runs and inspect their latest outputs.' },
|
|
54
|
+
{ name: 'vnext-keywords', category: 'vnext', description: 'List, create, update, or delete vNext keywords.' },
|
|
55
|
+
{ name: 'vnext-personas', category: 'vnext', description: 'List, create, update, retire, or reactivate vNext personas.' },
|
|
56
|
+
{ name: 'vnext-pillars', category: 'vnext', description: 'List, create, update, or delete vNext content pillars.' },
|
|
57
|
+
{ name: 'vnext-topics', category: 'vnext', description: 'Manage topics, assignments, queues, and topic suggestions.' },
|
|
58
|
+
{ name: 'vnext-topics-auto-tag', category: 'vnext', description: 'Auto-tag keyword and topic assignments with Gemini-assisted review.' },
|
|
59
|
+
];
|
|
26
60
|
|
|
27
61
|
function loadConfig() {
|
|
28
62
|
const configPath = process.env.SOCIALSEAL_CONFIG || DEFAULT_CONFIG_PATH;
|
|
@@ -76,12 +110,11 @@ function normalizeMethod(method) {
|
|
|
76
110
|
return normalized;
|
|
77
111
|
}
|
|
78
112
|
|
|
79
|
-
function
|
|
80
|
-
|
|
81
|
-
if (raw == null || raw === '') return DEFAULT_TIMEOUT_MS;
|
|
113
|
+
function parseTimeoutMs(raw, { defaultValue = DEFAULT_TIMEOUT_MS, label = 'timeout' } = {}) {
|
|
114
|
+
if (raw == null || raw === '') return defaultValue;
|
|
82
115
|
const parsed = Number(raw);
|
|
83
116
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
84
|
-
throw new CliError(
|
|
117
|
+
throw new CliError(`Invalid ${label} value. Use a positive number of milliseconds.`, {
|
|
85
118
|
code: 'INVALID_TIMEOUT',
|
|
86
119
|
exitCode: EXIT_CODES.USAGE,
|
|
87
120
|
});
|
|
@@ -89,6 +122,31 @@ function resolveTimeoutMs(opts, config) {
|
|
|
89
122
|
return Math.min(parsed, MAX_TIMEOUT_MS);
|
|
90
123
|
}
|
|
91
124
|
|
|
125
|
+
function resolveTimeoutMs(opts, config) {
|
|
126
|
+
const raw = opts.timeout ?? process.env.SOCIALSEAL_TIMEOUT_MS ?? config.timeoutMs;
|
|
127
|
+
return parseTimeoutMs(raw, { defaultValue: DEFAULT_TIMEOUT_MS, label: 'timeout' });
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function resolveAgentIdleTimeoutMs(opts, config, fallbackTimeoutMs) {
|
|
131
|
+
const explicitIdleTimeout =
|
|
132
|
+
opts.idleTimeout
|
|
133
|
+
?? process.env.SOCIALSEAL_AGENT_IDLE_TIMEOUT_MS
|
|
134
|
+
?? config.agentIdleTimeoutMs;
|
|
135
|
+
if (explicitIdleTimeout != null && explicitIdleTimeout !== '') {
|
|
136
|
+
return parseTimeoutMs(explicitIdleTimeout, {
|
|
137
|
+
defaultValue: DEFAULT_AGENT_IDLE_TIMEOUT_MS,
|
|
138
|
+
label: 'idle timeout',
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const explicitTimeout = opts.timeout ?? process.env.SOCIALSEAL_TIMEOUT_MS ?? config.timeoutMs;
|
|
143
|
+
if (explicitTimeout != null && explicitTimeout !== '') {
|
|
144
|
+
return fallbackTimeoutMs;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return DEFAULT_AGENT_IDLE_TIMEOUT_MS;
|
|
148
|
+
}
|
|
149
|
+
|
|
92
150
|
function resolveLegacyUrl(value, label) {
|
|
93
151
|
if (!value) return null;
|
|
94
152
|
if (!LEGACY_ENABLED) {
|
|
@@ -101,6 +159,52 @@ function resolveLegacyUrl(value, label) {
|
|
|
101
159
|
return value;
|
|
102
160
|
}
|
|
103
161
|
|
|
162
|
+
function emitInfo(opts, message) {
|
|
163
|
+
if (opts?.verbose) {
|
|
164
|
+
process.stderr.write(`[socialseal] ${message}\n`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function formatCloseReason(reason) {
|
|
169
|
+
if (reason == null) return '';
|
|
170
|
+
if (Buffer.isBuffer(reason)) return reason.toString('utf8');
|
|
171
|
+
if (typeof reason === 'string') return reason;
|
|
172
|
+
return String(reason);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async function readNodeResponseBody(response, limit = 2000) {
|
|
176
|
+
if (!response) return null;
|
|
177
|
+
|
|
178
|
+
return await new Promise((resolve) => {
|
|
179
|
+
const chunks = [];
|
|
180
|
+
let bufferedBytes = 0;
|
|
181
|
+
let totalBytes = 0;
|
|
182
|
+
let settled = false;
|
|
183
|
+
|
|
184
|
+
const finish = (value) => {
|
|
185
|
+
if (settled) return;
|
|
186
|
+
settled = true;
|
|
187
|
+
resolve(value);
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
response.on('data', (chunk) => {
|
|
191
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
|
|
192
|
+
totalBytes += buffer.length;
|
|
193
|
+
if (bufferedBytes >= limit) return;
|
|
194
|
+
|
|
195
|
+
const remaining = limit - bufferedBytes;
|
|
196
|
+
const slice = buffer.subarray(0, remaining);
|
|
197
|
+
chunks.push(slice);
|
|
198
|
+
bufferedBytes += slice.length;
|
|
199
|
+
});
|
|
200
|
+
response.on('end', () => {
|
|
201
|
+
const text = chunks.length > 0 ? Buffer.concat(chunks).toString('utf8') : '';
|
|
202
|
+
finish(totalBytes > limit ? `${text}…` : text);
|
|
203
|
+
});
|
|
204
|
+
response.on('error', () => finish(null));
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
104
208
|
function parseJsonInput(value, { label = 'payload', allowString = false } = {}) {
|
|
105
209
|
if (!value) return null;
|
|
106
210
|
if (value.startsWith('@')) {
|
|
@@ -231,7 +335,11 @@ function emitError(err, opts = {}) {
|
|
|
231
335
|
process.stderr.write(`[socialseal] ${payload.error.hint}\n`);
|
|
232
336
|
}
|
|
233
337
|
if (showDetails && payload.error.details) {
|
|
234
|
-
|
|
338
|
+
const detailsText =
|
|
339
|
+
typeof payload.error.details === 'string'
|
|
340
|
+
? payload.error.details
|
|
341
|
+
: JSON.stringify(payload.error.details);
|
|
342
|
+
process.stderr.write(`[socialseal] Details: ${detailsText}\n`);
|
|
235
343
|
} else if (!showDetails && err.details) {
|
|
236
344
|
process.stderr.write('[socialseal] Use --verbose to see error details.\n');
|
|
237
345
|
}
|
|
@@ -336,6 +444,7 @@ async function handleAgentRun(opts) {
|
|
|
336
444
|
const agentUrl = resolveLegacyUrl(resolveAgentUrl(opts, config), 'SOCIALSEAL_AGENT_URL');
|
|
337
445
|
const { resolvedApiBase, legacyUrl } = resolveApiTarget({ apiBase, legacyUrl: agentUrl });
|
|
338
446
|
const timeoutMs = resolveTimeoutMs(opts, config);
|
|
447
|
+
const idleTimeoutMs = resolveAgentIdleTimeoutMs(opts, config, timeoutMs);
|
|
339
448
|
|
|
340
449
|
const headers = {
|
|
341
450
|
'Content-Type': 'application/json',
|
|
@@ -361,6 +470,8 @@ async function handleAgentRun(opts) {
|
|
|
361
470
|
}
|
|
362
471
|
|
|
363
472
|
const sessionData = await sessionRes.json();
|
|
473
|
+
const sessionId = sessionData?.data?.sessionId || null;
|
|
474
|
+
const initialConversationId = sessionData?.data?.activeConversationId || opts.conversationId || null;
|
|
364
475
|
const wsUrl = sessionData?.data?.websocketUrl;
|
|
365
476
|
if (!wsUrl) {
|
|
366
477
|
throw new CliError('Missing websocketUrl in session response.', {
|
|
@@ -368,6 +479,10 @@ async function handleAgentRun(opts) {
|
|
|
368
479
|
exitCode: EXIT_CODES.SERVER,
|
|
369
480
|
});
|
|
370
481
|
}
|
|
482
|
+
emitInfo(
|
|
483
|
+
opts,
|
|
484
|
+
`Agent session created${sessionId ? ` (session ${sessionId})` : ''}${initialConversationId ? ` for conversation ${initialConversationId}` : ''}.`,
|
|
485
|
+
);
|
|
371
486
|
|
|
372
487
|
const context = parseJsonInput(opts.context, { label: 'context', allowString: true });
|
|
373
488
|
const message = opts.message;
|
|
@@ -375,10 +490,36 @@ async function handleAgentRun(opts) {
|
|
|
375
490
|
await new Promise((resolve, reject) => {
|
|
376
491
|
const ws = new WebSocket(wsUrl);
|
|
377
492
|
let finished = false;
|
|
493
|
+
let settled = false;
|
|
378
494
|
let inactivityTimer = null;
|
|
495
|
+
let sawAssistantChunk = false;
|
|
496
|
+
let sawToolCall = false;
|
|
497
|
+
let sawThinking = false;
|
|
498
|
+
let lastMessageType = 'none';
|
|
499
|
+
let activeConversationId = initialConversationId;
|
|
500
|
+
const toolProgressStatus = new Map();
|
|
501
|
+
|
|
502
|
+
const settleResolve = () => {
|
|
503
|
+
if (settled) return;
|
|
504
|
+
settled = true;
|
|
505
|
+
if (inactivityTimer) clearTimeout(inactivityTimer);
|
|
506
|
+
resolve();
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
const settleReject = (error) => {
|
|
510
|
+
if (settled) return;
|
|
511
|
+
settled = true;
|
|
512
|
+
if (inactivityTimer) clearTimeout(inactivityTimer);
|
|
513
|
+
try {
|
|
514
|
+
ws.terminate();
|
|
515
|
+
} catch {
|
|
516
|
+
// ignore
|
|
517
|
+
}
|
|
518
|
+
reject(error);
|
|
519
|
+
};
|
|
379
520
|
|
|
380
521
|
const resetInactivity = () => {
|
|
381
|
-
if (!
|
|
522
|
+
if (!idleTimeoutMs) return;
|
|
382
523
|
if (inactivityTimer) clearTimeout(inactivityTimer);
|
|
383
524
|
inactivityTimer = setTimeout(() => {
|
|
384
525
|
try {
|
|
@@ -386,38 +527,74 @@ async function handleAgentRun(opts) {
|
|
|
386
527
|
} catch {
|
|
387
528
|
// ignore
|
|
388
529
|
}
|
|
389
|
-
|
|
530
|
+
settleReject(new CliError('WebSocket timed out waiting for agent response.', {
|
|
390
531
|
code: 'WEBSOCKET_TIMEOUT',
|
|
391
532
|
exitCode: EXIT_CODES.SERVER,
|
|
392
|
-
hint: 'Increase the timeout with --timeout <ms>.',
|
|
533
|
+
hint: 'Increase the timeout with --idle-timeout <ms> or --timeout <ms>.',
|
|
534
|
+
details: truncateDetails({
|
|
535
|
+
sessionId,
|
|
536
|
+
activeConversationId,
|
|
537
|
+
lastMessageType,
|
|
538
|
+
sawAssistantChunk,
|
|
539
|
+
sawToolCall,
|
|
540
|
+
sawThinking,
|
|
541
|
+
idleTimeoutMs,
|
|
542
|
+
}),
|
|
393
543
|
}));
|
|
394
|
-
},
|
|
544
|
+
}, idleTimeoutMs);
|
|
395
545
|
};
|
|
396
546
|
|
|
397
547
|
ws.on('open', () => {
|
|
398
548
|
resetInactivity();
|
|
549
|
+
emitInfo(opts, 'Connected to agent WebSocket.');
|
|
399
550
|
const payload = {
|
|
400
551
|
type: 'user_message',
|
|
401
552
|
payload: { content: message, context: context || undefined },
|
|
402
553
|
timestamp: Date.now(),
|
|
403
554
|
};
|
|
404
555
|
ws.send(JSON.stringify(payload));
|
|
556
|
+
emitInfo(opts, 'User message sent to agent.');
|
|
405
557
|
});
|
|
406
558
|
|
|
407
559
|
ws.on('message', (data) => {
|
|
408
560
|
try {
|
|
409
561
|
resetInactivity();
|
|
410
562
|
const msg = JSON.parse(data.toString());
|
|
563
|
+
lastMessageType = msg.type || 'unknown';
|
|
564
|
+
|
|
565
|
+
if (msg.type === 'session_state' && msg.payload?.activeConversationId) {
|
|
566
|
+
activeConversationId = msg.payload.activeConversationId;
|
|
567
|
+
emitInfo(
|
|
568
|
+
opts,
|
|
569
|
+
`Session state received${sessionId ? ` for session ${sessionId}` : ''}${activeConversationId ? ` (conversation ${activeConversationId})` : ''}.`,
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
|
|
411
573
|
if (opts.json) {
|
|
412
574
|
process.stdout.write(JSON.stringify(msg) + '\n');
|
|
413
575
|
if (msg.type === 'assistant_chunk' && msg.payload?.done) {
|
|
414
576
|
finished = true;
|
|
415
577
|
ws.close(1000, 'done');
|
|
416
578
|
}
|
|
579
|
+
if (msg.type === 'error') {
|
|
580
|
+
const payload = msg.payload || {};
|
|
581
|
+
settleReject(new CliError(`Agent error: ${payload.message || 'unknown'}`, {
|
|
582
|
+
code: payload.code || 'AGENT_ERROR',
|
|
583
|
+
exitCode: EXIT_CODES.SERVER,
|
|
584
|
+
hint: payload.retryable ? 'Retry the request or inspect backend status.' : null,
|
|
585
|
+
details: truncateDetails({
|
|
586
|
+
...payload,
|
|
587
|
+
sessionId,
|
|
588
|
+
activeConversationId,
|
|
589
|
+
lastMessageType,
|
|
590
|
+
}),
|
|
591
|
+
}));
|
|
592
|
+
}
|
|
417
593
|
return;
|
|
418
594
|
}
|
|
419
595
|
if (msg.type === 'assistant_chunk') {
|
|
420
596
|
const chunk = msg.payload?.chunk ?? '';
|
|
597
|
+
sawAssistantChunk = sawAssistantChunk || chunk.length > 0 || !!msg.payload?.done;
|
|
421
598
|
if (chunk) process.stdout.write(chunk);
|
|
422
599
|
if (msg.payload?.done) {
|
|
423
600
|
finished = true;
|
|
@@ -425,28 +602,114 @@ async function handleAgentRun(opts) {
|
|
|
425
602
|
ws.close(1000, 'done');
|
|
426
603
|
}
|
|
427
604
|
} else if (msg.type === 'error') {
|
|
428
|
-
|
|
605
|
+
const payload = msg.payload || {};
|
|
606
|
+
settleReject(new CliError(`Agent error: ${payload.message || 'unknown'}`, {
|
|
607
|
+
code: payload.code || 'AGENT_ERROR',
|
|
608
|
+
exitCode: EXIT_CODES.SERVER,
|
|
609
|
+
hint: payload.retryable ? 'Retry the request or inspect backend status.' : null,
|
|
610
|
+
details: truncateDetails({
|
|
611
|
+
...payload,
|
|
612
|
+
sessionId,
|
|
613
|
+
activeConversationId,
|
|
614
|
+
lastMessageType,
|
|
615
|
+
}),
|
|
616
|
+
}));
|
|
617
|
+
} else if (msg.type === 'thinking_chunk') {
|
|
618
|
+
sawThinking = true;
|
|
619
|
+
emitInfo(opts, 'Agent is thinking.');
|
|
620
|
+
} else if (msg.type === 'assistant_status') {
|
|
621
|
+
const code = msg.payload?.code || 'unknown';
|
|
622
|
+
const statusMessage = msg.payload?.message || 'Agent reported a status update.';
|
|
623
|
+
emitInfo(opts, `Agent status [${code}]: ${statusMessage}`);
|
|
624
|
+
} else if (msg.type === 'tool_call_start') {
|
|
625
|
+
sawToolCall = true;
|
|
626
|
+
emitInfo(opts, `Tool start: ${msg.payload?.name || 'unknown'}`);
|
|
627
|
+
} else if (msg.type === 'tool_call_progress') {
|
|
628
|
+
const toolCallId = msg.payload?.toolCallId || '';
|
|
629
|
+
const progressStatus = msg.payload?.status || 'running';
|
|
630
|
+
if (toolProgressStatus.get(toolCallId) !== progressStatus) {
|
|
631
|
+
toolProgressStatus.set(toolCallId, progressStatus);
|
|
632
|
+
emitInfo(opts, `Tool progress: ${progressStatus}`);
|
|
633
|
+
}
|
|
634
|
+
} else if (msg.type === 'tool_call_complete') {
|
|
635
|
+
const error = msg.payload?.error;
|
|
636
|
+
const duration = typeof msg.payload?.duration_ms === 'number'
|
|
637
|
+
? `${msg.payload.duration_ms}ms`
|
|
638
|
+
: 'unknown duration';
|
|
639
|
+
if (error) {
|
|
640
|
+
emitInfo(opts, `Tool failed after ${duration}: ${error}`);
|
|
641
|
+
} else {
|
|
642
|
+
emitInfo(opts, `Tool completed in ${duration}.`);
|
|
643
|
+
}
|
|
429
644
|
}
|
|
430
645
|
} catch (err) {
|
|
431
|
-
|
|
646
|
+
settleReject(new CliError(`Failed to parse agent message: ${err.message || err}`, {
|
|
647
|
+
code: 'INVALID_AGENT_MESSAGE',
|
|
648
|
+
exitCode: EXIT_CODES.SERVER,
|
|
649
|
+
details: data.toString(),
|
|
650
|
+
}));
|
|
432
651
|
}
|
|
433
652
|
});
|
|
434
653
|
|
|
435
|
-
ws.on('
|
|
436
|
-
|
|
654
|
+
ws.on('unexpected-response', async (_req, response) => {
|
|
655
|
+
const statusText = response.statusCode
|
|
656
|
+
? `${response.statusCode}${response.statusMessage ? ` ${response.statusMessage}` : ''}`
|
|
657
|
+
: 'unknown';
|
|
658
|
+
const details = await readNodeResponseBody(response);
|
|
659
|
+
settleReject(new CliError(`WebSocket upgrade failed: ${statusText}`.trim(), {
|
|
660
|
+
code: 'WEBSOCKET_UPGRADE_FAILED',
|
|
661
|
+
exitCode:
|
|
662
|
+
response.statusCode === 401 || response.statusCode === 403
|
|
663
|
+
? EXIT_CODES.AUTH
|
|
664
|
+
: EXIT_CODES.SERVER,
|
|
665
|
+
hint:
|
|
666
|
+
response.statusCode === 401 || response.statusCode === 403
|
|
667
|
+
? 'Check your CLI key, workspace scope, and session endpoint auth.'
|
|
668
|
+
: 'Retry with --verbose to inspect gateway or backend behavior.',
|
|
669
|
+
details: truncateDetails({
|
|
670
|
+
sessionId,
|
|
671
|
+
activeConversationId,
|
|
672
|
+
responseBody: details,
|
|
673
|
+
}),
|
|
674
|
+
}));
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
ws.on('close', (code, reason) => {
|
|
678
|
+
const closeReason = formatCloseReason(reason);
|
|
437
679
|
if (!finished) {
|
|
438
|
-
|
|
439
|
-
code: '
|
|
440
|
-
|
|
441
|
-
|
|
680
|
+
settleReject(new CliError(
|
|
681
|
+
`WebSocket closed before completion (code ${code}${closeReason ? `: ${closeReason}` : ''}).`,
|
|
682
|
+
{
|
|
683
|
+
code: 'WEBSOCKET_CLOSED',
|
|
684
|
+
exitCode: EXIT_CODES.SERVER,
|
|
685
|
+
hint: sawAssistantChunk
|
|
686
|
+
? 'The agent disconnected mid-response. Retry the request.'
|
|
687
|
+
: 'The agent closed the connection before completing. Retry with --verbose for more diagnostics.',
|
|
688
|
+
details: truncateDetails({
|
|
689
|
+
sessionId,
|
|
690
|
+
activeConversationId,
|
|
691
|
+
lastMessageType,
|
|
692
|
+
sawAssistantChunk,
|
|
693
|
+
sawToolCall,
|
|
694
|
+
sawThinking,
|
|
695
|
+
}),
|
|
696
|
+
},
|
|
697
|
+
));
|
|
442
698
|
} else {
|
|
443
|
-
|
|
699
|
+
settleResolve();
|
|
444
700
|
}
|
|
445
701
|
});
|
|
446
702
|
|
|
447
703
|
ws.on('error', (err) => {
|
|
448
|
-
|
|
449
|
-
|
|
704
|
+
settleReject(new CliError(`WebSocket error: ${err.message || err}`, {
|
|
705
|
+
code: 'WEBSOCKET_ERROR',
|
|
706
|
+
exitCode: EXIT_CODES.SERVER,
|
|
707
|
+
details: truncateDetails({
|
|
708
|
+
sessionId,
|
|
709
|
+
activeConversationId,
|
|
710
|
+
lastMessageType,
|
|
711
|
+
}),
|
|
712
|
+
}));
|
|
450
713
|
});
|
|
451
714
|
});
|
|
452
715
|
}
|
|
@@ -492,8 +755,9 @@ async function handleToolsCall(opts) {
|
|
|
492
755
|
|
|
493
756
|
function handleToolsList(opts) {
|
|
494
757
|
const payload = {
|
|
758
|
+
discovery: 'built_in_registry',
|
|
495
759
|
tools: KNOWN_TOOLS,
|
|
496
|
-
note: '
|
|
760
|
+
note: 'This registry is shipped with the CLI for stable discovery. It is not live backend enumeration.',
|
|
497
761
|
};
|
|
498
762
|
|
|
499
763
|
if (opts.json) {
|
|
@@ -501,8 +765,19 @@ function handleToolsList(opts) {
|
|
|
501
765
|
return;
|
|
502
766
|
}
|
|
503
767
|
|
|
504
|
-
process.stdout.write('[socialseal]
|
|
768
|
+
process.stdout.write('[socialseal] Built-in tool registry\n');
|
|
505
769
|
process.stdout.write(`[socialseal] ${payload.note}\n`);
|
|
770
|
+
|
|
771
|
+
let currentCategory = null;
|
|
772
|
+
for (const tool of KNOWN_TOOLS) {
|
|
773
|
+
if (tool.category !== currentCategory) {
|
|
774
|
+
currentCategory = tool.category;
|
|
775
|
+
process.stdout.write(`\n${currentCategory}\n`);
|
|
776
|
+
}
|
|
777
|
+
process.stdout.write(`- ${tool.name}: ${tool.description}\n`);
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
process.stdout.write('\n[socialseal] Call a tool with: socialseal tools call --function <name> --body @payload.json\n');
|
|
506
781
|
}
|
|
507
782
|
|
|
508
783
|
async function handleDataExportTracking(opts) {
|
|
@@ -540,6 +815,13 @@ async function handleDataExportTracking(opts) {
|
|
|
540
815
|
throw await buildHttpError(res, { label: 'Tracking export' });
|
|
541
816
|
}
|
|
542
817
|
|
|
818
|
+
if (!res.body) {
|
|
819
|
+
throw new CliError('Export response contained no body.', {
|
|
820
|
+
code: 'EMPTY_RESPONSE',
|
|
821
|
+
exitCode: EXIT_CODES.SERVER,
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
|
|
543
825
|
const outPath = opts.stdout ? null : (opts.out || 'tracking_export.csv');
|
|
544
826
|
if (outPath) {
|
|
545
827
|
await pipeline(res.body, fs.createWriteStream(outPath));
|
|
@@ -615,7 +897,7 @@ const program = new Command();
|
|
|
615
897
|
program
|
|
616
898
|
.name('socialseal')
|
|
617
899
|
.description('SocialSeal CLI (non-interactive)')
|
|
618
|
-
.version('0.1.
|
|
900
|
+
.version('0.1.1');
|
|
619
901
|
|
|
620
902
|
if (typeof program.showHelpAfterError === 'function') {
|
|
621
903
|
program.showHelpAfterError(true);
|
|
@@ -638,6 +920,7 @@ program
|
|
|
638
920
|
.option('--create-new', 'Create a new conversation')
|
|
639
921
|
.option('--json', 'Emit NDJSON events')
|
|
640
922
|
.option('--timeout <ms>', 'Request timeout in milliseconds')
|
|
923
|
+
.option('--idle-timeout <ms>', 'WebSocket inactivity timeout in milliseconds')
|
|
641
924
|
.option('--verbose', 'Show error details')
|
|
642
925
|
.action((opts) => runCommand(handleAgentRun, opts));
|
|
643
926
|
|
|
@@ -645,7 +928,7 @@ const tools = program.command('tools').description('Call edge functions directly
|
|
|
645
928
|
|
|
646
929
|
tools
|
|
647
930
|
.command('list')
|
|
648
|
-
.description('List
|
|
931
|
+
.description('List built-in tool registry entries')
|
|
649
932
|
.option('--json', 'Emit machine-readable output')
|
|
650
933
|
.option('--pretty', 'Pretty-print JSON')
|
|
651
934
|
.option('--verbose', 'Show error details')
|