botinabox 0.5.1 → 0.5.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.
@@ -1,296 +1,13 @@
1
- // src/connectors/google/oauth.ts
2
- var _google;
3
- async function getGoogle() {
4
- if (!_google) {
5
- try {
6
- const mod = await import("googleapis");
7
- _google = mod.google;
8
- } catch {
9
- throw new Error(
10
- "googleapis is required for Google connectors. Install it: npm install googleapis"
11
- );
12
- }
13
- }
14
- return _google;
15
- }
16
- async function createOAuth2Client(config) {
17
- const google = await getGoogle();
18
- return new google.auth.OAuth2(
19
- config.clientId,
20
- config.clientSecret,
21
- config.redirectUri
22
- );
23
- }
24
- function getAuthUrl(client, scopes) {
25
- return client.generateAuthUrl({
26
- access_type: "offline",
27
- prompt: "consent",
28
- scope: scopes
29
- });
30
- }
31
- async function exchangeCode(client, code) {
32
- const { tokens } = await client.getToken(code);
33
- return tokens;
34
- }
35
- async function loadTokens(getter, accountKey) {
36
- const raw = await getter(`google_tokens:${accountKey}`);
37
- if (!raw) return null;
38
- try {
39
- return JSON.parse(raw);
40
- } catch {
41
- return null;
42
- }
43
- }
44
- async function saveTokens(setter, accountKey, tokens) {
45
- await setter(`google_tokens:${accountKey}`, JSON.stringify(tokens));
46
- }
47
- async function refreshIfNeeded(client, tokens, saver) {
48
- const buffer = 6e4;
49
- const isExpired = tokens.expiry_date != null && Date.now() >= tokens.expiry_date - buffer;
50
- if (!isExpired) return tokens;
51
- client.setCredentials(tokens);
52
- const { credentials } = await client.refreshAccessToken();
53
- const refreshed = {
54
- access_token: credentials.access_token,
55
- refresh_token: credentials.refresh_token ?? tokens.refresh_token,
56
- expiry_date: credentials.expiry_date ?? void 0,
57
- token_type: credentials.token_type ?? "Bearer"
58
- };
59
- if (saver) {
60
- await saver(refreshed);
61
- }
62
- return refreshed;
63
- }
64
-
65
- // src/connectors/google/gmail-connector.ts
66
- var GoogleGmailConnector = class {
67
- id = "google-gmail";
68
- meta = {
69
- displayName: "Google Gmail",
70
- provider: "google",
71
- dataType: "email"
72
- };
73
- tokenLoader;
74
- tokenSaver;
75
- client = null;
76
- config = null;
77
- tokens = null;
78
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
79
- gmail = null;
80
- constructor(opts) {
81
- this.tokenLoader = opts.tokenLoader;
82
- this.tokenSaver = opts.tokenSaver;
83
- }
84
- // ── Lifecycle ──────────────────────────────────────────────────
85
- async connect(config) {
86
- this.config = config;
87
- this.client = await createOAuth2Client(config.oauth);
88
- this.tokens = await loadTokens(this.tokenLoader, config.account);
89
- if (!this.tokens) {
90
- throw new Error(
91
- `No stored tokens for account ${config.account}. Complete the OAuth flow first.`
92
- );
93
- }
94
- this.tokens = await refreshIfNeeded(
95
- this.client,
96
- this.tokens,
97
- async (t) => saveTokens(this.tokenSaver, config.account, t)
98
- );
99
- this.client.setCredentials(this.tokens);
100
- const { google } = await import("googleapis");
101
- this.gmail = google.gmail({ version: "v1", auth: this.client });
102
- }
103
- async disconnect() {
104
- this.client = null;
105
- this.gmail = null;
106
- this.tokens = null;
107
- this.config = null;
108
- }
109
- async healthCheck() {
110
- try {
111
- this.ensureConnected();
112
- const res = await this.gmail.users.getProfile({ userId: "me" });
113
- return { ok: true, account: res.data.emailAddress };
114
- } catch (err) {
115
- return { ok: false, error: errorMessage(err) };
116
- }
117
- }
118
- // ── Sync ───────────────────────────────────────────────────────
119
- async sync(options) {
120
- this.ensureConnected();
121
- if (options?.cursor) {
122
- return this.syncIncremental(options.cursor, options.limit);
123
- }
124
- return this.syncFull(options);
125
- }
126
- /** Incremental sync using Gmail history API. */
127
- async syncIncremental(startHistoryId, limit) {
128
- const records = [];
129
- const errors = [];
130
- const seenIds = /* @__PURE__ */ new Set();
131
- let pageToken;
132
- let latestHistoryId = startHistoryId;
133
- do {
134
- const res = await this.gmail.users.history.list({
135
- userId: "me",
136
- startHistoryId,
137
- historyTypes: ["messageAdded"],
138
- ...pageToken ? { pageToken } : {}
139
- });
140
- latestHistoryId = res.data.historyId ?? latestHistoryId;
141
- const histories = res.data.history ?? [];
142
- for (const h of histories) {
143
- for (const added of h.messagesAdded ?? []) {
144
- const msgId = added.message?.id;
145
- if (!msgId || seenIds.has(msgId)) continue;
146
- seenIds.add(msgId);
147
- try {
148
- const record = await this.fetchMessage(msgId);
149
- records.push(record);
150
- } catch (err) {
151
- errors.push({ id: msgId, error: errorMessage(err) });
152
- }
153
- if (limit && records.length >= limit) {
154
- return { records, cursor: latestHistoryId, hasMore: true, errors };
155
- }
156
- }
157
- }
158
- pageToken = res.data.nextPageToken ?? void 0;
159
- } while (pageToken);
160
- return { records, cursor: latestHistoryId, hasMore: false, errors };
161
- }
162
- /** Full sync — list messages and fetch each one. */
163
- async syncFull(options) {
164
- const records = [];
165
- const errors = [];
166
- const maxResults = options?.limit ?? 100;
167
- let query = "";
168
- if (options?.since) {
169
- const epoch = Math.floor(new Date(options.since).getTime() / 1e3);
170
- query = `after:${epoch}`;
171
- }
172
- if (options?.filters?.q) {
173
- query = query ? `${query} ${options.filters.q}` : String(options.filters.q);
174
- }
175
- let pageToken;
176
- let collected = 0;
177
- do {
178
- const res = await this.gmail.users.messages.list({
179
- userId: "me",
180
- maxResults: Math.min(maxResults - collected, 100),
181
- ...query ? { q: query } : {},
182
- ...pageToken ? { pageToken } : {}
183
- });
184
- const messages = res.data.messages ?? [];
185
- for (const msg of messages) {
186
- try {
187
- const record = await this.fetchMessage(msg.id);
188
- records.push(record);
189
- } catch (err) {
190
- errors.push({ id: msg.id, error: errorMessage(err) });
191
- }
192
- collected++;
193
- if (collected >= maxResults) break;
194
- }
195
- pageToken = res.data.nextPageToken ?? void 0;
196
- } while (pageToken && collected < maxResults);
197
- const profile = await this.gmail.users.getProfile({ userId: "me" });
198
- const cursor = profile.data.historyId ?? void 0;
199
- return {
200
- records,
201
- cursor,
202
- hasMore: !!pageToken,
203
- errors
204
- };
205
- }
206
- // ── Push (send email) ─────────────────────────────────────────
207
- async push(payload) {
208
- this.ensureConnected();
209
- try {
210
- const toHeader = payload.to.map(formatAddress).join(", ");
211
- const ccHeader = payload.cc.length ? `Cc: ${payload.cc.map(formatAddress).join(", ")}\r
212
- ` : "";
213
- const bccHeader = payload.bcc.length ? `Bcc: ${payload.bcc.map(formatAddress).join(", ")}\r
214
- ` : "";
215
- const mime = [
216
- `To: ${toHeader}\r
217
- `,
218
- ccHeader,
219
- bccHeader,
220
- `Subject: ${payload.subject}\r
221
- `,
222
- `Content-Type: text/plain; charset="UTF-8"\r
223
- `,
224
- `\r
225
- `,
226
- payload.body ?? ""
227
- ].join("");
228
- const encoded = Buffer.from(mime).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
229
- const res = await this.gmail.users.messages.send({
230
- userId: "me",
231
- requestBody: { raw: encoded }
232
- });
233
- return { success: true, externalId: res.data.id };
234
- } catch (err) {
235
- return { success: false, error: errorMessage(err) };
236
- }
237
- }
238
- // ── Internals ─────────────────────────────────────────────────
239
- ensureConnected() {
240
- if (!this.gmail || !this.config) {
241
- throw new Error("GoogleGmailConnector is not connected. Call connect() first.");
242
- }
243
- }
244
- /** Fetch a single message by ID and parse into an EmailRecord. */
245
- async fetchMessage(messageId) {
246
- const res = await this.gmail.users.messages.get({
247
- userId: "me",
248
- id: messageId,
249
- format: "metadata",
250
- metadataHeaders: ["From", "To", "Cc", "Bcc", "Subject", "Date"]
251
- });
252
- const msg = res.data;
253
- const headers = msg.payload?.headers ?? [];
254
- const getHeader = (name) => headers.find((h) => h.name.toLowerCase() === name.toLowerCase())?.value ?? "";
255
- return {
256
- gmailId: msg.id,
257
- threadId: msg.threadId,
258
- account: this.config.account,
259
- subject: getHeader("Subject"),
260
- from: parseAddress(getHeader("From")),
261
- to: parseAddressList(getHeader("To")),
262
- cc: parseAddressList(getHeader("Cc")),
263
- bcc: parseAddressList(getHeader("Bcc")),
264
- date: new Date(getHeader("Date")).toISOString(),
265
- snippet: msg.snippet ?? "",
266
- labels: msg.labelIds ?? [],
267
- isRead: !(msg.labelIds ?? []).includes("UNREAD")
268
- };
269
- }
270
- };
271
- function parseAddress(raw) {
272
- const match = raw.match(/^(.+?)\s*<([^>]+)>$/);
273
- if (match) {
274
- return { name: match[1].replace(/^["']|["']$/g, "").trim(), email: match[2] };
275
- }
276
- return { email: raw.trim() };
277
- }
278
- function parseAddressList(raw) {
279
- if (!raw.trim()) return [];
280
- const results = [];
281
- const parts = raw.split(/,(?=(?:[^<]*<[^>]*>)*[^>]*$)/);
282
- for (const part of parts) {
283
- const trimmed = part.trim();
284
- if (trimmed) results.push(parseAddress(trimmed));
285
- }
286
- return results;
287
- }
288
- function formatAddress(addr) {
289
- return addr.name ? `${addr.name} <${addr.email}>` : addr.email;
290
- }
291
- function errorMessage(err) {
292
- return err instanceof Error ? err.message : String(err);
293
- }
1
+ import {
2
+ GoogleGmailConnector,
3
+ createOAuth2Client,
4
+ createServiceAccountClient,
5
+ exchangeCode,
6
+ getAuthUrl,
7
+ loadTokens,
8
+ refreshIfNeeded,
9
+ saveTokens
10
+ } from "../../chunk-3X3YKI4T.js";
294
11
 
295
12
  // src/connectors/google/calendar-connector.ts
296
13
  var GoogleCalendarConnector = class {
@@ -307,26 +24,38 @@ var GoogleCalendarConnector = class {
307
24
  tokens = null;
308
25
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
309
26
  calendar = null;
310
- constructor(opts) {
27
+ constructor(opts = {}) {
311
28
  this.tokenLoader = opts.tokenLoader;
312
29
  this.tokenSaver = opts.tokenSaver;
313
30
  }
314
31
  // ── Lifecycle ──────────────────────────────────────────────────
315
32
  async connect(config) {
316
33
  this.config = config;
317
- this.client = await createOAuth2Client(config.oauth);
318
- this.tokens = await loadTokens(this.tokenLoader, config.account);
319
- if (!this.tokens) {
320
- throw new Error(
321
- `No stored tokens for account ${config.account}. Complete the OAuth flow first.`
34
+ const scopes = config.scopes ?? [
35
+ "https://www.googleapis.com/auth/calendar.readonly"
36
+ ];
37
+ if (config.serviceAccount) {
38
+ this.client = await createServiceAccountClient(config.serviceAccount, scopes);
39
+ } else if (config.oauth) {
40
+ this.client = await createOAuth2Client(config.oauth);
41
+ if (!this.tokenLoader) {
42
+ throw new Error("tokenLoader required for OAuth2 flow");
43
+ }
44
+ this.tokens = await loadTokens(this.tokenLoader, config.account);
45
+ if (!this.tokens) {
46
+ throw new Error(
47
+ `No stored tokens for account ${config.account}. Complete the OAuth flow first.`
48
+ );
49
+ }
50
+ this.tokens = await refreshIfNeeded(
51
+ this.client,
52
+ this.tokens,
53
+ this.tokenSaver ? async (t) => saveTokens(this.tokenSaver, config.account, t) : void 0
322
54
  );
55
+ this.client.setCredentials(this.tokens);
56
+ } else {
57
+ throw new Error("Either serviceAccount or oauth config is required");
323
58
  }
324
- this.tokens = await refreshIfNeeded(
325
- this.client,
326
- this.tokens,
327
- async (t) => saveTokens(this.tokenSaver, config.account, t)
328
- );
329
- this.client.setCredentials(this.tokens);
330
59
  const { google } = await import("googleapis");
331
60
  this.calendar = google.calendar({ version: "v3", auth: this.client });
332
61
  }
@@ -346,7 +75,37 @@ var GoogleCalendarConnector = class {
346
75
  );
347
76
  return { ok: true, account: primary?.id ?? this.config.account };
348
77
  } catch (err) {
349
- return { ok: false, error: errorMessage2(err) };
78
+ return { ok: false, error: errorMessage(err) };
79
+ }
80
+ }
81
+ // ── Auth ───────────────────────────────────────────────────────
82
+ async authenticate(codeProvider) {
83
+ if (!this.config) {
84
+ return { success: false, error: "Call connect() first to set config, or pass config and call authenticate() before connect()." };
85
+ }
86
+ try {
87
+ if (!this.config.oauth) {
88
+ return { success: false, error: "OAuth config required for browser-based authenticate(). Use serviceAccount for headless auth." };
89
+ }
90
+ if (!this.tokenSaver) {
91
+ return { success: false, error: "tokenSaver required for authenticate() flow." };
92
+ }
93
+ const client = await createOAuth2Client(this.config.oauth);
94
+ const scopes = this.config.scopes ?? [
95
+ "https://www.googleapis.com/auth/calendar.readonly"
96
+ ];
97
+ const authUrl = getAuthUrl(client, scopes);
98
+ const code = await codeProvider(authUrl);
99
+ const tokens = await exchangeCode(client, code);
100
+ await saveTokens(this.tokenSaver, this.config.account, tokens);
101
+ this.tokens = tokens;
102
+ this.client = client;
103
+ this.client.setCredentials(tokens);
104
+ const { google } = await import("googleapis");
105
+ this.calendar = google.calendar({ version: "v3", auth: this.client });
106
+ return { success: true, account: this.config.account };
107
+ } catch (err) {
108
+ return { success: false, error: errorMessage(err) };
350
109
  }
351
110
  }
352
111
  // ── Sync ───────────────────────────────────────────────────────
@@ -376,7 +135,7 @@ var GoogleCalendarConnector = class {
376
135
  try {
377
136
  records.push(this.mapEvent(event, calendarId));
378
137
  } catch (err) {
379
- errors.push({ id: event.id, error: errorMessage2(err) });
138
+ errors.push({ id: event.id, error: errorMessage(err) });
380
139
  }
381
140
  if (options?.limit && records.length >= options.limit) break;
382
141
  }
@@ -418,7 +177,7 @@ var GoogleCalendarConnector = class {
418
177
  try {
419
178
  records.push(this.mapEvent(event, calendarId));
420
179
  } catch (err) {
421
- errors.push({ id: event.id, error: errorMessage2(err) });
180
+ errors.push({ id: event.id, error: errorMessage(err) });
422
181
  }
423
182
  if (records.length >= maxResults) break;
424
183
  }
@@ -472,13 +231,14 @@ var GoogleCalendarConnector = class {
472
231
  };
473
232
  }
474
233
  };
475
- function errorMessage2(err) {
234
+ function errorMessage(err) {
476
235
  return err instanceof Error ? err.message : String(err);
477
236
  }
478
237
  export {
479
238
  GoogleCalendarConnector,
480
239
  GoogleGmailConnector,
481
240
  createOAuth2Client,
241
+ createServiceAccountClient,
482
242
  exchangeCode,
483
243
  getAuthUrl,
484
244
  loadTokens,
@@ -0,0 +1,6 @@
1
+ import {
2
+ GoogleGmailConnector
3
+ } from "./chunk-DSNJKNEW.js";
4
+ export {
5
+ GoogleGmailConnector
6
+ };
@@ -0,0 +1,6 @@
1
+ import {
2
+ GoogleGmailConnector
3
+ } from "./chunk-3X3YKI4T.js";
4
+ export {
5
+ GoogleGmailConnector
6
+ };
@@ -0,0 +1,6 @@
1
+ import {
2
+ GoogleGmailConnector
3
+ } from "./chunk-D47AIFOD.js";
4
+ export {
5
+ GoogleGmailConnector
6
+ };
package/dist/index.d.ts CHANGED
@@ -2,8 +2,8 @@ import { C as ChannelAdapter, H as HealthStatus, I as InboundMessage } from './c
2
2
  export { A as Attachment, a as ChannelCapabilities, b as ChannelConfig, c as ChannelMeta, d as ChatType, F as FormattingMode, O as OutboundPayload, S as SendResult } from './channel-06G0vbIn.js';
3
3
  import { T as TokenUsage, L as LLMProvider, M as ModelInfo, R as ResolvedModel, C as ChatMessage } from './provider-qqJYv9nv.js';
4
4
  export { a as ChatParams, b as ChatResult, c as ContentBlock, d as ToolDefinition, e as ToolUse } from './provider-qqJYv9nv.js';
5
- import { C as ConnectorConfig } from './connector-DDahQw-2.js';
6
- export { a as Connector, b as ConnectorMeta, P as PushResult, S as SyncOptions, c as SyncResult } from './connector-DDahQw-2.js';
5
+ import { C as ConnectorConfig } from './connector-B4Mj0P1b.js';
6
+ export { A as AuthResult, a as Connector, b as ConnectorMeta, P as PushResult, S as SyncOptions, c as SyncResult } from './connector-B4Mj0P1b.js';
7
7
  import * as better_sqlite3 from 'better-sqlite3';
8
8
 
9
9
  /** Execution adapter types — Story 1.5 / 3.4 / 3.5 */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "botinabox",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "description": "Bot in a Box — framework for building multi-agent bots",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -42,10 +42,18 @@
42
42
  "bin": {
43
43
  "botinabox": "./bin/botinabox.mjs"
44
44
  },
45
+ "files": [
46
+ "dist",
47
+ "bin"
48
+ ],
49
+ "engines": {
50
+ "node": ">=18"
51
+ },
45
52
  "scripts": {
46
53
  "build": "tsup",
47
54
  "test": "vitest run",
48
- "typecheck": "tsc --noEmit"
55
+ "typecheck": "tsc --noEmit",
56
+ "prepublishOnly": "npm run build && npm run typecheck && npm test"
49
57
  },
50
58
  "dependencies": {
51
59
  "@types/uuid": "^10.0.0",
package/CHANGELOG.md DELETED
@@ -1,68 +0,0 @@
1
- # Changelog
2
-
3
- All notable changes to `botinabox` are documented here.
4
-
5
- Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versioning: [SemVer](https://semver.org/).
6
-
7
- ---
8
-
9
- ## [0.5.1] — 2026-04-04
10
-
11
- ### Fixed
12
-
13
- - **cron-parser ESM import** — cron-parser v4 is CommonJS-only; fixed named import to default import (`import cronParser from "cron-parser"`).
14
-
15
- ## [0.5.0] — 2026-04-04
16
-
17
- ### Added
18
-
19
- - **Connector interface** — Generic `Connector<T>` abstraction for external service integrations (Gmail, Calendar, Trello, Jira, Salesforce, etc.). Pull-based `sync()` returns typed records; optional `push()` writes back. Connectors produce data — consumers decide where to store it. New `connectors` config key in `BotConfig`.
20
- - **Google connectors** — `GoogleGmailConnector` and `GoogleCalendarConnector` implementing `Connector<EmailRecord>` and `Connector<CalendarEventRecord>`. Incremental sync (Gmail historyId, Calendar syncToken), full sync with pagination, email sending via `push()`. OAuth2 helpers with callback-based token persistence. Exported from `botinabox/google`. `googleapis` as optional peer dependency.
21
- - **Scheduler** — Database-backed `Scheduler` class with `schedules` core table. Supports recurring (cron expressions via `cron-parser`) and one-time schedules. Hook-based actions: when a schedule fires, it emits the configured action as a hook event. Methods: `register()`, `update()`, `unregister()`, `list()`, `tick()`.
22
- - **`schedules` core table** — id, name, type (recurring/one_time), cron, run_at, timezone, enabled, action, action_config, last_fired_at, next_fire_at.
23
-
24
- ### Deprecated
25
-
26
- - **HeartbeatScheduler** — Replaced by `Scheduler`. HeartbeatScheduler uses in-memory `setInterval` which loses state on restart. Kept for backward compatibility but marked `@deprecated`.
27
-
28
- ### Dependencies
29
-
30
- - Added `cron-parser` ^4.9.0.
31
- - Added `googleapis` >=140.0.0 as optional peer dependency.
32
-
33
- ## [0.3.0] — 2026-04-03
34
-
35
- ### Added
36
-
37
- - **Domain tables** — `defineDomainTables(db, options?)` creates standard multi-agent app tables: org, project, client, invoice, repository, file, channel, rule, event + junction tables. Configurable: disable clients, repos, files, channels, rules, or events.
38
- - **Domain entity contexts** — `defineDomainEntityContexts(db, options?)` renders per-entity context directories for all domain tables. Projects get REPOS.md + RULES.md. Clients get REPOS.md + AGENTS.md + INVOICES.md.
39
- - **Claude stream parser** — `parseClaudeStream(stdout)` parses Claude CLI NDJSON output into structured results (session, model, cost, tokens, text, errors). Plus `isMaxTurns()`, `isLoginRequired()`, `deactivateLocalImagePaths()`.
40
- - **Process env builder** — `buildProcessEnv(allowedKeys?, inject?)` creates a clean subprocess environment with only safe variables. Strips all secrets.
41
-
42
- ## [0.2.0] — 2026-04-03
43
-
44
- ### Added
45
-
46
- - **Users primitive** — `users` and `user_identities` core tables. Users are protected objects (never auto-rendered into other entities' context). `UserRegistry` class: `register()`, `getById()`, `getByEmail()`, `resolveByIdentity()`, `resolveOrCreate()`, `addIdentity()`.
47
- - **Secrets primitive** — `secrets` core table for encrypted credential storage. Protected by default. `SecretStore` class: `set()`, `get()`, `getMeta()`, `list()`, `rotate()`, `delete()`.
48
- - **Message pipeline user resolution** — `MessagePipeline` accepts optional `UserRegistry`. When provided, resolves `InboundMessage.from` to a user ID via `resolveOrCreate()` before task creation. `InboundMessage.userId` field added.
49
- - **`user_id` on messages table** — Tracks resolved user alongside raw `peer_id`.
50
- - **Protected/encrypted passthrough** — `EntityContextDef` now supports `protected` and `encrypted` fields, passed through to Lattice's entity context system.
51
-
52
- ### Changed
53
-
54
- - Core table count: 15 → 18 (added `users`, `user_identities`, `secrets`).
55
- - `messages` table gains `user_id` column.
56
-
57
- ## [0.1.1] — 2026-03-28
58
-
59
- ### Fixed
60
-
61
- - Initial release bug fixes and stability improvements.
62
-
63
- ## [0.1.0] — 2026-03-25
64
-
65
- ### Added
66
-
67
- - Initial release: DataStore, HookBus, AgentRegistry, TaskQueue, RunManager, WakeupQueue, BudgetController, WorkflowEngine, SessionManager, ChannelRegistry, MessagePipeline.
68
- - 15 core tables, LLM provider routing, channel adapters (Slack, Discord, Webhook).
package/CONTRIBUTING.md DELETED
@@ -1,92 +0,0 @@
1
- # Contributing
2
-
3
- Thanks for your interest in contributing to Bot in a Box.
4
-
5
- ## Development Setup
6
-
7
- ```bash
8
- git clone https://github.com/automated-industries/botinabox.git
9
- cd botinabox
10
- pnpm install
11
- pnpm build
12
- ```
13
-
14
- ## Project Structure
15
-
16
- This is a pnpm monorepo. Packages are in `packages/`:
17
-
18
- - `shared/` — Types and constants (zero dependencies)
19
- - `core/` — Core framework
20
- - `cli/` — CLI scaffolding tool
21
- - `providers/` — LLM provider adapters
22
- - `channels/` — Messaging channel adapters
23
-
24
- ## Running Tests
25
-
26
- ```bash
27
- # All packages
28
- pnpm test:run
29
-
30
- # Single package
31
- cd packages/core && pnpm test
32
- ```
33
-
34
- Tests use [Vitest](https://vitest.dev/). Each package has its own `vitest.config.ts`.
35
-
36
- ## Building
37
-
38
- ```bash
39
- # All packages
40
- pnpm build
41
-
42
- # Single package
43
- cd packages/core && pnpm build
44
- ```
45
-
46
- Build uses [tsup](https://tsup.egoist.dev/) targeting ESM with declaration files.
47
-
48
- ## Type Checking
49
-
50
- ```bash
51
- pnpm typecheck
52
- ```
53
-
54
- ## Code Style
55
-
56
- - TypeScript with strict mode
57
- - ESM modules (`"type": "module"`)
58
- - ES2022 target
59
- - No default exports except for provider/channel factory functions
60
-
61
- ## Making Changes
62
-
63
- 1. Create a branch from `main`
64
- 2. Make your changes
65
- 3. Add or update tests
66
- 4. Run `pnpm test:run` and `pnpm typecheck`
67
- 5. Open a pull request
68
-
69
- ## Adding a Provider
70
-
71
- 1. Create `packages/providers/your-provider/`
72
- 2. Implement the `LLMProvider` interface from `@botinabox/shared`
73
- 3. Export a default factory function
74
- 4. Add `"botinabox": { "type": "provider" }` to `package.json`
75
- 5. Add tests
76
-
77
- ## Adding a Channel Adapter
78
-
79
- 1. Create `packages/channels/your-channel/`
80
- 2. Implement the `ChannelAdapter` interface from `@botinabox/shared`
81
- 3. Export a default factory function
82
- 4. Add `"botinabox": { "type": "channel" }` to `package.json`
83
- 5. Add tests
84
-
85
- ## Reporting Issues
86
-
87
- Open an issue on GitHub with:
88
-
89
- - Steps to reproduce
90
- - Expected behavior
91
- - Actual behavior
92
- - Node.js and pnpm versions