kanban-lite 1.2.3 → 1.2.6

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/dist/cli.js CHANGED
@@ -199,6 +199,10 @@ function loadDotEnv(dir) {
199
199
  }
200
200
  }
201
201
  function resolveConfigEnvVars(node, configFileName, nodePath = "") {
202
+ const isFormDefaultDataPath = /^\.forms\.(?:[^.]+|"[^"]+")\.data(?:$|[.\[])/.test(nodePath);
203
+ if (isFormDefaultDataPath) {
204
+ return node;
205
+ }
202
206
  if (typeof node === "string") {
203
207
  return node.replace(/\$\{([^}]+)\}/g, (_match, varName) => {
204
208
  const envValue = process.env[varName];
@@ -26234,6 +26238,25 @@ var init_KanbanSDK = __esm({
26234
26238
  get workspaceRoot() {
26235
26239
  return path14.dirname(this.kanbanDir);
26236
26240
  }
26241
+ /**
26242
+ * Returns a cloned read-only snapshot of the current workspace config.
26243
+ *
26244
+ * The returned snapshot is created from a fresh config read and deep-cloned
26245
+ * before being returned, so callers receive an isolated view of the current
26246
+ * `.kanban.json` state rather than a live mutable runtime object. Mutating the
26247
+ * returned snapshot does not update persisted config or affect this SDK instance.
26248
+ *
26249
+ * @returns A cloned read-only snapshot of the current {@link KanbanConfig}.
26250
+ *
26251
+ * @example
26252
+ * ```ts
26253
+ * const config = sdk.getConfigSnapshot()
26254
+ * console.log(config.defaultBoard)
26255
+ * ```
26256
+ */
26257
+ getConfigSnapshot() {
26258
+ return structuredClone(readConfig(this.workspaceRoot));
26259
+ }
26237
26260
  // --- Board resolution helpers ---
26238
26261
  /** @internal */
26239
26262
  _resolveBoardId(boardId) {
@@ -114318,9 +114341,14 @@ var init_messageHandlers = __esm({
114318
114341
  });
114319
114342
 
114320
114343
  // src/standalone/internal/websocket.ts
114321
- function attachWebSocketHandlers(ctx) {
114322
- ctx.wss.on("connection", (ws, req) => {
114323
- const authContext = extractAuthContext(req);
114344
+ function attachWebSocketHandlers(ctx, resolveAuthContext) {
114345
+ ctx.wss.on("connection", async (ws, req) => {
114346
+ let authContext;
114347
+ try {
114348
+ authContext = resolveAuthContext ? await resolveAuthContext(req) : extractAuthContext(req);
114349
+ } catch {
114350
+ authContext = extractAuthContext(req);
114351
+ }
114324
114352
  setClientEditingCard(ctx, ws, null);
114325
114353
  ws.on("message", (data) => {
114326
114354
  let message;
@@ -114395,6 +114423,7 @@ function isPageRequest(method, pathname) {
114395
114423
  function collectStandaloneHttpHandlers(requestType, ctx) {
114396
114424
  const plugins = ctx.sdk.capabilities?.standaloneHttpPlugins ?? [];
114397
114425
  const registrationOptions = {
114426
+ sdk: ctx.sdk,
114398
114427
  workspaceRoot: ctx.workspaceRoot,
114399
114428
  kanbanDir: ctx.absoluteKanbanDir,
114400
114429
  capabilities: ctx.sdk.capabilities?.providers ?? {
@@ -114522,7 +114551,62 @@ function startServer(kanbanDir, port, webviewDir, resolvedConfigPath) {
114522
114551
  }
114523
114552
  reply.hijack();
114524
114553
  });
114525
- attachWebSocketHandlers(ctx);
114554
+ const resolveWsAuthContext = async (req) => {
114555
+ const silentRes = /* @__PURE__ */ (() => {
114556
+ const r = {
114557
+ writableEnded: false,
114558
+ writeHead() {
114559
+ return r;
114560
+ },
114561
+ setHeader() {
114562
+ return r;
114563
+ },
114564
+ removeHeader() {
114565
+ },
114566
+ getHeader() {
114567
+ return void 0;
114568
+ },
114569
+ getHeaders() {
114570
+ return {};
114571
+ },
114572
+ end(..._args) {
114573
+ r.writableEnded = true;
114574
+ return r;
114575
+ },
114576
+ write() {
114577
+ return false;
114578
+ }
114579
+ };
114580
+ return r;
114581
+ })();
114582
+ const reqWithBody = req;
114583
+ const wsUrl = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
114584
+ const requestContext = {
114585
+ ctx,
114586
+ sdk: ctx.sdk,
114587
+ workspaceRoot: ctx.workspaceRoot,
114588
+ kanbanDir: ctx.absoluteKanbanDir,
114589
+ req: reqWithBody,
114590
+ res: silentRes,
114591
+ url: wsUrl,
114592
+ pathname: wsUrl.pathname,
114593
+ method: "GET",
114594
+ resolvedWebviewDir,
114595
+ indexHtml: resolvedIndexHtml,
114596
+ route: createRouteMatcher("GET", wsUrl.pathname, matchRoute),
114597
+ isApiRequest: false,
114598
+ isPageRequest: false,
114599
+ getAuthContext: () => getRequestAuthContext(req),
114600
+ setAuthContext: (auth) => setRequestAuthContext(req, auth),
114601
+ mergeAuthContext: (auth) => mergeRequestAuthContext(req, auth)
114602
+ };
114603
+ for (const handler of middlewareHandlers) {
114604
+ if (await handler(requestContext))
114605
+ break;
114606
+ }
114607
+ return extractAuthContext(req);
114608
+ };
114609
+ attachWebSocketHandlers(ctx, resolveWsAuthContext);
114526
114610
  setupStandaloneLifecycle(ctx, fastify.server);
114527
114611
  const effectiveConfigPath = resolvedConfigPath ?? configPath(path21.dirname(ctx.absoluteKanbanDir));
114528
114612
  fastify.listen({ port, host: "0.0.0.0" }, (err) => {
@@ -116952,7 +117036,7 @@ async function cmdAuth(sdk, positional, flags, cliPlugins, workspaceRoot) {
116952
117036
  if (sub !== "status") {
116953
117037
  const authPlugin = findCliPlugin(cliPlugins, "auth");
116954
117038
  if (authPlugin) {
116955
- await authPlugin.run(positional, flags, { workspaceRoot });
117039
+ await runCliPlugin(authPlugin, positional, flags, workspaceRoot, sdk);
116956
117040
  return;
116957
117041
  }
116958
117042
  console.error(red(`Unknown auth sub-command: ${sub}`));
package/dist/extension.js CHANGED
@@ -65355,6 +65355,10 @@ function loadDotEnv(dir) {
65355
65355
  }
65356
65356
  }
65357
65357
  function resolveConfigEnvVars(node, configFileName, nodePath = "") {
65358
+ const isFormDefaultDataPath = /^\.forms\.(?:[^.]+|"[^"]+")\.data(?:$|[.\[])/.test(nodePath);
65359
+ if (isFormDefaultDataPath) {
65360
+ return node;
65361
+ }
65358
65362
  if (typeof node === "string") {
65359
65363
  return node.replace(/\$\{([^}]+)\}/g, (_match, varName) => {
65360
65364
  const envValue = process.env[varName];
@@ -73680,6 +73684,25 @@ var KanbanSDK = class _KanbanSDK {
73680
73684
  get workspaceRoot() {
73681
73685
  return path14.dirname(this.kanbanDir);
73682
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
+ }
73683
73706
  // --- Board resolution helpers ---
73684
73707
  /** @internal */
73685
73708
  _resolveBoardId(boardId) {
@@ -80646,9 +80669,14 @@ async function handleMessage(ctx, ws, message, authContext) {
80646
80669
  }
80647
80670
 
80648
80671
  // src/standalone/internal/websocket.ts
80649
- function attachWebSocketHandlers(ctx) {
80650
- ctx.wss.on("connection", (ws, req) => {
80651
- 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
+ }
80652
80680
  setClientEditingCard(ctx, ws, null);
80653
80681
  ws.on("message", (data) => {
80654
80682
  let message;
@@ -80815,6 +80843,7 @@ function isPageRequest(method, pathname) {
80815
80843
  function collectStandaloneHttpHandlers(requestType, ctx) {
80816
80844
  const plugins = ctx.sdk.capabilities?.standaloneHttpPlugins ?? [];
80817
80845
  const registrationOptions = {
80846
+ sdk: ctx.sdk,
80818
80847
  workspaceRoot: ctx.workspaceRoot,
80819
80848
  kanbanDir: ctx.absoluteKanbanDir,
80820
80849
  capabilities: ctx.sdk.capabilities?.providers ?? {
@@ -80942,7 +80971,62 @@ function startServer(kanbanDir, port, webviewDir, resolvedConfigPath) {
80942
80971
  }
80943
80972
  reply.hijack();
80944
80973
  });
80945
- 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);
80946
81030
  setupStandaloneLifecycle(ctx, fastify.server);
80947
81031
  const effectiveConfigPath = resolvedConfigPath ?? configPath(path20.dirname(ctx.absoluteKanbanDir));
80948
81032
  fastify.listen({ port, host: "0.0.0.0" }, (err) => {
@@ -17690,6 +17690,10 @@ function loadDotEnv(dir) {
17690
17690
  }
17691
17691
  }
17692
17692
  function resolveConfigEnvVars(node, configFileName, nodePath = "") {
17693
+ const isFormDefaultDataPath = /^\.forms\.(?:[^.]+|"[^"]+")\.data(?:$|[.\[])/.test(nodePath);
17694
+ if (isFormDefaultDataPath) {
17695
+ return node;
17696
+ }
17693
17697
  if (typeof node === "string") {
17694
17698
  return node.replace(/\$\{([^}]+)\}/g, (_match, varName) => {
17695
17699
  const envValue = process.env[varName];
@@ -26078,6 +26082,25 @@ var KanbanSDK = class _KanbanSDK {
26078
26082
  get workspaceRoot() {
26079
26083
  return path14.dirname(this.kanbanDir);
26080
26084
  }
26085
+ /**
26086
+ * Returns a cloned read-only snapshot of the current workspace config.
26087
+ *
26088
+ * The returned snapshot is created from a fresh config read and deep-cloned
26089
+ * before being returned, so callers receive an isolated view of the current
26090
+ * `.kanban.json` state rather than a live mutable runtime object. Mutating the
26091
+ * returned snapshot does not update persisted config or affect this SDK instance.
26092
+ *
26093
+ * @returns A cloned read-only snapshot of the current {@link KanbanConfig}.
26094
+ *
26095
+ * @example
26096
+ * ```ts
26097
+ * const config = sdk.getConfigSnapshot()
26098
+ * console.log(config.defaultBoard)
26099
+ * ```
26100
+ */
26101
+ getConfigSnapshot() {
26102
+ return structuredClone(readConfig(this.workspaceRoot));
26103
+ }
26081
26104
  // --- Board resolution helpers ---
26082
26105
  /** @internal */
26083
26106
  _resolveBoardId(boardId) {
@@ -17720,6 +17720,10 @@ function loadDotEnv(dir) {
17720
17720
  }
17721
17721
  }
17722
17722
  function resolveConfigEnvVars(node, configFileName, nodePath = "") {
17723
+ const isFormDefaultDataPath = /^\.forms\.(?:[^.]+|"[^"]+")\.data(?:$|[.\[])/.test(nodePath);
17724
+ if (isFormDefaultDataPath) {
17725
+ return node;
17726
+ }
17723
17727
  if (typeof node === "string") {
17724
17728
  return node.replace(/\$\{([^}]+)\}/g, (_match, varName) => {
17725
17729
  const envValue = process.env[varName];
@@ -26045,6 +26049,25 @@ var KanbanSDK = class _KanbanSDK {
26045
26049
  get workspaceRoot() {
26046
26050
  return path14.dirname(this.kanbanDir);
26047
26051
  }
26052
+ /**
26053
+ * Returns a cloned read-only snapshot of the current workspace config.
26054
+ *
26055
+ * The returned snapshot is created from a fresh config read and deep-cloned
26056
+ * before being returned, so callers receive an isolated view of the current
26057
+ * `.kanban.json` state rather than a live mutable runtime object. Mutating the
26058
+ * returned snapshot does not update persisted config or affect this SDK instance.
26059
+ *
26060
+ * @returns A cloned read-only snapshot of the current {@link KanbanConfig}.
26061
+ *
26062
+ * @example
26063
+ * ```ts
26064
+ * const config = sdk.getConfigSnapshot()
26065
+ * console.log(config.defaultBoard)
26066
+ * ```
26067
+ */
26068
+ getConfigSnapshot() {
26069
+ return structuredClone(readConfig(this.workspaceRoot));
26070
+ }
26048
26071
  // --- Board resolution helpers ---
26049
26072
  /** @internal */
26050
26073
  _resolveBoardId(boardId) {
@@ -17679,6 +17679,10 @@ function loadDotEnv(dir) {
17679
17679
  }
17680
17680
  }
17681
17681
  function resolveConfigEnvVars(node, configFileName, nodePath = "") {
17682
+ const isFormDefaultDataPath = /^\.forms\.(?:[^.]+|"[^"]+")\.data(?:$|[.\[])/.test(nodePath);
17683
+ if (isFormDefaultDataPath) {
17684
+ return node;
17685
+ }
17682
17686
  if (typeof node === "string") {
17683
17687
  return node.replace(/\$\{([^}]+)\}/g, (_match, varName) => {
17684
17688
  const envValue = process.env[varName];
@@ -26004,6 +26008,25 @@ var KanbanSDK = class _KanbanSDK {
26004
26008
  get workspaceRoot() {
26005
26009
  return path14.dirname(this.kanbanDir);
26006
26010
  }
26011
+ /**
26012
+ * Returns a cloned read-only snapshot of the current workspace config.
26013
+ *
26014
+ * The returned snapshot is created from a fresh config read and deep-cloned
26015
+ * before being returned, so callers receive an isolated view of the current
26016
+ * `.kanban.json` state rather than a live mutable runtime object. Mutating the
26017
+ * returned snapshot does not update persisted config or affect this SDK instance.
26018
+ *
26019
+ * @returns A cloned read-only snapshot of the current {@link KanbanConfig}.
26020
+ *
26021
+ * @example
26022
+ * ```ts
26023
+ * const config = sdk.getConfigSnapshot()
26024
+ * console.log(config.defaultBoard)
26025
+ * ```
26026
+ */
26027
+ getConfigSnapshot() {
26028
+ return structuredClone(readConfig(this.workspaceRoot));
26029
+ }
26007
26030
  // --- Board resolution helpers ---
26008
26031
  /** @internal */
26009
26032
  _resolveBoardId(boardId) {
@@ -1,6 +1,6 @@
1
1
  import type { Comment, Card, KanbanColumn, BoardInfo, LabelDefinition, CardSortOption, LogEntry } from '../shared/types';
2
2
  import type { CardDisplaySettings, Priority } from '../shared/types';
3
- import type { BoardConfig, ResolvedCapabilities, Webhook } from '../shared/config';
3
+ import type { BoardConfig, KanbanConfig, ResolvedCapabilities, Webhook } from '../shared/config';
4
4
  import type { CreateCardInput, SDKEvent, SDKEventType, SDKOptions, SubmitFormInput, SubmitFormResult, AuthContext, AuthDecision, SDKBeforeEventType, SDKAfterEventType, CardStateStatus, CardUnreadSummary } from './types';
5
5
  import type { EventBusAnyListener, EventBusWaitOptions } from './eventBus';
6
6
  import { EventBus } from './eventBus';
@@ -46,6 +46,9 @@ export interface AuthStatus {
46
46
  */
47
47
  policyEnabled: boolean;
48
48
  }
49
+ type ReadonlySnapshot<T> = T extends (...args: never[]) => unknown ? T : T extends readonly (infer U)[] ? readonly ReadonlySnapshot<U>[] : T extends object ? {
50
+ readonly [K in keyof T]: ReadonlySnapshot<T[K]>;
51
+ } : T;
49
52
  /**
50
53
  * Active webhook provider metadata for diagnostics and host surfaces.
51
54
  *
@@ -535,6 +538,23 @@ export declare class KanbanSDK {
535
538
  * ```
536
539
  */
537
540
  get workspaceRoot(): string;
541
+ /**
542
+ * Returns a cloned read-only snapshot of the current workspace config.
543
+ *
544
+ * The returned snapshot is created from a fresh config read and deep-cloned
545
+ * before being returned, so callers receive an isolated view of the current
546
+ * `.kanban.json` state rather than a live mutable runtime object. Mutating the
547
+ * returned snapshot does not update persisted config or affect this SDK instance.
548
+ *
549
+ * @returns A cloned read-only snapshot of the current {@link KanbanConfig}.
550
+ *
551
+ * @example
552
+ * ```ts
553
+ * const config = sdk.getConfigSnapshot()
554
+ * console.log(config.defaultBoard)
555
+ * ```
556
+ */
557
+ getConfigSnapshot(): ReadonlySnapshot<KanbanConfig>;
538
558
  /** @internal */
539
559
  _resolveBoardId(boardId?: string): string;
540
560
  /** @internal */
@@ -1771,3 +1791,4 @@ export declare class KanbanSDK {
1771
1791
  */
1772
1792
  updateWebhook(id: string, updates: Partial<Pick<Webhook, 'url' | 'events' | 'secret' | 'active'>>): Promise<Webhook | null>;
1773
1793
  }
1794
+ export {};
@@ -337,6 +337,13 @@ export type StandaloneHttpHandler = (request: StandaloneHttpRequestContext) => P
337
337
  * resolved the active workspace capability selections.
338
338
  */
339
339
  export interface StandaloneHttpPluginRegistrationOptions {
340
+ /**
341
+ * Active SDK instance backing the standalone runtime, when provided by the host.
342
+ *
343
+ * Plugin registration code may use the full public {@link KanbanSDK} surface,
344
+ * including `getConfigSnapshot()`, when this seam is available.
345
+ */
346
+ readonly sdk?: KanbanSDK;
340
347
  /** Absolute workspace root containing `.kanban.json`. */
341
348
  readonly workspaceRoot: string;
342
349
  /** Absolute workspace `.kanban` directory. */
@@ -1,5 +1,6 @@
1
1
  import type { Card, CardFormAttachment, CardFormDataMap, Priority, ResolvedFormDescriptor } from '../shared/types';
2
- import type { CapabilitySelections, Webhook } from '../shared/config';
2
+ import type { CapabilitySelections } from '../shared/config';
3
+ import type { KanbanSDK } from './KanbanSDK';
3
4
  import type { StorageEngine, StorageEngineType } from './plugins/types';
4
5
  import type { CardStateCursor } from './plugins';
5
6
  export type { StorageEngine, StorageEngineType } from './plugins/types';
@@ -489,29 +490,11 @@ export declare class CardStateError extends Error {
489
490
  constructor(code: CardStateErrorCode, message: string);
490
491
  }
491
492
  /**
492
- * Minimal SDK webhook facade supplied to CLI plugins via {@link CliPluginContext}.
493
- *
494
- * Structural subset of `KanbanSDK`; plugins should use this surface instead of
495
- * importing `KanbanSDK` directly so they remain decoupled from core internals.
493
+ * @deprecated Use {@link KanbanSDK}. CLI plugin hosts now advertise the full
494
+ * public SDK surface, including `getConfigSnapshot()`, instead of a narrowed
495
+ * webhook-only facade.
496
496
  */
497
- export interface CliPluginSdk {
498
- /**
499
- * Returns the SDK extension bag contributed by the plugin with the given id,
500
- * when the host is backed by a full `KanbanSDK` instance.
501
- *
502
- * CLI plugins should prefer this extension path when available and fall back
503
- * to compatibility methods only when running against older or mocked SDK facades.
504
- */
505
- getExtension?<T extends Record<string, unknown> = Record<string, unknown>>(id: string): T | undefined;
506
- listWebhooks(): Webhook[];
507
- createWebhook(input: {
508
- url: string;
509
- events: string[];
510
- secret?: string;
511
- }): Promise<Webhook>;
512
- updateWebhook(id: string, updates: Partial<Pick<Webhook, 'url' | 'events' | 'secret' | 'active'>>): Promise<Webhook | null>;
513
- deleteWebhook(id: string): Promise<boolean>;
514
- }
497
+ export type CliPluginSdk = KanbanSDK;
515
498
  /**
516
499
  * Runtime context supplied to a {@link KanbanCliPlugin} when it is invoked by
517
500
  * the `kl` CLI.
@@ -524,10 +507,12 @@ export interface CliPluginContext {
524
507
  *
525
508
  * Present when the plugin is invoked through the core `kl` CLI.
526
509
  * Absent in isolated unit tests or standalone invocations.
527
- * Plugins should prefer this over constructing their own SDK so that
528
- * SDK-level auth policy is honoured.
510
+ * Plugins may use the full public {@link KanbanSDK} contract here, including
511
+ * extension lookup and `getConfigSnapshot()`, instead of relying on older
512
+ * helper-only SDK facades. Plugins should prefer this over constructing their
513
+ * own SDK so that SDK-level auth policy is honoured.
529
514
  */
530
- sdk?: CliPluginSdk;
515
+ sdk?: KanbanSDK;
531
516
  /**
532
517
  * Core-owned CLI auth helper.
533
518
  *
@@ -49920,6 +49920,10 @@ function loadDotEnv(dir2) {
49920
49920
  }
49921
49921
  }
49922
49922
  function resolveConfigEnvVars(node, configFileName, nodePath = "") {
49923
+ const isFormDefaultDataPath = /^\.forms\.(?:[^.]+|"[^"]+")\.data(?:$|[.\[])/.test(nodePath);
49924
+ if (isFormDefaultDataPath) {
49925
+ return node;
49926
+ }
49923
49927
  if (typeof node === "string") {
49924
49928
  return node.replace(/\$\{([^}]+)\}/g, (_match, varName) => {
49925
49929
  const envValue = process.env[varName];
@@ -61678,6 +61682,25 @@ var KanbanSDK = class _KanbanSDK {
61678
61682
  get workspaceRoot() {
61679
61683
  return path17.dirname(this.kanbanDir);
61680
61684
  }
61685
+ /**
61686
+ * Returns a cloned read-only snapshot of the current workspace config.
61687
+ *
61688
+ * The returned snapshot is created from a fresh config read and deep-cloned
61689
+ * before being returned, so callers receive an isolated view of the current
61690
+ * `.kanban.json` state rather than a live mutable runtime object. Mutating the
61691
+ * returned snapshot does not update persisted config or affect this SDK instance.
61692
+ *
61693
+ * @returns A cloned read-only snapshot of the current {@link KanbanConfig}.
61694
+ *
61695
+ * @example
61696
+ * ```ts
61697
+ * const config = sdk.getConfigSnapshot()
61698
+ * console.log(config.defaultBoard)
61699
+ * ```
61700
+ */
61701
+ getConfigSnapshot() {
61702
+ return structuredClone(readConfig(this.workspaceRoot));
61703
+ }
61681
61704
  // --- Board resolution helpers ---
61682
61705
  /** @internal */
61683
61706
  _resolveBoardId(boardId) {
@@ -65218,9 +65241,14 @@ async function handleMessage(ctx, ws, message, authContext) {
65218
65241
  }
65219
65242
 
65220
65243
  // src/standalone/internal/websocket.ts
65221
- function attachWebSocketHandlers(ctx) {
65222
- ctx.wss.on("connection", (ws, req) => {
65223
- const authContext = extractAuthContext(req);
65244
+ function attachWebSocketHandlers(ctx, resolveAuthContext) {
65245
+ ctx.wss.on("connection", async (ws, req) => {
65246
+ let authContext;
65247
+ try {
65248
+ authContext = resolveAuthContext ? await resolveAuthContext(req) : extractAuthContext(req);
65249
+ } catch {
65250
+ authContext = extractAuthContext(req);
65251
+ }
65224
65252
  setClientEditingCard(ctx, ws, null);
65225
65253
  ws.on("message", (data) => {
65226
65254
  let message;
@@ -65387,6 +65415,7 @@ function isPageRequest(method, pathname) {
65387
65415
  function collectStandaloneHttpHandlers(requestType, ctx) {
65388
65416
  const plugins = ctx.sdk.capabilities?.standaloneHttpPlugins ?? [];
65389
65417
  const registrationOptions = {
65418
+ sdk: ctx.sdk,
65390
65419
  workspaceRoot: ctx.workspaceRoot,
65391
65420
  kanbanDir: ctx.absoluteKanbanDir,
65392
65421
  capabilities: ctx.sdk.capabilities?.providers ?? {
@@ -65514,7 +65543,62 @@ function startServer(kanbanDir, port2, webviewDir, resolvedConfigPath) {
65514
65543
  }
65515
65544
  reply.hijack();
65516
65545
  });
65517
- attachWebSocketHandlers(ctx);
65546
+ const resolveWsAuthContext = async (req) => {
65547
+ const silentRes = /* @__PURE__ */ (() => {
65548
+ const r = {
65549
+ writableEnded: false,
65550
+ writeHead() {
65551
+ return r;
65552
+ },
65553
+ setHeader() {
65554
+ return r;
65555
+ },
65556
+ removeHeader() {
65557
+ },
65558
+ getHeader() {
65559
+ return void 0;
65560
+ },
65561
+ getHeaders() {
65562
+ return {};
65563
+ },
65564
+ end(..._args) {
65565
+ r.writableEnded = true;
65566
+ return r;
65567
+ },
65568
+ write() {
65569
+ return false;
65570
+ }
65571
+ };
65572
+ return r;
65573
+ })();
65574
+ const reqWithBody = req;
65575
+ const wsUrl = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
65576
+ const requestContext = {
65577
+ ctx,
65578
+ sdk: ctx.sdk,
65579
+ workspaceRoot: ctx.workspaceRoot,
65580
+ kanbanDir: ctx.absoluteKanbanDir,
65581
+ req: reqWithBody,
65582
+ res: silentRes,
65583
+ url: wsUrl,
65584
+ pathname: wsUrl.pathname,
65585
+ method: "GET",
65586
+ resolvedWebviewDir,
65587
+ indexHtml: resolvedIndexHtml,
65588
+ route: createRouteMatcher("GET", wsUrl.pathname, matchRoute),
65589
+ isApiRequest: false,
65590
+ isPageRequest: false,
65591
+ getAuthContext: () => getRequestAuthContext(req),
65592
+ setAuthContext: (auth) => setRequestAuthContext(req, auth),
65593
+ mergeAuthContext: (auth) => mergeRequestAuthContext(req, auth)
65594
+ };
65595
+ for (const handler of middlewareHandlers) {
65596
+ if (await handler(requestContext))
65597
+ break;
65598
+ }
65599
+ return extractAuthContext(req);
65600
+ };
65601
+ attachWebSocketHandlers(ctx, resolveWsAuthContext);
65518
65602
  setupStandaloneLifecycle(ctx, fastify.server);
65519
65603
  const effectiveConfigPath = resolvedConfigPath ?? configPath(path20.dirname(ctx.absoluteKanbanDir));
65520
65604
  fastify.listen({ port: port2, host: "0.0.0.0" }, (err) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kanban-lite",
3
- "version": "1.2.3",
3
+ "version": "1.2.6",
4
4
  "description": "Kanban board manager - VSCode extension, CLI, MCP server, and standalone web app",
5
5
  "license": "MIT",
6
6
  "repository": {