offsite 0.0.11 → 0.0.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.
- package/README.md +15 -15
- package/apps/server/dist/main.mjs +298 -143
- package/apps/server/dist/main.mjs.map +1 -1
- package/apps/server/drizzle/20261017120143_device_tokens/migration.sql +15 -0
- package/apps/server/drizzle/20261017120143_device_tokens/snapshot.json +305 -0
- package/apps/web/dist/apple-touch-icon.png +0 -0
- package/apps/web/dist/assets/{angular-html-DTNmgvth.js → angular-html-CtL9u31h.js} +1 -1
- package/apps/web/dist/assets/{angular-ts-DOOLa99A.js → angular-ts-D4IFBcAh.js} +1 -1
- package/apps/web/dist/assets/{apl-CSWmNqJI.js → apl-CqnJhScR.js} +1 -1
- package/apps/web/dist/assets/{astro-CK7We5bo.js → astro-Dn1_avL_.js} +1 -1
- package/apps/web/dist/assets/{blade-B9Q94PMc.js → blade-BKSa6Bs3.js} +1 -1
- package/apps/web/dist/assets/{c-D05zRIsd.js → c-C8sbMt_8.js} +1 -1
- package/apps/web/dist/assets/{chapel-CJbjuVPR.js → chapel-BWxXvR2-.js} +1 -1
- package/apps/web/dist/assets/{cobol-CE0VhU2R.js → cobol-DNoVp--S.js} +1 -1
- package/apps/web/dist/assets/{coffee-C0i-RL39.js → coffee-m1P3e2U1.js} +1 -1
- package/apps/web/dist/assets/{cpp-CBJhfD_V.js → cpp-CDvshXRJ.js} +1 -1
- package/apps/web/dist/assets/{crystal-DRdgVTW1.js → crystal-C4b65AQe.js} +1 -1
- package/apps/web/dist/assets/{css-DgMip4qW.js → css-CqQV9eEE.js} +1 -1
- package/apps/web/dist/assets/{edge-BCmeW_3H.js → edge-CEdbZQ1U.js} +1 -1
- package/apps/web/dist/assets/{elixir-BiRSxKnc.js → elixir-CYDX6OgA.js} +1 -1
- package/apps/web/dist/assets/{elm-zBiqCp9W.js → elm-10RuU89D.js} +1 -1
- package/apps/web/dist/assets/{erb-1QviMyT9.js → erb-BiMfBUaM.js} +1 -1
- package/apps/web/dist/assets/{git-rebase-TjFhp_Gn.js → git-rebase-D4Se-3SO.js} +1 -1
- package/apps/web/dist/assets/{glimmer-js-dThwC_hZ.js → glimmer-js-sKOVTqrH.js} +1 -1
- package/apps/web/dist/assets/{glimmer-ts-cCThNW17.js → glimmer-ts-DWMGXcHA.js} +1 -1
- package/apps/web/dist/assets/{glsl-BxmyIQYG.js → glsl-6ltM2JGZ.js} +1 -1
- package/apps/web/dist/assets/{graphql-DygyczJj.js → graphql-DwyBbitO.js} +1 -1
- package/apps/web/dist/assets/{hack-Bl1_oIjY.js → hack-DKDbxv34.js} +1 -1
- package/apps/web/dist/assets/{haml-CeOBocB0.js → haml-Bsfivw5f.js} +1 -1
- package/apps/web/dist/assets/{handlebars-Bw1KMTCv.js → handlebars-B3HW0h8N.js} +1 -1
- package/apps/web/dist/assets/{html-BaNo1IFo.js → html-D9EhGmAq.js} +1 -1
- package/apps/web/dist/assets/{html-derivative-f4LhlsLO.js → html-derivative-Cs1eIi3k.js} +1 -1
- package/apps/web/dist/assets/{http-Be2FOF-U.js → http-BG4vxnc2.js} +1 -1
- package/apps/web/dist/assets/{hurl-BrtPQQhS.js → hurl-OIF0UEfX.js} +1 -1
- package/apps/web/dist/assets/index-CEx2Hw3M.js +1203 -0
- package/apps/web/dist/assets/{java-Bum5bcia.js → java-2iLSH1xc.js} +1 -1
- package/apps/web/dist/assets/{javascript-C5oYhZKk.js → javascript-Di7CdqTh.js} +1 -1
- package/apps/web/dist/assets/{jinja-CNXoe8pn.js → jinja-2mjIAN_z.js} +1 -1
- package/apps/web/dist/assets/{jison-g1hzxo4W.js → jison-Ciw1rqtP.js} +1 -1
- package/apps/web/dist/assets/{json-CiAAuc5j.js → json-D7Dp9EFU.js} +1 -1
- package/apps/web/dist/assets/{jsx-Z0awY4ip.js → jsx-WEelr3xJ.js} +1 -1
- package/apps/web/dist/assets/{julia-DX3JjwcM.js → julia-DL3m48KZ.js} +1 -1
- package/apps/web/dist/assets/{just-DS6eCX0g.js → just-B-5HaJEz.js} +1 -1
- package/apps/web/dist/assets/{latex-CaBic_pJ.js → latex-DKmfapvy.js} +1 -1
- package/apps/web/dist/assets/{liquid-DMaOBf3v.js → liquid-Bqxp18sH.js} +1 -1
- package/apps/web/dist/assets/{lua-DeLz5pfA.js → lua-BSLGBVbN.js} +1 -1
- package/apps/web/dist/assets/{marko-DqlzpgnR.js → marko-yLzCBGNR.js} +1 -1
- package/apps/web/dist/assets/{mdc-CRcSVpSn.js → mdc-DSGSIXIk.js} +1 -1
- package/apps/web/dist/assets/{nginx-BvszHPa-.js → nginx-BF1hRuVH.js} +1 -1
- package/apps/web/dist/assets/{nim-B19-Hebo.js → nim-kAH67IJv.js} +1 -1
- package/apps/web/dist/assets/{org-Dx7yMTgG.js → org-DCuW6Ito.js} +1 -1
- package/apps/web/dist/assets/{perl-C-dISCJW.js → perl-ChYkJruC.js} +1 -1
- package/apps/web/dist/assets/{php-BHBxrwsl.js → php-Dy0o5CHL.js} +1 -1
- package/apps/web/dist/assets/{pug-4a3x4vRs.js → pug-C-vFGcyR.js} +1 -1
- package/apps/web/dist/assets/{qml-CGOhzFqi.js → qml-Bbhsm3bz.js} +1 -1
- package/apps/web/dist/assets/{r-8jr0rtQL.js → r-D6AOSU_n.js} +1 -1
- package/apps/web/dist/assets/{razor-BDrURPIA.js → razor-CsDTm1QE.js} +1 -1
- package/apps/web/dist/assets/{regexp-Cg-MfP18.js → regexp-Cxw4Sy2X.js} +1 -1
- package/apps/web/dist/assets/{rst-0OrP55QZ.js → rst-BdEa7eTr.js} +1 -1
- package/apps/web/dist/assets/{ruby-dAApWLhj.js → ruby-CrX6uYJq.js} +1 -1
- package/apps/web/dist/assets/{sas-3_hKTjL9.js → sas-Dq1DiCgf.js} +1 -1
- package/apps/web/dist/assets/{scss-D6ollEQv.js → scss-Cq-_Yezx.js} +1 -1
- package/apps/web/dist/assets/{shellscript-V3H5Rwnn.js → shellscript-N1TdW_Vo.js} +1 -1
- package/apps/web/dist/assets/{shellsession-TxydKEoU.js → shellsession-DGy-5gMa.js} +1 -1
- package/apps/web/dist/assets/{soy-t3rHc1tL.js → soy-BZmv_HHk.js} +1 -1
- package/apps/web/dist/assets/{sql-B-NkeM8R.js → sql-B0TfXN6P.js} +1 -1
- package/apps/web/dist/assets/{stata-BW6neeVL.js → stata-D0cL92SE.js} +1 -1
- package/apps/web/dist/assets/{surrealql-CRBKdx6K.js → surrealql-B9RWrsWf.js} +1 -1
- package/apps/web/dist/assets/{svelte-DTTmtyn-.js → svelte-Erfh9F_0.js} +1 -1
- package/apps/web/dist/assets/{templ-CCBYQttY.js → templ-C1hdk1C3.js} +1 -1
- package/apps/web/dist/assets/{tex-D9XxqR5N.js → tex-B6y5Z0Oe.js} +1 -1
- package/apps/web/dist/assets/{ts-tags-CI4pWHnF.js → ts-tags-B8Q3bBJx.js} +1 -1
- package/apps/web/dist/assets/{tsx-DlfK3Vuf.js → tsx-CA2zlxVX.js} +1 -1
- package/apps/web/dist/assets/{twig-D-C2s1f2.js → twig-xr1-p8qn.js} +1 -1
- package/apps/web/dist/assets/{typescript-BnqYTwqM.js → typescript-Bdd7ljtG.js} +1 -1
- package/apps/web/dist/assets/{typst-DdTyavvE.js → typst-BGU6c2s1.js} +1 -1
- package/apps/web/dist/assets/{vue-zbXTTw4n.js → vue-CxnbGcMK.js} +1 -1
- package/apps/web/dist/assets/{vue-html-CIXNcZez.js → vue-html-D3pkBH4b.js} +1 -1
- package/apps/web/dist/assets/{vue-vine-Czo_W6u3.js → vue-vine-0adNns8e.js} +1 -1
- package/apps/web/dist/assets/{xml-DWv3jkXo.js → xml-Bf5yulh9.js} +1 -1
- package/apps/web/dist/assets/{xsl-BiggF1J3.js → xsl-DnGCH2C6.js} +1 -1
- package/apps/web/dist/assets/{yaml-D9oC3m1d.js → yaml-ALFPjwWA.js} +1 -1
- package/apps/web/dist/icon-192.png +0 -0
- package/apps/web/dist/icon-512.png +0 -0
- package/apps/web/dist/icon-maskable-512.png +0 -0
- package/apps/web/dist/icon.svg +4 -2
- package/apps/web/dist/index.html +3 -3
- package/apps/web/dist/manifest.webmanifest +1 -0
- package/apps/web/dist/sw.js +1 -1
- package/package.json +1 -1
- package/apps/web/dist/assets/index-NWUHLKte.js +0 -1203
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
1
2
|
import { chmodSync, closeSync, createReadStream, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, watch, writeFileSync } from "node:fs";
|
|
2
3
|
import { createServer } from "node:http";
|
|
3
4
|
import path from "node:path";
|
|
@@ -5,7 +6,6 @@ import { consola } from "consola";
|
|
|
5
6
|
import { WebSocketServer } from "ws";
|
|
6
7
|
import os from "node:os";
|
|
7
8
|
import { deleteSession, forkSession, getSessionMessages, getSubagentMessages, listSessions, query, renameSession } from "@anthropic-ai/claude-agent-sdk";
|
|
8
|
-
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
9
9
|
import { execFile, spawn } from "node:child_process";
|
|
10
10
|
import readline from "node:readline";
|
|
11
11
|
import { z } from "zod";
|
|
@@ -13,7 +13,7 @@ import { Command } from "@commander-js/extra-typings";
|
|
|
13
13
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
14
14
|
import { OpenCode } from "@opencode/client";
|
|
15
15
|
import { ensure, headers } from "@opencode/client/service";
|
|
16
|
-
import { and, eq, inArray, sql } from "drizzle-orm";
|
|
16
|
+
import { and, eq, gt, inArray, lte, sql } from "drizzle-orm";
|
|
17
17
|
import { sumBy } from "es-toolkit";
|
|
18
18
|
import { DatabaseSync } from "node:sqlite";
|
|
19
19
|
import { drizzle } from "drizzle-orm/node-sqlite";
|
|
@@ -127,7 +127,7 @@ var AppServer = class {
|
|
|
127
127
|
this.ready = this.call("initialize", {
|
|
128
128
|
clientInfo: {
|
|
129
129
|
name: "offsite",
|
|
130
|
-
title: "
|
|
130
|
+
title: "OffSite",
|
|
131
131
|
version: "0.0.0"
|
|
132
132
|
},
|
|
133
133
|
capabilities: {
|
|
@@ -698,12 +698,12 @@ function record$1(value) {
|
|
|
698
698
|
//#region src/args.ts
|
|
699
699
|
let command = "web";
|
|
700
700
|
let name = null;
|
|
701
|
-
const program = new Command("offsite").description("Starts the server and the web app").option("--host <host>", "Address to listen on", "127.0.0.1").option("--port <port>", "Port to listen on", "47841").option("--dir <dir>", "Folder for settings and accounts", path.join(os.homedir(), ".offsite")).option("--no-auth", "Accept connections without
|
|
701
|
+
const program = new Command("offsite").description("Starts the server and the web app").option("--host <host>", "Address to listen on", "127.0.0.1").option("--port <port>", "Port to listen on", "47841").option("--dir <dir>", "Folder for settings and accounts", path.join(os.homedir(), ".offsite")).option("--no-auth", "Accept connections without pairing").option("--no-pair", "Don't create a pairing code or print pairing links").option("--pwa", "Serve the web app as an installable app that works offline");
|
|
702
702
|
program.command("web", { isDefault: true }).description("Starts the server and the web app, same as offsite");
|
|
703
703
|
program.command("serve").description("Starts only the server, without the web app").action(() => {
|
|
704
704
|
command = "serve";
|
|
705
705
|
});
|
|
706
|
-
program.command("pair").description("Creates a pairing
|
|
706
|
+
program.command("pair").description("Creates a pairing code for another browser and prints its link").option("--name <name>", "Name to recognize this browser by").action((options) => {
|
|
707
707
|
command = "pair";
|
|
708
708
|
name = options.name ?? null;
|
|
709
709
|
});
|
|
@@ -788,58 +788,31 @@ const modelSetting = z.strictObject({
|
|
|
788
788
|
hidden: z.boolean(),
|
|
789
789
|
effort: z.string().nullable()
|
|
790
790
|
});
|
|
791
|
+
const posixDir = z.string().refine((value) => path.posix.isAbsolute(value), "must be an absolute path").nullable().default(null);
|
|
792
|
+
const platformDirs = z.strictObject({
|
|
793
|
+
macos: posixDir,
|
|
794
|
+
linux: posixDir,
|
|
795
|
+
windows: z.string().refine((value) => path.win32.isAbsolute(value), "must be an absolute path").nullable().default(null)
|
|
796
|
+
}).prefault({});
|
|
797
|
+
function accountSchema(provider) {
|
|
798
|
+
return z.strictObject({
|
|
799
|
+
id: z.string(),
|
|
800
|
+
provider: z.literal(provider),
|
|
801
|
+
name: z.string(),
|
|
802
|
+
configDir: platformDirs,
|
|
803
|
+
enabled: z.boolean().default(true),
|
|
804
|
+
hiddenModels: z.record(z.string(), z.boolean()).default({})
|
|
805
|
+
});
|
|
806
|
+
}
|
|
791
807
|
const configSchema = z.strictObject({
|
|
792
|
-
claudeBaseDir:
|
|
808
|
+
claudeBaseDir: platformDirs,
|
|
809
|
+
claudeAccounts: z.array(accountSchema("claude")).default([]),
|
|
810
|
+
codexAccounts: z.array(accountSchema("codex")).default([]),
|
|
793
811
|
enabledProviders: z.strictObject({
|
|
794
812
|
claude: z.boolean().default(true),
|
|
795
813
|
codex: z.boolean().default(true),
|
|
796
814
|
opencode: z.boolean().default(true)
|
|
797
815
|
}).prefault({}),
|
|
798
|
-
composer: z.strictObject({
|
|
799
|
-
provider: z.enum([
|
|
800
|
-
"claude",
|
|
801
|
-
"codex",
|
|
802
|
-
"opencode"
|
|
803
|
-
]).default("claude"),
|
|
804
|
-
claude: z.strictObject({
|
|
805
|
-
accountId: z.string().nullable().default(null),
|
|
806
|
-
model: z.string().nullable().default(null),
|
|
807
|
-
effort: z.enum([
|
|
808
|
-
"low",
|
|
809
|
-
"medium",
|
|
810
|
-
"high",
|
|
811
|
-
"xhigh",
|
|
812
|
-
"max"
|
|
813
|
-
]).nullable().default(null),
|
|
814
|
-
mode: z.enum([
|
|
815
|
-
"default",
|
|
816
|
-
"acceptEdits",
|
|
817
|
-
"bypassPermissions",
|
|
818
|
-
"plan",
|
|
819
|
-
"dontAsk",
|
|
820
|
-
"auto"
|
|
821
|
-
]).nullable().default("bypassPermissions"),
|
|
822
|
-
plan: z.boolean().default(false)
|
|
823
|
-
}).prefault({}),
|
|
824
|
-
codex: z.strictObject({
|
|
825
|
-
accountId: z.string().nullable().default(null),
|
|
826
|
-
model: z.string().nullable().default(null),
|
|
827
|
-
effort: z.string().nullable().default(null),
|
|
828
|
-
mode: z.enum([
|
|
829
|
-
"read-only",
|
|
830
|
-
"auto",
|
|
831
|
-
"full-access"
|
|
832
|
-
]).nullable().default("auto"),
|
|
833
|
-
plan: z.boolean().default(false)
|
|
834
|
-
}).prefault({}),
|
|
835
|
-
opencode: z.strictObject({
|
|
836
|
-
accountId: z.null().default(null),
|
|
837
|
-
model: z.string().nullable().default(null),
|
|
838
|
-
effort: z.string().nullable().default(null),
|
|
839
|
-
mode: z.string().nullable().default(null),
|
|
840
|
-
plan: z.boolean().default(false)
|
|
841
|
-
}).prefault({})
|
|
842
|
-
}).prefault({}),
|
|
843
816
|
commitMessage: z.strictObject({
|
|
844
817
|
provider: z.enum([
|
|
845
818
|
"claude",
|
|
@@ -876,13 +849,16 @@ const configSchema = z.strictObject({
|
|
|
876
849
|
}).prefault({})
|
|
877
850
|
}).prefault({})
|
|
878
851
|
});
|
|
879
|
-
const defaults = configSchema.parse({});
|
|
852
|
+
const defaults$1 = configSchema.parse({});
|
|
880
853
|
function platform() {
|
|
881
854
|
if (process.platform === "darwin") return "macos";
|
|
882
855
|
if (process.platform === "linux") return "linux";
|
|
883
856
|
if (process.platform === "win32") return "windows";
|
|
884
857
|
throw new Error(`unsupported platform ${process.platform}`);
|
|
885
858
|
}
|
|
859
|
+
function claudeBaseDir() {
|
|
860
|
+
return readConfig().claudeBaseDir[platform()] ?? path.join(os.homedir(), ".claude");
|
|
861
|
+
}
|
|
886
862
|
function defaultShell() {
|
|
887
863
|
if (process.platform === "win32") return process.env.COMSPEC ?? "cmd.exe";
|
|
888
864
|
return os.userInfo().shell ?? "/bin/sh";
|
|
@@ -890,9 +866,10 @@ function defaultShell() {
|
|
|
890
866
|
function configInfo() {
|
|
891
867
|
return {
|
|
892
868
|
config: readConfig(),
|
|
893
|
-
defaults,
|
|
869
|
+
defaults: defaults$1,
|
|
894
870
|
platform: platform(),
|
|
895
|
-
defaultShell: defaultShell()
|
|
871
|
+
defaultShell: defaultShell(),
|
|
872
|
+
defaultClaudeBaseDir: path.join(os.homedir(), ".claude")
|
|
896
873
|
};
|
|
897
874
|
}
|
|
898
875
|
function readConfig() {
|
|
@@ -901,13 +878,13 @@ function readConfig() {
|
|
|
901
878
|
user = readJson("config.json");
|
|
902
879
|
} catch (error) {
|
|
903
880
|
console.warn(`invalid ${configFile}, using the default config: ${error}`);
|
|
904
|
-
return structuredClone(defaults);
|
|
881
|
+
return structuredClone(defaults$1);
|
|
905
882
|
}
|
|
906
|
-
if (user === null) return structuredClone(defaults);
|
|
883
|
+
if (user === null) return structuredClone(defaults$1);
|
|
907
884
|
const result = configSchema.safeParse(user);
|
|
908
885
|
if (!result.success) {
|
|
909
886
|
console.warn(`invalid ${configFile}, using the default config:\n${z.prettifyError(result.error)}`);
|
|
910
|
-
return structuredClone(defaults);
|
|
887
|
+
return structuredClone(defaults$1);
|
|
911
888
|
}
|
|
912
889
|
return result.data;
|
|
913
890
|
}
|
|
@@ -915,7 +892,7 @@ function saveConfig(value) {
|
|
|
915
892
|
const result = configSchema.safeParse(value);
|
|
916
893
|
if (!result.success) throw new Error(z.prettifyError(result.error));
|
|
917
894
|
writeJson("config.json", result.data);
|
|
918
|
-
process.env.CLAUDE_CONFIG_DIR =
|
|
895
|
+
process.env.CLAUDE_CONFIG_DIR = claudeBaseDir();
|
|
919
896
|
broadcast({
|
|
920
897
|
type: "config",
|
|
921
898
|
config: result.data
|
|
@@ -2423,10 +2400,16 @@ const sessions$1 = snakeCase.table("sessions", {
|
|
|
2423
2400
|
"minimized"
|
|
2424
2401
|
] }).notNull()
|
|
2425
2402
|
});
|
|
2426
|
-
const
|
|
2403
|
+
const pairingCodes = snakeCase.table("pairing_codes", {
|
|
2404
|
+
hash: text().primaryKey(),
|
|
2405
|
+
name: text(),
|
|
2406
|
+
expiresAt: integer().notNull()
|
|
2407
|
+
});
|
|
2408
|
+
const devices = snakeCase.table("devices", {
|
|
2427
2409
|
hash: text().primaryKey(),
|
|
2428
2410
|
name: text(),
|
|
2429
2411
|
createdAt: integer().notNull(),
|
|
2412
|
+
expiresAt: integer().notNull(),
|
|
2430
2413
|
lastUsedAt: integer()
|
|
2431
2414
|
});
|
|
2432
2415
|
const pushSubscriptions = snakeCase.table("push_subscriptions", {
|
|
@@ -2717,7 +2700,7 @@ async function find(provider, paths) {
|
|
|
2717
2700
|
}
|
|
2718
2701
|
function watchSessions() {
|
|
2719
2702
|
for (const watcher of watchers) watcher.close();
|
|
2720
|
-
watchers = [["claude", path.join(
|
|
2703
|
+
watchers = [["claude", path.join(claudeBaseDir(), "projects")], ["codex", path.join(codexBaseDir, "sessions")]].flatMap(([provider, dir]) => {
|
|
2721
2704
|
if (!readConfig().enabledProviders[provider]) return [];
|
|
2722
2705
|
try {
|
|
2723
2706
|
const watcher = watch(dir, { recursive: true }, () => refreshSessions(provider));
|
|
@@ -2913,14 +2896,15 @@ function providerName(provider) {
|
|
|
2913
2896
|
return providerNames[provider];
|
|
2914
2897
|
}
|
|
2915
2898
|
function storedAccounts() {
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2899
|
+
const config = readConfig();
|
|
2900
|
+
return [...config.claudeAccounts, ...config.codexAccounts].flatMap((item) => {
|
|
2901
|
+
const configDir = item.configDir[platform()];
|
|
2902
|
+
if (configDir === null) return [];
|
|
2903
|
+
return [{
|
|
2904
|
+
...item,
|
|
2905
|
+
configDir
|
|
2906
|
+
}];
|
|
2907
|
+
});
|
|
2924
2908
|
}
|
|
2925
2909
|
function assertEnabled(provider, accountId) {
|
|
2926
2910
|
if (!readConfig().enabledProviders[provider]) throw new Error(`${providerName(provider)} is disabled in Settings`);
|
|
@@ -3019,29 +3003,50 @@ function providerCapabilities(provider, accountId, cwd) {
|
|
|
3019
3003
|
}
|
|
3020
3004
|
}
|
|
3021
3005
|
function addAccount(provider, name, configDir) {
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3006
|
+
const account = validAccount(name, configDir);
|
|
3007
|
+
const config = readConfig();
|
|
3008
|
+
const key = `${provider}Accounts`;
|
|
3009
|
+
saveConfig({
|
|
3010
|
+
...config,
|
|
3011
|
+
[key]: [...config[key], {
|
|
3012
|
+
id: randomUUID(),
|
|
3013
|
+
provider,
|
|
3014
|
+
name: account.name,
|
|
3015
|
+
configDir: { [platform()]: account.configDir },
|
|
3016
|
+
enabled: true,
|
|
3017
|
+
hiddenModels: {}
|
|
3018
|
+
}]
|
|
3019
|
+
});
|
|
3029
3020
|
}
|
|
3030
3021
|
async function updateAccount(update) {
|
|
3031
3022
|
const before = storedAccounts().find((item) => item.id === update.id);
|
|
3032
3023
|
if (before === void 0) throw new Error("That account was removed");
|
|
3033
3024
|
const account = validAccount(update.name, update.configDir);
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3025
|
+
const config = readConfig();
|
|
3026
|
+
const key = `${before.provider}Accounts`;
|
|
3027
|
+
saveConfig({
|
|
3028
|
+
...config,
|
|
3029
|
+
[key]: config[key].map((item) => item.id === update.id ? {
|
|
3030
|
+
...item,
|
|
3031
|
+
name: account.name,
|
|
3032
|
+
configDir: {
|
|
3033
|
+
...item.configDir,
|
|
3034
|
+
[platform()]: account.configDir
|
|
3035
|
+
},
|
|
3036
|
+
enabled: update.enabled,
|
|
3037
|
+
hiddenModels: update.hiddenModels
|
|
3038
|
+
} : item)
|
|
3039
|
+
});
|
|
3040
3040
|
forgetClaude(update.id);
|
|
3041
3041
|
if (before.enabled && !update.enabled) await stopAccount(before);
|
|
3042
3042
|
}
|
|
3043
3043
|
function removeAccount(id) {
|
|
3044
|
-
|
|
3044
|
+
const config = readConfig();
|
|
3045
|
+
saveConfig({
|
|
3046
|
+
...config,
|
|
3047
|
+
claudeAccounts: config.claudeAccounts.filter((item) => item.id !== id),
|
|
3048
|
+
codexAccounts: config.codexAccounts.filter((item) => item.id !== id)
|
|
3049
|
+
});
|
|
3045
3050
|
forgetClaude(id);
|
|
3046
3051
|
}
|
|
3047
3052
|
function validAccount(name, configDir) {
|
|
@@ -3057,9 +3062,6 @@ function validAccount(name, configDir) {
|
|
|
3057
3062
|
};
|
|
3058
3063
|
throw new Error("Use an absolute config directory or one starting with ~/");
|
|
3059
3064
|
}
|
|
3060
|
-
function saveAccounts(items) {
|
|
3061
|
-
writeJson("accounts.json", items);
|
|
3062
|
-
}
|
|
3063
3065
|
//#endregion
|
|
3064
3066
|
//#region src/lib/claude/claude.ts
|
|
3065
3067
|
const efforts = [
|
|
@@ -3233,7 +3235,7 @@ async function recordUsage(accountId, probe) {
|
|
|
3233
3235
|
}
|
|
3234
3236
|
}
|
|
3235
3237
|
function isLinked(configDir) {
|
|
3236
|
-
const base =
|
|
3238
|
+
const base = claudeBaseDir();
|
|
3237
3239
|
try {
|
|
3238
3240
|
if (realpathSync(configDir) === realpathSync(base)) return true;
|
|
3239
3241
|
return realpathSync(path.join(configDir, "projects")) === realpathSync(path.join(base, "projects"));
|
|
@@ -3689,7 +3691,7 @@ function text$1(value) {
|
|
|
3689
3691
|
//#endregion
|
|
3690
3692
|
//#region src/lib/claude/history.ts
|
|
3691
3693
|
async function sessionFile(sessionId) {
|
|
3692
|
-
const projectsDir = path.join(
|
|
3694
|
+
const projectsDir = path.join(claudeBaseDir(), "projects");
|
|
3693
3695
|
for (const dir of await readdir(projectsDir)) {
|
|
3694
3696
|
const file = path.join(projectsDir, dir, `${sessionId}.jsonl`);
|
|
3695
3697
|
try {
|
|
@@ -5618,6 +5620,81 @@ function send(connection, event) {
|
|
|
5618
5620
|
});
|
|
5619
5621
|
}
|
|
5620
5622
|
//#endregion
|
|
5623
|
+
//#region src/storage/state.ts
|
|
5624
|
+
const stateFile = path.join(home, "state.json");
|
|
5625
|
+
const stateSchema = z.strictObject({ composer: z.strictObject({
|
|
5626
|
+
provider: z.enum([
|
|
5627
|
+
"claude",
|
|
5628
|
+
"codex",
|
|
5629
|
+
"opencode"
|
|
5630
|
+
]).default("claude"),
|
|
5631
|
+
claude: z.strictObject({
|
|
5632
|
+
accountId: z.string().nullable().default(null),
|
|
5633
|
+
model: z.string().nullable().default(null),
|
|
5634
|
+
effort: z.enum([
|
|
5635
|
+
"low",
|
|
5636
|
+
"medium",
|
|
5637
|
+
"high",
|
|
5638
|
+
"xhigh",
|
|
5639
|
+
"max"
|
|
5640
|
+
]).nullable().default(null),
|
|
5641
|
+
mode: z.enum([
|
|
5642
|
+
"default",
|
|
5643
|
+
"acceptEdits",
|
|
5644
|
+
"bypassPermissions",
|
|
5645
|
+
"plan",
|
|
5646
|
+
"dontAsk",
|
|
5647
|
+
"auto"
|
|
5648
|
+
]).nullable().default("bypassPermissions"),
|
|
5649
|
+
plan: z.boolean().default(false)
|
|
5650
|
+
}).prefault({}),
|
|
5651
|
+
codex: z.strictObject({
|
|
5652
|
+
accountId: z.string().nullable().default(null),
|
|
5653
|
+
model: z.string().nullable().default(null),
|
|
5654
|
+
effort: z.string().nullable().default(null),
|
|
5655
|
+
mode: z.enum([
|
|
5656
|
+
"read-only",
|
|
5657
|
+
"auto",
|
|
5658
|
+
"full-access"
|
|
5659
|
+
]).nullable().default("auto"),
|
|
5660
|
+
plan: z.boolean().default(false)
|
|
5661
|
+
}).prefault({}),
|
|
5662
|
+
opencode: z.strictObject({
|
|
5663
|
+
accountId: z.null().default(null),
|
|
5664
|
+
model: z.string().nullable().default(null),
|
|
5665
|
+
effort: z.string().nullable().default(null),
|
|
5666
|
+
mode: z.string().nullable().default(null),
|
|
5667
|
+
plan: z.boolean().default(false)
|
|
5668
|
+
}).prefault({})
|
|
5669
|
+
}).prefault({}) });
|
|
5670
|
+
const defaults = stateSchema.parse({});
|
|
5671
|
+
function readComposer() {
|
|
5672
|
+
let user;
|
|
5673
|
+
try {
|
|
5674
|
+
user = readJson("state.json");
|
|
5675
|
+
} catch (error) {
|
|
5676
|
+
console.warn(`invalid ${stateFile}, using the default state: ${error}`);
|
|
5677
|
+
return structuredClone(defaults.composer);
|
|
5678
|
+
}
|
|
5679
|
+
if (user === null) return structuredClone(defaults.composer);
|
|
5680
|
+
const result = stateSchema.safeParse(user);
|
|
5681
|
+
if (!result.success) {
|
|
5682
|
+
console.warn(`invalid ${stateFile}, using the default state:\n${z.prettifyError(result.error)}`);
|
|
5683
|
+
return structuredClone(defaults.composer);
|
|
5684
|
+
}
|
|
5685
|
+
return result.data.composer;
|
|
5686
|
+
}
|
|
5687
|
+
function saveComposer(value) {
|
|
5688
|
+
const result = stateSchema.safeParse({ composer: value });
|
|
5689
|
+
if (!result.success) throw new Error(z.prettifyError(result.error));
|
|
5690
|
+
writeJson("state.json", result.data);
|
|
5691
|
+
broadcast({
|
|
5692
|
+
type: "composer",
|
|
5693
|
+
composer: result.data.composer
|
|
5694
|
+
});
|
|
5695
|
+
return result.data.composer;
|
|
5696
|
+
}
|
|
5697
|
+
//#endregion
|
|
5621
5698
|
//#region src/api/socket.ts
|
|
5622
5699
|
const handlers = {
|
|
5623
5700
|
ping: () => null,
|
|
@@ -5625,6 +5702,7 @@ const handlers = {
|
|
|
5625
5702
|
refreshAll();
|
|
5626
5703
|
return {
|
|
5627
5704
|
...configInfo(),
|
|
5705
|
+
composer: readComposer(),
|
|
5628
5706
|
hostname: os.hostname(),
|
|
5629
5707
|
projects: projects$1(),
|
|
5630
5708
|
sessions: sessions(),
|
|
@@ -5633,6 +5711,8 @@ const handlers = {
|
|
|
5633
5711
|
};
|
|
5634
5712
|
},
|
|
5635
5713
|
"config.get": () => configInfo(),
|
|
5714
|
+
"composer.get": () => readComposer(),
|
|
5715
|
+
"composer.save": (params) => saveComposer(params),
|
|
5636
5716
|
"config.save": async (params) => {
|
|
5637
5717
|
const before = readConfig();
|
|
5638
5718
|
const saved = saveConfig(params);
|
|
@@ -5641,7 +5721,7 @@ const handlers = {
|
|
|
5641
5721
|
"codex",
|
|
5642
5722
|
"opencode"
|
|
5643
5723
|
]) if (before.enabledProviders[provider] && !saved.enabledProviders[provider]) await stopProvider(provider);
|
|
5644
|
-
if (saved.claudeBaseDir === before.claudeBaseDir && JSON.stringify(saved.enabledProviders) === JSON.stringify(before.enabledProviders)) return saved;
|
|
5724
|
+
if (JSON.stringify(saved.claudeBaseDir) === JSON.stringify(before.claudeBaseDir) && JSON.stringify(saved.enabledProviders) === JSON.stringify(before.enabledProviders)) return saved;
|
|
5645
5725
|
watchSessions();
|
|
5646
5726
|
refreshAll();
|
|
5647
5727
|
publishAccounts();
|
|
@@ -5821,38 +5901,58 @@ async function answer(client, text) {
|
|
|
5821
5901
|
}
|
|
5822
5902
|
}
|
|
5823
5903
|
//#endregion
|
|
5824
|
-
//#region src/storage/
|
|
5825
|
-
|
|
5826
|
-
|
|
5904
|
+
//#region src/storage/auth.ts
|
|
5905
|
+
const CODE_LIFETIME = 3e5;
|
|
5906
|
+
const DEVICE_LIFETIME = 2592e6;
|
|
5907
|
+
function addCode(name) {
|
|
5908
|
+
const code = randomBytes(16).toString("base64url");
|
|
5909
|
+
db.delete(pairingCodes).where(lte(pairingCodes.expiresAt, Date.now())).run();
|
|
5910
|
+
db.insert(pairingCodes).values({
|
|
5911
|
+
hash: hash(code),
|
|
5912
|
+
name,
|
|
5913
|
+
expiresAt: Date.now() + CODE_LIFETIME
|
|
5914
|
+
}).run();
|
|
5915
|
+
return code;
|
|
5827
5916
|
}
|
|
5828
|
-
function
|
|
5917
|
+
function redeemCode(code) {
|
|
5918
|
+
const found = db.delete(pairingCodes).where(eq(pairingCodes.hash, hash(code))).returning().get();
|
|
5919
|
+
if (found === void 0 || found.expiresAt <= Date.now()) return null;
|
|
5829
5920
|
const token = randomBytes(32).toString("base64url");
|
|
5830
|
-
|
|
5831
|
-
|
|
5832
|
-
|
|
5833
|
-
|
|
5921
|
+
const now = Date.now();
|
|
5922
|
+
db.insert(devices).values({
|
|
5923
|
+
hash: hash(token),
|
|
5924
|
+
name: found.name,
|
|
5925
|
+
createdAt: now,
|
|
5926
|
+
expiresAt: now + DEVICE_LIFETIME
|
|
5834
5927
|
}).run();
|
|
5835
5928
|
return token;
|
|
5836
5929
|
}
|
|
5837
|
-
function
|
|
5838
|
-
db.
|
|
5930
|
+
function findDevice(token) {
|
|
5931
|
+
const found = db.select().from(devices).where(and(eq(devices.hash, hash(token)), gt(devices.expiresAt, Date.now()))).get();
|
|
5932
|
+
return found === void 0 ? null : found.hash;
|
|
5933
|
+
}
|
|
5934
|
+
function isPaired(device) {
|
|
5935
|
+
return db.select().from(devices).where(and(eq(devices.hash, device), gt(devices.expiresAt, Date.now()))).get() !== void 0;
|
|
5839
5936
|
}
|
|
5840
|
-
function
|
|
5841
|
-
return db.select().from(
|
|
5937
|
+
function hasDevices() {
|
|
5938
|
+
return db.select().from(devices).where(gt(devices.expiresAt, Date.now())).limit(1).get() !== void 0;
|
|
5842
5939
|
}
|
|
5843
|
-
function
|
|
5844
|
-
|
|
5845
|
-
return db.select().from(tokens).where(eq(tokens.hash, hashToken(given))).get() !== void 0;
|
|
5940
|
+
function readDevices() {
|
|
5941
|
+
return db.select().from(devices).orderBy(devices.createdAt).all();
|
|
5846
5942
|
}
|
|
5847
|
-
function
|
|
5848
|
-
db.
|
|
5943
|
+
function removeDevices(hashes) {
|
|
5944
|
+
db.delete(devices).where(inArray(devices.hash, hashes)).run();
|
|
5849
5945
|
}
|
|
5850
|
-
function
|
|
5851
|
-
|
|
5946
|
+
function recordUse(device) {
|
|
5947
|
+
db.update(devices).set({ lastUsedAt: Date.now() }).where(eq(devices.hash, device)).run();
|
|
5948
|
+
}
|
|
5949
|
+
function hash(secret) {
|
|
5950
|
+
return createHash("sha256").update(secret).digest("hex");
|
|
5852
5951
|
}
|
|
5853
5952
|
//#endregion
|
|
5854
5953
|
//#region src/main.ts
|
|
5855
5954
|
const UNPAIR_CHECK = 5e3;
|
|
5955
|
+
const TICKET_LIFETIME = 6e4;
|
|
5856
5956
|
const contentTypes = {
|
|
5857
5957
|
".html": "text/html; charset=utf-8",
|
|
5858
5958
|
".js": "text/javascript; charset=utf-8",
|
|
@@ -5870,12 +5970,10 @@ if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
|
5870
5970
|
process.exit(1);
|
|
5871
5971
|
}
|
|
5872
5972
|
const address = `http://${args.host}:${port}`;
|
|
5873
|
-
const fallback = args.fallback === void 0 ? null : readFallback(args.fallback);
|
|
5874
5973
|
if (args.command === "pair") {
|
|
5875
|
-
const links = pairingLinks(
|
|
5876
|
-
console.log("Open this link in
|
|
5974
|
+
const links = pairingLinks(addCode(args.name));
|
|
5975
|
+
console.log("Open this link in a browser within 5 minutes to pair it:");
|
|
5877
5976
|
console.log(` ${links.local}`);
|
|
5878
|
-
if (fallback !== null) console.log(`Fallback: ${fallback}`);
|
|
5879
5977
|
process.exit(0);
|
|
5880
5978
|
}
|
|
5881
5979
|
if (args.command === "unpair") {
|
|
@@ -5883,7 +5981,7 @@ if (args.command === "unpair") {
|
|
|
5883
5981
|
process.exit(0);
|
|
5884
5982
|
}
|
|
5885
5983
|
lockHome();
|
|
5886
|
-
process.env.CLAUDE_CONFIG_DIR =
|
|
5984
|
+
process.env.CLAUDE_CONFIG_DIR = claudeBaseDir();
|
|
5887
5985
|
watchSessions();
|
|
5888
5986
|
refreshAll();
|
|
5889
5987
|
if (findClaude() === null) {
|
|
@@ -5891,9 +5989,14 @@ if (findClaude() === null) {
|
|
|
5891
5989
|
process.exit(1);
|
|
5892
5990
|
}
|
|
5893
5991
|
const publicDir = path.join(import.meta.dirname, "..", "..", "web", "dist");
|
|
5894
|
-
const
|
|
5992
|
+
const code = args.auth && args.pair && !hasDevices() ? addCode(null) : null;
|
|
5993
|
+
const tickets = /* @__PURE__ */ new Map();
|
|
5895
5994
|
const server = createServer((request, response) => {
|
|
5896
5995
|
const url = new URL(request.url ?? "/", "http://localhost");
|
|
5996
|
+
if (url.pathname.startsWith("/api/")) {
|
|
5997
|
+
answerApi(url.pathname, request, response);
|
|
5998
|
+
return;
|
|
5999
|
+
}
|
|
5897
6000
|
if (args.command === "serve") {
|
|
5898
6001
|
response.statusCode = 404;
|
|
5899
6002
|
response.end("offsite serve has no web app");
|
|
@@ -5905,26 +6008,27 @@ const sockets = new WebSocketServer({ noServer: true });
|
|
|
5905
6008
|
const paired = /* @__PURE__ */ new Map();
|
|
5906
6009
|
server.on("upgrade", (request, socket, head) => {
|
|
5907
6010
|
const url = new URL(request.url ?? "/", "http://localhost");
|
|
5908
|
-
const
|
|
5909
|
-
if (url.pathname !== "/ws" ||
|
|
6011
|
+
const device = args.auth ? redeemTicket(url.searchParams.get("ticket")) : null;
|
|
6012
|
+
if (url.pathname !== "/ws" || args.auth && device === null) {
|
|
5910
6013
|
socket.destroy();
|
|
5911
6014
|
return;
|
|
5912
6015
|
}
|
|
5913
6016
|
sockets.handleUpgrade(request, socket, head, (client) => {
|
|
5914
6017
|
connectClient(client);
|
|
5915
|
-
if (
|
|
5916
|
-
paired.set(client,
|
|
6018
|
+
if (device === null) return;
|
|
6019
|
+
paired.set(client, device);
|
|
5917
6020
|
client.on("close", () => paired.delete(client));
|
|
5918
|
-
recordUse(
|
|
6021
|
+
recordUse(device);
|
|
5919
6022
|
});
|
|
5920
6023
|
});
|
|
5921
6024
|
if (args.auth) setInterval(() => {
|
|
5922
|
-
for (const [client,
|
|
6025
|
+
for (const [client, device] of paired) if (!isPaired(device)) client.close();
|
|
6026
|
+
for (const [ticket, item] of tickets) if (item.expiresAt <= Date.now()) tickets.delete(ticket);
|
|
5923
6027
|
}, UNPAIR_CHECK);
|
|
5924
6028
|
server.listen(port, args.host, () => {
|
|
5925
6029
|
startPush(`${args.host}:${port}`);
|
|
5926
|
-
if (args.pair && (
|
|
5927
|
-
const links = pairingLinks(
|
|
6030
|
+
if (args.pair && (code !== null || !args.auth)) {
|
|
6031
|
+
const links = pairingLinks(code);
|
|
5928
6032
|
if (args.command === "web") console.log(`offsite is running at ${links.local}`);
|
|
5929
6033
|
} else {
|
|
5930
6034
|
console.log(`offsite is running at ${address}`);
|
|
@@ -5938,12 +6042,67 @@ function stop() {
|
|
|
5938
6042
|
stopAll();
|
|
5939
6043
|
process.exit(0);
|
|
5940
6044
|
}
|
|
5941
|
-
function
|
|
5942
|
-
|
|
6045
|
+
function answerApi(pathname, request, response) {
|
|
6046
|
+
response.setHeader("access-control-allow-origin", "*");
|
|
6047
|
+
if (request.method === "OPTIONS") {
|
|
6048
|
+
response.setHeader("access-control-allow-methods", "POST");
|
|
6049
|
+
response.setHeader("access-control-allow-headers", "authorization");
|
|
6050
|
+
response.statusCode = 204;
|
|
6051
|
+
response.end();
|
|
6052
|
+
return;
|
|
6053
|
+
}
|
|
6054
|
+
if (request.method !== "POST") {
|
|
6055
|
+
response.statusCode = 405;
|
|
6056
|
+
response.end("use POST");
|
|
6057
|
+
return;
|
|
6058
|
+
}
|
|
6059
|
+
const given = request.headers.authorization?.match(/^Bearer (.+)$/)?.[1] ?? null;
|
|
6060
|
+
if (pathname === "/api/pair") {
|
|
6061
|
+
const token = given === null ? null : redeemCode(given);
|
|
6062
|
+
if (token === null) {
|
|
6063
|
+
response.statusCode = 401;
|
|
6064
|
+
response.end("That pairing code expired or was already used");
|
|
6065
|
+
return;
|
|
6066
|
+
}
|
|
6067
|
+
answerJson(response, { token });
|
|
6068
|
+
return;
|
|
6069
|
+
}
|
|
6070
|
+
if (pathname === "/api/ticket") {
|
|
6071
|
+
if (!args.auth) {
|
|
6072
|
+
answerJson(response, { ticket: null });
|
|
6073
|
+
return;
|
|
6074
|
+
}
|
|
6075
|
+
const device = given === null ? null : findDevice(given);
|
|
6076
|
+
if (device === null) {
|
|
6077
|
+
response.statusCode = 401;
|
|
6078
|
+
response.end("This browser needs pairing");
|
|
6079
|
+
return;
|
|
6080
|
+
}
|
|
6081
|
+
const ticket = randomBytes(32).toString("base64url");
|
|
6082
|
+
tickets.set(ticket, {
|
|
6083
|
+
device,
|
|
6084
|
+
expiresAt: Date.now() + TICKET_LIFETIME
|
|
6085
|
+
});
|
|
6086
|
+
answerJson(response, { ticket });
|
|
6087
|
+
return;
|
|
6088
|
+
}
|
|
6089
|
+
response.statusCode = 404;
|
|
6090
|
+
response.end("not found");
|
|
6091
|
+
}
|
|
6092
|
+
function answerJson(response, body) {
|
|
6093
|
+
response.setHeader("content-type", "application/json");
|
|
6094
|
+
response.end(JSON.stringify(body));
|
|
6095
|
+
}
|
|
6096
|
+
function redeemTicket(ticket) {
|
|
6097
|
+
if (ticket === null) return null;
|
|
6098
|
+
const found = tickets.get(ticket);
|
|
6099
|
+
tickets.delete(ticket);
|
|
6100
|
+
if (found === void 0 || found.expiresAt <= Date.now()) return null;
|
|
6101
|
+
return found.device;
|
|
5943
6102
|
}
|
|
5944
6103
|
async function unpair() {
|
|
5945
|
-
const
|
|
5946
|
-
if (
|
|
6104
|
+
const devices = readDevices();
|
|
6105
|
+
if (devices.length === 0) {
|
|
5947
6106
|
console.log("No browsers are paired");
|
|
5948
6107
|
return;
|
|
5949
6108
|
}
|
|
@@ -5955,11 +6114,12 @@ async function unpair() {
|
|
|
5955
6114
|
type: "multiselect",
|
|
5956
6115
|
required: false,
|
|
5957
6116
|
cancel: "null",
|
|
5958
|
-
options:
|
|
6117
|
+
options: devices.map((item) => ({
|
|
5959
6118
|
value: item.hash,
|
|
5960
6119
|
label: [
|
|
5961
6120
|
item.name ?? "Unnamed",
|
|
5962
6121
|
`paired ${new Date(item.createdAt).toLocaleString()}`,
|
|
6122
|
+
`${item.expiresAt <= Date.now() ? "expired" : "expires"} ${new Date(item.expiresAt).toLocaleString()}`,
|
|
5963
6123
|
item.lastUsedAt === null ? "never used" : `last used ${new Date(item.lastUsedAt).toLocaleString()}`
|
|
5964
6124
|
].join(" · ")
|
|
5965
6125
|
}))
|
|
@@ -5968,30 +6128,21 @@ async function unpair() {
|
|
|
5968
6128
|
console.log("Nothing unpaired");
|
|
5969
6129
|
return;
|
|
5970
6130
|
}
|
|
5971
|
-
|
|
6131
|
+
removeDevices(picked);
|
|
5972
6132
|
console.log(`Unpaired ${picked.length} ${picked.length === 1 ? "browser" : "browsers"}; a running offsite disconnects them within seconds`);
|
|
5973
6133
|
}
|
|
5974
|
-
function
|
|
5975
|
-
let url;
|
|
5976
|
-
try {
|
|
5977
|
-
url = new URL(link);
|
|
5978
|
-
} catch {
|
|
5979
|
-
console.error(`invalid fallback URL ${link}`);
|
|
5980
|
-
process.exit(1);
|
|
5981
|
-
}
|
|
5982
|
-
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
5983
|
-
console.error(`the fallback URL ${link} needs http:// or https://`);
|
|
5984
|
-
process.exit(1);
|
|
5985
|
-
}
|
|
5986
|
-
return url.origin;
|
|
5987
|
-
}
|
|
5988
|
-
function pairingLinks(token) {
|
|
6134
|
+
function pairingLinks(code) {
|
|
5989
6135
|
return { local: `${address}/pair#${new URLSearchParams({
|
|
5990
6136
|
machine: address,
|
|
5991
|
-
...
|
|
6137
|
+
...code !== null && { code }
|
|
5992
6138
|
})}` };
|
|
5993
6139
|
}
|
|
5994
6140
|
function serveFile(pathname, response) {
|
|
6141
|
+
if (!args.pwa && pathname === "/manifest.webmanifest") {
|
|
6142
|
+
response.statusCode = 404;
|
|
6143
|
+
response.end("offsite runs without --pwa");
|
|
6144
|
+
return;
|
|
6145
|
+
}
|
|
5995
6146
|
const requested = path.join(publicDir, path.normalize(pathname));
|
|
5996
6147
|
const file = requested.startsWith(publicDir + path.sep) && statSync(requested, { throwIfNoEntry: false })?.isFile() ? requested : path.join(publicDir, "index.html");
|
|
5997
6148
|
if (!existsSync(file)) {
|
|
@@ -6000,6 +6151,10 @@ function serveFile(pathname, response) {
|
|
|
6000
6151
|
return;
|
|
6001
6152
|
}
|
|
6002
6153
|
response.setHeader("content-type", contentTypes[path.extname(file)] ?? "application/octet-stream");
|
|
6154
|
+
if (!args.pwa && path.basename(file) === "index.html") {
|
|
6155
|
+
response.end(readFileSync(file, "utf8").replace(/<link rel="manifest"[^>]*>/, ""));
|
|
6156
|
+
return;
|
|
6157
|
+
}
|
|
6003
6158
|
createReadStream(file).pipe(response);
|
|
6004
6159
|
}
|
|
6005
6160
|
//#endregion
|