codekanban 0.31.0 → 0.33.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/README.md +22 -0
- package/README.zh-CN.md +15 -0
- package/package.json +6 -6
- package/packages/codekanban-cli/README.md +118 -0
- package/packages/codekanban-cli/package.json +48 -0
- package/packages/codekanban-cli/release/install-cli-unix.sh +9 -0
- package/packages/codekanban-cli/release/install-skills-unix.sh +12 -0
- package/packages/codekanban-cli/release/install-unix.sh +16 -0
- package/packages/codekanban-cli/release/install-windows.cmd +19 -0
- package/packages/codekanban-cli/skills/README.md +18 -0
- package/packages/codekanban-cli/skills/codekanban-cli/agents/openai.yaml +4 -0
- package/packages/codekanban-cli/src/core.js +1019 -0
- package/packages/codekanban-cli/src/index.js +13 -0
- package/packages/{node-sdk/src/cli.js → codekanban-cli/src/runtime.js} +353 -6
- package/packages/node-sdk/README.md +57 -33
- package/packages/node-sdk/package.json +2 -7
- package/packages/node-sdk/src/client.js +810 -56
- package/packages/node-sdk/src/terminal-connection.js +2 -2
- package/packages/node-sdk/src/utils.js +25 -3
- package/packages/node-sdk/src/web-session-command-channel.js +2 -2
- package/packages/node-sdk/src/web-session-event-stream.js +1 -1
- package/packages/node-sdk/src/web-session-shared.js +44 -1
- package/skills/codekanban-project-session/agents/openai.yaml +0 -4
- package/skills/codekanban-project-session/scripts/run-codekanban-session.mjs +0 -7
|
@@ -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 '
|
|
2
|
-
|
|
3
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
3
|
+
Node SDK for CodeKanban workflows, terminal sessions, and web sessions.
|
|
4
4
|
|
|
5
|
-
Use
|
|
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
|
|
17
|
-
- Read `web
|
|
18
|
-
- Send `web
|
|
19
|
-
- Receive normalized `web
|
|
20
|
-
- Poll a `web
|
|
21
|
-
-
|
|
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
|
-
##
|
|
24
|
+
## Install
|
|
24
25
|
|
|
25
26
|
```bash
|
|
26
|
-
|
|
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:
|
|
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:
|
|
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
|
|
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
|
],
|