@knowsuchagency/fulcrum 5.5.1 → 5.6.1

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/bin/fulcrum.js CHANGED
@@ -1136,6 +1136,19 @@ class FulcrumClient {
1136
1136
  body: JSON.stringify({ title, message })
1137
1137
  });
1138
1138
  }
1139
+ async getServerExpose() {
1140
+ return this.fetch("/api/server/expose");
1141
+ }
1142
+ async createServerExpose(input) {
1143
+ return this.fetch("/api/server/expose", {
1144
+ method: "POST",
1145
+ body: JSON.stringify(input)
1146
+ });
1147
+ }
1148
+ async deleteServerExpose(removeTunnel) {
1149
+ const query = removeTunnel ? "?removeTunnel=true" : "";
1150
+ return this.fetch(`/api/server/expose${query}`, { method: "DELETE" });
1151
+ }
1139
1152
  async getDeveloperMode() {
1140
1153
  return this.fetch("/api/config/developer-mode");
1141
1154
  }
@@ -31923,7 +31936,7 @@ __export(exports_regexes2, {
31923
31936
  integer: () => integer2,
31924
31937
  idnEmail: () => idnEmail,
31925
31938
  html5Email: () => html5Email,
31926
- hostname: () => hostname,
31939
+ hostname: () => hostname2,
31927
31940
  hex: () => hex,
31928
31941
  guid: () => guid2,
31929
31942
  extendedDuration: () => extendedDuration,
@@ -31975,7 +31988,7 @@ var cuid3, cuid22, ulid2, xid2, ksuid2, nanoid2, duration3, extendedDuration, gu
31975
31988
  if (!version2)
31976
31989
  return /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/;
31977
31990
  return new RegExp(`^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-${version2}[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$`);
31978
- }, uuid4, uuid6, uuid7, email2, html5Email, rfc5322Email, unicodeEmail, idnEmail, browserEmail, _emoji3 = `^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$`, ipv42, ipv62, cidrv42, cidrv62, base642, base64url2, hostname, domain, e1642, dateSource2 = `(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))`, date4, string4 = (params) => {
31991
+ }, uuid4, uuid6, uuid7, email2, html5Email, rfc5322Email, unicodeEmail, idnEmail, browserEmail, _emoji3 = `^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$`, ipv42, ipv62, cidrv42, cidrv62, base642, base64url2, hostname2, domain, e1642, dateSource2 = `(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))`, date4, string4 = (params) => {
31979
31992
  const regex2 = params ? `[\\s\\S]{${params?.minimum ?? 0},${params?.maximum ?? ""}}` : `[\\s\\S]*`;
31980
31993
  return new RegExp(`^${regex2}$`);
31981
31994
  }, bigint3, integer2, number4, boolean4, _null4, _undefined3, lowercase2, uppercase2, hex, md5_hex, md5_base64, md5_base64url, sha1_hex, sha1_base64, sha1_base64url, sha256_hex, sha256_base64, sha256_base64url, sha384_hex, sha384_base64, sha384_base64url, sha512_hex, sha512_base64, sha512_base64url;
@@ -32004,7 +32017,7 @@ var init_regexes2 = __esm(() => {
32004
32017
  cidrv62 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/;
32005
32018
  base642 = /^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/;
32006
32019
  base64url2 = /^[A-Za-z0-9_-]*$/;
32007
- hostname = /^(?=.{1,253}\.?$)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[-0-9a-zA-Z]{0,61}[0-9a-zA-Z])?)*\.?$/;
32020
+ hostname2 = /^(?=.{1,253}\.?$)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[-0-9a-zA-Z]{0,61}[0-9a-zA-Z])?)*\.?$/;
32008
32021
  domain = /^([a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/;
32009
32022
  e1642 = /^\+(?:[0-9]){6,14}[0-9]$/;
32010
32023
  date4 = /* @__PURE__ */ new RegExp(`^${dateSource2}$`);
@@ -33115,7 +33128,7 @@ var init_schemas4 = __esm(() => {
33115
33128
  code: "invalid_format",
33116
33129
  format: "url",
33117
33130
  note: "Invalid hostname",
33118
- pattern: hostname.source,
33131
+ pattern: hostname2.source,
33119
33132
  input: payload.value,
33120
33133
  inst,
33121
33134
  continue: !def.abort
@@ -42389,7 +42402,7 @@ function jwt(params) {
42389
42402
  function stringFormat(format, fnOrRegex, _params = {}) {
42390
42403
  return _stringFormat2(ZodCustomStringFormat, format, fnOrRegex, _params);
42391
42404
  }
42392
- function hostname2(_params) {
42405
+ function hostname3(_params) {
42393
42406
  return _stringFormat2(ZodCustomStringFormat, "hostname", exports_regexes2.hostname, _params);
42394
42407
  }
42395
42408
  function hex2(_params) {
@@ -43453,7 +43466,7 @@ __export(exports_external, {
43453
43466
  instanceof: () => _instanceof,
43454
43467
  includes: () => _includes2,
43455
43468
  httpUrl: () => httpUrl,
43456
- hostname: () => hostname2,
43469
+ hostname: () => hostname3,
43457
43470
  hex: () => hex2,
43458
43471
  hash: () => hash,
43459
43472
  guid: () => guid3,
@@ -46838,7 +46851,7 @@ async function runMcpServer(urlOverride, portOverride) {
46838
46851
  const client = new FulcrumClient(urlOverride, portOverride);
46839
46852
  const server = new McpServer({
46840
46853
  name: "fulcrum",
46841
- version: "5.5.1"
46854
+ version: "5.6.1"
46842
46855
  });
46843
46856
  registerTools(server, client);
46844
46857
  const transport = new StdioServerTransport;
@@ -49187,7 +49200,7 @@ var marketplace_default = `{
49187
49200
  "name": "fulcrum",
49188
49201
  "source": "./",
49189
49202
  "description": "Task orchestration for Claude Code",
49190
- "version": "5.5.1",
49203
+ "version": "5.6.1",
49191
49204
  "skills": [
49192
49205
  "./skills/fulcrum"
49193
49206
  ],
@@ -49210,7 +49223,7 @@ var marketplace_default = `{
49210
49223
  var plugin_default = `{
49211
49224
  "name": "fulcrum",
49212
49225
  "description": "Fulcrum task orchestration for Claude Code",
49213
- "version": "5.5.1",
49226
+ "version": "5.6.1",
49214
49227
  "author": {
49215
49228
  "name": "Fulcrum"
49216
49229
  },
@@ -50227,7 +50240,7 @@ function compareVersions(v1, v2) {
50227
50240
  var package_default = {
50228
50241
  name: "@knowsuchagency/fulcrum",
50229
50242
  private: true,
50230
- version: "5.5.1",
50243
+ version: "5.6.1",
50231
50244
  description: "Harness Attention. Orchestrate Agents. Ship.",
50232
50245
  license: "PolyForm-Perimeter-1.0.0",
50233
50246
  type: "module",
@@ -50243,7 +50256,7 @@ var package_default = {
50243
50256
  "db:studio": "drizzle-kit studio"
50244
50257
  },
50245
50258
  dependencies: {
50246
- "@anthropic-ai/claude-agent-sdk": "0.2.117",
50259
+ "@anthropic-ai/claude-agent-sdk": "0.2.123",
50247
50260
  "@atlaskit/pragmatic-drag-and-drop": "^1.7.7",
50248
50261
  "@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.1.0",
50249
50262
  "@azurity/pure-nerd-font": "^3.0.5",
@@ -50768,6 +50781,267 @@ var downCommand = defineCommand({
50768
50781
  }
50769
50782
  });
50770
50783
 
50784
+ // cli/src/commands/expose.ts
50785
+ init_client();
50786
+ init_errors();
50787
+ import { execSync as execSync4, spawnSync as spawnSync4 } from "child_process";
50788
+ import { existsSync as existsSync7, mkdirSync as mkdirSync6, writeFileSync as writeFileSync5, chmodSync as chmodSync2, unlinkSync as unlinkSync3 } from "fs";
50789
+ import { homedir as homedir4, hostname, platform as platform2 } from "os";
50790
+ import { dirname as dirname5, join as join7 } from "path";
50791
+ var SYSTEMD_SERVICE_TEMPLATE = `[Unit]
50792
+ Description=Fulcrum Cloudflare Tunnel
50793
+ Documentation=https://github.com/knowsuchagency/fulcrum
50794
+ After=network-online.target
50795
+ Wants=network-online.target
50796
+
50797
+ [Service]
50798
+ Type=notify
50799
+ ExecStart=/usr/bin/env cloudflared --no-autoupdate tunnel --token __TUNNEL_TOKEN__ run
50800
+ Restart=on-failure
50801
+ RestartSec=5s
50802
+ TimeoutStopSec=20
50803
+
50804
+ [Install]
50805
+ WantedBy=default.target
50806
+ `;
50807
+ var LAUNCHD_PLIST_TEMPLATE = `<?xml version="1.0" encoding="UTF-8"?>
50808
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
50809
+ <plist version="1.0">
50810
+ <dict>
50811
+ <key>Label</key>
50812
+ <string>com.fulcrum.tunnel</string>
50813
+ <key>ProgramArguments</key>
50814
+ <array>
50815
+ <string>__CLOUDFLARED_PATH__</string>
50816
+ <string>--no-autoupdate</string>
50817
+ <string>tunnel</string>
50818
+ <string>--token</string>
50819
+ <string>__TUNNEL_TOKEN__</string>
50820
+ <string>run</string>
50821
+ </array>
50822
+ <key>RunAtLoad</key>
50823
+ <true/>
50824
+ <key>KeepAlive</key>
50825
+ <true/>
50826
+ <key>StandardErrorPath</key>
50827
+ <string>__LOG_PATH__</string>
50828
+ <key>StandardOutPath</key>
50829
+ <string>__LOG_PATH__</string>
50830
+ </dict>
50831
+ </plist>
50832
+ `;
50833
+ function isCloudflaredInstalled() {
50834
+ try {
50835
+ execSync4("which cloudflared", { stdio: "ignore" });
50836
+ return true;
50837
+ } catch {
50838
+ return false;
50839
+ }
50840
+ }
50841
+ function whichCloudflared() {
50842
+ return execSync4("which cloudflared", { encoding: "utf-8" }).trim();
50843
+ }
50844
+ var LINUX_SERVICE_NAME = "fulcrum-tunnel";
50845
+ var MACOS_LABEL = "com.fulcrum.tunnel";
50846
+ function linuxUnitPath() {
50847
+ return join7(homedir4(), ".config", "systemd", "user", `${LINUX_SERVICE_NAME}.service`);
50848
+ }
50849
+ function macosPlistPath() {
50850
+ return join7(homedir4(), "Library", "LaunchAgents", `${MACOS_LABEL}.plist`);
50851
+ }
50852
+ function macosLogPath() {
50853
+ return join7(homedir4(), "Library", "Logs", "fulcrum-tunnel.log");
50854
+ }
50855
+ function installService(tunnelToken) {
50856
+ if (platform2() === "darwin") {
50857
+ const cloudflaredPath = whichCloudflared();
50858
+ const plist = LAUNCHD_PLIST_TEMPLATE.replace("__CLOUDFLARED_PATH__", cloudflaredPath).replace("__TUNNEL_TOKEN__", tunnelToken).replace(/__LOG_PATH__/g, macosLogPath());
50859
+ const dest2 = macosPlistPath();
50860
+ mkdirSync6(dirname5(dest2), { recursive: true });
50861
+ writeFileSync5(dest2, plist, "utf-8");
50862
+ chmodSync2(dest2, 420);
50863
+ spawnSync4("launchctl", ["unload", dest2], { stdio: "ignore" });
50864
+ const load = spawnSync4("launchctl", ["load", dest2], { stdio: "inherit" });
50865
+ if (load.status !== 0) {
50866
+ throw new CliError("LAUNCHCTL_LOAD_FAILED", "launchctl load failed", ExitCodes.ERROR);
50867
+ }
50868
+ return { path: dest2, how: "launchd" };
50869
+ }
50870
+ const unit = SYSTEMD_SERVICE_TEMPLATE.replace("__TUNNEL_TOKEN__", tunnelToken);
50871
+ const dest = linuxUnitPath();
50872
+ mkdirSync6(dirname5(dest), { recursive: true });
50873
+ writeFileSync5(dest, unit, "utf-8");
50874
+ chmodSync2(dest, 420);
50875
+ for (const args of [
50876
+ ["--user", "daemon-reload"],
50877
+ ["--user", "enable", LINUX_SERVICE_NAME],
50878
+ ["--user", "restart", LINUX_SERVICE_NAME]
50879
+ ]) {
50880
+ const r3 = spawnSync4("systemctl", args, { stdio: "inherit" });
50881
+ if (r3.status !== 0) {
50882
+ throw new CliError("SYSTEMCTL_FAILED", `systemctl ${args.join(" ")} failed (exit ${r3.status})`, ExitCodes.ERROR);
50883
+ }
50884
+ }
50885
+ return { path: dest, how: "systemd" };
50886
+ }
50887
+ function uninstallService() {
50888
+ if (platform2() === "darwin") {
50889
+ const dest2 = macosPlistPath();
50890
+ if (!existsSync7(dest2))
50891
+ return { stopped: false, removed: false };
50892
+ spawnSync4("launchctl", ["unload", dest2], { stdio: "ignore" });
50893
+ unlinkSync3(dest2);
50894
+ return { stopped: true, removed: true };
50895
+ }
50896
+ const dest = linuxUnitPath();
50897
+ if (!existsSync7(dest))
50898
+ return { stopped: false, removed: false };
50899
+ spawnSync4("systemctl", ["--user", "stop", LINUX_SERVICE_NAME], { stdio: "ignore" });
50900
+ spawnSync4("systemctl", ["--user", "disable", LINUX_SERVICE_NAME], { stdio: "ignore" });
50901
+ unlinkSync3(dest);
50902
+ spawnSync4("systemctl", ["--user", "daemon-reload"], { stdio: "ignore" });
50903
+ return { stopped: true, removed: true };
50904
+ }
50905
+ function serviceStatus() {
50906
+ if (platform2() === "darwin") {
50907
+ const r4 = spawnSync4("launchctl", ["list", MACOS_LABEL], { encoding: "utf-8" });
50908
+ if (r4.status === 0) {
50909
+ const match = r4.stdout.match(/"PID"\s*=\s*(\d+);/);
50910
+ return match ? `running (pid ${match[1]})` : "loaded";
50911
+ }
50912
+ return "not loaded";
50913
+ }
50914
+ const r3 = spawnSync4("systemctl", ["--user", "is-active", LINUX_SERVICE_NAME], { encoding: "utf-8" });
50915
+ return (r3.stdout || r3.stderr).trim() || "unknown";
50916
+ }
50917
+ async function handleExpose(args) {
50918
+ const { positional, flags } = args;
50919
+ const subdomain = positional[0]?.trim() || hostname().split(".")[0];
50920
+ const domain = flags.domain?.trim();
50921
+ if (!domain) {
50922
+ throw new CliError("MISSING_DOMAIN", "Required: --domain <zone> (e.g. --domain fulcrum.example.com)", ExitCodes.INVALID_ARGS);
50923
+ }
50924
+ const skipService = flags["no-service"] === "true";
50925
+ const autoYes = flags.yes === "true" || flags.y === "true";
50926
+ if (!skipService && !isCloudflaredInstalled()) {
50927
+ console.error("cloudflared is not installed.");
50928
+ console.error("Install it from https://github.com/cloudflare/cloudflared/releases or via your package manager.");
50929
+ if (!autoYes) {
50930
+ const proceed = await confirm("Continue without installing the system service? (the tunnel will be created but not running)");
50931
+ if (!proceed)
50932
+ throw new CliError("CLOUDFLARED_MISSING", "cloudflared required to start tunnel", ExitCodes.ERROR);
50933
+ }
50934
+ }
50935
+ const client = new FulcrumClient(flags.url, flags.port);
50936
+ const result = await client.createServerExpose({ subdomain, domain });
50937
+ console.error(`Tunnel ready: ${result.publicDomain} (${result.reusedExisting ? "reused" : "created"})`);
50938
+ console.error(`Tunnel ID: ${result.tunnelId}`);
50939
+ let serviceInfo = null;
50940
+ if (skipService) {
50941
+ console.error("Skipping system service installation (--no-service).");
50942
+ } else if (!result.tunnelToken) {
50943
+ console.error("Existing tunnel reused; the tunnel token was not returned by Cloudflare.");
50944
+ console.error("Keep the existing system service running, or rotate the token via the Cloudflare dashboard.");
50945
+ } else if (!isCloudflaredInstalled()) {
50946
+ console.error("Skipping service install: cloudflared not on PATH.");
50947
+ } else {
50948
+ const installed = installService(result.tunnelToken);
50949
+ serviceInfo = { path: installed.path, how: installed.how };
50950
+ console.error(`Installed and started ${installed.how} service: ${installed.path}`);
50951
+ }
50952
+ console.error("");
50953
+ console.error("Next steps:");
50954
+ console.error(` 1. Visit ${result.accessSetupUrl} and create a Cloudflare Access policy for *.${domain}.`);
50955
+ console.error(` 2. Open https://${result.publicDomain} in your browser.`);
50956
+ if (isJsonOutput()) {
50957
+ output({
50958
+ publicDomain: result.publicDomain,
50959
+ tunnelId: result.tunnelId,
50960
+ reusedExisting: result.reusedExisting,
50961
+ service: serviceInfo
50962
+ });
50963
+ }
50964
+ }
50965
+ async function handleStatus(flags) {
50966
+ const client = new FulcrumClient(flags.url, flags.port);
50967
+ const status = await client.getServerExpose();
50968
+ const svc = serviceStatus();
50969
+ if (isJsonOutput()) {
50970
+ output({ ...status, serviceStatus: svc });
50971
+ return;
50972
+ }
50973
+ console.log(`Public domain: ${status.publicDomain ?? "(not set)"}`);
50974
+ console.log(`Tailscale hostname: ${status.tailscaleHostname ?? "(not set)"}`);
50975
+ console.log(`Detected tailnet: ${status.detectedTailscaleHostname ?? "(not detected)"}`);
50976
+ console.log(`Cloudflare ready: ${status.tunnelAvailable ? "yes" : "no"}`);
50977
+ console.log(`Tunnel ID: ${status.tunnelId ?? "(none)"}`);
50978
+ console.log(`Tunnel status: ${status.tunnelStatus ?? "(unknown)"}`);
50979
+ console.log(`Local service: ${svc}`);
50980
+ }
50981
+ async function handleDown(flags) {
50982
+ const removeTunnel = flags["remove-tunnel"] === "true";
50983
+ const autoYes = flags.yes === "true" || flags.y === "true";
50984
+ if (removeTunnel && !autoYes) {
50985
+ const ok = await confirm("Permanently delete the Cloudflare Tunnel from your account?");
50986
+ if (!ok) {
50987
+ console.error("Aborted.");
50988
+ return;
50989
+ }
50990
+ }
50991
+ const svc = uninstallService();
50992
+ if (svc.removed) {
50993
+ console.error("Stopped and removed the local cloudflared service.");
50994
+ }
50995
+ const client = new FulcrumClient(flags.url, flags.port);
50996
+ const result = await client.deleteServerExpose(removeTunnel);
50997
+ if (result.tunnelDeleted)
50998
+ console.error("Deleted the Cloudflare Tunnel from your account.");
50999
+ console.error("Cleared server.publicDomain.");
51000
+ if (isJsonOutput()) {
51001
+ output({ success: true, serviceRemoved: svc.removed, tunnelDeleted: !!result.tunnelDeleted });
51002
+ }
51003
+ }
51004
+ var exposeCommand = defineCommand({
51005
+ meta: {
51006
+ name: "expose",
51007
+ description: "Expose this Fulcrum server publicly via a Cloudflare Tunnel"
51008
+ },
51009
+ args: {
51010
+ ...globalArgs,
51011
+ domain: { type: "string", description: "Cloudflare zone for the public hostname (e.g. fulcrum.example.com)" },
51012
+ "no-service": { type: "boolean", description: "Create the tunnel but do not install/start the system service" },
51013
+ "remove-tunnel": { type: "boolean", description: "For `down`: also delete the tunnel from Cloudflare" },
51014
+ yes: { type: "boolean", alias: "y", description: "Auto-answer yes to prompts" }
51015
+ },
51016
+ subCommands: {
51017
+ status: defineCommand({
51018
+ meta: { name: "status", description: "Show current expose state and service status" },
51019
+ args: { ...globalArgs },
51020
+ async run({ args }) {
51021
+ setupJsonOutput(args);
51022
+ await handleStatus(toFlags(args));
51023
+ }
51024
+ }),
51025
+ down: defineCommand({
51026
+ meta: { name: "down", description: "Stop the local tunnel service and clear publicDomain" },
51027
+ args: {
51028
+ ...globalArgs,
51029
+ "remove-tunnel": { type: "boolean", description: "Also delete the tunnel from Cloudflare" },
51030
+ yes: { type: "boolean", alias: "y", description: "Auto-answer yes to prompts" }
51031
+ },
51032
+ async run({ args }) {
51033
+ setupJsonOutput(args);
51034
+ await handleDown(toFlags(args));
51035
+ }
51036
+ })
51037
+ },
51038
+ async run({ args, rawArgs }) {
51039
+ setupJsonOutput(args);
51040
+ const positional = args._ ?? rawArgs.filter((a2) => !a2.startsWith("-"));
51041
+ await handleExpose({ positional, flags: toFlags(args) });
51042
+ }
51043
+ });
51044
+
50771
51045
  // cli/src/commands/status.ts
50772
51046
  init_server();
50773
51047
  async function handleStatusCommand(flags) {
@@ -51071,6 +51345,7 @@ var main = defineCommand({
51071
51345
  notify: notifyCommand,
51072
51346
  up: upCommand,
51073
51347
  down: downCommand,
51348
+ expose: exposeCommand,
51074
51349
  status: statusCommand,
51075
51350
  doctor: doctorCommand,
51076
51351
  dev: devCommand,