accelr8 3.8.2 → 4.0.0-next.1

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.
@@ -1,15 +1,17 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ accelr8Dir
4
+ } from "./chunk-62O2F73J.js";
2
5
 
3
6
  // src/update.ts
4
7
  import fs from "node:fs";
5
- import os from "node:os";
6
8
  import path from "node:path";
7
- import { spawn } from "node:child_process";
9
+ import { spawn, spawnSync } from "node:child_process";
8
10
  var REGISTRY = "https://registry.npmjs.org/accelr8/latest";
9
11
  var CACHE_FILE = "update-check.json";
10
12
  var CHECK_EVERY_MS = 6 * 60 * 60 * 1e3;
11
13
  function cachePath(baseDir) {
12
- return path.join(baseDir ?? path.join(os.homedir(), ".accelr8"), CACHE_FILE);
14
+ return path.join(baseDir ?? accelr8Dir(), CACHE_FILE);
13
15
  }
14
16
  function compareVersions(a, b) {
15
17
  const clean = (v) => v.trim().replace(/^v/, "").split("-")[0].split(".").map((n) => parseInt(n, 10) || 0);
@@ -44,7 +46,7 @@ function refreshUpdateCache(baseDir, now = Date.now()) {
44
46
  Promise.resolve().then(() => globalThis.fetch ? globalThis.fetch(REGISTRY, { signal: ac.signal, headers: { accept: "application/json" } }) : null).then((r) => r && r.ok ? r.json() : null).then((j) => {
45
47
  const latest = j && typeof j.version === "string" ? j.version : null;
46
48
  if (!latest) return;
47
- const dir = baseDir ?? path.join(os.homedir(), ".accelr8");
49
+ const dir = baseDir ?? accelr8Dir();
48
50
  try {
49
51
  fs.mkdirSync(dir, { recursive: true });
50
52
  } catch {
@@ -60,11 +62,20 @@ function updateBanner(current, baseDir) {
60
62
  const u = cachedUpdate(current, baseDir);
61
63
  return u ? `update available \xB7 v${u.latest} \xB7 npm i -g accelr8@latest` : null;
62
64
  }
63
- function runUpdate(cb) {
65
+ function updateSpawnPlan(platform = process.platform) {
66
+ const args = ["install", "-g", "accelr8@latest", "--no-fund", "--no-audit"];
67
+ return platform === "win32" ? { command: ["npm", ...args].join(" "), args: [], shell: true } : { command: "npm", args, shell: false };
68
+ }
69
+ function updateCancelPlan(pid, platform = process.platform) {
70
+ if (platform !== "win32" || !pid) return null;
71
+ return { command: "taskkill", args: ["/pid", String(pid), "/T", "/F"] };
72
+ }
73
+ function runUpdate(cb, platform = process.platform) {
64
74
  let child;
65
75
  const MANUAL = "run it yourself: npm i -g accelr8@latest";
66
76
  try {
67
- child = spawn("npm", ["install", "-g", "accelr8@latest", "--no-fund", "--no-audit"], { stdio: ["ignore", "pipe", "pipe"] });
77
+ const plan = updateSpawnPlan(platform);
78
+ child = spawn(plan.command, plan.args, { stdio: ["ignore", "pipe", "pipe"], shell: plan.shell, windowsHide: true });
68
79
  } catch (e) {
69
80
  queueMicrotask(() => cb.onDone(false, `couldn't start npm \xB7 ${MANUAL}`));
70
81
  return () => {
@@ -91,6 +102,11 @@ function runUpdate(cb) {
91
102
  else cb.onDone(false, code === 243 || code === 3 ? `permission denied \xB7 ${MANUAL}` : `npm exited ${code} \xB7 ${MANUAL}`);
92
103
  });
93
104
  return () => {
105
+ const tree = updateCancelPlan(child.pid, platform);
106
+ if (tree) {
107
+ const r = spawnSync(tree.command, tree.args, { stdio: "ignore", windowsHide: true, timeout: 1e4 });
108
+ if (!r.error && r.status === 0) return;
109
+ }
94
110
  try {
95
111
  child.kill();
96
112
  } catch {
@@ -102,5 +118,7 @@ export {
102
118
  compareVersions,
103
119
  refreshUpdateCache,
104
120
  runUpdate,
105
- updateBanner
121
+ updateBanner,
122
+ updateCancelPlan,
123
+ updateSpawnPlan
106
124
  };
package/dist/cli.js CHANGED
@@ -3,24 +3,29 @@ import {
3
3
  DEFAULT_SERVER,
4
4
  PORT,
5
5
  VERSION
6
- } from "./chunks/chunk-7JSMON7M.js";
6
+ } from "./chunks/chunk-5QXGOLQ3.js";
7
7
 
8
8
  // src/cli.ts
9
9
  var HELP = `
10
10
  accelr8 v${VERSION} - collaborative ascii canvas in your terminal
11
11
 
12
12
  USAGE
13
- accelr8 open the app
13
+ accelr8 open the app (signs you in first if needed)
14
14
  accelr8 join <CODE> open straight into a project
15
+ accelr8 login sign in: you type a code on a web page
16
+ accelr8 logout sign this machine out (--all: every machine)
17
+ accelr8 whoami show who this machine is signed in as
18
+ accelr8 approve <CODE> sign another machine in as you (it shows the code)
15
19
  accelr8 serve run your own sync server
16
20
  accelr8 radio tune in to ACCELR8 radio (or press the button)
17
21
  accelr8 --intro replay the opening cinematic
18
22
 
19
23
  OPTIONS
20
- --name <name> display name (remembered for next time)
24
+ --name <name> rename yourself (your account keeps it)
21
25
  --server <url> sync server url
22
26
  (default: ${DEFAULT_SERVER})
23
27
  --port <port> port for serve (default: ${PORT})
28
+ --host <address> address serve listens on (127.0.0.1: this machine only)
24
29
  -v, --version print version
25
30
  -h, --help this help
26
31
 
@@ -53,12 +58,22 @@ async function main() {
53
58
  console.error(`invalid port: ${argVal(args, "--port")}`);
54
59
  process.exit(1);
55
60
  }
56
- const { runServer } = await import("./chunks/server-IRLRECZ7.js");
57
- runServer(port);
61
+ const { runServer } = await import("./chunks/server-A4T45QZ4.js");
62
+ try {
63
+ const host = argVal(args, "--host");
64
+ await runServer(port, host ? { host } : {});
65
+ } catch (e) {
66
+ console.error(`could not start the server: ${e instanceof Error ? e.message : e}`);
67
+ process.exit(1);
68
+ }
69
+ return;
70
+ }
71
+ if (args[0] === "login" || args[0] === "logout" || args[0] === "whoami" || args[0] === "approve") {
72
+ process.exitCode = await runAuthCommand(args[0], args);
58
73
  return;
59
74
  }
60
75
  if (args[0] === "agent") {
61
- const { runAgentCli } = await import("./chunks/agentcli-ZI2O7ID7.js");
76
+ const { runAgentCli } = await import("./chunks/agentcli-HL6V26E7.js");
62
77
  await runAgentCli(args.slice(1));
63
78
  return;
64
79
  }
@@ -67,7 +82,7 @@ async function main() {
67
82
  console.error("accelr8 radio needs an interactive terminal (TTY).");
68
83
  process.exit(1);
69
84
  }
70
- const { runRadio } = await import("./chunks/app-SYLCS3QX.js");
85
+ const { runRadio } = await import("./chunks/app-JMUXGMWK.js");
71
86
  runRadio({
72
87
  name: argVal(args, "--name"),
73
88
  server: argVal(args, "--server"),
@@ -87,11 +102,11 @@ async function main() {
87
102
  console.error("accelr8 is a terminal app and needs an interactive terminal (TTY).");
88
103
  process.exit(1);
89
104
  }
90
- const { cachedUpdate, refreshUpdateCache } = await import("./chunks/update-UEPNGB6Y.js");
105
+ const { cachedUpdate, refreshUpdateCache } = await import("./chunks/update-RGDNBI4T.js");
91
106
  refreshUpdateCache();
92
107
  const update = cachedUpdate(VERSION);
93
- const { runClient } = await import("./chunks/client-BKQLCKVT.js");
94
- runClient({
108
+ const { runClient } = await import("./chunks/client-UCMRJVMA.js");
109
+ await runClient({
95
110
  name: argVal(args, "--name"),
96
111
  server: argVal(args, "--server"),
97
112
  joinCode,
@@ -99,4 +114,111 @@ async function main() {
99
114
  update
100
115
  });
101
116
  }
117
+ function serverArg(args) {
118
+ let s = argVal(args, "--server") || process.env.ACCELR8_SERVER || DEFAULT_SERVER;
119
+ if (!/^[a-z]+:\/\//i.test(s)) s = "ws://" + s;
120
+ return s;
121
+ }
122
+ async function runAuthCommand(cmd, args) {
123
+ const server = serverArg(args);
124
+ try {
125
+ new URL(server);
126
+ } catch {
127
+ console.error(`invalid server url: ${server}`);
128
+ return 1;
129
+ }
130
+ const auth = await import("./chunks/authclient-AUBBFWCI.js");
131
+ const { credentialsKey } = await import("./chunks/accounts-CCKXHSQQ.js");
132
+ const where = credentialsKey(server);
133
+ if (cmd === "whoami") {
134
+ const me = await auth.whoami(server);
135
+ if (!me) {
136
+ console.log(`Not signed in to ${where}. Sign in with: accelr8 login`);
137
+ return 1;
138
+ }
139
+ console.log(`${me.user.name} (${me.user.id}) on ${where}`);
140
+ if (me.machine) console.log(`this machine: ${me.machine}, signed in ${new Date(me.since ?? 0).toLocaleString()}`);
141
+ if (!me.verified) console.log(`(could not check with the server: ${me.note ?? "no answer"})`);
142
+ return 0;
143
+ }
144
+ if (cmd === "logout") {
145
+ const r = await auth.logout(server, { all: args.includes("--all") });
146
+ if (!r.wasSignedIn) {
147
+ console.log(`Not signed in to ${where}.`);
148
+ return 0;
149
+ }
150
+ if (r.revoked === null) console.log(`Signed out here. The server could not be reached, so its copy of this sign-in ends when it expires.`);
151
+ else if (args.includes("--all")) console.log(`Signed ${r.user?.name} out of ${r.revoked} machine${r.revoked === 1 ? "" : "s"} on ${where}.`);
152
+ else console.log(`Signed ${r.user?.name} out of ${where} on this machine.`);
153
+ return 0;
154
+ }
155
+ if (cmd === "approve") return approveCommand(server, args[1]);
156
+ const existing = await auth.whoami(server);
157
+ if (existing?.verified) {
158
+ console.log(`Already signed in to ${where} as ${existing.user.name}. To switch accounts, run accelr8 logout first.`);
159
+ return 0;
160
+ }
161
+ try {
162
+ const creds = await auth.deviceLogin(server, { name: argVal(args, "--name") });
163
+ console.log(` Signed in to ${where} as ${creds.user.name}.`);
164
+ return 0;
165
+ } catch (e) {
166
+ console.error(` ${e instanceof Error ? e.message : e}`);
167
+ return 1;
168
+ }
169
+ }
170
+ async function approveCommand(server, code) {
171
+ const auth = await import("./chunks/authclient-AUBBFWCI.js");
172
+ const { ago, platformName } = await import("./chunks/authpage-X2AKCMVH.js");
173
+ if (!code) {
174
+ console.error("Usage: accelr8 approve <CODE> (the code the other machine shows)");
175
+ return 1;
176
+ }
177
+ let p;
178
+ try {
179
+ p = await auth.pendingSignIn(server, code);
180
+ } catch (e) {
181
+ console.error(e instanceof Error ? e.message : e);
182
+ return 1;
183
+ }
184
+ const me = await auth.whoami(server);
185
+ console.log("");
186
+ console.log(" A terminal is asking to sign in to accelr8. The name and system are what it");
187
+ console.log(" says about itself, so anyone can make them match your machine.");
188
+ console.log(` Says it is ${p.machine}`);
189
+ console.log(` Says it runs ${platformName(p.platform)}`);
190
+ console.log(` Asked ${ago(p.ageMs)}`);
191
+ if (p.ip) console.log(` From ${p.ip}`);
192
+ console.log(` Code ${p.userCode}`);
193
+ console.log("");
194
+ if (p.sameNetwork === false) {
195
+ console.log(" This code was asked for from a different network than this machine.");
196
+ console.log(" If someone sent you this code, do not approve it.");
197
+ console.log("");
198
+ }
199
+ console.log(" Only approve if you ran accelr8 login yourself, in the last few minutes.");
200
+ console.log(` Approving lets that terminal use your account${me ? ` (${me.user.name})` : ""}: your projects, your chat and your agents.`);
201
+ console.log(" If someone else gave you this code or asked you to type it, do not approve it.");
202
+ console.log("");
203
+ if (!process.stdin.isTTY) {
204
+ console.error(" Not approved: run this in a terminal so you can confirm it.");
205
+ return 1;
206
+ }
207
+ const { createInterface } = await import("node:readline/promises");
208
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
209
+ const answer = (await rl.question(" Type yes to approve: ")).trim().toLowerCase();
210
+ rl.close();
211
+ if (answer !== "yes") {
212
+ console.log(" Not approved. Nothing changed.");
213
+ return 1;
214
+ }
215
+ try {
216
+ const user = await auth.approveSignIn(server, code);
217
+ console.log(` Approved. The terminal that calls itself ${p.machine} is now signed in as ${user.name}.`);
218
+ return 0;
219
+ } catch (e) {
220
+ console.error(` ${e instanceof Error ? e.message : e}`);
221
+ return 1;
222
+ }
223
+ }
102
224
  main();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "accelr8",
3
- "version": "3.8.2",
4
- "description": "Collaborative ascii canvas in your terminal. Real multiplayer, live cursors, mouse-driven TUI.",
3
+ "version": "4.0.0-next.1",
4
+ "description": "A multiplayer room in your terminal: chat, a shared ASCII canvas with live cursors, voice, AI agents and a radio station.",
5
5
  "type": "module",
6
6
  "license": "ISC",
7
7
  "author": "Pat Santiago",
@@ -35,15 +35,15 @@
35
35
  "serve": "tsx src/cli.ts serve",
36
36
  "smoketest": "tsx src/smoketest.ts",
37
37
  "rendertest": "tsx src/rendertest.ts",
38
- "typecheck": "tsc --noEmit",
38
+ "typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p worker/tsconfig.json",
39
39
  "prepublishOnly": "npm run build",
40
40
  "coretest": "tsx src/coretest.ts",
41
41
  "orbtest": "tsx src/orbtest.ts",
42
42
  "chattest": "tsx src/chattest.ts",
43
43
  "inputtest": "tsx src/inputtest.ts",
44
44
  "torturetest": "tsx src/torturetest.ts",
45
- "test": "for t in core chat input ladder boot orb crypto accounts executor agentcontext map voice voiceroom glyph materialize update install geo map contrast; do tsx src/${t}test.ts || exit 1; done && for t in station asciivideo audio funfacts launch overlay visualizers showrunner scenessweep; do tsx src/radio/${t}test.ts || exit 1; done && tsx src/rendertest.ts > /dev/null && echo RENDER OK",
46
- "teste2e": "tsx src/voicetransporttest.ts && tsx src/chaintest.ts",
45
+ "test": "for t in core home chat input ladder boot orb crypto accounts auth olderserver roomsecurity authe2e executor agentcontext map voice voiceroom glyph materialize update install geo contrast docs; do tsx src/${t}test.ts || exit 1; done && for t in station asciivideo audio funfacts launch overlay visualizers showrunner scenessweep; do tsx src/radio/${t}test.ts || exit 1; done && tsx src/rendertest.ts > /dev/null && echo RENDER OK",
46
+ "teste2e": "tsx src/smoketest.ts && tsx src/agenttest.ts && tsx src/chaintest.ts && tsx src/voicetransporttest.ts && tsx src/reconnecttest.ts && tsx src/torturetest.ts",
47
47
  "laddertest": "tsx src/laddertest.ts",
48
48
  "boottest": "tsx src/boottest.ts",
49
49
  "reconnecttest": "tsx src/reconnecttest.ts",
@@ -52,6 +52,9 @@
52
52
  "voicetest": "tsx src/voicetest.ts",
53
53
  "cryptotest": "tsx src/cryptotest.ts",
54
54
  "accountstest": "tsx src/accountstest.ts",
55
+ "authtest": "tsx src/authtest.ts",
56
+ "roomsecuritytest": "tsx src/roomsecuritytest.ts",
57
+ "authe2etest": "tsx src/authe2etest.ts",
55
58
  "executortest": "tsx src/executortest.ts",
56
59
  "agentcontexttest": "tsx src/agentcontexttest.ts",
57
60
  "chaintest": "tsx src/chaintest.ts",
@@ -60,14 +63,14 @@
60
63
  },
61
64
  "dependencies": {
62
65
  "terminal-kit": "3.1.2",
63
- "ws": "8.18.0"
66
+ "ws": "8.22.1"
64
67
  },
65
68
  "devDependencies": {
66
69
  "@types/node": "22.10.2",
67
70
  "@types/terminal-kit": "2.5.7",
68
71
  "@types/ws": "8.5.13",
69
72
  "esbuild": "0.28.1",
70
- "tsx": "4.19.2",
73
+ "tsx": "4.23.15",
71
74
  "typescript": "5.7.2"
72
75
  }
73
76
  }
@@ -1,64 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/protocol.ts
4
- var VERSION = true ? "3.8.2" : "3.8.2";
5
- var PROTO = 3;
6
- var PORT = 8787;
7
- var DEFAULT_SERVER = "wss://accelr8-canvas.patonchain.workers.dev";
8
- var CANVAS_W = 100;
9
- var CANVAS_H = 30;
10
- var LIMITS = {
11
- chatMaxCp: 4e3,
12
- // code points per chat message
13
- chatMaxJson: 8192,
14
- // serialized bytes per chat message
15
- chatPerWindow: 10,
16
- // messages per 10s per session
17
- chatTail: 50,
18
- // messages delivered in `joined`
19
- chatPage: 50,
20
- // default history page (max 100)
21
- chatSyncMaxGap: 200,
22
- // bigger gap -> full resync
23
- chatRetain: 2e3,
24
- // messages kept per project
25
- draftMinIntervalMs: 100,
26
- // server drops faster arrivals
27
- draftMaxCp: 4e3,
28
- framesPerWindow: 200,
29
- // frames per 10s per session -> disconnect
30
- invokesPerProjectMin: 20,
31
- triggersPerUserMin: 6,
32
- joinGuessesPerMin: 5,
33
- revealsPerMin: 10,
34
- secretsPerProject: 50,
35
- secretValueMax: 8192,
36
- agentQueueCap: 5,
37
- agentMaxDepth: 3,
38
- agentBudgetPerTrigger: 5,
39
- agentRepeatPerChain: 2,
40
- voiceFramesPerSec: 50,
41
- // ~25/s real, 2x headroom; exempt from framesPerWindow
42
- voiceFrameMaxB64: 4096
43
- // a 40ms s16le mono chunk is ~1.7KB base64; cap slack
44
- };
45
- var DEFAULT_ACCESS = { mode: "owner", users: [] };
46
- function encode(msg) {
47
- return JSON.stringify(msg);
48
- }
49
- function decode(data) {
50
- return JSON.parse(data);
51
- }
52
-
53
- export {
54
- VERSION,
55
- PROTO,
56
- PORT,
57
- DEFAULT_SERVER,
58
- CANVAS_W,
59
- CANVAS_H,
60
- LIMITS,
61
- DEFAULT_ACCESS,
62
- encode,
63
- decode
64
- };