getbotons 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +505 -2
- package/installer/admin.mjs +6 -0
- package/installer/botons.mjs +430 -0
- package/installer/brain-clis.mjs +28 -0
- package/installer/linger.mjs +39 -0
- package/installer/output.mjs +27 -0
- package/installer/revision.mjs +30 -0
- package/installer/shell-path.mjs +66 -0
- package/installer/source-lock.json +2891 -0
- package/package.json +26 -4
- package/payload/LICENSE +21 -0
- package/payload/SECURITY.md +33 -0
- package/payload/installer/admin.mjs +6 -0
- package/payload/installer/botons.mjs +430 -0
- package/payload/installer/brain-clis.mjs +28 -0
- package/payload/installer/linger.mjs +39 -0
- package/payload/installer/output.mjs +27 -0
- package/payload/installer/revision.mjs +30 -0
- package/payload/installer/shell-path.mjs +66 -0
- package/payload/installer/source-lock.json +2891 -0
- package/payload/npm-shrinkwrap.json +51 -0
- package/payload/package.json +13 -0
- package/payload/scripts/backup.ts +47 -0
- package/payload/scripts/botons-backup.service +15 -0
- package/payload/scripts/botons-backup.timer +11 -0
- package/payload/scripts/botons.service +36 -0
- package/payload/scripts/check-browser.ts +90 -0
- package/payload/scripts/doctor.ts +790 -0
- package/payload/scripts/install-backup-timer.ts +52 -0
- package/payload/scripts/install-service.ts +206 -0
- package/payload/scripts/login.ts +17 -0
- package/payload/scripts/migrate-names.ts +47 -0
- package/payload/scripts/pair.ts +163 -0
- package/payload/scripts/restore.ts +108 -0
- package/payload/server/bin/botons +4 -0
- package/payload/server/bin/legacy/minion +4 -0
- package/payload/server/bin/legacy/tots +4 -0
- package/payload/server/src/acting.ts +16 -0
- package/payload/server/src/api-keys.ts +544 -0
- package/payload/server/src/api.ts +1484 -0
- package/payload/server/src/app.ts +546 -0
- package/payload/server/src/approvals.ts +873 -0
- package/payload/server/src/ask-rules.ts +181 -0
- package/payload/server/src/audit.ts +158 -0
- package/payload/server/src/auth.ts +497 -0
- package/payload/server/src/backup.ts +382 -0
- package/payload/server/src/brain-login.ts +75 -0
- package/payload/server/src/browser-cert.ts +67 -0
- package/payload/server/src/browser-handoff.ts +119 -0
- package/payload/server/src/browser-handover.ts +175 -0
- package/payload/server/src/browser-policy.ts +210 -0
- package/payload/server/src/browser-proxy.ts +580 -0
- package/payload/server/src/browser-request.ts +335 -0
- package/payload/server/src/browser-screenshot.ts +56 -0
- package/payload/server/src/browser-send.ts +155 -0
- package/payload/server/src/browser.ts +2164 -0
- package/payload/server/src/callback.ts +218 -0
- package/payload/server/src/capabilities.ts +90 -0
- package/payload/server/src/cards.ts +243 -0
- package/payload/server/src/cli/approve-mcp.ts +119 -0
- package/payload/server/src/cli/botons.ts +629 -0
- package/payload/server/src/cli/browser-mcp.ts +168 -0
- package/payload/server/src/cli/workspace-file.ts +78 -0
- package/payload/server/src/cli-homes.ts +38 -0
- package/payload/server/src/cli-install.ts +104 -0
- package/payload/server/src/cli-updates.ts +112 -0
- package/payload/server/src/codex-config-repair.ts +80 -0
- package/payload/server/src/config.ts +473 -0
- package/payload/server/src/connector-access.ts +158 -0
- package/payload/server/src/connector-launch.ts +203 -0
- package/payload/server/src/connectors.ts +947 -0
- package/payload/server/src/disk-usage.ts +62 -0
- package/payload/server/src/engine.ts +2328 -0
- package/payload/server/src/env-init.ts +5 -0
- package/payload/server/src/events.ts +46 -0
- package/payload/server/src/files.ts +528 -0
- package/payload/server/src/github-access.ts +52 -0
- package/payload/server/src/health.ts +117 -0
- package/payload/server/src/held-dir.ts +96 -0
- package/payload/server/src/hooks.ts +141 -0
- package/payload/server/src/http.ts +207 -0
- package/payload/server/src/index.ts +139 -0
- package/payload/server/src/install-type.ts +11 -0
- package/payload/server/src/instructions.ts +204 -0
- package/payload/server/src/isolation.ts +234 -0
- package/payload/server/src/legacy-upgrade.ts +668 -0
- package/payload/server/src/limits.ts +44 -0
- package/payload/server/src/lock.ts +75 -0
- package/payload/server/src/log.ts +137 -0
- package/payload/server/src/managed-clis.ts +65 -0
- package/payload/server/src/mcp-client.ts +229 -0
- package/payload/server/src/notify.ts +193 -0
- package/payload/server/src/oauth-connections.ts +650 -0
- package/payload/server/src/oauth.ts +311 -0
- package/payload/server/src/outbound.ts +142 -0
- package/payload/server/src/portable.ts +1669 -0
- package/payload/server/src/prompt.ts +676 -0
- package/payload/server/src/providers/api-brain.ts +612 -0
- package/payload/server/src/providers/api-history.ts +131 -0
- package/payload/server/src/providers/api-tools.ts +481 -0
- package/payload/server/src/providers/api-wire.ts +690 -0
- package/payload/server/src/providers/claude-cli.ts +620 -0
- package/payload/server/src/providers/cli-check.ts +265 -0
- package/payload/server/src/providers/cli-turn.ts +20 -0
- package/payload/server/src/providers/codex-cli.ts +552 -0
- package/payload/server/src/providers/codex-workspace.ts +61 -0
- package/payload/server/src/providers/failures.ts +143 -0
- package/payload/server/src/providers/index.ts +131 -0
- package/payload/server/src/providers/mcp-stdio.ts +147 -0
- package/payload/server/src/providers/mock.ts +94 -0
- package/payload/server/src/providers/process.ts +268 -0
- package/payload/server/src/providers/readiness.ts +43 -0
- package/payload/server/src/providers/sse.ts +73 -0
- package/payload/server/src/providers/types.ts +173 -0
- package/payload/server/src/rates.ts +128 -0
- package/payload/server/src/reactions.ts +266 -0
- package/payload/server/src/recent-logs.ts +489 -0
- package/payload/server/src/rehearsal.ts +593 -0
- package/payload/server/src/rehearsals.ts +303 -0
- package/payload/server/src/replay.ts +165 -0
- package/payload/server/src/sandbox.ts +767 -0
- package/payload/server/src/schedule.ts +214 -0
- package/payload/server/src/scheduler.ts +24 -0
- package/payload/server/src/schema.ts +882 -0
- package/payload/server/src/secrets.ts +46 -0
- package/payload/server/src/setup.ts +356 -0
- package/payload/server/src/skill-packages.ts +363 -0
- package/payload/server/src/skills.ts +527 -0
- package/payload/server/src/spend.ts +255 -0
- package/payload/server/src/ssrf.ts +126 -0
- package/payload/server/src/starter-gate.ts +168 -0
- package/payload/server/src/starters.ts +610 -0
- package/payload/server/src/storage.ts +32 -0
- package/payload/server/src/store.ts +3087 -0
- package/payload/server/src/tailnet.ts +132 -0
- package/payload/server/src/templates.ts +454 -0
- package/payload/server/src/trash.ts +772 -0
- package/payload/server/src/util.ts +97 -0
- package/payload/server/src/webhooks.ts +216 -0
- package/payload/server/src/work-feed.ts +473 -0
- package/payload/shared/app-version.d.mts +1 -0
- package/payload/shared/app-version.mjs +3 -0
- package/payload/shared/boton.ts +323 -0
- package/payload/shared/brand.ts +107 -0
- package/payload/shared/cards.ts +427 -0
- package/payload/shared/effort.ts +91 -0
- package/payload/shared/files.ts +104 -0
- package/payload/shared/handoff.ts +97 -0
- package/payload/shared/install-commands.ts +7 -0
- package/payload/shared/legacy-names.d.mts +35 -0
- package/payload/shared/legacy-names.mjs +244 -0
- package/payload/shared/live-view.ts +94 -0
- package/payload/shared/look-critter.ts +189 -0
- package/payload/shared/look-tater.ts +127 -0
- package/payload/shared/look.ts +200 -0
- package/payload/shared/reactions.ts +43 -0
- package/payload/shared/seeded.ts +34 -0
- package/payload/shared/services.ts +90 -0
- package/payload/shared/types.ts +3016 -0
- package/payload/shared/util.ts +68 -0
- package/payload/shared/versions.ts +44 -0
- package/payload/shared/wake.ts +164 -0
- package/payload/web/dist/apple-touch-icon.png +0 -0
- package/payload/web/dist/assets/Icons-nytVAa9g.js +1 -0
- package/payload/web/dist/assets/NewBotDialog-DXr2XhDP.js +1 -0
- package/payload/web/dist/assets/PairScreen-CMcJO7cQ.js +1 -0
- package/payload/web/dist/assets/Panel-Bvqz3I3m.js +4 -0
- package/payload/web/dist/assets/ReactionPicker-B0SKsjiy.js +1 -0
- package/payload/web/dist/assets/SettingRows-CvLomtuo.js +4 -0
- package/payload/web/dist/assets/SettingsDialog-CQ41LV-P.js +8 -0
- package/payload/web/dist/assets/Setup-C-3P7Mtn.js +1 -0
- package/payload/web/dist/assets/Skills-DXMH69kH.js +1 -0
- package/payload/web/dist/assets/StarterSetup-Ce2V1_Cs.js +5 -0
- package/payload/web/dist/assets/Steps-CKtB3GAM.js +1 -0
- package/payload/web/dist/assets/emoji-catalog-DfpPz8qw.js +540 -0
- package/payload/web/dist/assets/index-BEU7FDxV.css +1 -0
- package/payload/web/dist/assets/index-Dhwn-rf0.js +3 -0
- package/payload/web/dist/assets/react-C1iE8mnY.js +9 -0
- package/payload/web/dist/assets/reactions-rw0YeCmA.js +1 -0
- package/payload/web/dist/assets/rolldown-runtime-XKyaHtl9.js +1 -0
- package/payload/web/dist/assets/util-Cx5feSJu.js +1 -0
- package/payload/web/dist/assets/vendor-B3qdcjGo.js +14 -0
- package/payload/web/dist/favicon.svg +1 -0
- package/payload/web/dist/icon-192.png +0 -0
- package/payload/web/dist/icon-512.png +0 -0
- package/payload/web/dist/icon.svg +1 -0
- package/payload/web/dist/index.html +67 -0
- package/payload/web/dist/manifest.webmanifest +12 -0
- package/shared/legacy-names.d.mts +35 -0
- package/shared/legacy-names.mjs +244 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import type { NotifyDelivery, NotifyPriority, PushProblem, PushStatus } from '../../shared/types.ts';
|
|
3
|
+
import { log } from './log.ts';
|
|
4
|
+
import { nowIso } from './util.ts';
|
|
5
|
+
|
|
6
|
+
export const PUSH_TEXT_MAX = 200;
|
|
7
|
+
|
|
8
|
+
interface Push {
|
|
9
|
+
title: string;
|
|
10
|
+
body: string;
|
|
11
|
+
click: string;
|
|
12
|
+
priority: NotifyPriority;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type SendResult = Pick<NotifyDelivery, 'ok' | 'status' | 'error'> & { problem: PushProblem | null };
|
|
16
|
+
|
|
17
|
+
export interface NtfyConfig {
|
|
18
|
+
enabled: boolean;
|
|
19
|
+
server: string;
|
|
20
|
+
topic: string;
|
|
21
|
+
token: string | null;
|
|
22
|
+
preview: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function describe(err: unknown): string {
|
|
26
|
+
const e = err as { cause?: { code?: string; message?: string }; message?: string };
|
|
27
|
+
return e?.cause?.code ?? e?.cause?.message ?? e?.message ?? String(err);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** What a failed push says about where to look, as a fixed word: the owner's view never carries the error text. */
|
|
31
|
+
export function httpProblem(status: number): PushProblem {
|
|
32
|
+
if (status >= 300 && status < 400) return 'redirected';
|
|
33
|
+
if (status === 401 || status === 403) return 'refused';
|
|
34
|
+
if (status >= 500) return 'server-error';
|
|
35
|
+
return 'rejected';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function thrownProblem(err: unknown): PushProblem {
|
|
39
|
+
const e = err as { name?: string; cause?: { name?: string; code?: string } };
|
|
40
|
+
if (e?.name === 'TimeoutError' || e?.name === 'AbortError' || e?.cause?.code === 'UND_ERR_CONNECT_TIMEOUT' || e?.cause?.code === 'ETIMEDOUT') return 'timeout';
|
|
41
|
+
return 'unreachable';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Publishes with ntfy's JSON API. Redirects are not followed, so the token only ever goes to the configured server. */
|
|
45
|
+
async function sendNtfy(cfg: NtfyConfig, push: Push): Promise<SendResult> {
|
|
46
|
+
const headers: Record<string, string> = { 'content-type': 'application/json' };
|
|
47
|
+
if (cfg.token) headers.authorization = `Bearer ${cfg.token}`;
|
|
48
|
+
const body = {
|
|
49
|
+
topic: cfg.topic,
|
|
50
|
+
title: push.title,
|
|
51
|
+
message: push.body,
|
|
52
|
+
// ntfy's 1 (min) to 5 (max); 3 is its default.
|
|
53
|
+
priority: push.priority === 'high' ? 4 : 3,
|
|
54
|
+
click: push.click,
|
|
55
|
+
};
|
|
56
|
+
try {
|
|
57
|
+
const res = await fetch(`${cfg.server.replace(/\/+$/, '')}/`, {
|
|
58
|
+
method: 'POST',
|
|
59
|
+
headers,
|
|
60
|
+
body: JSON.stringify(body),
|
|
61
|
+
redirect: 'manual',
|
|
62
|
+
signal: AbortSignal.timeout(10_000),
|
|
63
|
+
});
|
|
64
|
+
const text = await res.text().catch(() => '');
|
|
65
|
+
if (res.status >= 200 && res.status < 300) return { ok: true, status: res.status, error: null, problem: null };
|
|
66
|
+
let detail = text.trim().slice(0, 300);
|
|
67
|
+
try {
|
|
68
|
+
detail = String(JSON.parse(text).error ?? detail);
|
|
69
|
+
} catch {}
|
|
70
|
+
if (res.status >= 300 && res.status < 400) detail = `redirected to ${res.headers.get('location') ?? 'another address'}; check the server URL`;
|
|
71
|
+
return { ok: false, status: res.status, error: `HTTP ${res.status}${detail ? `: ${detail}` : ''}`, problem: httpProblem(res.status) };
|
|
72
|
+
} catch (err) {
|
|
73
|
+
return { ok: false, status: null, error: describe(err), problem: thrownProblem(err) };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Trimmed, and cut to `max` characters with an ellipsis. */
|
|
78
|
+
export function clip(text: string, max = PUSH_TEXT_MAX): string {
|
|
79
|
+
const t = text.trim();
|
|
80
|
+
return t.length > max ? `${t.slice(0, max - 1).trimEnd()}…` : t;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface PushRequest {
|
|
84
|
+
/** Null for the test from Settings. */
|
|
85
|
+
bot: { id: string; name: string } | null;
|
|
86
|
+
reason: NotifyDelivery['reason'];
|
|
87
|
+
/** Sent only with previews on. Never a webhook payload. */
|
|
88
|
+
detail: string;
|
|
89
|
+
/** Sent with previews off: anyone who knows a topic's name on a public ntfy server can read it. */
|
|
90
|
+
generic: string;
|
|
91
|
+
priority: NotifyPriority;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function buildPush(req: PushRequest, preview: boolean, publicUrl: string): Push {
|
|
95
|
+
const base = publicUrl.replace(/\/+$/, '');
|
|
96
|
+
return {
|
|
97
|
+
title: req.bot?.name ?? 'Botons',
|
|
98
|
+
body: clip((preview && req.detail.trim()) || req.generic),
|
|
99
|
+
click: req.bot ? `${base}/#/m/${encodeURIComponent(req.bot.id)}` : `${base}/`,
|
|
100
|
+
priority: req.priority,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Which ntfy server, topic and token an attempt went to: the push status describes one of these at a time. */
|
|
105
|
+
function scopeOf(cfg: NtfyConfig | null): string | null {
|
|
106
|
+
return cfg ? createHash('sha256').update(JSON.stringify([cfg.server, cfg.topic, cfg.token ?? ''])).digest('hex') : null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export class Notifier {
|
|
110
|
+
#opts: { config: () => NtfyConfig | null; publicUrl: () => string };
|
|
111
|
+
/** The server, topic and token the status below is about; undefined until first seen. */
|
|
112
|
+
#scope: string | null | undefined = undefined;
|
|
113
|
+
#since = nowIso();
|
|
114
|
+
#sinceSettingsChange = false;
|
|
115
|
+
#last: PushStatus['last'] = null;
|
|
116
|
+
#lastSuccessAt: string | null = null;
|
|
117
|
+
#failuresInARow = 0;
|
|
118
|
+
/** Attempts are numbered as they start; one that finishes after a newer one, or after a settings change, is not counted. */
|
|
119
|
+
#started = 0;
|
|
120
|
+
#counted = 0;
|
|
121
|
+
|
|
122
|
+
constructor(opts: { config: () => NtfyConfig | null; publicUrl: () => string }) {
|
|
123
|
+
this.#opts = opts;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
get enabled(): boolean {
|
|
127
|
+
return !!this.#opts.config()?.enabled;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Starts the status again when the ntfy server, topic or token is not the one it describes, so a failure to one is
|
|
132
|
+
* never cleared by a success to another. Settings calls it on every change; push() and status() check it too.
|
|
133
|
+
*/
|
|
134
|
+
syncConfig(cfg: NtfyConfig | null = this.#opts.config()): string | null {
|
|
135
|
+
const scope = scopeOf(cfg);
|
|
136
|
+
if (this.#scope !== undefined && scope !== this.#scope) {
|
|
137
|
+
this.#since = nowIso();
|
|
138
|
+
this.#sinceSettingsChange = true;
|
|
139
|
+
this.#last = null;
|
|
140
|
+
this.#lastSuccessAt = null;
|
|
141
|
+
this.#failuresInARow = 0;
|
|
142
|
+
this.#counted = this.#started;
|
|
143
|
+
}
|
|
144
|
+
this.#scope = scope;
|
|
145
|
+
return scope;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Nothing if push is off (except a test, which only needs a server and topic). Never throws. */
|
|
149
|
+
async push(req: PushRequest): Promise<NotifyDelivery[]> {
|
|
150
|
+
const sent = await this.#attempt(req);
|
|
151
|
+
return sent ? [sent.delivery] : [];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** The owner's test push, with its fixed word for a failure; null without a server and topic. Never throws. */
|
|
155
|
+
async test(): Promise<{ delivery: NotifyDelivery; problem: PushProblem | null } | null> {
|
|
156
|
+
const text = 'Test notification from Botons.';
|
|
157
|
+
return this.#attempt({ bot: null, reason: 'test', detail: text, generic: text, priority: 'default' });
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async #attempt(req: PushRequest): Promise<{ delivery: NotifyDelivery; problem: PushProblem | null } | null> {
|
|
161
|
+
const cfg = this.#opts.config();
|
|
162
|
+
if (!cfg || (!cfg.enabled && req.reason !== 'test')) return null;
|
|
163
|
+
const scope = this.syncConfig(cfg);
|
|
164
|
+
const attempt = ++this.#started;
|
|
165
|
+
const r = await sendNtfy(cfg, buildPush(req, cfg.preview, this.#opts.publicUrl()));
|
|
166
|
+
if (!r.ok) log.warn('push failed', { channel: 'ntfy', reason: req.reason, botId: req.bot?.id, status: r.status, error: r.error });
|
|
167
|
+
const at = nowIso();
|
|
168
|
+
// Count it only if it is still about the current settings and no newer attempt has been counted.
|
|
169
|
+
if (this.syncConfig() === scope && attempt > this.#counted) {
|
|
170
|
+
this.#counted = attempt;
|
|
171
|
+
this.#last = { at, ok: r.ok, reason: req.reason, status: r.status, problem: r.problem };
|
|
172
|
+
if (r.ok) {
|
|
173
|
+
this.#lastSuccessAt = at;
|
|
174
|
+
this.#failuresInARow = 0;
|
|
175
|
+
} else this.#failuresInARow++;
|
|
176
|
+
}
|
|
177
|
+
return { delivery: { channel: 'ntfy', reason: req.reason, ok: r.ok, status: r.status, error: r.error, at }, problem: r.problem };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** How pushes to the current ntfy settings have gone, for Settings → Health. Kept in memory: a restart starts again. */
|
|
181
|
+
status(): PushStatus {
|
|
182
|
+
const cfg = this.#opts.config();
|
|
183
|
+
this.syncConfig(cfg);
|
|
184
|
+
return {
|
|
185
|
+
state: !cfg ? 'unset' : cfg.enabled ? 'on' : 'off',
|
|
186
|
+
last: this.#last,
|
|
187
|
+
lastSuccessAt: this.#lastSuccessAt,
|
|
188
|
+
failuresInARow: this.#failuresInARow,
|
|
189
|
+
since: this.#since,
|
|
190
|
+
sinceSettingsChange: this.#sinceSettingsChange,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
}
|