codekanban 0.26.0 → 0.28.0

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 CHANGED
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "codekanban",
3
- "version": "0.26.0",
3
+ "version": "0.28.0",
4
4
  "description": "An auxiliary programming tool for the AI era, helping you speed up 10x.",
5
5
  "bin": {
6
6
  "codekanban": "npm-bin/codekanban.js"
7
7
  },
8
8
  "optionalDependencies": {
9
- "@codekanban/win32-x64": "0.26.0",
10
- "@codekanban/darwin-x64": "0.26.0",
11
- "@codekanban/darwin-arm64": "0.26.0",
12
- "@codekanban/linux-x64": "0.26.0",
13
- "@codekanban/linux-arm64": "0.26.0"
9
+ "@codekanban/win32-x64": "0.28.0",
10
+ "@codekanban/darwin-x64": "0.28.0",
11
+ "@codekanban/darwin-arm64": "0.28.0",
12
+ "@codekanban/linux-x64": "0.28.0",
13
+ "@codekanban/linux-arm64": "0.28.0"
14
14
  },
15
15
  "keywords": [
16
16
  "ai",
@@ -1,16 +1,24 @@
1
1
  # `@codekanban/sdk`
2
2
 
3
- Node SDK and CLI for CodeKanban project workflows.
3
+ Node SDK and CLI for CodeKanban coding workflows, terminal sessions, and web sessions.
4
+
5
+ Use it as a programming-assistant toolkit for launching, observing, and steering CodeKanban work in a registered project or local path, with `web session` as the default structured path and `terminal session` for PTY-style flows.
4
6
 
5
7
  ## Features
6
8
 
7
9
  - Resolve CodeKanban projects by `projectId` or local `path`
8
10
  - Auto-create projects for new paths
9
11
  - Select the main worktree automatically
10
- - Launch terminal + agent workflows for `codex` or `claude`
12
+ - Launch coding workflows for `codex` or `claude`
11
13
  - Support `standard`, `plan`, and `yolo` Codex profiles
12
14
  - Read terminal session lists and AI session summaries
13
15
  - Read AI conversations and continue existing terminal sessions
16
+ - Control CodeKanban `web session` runs over HTTP and WebSocket
17
+ - Read `web session` snapshots, history, runtime config, command groups, and archived sessions
18
+ - Send `web session` messages, approvals, user-input answers, model/workflow/permission updates, and move operations
19
+ - Receive normalized `web session` events through `on/off`, `waitFor`, or `for await`
20
+ - Poll a `web session` for a derived actionable state instead of reconstructing history logic yourself
21
+ - Execute the latest plan or answer the active structured prompt with one SDK call
14
22
 
15
23
  ## CLI
16
24
 
@@ -21,6 +29,19 @@ node packages/node-sdk/bin/codekanban-sdk.js session conversation --base-url htt
21
29
  node packages/node-sdk/bin/codekanban-sdk.js terminal continue --base-url http://127.0.0.1:3000 --session-id <terminal-session-id> --prompt "Continue from the previous plan"
22
30
  ```
23
31
 
32
+ ### Web Session CLI
33
+
34
+ ```bash
35
+ node packages/node-sdk/bin/codekanban-sdk.js web-session create --base-url http://127.0.0.1:3000 --path D:\repo --agent codex --workflow-mode plan --permission-level elevated --title "Planning session"
36
+ node packages/node-sdk/bin/codekanban-sdk.js web-session snapshot --base-url http://127.0.0.1:3000 --project-id <project-id> --session-id <session-id> --limit 80
37
+ node packages/node-sdk/bin/codekanban-sdk.js web-session send --base-url http://127.0.0.1:3000 --session-id <session-id> --text "Continue from the last plan"
38
+ node packages/node-sdk/bin/codekanban-sdk.js web-session approve --base-url http://127.0.0.1:3000 --session-id <session-id>
39
+ node packages/node-sdk/bin/codekanban-sdk.js web-session user-input --base-url http://127.0.0.1:3000 --session-id <session-id> --item-id <item-id> --answers-json '{"scope":["full repo"]}'
40
+ node packages/node-sdk/bin/codekanban-sdk.js web-session watch --base-url http://127.0.0.1:3000 --session-id <session-id> --max-events 20
41
+ ```
42
+
43
+ `web-session watch` writes NDJSON. Add `--raw` to print raw short-key protocol frames.
44
+
24
45
  ## Library
25
46
 
26
47
  ```js
@@ -37,3 +58,117 @@ const result = await client.startWorkflow({
37
58
  },
38
59
  });
