@owloops/browserbird 1.0.2 → 1.0.4

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.
Files changed (56) hide show
  1. package/bin/browserbird +7 -1
  2. package/dist/db-BsYEYsul.mjs +1011 -0
  3. package/dist/index.mjs +4749 -0
  4. package/package.json +6 -3
  5. package/src/channel/blocks.ts +0 -485
  6. package/src/channel/coalesce.ts +0 -79
  7. package/src/channel/commands.ts +0 -216
  8. package/src/channel/handler.ts +0 -272
  9. package/src/channel/slack.ts +0 -573
  10. package/src/channel/types.ts +0 -59
  11. package/src/cli/banner.ts +0 -10
  12. package/src/cli/birds.ts +0 -396
  13. package/src/cli/config.ts +0 -77
  14. package/src/cli/doctor.ts +0 -63
  15. package/src/cli/index.ts +0 -5
  16. package/src/cli/jobs.ts +0 -166
  17. package/src/cli/logs.ts +0 -67
  18. package/src/cli/run.ts +0 -148
  19. package/src/cli/sessions.ts +0 -158
  20. package/src/cli/style.ts +0 -19
  21. package/src/config.ts +0 -291
  22. package/src/core/logger.ts +0 -78
  23. package/src/core/redact.ts +0 -75
  24. package/src/core/types.ts +0 -83
  25. package/src/core/uid.ts +0 -26
  26. package/src/core/utils.ts +0 -137
  27. package/src/cron/parse.ts +0 -146
  28. package/src/cron/scheduler.ts +0 -242
  29. package/src/daemon.ts +0 -169
  30. package/src/db/auth.ts +0 -49
  31. package/src/db/birds.ts +0 -357
  32. package/src/db/core.ts +0 -377
  33. package/src/db/index.ts +0 -10
  34. package/src/db/jobs.ts +0 -289
  35. package/src/db/logs.ts +0 -64
  36. package/src/db/messages.ts +0 -79
  37. package/src/db/path.ts +0 -30
  38. package/src/db/sessions.ts +0 -165
  39. package/src/jobs.ts +0 -140
  40. package/src/provider/claude.test.ts +0 -95
  41. package/src/provider/claude.ts +0 -196
  42. package/src/provider/opencode.test.ts +0 -169
  43. package/src/provider/opencode.ts +0 -248
  44. package/src/provider/session.ts +0 -65
  45. package/src/provider/spawn.ts +0 -173
  46. package/src/provider/stream.ts +0 -67
  47. package/src/provider/types.ts +0 -24
  48. package/src/server/auth.ts +0 -135
  49. package/src/server/health.ts +0 -87
  50. package/src/server/http.ts +0 -132
  51. package/src/server/index.ts +0 -6
  52. package/src/server/lifecycle.ts +0 -135
  53. package/src/server/routes.ts +0 -1199
  54. package/src/server/sse.ts +0 -54
  55. package/src/server/static.ts +0 -45
  56. package/src/server/vnc-proxy.ts +0 -75
