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 +118 -0
- package/dist/chunks/agentcli-ZI2O7ID7.js +176 -0
- package/dist/chunks/chunk-3JNUWI22.js +563 -0
- package/dist/chunks/chunk-SF6NNSYZ.js +60 -0
- package/dist/chunks/client-7BVK2DEW.js +3454 -0
- package/dist/chunks/server-ZP2DV4IO.js +930 -0
- package/dist/chunks/update-UEPNGB6Y.js +106 -0
- package/dist/cli.js +88 -0
- package/package.json +63 -12
- package/ascii.txt +0 -11
- package/bin/index.js +0 -314
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
|
+
};
|