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.
- package/dist/daemon.mjs +256 -35
- 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
|
|
4780
|
-
import { existsSync as
|
|
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((
|
|
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((
|
|
4957
|
+
return new Promise((resolve6) => {
|
|
4935
4958
|
const p2 = spawn(this.resolveBin(), ["--version"], { stdio: "ignore" });
|
|
4936
|
-
p2.on("error", () =>
|
|
4937
|
-
p2.on("exit", (code) =>
|
|
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((
|
|
5078
|
+
return new Promise((resolve6) => {
|
|
5056
5079
|
const p2 = spawn2(bin, ["--version"], { stdio: "ignore", shell: process.platform === "win32" });
|
|
5057
|
-
p2.on("error", () =>
|
|
5058
|
-
p2.on("exit", (code) =>
|
|
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((
|
|
5203
|
+
return new Promise((resolve6) => {
|
|
5181
5204
|
const p2 = spawn3(cmd[0], [...cmd.slice(1), "--version"], { stdio: "ignore" });
|
|
5182
|
-
p2.on("error", () =>
|
|
5183
|
-
p2.on("exit", (code) =>
|
|
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
|
-
|
|
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
|
|
10554
|
-
*
|
|
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({
|
|
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(
|
|
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
|
-
|
|
10714
|
+
resolve6();
|
|
10663
10715
|
} catch (err2) {
|
|
10664
10716
|
reject(err2);
|
|
10665
10717
|
}
|
|
10666
10718
|
};
|
|
10667
10719
|
Module2.useBackupModule = function() {
|
|
10668
|
-
return new Promise(function(
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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((
|
|
37411
|
+
return new Promise((resolve8, reject3) => {
|
|
37360
37412
|
Module3["instantiateWasm"](info, (inst, mod) => {
|
|
37361
|
-
|
|
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((
|
|
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
|
-
|
|
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((
|
|
38034
|
+
return new Promise((resolve7, reject2) => {
|
|
37983
38035
|
Module2["instantiateWasm"](info, (inst, mod) => {
|
|
37984
|
-
|
|
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
|
|
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(
|
|
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) =>
|
|
41758
|
+
var wsArgs = argValues("--workspace").map((w2) => resolve5(w2));
|
|
41558
41759
|
for (const w2 of wsArgs) {
|
|
41559
|
-
if (!
|
|
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.
|
|
4
|
-
"description": "
|
|
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
|
-
"
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
+
}
|