claudekit-cli 3.37.0-dev.1 → 3.37.0-dev.3

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 CHANGED
@@ -91,6 +91,9 @@ ck --help
91
91
  # Open config dashboard immediately
92
92
  ck config
93
93
 
94
+ # Expose the dashboard intentionally to your LAN/Tailscale
95
+ ck config --host 0.0.0.0 --no-open
96
+
94
97
  # Command-level help (recommended)
95
98
  ck config --help
96
99
  ck skills --help
@@ -99,6 +102,23 @@ ck commands --help
99
102
  ck migrate --help
100
103
  ```
101
104
 
105
+ ### Config Dashboard Access
106
+
107
+ By default, `ck config` binds the dashboard to `127.0.0.1` for local-only access.
108
+
109
+ Use `--host` when you intentionally want remote access from another device on the same trusted network:
110
+
111
+ ```bash
112
+ # Bind to all interfaces
113
+ ck config --host 0.0.0.0 --no-open
114
+
115
+ # Bind to a specific interface or hostname
116
+ ck config --host 100.88.12.4 --no-open
117
+ ck config --host dashboard.local --no-open
118
+ ```
119
+
120
+ The dashboard still enforces same-origin browser access. Remote access works when you open the UI from the same host/origin that reaches the server, instead of relying on a hardcoded IP allowlist.
121
+
102
122
  ### Create New Project
103
123
 
104
124
  ```bash
package/dist/index.js CHANGED
@@ -42221,6 +42221,68 @@ var init_claudekit_data = __esm(() => {
42221
42221
  });
42222
42222
 
42223
42223
  // src/types/ck-config.ts
42224
+ function normalizeMigrateProviderToken(token) {
42225
+ const trimmed = token.trim();
42226
+ const unwrapped = trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'") ? trimmed.slice(1, -1) : trimmed;
42227
+ return unwrapped.trim().toLowerCase();
42228
+ }
42229
+ function parseMigrateProvidersString(value) {
42230
+ const trimmed = value.trim();
42231
+ if (!trimmed)
42232
+ return [];
42233
+ try {
42234
+ const parsed = JSON.parse(trimmed);
42235
+ if (typeof parsed === "string" || Array.isArray(parsed)) {
42236
+ return parsed;
42237
+ }
42238
+ } catch {}
42239
+ if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
42240
+ return trimmed.slice(1, -1).split(",").map((part) => part.trim()).filter(Boolean);
42241
+ }
42242
+ return trimmed;
42243
+ }
42244
+ function normalizeMigrateProviderList(value) {
42245
+ const parts = value.map(normalizeMigrateProviderToken).filter(Boolean).filter((part, index, list) => list.indexOf(part) === index);
42246
+ if (parts.length === 0 || parts.length === 1 && parts[0] === "auto") {
42247
+ return "auto";
42248
+ }
42249
+ return parts.filter((part) => part !== "auto");
42250
+ }
42251
+ function normalizeMigrateProvidersValue(value) {
42252
+ if (typeof value === "string") {
42253
+ const parsed = parseMigrateProvidersString(value);
42254
+ const parts = Array.isArray(parsed) ? parsed : String(parsed).split(",");
42255
+ return normalizeMigrateProviderList(parts);
42256
+ }
42257
+ if (Array.isArray(value)) {
42258
+ return normalizeMigrateProviderList(value.filter((item) => typeof item === "string"));
42259
+ }
42260
+ if (typeof value === "number" || typeof value === "boolean") {
42261
+ return normalizeMigrateProvidersValue(String(value));
42262
+ }
42263
+ return value;
42264
+ }
42265
+ function normalizeMigrateProvidersInput(value) {
42266
+ const normalized = normalizeMigrateProvidersValue(value);
42267
+ if (normalized === "auto" || Array.isArray(normalized)) {
42268
+ return normalized;
42269
+ }
42270
+ return normalizeMigrateProviderList(String(normalized).split(","));
42271
+ }
42272
+ function normalizeCkConfigInput(value) {
42273
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
42274
+ return value;
42275
+ }
42276
+ const normalized = structuredClone(value);
42277
+ const updatePipeline = normalized.updatePipeline;
42278
+ if (updatePipeline && typeof updatePipeline === "object" && !Array.isArray(updatePipeline)) {
42279
+ const pipeline = updatePipeline;
42280
+ if ("migrateProviders" in pipeline) {
42281
+ pipeline.migrateProviders = normalizeMigrateProvidersValue(pipeline.migrateProviders);
42282
+ }
42283
+ }
42284
+ return normalized;
42285
+ }
42224
42286
  var PlanValidationModeSchema, PlanFocusAreaSchema, PlanResolutionOrderSchema, ProjectTypeSchema, PackageManagerSchema, FrameworkSchema, GeminiModelSchema, StatuslineModeSchema, CodingLevelSchema, PlanResolutionSchema, PlanValidationSchema, CkPlanConfigSchema, CkDocsConfigSchema, CkPathsConfigSchema, CkLocaleConfigSchema, CkTrustConfigSchema, CkProjectConfigSchema, CkGeminiConfigSchema, CkSkillsConfigSchema, UpdatePipelineSchema, ResolvedModelConfigSchema, ModelTierMapSchema, CkModelTaxonomySchema, CkAssertionSchema, CkHooksConfigSchema, CkConfigSchema, DEFAULT_CK_CONFIG, CK_HOOK_NAMES;
