kanban-lite 1.2.2 → 1.2.4

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.
Files changed (39) hide show
  1. package/dist/cli.js +257 -90
  2. package/dist/extension.js +245 -78
  3. package/dist/mcp-server.js +117 -33
  4. package/dist/sdk/index.cjs +117 -32
  5. package/dist/sdk/index.mjs +117 -32
  6. package/dist/sdk/sdk/KanbanSDK.d.ts +27 -10
  7. package/dist/sdk/sdk/modules/cards.d.ts +11 -2
  8. package/dist/sdk/sdk/plugins/index.d.ts +7 -0
  9. package/dist/sdk/sdk/types.d.ts +12 -27
  10. package/dist/sdk/shared/config.d.ts +17 -1
  11. package/dist/standalone-webview/index.js +38 -38
  12. package/dist/standalone-webview/index.js.map +1 -1
  13. package/dist/standalone.js +307 -125
  14. package/package.json +1 -1
  15. package/src/cli/index.test.ts +157 -0
  16. package/src/cli/index.ts +1 -1
  17. package/src/mcp-server/index.test.ts +76 -0
  18. package/src/mcp-server/index.ts +1 -1
  19. package/src/sdk/KanbanSDK.d.ts +26 -10
  20. package/src/sdk/KanbanSDK.ts +37 -11
  21. package/src/sdk/__tests__/KanbanSDK.test.ts +79 -24
  22. package/src/sdk/integrationCatalog.ts +1 -0
  23. package/src/sdk/modules/cards.ts +13 -24
  24. package/src/sdk/plugins/index.d.ts +7 -0
  25. package/src/sdk/plugins/index.ts +17 -2
  26. package/src/sdk/types.d.ts +10 -26
  27. package/src/sdk/types.ts +11 -24
  28. package/src/sdk/webhooks.ts +19 -2
  29. package/src/shared/config.ts +130 -2
  30. package/src/standalone/__tests__/server.integration.test.ts +81 -2
  31. package/src/standalone/internal/runtime.ts +11 -6
  32. package/src/standalone/internal/websocket.ts +13 -3
  33. package/src/standalone/server.ts +67 -9
  34. package/src/standalone/watcherSetup.ts +9 -0
  35. package/src/webview/standalone-shim.ts +2 -1
  36. package/tmp/screenshots-workspace/.kanban/.active-card.json +5 -0
  37. package/tmp/screenshots-workspace/.kanban/boards/default/deleted/1-dddd.md +17 -0
  38. package/tmp/screenshots-workspace/.kanban/boards/default/deleted/attachments/1.log +1 -0
  39. package/tmp/screenshots-workspace/.kanban.json +59 -0
