@socialseal/cli 0.1.0 → 0.1.2
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 +9 -0
- package/README.md +35 -5
- package/package.json +1 -1
- package/src/index.js +823 -36
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 0.1.2 - 2026-03-18
|
|
6
|
+
- Add `search-journey-run` async CLI ergonomics: `--async` starts the backend async mode, polling is on by default, and `--no-poll` returns the initial `runId` immediately.
|
|
7
|
+
- Add `--poll-interval <ms>` for async `search-journey-run` status polling.
|
|
8
|
+
- Treat terminal async `search-journey-run` failures as non-zero CLI exits instead of silent `200` JSON output.
|
|
9
|
+
|
|
10
|
+
## 0.1.1 - 2026-03-13
|
|
4
11
|
- Document public base URL and CLI error output.
|
|
5
12
|
- Add request timeouts, verbose error output, and OSS-safe tool discovery behavior.
|
|
13
|
+
- Ship a stable built-in tool registry for `tools list` instead of the hard-disabled discovery message.
|
|
14
|
+
- Fail fast on agent WebSocket `error` events and surface session/tool progress diagnostics in `--verbose` mode.
|
|
6
15
|
|
|
7
16
|
## 0.1.0
|
|
8
17
|
- Initial CLI with agent streaming, tools calls, and provisional data exports.
|
package/README.md
CHANGED
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
Environment variables:
|
|
10
10
|
- `SOCIALSEAL_API_KEY`
|
|
11
11
|
- `SOCIALSEAL_API_BASE` (default `https://api.socialseal.co`)
|
|
12
|
+
- `SOCIALSEAL_WORKSPACE_ID` (optional workspace override; takes precedence over config)
|
|
12
13
|
- `SOCIALSEAL_TIMEOUT_MS` (optional request timeout override)
|
|
14
|
+
- `SOCIALSEAL_AGENT_IDLE_TIMEOUT_MS` (optional agent WebSocket inactivity timeout override; default 300000)
|
|
13
15
|
|
|
14
16
|
Optional config file:
|
|
15
17
|
- `~/.config/socialseal/config.json`
|
|
@@ -18,22 +20,34 @@ Optional config file:
|
|
|
18
20
|
{
|
|
19
21
|
"apiKey": "ss_cli_...",
|
|
20
22
|
"apiBase": "https://api.socialseal.co",
|
|
21
|
-
"
|
|
23
|
+
"workspaceId": "00000000-0000-0000-0000-000000000000",
|
|
24
|
+
"timeoutMs": 30000,
|
|
25
|
+
"agentIdleTimeoutMs": 300000
|
|
22
26
|
}
|
|
23
27
|
```
|
|
24
28
|
|
|
25
29
|
## Commands
|
|
30
|
+
- Workspace discovery/defaults:
|
|
31
|
+
- `socialseal workspace list`
|
|
32
|
+
- `socialseal workspace current`
|
|
33
|
+
- `socialseal workspace use <workspace-id|slug|exact-name>`
|
|
34
|
+
- `socialseal workspace clear`
|
|
35
|
+
|
|
26
36
|
- Agent (non-interactive, streaming):
|
|
27
37
|
- `socialseal agent run --message "..." --api-base https://api.socialseal.co --api-key <key> [--workspace-id <uuid>]`
|
|
38
|
+
- `socialseal agent run --message "..." --continue <token>`
|
|
28
39
|
- `socialseal agent run --message "..." --timeout 60000`
|
|
40
|
+
- `socialseal agent run --message "..." --idle-timeout 300000 --verbose`
|
|
29
41
|
|
|
30
|
-
- Tools list (
|
|
42
|
+
- Tools list (built-in registry):
|
|
31
43
|
- `socialseal tools list`
|
|
32
44
|
- `socialseal tools list --json`
|
|
33
45
|
|
|
34
46
|
- Tools (direct edge function call):
|
|
35
47
|
- `socialseal tools call --function <tool> --body @payload.json --api-base https://api.socialseal.co --api-key <key>`
|
|
36
48
|
- `socialseal tools call --function <tool> --body @payload.json --json`
|
|
49
|
+
- `socialseal tools call --function search-journey-run --body @payload.json --async --workspace-id <uuid>`
|
|
50
|
+
- `socialseal tools call --function search-journey-run --body @payload.json --async --no-poll --workspace-id <uuid>`
|
|
37
51
|
|
|
38
52
|
- Data exports (provisional):
|
|
39
53
|
- `socialseal data export-tracking --group-id 123 --time-period 30d --out out.csv`
|
|
@@ -41,13 +55,29 @@ Optional config file:
|
|
|
41
55
|
|
|
42
56
|
## Notes
|
|
43
57
|
- `export-report` and `export_tracking_data` are provisional until CLI export specs are finalized.
|
|
44
|
-
- `tools list`
|
|
45
|
-
-
|
|
58
|
+
- `tools list` ships a stable built-in registry of supported direct-call function targets. It is not live backend enumeration.
|
|
59
|
+
- `--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).
|
|
60
|
+
- `search-journey-run` supports CLI-managed async polling: `--async` starts backend async mode, polling is on by default, `--no-poll` returns the initial `runId`, and `--poll-interval <ms>` controls the status polling cadence.
|
|
61
|
+
- `socialseal agent run` now defaults to a fresh conversation. The CLI prints a continuation token to `stderr`; pass it back with `--continue <token>` to resume the same agent conversation explicitly.
|
|
62
|
+
- Effective workspace precedence is: `--workspace-id` → `SOCIALSEAL_WORKSPACE_ID` → config `workspaceId` → backend personal-workspace fallback.
|
|
63
|
+
- `socialseal workspace use ...` writes a local default workspace into `~/.config/socialseal/config.json`, which the CLI reuses for `agent`, `tools`, and `data` commands.
|
|
64
|
+
- `socialseal workspace list` discovers the workspaces accessible to the current CLI key and marks the active/suggested default.
|
|
65
|
+
- If a scoped CLI key cannot safely infer a workspace, `agent run` now fails closed and tells you to set `--workspace-id` or configure a local default first.
|
|
46
66
|
|
|
47
67
|
## Errors and exit codes
|
|
48
68
|
- Exit codes: `2` (usage), `3` (auth), `4` (not found), `5` (server), `1` (unknown)
|
|
49
69
|
- Add `--json` to `tools call` or `data` commands to emit machine-readable errors.
|
|
50
|
-
- Add `--verbose` to print error details
|
|
70
|
+
- Add `--verbose` to print error details plus agent session/tool progress diagnostics.
|
|
71
|
+
|
|
72
|
+
## Troubleshooting
|
|
73
|
+
- `SUPABASE_ANON_KEY not configured`
|
|
74
|
+
- This comes from the CLI gateway, not the local CLI install.
|
|
75
|
+
- The deployed gateway is missing its `SUPABASE_ANON_KEY` secret, so `/cli/tools/*` cannot proxy to Supabase Edge Functions.
|
|
76
|
+
- Fix on the server side with `wrangler secret put SUPABASE_ANON_KEY --env <staging|production>` for the gateway Worker, then re-run a `socialseal tools call ...` smoke test.
|
|
77
|
+
- `AI_UNSUPPORTED_LOCATION` or `The live agent is unavailable in this region right now.`
|
|
78
|
+
- This is raised when the upstream Gemini API rejects the worker egress location.
|
|
79
|
+
- The agent worker currently uses Google Gemini directly from Cloudflare Workers; there is no SocialSeal-side region allowlist in the CLI.
|
|
80
|
+
- If this reproduces from a supported Google AI region, treat it as an infrastructure/runtime issue. Practical workarounds are to run the agent from a worker placement/egress region that Google accepts, or switch the agent runtime to Vertex AI for server-side calls.
|
|
51
81
|
|
|
52
82
|
## Smoke Test (manual)
|
|
53
83
|
1. `SOCIALSEAL_API_KEY=... socialseal agent run --message "ping"`
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -11,7 +11,9 @@ 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 DEFAULT_POLL_INTERVAL_MS = 2000;
|
|
16
|
+
const MAX_TIMEOUT_MS = 900000;
|
|
15
17
|
const LEGACY_ENABLED = process.env.SOCIALSEAL_ENABLE_LEGACY === '1';
|
|
16
18
|
const EXIT_CODES = {
|
|
17
19
|
OK: 0,
|
|
@@ -22,10 +24,47 @@ const EXIT_CODES = {
|
|
|
22
24
|
SERVER: 5,
|
|
23
25
|
};
|
|
24
26
|
const HTTP_METHODS = new Set(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']);
|
|
25
|
-
const KNOWN_TOOLS = [
|
|
27
|
+
const KNOWN_TOOLS = [
|
|
28
|
+
{ name: 'agent-tool-jobs', category: 'agent', description: 'Poll queued agent-backed tool jobs and fetch their results.' },
|
|
29
|
+
{ name: 'deep-exploration-runs', category: 'agent', description: 'Read or persist deep exploration render runs.' },
|
|
30
|
+
{ name: 'workspace-notes', category: 'agent', description: 'Search, create, update, and pin workspace note memory.' },
|
|
31
|
+
{ name: 'workspace-onboarding', category: 'agent', description: 'Read or update workspace onboarding metadata used by the agent.' },
|
|
32
|
+
{ name: 'brand-group-management', category: 'brand', description: 'Manage brand groups, aliases, competitors, and rule configuration.' },
|
|
33
|
+
{ name: 'enqueue-brand-metrics-backfill', category: 'brand', description: 'Queue backfill jobs for brand metrics refreshes.' },
|
|
34
|
+
{ name: 'export-report', category: 'export', description: 'Generate report exports (csv/json/markdown/html/excel_data).' },
|
|
35
|
+
{ name: 'export_tracking_data', category: 'export', description: 'Stream tracking exports as CSV for a group or tracking item.' },
|
|
36
|
+
{ name: 'douyin-geo-api', category: 'search', description: 'Query Douyin search and geo data.' },
|
|
37
|
+
{ name: 'google-ai-search', category: 'search', description: 'Run Google AI search queries and fetch result snapshots.' },
|
|
38
|
+
{ name: 'instagram-geo-api', category: 'search', description: 'Query Instagram search and geo data.' },
|
|
39
|
+
{ name: 'tiktok-geo-api', category: 'search', description: 'Query TikTok search and geo data.' },
|
|
40
|
+
{ name: 'xhs-geo-api', category: 'search', description: 'Query Xiaohongshu search and geo data.' },
|
|
41
|
+
{ name: 'youtube-geo-api', category: 'search', description: 'Query YouTube search and geo data.' },
|
|
42
|
+
{ name: 'group-management', category: 'tracking', description: 'Create, update, list, and delete tracking groups and memberships.' },
|
|
43
|
+
{ name: 'tracking', category: 'tracking', description: 'Create, update, list, refresh, and delete tracking items.' },
|
|
44
|
+
{ name: 'journey-feedback', category: 'vnext', description: 'Record acceptance or rejection feedback for opportunity bundles.' },
|
|
45
|
+
{ name: 'opportunity-bundle-approve', category: 'vnext', description: 'Approve an opportunity bundle and create tracking coverage.' },
|
|
46
|
+
{ name: 'search-journey-run', category: 'vnext', description: 'Run a search journey for a subject across supported platforms.' },
|
|
47
|
+
{ name: 'vnext-blueprints-create', category: 'vnext', description: 'Create a vNext blueprint from grounded evidence.' },
|
|
48
|
+
{ name: 'vnext-blueprints-generate', category: 'vnext', description: 'Generate a vNext blueprint from workspace opportunity data.' },
|
|
49
|
+
{ name: 'vnext-blueprints-read', category: 'vnext', description: 'Read vNext blueprint history and specific versions.' },
|
|
50
|
+
{ name: 'vnext-briefs-create', category: 'vnext', description: 'Create a vNext brief record.' },
|
|
51
|
+
{ name: 'vnext-briefs-generate', category: 'vnext', description: 'Generate a vNext brief from a blueprint or opportunity.' },
|
|
52
|
+
{ name: 'vnext-briefs-read', category: 'vnext', description: 'Read generated vNext briefs and version history.' },
|
|
53
|
+
{ name: 'vnext-intents', category: 'vnext', description: 'List, create, update, or delete vNext intents.' },
|
|
54
|
+
{ name: 'vnext-journeys', category: 'vnext', description: 'List journey runs and inspect their latest outputs.' },
|
|
55
|
+
{ name: 'vnext-keywords', category: 'vnext', description: 'List, create, update, or delete vNext keywords.' },
|
|
56
|
+
{ name: 'vnext-personas', category: 'vnext', description: 'List, create, update, retire, or reactivate vNext personas.' },
|
|
57
|
+
{ name: 'vnext-pillars', category: 'vnext', description: 'List, create, update, or delete vNext content pillars.' },
|
|
58
|
+
{ name: 'vnext-topics', category: 'vnext', description: 'Manage topics, assignments, queues, and topic suggestions.' },
|
|
59
|
+
{ name: 'vnext-topics-auto-tag', category: 'vnext', description: 'Auto-tag keyword and topic assignments with Gemini-assisted review.' },
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
function getConfigPath() {
|
|
63
|
+
return process.env.SOCIALSEAL_CONFIG || DEFAULT_CONFIG_PATH;
|
|
64
|
+
}
|
|
26
65
|
|
|
27
66
|
function loadConfig() {
|
|
28
|
-
const configPath =
|
|
67
|
+
const configPath = getConfigPath();
|
|
29
68
|
try {
|
|
30
69
|
if (!fs.existsSync(configPath)) return {};
|
|
31
70
|
const raw = fs.readFileSync(configPath, 'utf8');
|
|
@@ -36,6 +75,15 @@ function loadConfig() {
|
|
|
36
75
|
}
|
|
37
76
|
}
|
|
38
77
|
|
|
78
|
+
function saveConfig(config) {
|
|
79
|
+
const configPath = getConfigPath();
|
|
80
|
+
const normalizedConfig = Object.fromEntries(
|
|
81
|
+
Object.entries(config || {}).filter(([, value]) => value !== undefined),
|
|
82
|
+
);
|
|
83
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
84
|
+
fs.writeFileSync(configPath, `${JSON.stringify(normalizedConfig, null, 2)}\n`);
|
|
85
|
+
}
|
|
86
|
+
|
|
39
87
|
function resolveApiKey(opts, config) {
|
|
40
88
|
return opts.apiKey || process.env.SOCIALSEAL_API_KEY || config.apiKey;
|
|
41
89
|
}
|
|
@@ -52,6 +100,19 @@ function resolveSupabaseUrl(opts, config) {
|
|
|
52
100
|
return opts.supabaseUrl || process.env.SOCIALSEAL_SUPABASE_URL || config.supabaseUrl;
|
|
53
101
|
}
|
|
54
102
|
|
|
103
|
+
function resolveWorkspaceSelection(opts, config) {
|
|
104
|
+
if (typeof opts.workspaceId === 'string' && opts.workspaceId.trim().length > 0) {
|
|
105
|
+
return { workspaceId: opts.workspaceId.trim(), source: 'flag' };
|
|
106
|
+
}
|
|
107
|
+
if (typeof process.env.SOCIALSEAL_WORKSPACE_ID === 'string' && process.env.SOCIALSEAL_WORKSPACE_ID.trim().length > 0) {
|
|
108
|
+
return { workspaceId: process.env.SOCIALSEAL_WORKSPACE_ID.trim(), source: 'env' };
|
|
109
|
+
}
|
|
110
|
+
if (typeof config.workspaceId === 'string' && config.workspaceId.trim().length > 0) {
|
|
111
|
+
return { workspaceId: config.workspaceId.trim(), source: 'config' };
|
|
112
|
+
}
|
|
113
|
+
return { workspaceId: null, source: null };
|
|
114
|
+
}
|
|
115
|
+
|
|
55
116
|
class CliError extends Error {
|
|
56
117
|
constructor(message, { code = 'CLI_ERROR', exitCode = EXIT_CODES.UNKNOWN, status, hint, details } = {}) {
|
|
57
118
|
super(message);
|
|
@@ -76,12 +137,11 @@ function normalizeMethod(method) {
|
|
|
76
137
|
return normalized;
|
|
77
138
|
}
|
|
78
139
|
|
|
79
|
-
function
|
|
80
|
-
|
|
81
|
-
if (raw == null || raw === '') return DEFAULT_TIMEOUT_MS;
|
|
140
|
+
function parseTimeoutMs(raw, { defaultValue = DEFAULT_TIMEOUT_MS, label = 'timeout' } = {}) {
|
|
141
|
+
if (raw == null || raw === '') return defaultValue;
|
|
82
142
|
const parsed = Number(raw);
|
|
83
143
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
84
|
-
throw new CliError(
|
|
144
|
+
throw new CliError(`Invalid ${label} value. Use a positive number of milliseconds.`, {
|
|
85
145
|
code: 'INVALID_TIMEOUT',
|
|
86
146
|
exitCode: EXIT_CODES.USAGE,
|
|
87
147
|
});
|
|
@@ -89,6 +149,31 @@ function resolveTimeoutMs(opts, config) {
|
|
|
89
149
|
return Math.min(parsed, MAX_TIMEOUT_MS);
|
|
90
150
|
}
|
|
91
151
|
|
|
152
|
+
function resolveTimeoutMs(opts, config) {
|
|
153
|
+
const raw = opts.timeout ?? process.env.SOCIALSEAL_TIMEOUT_MS ?? config.timeoutMs;
|
|
154
|
+
return parseTimeoutMs(raw, { defaultValue: DEFAULT_TIMEOUT_MS, label: 'timeout' });
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function resolveAgentIdleTimeoutMs(opts, config, fallbackTimeoutMs) {
|
|
158
|
+
const explicitIdleTimeout =
|
|
159
|
+
opts.idleTimeout
|
|
160
|
+
?? process.env.SOCIALSEAL_AGENT_IDLE_TIMEOUT_MS
|
|
161
|
+
?? config.agentIdleTimeoutMs;
|
|
162
|
+
if (explicitIdleTimeout != null && explicitIdleTimeout !== '') {
|
|
163
|
+
return parseTimeoutMs(explicitIdleTimeout, {
|
|
164
|
+
defaultValue: DEFAULT_AGENT_IDLE_TIMEOUT_MS,
|
|
165
|
+
label: 'idle timeout',
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const explicitTimeout = opts.timeout ?? process.env.SOCIALSEAL_TIMEOUT_MS ?? config.timeoutMs;
|
|
170
|
+
if (explicitTimeout != null && explicitTimeout !== '') {
|
|
171
|
+
return fallbackTimeoutMs;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return DEFAULT_AGENT_IDLE_TIMEOUT_MS;
|
|
175
|
+
}
|
|
176
|
+
|
|
92
177
|
function resolveLegacyUrl(value, label) {
|
|
93
178
|
if (!value) return null;
|
|
94
179
|
if (!LEGACY_ENABLED) {
|
|
@@ -101,6 +186,52 @@ function resolveLegacyUrl(value, label) {
|
|
|
101
186
|
return value;
|
|
102
187
|
}
|
|
103
188
|
|
|
189
|
+
function emitInfo(opts, message) {
|
|
190
|
+
if (opts?.verbose) {
|
|
191
|
+
process.stderr.write(`[socialseal] ${message}\n`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function formatCloseReason(reason) {
|
|
196
|
+
if (reason == null) return '';
|
|
197
|
+
if (Buffer.isBuffer(reason)) return reason.toString('utf8');
|
|
198
|
+
if (typeof reason === 'string') return reason;
|
|
199
|
+
return String(reason);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function readNodeResponseBody(response, limit = 2000) {
|
|
203
|
+
if (!response) return null;
|
|
204
|
+
|
|
205
|
+
return await new Promise((resolve) => {
|
|
206
|
+
const chunks = [];
|
|
207
|
+
let bufferedBytes = 0;
|
|
208
|
+
let totalBytes = 0;
|
|
209
|
+
let settled = false;
|
|
210
|
+
|
|
211
|
+
const finish = (value) => {
|
|
212
|
+
if (settled) return;
|
|
213
|
+
settled = true;
|
|
214
|
+
resolve(value);
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
response.on('data', (chunk) => {
|
|
218
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
|
|
219
|
+
totalBytes += buffer.length;
|
|
220
|
+
if (bufferedBytes >= limit) return;
|
|
221
|
+
|
|
222
|
+
const remaining = limit - bufferedBytes;
|
|
223
|
+
const slice = buffer.subarray(0, remaining);
|
|
224
|
+
chunks.push(slice);
|
|
225
|
+
bufferedBytes += slice.length;
|
|
226
|
+
});
|
|
227
|
+
response.on('end', () => {
|
|
228
|
+
const text = chunks.length > 0 ? Buffer.concat(chunks).toString('utf8') : '';
|
|
229
|
+
finish(totalBytes > limit ? `${text}…` : text);
|
|
230
|
+
});
|
|
231
|
+
response.on('error', () => finish(null));
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
104
235
|
function parseJsonInput(value, { label = 'payload', allowString = false } = {}) {
|
|
105
236
|
if (!value) return null;
|
|
106
237
|
if (value.startsWith('@')) {
|
|
@@ -148,6 +279,150 @@ function ensureJsonObject(value, label) {
|
|
|
148
279
|
return value;
|
|
149
280
|
}
|
|
150
281
|
|
|
282
|
+
function mergeWorkspaceIdIntoPayload(payload, workspaceId) {
|
|
283
|
+
if (!workspaceId) return payload;
|
|
284
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
285
|
+
return payload;
|
|
286
|
+
}
|
|
287
|
+
if (typeof payload.workspaceId === 'string' && payload.workspaceId.trim().length > 0) {
|
|
288
|
+
return payload;
|
|
289
|
+
}
|
|
290
|
+
return { ...payload, workspaceId };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function isJsonObject(value) {
|
|
294
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function sleep(ms) {
|
|
298
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function resolvePollIntervalMs(opts) {
|
|
302
|
+
const raw = opts.pollInterval ?? process.env.SOCIALSEAL_POLL_INTERVAL_MS;
|
|
303
|
+
return parseTimeoutMs(raw, { defaultValue: DEFAULT_POLL_INTERVAL_MS, label: 'poll interval' });
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function shouldHandleSearchJourneyRunAsync(functionName, method, payload, opts) {
|
|
307
|
+
if (String(functionName || '').trim() !== 'search-journey-run') return false;
|
|
308
|
+
if (method !== 'POST') return false;
|
|
309
|
+
if (!isJsonObject(payload)) return false;
|
|
310
|
+
if (payload.action === 'status') return false;
|
|
311
|
+
return opts.async === true || payload.executionMode === 'async';
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function applySearchJourneyRunAsyncStart(payload, opts) {
|
|
315
|
+
if (!shouldHandleSearchJourneyRunAsync(opts.function, normalizeMethod(opts.method), payload, opts)) {
|
|
316
|
+
return payload;
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
...payload,
|
|
320
|
+
executionMode: 'async',
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function formatJsonOutput(value, pretty) {
|
|
325
|
+
return JSON.stringify(value, null, pretty ? 2 : 0);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function emitJsonOutput(value, pretty) {
|
|
329
|
+
process.stdout.write(formatJsonOutput(value, pretty) + '\n');
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function buildSearchJourneyRunFailure(data) {
|
|
333
|
+
const message = isJsonObject(data) && typeof data.error === 'string' && data.error.trim().length > 0
|
|
334
|
+
? data.error
|
|
335
|
+
: 'search-journey-run failed';
|
|
336
|
+
return new CliError(message, {
|
|
337
|
+
code: 'ASYNC_RUN_FAILED',
|
|
338
|
+
exitCode: EXIT_CODES.SERVER,
|
|
339
|
+
details: truncateDetails(data),
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
async function pollSearchJourneyRun({
|
|
344
|
+
apiBase,
|
|
345
|
+
apiKey,
|
|
346
|
+
path,
|
|
347
|
+
workspaceId,
|
|
348
|
+
timeoutMs,
|
|
349
|
+
pollIntervalMs,
|
|
350
|
+
runId,
|
|
351
|
+
opts,
|
|
352
|
+
}) {
|
|
353
|
+
if (!workspaceId) {
|
|
354
|
+
throw new CliError('Async search-journey-run polling requires a workspace id.', {
|
|
355
|
+
code: 'WORKSPACE_REQUIRED',
|
|
356
|
+
exitCode: EXIT_CODES.USAGE,
|
|
357
|
+
hint: 'Pass --workspace-id, set SOCIALSEAL_WORKSPACE_ID, or configure a default workspace.',
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const deadline = Date.now() + timeoutMs;
|
|
362
|
+
let lastStatus = null;
|
|
363
|
+
|
|
364
|
+
for (;;) {
|
|
365
|
+
const remainingMs = deadline - Date.now();
|
|
366
|
+
if (remainingMs <= 0) {
|
|
367
|
+
throw new CliError('Timed out waiting for search-journey-run async completion.', {
|
|
368
|
+
code: 'ASYNC_WAIT_TIMEOUT',
|
|
369
|
+
exitCode: EXIT_CODES.SERVER,
|
|
370
|
+
hint: 'Increase --timeout <ms> or use --no-poll to return the run id immediately.',
|
|
371
|
+
details: truncateDetails({ runId, workspaceId, lastStatus }),
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
await sleep(Math.min(pollIntervalMs, remainingMs));
|
|
376
|
+
|
|
377
|
+
const res = await callApi({
|
|
378
|
+
apiBase,
|
|
379
|
+
apiKey,
|
|
380
|
+
path,
|
|
381
|
+
method: 'POST',
|
|
382
|
+
body: {
|
|
383
|
+
action: 'status',
|
|
384
|
+
workspaceId,
|
|
385
|
+
runId,
|
|
386
|
+
},
|
|
387
|
+
workspaceId,
|
|
388
|
+
timeoutMs: remainingMs,
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
if (!res.ok) {
|
|
392
|
+
throw await buildHttpError(res, {
|
|
393
|
+
label: 'Tool status poll',
|
|
394
|
+
functionName: 'search-journey-run',
|
|
395
|
+
method: 'POST',
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const contentType = res.headers.get('content-type') || '';
|
|
400
|
+
if (!contentType.includes('application/json')) {
|
|
401
|
+
throw new CliError('search-journey-run status poll returned a non-JSON response.', {
|
|
402
|
+
code: 'INVALID_STATUS_RESPONSE',
|
|
403
|
+
exitCode: EXIT_CODES.SERVER,
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const data = await res.json();
|
|
408
|
+
const status = isJsonObject(data) && typeof data.status === 'string' ? data.status : null;
|
|
409
|
+
if (status && status !== lastStatus) {
|
|
410
|
+
emitInfo(opts, `search-journey-run status: ${status}`);
|
|
411
|
+
lastStatus = status;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (status === 'completed') return data;
|
|
415
|
+
if (status === 'failed') throw buildSearchJourneyRunFailure(data);
|
|
416
|
+
if (status === 'pending' || status === 'processing') continue;
|
|
417
|
+
|
|
418
|
+
throw new CliError('search-journey-run status poll returned an unexpected payload.', {
|
|
419
|
+
code: 'INVALID_STATUS_RESPONSE',
|
|
420
|
+
exitCode: EXIT_CODES.SERVER,
|
|
421
|
+
details: truncateDetails(data),
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
151
426
|
function mapStatusToExitCode(status) {
|
|
152
427
|
if (status === 401 || status === 403) return EXIT_CODES.AUTH;
|
|
153
428
|
if (status === 404) return EXIT_CODES.NOT_FOUND;
|
|
@@ -231,7 +506,11 @@ function emitError(err, opts = {}) {
|
|
|
231
506
|
process.stderr.write(`[socialseal] ${payload.error.hint}\n`);
|
|
232
507
|
}
|
|
233
508
|
if (showDetails && payload.error.details) {
|
|
234
|
-
|
|
509
|
+
const detailsText =
|
|
510
|
+
typeof payload.error.details === 'string'
|
|
511
|
+
? payload.error.details
|
|
512
|
+
: JSON.stringify(payload.error.details);
|
|
513
|
+
process.stderr.write(`[socialseal] Details: ${detailsText}\n`);
|
|
235
514
|
} else if (!showDetails && err.details) {
|
|
236
515
|
process.stderr.write('[socialseal] Use --verbose to see error details.\n');
|
|
237
516
|
}
|
|
@@ -329,6 +608,68 @@ async function callApi({ apiBase, apiKey, path, method = 'POST', body, workspace
|
|
|
329
608
|
return res;
|
|
330
609
|
}
|
|
331
610
|
|
|
611
|
+
async function fetchWorkspaceDirectory({ apiBase, apiKey, timeoutMs }) {
|
|
612
|
+
const res = await callApi({
|
|
613
|
+
apiBase,
|
|
614
|
+
apiKey,
|
|
615
|
+
path: '/cli/workspaces',
|
|
616
|
+
method: 'GET',
|
|
617
|
+
timeoutMs,
|
|
618
|
+
});
|
|
619
|
+
if (!res.ok) {
|
|
620
|
+
throw await buildHttpError(res, { label: 'Workspace discovery' });
|
|
621
|
+
}
|
|
622
|
+
const payload = await res.json();
|
|
623
|
+
return payload?.data || {};
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function matchWorkspaceIdentifier(workspaces, identifier) {
|
|
627
|
+
const normalized = String(identifier || '').trim();
|
|
628
|
+
if (!normalized) {
|
|
629
|
+
throw new CliError('Missing workspace identifier.', {
|
|
630
|
+
code: 'MISSING_ARGUMENT',
|
|
631
|
+
exitCode: EXIT_CODES.USAGE,
|
|
632
|
+
hint: 'Use a workspace id, slug, or exact name from `socialseal workspace list`.',
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const exactId = workspaces.find((workspace) => workspace.id === normalized);
|
|
637
|
+
if (exactId) return exactId;
|
|
638
|
+
|
|
639
|
+
const exactSlug = workspaces.find((workspace) => workspace.slug === normalized);
|
|
640
|
+
if (exactSlug) return exactSlug;
|
|
641
|
+
|
|
642
|
+
const exactNameMatches = workspaces.filter(
|
|
643
|
+
(workspace) => typeof workspace.name === 'string' && workspace.name.trim().toLowerCase() === normalized.toLowerCase(),
|
|
644
|
+
);
|
|
645
|
+
if (exactNameMatches.length === 1) {
|
|
646
|
+
return exactNameMatches[0];
|
|
647
|
+
}
|
|
648
|
+
if (exactNameMatches.length > 1) {
|
|
649
|
+
throw new CliError(`Workspace name "${normalized}" is ambiguous.`, {
|
|
650
|
+
code: 'AMBIGUOUS_WORKSPACE',
|
|
651
|
+
exitCode: EXIT_CODES.USAGE,
|
|
652
|
+
hint: 'Use the workspace id or slug from `socialseal workspace list`.',
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
throw new CliError(`Workspace "${normalized}" was not found.`, {
|
|
657
|
+
code: 'WORKSPACE_NOT_FOUND',
|
|
658
|
+
exitCode: EXIT_CODES.NOT_FOUND,
|
|
659
|
+
hint: 'Run `socialseal workspace list` to discover available workspaces.',
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
function formatWorkspaceLine(workspace, { isEffective = false, source = null, isSuggested = false } = {}) {
|
|
664
|
+
const tags = [];
|
|
665
|
+
if (workspace.isPersonalWorkspace) tags.push('personal');
|
|
666
|
+
if (isEffective) tags.push(source === 'config' ? 'default' : `active:${source}`);
|
|
667
|
+
if (isSuggested) tags.push('suggested');
|
|
668
|
+
const tagText = tags.length > 0 ? ` [${tags.join(', ')}]` : '';
|
|
669
|
+
const slugText = workspace.slug ? ` slug=${workspace.slug}` : '';
|
|
670
|
+
return `- ${workspace.name} (${workspace.id}) role=${workspace.role}${slugText}${tagText}`;
|
|
671
|
+
}
|
|
672
|
+
|
|
332
673
|
async function handleAgentRun(opts) {
|
|
333
674
|
const config = loadConfig();
|
|
334
675
|
const apiKey = requireApiKey(opts, config);
|
|
@@ -336,12 +677,34 @@ async function handleAgentRun(opts) {
|
|
|
336
677
|
const agentUrl = resolveLegacyUrl(resolveAgentUrl(opts, config), 'SOCIALSEAL_AGENT_URL');
|
|
337
678
|
const { resolvedApiBase, legacyUrl } = resolveApiTarget({ apiBase, legacyUrl: agentUrl });
|
|
338
679
|
const timeoutMs = resolveTimeoutMs(opts, config);
|
|
680
|
+
const idleTimeoutMs = resolveAgentIdleTimeoutMs(opts, config, timeoutMs);
|
|
681
|
+
const continuationToken = typeof opts.continue === 'string' ? opts.continue.trim() : '';
|
|
682
|
+
const { workspaceId: resolvedWorkspaceIdInput } = resolveWorkspaceSelection(opts, config);
|
|
683
|
+
|
|
684
|
+
if (continuationToken && opts.conversationId) {
|
|
685
|
+
throw new CliError('Use either --continue or --conversation-id, not both.', {
|
|
686
|
+
code: 'INVALID_ARGUMENTS',
|
|
687
|
+
exitCode: EXIT_CODES.USAGE,
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
if (continuationToken && opts.createNew) {
|
|
691
|
+
throw new CliError('Use either --continue or --create-new, not both.', {
|
|
692
|
+
code: 'INVALID_ARGUMENTS',
|
|
693
|
+
exitCode: EXIT_CODES.USAGE,
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
if (opts.conversationId && opts.createNew) {
|
|
697
|
+
throw new CliError('Use either --conversation-id or --create-new, not both.', {
|
|
698
|
+
code: 'INVALID_ARGUMENTS',
|
|
699
|
+
exitCode: EXIT_CODES.USAGE,
|
|
700
|
+
});
|
|
701
|
+
}
|
|
339
702
|
|
|
340
703
|
const headers = {
|
|
341
704
|
'Content-Type': 'application/json',
|
|
342
705
|
[CLI_KEY_HEADER]: apiKey,
|
|
343
706
|
};
|
|
344
|
-
if (
|
|
707
|
+
if (resolvedWorkspaceIdInput) headers[WORKSPACE_HEADER] = resolvedWorkspaceIdInput;
|
|
345
708
|
|
|
346
709
|
const sessionUrl = resolvedApiBase
|
|
347
710
|
? `${resolvedApiBase.replace(/\/$/, '')}/cli/agent/session`
|
|
@@ -351,8 +714,9 @@ async function handleAgentRun(opts) {
|
|
|
351
714
|
method: 'POST',
|
|
352
715
|
headers,
|
|
353
716
|
body: JSON.stringify({
|
|
354
|
-
|
|
355
|
-
|
|
717
|
+
continuationToken: continuationToken || undefined,
|
|
718
|
+
conversationId: continuationToken ? undefined : (opts.conversationId || undefined),
|
|
719
|
+
createNew: continuationToken || opts.conversationId ? undefined : true,
|
|
356
720
|
}),
|
|
357
721
|
}, timeoutMs);
|
|
358
722
|
|
|
@@ -361,6 +725,10 @@ async function handleAgentRun(opts) {
|
|
|
361
725
|
}
|
|
362
726
|
|
|
363
727
|
const sessionData = await sessionRes.json();
|
|
728
|
+
const sessionId = sessionData?.data?.sessionId || null;
|
|
729
|
+
const initialConversationId = sessionData?.data?.activeConversationId || opts.conversationId || null;
|
|
730
|
+
const resolvedWorkspaceId = sessionData?.data?.workspaceId || resolvedWorkspaceIdInput || null;
|
|
731
|
+
const nextContinuationToken = sessionData?.data?.continuationToken || null;
|
|
364
732
|
const wsUrl = sessionData?.data?.websocketUrl;
|
|
365
733
|
if (!wsUrl) {
|
|
366
734
|
throw new CliError('Missing websocketUrl in session response.', {
|
|
@@ -368,6 +736,23 @@ async function handleAgentRun(opts) {
|
|
|
368
736
|
exitCode: EXIT_CODES.SERVER,
|
|
369
737
|
});
|
|
370
738
|
}
|
|
739
|
+
emitInfo(
|
|
740
|
+
opts,
|
|
741
|
+
`Agent session created${sessionId ? ` (session ${sessionId})` : ''}${initialConversationId ? ` for conversation ${initialConversationId}` : ''}.`,
|
|
742
|
+
);
|
|
743
|
+
if (opts.json) {
|
|
744
|
+
process.stdout.write(JSON.stringify({
|
|
745
|
+
type: 'session_bootstrap',
|
|
746
|
+
payload: {
|
|
747
|
+
sessionId,
|
|
748
|
+
conversationId: initialConversationId,
|
|
749
|
+
workspaceId: resolvedWorkspaceId,
|
|
750
|
+
continuationToken: nextContinuationToken,
|
|
751
|
+
},
|
|
752
|
+
}) + '\n');
|
|
753
|
+
} else if (nextContinuationToken) {
|
|
754
|
+
process.stderr.write(`[socialseal] Continuation token: ${nextContinuationToken}\n`);
|
|
755
|
+
}
|
|
371
756
|
|
|
372
757
|
const context = parseJsonInput(opts.context, { label: 'context', allowString: true });
|
|
373
758
|
const message = opts.message;
|
|
@@ -375,10 +760,36 @@ async function handleAgentRun(opts) {
|
|
|
375
760
|
await new Promise((resolve, reject) => {
|
|
376
761
|
const ws = new WebSocket(wsUrl);
|
|
377
762
|
let finished = false;
|
|
763
|
+
let settled = false;
|
|
378
764
|
let inactivityTimer = null;
|
|
765
|
+
let sawAssistantChunk = false;
|
|
766
|
+
let sawToolCall = false;
|
|
767
|
+
let sawThinking = false;
|
|
768
|
+
let lastMessageType = 'none';
|
|
769
|
+
let activeConversationId = initialConversationId;
|
|
770
|
+
const toolProgressStatus = new Map();
|
|
771
|
+
|
|
772
|
+
const settleResolve = () => {
|
|
773
|
+
if (settled) return;
|
|
774
|
+
settled = true;
|
|
775
|
+
if (inactivityTimer) clearTimeout(inactivityTimer);
|
|
776
|
+
resolve();
|
|
777
|
+
};
|
|
778
|
+
|
|
779
|
+
const settleReject = (error) => {
|
|
780
|
+
if (settled) return;
|
|
781
|
+
settled = true;
|
|
782
|
+
if (inactivityTimer) clearTimeout(inactivityTimer);
|
|
783
|
+
try {
|
|
784
|
+
ws.terminate();
|
|
785
|
+
} catch {
|
|
786
|
+
// ignore
|
|
787
|
+
}
|
|
788
|
+
reject(error);
|
|
789
|
+
};
|
|
379
790
|
|
|
380
791
|
const resetInactivity = () => {
|
|
381
|
-
if (!
|
|
792
|
+
if (!idleTimeoutMs) return;
|
|
382
793
|
if (inactivityTimer) clearTimeout(inactivityTimer);
|
|
383
794
|
inactivityTimer = setTimeout(() => {
|
|
384
795
|
try {
|
|
@@ -386,38 +797,74 @@ async function handleAgentRun(opts) {
|
|
|
386
797
|
} catch {
|
|
387
798
|
// ignore
|
|
388
799
|
}
|
|
389
|
-
|
|
800
|
+
settleReject(new CliError('WebSocket timed out waiting for agent response.', {
|
|
390
801
|
code: 'WEBSOCKET_TIMEOUT',
|
|
391
802
|
exitCode: EXIT_CODES.SERVER,
|
|
392
|
-
hint: 'Increase the timeout with --timeout <ms>.',
|
|
803
|
+
hint: 'Increase the timeout with --idle-timeout <ms> or --timeout <ms>.',
|
|
804
|
+
details: truncateDetails({
|
|
805
|
+
sessionId,
|
|
806
|
+
activeConversationId,
|
|
807
|
+
lastMessageType,
|
|
808
|
+
sawAssistantChunk,
|
|
809
|
+
sawToolCall,
|
|
810
|
+
sawThinking,
|
|
811
|
+
idleTimeoutMs,
|
|
812
|
+
}),
|
|
393
813
|
}));
|
|
394
|
-
},
|
|
814
|
+
}, idleTimeoutMs);
|
|
395
815
|
};
|
|
396
816
|
|
|
397
817
|
ws.on('open', () => {
|
|
398
818
|
resetInactivity();
|
|
819
|
+
emitInfo(opts, 'Connected to agent WebSocket.');
|
|
399
820
|
const payload = {
|
|
400
821
|
type: 'user_message',
|
|
401
822
|
payload: { content: message, context: context || undefined },
|
|
402
823
|
timestamp: Date.now(),
|
|
403
824
|
};
|
|
404
825
|
ws.send(JSON.stringify(payload));
|
|
826
|
+
emitInfo(opts, 'User message sent to agent.');
|
|
405
827
|
});
|
|
406
828
|
|
|
407
829
|
ws.on('message', (data) => {
|
|
408
830
|
try {
|
|
409
831
|
resetInactivity();
|
|
410
832
|
const msg = JSON.parse(data.toString());
|
|
833
|
+
lastMessageType = msg.type || 'unknown';
|
|
834
|
+
|
|
835
|
+
if (msg.type === 'session_state' && msg.payload?.activeConversationId) {
|
|
836
|
+
activeConversationId = msg.payload.activeConversationId;
|
|
837
|
+
emitInfo(
|
|
838
|
+
opts,
|
|
839
|
+
`Session state received${sessionId ? ` for session ${sessionId}` : ''}${activeConversationId ? ` (conversation ${activeConversationId})` : ''}.`,
|
|
840
|
+
);
|
|
841
|
+
}
|
|
842
|
+
|
|
411
843
|
if (opts.json) {
|
|
412
844
|
process.stdout.write(JSON.stringify(msg) + '\n');
|
|
413
845
|
if (msg.type === 'assistant_chunk' && msg.payload?.done) {
|
|
414
846
|
finished = true;
|
|
415
847
|
ws.close(1000, 'done');
|
|
416
848
|
}
|
|
849
|
+
if (msg.type === 'error') {
|
|
850
|
+
const payload = msg.payload || {};
|
|
851
|
+
settleReject(new CliError(`Agent error: ${payload.message || 'unknown'}`, {
|
|
852
|
+
code: payload.code || 'AGENT_ERROR',
|
|
853
|
+
exitCode: EXIT_CODES.SERVER,
|
|
854
|
+
hint: payload.retryable ? 'Retry the request or inspect backend status.' : null,
|
|
855
|
+
details: truncateDetails({
|
|
856
|
+
...payload,
|
|
857
|
+
sessionId,
|
|
858
|
+
activeConversationId,
|
|
859
|
+
lastMessageType,
|
|
860
|
+
}),
|
|
861
|
+
}));
|
|
862
|
+
}
|
|
417
863
|
return;
|
|
418
864
|
}
|
|
419
865
|
if (msg.type === 'assistant_chunk') {
|
|
420
866
|
const chunk = msg.payload?.chunk ?? '';
|
|
867
|
+
sawAssistantChunk = sawAssistantChunk || chunk.length > 0 || !!msg.payload?.done;
|
|
421
868
|
if (chunk) process.stdout.write(chunk);
|
|
422
869
|
if (msg.payload?.done) {
|
|
423
870
|
finished = true;
|
|
@@ -425,28 +872,114 @@ async function handleAgentRun(opts) {
|
|
|
425
872
|
ws.close(1000, 'done');
|
|
426
873
|
}
|
|
427
874
|
} else if (msg.type === 'error') {
|
|
428
|
-
|
|
875
|
+
const payload = msg.payload || {};
|
|
876
|
+
settleReject(new CliError(`Agent error: ${payload.message || 'unknown'}`, {
|
|
877
|
+
code: payload.code || 'AGENT_ERROR',
|
|
878
|
+
exitCode: EXIT_CODES.SERVER,
|
|
879
|
+
hint: payload.retryable ? 'Retry the request or inspect backend status.' : null,
|
|
880
|
+
details: truncateDetails({
|
|
881
|
+
...payload,
|
|
882
|
+
sessionId,
|
|
883
|
+
activeConversationId,
|
|
884
|
+
lastMessageType,
|
|
885
|
+
}),
|
|
886
|
+
}));
|
|
887
|
+
} else if (msg.type === 'thinking_chunk') {
|
|
888
|
+
sawThinking = true;
|
|
889
|
+
emitInfo(opts, 'Agent is thinking.');
|
|
890
|
+
} else if (msg.type === 'assistant_status') {
|
|
891
|
+
const code = msg.payload?.code || 'unknown';
|
|
892
|
+
const statusMessage = msg.payload?.message || 'Agent reported a status update.';
|
|
893
|
+
emitInfo(opts, `Agent status [${code}]: ${statusMessage}`);
|
|
894
|
+
} else if (msg.type === 'tool_call_start') {
|
|
895
|
+
sawToolCall = true;
|
|
896
|
+
emitInfo(opts, `Tool start: ${msg.payload?.name || 'unknown'}`);
|
|
897
|
+
} else if (msg.type === 'tool_call_progress') {
|
|
898
|
+
const toolCallId = msg.payload?.toolCallId || '';
|
|
899
|
+
const progressStatus = msg.payload?.status || 'running';
|
|
900
|
+
if (toolProgressStatus.get(toolCallId) !== progressStatus) {
|
|
901
|
+
toolProgressStatus.set(toolCallId, progressStatus);
|
|
902
|
+
emitInfo(opts, `Tool progress: ${progressStatus}`);
|
|
903
|
+
}
|
|
904
|
+
} else if (msg.type === 'tool_call_complete') {
|
|
905
|
+
const error = msg.payload?.error;
|
|
906
|
+
const duration = typeof msg.payload?.duration_ms === 'number'
|
|
907
|
+
? `${msg.payload.duration_ms}ms`
|
|
908
|
+
: 'unknown duration';
|
|
909
|
+
if (error) {
|
|
910
|
+
emitInfo(opts, `Tool failed after ${duration}: ${error}`);
|
|
911
|
+
} else {
|
|
912
|
+
emitInfo(opts, `Tool completed in ${duration}.`);
|
|
913
|
+
}
|
|
429
914
|
}
|
|
430
915
|
} catch (err) {
|
|
431
|
-
|
|
916
|
+
settleReject(new CliError(`Failed to parse agent message: ${err.message || err}`, {
|
|
917
|
+
code: 'INVALID_AGENT_MESSAGE',
|
|
918
|
+
exitCode: EXIT_CODES.SERVER,
|
|
919
|
+
details: data.toString(),
|
|
920
|
+
}));
|
|
432
921
|
}
|
|
433
922
|
});
|
|
434
923
|
|
|
435
|
-
ws.on('
|
|
436
|
-
|
|
924
|
+
ws.on('unexpected-response', async (_req, response) => {
|
|
925
|
+
const statusText = response.statusCode
|
|
926
|
+
? `${response.statusCode}${response.statusMessage ? ` ${response.statusMessage}` : ''}`
|
|
927
|
+
: 'unknown';
|
|
928
|
+
const details = await readNodeResponseBody(response);
|
|
929
|
+
settleReject(new CliError(`WebSocket upgrade failed: ${statusText}`.trim(), {
|
|
930
|
+
code: 'WEBSOCKET_UPGRADE_FAILED',
|
|
931
|
+
exitCode:
|
|
932
|
+
response.statusCode === 401 || response.statusCode === 403
|
|
933
|
+
? EXIT_CODES.AUTH
|
|
934
|
+
: EXIT_CODES.SERVER,
|
|
935
|
+
hint:
|
|
936
|
+
response.statusCode === 401 || response.statusCode === 403
|
|
937
|
+
? 'Check your CLI key, workspace scope, and session endpoint auth.'
|
|
938
|
+
: 'Retry with --verbose to inspect gateway or backend behavior.',
|
|
939
|
+
details: truncateDetails({
|
|
940
|
+
sessionId,
|
|
941
|
+
activeConversationId,
|
|
942
|
+
responseBody: details,
|
|
943
|
+
}),
|
|
944
|
+
}));
|
|
945
|
+
});
|
|
946
|
+
|
|
947
|
+
ws.on('close', (code, reason) => {
|
|
948
|
+
const closeReason = formatCloseReason(reason);
|
|
437
949
|
if (!finished) {
|
|
438
|
-
|
|
439
|
-
code: '
|
|
440
|
-
|
|
441
|
-
|
|
950
|
+
settleReject(new CliError(
|
|
951
|
+
`WebSocket closed before completion (code ${code}${closeReason ? `: ${closeReason}` : ''}).`,
|
|
952
|
+
{
|
|
953
|
+
code: 'WEBSOCKET_CLOSED',
|
|
954
|
+
exitCode: EXIT_CODES.SERVER,
|
|
955
|
+
hint: sawAssistantChunk
|
|
956
|
+
? 'The agent disconnected mid-response. Retry the request.'
|
|
957
|
+
: 'The agent closed the connection before completing. Retry with --verbose for more diagnostics.',
|
|
958
|
+
details: truncateDetails({
|
|
959
|
+
sessionId,
|
|
960
|
+
activeConversationId,
|
|
961
|
+
lastMessageType,
|
|
962
|
+
sawAssistantChunk,
|
|
963
|
+
sawToolCall,
|
|
964
|
+
sawThinking,
|
|
965
|
+
}),
|
|
966
|
+
},
|
|
967
|
+
));
|
|
442
968
|
} else {
|
|
443
|
-
|
|
969
|
+
settleResolve();
|
|
444
970
|
}
|
|
445
971
|
});
|
|
446
972
|
|
|
447
973
|
ws.on('error', (err) => {
|
|
448
|
-
|
|
449
|
-
|
|
974
|
+
settleReject(new CliError(`WebSocket error: ${err.message || err}`, {
|
|
975
|
+
code: 'WEBSOCKET_ERROR',
|
|
976
|
+
exitCode: EXIT_CODES.SERVER,
|
|
977
|
+
details: truncateDetails({
|
|
978
|
+
sessionId,
|
|
979
|
+
activeConversationId,
|
|
980
|
+
lastMessageType,
|
|
981
|
+
}),
|
|
982
|
+
}));
|
|
450
983
|
});
|
|
451
984
|
});
|
|
452
985
|
}
|
|
@@ -458,16 +991,19 @@ async function handleToolsCall(opts) {
|
|
|
458
991
|
const supabaseUrl = resolveLegacyUrl(resolveSupabaseUrl(opts, config), 'SOCIALSEAL_SUPABASE_URL');
|
|
459
992
|
const { resolvedApiBase, legacyUrl, useGateway } = resolveApiTarget({ apiBase, legacyUrl: supabaseUrl });
|
|
460
993
|
const timeoutMs = resolveTimeoutMs(opts, config);
|
|
994
|
+
const { workspaceId: resolvedWorkspaceId } = resolveWorkspaceSelection(opts, config);
|
|
461
995
|
|
|
462
|
-
const
|
|
996
|
+
const parsedPayload = parseJsonInput(opts.body, { label: 'body' }) ?? {};
|
|
997
|
+
const mergedPayload = mergeWorkspaceIdIntoPayload(parsedPayload, resolvedWorkspaceId);
|
|
463
998
|
const method = normalizeMethod(opts.method);
|
|
999
|
+
const payload = applySearchJourneyRunAsyncStart(mergedPayload, { ...opts, method });
|
|
464
1000
|
const res = await callApi({
|
|
465
1001
|
apiBase: useGateway ? resolvedApiBase : legacyUrl,
|
|
466
1002
|
apiKey,
|
|
467
1003
|
path: useGateway ? `/cli/tools/${opts.function}` : `/functions/v1/${opts.function}`,
|
|
468
1004
|
method,
|
|
469
1005
|
body: payload,
|
|
470
|
-
workspaceId:
|
|
1006
|
+
workspaceId: resolvedWorkspaceId,
|
|
471
1007
|
timeoutMs,
|
|
472
1008
|
});
|
|
473
1009
|
|
|
@@ -482,7 +1018,45 @@ async function handleToolsCall(opts) {
|
|
|
482
1018
|
const contentType = res.headers.get('content-type') || '';
|
|
483
1019
|
if (contentType.includes('application/json')) {
|
|
484
1020
|
const data = await res.json();
|
|
485
|
-
|
|
1021
|
+
const shouldPoll = shouldHandleSearchJourneyRunAsync(opts.function, method, payload, opts) && opts.poll !== false;
|
|
1022
|
+
if (!shouldPoll) {
|
|
1023
|
+
emitJsonOutput(data, opts.pretty);
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
const startStatus = isJsonObject(data) && typeof data.status === 'string' ? data.status : null;
|
|
1028
|
+
if (startStatus === 'failed') {
|
|
1029
|
+
throw buildSearchJourneyRunFailure(data);
|
|
1030
|
+
}
|
|
1031
|
+
if (startStatus === 'completed') {
|
|
1032
|
+
emitJsonOutput(data, opts.pretty);
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
const runId = isJsonObject(data) && typeof data.runId === 'string' ? data.runId : null;
|
|
1037
|
+
if (!runId) {
|
|
1038
|
+
throw new CliError('Async search-journey-run start response did not include a runId.', {
|
|
1039
|
+
code: 'INVALID_START_RESPONSE',
|
|
1040
|
+
exitCode: EXIT_CODES.SERVER,
|
|
1041
|
+
details: truncateDetails(data),
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
emitInfo(opts, `search-journey-run async run started: ${runId}`);
|
|
1046
|
+
const finalData = await pollSearchJourneyRun({
|
|
1047
|
+
apiBase: useGateway ? resolvedApiBase : legacyUrl,
|
|
1048
|
+
apiKey,
|
|
1049
|
+
path: useGateway ? `/cli/tools/${opts.function}` : `/functions/v1/${opts.function}`,
|
|
1050
|
+
workspaceId: isJsonObject(payload) && typeof payload.workspaceId === 'string'
|
|
1051
|
+
? payload.workspaceId
|
|
1052
|
+
: resolvedWorkspaceId,
|
|
1053
|
+
timeoutMs,
|
|
1054
|
+
pollIntervalMs: resolvePollIntervalMs(opts),
|
|
1055
|
+
runId,
|
|
1056
|
+
opts,
|
|
1057
|
+
});
|
|
1058
|
+
|
|
1059
|
+
emitJsonOutput(finalData, opts.pretty);
|
|
486
1060
|
return;
|
|
487
1061
|
}
|
|
488
1062
|
|
|
@@ -492,8 +1066,9 @@ async function handleToolsCall(opts) {
|
|
|
492
1066
|
|
|
493
1067
|
function handleToolsList(opts) {
|
|
494
1068
|
const payload = {
|
|
1069
|
+
discovery: 'built_in_registry',
|
|
495
1070
|
tools: KNOWN_TOOLS,
|
|
496
|
-
note: '
|
|
1071
|
+
note: 'This registry is shipped with the CLI for stable discovery. It is not live backend enumeration.',
|
|
497
1072
|
};
|
|
498
1073
|
|
|
499
1074
|
if (opts.json) {
|
|
@@ -501,8 +1076,19 @@ function handleToolsList(opts) {
|
|
|
501
1076
|
return;
|
|
502
1077
|
}
|
|
503
1078
|
|
|
504
|
-
process.stdout.write('[socialseal]
|
|
1079
|
+
process.stdout.write('[socialseal] Built-in tool registry\n');
|
|
505
1080
|
process.stdout.write(`[socialseal] ${payload.note}\n`);
|
|
1081
|
+
|
|
1082
|
+
let currentCategory = null;
|
|
1083
|
+
for (const tool of KNOWN_TOOLS) {
|
|
1084
|
+
if (tool.category !== currentCategory) {
|
|
1085
|
+
currentCategory = tool.category;
|
|
1086
|
+
process.stdout.write(`\n${currentCategory}\n`);
|
|
1087
|
+
}
|
|
1088
|
+
process.stdout.write(`- ${tool.name}: ${tool.description}\n`);
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
process.stdout.write('\n[socialseal] Call a tool with: socialseal tools call --function <name> --body @payload.json\n');
|
|
506
1092
|
}
|
|
507
1093
|
|
|
508
1094
|
async function handleDataExportTracking(opts) {
|
|
@@ -512,6 +1098,7 @@ async function handleDataExportTracking(opts) {
|
|
|
512
1098
|
const supabaseUrl = resolveLegacyUrl(resolveSupabaseUrl(opts, config), 'SOCIALSEAL_SUPABASE_URL');
|
|
513
1099
|
const { resolvedApiBase, legacyUrl, useGateway } = resolveApiTarget({ apiBase, legacyUrl: supabaseUrl });
|
|
514
1100
|
const timeoutMs = resolveTimeoutMs(opts, config);
|
|
1101
|
+
const { workspaceId: resolvedWorkspaceId } = resolveWorkspaceSelection(opts, config);
|
|
515
1102
|
|
|
516
1103
|
if (!opts.groupId && !opts.itemId) {
|
|
517
1104
|
throw new CliError('Provide --group-id or --item-id.', {
|
|
@@ -532,7 +1119,7 @@ async function handleDataExportTracking(opts) {
|
|
|
532
1119
|
path: useGateway ? '/cli/tools/export_tracking_data' : '/functions/v1/export_tracking_data',
|
|
533
1120
|
method: 'POST',
|
|
534
1121
|
body: payload,
|
|
535
|
-
workspaceId:
|
|
1122
|
+
workspaceId: resolvedWorkspaceId,
|
|
536
1123
|
timeoutMs,
|
|
537
1124
|
});
|
|
538
1125
|
|
|
@@ -540,6 +1127,13 @@ async function handleDataExportTracking(opts) {
|
|
|
540
1127
|
throw await buildHttpError(res, { label: 'Tracking export' });
|
|
541
1128
|
}
|
|
542
1129
|
|
|
1130
|
+
if (!res.body) {
|
|
1131
|
+
throw new CliError('Export response contained no body.', {
|
|
1132
|
+
code: 'EMPTY_RESPONSE',
|
|
1133
|
+
exitCode: EXIT_CODES.SERVER,
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
|
|
543
1137
|
const outPath = opts.stdout ? null : (opts.out || 'tracking_export.csv');
|
|
544
1138
|
if (outPath) {
|
|
545
1139
|
await pipeline(res.body, fs.createWriteStream(outPath));
|
|
@@ -556,6 +1150,7 @@ async function handleDataExportReport(opts) {
|
|
|
556
1150
|
const supabaseUrl = resolveLegacyUrl(resolveSupabaseUrl(opts, config), 'SOCIALSEAL_SUPABASE_URL');
|
|
557
1151
|
const { resolvedApiBase, legacyUrl, useGateway } = resolveApiTarget({ apiBase, legacyUrl: supabaseUrl });
|
|
558
1152
|
const timeoutMs = resolveTimeoutMs(opts, config);
|
|
1153
|
+
const { workspaceId: resolvedWorkspaceId } = resolveWorkspaceSelection(opts, config);
|
|
559
1154
|
|
|
560
1155
|
const payload = ensureJsonObject(parseJsonInput(opts.payload, { label: 'payload' }), 'payload');
|
|
561
1156
|
|
|
@@ -569,7 +1164,7 @@ async function handleDataExportReport(opts) {
|
|
|
569
1164
|
format: opts.format,
|
|
570
1165
|
payload,
|
|
571
1166
|
},
|
|
572
|
-
workspaceId:
|
|
1167
|
+
workspaceId: resolvedWorkspaceId,
|
|
573
1168
|
timeoutMs,
|
|
574
1169
|
});
|
|
575
1170
|
|
|
@@ -611,11 +1206,155 @@ async function handleDataExportReport(opts) {
|
|
|
611
1206
|
process.stdout.write(JSON.stringify(json, null, opts.pretty ? 2 : 0) + '\n');
|
|
612
1207
|
}
|
|
613
1208
|
|
|
1209
|
+
async function handleWorkspaceList(opts) {
|
|
1210
|
+
const config = loadConfig();
|
|
1211
|
+
const apiKey = requireApiKey(opts, config);
|
|
1212
|
+
const apiBase = resolveApiBase(opts, config);
|
|
1213
|
+
const { resolvedApiBase } = resolveApiTarget({ apiBase, legacyUrl: null });
|
|
1214
|
+
const timeoutMs = resolveTimeoutMs(opts, config);
|
|
1215
|
+
const directory = await fetchWorkspaceDirectory({
|
|
1216
|
+
apiBase: resolvedApiBase,
|
|
1217
|
+
apiKey,
|
|
1218
|
+
timeoutMs,
|
|
1219
|
+
});
|
|
1220
|
+
const selection = resolveWorkspaceSelection({}, config);
|
|
1221
|
+
const workspaces = Array.isArray(directory.workspaces) ? directory.workspaces : [];
|
|
1222
|
+
const payload = {
|
|
1223
|
+
...directory,
|
|
1224
|
+
effectiveWorkspaceId: selection.workspaceId,
|
|
1225
|
+
effectiveWorkspaceSource: selection.source,
|
|
1226
|
+
};
|
|
1227
|
+
|
|
1228
|
+
if (opts.json) {
|
|
1229
|
+
process.stdout.write(JSON.stringify(payload, null, opts.pretty ? 2 : 0) + '\n');
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
process.stdout.write('[socialseal] Available workspaces\n');
|
|
1234
|
+
if (workspaces.length === 0) {
|
|
1235
|
+
process.stdout.write('[socialseal] No accessible workspaces were returned for this key.\n');
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
for (const workspace of workspaces) {
|
|
1240
|
+
const isEffective = selection.workspaceId === workspace.id;
|
|
1241
|
+
const isSuggested = !selection.workspaceId && directory.defaultWorkspaceId === workspace.id;
|
|
1242
|
+
process.stdout.write(`${formatWorkspaceLine(workspace, { isEffective, source: selection.source, isSuggested })}\n`);
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
if (!selection.workspaceId && directory.defaultWorkspaceId) {
|
|
1246
|
+
process.stdout.write('\n[socialseal] No local default is configured. Set one with: socialseal workspace use <id>\n');
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
async function handleWorkspaceCurrent(opts) {
|
|
1251
|
+
const config = loadConfig();
|
|
1252
|
+
const apiKey = requireApiKey(opts, config);
|
|
1253
|
+
const apiBase = resolveApiBase(opts, config);
|
|
1254
|
+
const { resolvedApiBase } = resolveApiTarget({ apiBase, legacyUrl: null });
|
|
1255
|
+
const timeoutMs = resolveTimeoutMs(opts, config);
|
|
1256
|
+
const directory = await fetchWorkspaceDirectory({
|
|
1257
|
+
apiBase: resolvedApiBase,
|
|
1258
|
+
apiKey,
|
|
1259
|
+
timeoutMs,
|
|
1260
|
+
});
|
|
1261
|
+
const selection = resolveWorkspaceSelection({}, config);
|
|
1262
|
+
const workspaces = Array.isArray(directory.workspaces) ? directory.workspaces : [];
|
|
1263
|
+
const effectiveWorkspace = selection.workspaceId
|
|
1264
|
+
? workspaces.find((workspace) => workspace.id === selection.workspaceId) || null
|
|
1265
|
+
: null;
|
|
1266
|
+
|
|
1267
|
+
if (selection.workspaceId && !effectiveWorkspace) {
|
|
1268
|
+
throw new CliError(`Configured workspace "${selection.workspaceId}" is not accessible with this CLI key.`, {
|
|
1269
|
+
code: 'WORKSPACE_NOT_ACCESSIBLE',
|
|
1270
|
+
exitCode: EXIT_CODES.NOT_FOUND,
|
|
1271
|
+
hint: 'Run `socialseal workspace list` to pick a valid workspace or `socialseal workspace clear` to unset the default.',
|
|
1272
|
+
});
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
const payload = {
|
|
1276
|
+
effectiveWorkspaceId: selection.workspaceId,
|
|
1277
|
+
effectiveWorkspaceSource: selection.source,
|
|
1278
|
+
workspace: effectiveWorkspace,
|
|
1279
|
+
defaultWorkspaceId: directory.defaultWorkspaceId || null,
|
|
1280
|
+
personalWorkspaceId: directory.personalWorkspaceId || null,
|
|
1281
|
+
};
|
|
1282
|
+
if (opts.json) {
|
|
1283
|
+
process.stdout.write(JSON.stringify(payload, null, opts.pretty ? 2 : 0) + '\n');
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
if (effectiveWorkspace) {
|
|
1288
|
+
process.stdout.write(`[socialseal] Effective workspace: ${effectiveWorkspace.name} (${effectiveWorkspace.id}) via ${selection.source}\n`);
|
|
1289
|
+
return;
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
if (directory.defaultWorkspaceId) {
|
|
1293
|
+
const suggestedWorkspace = workspaces.find((workspace) => workspace.id === directory.defaultWorkspaceId) || null;
|
|
1294
|
+
if (suggestedWorkspace) {
|
|
1295
|
+
process.stdout.write(`[socialseal] No local default workspace is configured. Suggested workspace: ${suggestedWorkspace.name} (${suggestedWorkspace.id})\n`);
|
|
1296
|
+
return;
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
process.stdout.write('[socialseal] No default workspace is configured and no accessible workspace suggestion is available.\n');
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
async function handleWorkspaceUse(opts) {
|
|
1304
|
+
const config = loadConfig();
|
|
1305
|
+
const apiKey = requireApiKey(opts, config);
|
|
1306
|
+
const apiBase = resolveApiBase(opts, config);
|
|
1307
|
+
const { resolvedApiBase } = resolveApiTarget({ apiBase, legacyUrl: null });
|
|
1308
|
+
const timeoutMs = resolveTimeoutMs(opts, config);
|
|
1309
|
+
const directory = await fetchWorkspaceDirectory({
|
|
1310
|
+
apiBase: resolvedApiBase,
|
|
1311
|
+
apiKey,
|
|
1312
|
+
timeoutMs,
|
|
1313
|
+
});
|
|
1314
|
+
const workspaces = Array.isArray(directory.workspaces) ? directory.workspaces : [];
|
|
1315
|
+
const workspace = matchWorkspaceIdentifier(workspaces, opts.identifier);
|
|
1316
|
+
saveConfig({
|
|
1317
|
+
...config,
|
|
1318
|
+
workspaceId: workspace.id,
|
|
1319
|
+
});
|
|
1320
|
+
|
|
1321
|
+
const payload = {
|
|
1322
|
+
success: true,
|
|
1323
|
+
workspaceId: workspace.id,
|
|
1324
|
+
workspace,
|
|
1325
|
+
configPath: getConfigPath(),
|
|
1326
|
+
};
|
|
1327
|
+
if (opts.json) {
|
|
1328
|
+
process.stdout.write(JSON.stringify(payload, null, opts.pretty ? 2 : 0) + '\n');
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
process.stdout.write(`[socialseal] Default workspace set to ${workspace.name} (${workspace.id})\n`);
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
function handleWorkspaceClear(opts) {
|
|
1336
|
+
const config = loadConfig();
|
|
1337
|
+
const nextConfig = { ...config };
|
|
1338
|
+
delete nextConfig.workspaceId;
|
|
1339
|
+
saveConfig(nextConfig);
|
|
1340
|
+
|
|
1341
|
+
const payload = {
|
|
1342
|
+
success: true,
|
|
1343
|
+
configPath: getConfigPath(),
|
|
1344
|
+
};
|
|
1345
|
+
if (opts.json) {
|
|
1346
|
+
process.stdout.write(JSON.stringify(payload, null, opts.pretty ? 2 : 0) + '\n');
|
|
1347
|
+
return;
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
process.stdout.write('[socialseal] Default workspace cleared.\n');
|
|
1351
|
+
}
|
|
1352
|
+
|
|
614
1353
|
const program = new Command();
|
|
615
1354
|
program
|
|
616
1355
|
.name('socialseal')
|
|
617
1356
|
.description('SocialSeal CLI (non-interactive)')
|
|
618
|
-
.version('0.1.
|
|
1357
|
+
.version('0.1.1');
|
|
619
1358
|
|
|
620
1359
|
if (typeof program.showHelpAfterError === 'function') {
|
|
621
1360
|
program.showHelpAfterError(true);
|
|
@@ -623,7 +1362,7 @@ if (typeof program.showHelpAfterError === 'function') {
|
|
|
623
1362
|
if (typeof program.showSuggestionAfterError === 'function') {
|
|
624
1363
|
program.showSuggestionAfterError(true);
|
|
625
1364
|
}
|
|
626
|
-
program.addHelpText('after', `\nExamples:\n socialseal agent run --message \"ping\"\n socialseal tools list\n socialseal tools call --function <tool> --body @payload.json\n socialseal data export-tracking --group-id 123 --time-period 30d\n`);
|
|
1365
|
+
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 call --function <tool> --body @payload.json\n socialseal tools call --function search-journey-run --body @payload.json --async --workspace-id <uuid>\n socialseal data export-tracking --group-id 123 --time-period 30d\n`);
|
|
627
1366
|
|
|
628
1367
|
program
|
|
629
1368
|
.command('agent')
|
|
@@ -634,18 +1373,63 @@ program
|
|
|
634
1373
|
.option('--api-base <url>', 'API base URL (default https://api.socialseal.co)')
|
|
635
1374
|
.option('--api-key <key>', 'CLI API key')
|
|
636
1375
|
.option('--workspace-id <id>', 'Workspace id (for scoped keys)')
|
|
1376
|
+
.option('--continue <token>', 'Continuation token from a previous agent run')
|
|
637
1377
|
.option('--conversation-id <id>', 'Conversation id to resume')
|
|
638
1378
|
.option('--create-new', 'Create a new conversation')
|
|
639
1379
|
.option('--json', 'Emit NDJSON events')
|
|
640
1380
|
.option('--timeout <ms>', 'Request timeout in milliseconds')
|
|
1381
|
+
.option('--idle-timeout <ms>', 'WebSocket inactivity timeout in milliseconds')
|
|
641
1382
|
.option('--verbose', 'Show error details')
|
|
642
1383
|
.action((opts) => runCommand(handleAgentRun, opts));
|
|
643
1384
|
|
|
1385
|
+
const workspace = program.command('workspace').description('Discover and manage the default workspace');
|
|
1386
|
+
|
|
1387
|
+
workspace
|
|
1388
|
+
.command('list')
|
|
1389
|
+
.description('List accessible workspaces for this CLI key')
|
|
1390
|
+
.option('--api-base <url>', 'API base URL (default https://api.socialseal.co)')
|
|
1391
|
+
.option('--api-key <key>', 'CLI API key')
|
|
1392
|
+
.option('--json', 'Emit machine-readable output')
|
|
1393
|
+
.option('--pretty', 'Pretty-print JSON')
|
|
1394
|
+
.option('--timeout <ms>', 'Request timeout in milliseconds')
|
|
1395
|
+
.option('--verbose', 'Show error details')
|
|
1396
|
+
.action((opts) => runCommand(handleWorkspaceList, opts));
|
|
1397
|
+
|
|
1398
|
+
workspace
|
|
1399
|
+
.command('current')
|
|
1400
|
+
.description('Show the effective default workspace')
|
|
1401
|
+
.option('--api-base <url>', 'API base URL (default https://api.socialseal.co)')
|
|
1402
|
+
.option('--api-key <key>', 'CLI API key')
|
|
1403
|
+
.option('--json', 'Emit machine-readable output')
|
|
1404
|
+
.option('--pretty', 'Pretty-print JSON')
|
|
1405
|
+
.option('--timeout <ms>', 'Request timeout in milliseconds')
|
|
1406
|
+
.option('--verbose', 'Show error details')
|
|
1407
|
+
.action((opts) => runCommand(handleWorkspaceCurrent, opts));
|
|
1408
|
+
|
|
1409
|
+
workspace
|
|
1410
|
+
.command('use <identifier>')
|
|
1411
|
+
.description('Persist a default workspace by id, slug, or exact name')
|
|
1412
|
+
.option('--api-base <url>', 'API base URL (default https://api.socialseal.co)')
|
|
1413
|
+
.option('--api-key <key>', 'CLI API key')
|
|
1414
|
+
.option('--json', 'Emit machine-readable output')
|
|
1415
|
+
.option('--pretty', 'Pretty-print JSON')
|
|
1416
|
+
.option('--timeout <ms>', 'Request timeout in milliseconds')
|
|
1417
|
+
.option('--verbose', 'Show error details')
|
|
1418
|
+
.action((identifier, opts) => runCommand(handleWorkspaceUse, { ...opts, identifier }));
|
|
1419
|
+
|
|
1420
|
+
workspace
|
|
1421
|
+
.command('clear')
|
|
1422
|
+
.description('Clear the locally configured default workspace')
|
|
1423
|
+
.option('--json', 'Emit machine-readable output')
|
|
1424
|
+
.option('--pretty', 'Pretty-print JSON')
|
|
1425
|
+
.option('--verbose', 'Show error details')
|
|
1426
|
+
.action((opts) => runCommand(handleWorkspaceClear, opts));
|
|
1427
|
+
|
|
644
1428
|
const tools = program.command('tools').description('Call edge functions directly (tool backends)');
|
|
645
1429
|
|
|
646
1430
|
tools
|
|
647
1431
|
.command('list')
|
|
648
|
-
.description('List
|
|
1432
|
+
.description('List built-in tool registry entries')
|
|
649
1433
|
.option('--json', 'Emit machine-readable output')
|
|
650
1434
|
.option('--pretty', 'Pretty-print JSON')
|
|
651
1435
|
.option('--verbose', 'Show error details')
|
|
@@ -656,6 +1440,9 @@ tools
|
|
|
656
1440
|
.requiredOption('--function <name>', 'Tool name (see official docs)')
|
|
657
1441
|
.option('--method <method>', 'HTTP method', 'POST')
|
|
658
1442
|
.option('--body <jsonOrFile>', 'JSON body or @file.json')
|
|
1443
|
+
.option('--async', 'Request async execution for supported tool backends')
|
|
1444
|
+
.option('--no-poll', 'Return immediately after async start instead of polling to completion')
|
|
1445
|
+
.option('--poll-interval <ms>', 'Polling interval in milliseconds for supported async tool calls')
|
|
659
1446
|
.option('--api-base <url>', 'API base URL (default https://api.socialseal.co)')
|
|
660
1447
|
.option('--api-key <key>', 'CLI API key')
|
|
661
1448
|
.option('--workspace-id <id>', 'Workspace id (for scoped keys)')
|