42225
42287
  var init_ck_config = __esm(() => {
42226
42288
  init_zod();
@@ -42447,6 +42509,8 @@ var init_ck_config = __esm(() => {
42447
42509
  // src/types/index.ts
42448
42510
  var exports_types = {};
42449
42511
  __export(exports_types, {
42512
+ normalizeMigrateProvidersInput: () => normalizeMigrateProvidersInput,
42513
+ normalizeCkConfigInput: () => normalizeCkConfigInput,
42450
42514
  isValidKitType: () => isValidKitType,
42451
42515
  VidcapOptionsSchema: () => VidcapOptionsSchema,
42452
42516
  VersionCommandOptionsSchema: () => VersionCommandOptionsSchema,
@@ -42809,7 +42873,7 @@ class CkConfigManager {
42809
42873
  if (!existsSync12(configPath))
42810
42874
  return null;
42811
42875
  const content = await readFile8(configPath, "utf-8");
42812
- const data = JSON.parse(content);
42876
+ const data = normalizeCkConfigInput(JSON.parse(content));
42813
42877
  return CkConfigSchema.parse(data);
42814
42878
  } catch (error) {
42815
42879
  logger.warning(`Failed to load config from ${configPath}: ${error instanceof Error ? error.message : "Unknown"}`);
@@ -42863,7 +42927,7 @@ class CkConfigManager {
42863
42927
  };
42864
42928
  }
42865
42929
  static async saveFull(config, scope, projectDir) {
42866
- const validConfig = CkConfigSchema.parse(config);
42930
+ const validConfig = CkConfigSchema.parse(normalizeCkConfigInput(config));
42867
42931
  const configPath = scope === "global" ? CkConfigManager.getGlobalConfigPath() : projectDir ? CkConfigManager.getProjectConfigPath(projectDir) : null;
42868
42932
  if (!configPath) {
42869
42933
  throw new Error("Project directory required for project scope");
@@ -46709,21 +46773,15 @@ var init_file_watcher = __esm(() => {
46709
46773
 
46710
46774
  // src/domains/web-server/middleware/cors.ts
46711
46775
  function corsMiddleware(req, res, next) {
46712
- const origin = req.headers.origin;
46713
- if (origin && !origin.match(/^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/)) {
46776
+ const origin = getHeaderValue(req.headers.origin);
46777
+ const normalizedOrigin = origin ? normalizeOrigin(origin) : null;
46778
+ if (origin && (!normalizedOrigin || !isAllowedOrigin(normalizedOrigin, req))) {
46714
46779
  res.status(403).json({ error: "Forbidden: invalid origin" });
46715
46780
  return;
46716
46781
  }
46717
- const allowedOrigins = [
46718
- "http://localhost:3000",
46719
- "http://localhost:3456",
46720
- "http://localhost:5173",
46721
- "http://127.0.0.1:3000",
46722
- "http://127.0.0.1:3456",
46723
- "http://127.0.0.1:5173"
46724
- ];
46725
- if (origin && allowedOrigins.includes(origin)) {
46726
- res.setHeader("Access-Control-Allow-Origin", origin);
46782
+ if (normalizedOrigin) {
46783
+ appendVaryHeader(res, "Origin");
46784
+ res.setHeader("Access-Control-Allow-Origin", normalizedOrigin);
46727
46785
  }
46728
46786
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
46729
46787
  res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
@@ -46734,6 +46792,68 @@ function corsMiddleware(req, res, next) {
46734
46792
  }
46735
46793
  next();
46736
46794
  }
46795
+ function isAllowedOrigin(origin, req) {
46796
+ if (LOCAL_DEV_ORIGINS.has(origin)) {
46797
+ return true;
46798
+ }
46799
+ return getRequestOrigins(req).has(origin);
46800
+ }
46801
+ function getRequestOrigins(req) {
46802
+ const origins = new Set;
46803
+ const hosts = getHeaderValues(req.headers["x-forwarded-host"]).concat(getHeaderValues(req.headers.host));
46804
+ const protocols = getForwardedProtocols(req);
46805
+ for (const host of hosts) {
46806
+ for (const protocol of protocols) {
46807
+ origins.add(`${protocol}://${host}`);
46808
+ }
46809
+ }
46810
+ return origins;
46811
+ }
46812
+ function getForwardedProtocols(req) {
46813
+ const forwarded = getHeaderValues(req.headers["x-forwarded-proto"]).map((value) => value.toLowerCase());
46814
+ const current = typeof req.protocol === "string" && req.protocol ? [req.protocol.toLowerCase()] : ["http"];
46815
+ return Array.from(new Set([...forwarded, ...current].filter((value) => value === "http" || value === "https")));
46816
+ }
46817
+ function normalizeOrigin(origin) {
46818
+ try {
46819
+ return new URL(origin).origin;
46820
+ } catch {
46821
+ return null;
46822
+ }
46823
+ }
46824
+ function getHeaderValue(value) {
46825
+ if (Array.isArray(value)) {
46826
+ return value[0];
46827
+ }
46828
+ return value;
46829
+ }
46830
+ function getHeaderValues(value) {
46831
+ const raw = Array.isArray(value) ? value.join(",") : value;
46832
+ if (!raw) {
46833
+ return [];
46834
+ }
46835
+ return raw.split(",").map((entry) => entry.trim()).filter(Boolean);
46836
+ }
46837
+ function appendVaryHeader(res, value) {
46838
+ const current = res.getHeader("Vary");
46839
+ if (!current) {
46840
+ res.setHeader("Vary", value);
46841
+ return;
46842
+ }
46843
+ const entries = String(current).split(",").map((entry) => entry.trim()).filter(Boolean);
46844
+ if (!entries.includes(value)) {
46845
+ entries.push(value);
46846
+ res.setHeader("Vary", entries.join(", "));
46847
+ }
46848
+ }
46849
+ var LOCAL_DEV_ORIGINS;
46850
+ var init_cors = __esm(() => {
46851
+ LOCAL_DEV_ORIGINS = new Set(["localhost", "127.0.0.1", "[::1]"].flatMap((host) => [
46852
+ `http://${host}:3000`,
46853
+ `http://${host}:3456`,
46854
+ `http://${host}:5173`
46855
+ ]));
46856
+ });
46737
46857
 
46738
46858
  // src/domains/web-server/middleware/error-handler.ts
46739
46859
  function errorHandler(err, _req, res, _next) {
@@ -48509,7 +48629,7 @@ function registerCkConfigRoutes(app) {
48509
48629
  res.status(400).json({ error: "Invalid config payload" });
48510
48630
  return;
48511
48631
  }
48512
- const parseResult = CkConfigSchema.safeParse(config);
48632
+ const parseResult = CkConfigSchema.safeParse(normalizeCkConfigInput(config));
48513
48633
  if (!parseResult.success) {
48514
48634
  res.status(400).json({
48515
48635
  error: "Config validation failed",
@@ -56724,7 +56844,7 @@ var package_default;
56724
56844
  var init_package = __esm(() => {
56725
56845
  package_default = {
56726
56846
  name: "claudekit-cli",
56727
- version: "3.37.0-dev.1",
56847
+ version: "3.37.0-dev.3",
56728
56848
  description: "CLI tool for bootstrapping and updating ClaudeKit projects",
56729
56849
  type: "module",
56730
56850
  repository: {
@@ -62150,7 +62270,12 @@ var init_websocket_manager = __esm(() => {
62150
62270
  import { createServer } from "node:http";
62151
62271
  import { fileURLToPath as fileURLToPath3 } from "node:url";
62152
62272
  async function createAppServer(options2 = {}) {
62153
- const { port: preferredPort, openBrowser = true, devMode = false } = options2;
62273
+ const {
62274
+ port: preferredPort,
62275
+ openBrowser = true,
62276
+ devMode = false,
62277
+ host = DEFAULT_DASHBOARD_HOST
62278
+ } = options2;
62154
62279
  const port = await getPorts({ port: preferredPort || [3456, 3457, 3458, 3459, 3460] });
62155
62280
  const app = import_express2.default();
62156
62281
  app.use(import_express2.default.json({ limit: "10mb" }));
@@ -62183,15 +62308,15 @@ async function createAppServer(options2 = {}) {
62183
62308
  };
62184
62309
  server.once("listening", onListening);
62185
62310
  server.once("error", onError);
62186
- server.listen(port);
62311
+ server.listen(port, host);
62187
62312
  });
62188
- logger.debug(`Server listening on port ${port}`);
62313
+ logger.debug(`Server listening on ${host}:${port}`);
62189
62314
  if (openBrowser) {
62190
62315
  try {
62191
- await open_default(`http://localhost:${port}`);
62316
+ await open_default(getBrowserUrl(host, port));
62192
62317
  } catch (err) {
62193
62318
  logger.warning(`Failed to open browser: ${err instanceof Error ? err.message : err}`);
62194
- logger.info(`Open http://localhost:${port} manually`);
62319
+ logger.info(`Open ${getBrowserUrl(host, port)} manually`);
62195
62320
  }
62196
62321
  }
62197
62322
  } catch (error) {
@@ -62202,6 +62327,7 @@ async function createAppServer(options2 = {}) {
62202
62327
  }
62203
62328
  return {
62204
62329
  port,
62330
+ host,
62205
62331
  server,
62206
62332
  close: async () => {
62207
62333
  fileWatcher?.stop();
@@ -62252,17 +62378,29 @@ async function setupViteDevServer(app, httpServer, options2) {
62252
62378
  serveStatic(app);
62253
62379
  }
62254
62380
  }
62255
- var import_express2;
62381
+ function getBrowserUrl(host, port) {
62382
+ if (WILDCARD_HOSTS.has(host) || LOOPBACK_OPEN_HOSTS.has(host)) {
62383
+ return `http://localhost:${port}`;
62384
+ }
62385
+ return `http://${formatHostForUrl(host)}:${port}`;
62386
+ }
62387
+ function formatHostForUrl(host) {
62388
+ return host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
62389
+ }
62390
+ var import_express2, DEFAULT_DASHBOARD_HOST = "127.0.0.1", LOOPBACK_OPEN_HOSTS, WILDCARD_HOSTS;
62256
62391
  var init_server = __esm(() => {
62257
62392
  init_logger();
62258
62393
  init_get_port();
62259
62394
  init_open();
62260
62395
  init_file_watcher();
62396
+ init_cors();
62261
62397
  init_error_handler();
62262
62398
  init_routes();
62263
62399
  init_static_server();
62264
62400
  init_websocket_manager();
62265
62401
  import_express2 = __toESM(require_express2(), 1);
62402
+ LOOPBACK_OPEN_HOSTS = new Set(["127.0.0.1", "localhost", "::1"]);
62403
+ WILDCARD_HOSTS = new Set(["0.0.0.0", "::"]);
62266
62404
  });
62267
62405
 
62268
62406
  // src/domains/web-server/index.ts
@@ -70604,6 +70742,10 @@ var init_config_command_help = __esm(() => {
70604
70742
  command: "ck config",
70605
70743
  description: "Launch the web dashboard (same as 'ck config ui')"
70606
70744
  },
70745
+ {
70746
+ command: "ck config --host 0.0.0.0 --no-open",
70747
+ description: "Expose the dashboard to your network intentionally"
70748
+ },
70607
70749
  {
70608
70750
  command: "ck config set defaults.kit engineer",
70609
70751
  description: "Set a config value from the CLI"
@@ -70651,6 +70793,10 @@ var init_config_command_help = __esm(() => {
70651
70793
  flags: "--port <port>",
70652
70794
  description: "Port for dashboard server"
70653
70795
  },
70796
+ {
70797
+ flags: "--host <host>",
70798
+ description: "Bind dashboard host (default: 127.0.0.1)"
70799
+ },
70654
70800
  {
70655
70801
  flags: "--no-open",
70656
70802
  description: "Do not auto-open browser when launching dashboard"
@@ -70674,7 +70820,7 @@ var init_config_command_help = __esm(() => {
70674
70820
  sections: [
70675
70821
  {
70676
70822
  title: "Notes",
70677
- content: "Run 'ck config --help' to see both CLI actions and dashboard flags. Running bare 'ck config' opens the dashboard directly."
70823
+ content: "Run 'ck config --help' to see both CLI actions and dashboard flags. Running bare 'ck config' opens the dashboard directly. Use '--host' to expose the dashboard intentionally beyond localhost."
70678
70824
  }
70679
70825
  ]
70680
70826
  };
@@ -74708,12 +74854,16 @@ init_commands_discovery();
74708
74854
  // src/commands/config/config-ui-command.ts
74709
74855
  init_logger();
74710
74856
  var import_picocolors14 = __toESM(require_picocolors(), 1);
74857
+ import { networkInterfaces } from "node:os";
74858
+ var LOOPBACK_HOSTS = new Set(["127.0.0.1", "localhost", "::1"]);
74859
+ var WILDCARD_HOSTS2 = new Set(["0.0.0.0", "::"]);
74711
74860
  async function configUICommand(options2 = {}) {
74712
74861
  const { port, dev = false } = options2;
74862
+ const host = options2.host?.trim() || undefined;
74713
74863
  const noOpen = options2.open === false || options2.noOpen === true;
74714
74864
  try {
74715
74865
  if (port) {
74716
- const isAvailable = await checkPort(port);
74866
+ const isAvailable = await checkPort(port, host);
74717
74867
  if (!isAvailable) {
74718
74868
  logger.error(`Port ${port} is already in use`);
74719
74869
  logger.info("Try: ck config (auto-selects available port)");
@@ -74726,13 +74876,20 @@ async function configUICommand(options2 = {}) {
74726
74876
  const server = await startServer({
74727
74877
  port,
74728
74878
  openBrowser: !noOpen,
74729
- devMode: dev
74879
+ devMode: dev,
74880
+ host
74730
74881
  });
74731
- const url = `http://localhost:${server.port}`;
74882
+ const urls = getDashboardUrls(server.host, server.port);
74732
74883
  console.log();
74733
74884
  console.log(import_picocolors14.default.bold(" ClaudeKit Dashboard"));
74734
74885
  console.log(import_picocolors14.default.dim(" ─────────────────────"));
74735
- console.log(` ${import_picocolors14.default.green("➜")} Local: ${import_picocolors14.default.cyan(url)}`);
74886
+ if (urls.local) {
74887
+ console.log(` ${import_picocolors14.default.green("➜")} Local: ${import_picocolors14.default.cyan(urls.local)}`);
74888
+ }
74889
+ for (const url of urls.network) {
74890
+ console.log(` ${import_picocolors14.default.green(urls.local ? "•" : "➜")} Network: ${import_picocolors14.default.cyan(url)}`);
74891
+ }
74892
+ console.log(` ${import_picocolors14.default.green("•")} Bind: ${import_picocolors14.default.cyan(server.host)}`);
74736
74893
  console.log();
74737
74894
  console.log(import_picocolors14.default.dim(" Press Ctrl+C to stop"));
74738
74895
  console.log();
@@ -74760,7 +74917,7 @@ async function configUICommand(options2 = {}) {
74760
74917
  process.exitCode = 1;
74761
74918
  }
74762
74919
  }
74763
- async function checkPort(port) {
74920
+ async function checkPort(port, host) {
74764
74921
  const { createServer: createServer2 } = await import("node:net");
74765
74922
  return new Promise((resolve13) => {
74766
74923
  const server = createServer2();
@@ -74769,9 +74926,43 @@ async function checkPort(port) {
74769
74926
  server.close();
74770
74927
  resolve13(true);
74771
74928
  });
74772
- server.listen(port);
74929
+ server.listen(port, host);
74773
74930
  });
74774
74931
  }
74932
+ function getDashboardUrls(host, port) {
74933
+ if (WILDCARD_HOSTS2.has(host)) {
74934
+ return {
74935
+ local: `http://localhost:${port}`,
74936
+ network: getDetectedNetworkUrls(port)
74937
+ };
74938
+ }
74939
+ if (LOOPBACK_HOSTS.has(host)) {
74940
+ return {
74941
+ local: `http://localhost:${port}`,
74942
+ network: []
74943
+ };
74944
+ }
74945
+ return {
74946
+ local: null,
74947
+ network: [buildDashboardUrl(host, port)]
74948
+ };
74949
+ }
74950
+ function getDetectedNetworkUrls(port) {
74951
+ const urls = new Set;
74952
+ for (const addresses of Object.values(networkInterfaces())) {
74953
+ for (const address of addresses ?? []) {
74954
+ if (address.internal) {
74955
+ continue;
74956
+ }
74957
+ urls.add(buildDashboardUrl(address.address, port));
74958
+ }
74959
+ }
74960
+ return Array.from(urls).sort();
74961
+ }
74962
+ function buildDashboardUrl(host, port) {
74963
+ const formattedHost = host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
74964
+ return `http://${formattedHost}:${port}`;
74965
+ }
74775
74966
 
74776
74967
  // src/commands/config/phases/get-handler.ts
74777
74968
  init_config();
@@ -74935,9 +75126,10 @@ async function configCommand(action, keyOrOptions, valueOrOptions, options2) {
74935
75126
  }
74936
75127
  const rawOpts = options2 || (typeof keyOrOptions === "object" ? keyOrOptions : {});
74937
75128
  const uiOpts = {
74938
- port: rawOpts?.port,
74939
- noOpen: rawOpts?.noOpen,
74940
- dev: rawOpts?.dev
75129
+ port: rawOpts.port,
75130
+ noOpen: rawOpts.noOpen,
75131
+ dev: rawOpts.dev,
75132
+ host: rawOpts.host
74941
75133
  };
74942
75134
  return configUICommand(uiOpts);
74943
75135
  }
@@ -102663,7 +102855,7 @@ function registerCommands(cli) {
102663
102855
  console.error(`Unknown action: ${action}. Available: start, stop, status, logs, setup, queue, approve, reject`);
102664
102856
  }
102665
102857
  });
102666
- cli.command("config [action] [key] [value]", "Manage ClaudeKit configuration").option("-g, --global", "Use global config (~/.claudekit/config.json)").option("-l, --local", "Use local config (.claude/.ck.json)").option("--json", "Output in JSON format").option("--port <port>", "Port for UI server (default: auto)").option("--no-open", "Don't auto-open browser").option("--dev", "Run UI in development mode with HMR").action(async (action, key, value, options2) => {
102858
+ cli.command("config [action] [key] [value]", "Manage ClaudeKit configuration").option("-g, --global", "Use global config (~/.claudekit/config.json)").option("-l, --local", "Use local config (.claude/.ck.json)").option("--json", "Output in JSON format").option("--port <port>", "Port for UI server (default: auto)").option("--host <host>", "Bind dashboard host (default: 127.0.0.1)").option("--no-open", "Don't auto-open browser").option("--dev", "Run UI in development mode with HMR").action(async (action, key, value, options2) => {
102667
102859
  await configCommand(action, key, value, options2);
102668
102860
  });
102669
102861
  registerProjectsCommand(cli);