offsite 0.0.73 → 0.0.75

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.
@@ -3147,6 +3147,31 @@ function checkInProject(root, full, file) {
3147
3147
  const relative = path.relative(root, full);
3148
3148
  if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) throw new Error(`${file} is outside the project`);
3149
3149
  }
3150
+ function serveMedia(token, request, response) {
3151
+ if (request.method !== "GET") {
3152
+ response.statusCode = 405;
3153
+ response.end("use GET");
3154
+ return;
3155
+ }
3156
+ const link = findMediaLink(token);
3157
+ if (link === null) {
3158
+ response.statusCode = 404;
3159
+ response.end("This media link expired");
3160
+ return;
3161
+ }
3162
+ const stats = statSync(link.file, { throwIfNoEntry: false });
3163
+ if (stats === void 0 || !stats.isFile()) {
3164
+ response.statusCode = 404;
3165
+ response.end("not found");
3166
+ return;
3167
+ }
3168
+ response.setHeader("access-control-allow-origin", "*");
3169
+ response.setHeader("content-type", link.mediaType);
3170
+ response.setHeader("cache-control", "private, max-age=3600");
3171
+ response.setHeader("content-security-policy", "sandbox");
3172
+ response.setHeader("x-content-type-options", "nosniff");
3173
+ streamFile(link.file, stats.size, request, response);
3174
+ }
3150
3175
  // src/services/generate-text.ts
3151
3176
  async function generateText(setting, cwd, instructions, prompt) {
3152
3177
  const settings = readConfig()[setting];
@@ -7599,58 +7624,139 @@ function send(connection, event) {
7599
7624
  event
7600
7625
  });
7601
7626
  }
7627
+ // ../core/src/app-icons.ts
7628
+ const appIcons = [
7629
+ {
7630
+ id: "original",
7631
+ name: "Original",
7632
+ scale: 1,
7633
+ tile: null
7634
+ },
7635
+ {
7636
+ id: "small",
7637
+ name: "Small",
7638
+ scale: .7,
7639
+ tile: null
7640
+ },
7641
+ {
7642
+ id: "dark",
7643
+ name: "Dark",
7644
+ scale: .7,
7645
+ tile: {
7646
+ color: "#0a0a0a",
7647
+ shape: "rounded"
7648
+ }
7649
+ },
7650
+ {
7651
+ id: "light",
7652
+ name: "Light",
7653
+ scale: .7,
7654
+ tile: {
7655
+ color: "#ffffff",
7656
+ shape: "rounded"
7657
+ }
7658
+ },
7659
+ {
7660
+ id: "dark-circle",
7661
+ name: "Dark circle",
7662
+ scale: .62,
7663
+ tile: {
7664
+ color: "#0a0a0a",
7665
+ shape: "circle"
7666
+ }
7667
+ },
7668
+ {
7669
+ id: "light-circle",
7670
+ name: "Light circle",
7671
+ scale: .62,
7672
+ tile: {
7673
+ color: "#ffffff",
7674
+ shape: "circle"
7675
+ }
7676
+ },
7677
+ {
7678
+ id: "dark-square",
7679
+ name: "Dark square",
7680
+ scale: .7,
7681
+ tile: {
7682
+ color: "#0a0a0a",
7683
+ shape: "square"
7684
+ }
7685
+ },
7686
+ {
7687
+ id: "light-square",
7688
+ name: "Light square",
7689
+ scale: .7,
7690
+ tile: {
7691
+ color: "#ffffff",
7692
+ shape: "square"
7693
+ }
7694
+ }
7695
+ ];
7602
7696
  // src/storage/state.ts
