botinabox 0.2.4 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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
@@ -1140,7 +1140,10 @@ var DataStore = class {
1140
1140
  protectedFiles: def.protectedFiles,
1141
1141
  protected: def.protected,
1142
1142
  encrypted: def.encrypted,
1143
- sourceDefaults: { softDelete: true },
1143
+ // Note: sourceDefaults.softDelete NOT set here because junction tables
1144
+ // (agent_project, rule_agent, etc.) don't have deleted_at columns.
1145
+ // Entity context render functions should add softDelete per-source when targeting
1146
+ // tables that support it.
1144
1147
  index: def.indexFile ? {
1145
1148
  outputFile: def.indexFile,
1146
1149
  render: def.indexRender ?? ((rows) => {
@@ -1670,6 +1673,501 @@ ${s.definition}` : null,
1670
1673
  });
1671
1674
  }
1672
1675
 
1676
+ // src/core/data/domain-schema.ts
1677
+ function defineDomainTables(db, options = {}) {
1678
+ const opts = {
1679
+ clients: true,
1680
+ repositories: true,
1681
+ files: true,
1682
+ channels: true,
1683
+ rules: true,
1684
+ events: true,
1685
+ ...options
1686
+ };
1687
+ db.define("org", {
1688
+ columns: {
1689
+ id: "TEXT PRIMARY KEY",
1690
+ name: "TEXT NOT NULL",
1691
+ type: "TEXT NOT NULL DEFAULT 'company'",
1692
+ description: "TEXT",
1693
+ mission: "TEXT",
1694
+ website: "TEXT",
1695
+ created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
1696
+ updated_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
1697
+ deleted_at: "TEXT"
1698
+ }
1699
+ });
1700
+ db.define("project", {
1701
+ columns: {
1702
+ id: "TEXT PRIMARY KEY",
1703
+ org_id: "TEXT NOT NULL",
1704
+ name: "TEXT NOT NULL",
1705
+ status: "TEXT",
1706
+ description: "TEXT",
1707
+ tech_stack: "TEXT",
1708
+ github_repo: "TEXT",
1709
+ deploy_target: "TEXT",
1710
+ production_url: "TEXT",
1711
+ branch_strategy: "TEXT",
1712
+ notes: "TEXT",
1713
+ created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
1714
+ updated_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
1715
+ deleted_at: "TEXT"
1716
+ }
1717
+ });
1718
+ db.define("agent_project", {
1719
+ columns: {
1720
+ agent_id: "TEXT NOT NULL",
1721
+ project_id: "TEXT NOT NULL",
1722
+ role: "TEXT",
1723
+ created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP"
1724
+ },
1725
+ primaryKey: ["agent_id", "project_id"]
1726
+ });
1727
+ db.define("secret_project", {
1728
+ columns: {
1729
+ secret_id: "TEXT NOT NULL",
1730
+ project_id: "TEXT NOT NULL",
1731
+ created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP"
1732
+ },
1733
+ primaryKey: ["secret_id", "project_id"]
1734
+ });
1735
+ db.define("secret_org", {
1736
+ columns: {
1737
+ secret_id: "TEXT NOT NULL",
1738
+ org_id: "TEXT NOT NULL",
1739
+ created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP"
1740
+ },
1741
+ primaryKey: ["secret_id", "org_id"]
1742
+ });
1743
+ if (opts.clients) {
1744
+ db.define("client", {
1745
+ columns: {
1746
+ id: "TEXT PRIMARY KEY",
1747
+ org_id: "TEXT NOT NULL",
1748
+ name: "TEXT NOT NULL",
1749
+ contact_name: "TEXT",
1750
+ contact_email: "TEXT",
1751
+ phone: "TEXT",
1752
+ status: "TEXT NOT NULL DEFAULT 'active'",
1753
+ notes: "TEXT",
1754
+ created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
1755
+ updated_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
1756
+ deleted_at: "TEXT"
1757
+ }
1758
+ });
1759
+ db.define("invoice", {
1760
+ columns: {
1761
+ id: "TEXT PRIMARY KEY",
1762
+ org_id: "TEXT NOT NULL",
1763
+ client_id: "TEXT NOT NULL",
1764
+ number: "TEXT",
1765
+ amount_cents: "INTEGER",
1766
+ currency: "TEXT DEFAULT 'USD'",
1767
+ status: "TEXT NOT NULL DEFAULT 'draft'",
1768
+ description: "TEXT",
1769
+ hours: "REAL",
1770
+ rate_cents: "INTEGER",
1771
+ items_json: "TEXT",
1772
+ issued_at: "TEXT",
1773
+ due_at: "TEXT",
1774
+ paid_at: "TEXT",
1775
+ notes: "TEXT",
1776
+ created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
1777
+ updated_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
1778
+ deleted_at: "TEXT"
1779
+ },
1780
+ tableConstraints: [
1781
+ "CREATE INDEX IF NOT EXISTS idx_invoice_client ON invoice(client_id)"
1782
+ ]
1783
+ });
1784
+ db.define("agent_client", {
1785
+ columns: {
1786
+ agent_id: "TEXT NOT NULL",
1787
+ client_id: "TEXT NOT NULL",
1788
+ created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP"
1789
+ },
1790
+ primaryKey: ["agent_id", "client_id"]
1791
+ });
1792
+ }
1793
+ if (opts.repositories) {
1794
+ db.define("repository", {
1795
+ columns: {
1796
+ id: "TEXT PRIMARY KEY",
1797
+ org_id: "TEXT NOT NULL",
1798
+ project_id: "TEXT",
1799
+ client_id: "TEXT",
1800
+ name: "TEXT NOT NULL",
1801
+ url: "TEXT",
1802
+ local_path: "TEXT",
1803
+ default_branch: "TEXT DEFAULT 'main'",
1804
+ platform: "TEXT DEFAULT 'github'",
1805
+ status: "TEXT NOT NULL DEFAULT 'active'",
1806
+ notes: "TEXT",
1807
+ created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
1808
+ updated_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
1809
+ deleted_at: "TEXT"
1810
+ },
1811
+ tableConstraints: [
1812
+ "CREATE INDEX IF NOT EXISTS idx_repository_project ON repository(project_id)",
1813
+ "CREATE INDEX IF NOT EXISTS idx_repository_client ON repository(client_id)"
1814
+ ]
1815
+ });
1816
+ }
1817
+ if (opts.files) {
1818
+ db.define("file", {
1819
+ columns: {
1820
+ id: "TEXT PRIMARY KEY",
1821
+ org_id: "TEXT",
1822
+ name: "TEXT NOT NULL",
1823
+ file_path: "TEXT",
1824
+ mime_type: "TEXT",
1825
+ size_bytes: "INTEGER",
1826
+ project_id: "TEXT",
1827
+ access_level: "TEXT NOT NULL DEFAULT 'org'",
1828
+ description: "TEXT",
1829
+ notes: "TEXT",
1830
+ created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
1831
+ updated_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
1832
+ deleted_at: "TEXT"
1833
+ }
1834
+ });
1835
+ }
1836
+ if (opts.channels) {
1837
+ db.define("channel", {
1838
+ columns: {
1839
+ id: "TEXT PRIMARY KEY",
1840
+ org_id: "TEXT",
1841
+ platform: "TEXT NOT NULL",
1842
+ external_id: "TEXT",
1843
+ name: "TEXT NOT NULL",
1844
+ type: "TEXT NOT NULL DEFAULT 'channel'",
1845
+ instructions: "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
+ });
1851
+ }
1852
+ if (opts.rules) {
1853
+ db.define("rule", {
1854
+ columns: {
1855
+ id: "TEXT PRIMARY KEY",
1856
+ org_id: "TEXT",
1857
+ title: "TEXT NOT NULL",
1858
+ rule_text: "TEXT NOT NULL",
1859
+ scope: "TEXT NOT NULL DEFAULT 'org'",
1860
+ category: "TEXT NOT NULL DEFAULT 'process'",
1861
+ priority: "INTEGER NOT NULL DEFAULT 50",
1862
+ created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
1863
+ updated_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
1864
+ deleted_at: "TEXT"
1865
+ }
1866
+ });
1867
+ db.define("rule_agent", {
1868
+ columns: {
1869
+ rule_id: "TEXT NOT NULL",
1870
+ agent_id: "TEXT NOT NULL",
1871
+ created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP"
1872
+ },
1873
+ primaryKey: ["rule_id", "agent_id"]
1874
+ });
1875
+ db.define("rule_project", {
1876
+ columns: {
1877
+ rule_id: "TEXT NOT NULL",
1878
+ project_id: "TEXT NOT NULL",
1879
+ created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP"
1880
+ },
1881
+ primaryKey: ["rule_id", "project_id"]
1882
+ });
1883
+ db.define("rule_org", {
1884
+ columns: {
1885
+ rule_id: "TEXT NOT NULL",
1886
+ org_id: "TEXT NOT NULL",
1887
+ created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP"
1888
+ },
1889
+ primaryKey: ["rule_id", "org_id"]
1890
+ });
1891
+ }
1892
+ if (opts.events) {
1893
+ db.define("event", {
1894
+ columns: {
1895
+ id: "TEXT PRIMARY KEY",
1896
+ org_id: "TEXT",
1897
+ type: "TEXT NOT NULL",
1898
+ summary: "TEXT NOT NULL",
1899
+ details: "TEXT",
1900
+ severity: "TEXT NOT NULL DEFAULT 'info'",
1901
+ actor_agent_id: "TEXT",
1902
+ actor_user_id: "TEXT",
1903
+ project_id: "TEXT",
1904
+ channel_id: "TEXT",
1905
+ created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
1906
+ deleted_at: "TEXT"
1907
+ },
1908
+ tableConstraints: [
1909
+ "CREATE INDEX IF NOT EXISTS idx_event_type ON event(type, created_at)",
1910
+ "CREATE INDEX IF NOT EXISTS idx_event_project ON event(project_id)"
1911
+ ]
1912
+ });
1913
+ }
1914
+ }
1915
+
1916
+ // src/core/data/domain-entity-contexts.ts
1917
+ function defineDomainEntityContexts(db, options = {}) {
1918
+ const opts = {
1919
+ clients: true,
1920
+ repositories: true,
1921
+ files: true,
1922
+ channels: true,
1923
+ rules: true,
1924
+ ...options
1925
+ };
1926
+ db.defineEntityContext("org", {
1927
+ table: "org",
1928
+ directory: "orgs",
1929
+ slugColumn: "name",
1930
+ indexFile: "orgs/ORGS.md",
1931
+ files: {
1932
+ "ORG.md": {
1933
+ source: { type: "self" },
1934
+ render: (rows) => {
1935
+ const o = rows[0];
1936
+ if (!o) return "";
1937
+ return [
1938
+ `# ${o.name}`,
1939
+ "",
1940
+ o.type ? `**Type:** ${o.type}` : null,
1941
+ o.description ? `
1942
+ ${o.description}` : null,
1943
+ o.mission ? `
1944
+ **Mission:** ${o.mission}` : null,
1945
+ o.website ? `**Website:** ${o.website}` : null,
1946
+ ""
1947
+ ].filter(Boolean).join("\n");
1948
+ }
1949
+ }
1950
+ }
1951
+ });
1952
+ db.defineEntityContext("project", {
1953
+ table: "project",
1954
+ directory: "projects",
1955
+ slugColumn: "name",
1956
+ indexFile: "projects/PROJECTS.md",
1957
+ files: {
1958
+ "PROJECT.md": {
1959
+ source: { type: "self" },
1960
+ render: (rows) => {
1961
+ const p = rows[0];
1962
+ if (!p) return "";
1963
+ return [
1964
+ `# ${p.name}`,
1965
+ "",
1966
+ p.status ? `**Status:** ${p.status}` : null,
1967
+ p.description ? `
1968
+ ${p.description}` : null,
1969
+ p.tech_stack ? `
1970
+ **Tech Stack:** ${p.tech_stack}` : null,
1971
+ p.production_url ? `**URL:** ${p.production_url}` : null,
1972
+ p.github_repo ? `**GitHub:** ${p.github_repo}` : null,
1973
+ p.deploy_target ? `**Deploy:** ${p.deploy_target}` : null,
1974
+ p.branch_strategy ? `**Branch Strategy:** ${p.branch_strategy}` : null,
1975
+ p.notes ? `
1976
+ **Notes:**
1977
+ ${p.notes}` : null,
1978
+ ""
1979
+ ].filter(Boolean).join("\n");
1980
+ }
1981
+ },
1982
+ ...opts.repositories ? {
1983
+ "REPOS.md": {
1984
+ source: {
1985
+ type: "hasMany",
1986
+ table: "repository",
1987
+ foreignKey: "project_id"
1988
+ },
1989
+ render: (rows) => {
1990
+ if (!rows.length) return "";
1991
+ const lines = rows.map(
1992
+ (r) => `- **${r.name}** \u2014 ${r.url ?? ""}`
1993
+ );
1994
+ return `# Repositories
1995
+
1996
+ ${lines.join("\n")}
1997
+ `;
1998
+ },
1999
+ omitIfEmpty: true
2000
+ }
2001
+ } : {},
2002
+ ...opts.rules ? {
2003
+ "RULES.md": {
2004
+ source: {
2005
+ type: "manyToMany",
2006
+ junctionTable: "rule_project",
2007
+ localKey: "project_id",
2008
+ remoteKey: "rule_id",
2009
+ remoteTable: "rule",
2010
+ softDelete: true,
2011
+ orderBy: "priority"
2012
+ },
2013
+ render: (rows) => {
2014
+ if (!rows.length) return "";
2015
+ const lines = rows.map(
2016
+ (r) => `### ${r.title}
2017
+ ${r.rule_text}`
2018
+ );
2019
+ return `# Project Rules
2020
+
2021
+ ${lines.join("\n\n")}
2022
+ `;
2023
+ },
2024
+ omitIfEmpty: true
2025
+ }
2026
+ } : {}
2027
+ }
2028
+ });
2029
+ if (opts.clients) {
2030
+ db.defineEntityContext("client", {
2031
+ table: "client",
2032
+ directory: "clients",
2033
+ slugColumn: "name",
2034
+ indexFile: "clients/CLIENTS.md",
2035
+ files: {
2036
+ "CLIENT.md": {
2037
+ source: { type: "self" },
2038
+ render: (rows) => {
2039
+ const c = rows[0];
2040
+ if (!c) return "";
2041
+ return [
2042
+ `# ${c.name}`,
2043
+ "",
2044
+ c.contact_name ? `**Contact:** ${c.contact_name}` : null,
2045
+ c.contact_email ? `**Email:** ${c.contact_email}` : null,
2046
+ c.phone ? `**Phone:** ${c.phone}` : null,
2047
+ c.status ? `**Status:** ${c.status}` : null,
2048
+ c.notes ? `
2049
+ ${c.notes}` : null,
2050
+ ""
2051
+ ].filter(Boolean).join("\n");
2052
+ }
2053
+ },
2054
+ ...opts.repositories ? {
2055
+ "REPOS.md": {
2056
+ source: {
2057
+ type: "hasMany",
2058
+ table: "repository",
2059
+ foreignKey: "client_id"
2060
+ },
2061
+ render: (rows) => {
2062
+ if (!rows.length) return "";
2063
+ const lines = rows.map(
2064
+ (r) => `- **${r.name}** \u2014 ${r.url ?? ""}`
2065
+ );
2066
+ return `# Repositories
2067
+
2068
+ ${lines.join("\n")}
2069
+ `;
2070
+ },
2071
+ omitIfEmpty: true
2072
+ }
2073
+ } : {},
2074
+ AGENTS: {
2075
+ source: {
2076
+ type: "manyToMany",
2077
+ junctionTable: "agent_client",
2078
+ localKey: "client_id",
2079
+ remoteKey: "agent_id",
2080
+ remoteTable: "agents"
2081
+ },
2082
+ render: (rows) => {
2083
+ if (!rows.length) return "";
2084
+ const lines = rows.map(
2085
+ (r) => `- **${r.name}** (${r.role ?? "agent"})`
2086
+ );
2087
+ return `# Assigned Agents
2088
+
2089
+ ${lines.join("\n")}
2090
+ `;
2091
+ },
2092
+ omitIfEmpty: true
2093
+ },
2094
+ "INVOICES.md": {
2095
+ source: {
2096
+ type: "hasMany",
2097
+ table: "invoice",
2098
+ foreignKey: "client_id"
2099
+ },
2100
+ render: (rows) => {
2101
+ if (!rows.length) return "";
2102
+ const lines = rows.map((r) => {
2103
+ const amt = r.amount_cents ? `$${(r.amount_cents / 100).toFixed(2)}` : "TBD";
2104
+ return `- **${r.number ?? "Draft"}** \u2014 ${amt} (${r.status})${r.description ? ": " + r.description : ""}`;
2105
+ });
2106
+ return `# Invoices
2107
+
2108
+ ${lines.join("\n")}
2109
+ `;
2110
+ },
2111
+ omitIfEmpty: true
2112
+ }
2113
+ }
2114
+ });
2115
+ }
2116
+ if (opts.files) {
2117
+ db.defineEntityContext("file", {
2118
+ table: "file",
2119
+ directory: "files",
2120
+ slugColumn: "name",
2121
+ indexFile: "files/FILES.md",
2122
+ files: {
2123
+ "FILE.md": {
2124
+ source: { type: "self" },
2125
+ render: (rows) => {
2126
+ const f = rows[0];
2127
+ if (!f) return "";
2128
+ return [
2129
+ `# ${f.name}`,
2130
+ "",
2131
+ f.mime_type ? `**Type:** ${f.mime_type}` : null,
2132
+ f.access_level ? `**Access:** ${f.access_level}` : null,
2133
+ f.file_path ? `**Path:** ${f.file_path}` : null,
2134
+ f.description ? `
2135
+ ${f.description}` : null,
2136
+ ""
2137
+ ].filter(Boolean).join("\n");
2138
+ }
2139
+ }
2140
+ }
2141
+ });
2142
+ }
2143
+ if (opts.channels) {
2144
+ db.defineEntityContext("channel", {
2145
+ table: "channel",
2146
+ directory: "channels",
2147
+ slugColumn: "name",
2148
+ indexFile: "channels/CHANNELS.md",
2149
+ files: {
2150
+ "CHANNEL.md": {
2151
+ source: { type: "self" },
2152
+ render: (rows) => {
2153
+ const c = rows[0];
2154
+ if (!c) return "";
2155
+ return [
2156
+ `# ${c.name}`,
2157
+ "",
2158
+ c.platform ? `**Platform:** ${c.platform}` : null,
2159
+ c.type ? `**Type:** ${c.type}` : null,
2160
+ c.instructions ? `
2161
+ ${c.instructions}` : null,
2162
+ ""
2163
+ ].filter(Boolean).join("\n");
2164
+ }
2165
+ }
2166
+ }
2167
+ });
2168
+ }
2169
+ }
2170
+
1673
2171
  // src/core/security/sanitizer.ts
