offhands 0.1.1 → 0.1.3
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 +659 -103
- 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 existsSync9 } 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 : [];
|
|
@@ -4874,11 +4897,11 @@ function contextUsage(usage, costUsd) {
|
|
|
4874
4897
|
function summariseToolInput(name, input) {
|
|
4875
4898
|
if (typeof input !== "object" || input === null) return name;
|
|
4876
4899
|
const i2 = input;
|
|
4877
|
-
const
|
|
4900
|
+
const firstString3 = (...keys) => {
|
|
4878
4901
|
for (const k2 of keys) if (typeof i2[k2] === "string" && i2[k2] !== "") return i2[k2];
|
|
4879
4902
|
return void 0;
|
|
4880
4903
|
};
|
|
4881
|
-
const hint =
|
|
4904
|
+
const hint = firstString3("file_path", "path", "command", "pattern", "query", "url", "description") ?? "";
|
|
4882
4905
|
return hint ? `${name}: ${truncate(hint, 120)}` : name;
|
|
4883
4906
|
}
|
|
4884
4907
|
function truncate(s2, max) {
|
|
@@ -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() {
|
|
@@ -5274,11 +5297,345 @@ stderr: ${stderrTail}` : ""}`
|
|
|
5274
5297
|
}
|
|
5275
5298
|
};
|
|
5276
5299
|
|
|
5300
|
+
// ../daemon/src/runners/opencode-cli.ts
|
|
5301
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
5302
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
5303
|
+
import { homedir as homedir3 } from "node:os";
|
|
5304
|
+
import { delimiter as delimiter2, join as join3 } from "node:path";
|
|
5305
|
+
|
|
5306
|
+
// ../daemon/src/runners/opencode-map.ts
|
|
5307
|
+
function mapOpenCodeEvent(value) {
|
|
5308
|
+
if (typeof value !== "object" || value === null) return [];
|
|
5309
|
+
const v2 = value;
|
|
5310
|
+
const type = typeof v2.type === "string" ? v2.type : "";
|
|
5311
|
+
const data = v2.data ?? {};
|
|
5312
|
+
if (type === "text_delta" || type === "message.delta" && data.type === "text") {
|
|
5313
|
+
const chunk = typeof data.text === "string" ? data.text : "";
|
|
5314
|
+
return chunk ? [{ type: "text", chunk }] : [];
|
|
5315
|
+
}
|
|
5316
|
+
if (type === "thinking_delta" || type === "message.delta" && data.type === "thinking") {
|
|
5317
|
+
const chunk = typeof data.thinking === "string" ? data.thinking : "";
|
|
5318
|
+
return chunk ? [{ type: "thinking", chunk }] : [];
|
|
5319
|
+
}
|
|
5320
|
+
if (type === "tool_call_start" || type === "message.delta" && data.type === "tool_use") {
|
|
5321
|
+
const name = firstString2(data, "toolName", "name", "tool") ?? "tool";
|
|
5322
|
+
const summary = firstString2(data, "command", "path", "file", "summary", "description", "query") ?? "";
|
|
5323
|
+
return [{ type: "tool", name, summary: summary ? `${name}: ${truncate3(summary, 120)}` : name }];
|
|
5324
|
+
}
|
|
5325
|
+
if (type === "tool_call_end" || type === "tool_call_result") {
|
|
5326
|
+
return [];
|
|
5327
|
+
}
|
|
5328
|
+
if (type === "permission.request" || type === "permission_request") {
|
|
5329
|
+
const id = firstString2(data, "id", "permissionId") ?? crypto.randomUUID();
|
|
5330
|
+
const action = firstString2(data, "toolName", "action", "command") ?? "action";
|
|
5331
|
+
const detail = firstString2(data, "detail", "description", "message") ?? "";
|
|
5332
|
+
const risk = firstString2(data, "risk") === "high" ? "high" : "low";
|
|
5333
|
+
const preview = firstString2(data, "preview", "diff", "command") ?? void 0;
|
|
5334
|
+
const question = data.question ? mapQuestion(data.question) : void 0;
|
|
5335
|
+
return [{
|
|
5336
|
+
type: "approval",
|
|
5337
|
+
id,
|
|
5338
|
+
action,
|
|
5339
|
+
detail,
|
|
5340
|
+
risk,
|
|
5341
|
+
preview,
|
|
5342
|
+
question
|
|
5343
|
+
}];
|
|
5344
|
+
}
|
|
5345
|
+
if (type === "result" || type === "completed" || type === "done") {
|
|
5346
|
+
const isError = v2.is_error === true || v2.error === true || typeof data.exitCode === "number" && data.exitCode !== 0;
|
|
5347
|
+
const text = firstString2(v2, "result", "text", "summary", "message") ?? "";
|
|
5348
|
+
if (isError) {
|
|
5349
|
+
return [{ type: "error", message: text || `run failed (${String(v2.subtype ?? v2.type ?? "unknown")})` }];
|
|
5350
|
+
}
|
|
5351
|
+
const events = [{ type: "done", summary: text }];
|
|
5352
|
+
const usage = extractUsage(v2, data);
|
|
5353
|
+
if (usage) events.push(usage);
|
|
5354
|
+
return events;
|
|
5355
|
+
}
|
|
5356
|
+
if (type === "error" || type === "failed") {
|
|
5357
|
+
const message = firstString2(v2, "message", "error", "detail", "result") ?? "unknown error";
|
|
5358
|
+
return [{ type: "error", message }];
|
|
5359
|
+
}
|
|
5360
|
+
if (type === "rate_limit" || type === "usage") {
|
|
5361
|
+
const usage = extractUsage(v2, data);
|
|
5362
|
+
return usage ? [usage] : [];
|
|
5363
|
+
}
|
|
5364
|
+
if (type === "session.created" || type === "session.updated") {
|
|
5365
|
+
return [];
|
|
5366
|
+
}
|
|
5367
|
+
return [];
|
|
5368
|
+
}
|
|
5369
|
+
function extractOpenCodeSessionId(value) {
|
|
5370
|
+
if (typeof value !== "object" || value === null) return null;
|
|
5371
|
+
const v2 = value;
|
|
5372
|
+
const data = v2.data ?? {};
|
|
5373
|
+
return firstString2(v2, "sessionId", "session_id", "id") ?? firstString2(data, "sessionId", "session_id", "id") ?? null;
|
|
5374
|
+
}
|
|
5375
|
+
function mapQuestion(q2) {
|
|
5376
|
+
if (typeof q2 !== "object" || q2 === null) return void 0;
|
|
5377
|
+
const qq = q2;
|
|
5378
|
+
const text = typeof qq.text === "string" ? qq.text : "";
|
|
5379
|
+
const options = Array.isArray(qq.options) ? qq.options.filter((o2) => typeof o2 === "object" && o2 !== null).map((o2) => ({
|
|
5380
|
+
label: typeof o2.label === "string" ? o2.label : "",
|
|
5381
|
+
description: typeof o2.description === "string" ? o2.description : void 0
|
|
5382
|
+
})) : [];
|
|
5383
|
+
const multiSelect = typeof qq.multiSelect === "boolean" ? qq.multiSelect : void 0;
|
|
5384
|
+
if (!text && options.length === 0) return void 0;
|
|
5385
|
+
return { text, options, multiSelect };
|
|
5386
|
+
}
|
|
5387
|
+
function extractUsage(v2, data) {
|
|
5388
|
+
const usage = v2.usage ?? data.usage;
|
|
5389
|
+
const costUsd = typeof v2.costUsd === "number" ? v2.costUsd : typeof data.costUsd === "number" ? data.costUsd : typeof v2.total_cost_usd === "number" ? v2.total_cost_usd : void 0;
|
|
5390
|
+
if (!usage && costUsd === void 0) return null;
|
|
5391
|
+
const n2 = (k2) => {
|
|
5392
|
+
const val = usage?.[k2] ?? data[k2];
|
|
5393
|
+
return typeof val === "number" ? val : 0;
|
|
5394
|
+
};
|
|
5395
|
+
const contextTokens = n2("inputTokens") + n2("input_tokens") + n2("cacheReadInputTokens") + n2("cache_read_input_tokens") + n2("cacheCreationInputTokens") + n2("cache_creation_input_tokens") + n2("outputTokens") + n2("output_tokens") + n2("reasoningTokens") + n2("reasoning_tokens");
|
|
5396
|
+
if (contextTokens <= 0 && costUsd === void 0) return null;
|
|
5397
|
+
return {
|
|
5398
|
+
type: "usage",
|
|
5399
|
+
contextTokens,
|
|
5400
|
+
contextWindow: 2e5,
|
|
5401
|
+
// OpenCode doesn't expose window; use reasonable default
|
|
5402
|
+
...costUsd !== void 0 ? { costUsd } : {}
|
|
5403
|
+
};
|
|
5404
|
+
}
|
|
5405
|
+
function firstString2(obj, ...keys) {
|
|
5406
|
+
for (const k2 of keys) {
|
|
5407
|
+
const val = obj[k2];
|
|
5408
|
+
if (typeof val === "string" && val !== "") return val;
|
|
5409
|
+
}
|
|
5410
|
+
return void 0;
|
|
5411
|
+
}
|
|
5412
|
+
function truncate3(s2, max) {
|
|
5413
|
+
return s2.length <= max ? s2 : s2.slice(0, max - 1) + "\u2026";
|
|
5414
|
+
}
|
|
5415
|
+
|
|
5416
|
+
// ../daemon/src/runners/opencode-cli.ts
|
|
5417
|
+
var OpenCodeRunner = class {
|
|
5418
|
+
id = "opencode";
|
|
5419
|
+
name = "OpenCode";
|
|
5420
|
+
supportsApprovals = true;
|
|
5421
|
+
models = [];
|
|
5422
|
+
// populated dynamically via `opencode models`
|
|
5423
|
+
/** [command, ...prefixArgs] resolved once. */
|
|
5424
|
+
resolved = null;
|
|
5425
|
+
modelsCache = [];
|
|
5426
|
+
resolveCommand() {
|
|
5427
|
+
if (this.resolved) return this.resolved;
|
|
5428
|
+
if (process.platform === "win32") {
|
|
5429
|
+
const pathDirs = (process.env.PATH ?? "").split(delimiter2);
|
|
5430
|
+
const npmGlobalPaths = [
|
|
5431
|
+
process.env.APPDATA ? join3(process.env.APPDATA, "npm") : null,
|
|
5432
|
+
process.env.LOCALAPPDATA ? join3(process.env.LOCALAPPDATA, "npm") : null,
|
|
5433
|
+
"C:\\nvm4w\\nodejs",
|
|
5434
|
+
"C:\\Program Files\\nodejs",
|
|
5435
|
+
"C:\\Program Files (x86)\\nodejs"
|
|
5436
|
+
].filter((p2) => p2 !== null && existsSync3(p2));
|
|
5437
|
+
const allDirs = [...pathDirs, ...npmGlobalPaths];
|
|
5438
|
+
for (const pathDir of allDirs) {
|
|
5439
|
+
if (!pathDir) continue;
|
|
5440
|
+
const cmdPath = join3(pathDir, "opencode.cmd");
|
|
5441
|
+
if (existsSync3(cmdPath)) {
|
|
5442
|
+
this.resolved = [cmdPath];
|
|
5443
|
+
return this.resolved;
|
|
5444
|
+
}
|
|
5445
|
+
const nodeModulesBin = join3(pathDir, "node_modules", ".bin", "opencode.cmd");
|
|
5446
|
+
if (existsSync3(nodeModulesBin)) {
|
|
5447
|
+
this.resolved = [nodeModulesBin];
|
|
5448
|
+
return this.resolved;
|
|
5449
|
+
}
|
|
5450
|
+
for (const bin of ["bun", "pnpm", "yarn"]) {
|
|
5451
|
+
const binPath = join3(pathDir, bin + ".cmd");
|
|
5452
|
+
if (existsSync3(binPath)) {
|
|
5453
|
+
this.resolved = [binPath, "exec", "opencode"];
|
|
5454
|
+
return this.resolved;
|
|
5455
|
+
}
|
|
5456
|
+
}
|
|
5457
|
+
}
|
|
5458
|
+
const native = join3(homedir3(), "AppData", "Local", "opencode", "opencode.exe");
|
|
5459
|
+
if (existsSync3(native)) {
|
|
5460
|
+
this.resolved = [native];
|
|
5461
|
+
return this.resolved;
|
|
5462
|
+
}
|
|
5463
|
+
return null;
|
|
5464
|
+
}
|
|
5465
|
+
this.resolved = ["opencode"];
|
|
5466
|
+
return this.resolved;
|
|
5467
|
+
}
|
|
5468
|
+
async detect() {
|
|
5469
|
+
const cmd = this.resolveCommand();
|
|
5470
|
+
if (!cmd) return false;
|
|
5471
|
+
await this.fetchModels();
|
|
5472
|
+
return new Promise((resolve6) => {
|
|
5473
|
+
const p2 = spawn4(cmd[0], [...cmd.slice(1), "--version"], { stdio: "ignore", shell: process.platform === "win32" });
|
|
5474
|
+
p2.on("error", () => resolve6(false));
|
|
5475
|
+
p2.on("exit", (code) => resolve6(code === 0));
|
|
5476
|
+
});
|
|
5477
|
+
}
|
|
5478
|
+
async fetchModels() {
|
|
5479
|
+
if (this.modelsCache.length > 0) return this.modelsCache;
|
|
5480
|
+
const cmd = this.resolveCommand();
|
|
5481
|
+
if (!cmd) return [];
|
|
5482
|
+
return new Promise((resolve6) => {
|
|
5483
|
+
const p2 = spawn4(cmd[0], [...cmd.slice(1), "models"], {
|
|
5484
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
5485
|
+
shell: process.platform === "win32"
|
|
5486
|
+
});
|
|
5487
|
+
let output = "";
|
|
5488
|
+
p2.stdout.setEncoding("utf8");
|
|
5489
|
+
p2.stdout.on("data", (chunk) => {
|
|
5490
|
+
output += chunk;
|
|
5491
|
+
});
|
|
5492
|
+
p2.on("close", (code) => {
|
|
5493
|
+
if (code === 0) {
|
|
5494
|
+
try {
|
|
5495
|
+
const lines = output.trim().split("\n");
|
|
5496
|
+
const models = lines.map((l2) => l2.trim()).filter((l2) => l2 && !l2.startsWith("opencode/"));
|
|
5497
|
+
this.modelsCache = models;
|
|
5498
|
+
Object.defineProperty(this, "models", {
|
|
5499
|
+
value: models,
|
|
5500
|
+
writable: false,
|
|
5501
|
+
configurable: true
|
|
5502
|
+
});
|
|
5503
|
+
resolve6(models);
|
|
5504
|
+
} catch {
|
|
5505
|
+
resolve6([]);
|
|
5506
|
+
}
|
|
5507
|
+
} else {
|
|
5508
|
+
resolve6([]);
|
|
5509
|
+
}
|
|
5510
|
+
});
|
|
5511
|
+
p2.on("error", () => resolve6([]));
|
|
5512
|
+
});
|
|
5513
|
+
}
|
|
5514
|
+
loggedIn() {
|
|
5515
|
+
return existsSync3(join3(homedir3(), ".local", "share", "opencode", "auth.json"));
|
|
5516
|
+
}
|
|
5517
|
+
start(run, callbacks) {
|
|
5518
|
+
const queue = new AsyncEventQueue();
|
|
5519
|
+
const parser = new NdjsonParser();
|
|
5520
|
+
let child;
|
|
5521
|
+
let stderrTail = "";
|
|
5522
|
+
let sawTerminal = false;
|
|
5523
|
+
let reportedConversation = false;
|
|
5524
|
+
const emit = (events) => {
|
|
5525
|
+
for (const e of events) {
|
|
5526
|
+
if (e.type === "done" || e.type === "error") sawTerminal = true;
|
|
5527
|
+
queue.push(e);
|
|
5528
|
+
}
|
|
5529
|
+
};
|
|
5530
|
+
const cmd = this.resolveCommand();
|
|
5531
|
+
if (!cmd) {
|
|
5532
|
+
queue.push({ type: "error", message: "opencode CLI not found" });
|
|
5533
|
+
queue.close();
|
|
5534
|
+
return { events: queue, respond: () => {
|
|
5535
|
+
}, cancel: () => {
|
|
5536
|
+
} };
|
|
5537
|
+
}
|
|
5538
|
+
const args2 = [
|
|
5539
|
+
...cmd.slice(1),
|
|
5540
|
+
"run",
|
|
5541
|
+
run.prompt,
|
|
5542
|
+
"--format",
|
|
5543
|
+
"json",
|
|
5544
|
+
"--no-color"
|
|
5545
|
+
];
|
|
5546
|
+
if (run.model) {
|
|
5547
|
+
args2.push("--model", run.model);
|
|
5548
|
+
}
|
|
5549
|
+
if (run.resumeConversationId) {
|
|
5550
|
+
args2.push("--session", run.resumeConversationId);
|
|
5551
|
+
}
|
|
5552
|
+
const mode = run.permissionMode ?? "guarded";
|
|
5553
|
+
if (mode === "bypass") {
|
|
5554
|
+
args2.push("--auto");
|
|
5555
|
+
} else if (mode === "plan") {
|
|
5556
|
+
args2.push("--agent", "plan");
|
|
5557
|
+
} else {
|
|
5558
|
+
}
|
|
5559
|
+
if (run.effort) {
|
|
5560
|
+
const variantMap = { low: "minimal", medium: "high", high: "max", max: "max" };
|
|
5561
|
+
const variant = variantMap[run.effort];
|
|
5562
|
+
if (variant) {
|
|
5563
|
+
args2.push("--variant", variant);
|
|
5564
|
+
args2.push("--thinking");
|
|
5565
|
+
}
|
|
5566
|
+
}
|
|
5567
|
+
if (run.attachments?.length) {
|
|
5568
|
+
for (const file of run.attachments) {
|
|
5569
|
+
args2.push("--file", file);
|
|
5570
|
+
}
|
|
5571
|
+
}
|
|
5572
|
+
args2.push("--dir", run.workspace);
|
|
5573
|
+
try {
|
|
5574
|
+
child = spawn4(cmd[0], args2, {
|
|
5575
|
+
cwd: run.workspace,
|
|
5576
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
5577
|
+
shell: process.platform === "win32"
|
|
5578
|
+
});
|
|
5579
|
+
} catch (e) {
|
|
5580
|
+
queue.push({ type: "error", message: `failed to spawn opencode: ${String(e)}` });
|
|
5581
|
+
queue.close();
|
|
5582
|
+
return { events: queue, respond: () => {
|
|
5583
|
+
}, cancel: () => {
|
|
5584
|
+
} };
|
|
5585
|
+
}
|
|
5586
|
+
child.stdout.setEncoding("utf8");
|
|
5587
|
+
child.stdout.on("data", (chunk) => {
|
|
5588
|
+
for (const result of parser.push(chunk)) {
|
|
5589
|
+
if (result.ok) {
|
|
5590
|
+
if (!reportedConversation) {
|
|
5591
|
+
const id = extractOpenCodeSessionId(result.value);
|
|
5592
|
+
if (id) {
|
|
5593
|
+
reportedConversation = true;
|
|
5594
|
+
callbacks?.onConversationId?.(id);
|
|
5595
|
+
}
|
|
5596
|
+
}
|
|
5597
|
+
emit(mapOpenCodeEvent(result.value));
|
|
5598
|
+
}
|
|
5599
|
+
}
|
|
5600
|
+
});
|
|
5601
|
+
child.stderr.setEncoding("utf8");
|
|
5602
|
+
child.stderr.on("data", (chunk) => {
|
|
5603
|
+
stderrTail = (stderrTail + chunk).slice(-2e3);
|
|
5604
|
+
});
|
|
5605
|
+
child.on("error", (err) => {
|
|
5606
|
+
emit([{ type: "error", message: `opencode process error: ${err.message}` }]);
|
|
5607
|
+
queue.close();
|
|
5608
|
+
});
|
|
5609
|
+
child.on("close", (code) => {
|
|
5610
|
+
for (const result of parser.flush()) {
|
|
5611
|
+
if (result.ok) emit(mapOpenCodeEvent(result.value));
|
|
5612
|
+
}
|
|
5613
|
+
if (!sawTerminal) {
|
|
5614
|
+
emit([
|
|
5615
|
+
{
|
|
5616
|
+
type: "error",
|
|
5617
|
+
message: `opencode exited with code ${code ?? "unknown"} before a result${stderrTail ? `
|
|
5618
|
+
stderr: ${stderrTail}` : ""}`
|
|
5619
|
+
}
|
|
5620
|
+
]);
|
|
5621
|
+
}
|
|
5622
|
+
queue.close();
|
|
5623
|
+
});
|
|
5624
|
+
return {
|
|
5625
|
+
events: queue,
|
|
5626
|
+
respond: (approvalId, ok, answer) => {
|
|
5627
|
+
},
|
|
5628
|
+
cancel: () => child.kill()
|
|
5629
|
+
};
|
|
5630
|
+
}
|
|
5631
|
+
};
|
|
5632
|
+
|
|
5277
5633
|
// ../daemon/src/session-manager.ts
|
|
5278
5634
|
import { randomUUID } from "node:crypto";
|
|
5279
5635
|
import { hostname, platform, tmpdir } from "node:os";
|
|
5280
|
-
import { existsSync as
|
|
5281
|
-
import { dirname as dirname2, join as
|
|
5636
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync2, readdirSync as readdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
5637
|
+
import { dirname as dirname2, join as join6, basename as basename3, parse as parse2, resolve as resolve3 } from "node:path";
|
|
5638
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
5282
5639
|
|
|
5283
5640
|
// ../daemon/src/receipts.ts
|
|
5284
5641
|
import { execFile } from "node:child_process";
|
|
@@ -5342,25 +5699,25 @@ function shouldScreenshot(touchedFiles, devUrl2) {
|
|
|
5342
5699
|
}
|
|
5343
5700
|
|
|
5344
5701
|
// ../daemon/src/claude-history.ts
|
|
5345
|
-
import { existsSync as
|
|
5346
|
-
import { homedir as
|
|
5347
|
-
import { basename, join as
|
|
5348
|
-
var DEFAULT_PROJECTS_DIR =
|
|
5702
|
+
import { existsSync as existsSync4, readdirSync, readFileSync, statSync } from "node:fs";
|
|
5703
|
+
import { homedir as homedir4 } from "node:os";
|
|
5704
|
+
import { basename, join as join4, resolve } from "node:path";
|
|
5705
|
+
var DEFAULT_PROJECTS_DIR = join4(homedir4(), ".claude", "projects");
|
|
5349
5706
|
function listClaudeConversations(workspace, projectsDir = process.env.OFFHAND_CLAUDE_PROJECTS_DIR ?? DEFAULT_PROJECTS_DIR) {
|
|
5350
5707
|
const projectDir = findProjectDir(workspace, projectsDir);
|
|
5351
5708
|
if (!projectDir) return [];
|
|
5352
5709
|
return listJsonlFiles(projectDir).map((path) => summarizeConversation(path, workspace)).filter((c2) => c2 !== null).sort((a2, b2) => b2.lastActiveMs - a2.lastActiveMs).slice(0, 50);
|
|
5353
5710
|
}
|
|
5354
5711
|
function findProjectDir(workspace, projectsDir) {
|
|
5355
|
-
if (!
|
|
5712
|
+
if (!existsSync4(projectsDir)) return null;
|
|
5356
5713
|
const candidates = /* @__PURE__ */ new Set([encodeWorkspace(workspace), encodeWorkspace(resolve(workspace))]);
|
|
5357
5714
|
for (const candidate of candidates) {
|
|
5358
|
-
const exact =
|
|
5359
|
-
if (
|
|
5715
|
+
const exact = join4(projectsDir, candidate);
|
|
5716
|
+
if (existsSync4(exact)) return exact;
|
|
5360
5717
|
}
|
|
5361
5718
|
const lower = new Set([...candidates].map((c2) => c2.toLowerCase()));
|
|
5362
5719
|
for (const entry of safeReadDir(projectsDir)) {
|
|
5363
|
-
if (entry.isDirectory() && lower.has(entry.name.toLowerCase())) return
|
|
5720
|
+
if (entry.isDirectory() && lower.has(entry.name.toLowerCase())) return join4(projectsDir, entry.name);
|
|
5364
5721
|
}
|
|
5365
5722
|
return null;
|
|
5366
5723
|
}
|
|
@@ -5368,12 +5725,12 @@ function encodeWorkspace(workspace) {
|
|
|
5368
5725
|
return workspace.replace(/[^A-Za-z0-9]/g, "-");
|
|
5369
5726
|
}
|
|
5370
5727
|
function listJsonlFiles(projectDir) {
|
|
5371
|
-
const files = safeReadDir(projectDir).filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map((entry) =>
|
|
5372
|
-
const sessionsDir =
|
|
5373
|
-
if (!
|
|
5728
|
+
const files = safeReadDir(projectDir).filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map((entry) => join4(projectDir, entry.name));
|
|
5729
|
+
const sessionsDir = join4(projectDir, "sessions");
|
|
5730
|
+
if (!existsSync4(sessionsDir)) return files;
|
|
5374
5731
|
return [
|
|
5375
5732
|
...files,
|
|
5376
|
-
...safeReadDir(sessionsDir).filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map((entry) =>
|
|
5733
|
+
...safeReadDir(sessionsDir).filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map((entry) => join4(sessionsDir, entry.name))
|
|
5377
5734
|
];
|
|
5378
5735
|
}
|
|
5379
5736
|
function summarizeConversation(path, workspace) {
|
|
@@ -5392,7 +5749,7 @@ function summarizeConversation(path, workspace) {
|
|
|
5392
5749
|
return {
|
|
5393
5750
|
conversationId: basename(path, ".jsonl"),
|
|
5394
5751
|
workspace,
|
|
5395
|
-
firstPrompt:
|
|
5752
|
+
firstPrompt: truncate4(firstPrompt || "(empty prompt)", 120),
|
|
5396
5753
|
lastActiveMs: Math.floor(statSync(path).mtimeMs),
|
|
5397
5754
|
messageCount
|
|
5398
5755
|
};
|
|
@@ -5418,7 +5775,7 @@ function userMessageText(value) {
|
|
|
5418
5775
|
function collapseText(text) {
|
|
5419
5776
|
return text.replace(/\s+/g, " ").trim();
|
|
5420
5777
|
}
|
|
5421
|
-
function
|
|
5778
|
+
function truncate4(text, max) {
|
|
5422
5779
|
return text.length <= max ? text : `${text.slice(0, max - 1)}\u2026`;
|
|
5423
5780
|
}
|
|
5424
5781
|
function safeReadDir(path) {
|
|
@@ -5435,7 +5792,7 @@ function isRecord(value) {
|
|
|
5435
5792
|
// ../daemon/src/drop.ts
|
|
5436
5793
|
import {
|
|
5437
5794
|
copyFileSync,
|
|
5438
|
-
existsSync as
|
|
5795
|
+
existsSync as existsSync5,
|
|
5439
5796
|
mkdirSync,
|
|
5440
5797
|
readdirSync as readdirSync2,
|
|
5441
5798
|
readFileSync as readFileSync2,
|
|
@@ -5444,18 +5801,18 @@ import {
|
|
|
5444
5801
|
watch,
|
|
5445
5802
|
writeFileSync
|
|
5446
5803
|
} from "node:fs";
|
|
5447
|
-
import { homedir as
|
|
5448
|
-
import { basename as basename2, join as
|
|
5449
|
-
import { spawn as
|
|
5804
|
+
import { homedir as homedir5 } from "node:os";
|
|
5805
|
+
import { basename as basename2, join as join5, parse, resolve as resolve2 } from "node:path";
|
|
5806
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
5450
5807
|
var DROP_LOG_SESSION_ID = "drop";
|
|
5451
5808
|
function offhandHome() {
|
|
5452
|
-
return process.env.OFFHAND_HOME ??
|
|
5809
|
+
return process.env.OFFHAND_HOME ?? join5(homedir5(), ".offhand");
|
|
5453
5810
|
}
|
|
5454
5811
|
function dropOutboxDir(home = offhandHome()) {
|
|
5455
|
-
return
|
|
5812
|
+
return join5(home, "drop-outbox");
|
|
5456
5813
|
}
|
|
5457
5814
|
function incomingDropDir() {
|
|
5458
|
-
return
|
|
5815
|
+
return join5(homedir5(), "Downloads", "offhand");
|
|
5459
5816
|
}
|
|
5460
5817
|
function safeDropName(input) {
|
|
5461
5818
|
const base = basename2(input).replace(/[<>:"/\\|?*\x00-\x1F]/g, "_").replace(/[. ]+$/g, "").trim();
|
|
@@ -5479,13 +5836,13 @@ function queueDropFileForPhone(sourcePath, outbox = dropOutboxDir()) {
|
|
|
5479
5836
|
const st = statSync2(source);
|
|
5480
5837
|
if (!st.isFile()) throw new Error(`not a file: ${sourcePath}`);
|
|
5481
5838
|
mkdirSync(outbox, { recursive: true });
|
|
5482
|
-
const dest =
|
|
5839
|
+
const dest = join5(outbox, dedupeDropName(basename2(source), safeNames(outbox)));
|
|
5483
5840
|
copyFileSync(source, dest);
|
|
5484
5841
|
return dest;
|
|
5485
5842
|
}
|
|
5486
5843
|
function saveIncomingDrop(name, bytes, dir = incomingDropDir()) {
|
|
5487
5844
|
mkdirSync(dir, { recursive: true });
|
|
5488
|
-
const path =
|
|
5845
|
+
const path = join5(dir, dedupeDropName(name, safeNames(dir)));
|
|
5489
5846
|
writeFileSync(path, bytes);
|
|
5490
5847
|
return path;
|
|
5491
5848
|
}
|
|
@@ -5528,7 +5885,7 @@ function startDropOutboxWatcher(outbox, sendFile) {
|
|
|
5528
5885
|
console.error(`drop: sent but could not delete ${path}: ${e instanceof Error ? e.message : String(e)}`);
|
|
5529
5886
|
}
|
|
5530
5887
|
} catch (e) {
|
|
5531
|
-
if (
|
|
5888
|
+
if (existsSync5(path)) {
|
|
5532
5889
|
console.error(`drop: failed to send ${path}: ${e instanceof Error ? e.message : String(e)}`);
|
|
5533
5890
|
schedule(15e3);
|
|
5534
5891
|
}
|
|
@@ -5540,7 +5897,7 @@ function startDropOutboxWatcher(outbox, sendFile) {
|
|
|
5540
5897
|
timer = null;
|
|
5541
5898
|
if (closed) return;
|
|
5542
5899
|
for (const entry of safeEntries(outbox)) {
|
|
5543
|
-
if (entry.isFile()) void processFile(
|
|
5900
|
+
if (entry.isFile()) void processFile(join5(outbox, entry.name));
|
|
5544
5901
|
}
|
|
5545
5902
|
};
|
|
5546
5903
|
watcher = watch(outbox, () => schedule());
|
|
@@ -5568,7 +5925,7 @@ function showDropToast(savedPath, textToClipboard) {
|
|
|
5568
5925
|
`$toast = [Windows.UI.Notifications.ToastNotification]::new($xml)`,
|
|
5569
5926
|
`[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier(${psQuote(appId)}).Show($toast)`
|
|
5570
5927
|
].filter(Boolean);
|
|
5571
|
-
const child =
|
|
5928
|
+
const child = spawn5("powershell", ["-NoProfile", "-Command", lines.join("; ")], {
|
|
5572
5929
|
windowsHide: true,
|
|
5573
5930
|
stdio: ["ignore", "ignore", "pipe"]
|
|
5574
5931
|
});
|
|
@@ -5624,7 +5981,16 @@ function mimeFromName(path) {
|
|
|
5624
5981
|
}
|
|
5625
5982
|
|
|
5626
5983
|
// ../daemon/src/session-manager.ts
|
|
5627
|
-
|
|
5984
|
+
function resolveDaemonVersion() {
|
|
5985
|
+
try {
|
|
5986
|
+
const pkgPath = join6(dirname2(fileURLToPath2(import.meta.url)), "..", "package.json");
|
|
5987
|
+
const pkg = JSON.parse(readFileSync3(pkgPath, "utf8"));
|
|
5988
|
+
return pkg.version ?? "0.0.0";
|
|
5989
|
+
} catch {
|
|
5990
|
+
return "0.0.0";
|
|
5991
|
+
}
|
|
5992
|
+
}
|
|
5993
|
+
var DAEMON_VERSION = resolveDaemonVersion();
|
|
5628
5994
|
var SessionManager = class {
|
|
5629
5995
|
constructor(store2, runners2) {
|
|
5630
5996
|
this.store = store2;
|
|
@@ -5715,9 +6081,9 @@ var SessionManager = class {
|
|
|
5715
6081
|
prompt += "\n\nAttached files (read them from disk):";
|
|
5716
6082
|
for (const a2 of msg.attachments) {
|
|
5717
6083
|
const bytes = await this.attachmentFetcher(a2.blobId);
|
|
5718
|
-
const dir =
|
|
6084
|
+
const dir = join6(tmpdir(), "offhand-attachments");
|
|
5719
6085
|
mkdirSync2(dir, { recursive: true });
|
|
5720
|
-
const path =
|
|
6086
|
+
const path = join6(dir, `${a2.blobId.slice(0, 8)}-${basename3(a2.name)}`);
|
|
5721
6087
|
writeFileSync2(path, bytes);
|
|
5722
6088
|
prompt += `
|
|
5723
6089
|
- ${path} (${a2.mime})`;
|
|
@@ -5955,17 +6321,17 @@ function listFolders(path) {
|
|
|
5955
6321
|
if (!path) return { path: "", parent: null, dirs: driveRoots() };
|
|
5956
6322
|
const current = resolve3(path);
|
|
5957
6323
|
const dirs = safeReadDir2(current).filter((entry) => entry.isDirectory() && !skipDir(entry.name)).map((entry) => {
|
|
5958
|
-
const full =
|
|
5959
|
-
return { name: entry.name, path: full, isGit:
|
|
6324
|
+
const full = join6(current, entry.name);
|
|
6325
|
+
return { name: entry.name, path: full, isGit: existsSync6(join6(full, ".git")) };
|
|
5960
6326
|
}).sort((a2, b2) => Number(b2.isGit) - Number(a2.isGit) || a2.name.localeCompare(b2.name)).slice(0, 200);
|
|
5961
6327
|
return { path: current, parent: isRoot(current) ? null : dirname2(current), dirs };
|
|
5962
6328
|
}
|
|
5963
6329
|
function driveRoots() {
|
|
5964
|
-
if (process.platform !== "win32") return [{ name: "/", path: "/", isGit:
|
|
6330
|
+
if (process.platform !== "win32") return [{ name: "/", path: "/", isGit: existsSync6("/.git") }];
|
|
5965
6331
|
const roots = [];
|
|
5966
6332
|
for (let code = 67; code <= 90; code++) {
|
|
5967
6333
|
const name = `${String.fromCharCode(code)}:\\`;
|
|
5968
|
-
if (
|
|
6334
|
+
if (existsSync6(name)) roots.push({ name, path: name, isGit: existsSync6(join6(name, ".git")) });
|
|
5969
6335
|
}
|
|
5970
6336
|
return roots.sort((a2, b2) => a2.name.localeCompare(b2.name)).slice(0, 200);
|
|
5971
6337
|
}
|
|
@@ -5998,14 +6364,14 @@ function stripTrailing(path) {
|
|
|
5998
6364
|
// ../daemon/src/store.ts
|
|
5999
6365
|
import { DatabaseSync } from "node:sqlite";
|
|
6000
6366
|
import { mkdirSync as mkdirSync3 } from "node:fs";
|
|
6001
|
-
import { homedir as
|
|
6002
|
-
import { join as
|
|
6367
|
+
import { homedir as homedir6 } from "node:os";
|
|
6368
|
+
import { join as join7, basename as basename4 } from "node:path";
|
|
6003
6369
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
6004
6370
|
var Store = class {
|
|
6005
6371
|
db;
|
|
6006
|
-
constructor(dir = process.env.OFFHAND_HOME ??
|
|
6372
|
+
constructor(dir = process.env.OFFHAND_HOME ?? join7(homedir6(), ".offhand")) {
|
|
6007
6373
|
mkdirSync3(dir, { recursive: true });
|
|
6008
|
-
this.db = new DatabaseSync(
|
|
6374
|
+
this.db = new DatabaseSync(join7(dir, "offhand.db"));
|
|
6009
6375
|
this.db.exec(`
|
|
6010
6376
|
PRAGMA journal_mode = WAL;
|
|
6011
6377
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
@@ -10247,8 +10613,15 @@ var coerce = {
|
|
|
10247
10613
|
var NEVER = INVALID;
|
|
10248
10614
|
|
|
10249
10615
|
// ../shared/src/run-events.ts
|
|
10616
|
+
var ApprovalQuestionSchema = external_exports.object({
|
|
10617
|
+
text: external_exports.string(),
|
|
10618
|
+
options: external_exports.array(external_exports.object({ label: external_exports.string(), description: external_exports.string().optional() })),
|
|
10619
|
+
multiSelect: external_exports.boolean().optional()
|
|
10620
|
+
});
|
|
10250
10621
|
var RunEventSchema = external_exports.discriminatedUnion("type", [
|
|
10251
10622
|
external_exports.object({ type: external_exports.literal("text"), chunk: external_exports.string() }),
|
|
10623
|
+
/** Extended-thinking content, streamed the same way as text (medium+ effort tiers). */
|
|
10624
|
+
external_exports.object({ type: external_exports.literal("thinking"), chunk: external_exports.string() }),
|
|
10252
10625
|
external_exports.object({ type: external_exports.literal("tool"), name: external_exports.string(), summary: external_exports.string() }),
|
|
10253
10626
|
external_exports.object({
|
|
10254
10627
|
type: external_exports.literal("approval"),
|
|
@@ -10259,11 +10632,7 @@ var RunEventSchema = external_exports.discriminatedUnion("type", [
|
|
|
10259
10632
|
/** Optional content preview (edit diff / command) shown in the approval sheet. */
|
|
10260
10633
|
preview: external_exports.string().optional(),
|
|
10261
10634
|
/** AskUserQuestion payload: render the actual question with options. */
|
|
10262
|
-
question:
|
|
10263
|
-
text: external_exports.string(),
|
|
10264
|
-
options: external_exports.array(external_exports.object({ label: external_exports.string(), description: external_exports.string().optional() })),
|
|
10265
|
-
multiSelect: external_exports.boolean().optional()
|
|
10266
|
-
}).optional()
|
|
10635
|
+
question: ApprovalQuestionSchema.optional()
|
|
10267
10636
|
}),
|
|
10268
10637
|
/** Encrypted blob reference (M5): phone fetches + decrypts locally. */
|
|
10269
10638
|
external_exports.object({
|
|
@@ -10290,7 +10659,15 @@ var RunEventSchema = external_exports.discriminatedUnion("type", [
|
|
|
10290
10659
|
/** API-equivalent cost of the run (informational for subscription plans). */
|
|
10291
10660
|
costUsd: external_exports.number().optional()
|
|
10292
10661
|
}),
|
|
10293
|
-
external_exports.object({ type: external_exports.literal("error"), message: external_exports.string() })
|
|
10662
|
+
external_exports.object({ type: external_exports.literal("error"), message: external_exports.string() }),
|
|
10663
|
+
/** Account-level plan usage (from the CLI's own rate_limit_event) — real numbers only. */
|
|
10664
|
+
external_exports.object({
|
|
10665
|
+
type: external_exports.literal("rate-limit"),
|
|
10666
|
+
fiveHourUtilization: external_exports.number(),
|
|
10667
|
+
fiveHourResetsAtMs: external_exports.number(),
|
|
10668
|
+
sevenDayUtilization: external_exports.number(),
|
|
10669
|
+
sevenDayResetsAtMs: external_exports.number()
|
|
10670
|
+
})
|
|
10294
10671
|
]);
|
|
10295
10672
|
var PermissionModeSchema = external_exports.enum(["guarded", "plan", "acceptEdits", "bypass"]);
|
|
10296
10673
|
var EffortSchema = external_exports.enum(["low", "medium", "high", "max"]);
|
|
@@ -10550,10 +10927,19 @@ var RelayFrameSchema = external_exports.discriminatedUnion("kind", [
|
|
|
10550
10927
|
}),
|
|
10551
10928
|
/**
|
|
10552
10929
|
* daemon → relay: ask the relay to web-push the session's phones. Carries
|
|
10553
|
-
* ONLY
|
|
10554
|
-
*
|
|
10930
|
+
* ONLY opaque ids — never content (push bodies transit Apple/Google;
|
|
10931
|
+
* 02-architecture.md). `id` is the approval id (approval only); `sessionId`
|
|
10932
|
+
* is the offhand chat session the notification concerns (approval/done/
|
|
10933
|
+
* error) — lets the phone deep-link straight to the right session.
|
|
10555
10934
|
*/
|
|
10556
|
-
external_exports.object({
|
|
10935
|
+
external_exports.object({
|
|
10936
|
+
kind: external_exports.literal("notify"),
|
|
10937
|
+
notice: external_exports.enum(["approval", "drop", "done", "error", "progress"]),
|
|
10938
|
+
id: external_exports.string().optional(),
|
|
10939
|
+
sessionId: external_exports.string().optional(),
|
|
10940
|
+
/** 'progress' only — a bare action count, never the action itself (no content in push). */
|
|
10941
|
+
count: external_exports.number().int().nonnegative().optional()
|
|
10942
|
+
}),
|
|
10557
10943
|
/** relay → daemon: verdict delivered via push-notification action button. */
|
|
10558
10944
|
external_exports.object({ kind: external_exports.literal("verdict"), approvalId: external_exports.string(), approve: external_exports.boolean() }),
|
|
10559
10945
|
external_exports.object({ kind: external_exports.literal("ping") }),
|
|
@@ -10629,9 +11015,9 @@ if (typeof Module.getRandomValue === "undefined") {
|
|
|
10629
11015
|
Module.getRandomValue = randomValuesStandard;
|
|
10630
11016
|
} catch (e) {
|
|
10631
11017
|
try {
|
|
10632
|
-
|
|
11018
|
+
crypto2 = null;
|
|
10633
11019
|
randomValueNodeJS = function() {
|
|
10634
|
-
var buf =
|
|
11020
|
+
var buf = crypto2["randomBytes"](4);
|
|
10635
11021
|
return (buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3]) >>> 0;
|
|
10636
11022
|
};
|
|
10637
11023
|
randomValueNodeJS();
|
|
@@ -10644,10 +11030,10 @@ if (typeof Module.getRandomValue === "undefined") {
|
|
|
10644
11030
|
var window_;
|
|
10645
11031
|
var crypto_;
|
|
10646
11032
|
var randomValuesStandard;
|
|
10647
|
-
var
|
|
11033
|
+
var crypto2;
|
|
10648
11034
|
var randomValueNodeJS;
|
|
10649
11035
|
var _Module = Module;
|
|
10650
|
-
Module.ready = new Promise(function(
|
|
11036
|
+
Module.ready = new Promise(function(resolve6, reject) {
|
|
10651
11037
|
var Module2 = _Module;
|
|
10652
11038
|
Module2.onAbort = reject;
|
|
10653
11039
|
Module2.print = function(what) {
|
|
@@ -10659,13 +11045,13 @@ Module.ready = new Promise(function(resolve5, reject) {
|
|
|
10659
11045
|
Module2.onRuntimeInitialized = function() {
|
|
10660
11046
|
try {
|
|
10661
11047
|
Module2._crypto_secretbox_keybytes();
|
|
10662
|
-
|
|
11048
|
+
resolve6();
|
|
10663
11049
|
} catch (err2) {
|
|
10664
11050
|
reject(err2);
|
|
10665
11051
|
}
|
|
10666
11052
|
};
|
|
10667
11053
|
Module2.useBackupModule = function() {
|
|
10668
|
-
return new Promise(function(
|
|
11054
|
+
return new Promise(function(resolve7, reject2) {
|
|
10669
11055
|
var Module3 = {};
|
|
10670
11056
|
Module3.onAbort = reject2;
|
|
10671
11057
|
Module3.getRandomValue = _Module.getRandomValue;
|
|
@@ -10678,7 +11064,7 @@ Module.ready = new Promise(function(resolve5, reject) {
|
|
|
10678
11064
|
Object.keys(Module3).forEach(function(k2) {
|
|
10679
11065
|
_Module[k2] = Module3[k2];
|
|
10680
11066
|
});
|
|
10681
|
-
|
|
11067
|
+
resolve7();
|
|
10682
11068
|
};
|
|
10683
11069
|
var Module3 = typeof Module3 != "undefined" ? Module3 : {};
|
|
10684
11070
|
var ENVIRONMENT_IS_WEB2 = !!globalThis.window;
|
|
@@ -10738,13 +11124,13 @@ Module.ready = new Promise(function(resolve5, reject) {
|
|
|
10738
11124
|
}
|
|
10739
11125
|
readAsync2 = async (url) => {
|
|
10740
11126
|
if (isFileURI2(url)) {
|
|
10741
|
-
return new Promise((
|
|
11127
|
+
return new Promise((resolve8, reject3) => {
|
|
10742
11128
|
var xhr = new XMLHttpRequest();
|
|
10743
11129
|
xhr.open("GET", url, true);
|
|
10744
11130
|
xhr.responseType = "arraybuffer";
|
|
10745
11131
|
xhr.onload = () => {
|
|
10746
11132
|
if (xhr.status == 200 || xhr.status == 0 && xhr.response) {
|
|
10747
|
-
|
|
11133
|
+
resolve8(xhr.response);
|
|
10748
11134
|
return;
|
|
10749
11135
|
}
|
|
10750
11136
|
reject3(xhr.status);
|
|
@@ -37356,9 +37742,9 @@ Module.ready = new Promise(function(resolve5, reject) {
|
|
|
37356
37742
|
}
|
|
37357
37743
|
var info = getWasmImports2();
|
|
37358
37744
|
if (Module3["instantiateWasm"]) {
|
|
37359
|
-
return new Promise((
|
|
37745
|
+
return new Promise((resolve8, reject3) => {
|
|
37360
37746
|
Module3["instantiateWasm"](info, (inst, mod) => {
|
|
37361
|
-
|
|
37747
|
+
resolve8(receiveInstance(inst, mod));
|
|
37362
37748
|
});
|
|
37363
37749
|
});
|
|
37364
37750
|
}
|
|
@@ -37538,7 +37924,7 @@ Module.ready = new Promise(function(resolve5, reject) {
|
|
|
37538
37924
|
try {
|
|
37539
37925
|
var window_ = "object" === typeof window ? window : self;
|
|
37540
37926
|
var crypto_ = typeof window_.crypto !== "undefined" ? window_.crypto : window_.msCrypto;
|
|
37541
|
-
crypto_ = crypto_ === void 0 ?
|
|
37927
|
+
crypto_ = crypto_ === void 0 ? crypto2 : crypto_;
|
|
37542
37928
|
var randomValuesStandard = function() {
|
|
37543
37929
|
var buf = new Uint32Array(1);
|
|
37544
37930
|
crypto_.getRandomValues(buf);
|
|
@@ -37548,9 +37934,9 @@ Module.ready = new Promise(function(resolve5, reject) {
|
|
|
37548
37934
|
Module3.getRandomValue = randomValuesStandard;
|
|
37549
37935
|
} catch (e) {
|
|
37550
37936
|
try {
|
|
37551
|
-
var
|
|
37937
|
+
var crypto2 = null;
|
|
37552
37938
|
var randomValueNodeJS = function() {
|
|
37553
|
-
var buf =
|
|
37939
|
+
var buf = crypto2["randomBytes"](4);
|
|
37554
37940
|
return (buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3]) >>> 0;
|
|
37555
37941
|
};
|
|
37556
37942
|
randomValueNodeJS();
|
|
@@ -37859,13 +38245,13 @@ Module.ready = new Promise(function(resolve5, reject) {
|
|
|
37859
38245
|
}
|
|
37860
38246
|
readAsync = async (url) => {
|
|
37861
38247
|
if (isFileURI(url)) {
|
|
37862
|
-
return new Promise((
|
|
38248
|
+
return new Promise((resolve7, reject2) => {
|
|
37863
38249
|
var xhr = new XMLHttpRequest();
|
|
37864
38250
|
xhr.open("GET", url, true);
|
|
37865
38251
|
xhr.responseType = "arraybuffer";
|
|
37866
38252
|
xhr.onload = () => {
|
|
37867
38253
|
if (xhr.status == 200 || xhr.status == 0 && xhr.response) {
|
|
37868
|
-
|
|
38254
|
+
resolve7(xhr.response);
|
|
37869
38255
|
return;
|
|
37870
38256
|
}
|
|
37871
38257
|
reject2(xhr.status);
|
|
@@ -37979,9 +38365,9 @@ Module.ready = new Promise(function(resolve5, reject) {
|
|
|
37979
38365
|
}
|
|
37980
38366
|
var info = getWasmImports();
|
|
37981
38367
|
if (Module2["instantiateWasm"]) {
|
|
37982
|
-
return new Promise((
|
|
38368
|
+
return new Promise((resolve7, reject2) => {
|
|
37983
38369
|
Module2["instantiateWasm"](info, (inst, mod) => {
|
|
37984
|
-
|
|
38370
|
+
resolve7(receiveInstance(inst, mod));
|
|
37985
38371
|
});
|
|
37986
38372
|
});
|
|
37987
38373
|
}
|
|
@@ -38196,7 +38582,7 @@ Module.ready = new Promise(function(resolve5, reject) {
|
|
|
38196
38582
|
try {
|
|
38197
38583
|
var window_ = "object" === typeof window ? window : self;
|
|
38198
38584
|
var crypto_ = typeof window_.crypto !== "undefined" ? window_.crypto : window_.msCrypto;
|
|
38199
|
-
crypto_ = crypto_ === void 0 ?
|
|
38585
|
+
crypto_ = crypto_ === void 0 ? crypto2 : crypto_;
|
|
38200
38586
|
var randomValuesStandard = function() {
|
|
38201
38587
|
var buf = new Uint32Array(1);
|
|
38202
38588
|
crypto_.getRandomValues(buf);
|
|
@@ -38206,9 +38592,9 @@ Module.ready = new Promise(function(resolve5, reject) {
|
|
|
38206
38592
|
Module2.getRandomValue = randomValuesStandard;
|
|
38207
38593
|
} catch (e) {
|
|
38208
38594
|
try {
|
|
38209
|
-
var
|
|
38595
|
+
var crypto2 = null;
|
|
38210
38596
|
var randomValueNodeJS = function() {
|
|
38211
|
-
var buf =
|
|
38597
|
+
var buf = crypto2["randomBytes"](4);
|
|
38212
38598
|
return (buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3]) >>> 0;
|
|
38213
38599
|
};
|
|
38214
38600
|
randomValueNodeJS();
|
|
@@ -41133,6 +41519,18 @@ var LocalSessionServer = class {
|
|
|
41133
41519
|
const http = createServer((req, res) => void this.onRequest(req, res));
|
|
41134
41520
|
this.wss = new import_websocket_server.default({ server: http });
|
|
41135
41521
|
this.wss.on("connection", (ws) => this.onConnection(ws));
|
|
41522
|
+
http.on("error", (err) => {
|
|
41523
|
+
if (err.code === "EADDRINUSE") {
|
|
41524
|
+
console.error(
|
|
41525
|
+
`
|
|
41526
|
+
Another offhands daemon is already running (port ${port2} is taken).
|
|
41527
|
+
Run only one \u2014 or start this one on a different port: offhands --port ${port2 + 1}
|
|
41528
|
+
`
|
|
41529
|
+
);
|
|
41530
|
+
process.exit(1);
|
|
41531
|
+
}
|
|
41532
|
+
throw err;
|
|
41533
|
+
});
|
|
41136
41534
|
http.listen(port2, "127.0.0.1");
|
|
41137
41535
|
}
|
|
41138
41536
|
wss;
|
|
@@ -41186,6 +41584,8 @@ var RelayClient = class {
|
|
|
41186
41584
|
heartbeat = null;
|
|
41187
41585
|
detach = null;
|
|
41188
41586
|
stopped = false;
|
|
41587
|
+
/** sessionId → tool-call count for the in-flight run (opaque count only, never the action). */
|
|
41588
|
+
toolCounts = /* @__PURE__ */ new Map();
|
|
41189
41589
|
start() {
|
|
41190
41590
|
this.connect();
|
|
41191
41591
|
}
|
|
@@ -41206,15 +41606,41 @@ var RelayClient = class {
|
|
|
41206
41606
|
if (ws.readyState === wrapper_default.OPEN) {
|
|
41207
41607
|
const envelope = seal(msg, this.keys.tx);
|
|
41208
41608
|
ws.send(JSON.stringify({ kind: "peer", payload: envelope }));
|
|
41609
|
+
if (msg.type === "run-started") {
|
|
41610
|
+
this.toolCounts.set(msg.sessionId, 0);
|
|
41611
|
+
}
|
|
41612
|
+
if (msg.type === "run-event" && msg.event.type === "tool") {
|
|
41613
|
+
const count = (this.toolCounts.get(msg.sessionId) ?? 0) + 1;
|
|
41614
|
+
this.toolCounts.set(msg.sessionId, count);
|
|
41615
|
+
ws.send(
|
|
41616
|
+
JSON.stringify({
|
|
41617
|
+
kind: "notify",
|
|
41618
|
+
notice: "progress",
|
|
41619
|
+
sessionId: msg.sessionId,
|
|
41620
|
+
count
|
|
41621
|
+
})
|
|
41622
|
+
);
|
|
41623
|
+
}
|
|
41209
41624
|
if (msg.type === "run-event" && msg.event.type === "approval") {
|
|
41210
41625
|
ws.send(
|
|
41211
41626
|
JSON.stringify({
|
|
41212
41627
|
kind: "notify",
|
|
41213
41628
|
notice: "approval",
|
|
41214
|
-
id: msg.event.id
|
|
41629
|
+
id: msg.event.id,
|
|
41630
|
+
sessionId: msg.sessionId
|
|
41215
41631
|
})
|
|
41216
41632
|
);
|
|
41217
41633
|
}
|
|
41634
|
+
if (msg.type === "run-event" && msg.event.type === "done") {
|
|
41635
|
+
ws.send(
|
|
41636
|
+
JSON.stringify({ kind: "notify", notice: "done", sessionId: msg.sessionId })
|
|
41637
|
+
);
|
|
41638
|
+
}
|
|
41639
|
+
if (msg.type === "run-event" && msg.event.type === "error") {
|
|
41640
|
+
ws.send(
|
|
41641
|
+
JSON.stringify({ kind: "notify", notice: "error", sessionId: msg.sessionId })
|
|
41642
|
+
);
|
|
41643
|
+
}
|
|
41218
41644
|
if (msg.type === "drop" && msg.direction === "to-phone") {
|
|
41219
41645
|
ws.send(JSON.stringify({ kind: "notify", notice: "drop" }));
|
|
41220
41646
|
}
|
|
@@ -41274,18 +41700,18 @@ var RelayClient = class {
|
|
|
41274
41700
|
};
|
|
41275
41701
|
|
|
41276
41702
|
// ../daemon/src/pairing.ts
|
|
41277
|
-
import { existsSync as
|
|
41278
|
-
import { homedir as
|
|
41279
|
-
import { join as
|
|
41703
|
+
import { existsSync as existsSync7, readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4 } from "node:fs";
|
|
41704
|
+
import { homedir as homedir7 } from "node:os";
|
|
41705
|
+
import { join as join8 } from "node:path";
|
|
41280
41706
|
import { randomInt } from "node:crypto";
|
|
41281
|
-
var PAIRING_DIR = process.env.OFFHAND_HOME ??
|
|
41282
|
-
var PAIRING_PATH =
|
|
41707
|
+
var PAIRING_DIR = process.env.OFFHAND_HOME ?? join8(homedir7(), ".offhand");
|
|
41708
|
+
var PAIRING_PATH = join8(PAIRING_DIR, "pairing.json");
|
|
41283
41709
|
var POLL_MS = 2e3;
|
|
41284
41710
|
var POLL_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
41285
41711
|
async function ensurePairing(relayUrl2, forceNew = false, webUrl2 = "https://offhand-web.onrender.com") {
|
|
41286
41712
|
await ready;
|
|
41287
|
-
if (!forceNew &&
|
|
41288
|
-
const f2 = JSON.parse(
|
|
41713
|
+
if (!forceNew && existsSync7(PAIRING_PATH)) {
|
|
41714
|
+
const f2 = JSON.parse(readFileSync4(PAIRING_PATH, "utf8"));
|
|
41289
41715
|
const kp2 = { publicKey: fromB64u(f2.daemonPublicKey), secretKey: fromB64u(f2.daemonSecretKey) };
|
|
41290
41716
|
const phonePk = fromB64u(f2.phonePublicKey);
|
|
41291
41717
|
return {
|
|
@@ -41357,7 +41783,7 @@ async function ensurePairing(relayUrl2, forceNew = false, webUrl2 = "https://off
|
|
|
41357
41783
|
// ../daemon/src/approvals.ts
|
|
41358
41784
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
41359
41785
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
41360
|
-
import { join as
|
|
41786
|
+
import { join as join9 } from "node:path";
|
|
41361
41787
|
var ApprovalBroker = class {
|
|
41362
41788
|
constructor(timeoutMs) {
|
|
41363
41789
|
this.timeoutMs = timeoutMs;
|
|
@@ -41381,7 +41807,7 @@ var ApprovalBroker = class {
|
|
|
41381
41807
|
}
|
|
41382
41808
|
const risk = classifyRisk(toolName, input);
|
|
41383
41809
|
const filePath = input?.file_path;
|
|
41384
|
-
if (toolName === "Read" && typeof filePath === "string" && filePath.toLowerCase().startsWith(
|
|
41810
|
+
if (toolName === "Read" && typeof filePath === "string" && filePath.toLowerCase().startsWith(join9(tmpdir2(), "offhand-attachments").toLowerCase())) {
|
|
41385
41811
|
return Promise.resolve({ approve: true });
|
|
41386
41812
|
}
|
|
41387
41813
|
if (this.policyProvider() === "trusting" && risk === "low" && toolName !== "AskUserQuestion") {
|
|
@@ -41448,11 +41874,11 @@ function summariseInput(input) {
|
|
|
41448
41874
|
if (typeof input !== "object" || input === null) return String(input ?? "");
|
|
41449
41875
|
const i2 = input;
|
|
41450
41876
|
const q2 = firstQuestion(i2);
|
|
41451
|
-
if (q2) return
|
|
41877
|
+
if (q2) return truncate5(q2.text, 200);
|
|
41452
41878
|
for (const key of ["command", "file_path", "path", "url", "pattern", "description"]) {
|
|
41453
|
-
if (typeof i2[key] === "string" && i2[key] !== "") return
|
|
41879
|
+
if (typeof i2[key] === "string" && i2[key] !== "") return truncate5(`${key}: ${i2[key]}`, 200);
|
|
41454
41880
|
}
|
|
41455
|
-
return
|
|
41881
|
+
return truncate5(JSON.stringify(input), 200);
|
|
41456
41882
|
}
|
|
41457
41883
|
function firstQuestion(i2) {
|
|
41458
41884
|
if (!Array.isArray(i2.questions) || i2.questions.length === 0) return void 0;
|
|
@@ -41470,7 +41896,7 @@ function buildPreview(toolName, input) {
|
|
|
41470
41896
|
const q2 = firstQuestion(i2);
|
|
41471
41897
|
if (q2) {
|
|
41472
41898
|
const lines = [q2.text, ...q2.options.map((o2, n2) => `${n2 + 1}. ${o2.label}${o2.description ? ` \u2014 ${o2.description}` : ""}`)];
|
|
41473
|
-
return
|
|
41899
|
+
return truncate5(lines.join("\n"), 600);
|
|
41474
41900
|
}
|
|
41475
41901
|
if (/^(Edit|MultiEdit)/.test(toolName) && typeof i2.new_string === "string") {
|
|
41476
41902
|
const oldS = typeof i2.old_string === "string" ? i2.old_string : "";
|
|
@@ -41478,15 +41904,15 @@ function buildPreview(toolName, input) {
|
|
|
41478
41904
|
...oldS.split("\n").map((l2) => `- ${l2}`),
|
|
41479
41905
|
...i2.new_string.split("\n").map((l2) => `+ ${l2}`)
|
|
41480
41906
|
];
|
|
41481
|
-
return
|
|
41907
|
+
return truncate5(lines.join("\n"), 600);
|
|
41482
41908
|
}
|
|
41483
41909
|
if (/^Write/.test(toolName) && typeof i2.content === "string") {
|
|
41484
|
-
return
|
|
41910
|
+
return truncate5(i2.content.split("\n").map((l2) => `+ ${l2}`).join("\n"), 600);
|
|
41485
41911
|
}
|
|
41486
|
-
if (typeof i2.command === "string") return
|
|
41912
|
+
if (typeof i2.command === "string") return truncate5(`$ ${i2.command}`, 600);
|
|
41487
41913
|
return void 0;
|
|
41488
41914
|
}
|
|
41489
|
-
function
|
|
41915
|
+
function truncate5(s2, max) {
|
|
41490
41916
|
return s2.length <= max ? s2 : s2.slice(0, max - 1) + "\u2026";
|
|
41491
41917
|
}
|
|
41492
41918
|
|
|
@@ -41522,6 +41948,115 @@ async function downloadArtifact(relayUrl2, sessionId, blobId, keys) {
|
|
|
41522
41948
|
return openBytes(new Uint8Array(await res.arrayBuffer()), keys.rx);
|
|
41523
41949
|
}
|
|
41524
41950
|
|
|
41951
|
+
// ../daemon/src/autostart.ts
|
|
41952
|
+
import { homedir as homedir8 } from "node:os";
|
|
41953
|
+
import { join as join10, resolve as resolve4, dirname as dirname3 } from "node:path";
|
|
41954
|
+
import { existsSync as existsSync8, readFileSync as readFileSync5, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, rmSync } from "node:fs";
|
|
41955
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
41956
|
+
import { spawnSync } from "node:child_process";
|
|
41957
|
+
import { tmpdir as tmpdir3 } from "node:os";
|
|
41958
|
+
var __dirname2 = dirname3(fileURLToPath3(import.meta.url));
|
|
41959
|
+
var SHORTCUT_NAME = "OffhandDaemon.lnk";
|
|
41960
|
+
var OFFHAND_HOME = process.env.OFFHAND_HOME ?? join10(homedir8(), ".offhand");
|
|
41961
|
+
function getStartupDir() {
|
|
41962
|
+
if (process.platform !== "win32") {
|
|
41963
|
+
return join10(homedir8(), ".config", "autostart");
|
|
41964
|
+
}
|
|
41965
|
+
return join10(process.env.APPDATA ?? "", "Microsoft", "Windows", "Start Menu", "Programs", "Startup");
|
|
41966
|
+
}
|
|
41967
|
+
function getWrapperPath() {
|
|
41968
|
+
const wrapperDir = join10(OFFHAND_HOME, "autostart");
|
|
41969
|
+
mkdirSync5(wrapperDir, { recursive: true });
|
|
41970
|
+
return join10(wrapperDir, "start-daemon.bat");
|
|
41971
|
+
}
|
|
41972
|
+
function getShortcutPath() {
|
|
41973
|
+
return join10(getStartupDir(), SHORTCUT_NAME);
|
|
41974
|
+
}
|
|
41975
|
+
function buildWrapperScript(config) {
|
|
41976
|
+
const projectRoot = resolve4(__dirname2, "..", "..");
|
|
41977
|
+
const wsArgs2 = config.workspaces.map((w2) => `--workspace "${w2}"`).join(" ");
|
|
41978
|
+
const relayArg = config.relayUrl ? `--relay "${config.relayUrl}"` : "";
|
|
41979
|
+
const devUrlArg = config.devUrl ? `--dev-url "${config.devUrl}"` : "";
|
|
41980
|
+
const portArg = `--port ${config.port}`;
|
|
41981
|
+
const approvalArg = `--approval-timeout ${config.approvalTimeout ?? 300}`;
|
|
41982
|
+
const webUrlArg = `--web-url "${config.webUrl ?? "https://offhand-web.onrender.com"}"`;
|
|
41983
|
+
const parts = [
|
|
41984
|
+
"pnpm --filter @offhand/daemon dev",
|
|
41985
|
+
wsArgs2,
|
|
41986
|
+
relayArg,
|
|
41987
|
+
devUrlArg,
|
|
41988
|
+
portArg,
|
|
41989
|
+
approvalArg,
|
|
41990
|
+
webUrlArg
|
|
41991
|
+
].filter(Boolean);
|
|
41992
|
+
const cmd = parts.join(" ");
|
|
41993
|
+
return `@echo off
|
|
41994
|
+
cd /d "${projectRoot}"
|
|
41995
|
+
${cmd}
|
|
41996
|
+
`;
|
|
41997
|
+
}
|
|
41998
|
+
function createShortcut(targetPath, shortcutPath) {
|
|
41999
|
+
const psScript = `$WshShell = New-Object -ComObject WScript.Shell
|
|
42000
|
+
$Shortcut = $WshShell.CreateShortcut("${shortcutPath}")
|
|
42001
|
+
$Shortcut.TargetPath = "cmd.exe"
|
|
42002
|
+
$Shortcut.Arguments = "/c ${targetPath}"
|
|
42003
|
+
$Shortcut.WorkingDirectory = "${resolve4(__dirname2, "..", "..")}"
|
|
42004
|
+
$Shortcut.Description = "Offhand Daemon - Auto-start on login"
|
|
42005
|
+
$Shortcut.Save()`;
|
|
42006
|
+
const scriptPath = join10(tmpdir3(), "offhand-create-shortcut.ps1");
|
|
42007
|
+
writeFileSync4(scriptPath, psScript);
|
|
42008
|
+
const result = spawnSync("powershell", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", scriptPath], { encoding: "utf8", shell: true });
|
|
42009
|
+
try {
|
|
42010
|
+
rmSync(scriptPath);
|
|
42011
|
+
} catch {
|
|
42012
|
+
}
|
|
42013
|
+
return { success: result.status === 0, output: result.stdout + result.stderr };
|
|
42014
|
+
}
|
|
42015
|
+
function checkShortcutExists(shortcutPath) {
|
|
42016
|
+
return existsSync8(shortcutPath);
|
|
42017
|
+
}
|
|
42018
|
+
function installAutostart(config) {
|
|
42019
|
+
const wrapperPath = getWrapperPath();
|
|
42020
|
+
const script = buildWrapperScript(config);
|
|
42021
|
+
writeFileSync4(wrapperPath, script);
|
|
42022
|
+
const shortcutPath = getShortcutPath();
|
|
42023
|
+
const result = createShortcut(wrapperPath, shortcutPath);
|
|
42024
|
+
if (result.success) {
|
|
42025
|
+
console.log(`[autostart] Created shortcut at: ${shortcutPath}`);
|
|
42026
|
+
console.log(`[autostart] Wrapper script: ${wrapperPath}`);
|
|
42027
|
+
} else {
|
|
42028
|
+
console.error(`[autostart] Failed to create shortcut: ${result.output}`);
|
|
42029
|
+
}
|
|
42030
|
+
return result;
|
|
42031
|
+
}
|
|
42032
|
+
function getAutostartStatus() {
|
|
42033
|
+
const shortcutPath = getShortcutPath();
|
|
42034
|
+
const installed = checkShortcutExists(shortcutPath);
|
|
42035
|
+
const wrapperPath = getWrapperPath();
|
|
42036
|
+
const wrapperExists = existsSync8(wrapperPath);
|
|
42037
|
+
let details = `Shortcut: ${shortcutPath} (${installed ? "EXISTS" : "MISSING"})`;
|
|
42038
|
+
if (wrapperExists) {
|
|
42039
|
+
details += `
|
|
42040
|
+
Wrapper: ${wrapperPath} (EXISTS)`;
|
|
42041
|
+
}
|
|
42042
|
+
return { installed, details };
|
|
42043
|
+
}
|
|
42044
|
+
function saveConfig(config) {
|
|
42045
|
+
const configPath = join10(OFFHAND_HOME, "autostart.json");
|
|
42046
|
+
writeFileSync4(configPath, JSON.stringify(config, null, 2));
|
|
42047
|
+
}
|
|
42048
|
+
function getConfigFromStore() {
|
|
42049
|
+
const workspaces = ["C:\\Users\\udbha\\dev\\offhand"];
|
|
42050
|
+
return {
|
|
42051
|
+
workspaces,
|
|
42052
|
+
port: 4317,
|
|
42053
|
+
relayUrl: void 0,
|
|
42054
|
+
devUrl: void 0,
|
|
42055
|
+
approvalTimeout: 300,
|
|
42056
|
+
webUrl: "https://offhand-web.onrender.com"
|
|
42057
|
+
};
|
|
42058
|
+
}
|
|
42059
|
+
|
|
41525
42060
|
// ../daemon/src/index.ts
|
|
41526
42061
|
var args = process.argv.slice(2);
|
|
41527
42062
|
if (args[0] === "drop") {
|
|
@@ -41554,9 +42089,9 @@ var approvalTimeoutMs = Number(argValue("--approval-timeout") ?? 300) * 1e3;
|
|
|
41554
42089
|
var webUrl = argValue("--web-url") ?? "https://offhand-web.onrender.com";
|
|
41555
42090
|
var devUrl = argValue("--dev-url");
|
|
41556
42091
|
var store = new Store();
|
|
41557
|
-
var wsArgs = argValues("--workspace").map((w2) =>
|
|
42092
|
+
var wsArgs = argValues("--workspace").map((w2) => resolve5(w2));
|
|
41558
42093
|
for (const w2 of wsArgs) {
|
|
41559
|
-
if (!
|
|
42094
|
+
if (!existsSync9(w2)) {
|
|
41560
42095
|
console.error(`workspace does not exist: ${w2}`);
|
|
41561
42096
|
process.exit(1);
|
|
41562
42097
|
}
|
|
@@ -41568,6 +42103,7 @@ var approvalUrl = `http://127.0.0.1:${port}/approval`;
|
|
|
41568
42103
|
var runners = [
|
|
41569
42104
|
new ClaudeCodeRunner(broker, approvalUrl),
|
|
41570
42105
|
new CopilotCliRunner(),
|
|
42106
|
+
new OpenCodeRunner(),
|
|
41571
42107
|
new CodexCliRunner(),
|
|
41572
42108
|
new CursorAgentRunner(),
|
|
41573
42109
|
new GeminiCliRunner()
|
|
@@ -41588,6 +42124,26 @@ if (m2.type === "manifest") {
|
|
|
41588
42124
|
new LocalSessionServer(manager, port, broker);
|
|
41589
42125
|
console.log(` local : ws://127.0.0.1:${port}`);
|
|
41590
42126
|
console.log(` approvals : timeout ${approvalTimeoutMs / 1e3}s then auto-deny`);
|
|
42127
|
+
var autostartStatus = getAutostartStatus();
|
|
42128
|
+
if (!autostartStatus.installed) {
|
|
42129
|
+
console.log(` autostart : installing...`);
|
|
42130
|
+
const config = getConfigFromStore();
|
|
42131
|
+
config.workspaces = store.listWorkspaces().map((w2) => w2.path);
|
|
42132
|
+
config.port = port;
|
|
42133
|
+
config.relayUrl = relayUrl;
|
|
42134
|
+
config.devUrl = devUrl;
|
|
42135
|
+
config.approvalTimeout = approvalTimeoutMs / 1e3;
|
|
42136
|
+
config.webUrl = webUrl;
|
|
42137
|
+
saveConfig(config);
|
|
42138
|
+
const { success, output } = installAutostart(config);
|
|
42139
|
+
if (success) {
|
|
42140
|
+
console.log(` autostart : installed (runs on login)`);
|
|
42141
|
+
} else {
|
|
42142
|
+
console.warn(` autostart : failed to install: ${output}`);
|
|
42143
|
+
}
|
|
42144
|
+
} else {
|
|
42145
|
+
console.log(` autostart : already installed`);
|
|
42146
|
+
}
|
|
41591
42147
|
if (relayUrl) {
|
|
41592
42148
|
const pairing = await ensurePairing(relayUrl, forceRepair, webUrl);
|
|
41593
42149
|
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.3",
|
|
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
|
+
}
|