7603
- const stateSchema = z.strictObject({ composer: z.strictObject({
7604
- provider: z.enum([
7605
- "claude",
7606
- "codex",
7607
- "opencode"
7608
- ]).default("claude"),
7609
- claude: z.strictObject({
7610
- accountId: z.string().nullable().default(null),
7611
- model: z.string().nullable().default(null),
7612
- effort: z.enum([
7613
- "low",
7614
- "medium",
7615
- "high",
7616
- "xhigh",
7617
- "max"
7618
- ]).nullable().default(null),
7619
- mode: z.enum([
7620
- "default",
7621
- "acceptEdits",
7622
- "bypassPermissions",
7623
- "plan",
7624
- "dontAsk",
7625
- "auto"
7626
- ]).nullable().default("bypassPermissions"),
7627
- plan: z.boolean().default(false)
7628
- }).prefault({}),
7629
- codex: z.strictObject({
7630
- accountId: z.string().nullable().default(null),
7631
- model: z.string().nullable().default(null),
7632
- effort: z.string().nullable().default(null),
7633
- mode: z.enum([
7634
- "read-only",
7635
- "auto",
7636
- "full-access"
7637
- ]).nullable().default("auto"),
7638
- plan: z.boolean().default(false)
7697
+ const stateSchema = z.strictObject({
7698
+ composer: z.strictObject({
7699
+ provider: z.enum([
7700
+ "claude",
7701
+ "codex",
7702
+ "opencode"
7703
+ ]).default("claude"),
7704
+ claude: z.strictObject({
7705
+ accountId: z.string().nullable().default(null),
7706
+ model: z.string().nullable().default(null),
7707
+ effort: z.enum([
7708
+ "low",
7709
+ "medium",
7710
+ "high",
7711
+ "xhigh",
7712
+ "max"
7713
+ ]).nullable().default(null),
7714
+ mode: z.enum([
7715
+ "default",
7716
+ "acceptEdits",
7717
+ "bypassPermissions",
7718
+ "plan",
7719
+ "dontAsk",
7720
+ "auto"
7721
+ ]).nullable().default("bypassPermissions"),
7722
+ plan: z.boolean().default(false)
7723
+ }).prefault({}),
7724
+ codex: z.strictObject({
7725
+ accountId: z.string().nullable().default(null),
7726
+ model: z.string().nullable().default(null),
7727
+ effort: z.string().nullable().default(null),
7728
+ mode: z.enum([
7729
+ "read-only",
7730
+ "auto",
7731
+ "full-access"
7732
+ ]).nullable().default("auto"),
7733
+ plan: z.boolean().default(false)
7734
+ }).prefault({}),
7735
+ opencode: z.strictObject({
7736
+ accountId: z.null().default(null),
7737
+ model: z.string().nullable().default(null),
7738
+ effort: z.string().nullable().default(null),
7739
+ mode: z.string().nullable().default(null),
7740
+ plan: z.boolean().default(false)
7741
+ }).prefault({})
7639
7742
  }).prefault({}),
7640
- opencode: z.strictObject({
7641
- accountId: z.null().default(null),
7642
- model: z.string().nullable().default(null),
7643
- effort: z.string().nullable().default(null),
7644
- mode: z.string().nullable().default(null),
7645
- plan: z.boolean().default(false)
7646
- }).prefault({})
7647
- }).prefault({}) });
7743
+ appIcon: z.custom((value) => appIcons.some((icon) => icon.id === value)).default("original")
7744
+ });
7648
7745
  const defaults = stateSchema.parse({});
7746
+ function readState() {
7747
+ return readSettings("state.json", stateSchema, defaults);
7748
+ }
7649
7749
  function readComposer() {
7650
- return readSettings("state.json", stateSchema, defaults).composer;
7750
+ return readState().composer;
7751
+ }
7752
+ function readAppIcon() {
7753
+ return readState().appIcon;
7651
7754
  }
7652
7755
  function saveComposer(value) {
7653
- const result = stateSchema.safeParse({ composer: value });
7756
+ const result = stateSchema.safeParse({
7757
+ ...readState(),
7758
+ composer: value
7759
+ });
7654
7760
  if (!result.success) throw new Error(z.prettifyError(result.error));
7655
7761
  writeJson("state.json", result.data);
7656
7762
  broadcast({
@@ -7659,6 +7765,19 @@ function saveComposer(value) {
7659
7765
  });
7660
7766
  return result.data.composer;
7661
7767
  }
7768
+ function saveAppIcon(value) {
7769
+ const result = stateSchema.safeParse({
7770
+ ...readState(),
7771
+ appIcon: value
7772
+ });
7773
+ if (!result.success) throw new Error(z.prettifyError(result.error));
7774
+ writeJson("state.json", result.data);
7775
+ broadcast({
7776
+ type: "appIcon",
7777
+ appIcon: result.data.appIcon
7778
+ });
7779
+ return result.data.appIcon;
7780
+ }
7662
7781
  // src/api/socket.ts
7663
7782
  const handlers = {
7664
7783
  ping: () => null,
@@ -7672,12 +7791,14 @@ const handlers = {
7672
7791
  sessions: sessions(),
7673
7792
  providers: providerStatuses(),
7674
7793
  activity: activity(),
7675
- terminals: terminalTabs()
7794
+ terminals: terminalTabs(),
7795
+ appIcon: readAppIcon()
7676
7796
  };
7677
7797
  },
7678
7798
  "config.get": () => configInfo(),
7679
7799
  "composer.get": () => readComposer(),
7680
7800
  "composer.save": (params) => saveComposer(params),
7801
+ "appIcon.save": (params) => saveAppIcon(params),
7681
7802
  "config.save": async (params) => {
7682
7803
  const before = readConfig();
7683
7804
  const saved = saveConfig(params);
@@ -9094,6 +9215,71 @@ function fail(response, status, message) {
9094
9215
  response.setHeader("content-type", "text/plain; charset=utf-8");
9095
9216
  response.end(message);
9096
9217
  }
9218
+ // src/services/app-icon.ts
9219
+ const pngs = /* @__PURE__ */ new Map([
9220
+ ["icon-192.png", {
9221
+ size: 192,
9222
+ maskable: false
9223
+ }],
9224
+ ["icon-512.png", {
9225
+ size: 512,
9226
+ maskable: false
9227
+ }],
9228
+ ["icon-maskable-512.png", {
9229
+ size: 512,
9230
+ maskable: true
9231
+ }]
9232
+ ]);
9233
+ async function serveAppIcon(publicDir, pathname, response) {
9234
+ const logoPath = path.join(publicDir, "icon.svg");
9235
+ if (!existsSync(logoPath)) {
9236
+ response.statusCode = 404;
9237
+ response.end("web app not built; run pnpm build");
9238
+ return;
9239
+ }
9240
+ try {
9241
+ const icon = await renderAppIcon(readFileSync(logoPath, "utf8"), pathname.slice(14));
9242
+ if (icon === null) {
9243
+ response.statusCode = 404;
9244
+ response.end("not found");
9245
+ return;
9246
+ }
9247
+ response.setHeader("content-type", icon.type);
9248
+ response.setHeader("cache-control", "no-cache");
9249
+ response.end(icon.body);
9250
+ } catch (error) {
9251
+ console.warn(`could not serve the app icon: ${error}`);
9252
+ response.statusCode = 500;
9253
+ response.end("could not render the app icon");
9254
+ }
9255
+ }
9256
+ async function renderAppIcon(logo, file) {
9257
+ if (file === "icon.svg") return {
9258
+ type: "image/svg+xml",
9259
+ body: iconSvg(logo, false)
9260
+ };
9261
+ const png = pngs.get(file);
9262
+ if (png === void 0) return null;
9263
+ return {
9264
+ type: "image/png",
9265
+ body: await sharp(Buffer.from(iconSvg(logo, png.maskable))).resize(png.size, png.size).png().toBuffer()
9266
+ };
9267
+ }
9268
+ function iconSvg(logo, maskable) {
9269
+ const id = readAppIcon();
9270
+ const icon = appIcons.find((item) => item.id === id);
9271
+ if (icon === void 0) throw new Error(`unknown app icon ${id}`);
9272
+ const size = 512 * (maskable ? .72 : icon.scale);
9273
+ const offset = (512 - size) / 2;
9274
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">${tile(icon, maskable)}${logo.replace("<svg ", `<svg x="${offset}" y="${offset}" width="${size}" height="${size}" `)}</svg>`;
9275
+ }
9276
+ function tile(icon, maskable) {
9277
+ if (icon.tile === null) return "";
9278
+ if (maskable || icon.tile.shape === "square") return `<rect width="512" height="512" fill="${icon.tile.color}"/>`;
9279
+ if (icon.tile.shape === "rounded") return `<rect width="512" height="512" rx="112.64" fill="${icon.tile.color}"/>`;
9280
+ if (icon.tile.shape === "circle") return `<circle cx="256" cy="256" r="256" fill="${icon.tile.color}"/>`;
9281
+ throw new Error(`unknown tile shape for ${icon.id}`);
9282
+ }
9097
9283
  // src/main.ts
9098
9284
  const UNPAIR_CHECK = 5e3;
9099
9285
  const TICKET_LIFETIME = 6e4;
@@ -9194,6 +9380,10 @@ function answerRequest(request, response) {
9194
9380
  answerOpen(request, response);
9195
9381
  return;
9196
9382
  }
9383
+ if (url.pathname.startsWith("/api/app-icon/")) {
9384
+ serveAppIcon(publicDir, url.pathname, response);
9385
+ return;
9386
+ }
9197
9387
  if (url.pathname.startsWith("/api/")) {
9198
9388
  answerApi(url.pathname, request, response);
9199
9389
  return;
@@ -9303,31 +9493,6 @@ function answerApi(pathname, request, response) {
9303
9493
  response.statusCode = 404;
9304
9494
  response.end("not found");
9305
9495
  }
9306
- function serveMedia(token, request, response) {
9307
- if (request.method !== "GET") {
9308
- response.statusCode = 405;
9309
- response.end("use GET");
9310
- return;
9311
- }
9312
- const link = findMediaLink(token);
9313
- if (link === null) {
9314
- response.statusCode = 404;
9315
- response.end("This media link expired");
9316
- return;
9317
- }
9318
- const stats = statSync(link.file, { throwIfNoEntry: false });
9319
- if (stats === void 0 || !stats.isFile()) {
9320
- response.statusCode = 404;
9321
- response.end("not found");
9322
- return;
9323
- }
9324
- response.setHeader("access-control-allow-origin", "*");
9325
- response.setHeader("content-type", link.mediaType);
9326
- response.setHeader("cache-control", "private, max-age=3600");
9327
- response.setHeader("content-security-policy", "sandbox");
9328
- response.setHeader("x-content-type-options", "nosniff");
9329
- streamFile(link.file, stats.size, request, response);
9330
- }
9331
9496
  function serveBrowse(pathname, request, response) {
9332
9497
  if (request.method !== "GET") {
9333
9498
  response.statusCode = 405;
@@ -9464,6 +9629,7 @@ function serveFile(pathname, request, response) {
9464
9629
  }
9465
9630
  response.setHeader("content-type", contentTypes[path.extname(file)] ?? "application/octet-stream");
9466
9631
  if (path.basename(file) === "index.html") {
9632
+ response.setHeader("cache-control", "no-cache");
9467
9633
  let page = readFileSync(file, "utf8");
9468
9634
  if (!args.pwa) page = page.replace(/<link rel="manifest"[^>]*>/, "");
9469
9635
  if (args.connect !== null) page = page.replace("</head>", `<meta name="offsite-connect" content="${args.connect.join(" ")}"></head>`);
@@ -9471,6 +9637,7 @@ function serveFile(pathname, request, response) {
9471
9637
  return;
9472
9638
  }
9473
9639
  if (file === requested && pathname.startsWith("/assets/")) response.setHeader("cache-control", "public, max-age=31536000, immutable");
9640
+ if (pathname === "/sw.js") response.setHeader("cache-control", "no-cache");
9474
9641
  if (compressible.has(path.extname(file))) {
9475
9642
  response.setHeader("vary", "accept-encoding");
9476
9643
  if (/\bgzip\b/.test(request.headers["accept-encoding"] ?? "")) {