@owloops/browserbird 1.0.2 → 1.0.3

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 +4748 -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
@@ -1,573 +0,0 @@
1
- /** @fileoverview Slack adapter: SocketModeClient + WebClient, streaming, and assistant APIs. */
2
-
3
- import type { Config, SlackConfig } from '../core/types.ts';
4
- import type {
5
- ChannelClient,
6
- ChannelHandle,
7
- MessageOptions,
8
- StreamHandle,
9
- StreamStartOptions,
10
- } from './types.ts';
11
- import type { ModalView } from './blocks.ts';
12
- import type { SlashCommandBody } from './commands.ts';
13
-
14
- import type { Handler } from './handler.ts';
15
-
16
- import { SocketModeClient } from '@slack/socket-mode';
17
- import { WebClient, LogLevel } from '@slack/web-api';
18
- import { createCoalescer } from './coalesce.ts';
19
- import { createHandler } from './handler.ts';
20
- import { handleSlashCommand } from './commands.ts';
21
- import { logger } from '../core/logger.ts';
22
- import { isWithinTimeRange } from '../core/utils.ts';
23
-
24
- class SlackChannelClient implements ChannelClient {
25
- private readonly web: WebClient;
26
-
27
- constructor(web: WebClient) {
28
- this.web = web;
29
- }
30
-
31
- async postMessage(
32
- channelId: string,
33
- threadTs: string,
34
- text: string,
35
- opts?: MessageOptions,
36
- ): Promise<string> {
37
- const result = await this.web.chat.postMessage({
38
- channel: channelId,
39
- thread_ts: threadTs,
40
- text,
41
- ...(opts?.blocks ? { blocks: opts.blocks } : {}),
42
- });
43
- return result.ts ?? '';
44
- }
45
-
46
- async postEphemeral(
47
- channelId: string,
48
- threadTs: string,
49
- userId: string,
50
- text: string,
51
- opts?: MessageOptions,
52
- ): Promise<void> {
53
- await this.web.chat.postEphemeral({
54
- channel: channelId,
55
- thread_ts: threadTs,
56
- user: userId,
57
- text,
58
- ...(opts?.blocks ? { blocks: opts.blocks } : {}),
59
- });
60
- }
61
-
62
- async openModal(triggerId: string, view: ModalView): Promise<void> {
63
- await this.web.views.open({ trigger_id: triggerId, view });
64
- }
65
-
66
- async uploadFile(
67
- channelId: string,
68
- threadTs: string,
69
- content: Buffer,
70
- filename: string,
71
- title: string,
72
- ): Promise<void> {
73
- const upload = await this.web.files.getUploadURLExternal({
74
- filename,
75
- length: content.byteLength,
76
- });
77
- if (!upload.upload_url || !upload.file_id) return;
78
- await fetch(upload.upload_url, { method: 'POST', body: new Uint8Array(content) });
79
- await this.web.files.completeUploadExternal({
80
- files: [{ id: upload.file_id, title }],
81
- channel_id: channelId,
82
- thread_ts: threadTs,
83
- });
84
- }
85
-
86
- startStream(opts: StreamStartOptions): StreamHandle {
87
- return this.web.chatStream({
88
- channel: opts.channelId,
89
- thread_ts: opts.threadTs,
90
- recipient_team_id: opts.teamId,
91
- recipient_user_id: opts.userId,
92
- task_display_mode: 'timeline',
93
- buffer_size: 128,
94
- });
95
- }
96
-
97
- async setStatus(channelId: string, threadTs: string, status: string): Promise<void> {
98
- await this.web.assistant.threads.setStatus({
99
- channel_id: channelId,
100
- thread_ts: threadTs,
101
- status,
102
- });
103
- }
104
-
105
- async setTitle(channelId: string, threadTs: string, title: string): Promise<void> {
106
- await this.web.assistant.threads.setTitle({
107
- channel_id: channelId,
108
- thread_ts: threadTs,
109
- title,
110
- });
111
- }
112
-
113
- async setSuggestedPrompts(
114
- channelId: string,
115
- threadTs: string,
116
- prompts: Array<{ title: string; message: string }>,
117
- ): Promise<void> {
118
- await this.web.assistant.threads.setSuggestedPrompts({
119
- channel_id: channelId,
120
- thread_ts: threadTs,
121
- prompts,
122
- });
123
- }
124
- }
125
-
126
- const IGNORED_SUBTYPES = new Set([
127
- 'bot_message',
128
- 'message_changed',
129
- 'message_deleted',
130
- 'message_replied',
131
- 'channel_join',
132
- 'channel_leave',
133
- 'channel_topic',
134
- 'channel_purpose',
135
- 'channel_name',
136
- 'channel_archive',
137
- 'channel_unarchive',
138
- 'file_share',
139
- 'pinned_item',
140
- 'unpinned_item',
141
- ]);
142
-
143
- const DEDUP_TTL_MS = 30_000;
144
- const DEDUP_CLEANUP_MS = 60_000;
145
-
146
- function logDispatchError(err: unknown): void {
147
- logger.error(`dispatch error: ${err instanceof Error ? err.message : String(err)}`);
148
- }
149
-
150
- interface SocketModeEvent {
151
- ack: (response?: unknown) => Promise<void>;
152
- envelope_id: string;
153
- body: Record<string, unknown>;
154
- event?: Record<string, unknown>;
155
- retry_num?: number;
156
- retry_reason?: string;
157
- }
158
-
159
- export function createSlackChannel(config: Config, signal: AbortSignal): ChannelHandle {
160
- const recentEvents = new Map<string, number>();
161
- let botUserId = '';
162
- let teamId = '';
163
-
164
- const socketClient = new SocketModeClient({
165
- appToken: config.slack.appToken,
166
- logLevel: LogLevel.WARN,
167
- clientPingTimeout: 15_000,
168
- serverPingTimeout: 60_000,
169
- });
170
-
171
- const webClient = new WebClient(config.slack.botToken);
172
- const channelClient = new SlackChannelClient(webClient);
173
- const handler = createHandler(channelClient, config, signal, () => teamId);
174
- const coalescer = createCoalescer(config.slack.coalesce, (dispatch) => {
175
- handler.handle(dispatch).catch(logDispatchError);
176
- });
177
-
178
- const dedupTimer = setInterval(() => {
179
- const cutoff = Date.now() - DEDUP_TTL_MS;
180
- for (const [key, ts] of recentEvents) {
181
- if (ts < cutoff) recentEvents.delete(key);
182
- }
183
- }, DEDUP_CLEANUP_MS);
184
-
185
- signal.addEventListener('abort', () => {
186
- clearInterval(dedupTimer);
187
- });
188
-
189
- function isDuplicate(body: Record<string, unknown>): boolean {
190
- const eventId = body['event_id'] as string | undefined;
191
- if (!eventId) return false;
192
- if (recentEvents.has(eventId)) return true;
193
- recentEvents.set(eventId, Date.now());
194
- return false;
195
- }
196
-
197
- socketClient.on('message', async ({ ack, body, event }: SocketModeEvent) => {
198
- await ack();
199
- if (!event) return;
200
- if (event['user'] === botUserId) return;
201
-
202
- const text = event['text'] as string | undefined;
203
- if (!text) return;
204
-
205
- const subtype = event['subtype'] as string | undefined;
206
- if (subtype && IGNORED_SUBTYPES.has(subtype)) return;
207
- if (event['bot_id']) return;
208
- if (isDuplicate(body)) return;
209
-
210
- const channelType = event['channel_type'] as string | undefined;
211
- const isDm = channelType === 'im';
212
-
213
- if (!isDm && config.slack.requireMention) return;
214
-
215
- const channelId = event['channel'] as string;
216
- if (!isChannelAllowed(channelId, config.slack.channels)) return;
217
- if (!isDm && isQuietHours(config.slack.quietHours)) return;
218
-
219
- const threadTs = (event['thread_ts'] as string | undefined) ?? (event['ts'] as string);
220
- const userId = (event['user'] as string) ?? 'unknown';
221
- const messageTs = event['ts'] as string;
222
- const cleanText = stripMention(text);
223
-
224
- if (!cleanText.trim()) return;
225
-
226
- if (isDm && config.slack.coalesce.bypassDms) {
227
- handler
228
- .handle({
229
- channelId,
230
- threadTs,
231
- messages: [{ userId, text: cleanText, timestamp: messageTs }],
232
- })
233
- .catch(logDispatchError);
234
- } else {
235
- coalescer.push(channelId, threadTs, userId, cleanText, messageTs);
236
- }
237
- });
238
-
239
- if (config.slack.requireMention) {
240
- socketClient.on('app_mention', async ({ ack, body, event }: SocketModeEvent) => {
241
- await ack();
242
- if (!event) return;
243
- if (isDuplicate(body)) return;
244
-
245
- const channelId = event['channel'] as string;
246
- if (!isChannelAllowed(channelId, config.slack.channels)) return;
247
- if (isQuietHours(config.slack.quietHours)) return;
248
-
249
- const messageTs = event['ts'] as string | undefined;
250
- if (!messageTs) return;
251
-
252
- const threadTs = (event['thread_ts'] as string | undefined) ?? messageTs;
253
- const userId = (event['user'] as string) ?? 'unknown';
254
- const text = stripMention((event['text'] as string) ?? '');
255
-
256
- if (!text.trim()) return;
257
-
258
- coalescer.push(channelId, threadTs, userId, text, messageTs);
259
- });
260
- }
261
-
262
- socketClient.on('assistant_thread_started', async ({ ack, event }: SocketModeEvent) => {
263
- await ack();
264
- if (!event) return;
265
- const threadInfo = event['assistant_thread'] as Record<string, unknown> | undefined;
266
- if (!threadInfo) return;
267
-
268
- const channelId = threadInfo['channel_id'] as string | undefined;
269
- const threadTs = threadInfo['thread_ts'] as string | undefined;
270
- if (!channelId || !threadTs) return;
271
-
272
- channelClient
273
- .setSuggestedPrompts(channelId, threadTs, [
274
- { title: 'Browse a website', message: 'Browse https://example.com and summarize it' },
275
- { title: 'Run a command', message: 'List files in the current directory' },
276
- { title: 'Help me code', message: 'Help me write a function that...' },
277
- ])
278
- .catch(() => {});
279
- });
280
-
281
- socketClient.on('assistant_thread_context_changed', async ({ ack }: SocketModeEvent) => {
282
- await ack();
283
- });
284
-
285
- let connected = false;
286
- const statusProvider = {
287
- slackConnected: () => connected,
288
- activeCount: () => handler.activeCount(),
289
- };
290
-
291
- socketClient.on('slash_commands', async ({ ack, body }: SocketModeEvent) => {
292
- await ack();
293
- const commandBody = body as unknown as SlashCommandBody;
294
- if (commandBody.command !== '/bird') return;
295
-
296
- try {
297
- await handleSlashCommand(commandBody, webClient, channelClient, config, statusProvider);
298
- } catch (err) {
299
- logger.error(`/bird command error: ${err instanceof Error ? err.message : String(err)}`);
300
- }
301
- });
302
-
303
- socketClient.on('interactive', async ({ ack, body }: SocketModeEvent) => {
304
- await ack();
305
- const interactionType = body['type'] as string | undefined;
306
-
307
- if (interactionType === 'view_submission') {
308
- const view = body['view'] as Record<string, unknown> | undefined;
309
- if (view?.['callback_id'] === 'bird_create') {
310
- await handleBirdCreateSubmission(view, webClient, config.timezone);
311
- }
312
- }
313
-
314
- if (interactionType === 'block_actions') {
315
- const actionsArr = body['actions'] as Array<Record<string, unknown>> | undefined;
316
- const channel = (body['channel'] as Record<string, unknown> | undefined)?.['id'] as
317
- | string
318
- | undefined;
319
- const user = (body['user'] as Record<string, unknown> | undefined)?.['id'] as
320
- | string
321
- | undefined;
322
- if (!actionsArr || !channel) return;
323
-
324
- for (const action of actionsArr) {
325
- if (action['action_id'] !== 'session_error_overflow') continue;
326
- const selected = (action['selected_option'] as Record<string, unknown> | undefined)?.[
327
- 'value'
328
- ] as string | undefined;
329
- if (!selected) continue;
330
-
331
- if (selected.startsWith('retry:')) {
332
- const sessionUid = selected.slice('retry:'.length);
333
- if (!sessionUid) continue;
334
- await handleSessionRetry(sessionUid, channel, user ?? 'unknown', config, handler);
335
- }
336
- }
337
- }
338
- });
339
-
340
- const MAX_CONSECUTIVE_FAILURES = 5;
341
-
342
- socketClient.on('connected', () => {
343
- connected = true;
344
- logger.success('slack connected');
345
- });
346
-
347
- socketClient.on('reconnecting', () => {
348
- connected = false;
349
- logger.info('slack reconnecting...');
350
- });
351
-
352
- socketClient.on('disconnected', () => {
353
- connected = false;
354
- logger.warn('slack disconnected');
355
- });
356
-
357
- socketClient.on('close' as string, () => {
358
- const failures = (socketClient as unknown as Record<string, unknown>)[
359
- 'numOfConsecutiveReconnectionFailures'
360
- ] as number | undefined;
361
- if (failures != null && failures > MAX_CONSECUTIVE_FAILURES) {
362
- (socketClient as unknown as Record<string, number>)['numOfConsecutiveReconnectionFailures'] =
363
- 1;
364
- logger.info('reset reconnection back-off counter');
365
- }
366
- });
367
-
368
- async function resolveChannelNames(): Promise<void> {
369
- const namesToResolve = new Set<string>();
370
-
371
- function collectNames(channels: string[]): void {
372
- for (const ch of channels) {
373
- if (ch !== '*' && !ch.startsWith('C') && !ch.startsWith('D') && !ch.startsWith('G')) {
374
- namesToResolve.add(ch);
375
- }
376
- }
377
- }
378
-
379
- collectNames(config.slack.channels);
380
- for (const agent of config.agents) {
381
- collectNames(agent.channels);
382
- }
383
- if (namesToResolve.size === 0) return;
384
-
385
- const nameToId = new Map<string, string>();
386
- try {
387
- let cursor: string | undefined;
388
- do {
389
- const result = await webClient.conversations.list({
390
- types: 'public_channel,private_channel',
391
- limit: 200,
392
- exclude_archived: true,
393
- cursor,
394
- });
395
- for (const ch of result.channels ?? []) {
396
- if (ch.name && ch.id && namesToResolve.has(ch.name)) {
397
- nameToId.set(ch.name, ch.id);
398
- }
399
- }
400
- cursor = result.response_metadata?.next_cursor || undefined;
401
- } while (cursor);
402
- } catch (err) {
403
- logger.warn(
404
- `failed to resolve channel names: ${err instanceof Error ? err.message : String(err)}`,
405
- );
406
- return;
407
- }
408
-
409
- function resolveList(channels: string[], label: string): string[] {
410
- return channels.map((ch) => {
411
- const resolved = nameToId.get(ch);
412
- if (resolved) {
413
- logger.info(`${label}: resolved channel "${ch}" -> ${resolved}`);
414
- return resolved;
415
- }
416
- if (namesToResolve.has(ch)) {
417
- logger.warn(`${label}: channel "${ch}" not found in workspace`);
418
- }
419
- return ch;
420
- });
421
- }
422
-
423
- config.slack.channels = resolveList(config.slack.channels, 'slack');
424
- for (const agent of config.agents) {
425
- agent.channels = resolveList(agent.channels, `agent "${agent.id}"`);
426
- }
427
- }
428
-
429
- async function start(): Promise<void> {
430
- const authResult = await webClient.auth.test();
431
- botUserId = (authResult.user_id as string) ?? '';
432
- teamId = (authResult.team_id as string) ?? '';
433
- logger.info(`authenticated as ${authResult.user} (team: ${teamId})`);
434
-
435
- await resolveChannelNames();
436
-
437
- await socketClient.start();
438
- connected = true;
439
- }
440
-
441
- async function stop(): Promise<void> {
442
- connected = false;
443
- coalescer.destroy();
444
- handler.killAll();
445
- clearInterval(dedupTimer);
446
- await socketClient.disconnect();
447
- logger.info('slack stopped');
448
- }
449
-
450
- function isConnected(): boolean {
451
- return connected;
452
- }
453
-
454
- async function postMessage(channel: string, text: string, opts?: MessageOptions): Promise<void> {
455
- await webClient.chat.postMessage({
456
- channel,
457
- text,
458
- ...(opts?.blocks ? { blocks: opts.blocks } : {}),
459
- });
460
- }
461
-
462
- return { start, stop, isConnected, activeCount: () => handler.activeCount(), postMessage };
463
- }
464
-
465
- async function handleBirdCreateSubmission(
466
- view: Record<string, unknown>,
467
- webClient: WebClient,
468
- defaultTimezone: string,
469
- ): Promise<void> {
470
- try {
471
- const values = view['state'] as Record<string, unknown> | undefined;
472
- const stateValues = (values?.['values'] ?? {}) as Record<
473
- string,
474
- Record<string, Record<string, unknown>>
475
- >;
476
-
477
- const name = (stateValues['bird_name']?.['name_input']?.['value'] as string | undefined) ?? '';
478
- const schedule = (
479
- stateValues['bird_schedule']?.['schedule_select']?.['selected_option'] as
480
- | Record<string, unknown>
481
- | undefined
482
- )?.['value'] as string | undefined;
483
- const prompt =
484
- (stateValues['bird_prompt']?.['prompt_input']?.['value'] as string | undefined) ?? '';
485
- const channelId =
486
- (stateValues['bird_channel']?.['channel_select']?.['selected_conversation'] as
487
- | string
488
- | undefined) ?? '';
489
- const enabledValue = (
490
- stateValues['bird_enabled']?.['enabled_radio']?.['selected_option'] as
491
- | Record<string, unknown>
492
- | undefined
493
- )?.['value'] as string | undefined;
494
-
495
- if (!name || !schedule || !prompt) {
496
- logger.warn('bird_create submission missing required fields');
497
- return;
498
- }
499
-
500
- const { createCronJob, setCronJobEnabled } = await import('../db/index.ts');
501
- const bird = createCronJob(
502
- name,
503
- schedule,
504
- prompt,
505
- channelId || undefined,
506
- 'default',
507
- defaultTimezone,
508
- );
509
- if (enabledValue !== 'enabled') {
510
- setCronJobEnabled(bird.uid, false);
511
- }
512
-
513
- await webClient.chat.postMessage({
514
- channel: channelId || 'general',
515
- text: `Bird *${name}* created. Schedule: \`${schedule}\``,
516
- });
517
-
518
- logger.info(`bird created via modal: ${name}`);
519
- } catch (err) {
520
- logger.error(
521
- `bird_create submission error: ${err instanceof Error ? err.message : String(err)}`,
522
- );
523
- }
524
- }
525
-
526
- async function handleSessionRetry(
527
- sessionUid: string,
528
- channelId: string,
529
- userId: string,
530
- config: Config,
531
- handler: Handler,
532
- ): Promise<void> {
533
- try {
534
- const { getSession, getLastInboundMessage } = await import('../db/index.ts');
535
- const session = getSession(sessionUid);
536
- if (!session) {
537
- logger.warn(`retry: session ${sessionUid} not found`);
538
- return;
539
- }
540
-
541
- const lastMsg = getLastInboundMessage(session.channel_id, session.thread_id);
542
- if (!lastMsg) {
543
- logger.warn(`retry: no inbound message for session ${sessionUid}`);
544
- return;
545
- }
546
-
547
- handler
548
- .handle({
549
- channelId: session.channel_id,
550
- threadTs: session.thread_id ?? lastMsg.timestamp,
551
- messages: [{ userId, text: lastMsg.content, timestamp: lastMsg.timestamp }],
552
- })
553
- .catch(logDispatchError);
554
-
555
- logger.info(`retry: session ${sessionUid} re-dispatched by ${userId}`);
556
- } catch (err) {
557
- logger.error(`retry error: ${err instanceof Error ? err.message : String(err)}`);
558
- }
559
- }
560
-
561
- function isChannelAllowed(channelId: string, channels: string[]): boolean {
562
- if (channels.includes('*')) return true;
563
- return channels.includes(channelId);
564
- }
565
-
566
- function isQuietHours(quietHours: SlackConfig['quietHours']): boolean {
567
- if (!quietHours.enabled) return false;
568
- return isWithinTimeRange(quietHours.start, quietHours.end, new Date(), quietHours.timezone);
569
- }
570
-
571
- function stripMention(text: string): string {
572
- return text.replace(/^\s*<@[A-Z0-9]+>\s*/i, '').trim();
573
- }
@@ -1,59 +0,0 @@
1
- /** @fileoverview Platform-agnostic channel client interface. */
2
-
3
- import type { Block, ModalView } from './blocks.ts';
4
-
5
- export interface MessageOptions {
6
- blocks?: Block[];
7
- }
8
-
9
- export interface StreamHandle {
10
- append(args: { markdown_text?: string }): Promise<unknown>;
11
- stop(args?: { blocks?: Block[] }): Promise<unknown>;
12
- }
13
-
14
- export interface StreamStartOptions {
15
- channelId: string;
16
- threadTs: string;
17
- teamId: string;
18
- userId: string;
19
- }
20
-
21
- export interface ChannelClient {
22
- postMessage(
23
- channelId: string,
24
- threadTs: string,
25
- text: string,
26
- opts?: MessageOptions,
27
- ): Promise<string>;
28
- postEphemeral(
29
- channelId: string,
30
- threadTs: string,
31
- userId: string,
32
- text: string,
33
- opts?: MessageOptions,
34
- ): Promise<void>;
35
- uploadFile(
36
- channelId: string,
37
- threadTs: string,
38
- content: Buffer,
39
- filename: string,
40
- title: string,
41
- ): Promise<void>;
42
- startStream(opts: StreamStartOptions): StreamHandle;
43
- openModal?(triggerId: string, view: ModalView): Promise<void>;
44
- setStatus?(channelId: string, threadTs: string, status: string): Promise<void>;
45
- setTitle?(channelId: string, threadTs: string, title: string): Promise<void>;
46
- setSuggestedPrompts?(
47
- channelId: string,
48
- threadTs: string,
49
- prompts: Array<{ title: string; message: string }>,
50
- ): Promise<void>;
51
- }
52
-
53
- export interface ChannelHandle {
54
- start(): Promise<void>;
55
- stop(): Promise<void>;
56
- isConnected(): boolean;
57
- activeCount(): number;
58
- postMessage(channel: string, text: string, opts?: MessageOptions): Promise<void>;
59
- }
package/src/cli/banner.ts DELETED
@@ -1,10 +0,0 @@
1
- /** @fileoverview ASCII banner displayed on daemon startup and in help text. */
2
-
3
- import { createRequire } from 'node:module';
4
-
5
- const require = createRequire(import.meta.url);
6
- export const VERSION: string = (require('../../package.json') as { version: string }).version;
7
-
8
- const BIRD = [' .__.', ' ( ^>', ' / )\\', ' <_/_/', ' " "'].join('\n');
9
-
10
- export const BANNER = BIRD;