1674
2172
  import { Buffer as Buffer2 } from "buffer";
1675
2173
  var DEFAULT_FIELD_LIMIT = 65535;
@@ -1753,6 +2251,44 @@ var AuditEmitter = class {
1753
2251
  }
1754
2252
  };
1755
2253
 
2254
+ // src/core/security/process-env.ts
2255
+ var DEFAULT_ALLOWED_KEYS = /* @__PURE__ */ new Set([
2256
+ "PATH",
2257
+ "HOME",
2258
+ "USER",
2259
+ "SHELL",
2260
+ "LANG",
2261
+ "TERM",
2262
+ "TMPDIR",
2263
+ "XDG_RUNTIME_DIR",
2264
+ "NODE_ENV",
2265
+ // Git
2266
+ "GIT_AUTHOR_NAME",
2267
+ "GIT_AUTHOR_EMAIL",
2268
+ "GIT_COMMITTER_NAME",
2269
+ "GIT_COMMITTER_EMAIL",
2270
+ // Homebrew / system
2271
+ "HOMEBREW_PREFIX",
2272
+ "HOMEBREW_CELLAR",
2273
+ "HOMEBREW_REPOSITORY"
2274
+ ]);
2275
+ function buildProcessEnv(allowedKeys, inject) {
2276
+ const allowed = new Set(DEFAULT_ALLOWED_KEYS);
2277
+ if (allowedKeys) {
2278
+ for (const k of allowedKeys) allowed.add(k);
2279
+ }
2280
+ const env = {};
2281
+ for (const [key, value] of Object.entries(process.env)) {
2282
+ if (allowed.has(key) && value !== void 0) {
2283
+ env[key] = value;
2284
+ }
2285
+ }
2286
+ if (inject) {
2287
+ Object.assign(env, inject);
2288
+ }
2289
+ return env;
2290
+ }
2291
+
1756
2292
  // src/core/update/version-utils.ts