package/dist/extension.js CHANGED
@@ -22970,7 +22970,7 @@ var require_thread_stream = __commonJS({
22970
22970
  var { version } = require_package();
22971
22971
  var { EventEmitter: EventEmitter3 } = require("events");
22972
22972
  var { Worker } = require("worker_threads");
22973
- var { join: join23 } = require("path");
22973
+ var { join: join24 } = require("path");
22974
22974
  var { pathToFileURL } = require("url");
22975
22975
  var { wait } = require_wait();
22976
22976
  var {
@@ -23006,7 +23006,7 @@ var require_thread_stream = __commonJS({
23006
23006
  function createWorker(stream, opts) {
23007
23007
  const { filename, workerData } = opts;
23008
23008
  const bundlerOverrides = "__bundlerPathsOverrides" in globalThis ? globalThis.__bundlerPathsOverrides : {};
23009
- const toExecute = bundlerOverrides["thread-stream-worker"] || join23(__dirname, "lib", "worker.js");
23009
+ const toExecute = bundlerOverrides["thread-stream-worker"] || join24(__dirname, "lib", "worker.js");
23010
23010
  const worker = new Worker(toExecute, {
23011
23011
  ...opts.workerOpts,
23012
23012
  trackUnmanagedFds: false,
@@ -23397,7 +23397,7 @@ var require_transport = __commonJS({
23397
23397
  var { createRequire: createRequire2 } = require("module");
23398
23398
  var { existsSync: existsSync4 } = require("node:fs");
23399
23399
  var getCallers = require_caller();
23400
- var { join: join23, isAbsolute: isAbsolute2, sep: sep2 } = require("node:path");
23400
+ var { join: join24, isAbsolute: isAbsolute2, sep: sep2 } = require("node:path");
23401
23401
  var { fileURLToPath } = require("node:url");
23402
23402
  var sleep = require_atomic_sleep();
23403
23403
  var onExit = require_on_exit_leak_free();
@@ -23550,7 +23550,7 @@ var require_transport = __commonJS({
23550
23550
  throw new Error("only one of target or targets can be specified");
23551
23551
  }
23552
23552
  if (targets) {
23553
- target = bundlerOverrides["pino-worker"] || join23(__dirname, "worker.js");
23553
+ target = bundlerOverrides["pino-worker"] || join24(__dirname, "worker.js");
23554
23554
  options.targets = targets.filter((dest) => dest.target).map((dest) => {
23555
23555
  return {
23556
23556
  ...dest,
@@ -23568,7 +23568,7 @@ var require_transport = __commonJS({
23568
23568
  });
23569
23569
  });
23570
23570
  } else if (pipeline) {
23571
- target = bundlerOverrides["pino-worker"] || join23(__dirname, "worker.js");
23571
+ target = bundlerOverrides["pino-worker"] || join24(__dirname, "worker.js");
23572
23572
  options.pipelines = [pipeline.map((dest) => {
23573
23573
  return {
23574
23574
  ...dest,
@@ -23591,7 +23591,7 @@ var require_transport = __commonJS({
23591
23591
  return origin;
23592
23592
  }
23593
23593
  if (origin === "pino/file") {
23594
- return join23(__dirname, "..", "file.js");
23594
+ return join24(__dirname, "..", "file.js");
23595
23595
  }
23596
23596
  let fixTarget2;
23597
23597
  for (const filePath of callers) {
@@ -24581,7 +24581,7 @@ var require_safe_stable_stringify = __commonJS({
24581
24581
  return circularValue;
24582
24582
  }
24583
24583
  let res = "";
24584
- let join23 = ",";
24584
+ let join24 = ",";
24585
24585
  const originalIndentation = indentation;
24586
24586
  if (Array.isArray(value)) {
24587
24587
  if (value.length === 0) {
@@ -24595,7 +24595,7 @@ var require_safe_stable_stringify = __commonJS({
24595
24595
  indentation += spacer;
24596
24596
  res += `
24597
24597
  ${indentation}`;
24598
- join23 = `,
24598
+ join24 = `,
24599
24599
  ${indentation}`;
24600
24600
  }
24601
24601
  const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
@@ -24603,13 +24603,13 @@ ${indentation}`;
24603
24603
  for (; i < maximumValuesToStringify - 1; i++) {
24604
24604
  const tmp2 = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
24605
24605
  res += tmp2 !== void 0 ? tmp2 : "null";
24606
- res += join23;
24606
+ res += join24;
24607
24607
  }
24608
24608
  const tmp = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
24609
24609
  res += tmp !== void 0 ? tmp : "null";
24610
24610
  if (value.length - 1 > maximumBreadth) {
24611
24611
  const removedKeys = value.length - maximumBreadth - 1;
24612
- res += `${join23}"... ${getItemCount(removedKeys)} not stringified"`;
24612
+ res += `${join24}"... ${getItemCount(removedKeys)} not stringified"`;
24613
24613
  }
24614
24614
  if (spacer !== "") {
24615
24615
  res += `
@@ -24630,7 +24630,7 @@ ${originalIndentation}`;
24630
24630
  let separator = "";
24631
24631
  if (spacer !== "") {
24632
24632
  indentation += spacer;
24633
- join23 = `,
24633
+ join24 = `,
24634
24634
  ${indentation}`;
24635
24635
  whitespace = " ";
24636
24636
  }
@@ -24644,13 +24644,13 @@ ${indentation}`;
24644
24644
  const tmp = stringifyFnReplacer(key2, value, stack, replacer, spacer, indentation);
24645
24645
  if (tmp !== void 0) {
24646
24646
  res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
24647
- separator = join23;
24647
+ separator = join24;
24648
24648
  }
24649
24649
  }
24650
24650
  if (keyLength > maximumBreadth) {
24651
24651
  const removedKeys = keyLength - maximumBreadth;
24652
24652
  res += `${separator}"...":${whitespace}"${getItemCount(removedKeys)} not stringified"`;
24653
- separator = join23;
24653
+ separator = join24;
24654
24654
  }
24655
24655
  if (spacer !== "" && separator.length > 1) {
24656
24656
  res = `
@@ -24690,7 +24690,7 @@ ${originalIndentation}`;
24690
24690
  }
24691
24691
  const originalIndentation = indentation;
24692
24692
  let res = "";
24693
- let join23 = ",";
24693
+ let join24 = ",";
24694
24694
  if (Array.isArray(value)) {
24695
24695
  if (value.length === 0) {
24696
24696
  return "[]";
@@ -24703,7 +24703,7 @@ ${originalIndentation}`;
24703
24703
  indentation += spacer;
24704
24704
  res += `
24705
24705
  ${indentation}`;
24706
- join23 = `,
24706
+ join24 = `,
24707
24707
  ${indentation}`;
24708
24708
  }
24709
24709
  const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
@@ -24711,13 +24711,13 @@ ${indentation}`;
24711
24711
  for (; i < maximumValuesToStringify - 1; i++) {
24712
24712
  const tmp2 = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
24713
24713
  res += tmp2 !== void 0 ? tmp2 : "null";
24714
- res += join23;
24714
+ res += join24;
24715
24715
  }
24716
24716
  const tmp = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
24717
24717
  res += tmp !== void 0 ? tmp : "null";
24718
24718
  if (value.length - 1 > maximumBreadth) {
24719
24719
  const removedKeys = value.length - maximumBreadth - 1;
24720
- res += `${join23}"... ${getItemCount(removedKeys)} not stringified"`;
24720
+ res += `${join24}"... ${getItemCount(removedKeys)} not stringified"`;
24721
24721
  }
24722
24722
  if (spacer !== "") {
24723
24723
  res += `
@@ -24730,7 +24730,7 @@ ${originalIndentation}`;
24730
24730
  let whitespace = "";
24731
24731
  if (spacer !== "") {
24732
24732
  indentation += spacer;
24733
- join23 = `,
24733
+ join24 = `,
24734
24734
  ${indentation}`;
24735
24735
  whitespace = " ";
24736
24736
  }
@@ -24739,7 +24739,7 @@ ${indentation}`;
24739
24739
  const tmp = stringifyArrayReplacer(key2, value[key2], stack, replacer, spacer, indentation);
24740
24740
  if (tmp !== void 0) {
24741
24741
  res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
24742
- separator = join23;
24742
+ separator = join24;
24743
24743
  }
24744
24744
  }
24745
24745
  if (spacer !== "" && separator.length > 1) {
@@ -24796,20 +24796,20 @@ ${originalIndentation}`;
24796
24796
  indentation += spacer;
24797
24797
  let res2 = `
24798
24798
  ${indentation}`;
24799
- const join24 = `,
24799
+ const join25 = `,
24800
24800
  ${indentation}`;
24801
24801
  const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
24802
24802
  let i = 0;
24803
24803
  for (; i < maximumValuesToStringify - 1; i++) {
24804
24804
  const tmp2 = stringifyIndent(String(i), value[i], stack, spacer, indentation);
24805
24805
  res2 += tmp2 !== void 0 ? tmp2 : "null";
24806
- res2 += join24;
24806
+ res2 += join25;
24807
24807
  }
24808
24808
  const tmp = stringifyIndent(String(i), value[i], stack, spacer, indentation);
24809
24809
  res2 += tmp !== void 0 ? tmp : "null";
24810
24810
  if (value.length - 1 > maximumBreadth) {
24811
24811
  const removedKeys = value.length - maximumBreadth - 1;
24812
- res2 += `${join24}"... ${getItemCount(removedKeys)} not stringified"`;
24812
+ res2 += `${join25}"... ${getItemCount(removedKeys)} not stringified"`;
24813
24813
  }
24814
24814
  res2 += `
24815
24815
  ${originalIndentation}`;
@@ -24825,16 +24825,16 @@ ${originalIndentation}`;
24825
24825
  return '"[Object]"';
24826
24826
  }
24827
24827
  indentation += spacer;
24828
- const join23 = `,
24828
+ const join24 = `,
24829
24829
  ${indentation}`;
24830
24830
  let res = "";
24831
24831
  let separator = "";
24832
24832
  let maximumPropertiesToStringify = Math.min(keyLength, maximumBreadth);
24833
24833
  if (isTypedArrayWithEntries(value)) {
24834
- res += stringifyTypedArray(value, join23, maximumBreadth);
24834
+ res += stringifyTypedArray(value, join24, maximumBreadth);
24835
24835
  keys = keys.slice(value.length);
24836
24836
  maximumPropertiesToStringify -= value.length;
24837
- separator = join23;
24837
+ separator = join24;
24838
24838
  }
24839
24839
  if (deterministic) {
24840
24840
  keys = sort(keys, comparator);
@@ -24845,13 +24845,13 @@ ${indentation}`;
24845
24845
  const tmp = stringifyIndent(key2, value[key2], stack, spacer, indentation);
24846
24846
  if (tmp !== void 0) {
24847
24847
  res += `${separator}${strEscape(key2)}: ${tmp}`;
24848
- separator = join23;
24848
+ separator = join24;
24849
24849
  }
24850
24850
  }
24851
24851
  if (keyLength > maximumBreadth) {
24852
24852
  const removedKeys = keyLength - maximumBreadth;
24853
24853
  res += `${separator}"...": "${getItemCount(removedKeys)} not stringified"`;
24854
- separator = join23;
24854
+ separator = join24;
24855
24855
  }
24856
24856
  if (separator !== "") {
24857
24857
  res = `
@@ -59636,7 +59636,7 @@ var require_send = __commonJS({
59636
59636
  var { parseTokenList } = require_parseTokenList();
59637
59637
  var { createHttpError } = require_createHttpError();
59638
59638
  var extname5 = path24.extname;
59639
- var join23 = path24.join;
59639
+ var join24 = path24.join;
59640
59640
  var normalize2 = path24.normalize;
59641
59641
  var resolve10 = path24.resolve;
59642
59642
  var sep2 = path24.sep;
@@ -59723,7 +59723,7 @@ var require_send = __commonJS({
59723
59723
  return { statusCode: 403 };
59724
59724
  }
59725
59725
  parts = path25.split(sep2);
59726
- path25 = normalize2(join23(root, path25));
59726
+ path25 = normalize2(join24(root, path25));
59727
59727
  } else {
59728
59728
  if (UP_PATH_REGEXP.test(path25)) {
59729
59729
  debug('malicious path "%s"', path25);
@@ -60007,7 +60007,7 @@ var require_send = __commonJS({
60007
60007
  let err;
60008
60008
  for (let i = 0; i < options.index.length; i++) {
60009
60009
  const index = options.index[i];
60010
- const p = join23(path25, index);
60010
+ const p = join24(path25, index);
60011
60011
  const { error, stat: stat4 } = await tryStat(p);
60012
60012
  if (error) {
60013
60013
  err = error;
@@ -65329,11 +65329,87 @@ function migrateConfigV1ToV2(raw) {
65329
65329
  }
65330
65330
  return v2;
65331
65331
  }
65332
+ function loadDotEnv(dir) {
65333
+ const envPath = path.join(dir, ".env");
65334
+ let content;
65335
+ try {
65336
+ content = fs.readFileSync(envPath, "utf-8");
65337
+ } catch {
65338
+ return;
65339
+ }
65340
+ for (const line of content.split("\n")) {
65341
+ const trimmed = line.trim();
65342
+ if (!trimmed || trimmed.startsWith("#"))
65343
+ continue;
65344
+ const eqIdx = trimmed.indexOf("=");
65345
+ if (eqIdx < 1)
65346
+ continue;
65347
+ const key = trimmed.slice(0, eqIdx).trim();
65348
+ let val = trimmed.slice(eqIdx + 1).trim();
65349
+ if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) {
65350
+ val = val.slice(1, -1);
65351
+ }
65352
+ if (process.env[key] === void 0) {
65353
+ process.env[key] = val;
65354
+ }
65355
+ }
65356
+ }
65357
+ function resolveConfigEnvVars(node, configFileName, nodePath = "") {
65358
+ const isFormDefaultDataPath = /^\.forms\.(?:[^.]+|"[^"]+")\.data(?:$|[.\[])/.test(nodePath);
65359
+ if (isFormDefaultDataPath) {
65360
+ return node;
65361
+ }
65362
+ if (typeof node === "string") {
65363
+ return node.replace(/\$\{([^}]+)\}/g, (_match, varName) => {
65364
+ const envValue = process.env[varName];
65365
+ if (envValue === void 0) {
65366
+ throw new Error(
65367
+ `missing ${varName} in ${configFileName}: ${nodePath} "${node}"`
65368
+ );
65369
+ }
65370
+ return envValue;
65371
+ });
65372
+ }
65373
+ if (Array.isArray(node)) {
65374
+ for (let i = 0; i < node.length; i++) {
65375
+ node[i] = resolveConfigEnvVars(node[i], configFileName, `${nodePath}[${i}]`);
65376
+ }
65377
+ return node;
65378
+ }
65379
+ if (node !== null && typeof node === "object") {
65380
+ const obj = node;
65381
+ for (const key of Object.keys(obj)) {
65382
+ const jsonKey = /[^a-zA-Z0-9_]/.test(key) ? `"${key}"` : key;
65383
+ const childPath = nodePath ? `${nodePath}.${jsonKey}` : `.${jsonKey}`;
65384
+ obj[key] = resolveConfigEnvVars(obj[key], configFileName, childPath);
65385
+ }
65386
+ return obj;
65387
+ }
65388
+ return node;
65389
+ }
65332
65390
  function readConfig(workspaceRoot) {
65333
65391
  const filePath = configPath(workspaceRoot);
65334
65392
  const defaults = { ...DEFAULT_CONFIG, boards: { default: { ...DEFAULT_BOARD_CONFIG, columns: [...DEFAULT_COLUMNS] } } };
65393
+ let raw;
65394
+ try {
65395
+ raw = JSON.parse(fs.readFileSync(filePath, "utf-8"));
65396
+ } catch {
65397
+ return defaults;
65398
+ }
65399
+ loadDotEnv(workspaceRoot);
65400
+ try {
65401
+ resolveConfigEnvVars(raw, CONFIG_FILENAME);
65402
+ } catch (err) {
65403
+ const msg = err instanceof Error ? err.message : String(err);
65404
+ process.stderr.write(`
65405
+ Configuration error: ${msg}
65406
+
65407
+ Set the missing environment variable before starting the server.
65408
+
65409
+ `);
65410
+ process.exit(1);
65411
+ }
65335
65412
  try {
65336
- const raw = JSON.parse(fs.readFileSync(filePath, "utf-8"));
65337
65413
  const isV1 = raw.version === 1 || !raw.version && !(typeof raw.boards === "object" && raw.boards !== null && !Array.isArray(raw.boards));
65338
65414
  if (isV1) {
65339
65415
  const v2 = migrateConfigV1ToV2(raw);
@@ -68937,7 +69013,7 @@ function resolveAuthIdentityPlugin(ref) {
68937
69013
  if (ref.provider === "rbac")
68938
69014
  return RBAC_IDENTITY_PLUGIN;
68939
69015
  const packageName = AUTH_PROVIDER_ALIASES.get(ref.provider) ?? ref.provider;
68940
- return loadExternalAuthIdentityPlugin(packageName, ref.provider);
69016
+ return loadExternalAuthIdentityPlugin(packageName, ref.provider, ref.options);
68941
69017
  }
68942
69018
  function resolveAuthPolicyPlugin(ref) {
68943
69019
  if (ref.provider === "noop")
@@ -69163,8 +69239,13 @@ function selectAuthPolicyPlugin(mod, providerId) {
69163
69239
  return direct;
69164
69240
  return null;
69165
69241
  }
69166
- function loadExternalAuthIdentityPlugin(packageName, providerId) {
69242
+ function loadExternalAuthIdentityPlugin(packageName, providerId, options) {
69167
69243
  const mod = loadExternalModule(packageName);
69244
+ if (options !== void 0 && typeof mod.createAuthIdentityPlugin === "function") {
69245
+ const created = mod.createAuthIdentityPlugin(options);
69246
+ if (isValidAuthIdentityPlugin(created, providerId))
69247
+ return created;
69248
+ }
69168
69249
  const plugin = selectAuthIdentityPlugin(mod, providerId);
69169
69250
  if (!plugin) {
69170
69251
  throw new Error(
@@ -72167,29 +72248,10 @@ async function updateCard(ctx, { cardId, updates, boardId }) {
72167
72248
  return card;
72168
72249
  }
72169
72250
  async function triggerAction(ctx, { cardId, action, boardId }) {
72170
- const config = readConfig(ctx.workspaceRoot);
72171
- const { actionWebhookUrl } = config;
72172
- if (!actionWebhookUrl) {
72173
- throw new Error("No action webhook URL configured. Set actionWebhookUrl in .kanban.json");
72174
- }
72175
72251
  const card = await getCard(ctx, { cardId, boardId });
72176
72252
  if (!card)
72177
72253
  throw new Error(`Card not found: ${cardId}`);
72178
72254
  const resolvedBoardId = card.boardId || ctx._resolveBoardId(boardId);
72179
- const payload = {
72180
- action,
72181
- board: resolvedBoardId,
72182
- list: card.status,
72183
- card: sanitizeCard(card)
72184
- };
72185
- const response = await fetch(actionWebhookUrl, {
72186
- method: "POST",
72187
- headers: { "Content-Type": "application/json" },
72188
- body: JSON.stringify(payload)
72189
- });
72190
- if (!response.ok) {
72191
- throw new Error(`Action webhook responded with ${response.status}: ${response.statusText}`);
72192
- }
72193
72255
  await appendActivityLog(ctx, {
72194
72256
  cardId,
72195
72257
  boardId: resolvedBoardId,
@@ -72200,6 +72262,12 @@ async function triggerAction(ctx, { cardId, action, boardId }) {
72200
72262
  }
72201
72263
  }).catch(() => {
72202
72264
  });
72265
+ return {
72266
+ action,
72267
+ board: resolvedBoardId,
72268
+ list: card.status,
72269
+ card: sanitizeCard(card)
72270
+ };
72203
72271
  }
72204
72272
  async function submitForm(ctx, input) {
72205
72273
  const card = await getCard(ctx, { cardId: input.cardId, boardId: input.boardId });
@@ -73616,6 +73684,25 @@ var KanbanSDK = class _KanbanSDK {
73616
73684
  get workspaceRoot() {
73617
73685
  return path14.dirname(this.kanbanDir);
73618
73686
  }
73687
+ /**
73688
+ * Returns a cloned read-only snapshot of the current workspace config.
73689
+ *
73690
+ * The returned snapshot is created from a fresh config read and deep-cloned
73691
+ * before being returned, so callers receive an isolated view of the current
73692
+ * `.kanban.json` state rather than a live mutable runtime object. Mutating the
73693
+ * returned snapshot does not update persisted config or affect this SDK instance.
73694
+ *
73695
+ * @returns A cloned read-only snapshot of the current {@link KanbanConfig}.
73696
+ *
73697
+ * @example
73698
+ * ```ts
73699
+ * const config = sdk.getConfigSnapshot()
73700
+ * console.log(config.defaultBoard)
73701
+ * ```
73702
+ */
73703
+ getConfigSnapshot() {
73704
+ return structuredClone(readConfig(this.workspaceRoot));
73705
+ }
73619
73706
  // --- Board resolution helpers ---
73620
73707
  /** @internal */
73621
73708
  _resolveBoardId(boardId) {
@@ -74082,21 +74169,17 @@ var KanbanSDK = class _KanbanSDK {
74082
74169
  return result;
74083
74170
  }
74084
74171
  /**
74085
- * Triggers a named action for a card by POSTing to the global `actionWebhookUrl`
74086
- * configured in `.kanban.json`.
74172
+ * Triggers a named action for a card.
74087
74173
  *
74088
- * The payload sent to the webhook is:
74089
- * ```json
74090
- * { "action": "retry", "board": "default", "list": "in-progress", "card": { ...sanitizedCard } }
74091
- * ```
74174
+ * Validates the card, appends an activity log entry, and emits the
74175
+ * `card.action.triggered` after-event so registered webhooks receive
74176
+ * the action payload automatically.
74092
74177
  *
74093
74178
  * @param cardId - The ID of the card to trigger the action for.
74094
74179
  * @param action - The action name string (e.g. `'retry'`, `'sendEmail'`).
74095
74180
  * @param boardId - Optional board ID. Defaults to the workspace's default board.
74096
- * @returns A promise resolving when the webhook responds with 2xx.
74097
- * @throws {Error} If no `actionWebhookUrl` is configured in `.kanban.json`.
74181
+ * @returns A promise that resolves when the action has been processed.
74098
74182
  * @throws {Error} If the card is not found.
74099
- * @throws {Error} If the webhook responds with a non-2xx status.
74100
74183
  *
74101
74184
  * @example
74102
74185
  * ```ts
@@ -74106,7 +74189,8 @@ var KanbanSDK = class _KanbanSDK {
74106
74189
  */
74107
74190
  async triggerAction(cardId, action, boardId) {
74108
74191
  const mergedInput = await this._runBeforeEvent("card.action.trigger", { cardId, action, boardId }, void 0, boardId);
74109
- return triggerAction(this, mergedInput);
74192
+ const payload = await triggerAction(this, mergedInput);
74193
+ this._runAfterEvent("card.action.triggered", payload, void 0, payload.board);
74110
74194
  }
74111
74195
  /**
74112
74196
  * Moves a card to a different status column and/or position within that column.
@@ -78318,6 +78402,13 @@ function setupWatcher(ctx, server) {
78318
78402
  ctx.wss.close();
78319
78403
  });
78320
78404
  }
78405
+ const configFilePath = path16.join(ctx.workspaceRoot, ".kanban.json");
78406
+ const configWatcher = esm_default.watch(configFilePath, {
78407
+ ignoreInitial: true,
78408
+ awaitWriteFinish: { stabilityThreshold: 100 }
78409
+ });
78410
+ configWatcher.on("change", () => handleFileChange(ctx, debounceRef));
78411
+ server.on("close", () => configWatcher.close());
78321
78412
  }
78322
78413
 
78323
78414
  // src/standalone/httpUtils.ts
@@ -78494,20 +78585,24 @@ async function handleCardFileRoute(request) {
78494
78585
  var http = __toESM(require("http"));
78495
78586
  var fs12 = __toESM(require("fs"));
78496
78587
  var path18 = __toESM(require("path"));
78497
- var indexHtml = `<!DOCTYPE html>
78588
+ function getIndexHtml(basePath = "") {
78589
+ return `<!DOCTYPE html>
78498
78590
  <html lang="en">
78499
78591
  <head>
78500
78592
  <meta charset="UTF-8">
78501
78593
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
78502
- <link rel="icon" type="image/svg+xml" href="/favicon.svg">
78503
- <link href="/style.css" rel="stylesheet">
78594
+ <link rel="icon" type="image/svg+xml" href="${basePath}/favicon.svg">
78595
+ <link href="${basePath}/style.css" rel="stylesheet">
78504
78596
  <title>Kanban Board</title>
78597
+ <script>window.__KB_BASE__ = ${JSON.stringify(basePath)}</script>
78505
78598
  </head>
78506
78599
  <body>
78507
78600
  <div id="root"></div>
78508
- <script type="module" src="/index.js"></script>
78601
+ <script type="module" src="${basePath}/index.js"></script>
78509
78602
  </body>
78510
78603
  </html>`;
78604
+ }
78605
+ var indexHtml = getIndexHtml();
78511
78606
  function resolveStandaloneWebviewDir(webviewDir) {
78512
78607
  if (webviewDir)
78513
78608
  return webviewDir;
@@ -78521,12 +78616,12 @@ function resolveStandaloneWebviewDir(webviewDir) {
78521
78616
  }
78522
78617
  return candidates[0];
78523
78618
  }
78524
- function createStandaloneRuntime(kanbanDir, webviewDir, httpServer) {
78619
+ function createStandaloneRuntime(kanbanDir, webviewDir, httpServer, basePath) {
78525
78620
  const absoluteKanbanDir = path18.resolve(kanbanDir);
78526
78621
  const workspaceRoot = path18.dirname(absoluteKanbanDir);
78527
78622
  const resolvedWebviewDir = resolveStandaloneWebviewDir(webviewDir);
78528
78623
  const server = httpServer ?? http.createServer();
78529
- const wss = new import_websocket_server.default({ server, path: "/ws" });
78624
+ const wss = new import_websocket_server.default({ server, path: (basePath || "") + "/ws" });
78530
78625
  const ctx = {};
78531
78626
  const sdk = new KanbanSDK(absoluteKanbanDir, {
78532
78627
  onEvent: (event, data) => {
@@ -80574,9 +80669,14 @@ async function handleMessage(ctx, ws, message, authContext) {
80574
80669
  }
80575
80670
 
80576
80671
  // src/standalone/internal/websocket.ts
80577
- function attachWebSocketHandlers(ctx) {
80578
- ctx.wss.on("connection", (ws, req) => {
80579
- const authContext = extractAuthContext(req);
80672
+ function attachWebSocketHandlers(ctx, resolveAuthContext) {
80673
+ ctx.wss.on("connection", async (ws, req) => {
80674
+ let authContext;
80675
+ try {
80676
+ authContext = resolveAuthContext ? await resolveAuthContext(req) : extractAuthContext(req);
80677
+ } catch {
80678
+ authContext = extractAuthContext(req);
80679
+ }
80580
80680
  setClientEditingCard(ctx, ws, null);
80581
80681
  ws.on("message", (data) => {
80582
80682
  let message;
@@ -80743,6 +80843,7 @@ function isPageRequest(method, pathname) {
80743
80843
  function collectStandaloneHttpHandlers(requestType, ctx) {
80744
80844
  const plugins = ctx.sdk.capabilities?.standaloneHttpPlugins ?? [];
80745
80845
  const registrationOptions = {
80846
+ sdk: ctx.sdk,
80746
80847
  workspaceRoot: ctx.workspaceRoot,
80747
80848
  kanbanDir: ctx.absoluteKanbanDir,
80748
80849
  capabilities: ctx.sdk.capabilities?.providers ?? {
@@ -80800,10 +80901,13 @@ function startServer(kanbanDir, port, webviewDir, resolvedConfigPath) {
80800
80901
  const swaggerUiStaticDir = resolveSwaggerUiStaticDir();
80801
80902
  const swaggerUiLogoPath = swaggerUiStaticDir ? path20.join(swaggerUiStaticDir, "logo.svg") : void 0;
80802
80903
  const swaggerUiLogo = swaggerUiLogoPath && fs15.existsSync(swaggerUiLogoPath) ? { type: "image/svg+xml", content: fs15.readFileSync(swaggerUiLogoPath) } : null;
80803
- const runtime = createStandaloneRuntime(kanbanDir, webviewDir, fastify.server);
80904
+ const workspaceRoot = path20.dirname(path20.resolve(kanbanDir));
80905
+ const config = readConfig(workspaceRoot);
80906
+ const rawBase = config.basePath ?? "";
80907
+ const basePath = rawBase ? (rawBase.startsWith("/") ? rawBase : "/" + rawBase).replace(/\/+$/, "") : "";
80908
+ const runtime = createStandaloneRuntime(kanbanDir, webviewDir, fastify.server, basePath);
80804
80909
  const { ctx, resolvedWebviewDir } = runtime;
80805
- const config = readConfig(ctx.workspaceRoot);
80806
- let resolvedIndexHtml = indexHtml;
80910
+ let resolvedIndexHtml = getIndexHtml(basePath);
80807
80911
  let customHead = config.customHeadHtml || "";
80808
80912
  if (config.customHeadHtmlFile) {
80809
80913
  try {
@@ -80813,14 +80917,14 @@ function startServer(kanbanDir, port, webviewDir, resolvedConfigPath) {
80813
80917
  }
80814
80918
  }
80815
80919
  if (customHead) {
80816
- resolvedIndexHtml = indexHtml.replace("</head>", `${customHead}
80920
+ resolvedIndexHtml = resolvedIndexHtml.replace("</head>", `${customHead}
80817
80921
  </head>`);
80818
80922
  }
80819
80923
  const standaloneHttpPlugins = ctx.sdk.capabilities?.standaloneHttpPlugins ?? [];
80820
80924
  const standaloneOpenApiSpec = buildStandaloneOpenApiSpec(standaloneHttpPlugins);
80821
80925
  fastify.register(import_swagger.default, { openapi: standaloneOpenApiSpec });
80822
80926
  fastify.register(import_swagger_ui.default, {
80823
- routePrefix: "/api/docs",
80927
+ routePrefix: `${basePath}/api/docs`,
80824
80928
  uiConfig: { docExpansion: "list", deepLinking: false },
80825
80929
  logo: swaggerUiLogo,
80826
80930
  ...swaggerUiStaticDir ? { baseDir: swaggerUiStaticDir } : {}
@@ -80852,6 +80956,14 @@ function startServer(kanbanDir, port, webviewDir, resolvedConfigPath) {
80852
80956
  if (request.body instanceof Buffer && request.body.length > 0) {
80853
80957
  req._rawBody = request.body;
80854
80958
  }
80959
+ if (basePath) {
80960
+ const rawUrl = req.url ?? "/";
80961
+ if (rawUrl === basePath) {
80962
+ req.url = "/";
80963
+ } else if (rawUrl.startsWith(basePath + "/") || rawUrl.startsWith(basePath + "?")) {
80964
+ req.url = rawUrl.slice(basePath.length);
80965
+ }
80966
+ }
80855
80967
  const requestContext = createRequestContext(ctx, req, reply.raw, resolvedWebviewDir, resolvedIndexHtml);
80856
80968
  await dispatchRequest(requestContext, middlewareHandlers);
80857
80969
  if (!reply.sent && !reply.raw.writableEnded) {
@@ -80859,7 +80971,62 @@ function startServer(kanbanDir, port, webviewDir, resolvedConfigPath) {
80859
80971
  }
80860
80972
  reply.hijack();
80861
80973
  });
80862
- attachWebSocketHandlers(ctx);
80974
+ const resolveWsAuthContext = async (req) => {
80975
+ const silentRes = /* @__PURE__ */ (() => {
80976
+ const r = {
80977
+ writableEnded: false,
80978
+ writeHead() {
80979
+ return r;
80980
+ },
80981
+ setHeader() {
80982
+ return r;
80983
+ },
80984
+ removeHeader() {
80985
+ },
80986
+ getHeader() {
80987
+ return void 0;
80988
+ },
80989
+ getHeaders() {
80990
+ return {};
80991
+ },
80992
+ end(..._args) {
80993
+ r.writableEnded = true;
80994
+ return r;
80995
+ },
80996
+ write() {
80997
+ return false;
80998
+ }
80999
+ };
81000
+ return r;
81001
+ })();
81002
+ const reqWithBody = req;
81003
+ const wsUrl = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
81004
+ const requestContext = {
81005
+ ctx,
81006
+ sdk: ctx.sdk,
81007
+ workspaceRoot: ctx.workspaceRoot,
81008
+ kanbanDir: ctx.absoluteKanbanDir,
81009
+ req: reqWithBody,
81010
+ res: silentRes,
81011
+ url: wsUrl,
81012
+ pathname: wsUrl.pathname,
81013
+ method: "GET",
81014
+ resolvedWebviewDir,
81015
+ indexHtml: resolvedIndexHtml,
81016
+ route: createRouteMatcher("GET", wsUrl.pathname, matchRoute),
81017
+ isApiRequest: false,
81018
+ isPageRequest: false,
81019
+ getAuthContext: () => getRequestAuthContext(req),
81020
+ setAuthContext: (auth) => setRequestAuthContext(req, auth),
81021
+ mergeAuthContext: (auth) => mergeRequestAuthContext(req, auth)
81022
+ };
81023
+ for (const handler of middlewareHandlers) {
81024
+ if (await handler(requestContext))
81025
+ break;
81026
+ }
81027
+ return extractAuthContext(req);
81028
+ };
81029
+ attachWebSocketHandlers(ctx, resolveWsAuthContext);
80863
81030
  setupStandaloneLifecycle(ctx, fastify.server);
80864
81031
  const effectiveConfigPath = resolvedConfigPath ?? configPath(path20.dirname(ctx.absoluteKanbanDir));
80865
81032
  fastify.listen({ port, host: "0.0.0.0" }, (err) => {
@@ -80867,7 +81034,7 @@ function startServer(kanbanDir, port, webviewDir, resolvedConfigPath) {
80867
81034
  console.error("Failed to start server:", err);
80868
81035
  process.exit(1);
80869
81036
  }
80870
- console.log(`Kanban board running at http://localhost:${port}`);
81037
+ console.log(`Kanban board running at http://localhost:${port}${basePath}`);
80871
81038
  console.log(`API available at http://localhost:${port}/api`);
80872
81039
  console.log(`Kanban config: ${effectiveConfigPath}`);
80873
81040
  console.log(`Kanban directory: ${ctx.absoluteKanbanDir}`);