@dashup/connector 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +12 -19
  2. package/dist/index.js +110 -49
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -8,23 +8,16 @@ With Node 24:
8
8
  npx @dashup/connector pair
9
9
  ```
10
10
 
11
- From a checkout, use pnpm 10.34.5 from the workspace root and `pnpm --filter @dashup/connector dashup` in place of `dashup`:
11
+ To keep `dashup` as a command, install it with `npm install -g @dashup/connector`. The package bundles the workspace code; screen and input need the native helper from `packages/desktop`, which it does not include.
12
12
 
13
- ```sh
14
- pnpm install
15
- pnpm exec turbo build --filter=@dashup/connector...
16
- ```
17
-
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.
19
-
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`.
13
+ `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. To pair without the prompt, as an agent can, name the account with `--account you@example.com`; a phone signed in to any other account is refused. Without a terminal, the code opens in a browser on this computer instead of printing, because it carries the pairing's encryption key and an agent's output leaves the computer. Pairing then exits, and `dashup install` keeps Dashup running. Where no browser can open, pairing needs a terminal. 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`.
21
14
 
22
15
  ## Existing projects and alerts
23
16
 
24
17
  ```sh
25
- pnpm --filter @dashup/connector dashup doctor
26
- pnpm --filter @dashup/connector dashup projects
27
- pnpm --filter @dashup/connector dashup alert --project PROJECT_ID --title "Dashup is connected"
18
+ dashup doctor
19
+ dashup projects
20
+ dashup alert --project PROJECT_ID --title "Dashup is connected"
28
21
  ```
29
22
 
30
23
  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.
@@ -63,10 +56,10 @@ Live discovery is bounded to 100 sessions per endpoint. OpenCode supplies the la
63
56
  The default history and native-host path needs no wrapper or editor configuration. For an explicitly configured external ACP agent, use the connector as the editor's stdio wrapper; it uses the official ACP SDK's protocol contracts:
64
57
 
65
58
  ```sh
66
- node /absolute/path/to/dashup/apps/connector/dist/index.js acp -- /absolute/path/to/original-agent ORIGINAL_ARGUMENTS
59
+ dashup acp -- /absolute/path/to/original-agent ORIGINAL_ARGUMENTS
67
60
  ```
68
61
 
69
- Set the editor's existing agent command to Node, with the arguments above. Start the connector first and launch the agent through that editor configuration. The proxy starts the original command once and forwards the editor's own ACP stream. Phone messages and approval choices go to that same process and session. Only native approval choices are offered, including native file scope when the agent provides it. The proxy does not create another session when opening a phone thread.
62
+ Install `dashup` globally and set the editor's existing agent command to it, with the arguments above. Start the connector first and launch the agent through that editor configuration. The proxy starts the original command once and forwards the editor's own ACP stream. Phone messages and approval choices go to that same process and session. Only native approval choices are offered, including native file scope when the agent provides it. The proxy does not create another session when opening a phone thread.
70
63
 