1757
2293
  function parseVersion(v) {
1758
2294
  const cleaned = v.replace(/^v/, "").split("-")[0] ?? v;
@@ -3230,6 +3766,92 @@ var SecretStore = class {
3230
3766
  };
3231
3767
  }
3232
3768
  };
3769
+
3770
+ // src/core/orchestrator/claude-stream-parser.ts
3771
+ function parseClaudeStream(stdout) {
3772
+ let sessionId = null;
3773
+ let model = null;
3774
+ let costUsd = null;
3775
+ let usage = null;
3776
+ let isError = false;
3777
+ let errorMessage = null;
3778
+ let stopReason = null;
3779
+ const textBlocks = [];
3780
+ for (const line of stdout.split("\n")) {
3781
+ if (!line.trim()) continue;
3782
+ let event;
3783
+ try {
3784
+ event = JSON.parse(line);
3785
+ } catch {
3786
+ continue;
3787
+ }
3788
+ const type = event.type;
3789
+ if (type === "system" && event.subtype === "init") {
3790
+ sessionId = event.session_id ?? null;
3791
+ model = event.model ?? null;
3792
+ }
3793
+ if (type === "assistant") {
3794
+ const msg = event.message;
3795
+ const content = msg?.content ?? event.content;
3796
+ if (Array.isArray(content)) {
3797
+ for (const block of content) {
3798
+ if (block.type === "text" && block.text) {
3799
+ textBlocks.push(block.text);
3800
+ }
3801
+ }
3802
+ }
3803
+ }
3804
+ if (type === "result") {
3805
+ isError = !!event.is_error;
3806
+ stopReason = event.stop_reason ?? null;
3807
+ costUsd = typeof event.total_cost_usd === "number" ? event.total_cost_usd : null;
3808
+ const u = event.usage;
3809
+ if (u) {
3810
+ usage = {
3811
+ inputTokens: u.input_tokens ?? 0,
3812
+ cachedInputTokens: u.cache_read_input_tokens ?? 0,
3813
+ outputTokens: u.output_tokens ?? 0
3814
+ };
3815
+ }
3816
+ if (isError) {
3817
+ errorMessage = event.error ?? event.result ?? "Unknown error";
3818
+ }
3819
+ const resultContent = event.result;
3820
+ if (typeof resultContent === "string" && resultContent) {
3821
+ textBlocks.push(resultContent);
3822
+ }
3823
+ }
3824
+ }
3825
+ return {
3826
+ sessionId,
3827
+ model,
3828
+ costUsd,
3829
+ usage,
3830
+ summary: textBlocks.join("\n"),
3831
+ isError,
3832
+ errorMessage,
3833
+ stopReason
3834
+ };
3835
+ }
3836
+ function isMaxTurns(parsed) {
3837
+ return parsed.stopReason === "max_turns" || parsed.stopReason === "tool_use";
3838
+ }
3839
+ function isLoginRequired(stdout) {
3840
+ const patterns = [
3841
+ "not logged in",
3842
+ "login required",
3843
+ "authentication required",
3844
+ "please log in"
3845
+ ];
3846
+ const lower = stdout.toLowerCase();
3847
+ return patterns.some((p) => lower.includes(p));
3848
+ }
3849
+ function deactivateLocalImagePaths(prompt) {
3850
+ return prompt.replace(
3851
+ /(?<=\s|^)(\/[\w./-]+\.(?:png|jpg|jpeg|gif|webp|svg))(?=\s|$)/gi,
3852
+ "[image-path:$1]"
3853
+ );
3854
+ }
3233
3855
  export {
3234
3856
  AGENT_STATUSES,
3235
3857
  AgentRegistry,
@@ -3272,6 +3894,7 @@ export {
3272
3894
  areDependenciesMet,
3273
3895
  buildAgentBindings,
3274
3896
  buildChainOrigin,
3897
+ buildProcessEnv,
3275
3898
  checkAllowlist,
3276
3899
  checkChainDepth,
3277
3900
  checkMentionGate,
@@ -3279,8 +3902,11 @@ export {
3279
3902
  classifyUpdate,
3280
3903
  compareVersions,
3281
3904
  createConfigRevision,
3905
+ deactivateLocalImagePaths,
3282
3906
  defineCoreEntityContexts,
3283
3907
  defineCoreTables,
3908
+ defineDomainEntityContexts,
3909
+ defineDomainTables,
3284
3910
  detectCycle,
3285
3911
  discoverChannels,
3286
3912
  discoverProviders,
@@ -3289,7 +3915,10 @@ export {
3289
3915
  initConfig,
3290
3916
  interpolate,
3291
3917
  interpolateEnv,
3918
+ isLoginRequired,
3919
+ isMaxTurns,
3292
3920
  loadConfig,
3921
+ parseClaudeStream,
3293
3922
  parseVersion,
3294
3923
  runPackageMigrations,
3295
3924
  sanitize,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "botinabox",
3
- "version": "0.2.4",
3
+ "version": "0.3.0",
4
4
  "description": "Bot in a Box — framework for building multi-agent bots",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",