offhands 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/daemon.mjs +256 -35
  2. package/package.json +26 -28
package/dist/daemon.mjs CHANGED
@@ -4776,8 +4776,8 @@ var require_main = __commonJS({
4776
4776
  });
4777
4777
 
4778
4778
  // ../daemon/src/index.ts
4779
- import { resolve as resolve4 } from "node:path";
4780
- import { existsSync as existsSync7 } from "node:fs";
4779
+ import { resolve as resolve5 } from "node:path";
4780
+ import { existsSync as existsSync8 } from "node:fs";
4781
4781
 
4782
4782
  // ../daemon/src/runners/claude-code.ts
4783
4783
  import { spawn } from "node:child_process";
@@ -4830,8 +4830,31 @@ function mapClaudeEvent(value) {
4830
4830
  if (delta?.type === "text_delta" && typeof delta.text === "string" && delta.text !== "") {
4831
4831
  return [{ type: "text", chunk: delta.text }];
4832
4832
  }
4833
+ if (delta?.type === "thinking_delta" && typeof delta.thinking === "string" && delta.thinking !== "") {
4834
+ return [{ type: "thinking", chunk: delta.thinking }];
4835
+ }
4833
4836
  return [];
4834
4837
  }
4838
+ // Account-level plan usage, pushed by the CLI independent of any run's
4839
+ // result. Real numbers only — omit rather than guess if the shape drifts.
4840
+ case "rate_limit_event": {
4841
+ const info = v2.rate_limit_info;
4842
+ const windows = info?.unifiedWindows;
4843
+ const fiveHour = windows?.five_hour;
4844
+ const sevenDay = windows?.seven_day;
4845
+ if (typeof fiveHour?.utilization !== "number" || typeof fiveHour?.resetsAt !== "number" || typeof sevenDay?.utilization !== "number" || typeof sevenDay?.resetsAt !== "number") {
4846
+ return [];
4847
+ }
4848
+ return [
4849
+ {
4850
+ type: "rate-limit",
4851
+ fiveHourUtilization: fiveHour.utilization,
4852
+ fiveHourResetsAtMs: fiveHour.resetsAt * 1e3,
4853
+ sevenDayUtilization: sevenDay.utilization,
4854
+ sevenDayResetsAtMs: sevenDay.resetsAt * 1e3
4855
+ }
4856
+ ];
4857
+ }
4835
4858
  case "assistant": {
4836
4859
  const message = v2.message;
4837
4860
  const content = Array.isArray(message?.content) ? message.content : [];
@@ -4903,7 +4926,7 @@ var AsyncEventQueue = class {
4903
4926
  for (; ; ) {
4904
4927
  while (this.buffer.length > 0) yield this.buffer.shift();
4905
4928
  if (this.closed) return;
4906
- await new Promise((resolve5) => this.waiter = resolve5);
4929
+ await new Promise((resolve6) => this.waiter = resolve6);
4907
4930
  this.waiter = null;
4908
4931
  }
4909
4932
  }
@@ -4931,10 +4954,10 @@ var ClaudeCodeRunner = class {
4931
4954
  return "claude";
4932
4955
  }
4933
4956
  async detect() {
4934
- return new Promise((resolve5) => {
4957
+ return new Promise((resolve6) => {
4935
4958
  const p2 = spawn(this.resolveBin(), ["--version"], { stdio: "ignore" });
4936
- p2.on("error", () => resolve5(false));
4937
- p2.on("exit", (code) => resolve5(code === 0));
4959
+ p2.on("error", () => resolve6(false));
4960
+ p2.on("exit", (code) => resolve6(code === 0));
4938
4961
  });
4939
4962
  }
4940
4963
  start(run, callbacks) {
@@ -5052,10 +5075,10 @@ function extractSessionId(value) {
5052
5075
  // ../daemon/src/runners/stubs.ts
5053
5076
  import { spawn as spawn2 } from "node:child_process";
5054
5077
  function commandExists(bin) {
5055
- return new Promise((resolve5) => {
5078
+ return new Promise((resolve6) => {
5056
5079
  const p2 = spawn2(bin, ["--version"], { stdio: "ignore", shell: process.platform === "win32" });
5057
- p2.on("error", () => resolve5(false));
5058
- p2.on("exit", (code) => resolve5(code === 0));
5080
+ p2.on("error", () => resolve6(false));
5081
+ p2.on("exit", (code) => resolve6(code === 0));
5059
5082
  });
5060
5083
  }
5061
5084
  function notImplemented(id) {
@@ -5177,10 +5200,10 @@ var CopilotCliRunner = class {
5177
5200
  async detect() {
5178
5201
  const cmd = this.resolveCommand();
5179
5202
  if (!cmd) return false;
5180
- return new Promise((resolve5) => {
5203
+ return new Promise((resolve6) => {
5181
5204
  const p2 = spawn3(cmd[0], [...cmd.slice(1), "--version"], { stdio: "ignore" });
5182
- p2.on("error", () => resolve5(false));
5183
- p2.on("exit", (code) => resolve5(code === 0));
5205
+ p2.on("error", () => resolve6(false));
5206
+ p2.on("exit", (code) => resolve6(code === 0));
5184
5207
  });
5185
5208
  }
5186
5209
  loggedIn() {
@@ -5277,8 +5300,9 @@ stderr: ${stderrTail}` : ""}`
5277
5300
  // ../daemon/src/session-manager.ts
5278
5301
  import { randomUUID } from "node:crypto";
5279
5302
  import { hostname, platform, tmpdir } from "node:os";
5280
- import { existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync as readdirSync3, writeFileSync as writeFileSync2 } from "node:fs";
5303
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync as readdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
5281
5304
  import { dirname as dirname2, join as join5, basename as basename3, parse as parse2, resolve as resolve3 } from "node:path";
5305
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
5282
5306
 
5283
5307
  // ../daemon/src/receipts.ts
5284
5308
  import { execFile } from "node:child_process";
@@ -5624,7 +5648,16 @@ function mimeFromName(path) {
5624
5648
  }
5625
5649
 
5626
5650
  // ../daemon/src/session-manager.ts
5627
- var DAEMON_VERSION = "0.1.0";
5651
+ function resolveDaemonVersion() {
5652
+ try {
5653
+ const pkgPath = join5(dirname2(fileURLToPath2(import.meta.url)), "..", "package.json");
5654
+ const pkg = JSON.parse(readFileSync3(pkgPath, "utf8"));
5655
+ return pkg.version ?? "0.0.0";
5656
+ } catch {
5657
+ return "0.0.0";
5658
+ }
5659
+ }
5660
+ var DAEMON_VERSION = resolveDaemonVersion();
5628
5661
  var SessionManager = class {
5629
5662
  constructor(store2, runners2) {
5630
5663
  this.store = store2;
@@ -10249,6 +10282,8 @@ var NEVER = INVALID;
10249
10282
  // ../shared/src/run-events.ts
10250
10283
  var RunEventSchema = external_exports.discriminatedUnion("type", [
10251
10284
  external_exports.object({ type: external_exports.literal("text"), chunk: external_exports.string() }),
10285
+ /** Extended-thinking content, streamed the same way as text (medium+ effort tiers). */
10286
+ external_exports.object({ type: external_exports.literal("thinking"), chunk: external_exports.string() }),
10252
10287
  external_exports.object({ type: external_exports.literal("tool"), name: external_exports.string(), summary: external_exports.string() }),
10253
10288
  external_exports.object({
10254
10289
  type: external_exports.literal("approval"),
@@ -10290,7 +10325,15 @@ var RunEventSchema = external_exports.discriminatedUnion("type", [
10290
10325
  /** API-equivalent cost of the run (informational for subscription plans). */
10291
10326
  costUsd: external_exports.number().optional()
10292
10327
  }),
10293
- external_exports.object({ type: external_exports.literal("error"), message: external_exports.string() })
10328
+ external_exports.object({ type: external_exports.literal("error"), message: external_exports.string() }),
10329
+ /** Account-level plan usage (from the CLI's own rate_limit_event) — real numbers only. */
10330
+ external_exports.object({
10331
+ type: external_exports.literal("rate-limit"),
10332
+ fiveHourUtilization: external_exports.number(),
10333
+ fiveHourResetsAtMs: external_exports.number(),
10334
+ sevenDayUtilization: external_exports.number(),
10335
+ sevenDayResetsAtMs: external_exports.number()
10336
+ })
10294
10337
  ]);
10295
10338
  var PermissionModeSchema = external_exports.enum(["guarded", "plan", "acceptEdits", "bypass"]);
10296
10339
  var EffortSchema = external_exports.enum(["low", "medium", "high", "max"]);
@@ -10550,10 +10593,19 @@ var RelayFrameSchema = external_exports.discriminatedUnion("kind", [
10550
10593
  }),
10551
10594
  /**
10552
10595
  * daemon → relay: ask the relay to web-push the session's phones. Carries
10553
- * ONLY an opaque approval id — never content (push bodies transit
10554
- * Apple/Google; 02-architecture.md).
10596
+ * ONLY opaque ids — never content (push bodies transit Apple/Google;
10597
+ * 02-architecture.md). `id` is the approval id (approval only); `sessionId`
10598
+ * is the offhand chat session the notification concerns (approval/done/
10599
+ * error) — lets the phone deep-link straight to the right session.
10555
10600
  */
10556
- external_exports.object({ kind: external_exports.literal("notify"), notice: external_exports.enum(["approval", "drop"]), id: external_exports.string().optional() }),
10601
+ external_exports.object({
10602
+ kind: external_exports.literal("notify"),
10603
+ notice: external_exports.enum(["approval", "drop", "done", "error", "progress"]),
10604
+ id: external_exports.string().optional(),
10605
+ sessionId: external_exports.string().optional(),
10606
+ /** 'progress' only — a bare action count, never the action itself (no content in push). */
10607
+ count: external_exports.number().int().nonnegative().optional()
10608
+ }),
10557
10609
  /** relay → daemon: verdict delivered via push-notification action button. */
10558
10610
  external_exports.object({ kind: external_exports.literal("verdict"), approvalId: external_exports.string(), approve: external_exports.boolean() }),
10559
10611
  external_exports.object({ kind: external_exports.literal("ping") }),
@@ -10647,7 +10699,7 @@ var randomValuesStandard;
10647
10699
  var crypto;
10648
10700
  var randomValueNodeJS;
10649
10701
  var _Module = Module;
10650
- Module.ready = new Promise(function(resolve5, reject) {
10702
+ Module.ready = new Promise(function(resolve6, reject) {
10651
10703
  var Module2 = _Module;
10652
10704
  Module2.onAbort = reject;
10653
10705
  Module2.print = function(what) {
@@ -10659,13 +10711,13 @@ Module.ready = new Promise(function(resolve5, reject) {
10659
10711
  Module2.onRuntimeInitialized = function() {
10660
10712
  try {
10661
10713
  Module2._crypto_secretbox_keybytes();
10662
- resolve5();
10714
+ resolve6();
10663
10715
  } catch (err2) {
10664
10716
  reject(err2);
10665
10717
  }
10666
10718
  };
10667
10719
  Module2.useBackupModule = function() {
10668
- return new Promise(function(resolve6, reject2) {
10720
+ return new Promise(function(resolve7, reject2) {
10669
10721
  var Module3 = {};
10670
10722
  Module3.onAbort = reject2;
10671
10723
  Module3.getRandomValue = _Module.getRandomValue;
@@ -10678,7 +10730,7 @@ Module.ready = new Promise(function(resolve5, reject) {
10678
10730
  Object.keys(Module3).forEach(function(k2) {
10679
10731
  _Module[k2] = Module3[k2];
10680
10732
  });
10681
- resolve6();
10733
+ resolve7();
10682
10734
  };
10683
10735
  var Module3 = typeof Module3 != "undefined" ? Module3 : {};
10684
10736
  var ENVIRONMENT_IS_WEB2 = !!globalThis.window;
@@ -10738,13 +10790,13 @@ Module.ready = new Promise(function(resolve5, reject) {
10738
10790
  }
10739
10791
  readAsync2 = async (url) => {
10740
10792
  if (isFileURI2(url)) {
10741
- return new Promise((resolve7, reject3) => {
10793
+ return new Promise((resolve8, reject3) => {
10742
10794
  var xhr = new XMLHttpRequest();
10743
10795
  xhr.open("GET", url, true);
10744
10796
  xhr.responseType = "arraybuffer";
10745
10797
  xhr.onload = () => {
10746
10798
  if (xhr.status == 200 || xhr.status == 0 && xhr.response) {
10747
- resolve7(xhr.response);
10799
+ resolve8(xhr.response);
10748
10800
  return;
10749
10801
  }
10750
10802
  reject3(xhr.status);
@@ -37356,9 +37408,9 @@ Module.ready = new Promise(function(resolve5, reject) {
37356
37408
  }
37357
37409
  var info = getWasmImports2();
37358
37410
  if (Module3["instantiateWasm"]) {
37359
- return new Promise((resolve7, reject3) => {
37411
+ return new Promise((resolve8, reject3) => {
37360
37412
  Module3["instantiateWasm"](info, (inst, mod) => {
37361
- resolve7(receiveInstance(inst, mod));
37413
+ resolve8(receiveInstance(inst, mod));
37362
37414
  });
37363
37415
  });
37364
37416
  }
@@ -37859,13 +37911,13 @@ Module.ready = new Promise(function(resolve5, reject) {
37859
37911
  }
37860
37912
  readAsync = async (url) => {
37861
37913
  if (isFileURI(url)) {
37862
- return new Promise((resolve6, reject2) => {
37914
+ return new Promise((resolve7, reject2) => {
37863
37915
  var xhr = new XMLHttpRequest();
37864
37916
  xhr.open("GET", url, true);
37865
37917
  xhr.responseType = "arraybuffer";
37866
37918
  xhr.onload = () => {
37867
37919
  if (xhr.status == 200 || xhr.status == 0 && xhr.response) {
37868
- resolve6(xhr.response);
37920
+ resolve7(xhr.response);
37869
37921
  return;
37870
37922
  }
37871
37923
  reject2(xhr.status);
@@ -37979,9 +38031,9 @@ Module.ready = new Promise(function(resolve5, reject) {
37979
38031
  }
37980
38032
  var info = getWasmImports();
37981
38033
  if (Module2["instantiateWasm"]) {
37982
- return new Promise((resolve6, reject2) => {
38034
+ return new Promise((resolve7, reject2) => {
37983
38035
  Module2["instantiateWasm"](info, (inst, mod) => {
37984
- resolve6(receiveInstance(inst, mod));
38036
+ resolve7(receiveInstance(inst, mod));
37985
38037
  });
37986
38038
  });
37987
38039
  }
@@ -41133,6 +41185,18 @@ var LocalSessionServer = class {
41133
41185
  const http = createServer((req, res) => void this.onRequest(req, res));
41134
41186
  this.wss = new import_websocket_server.default({ server: http });
41135
41187
  this.wss.on("connection", (ws) => this.onConnection(ws));
41188
+ http.on("error", (err) => {
41189
+ if (err.code === "EADDRINUSE") {
41190
+ console.error(
41191
+ `
41192
+ Another offhands daemon is already running (port ${port2} is taken).
41193
+ Run only one \u2014 or start this one on a different port: offhands --port ${port2 + 1}
41194
+ `
41195
+ );
41196
+ process.exit(1);
41197
+ }
41198
+ throw err;
41199
+ });
41136
41200
  http.listen(port2, "127.0.0.1");
41137
41201
  }
41138
41202
  wss;
@@ -41186,6 +41250,8 @@ var RelayClient = class {
41186
41250
  heartbeat = null;
41187
41251
  detach = null;
41188
41252
  stopped = false;
41253
+ /** sessionId → tool-call count for the in-flight run (opaque count only, never the action). */
41254
+ toolCounts = /* @__PURE__ */ new Map();
41189
41255
  start() {
41190
41256
  this.connect();
41191
41257
  }
@@ -41206,15 +41272,41 @@ var RelayClient = class {
41206
41272
  if (ws.readyState === wrapper_default.OPEN) {
41207
41273
  const envelope = seal(msg, this.keys.tx);
41208
41274
  ws.send(JSON.stringify({ kind: "peer", payload: envelope }));
41275
+ if (msg.type === "run-started") {
41276
+ this.toolCounts.set(msg.sessionId, 0);
41277
+ }
41278
+ if (msg.type === "run-event" && msg.event.type === "tool") {
41279
+ const count = (this.toolCounts.get(msg.sessionId) ?? 0) + 1;
41280
+ this.toolCounts.set(msg.sessionId, count);
41281
+ ws.send(
41282
+ JSON.stringify({
41283
+ kind: "notify",
41284
+ notice: "progress",
41285
+ sessionId: msg.sessionId,
41286
+ count
41287
+ })
41288
+ );
41289
+ }
41209
41290
  if (msg.type === "run-event" && msg.event.type === "approval") {
41210
41291
  ws.send(
41211
41292
  JSON.stringify({
41212
41293
  kind: "notify",
41213
41294
  notice: "approval",
41214
- id: msg.event.id
41295
+ id: msg.event.id,
41296
+ sessionId: msg.sessionId
41215
41297
  })
41216
41298
  );
41217
41299
  }
41300
+ if (msg.type === "run-event" && msg.event.type === "done") {
41301
+ ws.send(
41302
+ JSON.stringify({ kind: "notify", notice: "done", sessionId: msg.sessionId })
41303
+ );
41304
+ }
41305
+ if (msg.type === "run-event" && msg.event.type === "error") {
41306
+ ws.send(
41307
+ JSON.stringify({ kind: "notify", notice: "error", sessionId: msg.sessionId })
41308
+ );
41309
+ }
41218
41310
  if (msg.type === "drop" && msg.direction === "to-phone") {
41219
41311
  ws.send(JSON.stringify({ kind: "notify", notice: "drop" }));
41220
41312
  }
@@ -41274,7 +41366,7 @@ var RelayClient = class {
41274
41366
  };
41275
41367
 
41276
41368
  // ../daemon/src/pairing.ts
41277
- import { existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4 } from "node:fs";
41369
+ import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4 } from "node:fs";
41278
41370
  import { homedir as homedir6 } from "node:os";
41279
41371
  import { join as join7 } from "node:path";
41280
41372
  import { randomInt } from "node:crypto";
@@ -41285,7 +41377,7 @@ var POLL_TIMEOUT_MS = 10 * 60 * 1e3;
41285
41377
  async function ensurePairing(relayUrl2, forceNew = false, webUrl2 = "https://offhand-web.onrender.com") {
41286
41378
  await ready;
41287
41379
  if (!forceNew && existsSync6(PAIRING_PATH)) {
41288
- const f2 = JSON.parse(readFileSync3(PAIRING_PATH, "utf8"));
41380
+ const f2 = JSON.parse(readFileSync4(PAIRING_PATH, "utf8"));
41289
41381
  const kp2 = { publicKey: fromB64u(f2.daemonPublicKey), secretKey: fromB64u(f2.daemonSecretKey) };
41290
41382
  const phonePk = fromB64u(f2.phonePublicKey);
41291
41383
  return {
@@ -41522,6 +41614,115 @@ async function downloadArtifact(relayUrl2, sessionId, blobId, keys) {
41522
41614
  return openBytes(new Uint8Array(await res.arrayBuffer()), keys.rx);
41523
41615
  }
41524
41616
 
41617
+ // ../daemon/src/autostart.ts
41618
+ import { homedir as homedir7 } from "node:os";
41619
+ import { join as join9, resolve as resolve4, dirname as dirname3 } from "node:path";
41620
+ import { existsSync as existsSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, rmSync } from "node:fs";
41621
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
41622
+ import { spawnSync } from "node:child_process";
41623
+ import { tmpdir as tmpdir3 } from "node:os";
41624
+ var __dirname2 = dirname3(fileURLToPath3(import.meta.url));
41625
+ var SHORTCUT_NAME = "OffhandDaemon.lnk";
41626
+ var OFFHAND_HOME = process.env.OFFHAND_HOME ?? join9(homedir7(), ".offhand");
41627
+ function getStartupDir() {
41628
+ if (process.platform !== "win32") {
41629
+ return join9(homedir7(), ".config", "autostart");
41630
+ }
41631
+ return join9(process.env.APPDATA ?? "", "Microsoft", "Windows", "Start Menu", "Programs", "Startup");
41632
+ }
41633
+ function getWrapperPath() {
41634
+ const wrapperDir = join9(OFFHAND_HOME, "autostart");
41635
+ mkdirSync5(wrapperDir, { recursive: true });
41636
+ return join9(wrapperDir, "start-daemon.bat");
41637
+ }
41638
+ function getShortcutPath() {
41639
+ return join9(getStartupDir(), SHORTCUT_NAME);
41640
+ }
41641
+ function buildWrapperScript(config) {
41642
+ const projectRoot = resolve4(__dirname2, "..", "..");
41643
+ const wsArgs2 = config.workspaces.map((w2) => `--workspace "${w2}"`).join(" ");
41644
+ const relayArg = config.relayUrl ? `--relay "${config.relayUrl}"` : "";
41645
+ const devUrlArg = config.devUrl ? `--dev-url "${config.devUrl}"` : "";
41646
+ const portArg = `--port ${config.port}`;
41647
+ const approvalArg = `--approval-timeout ${config.approvalTimeout ?? 300}`;
41648
+ const webUrlArg = `--web-url "${config.webUrl ?? "https://offhand-web.onrender.com"}"`;
41649
+ const parts = [
41650
+ "pnpm --filter @offhand/daemon dev",
41651
+ wsArgs2,
41652
+ relayArg,
41653
+ devUrlArg,
41654
+ portArg,
41655
+ approvalArg,
41656
+ webUrlArg
41657
+ ].filter(Boolean);
41658
+ const cmd = parts.join(" ");
41659
+ return `@echo off
41660
+ cd /d "${projectRoot}"
41661
+ ${cmd}
41662
+ `;
41663
+ }
41664
+ function createShortcut(targetPath, shortcutPath) {
41665
+ const psScript = `$WshShell = New-Object -ComObject WScript.Shell
41666
+ $Shortcut = $WshShell.CreateShortcut("${shortcutPath}")
41667
+ $Shortcut.TargetPath = "cmd.exe"
41668
+ $Shortcut.Arguments = "/c ${targetPath}"
41669
+ $Shortcut.WorkingDirectory = "${resolve4(__dirname2, "..", "..")}"
41670
+ $Shortcut.Description = "Offhand Daemon - Auto-start on login"
41671
+ $Shortcut.Save()`;
41672
+ const scriptPath = join9(tmpdir3(), "offhand-create-shortcut.ps1");
41673
+ writeFileSync4(scriptPath, psScript);
41674
+ const result = spawnSync("powershell", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", scriptPath], { encoding: "utf8", shell: true });
41675
+ try {
41676
+ rmSync(scriptPath);
41677
+ } catch {
41678
+ }
41679
+ return { success: result.status === 0, output: result.stdout + result.stderr };
41680
+ }
41681
+ function checkShortcutExists(shortcutPath) {
41682
+ return existsSync7(shortcutPath);
41683
+ }
41684
+ function installAutostart(config) {
41685
+ const wrapperPath = getWrapperPath();
41686
+ const script = buildWrapperScript(config);
41687
+ writeFileSync4(wrapperPath, script);
41688
+ const shortcutPath = getShortcutPath();
41689
+ const result = createShortcut(wrapperPath, shortcutPath);
41690
+ if (result.success) {
41691
+ console.log(`[autostart] Created shortcut at: ${shortcutPath}`);
41692
+ console.log(`[autostart] Wrapper script: ${wrapperPath}`);
41693
+ } else {
41694
+ console.error(`[autostart] Failed to create shortcut: ${result.output}`);
41695
+ }
41696
+ return result;
41697
+ }
41698
+ function getAutostartStatus() {
41699
+ const shortcutPath = getShortcutPath();
41700
+ const installed = checkShortcutExists(shortcutPath);
41701
+ const wrapperPath = getWrapperPath();
41702
+ const wrapperExists = existsSync7(wrapperPath);
41703
+ let details = `Shortcut: ${shortcutPath} (${installed ? "EXISTS" : "MISSING"})`;
41704
+ if (wrapperExists) {
41705
+ details += `
41706
+ Wrapper: ${wrapperPath} (EXISTS)`;
41707
+ }
41708
+ return { installed, details };
41709
+ }
41710
+ function saveConfig(config) {
41711
+ const configPath = join9(OFFHAND_HOME, "autostart.json");
41712
+ writeFileSync4(configPath, JSON.stringify(config, null, 2));
41713
+ }
41714
+ function getConfigFromStore() {
41715
+ const workspaces = ["C:\\Users\\udbha\\dev\\offhand"];
41716
+ return {
41717
+ workspaces,
41718
+ port: 4317,
41719
+ relayUrl: void 0,
41720
+ devUrl: void 0,
41721
+ approvalTimeout: 300,
41722
+ webUrl: "https://offhand-web.onrender.com"
41723
+ };
41724
+ }
41725
+
41525
41726
  // ../daemon/src/index.ts
41526
41727
  var args = process.argv.slice(2);
41527
41728
  if (args[0] === "drop") {
@@ -41554,9 +41755,9 @@ var approvalTimeoutMs = Number(argValue("--approval-timeout") ?? 300) * 1e3;
41554
41755
  var webUrl = argValue("--web-url") ?? "https://offhand-web.onrender.com";
41555
41756
  var devUrl = argValue("--dev-url");
41556
41757
  var store = new Store();
41557
- var wsArgs = argValues("--workspace").map((w2) => resolve4(w2));
41758
+ var wsArgs = argValues("--workspace").map((w2) => resolve5(w2));
41558
41759
  for (const w2 of wsArgs) {
41559
- if (!existsSync7(w2)) {
41760
+ if (!existsSync8(w2)) {
41560
41761
  console.error(`workspace does not exist: ${w2}`);
41561
41762
  process.exit(1);
41562
41763
  }
@@ -41588,6 +41789,26 @@ if (m2.type === "manifest") {
41588
41789
  new LocalSessionServer(manager, port, broker);
41589
41790
  console.log(` local : ws://127.0.0.1:${port}`);
41590
41791
  console.log(` approvals : timeout ${approvalTimeoutMs / 1e3}s then auto-deny`);
41792
+ var autostartStatus = getAutostartStatus();
41793
+ if (!autostartStatus.installed) {
41794
+ console.log(` autostart : installing...`);
41795
+ const config = getConfigFromStore();
41796
+ config.workspaces = store.listWorkspaces().map((w2) => w2.path);
41797
+ config.port = port;
41798
+ config.relayUrl = relayUrl;
41799
+ config.devUrl = devUrl;
41800
+ config.approvalTimeout = approvalTimeoutMs / 1e3;
41801
+ config.webUrl = webUrl;
41802
+ saveConfig(config);
41803
+ const { success, output } = installAutostart(config);
41804
+ if (success) {
41805
+ console.log(` autostart : installed (runs on login)`);
41806
+ } else {
41807
+ console.warn(` autostart : failed to install: ${output}`);
41808
+ }
41809
+ } else {
41810
+ console.log(` autostart : already installed`);
41811
+ }
41591
41812
  if (relayUrl) {
41592
41813
  const pairing = await ensurePairing(relayUrl, forceRepair, webUrl);
41593
41814
  manager.capture = captureScreenshot;
package/package.json CHANGED
@@ -1,28 +1,26 @@
1
- {
2
- "name": "offhands",
3
- "version": "0.1.1",
4
- "description": "Reserved.",
5
- "license": "UNLICENSED",
6
- "type": "module",
7
- "bin": {
8
- "offhands": "bin/offhands.mjs"
9
- },
10
- "files": [
11
- "bin",
12
- "dist",
13
- "approval-mcp.mjs",
14
- "README.md"
15
- ],
16
- "engines": {
17
- "node": ">=22.5.0"
18
- },
19
- "scripts": {
20
- "build": "node build.mjs",
21
- "typecheck": "node --version",
22
- "test": "node --version",
23
- "prepublishOnly": "node build.mjs"
24
- },
25
- "devDependencies": {
26
- "esbuild": "^0.24.0"
27
- }
28
- }
1
+ {
2
+ "name": "offhands",
3
+ "version": "0.1.2",
4
+ "description": "Phone → coding-agent relay. Daemon runs on your laptop, PWA on your phone. E2E encrypted.",
5
+ "license": "UNLICENSED",
6
+ "type": "module",
7
+ "bin": {
8
+ "offhands": "bin/offhands.mjs"
9
+ },
10
+ "files": [
11
+ "bin",
12
+ "dist",
13
+ "approval-mcp.mjs",
14
+ "README.md"
15
+ ],
16
+ "engines": {
17
+ "node": ">=22.5.0"
18
+ },
19
+ "scripts": {
20
+ "typecheck": "node --version",
21
+ "test": "node --version"
22
+ },
23
+ "devDependencies": {
24
+ "esbuild": "^0.24.0"
25
+ }
26
+ }