claudekit-cli 3.37.0-dev.1 → 3.37.0-dev.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.
Files changed (3) hide show
  1. package/README.md +20 -0
  2. package/dist/index.js +158 -30
  3. package/package.json +1 -1
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
@@ -46709,21 +46709,15 @@ var init_file_watcher = __esm(() => {
46709
46709
 
46710
46710
  // src/domains/web-server/middleware/cors.ts
46711
46711
  function corsMiddleware(req, res, next) {
46712
- const origin = req.headers.origin;
46713
- if (origin && !origin.match(/^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/)) {
46712
+ const origin = getHeaderValue(req.headers.origin);
46713
+ const normalizedOrigin = origin ? normalizeOrigin(origin) : null;
46714
+ if (origin && (!normalizedOrigin || !isAllowedOrigin(normalizedOrigin, req))) {
46714
46715
  res.status(403).json({ error: "Forbidden: invalid origin" });
46715
46716
  return;
46716
46717
  }
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);
46718
+ if (normalizedOrigin) {
46719
+ appendVaryHeader(res, "Origin");
46720
+ res.setHeader("Access-Control-Allow-Origin", normalizedOrigin);
46727
46721
  }
46728
46722
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
46729
46723
  res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
@@ -46734,6 +46728,68 @@ function corsMiddleware(req, res, next) {
46734
46728
  }
46735
46729
  next();
46736
46730
  }
46731
+ function isAllowedOrigin(origin, req) {
46732
+ if (LOCAL_DEV_ORIGINS.has(origin)) {
46733
+ return true;
46734
+ }
46735
+ return getRequestOrigins(req).has(origin);
46736
+ }
46737
+ function getRequestOrigins(req) {
46738
+ const origins = new Set;
46739
+ const hosts = getHeaderValues(req.headers["x-forwarded-host"]).concat(getHeaderValues(req.headers.host));
46740
+ const protocols = getForwardedProtocols(req);
46741
+ for (const host of hosts) {
46742
+ for (const protocol of protocols) {
46743
+ origins.add(`${protocol}://${host}`);
46744
+ }
46745
+ }
46746
+ return origins;
46747
+ }
46748
+ function getForwardedProtocols(req) {
46749
+ const forwarded = getHeaderValues(req.headers["x-forwarded-proto"]).map((value) => value.toLowerCase());
46750
+ const current = typeof req.protocol === "string" && req.protocol ? [req.protocol.toLowerCase()] : ["http"];
46751
+ return Array.from(new Set([...forwarded, ...current].filter((value) => value === "http" || value === "https")));
46752
+ }
46753
+ function normalizeOrigin(origin) {
46754
+ try {
46755
+ return new URL(origin).origin;
46756
+ } catch {
46757
+ return null;
46758
+ }
46759
+ }
46760
+ function getHeaderValue(value) {
46761
+ if (Array.isArray(value)) {
46762
+ return value[0];
46763
+ }
46764
+ return value;
46765
+ }
46766
+ function getHeaderValues(value) {
46767
+ const raw = Array.isArray(value) ? value.join(",") : value;
46768
+ if (!raw) {
46769
+ return [];
46770
+ }
46771
+ return raw.split(",").map((entry) => entry.trim()).filter(Boolean);
46772
+ }
46773
+ function appendVaryHeader(res, value) {
46774
+ const current = res.getHeader("Vary");
46775
+ if (!current) {
46776
+ res.setHeader("Vary", value);
46777
+ return;
46778
+ }
46779
+ const entries = String(current).split(",").map((entry) => entry.trim()).filter(Boolean);
46780
+ if (!entries.includes(value)) {
46781
+ entries.push(value);
46782
+ res.setHeader("Vary", entries.join(", "));
46783
+ }
46784
+ }
46785
+ var LOCAL_DEV_ORIGINS;
46786
+ var init_cors = __esm(() => {
46787
+ LOCAL_DEV_ORIGINS = new Set(["localhost", "127.0.0.1", "[::1]"].flatMap((host) => [
46788
+ `http://${host}:3000`,
46789
+ `http://${host}:3456`,
46790
+ `http://${host}:5173`
46791
+ ]));
46792
+ });
46737
46793
 
46738
46794
  // src/domains/web-server/middleware/error-handler.ts
46739
46795
  function errorHandler(err, _req, res, _next) {
@@ -56724,7 +56780,7 @@ var package_default;
56724
56780
  var init_package = __esm(() => {
56725
56781
  package_default = {
56726
56782
  name: "claudekit-cli",
56727
- version: "3.37.0-dev.1",
56783
+ version: "3.37.0-dev.2",
56728
56784
  description: "CLI tool for bootstrapping and updating ClaudeKit projects",
56729
56785
  type: "module",
56730
56786
  repository: {
@@ -62150,7 +62206,12 @@ var init_websocket_manager = __esm(() => {
62150
62206
  import { createServer } from "node:http";
62151
62207
  import { fileURLToPath as fileURLToPath3 } from "node:url";
62152
62208
  async function createAppServer(options2 = {}) {
62153
- const { port: preferredPort, openBrowser = true, devMode = false } = options2;
62209
+ const {
62210
+ port: preferredPort,
62211
+ openBrowser = true,
62212
+ devMode = false,
62213
+ host = DEFAULT_DASHBOARD_HOST
62214
+ } = options2;
62154
62215
  const port = await getPorts({ port: preferredPort || [3456, 3457, 3458, 3459, 3460] });
62155
62216
  const app = import_express2.default();
62156
62217
  app.use(import_express2.default.json({ limit: "10mb" }));
@@ -62183,15 +62244,15 @@ async function createAppServer(options2 = {}) {
62183
62244
  };
62184
62245
  server.once("listening", onListening);
62185
62246
  server.once("error", onError);
62186
- server.listen(port);
62247
+ server.listen(port, host);
62187
62248
  });
62188
- logger.debug(`Server listening on port ${port}`);
62249
+ logger.debug(`Server listening on ${host}:${port}`);
62189
62250
  if (openBrowser) {
62190
62251
  try {
62191
- await open_default(`http://localhost:${port}`);
62252
+ await open_default(getBrowserUrl(host, port));
62192
62253
  } catch (err) {
62193
62254
  logger.warning(`Failed to open browser: ${err instanceof Error ? err.message : err}`);
62194
- logger.info(`Open http://localhost:${port} manually`);
62255
+ logger.info(`Open ${getBrowserUrl(host, port)} manually`);
62195
62256
  }
62196
62257
  }
62197
62258
  } catch (error) {
@@ -62202,6 +62263,7 @@ async function createAppServer(options2 = {}) {
62202
62263
  }
62203
62264
  return {
62204
62265
  port,
62266
+ host,
62205
62267
  server,
62206
62268
  close: async () => {
62207
62269
  fileWatcher?.stop();
@@ -62252,17 +62314,29 @@ async function setupViteDevServer(app, httpServer, options2) {
62252
62314
  serveStatic(app);
62253
62315
  }
62254
62316
  }
62255
- var import_express2;
62317
+ function getBrowserUrl(host, port) {
62318
+ if (WILDCARD_HOSTS.has(host) || LOOPBACK_OPEN_HOSTS.has(host)) {
62319
+ return `http://localhost:${port}`;
62320
+ }
62321
+ return `http://${formatHostForUrl(host)}:${port}`;
62322
+ }
62323
+ function formatHostForUrl(host) {
62324
+ return host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
62325
+ }
62326
+ var import_express2, DEFAULT_DASHBOARD_HOST = "127.0.0.1", LOOPBACK_OPEN_HOSTS, WILDCARD_HOSTS;
62256
62327
  var init_server = __esm(() => {
62257
62328
  init_logger();
62258
62329
  init_get_port();
62259
62330
  init_open();
62260
62331
  init_file_watcher();
62332
+ init_cors();
62261
62333
  init_error_handler();
62262
62334
  init_routes();
62263
62335
  init_static_server();
62264
62336
  init_websocket_manager();
62265
62337
  import_express2 = __toESM(require_express2(), 1);
62338
+ LOOPBACK_OPEN_HOSTS = new Set(["127.0.0.1", "localhost", "::1"]);
62339
+ WILDCARD_HOSTS = new Set(["0.0.0.0", "::"]);
62266
62340
  });
62267
62341
 
62268
62342
  // src/domains/web-server/index.ts
@@ -70604,6 +70678,10 @@ var init_config_command_help = __esm(() => {
70604
70678
  command: "ck config",
70605
70679
  description: "Launch the web dashboard (same as 'ck config ui')"
70606
70680
  },
70681
+ {
70682
+ command: "ck config --host 0.0.0.0 --no-open",
70683
+ description: "Expose the dashboard to your network intentionally"
70684
+ },
70607
70685
  {
70608
70686
  command: "ck config set defaults.kit engineer",
70609
70687
  description: "Set a config value from the CLI"
@@ -70651,6 +70729,10 @@ var init_config_command_help = __esm(() => {
70651
70729
  flags: "--port <port>",
70652
70730
  description: "Port for dashboard server"
70653
70731
  },
70732
+ {
70733
+ flags: "--host <host>",
70734
+ description: "Bind dashboard host (default: 127.0.0.1)"
70735
+ },
70654
70736
  {
70655
70737
  flags: "--no-open",
70656
70738
  description: "Do not auto-open browser when launching dashboard"
@@ -70674,7 +70756,7 @@ var init_config_command_help = __esm(() => {
70674
70756
  sections: [
70675
70757
  {
70676
70758
  title: "Notes",
70677
- content: "Run 'ck config --help' to see both CLI actions and dashboard flags. Running bare 'ck config' opens the dashboard directly."
70759
+ 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
70760
  }
70679
70761
  ]
70680
70762
  };
@@ -74708,12 +74790,16 @@ init_commands_discovery();
74708
74790
  // src/commands/config/config-ui-command.ts
74709
74791
  init_logger();
74710
74792
  var import_picocolors14 = __toESM(require_picocolors(), 1);
74793
+ import { networkInterfaces } from "node:os";
74794
+ var LOOPBACK_HOSTS = new Set(["127.0.0.1", "localhost", "::1"]);
74795
+ var WILDCARD_HOSTS2 = new Set(["0.0.0.0", "::"]);
74711
74796
  async function configUICommand(options2 = {}) {
74712
74797
  const { port, dev = false } = options2;
74798
+ const host = options2.host?.trim() || undefined;
74713
74799
  const noOpen = options2.open === false || options2.noOpen === true;
74714
74800
  try {
74715
74801
  if (port) {
74716
- const isAvailable = await checkPort(port);
74802
+ const isAvailable = await checkPort(port, host);
74717
74803
  if (!isAvailable) {
74718
74804
  logger.error(`Port ${port} is already in use`);
74719
74805
  logger.info("Try: ck config (auto-selects available port)");
@@ -74726,13 +74812,20 @@ async function configUICommand(options2 = {}) {
74726
74812
  const server = await startServer({
74727
74813
  port,
74728
74814
  openBrowser: !noOpen,
74729
- devMode: dev
74815
+ devMode: dev,
74816
+ host
74730
74817
  });
74731
- const url = `http://localhost:${server.port}`;
74818
+ const urls = getDashboardUrls(server.host, server.port);
74732
74819
  console.log();
74733
74820
  console.log(import_picocolors14.default.bold(" ClaudeKit Dashboard"));
74734
74821
  console.log(import_picocolors14.default.dim(" ─────────────────────"));
74735
- console.log(` ${import_picocolors14.default.green("➜")} Local: ${import_picocolors14.default.cyan(url)}`);
74822
+ if (urls.local) {
74823
+ console.log(` ${import_picocolors14.default.green("➜")} Local: ${import_picocolors14.default.cyan(urls.local)}`);
74824
+ }
74825
+ for (const url of urls.network) {
74826
+ console.log(` ${import_picocolors14.default.green(urls.local ? "•" : "➜")} Network: ${import_picocolors14.default.cyan(url)}`);
74827
+ }
74828
+ console.log(` ${import_picocolors14.default.green("•")} Bind: ${import_picocolors14.default.cyan(server.host)}`);
74736
74829
  console.log();
74737
74830
  console.log(import_picocolors14.default.dim(" Press Ctrl+C to stop"));
74738
74831
  console.log();
@@ -74760,7 +74853,7 @@ async function configUICommand(options2 = {}) {
74760
74853
  process.exitCode = 1;
74761
74854
  }
74762
74855
  }
74763
- async function checkPort(port) {
74856
+ async function checkPort(port, host) {
74764
74857
  const { createServer: createServer2 } = await import("node:net");
74765
74858
  return new Promise((resolve13) => {
74766
74859
  const server = createServer2();
@@ -74769,9 +74862,43 @@ async function checkPort(port) {
74769
74862
  server.close();
74770
74863
  resolve13(true);
74771
74864
  });
74772
- server.listen(port);
74865
+ server.listen(port, host);
74773
74866
  });
74774
74867
  }
74868
+ function getDashboardUrls(host, port) {
74869
+ if (WILDCARD_HOSTS2.has(host)) {
74870
+ return {
74871
+ local: `http://localhost:${port}`,
74872
+ network: getDetectedNetworkUrls(port)
74873
+ };
74874
+ }
74875
+ if (LOOPBACK_HOSTS.has(host)) {
74876
+ return {
74877
+ local: `http://localhost:${port}`,
74878
+ network: []
74879
+ };
74880
+ }
74881
+ return {
74882
+ local: null,
74883
+ network: [buildDashboardUrl(host, port)]
74884
+ };
74885
+ }
74886
+ function getDetectedNetworkUrls(port) {
74887
+ const urls = new Set;
74888
+ for (const addresses of Object.values(networkInterfaces())) {
74889
+ for (const address of addresses ?? []) {
74890
+ if (address.internal) {
74891
+ continue;
74892
+ }
74893
+ urls.add(buildDashboardUrl(address.address, port));
74894
+ }
74895
+ }
74896
+ return Array.from(urls).sort();
74897
+ }
74898
+ function buildDashboardUrl(host, port) {
74899
+ const formattedHost = host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
74900
+ return `http://${formattedHost}:${port}`;
74901
+ }
74775
74902
 
74776
74903
  // src/commands/config/phases/get-handler.ts
74777
74904
  init_config();
@@ -74935,9 +75062,10 @@ async function configCommand(action, keyOrOptions, valueOrOptions, options2) {
74935
75062
  }
74936
75063
  const rawOpts = options2 || (typeof keyOrOptions === "object" ? keyOrOptions : {});
74937
75064
  const uiOpts = {
74938
- port: rawOpts?.port,
74939
- noOpen: rawOpts?.noOpen,
74940
- dev: rawOpts?.dev
75065
+ port: rawOpts.port,
75066
+ noOpen: rawOpts.noOpen,
75067
+ dev: rawOpts.dev,
75068
+ host: rawOpts.host
74941
75069
  };
74942
75070
  return configUICommand(uiOpts);
74943
75071
  }
@@ -102663,7 +102791,7 @@ function registerCommands(cli) {
102663
102791
  console.error(`Unknown action: ${action}. Available: start, stop, status, logs, setup, queue, approve, reject`);
102664
102792
  }
102665
102793
  });
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) => {
102794
+ 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
102795
  await configCommand(action, key, value, options2);
102668
102796
  });
102669
102797
  registerProjectsCommand(cli);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudekit-cli",
3
- "version": "3.37.0-dev.1",
3
+ "version": "3.37.0-dev.2",
4
4
  "description": "CLI tool for bootstrapping and updating ClaudeKit projects",
5
5
  "type": "module",
6
6
  "repository": {