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.
@@ -0,0 +1,306 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ accelr8Dir
4
+ } from "./chunk-62O2F73J.js";
5
+
6
+ // src/accounts.ts
7
+ import fs from "node:fs";
8
+ import path from "node:path";
9
+ var HANDLE_RE = /^[a-z0-9][a-z0-9-]{2,19}$/;
10
+ var DEFAULT_MODEL = {
11
+ anthropic: "claude-opus-4-8",
12
+ openai: "gpt-4o"
13
+ };
14
+ var DEFAULT_MAX_CALLS_PER_HOUR = 30;
15
+ var DEFAULT_MAX_CALLS_PER_REQUESTER_HOUR = 10;
16
+ var DEFAULT_MAX_OUTPUT_TOKENS = 1024;
17
+ var ACCOUNTS_FILE = "accounts.json";
18
+ var TOKENS_FILE = "agent-tokens.json";
19
+ function dirOf(baseDir) {
20
+ return baseDir ?? accelr8Dir();
21
+ }
22
+ function ensureDir(dir) {
23
+ fs.mkdirSync(dir, { recursive: true, mode: 448 });
24
+ try {
25
+ fs.chmodSync(dir, 448);
26
+ } catch {
27
+ }
28
+ }
29
+ function readJson(file) {
30
+ let raw;
31
+ try {
32
+ raw = fs.readFileSync(file, "utf8");
33
+ } catch {
34
+ return void 0;
35
+ }
36
+ try {
37
+ if ((fs.statSync(file).mode & 63) !== 0) {
38
+ try {
39
+ fs.chmodSync(file, 384);
40
+ } catch {
41
+ }
42
+ if ((fs.statSync(file).mode & 63) !== 0) {
43
+ process.stderr.write(`accelr8: refusing to load ${file}: it is readable by others and could not be locked to 600. run: chmod 600 ${file}
44
+ `);
45
+ return void 0;
46
+ }
47
+ }
48
+ } catch {
49
+ }
50
+ try {
51
+ return JSON.parse(raw);
52
+ } catch {
53
+ return void 0;
54
+ }
55
+ }
56
+ function writeJson(file, value) {
57
+ ensureDir(path.dirname(file));
58
+ const tmp = `${file}.tmp-${process.pid}-${Math.random().toString(36).slice(2, 8)}`;
59
+ fs.writeFileSync(tmp, JSON.stringify(value, null, 2) + "\n", { mode: 384 });
60
+ fs.chmodSync(tmp, 384);
61
+ fs.renameSync(tmp, file);
62
+ fs.chmodSync(file, 384);
63
+ }
64
+ function isAccount(v) {
65
+ if (!v || typeof v !== "object") return false;
66
+ const a = v;
67
+ const auth = a.auth;
68
+ return typeof a.id === "string" && (a.provider === "anthropic" || a.provider === "openai") && !!auth && typeof auth === "object" && (auth.kind === "api_key" ? typeof auth.apiKey === "string" : auth.kind === "claude_code_login") && typeof a.model === "string" && typeof a.handle === "string" && typeof a.maxCallsPerHour === "number" && typeof a.maxCallsPerRequesterHour === "number" && typeof a.maxOutputTokens === "number" && typeof a.createdAt === "number";
69
+ }
70
+ function loadAccounts(baseDir) {
71
+ const data = readJson(path.join(dirOf(baseDir), ACCOUNTS_FILE));
72
+ if (!Array.isArray(data)) return [];
73
+ return data.filter(isAccount).map((a) => ({ ...a, mode: a.mode ?? "approve" }));
74
+ }
75
+ function saveAccounts(list, baseDir) {
76
+ writeJson(path.join(dirOf(baseDir), ACCOUNTS_FILE), list);
77
+ }
78
+ function addAccount(partial, baseDir) {
79
+ if (!HANDLE_RE.test(partial.handle)) {
80
+ throw new Error(`invalid handle '${partial.handle}' (want 3-20 chars of a-z 0-9 -, starting alphanumeric)`);
81
+ }
82
+ if (partial.auth.kind === "api_key" && !partial.auth.apiKey) {
83
+ throw new Error("apiKey must not be empty");
84
+ }
85
+ const account = {
86
+ id: "acct_" + Math.random().toString(36).slice(2, 10) + Math.random().toString(36).slice(2, 6),
87
+ provider: partial.provider,
88
+ auth: partial.auth,
89
+ model: partial.model || DEFAULT_MODEL[partial.provider],
90
+ handle: partial.handle,
91
+ mode: partial.mode ?? "approve",
92
+ maxCallsPerHour: partial.maxCallsPerHour ?? DEFAULT_MAX_CALLS_PER_HOUR,
93
+ maxCallsPerRequesterHour: partial.maxCallsPerRequesterHour ?? DEFAULT_MAX_CALLS_PER_REQUESTER_HOUR,
94
+ maxOutputTokens: partial.maxOutputTokens ?? DEFAULT_MAX_OUTPUT_TOKENS,
95
+ createdAt: Date.now()
96
+ };
97
+ const list = loadAccounts(baseDir);
98
+ list.push(account);
99
+ saveAccounts(list, baseDir);
100
+ return account;
101
+ }
102
+ function removeAccount(id, baseDir) {
103
+ const list = loadAccounts(baseDir);
104
+ const next = list.filter((a) => a.id !== id);
105
+ if (next.length === list.length) return false;
106
+ saveAccounts(next, baseDir);
107
+ return true;
108
+ }
109
+ var tokenKey = (projectId, agentId) => `${projectId}:${agentId}`;
110
+ function loadTokens(baseDir) {
111
+ const data = readJson(path.join(dirOf(baseDir), TOKENS_FILE));
112
+ if (!data || typeof data !== "object" || Array.isArray(data)) return {};
113
+ const out = {};
114
+ for (const [k, v] of Object.entries(data)) if (typeof v === "string") out[k] = v;
115
+ return out;
116
+ }
117
+ function saveToken(projectId, agentId, token, baseDir) {
118
+ const tokens = loadTokens(baseDir);
119
+ tokens[tokenKey(projectId, agentId)] = token;
120
+ writeJson(path.join(dirOf(baseDir), TOKENS_FILE), tokens);
121
+ }
122
+ function getToken(projectId, agentId, baseDir) {
123
+ return loadTokens(baseDir)[tokenKey(projectId, agentId)];
124
+ }
125
+ var AUTH_FILE = "auth.json";
126
+ var IDENTITY_FILE = "identity.json";
127
+ function credentialsKey(serverUrl) {
128
+ let raw = serverUrl.trim();
129
+ if (!/^[a-z]+:\/\//i.test(raw)) raw = "ws://" + raw;
130
+ const u = new URL(raw);
131
+ const scheme = u.protocol === "https:" || u.protocol === "wss:" ? "wss" : "ws";
132
+ const port = u.port && !(scheme === "wss" && u.port === "443" || scheme === "ws" && u.port === "80") ? ":" + u.port : "";
133
+ return `${scheme}://${u.hostname.toLowerCase()}${port}`;
134
+ }
135
+ function isCredentials(v) {
136
+ const c = v;
137
+ return !!c && typeof c === "object" && typeof c.server === "string" && typeof c.accessToken === "string" && typeof c.refreshToken === "string" && typeof c.accessExpiresAt === "number" && !!c.user && typeof c.user.id === "string" && typeof c.user.name === "string";
138
+ }
139
+ function loadAuthFile(baseDir) {
140
+ const data = readJson(path.join(dirOf(baseDir), AUTH_FILE));
141
+ const out = {};
142
+ for (const [k, v] of Object.entries(data?.servers ?? {})) if (isCredentials(v)) out[k] = v;
143
+ return out;
144
+ }
145
+ function loadCredentials(serverUrl, baseDir) {
146
+ return loadAuthFile(baseDir)[credentialsKey(serverUrl)];
147
+ }
148
+ function listCredentials(baseDir) {
149
+ return Object.values(loadAuthFile(baseDir));
150
+ }
151
+ function saveCredentials(creds, baseDir) {
152
+ const servers = loadAuthFile(baseDir);
153
+ servers[creds.server] = creds;
154
+ writeJson(path.join(dirOf(baseDir), AUTH_FILE), { v: 1, servers });
155
+ }
156
+ function deleteCredentials(serverUrl, baseDir, onlyIf) {
157
+ const servers = loadAuthFile(baseDir);
158
+ const key = credentialsKey(serverUrl);
159
+ if (!servers[key]) return false;
160
+ if (onlyIf && !onlyIf(servers[key])) return false;
161
+ delete servers[key];
162
+ writeJson(path.join(dirOf(baseDir), AUTH_FILE), { v: 1, servers });
163
+ return true;
164
+ }
165
+ function loadLegacyIdentity(baseDir) {
166
+ try {
167
+ const v = JSON.parse(fs.readFileSync(path.join(dirOf(baseDir), IDENTITY_FILE), "utf8"));
168
+ return {
169
+ id: typeof v.id === "string" ? v.id : void 0,
170
+ name: typeof v.name === "string" ? v.name : void 0
171
+ };
172
+ } catch {
173
+ return {};
174
+ }
175
+ }
176
+ var AuthLockTimeout = class extends Error {
177
+ };
178
+ async function withAuthLock(fn, baseDir, opts = {}) {
179
+ const timeoutMs = opts.timeoutMs ?? 2e4;
180
+ const staleMs = opts.staleMs ?? 3e4;
181
+ const heartbeatMs = opts.heartbeatMs ?? 5e3;
182
+ const dir = dirOf(baseDir);
183
+ ensureDir(dir);
184
+ const lock = path.join(dir, "auth.lock");
185
+ const nonce = `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
186
+ const readLock = () => {
187
+ try {
188
+ return fs.readFileSync(lock, "utf8");
189
+ } catch {
190
+ return null;
191
+ }
192
+ };
193
+ const deadline = Date.now() + timeoutMs;
194
+ for (; ; ) {
195
+ try {
196
+ const fd = fs.openSync(lock, "wx", 384);
197
+ try {
198
+ fs.writeSync(fd, nonce);
199
+ } finally {
200
+ fs.closeSync(fd);
201
+ }
202
+ break;
203
+ } catch {
204
+ try {
205
+ const st = fs.statSync(lock);
206
+ if (Date.now() - st.mtimeMs > staleMs) {
207
+ const seen = readLock();
208
+ if (seen !== null && fs.statSync(lock).mtimeMs === st.mtimeMs && readLock() === seen) fs.unlinkSync(lock);
209
+ continue;
210
+ }
211
+ } catch {
212
+ continue;
213
+ }
214
+ if (Date.now() > deadline) throw new AuthLockTimeout("another accelr8 process is holding the sign-in lock");
215
+ await new Promise((r) => setTimeout(r, 25));
216
+ }
217
+ }
218
+ const beat = setInterval(() => {
219
+ try {
220
+ if (readLock() === nonce) {
221
+ const t = /* @__PURE__ */ new Date();
222
+ fs.utimesSync(lock, t, t);
223
+ }
224
+ } catch {
225
+ }
226
+ }, heartbeatMs);
227
+ beat.unref?.();
228
+ try {
229
+ return await fn();
230
+ } finally {
231
+ clearInterval(beat);
232
+ try {
233
+ if (readLock() === nonce) fs.unlinkSync(lock);
234
+ } catch {
235
+ }
236
+ }
237
+ }
238
+ function redact(obj) {
239
+ return redactInner(obj, /* @__PURE__ */ new WeakSet());
240
+ }
241
+ var SECRET_KEYS = /* @__PURE__ */ new Set(["apiKey", "accessToken", "refreshToken", "deviceCode"]);
242
+ function redactInner(v, seen) {
243
+ if (v === null || typeof v !== "object") return v;
244
+ if (seen.has(v)) return "[circular]";
245
+ seen.add(v);
246
+ if (Array.isArray(v)) return v.map((x) => redactInner(x, seen));
247
+ const out = {};
248
+ for (const [k, val] of Object.entries(v)) {
249
+ out[k] = SECRET_KEYS.has(k) && typeof val === "string" ? "***" : redactInner(val, seen);
250
+ }
251
+ return out;
252
+ }
253
+ function detectClaudeCode() {
254
+ const dirs = (process.env.PATH ?? "").split(path.delimiter).filter(Boolean);
255
+ for (const dir of dirs) {
256
+ const candidate = path.join(dir, "claude");
257
+ try {
258
+ if (!fs.existsSync(candidate)) continue;
259
+ fs.accessSync(candidate, fs.constants.X_OK);
260
+ if (fs.statSync(candidate).isFile()) return { available: true, path: candidate };
261
+ } catch {
262
+ }
263
+ }
264
+ return { available: false };
265
+ }
266
+ function detectEnvKeys() {
267
+ const out = {};
268
+ const a = process.env.ANTHROPIC_API_KEY?.trim();
269
+ const o = process.env.OPENAI_API_KEY?.trim();
270
+ if (a) out.anthropic = a;
271
+ if (o) out.openai = o;
272
+ return out;
273
+ }
274
+ function defaultHandle(userName, provider) {
275
+ const suffix = "s" + (provider === "anthropic" ? "claude" : "gpt");
276
+ let name = userName.toLowerCase().replace(/[^a-z0-9-]/g, "").replace(/^-+/, "");
277
+ if (!name) name = "my";
278
+ return name.slice(0, 20 - suffix.length) + suffix;
279
+ }
280
+
281
+ export {
282
+ HANDLE_RE,
283
+ DEFAULT_MODEL,
284
+ DEFAULT_MAX_CALLS_PER_HOUR,
285
+ DEFAULT_MAX_CALLS_PER_REQUESTER_HOUR,
286
+ DEFAULT_MAX_OUTPUT_TOKENS,
287
+ loadAccounts,
288
+ saveAccounts,
289
+ addAccount,
290
+ removeAccount,
291
+ loadTokens,
292
+ saveToken,
293
+ getToken,
294
+ credentialsKey,
295
+ loadCredentials,
296
+ listCredentials,
297
+ saveCredentials,
298
+ deleteCredentials,
299
+ loadLegacyIdentity,
300
+ AuthLockTimeout,
301
+ withAuthLock,
302
+ redact,
303
+ detectClaudeCode,
304
+ detectEnvKeys,
305
+ defaultHandle
306
+ };
@@ -1,149 +1,7 @@
1
1
  #!/usr/bin/env node
2
-
3
- // src/accounts.ts
4
- import fs from "node:fs";
5
- import os from "node:os";
6
- import path from "node:path";
7
- var HANDLE_RE = /^[a-z0-9][a-z0-9-]{2,19}$/;
8
- var DEFAULT_MODEL = {
9
- anthropic: "claude-opus-4-8",
10
- openai: "gpt-4o"
11
- };
12
- var DEFAULT_MAX_CALLS_PER_HOUR = 30;
13
- var DEFAULT_MAX_CALLS_PER_REQUESTER_HOUR = 10;
14
- var DEFAULT_MAX_OUTPUT_TOKENS = 1024;
15
- var ACCOUNTS_FILE = "accounts.json";
16
- var TOKENS_FILE = "agent-tokens.json";
17
- function dirOf(baseDir) {
18
- return baseDir ?? path.join(os.homedir(), ".accelr8");
19
- }
20
- function ensureDir(dir) {
21
- fs.mkdirSync(dir, { recursive: true, mode: 448 });
22
- try {
23
- fs.chmodSync(dir, 448);
24
- } catch {
25
- }
26
- }
27
- function readJson(file) {
28
- let raw;
29
- try {
30
- raw = fs.readFileSync(file, "utf8");
31
- } catch {
32
- return void 0;
33
- }
34
- try {
35
- if ((fs.statSync(file).mode & 63) !== 0) {
36
- try {
37
- fs.chmodSync(file, 384);
38
- } catch {
39
- }
40
- if ((fs.statSync(file).mode & 63) !== 0) {
41
- process.stderr.write(`accelr8: refusing to load ${file}: it is readable by others and could not be locked to 600. run: chmod 600 ${file}
42
- `);
43
- return void 0;
44
- }
45
- }
46
- } catch {
47
- }
48
- try {
49
- return JSON.parse(raw);
50
- } catch {
51
- return void 0;
52
- }
53
- }
54
- function writeJson(file, value) {
55
- ensureDir(path.dirname(file));
56
- const tmp = `${file}.tmp-${process.pid}-${Math.random().toString(36).slice(2, 8)}`;
57
- fs.writeFileSync(tmp, JSON.stringify(value, null, 2) + "\n", { mode: 384 });
58
- fs.chmodSync(tmp, 384);
59
- fs.renameSync(tmp, file);
60
- fs.chmodSync(file, 384);
61
- }
62
- function isAccount(v) {
63
- if (!v || typeof v !== "object") return false;
64
- const a = v;
65
- const auth = a.auth;
66
- return typeof a.id === "string" && (a.provider === "anthropic" || a.provider === "openai") && !!auth && typeof auth === "object" && (auth.kind === "api_key" ? typeof auth.apiKey === "string" : auth.kind === "claude_code_login") && typeof a.model === "string" && typeof a.handle === "string" && typeof a.maxCallsPerHour === "number" && typeof a.maxCallsPerRequesterHour === "number" && typeof a.maxOutputTokens === "number" && typeof a.createdAt === "number";
67
- }
68
- function loadAccounts(baseDir) {
69
- const data = readJson(path.join(dirOf(baseDir), ACCOUNTS_FILE));
70
- if (!Array.isArray(data)) return [];
71
- return data.filter(isAccount).map((a) => ({ ...a, mode: a.mode ?? "approve" }));
72
- }
73
- function saveAccounts(list, baseDir) {
74
- writeJson(path.join(dirOf(baseDir), ACCOUNTS_FILE), list);
75
- }
76
- function addAccount(partial, baseDir) {
77
- if (!HANDLE_RE.test(partial.handle)) {
78
- throw new Error(`invalid handle '${partial.handle}' (want 3-20 chars of a-z 0-9 -, starting alphanumeric)`);
79
- }
80
- if (partial.auth.kind === "api_key" && !partial.auth.apiKey) {
81
- throw new Error("apiKey must not be empty");
82
- }
83
- const account = {
84
- id: "acct_" + Math.random().toString(36).slice(2, 10) + Math.random().toString(36).slice(2, 6),
85
- provider: partial.provider,
86
- auth: partial.auth,
87
- model: partial.model || DEFAULT_MODEL[partial.provider],
88
- handle: partial.handle,
89
- mode: partial.mode ?? "approve",
90
- maxCallsPerHour: partial.maxCallsPerHour ?? DEFAULT_MAX_CALLS_PER_HOUR,
91
- maxCallsPerRequesterHour: partial.maxCallsPerRequesterHour ?? DEFAULT_MAX_CALLS_PER_REQUESTER_HOUR,
92
- maxOutputTokens: partial.maxOutputTokens ?? DEFAULT_MAX_OUTPUT_TOKENS,
93
- createdAt: Date.now()
94
- };
95
- const list = loadAccounts(baseDir);
96
- list.push(account);
97
- saveAccounts(list, baseDir);
98
- return account;
99
- }
100
- function removeAccount(id, baseDir) {
101
- const list = loadAccounts(baseDir);
102
- const next = list.filter((a) => a.id !== id);
103
- if (next.length === list.length) return false;
104
- saveAccounts(next, baseDir);
105
- return true;
106
- }
107
- var tokenKey = (projectId, agentId) => `${projectId}:${agentId}`;
108
- function loadTokens(baseDir) {
109
- const data = readJson(path.join(dirOf(baseDir), TOKENS_FILE));
110
- if (!data || typeof data !== "object" || Array.isArray(data)) return {};
111
- const out = {};
112
- for (const [k, v] of Object.entries(data)) if (typeof v === "string") out[k] = v;
113
- return out;
114
- }
115
- function saveToken(projectId, agentId, token, baseDir) {
116
- const tokens = loadTokens(baseDir);
117
- tokens[tokenKey(projectId, agentId)] = token;
118
- writeJson(path.join(dirOf(baseDir), TOKENS_FILE), tokens);
119
- }
120
- function detectClaudeCode() {
121
- const dirs = (process.env.PATH ?? "").split(path.delimiter).filter(Boolean);
122
- for (const dir of dirs) {
123
- const candidate = path.join(dir, "claude");
124
- try {
125
- if (!fs.existsSync(candidate)) continue;
126
- fs.accessSync(candidate, fs.constants.X_OK);
127
- if (fs.statSync(candidate).isFile()) return { available: true, path: candidate };
128
- } catch {
129
- }
130
- }
131
- return { available: false };
132
- }
133
- function detectEnvKeys() {
134
- const out = {};
135
- const a = process.env.ANTHROPIC_API_KEY?.trim();
136
- const o = process.env.OPENAI_API_KEY?.trim();
137
- if (a) out.anthropic = a;
138
- if (o) out.openai = o;
139
- return out;
140
- }
141
- function defaultHandle(userName, provider) {
142
- const suffix = "s" + (provider === "anthropic" ? "claude" : "gpt");
143
- let name = userName.toLowerCase().replace(/[^a-z0-9-]/g, "").replace(/^-+/, "");
144
- if (!name) name = "my";
145
- return name.slice(0, 20 - suffix.length) + suffix;
146
- }
2
+ import {
3
+ detectClaudeCode
4
+ } from "./chunk-FVC2UPMV.js";
147
5
 
148
6
  // src/executor.ts
149
7
  import { spawn } from "node:child_process";
@@ -548,16 +406,6 @@ function errMsg(e) {
548
406
  }
549
407
 
550
408
  export {
551
- DEFAULT_MODEL,
552
- loadAccounts,
553
- saveAccounts,
554
- addAccount,
555
- removeAccount,
556
- loadTokens,
557
- saveToken,
558
- detectClaudeCode,
559
- detectEnvKeys,
560
- defaultHandle,
561
409
  execute,
562
410
  verifyAccount
563
411
  };
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/authpage.ts
4
+ function esc(v) {
5
+ return String(v ?? "").replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
6
+ }
7
+ function ago(ms) {
8
+ const s = Math.floor(ms / 1e3);
9
+ if (s < 5) return "just now";
10
+ if (s < 60) return `${s} seconds ago`;
11
+ const m = Math.floor(s / 60);
12
+ return m === 1 ? "1 minute ago" : `${m} minutes ago`;
13
+ }
14
+ function minutes(ms) {
15
+ const n = Math.max(1, Math.ceil(ms / 6e4));
16
+ return n === 1 ? "1 minute" : `${n} minutes`;
17
+ }
18
+ function platformName(p) {
19
+ return { darwin: "macOS", win32: "Windows", linux: "Linux", freebsd: "FreeBSD", openbsd: "OpenBSD" }[p] ?? (p || "unknown");
20
+ }
21
+ var STYLE = `
22
+ :root { --bg:#f7f7f5; --fg:#1b1b1b; --dim:#5d5d5d; --card:#ffffff; --line:#d9d9d4; --accent:#2457d6; --warnbg:#fff4e5; --warnfg:#7a3d00; --danger:#a1271d; }
23
+ @media (prefers-color-scheme: dark) { :root { --bg:#121314; --fg:#ececea; --dim:#a3a3a0; --card:#1c1d1f; --line:#34363a; --accent:#7ea2ff; --warnbg:#3a2a12; --warnfg:#ffd59a; --danger:#ff8f84; } }
24
+ * { box-sizing:border-box; }
25
+ body { margin:0; background:var(--bg); color:var(--fg); font:16px/1.5 system-ui,-apple-system,Segoe UI,sans-serif; }
26
+ main { max-width:520px; margin:0 auto; padding:40px 16px 64px; }
27
+ h1 { font-size:24px; margin:0 0 8px; }
28
+ h2 { font-size:17px; margin:28px 0 8px; }
29
+ p { margin:8px 0; }
30
+ .dim { color:var(--dim); }
31
+ .card { background:var(--card); border:1px solid var(--line); border-radius:10px; padding:16px; margin:16px 0; }
32
+ dl { display:grid; grid-template-columns:auto 1fr; gap:6px 16px; margin:0; }
33
+ dt { color:var(--dim); }
34
+ dd { margin:0; font-weight:600; overflow-wrap:anywhere; }
35
+ .warn { background:var(--warnbg); color:var(--warnfg); border-radius:10px; padding:14px 16px; margin:16px 0; }
36
+ .error { color:var(--danger); font-weight:600; }
37
+ label { display:block; font-weight:600; margin:12px 0 6px; }
38
+ input[type=text] { width:100%; font:inherit; padding:10px 12px; border:1px solid var(--line); border-radius:8px; background:var(--card); color:var(--fg); }
39
+ input.code { font:600 22px/1.2 ui-monospace,Menlo,monospace; letter-spacing:3px; text-transform:uppercase; }
40
+ button { font:inherit; font-weight:600; padding:10px 16px; border-radius:8px; border:1px solid var(--accent); background:var(--accent); color:#fff; cursor:pointer; margin:12px 8px 0 0; }
41
+ button.quiet { background:transparent; color:var(--accent); }
42
+ button.deny { background:transparent; color:var(--danger); border-color:var(--danger); }
43
+ form { margin:0; }
44
+ `;
45
+ function layout(title, body) {
46
+ return `<!doctype html>
47
+ <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
48
+ <meta name="referrer" content="no-referrer"><title>${esc(title)}</title><style>${STYLE}</style></head>
49
+ <body><main>${body}</main></body></html>`;
50
+ }
51
+ function hidden(fields) {
52
+ return Object.entries(fields).map(([k, v]) => `<input type="hidden" name="${esc(k)}" value="${esc(v)}">`).join("");
53
+ }
54
+ function enterCodePage(o) {
55
+ return layout("Sign in a terminal to accelr8", `
56
+ <h1>Sign in a terminal</h1>
57
+ <p>Type the code your terminal is showing after <code>accelr8 login</code>.</p>
58
+ ${o.error ? `<p class="error" role="alert">${esc(o.error)}</p>` : ""}
59
+ ${o.note ? `<p>${esc(o.note)}</p>` : ""}
60
+ <form method="post" action="/approve">${hidden({ csrf: o.csrf, step: "code" })}
61
+ <label for="code">Code</label>
62
+ <input class="code" id="code" name="code" type="text" inputmode="text" autocomplete="off" autocapitalize="characters" spellcheck="false" maxlength="12" placeholder="XXXX-XXXX" required autofocus>
63
+ <button type="submit">Continue</button>
64
+ </form>
65
+ ${o.signedInAs ? `<div class="card"><p>This browser is signed in as <strong>${esc(o.signedInAs)}</strong>.</p>
66
+ <form method="post" action="/approve">${hidden({ csrf: o.csrf, action: "signout" })}<button class="quiet" type="submit">Sign out of this browser</button></form></div>` : ""}
67
+ `);
68
+ }
69
+ var DIFFERENT_NETWORK = "This code was asked for from a different network than this browser. If someone sent you this code, deny it.";
70
+ function confirmPage(o) {
71
+ const v = o.view;
72
+ const base = { csrf: o.csrf, step: "decide", code: v.userCode };
73
+ const legacyName = v.legacy?.name ? ` as <strong>${esc(v.legacy.name)}</strong>` : "";
74
+ const legacy = v.legacy?.willLink ? `<p>This terminal used accelr8 before${legacyName}, and proved it with an agent key it holds. The projects it was in are added to the account you approve with.</p>` : v.legacy && !v.legacy.proved ? `<p class="dim">This terminal says it used accelr8 before${legacyName}, but it holds no agent key to prove it, so its old projects are not added. To get back into one, ask someone in it for the join code.</p>` : "";
75
+ const network = v.sameNetwork === false ? `<div class="warn" role="alert"><strong>${esc(DIFFERENT_NETWORK)}</strong></div>` : "";
76
+ const asExisting = o.signedInAs ? `<form method="post" action="/approve">${hidden({ ...base, action: "approve" })}
77
+ <button type="submit">Approve and sign in as ${esc(o.signedInAs)}</button></form>
78
+ <h2>Or use a new account</h2>` : `<h2>Create your account</h2>
79
+ <p class="dim">Already use accelr8 on another machine? Do not make a second account here. On that machine, run <code>accelr8 approve ${esc(v.userCode)}</code> instead.</p>`;
80
+ return layout("Approve this sign-in? \xB7 accelr8", `
81
+ <h1>Approve this sign-in?</h1>
82
+ <p>A terminal is asking to sign in to accelr8. The name and system below are what it says about itself, so anyone can make them match your machine.</p>
83
+ <div class="card"><dl>
84
+ <dt>Says it is</dt><dd>${esc(v.machine)}</dd>
85
+ <dt>Says it runs</dt><dd>${esc(platformName(v.platform))}</dd>
86
+ <dt>Asked</dt><dd>${esc(ago(v.ageMs))}</dd>
87
+ ${v.ip ? `<dt>From</dt><dd>${esc(v.ip)}</dd>` : ""}
88
+ <dt>Code</dt><dd>${esc(v.userCode)}</dd>
89
+ </dl></div>
90
+ ${network}
91
+ <div class="warn" role="alert"><strong>Only approve if you ran accelr8 login yourself, in the last few minutes.</strong>
92
+ Approving lets that terminal use your accelr8 account: your projects, your chat and your agents.
93
+ If someone else gave you this code or asked you to type it here, deny it.</div>
94
+ ${legacy}
95
+ ${asExisting}
96
+ <form method="post" action="/approve">${hidden({ ...base, action: "create" })}
97
+ <label for="name">Your name</label>
98
+ <input id="name" name="name" type="text" maxlength="16" autocomplete="nickname" value="${esc(v.name)}" required>
99
+ <button type="submit"${o.signedInAs ? ' class="quiet"' : ""}>Create account and approve</button></form>
100
+ <form method="post" action="/approve">${hidden({ ...base, action: "deny" })}
101
+ <button class="deny" type="submit">Deny</button></form>
102
+ <p class="dim">This code stops working in ${minutes(v.expiresInMs)}.</p>
103
+ `);
104
+ }
105
+ function donePage(o) {
106
+ return layout("Signed in \xB7 accelr8", `
107
+ <h1>Signed in</h1>
108
+ <p>The terminal that calls itself <strong>${esc(o.machine)}</strong> is now signed in as <strong>${esc(o.name)}</strong>. Go back to your terminal.</p>
109
+ ${o.rememberedBrowser ? `<p class="dim">This browser now remembers you as ${esc(o.name)}, so the next sign-in is one click.</p>` : ""}
110
+ <p class="dim">You can close this tab.</p>
111
+ `);
112
+ }
113
+ function deniedPage() {
114
+ return layout("Sign-in denied \xB7 accelr8", `
115
+ <h1>Sign-in denied</h1>
116
+ <p>That terminal was not signed in. If you did not ask for this code, you do not need to do anything else.</p>
117
+ `);
118
+ }
119
+
120
+ export {
121
+ esc,
122
+ ago,
123
+ platformName,
124
+ enterCodePage,
125
+ DIFFERENT_NETWORK,
126
+ confirmPage,
127
+ donePage,
128
+ deniedPage
129
+ };