kanbo-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/README.md +2 -2
- package/dist/cli.cjs +185 -39
- package/dist/cli.cjs.map +1 -1
- package/dist/lib/{board-store-B16YarvX.d.mts → board-store-BfszzAJB.d.cts} +2 -2
- package/dist/lib/{board-store-BHHo_ZlR.d.cts → board-store-BvWn9xTB.d.mts} +2 -2
- package/dist/lib/index.cjs +2 -29
- package/dist/lib/index.cjs.map +1 -1
- package/dist/lib/index.d.cts +363 -363
- package/dist/lib/index.d.mts +363 -363
- package/dist/lib/index.mjs +1 -28
- package/dist/lib/index.mjs.map +1 -1
- package/dist/lib/{manifest-CpZOcB_6.cjs → manifest-CmiuDqH-.cjs} +200 -41
- package/dist/lib/manifest-CmiuDqH-.cjs.map +1 -0
- package/dist/lib/{manifest-C0EcwwgO.mjs → manifest-NRhhhOkC.mjs} +190 -43
- package/dist/lib/manifest-NRhhhOkC.mjs.map +1 -0
- package/dist/lib/mcp/index.cjs +2 -1
- package/dist/lib/mcp/index.d.cts +29 -12
- package/dist/lib/mcp/index.d.mts +29 -12
- package/dist/lib/mcp/index.mjs +2 -2
- package/dist/lib/postgres/index.d.cts +151 -151
- package/dist/lib/postgres/index.d.mts +151 -151
- package/dist/lib/{schema-fsHqNAip.d.cts → schema-D0MyXcej.d.cts} +41 -41
- package/dist/lib/{schema-fsHqNAip.d.mts → schema-D0MyXcej.d.mts} +41 -41
- package/dist/lib/sqlite/index.d.cts +12 -12
- package/dist/lib/sqlite/index.d.mts +12 -12
- package/dist/lib/sqlite/schema.d.cts +1 -1
- package/dist/lib/sqlite/schema.d.mts +1 -1
- package/dist/lib/{tool-names-9cmCn0gP.d.cts → tool-names-3q_GEZej.d.mts} +5 -5
- package/dist/lib/{tool-names-Dxg1UTUl.d.mts → tool-names-WfH4IURz.d.cts} +5 -5
- package/dist/page/board.js +1 -1
- package/package.json +4 -4
- package/dist/lib/manifest-C0EcwwgO.mjs.map +0 -1
- package/dist/lib/manifest-CpZOcB_6.cjs.map +0 -1
|
@@ -4,7 +4,7 @@ import { a as openBoardDatabase, c as createSqliteBoardStore, i as markBoardFile
|
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import { hostname, userInfo } from "node:os";
|
|
7
|
-
import { createHash, randomUUID, timingSafeEqual } from "node:crypto";
|
|
7
|
+
import { createHash, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
8
8
|
import { sql } from "drizzle-orm";
|
|
9
9
|
import { chmodSync, existsSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
10
10
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
@@ -14,6 +14,7 @@ import postgres from "postgres";
|
|
|
14
14
|
import { fileURLToPath } from "node:url";
|
|
15
15
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
16
16
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
17
|
+
import { spawn } from "node:child_process";
|
|
17
18
|
import { createServer } from "node:http";
|
|
18
19
|
/**
|
|
19
20
|
* Every exit code this tool promises, and what it means — the capabilities
|
|
@@ -2290,32 +2291,61 @@ async function migrateCards(store, input) {
|
|
|
2290
2291
|
};
|
|
2291
2292
|
}
|
|
2292
2293
|
//#endregion
|
|
2293
|
-
//#region src/ops/
|
|
2294
|
-
/**
|
|
2295
|
-
* The
|
|
2294
|
+
//#region src/ops/agent-rules.ts
|
|
2295
|
+
/**
|
|
2296
|
+
* The sentences that tell an agent how to work this board, written once.
|
|
2297
|
+
*
|
|
2298
|
+
* Four surfaces say them: the text `kanbo prime` returns (`BOARD_RULES`), the
|
|
2299
|
+
* block `kanbo init` writes into a project's `CLAUDE.md` / `AGENTS.md`, the
|
|
2300
|
+
* `instructions` the `kanbo mcp` server sends when a client connects, and the
|
|
2301
|
+
* `kanbo_card_create` tool's description. Each surface adds its own way of
|
|
2302
|
+
* doing the thing — a command, a tool name — but the rule itself is read from
|
|
2303
|
+
* here, so the four cannot come to say different things.
|
|
2304
|
+
*
|
|
2305
|
+
* No sentence names a tool or a command: the same text reaches an agent holding
|
|
2306
|
+
* MCP tools, one with a command line, and one talking to a server over HTTP.
|
|
2307
|
+
*/
|
|
2308
|
+
const MOVE_CARD_RULE = "You move the card between columns yourself, at the moment its real state changes. The board never moves a card for you.";
|
|
2309
|
+
const STATUS_LINE_RULE = "Write a status line at every step, including before and after anything long-running. One sentence, present tense, about what is happening right now.";
|
|
2310
|
+
const WAIT_FOR_PERSON_RULE = "When you need a person, mark the card as waiting for approval and end your turn. You will be told when the answer comes.";
|
|
2311
|
+
const PERSON_ONLY_RULE = "Only a person approves a card, and only a person takes one out of waiting. Never do either yourself.";
|
|
2312
|
+
const EXECUTION_MODE_RULE = "Each card says where it runs: `worktree` means a separate copy of the project, `main` means the project folder itself. Work in the mode the card names.";
|
|
2313
|
+
/** When to split a card at all — said on its own where the surface names the parent its own way. */
|
|
2314
|
+
const SUBTASK_WHEN_SENTENCE = "Create subtasks only when the person asks for them, or when a task has parts that can be done and checked separately (different stages, owners or pull requests); do not split small work you will finish in one go.";
|
|
2315
|
+
/** How to split one, with the way this surface names the parent card. */
|
|
2316
|
+
function subtaskSplitSentence(parentReference) {
|
|
2317
|
+
return `When you do split a card, make the parts subtasks of it (${parentReference}), not separate top-level cards.`;
|
|
2318
|
+
}
|
|
2319
|
+
const SUBTASK_RULE = `${SUBTASK_WHEN_SENTENCE} ${subtaskSplitSentence("the parent card's id")}`;
|
|
2320
|
+
const PULL_REQUEST_RULE = "Opened a pull request? Link it to the card. Its state and CI checks then reach the card as comments on their own — nothing else tells the board a pull request exists.";
|
|
2321
|
+
const OWN_SESSION_RULE = "Started the work yourself, not launched by an app that tracks the run for you? Say so when you start the run — `claude:<session id>` from Claude Code, `codex:<session id>` from Codex (the session id Codex prints) — so your own log of it can be found later.";
|
|
2322
|
+
/** Said by the `kanbo init` block and the MCP instructions, which name the comment command or tool beside it. */
|
|
2323
|
+
const COMMENT_RULE = "Findings, results, decisions and questions go on the card as comments.";
|
|
2324
|
+
/**
|
|
2325
|
+
* The rules of this board, as `kanbo prime` says them under its column list.
|
|
2296
2326
|
*
|
|
2297
2327
|
* They are about the board and nothing else. The board does not hand out a role, a
|
|
2298
2328
|
* method or a pipeline — that belongs to the instructions the person wrote for
|
|
2299
2329
|
* their own agent — so the list stops at what the board itself guarantees and
|
|
2300
|
-
* expects.
|
|
2301
|
-
* tools, one with a command line, and one talking to a server over HTTP, and each
|
|
2302
|
-
* of them already knows what it is holding.
|
|
2330
|
+
* expects.
|
|
2303
2331
|
*
|
|
2304
|
-
*
|
|
2305
|
-
*
|
|
2306
|
-
*
|
|
2332
|
+
* Also the source `buildCapabilitiesManifest` reads its `rules` from — one list,
|
|
2333
|
+
* read by the prompt an agent sees and by the manifest an orchestrator reads
|
|
2334
|
+
* about the board.
|
|
2307
2335
|
*/
|
|
2308
2336
|
const BOARD_RULES = [
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2337
|
+
MOVE_CARD_RULE,
|
|
2338
|
+
STATUS_LINE_RULE,
|
|
2339
|
+
WAIT_FOR_PERSON_RULE,
|
|
2340
|
+
PERSON_ONLY_RULE,
|
|
2341
|
+
EXECUTION_MODE_RULE,
|
|
2342
|
+
SUBTASK_RULE,
|
|
2343
|
+
PULL_REQUEST_RULE,
|
|
2344
|
+
OWN_SESSION_RULE,
|
|
2317
2345
|
"Read the column descriptions above before moving a card, and do all of this through the board tools or commands you have — a statement in your reply changes nothing."
|
|
2318
2346
|
];
|
|
2347
|
+
//#endregion
|
|
2348
|
+
//#region src/ops/prime.ts
|
|
2319
2349
|
/**
|
|
2320
2350
|
* What the board tells an agent about itself: the columns it has, what each one
|
|
2321
2351
|
* means, and how a card is expected to travel across them.
|
|
@@ -3653,6 +3683,33 @@ function readCardNumber(session, reference) {
|
|
|
3653
3683
|
return reference.slice(0, separator).toUpperCase() === prefix ? Number(digits) : null;
|
|
3654
3684
|
}
|
|
3655
3685
|
//#endregion
|
|
3686
|
+
//#region src/domain/card-display-title.ts
|
|
3687
|
+
/**
|
|
3688
|
+
* What a card is called wherever a person or an agent reads it by name.
|
|
3689
|
+
*
|
|
3690
|
+
* A card written as a description alone is titled with its own key (every
|
|
3691
|
+
* writer does that). Named by its key, such a card says nothing — so its name
|
|
3692
|
+
* is the first line of its description instead. A browser bundle that does not
|
|
3693
|
+
* load this package keeps the same rule on its own, tested with the same
|
|
3694
|
+
* examples.
|
|
3695
|
+
*/
|
|
3696
|
+
/** How long a description line may run before it is cut with an ellipsis. */
|
|
3697
|
+
const DESCRIPTION_LINE_LIMIT = 120;
|
|
3698
|
+
function cardDisplayTitle(card) {
|
|
3699
|
+
if (card.title !== card.id) return card.title;
|
|
3700
|
+
return descriptionFirstLine(card.description) ?? card.title;
|
|
3701
|
+
}
|
|
3702
|
+
function descriptionFirstLine(description) {
|
|
3703
|
+
for (const rawLine of (description ?? "").split("\n")) {
|
|
3704
|
+
const line = stripMarkdown(rawLine);
|
|
3705
|
+
if (line) return line.length > DESCRIPTION_LINE_LIMIT ? `${line.slice(0, 119).trimEnd()}…` : line;
|
|
3706
|
+
}
|
|
3707
|
+
return null;
|
|
3708
|
+
}
|
|
3709
|
+
function stripMarkdown(line) {
|
|
3710
|
+
return line.replace(/^\s*(?:>\s*)+/, "").replace(/^\s*#{1,6}\s+/, "").replace(/^\s*(?:[-*+]|\d+[.)])\s+(?:\[[ x]\]\s+)?/i, "").replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/<[^>]+>/g, "").replace(/[*_~`]/g, "").replace(/\s+/g, " ").trim();
|
|
3711
|
+
}
|
|
3712
|
+
//#endregion
|
|
3656
3713
|
//#region src/cli/view.ts
|
|
3657
3714
|
/** Every field `kanbo card list|get --json` may ask for, in listing order. */
|
|
3658
3715
|
const CARD_VIEW_FIELDS = [
|
|
@@ -3769,16 +3826,16 @@ function projectRun(run) {
|
|
|
3769
3826
|
launchedByKind: run.launchedByKind
|
|
3770
3827
|
};
|
|
3771
3828
|
}
|
|
3772
|
-
/** One card per line: the key, the column it is in, and what it is called. */
|
|
3829
|
+
/** One card per line: the key, the column it is in, and what it is called (`cardDisplayTitle`). */
|
|
3773
3830
|
function describeCards(cards) {
|
|
3774
3831
|
if (cards.length === 0) return "No cards";
|
|
3775
3832
|
const keyWidth = Math.max(...cards.map((card) => card.id.length));
|
|
3776
3833
|
const columnWidth = Math.max(...cards.map((card) => (card.column ?? "—").length));
|
|
3777
|
-
return cards.map((card) => `${card.id.padEnd(keyWidth)} ${(card.column ?? "—").padEnd(columnWidth)} ${card
|
|
3834
|
+
return cards.map((card) => `${card.id.padEnd(keyWidth)} ${(card.column ?? "—").padEnd(columnWidth)} ${cardDisplayTitle(card)}`).join("\n");
|
|
3778
3835
|
}
|
|
3779
3836
|
/** The whole card, for a person who asked about exactly one. */
|
|
3780
3837
|
function describeCard(card) {
|
|
3781
|
-
const lines = [`${card.id} ${card.column ?? "—"} ${card.executionMode}`, card
|
|
3838
|
+
const lines = [`${card.id} ${card.column ?? "—"} ${card.executionMode}`, cardDisplayTitle(card)];
|
|
3782
3839
|
if (card.description) lines.push("", card.description);
|
|
3783
3840
|
const facts = [
|
|
3784
3841
|
card.statusLine ? `status line: ${card.statusLine}` : null,
|
|
@@ -4306,7 +4363,8 @@ const CODEX_TABLE_PATTERN = new RegExp(String.raw`^\s*\[\s*mcp_servers\s*\.\s*(?
|
|
|
4306
4363
|
*
|
|
4307
4364
|
* It says what to run and when, and nothing about how to do the work: the
|
|
4308
4365
|
* method is the person's own instructions to their agent, and the board only
|
|
4309
|
-
* insists on the part that keeps the card honest.
|
|
4366
|
+
* insists on the part that keeps the card honest. The rules are the sentences
|
|
4367
|
+
* `ops/agent-rules.ts` holds for every surface, with the command beside each.
|
|
4310
4368
|
*/
|
|
4311
4369
|
const INSTRUCTION_BLOCK = [
|
|
4312
4370
|
INSTRUCTION_START,
|
|
@@ -4315,12 +4373,13 @@ const INSTRUCTION_BLOCK = [
|
|
|
4315
4373
|
"Work on this project is tracked on a kanbo board. Before starting a task run `kanbo prime` — it prints the board's columns and what each one means.",
|
|
4316
4374
|
"",
|
|
4317
4375
|
"- Take a card from `kanbo ready`.",
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4376
|
+
`- ${MOVE_CARD_RULE} (\`kanbo card move <id> <column>\`)`,
|
|
4377
|
+
`- ${STATUS_LINE_RULE} (\`kanbo card status-line <id> --text "..."\`)`,
|
|
4378
|
+
`- ${SUBTASK_RULE} (\`kanbo card create --description "..." --parent <id>\`)`,
|
|
4379
|
+
`- ${COMMENT_RULE} (\`kanbo card comment <id> --content "..."\`)`,
|
|
4380
|
+
`- ${WAIT_FOR_PERSON_RULE} (\`kanbo card wait-approval <id>\`)`,
|
|
4381
|
+
`- ${PERSON_ONLY_RULE}`,
|
|
4382
|
+
`- ${OWN_SESSION_RULE} (\`kanbo run start <id> --agent <name> --session <ref>\`)`,
|
|
4324
4383
|
"- Run `kanbo capabilities` for the full list of tools, commands and rules.",
|
|
4325
4384
|
INSTRUCTION_END
|
|
4326
4385
|
].join("\n");
|
|
@@ -5112,7 +5171,7 @@ dirname(fileURLToPath(import.meta.url));
|
|
|
5112
5171
|
* `package.version`. A build carries it as a literal; running from source
|
|
5113
5172
|
* (tests, `tsx`) reads the package's own `package.json` instead.
|
|
5114
5173
|
*/
|
|
5115
|
-
const KANBO_PACKAGE_VERSION = "0.1.
|
|
5174
|
+
const KANBO_PACKAGE_VERSION = "0.1.2";
|
|
5116
5175
|
//#endregion
|
|
5117
5176
|
//#region src/mcp/resources.ts
|
|
5118
5177
|
/** The URI a client reads the manifest as JSON from. */
|
|
@@ -5153,6 +5212,36 @@ function registerKanboCapabilitiesResources(server) {
|
|
|
5153
5212
|
}] }));
|
|
5154
5213
|
}
|
|
5155
5214
|
//#endregion
|
|
5215
|
+
//#region src/mcp/instructions.ts
|
|
5216
|
+
/** A tool name as the instructions print it — typed, so a renamed tool fails the build here. */
|
|
5217
|
+
function tool(name) {
|
|
5218
|
+
return `\`${name}\``;
|
|
5219
|
+
}
|
|
5220
|
+
/**
|
|
5221
|
+
* What `kanbo mcp` tells a client when it connects — the `instructions` of the
|
|
5222
|
+
* MCP initialize result, which a client hands its model before any tool is
|
|
5223
|
+
* called. An agent with nothing but this server connected learns from it how
|
|
5224
|
+
* to work the board, with no instruction file in the project.
|
|
5225
|
+
*
|
|
5226
|
+
* Static on purpose: it is sent before any board is resolved, so it says
|
|
5227
|
+
* nothing about columns — `kanbo_prime` returns those. The rules are the
|
|
5228
|
+
* sentences `ops/agent-rules.ts` holds for every surface, with the tool beside
|
|
5229
|
+
* each; the full manifest stays a resource.
|
|
5230
|
+
*/
|
|
5231
|
+
const KANBO_MCP_INSTRUCTIONS = [
|
|
5232
|
+
"kanbo is a kanban board shared by people and agents; these tools read and change its cards.",
|
|
5233
|
+
"",
|
|
5234
|
+
`- At the start of a session call ${tool("kanbo_prime")} — it returns this board's columns and what each one means.`,
|
|
5235
|
+
`- Take work from ${tool("kanbo_ready")}.`,
|
|
5236
|
+
`- ${MOVE_CARD_RULE} (${tool("kanbo_card_move")})`,
|
|
5237
|
+
`- ${STATUS_LINE_RULE} (${tool("kanbo_status_line")})`,
|
|
5238
|
+
`- ${COMMENT_RULE} (${tool("kanbo_card_comment")})`,
|
|
5239
|
+
`- ${SUBTASK_RULE} (${tool("kanbo_card_create")} with \`parent\`)`,
|
|
5240
|
+
`- ${WAIT_FOR_PERSON_RULE} (${tool("kanbo_wait_approval")})`,
|
|
5241
|
+
`- ${PERSON_ONLY_RULE}`,
|
|
5242
|
+
`- Every tool, rule and canonical string: the resource \`${KANBO_CAPABILITIES_MARKDOWN_RESOURCE_URI}\`.`
|
|
5243
|
+
].join("\n");
|
|
5244
|
+
//#endregion
|
|
5156
5245
|
//#region src/mcp/tools/tool.ts
|
|
5157
5246
|
/**
|
|
5158
5247
|
* Declare a tool: what it is called, what it takes, and the one thing it does.
|
|
@@ -5250,7 +5339,7 @@ const SUBTASK_SENTENCE = "To create a subtask (sub-card) under another card, pas
|
|
|
5250
5339
|
const cardCreateTool = defineKanboTool({
|
|
5251
5340
|
name: "kanbo_card_create",
|
|
5252
5341
|
title: "Create a card",
|
|
5253
|
-
description: `Put a new card on this board. ${SUBTASK_SENTENCE}
|
|
5342
|
+
description: `Put a new card on this board. ${SUBTASK_SENTENCE} ${SUBTASK_WHEN_SENTENCE} ${subtaskSplitSentence("pass `parent`")}`,
|
|
5254
5343
|
inputSchema: {
|
|
5255
5344
|
title: z.string().optional().describe("What the card is called. Leave it out and the card is titled with its own key; the description then says what the task is."),
|
|
5256
5345
|
description: z.string().optional().describe("What the card is about, spelled out for whoever picks it up."),
|
|
@@ -5522,12 +5611,15 @@ function registerKanboTools(server, transport, options) {
|
|
|
5522
5611
|
* function: it documents its capabilities its own way, and adding a second
|
|
5523
5612
|
* `kanbo://` resource namespace to an inventory that is not only the board's
|
|
5524
5613
|
* is a decision for that server to make, not this one.
|
|
5614
|
+
*
|
|
5615
|
+
* For the same reason it alone sends `KANBO_MCP_INSTRUCTIONS` in the initialize
|
|
5616
|
+
* result: the instructions of a server that is only the board.
|
|
5525
5617
|
*/
|
|
5526
5618
|
function createKanboMcpServer(transport, options) {
|
|
5527
5619
|
const server = new McpServer({
|
|
5528
5620
|
name: KANBO_MCP_SERVER_NAME,
|
|
5529
5621
|
version: KANBO_MCP_SERVER_VERSION
|
|
5530
|
-
});
|
|
5622
|
+
}, { instructions: KANBO_MCP_INSTRUCTIONS });
|
|
5531
5623
|
registerKanboTools(server, transport, options);
|
|
5532
5624
|
registerKanboCapabilitiesResources(server);
|
|
5533
5625
|
return server;
|
|
@@ -7316,12 +7408,11 @@ async function startKanboServer(options) {
|
|
|
7316
7408
|
}
|
|
7317
7409
|
/**
|
|
7318
7410
|
* Who may act for a person on this server, said once when it starts: only a
|
|
7319
|
-
* request presenting the token does, and a server
|
|
7320
|
-
*
|
|
7411
|
+
* request presenting the token does, and a server started from an agent's
|
|
7412
|
+
* shell has nobody who may.
|
|
7321
7413
|
*/
|
|
7322
|
-
function describePersonRights(actors
|
|
7414
|
+
function describePersonRights(actors) {
|
|
7323
7415
|
if (!actors.person) return "no request acts for a person (started from an agent's shell)";
|
|
7324
|
-
if (!token) return "no request acts for a person (no token; set KANBO_SERVE_TOKEN to allow it)";
|
|
7325
7416
|
return `a request with the token acts for ${actors.person.id}`;
|
|
7326
7417
|
}
|
|
7327
7418
|
function stripBrackets(host) {
|
|
@@ -7503,12 +7594,58 @@ function sendJson(response, status, value) {
|
|
|
7503
7594
|
response.end(body);
|
|
7504
7595
|
}
|
|
7505
7596
|
//#endregion
|
|
7597
|
+
//#region src/serve/access.ts
|
|
7598
|
+
/** The token given, or one made up for a loopback server; refused off loopback without one. */
|
|
7599
|
+
function resolveServeAccess(host, givenToken) {
|
|
7600
|
+
assertServeBinding(host, givenToken);
|
|
7601
|
+
if (givenToken) return {
|
|
7602
|
+
token: givenToken,
|
|
7603
|
+
generated: false
|
|
7604
|
+
};
|
|
7605
|
+
return {
|
|
7606
|
+
token: randomBytes(24).toString("base64url"),
|
|
7607
|
+
generated: true
|
|
7608
|
+
};
|
|
7609
|
+
}
|
|
7610
|
+
/**
|
|
7611
|
+
* Hand the person the board page: a link printed once, and opened in the
|
|
7612
|
+
* browser unless they said not to. A generated token rides in the link's
|
|
7613
|
+
* fragment; one they gave is theirs already and is left out.
|
|
7614
|
+
*/
|
|
7615
|
+
function presentServePage(presentation, output) {
|
|
7616
|
+
const { url, access, open } = presentation;
|
|
7617
|
+
if (presentation.agentShell || !presentation.terminal) {
|
|
7618
|
+
if (access.generated) output.print("kanbo serve: a token was generated for this run and is not shown here; set KANBO_SERVE_TOKEN to choose your own");
|
|
7619
|
+
return;
|
|
7620
|
+
}
|
|
7621
|
+
const link = access.generated ? `${url}/#token=${access.token}` : `${url}/`;
|
|
7622
|
+
output.print(`kanbo serve: board page ${link}`);
|
|
7623
|
+
if (open) output.openInBrowser(link);
|
|
7624
|
+
}
|
|
7625
|
+
/** Open a link in the default browser, without waiting for it and without failing when there is none. */
|
|
7626
|
+
function openInBrowser(url) {
|
|
7627
|
+
const [command, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", [
|
|
7628
|
+
"/c",
|
|
7629
|
+
"start",
|
|
7630
|
+
"\"\"",
|
|
7631
|
+
url
|
|
7632
|
+
]] : ["xdg-open", [url]];
|
|
7633
|
+
try {
|
|
7634
|
+
const child = spawn(command, args, {
|
|
7635
|
+
detached: true,
|
|
7636
|
+
stdio: "ignore",
|
|
7637
|
+
windowsVerbatimArguments: process.platform === "win32"
|
|
7638
|
+
});
|
|
7639
|
+
child.on("error", () => {});
|
|
7640
|
+
child.unref();
|
|
7641
|
+
} catch {}
|
|
7642
|
+
}
|
|
7643
|
+
//#endregion
|
|
7506
7644
|
//#region src/cli/commands/serve.ts
|
|
7507
7645
|
function registerServeCommand(program) {
|
|
7508
|
-
program.command("serve").description("serve this board over HTTP, with a board page for the browser").option("--port <port>", "port to listen on", String(DEFAULT_SERVE_PORT)).option("--host <host>", "address to bind; anything but loopback needs a token", DEFAULT_SERVE_HOST).option("--db <path>", "board database file to open").option("--database-url <url>", "external Postgres board to serve instead of a board file").option("--workspace <nameOrId>", "workspace the server is about").option("--token <token>", "bearer token every request must carry (or set KANBO_SERVE_TOKEN)").option("--cors-origin <origin>", "let a browser call from this exact origin; repeat for more", collect, []).action(async (options) => {
|
|
7646
|
+
program.command("serve").description("serve this board over HTTP, with a board page for the browser").option("--port <port>", "port to listen on", String(DEFAULT_SERVE_PORT)).option("--host <host>", "address to bind; anything but loopback needs a token", DEFAULT_SERVE_HOST).option("--db <path>", "board database file to open").option("--database-url <url>", "external Postgres board to serve instead of a board file").option("--workspace <nameOrId>", "workspace the server is about").option("--token <token>", "bearer token every request must carry (or set KANBO_SERVE_TOKEN); made up for the run on loopback when absent").option("--cors-origin <origin>", "let a browser call from this exact origin; repeat for more", collect, []).option("--no-open", "print the board page link without opening it in the browser").action(async (options) => {
|
|
7509
7647
|
const port = parsePort(options.port);
|
|
7510
|
-
const
|
|
7511
|
-
assertServeBinding(options.host, token);
|
|
7648
|
+
const access = resolveServeAccess(options.host, options.token?.trim() || process.env.KANBO_SERVE_TOKEN?.trim() || null);
|
|
7512
7649
|
const session = await openBoardSession({
|
|
7513
7650
|
db: options.db,
|
|
7514
7651
|
databaseUrl: options.databaseUrl,
|
|
@@ -7526,15 +7663,25 @@ function registerServeCommand(program) {
|
|
|
7526
7663
|
actors,
|
|
7527
7664
|
host: options.host,
|
|
7528
7665
|
port,
|
|
7529
|
-
token,
|
|
7666
|
+
token: access.token,
|
|
7530
7667
|
corsOrigins: options.corsOrigin
|
|
7531
7668
|
});
|
|
7532
7669
|
} catch (error) {
|
|
7533
7670
|
await session.close();
|
|
7534
7671
|
throw error;
|
|
7535
7672
|
}
|
|
7536
|
-
console.error(`kanbo serve: ${server.url} — workspace ${session.workspace.id}${
|
|
7537
|
-
console.error(`kanbo serve: ${describePersonRights(actors
|
|
7673
|
+
console.error(`kanbo serve: ${server.url} — workspace ${session.workspace.id}, bearer token required${access.generated ? " (generated for this run)" : ""}`);
|
|
7674
|
+
console.error(`kanbo serve: ${describePersonRights(actors)}`);
|
|
7675
|
+
presentServePage({
|
|
7676
|
+
url: server.url,
|
|
7677
|
+
access,
|
|
7678
|
+
agentShell,
|
|
7679
|
+
terminal: process.stdout.isTTY === true,
|
|
7680
|
+
open: options.open
|
|
7681
|
+
}, {
|
|
7682
|
+
print: (line) => console.log(line),
|
|
7683
|
+
openInBrowser
|
|
7684
|
+
});
|
|
7538
7685
|
let stopping = false;
|
|
7539
7686
|
const stop = async () => {
|
|
7540
7687
|
if (stopping) return;
|
|
@@ -7861,6 +8008,6 @@ function buildCapabilitiesManifest() {
|
|
|
7861
8008
|
};
|
|
7862
8009
|
}
|
|
7863
8010
|
//#endregion
|
|
7864
|
-
export {
|
|
8011
|
+
export { commentAuthorReference as $, maskDatabaseUrls as A, readEntryRules as B, cardDisplayTitle as C, readProjectBinding as D, readBinding as E, ENTRY_RULES as F, normalizeStatusName as G, readChangeSeq as H, ENTRY_RULE_DESCRIPTIONS as I, BOARD_ACTOR as J, TRACKED_FIELDS as K, UNMET_ENTRY_RULE_REASONS as L, formatExternalSessionRef as M, parseExternalSessionRef as N, writeBinding as O, pullRequestFactKeys as P, activityToken as Q, evaluateEntryRules as R, renderCapabilitiesMarkdown as S, ignoreBoardFile as T, runBoardWrite as U, serializeEntryRules as V, DEFAULT_STATUSES as W, activityKindOrder as X, currentUnixSeconds as Y, activityText as Z, projectKanboPullRequest as _, BOARD_ERROR_RESPONSES as a, isOutsideWriter as at, projectKanboSubCard as b, registerKanboTools as c, formatIssueId as ct, KANBO_MCP_INSTRUCTIONS as d, nextIssueOrder as dt, formatActivityIssueReference as et, KANBO_CAPABILITIES_MARKDOWN_RESOURCE_URI as f, readIssuePrefix as ft, projectKanboColumn as g, projectKanboCard as h, KANBO_TOOL_NAMES as i, readSystemCommentKind as it, createBoardOps as j, maskDatabaseUrl as k, KANBO_TOOLS as l, nextIssueIdentity as lt, registerKanboCapabilitiesResources as m, buildCapabilitiesManifest as n, isEmptyActivityValue as nt, KANBO_MCP_SERVER_NAME as o, normalizeCommentAuthorKind as ot, KANBO_CAPABILITIES_RESOURCE_URI as p, diffFieldChanges as q, KANBO_TOOL_CLI_EQUIVALENTS as r, parseActivityStringArray as rt, createKanboMcpServer as s, toIssueView as st, CAPABILITIES_MANIFEST_VERSION as t, formatPlainActivityValue as tt, buildKanboTools as u, nextIssueNumber as ut, projectKanboRun as v, BINDING_FILE_PATH as w, DEFAULT_BOARD_FILE_PATH as x, projectKanboSprint as y, readChecklist as z };
|
|
7865
8012
|
|
|
7866
|
-
//# sourceMappingURL=manifest-
|
|
8013
|
+
//# sourceMappingURL=manifest-NRhhhOkC.mjs.map
|