71
64
  The wrapper and connector must run on the same execution host. For SSH projects, confirm where that particular external agent runs before configuring its command; [Zed treats external-agent runtime and authentication as agent-owned](https://zed.dev/docs/ai/external-agents#remote-projects). Pair the development machine separately if it owns the wrapper process. Reading the PC's saved SSH project does not attach to a remote agent process.
72
65
 
@@ -78,8 +71,8 @@ Build the native WebRTC helper on the computer, following [desktop build require
78
71
 
79
72
  ```sh
80
73
  pnpm --filter @dashup/desktop build
81
- pnpm --filter @dashup/connector dashup permissions --screen
82
- pnpm --filter @dashup/connector dashup permissions --screen --control
74
+ dashup permissions --screen
75
+ dashup permissions --screen --control
83
76
  ```
84
77
 
85
78
  Run `permissions` without flags to disable access. Video and input use a WebRTC peer connection; SDP, ICE candidates, and DTLS fingerprints travel inside paired-device encrypted envelopes. Only one screen session is active at a time. The relay does not carry decoded frames. Set `DASHUP_DESKTOP_BIN` if the helper lives outside its workspace build path.
@@ -88,15 +81,15 @@ macOS requires Screen Recording and Accessibility permission. Windows uses the s
88
81
 
89
82
  ## Start at login
90
83
 
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.
84
+ 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; without a desktop session, as over SSH, install also turns on lingering so the service keeps running after you log out. 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.
92
85
 
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.
86
+ `dashup uninstall` stops Dashup and removes the startup entry and its `app/` copy; the computer stays paired. `dashup stop` leaves the service stopped until the next sign-in; a crash restarts it.
94
87
 
95
88
  ## Local state and checks
96
89
 
97
90
  `~/.dashup/connector.json` holds this computer's secret, pairing keys, encrypted outbox, and replay counters. It is written atomically with private Unix permissions; it is not currently protected by an OS credential vault. Do not share or copy it to another connector. `DASHUP_CONFIG` selects a separate state file. `DASHUP_ZED_DATA` and `DASHUP_VSCODE_DATA` select custom editor data directories. Native history discovery honors `CLAUDE_CONFIG_DIR`, `CODEX_HOME`, and XDG paths. The selected conversation is parsed locally on demand; Dashup does not create another plaintext transcript database.
98
91
 
99
- For isolated development fixtures, `DASHUP_LIVE_DISCOVERY=0` disables automatic process discovery. Explicit `DASHUP_OPENCODE_ENDPOINT`, `DASHUP_CODEX_SOCKET`, and `DASHUP_AHP_DATA` overrides allow local test endpoints without modifying editor configuration. They are not required for ordinary discovery.
92
+ From a checkout, run `pnpm install` and `pnpm exec turbo build --filter=@dashup/connector...` with pnpm 10.34.5, then use `pnpm --filter @dashup/connector dashup` in place of `dashup`. For isolated development fixtures, `DASHUP_LIVE_DISCOVERY=0` disables automatic process discovery. Explicit `DASHUP_OPENCODE_ENDPOINT`, `DASHUP_CODEX_SOCKET`, and `DASHUP_AHP_DATA` overrides allow local test endpoints without modifying editor configuration. They are not required for ordinary discovery.
100
93
 
101
94
  ```sh
102
95
  pnpm --filter @dashup/connector test
package/dist/index.js CHANGED
@@ -2706,7 +2706,7 @@ var require_pluralize = __commonJS({
2706
2706
 
2707
2707
  // src/index.ts
2708
2708
  import { randomBytes as randomBytes5, timingSafeEqual } from "node:crypto";
2709
- import { createServer } from "node:http";
2709
+ import { createServer as createServer2 } from "node:http";
2710
2710
  import * as ui2 from "@clack/prompts";
2711
2711
  import { z as z3 } from "zod";
2712
2712
 
@@ -4004,13 +4004,16 @@ async function proxyAcp(config2, executable, args2) {
4004
4004
 
4005
4005
  // src/pair.ts
4006
4006
  import { hostname } from "node:os";
4007
+ import { promisify } from "node:util";
4008
+ import { createServer } from "node:http";
4009
+ import { execFile } from "node:child_process";
4007
4010
  import { randomBytes as randomBytes2, createHash as createHash2 } from "node:crypto";
4008
4011
  import QRCode from "qrcode";
4009
4012
  import * as ui from "@clack/prompts";
4010
4013
  import { z } from "zod";
4011
4014
 
4012
4015
  // package.json
4013
- var version = "0.1.3";
4016
+ var version = "0.1.4";
4014
4017
 
4015
4018
  // src/pair.ts
4016
4019
  var Invitation = z.object({
@@ -4026,29 +4029,55 @@ function invitation(config2, secret = config2.secret) {
4026
4029
  const computer = createHash2("sha256").update(secret).digest("hex");
4027
4030
  return { code, key, text: formatPairingCode({ api: config2.api, computer, code, key }) };
4028
4031
  }
4029
- async function scanned(config2, { code, text: text3 }, done, secret = config2.secret) {
4030
- const qr = await QRCode.toString(text3, {
4031
- type: "terminal",
4032
- small: true,
4033
- errorCorrectionLevel: "L"
4032
+ var SCAN = "Scan this in the Dashup app under Pair a computer. It expires in 5 minutes.";
4033
+ var BROWSER = {
4034
+ darwin: ["open"],
4035
+ win32: ["rundll32", "url.dll,FileProtocolHandler"]
4036
+ };
4037
+ var NO_BROWSER = "No browser here can show the pairing code. Run dashup pair in a terminal on this computer.";
4038
+ async function show(text3) {
4039
+ if (process.platform === "linux" && !process.env.DISPLAY && !process.env.WAYLAND_DISPLAY)
4040
+ throw new Error(NO_BROWSER);
4041
+ const path = `/${randomBytes2(16).toString("hex")}`;
4042
+ const qr = await QRCode.toString(text3, { type: "svg", margin: 2, errorCorrectionLevel: "L" });
4043
+ const page = `<!doctype html><meta charset="utf-8"><title>Pair with Dashup</title><style>body{margin:0;min-height:100vh;display:grid;place-items:center;background:#fff;color:#16181d;font:16px/1.5 system-ui,sans-serif}main{max-width:22rem;padding:2rem;text-align:center}svg{width:100%;margin:1rem 0}code{font-size:11px;word-break:break-all;color:#5d6470}</style><main><p>${SCAN}</p>${qr}<p>Can't scan? Paste this code instead:</p><code>${text3}</code></main>`;
4044
+ const server = createServer((request2, response) => {
4045
+ if (request2.url !== path) return void response.writeHead(404).end();
4046
+ response.writeHead(200, {
4047
+ "Content-Type": "text/html; charset=utf-8",
4048
+ "Cache-Control": "no-store",
4049
+ "Content-Security-Policy": "default-src 'none'; style-src 'unsafe-inline'",
4050
+ "Referrer-Policy": "no-referrer"
4051
+ });
4052
+ response.end(page);
4034
4053
  });
4035
- const rows = qr.split("\n").filter((row) => row.replace(/\x1b\[\d+m/g, ""));
4054
+ await new Promise((resolve3) => server.listen(0, "127.0.0.1", resolve3));
4055
+ const [command2, ...args2] = BROWSER[process.platform] ?? ["xdg-open"];
4056
+ const url2 = `http://127.0.0.1:${server.address().port}${path}`;
4057
+ try {
4058
+ await promisify(execFile)(command2, [...args2, url2]);
4059
+ } catch {
4060
+ server.close();
4061
+ throw new Error(NO_BROWSER);
4062
+ }
4063
+ process.stdout.write(`Opened the pairing code in a browser on this computer. ${SCAN}
4064
+ `);
4065
+ return () => server.close();
4066
+ }
4067
+ async function scanned(config2, { code, text: text3 }, done, secret = config2.secret) {
4036
4068
  if (interactive) {
4037
- ui.log.step("Scan this in the Dashup app under Pair a computer. It expires in 5 minutes.");
4069
+ const qr = await QRCode.toString(text3, {
4070
+ type: "terminal",
4071
+ small: true,
4072
+ errorCorrectionLevel: "L"
4073
+ });
4074
+ ui.log.step(SCAN);
4038
4075
  ui.log.message("Can't scan? Paste this code instead:", { spacing: 0 });
4039
4076
  process.stdout.write(` ${text3}
4040
4077
  `);
4041
- ui.log.message(rows);
4042
- } else {
4043
- process.stdout.write(
4044
- `
4045
- Pairing code: ${text3}
4046
-
4047
- ${rows.join("\n")}
4048
- Scan this in the Dashup app (Pair a computer). It expires in 5 minutes.
4049
- `
4050
- );
4078
+ ui.log.message(qr.split("\n").filter((row) => row.replace(/\x1b\[\d+m/g, "")));
4051
4079
  }
4080
+ const close = interactive ? void 0 : await show(text3);
4052
4081
  const waiting = interactive ? ui.spinner() : void 0;
4053
4082
  waiting?.start("Waiting for your phone");
4054
4083
  try {
@@ -4071,16 +4100,18 @@ Scan this in the Dashup app (Pair a computer). It expires in 5 minutes.
4071
4100
  if (!waiting) throw error;
4072
4101
  waiting.error(error instanceof Error ? error.message : "Pairing failed");
4073
4102
  throw Object.assign(error, { reported: true });
4103
+ } finally {
4104
+ close?.();
4074
4105
  }
4075
4106
  }
4076
4107
  var reported = (error) => error?.reported === true;
4077
- async function link(config2) {
4108
+ async function link(config2, account) {
4078
4109
  const secret = newSecret(randomBytes2);
4079
4110
  const invite = invitation(config2, secret);
4080
4111
  const { pairing, email } = await scanned(
4081
4112
  config2,
4082
4113
  invite,
4083
- (account) => `Scanned by ${account}`,
4114
+ (account2) => `Scanned by ${account2}`,
4084
4115
  secret
4085
4116
  );
4086
4117
  const linked = { ...config2, secret };
@@ -4091,14 +4122,13 @@ async function link(config2) {
4091
4122
  kind: "desktop",
4092
4123
  platform
4093
4124
  });
4094
- const confirmed = await ui.confirm({
4095
- message: `Pair this computer with ${email}?`,
4096
- initialValue: false
4097
- });
4125
+ const confirmed = account ? email.toLowerCase() === account.toLowerCase() : await ui.confirm({ message: `Pair this computer with ${email}?`, initialValue: false });
4098
4126
  if (confirmed !== true) {
4099
4127
  await api(linked, `/dashup/pairings/${pairing.id}`, void 0, "DELETE").catch(() => {
4100
4128
  });
4101
- ui.cancel("Pairing cancelled. Nothing was shared.");
4129
+ ui.cancel(
4130
+ account ? `Pairing cancelled: that phone uses ${email}, not ${account}. Nothing was shared.` : "Pairing cancelled. Nothing was shared."
4131
+ );
4102
4132
  return false;
4103
4133
  }
4104
4134
  Object.assign(config2, {
@@ -4120,9 +4150,9 @@ import { createInterface as createInterface2 } from "node:readline";
4120
4150
  import { spawn as spawn2 } from "node:child_process";
4121
4151
 
4122
4152
  // src/screen.ts
4123
- import { promisify } from "node:util";
4124
- import { execFile, spawnSync } from "node:child_process";
4125
- var run = promisify(execFile);
4153
+ import { promisify as promisify2 } from "node:util";
4154
+ import { execFile as execFile2, spawnSync } from "node:child_process";
4155
+ var run = promisify2(execFile2);
4126
4156
  var powershell = (source) => run("powershell.exe", [
4127
4157
  "-NoProfile",
4128
4158
  "-NonInteractive",
@@ -16704,9 +16734,9 @@ var Codex = class {
16704
16734
  import Socket3 from "ws";
16705
16735
  import { homedir as homedir5 } from "node:os";
16706
16736
  import { connect as connect2 } from "node:net";
16707
- import { promisify as promisify2 } from "node:util";
16737
+ import { promisify as promisify3 } from "node:util";
16708
16738
  import { fileURLToPath as fileURLToPath2 } from "node:url";
16709
- import { execFile as execFile2 } from "node:child_process";
16739
+ import { execFile as execFile3 } from "node:child_process";
16710
16740
  import { createHash as createHash3, randomUUID as randomUUID3 } from "node:crypto";
16711
16741
  import { stat as stat2, readdir as readdir2, readFile as readFile2 } from "node:fs/promises";
16712
16742
  import { join as join5, dirname as dirname3, basename as basename2, isAbsolute as isAbsolute3 } from "node:path";
@@ -16719,7 +16749,7 @@ var MAX_METADATA = 64 * 1024;
16719
16749
  var MAX_HOSTS = 16;
16720
16750
  var MAX_SESSIONS2 = 100;
16721
16751
  var ROOT_CHANNEL = "ahp-root://";
16722
- var run2 = promisify2(execFile2);
16752
+ var run2 = promisify3(execFile3);
16723
16753
  var CONFIRMATION_CHOICES = [
16724
16754
  { id: "approve", label: "Allow once", kind: "approve" },
16725
16755
  { id: "deny", label: "Reject", kind: "deny" }
@@ -17201,11 +17231,11 @@ var VSCode = class {
17201
17231
 
17202
17232
  // src/harness/opencode.ts
17203
17233
  import { join as join6 } from "node:path";
17204
- import { promisify as promisify3 } from "node:util";
17234
+ import { promisify as promisify4 } from "node:util";
17205
17235
  import { createHash as createHash4 } from "node:crypto";
17206
- import { execFile as execFile3 } from "node:child_process";
17236
+ import { execFile as execFile4 } from "node:child_process";
17207
17237
  import { stat as stat3, readlink, readdir as readdir3, readFile as readFile3 } from "node:fs/promises";
17208
- var run3 = promisify3(execFile3);
17238
+ var run3 = promisify4(execFile4);
17209
17239
  var REQUEST_MS3 = 4e3;
17210
17240
  var MAX_BYTES3 = 8 * 1024 * 1024;
17211
17241
  var MAX_SESSIONS3 = 100;
@@ -18790,13 +18820,13 @@ var Connector = class {
18790
18820
 
18791
18821
  // src/install.ts
18792
18822
  import { existsSync as existsSync6 } from "node:fs";
18793
- import { homedir as homedir6 } from "node:os";
18794
- import { promisify as promisify4 } from "node:util";
18823
+ import { homedir as homedir6, userInfo } from "node:os";
18824
+ import { promisify as promisify5 } from "node:util";
18795
18825
  import { fileURLToPath as fileURLToPath3 } from "node:url";
18796
- import { execFile as execFile4, spawn as spawn4 } from "node:child_process";
18826
+ import { execFile as execFile5, spawn as spawn4 } from "node:child_process";
18797
18827
  import { cp, mkdir, rm, writeFile } from "node:fs/promises";
18798
18828
  import { dirname as dirname5, join as join8, relative, sep } from "node:path";
18799
- var run4 = promisify4(execFile4);
18829
+ var run4 = promisify5(execFile5);
18800
18830
  var xml = (value2) => value2.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
18801
18831
  var NODE_FLAGS = ["--disable-warning=ExperimentalWarning"];
18802
18832
  var serviceFile = () => process.platform === "darwin" ? join8(homedir6(), "Library/LaunchAgents/com.dashup.connector.plist") : process.platform === "win32" ? join8(
@@ -18863,8 +18893,23 @@ async function installStartup() {
18863
18893
  await run4("systemctl", ["--user", "daemon-reload"]);
18864
18894
  await run4("systemctl", ["--user", "enable", "dashup.service"]);
18865
18895
  await run4("systemctl", ["--user", "restart", "dashup.service"]);
18896
+ if (!process.env.DISPLAY && !process.env.WAYLAND_DISPLAY)
18897
+ return run4("loginctl", ["enable-linger"]).then(
18898
+ () => void 0,
18899
+ () => `It stops when you log out. To keep it running, run sudo loginctl enable-linger ${userInfo().username}.`
18900
+ );
18866
18901
  }
18867
18902
  }
18903
+ async function uninstallStartup() {
18904
+ const path = serviceFile();
18905
+ if (process.platform === "darwin") await run4("launchctl", ["unload", path]).catch(() => {
18906
+ });
18907
+ else if (process.platform !== "win32")
18908
+ await run4("systemctl", ["--user", "disable", "dashup.service"]).catch(() => {
18909
+ });
18910
+ await rm(path, { force: true });
18911
+ await rm(join8(dirname5(configPath()), "app"), { recursive: true, force: true });
18912
+ }
18868
18913
 
18869
18914
  // src/index.ts
18870
18915
  var config = loadConfig();
@@ -18902,9 +18947,9 @@ async function background() {
18902
18947
  try {
18903
18948
  await running("/stop", { method: "POST" }).catch(() => null);
18904
18949
  await answering(false);
18905
- await installStartup();
18950
+ const note = await installStartup();
18906
18951
  if (await answering(true)) {
18907
- const message = "Dashup runs in the background and starts when you sign in.";
18952
+ const message = `Dashup runs in the background and starts when you sign in.${note ? ` ${note}` : ""}`;
18908
18953
  if (waiting) waiting.stop(message);
18909
18954
  else process.stdout.write(`${message}
18910
18955
  `);
@@ -18941,11 +18986,21 @@ async function main() {
18941
18986
  throw new Error("Use a separate DASHUP_CONFIG file to connect a different server");
18942
18987
  config.api = url2.origin;
18943
18988
  }
18989
+ const account = option("--account");
18990
+ if (account !== void 0 && !/^[^\s@]+@[^\s@]+$/.test(account))
18991
+ throw new Error("Use --account with the email you sign in to the Dashup app with");
18944
18992
  if (command === "install") {
18945
18993
  if (!config.secret) throw new Error("Pair this computer first: run dashup pair.");
18946
18994
  if (!await background()) process.exitCode = 1;
18947
18995
  return;
18948
18996
  }
18997
+ if (command === "uninstall") {
18998
+ await running("/stop", { method: "POST" }).catch(() => null);
18999
+ await answering(false);
19000
+ await uninstallStartup();
19001
+ process.stdout.write("Dashup no longer starts when you sign in. This computer stays paired.\n");
19002
+ return;
19003
+ }
18949
19004
  if (command === "stop") {
18950
19005
  const response = await running("/stop", { method: "POST" }).catch(refused);
18951
19006
  if (!response) throw new Error("Dashup is not running on this computer");
@@ -19036,20 +19091,24 @@ async function main() {
19036
19091
  }
19037
19092
  if (!["pair", "watch"].includes(command)) {
19038
19093
  process.stdout.write(
19039
- "Dashup: pair | watch | projects | alert --project <id> | doctor | permissions --screen --control | stop | acp -- <original-agent-command> | install\n"
19094
+ "Dashup: pair [--account <email>] | watch | projects | alert --project <id> | doctor | permissions --screen --control | stop | acp -- <original-agent-command> | install | uninstall\n"
19040
19095
  );
19041
19096
  return;
19042
19097
  }
19043
19098
  const pairPhone = (invite) => scanned(config, invite, (email) => `Paired a phone signed in as ${email}`);
19044
19099
  const keepRunning = async (stopHere) => {
19045
- if (!interactive) return;
19046
- const answer = startupInstalled() || await ui2.confirm({ message: BACKGROUND });
19047
- if (answer === true) {
19100
+ const answer = startupInstalled() || interactive && await ui2.confirm({ message: BACKGROUND }) === true;
19101
+ if (answer) {
19048
19102
  stopHere();
19049
19103
  const started = await background();
19050
19104
  ui2.outro(started ? DONE2 : "Run dashup to keep Dashup running in this terminal.");
19051
19105
  process.exit(started ? 0 : 1);
19052
19106
  }
19107
+ if (!interactive) {
19108
+ stopHere();
19109
+ process.stdout.write("Paired. Run dashup install to keep Dashup running in the background.\n");
19110
+ process.exit(0);
19111
+ }
19053
19112
  ui2.log.info("Dashup runs in this terminal. Keep it open; Ctrl+C stops it.");
19054
19113
  ui2.outro(DONE2);
19055
19114
  };
@@ -19075,10 +19134,12 @@ async function main() {
19075
19134
  if (interactive) ui2.outro(DONE2);
19076
19135
  process.exit();
19077
19136
  }
19078
- if (!config.secret && !interactive)
19079
- throw new Error("Run dashup pair in a terminal to pair this computer.");
19137
+ if (!config.secret && !interactive && !account)
19138
+ throw new Error(
19139
+ "To pair this computer, run dashup pair in a terminal, or add --account with your Dashup email."
19140
+ );
19080
19141
  let connector;
19081
- const server = createServer(async (request2, response) => {
19142
+ const server = createServer2(async (request2, response) => {
19082
19143
  if (!authorized(request2)) {
19083
19144
  response.writeHead(403).end();
19084
19145
  return;
@@ -19157,7 +19218,7 @@ async function main() {
19157
19218
  const linking = !config.secret;
19158
19219
  if (linking) {
19159
19220
  intro2();
19160
- if (!await link(config)) process.exit(1);
19221
+ if (!await link(config, account)) process.exit(1);
19161
19222
  await keepRunning(() => server.close());
19162
19223
  }
19163
19224
  connector = new Connector(config);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dashup/connector",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "dashup": "dist/index.js"
@@ -29,10 +29,10 @@
29
29
  "tsup": "^8.5.1",
30
30
  "tsx": "^4.22.4",
31
31
  "typescript": "^5.9.3",
32
- "@dashup/models": "0.1.0",
33
- "@dashup/protocol": "0.1.0",
34
32
  "@dashup/typescript-config": "0.0.0",
35
33
  "@parcae/lynx": "0.1.0",
34
+ "@dashup/models": "0.1.0",
35
+ "@dashup/protocol": "0.1.0",
36
36
  "@parcae/sdk": "0.8.1"
37
37
  },
38
38
  "scripts": {