39
60
  ```
61
+
62
+ ### Web Session HTTP
63
+
64
+ ```js
65
+ const client = new CodeKanbanClient({ baseURL: 'http://127.0.0.1:3000' });
66
+
67
+ const session = await client.createWebSession({
68
+ path: 'D:/repo',
69
+ agent: 'codex',
70
+ workflowMode: 'plan',
71
+ permissionLevel: 'elevated',
72
+ title: 'Planning session',
73
+ });
74
+
75
+ const snapshot = await client.getWebSessionSnapshot({
76
+ projectId: session.projectId,
77
+ sessionId: session.id,
78
+ });
79
+ ```
80
+
81
+ ### Polling-Friendly Web Session State
82
+
83
+ ```js
84
+ const state = await client.getWebSessionState({
85
+ projectId: session.projectId,
86
+ sessionId: session.id,
87
+ });
88
+
89
+ if (state.phase === 'running') {
90
+ return;
91
+ }
92
+
93
+ if (state.nextAction?.type === 'answer_user_input') {
94
+ await client.answerPendingUserInput({
95
+ projectId: session.projectId,
96
+ sessionId: session.id,
97
+ answers: {
98
+ scope: ['full repo'],
99
+ },
100
+ });
101
+ }
102
+
103
+ if (state.nextAction?.type === 'execute_plan') {
104
+ await client.executeLatestPlan({
105
+ projectId: session.projectId,
106
+ sessionId: session.id,
107
+ });
108
+ }
109
+ ```
110
+
111
+ `getWebSessionState()` derives:
112
+
113
+ - `phase`
114
+ - `canSend`
115
+ - `needsAction`
116
+ - `nextAction`
117
+ - `pendingApproval`
118
+ - `pendingUserInput`
119
+ - `latestPlan`
120
+ - `lastAssistantMessage`
121
+ - `snapshot`
122
+
123
+ ### Waiting With Polling
124
+
125
+ ```js
126
+ const doneState = await client.waitForWebSessionState({
127
+ projectId: session.projectId,
128
+ sessionId: session.id,
129
+ until: 'done',
130
+ intervalMs: 5000,
131
+ timeoutMs: 120000,
132
+ });
133
+
134
+ console.log(doneState.lastAssistantMessage?.text);
135
+ ```
136
+
137
+ ### Web Session Command Channel
138
+
139
+ ```js
140
+ const channel = client.openWebSessionCommandChannel();
141
+ await channel.waitForOpen();
142
+
143
+ await channel.sendMessage(session.id, {
144
+ text: 'Continue from the latest review.',
145
+ });
146
+
147
+ await channel.updateWorkflowMode(session.id, {
148
+ workflowMode: 'plan',
149
+ });
150
+
151
+ channel.close();
152
+ ```
153
+
154
+ ### Web Session Event Stream
155
+
156
+ ```js
157
+ const events = client.openWebSessionEventStream({ sessionId: session.id });
158
+ await events.waitForOpen();
159
+
160
+ events.on('snapshot', event => {
161
+ console.log('snapshot', event.snapshot.session.title);
162
+ });
163
+
164
+ const nextApproval = events.waitFor(event => event.type === 'historyItem' && event.item.detail?.type === 'approval_request');
165
+
166
+ for await (const event of events) {
167
+ if (event.type === 'historyItem') {
168
+ console.log(event.item.itemType, event.item.text);
169
+ }
170
+ }
171
+
172
+ await nextApproval;
173
+ events.close();
174
+ ```
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@codekanban/sdk",
3
3
  "version": "0.1.0",
4
- "description": "Node SDK and CLI for launching CodeKanban project workflows and reading business sessions.",
4
+ "description": "Node SDK and CLI for launching CodeKanban coding workflows, terminal sessions, and web sessions.",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
7
7
  "exports": {
@@ -10,14 +10,39 @@ function readFlagValue(argv, index, flag) {
10
10
  return value;
11
11
  }
12
12
 
13
+ function parseIntegerFlag(value, fieldName) {
14
+ if (value == null || value === '') {
15
+ return undefined;
16
+ }
17
+ const parsed = Number(value);
18
+ if (!Number.isFinite(parsed)) {
19
+ throw new Error(`${fieldName} must be a number`);
20
+ }
21
+ return Math.trunc(parsed);
22
+ }
23
+
24
+ function parseJsonFlag(value, fieldName) {
25
+ if (!value) {
26
+ throw new Error(`${fieldName} is required`);
27
+ }
28
+ try {
29
+ return JSON.parse(value);
30
+ } catch (error) {
31
+ throw new Error(`${fieldName} must be valid JSON`);
32
+ }
33
+ }
34
+
13
35
  function parseCliArgs(argv) {
14
36
  const positionals = [];
15
37
  const flags = {
16
38
  addDirs: [],
39
+ attachmentIds: [],
17
40
  extraArgs: [],
18
41
  includeTerminal: true,
19
42
  includeAI: true,
20
43
  refresh: false,
44
+ clearExisting: false,
45
+ raw: false,
21
46
  };
22
47
 
23
48
  for (let index = 0; index < argv.length; index += 1) {
@@ -44,10 +69,18 @@ function parseCliArgs(argv) {
44
69
  flags.prompt = readFlagValue(argv, index, token);
45
70
  index += 1;
46
71
  break;
72
+ case '--text':
73
+ flags.text = readFlagValue(argv, index, token);
74
+ index += 1;
75
+ break;
47
76
  case '--agent':
48
77
  flags.agent = readFlagValue(argv, index, token);
49
78
  index += 1;
50
79
  break;
80
+ case '--model':
81
+ flags.model = readFlagValue(argv, index, token);
82
+ index += 1;
83
+ break;
51
84
  case '--profile':
52
85
  flags.profile = readFlagValue(argv, index, token);
53
86
  index += 1;
@@ -64,6 +97,10 @@ function parseCliArgs(argv) {
64
97
  flags.addDirs.push(readFlagValue(argv, index, token));
65
98
  index += 1;
66
99
  break;
100
+ case '--attachment-id':
101
+ flags.attachmentIds.push(readFlagValue(argv, index, token));
102
+ index += 1;
103
+ break;
67
104
  case '--extra-arg':
68
105
  flags.extraArgs.push(readFlagValue(argv, index, token));
69
106
  index += 1;
@@ -76,10 +113,6 @@ function parseCliArgs(argv) {
76
113
  flags.workingDir = readFlagValue(argv, index, token);
77
114
  index += 1;
78
115
  break;
79
- case '--task-id':
80
- flags.taskId = readFlagValue(argv, index, token);
81
- index += 1;
82
- break;
83
116
  case '--worktree-id':
84
117
  flags.worktreeId = readFlagValue(argv, index, token);
85
118
  index += 1;
@@ -96,9 +129,75 @@ function parseCliArgs(argv) {
96
129
  flags.toolUseId = readFlagValue(argv, index, token);
97
130
  index += 1;
98
131
  break;
132
+ case '--reasoning-effort':
133
+ flags.reasoningEffort = readFlagValue(argv, index, token);
134
+ index += 1;
135
+ break;
136
+ case '--workflow-mode':
137
+ flags.workflowMode = readFlagValue(argv, index, token);
138
+ index += 1;
139
+ break;
140
+ case '--permission-level':
141
+ flags.permissionLevel = readFlagValue(argv, index, token);
142
+ index += 1;
143
+ break;
144
+ case '--permission-mode':
145
+ flags.permissionMode = readFlagValue(argv, index, token);
146
+ index += 1;
147
+ break;
148
+ case '--limit':
149
+ flags.limit = readFlagValue(argv, index, token);
150
+ index += 1;
151
+ break;
152
+ case '--before-cursor':
153
+ flags.beforeCursor = readFlagValue(argv, index, token);
154
+ index += 1;
155
+ break;
156
+ case '--mode':
157
+ flags.mode = readFlagValue(argv, index, token);
158
+ index += 1;
159
+ break;
160
+ case '--group-id':
161
+ flags.groupId = readFlagValue(argv, index, token);
162
+ index += 1;
163
+ break;
164
+ case '--file':
165
+ flags.file = readFlagValue(argv, index, token);
166
+ index += 1;
167
+ break;
168
+ case '--item-id':
169
+ flags.itemId = readFlagValue(argv, index, token);
170
+ index += 1;
171
+ break;
172
+ case '--answers-json':
173
+ flags.answersJson = readFlagValue(argv, index, token);
174
+ index += 1;
175
+ break;
176
+ case '--prev-session-id':
177
+ flags.prevSessionId = readFlagValue(argv, index, token);
178
+ index += 1;
179
+ break;
180
+ case '--next-session-id':
181
+ flags.nextSessionId = readFlagValue(argv, index, token);
182
+ index += 1;
183
+ break;
184
+ case '--idle-timeout-ms':
185
+ flags.idleTimeoutMs = readFlagValue(argv, index, token);
186
+ index += 1;
187
+ break;
188
+ case '--max-events':
189
+ flags.maxEvents = readFlagValue(argv, index, token);
190
+ index += 1;
191
+ break;
99
192
  case '--refresh':
100
193
  flags.refresh = true;
101
194
  break;
195
+ case '--clear-existing':
196
+ flags.clearExisting = true;
197
+ break;
198
+ case '--raw':
199
+ flags.raw = true;
200
+ break;
102
201
  case '--no-terminal':
103
202
  flags.includeTerminal = false;
104
203
  break;
@@ -136,6 +235,9 @@ function sanitizeConnection(value) {
136
235
  }
137
236
  const next = { ...value };
138
237
  delete next.connection;
238
+ delete next.commandChannel;
239
+ delete next.eventStream;
240
+ delete next.socket;
139
241
  return next;
140
242
  }
141
243
 
@@ -143,12 +245,88 @@ function printJson(stream, payload) {
143
245
  stream.write(createJsonOutput(payload));
144
246
  }
145
247
 
146
- export async function runCli(argv) {
248
+ function writeJsonLine(stream, payload) {
249
+ stream.write(`${JSON.stringify(payload)}\n`);
250
+ }
251
+
252
+ async function withWebSessionCommandChannel(client, handler) {
253
+ const channel = client.openWebSessionCommandChannel();
254
+ try {
255
+ await channel.waitForOpen();
256
+ return await handler(channel);
257
+ } finally {
258
+ channel.close();
259
+ }
260
+ }
261
+
262
+ async function watchWebSession(client, flags, stdout) {
263
+ const eventStream = client.openWebSessionEventStream({
264
+ sessionId: flags.sessionId,
265
+ });
266
+ const maxEvents = parseIntegerFlag(flags.maxEvents, 'maxEvents');
267
+ const idleTimeoutMs = parseIntegerFlag(flags.idleTimeoutMs, 'idleTimeoutMs');
268
+ let eventCount = 0;
269
+ let idleTimer = null;
270
+ let closedByPolicy = false;
271
+
272
+ const armIdleTimer = () => {
273
+ if (idleTimer) {
274
+ clearTimeout(idleTimer);
275
+ idleTimer = null;
276
+ }
277
+ if (!Number.isFinite(idleTimeoutMs) || idleTimeoutMs == null || idleTimeoutMs <= 0) {
278
+ return;
279
+ }
280
+ idleTimer = setTimeout(() => {
281
+ closedByPolicy = true;
282
+ eventStream.close();
283
+ }, idleTimeoutMs);
284
+ };
285
+
286
+ try {
287
+ await eventStream.waitForOpen();
288
+ armIdleTimer();
289
+
290
+ for await (const event of eventStream) {
291
+ if (event.type === 'open' || event.type === 'close') {
292
+ continue;
293
+ }
294
+
295
+ if (event.type === 'error' && event.errorType !== 'frame') {
296
+ throw event.error instanceof Error ? event.error : new Error(event.message || 'event stream failed');
297
+ }
298
+
299
+ const payload = flags.raw && event.raw ? event.raw : event;
300
+ writeJsonLine(stdout, payload);
301
+ eventCount += 1;
302
+ armIdleTimer();
303
+
304
+ if (maxEvents != null && maxEvents > 0 && eventCount >= maxEvents) {
305
+ closedByPolicy = true;
306
+ eventStream.close();
307
+ }
308
+ }
309
+
310
+ return 0;
311
+ } finally {
312
+ if (idleTimer) {
313
+ clearTimeout(idleTimer);
314
+ }
315
+ if (!closedByPolicy) {
316
+ eventStream.close();
317
+ }
318
+ }
319
+ }
320
+
321
+ export async function runCli(argv, options = {}) {
322
+ const stdout = options.stdout || process.stdout;
323
+ const stderr = options.stderr || process.stderr;
324
+
147
325
  try {
148
326
  const { positionals, flags } = parseCliArgs(argv);
149
327
  const [scope, action] = positionals;
150
328
  if (!scope || !action) {
151
- throw new Error('usage: <workflow|session|terminal> <action> --base-url <url> [...]');
329
+ throw new Error('usage: <workflow|session|terminal|web-session> <action> --base-url <url> [...]');
152
330
  }
153
331
 
154
332
  if (scope === 'workflow' && action === 'command') {
@@ -159,7 +337,7 @@ export async function runCli(argv) {
159
337
  extraArgs: flags.extraArgs,
160
338
  prompt: flags.prompt || 'Inspect the project and respond.',
161
339
  });
162
- printJson(process.stdout, sanitizeConnection(result));
340
+ printJson(stdout, sanitizeConnection(result));
163
341
  return 0;
164
342
  }
165
343
 
@@ -182,7 +360,6 @@ export async function runCli(argv) {
182
360
  extraArgs: flags.extraArgs,
183
361
  title: flags.title,
184
362
  workingDir: flags.workingDir,
185
- taskId: flags.taskId,
186
363
  });
187
364
  } else if (scope === 'session' && action === 'list') {
188
365
  result = await client.listSessions({
@@ -210,11 +387,155 @@ export async function runCli(argv) {
210
387
  sessionId: flags.sessionId,
211
388
  prompt: flags.prompt,
212
389
  });
390
+ } else if (scope === 'web-session' && action === 'list') {
391
+ result = await client.listWebSessions({
392
+ projectId: flags.projectId,
393
+ path: flags.path,
394
+ });
395
+ } else if (scope === 'web-session' && action === 'create') {
396
+ result = await client.createWebSession({
397
+ projectId: flags.projectId,
398
+ path: flags.path,
399
+ worktreeId: flags.worktreeId,
400
+ agent: flags.agent,
401
+ model: flags.model,
402
+ reasoningEffort: flags.reasoningEffort,
403
+ workflowMode: flags.workflowMode,
404
+ permissionLevel: flags.permissionLevel,
405
+ permissionMode: flags.permissionMode,
406
+ title: flags.title,
407
+ });
408
+ } else if (scope === 'web-session' && action === 'connect') {
409
+ result = await withWebSessionCommandChannel(client, channel => channel.connect(flags.sessionId));
410
+ } else if (scope === 'web-session' && action === 'snapshot') {
411
+ result = await client.getWebSessionSnapshot({
412
+ projectId: flags.projectId,
413
+ path: flags.path,
414
+ sessionId: flags.sessionId,
415
+ limit: parseIntegerFlag(flags.limit, 'limit'),
416
+ });
417
+ } else if (scope === 'web-session' && action === 'history') {
418
+ result = await client.getWebSessionHistory({
419
+ projectId: flags.projectId,
420
+ path: flags.path,
421
+ sessionId: flags.sessionId,
422
+ beforeCursor: flags.beforeCursor,
423
+ limit: parseIntegerFlag(flags.limit, 'limit'),
424
+ });
425
+ } else if (scope === 'web-session' && action === 'sync') {
426
+ result = await client.syncWebSession({
427
+ projectId: flags.projectId,
428
+ path: flags.path,
429
+ sessionId: flags.sessionId,
430
+ mode: flags.mode,
431
+ clearExisting: flags.clearExisting,
432
+ });
433
+ } else if (scope === 'web-session' && action === 'archive') {
434
+ result = await client.archiveWebSession({
435
+ projectId: flags.projectId,
436
+ path: flags.path,
437
+ sessionId: flags.sessionId,
438
+ });
439
+ } else if (scope === 'web-session' && action === 'unarchive') {
440
+ result = await client.unarchiveWebSession({
441
+ projectId: flags.projectId,
442
+ path: flags.path,
443
+ sessionId: flags.sessionId,
444
+ });
445
+ } else if (scope === 'web-session' && action === 'rename') {
446
+ result = await client.renameWebSession({
447
+ projectId: flags.projectId,
448
+ path: flags.path,
449
+ sessionId: flags.sessionId,
450
+ title: flags.title,
451
+ });
452
+ } else if (scope === 'web-session' && action === 'close') {
453
+ result = await client.closeWebSession({
454
+ projectId: flags.projectId,
455
+ path: flags.path,
456
+ sessionId: flags.sessionId,
457
+ });
458
+ } else if (scope === 'web-session' && action === 'delete') {
459
+ result = await client.deleteWebSession({
460
+ projectId: flags.projectId,
461
+ path: flags.path,
462
+ sessionId: flags.sessionId,
463
+ });
464
+ } else if (scope === 'web-session' && action === 'runtime-config') {
465
+ result = await client.getWebSessionRuntimeConfig();
466
+ } else if (scope === 'web-session' && action === 'command-group') {
467
+ result = await client.getWebSessionCommandGroup({
468
+ projectId: flags.projectId,
469
+ path: flags.path,
470
+ sessionId: flags.sessionId,
471
+ groupId: flags.groupId,
472
+ });
473
+ } else if (scope === 'web-session' && action === 'attach') {
474
+ result = await client.uploadWebSessionAttachment({
475
+ projectId: flags.projectId,
476
+ path: flags.path,
477
+ filePath: flags.file,
478
+ });
479
+ } else if (scope === 'web-session' && action === 'send') {
480
+ result = await withWebSessionCommandChannel(client, channel =>
481
+ channel.sendMessage(flags.sessionId, {
482
+ text: flags.text || flags.prompt,
483
+ attachmentIds: flags.attachmentIds,
484
+ }),
485
+ );
486
+ } else if (scope === 'web-session' && action === 'approve') {
487
+ result = await withWebSessionCommandChannel(client, channel => channel.approve(flags.sessionId));
488
+ } else if (scope === 'web-session' && action === 'reject') {
489
+ result = await withWebSessionCommandChannel(client, channel => channel.reject(flags.sessionId));
490
+ } else if (scope === 'web-session' && action === 'user-input') {
491
+ result = await withWebSessionCommandChannel(client, channel =>
492
+ channel.answerUserInput(flags.sessionId, {
493
+ itemId: flags.itemId,
494
+ answers: parseJsonFlag(flags.answersJson, 'answersJson'),
495
+ }),
496
+ );
497
+ } else if (scope === 'web-session' && action === 'set-model') {
498
+ result = await withWebSessionCommandChannel(client, channel =>
499
+ channel.updateModel(flags.sessionId, { model: flags.model }),
500
+ );
501
+ } else if (scope === 'web-session' && action === 'set-reasoning') {
502
+ result = await withWebSessionCommandChannel(client, channel =>
503
+ channel.updateReasoningEffort(flags.sessionId, {
504
+ reasoningEffort: flags.reasoningEffort,
505
+ }),
506
+ );
507
+ } else if (scope === 'web-session' && action === 'set-workflow') {
508
+ result = await withWebSessionCommandChannel(client, channel =>
509
+ channel.updateWorkflowMode(flags.sessionId, {
510
+ workflowMode: flags.workflowMode,
511
+ }),
512
+ );
513
+ } else if (scope === 'web-session' && action === 'set-permission') {
514
+ result = await withWebSessionCommandChannel(client, channel =>
515
+ channel.updatePermissionLevel(flags.sessionId, {
516
+ permissionLevel: flags.permissionLevel,
517
+ }),
518
+ );
519
+ } else if (scope === 'web-session' && action === 'set-agent') {
520
+ result = await withWebSessionCommandChannel(client, channel =>
521
+ channel.updateAgent(flags.sessionId, {
522
+ agent: flags.agent,
523
+ }),
524
+ );
525
+ } else if (scope === 'web-session' && action === 'move') {
526
+ result = await withWebSessionCommandChannel(client, channel =>
527
+ channel.move(flags.sessionId, {
528
+ prevSessionId: flags.prevSessionId,
529
+ nextSessionId: flags.nextSessionId,
530
+ }),
531
+ );
532
+ } else if (scope === 'web-session' && action === 'watch') {
533
+ return await watchWebSession(client, flags, stdout);
213
534
  } else {
214
535
  throw new Error(`unsupported command: ${scope} ${action}`);
215
536
  }
216
537
 
217
- printJson(process.stdout, sanitizeConnection(result));
538
+ printJson(stdout, sanitizeConnection(result));
218
539
  return 0;
219
540
  } catch (error) {
220
541
  const payload = {
@@ -223,7 +544,7 @@ export async function runCli(argv) {
223
544
  message: error instanceof Error ? error.message : String(error),
224
545
  },
225
546
  };
226
- printJson(process.stderr, payload);
547
+ printJson(stderr, payload);
227
548
  return 1;
228
549
  }
229
550
  }