accelr8 1.0.0 → 3.0.0

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 ADDED
@@ -0,0 +1,118 @@
1
+ # accelr8
2
+
3
+ Collaborative ascii canvas in your terminal. A real CLI app: mouse-driven TUI,
4
+ real multiplayer, and live cursors that glide as your friends move them.
5
+
6
+ ```
7
+ ('-. ('-. _ .-')
8
+ ( OO ).-. _( OO) ( \( -O )
9
+ / . --. / .-----. .-----. (,------. ,--. ,------. .-----.
10
+ | \-. \ ' .--./ ' .--./ | .---' | |.-') | /`. ' / .-. \
11
+ .-'-' | | | |('-. | |('-. | | | | OO ) | / | | | \_.' /
12
+ \| |_.' | /_) |OO ) /_) |OO ) (| '--. | |`-' | | |_.' | / .-. '.
13
+ | .-. | || |`-'| || |`-'| | .--' (| '---.' | . '.' | | | |
14
+ | | | | (_' '--'\ (_' '--'\ | `---. | | | |\ \ \ '-' /
15
+ `--' `--' `-----' `-----' `------' `------' `--' '--' `----''
16
+ ```
17
+
18
+ ## Install
19
+
20
+ ```bash
21
+ npm install -g accelr8
22
+ ```
23
+
24
+ ## Use
25
+
26
+ ```bash
27
+ accelr8 # open the app
28
+ accelr8 join XYMRAU # jump straight into a friend's project
29
+ accelr8 --name pat # pick your display name (remembered)
30
+ ```
31
+
32
+ Create a project in the **Projects** tab, paint on the **Canvas** by clicking
33
+ and dragging (right-click erases), and grab the join code from the **Members**
34
+ tab. Everyone in a project sees everyone else's cursor move live.
35
+
36
+ ```
37
+ accelr8 collaborative ascii canvas you ●
38
+ Projects Canvas Members ● live
39
+ ────────────────────────────────────────────────────────────────────────
40
+ brush █ ▓ ▒ ░ ● ◆ ★ ▲ ■ · # @ * ⌫ erase clear
41
+ ┌─ canvas 100×30 ───────────────────────────────────────────────────────┐
42
+ │ █ │
43
+ │ █ ▲ alice │
44
+ │ █ │
45
+ │ ▲ bob │
46
+ └────────────────────────────────────────────────────────────────────────┘
47
+ landing page redesign · code XYMRAU · 3 online [tab] switch [q] quit
48
+ ```
49
+
50
+ ### Controls
51
+
52
+ | Input | Action |
53
+ |---|---|
54
+ | Mouse click | tabs, buttons, project rows, palette |
55
+ | Click + drag on canvas | paint with the current brush |
56
+ | Right-click on canvas | erase a cell |
57
+ | `Tab` / `1` `2` `3` | switch tabs |
58
+ | `q` / `Ctrl-C` | quit |
59
+
60
+ ## How the live cursors work
61
+
62
+ The terminal reports mouse motion via ANSI escape sequences (any-event
63
+ tracking, `ESC[?1003h`). The app:
64
+
65
+ 1. **Throttles** outgoing cursor positions to ~25/sec.
66
+ 2. **Interpolates** on the receiving side: each remote cursor lerps toward its
67
+ last reported position every frame (`x += (target - x) * 0.35`), so it
68
+ glides smoothly instead of teleporting between throttled updates.
69
+
70
+ Sparse sends plus client-side smoothing is what makes it feel live.
71
+
72
+ ## Sync server
73
+
74
+ By default the CLI talks to the hosted sync server (a Cloudflare Worker with a
75
+ Durable Object, source in `worker/`). You can point it anywhere:
76
+
77
+ ```bash
78
+ accelr8 --server ws://localhost:8787 # or export ACCELR8_SERVER=...
79
+ ```
80
+
81
+ ### Self-hosting
82
+
83
+ ```bash
84
+ accelr8 serve # starts a sync server on :8787
85
+ accelr8 serve --port 9000 # custom port
86
+ ```
87
+
88
+ Expose it beyond your machine with a quick tunnel:
89
+
90
+ ```bash
91
+ cloudflared tunnel --url http://localhost:8787
92
+ # friends then run: accelr8 --server wss://something.trycloudflare.com
93
+ ```
94
+
95
+ Deploy your own copy of the hosted worker:
96
+
97
+ ```bash
98
+ cd worker && npx wrangler deploy
99
+ ```
100
+
101
+ ## Development
102
+
103
+ ```bash
104
+ npm install
105
+ npm run dev # run the TUI from source
106
+ npm run serve # run the sync server from source
107
+ npm run smoketest # headless two-client sync test (needs a server)
108
+ npm run rendertest # headless frame dumps of every screen
109
+ npm run build # bundle dist/cli.js
110
+ ```
111
+
112
+ Layout:
113
+
114
+ - `src/protocol.ts` shared wire types + constants (fixed 100×30 logical canvas)
115
+ - `src/cli.ts` entry point, arg parsing
116
+ - `src/client.ts` the TUI: mouse/keyboard, flicker-free rendering, cursor lerp
117
+ - `src/server.ts` self-host sync server (Node, `ws`)
118
+ - `worker/` hosted sync server (Cloudflare Worker + Durable Object)
@@ -0,0 +1,176 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ DEFAULT_MODEL,
4
+ addAccount,
5
+ defaultHandle,
6
+ detectClaudeCode,
7
+ detectEnvKeys,
8
+ loadAccounts,
9
+ removeAccount,
10
+ verifyAccount
11
+ } from "./chunk-3JNUWI22.js";
12
+
13
+ // src/agentcli.ts
14
+ import { createInterface } from "node:readline";
15
+ function ask(q, opts) {
16
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
17
+ if (opts?.hidden) {
18
+ const out = process.stdout;
19
+ rl._writeToOutput = (s) => {
20
+ if (s.includes("\n")) out.write("\n");
21
+ else out.write("*");
22
+ };
23
+ }
24
+ return new Promise((res) => rl.question(q, (a) => {
25
+ rl.close();
26
+ res(a.trim());
27
+ }));
28
+ }
29
+ function select(title, choices) {
30
+ const out = process.stdout;
31
+ const stdin = process.stdin;
32
+ if (!stdin.isTTY) return Promise.resolve(choices[0].value);
33
+ return new Promise((resolve) => {
34
+ let idx = 0;
35
+ out.write(title + "\n");
36
+ const draw = (first) => {
37
+ if (!first) out.write(`\x1B[${choices.length}A`);
38
+ out.write("\x1B[J");
39
+ for (let i = 0; i < choices.length; i++) {
40
+ const c = choices[i];
41
+ const hint = c.hint ? ` \x1B[2m${c.hint}\x1B[0m` : "";
42
+ if (i === idx) out.write(`\x1B[36m\u276F ${c.label}\x1B[0m${hint}
43
+ `);
44
+ else out.write(` ${c.label}${hint}
45
+ `);
46
+ }
47
+ };
48
+ draw(true);
49
+ stdin.setRawMode(true);
50
+ stdin.resume();
51
+ const onData = (buf) => {
52
+ const s = buf.toString();
53
+ if (s === "\x1B[A" || s === "k") {
54
+ idx = (idx - 1 + choices.length) % choices.length;
55
+ draw(false);
56
+ } else if (s === "\x1B[B" || s === "j") {
57
+ idx = (idx + 1) % choices.length;
58
+ draw(false);
59
+ } else if (s === "\r" || s === "\n") {
60
+ done();
61
+ resolve(choices[idx].value);
62
+ } else if (s === "") {
63
+ done();
64
+ process.exit(130);
65
+ }
66
+ };
67
+ const done = () => {
68
+ stdin.removeListener("data", onData);
69
+ stdin.setRawMode(false);
70
+ };
71
+ stdin.on("data", onData);
72
+ });
73
+ }
74
+ var MODEL_CHOICES = {
75
+ anthropic: [
76
+ { label: "claude-opus-4-8", value: "claude-opus-4-8", hint: "most capable" },
77
+ { label: "claude-sonnet-5", value: "claude-sonnet-5", hint: "balanced" },
78
+ { label: "claude-haiku-4-5", value: "claude-haiku-4-5", hint: "fastest, cheapest" },
79
+ { label: "claude-fable-5", value: "claude-fable-5", hint: "most capable, premium" },
80
+ { label: "other\u2026", value: "__other__", hint: "type a model id" }
81
+ ],
82
+ openai: [
83
+ { label: "gpt-4o", value: "gpt-4o", hint: "flagship" },
84
+ { label: "gpt-4o-mini", value: "gpt-4o-mini", hint: "fast, cheap" },
85
+ { label: "o1", value: "o1", hint: "reasoning" },
86
+ { label: "other\u2026", value: "__other__", hint: "type a model id" }
87
+ ]
88
+ };
89
+ async function runAgentCli(args) {
90
+ const cmd = args[0] || "list";
91
+ if (cmd === "list") {
92
+ const accts = loadAccounts();
93
+ if (!accts.length) {
94
+ console.log("no agents connected. add one with: accelr8 agent add");
95
+ return;
96
+ }
97
+ console.log("connected agents (this machine):");
98
+ for (const a of accts) console.log(` \u2726${a.handle} ${a.provider} \xB7 ${a.model} ${a.auth.kind === "api_key" ? "(api key)" : "(claude login)"}`);
99
+ return;
100
+ }
101
+ if (cmd === "remove") {
102
+ const handle = args[1];
103
+ if (!handle) {
104
+ console.error("usage: accelr8 agent remove <handle>");
105
+ process.exit(1);
106
+ }
107
+ const acct = loadAccounts().find((a) => a.handle === handle);
108
+ if (!acct) {
109
+ console.error(`no agent named @${handle}`);
110
+ process.exit(1);
111
+ }
112
+ removeAccount(acct.id);
113
+ console.log(`removed \u2726${handle}`);
114
+ return;
115
+ }
116
+ if (cmd === "add") {
117
+ console.log("connect an AI account. it becomes a named agent anyone in your projects can @mention.");
118
+ console.log("your api key never leaves this machine.\n");
119
+ const cc = detectClaudeCode();
120
+ const env = detectEnvKeys();
121
+ const provider = await select("provider:", [
122
+ { label: "Anthropic (Claude)", value: "anthropic" },
123
+ { label: "OpenAI", value: "openai" }
124
+ ]);
125
+ let auth;
126
+ if (provider === "anthropic" && cc.available) {
127
+ const how = await select("\nhow should this agent authenticate?", [
128
+ { label: "Use my Claude Code login", value: "login", hint: "runs on your Claude subscription" },
129
+ { label: "Paste an API key", value: "key", hint: "billed per token via the console" }
130
+ ]);
131
+ if (how === "login") auth = { kind: "claude_code_login" };
132
+ else auth = { kind: "api_key", apiKey: await ask("\npaste your anthropic api key: ", { hidden: true }) };
133
+ } else {
134
+ const envKey = provider === "anthropic" ? env.anthropic : env.openai;
135
+ const key = envKey || await ask(`
136
+ paste your ${provider} api key: `, { hidden: true });
137
+ if (envKey) console.log("using key from environment.");
138
+ auth = { kind: "api_key", apiKey: key };
139
+ }
140
+ const suggested = defaultHandle(process.env.USER || "my", provider);
141
+ const handle = await ask(`
142
+ name your agent (${suggested}): `) || suggested;
143
+ let model;
144
+ if (auth.kind === "claude_code_login") {
145
+ model = DEFAULT_MODEL.anthropic;
146
+ console.log("model: follows your Claude Code setup");
147
+ } else {
148
+ const picked = await select(`
149
+ model for @${handle}:`, MODEL_CHOICES[provider]);
150
+ model = picked === "__other__" ? await ask("model id: ") || DEFAULT_MODEL[provider] : picked;
151
+ }
152
+ const mode = await select("\nhow autonomous should it be?", [
153
+ { label: "approve", value: "approve", hint: "ask before each file write / command" },
154
+ { label: "plan", value: "plan", hint: "propose only, never acts" },
155
+ { label: "auto", value: "auto", hint: "run freely (trusted rooms only)" }
156
+ ]);
157
+ let acct;
158
+ try {
159
+ acct = addAccount({ provider, auth, handle, model, mode });
160
+ } catch (e) {
161
+ console.error("could not add agent:", e instanceof Error ? e.message : e);
162
+ process.exit(1);
163
+ }
164
+ process.stdout.write("verifying... ");
165
+ const v = await verifyAccount(acct);
166
+ console.log(v.ok ? "ok" : `warning: ${v.message}`);
167
+ console.log(`
168
+ \u2726${handle} is ready. open a project and it joins automatically.`);
169
+ return;
170
+ }
171
+ console.error("usage: accelr8 agent [list|add|remove <handle>]");
172
+ process.exit(1);
173
+ }
174
+ export {
175
+ runAgentCli
176
+ };