@vibedeckx/linux-x64 0.2.12 → 0.2.13

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 (2) hide show
  1. package/dist/bin.js +194 -4
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -238010,6 +238010,20 @@ var import_fastify_plugin23 = __toESM(require_plugin2(), 1);
238010
238010
  import { randomBytes as randomBytes2, createHash as createHash3, verify as cryptoVerify } from "crypto";
238011
238011
  var MACHINE_HANDSHAKE_TIMEOUT_MS = 5e3;
238012
238012
  var routes22 = async (fastify2) => {
238013
+ fastify2.get("/api/reverse-connect/identity", async (req, reply) => {
238014
+ const token = req.headers["x-vibedeckx-connect-token"];
238015
+ if (typeof token !== "string" || token.length === 0) {
238016
+ return reply.code(401).send({ error: "Connect token required" });
238017
+ }
238018
+ const server = await fastify2.storage.remoteServers.getByToken(token);
238019
+ if (!server) {
238020
+ return reply.code(401).send({ error: "Invalid token" });
238021
+ }
238022
+ if (server.connection_mode !== "inbound") {
238023
+ return reply.code(403).send({ error: "Server is not configured for inbound connections" });
238024
+ }
238025
+ return { serverId: server.id, name: server.name };
238026
+ });
238013
238027
  fastify2.after(() => {
238014
238028
  fastify2.get(
238015
238029
  "/api/reverse-connect",
@@ -241163,6 +241177,10 @@ var createServer = async (opts) => {
241163
241177
  if (!API_KEY) return done();
241164
241178
  if (!req.url.startsWith("/api/")) return done();
241165
241179
  if (req.method === "OPTIONS") return done();
241180
+ const pathname = req.url.split("?")[0];
241181
+ if (pathname === "/api/config" || pathname === "/api/reverse-connect/identity") {
241182
+ return done();
241183
+ }
241166
241184
  const providedKey = req.headers["x-vibedeckx-api-key"] || req.query?.apiKey;
241167
241185
  if (!providedKey && authEnabled) {
241168
241186
  return done();
@@ -241204,7 +241222,12 @@ var createServer = async (opts) => {
241204
241222
  server.get("/api/config", async () => ({
241205
241223
  authEnabled,
241206
241224
  clerkPublishableKey: authEnabled ? process.env.CLERK_PUBLISHABLE_KEY : void 0,
241207
- localProjectsEnabled: !noLocalProjects
241225
+ localProjectsEnabled: !noLocalProjects,
241226
+ // Capability flag for the reverse-connect identity preflight. Workers
241227
+ // check this before calling /api/reverse-connect/identity so an auth
241228
+ // middleware 401 from an older hub (which lacks the endpoint AND its
241229
+ // exemptions) is never confused with a genuinely rejected token.
241230
+ reverseConnectIdentity: true
241208
241231
  }));
241209
241232
  server.register(shared_services_default, { storage: opts.storage, authEnabled });
241210
241233
  server.register(import_websocket2.default);
@@ -241215,6 +241238,7 @@ var createServer = async (opts) => {
241215
241238
  server.decorateRequest("auth", null);
241216
241239
  server.addHook("preHandler", async (req, reply) => {
241217
241240
  if (req.headers.upgrade?.toLowerCase() === "websocket") return;
241241
+ if (req.url.split("?")[0] === "/api/reverse-connect/identity") return;
241218
241242
  const headers = new Headers();
241219
241243
  for (const [key, value] of Object.entries(req.headers)) {
241220
241244
  if (value) headers.set(key, Array.isArray(value) ? value.join(",") : String(value));
@@ -241760,6 +241784,69 @@ import { spawn as spawn5 } from "node:child_process";
241760
241784
  import { randomUUID as randomUUID22 } from "node:crypto";
241761
241785
  import fs7 from "node:fs";
241762
241786
  import path19 from "node:path";
241787
+
241788
+ // src/update-check.ts
241789
+ var SEMVER_RE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*)?$/;
241790
+ async function fetchLatestPublishedVersion(pkg = "vibedeckx", fetchImpl = fetch) {
241791
+ try {
241792
+ const response = await fetchImpl(
241793
+ `https://registry.npmjs.org/${pkg}/latest`,
241794
+ { signal: AbortSignal.timeout(4e3) }
241795
+ );
241796
+ if (!response.ok) return void 0;
241797
+ const body = await response.json();
241798
+ return typeof body.version === "string" && parseSemver(body.version) ? body.version : void 0;
241799
+ } catch {
241800
+ return void 0;
241801
+ }
241802
+ }
241803
+ function parseSemver(version2) {
241804
+ const match2 = SEMVER_RE.exec(version2);
241805
+ if (!match2) return void 0;
241806
+ return {
241807
+ core: [match2[1], match2[2], match2[3]],
241808
+ prerelease: match2[4]?.split(".")
241809
+ };
241810
+ }
241811
+ function compareNumericStrings(a, b2) {
241812
+ if (a.length !== b2.length) return a.length - b2.length;
241813
+ return a < b2 ? -1 : a > b2 ? 1 : 0;
241814
+ }
241815
+ function comparePrereleaseIdentifiers(a, b2) {
241816
+ const aNumeric = /^\d+$/.test(a);
241817
+ const bNumeric = /^\d+$/.test(b2);
241818
+ if (aNumeric && bNumeric) return compareNumericStrings(a, b2);
241819
+ if (aNumeric !== bNumeric) return aNumeric ? -1 : 1;
241820
+ return a < b2 ? -1 : a > b2 ? 1 : 0;
241821
+ }
241822
+ function compareSemver(a, b2) {
241823
+ for (let i = 0; i < 3; i += 1) {
241824
+ const compared = compareNumericStrings(a.core[i], b2.core[i]);
241825
+ if (compared !== 0) return compared;
241826
+ }
241827
+ if (!a.prerelease && !b2.prerelease) return 0;
241828
+ if (!a.prerelease) return 1;
241829
+ if (!b2.prerelease) return -1;
241830
+ const length = Math.max(a.prerelease.length, b2.prerelease.length);
241831
+ for (let i = 0; i < length; i += 1) {
241832
+ const aId = a.prerelease[i];
241833
+ const bId = b2.prerelease[i];
241834
+ if (aId === void 0) return -1;
241835
+ if (bId === void 0) return 1;
241836
+ const compared = comparePrereleaseIdentifiers(aId, bId);
241837
+ if (compared !== 0) return compared;
241838
+ }
241839
+ return 0;
241840
+ }
241841
+ function compareUpdateStatus(current, latest) {
241842
+ if (latest === void 0) return "unknown";
241843
+ const currentParsed = parseSemver(current);
241844
+ const latestParsed = parseSemver(latest);
241845
+ if (!currentParsed || !latestParsed) return "unknown";
241846
+ return compareSemver(latestParsed, currentParsed) > 0 ? "update-available" : "up-to-date";
241847
+ }
241848
+
241849
+ // src/connect-daemon.ts
241763
241850
  var CONNECT_DAEMON_CHILD_ENV = "VIBEDECKX_INTERNAL_CONNECT_DAEMON";
241764
241851
  var CONNECT_DAEMON_TOKEN_ENV = "VIBEDECKX_INTERNAL_CONNECT_TOKEN";
241765
241852
  function buildDaemonChildArgs(argv) {
@@ -242307,18 +242394,31 @@ function inspectDaemonState(dataDir) {
242307
242394
  const actualTicks = readLinuxProcessStartTicks(parsed.pid);
242308
242395
  return actualTicks === parsed.processStartTicks ? { kind: "running", state: parsed } : { kind: "stale", state: parsed };
242309
242396
  }
242310
- function describeConnectDaemon(dataDir) {
242397
+ function describeUpdateStatus(current, latest) {
242398
+ switch (compareUpdateStatus(current, latest)) {
242399
+ case "update-available":
242400
+ return `(update available: ${latest})`;
242401
+ case "up-to-date":
242402
+ return "(up to date)";
242403
+ case "unknown":
242404
+ return "(update check failed)";
242405
+ }
242406
+ }
242407
+ async function describeConnectDaemon(dataDir, fetchLatestVersion = fetchLatestPublishedVersion) {
242311
242408
  const inspection = inspectDaemonState(dataDir);
242312
242409
  switch (inspection.kind) {
242313
- case "running":
242410
+ case "running": {
242411
+ const latest = await fetchLatestVersion();
242314
242412
  return {
242315
242413
  exitCode: 0,
242316
242414
  message: [
242317
242415
  `Running (PID ${inspection.state.pid}, since ${inspection.state.startedAt})`,
242416
+ `Version: ${inspection.state.version} ${describeUpdateStatus(inspection.state.version, latest)}`,
242318
242417
  `Target: ${inspection.state.connectTo}`,
242319
242418
  `Logs: ${path19.join(dataDir, "logs", "vibedeckx.log")}`
242320
242419
  ].join("\n")
242321
242420
  };
242421
+ }
242322
242422
  case "missing":
242323
242423
  return { exitCode: 1, message: "Vibedeckx connect is not running" };
242324
242424
  case "stale":
@@ -242541,6 +242641,78 @@ function removeVerifiedStaleStateUnlocked(dataDir) {
242541
242641
  }
242542
242642
  }
242543
242643
 
242644
+ // src/connect-preflight.ts
242645
+ var CONNECT_IDENTITY_HEADER = "x-vibedeckx-connect-token";
242646
+ var PINNED_IDENTITY_SETTING_PREFIX = "reverse_pinned_identity:";
242647
+ function canonicalizeHubUrl(raw) {
242648
+ let url2;
242649
+ try {
242650
+ url2 = new URL(raw);
242651
+ } catch {
242652
+ throw new Error(`Invalid --connect-to URL: ${raw}`);
242653
+ }
242654
+ if (url2.protocol !== "http:" && url2.protocol !== "https:") {
242655
+ throw new Error(`--connect-to must be an http(s) URL, got: ${raw}`);
242656
+ }
242657
+ const pathname = url2.pathname.replace(/\/+$/, "");
242658
+ return `${url2.origin}${pathname}`;
242659
+ }
242660
+ function parsePin(raw) {
242661
+ try {
242662
+ const parsed = JSON.parse(raw);
242663
+ if (parsed && typeof parsed === "object") return parsed;
242664
+ } catch {
242665
+ }
242666
+ return {};
242667
+ }
242668
+ async function preflightIdentityCheck(opts) {
242669
+ const fetchImpl = opts.fetchImpl ?? fetch;
242670
+ const hubUrl = canonicalizeHubUrl(opts.connectTo);
242671
+ let config2;
242672
+ try {
242673
+ const res2 = await fetchImpl(`${hubUrl}/api/config`);
242674
+ if (res2.ok) config2 = await res2.json();
242675
+ } catch {
242676
+ }
242677
+ if (config2?.reverseConnectIdentity !== true) return { checked: false };
242678
+ const res = await fetchImpl(`${hubUrl}/api/reverse-connect/identity`, {
242679
+ headers: { [CONNECT_IDENTITY_HEADER]: opts.token }
242680
+ });
242681
+ if (res.status === 401) {
242682
+ throw new Error("Connect token was rejected by the server (invalid or revoked)");
242683
+ }
242684
+ if (res.status === 403) {
242685
+ throw new Error("This token's remote is not configured for inbound connections");
242686
+ }
242687
+ if (!res.ok) {
242688
+ throw new Error(`Identity preflight failed with status ${res.status}`);
242689
+ }
242690
+ const body = await res.json();
242691
+ if (!body || typeof body.serverId !== "string" || body.serverId.length === 0) {
242692
+ throw new Error("Malformed identity response from server");
242693
+ }
242694
+ const identity = { serverId: body.serverId, name: body.name ?? "" };
242695
+ const key = `${PINNED_IDENTITY_SETTING_PREFIX}${hubUrl}`;
242696
+ const desired = JSON.stringify(identity);
242697
+ if (opts.force) {
242698
+ await opts.settings.set(key, desired);
242699
+ return { checked: true, identity };
242700
+ }
242701
+ const persisted = await opts.settings.getOrCreate(key, () => desired);
242702
+ const pinned = parsePin(persisted);
242703
+ if (pinned.serverId && pinned.serverId !== identity.serverId) {
242704
+ const pinnedLabel = pinned.name || pinned.serverId;
242705
+ const tokenLabel = identity.name || identity.serverId;
242706
+ throw new Error(
242707
+ `This machine previously served remote "${pinnedLabel}" on ${hubUrl}, but the provided token belongs to "${tokenLabel}". If this is intentional, re-run with --force to re-pin.`
242708
+ );
242709
+ }
242710
+ if (!pinned.serverId || pinned.name !== identity.name) {
242711
+ await opts.settings.set(key, desired);
242712
+ }
242713
+ return { checked: true, identity };
242714
+ }
242715
+
242544
242716
  // ../../node_modules/.pnpm/open@10.2.0/node_modules/open/index.js
242545
242717
  import process8 from "node:process";
242546
242718
  import { Buffer as Buffer2 } from "node:buffer";
@@ -243237,6 +243409,11 @@ var connectCommand = buildCommand({
243237
243409
  brief: "Run in the background after initialization (Linux only)",
243238
243410
  optional: true
243239
243411
  },
243412
+ force: {
243413
+ kind: "boolean",
243414
+ brief: "Re-pin this machine to the token's remote, overriding the pinned-identity mismatch guard",
243415
+ optional: true
243416
+ },
243240
243417
  port: {
243241
243418
  kind: "parsed",
243242
243419
  parse: parseInt,
@@ -243336,6 +243513,17 @@ var connectCommand = buildCommand({
243336
243513
  console.log("Starting vibedeckx in reverse-connect mode...");
243337
243514
  const dbPath = path21.join(dataDir, "data.sqlite");
243338
243515
  storage = await createSqliteStorage(dbPath);
243516
+ const preflight = await preflightIdentityCheck({
243517
+ connectTo: flags["connect-to"],
243518
+ token,
243519
+ settings: storage.settings,
243520
+ force: flags.force ?? false
243521
+ });
243522
+ if (preflight.checked && preflight.identity) {
243523
+ console.log(
243524
+ `Remote identity verified: ${preflight.identity.name || preflight.identity.serverId}`
243525
+ );
243526
+ }
243339
243527
  server = await createServer({
243340
243528
  storage,
243341
243529
  acceptRemote: true,
@@ -243408,7 +243596,9 @@ var connectStatusCommand = buildCommand({
243408
243596
  parameters: { flags: connectManagementFlags },
243409
243597
  func: async (flags) => {
243410
243598
  assertConnectDaemonPlatform();
243411
- const result = describeConnectDaemon(flags["data-dir"] ?? VIBEDECKX_HOME);
243599
+ const result = await describeConnectDaemon(
243600
+ flags["data-dir"] ?? VIBEDECKX_HOME
243601
+ );
243412
243602
  return reportDaemonCommandResult(result);
243413
243603
  },
243414
243604
  docs: { brief: "Show the connect daemon status" }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibedeckx/linux-x64",
3
- "version": "0.2.12",
3
+ "version": "0.2.13",
4
4
  "description": "Vibedeckx platform binaries for Linux x64",
5
5
  "os": [
6
6
  "linux"