botinabox 0.2.5 → 0.3.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.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,15 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versioning: [S
6
6
 
7
7
  ---
8
8
 
9
+ ## [0.3.0] — 2026-04-03
10
+
11
+ ### Added
12
+
13
+ - **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.
14
+ - **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.
15
+ - **Claude stream parser** — `parseClaudeStream(stdout)` parses Claude CLI NDJSON output into structured results (session, model, cost, tokens, text, errors). Plus `isMaxTurns()`, `isLoginRequired()`, `deactivateLocalImagePaths()`.
16
+ - **Process env builder** — `buildProcessEnv(allowedKeys?, inject?)` creates a clean subprocess environment with only safe variables. Strips all secrets.
17
+
9
18
  ## [0.2.0] — 2026-04-03
10
19
 
11
20
  ### Added
package/dist/index.js CHANGED
@@ -830,14 +830,14 @@ var SessionManager = class {
830
830
  async save(agentId, channelId, peerId, params) {
831
831
  const existing = await this._find(agentId, channelId, peerId);
832
832
  if (existing) {
833
- await this.db.update("messages", { id: existing["id"] }, {
833
+ await this.db.update("sessions", { id: existing["id"] }, {
834
834
  context: JSON.stringify(params),
835
835
  last_message_at: (/* @__PURE__ */ new Date()).toISOString(),
836
836
  message_count: (existing["message_count"] ?? 0) + 1
837
837
  });
838
838
  return existing["id"];
839
839
  } else {
840
- const row = await this.db.insert("messages", {
840
+ const row = await this.db.insert("sessions", {
841
841
  agent_id: agentId,
842
842
  channel: channelId,
843
843
  peer_id: peerId,
@@ -857,7 +857,7 @@ var SessionManager = class {
857
857
  async clear(agentId, channelId, peerId) {
858
858
  const session = await this._find(agentId, channelId, peerId);
859
859
  if (session) {
860
- await this.db.delete("messages", { id: session["id"] });
860
+ await this.db.delete("sessions", { id: session["id"] });
861
861
  }
862
862
  }
863
863
  async shouldClear(session, opts) {
@@ -876,7 +876,7 @@ var SessionManager = class {
876
876
  return false;
877
877
  }
878
878
  async _find(agentId, channelId, peerId) {
879
- const rows = await this.db.query("messages", {
879
+ const rows = await this.db.query("sessions", {
880
880
  where: { agent_id: agentId, channel: channelId, peer_id: peerId }
881
881
  });
882
882
  return rows[0] ?? void 0;
@@ -1369,20 +1369,41 @@ function defineCoreTables(db) {
1369
1369
  ]
1370
1370
  });
1371
1371
  db.define("messages", {
1372
+ columns: {
1373
+ id: "TEXT PRIMARY KEY",
1374
+ channel: "TEXT NOT NULL DEFAULT 'slack'",
1375
+ direction: "TEXT NOT NULL DEFAULT 'inbound'",
1376
+ from_user: "TEXT",
1377
+ from_agent: "TEXT",
1378
+ agent_id: "TEXT",
1379
+ user_id: "TEXT",
1380
+ body: "TEXT NOT NULL",
1381
+ thread_id: "TEXT",
1382
+ task_id: "TEXT",
1383
+ created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
1384
+ deleted_at: "TEXT"
1385
+ },
1386
+ tableConstraints: [
1387
+ "CREATE INDEX IF NOT EXISTS idx_messages_created ON messages(created_at)",
1388
+ "CREATE INDEX IF NOT EXISTS idx_messages_thread ON messages(thread_id)",
1389
+ "CREATE INDEX IF NOT EXISTS idx_messages_agent ON messages(agent_id)"
1390
+ ]
1391
+ });
1392
+ db.define("sessions", {
1372
1393
  columns: {
1373
1394
  id: "TEXT PRIMARY KEY",
1374
1395
  agent_id: "TEXT NOT NULL",
1375
1396
  channel: "TEXT NOT NULL",
1376
1397
  peer_id: "TEXT NOT NULL",
1377
1398
  user_id: "TEXT",
1378
- last_message_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
1379
- message_count: "INTEGER NOT NULL DEFAULT 0",
1380
1399
  context: "TEXT NOT NULL DEFAULT '{}'",
1400
+ message_count: "INTEGER NOT NULL DEFAULT 0",
1401
+ last_message_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
1381
1402
  created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
1382
1403
  expires_at: "TEXT"
1383
1404
  },
1384
1405
  tableConstraints: [
1385
- "CREATE UNIQUE INDEX IF NOT EXISTS idx_messages_agent_channel_peer ON messages(agent_id, channel, peer_id)"
1406
+ "CREATE UNIQUE INDEX IF NOT EXISTS idx_sessions_agent_channel_peer ON sessions(agent_id, channel, peer_id)"
1386
1407
  ]
1387
1408
  });
1388
1409
  db.define("skills", {
@@ -1671,6 +1692,550 @@ ${s.definition}` : null,
1671
1692
  }
1672
1693
  }
1673
1694
  });
1695
+ db.defineEntityContext("messages", {
1696
+ table: "messages",
1697
+ directory: "messages",
1698
+ slugColumn: "id",
1699
+ indexFile: "messages/MESSAGES.md",
1700
+ indexRender: (rows) => {
1701
+ const active = rows.filter((r) => r.deleted_at == null);
1702
+ if (!active.length) return "# Messages\n\nNo messages.\n";
1703
+ const recent = active.slice(-100);
1704
+ const lines = recent.map((r) => {
1705
+ const dir = r.direction === "outbound" ? "\u2192" : "\u2190";
1706
+ const who = r.from_agent ?? r.from_user ?? "unknown";
1707
+ const time = (r.created_at ?? "").slice(0, 16);
1708
+ const preview = (r.body ?? "").slice(0, 80);
1709
+ return `- ${dir} **${who}** (${time}): ${preview}`;
1710
+ });
1711
+ return `# Messages
1712
+
1713
+ Last ${lines.length} messages:
1714
+
1715
+ ${lines.join("\n")}
1716
+ `;
1717
+ },
1718
+ files: {
1719
+ "MESSAGE.md": {
1720
+ source: { type: "self" },
1721
+ render: (rows) => {
1722
+ const m = rows[0];
1723
+ if (!m) return "";
1724
+ return [
1725
+ "# Message",
1726
+ "",
1727
+ `**Direction:** ${m.direction}`,
1728
+ m.from_user ? `**From User:** ${m.from_user}` : null,
1729
+ m.from_agent ? `**From Agent:** ${m.from_agent}` : null,
1730
+ `**Channel:** ${m.channel}`,
1731
+ m.thread_id ? `**Thread:** ${m.thread_id}` : null,
1732
+ m.task_id ? `**Task:** ${m.task_id}` : null,
1733
+ `**Time:** ${m.created_at}`,
1734
+ "",
1735
+ "---",
1736
+ "",
1737
+ m.body,
1738
+ ""
1739
+ ].filter(Boolean).join("\n");
1740
+ }
1741
+ }
1742
+ }
1743
+ });
1744
+ }
1745
+
1746
+ // src/core/data/domain-schema.ts
1747
+ function defineDomainTables(db, options = {}) {
1748
+ const opts = {
1749
+ clients: true,
1750
+ repositories: true,
1751
+ files: true,
1752
+ channels: true,
1753
+ rules: true,
1754
+ events: true,
1755
+ ...options
1756
+ };
1757
+ db.define("org", {
1758
+ columns: {
1759
+ id: "TEXT PRIMARY KEY",
1760
+ name: "TEXT NOT NULL",
1761
+ type: "TEXT NOT NULL DEFAULT 'company'",
1762
+ description: "TEXT",
1763
+ mission: "TEXT",
1764
+ website: "TEXT",
1765
+ created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
1766
+ updated_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
1767
+ deleted_at: "TEXT"
1768
+ }
1769
+ });
1770
+ db.define("project", {
1771
+ columns: {
1772
+ id: "TEXT PRIMARY KEY",
1773
+ org_id: "TEXT NOT NULL",
1774
+ name: "TEXT NOT NULL",
1775
+ status: "TEXT",
1776
+ description: "TEXT",
1777
+ tech_stack: "TEXT",
1778
+ github_repo: "TEXT",
1779
+ deploy_target: "TEXT",
1780
+ production_url: "TEXT",
1781
+ branch_strategy: "TEXT",
1782
+ notes: "TEXT",
1783
+ created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
1784
+ updated_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
1785
+ deleted_at: "TEXT"
1786
+ }
1787
+ });
1788
+ db.define("agent_project", {
1789
+ columns: {
1790
+ agent_id: "TEXT NOT NULL",
1791
+ project_id: "TEXT NOT NULL",
1792
+ role: "TEXT",
1793
+ created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP"
1794
+ },
1795
+ primaryKey: ["agent_id", "project_id"]
1796
+ });
1797
+ db.define("secret_project", {
1798
+ columns: {
1799
+ secret_id: "TEXT NOT NULL",
1800
+ project_id: "TEXT NOT NULL",
1801
+ created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP"
1802
+ },
1803
+ primaryKey: ["secret_id", "project_id"]
1804
+ });
1805
+ db.define("secret_org", {
1806
+ columns: {
1807
+ secret_id: "TEXT NOT NULL",
1808
+ org_id: "TEXT NOT NULL",
1809
+ created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP"
1810
+ },
1811
+ primaryKey: ["secret_id", "org_id"]
1812
+ });
1813
+ if (opts.clients) {
1814
+ db.define("client", {
1815
+ columns: {
1816
+ id: "TEXT PRIMARY KEY",
1817
+ org_id: "TEXT NOT NULL",
1818
+ name: "TEXT NOT NULL",
1819
+ contact_name: "TEXT",
1820
+ contact_email: "TEXT",
1821
+ phone: "TEXT",
1822
+ status: "TEXT NOT NULL DEFAULT 'active'",
1823
+ notes: "TEXT",
1824
+ created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
1825
+ updated_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
1826
+ deleted_at: "TEXT"
1827
+ }
1828
+ });
1829
+ db.define("invoice", {
1830
+ columns: {
1831
+ id: "TEXT PRIMARY KEY",
1832
+ org_id: "TEXT NOT NULL",
1833
+ client_id: "TEXT NOT NULL",
1834
+ number: "TEXT",
1835
+ amount_cents: "INTEGER",
1836
+ currency: "TEXT DEFAULT 'USD'",
1837
+ status: "TEXT NOT NULL DEFAULT 'draft'",
1838
+ description: "TEXT",
1839
+ hours: "REAL",
1840
+ rate_cents: "INTEGER",
1841
+ items_json: "TEXT",
1842
+ issued_at: "TEXT",
1843
+ due_at: "TEXT",
1844
+ paid_at: "TEXT",
1845
+ notes: "TEXT",
1846
+ created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
1847
+ updated_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
1848
+ deleted_at: "TEXT"
1849
+ },
1850
+ tableConstraints: [
1851
+ "CREATE INDEX IF NOT EXISTS idx_invoice_client ON invoice(client_id)"
1852
+ ]
1853
+ });
1854
+ db.define("agent_client", {
1855
+ columns: {
1856
+ agent_id: "TEXT NOT NULL",
1857
+ client_id: "TEXT NOT NULL",
1858
+ created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP"
1859
+ },
1860
+ primaryKey: ["agent_id", "client_id"]
1861
+ });
1862
+ }
1863
+ if (opts.repositories) {
1864
+ db.define("repository", {
1865
+ columns: {
1866
+ id: "TEXT PRIMARY KEY",
1867
+ org_id: "TEXT NOT NULL",
1868
+ project_id: "TEXT",
1869
+ client_id: "TEXT",
1870
+ name: "TEXT NOT NULL",
1871
+ url: "TEXT",
1872
+ local_path: "TEXT",
1873
+ default_branch: "TEXT DEFAULT 'main'",
1874
+ platform: "TEXT DEFAULT 'github'",
1875
+ status: "TEXT NOT NULL DEFAULT 'active'",
1876
+ notes: "TEXT",
1877
+ created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
1878
+ updated_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
1879
+ deleted_at: "TEXT"
1880
+ },
1881
+ tableConstraints: [
1882
+ "CREATE INDEX IF NOT EXISTS idx_repository_project ON repository(project_id)",
1883
+ "CREATE INDEX IF NOT EXISTS idx_repository_client ON repository(client_id)"
1884
+ ]
1885
+ });
1886
+ }
1887
+ if (opts.files) {
1888
+ db.define("file", {
1889
+ columns: {
1890
+ id: "TEXT PRIMARY KEY",
1891
+ org_id: "TEXT",
1892
+ name: "TEXT NOT NULL",
1893
+ file_path: "TEXT",
1894
+ mime_type: "TEXT",
1895
+ size_bytes: "INTEGER",
1896
+ project_id: "TEXT",
1897
+ access_level: "TEXT NOT NULL DEFAULT 'org'",
1898
+ description: "TEXT",
1899
+ notes: "TEXT",
1900
+ created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
1901
+ updated_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
1902
+ deleted_at: "TEXT"
1903
+ }
1904
+ });
1905
+ }
1906
+ if (opts.channels) {
1907
+ db.define("channel", {
1908
+ columns: {
1909
+ id: "TEXT PRIMARY KEY",
1910
+ org_id: "TEXT",
1911
+ platform: "TEXT NOT NULL",
1912
+ external_id: "TEXT",
1913
+ name: "TEXT NOT NULL",
1914
+ type: "TEXT NOT NULL DEFAULT 'channel'",
1915
+ instructions: "TEXT",
1916
+ created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
1917
+ updated_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
1918
+ deleted_at: "TEXT"
1919
+ }
1920
+ });
1921
+ }
1922
+ if (opts.rules) {
1923
+ db.define("rule", {
1924
+ columns: {
1925
+ id: "TEXT PRIMARY KEY",
1926
+ org_id: "TEXT",
1927
+ title: "TEXT NOT NULL",
1928
+ rule_text: "TEXT NOT NULL",
1929
+ scope: "TEXT NOT NULL DEFAULT 'org'",
1930
+ category: "TEXT NOT NULL DEFAULT 'process'",
1931
+ priority: "INTEGER NOT NULL DEFAULT 50",
1932
+ created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
1933
+ updated_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
1934
+ deleted_at: "TEXT"
1935
+ }
1936
+ });
1937
+ db.define("rule_agent", {
1938
+ columns: {
1939
+ rule_id: "TEXT NOT NULL",
1940
+ agent_id: "TEXT NOT NULL",
1941
+ created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP"
1942
+ },
1943
+ primaryKey: ["rule_id", "agent_id"]
1944
+ });
1945
+ db.define("rule_project", {
1946
+ columns: {
1947
+ rule_id: "TEXT NOT NULL",
1948
+ project_id: "TEXT NOT NULL",
1949
+ created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP"
1950
+ },
1951
+ primaryKey: ["rule_id", "project_id"]
1952
+ });
1953
+ db.define("rule_org", {
1954
+ columns: {
1955
+ rule_id: "TEXT NOT NULL",
1956
+ org_id: "TEXT NOT NULL",
1957
+ created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP"
1958
+ },
1959
+ primaryKey: ["rule_id", "org_id"]
1960
+ });
1961
+ }
1962
+ if (opts.events) {
1963
+ db.define("event", {
1964
+ columns: {
1965
+ id: "TEXT PRIMARY KEY",
1966
+ org_id: "TEXT",
1967
+ type: "TEXT NOT NULL",
1968
+ summary: "TEXT NOT NULL",
1969
+ details: "TEXT",
1970
+ severity: "TEXT NOT NULL DEFAULT 'info'",
1971
+ actor_agent_id: "TEXT",
1972
+ actor_user_id: "TEXT",
1973
+ project_id: "TEXT",
1974
+ channel_id: "TEXT",
1975
+ created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
1976
+ deleted_at: "TEXT"
1977
+ },
1978
+ tableConstraints: [
1979
+ "CREATE INDEX IF NOT EXISTS idx_event_type ON event(type, created_at)",
1980
+ "CREATE INDEX IF NOT EXISTS idx_event_project ON event(project_id)"
1981
+ ]
1982
+ });
1983
+ }
1984
+ }
1985
+
1986
+ // src/core/data/domain-entity-contexts.ts
1987
+ function defineDomainEntityContexts(db, options = {}) {
1988
+ const opts = {
1989
+ clients: true,
1990
+ repositories: true,
1991
+ files: true,
1992
+ channels: true,
1993
+ rules: true,
1994
+ ...options
1995
+ };
1996
+ db.defineEntityContext("org", {
1997
+ table: "org",
1998
+ directory: "orgs",
1999
+ slugColumn: "name",
2000
+ indexFile: "orgs/ORGS.md",
2001
+ files: {
2002
+ "ORG.md": {
2003
+ source: { type: "self" },
2004
+ render: (rows) => {
2005
+ const o = rows[0];
2006
+ if (!o) return "";
2007
+ return [
2008
+ `# ${o.name}`,
2009
+ "",
2010
+ o.type ? `**Type:** ${o.type}` : null,
2011
+ o.description ? `
2012
+ ${o.description}` : null,
2013
+ o.mission ? `
2014
+ **Mission:** ${o.mission}` : null,
2015
+ o.website ? `**Website:** ${o.website}` : null,
2016
+ ""
2017
+ ].filter(Boolean).join("\n");
2018
+ }
2019
+ }
2020
+ }
2021
+ });
2022
+ db.defineEntityContext("project", {
2023
+ table: "project",
2024
+ directory: "projects",
2025
+ slugColumn: "name",
2026
+ indexFile: "projects/PROJECTS.md",
2027
+ files: {
2028
+ "PROJECT.md": {
2029
+ source: { type: "self" },
2030
+ render: (rows) => {
2031
+ const p = rows[0];
2032
+ if (!p) return "";
2033
+ return [
2034
+ `# ${p.name}`,
2035
+ "",
2036
+ p.status ? `**Status:** ${p.status}` : null,
2037
+ p.description ? `
2038
+ ${p.description}` : null,
2039
+ p.tech_stack ? `
2040
+ **Tech Stack:** ${p.tech_stack}` : null,
2041
+ p.production_url ? `**URL:** ${p.production_url}` : null,
2042
+ p.github_repo ? `**GitHub:** ${p.github_repo}` : null,
2043
+ p.deploy_target ? `**Deploy:** ${p.deploy_target}` : null,
2044
+ p.branch_strategy ? `**Branch Strategy:** ${p.branch_strategy}` : null,
2045
+ p.notes ? `
2046
+ **Notes:**
2047
+ ${p.notes}` : null,
2048
+ ""
2049
+ ].filter(Boolean).join("\n");
2050
+ }
2051
+ },
2052
+ ...opts.repositories ? {
2053
+ "REPOS.md": {
2054
+ source: {
2055
+ type: "hasMany",
2056
+ table: "repository",
2057
+ foreignKey: "project_id"
2058
+ },
2059
+ render: (rows) => {
2060
+ if (!rows.length) return "";
2061
+ const lines = rows.map(
2062
+ (r) => `- **${r.name}** \u2014 ${r.url ?? ""}`
2063
+ );
2064
+ return `# Repositories
2065
+
2066
+ ${lines.join("\n")}
2067
+ `;
2068
+ },
2069
+ omitIfEmpty: true
2070
+ }
2071
+ } : {},
2072
+ ...opts.rules ? {
2073
+ "RULES.md": {
2074
+ source: {
2075
+ type: "manyToMany",
2076
+ junctionTable: "rule_project",
2077
+ localKey: "project_id",
2078
+ remoteKey: "rule_id",
2079
+ remoteTable: "rule",
2080
+ softDelete: true,
2081
+ orderBy: "priority"
2082
+ },
2083
+ render: (rows) => {
2084
+ if (!rows.length) return "";
2085
+ const lines = rows.map(
2086
+ (r) => `### ${r.title}
2087
+ ${r.rule_text}`
2088
+ );
2089
+ return `# Project Rules
2090
+
2091
+ ${lines.join("\n\n")}
2092
+ `;
2093
+ },
2094
+ omitIfEmpty: true
2095
+ }
2096
+ } : {}
2097
+ }
2098
+ });
2099
+ if (opts.clients) {
2100
+ db.defineEntityContext("client", {
2101
+ table: "client",
2102
+ directory: "clients",
2103
+ slugColumn: "name",
2104
+ indexFile: "clients/CLIENTS.md",
2105
+ files: {
2106
+ "CLIENT.md": {
2107
+ source: { type: "self" },
2108
+ render: (rows) => {
2109
+ const c = rows[0];
2110
+ if (!c) return "";
2111
+ return [
2112
+ `# ${c.name}`,
2113
+ "",
2114
+ c.contact_name ? `**Contact:** ${c.contact_name}` : null,
2115
+ c.contact_email ? `**Email:** ${c.contact_email}` : null,
2116
+ c.phone ? `**Phone:** ${c.phone}` : null,
2117
+ c.status ? `**Status:** ${c.status}` : null,
2118
+ c.notes ? `
2119
+ ${c.notes}` : null,
2120
+ ""
2121
+ ].filter(Boolean).join("\n");
2122
+ }
2123
+ },
2124
+ ...opts.repositories ? {
2125
+ "REPOS.md": {
2126
+ source: {
2127
+ type: "hasMany",
2128
+ table: "repository",
2129
+ foreignKey: "client_id"
2130
+ },
2131
+ render: (rows) => {
2132
+ if (!rows.length) return "";
2133
+ const lines = rows.map(
2134
+ (r) => `- **${r.name}** \u2014 ${r.url ?? ""}`
2135
+ );
2136
+ return `# Repositories
2137
+
2138
+ ${lines.join("\n")}
2139
+ `;
2140
+ },
2141
+ omitIfEmpty: true
2142
+ }
2143
+ } : {},
2144
+ AGENTS: {
2145
+ source: {
2146
+ type: "manyToMany",
2147
+ junctionTable: "agent_client",
2148
+ localKey: "client_id",
2149
+ remoteKey: "agent_id",
2150
+ remoteTable: "agents"
2151
+ },
2152
+ render: (rows) => {
2153
+ if (!rows.length) return "";
2154
+ const lines = rows.map(
2155
+ (r) => `- **${r.name}** (${r.role ?? "agent"})`
2156
+ );
2157
+ return `# Assigned Agents
2158
+
2159
+ ${lines.join("\n")}
2160
+ `;
2161
+ },
2162
+ omitIfEmpty: true
2163
+ },
2164
+ "INVOICES.md": {
2165
+ source: {
2166
+ type: "hasMany",
2167
+ table: "invoice",
2168
+ foreignKey: "client_id"
2169
+ },
2170
+ render: (rows) => {
2171
+ if (!rows.length) return "";
2172
+ const lines = rows.map((r) => {
2173
+ const amt = r.amount_cents ? `$${(r.amount_cents / 100).toFixed(2)}` : "TBD";
2174
+ return `- **${r.number ?? "Draft"}** \u2014 ${amt} (${r.status})${r.description ? ": " + r.description : ""}`;
2175
+ });
2176
+ return `# Invoices
2177
+
2178
+ ${lines.join("\n")}
2179
+ `;
2180
+ },
2181
+ omitIfEmpty: true
2182
+ }
2183
+ }
2184
+ });
2185
+ }
2186
+ if (opts.files) {
2187
+ db.defineEntityContext("file", {
2188
+ table: "file",
2189
+ directory: "files",
2190
+ slugColumn: "name",
2191
+ indexFile: "files/FILES.md",
2192
+ files: {
2193
+ "FILE.md": {
2194
+ source: { type: "self" },
2195
+ render: (rows) => {
2196
+ const f = rows[0];
2197
+ if (!f) return "";
2198
+ return [
2199
+ `# ${f.name}`,
2200
+ "",
2201
+ f.mime_type ? `**Type:** ${f.mime_type}` : null,
2202
+ f.access_level ? `**Access:** ${f.access_level}` : null,
2203
+ f.file_path ? `**Path:** ${f.file_path}` : null,
2204
+ f.description ? `
2205
+ ${f.description}` : null,
2206
+ ""
2207
+ ].filter(Boolean).join("\n");
2208
+ }
2209
+ }
2210
+ }
2211
+ });
2212
+ }
2213
+ if (opts.channels) {
2214
+ db.defineEntityContext("channel", {
2215
+ table: "channel",
2216
+ directory: "channels",
2217
+ slugColumn: "name",
2218
+ indexFile: "channels/CHANNELS.md",
2219
+ files: {
2220
+ "CHANNEL.md": {
2221
+ source: { type: "self" },
2222
+ render: (rows) => {
2223
+ const c = rows[0];
2224
+ if (!c) return "";
2225
+ return [
2226
+ `# ${c.name}`,
2227
+ "",
2228
+ c.platform ? `**Platform:** ${c.platform}` : null,
2229
+ c.type ? `**Type:** ${c.type}` : null,
2230
+ c.instructions ? `
2231
+ ${c.instructions}` : null,
2232
+ ""
2233
+ ].filter(Boolean).join("\n");
2234
+ }
2235
+ }
2236
+ }
2237
+ });
2238
+ }
1674
2239
  }
1675
2240
 
1676
2241
  // src/core/security/sanitizer.ts
@@ -1756,6 +2321,44 @@ var AuditEmitter = class {
1756
2321
  }
1757
2322
  };
1758
2323
 
2324
+ // src/core/security/process-env.ts
2325
+ var DEFAULT_ALLOWED_KEYS = /* @__PURE__ */ new Set([
2326
+ "PATH",
2327
+ "HOME",
2328
+ "USER",
2329
+ "SHELL",
2330
+ "LANG",
2331
+ "TERM",
2332
+ "TMPDIR",
2333
+ "XDG_RUNTIME_DIR",
2334
+ "NODE_ENV",
2335
+ // Git
2336
+ "GIT_AUTHOR_NAME",
2337
+ "GIT_AUTHOR_EMAIL",
2338
+ "GIT_COMMITTER_NAME",
2339
+ "GIT_COMMITTER_EMAIL",
2340
+ // Homebrew / system
2341
+ "HOMEBREW_PREFIX",
2342
+ "HOMEBREW_CELLAR",
2343
+ "HOMEBREW_REPOSITORY"
2344
+ ]);
2345
+ function buildProcessEnv(allowedKeys, inject) {
2346
+ const allowed = new Set(DEFAULT_ALLOWED_KEYS);
2347
+ if (allowedKeys) {
2348
+ for (const k of allowedKeys) allowed.add(k);
2349
+ }
2350
+ const env = {};
2351
+ for (const [key, value] of Object.entries(process.env)) {
2352
+ if (allowed.has(key) && value !== void 0) {
2353
+ env[key] = value;
2354
+ }
2355
+ }
2356
+ if (inject) {
2357
+ Object.assign(env, inject);
2358
+ }
2359
+ return env;
2360
+ }
2361
+
1759
2362
  // src/core/update/version-utils.ts
1760
2363
  function parseVersion(v) {
1761
2364
  const cleaned = v.replace(/^v/, "").split("-")[0] ?? v;
@@ -3233,6 +3836,92 @@ var SecretStore = class {
3233
3836
  };
3234
3837
  }
3235
3838
  };
3839
+
3840
+ // src/core/orchestrator/claude-stream-parser.ts
3841
+ function parseClaudeStream(stdout) {
3842
+ let sessionId = null;
3843
+ let model = null;
3844
+ let costUsd = null;
3845
+ let usage = null;
3846
+ let isError = false;
3847
+ let errorMessage = null;
3848
+ let stopReason = null;
3849
+ const textBlocks = [];
3850
+ for (const line of stdout.split("\n")) {
3851
+ if (!line.trim()) continue;
3852
+ let event;
3853
+ try {
3854
+ event = JSON.parse(line);
3855
+ } catch {
3856
+ continue;
3857
+ }
3858
+ const type = event.type;
3859
+ if (type === "system" && event.subtype === "init") {
3860
+ sessionId = event.session_id ?? null;
3861
+ model = event.model ?? null;
3862
+ }
3863
+ if (type === "assistant") {
3864
+ const msg = event.message;
3865
+ const content = msg?.content ?? event.content;
3866
+ if (Array.isArray(content)) {
3867
+ for (const block of content) {
3868
+ if (block.type === "text" && block.text) {
3869
+ textBlocks.push(block.text);
3870
+ }
3871
+ }
3872
+ }
3873
+ }
3874
+ if (type === "result") {
3875
+ isError = !!event.is_error;
3876
+ stopReason = event.stop_reason ?? null;
3877
+ costUsd = typeof event.total_cost_usd === "number" ? event.total_cost_usd : null;
3878
+ const u = event.usage;
3879
+ if (u) {
3880
+ usage = {
3881
+ inputTokens: u.input_tokens ?? 0,
3882
+ cachedInputTokens: u.cache_read_input_tokens ?? 0,
3883
+ outputTokens: u.output_tokens ?? 0
3884
+ };
3885
+ }
3886
+ if (isError) {
3887
+ errorMessage = event.error ?? event.result ?? "Unknown error";
3888
+ }
3889
+ const resultContent = event.result;
3890
+ if (typeof resultContent === "string" && resultContent) {
3891
+ textBlocks.push(resultContent);
3892
+ }
3893
+ }
3894
+ }
3895
+ return {
3896
+ sessionId,
3897
+ model,
3898
+ costUsd,
3899
+ usage,
3900
+ summary: textBlocks.join("\n"),
3901
+ isError,
3902
+ errorMessage,
3903
+ stopReason
3904
+ };
3905
+ }
3906
+ function isMaxTurns(parsed) {
3907
+ return parsed.stopReason === "max_turns" || parsed.stopReason === "tool_use";
3908
+ }
3909
+ function isLoginRequired(stdout) {
3910
+ const patterns = [
3911
+ "not logged in",
3912
+ "login required",
3913
+ "authentication required",
3914
+ "please log in"
3915
+ ];
3916
+ const lower = stdout.toLowerCase();
3917
+ return patterns.some((p) => lower.includes(p));
3918
+ }
3919
+ function deactivateLocalImagePaths(prompt) {
3920
+ return prompt.replace(
3921
+ /(?<=\s|^)(\/[\w./-]+\.(?:png|jpg|jpeg|gif|webp|svg))(?=\s|$)/gi,
3922
+ "[image-path:$1]"
3923
+ );
3924
+ }
3236
3925
  export {
3237
3926
  AGENT_STATUSES,
3238
3927
  AgentRegistry,
@@ -3275,6 +3964,7 @@ export {
3275
3964
  areDependenciesMet,
3276
3965
  buildAgentBindings,
3277
3966
  buildChainOrigin,
3967
+ buildProcessEnv,
3278
3968
  checkAllowlist,
3279
3969
  checkChainDepth,
3280
3970
  checkMentionGate,
@@ -3282,8 +3972,11 @@ export {
3282
3972
  classifyUpdate,
3283
3973
  compareVersions,
3284
3974
  createConfigRevision,
3975
+ deactivateLocalImagePaths,
3285
3976
  defineCoreEntityContexts,
3286
3977
  defineCoreTables,
3978
+ defineDomainEntityContexts,
3979
+ defineDomainTables,
3287
3980
  detectCycle,
3288
3981
  discoverChannels,
3289
3982
  discoverProviders,
@@ -3292,7 +3985,10 @@ export {
3292
3985
  initConfig,
3293
3986
  interpolate,
3294
3987
  interpolateEnv,
3988
+ isLoginRequired,
3989
+ isMaxTurns,
3295
3990
  loadConfig,
3991
+ parseClaudeStream,
3296
3992
  parseVersion,
3297
3993
  runPackageMigrations,
3298
3994
  sanitize,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "botinabox",
3
- "version": "0.2.5",
3
+ "version": "0.3.1",
4
4
  "description": "Bot in a Box — framework for building multi-agent bots",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",