package/src/cli/birds.ts DELETED
@@ -1,396 +0,0 @@
1
- /** @fileoverview Birds command: manage scheduled birds (cron jobs). */
2
-
3
- import { parseArgs } from 'node:util';
4
- import { logger } from '../core/logger.ts';
5
- import { shortUid } from '../core/uid.ts';
6
- import { formatDuration, deriveBirdName, printTable, unknownSubcommand } from '../core/utils.ts';
7
- import { c } from './style.ts';
8
- import {
9
- openDatabase,
10
- closeDatabase,
11
- resolveByUid,
12
- listCronJobs,
13
- createCronJob,
14
- updateCronJob,
15
- setCronJobEnabled,
16
- deleteCronJob,
17
- listFlights,
18
- resolveDbPathFromArgv,
19
- } from '../db/index.ts';
20
- import type { CronJobRow } from '../db/index.ts';
21
- import { enqueue } from '../jobs.ts';
22
-
23
- export const BIRDS_HELP = `
24
- ${c('cyan', 'usage:')} browserbird birds <subcommand> [options]
25
-
26
- manage scheduled birds.
27
-
28
- ${c('dim', 'subcommands:')}
29
-
30
- ${c('cyan', 'list')} list all birds
31
- ${c('cyan', 'add')} <schedule> <prompt> add a new bird
32
- ${c('cyan', 'edit')} <uid> edit a bird
33
- ${c('cyan', 'remove')} <uid> remove a bird
34
- ${c('cyan', 'enable')} <uid> enable a bird
35
- ${c('cyan', 'disable')} <uid> disable a bird
36
- ${c('cyan', 'fly')} <uid> trigger a bird manually
37
- ${c('cyan', 'flights')} <uid> show flight history for a bird
38
-
39
- ${c('dim', 'options:')}
40
-
41
- ${c('yellow', '--channel')} <id> target slack channel
42
- ${c('yellow', '--agent')} <id> target agent id
43
- ${c('yellow', '--schedule')} <expr> cron schedule expression
44
- ${c('yellow', '--prompt')} <text> prompt text
45
- ${c('yellow', '--timezone')} <tz> IANA timezone (default: UTC)
46
- ${c('yellow', '--active-hours')} <range> restrict runs to a time window (e.g. "09:00-17:00")
47
- ${c('yellow', '--limit')} <n> number of flights to show (default: 10)
48
- ${c('yellow', '--json')} output as JSON (with list, flights)
49
- ${c('yellow', '--db')} <path> database file path (env: BROWSERBIRD_DB)
50
- ${c('yellow', '-h, --help')} show this help
51
- `.trim();
52
-
53
- function statusColor(status: string | null | undefined): string {
54
- if (status == null) return '-';
55
- switch (status) {
56
- case 'success':
57
- case 'completed':
58
- return c('green', status);
59
- case 'running':
60
- return c('blue', status);
61
- case 'failed':
62
- return c('red', status);
63
- default:
64
- return status;
65
- }
66
- }
67
-
68
- function parseActiveHours(raw: string): { start: string; end: string } | null {
69
- const match = raw.match(/^(\d{1,2}:\d{2})\s*-\s*(\d{1,2}:\d{2})$/);
70
- if (!match) return null;
71
- return { start: match[1]!, end: match[2]! };
72
- }
73
-
74
- function resolveBird(uidPrefix: string): CronJobRow | undefined {
75
- const result = resolveByUid<CronJobRow>('cron_jobs', uidPrefix);
76
- if (!result) {
77
- logger.error(`bird ${uidPrefix} not found`);
78
- process.exitCode = 1;
79
- return undefined;
80
- }
81
- if ('ambiguous' in result) {
82
- logger.error(
83
- `ambiguous bird ID "${uidPrefix}" matches ${result.count} birds, use a longer prefix`,
84
- );
85
- process.exitCode = 1;
86
- return undefined;
87
- }
88
- return result.row;
89
- }
90
-
91
- export function handleBirds(argv: string[]): void {
92
- const subcommand = argv[0] ?? 'list';
93
- const rest = argv.slice(1);
94
-
95
- const { values, positionals } = parseArgs({
96
- args: rest,
97
- options: {
98
- channel: { type: 'string' },
99
- agent: { type: 'string' },
100
- schedule: { type: 'string' },
101
- prompt: { type: 'string' },
102
- timezone: { type: 'string' },
103
- 'active-hours': { type: 'string' },
104
- limit: { type: 'string' },
105
- json: { type: 'boolean', default: false },
106
- },
107
- allowPositionals: true,
108
- strict: false,
109
- });
110
-
111
- openDatabase(resolveDbPathFromArgv(argv));
112
-
113
- try {
114
- switch (subcommand) {
115
- case 'list': {
116
- const result = listCronJobs(1, 100);
117
- if (values.json) {
118
- console.log(JSON.stringify(result.items, null, 2));
119
- break;
120
- }
121
- console.log(`birds (${result.totalItems} total):`);
122
- if (result.items.length === 0) {
123
- console.log('\n no birds configured');
124
- return;
125
- }
126
- console.log('');
127
- const rows = result.items.map((job) => [
128
- c('dim', shortUid(job.uid)),
129
- job.enabled ? c('green', 'enabled') : c('yellow', 'disabled'),
130
- job.schedule,
131
- job.agent_id,
132
- job.target_channel_id ?? '-',
133
- statusColor(job.last_status),
134
- job.prompt.slice(0, 50),
135
- ]);
136
- printTable(['uid', 'status', 'schedule', 'agent', 'channel', 'last', 'prompt'], rows, [
137
- undefined,
138
- undefined,
139
- undefined,
140
- undefined,
141
- undefined,
142
- undefined,
143
- 50,
144
- ]);
145
- break;
146
- }
147
-
148
- case 'add': {
149
- const schedule = positionals[0];
150
- const prompt = positionals.slice(1).join(' ') || (values.prompt as string | undefined);
151
- if (!schedule || !prompt) {
152
- logger.error(
153
- 'usage: browserbird birds add <schedule> <prompt> [--channel <id>] [--agent <id>]',
154
- );
155
- process.exitCode = 1;
156
- return;
157
- }
158
- const activeHoursRaw = values['active-hours'] as string | undefined;
159
- let activeStart: string | undefined;
160
- let activeEnd: string | undefined;
161
- if (activeHoursRaw) {
162
- const parsed = parseActiveHours(activeHoursRaw);
163
- if (!parsed) {
164
- logger.error('--active-hours must be HH:MM-HH:MM (e.g. "09:00-17:00")');
165
- process.exitCode = 1;
166
- return;
167
- }
168
- activeStart = parsed.start;
169
- activeEnd = parsed.end;
170
- }
171
- const job = createCronJob(
172
- deriveBirdName(prompt),
173
- schedule,
174
- prompt,
175
- values.channel as string | undefined,
176
- values.agent as string | undefined,
177
- values.timezone as string | undefined,
178
- activeStart,
179
- activeEnd,
180
- );
181
- logger.success(`bird ${shortUid(job.uid)} created: "${schedule}"`);
182
- process.stderr.write(
183
- c('dim', ` hint: run 'browserbird birds fly ${shortUid(job.uid)}' to trigger it now`) +
184
- '\n',
185
- );
186
- break;
187
- }
188
-
189
- case 'edit': {
190
- const uidPrefix = positionals[0];
191
- if (!uidPrefix) {
192
- logger.error(
193
- 'usage: browserbird birds edit <uid> [--schedule <expr>] [--prompt <text>] [--channel <id>] [--agent <id>] [--timezone <tz>] [--active-hours <range>]',
194
- );
195
- process.exitCode = 1;
196
- return;
197
- }
198
- const bird = resolveBird(uidPrefix);
199
- if (!bird) return;
200
- const channel = values.channel as string | undefined;
201
- const agent = values.agent as string | undefined;
202
- const schedule = values.schedule as string | undefined;
203
- const prompt = values.prompt as string | undefined;
204
- const timezone = values.timezone as string | undefined;
205
- const editActiveHoursRaw = values['active-hours'] as string | undefined;
206
- let editActiveStart: string | null | undefined;
207
- let editActiveEnd: string | null | undefined;
208
- if (editActiveHoursRaw === '') {
209
- editActiveStart = null;
210
- editActiveEnd = null;
211
- } else if (editActiveHoursRaw) {
212
- const parsed = parseActiveHours(editActiveHoursRaw);
213
- if (!parsed) {
214
- logger.error('--active-hours must be HH:MM-HH:MM (e.g. "09:00-17:00"), or "" to clear');
215
- process.exitCode = 1;
216
- return;
217
- }
218
- editActiveStart = parsed.start;
219
- editActiveEnd = parsed.end;
220
- }
221
- if (
222
- !schedule &&
223
- !prompt &&
224
- !channel &&
225
- !agent &&
226
- !timezone &&
227
- editActiveStart === undefined
228
- ) {
229
- logger.error(
230
- 'provide at least one of: --schedule, --prompt, --channel, --agent, --timezone, --active-hours',
231
- );
232
- process.exitCode = 1;
233
- return;
234
- }
235
- const updated = updateCronJob(bird.uid, {
236
- schedule,
237
- prompt,
238
- name: prompt ? deriveBirdName(prompt) : undefined,
239
- targetChannelId: channel !== undefined ? channel || null : undefined,
240
- agentId: agent,
241
- timezone,
242
- activeHoursStart: editActiveStart,
243
- activeHoursEnd: editActiveEnd,
244
- });
245
- if (updated) {
246
- logger.success(`bird ${shortUid(bird.uid)} updated`);
247
- process.stderr.write(
248
- c('dim', ` hint: run 'browserbird birds list' to see updated birds`) + '\n',
249
- );
250
- } else {
251
- logger.error(`bird ${shortUid(bird.uid)} not found`);
252
- process.exitCode = 1;
253
- }
254
- break;
255
- }
256
-
257
- case 'remove': {
258
- const uidPrefix = positionals[0];
259
- if (!uidPrefix) {
260
- logger.error('usage: browserbird birds remove <uid>');
261
- process.exitCode = 1;
262
- return;
263
- }
264
- const bird = resolveBird(uidPrefix);
265
- if (!bird) return;
266
- if (deleteCronJob(bird.uid)) {
267
- logger.success(`bird ${shortUid(bird.uid)} removed`);
268
- } else {
269
- logger.error(`bird ${shortUid(bird.uid)} not found`);
270
- process.exitCode = 1;
271
- }
272
- break;
273
- }
274
-
275
- case 'enable':
276
- case 'disable': {
277
- const uidPrefix = positionals[0];
278
- if (!uidPrefix) {
279
- logger.error(`usage: browserbird birds ${subcommand} <uid>`);
280
- process.exitCode = 1;
281
- return;
282
- }
283
- const bird = resolveBird(uidPrefix);
284
- if (!bird) return;
285
- const enabled = subcommand === 'enable';
286
- if (setCronJobEnabled(bird.uid, enabled)) {
287
- logger.success(`bird ${shortUid(bird.uid)} ${enabled ? 'enabled' : 'disabled'}`);
288
- if (enabled) {
289
- process.stderr.write(
290
- c(
291
- 'dim',
292
- ` hint: run 'browserbird birds fly ${shortUid(bird.uid)}' to trigger it now`,
293
- ) + '\n',
294
- );
295
- }
296
- } else {
297
- logger.error(`bird ${shortUid(bird.uid)} not found`);
298
- process.exitCode = 1;
299
- }
300
- break;
301
- }
302
-
303
- case 'fly': {
304
- const uidPrefix = positionals[0];
305
- if (!uidPrefix) {
306
- logger.error('usage: browserbird birds fly <uid>');
307
- process.exitCode = 1;
308
- return;
309
- }
310
- const cronJob = resolveBird(uidPrefix);
311
- if (!cronJob) return;
312
- const enqueuedJob = enqueue(
313
- 'cron_run',
314
- {
315
- cronJobUid: cronJob.uid,
316
- prompt: cronJob.prompt,
317
- channelId: cronJob.target_channel_id,
318
- agentId: cronJob.agent_id,
319
- },
320
- { cronJobUid: cronJob.uid },
321
- );
322
- logger.success(`enqueued job #${enqueuedJob.id} for bird ${shortUid(cronJob.uid)}`);
323
- process.stderr.write(
324
- c(
325
- 'dim',
326
- ` hint: run 'browserbird birds flights ${shortUid(cronJob.uid)}' to check results`,
327
- ) + '\n',
328
- );
329
- break;
330
- }
331
-
332
- case 'flights': {
333
- const uidPrefix = positionals[0];
334
- if (!uidPrefix) {
335
- logger.error('usage: browserbird birds flights <uid>');
336
- process.exitCode = 1;
337
- return;
338
- }
339
- const bird = resolveBird(uidPrefix);
340
- if (!bird) return;
341
- const perPage = values.limit != null ? Number(values.limit) : 10;
342
- if (!Number.isFinite(perPage) || perPage < 1) {
343
- logger.error('--limit must be a positive number');
344
- process.exitCode = 1;
345
- return;
346
- }
347
- const result = listFlights(1, perPage, { birdUid: bird.uid });
348
- if (values.json) {
349
- console.log(JSON.stringify(result.items, null, 2));
350
- break;
351
- }
352
- console.log(`flight history for bird ${shortUid(bird.uid)} (${result.totalItems} total):`);
353
- if (result.items.length === 0) {
354
- console.log('\n no flights recorded');
355
- return;
356
- }
357
- console.log('');
358
- const rows = result.items.map((flight) => {
359
- const durationMs = flight.finished_at
360
- ? new Date(flight.finished_at).getTime() - new Date(flight.started_at).getTime()
361
- : null;
362
- const duration = durationMs == null ? '-' : formatDuration(durationMs);
363
- return [
364
- c('dim', shortUid(flight.uid)),
365
- statusColor(flight.status),
366
- duration,
367
- flight.started_at.slice(0, 19),
368
- flight.error ?? flight.result?.slice(0, 60) ?? '',
369
- ];
370
- });
371
- printTable(['flight', 'status', 'duration', 'started', 'error / result'], rows, [
372
- undefined,
373
- undefined,
374
- undefined,
375
- undefined,
376
- 60,
377
- ]);
378
- break;
379
- }
380
-
381
- default:
382
- unknownSubcommand(subcommand, 'birds', [
383
- 'list',
384
- 'add',
385
- 'edit',
386
- 'remove',
387
- 'enable',
388
- 'disable',
389
- 'fly',
390
- 'flights',
391
- ]);
392
- }
393
- } finally {
394
- closeDatabase();
395
- }
396
- }
package/src/cli/config.ts DELETED
@@ -1,77 +0,0 @@
1
- /** @fileoverview Config command: display merged configuration. */
2
-
3
- import { parseArgs } from 'node:util';
4
- import { loadRawConfig } from '../config.ts';
5
- import type { Config } from '../core/types.ts';
6
- import { c } from './style.ts';
7
-
8
- export const CONFIG_HELP = `
9
- ${c('cyan', 'usage:')} browserbird config [options]
10
-
11
- view full configuration (defaults merged with browserbird.json).
12
-
13
- ${c('dim', 'options:')}
14
-
15
- ${c('yellow', '--config')} <path> config file path
16
- ${c('yellow', '-h, --help')} show this help
17
- `.trim();
18
-
19
- export function handleConfig(argv: string[]): void {
20
- const { values } = parseArgs({
21
- args: argv,
22
- options: { config: { type: 'string' } },
23
- allowPositionals: false,
24
- strict: false,
25
- });
26
- printConfig(values.config as string | undefined);
27
- }
28
-
29
- function printConfig(configPath?: string): void {
30
- const config = loadRawConfig(configPath) as unknown as Config;
31
-
32
- console.log(c('cyan', 'config'));
33
- console.log(c('dim', '------'));
34
- console.log(`\n${c('dim', 'timezone:')} ${config.timezone}`);
35
-
36
- console.log(`\n${c('cyan', 'agents:')}`);
37
- for (const a of config.agents) {
38
- console.log(` ${c('cyan', a.id)} (${a.name})`);
39
- console.log(` ${c('dim', 'provider:')} ${a.provider}`);
40
- console.log(` ${c('dim', 'model:')} ${a.model}`);
41
- console.log(` ${c('dim', 'max turns:')} ${a.maxTurns}`);
42
- console.log(` ${c('dim', 'channels:')} ${a.channels.join(', ') || '*'}`);
43
- }
44
-
45
- console.log(`\n${c('cyan', 'sessions:')}`);
46
- console.log(` ${c('dim', 'max concurrent:')} ${config.sessions.maxConcurrent}`);
47
- console.log(` ${c('dim', 'ttl:')} ${config.sessions.ttlHours}h`);
48
- console.log(` ${c('dim', 'timeout:')} ${config.sessions.processTimeoutMs / 1000}s`);
49
-
50
- console.log(`\n${c('cyan', 'slack:')}`);
51
- console.log(` ${c('dim', 'require mention:')} ${config.slack.requireMention ? 'yes' : 'no'}`);
52
- console.log(` ${c('dim', 'debounce:')} ${config.slack.coalesce.debounceMs}ms`);
53
- console.log(
54
- ` ${c('dim', 'bypass dms:')} ${config.slack.coalesce.bypassDms ? 'yes' : 'no'}`,
55
- );
56
- console.log(` ${c('dim', 'channels:')} ${config.slack.channels.join(', ') || '(all)'}`);
57
- console.log(
58
- ` ${c('dim', 'quiet hours:')} ${config.slack.quietHours.enabled ? `${config.slack.quietHours.start}-${config.slack.quietHours.end} (${config.slack.quietHours.timezone})` : 'disabled'}`,
59
- );
60
-
61
- console.log(`\n${c('cyan', 'birds:')}`);
62
- console.log(` ${c('dim', 'max attempts:')} ${config.birds.maxAttempts}`);
63
-
64
- console.log(`\n${c('cyan', 'browser:')}`);
65
- console.log(` ${c('dim', 'enabled:')} ${config.browser.enabled ? 'yes' : 'no'}`);
66
- if (config.browser.enabled) {
67
- console.log(` ${c('dim', 'mode:')} ${process.env['BROWSER_MODE'] ?? 'persistent'}`);
68
- console.log(` ${c('dim', 'vnc port:')} ${config.browser.vncPort}`);
69
- console.log(` ${c('dim', 'novnc port:')} ${config.browser.novncPort}`);
70
- }
71
-
72
- console.log(`\n${c('cyan', 'database:')}`);
73
- console.log(` ${c('dim', 'retention:')} ${config.database.retentionDays}d`);
74
-
75
- console.log(`\n${c('cyan', 'web:')}`);
76
- console.log(` ${c('dim', 'port:')} ${config.web.port}`);
77
- }
package/src/cli/doctor.ts DELETED
@@ -1,63 +0,0 @@
1
- /** @fileoverview Doctor command: checks system dependencies. */
2
-
3
- import { execFileSync } from 'node:child_process';
4
- import { logger } from '../core/logger.ts';
5
- import { c } from './style.ts';
6
-
7
- export const DOCTOR_HELP = `
8
- ${c('cyan', 'usage:')} browserbird doctor
9
-
10
- check system dependencies (agent clis, node.js).
11
- `.trim();
12
-
13
- interface CliStatus {
14
- available: boolean;
15
- version: string | null;
16
- }
17
-
18
- export interface DoctorResult {
19
- claude: CliStatus;
20
- opencode: CliStatus;
21
- node: string;
22
- }
23
-
24
- function checkCli(binary: string, versionArgs: string[]): CliStatus {
25
- try {
26
- const output = execFileSync(binary, versionArgs, {
27
- timeout: 5000,
28
- encoding: 'utf-8',
29
- stdio: ['ignore', 'pipe', 'ignore'],
30
- });
31
- const version = (output.trim().split('\n')[0] ?? '').replace(/\s*\(.*\)$/, '') || null;
32
- return { available: true, version };
33
- } catch {
34
- return { available: false, version: null };
35
- }
36
- }
37
-
38
- export function checkDoctor(): DoctorResult {
39
- return {
40
- claude: checkCli('claude', ['--version']),
41
- opencode: checkCli('opencode', ['--version']),
42
- node: process.version,
43
- };
44
- }
45
-
46
- export function handleDoctor(): void {
47
- const result = checkDoctor();
48
- console.log(c('cyan', 'doctor'));
49
- console.log(c('dim', '------'));
50
- if (result.claude.available) {
51
- logger.success(`claude cli: ${result.claude.version}`);
52
- } else {
53
- logger.error('claude cli: not found');
54
- process.stderr.write(' install: npm install -g @anthropic-ai/claude-code\n');
55
- }
56
- if (result.opencode.available) {
57
- logger.success(`opencode cli: ${result.opencode.version}`);
58
- } else {
59
- logger.warn('opencode cli: not found (optional)');
60
- process.stderr.write(' install: npm install -g opencode\n');
61
- }
62
- logger.success(`node.js: ${result.node}`);
63
- }
package/src/cli/index.ts DELETED
@@ -1,5 +0,0 @@
1
- /** @fileoverview CLI barrel: public API for the CLI module. */
2
-
3
- export { run } from './run.ts';
4
- export { checkDoctor } from './doctor.ts';
5
- export type { DoctorResult } from './doctor.ts';