codekanban 0.30.0 → 0.32.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.
@@ -0,0 +1,13 @@
1
+ import { runCli, createHelpText } from './runtime.js';
2
+
3
+ export * from './core.js';
4
+ export { runCodeKanbanCli };
5
+
6
+ import { runCodeKanbanCliWithRuntime } from './core.js';
7
+
8
+ async function runCodeKanbanCli(argv, options = {}) {
9
+ return await runCodeKanbanCliWithRuntime(argv, {
10
+ runCli,
11
+ createHelpText,
12
+ }, options);
13
+ }
@@ -1,6 +1,8 @@
1
- import { CodeKanbanClient } from './client.js';
2
- import { buildAgentLaunchSpec } from './command-builder.js';
3
- import { createJsonOutput } from './utils.js';
1
+ import { CodeKanbanClient, buildAgentLaunchSpec } from '@codekanban/sdk';
2
+
3
+ function createJsonOutput(value) {
4
+ return `${JSON.stringify(value, null, 2)}\n`;
5
+ }
4
6
 
5
7
  function readFlagValue(argv, index, flag) {
6
8
  const value = argv[index + 1];
@@ -37,12 +39,16 @@ function parseCliArgs(argv) {
37
39
  const flags = {
38
40
  addDirs: [],
39
41
  attachmentIds: [],
42
+ deleteFilesBefore: [],
40
43
  extraArgs: [],
44
+ readFilesAfter: [],
41
45
  includeTerminal: true,
42
46
  includeAI: true,
43
47
  refresh: false,
44
48
  clearExisting: false,
45
49
  raw: false,
50
+ strictCwd: false,
51
+ ifExists: false,
46
52
  };
47
53
 
48
54
  for (let index = 0; index < argv.length; index += 1) {
@@ -161,6 +167,10 @@ function parseCliArgs(argv) {
161
167
  flags.groupId = readFlagValue(argv, index, token);
162
168
  index += 1;
163
169
  break;
170
+ case '--scope-id':
171
+ flags.scopeId = readFlagValue(argv, index, token);
172
+ index += 1;
173
+ break;
164
174
  case '--file':
165
175
  flags.file = readFlagValue(argv, index, token);
166
176
  index += 1;
@@ -173,6 +183,10 @@ function parseCliArgs(argv) {
173
183
  flags.answersJson = readFlagValue(argv, index, token);
174
184
  index += 1;
175
185
  break;
186
+ case '--answer-strategy':
187
+ flags.answerStrategy = readFlagValue(argv, index, token);
188
+ index += 1;
189
+ break;
176
190
  case '--prev-session-id':
177
191
  flags.prevSessionId = readFlagValue(argv, index, token);
178
192
  index += 1;
@@ -189,6 +203,30 @@ function parseCliArgs(argv) {
189
203
  flags.maxEvents = readFlagValue(argv, index, token);
190
204
  index += 1;
191
205
  break;
206
+ case '--interval-ms':
207
+ flags.intervalMs = readFlagValue(argv, index, token);
208
+ index += 1;
209
+ break;
210
+ case '--settle-ms':
211
+ flags.settleMs = readFlagValue(argv, index, token);
212
+ index += 1;
213
+ break;
214
+ case '--timeout-ms':
215
+ flags.timeoutMs = readFlagValue(argv, index, token);
216
+ index += 1;
217
+ break;
218
+ case '--until':
219
+ flags.until = readFlagValue(argv, index, token);
220
+ index += 1;
221
+ break;
222
+ case '--delete-file-before':
223
+ flags.deleteFilesBefore.push(readFlagValue(argv, index, token));
224
+ index += 1;
225
+ break;
226
+ case '--read-file-after':
227
+ flags.readFilesAfter.push(readFlagValue(argv, index, token));
228
+ index += 1;
229
+ break;
192
230
  case '--refresh':
193
231
  flags.refresh = true;
194
232
  break;
@@ -198,6 +236,12 @@ function parseCliArgs(argv) {
198
236
  case '--raw':
199
237
  flags.raw = true;
200
238
  break;
239
+ case '--strict-cwd':
240
+ flags.strictCwd = true;
241
+ break;
242
+ case '--if-exists':
243
+ flags.ifExists = true;
244
+ break;
201
245
  case '--no-terminal':
202
246
  flags.includeTerminal = false;
203
247
  break;
@@ -215,6 +259,39 @@ function parseCliArgs(argv) {
215
259
  };
216
260
  }
217
261
 
262
+ export function createHelpText(commandName = 'codekanban-cli') {
263
+ return `${commandName} - CodeKanban command runtime
264
+
265
+ Usage:
266
+ ${commandName} <scope> <action> [options]
267
+
268
+ Scopes:
269
+ workflow start, command
270
+ session list, conversation, tool-result
271
+ terminal continue
272
+ file scopes, read, delete
273
+ web-session list, create, connect, snapshot, history, sync,
274
+ state, answer-pending, execute-plan, wait, run,
275
+ archived, archive, unarchive, rename, close, delete,
276
+ runtime-config, command-group, attach, send, approve,
277
+ reject, user-input, set-model, set-reasoning,
278
+ set-workflow, set-permission, set-agent, move, watch
279
+
280
+ Common options:
281
+ --base-url <url> CodeKanban server base URL
282
+ --project-id <id> Project identifier
283
+ --path <path> Local project path
284
+ --session-id <id> Session identifier
285
+ --help Show this help text
286
+
287
+ Examples:
288
+ ${commandName} session list --base-url http://127.0.0.1:3007 --path D:/repo
289
+ ${commandName} web-session state --base-url http://127.0.0.1:3007 --path D:/repo --session-id <id>
290
+ ${commandName} web-session run --base-url http://127.0.0.1:3007 --path D:/repo --agent codex --text "Create notes/123.md" --delete-file-before notes/123.md --read-file-after notes/123.md --strict-cwd
291
+ `;
292
+
293
+ }
294
+
218
295
  function buildPermissions(flags) {
219
296
  const permissions = {};
220
297
  if (flags.sandbox) {
@@ -230,6 +307,9 @@ function buildPermissions(flags) {
230
307
  }
231
308
 
232
309
  function sanitizeConnection(value) {
310
+ if (Array.isArray(value)) {
311
+ return value.map(item => sanitizeConnection(item));
312
+ }
233
313
  if (!value || typeof value !== 'object') {
234
314
  return value;
235
315
  }
@@ -318,15 +398,223 @@ async function watchWebSession(client, flags, stdout) {
318
398
  }
319
399
  }
320
400
 
401
+ const STRICT_CWD_INSTRUCTION =
402
+ 'Stay strictly inside the current working directory. Do not search sibling directories, parent directories, or nearby repositories unless the user explicitly asks.';
403
+
404
+ function normalizeChoiceLabel(value) {
405
+ return String(value || '').trim();
406
+ }
407
+
408
+ function buildAutoUserInputAnswers(questions = [], strategy = 'prefer-second-or-text') {
409
+ const normalizedStrategy = String(strategy || 'prefer-second-or-text').trim().toLowerCase();
410
+ if (!['prefer-second-or-text', 'prefer-second-or-first'].includes(normalizedStrategy)) {
411
+ throw new Error(`unsupported answer strategy: ${strategy}`);
412
+ }
413
+
414
+ const answers = {};
415
+ for (const question of Array.isArray(questions) ? questions : []) {
416
+ const questionId = normalizeChoiceLabel(question?.id);
417
+ if (!questionId) {
418
+ continue;
419
+ }
420
+ const options = Array.isArray(question?.options)
421
+ ? question.options.map(option => normalizeChoiceLabel(option?.label)).filter(Boolean)
422
+ : [];
423
+ if (options[1]) {
424
+ answers[questionId] = [options[1]];
425
+ continue;
426
+ }
427
+ if (normalizedStrategy === 'prefer-second-or-first' && options[0]) {
428
+ answers[questionId] = [options[0]];
429
+ continue;
430
+ }
431
+ answers[questionId] = [question?.isSecret ? 'redacted' : 'continue'];
432
+ }
433
+ return answers;
434
+ }
435
+
436
+ function normalizeUntil(value) {
437
+ const raw = String(value || '').trim();
438
+ if (!raw) {
439
+ return 'done';
440
+ }
441
+ const phases = raw
442
+ .split(',')
443
+ .map(entry => entry.trim())
444
+ .filter(Boolean);
445
+ if (phases.length === 0) {
446
+ return 'done';
447
+ }
448
+ return phases.length === 1 ? phases[0] : phases;
449
+ }
450
+
451
+ function withStrictCwdPrompt(text, strictCwd) {
452
+ const body = String(text || '').trim();
453
+ if (!strictCwd) {
454
+ return body;
455
+ }
456
+ return body
457
+ ? `${STRICT_CWD_INSTRUCTION}
458
+
459
+ ${body}`
460
+ : STRICT_CWD_INSTRUCTION;
461
+ }
462
+
463
+ async function answerPendingWithStrategy(client, flags) {
464
+ const answers = flags.answersJson
465
+ ? parseJsonFlag(flags.answersJson, 'answersJson')
466
+ : buildAutoUserInputAnswers([], flags.answerStrategy);
467
+ const state = await client.getWebSessionState({
468
+ projectId: flags.projectId,
469
+ path: flags.path,
470
+ sessionId: flags.sessionId,
471
+ limit: parseIntegerFlag(flags.limit, 'limit'),
472
+ });
473
+ if (!state.pendingUserInput) {
474
+ throw new Error(`web session ${flags.sessionId} has no pending user input`);
475
+ }
476
+ const resolvedAnswers = flags.answersJson
477
+ ? answers
478
+ : buildAutoUserInputAnswers(
479
+ state.pendingUserInput.questions,
480
+ flags.answerStrategy,
481
+ );
482
+ return await client.answerPendingUserInput({
483
+ projectId: flags.projectId,
484
+ path: flags.path,
485
+ sessionId: flags.sessionId,
486
+ answers: resolvedAnswers,
487
+ limit: parseIntegerFlag(flags.limit, 'limit'),
488
+ });
489
+ }
490
+
491
+ async function maybeDeleteFilesBefore(client, flags, sessionProjectId) {
492
+ const deleted = [];
493
+ for (const filePath of flags.deleteFilesBefore) {
494
+ const result = await client.deleteProjectFiles({
495
+ projectId: sessionProjectId || flags.projectId,
496
+ path: sessionProjectId ? undefined : flags.path,
497
+ scopeId: flags.scopeId,
498
+ paths: [filePath],
499
+ });
500
+ deleted.push({ path: filePath, result });
501
+ }
502
+ return deleted;
503
+ }
504
+
505
+ async function readFilesAfter(client, flags, sessionProjectId) {
506
+ const files = [];
507
+ for (const filePath of flags.readFilesAfter) {
508
+ const item = await client.readProjectFileText({
509
+ projectId: sessionProjectId || flags.projectId,
510
+ path: sessionProjectId ? undefined : flags.path,
511
+ scopeId: flags.scopeId,
512
+ filePath,
513
+ });
514
+ files.push(item);
515
+ }
516
+ return files;
517
+ }
518
+
519
+ async function runWebSessionFlow(client, flags) {
520
+ const intervalMs = parseIntegerFlag(flags.intervalMs, 'intervalMs') || 2000;
521
+ const timeoutMs = parseIntegerFlag(flags.timeoutMs, 'timeoutMs') || 120000;
522
+ const settleMs = parseIntegerFlag(flags.settleMs, 'settleMs') || 2000;
523
+
524
+ let session = null;
525
+ let sessionId = flags.sessionId;
526
+ let sessionProjectId = flags.projectId;
527
+ const initialPrompt = withStrictCwdPrompt(flags.text || flags.prompt, flags.strictCwd);
528
+
529
+ if (!sessionId) {
530
+ if (!initialPrompt) {
531
+ throw new Error('web-session run requires --session-id or an initial --text/--prompt');
532
+ }
533
+ session = await client.createWebSession({
534
+ projectId: flags.projectId,
535
+ path: flags.path,
536
+ worktreeId: flags.worktreeId,
537
+ agent: flags.agent || 'codex',
538
+ model: flags.model,
539
+ reasoningEffort: flags.reasoningEffort,
540
+ workflowMode: flags.workflowMode || 'plan',
541
+ permissionLevel: flags.permissionLevel,
542
+ permissionMode: flags.permissionMode,
543
+ title: flags.title,
544
+ });
545
+ sessionId = session?.id;
546
+ sessionProjectId = session?.projectId || sessionProjectId;
547
+ }
548
+
549
+ if (!sessionId) {
550
+ throw new Error('unable to resolve a web session id');
551
+ }
552
+
553
+ const deletedBefore = await maybeDeleteFilesBefore(client, flags, sessionProjectId);
554
+
555
+ if (initialPrompt) {
556
+ await client.sendWebSessionMessage({
557
+ sessionId,
558
+ text: initialPrompt,
559
+ attachmentIds: flags.attachmentIds,
560
+ mode: flags.mode,
561
+ });
562
+ }
563
+
564
+ const flow = await client.runWebSessionUntilDone({
565
+ projectId: sessionProjectId || flags.projectId,
566
+ path: sessionProjectId ? undefined : flags.path,
567
+ sessionId,
568
+ until: normalizeUntil(flags.until),
569
+ intervalMs,
570
+ timeoutMs,
571
+ limit: parseIntegerFlag(flags.limit, 'limit'),
572
+ settleMs,
573
+ answerStrategy: flags.answerStrategy || 'prefer-second-or-text',
574
+ autoExecutePlan: true,
575
+ executePlanPrompt: withStrictCwdPrompt('Implement the plan.', flags.strictCwd),
576
+ });
577
+
578
+ if (flow.stopReason === 'needs_approval') {
579
+ throw new Error('web-session run stopped on a pending approval; use web-session approve or reject explicitly');
580
+ }
581
+ if (flow.stopReason === 'needs_user_input') {
582
+ throw new Error('web-session run stopped on a pending user input; use web-session answer-pending explicitly');
583
+ }
584
+ if (flow.stopReason === 'needs_execute_plan') {
585
+ throw new Error('web-session run stopped before executing the latest plan; use web-session execute-plan explicitly');
586
+ }
587
+ if (flow.stopReason === 'timeout') {
588
+ throw new Error(`web-session run timed out after ${timeoutMs}ms`);
589
+ }
590
+
591
+ const filesAfter = await readFilesAfter(client, flags, sessionProjectId);
592
+ return {
593
+ session: session || { id: sessionId, projectId: sessionProjectId || flags.projectId },
594
+ deletedBefore,
595
+ actions: flow.actions,
596
+ finalState: flow.finalState,
597
+ filesAfter,
598
+ };
599
+ }
600
+
601
+
321
602
  export async function runCli(argv, options = {}) {
322
603
  const stdout = options.stdout || process.stdout;
323
604
  const stderr = options.stderr || process.stderr;
605
+ const commandName = options.commandName || 'codekanban-cli';
324
606
 
325
607
  try {
608
+ if (argv.includes('--help') || argv.includes('-h')) {
609
+ stdout.write(createHelpText(commandName));
610
+ return 0;
611
+ }
612
+
326
613
  const { positionals, flags } = parseCliArgs(argv);
327
614
  const [scope, action] = positionals;
328
615
  if (!scope || !action) {
329
- throw new Error('usage: <workflow|session|terminal|web-session> <action> --base-url <url> [...]');
616
+ stdout.write(createHelpText(commandName));
617
+ return 0;
330
618
  }
331
619
 
332
620
  if (scope === 'workflow' && action === 'command') {
@@ -341,11 +629,20 @@ export async function runCli(argv, options = {}) {
341
629
  return 0;
342
630
  }
343
631
 
632
+ if (!flags.baseURL && options.defaultBaseURL) {
633
+ flags.baseURL = options.defaultBaseURL;
634
+ }
635
+
344
636
  if (!flags.baseURL) {
345
637
  throw new Error('--base-url is required');
346
638
  }
347
639
 
348
- const client = new CodeKanbanClient({ baseURL: flags.baseURL });
640
+ const client =
641
+ options.clientFactory?.({ baseURL: flags.baseURL, flags }) ||
642
+ new CodeKanbanClient({
643
+ baseURL: flags.baseURL,
644
+ ...(options.clientOptions || {}),
645
+ });
349
646
  let result;
350
647
 
351
648
  if (scope === 'workflow' && action === 'start') {
@@ -387,6 +684,25 @@ export async function runCli(argv, options = {}) {
387
684
  sessionId: flags.sessionId,
388
685
  prompt: flags.prompt,
389
686
  });
687
+ } else if (scope === 'file' && action === 'scopes') {
688
+ result = await client.listProjectFileScopes({
689
+ projectId: flags.projectId,
690
+ path: flags.path,
691
+ });
692
+ } else if (scope === 'file' && action === 'read') {
693
+ result = await client.readProjectFileText({
694
+ projectId: flags.projectId,
695
+ path: flags.path,
696
+ scopeId: flags.scopeId,
697
+ filePath: flags.file,
698
+ });
699
+ } else if (scope === 'file' && action === 'delete') {
700
+ result = await client.deleteProjectFiles({
701
+ projectId: flags.projectId,
702
+ path: flags.path,
703
+ scopeId: flags.scopeId,
704
+ paths: [flags.file],
705
+ });
390
706
  } else if (scope === 'web-session' && action === 'list') {
391
707
  result = await client.listWebSessions({
392
708
  projectId: flags.projectId,
@@ -430,6 +746,36 @@ export async function runCli(argv, options = {}) {
430
746
  mode: flags.mode,
431
747
  clearExisting: flags.clearExisting,
432
748
  });
749
+ } else if (scope === 'web-session' && action === 'state') {
750
+ result = await client.getWebSessionState({
751
+ projectId: flags.projectId,
752
+ path: flags.path,
753
+ sessionId: flags.sessionId,
754
+ limit: parseIntegerFlag(flags.limit, 'limit'),
755
+ });
756
+ } else if (scope === 'web-session' && action === 'answer-pending') {
757
+ result = await answerPendingWithStrategy(client, flags);
758
+ } else if (scope === 'web-session' && action === 'execute-plan') {
759
+ result = await client.executeLatestPlan({
760
+ projectId: flags.projectId,
761
+ path: flags.path,
762
+ sessionId: flags.sessionId,
763
+ prompt: withStrictCwdPrompt('Implement the plan.', flags.strictCwd),
764
+ limit: parseIntegerFlag(flags.limit, 'limit'),
765
+ });
766
+ } else if (scope === 'web-session' && action === 'wait') {
767
+ result = await client.waitForWebSessionState({
768
+ projectId: flags.projectId,
769
+ path: flags.path,
770
+ sessionId: flags.sessionId,
771
+ until: normalizeUntil(flags.until),
772
+ intervalMs: parseIntegerFlag(flags.intervalMs, 'intervalMs') || 2000,
773
+ timeoutMs: parseIntegerFlag(flags.timeoutMs, 'timeoutMs') || 120000,
774
+ limit: parseIntegerFlag(flags.limit, 'limit'),
775
+ settleMs: parseIntegerFlag(flags.settleMs, 'settleMs') || 0,
776
+ });
777
+ } else if (scope === 'web-session' && action === 'run') {
778
+ result = await runWebSessionFlow(client, flags);
433
779
  } else if (scope === 'web-session' && action === 'archive') {
434
780
  result = await client.archiveWebSession({
435
781
  projectId: flags.projectId,
@@ -479,8 +825,9 @@ export async function runCli(argv, options = {}) {
479
825
  } else if (scope === 'web-session' && action === 'send') {
480
826
  result = await withWebSessionCommandChannel(client, channel =>
481
827
  channel.sendMessage(flags.sessionId, {
482
- text: flags.text || flags.prompt,
828
+ text: withStrictCwdPrompt(flags.text || flags.prompt, flags.strictCwd),
483
829
  attachmentIds: flags.attachmentIds,
830
+ mode: flags.mode,
484
831
  }),
485
832
  );
486
833
  } else if (scope === 'web-session' && action === 'approve') {
@@ -1,8 +1,8 @@
1
- # `@codekanban/sdk`
1
+ # `@codekanban/sdk`
2
2
 
3
- Node SDK and CLI for CodeKanban coding workflows, terminal sessions, and web sessions.
3
+ Node SDK for CodeKanban workflows, terminal sessions, and web sessions.
4
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.
5
+ Use `@codekanban/sdk` when you want to integrate with CodeKanban from JavaScript. For command-line usage and Codex skill packaging, use `@codekanban/cli`.
6
6
 
7
7
  ## Features
8
8
 
@@ -13,41 +13,26 @@ Use it as a programming-assistant toolkit for launching, observing, and steering
13
13
  - Support `standard`, `plan`, and `yolo` Codex profiles
14
14
  - Read terminal session lists and AI session summaries
15
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
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 derived actionable state
21
+ - Wait for a `web-session` to reach a pause/actionable state
22
+ - Run a short web-session loop with automatic user-input answers and latest-plan execution
22
23
 
23
- ## CLI
24
+ ## Install
24
25
 
25
26
  ```bash
26
- node packages/node-sdk/bin/codekanban-sdk.js workflow start --base-url http://127.0.0.1:3000 --path D:\repo --profile plan --add-dir D:\shared --prompt "Inspect and plan the refactor"
27
- node packages/node-sdk/bin/codekanban-sdk.js session list --base-url http://127.0.0.1:3000 --path D:\repo
28
- node packages/node-sdk/bin/codekanban-sdk.js session conversation --base-url http://127.0.0.1:3000 --id <db-id>
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"
27
+ npm install @codekanban/sdk
30
28
  ```
31
29
 
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
-
45
30
  ## Library
46
31
 
47
32
  ```js
48
33
  import { CodeKanbanClient } from '@codekanban/sdk';
49
34
 
50
- const client = new CodeKanbanClient({ baseURL: 'http://127.0.0.1:3000' });
35
+ const client = new CodeKanbanClient({ baseURL: 'http://127.0.0.1:3007' });
51
36
 
52
37
  const result = await client.startWorkflow({
53
38
  path: 'D:/repo',
@@ -62,7 +47,7 @@ const result = await client.startWorkflow({
62
47
  ### Web Session HTTP
63
48
 
64
49
  ```js
65
- const client = new CodeKanbanClient({ baseURL: 'http://127.0.0.1:3000' });
50
+ const client = new CodeKanbanClient({ baseURL: 'http://127.0.0.1:3007' });
66
51
 
67
52
  const session = await client.createWebSession({
68
53
  path: 'D:/repo',
@@ -86,10 +71,6 @@ const state = await client.getWebSessionState({
86
71
  sessionId: session.id,
87
72
  });
88
73
 
89
- if (state.phase === 'running') {
90
- return;
91
- }
92
-
93
74
  if (state.nextAction?.type === 'answer_user_input') {
94
75
  await client.answerPendingUserInput({
95
76
  projectId: session.projectId,
@@ -129,11 +110,54 @@ const doneState = await client.waitForWebSessionState({
129
110
  until: 'done',
130
111
  intervalMs: 5000,
131
112
  timeoutMs: 120000,
113
+ settleMs: 2000,
132
114
  });
133
115
 
134
116
  console.log(doneState.lastAssistantMessage?.text);
135
117
  ```
136
118
 
119
+ ### Waiting For Pause States
120
+
121
+ ```js
122
+ const pause = await client.waitForWebSessionPause({
123
+ projectId: session.projectId,
124
+ sessionId: session.id,
125
+ intervalMs: 1500,
126
+ timeoutMs: 120000,
127
+ settleMs: 2000,
128
+ });
129
+
130
+ console.log(pause.reason);
131
+ ```
132
+
133
+ `waitForWebSessionPause()` returns when the session stops making forward progress and needs outside judgment, for example:
134
+
135
+ - `done`
136
+ - `error`
137
+ - `approval`
138
+ - `user_input`
139
+ - `execute_plan`
140
+
141
+ ### Minimal Web Session Loop
142
+
143
+ ```js
144
+ const result = await client.runWebSessionUntilDone({
145
+ projectId: session.projectId,
146
+ sessionId: session.id,
147
+ intervalMs: 1500,
148
+ timeoutMs: 120000,
149
+ settleMs: 2000,
150
+ });
151
+
152
+ console.log(result.stopReason, result.finalState?.phase);
153
+ ```
154
+
155
+ `runWebSessionUntilDone()` automatically:
156
+
157
+ - answers ordinary structured user-input prompts with the default `prefer-second-or-text` strategy
158
+ - executes the latest plan when the session reaches an execute-plan pause
159
+ - returns control on `needs_approval`, `needs_user_input`, `needs_execute_plan`, `done`, `error`, `until`, or `timeout`
160
+
137
161
  ### Web Session Command Channel
138
162
 
139
163
  ```js
@@ -1,18 +1,13 @@
1
1
  {
2
2
  "name": "@codekanban/sdk",
3
3
  "version": "0.1.0",
4
- "description": "Node SDK and CLI for launching CodeKanban coding workflows, terminal sessions, and web sessions.",
4
+ "description": "Node SDK for CodeKanban workflows, terminal sessions, and web sessions.",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
7
7
  "exports": {
8
- ".": "./src/index.js",
9
- "./cli": "./src/cli.js"
10
- },
11
- "bin": {
12
- "codekanban-sdk": "./bin/codekanban-sdk.js"
8
+ ".": "./src/index.js"
13
9
  },
14
10
  "files": [
15
- "bin",
16
11
  "src",
17
12
  "README.md"
18
13
  ],