mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-28 20:22:45 +00:00
Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0820ee14f1 | ||
|
|
49ebc26cdb | ||
|
|
6bad956c8f | ||
|
|
233b309fd9 | ||
|
|
ae4123be10 | ||
|
|
6b0ff42748 | ||
|
|
627dc21736 |
@@ -0,0 +1,41 @@
|
||||
name: Fix Nix hash
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'package.json'
|
||||
- 'package-lock.json'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
fix-nix-hash:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.actor == 'dependabot[bot]'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
cache: 'npm'
|
||||
|
||||
- uses: cachix/install-nix-action@v31
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
|
||||
- name: Fix lockfile and update hash
|
||||
run: ./scripts/update-nix.sh
|
||||
|
||||
- name: Commit changes
|
||||
run: |
|
||||
git diff --quiet package-lock.json nix/package.nix && exit 0
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git add package-lock.json nix/package.nix
|
||||
git commit -m "fix: update lockfile signatures and Nix hash"
|
||||
git push
|
||||
@@ -0,0 +1,59 @@
|
||||
name: Nix Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'nix/**'
|
||||
- 'flake.nix'
|
||||
- 'flake.lock'
|
||||
- 'package.json'
|
||||
- 'package-lock.json'
|
||||
- 'packages/highlight/**'
|
||||
- 'packages/server/**'
|
||||
- 'packages/relay/**'
|
||||
- 'packages/cli/**'
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'nix/**'
|
||||
- 'flake.nix'
|
||||
- 'flake.lock'
|
||||
- 'package.json'
|
||||
- 'package-lock.json'
|
||||
- 'packages/highlight/**'
|
||||
- 'packages/server/**'
|
||||
- 'packages/relay/**'
|
||||
- 'packages/cli/**'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: cachix/install-nix-action@v31
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
|
||||
- name: Build Nix package
|
||||
run: nix build .#default -o result
|
||||
|
||||
- name: Verify lockfile is complete
|
||||
# npm silently omits resolved/integrity fields in workspace monorepos.
|
||||
# Nix needs them for offline builds. See https://github.com/npm/cli/issues/4460
|
||||
run: |
|
||||
node scripts/fix-lockfile.mjs package-lock.json
|
||||
git diff --exit-code package-lock.json || {
|
||||
echo "ERROR: package-lock.json has missing resolved/integrity fields."
|
||||
echo "This is a known npm bug: https://github.com/npm/cli/issues/4460"
|
||||
echo "Run 'node scripts/fix-lockfile.mjs' and commit the result."
|
||||
exit 1
|
||||
}
|
||||
|
||||
- name: Check npmDepsHash is up to date
|
||||
run: ./scripts/update-nix.sh --check
|
||||
Generated
+27
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1772963539,
|
||||
"narHash": "sha256-9jVDGZnvCckTGdYT53d/EfznygLskyLQXYwJLKMPsZs=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "9dcb002ca1690658be4a04645215baea8b95f31d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
{
|
||||
description = "Paseo - self-hosted daemon for AI coding agents";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
};
|
||||
|
||||
outputs =
|
||||
{
|
||||
self,
|
||||
nixpkgs,
|
||||
}:
|
||||
let
|
||||
supportedSystems = [
|
||||
"x86_64-linux"
|
||||
"aarch64-linux"
|
||||
"x86_64-darwin"
|
||||
"aarch64-darwin"
|
||||
];
|
||||
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
|
||||
pkgsFor = system: import nixpkgs { inherit system; };
|
||||
in
|
||||
{
|
||||
packages = forAllSystems (
|
||||
system:
|
||||
let
|
||||
pkgs = pkgsFor system;
|
||||
paseo = pkgs.callPackage ./nix/package.nix { };
|
||||
in
|
||||
{
|
||||
default = paseo;
|
||||
paseo = paseo;
|
||||
}
|
||||
);
|
||||
|
||||
nixosModules.default = self.nixosModules.paseo;
|
||||
nixosModules.paseo =
|
||||
{ pkgs, lib, ... }:
|
||||
{
|
||||
imports = [ ./nix/module.nix ];
|
||||
services.paseo.package = lib.mkDefault self.packages.${pkgs.stdenv.hostPlatform.system}.default;
|
||||
};
|
||||
|
||||
devShells = forAllSystems (
|
||||
system:
|
||||
let
|
||||
pkgs = pkgsFor system;
|
||||
in
|
||||
{
|
||||
default = pkgs.mkShell {
|
||||
packages = [
|
||||
pkgs.nodejs_22
|
||||
pkgs.python3
|
||||
];
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
+172
@@ -0,0 +1,172 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.services.paseo;
|
||||
in
|
||||
{
|
||||
options.services.paseo = {
|
||||
enable = lib.mkEnableOption "Paseo, a self-hosted daemon for AI coding agents";
|
||||
|
||||
package = lib.mkPackageOption pkgs "paseo" { };
|
||||
|
||||
user = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "paseo";
|
||||
description = "User account under which Paseo runs.";
|
||||
};
|
||||
|
||||
group = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "paseo";
|
||||
description = "Group under which Paseo runs.";
|
||||
};
|
||||
|
||||
dataDir = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default =
|
||||
if cfg.user == "paseo"
|
||||
then "/var/lib/paseo"
|
||||
else "/home/${cfg.user}/.paseo";
|
||||
defaultText = lib.literalExpression ''
|
||||
if cfg.user == "paseo"
|
||||
then "/var/lib/paseo"
|
||||
else "/home/''${cfg.user}/.paseo"
|
||||
'';
|
||||
description = "Directory for Paseo state (PASEO_HOME). Stores agent data, config, and logs.";
|
||||
};
|
||||
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 6767;
|
||||
description = "Port for the Paseo daemon to listen on.";
|
||||
};
|
||||
|
||||
listenAddress = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "127.0.0.1";
|
||||
description = "Address for the Paseo daemon to bind to.";
|
||||
};
|
||||
|
||||
openFirewall = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Whether to open the firewall for the Paseo daemon port.";
|
||||
};
|
||||
|
||||
allowedHosts = lib.mkOption {
|
||||
type = lib.types.either (lib.types.enum [ true ]) (lib.types.listOf lib.types.str);
|
||||
default = [ ];
|
||||
example = [ ".example.com" "myhost.local" ];
|
||||
description = ''
|
||||
Hosts allowed to connect to the Paseo daemon (DNS rebinding protection).
|
||||
Localhost and IP addresses are always allowed by default.
|
||||
|
||||
Use a leading dot to match a domain and all its subdomains
|
||||
(e.g. `".example.com"` matches `example.com` and `foo.example.com`).
|
||||
|
||||
Set to `true` to allow any host (not recommended).
|
||||
'';
|
||||
};
|
||||
|
||||
relay = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Whether to enable the relay connection for remote access via app.paseo.sh.";
|
||||
};
|
||||
};
|
||||
|
||||
inheritUserEnvironment = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = cfg.user != "paseo";
|
||||
defaultText = lib.literalExpression ''cfg.user != "paseo"'';
|
||||
description = ''
|
||||
Whether to include the user's profile PATH in the service environment.
|
||||
|
||||
When Paseo runs as a real user (not the default system user), AI agents
|
||||
need access to the user's tools (git, ssh, etc.). This adds the user's
|
||||
NixOS profile and system paths so agents can use them without manually
|
||||
setting PATH.
|
||||
|
||||
Enabled by default when `user` is set to a non-default value.
|
||||
'';
|
||||
};
|
||||
|
||||
environment = lib.mkOption {
|
||||
type = lib.types.attrsOf lib.types.str;
|
||||
default = { };
|
||||
example = lib.literalExpression ''
|
||||
{
|
||||
PASEO_RELAY_ENDPOINT = "relay.paseo.sh:443";
|
||||
}
|
||||
'';
|
||||
description = "Extra environment variables for the Paseo daemon.";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
users.users.${cfg.user} = lib.mkIf (cfg.user == "paseo") {
|
||||
isSystemUser = true;
|
||||
group = cfg.group;
|
||||
home = cfg.dataDir;
|
||||
};
|
||||
|
||||
users.groups.${cfg.group} = lib.mkIf (cfg.group == "paseo") { };
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d ${cfg.dataDir} 0700 ${cfg.user} ${cfg.group} - -"
|
||||
];
|
||||
|
||||
systemd.services.paseo = {
|
||||
description = "Paseo - self-hosted daemon for AI coding agents";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
environment = {
|
||||
NODE_ENV = "production";
|
||||
PASEO_HOME = cfg.dataDir;
|
||||
PASEO_LISTEN = "${cfg.listenAddress}:${toString cfg.port}";
|
||||
} // lib.optionalAttrs cfg.inheritUserEnvironment {
|
||||
# mkForce overrides the default PATH from NixOS's systemd module (which
|
||||
# only includes store paths for coreutils/grep/sed/systemd). Our PATH
|
||||
# includes /run/current-system/sw/bin which is a superset of those.
|
||||
PATH = lib.mkForce (lib.concatStringsSep ":" [
|
||||
"/etc/profiles/per-user/${cfg.user}/bin"
|
||||
"/run/current-system/sw/bin"
|
||||
"/run/wrappers/bin"
|
||||
"/nix/var/nix/profiles/default/bin"
|
||||
]);
|
||||
} // lib.optionalAttrs (cfg.allowedHosts == true) {
|
||||
PASEO_ALLOWED_HOSTS = "true";
|
||||
} // lib.optionalAttrs (lib.isList cfg.allowedHosts && cfg.allowedHosts != [ ]) {
|
||||
PASEO_ALLOWED_HOSTS = lib.concatStringsSep "," cfg.allowedHosts;
|
||||
} // cfg.environment;
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
|
||||
ExecStart =
|
||||
"${cfg.package}/bin/paseo-server"
|
||||
+ lib.optionalString (!cfg.relay.enable) " --no-relay";
|
||||
|
||||
Restart = "on-failure";
|
||||
RestartSec = 5;
|
||||
|
||||
# Graceful shutdown (server handles SIGTERM with a 10s timeout)
|
||||
KillSignal = "SIGTERM";
|
||||
TimeoutStopSec = 15;
|
||||
};
|
||||
};
|
||||
|
||||
environment.systemPackages = [ cfg.package ];
|
||||
|
||||
networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ cfg.port ];
|
||||
};
|
||||
}
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
{
|
||||
lib,
|
||||
stdenv,
|
||||
buildNpmPackage,
|
||||
nodejs_22,
|
||||
python3,
|
||||
makeWrapper,
|
||||
# node-pty needs libuv headers on Linux
|
||||
libuv,
|
||||
}:
|
||||
|
||||
buildNpmPackage rec {
|
||||
pname = "paseo";
|
||||
version = (builtins.fromJSON (builtins.readFile ../package.json)).version;
|
||||
|
||||
src = lib.cleanSourceWith {
|
||||
src = ./..;
|
||||
filter = path: type:
|
||||
let
|
||||
baseName = builtins.baseNameOf path;
|
||||
relPath = lib.removePrefix (toString ./..) path;
|
||||
in
|
||||
# Exclude non-daemon workspace contents (keep package.json for workspace resolution)
|
||||
!(lib.hasPrefix "/packages/app/src" relPath)
|
||||
&& !(lib.hasPrefix "/packages/app/assets" relPath)
|
||||
&& !(lib.hasPrefix "/packages/app/android" relPath)
|
||||
&& !(lib.hasPrefix "/packages/app/ios" relPath)
|
||||
&& !(lib.hasPrefix "/packages/website/src" relPath)
|
||||
&& !(lib.hasPrefix "/packages/website/public" relPath)
|
||||
&& !(lib.hasPrefix "/packages/desktop/src" relPath)
|
||||
&& !(lib.hasPrefix "/packages/desktop/src-tauri" relPath)
|
||||
# Exclude test fixtures and debug files
|
||||
&& !(lib.hasSuffix ".test.ts" baseName)
|
||||
&& !(lib.hasSuffix ".e2e.test.ts" baseName)
|
||||
&& baseName != "node_modules"
|
||||
&& baseName != ".git"
|
||||
&& baseName != ".paseo"
|
||||
&& baseName != ".DS_Store";
|
||||
};
|
||||
|
||||
nodejs = nodejs_22;
|
||||
|
||||
# To update: run `nix build` with lib.fakeHash, copy the `got:` hash.
|
||||
# CI auto-updates this when package-lock.json changes (see .github/workflows/).
|
||||
npmDepsHash = "sha256-qRknrMt2VBCCRGGedyTrPEBs3u+Z8lmDZuuvX7mE2Cg=";
|
||||
|
||||
# Prevent onnxruntime-node's install script from running during automatic
|
||||
# npm rebuild (it tries to download from api.nuget.org, which fails in the sandbox).
|
||||
# We manually rebuild only node-pty in buildPhase.
|
||||
npmRebuildFlags = [ "--ignore-scripts" ];
|
||||
|
||||
nativeBuildInputs = [
|
||||
python3 # for node-gyp (node-pty compilation)
|
||||
makeWrapper
|
||||
];
|
||||
|
||||
buildInputs = lib.optionals stdenv.hostPlatform.isLinux [
|
||||
libuv
|
||||
];
|
||||
|
||||
# Don't use the default npm build hook — we need a custom build sequence
|
||||
dontNpmBuild = true;
|
||||
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
|
||||
# Rebuild only node-pty (native addon for terminal emulation).
|
||||
# Speech-related native modules (sherpa-onnx, onnxruntime-node) are
|
||||
# intentionally left unbuilt — they're lazily loaded and gracefully
|
||||
# degrade when unavailable.
|
||||
npm rebuild node-pty
|
||||
|
||||
# Build all daemon packages in dependency order (defined in package.json)
|
||||
npm run build:daemon
|
||||
|
||||
runHook postBuild
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
|
||||
mkdir -p $out/lib/paseo
|
||||
|
||||
# Copy root package metadata
|
||||
cp package.json $out/lib/paseo/
|
||||
|
||||
# Copy node_modules (preserving workspace symlinks)
|
||||
cp -a node_modules $out/lib/paseo/
|
||||
|
||||
# Auto-detect which @getpaseo/* packages were built by build:daemon
|
||||
# (they'll have a dist/ directory). Copy those and remove the rest.
|
||||
for link in $out/lib/paseo/node_modules/@getpaseo/*; do
|
||||
name=$(basename "$link")
|
||||
if [ -d "packages/$name/dist" ]; then
|
||||
mkdir -p "$out/lib/paseo/packages/$name"
|
||||
cp "packages/$name/package.json" "$out/lib/paseo/packages/$name/"
|
||||
cp -a "packages/$name/dist" "$out/lib/paseo/packages/$name/"
|
||||
if [ -d "packages/$name/node_modules" ]; then
|
||||
cp -a "packages/$name/node_modules" "$out/lib/paseo/packages/$name/"
|
||||
fi
|
||||
else
|
||||
rm -f "$link"
|
||||
fi
|
||||
done
|
||||
|
||||
# Copy CLI bin entry
|
||||
mkdir -p $out/lib/paseo/packages/cli/bin
|
||||
cp packages/cli/bin/paseo $out/lib/paseo/packages/cli/bin/
|
||||
|
||||
# Copy extra server files referenced at runtime
|
||||
for f in agent-prompt.md .env.example; do
|
||||
if [ -f packages/server/$f ]; then
|
||||
cp packages/server/$f $out/lib/paseo/packages/server/
|
||||
fi
|
||||
done
|
||||
|
||||
# Copy server scripts (daemon-runner, supervisor) needed by CLI
|
||||
if [ -d packages/server/dist/scripts ]; then
|
||||
mkdir -p $out/lib/paseo/packages/server/dist/scripts
|
||||
cp -a packages/server/dist/scripts/* $out/lib/paseo/packages/server/dist/scripts/
|
||||
fi
|
||||
|
||||
# Create wrapper for the server entry point (for systemd / direct use)
|
||||
mkdir -p $out/bin
|
||||
makeWrapper ${nodejs}/bin/node $out/bin/paseo-server \
|
||||
--add-flags "$out/lib/paseo/packages/server/dist/server/server/index.js" \
|
||||
--set NODE_ENV production
|
||||
|
||||
# Create wrapper for the CLI
|
||||
makeWrapper ${nodejs}/bin/node $out/bin/paseo \
|
||||
--add-flags "$out/lib/paseo/packages/cli/dist/index.js" \
|
||||
--set NODE_PATH "$out/lib/paseo/node_modules"
|
||||
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
meta = {
|
||||
description = "Self-hosted daemon for Claude Code, Codex, and OpenCode";
|
||||
homepage = "https://github.com/getpaseo/paseo";
|
||||
license = lib.licenses.agpl3Plus;
|
||||
mainProgram = "paseo";
|
||||
platforms = lib.platforms.linux ++ lib.platforms.darwin;
|
||||
};
|
||||
}
|
||||
Generated
+75
-25
@@ -34942,7 +34942,9 @@
|
||||
"expo": "*",
|
||||
"react": "*",
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"resolved": "https://registry.npmjs.org/expo-clipboard/-/expo-clipboard-8.0.7.tgz",
|
||||
"integrity": "sha512-zvlfFV+wB2QQrQnHWlo0EKHAkdi2tycLtE+EXFUWTPZYkgu1XcH+aiKfd4ul7Z0SDF+1IuwoiW9AA9eO35aj3Q=="
|
||||
},
|
||||
"packages/app/node_modules/react-native-nitro-modules": {
|
||||
"version": "0.33.8",
|
||||
@@ -34959,7 +34961,9 @@
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
},
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="
|
||||
},
|
||||
"packages/cli": {
|
||||
"name": "@getpaseo/cli",
|
||||
@@ -34993,14 +34997,18 @@
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
|
||||
"integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="
|
||||
},
|
||||
"packages/cli/node_modules/commander": {
|
||||
"version": "12.1.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
|
||||
"integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="
|
||||
},
|
||||
"packages/desktop": {
|
||||
"name": "@getpaseo/desktop",
|
||||
@@ -35351,7 +35359,9 @@
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.20.1.tgz",
|
||||
"integrity": "sha512-j/P+yuxXfgxb+mW7OEoRCM3G47zCTDqUPivJo/VzpjbG8I9csTXtOprCf5FfOfHK4whOJny0aHuBEON+kS7CCA=="
|
||||
},
|
||||
"packages/server/node_modules/@modelcontextprotocol/sdk/node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
@@ -35365,7 +35375,9 @@
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="
|
||||
},
|
||||
"packages/server/node_modules/@modelcontextprotocol/sdk/node_modules/express": {
|
||||
"version": "5.1.0",
|
||||
@@ -35405,7 +35417,9 @@
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
|
||||
"integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="
|
||||
},
|
||||
"packages/server/node_modules/@modelcontextprotocol/sdk/node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
@@ -35422,7 +35436,9 @@
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
|
||||
"integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="
|
||||
},
|
||||
"packages/server/node_modules/ajv": {
|
||||
"version": "8.17.1",
|
||||
@@ -35436,7 +35452,9 @@
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
|
||||
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="
|
||||
},
|
||||
"packages/server/node_modules/ansi-regex": {
|
||||
"version": "6.2.2",
|
||||
@@ -35446,7 +35464,9 @@
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
|
||||
}
|
||||
},
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
|
||||
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="
|
||||
},
|
||||
"packages/server/node_modules/body-parser": {
|
||||
"version": "2.2.0",
|
||||
@@ -35464,7 +35484,9 @@
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
|
||||
"integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg=="
|
||||
},
|
||||
"packages/server/node_modules/content-disposition": {
|
||||
"version": "1.0.0",
|
||||
@@ -35474,14 +35496,18 @@
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
|
||||
"integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg=="
|
||||
},
|
||||
"packages/server/node_modules/cookie-signature": {
|
||||
"version": "1.2.2",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.6.0"
|
||||
}
|
||||
},
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
|
||||
"integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="
|
||||
},
|
||||
"packages/server/node_modules/finalhandler": {
|
||||
"version": "2.1.0",
|
||||
@@ -35496,21 +35522,27 @@
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
|
||||
"integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q=="
|
||||
},
|
||||
"packages/server/node_modules/fresh": {
|
||||
"version": "2.0.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
|
||||
"integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="
|
||||
},
|
||||
"packages/server/node_modules/media-typer": {
|
||||
"version": "1.1.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
|
||||
"integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="
|
||||
},
|
||||
"packages/server/node_modules/merge-descriptors": {
|
||||
"version": "2.0.0",
|
||||
@@ -35520,7 +35552,9 @@
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
|
||||
"integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="
|
||||
},
|
||||
"packages/server/node_modules/mime-types": {
|
||||
"version": "3.0.1",
|
||||
@@ -35530,14 +35564,18 @@
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
|
||||
"integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="
|
||||
},
|
||||
"packages/server/node_modules/negotiator": {
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
|
||||
"integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="
|
||||
},
|
||||
"packages/server/node_modules/raw-body": {
|
||||
"version": "3.0.2",
|
||||
@@ -35588,7 +35626,9 @@
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
|
||||
"integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw=="
|
||||
},
|
||||
"packages/server/node_modules/serve-static": {
|
||||
"version": "2.2.0",
|
||||
@@ -35601,7 +35641,9 @@
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
|
||||
"integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ=="
|
||||
},
|
||||
"packages/server/node_modules/strip-ansi": {
|
||||
"version": "7.1.2",
|
||||
@@ -35614,7 +35656,9 @@
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
|
||||
"integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="
|
||||
},
|
||||
"packages/server/node_modules/type-is": {
|
||||
"version": "2.0.1",
|
||||
@@ -35626,14 +35670,18 @@
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
|
||||
"integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="
|
||||
},
|
||||
"packages/server/node_modules/zod": {
|
||||
"version": "3.25.76",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
},
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="
|
||||
},
|
||||
"packages/website": {
|
||||
"name": "@getpaseo/website",
|
||||
@@ -35667,7 +35715,9 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.6.tgz",
|
||||
"integrity": "sha512-qm+G8HuG6hOHQigsi7VGuLjUVu6TtBo/F05zvX04Mw2uCg9Dv0Qxy3Qw7j41SidlTcl5D/5yg0SEZqOB+EqZnQ=="
|
||||
},
|
||||
"packages/website/node_modules/react": {
|
||||
"version": "19.2.4",
|
||||
|
||||
@@ -14,7 +14,6 @@ publish:
|
||||
owner: getpaseo
|
||||
repo: paseo
|
||||
mac:
|
||||
artifactName: "Paseo-${version}-${arch}.${ext}"
|
||||
category: public.app-category.developer-tools
|
||||
icon: assets/icon.icns
|
||||
hardenedRuntime: true
|
||||
@@ -36,8 +35,6 @@ linux:
|
||||
to: bin/paseo
|
||||
target:
|
||||
- AppImage
|
||||
- deb
|
||||
- rpm
|
||||
- tar.gz
|
||||
win:
|
||||
icon: assets/icon.ico
|
||||
|
||||
@@ -255,12 +255,6 @@ function Nav() {
|
||||
>
|
||||
Changelog
|
||||
</a>
|
||||
<a
|
||||
href="/download"
|
||||
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
Download
|
||||
</a>
|
||||
<a
|
||||
href="https://discord.gg/jz8T2uahpH"
|
||||
target="_blank"
|
||||
@@ -521,15 +515,7 @@ function GetStarted() {
|
||||
</a>
|
||||
<ServerInstallButton />
|
||||
</div>
|
||||
<div className="pt-3">
|
||||
<a
|
||||
href="/download"
|
||||
className="text-xs text-white/40 hover:text-white/70 transition-colors"
|
||||
>
|
||||
All download options
|
||||
</a>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 pt-6">
|
||||
<div className="flex items-center gap-2 pt-8">
|
||||
<span className="text-xs text-white/40">Supports</span>
|
||||
<div className="flex items-center gap-1">
|
||||
<AgentBadge name="Claude Code" icon={<ClaudeCodeIcon className="h-6 w-6" />} />
|
||||
@@ -543,19 +529,72 @@ function GetStarted() {
|
||||
|
||||
function DownloadButton() {
|
||||
const detectedPlatform = useDetectedPlatform();
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const ref = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
function handleClickOutside(e: MouseEvent) {
|
||||
if (ref.current && !ref.current.contains(e.target as Node)) {
|
||||
setOpen(false);
|
||||
}
|
||||
}
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||||
}, []);
|
||||
|
||||
const primary = downloadOptions.find((o) => o.platform === detectedPlatform)!;
|
||||
const secondaryOptions = downloadOptions.filter((o) => o.platform !== detectedPlatform);
|
||||
const PrimaryIcon = primary.icon;
|
||||
|
||||
return (
|
||||
<a
|
||||
href={primary.href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-2 rounded-lg bg-foreground px-4 py-2 text-sm font-medium text-background hover:bg-foreground/90 transition-colors"
|
||||
>
|
||||
<PrimaryIcon className="h-4 w-4" />
|
||||
Download for {primary.label}
|
||||
</a>
|
||||
<div className="relative" ref={ref}>
|
||||
<div className="flex items-stretch">
|
||||
<a
|
||||
href={primary.href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-2 rounded-l-lg bg-foreground px-4 py-2 text-sm font-medium text-background hover:bg-foreground/90 transition-colors"
|
||||
>
|
||||
<PrimaryIcon className="h-4 w-4" />
|
||||
Download for {primary.label}
|
||||
</a>
|
||||
<button
|
||||
onClick={() => setOpen(!open)}
|
||||
className="inline-flex items-center justify-center rounded-r-lg border-l border-background/20 bg-foreground px-2 py-2 text-background hover:bg-foreground/90 transition-colors"
|
||||
aria-label="More download options"
|
||||
>
|
||||
<ChevronDownIcon className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
<AnimatePresence>
|
||||
{open && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -4 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -4 }}
|
||||
transition={{ duration: 0.15, ease: "easeOut" }}
|
||||
className="absolute left-0 top-full mt-1 z-50 min-w-[200px] rounded-lg border border-white/20 bg-black/90 backdrop-blur-sm py-1"
|
||||
>
|
||||
{secondaryOptions.map((option) => {
|
||||
const Icon = option.icon;
|
||||
return (
|
||||
<a
|
||||
key={option.platform}
|
||||
href={option.href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={() => setOpen(false)}
|
||||
className="flex items-center gap-2 px-4 py-2 text-sm text-white/80 hover:bg-white/10 transition-colors"
|
||||
>
|
||||
<Icon className="h-4 w-4" />
|
||||
{option.label}
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,352 +8,332 @@
|
||||
// You should NOT make any changes in this file as it will be overwritten.
|
||||
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
||||
|
||||
import { Route as rootRouteImport } from './routes/__root'
|
||||
import { Route as PrivacyRouteImport } from './routes/privacy'
|
||||
import { Route as OpencodeRouteImport } from './routes/opencode'
|
||||
import { Route as DownloadRouteImport } from './routes/download'
|
||||
import { Route as DocsRouteImport } from './routes/docs'
|
||||
import { Route as CodexRouteImport } from './routes/codex'
|
||||
import { Route as ClaudeCodeRouteImport } from './routes/claude-code'
|
||||
import { Route as ChangelogRouteImport } from './routes/changelog'
|
||||
import { Route as IndexRouteImport } from './routes/index'
|
||||
import { Route as DocsIndexRouteImport } from './routes/docs/index'
|
||||
import { Route as DocsWorktreesRouteImport } from './routes/docs/worktrees'
|
||||
import { Route as DocsVoiceRouteImport } from './routes/docs/voice'
|
||||
import { Route as DocsUpdatesRouteImport } from './routes/docs/updates'
|
||||
import { Route as DocsSecurityRouteImport } from './routes/docs/security'
|
||||
import { Route as DocsConfigurationRouteImport } from './routes/docs/configuration'
|
||||
import { Route as DocsCliRouteImport } from './routes/docs/cli'
|
||||
import { Route as DocsBestPracticesRouteImport } from './routes/docs/best-practices'
|
||||
import { Route as rootRouteImport } from "./routes/__root";
|
||||
import { Route as PrivacyRouteImport } from "./routes/privacy";
|
||||
import { Route as OpencodeRouteImport } from "./routes/opencode";
|
||||
import { Route as DocsRouteImport } from "./routes/docs";
|
||||
import { Route as CodexRouteImport } from "./routes/codex";
|
||||
import { Route as ClaudeCodeRouteImport } from "./routes/claude-code";
|
||||
import { Route as ChangelogRouteImport } from "./routes/changelog";
|
||||
import { Route as IndexRouteImport } from "./routes/index";
|
||||
import { Route as DocsIndexRouteImport } from "./routes/docs/index";
|
||||
import { Route as DocsWorktreesRouteImport } from "./routes/docs/worktrees";
|
||||
import { Route as DocsVoiceRouteImport } from "./routes/docs/voice";
|
||||
import { Route as DocsUpdatesRouteImport } from "./routes/docs/updates";
|
||||
import { Route as DocsSecurityRouteImport } from "./routes/docs/security";
|
||||
import { Route as DocsConfigurationRouteImport } from "./routes/docs/configuration";
|
||||
import { Route as DocsCliRouteImport } from "./routes/docs/cli";
|
||||
import { Route as DocsBestPracticesRouteImport } from "./routes/docs/best-practices";
|
||||
|
||||
const PrivacyRoute = PrivacyRouteImport.update({
|
||||
id: '/privacy',
|
||||
path: '/privacy',
|
||||
id: "/privacy",
|
||||
path: "/privacy",
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
} as any);
|
||||
const OpencodeRoute = OpencodeRouteImport.update({
|
||||
id: '/opencode',
|
||||
path: '/opencode',
|
||||
id: "/opencode",
|
||||
path: "/opencode",
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const DownloadRoute = DownloadRouteImport.update({
|
||||
id: '/download',
|
||||
path: '/download',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
} as any);
|
||||
const DocsRoute = DocsRouteImport.update({
|
||||
id: '/docs',
|
||||
path: '/docs',
|
||||
id: "/docs",
|
||||
path: "/docs",
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
} as any);
|
||||
const CodexRoute = CodexRouteImport.update({
|
||||
id: '/codex',
|
||||
path: '/codex',
|
||||
id: "/codex",
|
||||
path: "/codex",
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
} as any);
|
||||
const ClaudeCodeRoute = ClaudeCodeRouteImport.update({
|
||||
id: '/claude-code',
|
||||
path: '/claude-code',
|
||||
id: "/claude-code",
|
||||
path: "/claude-code",
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
} as any);
|
||||
const ChangelogRoute = ChangelogRouteImport.update({
|
||||
id: '/changelog',
|
||||
path: '/changelog',
|
||||
id: "/changelog",
|
||||
path: "/changelog",
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
} as any);
|
||||
const IndexRoute = IndexRouteImport.update({
|
||||
id: '/',
|
||||
path: '/',
|
||||
id: "/",
|
||||
path: "/",
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
} as any);
|
||||
const DocsIndexRoute = DocsIndexRouteImport.update({
|
||||
id: '/',
|
||||
path: '/',
|
||||
id: "/",
|
||||
path: "/",
|
||||
getParentRoute: () => DocsRoute,
|
||||
} as any)
|
||||
} as any);
|
||||
const DocsWorktreesRoute = DocsWorktreesRouteImport.update({
|
||||
id: '/worktrees',
|
||||
path: '/worktrees',
|
||||
id: "/worktrees",
|
||||
path: "/worktrees",
|
||||
getParentRoute: () => DocsRoute,
|
||||
} as any)
|
||||
} as any);
|
||||
const DocsVoiceRoute = DocsVoiceRouteImport.update({
|
||||
id: '/voice',
|
||||
path: '/voice',
|
||||
id: "/voice",
|
||||
path: "/voice",
|
||||
getParentRoute: () => DocsRoute,
|
||||
} as any)
|
||||
} as any);
|
||||
const DocsUpdatesRoute = DocsUpdatesRouteImport.update({
|
||||
id: '/updates',
|
||||
path: '/updates',
|
||||
id: "/updates",
|
||||
path: "/updates",
|
||||
getParentRoute: () => DocsRoute,
|
||||
} as any)
|
||||
} as any);
|
||||
const DocsSecurityRoute = DocsSecurityRouteImport.update({
|
||||
id: '/security',
|
||||
path: '/security',
|
||||
id: "/security",
|
||||
path: "/security",
|
||||
getParentRoute: () => DocsRoute,
|
||||
} as any)
|
||||
} as any);
|
||||
const DocsConfigurationRoute = DocsConfigurationRouteImport.update({
|
||||
id: '/configuration',
|
||||
path: '/configuration',
|
||||
id: "/configuration",
|
||||
path: "/configuration",
|
||||
getParentRoute: () => DocsRoute,
|
||||
} as any)
|
||||
} as any);
|
||||
const DocsCliRoute = DocsCliRouteImport.update({
|
||||
id: '/cli',
|
||||
path: '/cli',
|
||||
id: "/cli",
|
||||
path: "/cli",
|
||||
getParentRoute: () => DocsRoute,
|
||||
} as any)
|
||||
} as any);
|
||||
const DocsBestPracticesRoute = DocsBestPracticesRouteImport.update({
|
||||
id: '/best-practices',
|
||||
path: '/best-practices',
|
||||
id: "/best-practices",
|
||||
path: "/best-practices",
|
||||
getParentRoute: () => DocsRoute,
|
||||
} as any)
|
||||
} as any);
|
||||
|
||||
export interface FileRoutesByFullPath {
|
||||
'/': typeof IndexRoute
|
||||
'/changelog': typeof ChangelogRoute
|
||||
'/claude-code': typeof ClaudeCodeRoute
|
||||
'/codex': typeof CodexRoute
|
||||
'/docs': typeof DocsRouteWithChildren
|
||||
'/download': typeof DownloadRoute
|
||||
'/opencode': typeof OpencodeRoute
|
||||
'/privacy': typeof PrivacyRoute
|
||||
'/docs/best-practices': typeof DocsBestPracticesRoute
|
||||
'/docs/cli': typeof DocsCliRoute
|
||||
'/docs/configuration': typeof DocsConfigurationRoute
|
||||
'/docs/security': typeof DocsSecurityRoute
|
||||
'/docs/updates': typeof DocsUpdatesRoute
|
||||
'/docs/voice': typeof DocsVoiceRoute
|
||||
'/docs/worktrees': typeof DocsWorktreesRoute
|
||||
'/docs/': typeof DocsIndexRoute
|
||||
"/": typeof IndexRoute;
|
||||
"/changelog": typeof ChangelogRoute;
|
||||
"/claude-code": typeof ClaudeCodeRoute;
|
||||
"/codex": typeof CodexRoute;
|
||||
"/docs": typeof DocsRouteWithChildren;
|
||||
"/opencode": typeof OpencodeRoute;
|
||||
"/privacy": typeof PrivacyRoute;
|
||||
"/docs/best-practices": typeof DocsBestPracticesRoute;
|
||||
"/docs/cli": typeof DocsCliRoute;
|
||||
"/docs/configuration": typeof DocsConfigurationRoute;
|
||||
"/docs/security": typeof DocsSecurityRoute;
|
||||
"/docs/updates": typeof DocsUpdatesRoute;
|
||||
"/docs/voice": typeof DocsVoiceRoute;
|
||||
"/docs/worktrees": typeof DocsWorktreesRoute;
|
||||
"/docs/": typeof DocsIndexRoute;
|
||||
}
|
||||
export interface FileRoutesByTo {
|
||||
'/': typeof IndexRoute
|
||||
'/changelog': typeof ChangelogRoute
|
||||
'/claude-code': typeof ClaudeCodeRoute
|
||||
'/codex': typeof CodexRoute
|
||||
'/download': typeof DownloadRoute
|
||||
'/opencode': typeof OpencodeRoute
|
||||
'/privacy': typeof PrivacyRoute
|
||||
'/docs/best-practices': typeof DocsBestPracticesRoute
|
||||
'/docs/cli': typeof DocsCliRoute
|
||||
'/docs/configuration': typeof DocsConfigurationRoute
|
||||
'/docs/security': typeof DocsSecurityRoute
|
||||
'/docs/updates': typeof DocsUpdatesRoute
|
||||
'/docs/voice': typeof DocsVoiceRoute
|
||||
'/docs/worktrees': typeof DocsWorktreesRoute
|
||||
'/docs': typeof DocsIndexRoute
|
||||
"/": typeof IndexRoute;
|
||||
"/changelog": typeof ChangelogRoute;
|
||||
"/claude-code": typeof ClaudeCodeRoute;
|
||||
"/codex": typeof CodexRoute;
|
||||
"/opencode": typeof OpencodeRoute;
|
||||
"/privacy": typeof PrivacyRoute;
|
||||
"/docs/best-practices": typeof DocsBestPracticesRoute;
|
||||
"/docs/cli": typeof DocsCliRoute;
|
||||
"/docs/configuration": typeof DocsConfigurationRoute;
|
||||
"/docs/security": typeof DocsSecurityRoute;
|
||||
"/docs/updates": typeof DocsUpdatesRoute;
|
||||
"/docs/voice": typeof DocsVoiceRoute;
|
||||
"/docs/worktrees": typeof DocsWorktreesRoute;
|
||||
"/docs": typeof DocsIndexRoute;
|
||||
}
|
||||
export interface FileRoutesById {
|
||||
__root__: typeof rootRouteImport
|
||||
'/': typeof IndexRoute
|
||||
'/changelog': typeof ChangelogRoute
|
||||
'/claude-code': typeof ClaudeCodeRoute
|
||||
'/codex': typeof CodexRoute
|
||||
'/docs': typeof DocsRouteWithChildren
|
||||
'/download': typeof DownloadRoute
|
||||
'/opencode': typeof OpencodeRoute
|
||||
'/privacy': typeof PrivacyRoute
|
||||
'/docs/best-practices': typeof DocsBestPracticesRoute
|
||||
'/docs/cli': typeof DocsCliRoute
|
||||
'/docs/configuration': typeof DocsConfigurationRoute
|
||||
'/docs/security': typeof DocsSecurityRoute
|
||||
'/docs/updates': typeof DocsUpdatesRoute
|
||||
'/docs/voice': typeof DocsVoiceRoute
|
||||
'/docs/worktrees': typeof DocsWorktreesRoute
|
||||
'/docs/': typeof DocsIndexRoute
|
||||
__root__: typeof rootRouteImport;
|
||||
"/": typeof IndexRoute;
|
||||
"/changelog": typeof ChangelogRoute;
|
||||
"/claude-code": typeof ClaudeCodeRoute;
|
||||
"/codex": typeof CodexRoute;
|
||||
"/docs": typeof DocsRouteWithChildren;
|
||||
"/opencode": typeof OpencodeRoute;
|
||||
"/privacy": typeof PrivacyRoute;
|
||||
"/docs/best-practices": typeof DocsBestPracticesRoute;
|
||||
"/docs/cli": typeof DocsCliRoute;
|
||||
"/docs/configuration": typeof DocsConfigurationRoute;
|
||||
"/docs/security": typeof DocsSecurityRoute;
|
||||
"/docs/updates": typeof DocsUpdatesRoute;
|
||||
"/docs/voice": typeof DocsVoiceRoute;
|
||||
"/docs/worktrees": typeof DocsWorktreesRoute;
|
||||
"/docs/": typeof DocsIndexRoute;
|
||||
}
|
||||
export interface FileRouteTypes {
|
||||
fileRoutesByFullPath: FileRoutesByFullPath
|
||||
fileRoutesByFullPath: FileRoutesByFullPath;
|
||||
fullPaths:
|
||||
| '/'
|
||||
| '/changelog'
|
||||
| '/claude-code'
|
||||
| '/codex'
|
||||
| '/docs'
|
||||
| '/download'
|
||||
| '/opencode'
|
||||
| '/privacy'
|
||||
| '/docs/best-practices'
|
||||
| '/docs/cli'
|
||||
| '/docs/configuration'
|
||||
| '/docs/security'
|
||||
| '/docs/updates'
|
||||
| '/docs/voice'
|
||||
| '/docs/worktrees'
|
||||
| '/docs/'
|
||||
fileRoutesByTo: FileRoutesByTo
|
||||
| "/"
|
||||
| "/changelog"
|
||||
| "/claude-code"
|
||||
| "/codex"
|
||||
| "/docs"
|
||||
| "/opencode"
|
||||
| "/privacy"
|
||||
| "/docs/best-practices"
|
||||
| "/docs/cli"
|
||||
| "/docs/configuration"
|
||||
| "/docs/security"
|
||||
| "/docs/updates"
|
||||
| "/docs/voice"
|
||||
| "/docs/worktrees"
|
||||
| "/docs/";
|
||||
fileRoutesByTo: FileRoutesByTo;
|
||||
to:
|
||||
| '/'
|
||||
| '/changelog'
|
||||
| '/claude-code'
|
||||
| '/codex'
|
||||
| '/download'
|
||||
| '/opencode'
|
||||
| '/privacy'
|
||||
| '/docs/best-practices'
|
||||
| '/docs/cli'
|
||||
| '/docs/configuration'
|
||||
| '/docs/security'
|
||||
| '/docs/updates'
|
||||
| '/docs/voice'
|
||||
| '/docs/worktrees'
|
||||
| '/docs'
|
||||
| "/"
|
||||
| "/changelog"
|
||||
| "/claude-code"
|
||||
| "/codex"
|
||||
| "/opencode"
|
||||
| "/privacy"
|
||||
| "/docs/best-practices"
|
||||
| "/docs/cli"
|
||||
| "/docs/configuration"
|
||||
| "/docs/security"
|
||||
| "/docs/updates"
|
||||
| "/docs/voice"
|
||||
| "/docs/worktrees"
|
||||
| "/docs";
|
||||
id:
|
||||
| '__root__'
|
||||
| '/'
|
||||
| '/changelog'
|
||||
| '/claude-code'
|
||||
| '/codex'
|
||||
| '/docs'
|
||||
| '/download'
|
||||
| '/opencode'
|
||||
| '/privacy'
|
||||
| '/docs/best-practices'
|
||||
| '/docs/cli'
|
||||
| '/docs/configuration'
|
||||
| '/docs/security'
|
||||
| '/docs/updates'
|
||||
| '/docs/voice'
|
||||
| '/docs/worktrees'
|
||||
| '/docs/'
|
||||
fileRoutesById: FileRoutesById
|
||||
| "__root__"
|
||||
| "/"
|
||||
| "/changelog"
|
||||
| "/claude-code"
|
||||
| "/codex"
|
||||
| "/docs"
|
||||
| "/opencode"
|
||||
| "/privacy"
|
||||
| "/docs/best-practices"
|
||||
| "/docs/cli"
|
||||
| "/docs/configuration"
|
||||
| "/docs/security"
|
||||
| "/docs/updates"
|
||||
| "/docs/voice"
|
||||
| "/docs/worktrees"
|
||||
| "/docs/";
|
||||
fileRoutesById: FileRoutesById;
|
||||
}
|
||||
export interface RootRouteChildren {
|
||||
IndexRoute: typeof IndexRoute
|
||||
ChangelogRoute: typeof ChangelogRoute
|
||||
ClaudeCodeRoute: typeof ClaudeCodeRoute
|
||||
CodexRoute: typeof CodexRoute
|
||||
DocsRoute: typeof DocsRouteWithChildren
|
||||
DownloadRoute: typeof DownloadRoute
|
||||
OpencodeRoute: typeof OpencodeRoute
|
||||
PrivacyRoute: typeof PrivacyRoute
|
||||
IndexRoute: typeof IndexRoute;
|
||||
ChangelogRoute: typeof ChangelogRoute;
|
||||
ClaudeCodeRoute: typeof ClaudeCodeRoute;
|
||||
CodexRoute: typeof CodexRoute;
|
||||
DocsRoute: typeof DocsRouteWithChildren;
|
||||
OpencodeRoute: typeof OpencodeRoute;
|
||||
PrivacyRoute: typeof PrivacyRoute;
|
||||
}
|
||||
|
||||
declare module '@tanstack/react-router' {
|
||||
declare module "@tanstack/react-router" {
|
||||
interface FileRoutesByPath {
|
||||
'/privacy': {
|
||||
id: '/privacy'
|
||||
path: '/privacy'
|
||||
fullPath: '/privacy'
|
||||
preLoaderRoute: typeof PrivacyRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/opencode': {
|
||||
id: '/opencode'
|
||||
path: '/opencode'
|
||||
fullPath: '/opencode'
|
||||
preLoaderRoute: typeof OpencodeRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/download': {
|
||||
id: '/download'
|
||||
path: '/download'
|
||||
fullPath: '/download'
|
||||
preLoaderRoute: typeof DownloadRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/docs': {
|
||||
id: '/docs'
|
||||
path: '/docs'
|
||||
fullPath: '/docs'
|
||||
preLoaderRoute: typeof DocsRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/codex': {
|
||||
id: '/codex'
|
||||
path: '/codex'
|
||||
fullPath: '/codex'
|
||||
preLoaderRoute: typeof CodexRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/claude-code': {
|
||||
id: '/claude-code'
|
||||
path: '/claude-code'
|
||||
fullPath: '/claude-code'
|
||||
preLoaderRoute: typeof ClaudeCodeRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/changelog': {
|
||||
id: '/changelog'
|
||||
path: '/changelog'
|
||||
fullPath: '/changelog'
|
||||
preLoaderRoute: typeof ChangelogRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/': {
|
||||
id: '/'
|
||||
path: '/'
|
||||
fullPath: '/'
|
||||
preLoaderRoute: typeof IndexRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/docs/': {
|
||||
id: '/docs/'
|
||||
path: '/'
|
||||
fullPath: '/docs/'
|
||||
preLoaderRoute: typeof DocsIndexRouteImport
|
||||
parentRoute: typeof DocsRoute
|
||||
}
|
||||
'/docs/worktrees': {
|
||||
id: '/docs/worktrees'
|
||||
path: '/worktrees'
|
||||
fullPath: '/docs/worktrees'
|
||||
preLoaderRoute: typeof DocsWorktreesRouteImport
|
||||
parentRoute: typeof DocsRoute
|
||||
}
|
||||
'/docs/voice': {
|
||||
id: '/docs/voice'
|
||||
path: '/voice'
|
||||
fullPath: '/docs/voice'
|
||||
preLoaderRoute: typeof DocsVoiceRouteImport
|
||||
parentRoute: typeof DocsRoute
|
||||
}
|
||||
'/docs/updates': {
|
||||
id: '/docs/updates'
|
||||
path: '/updates'
|
||||
fullPath: '/docs/updates'
|
||||
preLoaderRoute: typeof DocsUpdatesRouteImport
|
||||
parentRoute: typeof DocsRoute
|
||||
}
|
||||
'/docs/security': {
|
||||
id: '/docs/security'
|
||||
path: '/security'
|
||||
fullPath: '/docs/security'
|
||||
preLoaderRoute: typeof DocsSecurityRouteImport
|
||||
parentRoute: typeof DocsRoute
|
||||
}
|
||||
'/docs/configuration': {
|
||||
id: '/docs/configuration'
|
||||
path: '/configuration'
|
||||
fullPath: '/docs/configuration'
|
||||
preLoaderRoute: typeof DocsConfigurationRouteImport
|
||||
parentRoute: typeof DocsRoute
|
||||
}
|
||||
'/docs/cli': {
|
||||
id: '/docs/cli'
|
||||
path: '/cli'
|
||||
fullPath: '/docs/cli'
|
||||
preLoaderRoute: typeof DocsCliRouteImport
|
||||
parentRoute: typeof DocsRoute
|
||||
}
|
||||
'/docs/best-practices': {
|
||||
id: '/docs/best-practices'
|
||||
path: '/best-practices'
|
||||
fullPath: '/docs/best-practices'
|
||||
preLoaderRoute: typeof DocsBestPracticesRouteImport
|
||||
parentRoute: typeof DocsRoute
|
||||
}
|
||||
"/privacy": {
|
||||
id: "/privacy";
|
||||
path: "/privacy";
|
||||
fullPath: "/privacy";
|
||||
preLoaderRoute: typeof PrivacyRouteImport;
|
||||
parentRoute: typeof rootRouteImport;
|
||||
};
|
||||
"/opencode": {
|
||||
id: "/opencode";
|
||||
path: "/opencode";
|
||||
fullPath: "/opencode";
|
||||
preLoaderRoute: typeof OpencodeRouteImport;
|
||||
parentRoute: typeof rootRouteImport;
|
||||
};
|
||||
"/docs": {
|
||||
id: "/docs";
|
||||
path: "/docs";
|
||||
fullPath: "/docs";
|
||||
preLoaderRoute: typeof DocsRouteImport;
|
||||
parentRoute: typeof rootRouteImport;
|
||||
};
|
||||
"/codex": {
|
||||
id: "/codex";
|
||||
path: "/codex";
|
||||
fullPath: "/codex";
|
||||
preLoaderRoute: typeof CodexRouteImport;
|
||||
parentRoute: typeof rootRouteImport;
|
||||
};
|
||||
"/claude-code": {
|
||||
id: "/claude-code";
|
||||
path: "/claude-code";
|
||||
fullPath: "/claude-code";
|
||||
preLoaderRoute: typeof ClaudeCodeRouteImport;
|
||||
parentRoute: typeof rootRouteImport;
|
||||
};
|
||||
"/changelog": {
|
||||
id: "/changelog";
|
||||
path: "/changelog";
|
||||
fullPath: "/changelog";
|
||||
preLoaderRoute: typeof ChangelogRouteImport;
|
||||
parentRoute: typeof rootRouteImport;
|
||||
};
|
||||
"/": {
|
||||
id: "/";
|
||||
path: "/";
|
||||
fullPath: "/";
|
||||
preLoaderRoute: typeof IndexRouteImport;
|
||||
parentRoute: typeof rootRouteImport;
|
||||
};
|
||||
"/docs/": {
|
||||
id: "/docs/";
|
||||
path: "/";
|
||||
fullPath: "/docs/";
|
||||
preLoaderRoute: typeof DocsIndexRouteImport;
|
||||
parentRoute: typeof DocsRoute;
|
||||
};
|
||||
"/docs/worktrees": {
|
||||
id: "/docs/worktrees";
|
||||
path: "/worktrees";
|
||||
fullPath: "/docs/worktrees";
|
||||
preLoaderRoute: typeof DocsWorktreesRouteImport;
|
||||
parentRoute: typeof DocsRoute;
|
||||
};
|
||||
"/docs/voice": {
|
||||
id: "/docs/voice";
|
||||
path: "/voice";
|
||||
fullPath: "/docs/voice";
|
||||
preLoaderRoute: typeof DocsVoiceRouteImport;
|
||||
parentRoute: typeof DocsRoute;
|
||||
};
|
||||
"/docs/updates": {
|
||||
id: "/docs/updates";
|
||||
path: "/updates";
|
||||
fullPath: "/docs/updates";
|
||||
preLoaderRoute: typeof DocsUpdatesRouteImport;
|
||||
parentRoute: typeof DocsRoute;
|
||||
};
|
||||
"/docs/security": {
|
||||
id: "/docs/security";
|
||||
path: "/security";
|
||||
fullPath: "/docs/security";
|
||||
preLoaderRoute: typeof DocsSecurityRouteImport;
|
||||
parentRoute: typeof DocsRoute;
|
||||
};
|
||||
"/docs/configuration": {
|
||||
id: "/docs/configuration";
|
||||
path: "/configuration";
|
||||
fullPath: "/docs/configuration";
|
||||
preLoaderRoute: typeof DocsConfigurationRouteImport;
|
||||
parentRoute: typeof DocsRoute;
|
||||
};
|
||||
"/docs/cli": {
|
||||
id: "/docs/cli";
|
||||
path: "/cli";
|
||||
fullPath: "/docs/cli";
|
||||
preLoaderRoute: typeof DocsCliRouteImport;
|
||||
parentRoute: typeof DocsRoute;
|
||||
};
|
||||
"/docs/best-practices": {
|
||||
id: "/docs/best-practices";
|
||||
path: "/best-practices";
|
||||
fullPath: "/docs/best-practices";
|
||||
preLoaderRoute: typeof DocsBestPracticesRouteImport;
|
||||
parentRoute: typeof DocsRoute;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
interface DocsRouteChildren {
|
||||
DocsBestPracticesRoute: typeof DocsBestPracticesRoute
|
||||
DocsCliRoute: typeof DocsCliRoute
|
||||
DocsConfigurationRoute: typeof DocsConfigurationRoute
|
||||
DocsSecurityRoute: typeof DocsSecurityRoute
|
||||
DocsUpdatesRoute: typeof DocsUpdatesRoute
|
||||
DocsVoiceRoute: typeof DocsVoiceRoute
|
||||
DocsWorktreesRoute: typeof DocsWorktreesRoute
|
||||
DocsIndexRoute: typeof DocsIndexRoute
|
||||
DocsBestPracticesRoute: typeof DocsBestPracticesRoute;
|
||||
DocsCliRoute: typeof DocsCliRoute;
|
||||
DocsConfigurationRoute: typeof DocsConfigurationRoute;
|
||||
DocsSecurityRoute: typeof DocsSecurityRoute;
|
||||
DocsUpdatesRoute: typeof DocsUpdatesRoute;
|
||||
DocsVoiceRoute: typeof DocsVoiceRoute;
|
||||
DocsWorktreesRoute: typeof DocsWorktreesRoute;
|
||||
DocsIndexRoute: typeof DocsIndexRoute;
|
||||
}
|
||||
|
||||
const DocsRouteChildren: DocsRouteChildren = {
|
||||
@@ -365,9 +345,9 @@ const DocsRouteChildren: DocsRouteChildren = {
|
||||
DocsVoiceRoute: DocsVoiceRoute,
|
||||
DocsWorktreesRoute: DocsWorktreesRoute,
|
||||
DocsIndexRoute: DocsIndexRoute,
|
||||
}
|
||||
};
|
||||
|
||||
const DocsRouteWithChildren = DocsRoute._addFileChildren(DocsRouteChildren)
|
||||
const DocsRouteWithChildren = DocsRoute._addFileChildren(DocsRouteChildren);
|
||||
|
||||
const rootRouteChildren: RootRouteChildren = {
|
||||
IndexRoute: IndexRoute,
|
||||
@@ -375,19 +355,18 @@ const rootRouteChildren: RootRouteChildren = {
|
||||
ClaudeCodeRoute: ClaudeCodeRoute,
|
||||
CodexRoute: CodexRoute,
|
||||
DocsRoute: DocsRouteWithChildren,
|
||||
DownloadRoute: DownloadRoute,
|
||||
OpencodeRoute: OpencodeRoute,
|
||||
PrivacyRoute: PrivacyRoute,
|
||||
}
|
||||
};
|
||||
export const routeTree = rootRouteImport
|
||||
._addFileChildren(rootRouteChildren)
|
||||
._addFileTypes<FileRouteTypes>()
|
||||
._addFileTypes<FileRouteTypes>();
|
||||
|
||||
import type { getRouter } from './router.tsx'
|
||||
import type { createStart } from '@tanstack/react-start'
|
||||
declare module '@tanstack/react-start' {
|
||||
import type { getRouter } from "./router.tsx";
|
||||
import type { createStart } from "@tanstack/react-start";
|
||||
declare module "@tanstack/react-start" {
|
||||
interface Register {
|
||||
ssr: true
|
||||
router: Awaited<ReturnType<typeof getRouter>>
|
||||
ssr: true;
|
||||
router: Awaited<ReturnType<typeof getRouter>>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,419 +0,0 @@
|
||||
import { Link, createFileRoute } from "@tanstack/react-router";
|
||||
import { pageMeta } from "~/meta";
|
||||
import websitePackage from "../../package.json";
|
||||
import "~/styles.css";
|
||||
|
||||
const desktopVersion = websitePackage.version;
|
||||
const releaseBase = `https://github.com/getpaseo/paseo/releases/download/v${desktopVersion}`;
|
||||
|
||||
export const Route = createFileRoute("/download")({
|
||||
head: () => ({
|
||||
meta: pageMeta(
|
||||
"Download - Paseo",
|
||||
"Download Paseo for macOS, Windows, Linux, iOS, and Android. Your dev environment, in your pocket.",
|
||||
),
|
||||
}),
|
||||
component: Download,
|
||||
});
|
||||
|
||||
function Download() {
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<div className="max-w-3xl mx-auto p-6 md:p-12">
|
||||
<header className="flex items-center justify-between gap-4 mb-12">
|
||||
<Link to="/" className="flex items-center gap-3">
|
||||
<img src="/logo.svg" alt="Paseo" className="w-6 h-6" />
|
||||
<span className="text-lg font-medium">Paseo</span>
|
||||
</Link>
|
||||
<div className="flex items-center gap-4">
|
||||
<Link
|
||||
to="/docs"
|
||||
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
Docs
|
||||
</Link>
|
||||
<Link
|
||||
to="/changelog"
|
||||
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
Changelog
|
||||
</Link>
|
||||
<a
|
||||
href="https://discord.gg/jz8T2uahpH"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label="Discord"
|
||||
className="text-muted-foreground hover:text-foreground transition-colors inline-flex items-center"
|
||||
>
|
||||
<svg
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="18"
|
||||
height="18"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z" />
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/getpaseo/paseo"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label="GitHub"
|
||||
className="text-muted-foreground hover:text-foreground transition-colors inline-flex items-center"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="18"
|
||||
height="18"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path d="M12 0C5.37 0 0 5.484 0 12.252c0 5.418 3.438 10.013 8.205 11.637.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.738-4.042-1.61-4.042-1.61-.546-1.403-1.333-1.776-1.333-1.776-1.089-.756.084-.741.084-.741 1.205.087 1.838 1.262 1.838 1.262 1.07 1.87 2.809 1.33 3.495 1.017.108-.79.417-1.33.76-1.636-2.665-.31-5.467-1.35-5.467-6.005 0-1.327.465-2.413 1.235-3.262-.124-.31-.535-1.556.117-3.243 0 0 1.008-.33 3.3 1.248a11.2 11.2 0 0 1 3.003-.404c1.02.005 2.045.138 3.003.404 2.29-1.578 3.297-1.248 3.297-1.248.653 1.687.242 2.933.118 3.243.77.85 1.233 1.935 1.233 3.262 0 4.667-2.807 5.692-5.48 5.995.43.38.823 1.133.823 2.285 0 1.65-.015 2.98-.015 3.386 0 .315.218.694.825.576C20.565 22.26 24 17.667 24 12.252 24 5.484 18.627 0 12 0z" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<h1 className="text-3xl md:text-4xl font-semibold tracking-tight mb-2">Download</h1>
|
||||
<p className="text-muted-foreground mb-10">
|
||||
v{desktopVersion}
|
||||
</p>
|
||||
|
||||
{/* Desktop */}
|
||||
<section className="rounded-xl border border-border bg-card/40 p-6 md:p-8 mb-6">
|
||||
<div className="flex items-center justify-between mb-8">
|
||||
<h2 className="text-2xl font-semibold">Desktop</h2>
|
||||
<MonitorIcon className="h-5 w-5 text-muted-foreground" />
|
||||
</div>
|
||||
|
||||
<div className="divide-y divide-border">
|
||||
{/* macOS */}
|
||||
<div className="flex items-center justify-between py-5 first:pt-0 last:pb-0">
|
||||
<div className="flex items-center gap-3">
|
||||
<AppleIcon className="h-5 w-5 text-foreground" />
|
||||
<span className="font-medium">macOS</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<DownloadPill
|
||||
href={`${releaseBase}/Paseo-${desktopVersion}-arm64.dmg`}
|
||||
label="Apple Silicon"
|
||||
/>
|
||||
<DownloadPill
|
||||
href={`${releaseBase}/Paseo-${desktopVersion}.dmg`}
|
||||
label="Intel"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Windows */}
|
||||
<div className="flex items-center justify-between py-5 first:pt-0 last:pb-0">
|
||||
<div className="flex items-center gap-3">
|
||||
<WindowsIcon className="h-5 w-5 text-foreground" />
|
||||
<span className="font-medium">Windows</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<DownloadPill
|
||||
href={`${releaseBase}/Paseo-Setup-${desktopVersion}.exe`}
|
||||
label="Download"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Linux */}
|
||||
<div className="flex items-center justify-between py-5 first:pt-0 last:pb-0">
|
||||
<div className="flex items-center gap-3">
|
||||
<LinuxIcon className="h-5 w-5 text-foreground" />
|
||||
<span className="font-medium">Linux</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<DownloadPill
|
||||
href={`${releaseBase}/Paseo-${desktopVersion}.AppImage`}
|
||||
label="AppImage"
|
||||
/>
|
||||
<DownloadPill
|
||||
href={`${releaseBase}/paseo_${desktopVersion}_amd64.deb`}
|
||||
label="DEB"
|
||||
/>
|
||||
<DownloadPill
|
||||
href={`${releaseBase}/paseo-${desktopVersion}-1.x86_64.rpm`}
|
||||
label="RPM"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Mobile */}
|
||||
<section className="rounded-xl border border-border bg-card/40 p-6 md:p-8 mb-6">
|
||||
<div className="flex items-center justify-between mb-8">
|
||||
<h2 className="text-2xl font-semibold">Mobile</h2>
|
||||
<PhoneIcon className="h-5 w-5 text-muted-foreground" />
|
||||
</div>
|
||||
|
||||
<div className="divide-y divide-border">
|
||||
{/* Android */}
|
||||
<div className="flex items-center justify-between py-5 first:pt-0 last:pb-0">
|
||||
<div className="flex items-center gap-3">
|
||||
<AndroidIcon className="h-5 w-5 text-foreground" />
|
||||
<span className="font-medium">Android</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<DownloadPill
|
||||
href="https://play.google.com/store/apps/details?id=sh.paseo"
|
||||
label="Play Store"
|
||||
external
|
||||
/>
|
||||
<DownloadPill
|
||||
href={`${releaseBase}/paseo-${desktopVersion}.apk`}
|
||||
label="APK"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* iOS */}
|
||||
<div className="flex items-center justify-between py-5 first:pt-0 last:pb-0">
|
||||
<div className="flex items-center gap-3">
|
||||
<AppleIcon className="h-5 w-5 text-foreground" />
|
||||
<span className="font-medium">iOS</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<DownloadPill
|
||||
href="https://apps.apple.com/app/paseo-pocket-engineer/id6758887924"
|
||||
label="App Store"
|
||||
external
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Web & CLI */}
|
||||
<section className="rounded-xl border border-border bg-card/40 p-6 md:p-8">
|
||||
<div className="flex items-center justify-between mb-8">
|
||||
<h2 className="text-2xl font-semibold">Web & CLI</h2>
|
||||
<TerminalIcon className="h-5 w-5 text-muted-foreground" />
|
||||
</div>
|
||||
|
||||
<div className="divide-y divide-border">
|
||||
<div className="flex items-center justify-between py-5 first:pt-0 last:pb-0">
|
||||
<div className="flex items-center gap-3">
|
||||
<GlobeIcon className="h-5 w-5 text-foreground" />
|
||||
<span className="font-medium">Web App</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<DownloadPill
|
||||
href="https://app.paseo.sh"
|
||||
label="Open"
|
||||
external
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between py-5 first:pt-0 last:pb-0">
|
||||
<div className="flex items-center gap-3">
|
||||
<TerminalIcon className="h-5 w-5 text-foreground" />
|
||||
<span className="font-medium">CLI</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<code className="text-sm text-muted-foreground font-mono bg-muted px-3 py-1.5 rounded-lg">
|
||||
npx paseo
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<p className="text-center text-xs text-muted-foreground mt-8">
|
||||
All releases are available on{" "}
|
||||
<a
|
||||
href="https://github.com/getpaseo/paseo/releases"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="underline hover:text-foreground transition-colors"
|
||||
>
|
||||
GitHub
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function DownloadPill({
|
||||
href,
|
||||
label,
|
||||
external,
|
||||
}: {
|
||||
href: string;
|
||||
label: string;
|
||||
external?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center justify-center rounded-full bg-foreground px-4 py-1.5 text-sm font-medium text-background hover:bg-foreground/85 transition-colors"
|
||||
>
|
||||
{label}
|
||||
{external && (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="ml-1.5 h-3 w-3"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M7 17L17 7" />
|
||||
<path d="M7 7h10v10" />
|
||||
</svg>
|
||||
)}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
function AppleIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<path d="M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function AndroidIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 512 512"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<path d="M380.91,199l42.47-73.57a8.63,8.63,0,0,0-3.12-11.76,8.52,8.52,0,0,0-11.71,3.12l-43,74.52c-32.83-15-69.78-23.35-109.52-23.35s-76.69,8.36-109.52,23.35l-43-74.52a8.6,8.6,0,1,0-14.88,8.64L131,199C57.8,238.64,8.19,312.77,0,399.55H512C503.81,312.77,454.2,238.64,380.91,199ZM138.45,327.65a21.46,21.46,0,1,1,21.46-21.46A21.47,21.47,0,0,1,138.45,327.65Zm235,0A21.46,21.46,0,1,1,395,306.19,21.47,21.47,0,0,1,373.49,327.65Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function WindowsIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<path d="M0,0H11.377V11.372H0ZM12.623,0H24V11.372H12.623ZM0,12.623H11.377V24H0Zm12.623,0H24V24H12.623" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function LinuxIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<path d="M8.996 4.497c.104-.076.1-.168.186-.158s.022.102-.098.207c-.12.104-.308.243-.46.323-.291.152-.631.336-.993.336s-.647-.167-.853-.33c-.102-.082-.186-.162-.248-.221-.11-.086-.096-.207-.052-.204.075.01.087.109.134.153.064.06.144.137.241.214.195.154.454.304.778.304s.702-.19.932-.32c.13-.073.297-.204.433-.304M7.34 3.781c.055-.02.123-.031.174-.003.011.006.024.021.02.034-.012.038-.074.032-.11.05-.032.017-.057.052-.093.054-.034 0-.086-.012-.09-.046-.007-.044.058-.072.1-.089m.581-.003c.05-.028.119-.018.173.003.041.017.106.045.1.09-.004.033-.057.046-.09.045-.036-.002-.062-.037-.093-.053-.036-.019-.098-.013-.11-.051-.004-.013.008-.028.02-.034" />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M8.446.019c2.521.003 2.38 2.66 2.364 4.093-.01.939.509 1.574 1.04 2.244.474.56 1.095 1.38 1.45 2.32.29.765.402 1.613.115 2.465a.8.8 0 0 1 .254.152l.001.002c.207.175.271.447.329.698.058.252.112.488.224.615.344.382.494.667.48.922-.015.254-.203.43-.435.57-.465.28-1.164.491-1.586 1.002-.443.527-.99.83-1.505.871a1.25 1.25 0 0 1-1.256-.716v-.001a1 1 0 0 1-.078-.21c-.67.038-1.252-.165-1.718-.128-.687.038-1.116.204-1.506.206-.151.331-.445.547-.808.63-.5.114-1.126 0-1.743-.324-.577-.306-1.31-.278-1.85-.39-.27-.057-.51-.157-.626-.384-.116-.226-.095-.538.07-.988.051-.16.012-.398-.026-.648a2.5 2.5 0 0 1-.037-.369c0-.133.022-.265.087-.386v-.002c.14-.266.368-.377.577-.451s.397-.125.53-.258c.143-.15.27-.374.443-.56q.036-.037.073-.07c-.081-.538.007-1.105.192-1.662.393-1.18 1.223-2.314 1.811-3.014.502-.713.65-1.287.701-2.016.042-.997-.705-3.974 2.112-4.2q.168-.015.321-.013m2.596 10.866-.03.016c-.223.121-.348.337-.427.656-.08.32-.107.733-.13 1.206v.001c-.023.37-.192.824-.31 1.267s-.176.862-.036 1.128v.002c.226.452.608.636 1.051.601s.947-.304 1.36-.795c.474-.576 1.218-.796 1.638-1.05.21-.126.324-.242.333-.4.009-.157-.097-.403-.425-.767-.17-.192-.217-.462-.274-.71-.056-.247-.122-.468-.26-.585l-.001-.001c-.18-.157-.356-.17-.565-.164q-.069.001-.14.005c-.239.275-.805.612-1.197.508-.359-.09-.562-.508-.587-.918m-7.204.03H3.83c-.189.002-.314.09-.44.225-.149.158-.276.382-.445.56v.002h-.002c-.183.184-.414.239-.61.31-.195.069-.353.143-.46.35v.002c-.085.155-.066.378-.029.624.038.245.096.507.018.746v.002l-.001.002c-.157.427-.155.678-.082.822.074.143.235.22.48.272.493.103 1.26.069 1.906.41.583.305 1.168.404 1.598.305.431-.098.712-.369.75-.867v-.002c.029-.292-.195-.673-.485-1.052-.29-.38-.633-.752-.795-1.09v-.002l-.61-1.11c-.21-.286-.43-.462-.68-.5a1 1 0 0 0-.106-.008M9.584 4.85c-.14.2-.386.37-.695.467-.147.048-.302.17-.495.28a1.3 1.3 0 0 1-.74.19.97.97 0 0 1-.582-.227c-.14-.113-.25-.237-.394-.322a3 3 0 0 1-.192-.126c-.063 1.179-.85 2.658-1.226 3.511a5.4 5.4 0 0 0-.43 1.917c-.68-.906-.184-2.066.081-2.568.297-.55.343-.701.27-.649-.266.436-.685 1.13-.848 1.844-.085.372-.1.749.01 1.097.11.349.345.67.766.931.573.351.963.703 1.193 1.015s.302.584.23.777a.4.4 0 0 1-.212.22.7.7 0 0 1-.307.056l.184.235c.094.124.186.249.266.375 1.179.805 2.567.496 3.568-.218.1-.342.197-.664.212-.903.024-.474.05-.896.136-1.245s.244-.634.53-.791a1 1 0 0 1 .138-.061q.005-.045.013-.087c.082-.546.569-.572 1.18-.303.588.266.81.499.71.814h.13c.122-.398-.133-.69-.822-1.025l-.137-.06a2.35 2.35 0 0 0-.012-1.113c-.188-.79-.704-1.49-1.098-1.838-.072-.003-.065.06.081.203.363.333 1.156 1.532.727 2.644a1.2 1.2 0 0 0-.342-.043c-.164-.907-.543-1.66-.735-2.014-.359-.668-.918-2.036-1.158-2.983M7.72 3.503a1 1 0 0 0-.312.053c-.268.093-.447.286-.559.391-.022.021-.05.04-.119.091s-.172.126-.321.238q-.198.151-.13.38c.046.15.192.325.459.476.166.098.28.23.41.334a1 1 0 0 0 .215.133.9.9 0 0 0 .298.066c.282.017.49-.068.673-.173s.34-.233.518-.29c.365-.115.627-.345.709-.564a.37.37 0 0 0-.01-.309c-.048-.096-.148-.187-.318-.257h-.001c-.354-.151-.507-.162-.705-.29-.321-.207-.587-.28-.807-.279m-.89-1.122h-.025a.4.4 0 0 0-.278.135.76.76 0 0 0-.191.334 1.2 1.2 0 0 0-.051.445v.001c.01.162.041.299.102.436.05.116.109.204.183.274l.089-.065.117-.09-.023-.018a.4.4 0 0 1-.11-.161.7.7 0 0 1-.054-.22v-.01a.7.7 0 0 1 .014-.234.4.4 0 0 1 .08-.179q.056-.069.126-.073h.013a.18.18 0 0 1 .123.05c.045.04.08.09.11.162a.7.7 0 0 1 .054.22v.01a.7.7 0 0 1-.002.17 1.1 1.1 0 0 1 .317-.143 1.3 1.3 0 0 0 .002-.194V3.23a1.2 1.2 0 0 0-.102-.437.8.8 0 0 0-.227-.31.4.4 0 0 0-.268-.102m1.95-.155a.63.63 0 0 0-.394.14.9.9 0 0 0-.287.376 1.2 1.2 0 0 0-.1.51v.015q0 .079.01.152c.114.027.278.074.406.138a1 1 0 0 1-.011-.172.8.8 0 0 1 .058-.278.5.5 0 0 1 .139-.2.26.26 0 0 1 .182-.069.26.26 0 0 1 .178.081c.055.054.094.12.124.21.029.086.042.17.04.27l-.002.012a.8.8 0 0 1-.057.277c-.024.059-.089.106-.122.145.046.016.09.03.146.052a5 5 0 0 1 .248.102 1.2 1.2 0 0 0 .244-.763 1.2 1.2 0 0 0-.11-.495.9.9 0 0 0-.294-.37.64.64 0 0 0-.39-.133z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function MonitorIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<rect width="20" height="14" x="2" y="3" rx="2" />
|
||||
<line x1="8" x2="16" y1="21" y2="21" />
|
||||
<line x1="12" x2="12" y1="17" y2="21" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function PhoneIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<rect width="14" height="20" x="5" y="2" rx="2" ry="2" />
|
||||
<path d="M12 18h.01" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function TerminalIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<polyline points="4 17 10 11 4 5" />
|
||||
<line x1="12" x2="20" y1="19" y2="19" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function GlobeIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<circle cx="12" cy="12" r="9" />
|
||||
<path d="M3 12h18" />
|
||||
<path d="M12 3a15 15 0 0 1 0 18" />
|
||||
<path d="M12 3a15 15 0 0 0 0 18" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env node
|
||||
// Workaround for https://github.com/npm/cli/issues/4460
|
||||
//
|
||||
// npm silently omits `resolved` and `integrity` fields from some
|
||||
// package-lock.json entries in workspace monorepos (especially for
|
||||
// workspace-hoisted packages). npm acknowledged this as a bug in 2022
|
||||
// but has never shipped a fix.
|
||||
//
|
||||
// This is harmless for regular `npm ci`, but breaks offline installers
|
||||
// like Nix that need every entry to have a resolved URL + integrity hash
|
||||
// so they can pre-fetch all tarballs in a sandbox with no network access.
|
||||
//
|
||||
// This script finds incomplete entries and fills them in using `npm view`.
|
||||
// It's idempotent — running it on an already-complete lockfile is a no-op.
|
||||
//
|
||||
// See also: https://github.com/npm/cli/issues/4263
|
||||
// https://github.com/npm/cli/issues/6301
|
||||
//
|
||||
// Usage:
|
||||
// node scripts/fix-lockfile.mjs
|
||||
// node scripts/fix-lockfile.mjs path/to/package-lock.json
|
||||
|
||||
import fs from "fs";
|
||||
import { execSync } from "child_process";
|
||||
|
||||
const lockPath = process.argv[2] || "package-lock.json";
|
||||
const lock = JSON.parse(fs.readFileSync(lockPath, "utf8"));
|
||||
|
||||
// Collect workspace package roots (local packages, not from npm)
|
||||
const workspaceRoots = new Set();
|
||||
for (const [key, val] of Object.entries(lock.packages || {})) {
|
||||
if (val.link) {
|
||||
workspaceRoots.add(val.resolved || key);
|
||||
}
|
||||
}
|
||||
|
||||
let fixed = 0;
|
||||
|
||||
for (const [key, val] of Object.entries(lock.packages || {})) {
|
||||
if (
|
||||
!key || // root package
|
||||
key.startsWith("node_modules/") || // top-level (already has resolved)
|
||||
val.link || // workspace link entry
|
||||
(val.resolved && val.integrity) || // already complete
|
||||
!val.version || // no version to look up
|
||||
workspaceRoots.has(key) // workspace package root (local, not on npm)
|
||||
)
|
||||
continue;
|
||||
|
||||
const pkgName = key.replace(/.*node_modules\//, "");
|
||||
const version = val.version;
|
||||
|
||||
try {
|
||||
const info = JSON.parse(
|
||||
execSync(`npm view ${pkgName}@${version} --json dist`, {
|
||||
encoding: "utf8",
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
})
|
||||
);
|
||||
if (info.tarball && info.integrity) {
|
||||
val.resolved = info.tarball;
|
||||
val.integrity = info.integrity;
|
||||
fixed++;
|
||||
}
|
||||
} catch {
|
||||
console.error(`Warning: could not fetch info for ${pkgName}@${version}`);
|
||||
}
|
||||
}
|
||||
|
||||
fs.writeFileSync(lockPath, JSON.stringify(lock, null, 2) + "\n");
|
||||
|
||||
if (fixed > 0) {
|
||||
console.log(`Fixed ${fixed} lockfile entries with missing resolved/integrity`);
|
||||
} else {
|
||||
console.log("Lockfile is already complete");
|
||||
}
|
||||
Executable
+63
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env bash
|
||||
# Fix workspace-local lockfile entries and update the Nix dependency hash.
|
||||
# Requires: node, npm, nix
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/update-nix.sh # fix lockfile + update hash
|
||||
# ./scripts/update-nix.sh --check # verify everything is up to date (CI mode)
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
LOCK_FILE="$ROOT_DIR/package-lock.json"
|
||||
PACKAGE_NIX="$ROOT_DIR/nix/package.nix"
|
||||
|
||||
CHECK_MODE=false
|
||||
if [[ "${1:-}" == "--check" ]]; then
|
||||
CHECK_MODE=true
|
||||
fi
|
||||
|
||||
# 1. Fix lockfile (add resolved/integrity for workspace-local entries)
|
||||
# Workaround for https://github.com/npm/cli/issues/4460
|
||||
echo "Fixing lockfile..."
|
||||
node "$SCRIPT_DIR/fix-lockfile.mjs" "$LOCK_FILE"
|
||||
|
||||
# 2. Prefetch deps and compute hash
|
||||
echo "Prefetching npm dependencies..."
|
||||
|
||||
# Resolve prefetch-npm-deps from the same nixpkgs pinned in flake.lock
|
||||
NIXPKGS_URL="$(node -p "
|
||||
const l = JSON.parse(require('fs').readFileSync('$ROOT_DIR/flake.lock', 'utf8'));
|
||||
const n = l.nodes.nixpkgs.locked;
|
||||
'github:' + n.owner + '/' + n.repo + '/' + n.rev;
|
||||
")"
|
||||
|
||||
STDERR_LOG="$(mktemp)"
|
||||
trap "rm -f '$STDERR_LOG'" EXIT
|
||||
|
||||
if ! NEW_HASH="$(nix shell "${NIXPKGS_URL}#prefetch-npm-deps" -c prefetch-npm-deps "$LOCK_FILE" 2>"$STDERR_LOG")"; then
|
||||
echo "ERROR: prefetch-npm-deps failed:" >&2
|
||||
tail -20 "$STDERR_LOG" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Computed hash: $NEW_HASH"
|
||||
|
||||
# 3. Read current hash
|
||||
CURRENT_HASH="$(grep 'npmDepsHash' "$PACKAGE_NIX" | sed 's/.*"\(.*\)".*/\1/')"
|
||||
|
||||
if [[ "$NEW_HASH" == "$CURRENT_HASH" ]]; then
|
||||
echo "Hash is already up to date."
|
||||
else
|
||||
if $CHECK_MODE; then
|
||||
echo "ERROR: npmDepsHash is stale."
|
||||
echo " current: $CURRENT_HASH"
|
||||
echo " correct: $NEW_HASH"
|
||||
echo "Run ./scripts/update-nix.sh to fix."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Updating npmDepsHash in nix/package.nix..."
|
||||
sed -i.bak "s|npmDepsHash = \".*\"|npmDepsHash = \"$NEW_HASH\"|" "$PACKAGE_NIX"
|
||||
rm -f "$PACKAGE_NIX.bak"
|
||||
echo "Updated: $CURRENT_HASH -> $NEW_HASH"
|
||||
fi
|
||||
Reference in New Issue
Block a user