@dashup/connector 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/README.md +5 -13
- package/dist/index.js +275 -141
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -5,8 +5,7 @@ Run beside the editor on each computer, including the PC whose Zed window connec
|
|
|
5
5
|
With Node 24:
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
|
-
|
|
9
|
-
dashup pair
|
|
8
|
+
npx @dashup/connector pair
|
|
10
9
|
```
|
|
11
10
|
|
|
12
11
|
From a checkout, use pnpm 10.34.5 from the workspace root and `pnpm --filter @dashup/connector dashup` in place of `dashup`:
|
|
@@ -18,7 +17,7 @@ pnpm exec turbo build --filter=@dashup/connector...
|
|
|
18
17
|
|
|
19
18
|
The published package bundles the workspace code, and CI publishes it when the package version is new. Screen and input need the native helper from `packages/desktop`, which the package does not include.
|
|
20
19
|
|
|
21
|
-
`dashup pair`
|
|
20
|
+
`dashup pair` walks through pairing in the terminal: it shows a QR code that expires in five minutes, waits for a phone to scan it under Pair a computer, and offers to keep Dashup running in the background. The first phone adds the computer to its account, so the walkthrough names that account and shares nothing until you confirm it. Later codes pair more phones on the same account without asking, including through a connector that is already running. A phone that scans an expired code gets a pairing the computer revokes, because it never kept that key. Pairing needs a terminal: without one, an unpaired computer exits and says so. It uses `https://api.dashup.com`; for a local API, add `--api http://localhost:3000`. A physical phone needs a reachable HTTPS API. To run a paired connector in the foreground, use `dashup watch`.
|
|
22
21
|
|
|
23
22
|
## Existing projects and alerts
|
|
24
23
|
|
|
@@ -30,7 +29,7 @@ pnpm --filter @dashup/connector dashup alert --project PROJECT_ID --title "Dashu
|
|
|
30
29
|
|
|
31
30
|
The connector automatically reads existing Claude Code, Codex, and OpenCode conversations alongside Zed workspaces and VS Code chat history. It honors the harness's existing data directory, authentication, and native session IDs. Native stores are opened read-only; discovery never launches an agent, creates or resumes a conversation, or changes editor settings. Opening the same supported harness session through Zed and its native history uses one Dashup thread when their actual session identities match. Matching directory names alone never merges local and SSH projects.
|
|
32
31
|
|
|
33
|
-
A thread's time is its last conversation message in the agent's own history; editor bookkeeping such as Zed's sidebar restamps never moves it. Subagent sessions (OpenCode children, Codex spawned agents) carry their parent thread. Tool calls are joined with their results into one step with status, duration and, for file edits, a unified diff. Saved history has unknown live status and read-only controls. File changes publish project metadata
|
|
32
|
+
A thread's time is its last conversation message in the agent's own history; editor bookkeeping such as Zed's sidebar restamps never moves it. Subagent sessions (OpenCode children, Codex spawned agents) carry their parent thread. Tool calls are joined with their results into one step with status, duration and, for file edits, a unified diff. Saved history has unknown live status and read-only controls. File changes publish project metadata; only an approval, a question, an error or a finished turn sends an alert. A workspace becomes a project once it has a conversation. After every change the connector publishes its overview: every project, and the conversations Home shows (anything that needs the person or is working, then the newest 20). The phone renders Home from the overview alone and decodes a project's full snapshot only when a page opens it. Only a scan that read every source retires a project it published earlier and left off. Each phone read request reads a thread's native history once and publishes a snapshot of separately encrypted message fragments to the database. The phone reads bounded latest and earlier pages; realtime thread updates carry only summary and approval metadata. Native history remains the source, and an incomplete upload never replaces the previous published snapshot. Native live interfaces provide actual status and pending choices. Tool integrations can also queue explicit encrypted alerts with `alert`.
|
|
34
33
|
|
|
35
34
|
Project paths, SSH details, thread content, approvals, and alerts are encrypted separately for every paired phone. The encrypted outbox survives a connector restart. Parcae handles realtime delivery and reconnect; replay counters are saved before commands execute. Revoking a pairing stops its viewer and discards its connector key and queued content.
|
|
36
35
|
|
|
@@ -89,16 +88,9 @@ macOS requires Screen Recording and Accessibility permission. Windows uses the s
|
|
|
89
88
|
|
|
90
89
|
## Start at login
|
|
91
90
|
|
|
92
|
-
|
|
91
|
+
Answering yes to the walkthrough's background question, or running `dashup install` on a paired computer, registers a macOS LaunchAgent, Windows Startup entry, or Linux user systemd service and starts it, taking over from any connector already running for that state file. The service runs `watch` with the state file it was paired with (`DASHUP_CONFIG`). Linux requires a user systemd session. Under npx or pnpm dlx, whose caches can be cleared, the service runs a copy of that install kept in `app/` beside the state file; a checkout or global install runs in place. The Node executable must stay at its installed location. Signed desktop installers are not included.
|
|
93
92
|
|
|
94
|
-
|
|
95
|
-
node apps/connector/dist/index.js stop
|
|
96
|
-
node apps/connector/dist/index.js install
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
Installation registers a macOS LaunchAgent, Windows Startup entry, or Linux user systemd service. Linux requires a user systemd session. These source startup paths require the checkout and Node executable to remain at their installed locations. Signed desktop installers are not included.
|
|
100
|
-
|
|
101
|
-
Remove startup with `launchctl unload ~/Library/LaunchAgents/com.dashup.connector.plist` and delete that file on macOS; delete `%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\Dashup.vbs` on Windows; or run `systemctl --user disable --now dashup.service` and delete `~/.config/systemd/user/dashup.service` on Linux. A macOS KeepAlive job restarts after `stop`; unload it when stopping the service.
|
|
93
|
+
Remove startup with `launchctl unload ~/Library/LaunchAgents/com.dashup.connector.plist` and delete that file on macOS; delete `%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\Dashup.vbs` on Windows; or run `systemctl --user disable --now dashup.service` and delete `~/.config/systemd/user/dashup.service` on Linux. `dashup stop` leaves the service stopped until the next sign-in; a crash restarts it.
|
|
102
94
|
|
|
103
95
|
## Local state and checks
|
|
104
96
|
|
package/dist/index.js
CHANGED
|
@@ -42,7 +42,7 @@ var require_XMLHttpRequest = __commonJS({
|
|
|
42
42
|
"use strict";
|
|
43
43
|
var fs = __require("fs");
|
|
44
44
|
var Url = __require("url");
|
|
45
|
-
var
|
|
45
|
+
var spawn5 = __require("child_process").spawn;
|
|
46
46
|
module.exports = XMLHttpRequest3;
|
|
47
47
|
XMLHttpRequest3.XMLHttpRequest = XMLHttpRequest3;
|
|
48
48
|
function XMLHttpRequest3(opts) {
|
|
@@ -338,7 +338,7 @@ var require_XMLHttpRequest = __commonJS({
|
|
|
338
338
|
var syncFile = ".node-xmlhttprequest-sync-" + process.pid;
|
|
339
339
|
fs.writeFileSync(syncFile, "", "utf8");
|
|
340
340
|
var execString = "var http = require('http'), https = require('https'), fs = require('fs');var doRequest = http" + (ssl ? "s" : "") + ".request;var options = " + JSON.stringify(options) + ";var responseText = '';var responseData = Buffer.alloc(0);var req = doRequest(options, function(response) {response.on('data', function(chunk) { var data = Buffer.from(chunk); responseText += data.toString('utf8'); responseData = Buffer.concat([responseData, data]);});response.on('end', function() {fs.writeFileSync('" + contentFile + "', JSON.stringify({err: null, data: {statusCode: response.statusCode, headers: response.headers, text: responseText, data: responseData.toString('base64')}}), 'utf8');fs.unlinkSync('" + syncFile + "');});response.on('error', function(error) {fs.writeFileSync('" + contentFile + "', 'NODE-XMLHTTPREQUEST-ERROR:' + JSON.stringify(error), 'utf8');fs.unlinkSync('" + syncFile + "');});}).on('error', function(error) {fs.writeFileSync('" + contentFile + "', 'NODE-XMLHTTPREQUEST-ERROR:' + JSON.stringify(error), 'utf8');fs.unlinkSync('" + syncFile + "');});" + (data ? "req.write('" + JSON.stringify(data).slice(1, -1).replace(/'/g, "\\'") + "');" : "") + "req.end();";
|
|
341
|
-
var syncProc =
|
|
341
|
+
var syncProc = spawn5(process.argv[0], ["-e", execString]);
|
|
342
342
|
var statusText;
|
|
343
343
|
while (fs.existsSync(syncFile)) {
|
|
344
344
|
}
|
|
@@ -1028,7 +1028,7 @@ var require_node = __commonJS({
|
|
|
1028
1028
|
var tty = __require("tty");
|
|
1029
1029
|
var util = __require("util");
|
|
1030
1030
|
exports.init = init;
|
|
1031
|
-
exports.log =
|
|
1031
|
+
exports.log = log4;
|
|
1032
1032
|
exports.formatArgs = formatArgs;
|
|
1033
1033
|
exports.save = save;
|
|
1034
1034
|
exports.load = load;
|
|
@@ -1163,7 +1163,7 @@ var require_node = __commonJS({
|
|
|
1163
1163
|
}
|
|
1164
1164
|
return (/* @__PURE__ */ new Date()).toISOString() + " ";
|
|
1165
1165
|
}
|
|
1166
|
-
function
|
|
1166
|
+
function log4(...args2) {
|
|
1167
1167
|
return process.stderr.write(util.formatWithOptions(exports.inspectOpts, ...args2) + "\n");
|
|
1168
1168
|
}
|
|
1169
1169
|
function save(namespaces) {
|
|
@@ -2707,6 +2707,7 @@ var require_pluralize = __commonJS({
|
|
|
2707
2707
|
// src/index.ts
|
|
2708
2708
|
import { randomBytes as randomBytes5, timingSafeEqual } from "node:crypto";
|
|
2709
2709
|
import { createServer } from "node:http";
|
|
2710
|
+
import * as ui2 from "@clack/prompts";
|
|
2710
2711
|
import { z as z3 } from "zod";
|
|
2711
2712
|
|
|
2712
2713
|
// ../../packages/protocol/src/index.ts
|
|
@@ -4004,61 +4005,84 @@ async function proxyAcp(config2, executable, args2) {
|
|
|
4004
4005
|
// src/pair.ts
|
|
4005
4006
|
import { hostname } from "node:os";
|
|
4006
4007
|
import { randomBytes as randomBytes2, createHash as createHash2 } from "node:crypto";
|
|
4007
|
-
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
4008
4008
|
import QRCode from "qrcode";
|
|
4009
|
+
import * as ui from "@clack/prompts";
|
|
4009
4010
|
import { z } from "zod";
|
|
4011
|
+
|
|
4012
|
+
// package.json
|
|
4013
|
+
var version = "0.1.3";
|
|
4014
|
+
|
|
4015
|
+
// src/pair.ts
|
|
4010
4016
|
var Invitation = z.object({
|
|
4011
4017
|
code: z.string().regex(/^[a-f0-9]{16}$/),
|
|
4012
4018
|
key: z.string().regex(/^[a-f0-9]{64}$/),
|
|
4013
4019
|
text: z.string().min(1)
|
|
4014
4020
|
}).strict();
|
|
4021
|
+
var interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
4022
|
+
var intro2 = () => ui.intro(`Dashup ${version}`);
|
|
4015
4023
|
function invitation(config2, secret = config2.secret) {
|
|
4016
4024
|
const code = randomBytes2(8).toString("hex");
|
|
4017
4025
|
const key = newSecret(randomBytes2);
|
|
4018
4026
|
const computer = createHash2("sha256").update(secret).digest("hex");
|
|
4019
4027
|
return { code, key, text: formatPairingCode({ api: config2.api, computer, code, key }) };
|
|
4020
4028
|
}
|
|
4021
|
-
async function scanned(config2, { code, text: text3 }, secret = config2.secret) {
|
|
4029
|
+
async function scanned(config2, { code, text: text3 }, done, secret = config2.secret) {
|
|
4022
4030
|
const qr = await QRCode.toString(text3, {
|
|
4023
4031
|
type: "terminal",
|
|
4024
4032
|
small: true,
|
|
4025
4033
|
errorCorrectionLevel: "L"
|
|
4026
4034
|
});
|
|
4027
|
-
|
|
4028
|
-
|
|
4035
|
+
const rows = qr.split("\n").filter((row) => row.replace(/\x1b\[\d+m/g, ""));
|
|
4036
|
+
if (interactive) {
|
|
4037
|
+
ui.log.step("Scan this in the Dashup app under Pair a computer. It expires in 5 minutes.");
|
|
4038
|
+
ui.log.message("Can't scan? Paste this code instead:", { spacing: 0 });
|
|
4039
|
+
process.stdout.write(` ${text3}
|
|
4040
|
+
`);
|
|
4041
|
+
ui.log.message(rows);
|
|
4042
|
+
} else {
|
|
4043
|
+
process.stdout.write(
|
|
4044
|
+
`
|
|
4029
4045
|
Pairing code: ${text3}
|
|
4030
4046
|
|
|
4031
|
-
${
|
|
4047
|
+
${rows.join("\n")}
|
|
4032
4048
|
Scan this in the Dashup app (Pair a computer). It expires in 5 minutes.
|
|
4033
4049
|
`
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
}
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
})
|
|
4055
|
-
|
|
4056
|
-
|
|
4050
|
+
);
|
|
4051
|
+
}
|
|
4052
|
+
const waiting = interactive ? ui.spinner() : void 0;
|
|
4053
|
+
waiting?.start("Waiting for your phone");
|
|
4054
|
+
try {
|
|
4055
|
+
for (const deadline = Date.now() + PAIRING_TTL_MS; Date.now() < deadline; ) {
|
|
4056
|
+
await new Promise((resolve3) => setTimeout(resolve3, 1e3));
|
|
4057
|
+
const response = await fetch(`${config2.api}/v1/dashup/pairings?code=${code}`, {
|
|
4058
|
+
headers: { Authorization: `Bearer ${secret}` }
|
|
4059
|
+
});
|
|
4060
|
+
if (response.status === 401 || response.status === 404) continue;
|
|
4061
|
+
const value2 = await response.json();
|
|
4062
|
+
if (!response.ok || !value2.success)
|
|
4063
|
+
throw new Error(value2.error ?? `Dashup request failed (${response.status})`);
|
|
4064
|
+
if (waiting) waiting.stop(done(value2.result.email));
|
|
4065
|
+
else process.stdout.write(`${done(value2.result.email)}.
|
|
4066
|
+
`);
|
|
4067
|
+
return value2.result;
|
|
4068
|
+
}
|
|
4069
|
+
throw new Error("The pairing code expired. Run dashup pair for a new one.");
|
|
4070
|
+
} catch (error) {
|
|
4071
|
+
if (!waiting) throw error;
|
|
4072
|
+
waiting.error(error instanceof Error ? error.message : "Pairing failed");
|
|
4073
|
+
throw Object.assign(error, { reported: true });
|
|
4074
|
+
}
|
|
4057
4075
|
}
|
|
4076
|
+
var reported = (error) => error?.reported === true;
|
|
4058
4077
|
async function link(config2) {
|
|
4059
4078
|
const secret = newSecret(randomBytes2);
|
|
4060
4079
|
const invite = invitation(config2, secret);
|
|
4061
|
-
const { pairing, email } = await scanned(
|
|
4080
|
+
const { pairing, email } = await scanned(
|
|
4081
|
+
config2,
|
|
4082
|
+
invite,
|
|
4083
|
+
(account) => `Scanned by ${account}`,
|
|
4084
|
+
secret
|
|
4085
|
+
);
|
|
4062
4086
|
const linked = { ...config2, secret };
|
|
4063
4087
|
const platform = process.platform === "win32" ? "windows" : process.platform === "darwin" ? "macos" : "linux";
|
|
4064
4088
|
const { device } = await api(linked, "/dashup/devices", {
|
|
@@ -4067,11 +4091,15 @@ async function link(config2) {
|
|
|
4067
4091
|
kind: "desktop",
|
|
4068
4092
|
platform
|
|
4069
4093
|
});
|
|
4070
|
-
|
|
4071
|
-
|
|
4094
|
+
const confirmed = await ui.confirm({
|
|
4095
|
+
message: `Pair this computer with ${email}?`,
|
|
4096
|
+
initialValue: false
|
|
4097
|
+
});
|
|
4098
|
+
if (confirmed !== true) {
|
|
4072
4099
|
await api(linked, `/dashup/pairings/${pairing.id}`, void 0, "DELETE").catch(() => {
|
|
4073
4100
|
});
|
|
4074
|
-
|
|
4101
|
+
ui.cancel("Pairing cancelled. Nothing was shared.");
|
|
4102
|
+
return false;
|
|
4075
4103
|
}
|
|
4076
4104
|
Object.assign(config2, {
|
|
4077
4105
|
secret,
|
|
@@ -4082,12 +4110,13 @@ ${email} scanned the code. Pair this computer with that account? (y/N) `)) {
|
|
|
4082
4110
|
unconfirmed: {}
|
|
4083
4111
|
});
|
|
4084
4112
|
saveConfig(config2);
|
|
4113
|
+
return true;
|
|
4085
4114
|
}
|
|
4086
4115
|
|
|
4087
4116
|
// src/viewer.ts
|
|
4088
4117
|
import { existsSync as existsSync2 } from "node:fs";
|
|
4089
4118
|
import { fileURLToPath } from "node:url";
|
|
4090
|
-
import { createInterface as
|
|
4119
|
+
import { createInterface as createInterface2 } from "node:readline";
|
|
4091
4120
|
import { spawn as spawn2 } from "node:child_process";
|
|
4092
4121
|
|
|
4093
4122
|
// src/screen.ts
|
|
@@ -4268,7 +4297,7 @@ var Viewer = class _Viewer {
|
|
|
4268
4297
|
const child = spawn2(DESKTOP_BIN, [], { stdio: ["pipe", "pipe", "pipe"], windowsHide: true });
|
|
4269
4298
|
this.process = child;
|
|
4270
4299
|
let events = Promise.resolve();
|
|
4271
|
-
|
|
4300
|
+
createInterface2({ input: child.stdout }).on("error", () => this.failed(child)).on("line", (line) => {
|
|
4272
4301
|
events = events.then(() => {
|
|
4273
4302
|
if (this.process === child) return this.receive(line);
|
|
4274
4303
|
}).catch(() => {
|
|
@@ -5947,7 +5976,7 @@ var Polling = class extends Transport {
|
|
|
5947
5976
|
*/
|
|
5948
5977
|
pause(onPause) {
|
|
5949
5978
|
this.readyState = "pausing";
|
|
5950
|
-
const
|
|
5979
|
+
const pause2 = () => {
|
|
5951
5980
|
debug2("paused");
|
|
5952
5981
|
this.readyState = "paused";
|
|
5953
5982
|
onPause();
|
|
@@ -5959,7 +5988,7 @@ var Polling = class extends Transport {
|
|
|
5959
5988
|
total++;
|
|
5960
5989
|
this.once("pollComplete", function() {
|
|
5961
5990
|
debug2("pre-pause polling complete");
|
|
5962
|
-
--total ||
|
|
5991
|
+
--total || pause2();
|
|
5963
5992
|
});
|
|
5964
5993
|
}
|
|
5965
5994
|
if (!this.writable) {
|
|
@@ -5967,11 +5996,11 @@ var Polling = class extends Transport {
|
|
|
5967
5996
|
total++;
|
|
5968
5997
|
this.once("drain", function() {
|
|
5969
5998
|
debug2("pre-pause writing complete");
|
|
5970
|
-
--total ||
|
|
5999
|
+
--total || pause2();
|
|
5971
6000
|
});
|
|
5972
6001
|
}
|
|
5973
6002
|
} else {
|
|
5974
|
-
|
|
6003
|
+
pause2();
|
|
5975
6004
|
}
|
|
5976
6005
|
}
|
|
5977
6006
|
/**
|
|
@@ -15035,14 +15064,14 @@ var Model = class _Model extends import_index2.default {
|
|
|
15035
15064
|
continue;
|
|
15036
15065
|
}
|
|
15037
15066
|
const pathStart = op.path.indexOf("/", 1);
|
|
15038
|
-
const
|
|
15067
|
+
const relative2 = {
|
|
15039
15068
|
...op,
|
|
15040
15069
|
path: op.path.slice(pathStart),
|
|
15041
15070
|
...from ? { from: from.slice(from.indexOf("/", 1)) } : {}
|
|
15042
15071
|
};
|
|
15043
15072
|
const grouped = refOps.get(segments[0]);
|
|
15044
|
-
if (grouped) grouped.push(
|
|
15045
|
-
else refOps.set(segments[0], [
|
|
15073
|
+
if (grouped) grouped.push(relative2);
|
|
15074
|
+
else refOps.set(segments[0], [relative2]);
|
|
15046
15075
|
}
|
|
15047
15076
|
let didRefChange = false;
|
|
15048
15077
|
const changedRefOps = [];
|
|
@@ -15217,7 +15246,7 @@ function prefix(ansi) {
|
|
|
15217
15246
|
const code = codes[ansi] ?? "";
|
|
15218
15247
|
return [`${code}${tag}\x1B[0m`];
|
|
15219
15248
|
}
|
|
15220
|
-
var
|
|
15249
|
+
var log2 = {
|
|
15221
15250
|
warn: (...args2) => isDev && console.warn(...prefix("#fb3"), ...args2),
|
|
15222
15251
|
error: (...args2) => isDev && console.error(...prefix("#f44"), ...args2),
|
|
15223
15252
|
debug: (...args2) => isDev && console.debug(...prefix("#888"), ...args2)
|
|
@@ -15257,7 +15286,7 @@ var SessionMachine = class {
|
|
|
15257
15286
|
this.state.status = nextStatus;
|
|
15258
15287
|
this.state.userId = userId;
|
|
15259
15288
|
this.state.version++;
|
|
15260
|
-
|
|
15289
|
+
log2.debug(
|
|
15261
15290
|
userId ? `session: authenticated ${userId}` : "session: anonymous"
|
|
15262
15291
|
);
|
|
15263
15292
|
this._fireReady();
|
|
@@ -15272,7 +15301,7 @@ var SessionMachine = class {
|
|
|
15272
15301
|
this.state.status = "terminated";
|
|
15273
15302
|
this.state.userId = null;
|
|
15274
15303
|
this.state.version++;
|
|
15275
|
-
|
|
15304
|
+
log2.debug("session: terminated");
|
|
15276
15305
|
this._fireReady();
|
|
15277
15306
|
this._notify();
|
|
15278
15307
|
}
|
|
@@ -15324,7 +15353,7 @@ var ConnectionMachine = class {
|
|
|
15324
15353
|
this.state.status = status2;
|
|
15325
15354
|
this.state.lastError = err2;
|
|
15326
15355
|
this.state.version++;
|
|
15327
|
-
|
|
15356
|
+
log2.debug(
|
|
15328
15357
|
`connection: ${status2}${err2 ? ` (${err2.message})` : ""}`
|
|
15329
15358
|
);
|
|
15330
15359
|
for (const fn of this._listeners) fn();
|
|
@@ -15454,7 +15483,7 @@ var SocketTransport = class extends import_index2.default {
|
|
|
15454
15483
|
this.helloState = "resolved";
|
|
15455
15484
|
const userId = response.userId ?? null;
|
|
15456
15485
|
const ms = (performance.now() - t0).toFixed(0);
|
|
15457
|
-
|
|
15486
|
+
log2.debug(
|
|
15458
15487
|
`hello: ${userId ? `userId=${userId}` : "anonymous"} (${ms}ms)`
|
|
15459
15488
|
);
|
|
15460
15489
|
this.session.resolve(userId);
|
|
@@ -15465,7 +15494,7 @@ var SocketTransport = class extends import_index2.default {
|
|
|
15465
15494
|
},
|
|
15466
15495
|
(err2) => {
|
|
15467
15496
|
const error = err2 instanceof Error ? err2 : new Error(String(err2));
|
|
15468
|
-
|
|
15497
|
+
log2.warn(`hello: token resolution failed (${error.message})`);
|
|
15469
15498
|
this._rejectHandshake(generation, error);
|
|
15470
15499
|
}
|
|
15471
15500
|
);
|
|
@@ -15685,12 +15714,12 @@ var SocketTransport = class extends import_index2.default {
|
|
|
15685
15714
|
const t0 = performance.now();
|
|
15686
15715
|
const fullPath = `/${this.version}${path}`;
|
|
15687
15716
|
const timeoutMs = options?.timeout ?? DEFAULT_TIMEOUT;
|
|
15688
|
-
|
|
15717
|
+
log2.debug(`\u2192 ${method.toUpperCase()} ${fullPath}`);
|
|
15689
15718
|
return new Promise((resolve3, reject) => {
|
|
15690
15719
|
const timeout = setTimeout(() => {
|
|
15691
15720
|
this.socket.off(id);
|
|
15692
15721
|
this.pendingCalls.delete(id);
|
|
15693
|
-
|
|
15722
|
+
log2.debug(
|
|
15694
15723
|
`\u2717 ${method.toUpperCase()} ${fullPath} timeout (${(timeoutMs / 1e3).toFixed(0)}s)`
|
|
15695
15724
|
);
|
|
15696
15725
|
reject(new Error(`RPC timeout: ${method} ${path}`));
|
|
@@ -15704,10 +15733,10 @@ var SocketTransport = class extends import_index2.default {
|
|
|
15704
15733
|
const uncompressed = pako.ungzip(msg, { to: "string" });
|
|
15705
15734
|
const parsed = (0, import_compress_json.decompress)(JSON.parse(uncompressed));
|
|
15706
15735
|
if (parsed.success) {
|
|
15707
|
-
|
|
15736
|
+
log2.debug(`\u2190 ${method.toUpperCase()} ${fullPath} (${ms}ms)`);
|
|
15708
15737
|
resolve3(parsed.result);
|
|
15709
15738
|
} else {
|
|
15710
|
-
|
|
15739
|
+
log2.debug(
|
|
15711
15740
|
`\u2717 ${method.toUpperCase()} ${fullPath} (${ms}ms) ${parsed.error || parsed.message}`
|
|
15712
15741
|
);
|
|
15713
15742
|
const details = parsed.error && typeof parsed.error === "object" ? parsed.error : parsed;
|
|
@@ -15723,7 +15752,7 @@ var SocketTransport = class extends import_index2.default {
|
|
|
15723
15752
|
reject(error);
|
|
15724
15753
|
}
|
|
15725
15754
|
} catch (err2) {
|
|
15726
|
-
|
|
15755
|
+
log2.debug(
|
|
15727
15756
|
`\u2717 ${method.toUpperCase()} ${fullPath} (${ms}ms) parse error`
|
|
15728
15757
|
);
|
|
15729
15758
|
reject(err2);
|
|
@@ -16187,7 +16216,6 @@ var Device = class extends Model {
|
|
|
16187
16216
|
name = "";
|
|
16188
16217
|
kind = "desktop";
|
|
16189
16218
|
platform = "linux";
|
|
16190
|
-
lastSeenAt = /* @__PURE__ */ new Date();
|
|
16191
16219
|
pushToken = "";
|
|
16192
16220
|
pushProvider = "";
|
|
16193
16221
|
// A computer signs in with the secret this hashes; phones sign in with an account session.
|
|
@@ -16344,11 +16372,6 @@ import { connect } from "node:net";
|
|
|
16344
16372
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
16345
16373
|
import { join as join4, isAbsolute as isAbsolute2 } from "node:path";
|
|
16346
16374
|
import { stat, readdir, readFile } from "node:fs/promises";
|
|
16347
|
-
|
|
16348
|
-
// package.json
|
|
16349
|
-
var version = "0.1.1";
|
|
16350
|
-
|
|
16351
|
-
// src/harness/codex.ts
|
|
16352
16375
|
var REQUEST_MS = 4e3;
|
|
16353
16376
|
var MAX_BYTES = 8 * 1024 * 1024;
|
|
16354
16377
|
var MAX_SESSIONS = 100;
|
|
@@ -18079,7 +18102,7 @@ var NativeQueue = class {
|
|
|
18079
18102
|
|
|
18080
18103
|
// src/connector.ts
|
|
18081
18104
|
var EPHEMERAL_TTL_MS = 6e4;
|
|
18082
|
-
var
|
|
18105
|
+
var HOME_RECENT = 20;
|
|
18083
18106
|
var Command = z2.object({
|
|
18084
18107
|
type: z2.literal("command"),
|
|
18085
18108
|
requestId: z2.string().min(1),
|
|
@@ -18153,9 +18176,10 @@ var Connector = class {
|
|
|
18153
18176
|
releases = [];
|
|
18154
18177
|
announced = /* @__PURE__ */ new Set();
|
|
18155
18178
|
listed = /* @__PURE__ */ new Map();
|
|
18179
|
+
listedProjects = /* @__PURE__ */ new Map();
|
|
18180
|
+
overviewQueued = false;
|
|
18156
18181
|
snapshots = /* @__PURE__ */ new Set();
|
|
18157
18182
|
nativeSnapshots = /* @__PURE__ */ new Map();
|
|
18158
|
-
updatedAlerts = /* @__PURE__ */ new Map();
|
|
18159
18183
|
queueReceipts = /* @__PURE__ */ new Map();
|
|
18160
18184
|
// The content key behind each pairing code on show, until a phone pairs with it.
|
|
18161
18185
|
invitations = /* @__PURE__ */ new Map();
|
|
@@ -18321,6 +18345,7 @@ var Connector = class {
|
|
|
18321
18345
|
);
|
|
18322
18346
|
this.config.outbox.push(envelope);
|
|
18323
18347
|
saveConfig(this.config);
|
|
18348
|
+
if (content.type === "project" || content.type === "thread-state") this.refreshOverview();
|
|
18324
18349
|
await this.flush();
|
|
18325
18350
|
}
|
|
18326
18351
|
discardLegacySnapshots() {
|
|
@@ -18360,13 +18385,48 @@ var Connector = class {
|
|
|
18360
18385
|
await this.send(pair, { type: "project", project });
|
|
18361
18386
|
await this.listProjects(pair);
|
|
18362
18387
|
}
|
|
18363
|
-
//
|
|
18388
|
+
// The phone renders Home from this alone; it opens a project's full snapshot only on demand.
|
|
18389
|
+
overview() {
|
|
18390
|
+
const projects = [...this.discovery.projects.values()];
|
|
18391
|
+
const sessions = projects.flatMap((project) => project.threads.map((thread) => ({
|
|
18392
|
+
projectId: project.id,
|
|
18393
|
+
thread,
|
|
18394
|
+
approvals: this.threadState(project, thread).approvals
|
|
18395
|
+
}))).sort((a, b) => b.thread.updatedAt.localeCompare(a.thread.updatedAt));
|
|
18396
|
+
const busy = ({ thread, approvals }) => approvals.length > 0 || thread.status === "attention" || thread.status === "working";
|
|
18397
|
+
if (!this.discovery.warnings.length) this.listedProjects.clear();
|
|
18398
|
+
for (const { threads, ...project } of projects) {
|
|
18399
|
+
const started = threads.filter((thread) => !thread.parent);
|
|
18400
|
+
this.listedProjects.set(project.id, {
|
|
18401
|
+
...project,
|
|
18402
|
+
active: started.reduce((newest, thread) => thread.updatedAt > newest ? thread.updatedAt : newest, ""),
|
|
18403
|
+
started: started.length
|
|
18404
|
+
});
|
|
18405
|
+
}
|
|
18406
|
+
return {
|
|
18407
|
+
type: "projects",
|
|
18408
|
+
projects: [...this.listedProjects.values()],
|
|
18409
|
+
sessions: [
|
|
18410
|
+
...sessions.filter(busy),
|
|
18411
|
+
...sessions.filter((session) => !busy(session) && !session.thread.parent).slice(0, HOME_RECENT)
|
|
18412
|
+
]
|
|
18413
|
+
};
|
|
18414
|
+
}
|
|
18364
18415
|
async listProjects(pair) {
|
|
18365
|
-
const
|
|
18366
|
-
const
|
|
18367
|
-
if (this.
|
|
18368
|
-
await this.send(pair,
|
|
18369
|
-
this.listed.set(pair.id,
|
|
18416
|
+
const overview = this.overview();
|
|
18417
|
+
const text3 = JSON.stringify(overview);
|
|
18418
|
+
if (this.listed.get(pair.id) === text3) return;
|
|
18419
|
+
await this.send(pair, overview);
|
|
18420
|
+
this.listed.set(pair.id, text3);
|
|
18421
|
+
}
|
|
18422
|
+
// Each change of a project or thread brings the overview up to date once it settles.
|
|
18423
|
+
refreshOverview() {
|
|
18424
|
+
if (this.overviewQueued) return;
|
|
18425
|
+
this.overviewQueued = true;
|
|
18426
|
+
this.work = this.work.then(async () => {
|
|
18427
|
+
this.overviewQueued = false;
|
|
18428
|
+
for (const pair of this.activePairs()) await this.listProjects(pair);
|
|
18429
|
+
}).catch((error) => this.report(error));
|
|
18370
18430
|
}
|
|
18371
18431
|
queueChange() {
|
|
18372
18432
|
const run5 = this.work.then(() => this.changed());
|
|
@@ -18408,26 +18468,9 @@ var Connector = class {
|
|
|
18408
18468
|
this.stopWatching();
|
|
18409
18469
|
this.stopWatching = this.discovery.watch(this.rescan);
|
|
18410
18470
|
for (const project of projects) {
|
|
18411
|
-
|
|
18412
|
-
if (JSON.stringify(old) === JSON.stringify(project)) continue;
|
|
18471
|
+
if (JSON.stringify(previous.get(project.id)) === JSON.stringify(project)) continue;
|
|
18413
18472
|
for (const pair of this.activePairs()) await this.send(pair, { type: "project", project });
|
|
18414
|
-
for (const thread of project.threads) {
|
|
18415
|
-
const before = old?.threads.find((item) => item.id === thread.id);
|
|
18416
|
-
if (before && before.updatedAt !== thread.updatedAt && !this.discovery.locations.get(thread.id)?.live && !this.attached(thread)) {
|
|
18417
|
-
const last = this.updatedAlerts.get(thread.id) ?? 0;
|
|
18418
|
-
if (Date.now() - last < HISTORY_ALERT_INTERVAL_MS) continue;
|
|
18419
|
-
this.updatedAlerts.set(thread.id, Date.now());
|
|
18420
|
-
await this.attention(
|
|
18421
|
-
project.id,
|
|
18422
|
-
thread.id,
|
|
18423
|
-
thread.title,
|
|
18424
|
-
"The existing thread was updated.",
|
|
18425
|
-
"updated"
|
|
18426
|
-
);
|
|
18427
|
-
}
|
|
18428
|
-
}
|
|
18429
18473
|
}
|
|
18430
|
-
for (const pair of this.activePairs()) await this.listProjects(pair);
|
|
18431
18474
|
for (const { project, thread, session } of live) {
|
|
18432
18475
|
const snapshot = this.threadState(project, thread);
|
|
18433
18476
|
const prior = this.nativeSnapshots.get(thread.id);
|
|
@@ -18446,6 +18489,7 @@ var Connector = class {
|
|
|
18446
18489
|
"complete"
|
|
18447
18490
|
);
|
|
18448
18491
|
}
|
|
18492
|
+
for (const pair of this.activePairs()) await this.listProjects(pair);
|
|
18449
18493
|
await this.publishQueueReceipts();
|
|
18450
18494
|
}
|
|
18451
18495
|
refreshQueues() {
|
|
@@ -18745,63 +18789,81 @@ var Connector = class {
|
|
|
18745
18789
|
};
|
|
18746
18790
|
|
|
18747
18791
|
// src/install.ts
|
|
18792
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
18748
18793
|
import { homedir as homedir6 } from "node:os";
|
|
18749
|
-
import { dirname as dirname5, join as join8 } from "node:path";
|
|
18750
|
-
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
18751
18794
|
import { promisify as promisify4 } from "node:util";
|
|
18752
|
-
import {
|
|
18753
|
-
import {
|
|
18795
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
18796
|
+
import { execFile as execFile4, spawn as spawn4 } from "node:child_process";
|
|
18797
|
+
import { cp, mkdir, rm, writeFile } from "node:fs/promises";
|
|
18798
|
+
import { dirname as dirname5, join as join8, relative, sep } from "node:path";
|
|
18754
18799
|
var run4 = promisify4(execFile4);
|
|
18755
18800
|
var xml = (value2) => value2.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
18756
|
-
|
|
18757
|
-
|
|
18758
|
-
|
|
18759
|
-
|
|
18760
|
-
|
|
18761
|
-
|
|
18762
|
-
|
|
18763
|
-
|
|
18764
|
-
|
|
18765
|
-
|
|
18766
|
-
|
|
18767
|
-
|
|
18768
|
-
|
|
18769
|
-
|
|
18770
|
-
|
|
18771
|
-
|
|
18772
|
-
|
|
18773
|
-
|
|
18774
|
-
|
|
18775
|
-
);
|
|
18776
|
-
const quote = (value2) => '""' + value2.replaceAll('"', '""') + '""';
|
|
18777
|
-
await writeFile(
|
|
18778
|
-
path,
|
|
18779
|
-
`CreateObject("WScript.Shell").Run "${quote(node)} ${quote(entry)} watch", 0, False\r
|
|
18780
|
-
`
|
|
18781
|
-
);
|
|
18782
|
-
} else {
|
|
18783
|
-
const path = join8(homedir6(), ".config/systemd/user/dashup.service");
|
|
18784
|
-
await mkdir(dirname5(path), { recursive: true });
|
|
18785
|
-
const quote = (value2) => '"' + value2.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("%", "%%") + '"';
|
|
18786
|
-
await writeFile(
|
|
18787
|
-
path,
|
|
18788
|
-
`[Unit]
|
|
18801
|
+
var NODE_FLAGS = ["--disable-warning=ExperimentalWarning"];
|
|
18802
|
+
var serviceFile = () => process.platform === "darwin" ? join8(homedir6(), "Library/LaunchAgents/com.dashup.connector.plist") : process.platform === "win32" ? join8(
|
|
18803
|
+
process.env.APPDATA ?? join8(homedir6(), "AppData/Roaming"),
|
|
18804
|
+
"Microsoft/Windows/Start Menu/Programs/Startup/Dashup.vbs"
|
|
18805
|
+
) : join8(homedir6(), ".config/systemd/user/dashup.service");
|
|
18806
|
+
var startupInstalled = () => existsSync6(serviceFile());
|
|
18807
|
+
function service(platform, node, entry, config2) {
|
|
18808
|
+
const command2 = [node, ...NODE_FLAGS, entry];
|
|
18809
|
+
if (platform === "darwin")
|
|
18810
|
+
return `<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict><key>Label</key><string>com.dashup.connector</string><key>EnvironmentVariables</key><dict><key>DASHUP_CONFIG</key><string>${xml(config2)}</string></dict><key>ProgramArguments</key><array>${[...command2, "watch"].map((value2) => `<string>${xml(value2)}</string>`).join("")}</array><key>RunAtLoad</key><true/><key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict></dict></plist>`;
|
|
18811
|
+
if (platform === "win32") {
|
|
18812
|
+
const quote2 = (value2) => '""' + value2.replaceAll('"', '""') + '""';
|
|
18813
|
+
return `Set shell = CreateObject("WScript.Shell")\r
|
|
18814
|
+
shell.Environment("Process")("DASHUP_CONFIG") = "${config2.replaceAll('"', '""')}"\r
|
|
18815
|
+
shell.Run "${command2.map(quote2).join(" ")} watch", 0, False\r
|
|
18816
|
+
`;
|
|
18817
|
+
}
|
|
18818
|
+
const quote = (value2) => '"' + value2.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("%", "%%") + '"';
|
|
18819
|
+
return `[Unit]
|
|
18789
18820
|
Description=Dashup desktop connector
|
|
18790
18821
|
After=network-online.target
|
|
18791
18822
|
|
|
18792
18823
|
[Service]
|
|
18793
|
-
|
|
18824
|
+
Environment=${quote(`DASHUP_CONFIG=${config2}`)}
|
|
18825
|
+
ExecStart=${command2.map(quote).join(" ")} watch
|
|
18794
18826
|
Restart=on-failure
|
|
18795
18827
|
RestartSec=5
|
|
18796
18828
|
|
|
18797
18829
|
[Install]
|
|
18798
18830
|
WantedBy=default.target
|
|
18799
|
-
|
|
18831
|
+
`;
|
|
18832
|
+
}
|
|
18833
|
+
async function durableEntry() {
|
|
18834
|
+
const entry = fileURLToPath3(import.meta.url);
|
|
18835
|
+
if (!entry.endsWith(`${sep}dist${sep}index.js`))
|
|
18836
|
+
throw new Error(
|
|
18837
|
+
"Build the connector before installing startup: pnpm --filter @dashup/connector build"
|
|
18800
18838
|
);
|
|
18839
|
+
if (!["_npx", "dlx"].some((cache2) => entry.includes(`${sep}${cache2}${sep}`))) return entry;
|
|
18840
|
+
const root = entry.slice(0, entry.indexOf(`${sep}node_modules${sep}`));
|
|
18841
|
+
const app = join8(dirname5(configPath()), "app");
|
|
18842
|
+
await rm(app, { recursive: true, force: true });
|
|
18843
|
+
await cp(join8(root, "node_modules"), join8(app, "node_modules"), {
|
|
18844
|
+
recursive: true,
|
|
18845
|
+
verbatimSymlinks: true
|
|
18846
|
+
});
|
|
18847
|
+
return join8(app, relative(root, entry));
|
|
18848
|
+
}
|
|
18849
|
+
async function installStartup() {
|
|
18850
|
+
const path = serviceFile();
|
|
18851
|
+
await mkdir(dirname5(path), { recursive: true });
|
|
18852
|
+
await writeFile(
|
|
18853
|
+
path,
|
|
18854
|
+
service(process.platform, process.execPath, await durableEntry(), configPath())
|
|
18855
|
+
);
|
|
18856
|
+
if (process.platform === "darwin") {
|
|
18857
|
+
await run4("launchctl", ["unload", path]).catch(() => {
|
|
18858
|
+
});
|
|
18859
|
+
await run4("launchctl", ["load", path]);
|
|
18860
|
+
} else if (process.platform === "win32") {
|
|
18861
|
+
spawn4("wscript.exe", [path], { detached: true, stdio: "ignore" }).unref();
|
|
18862
|
+
} else {
|
|
18801
18863
|
await run4("systemctl", ["--user", "daemon-reload"]);
|
|
18802
|
-
await run4("systemctl", ["--user", "enable", "
|
|
18864
|
+
await run4("systemctl", ["--user", "enable", "dashup.service"]);
|
|
18865
|
+
await run4("systemctl", ["--user", "restart", "dashup.service"]);
|
|
18803
18866
|
}
|
|
18804
|
-
process.stdout.write("Dashup will start when you sign in to this computer.\n");
|
|
18805
18867
|
}
|
|
18806
18868
|
|
|
18807
18869
|
// src/index.ts
|
|
@@ -18820,6 +18882,42 @@ var refused = (error) => {
|
|
|
18820
18882
|
if (error.cause?.code === "ECONNREFUSED") return null;
|
|
18821
18883
|
throw error;
|
|
18822
18884
|
};
|
|
18885
|
+
var pause = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
18886
|
+
async function answering(expected) {
|
|
18887
|
+
for (let tries = 0; tries < 40; tries++) {
|
|
18888
|
+
const status2 = await running("/status", { signal: AbortSignal.timeout(1e3) }).catch(
|
|
18889
|
+
() => null
|
|
18890
|
+
);
|
|
18891
|
+
if (Boolean(status2?.ok) === expected) return true;
|
|
18892
|
+
await pause(250);
|
|
18893
|
+
}
|
|
18894
|
+
return false;
|
|
18895
|
+
}
|
|
18896
|
+
var BACKGROUND = "Run Dashup in the background and start it when you sign in?";
|
|
18897
|
+
var DONE2 = "Your projects appear on your phone.";
|
|
18898
|
+
async function background() {
|
|
18899
|
+
const waiting = interactive ? ui2.spinner() : void 0;
|
|
18900
|
+
waiting?.start("Starting Dashup in the background");
|
|
18901
|
+
let failure = "Dashup did not start in the background. Run dashup watch to see why.";
|
|
18902
|
+
try {
|
|
18903
|
+
await running("/stop", { method: "POST" }).catch(() => null);
|
|
18904
|
+
await answering(false);
|
|
18905
|
+
await installStartup();
|
|
18906
|
+
if (await answering(true)) {
|
|
18907
|
+
const message = "Dashup runs in the background and starts when you sign in.";
|
|
18908
|
+
if (waiting) waiting.stop(message);
|
|
18909
|
+
else process.stdout.write(`${message}
|
|
18910
|
+
`);
|
|
18911
|
+
return true;
|
|
18912
|
+
}
|
|
18913
|
+
} catch (error) {
|
|
18914
|
+
if (error instanceof Error) failure = error.message;
|
|
18915
|
+
}
|
|
18916
|
+
if (waiting) waiting.error(failure);
|
|
18917
|
+
else process.stderr.write(`${failure}
|
|
18918
|
+
`);
|
|
18919
|
+
return false;
|
|
18920
|
+
}
|
|
18823
18921
|
var authorized = (request2) => {
|
|
18824
18922
|
const expected = Buffer.from(`Bearer ${config.localSecret}`);
|
|
18825
18923
|
const actual = Buffer.from(request2.headers.authorization ?? "");
|
|
@@ -18830,7 +18928,7 @@ var Attention = z3.object({
|
|
|
18830
18928
|
threadId: z3.string().default(""),
|
|
18831
18929
|
title: z3.string().min(1).max(200),
|
|
18832
18930
|
body: z3.string().max(4e3).default(""),
|
|
18833
|
-
reason: z3.enum(["input", "approval", "error", "complete"
|
|
18931
|
+
reason: z3.enum(["input", "approval", "error", "complete"]).default("input")
|
|
18834
18932
|
}).strict();
|
|
18835
18933
|
async function main() {
|
|
18836
18934
|
if (option("--api")) {
|
|
@@ -18843,7 +18941,11 @@ async function main() {
|
|
|
18843
18941
|
throw new Error("Use a separate DASHUP_CONFIG file to connect a different server");
|
|
18844
18942
|
config.api = url2.origin;
|
|
18845
18943
|
}
|
|
18846
|
-
if (command === "install")
|
|
18944
|
+
if (command === "install") {
|
|
18945
|
+
if (!config.secret) throw new Error("Pair this computer first: run dashup pair.");
|
|
18946
|
+
if (!await background()) process.exitCode = 1;
|
|
18947
|
+
return;
|
|
18948
|
+
}
|
|
18847
18949
|
if (command === "stop") {
|
|
18848
18950
|
const response = await running("/stop", { method: "POST" }).catch(refused);
|
|
18849
18951
|
if (!response) throw new Error("Dashup is not running on this computer");
|
|
@@ -18938,10 +19040,18 @@ async function main() {
|
|
|
18938
19040
|
);
|
|
18939
19041
|
return;
|
|
18940
19042
|
}
|
|
18941
|
-
const pairPhone =
|
|
18942
|
-
|
|
18943
|
-
|
|
18944
|
-
|
|
19043
|
+
const pairPhone = (invite) => scanned(config, invite, (email) => `Paired a phone signed in as ${email}`);
|
|
19044
|
+
const keepRunning = async (stopHere) => {
|
|
19045
|
+
if (!interactive) return;
|
|
19046
|
+
const answer = startupInstalled() || await ui2.confirm({ message: BACKGROUND });
|
|
19047
|
+
if (answer === true) {
|
|
19048
|
+
stopHere();
|
|
19049
|
+
const started = await background();
|
|
19050
|
+
ui2.outro(started ? DONE2 : "Run dashup to keep Dashup running in this terminal.");
|
|
19051
|
+
process.exit(started ? 0 : 1);
|
|
19052
|
+
}
|
|
19053
|
+
ui2.log.info("Dashup runs in this terminal. Keep it open; Ctrl+C stops it.");
|
|
19054
|
+
ui2.outro(DONE2);
|
|
18945
19055
|
};
|
|
18946
19056
|
const status2 = await running("/status", { signal: AbortSignal.timeout(1e3) }).catch(refused);
|
|
18947
19057
|
if (status2?.ok) {
|
|
@@ -18956,8 +19066,17 @@ async function main() {
|
|
|
18956
19066
|
throw new Error(
|
|
18957
19067
|
"An older Dashup connector is running. Run dashup stop, then run dashup pair with the updated connector."
|
|
18958
19068
|
);
|
|
18959
|
-
|
|
19069
|
+
if (interactive) intro2();
|
|
19070
|
+
await pairPhone(invite.data);
|
|
19071
|
+
if (interactive && !startupInstalled()) {
|
|
19072
|
+
const answer = await ui2.confirm({ message: BACKGROUND });
|
|
19073
|
+
if (answer === true && !await background()) process.exitCode = 1;
|
|
19074
|
+
}
|
|
19075
|
+
if (interactive) ui2.outro(DONE2);
|
|
19076
|
+
process.exit();
|
|
18960
19077
|
}
|
|
19078
|
+
if (!config.secret && !interactive)
|
|
19079
|
+
throw new Error("Run dashup pair in a terminal to pair this computer.");
|
|
18961
19080
|
let connector;
|
|
18962
19081
|
const server = createServer(async (request2, response) => {
|
|
18963
19082
|
if (!authorized(request2)) {
|
|
@@ -19036,12 +19155,26 @@ async function main() {
|
|
|
19036
19155
|
});
|
|
19037
19156
|
try {
|
|
19038
19157
|
const linking = !config.secret;
|
|
19039
|
-
if (linking)
|
|
19158
|
+
if (linking) {
|
|
19159
|
+
intro2();
|
|
19160
|
+
if (!await link(config)) process.exit(1);
|
|
19161
|
+
await keepRunning(() => server.close());
|
|
19162
|
+
}
|
|
19040
19163
|
connector = new Connector(config);
|
|
19041
19164
|
await connector.start();
|
|
19042
|
-
if (command === "pair" && !linking)
|
|
19043
|
-
|
|
19044
|
-
|
|
19165
|
+
if (command === "pair" && !linking) {
|
|
19166
|
+
if (interactive) intro2();
|
|
19167
|
+
const paired = await pairPhone(connector.invite()).catch((error) => {
|
|
19168
|
+
if (!reported(error))
|
|
19169
|
+
process.stderr.write(`${error instanceof Error ? error.message : "Pairing failed"}
|
|
19170
|
+
`);
|
|
19171
|
+
});
|
|
19172
|
+
if (paired)
|
|
19173
|
+
await keepRunning(() => {
|
|
19174
|
+
connector?.stop();
|
|
19175
|
+
server.close();
|
|
19176
|
+
});
|
|
19177
|
+
}
|
|
19045
19178
|
} catch (error) {
|
|
19046
19179
|
connector?.stop();
|
|
19047
19180
|
server.close();
|
|
@@ -19049,7 +19182,8 @@ async function main() {
|
|
|
19049
19182
|
}
|
|
19050
19183
|
}
|
|
19051
19184
|
main().catch((error) => {
|
|
19052
|
-
|
|
19185
|
+
if (!reported(error))
|
|
19186
|
+
process.stderr.write(`${error instanceof Error ? error.message : "Dashup failed"}
|
|
19053
19187
|
`);
|
|
19054
19188
|
process.exit(1);
|
|
19055
19189
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dashup/connector",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"dashup": "dist/index.js"
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@agentclientprotocol/sdk": "1.9.0",
|
|
19
|
+
"@clack/prompts": "^1.8.1",
|
|
19
20
|
"@microsoft/agent-host-protocol": "1.0.0",
|
|
20
21
|
"qrcode": "^1.5.4",
|
|
21
22
|
"ws": "^8.21.3",
|
|
@@ -29,10 +30,10 @@
|
|
|
29
30
|
"tsx": "^4.22.4",
|
|
30
31
|
"typescript": "^5.9.3",
|
|
31
32
|
"@dashup/models": "0.1.0",
|
|
32
|
-
"@dashup/typescript-config": "0.0.0",
|
|
33
33
|
"@dashup/protocol": "0.1.0",
|
|
34
|
-
"@
|
|
35
|
-
"@parcae/lynx": "0.1.0"
|
|
34
|
+
"@dashup/typescript-config": "0.0.0",
|
|
35
|
+
"@parcae/lynx": "0.1.0",
|
|
36
|
+
"@parcae/sdk": "0.8.1"
|
|
36
37
|
},
|
|
37
38
|
"scripts": {
|
|
38
39
|
"dashup": "tsx src/index.ts",
|
|
@@ -40,7 +41,7 @@
|
|
|
40
41
|
"start": "node dist/index.js watch",
|
|
41
42
|
"build": "tsup",
|
|
42
43
|
"check-types": "tsc --noEmit",
|
|
43
|
-
"test": "node --import tsx --test tests/connector.test.ts tests/history.test.ts tests/harness-history.test.ts tests/harness-live.test.ts tests/harness-queue.test.ts tests/harness-vscode.test.ts tests/stop.test.ts tests/viewer.test.ts",
|
|
44
|
+
"test": "node --import tsx --test tests/connector.test.ts tests/history.test.ts tests/harness-history.test.ts tests/harness-live.test.ts tests/harness-queue.test.ts tests/harness-vscode.test.ts tests/install.test.ts tests/stop.test.ts tests/viewer.test.ts",
|
|
44
45
|
"test:workflow": "node --import tsx tests/workflow.test.ts"
|
|
45
46
|
}
|
|
46
47
|
}
|