@routecraft/cli 0.6.0-canary.8 → 0.6.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.
@@ -0,0 +1,2 @@
1
+ var r="0.6.0";export{r as a};//# sourceMappingURL=chunk-QJXOM5GC.js.map
2
+ //# sourceMappingURL=chunk-QJXOM5GC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../package.json"],"names":["version"],"mappings":"AAEE,IAAAA,CAAAA,CAAW","file":"chunk-QJXOM5GC.js","sourcesContent":["{\n \"name\": \"@routecraft/cli\",\n \"version\": \"0.6.0\",\n \"description\": \"CLI for running Routecraft capabilities\",\n \"private\": false,\n \"type\": \"module\",\n \"main\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"import\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\"\n }\n },\n \"files\": [\n \"dist\"\n ],\n \"scripts\": {\n \"build\": \"tsup src/index.ts src/tui/app.tsx src/tui/db.ts --format esm --dts --external react --external ink --external bun:sqlite\",\n \"test\": \"vitest\",\n \"test:coverage\": \"vitest --coverage\",\n \"prepublishOnly\": \"bun run build\"\n },\n \"dependencies\": {\n \"@opentelemetry/sdk-trace-base\": \"^2.10.0\",\n \"@routecraft/routecraft\": \"^0.6.0\",\n \"agent-browser\": \"^0.17.1\",\n \"cheerio\": \"^1.2.0\",\n \"commander\": \"^14.0.3\",\n \"croner\": \"^10.0.1\",\n \"dotenv\": \"^17.4.2\",\n \"fast-xml-parser\": \"^5.10.1\",\n \"imapflow\": \"^1.4.7\",\n \"ink\": \"^5.2.1\",\n \"jose\": \"^6.2.8\",\n \"mailparser\": \"^3.9.15\",\n \"nodemailer\": \"^6.10.1\",\n \"papaparse\": \"^5.5.4\",\n \"pino-pretty\": \"^13.1.3\",\n \"react\": \"^18.3.1\",\n \"tsdav\": \"^2.3.1\"\n },\n \"devDependencies\": {\n \"@types/mailparser\": \"^3.4.6\",\n \"@types/nodemailer\": \"^6.4.24\",\n \"@types/react\": \"^18.3.28\",\n \"ink-testing-library\": \"^4.0.0\"\n },\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"repository\": \"https://github.com/routecraftjs/routecraft\",\n \"bin\": {\n \"craft\": \"./dist/index.js\"\n },\n \"engines\": {\n \"bun\": \">=1.1.0\"\n },\n \"keywords\": [\n \"routecraft\",\n \"cli\",\n \"routes\",\n \"automation\",\n \"typescript\"\n ],\n \"author\": \"routecraftjs\",\n \"license\": \"Apache-2.0\",\n \"homepage\": \"https://routecraft.dev\"\n}\n"]}
@@ -0,0 +1,200 @@
1
+ function A(l){try{let s=JSON.parse(l);return s&&typeof s=="object"?s:{}}catch{return {}}}function d(l){return typeof l=="string"?l:void 0}function h(l){return typeof l=="number"&&Number.isFinite(l)?l:void 0}function p(l,s){let n=l._snapshot;if(n&&typeof n=="object"&&s in n){let t=n[s];return {present:true,json:JSON.stringify(t??null)}}return {present:false,json:null}}function T(l){let s=new Map,n=[];for(let t of l){let r=A(t.details),e=t.eventName.split(":").pop(),o=d(r.toolCallId)??`${t.id}`,a=s.get(o);a||(a={toolCallId:o,toolName:d(r.toolName)??"?",routeId:d(r.routeId)??"",exchangeId:t.exchangeId??d(r.exchangeId)??"",agentName:d(r.agentName)??null,status:"invoked",durationMs:null,timestamp:t.timestamp,hasInput:false,hasOutput:false,input:null,output:null,error:null,errorName:null},s.set(o,a),n.push(o));let u=d(r.toolName);if(u&&(a.toolName=u),e==="invoked"){let i=p(r,"input");a.hasInput=i.present,a.input=i.json;}else if(e==="result"){a.status="result",a.durationMs=h(r.duration)??a.durationMs;let i=p(r,"output");a.hasOutput=i.present,a.output=i.json;}else if(e==="error"){a.status="error",a.durationMs=h(r.duration)??a.durationMs,a.errorName=d(r.errorName)??a.errorName;let i=p(r,"error");i.present?a.error=i.json:"error"in r&&(a.error=JSON.stringify(r.error??null));}}return n.map(t=>s.get(t))}function S(l){return l.replace(/[%_\\]/g,"\\$&")}var f=class l{db;stmtCache=new Map;agentAgg={lastId:0,agents:new Map};toolAgg={lastId:0,tools:new Map};constructor(s){this.db=s;}stmt(s,n){let t=this.stmtCache.get(s);return t||(t=this.db.prepare(n),this.stmtCache.set(s,t)),t}static async open(s){let n;try{n=(await import('bun:sqlite')).Database;}catch{throw new Error("bun:sqlite is not available. The craft CLI requires Bun >= 1.1.0.")}let t=new n(s,{readonly:true});try{t.exec("PRAGMA journal_mode = WAL");}catch{}{let r=new n(s);try{r.prepare("CREATE INDEX IF NOT EXISTS idx_exchanges_started_at ON exchanges(started_at)").run(),r.prepare("CREATE INDEX IF NOT EXISTS idx_exchanges_route_started ON exchanges(route_id, started_at)").run(),r.prepare("CREATE INDEX IF NOT EXISTS idx_exchanges_correlation_id ON exchanges(correlation_id)").run(),r.prepare("PRAGMA table_info(events)").all().some(o=>o.name==="exchange_id")||(r.prepare("ALTER TABLE events ADD COLUMN exchange_id TEXT").run(),r.prepare("ALTER TABLE events ADD COLUMN correlation_id TEXT").run()),r.prepare("CREATE INDEX IF NOT EXISTS idx_events_exchange_id ON events(exchange_id)").run(),r.prepare("CREATE INDEX IF NOT EXISTS idx_events_correlation_id ON events(correlation_id)").run(),r.prepare(`CREATE TABLE IF NOT EXISTS exchange_snapshots (
2
+ exchange_id TEXT NOT NULL,
3
+ context_id TEXT NOT NULL,
4
+ headers TEXT NOT NULL,
5
+ body TEXT,
6
+ truncated INTEGER NOT NULL DEFAULT 0,
7
+ captured_at TEXT NOT NULL DEFAULT (datetime('now')),
8
+ PRIMARY KEY (exchange_id, context_id)
9
+ )`).run();}catch{}finally{r.close();}}return new l(t)}getRouteSummary(){return this.stmt("routeSummary",`WITH unique_routes AS (
10
+ SELECT
11
+ id,
12
+ MAX(registered_at) AS registered_at,
13
+ (SELECT r2.status FROM routes r2
14
+ WHERE r2.id = r.id
15
+ ORDER BY r2.registered_at DESC LIMIT 1) AS status
16
+ FROM routes r
17
+ GROUP BY id
18
+ ),
19
+ exchange_counts AS (
20
+ SELECT
21
+ route_id,
22
+ COUNT(*) AS total,
23
+ SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) AS completed,
24
+ SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) AS failed,
25
+ SUM(CASE WHEN status = 'dropped' THEN 1 ELSE 0 END) AS dropped
26
+ FROM exchanges
27
+ GROUP BY route_id
28
+ ),
29
+ recent_avg AS (
30
+ SELECT route_id, AVG(duration_ms) AS avgDur
31
+ FROM exchanges
32
+ WHERE started_at >= datetime('now', '-5 minutes')
33
+ GROUP BY route_id
34
+ )
35
+ SELECT
36
+ ur.id,
37
+ ur.status,
38
+ COALESCE(ec.total, 0) AS totalExchanges,
39
+ COALESCE(ec.completed, 0) AS completedExchanges,
40
+ COALESCE(ec.failed, 0) AS failedExchanges,
41
+ COALESCE(ec.dropped, 0) AS droppedExchanges,
42
+ ra.avgDur AS avgDurationMs
43
+ FROM unique_routes ur
44
+ LEFT JOIN exchange_counts ec ON ur.id = ec.route_id
45
+ LEFT JOIN recent_avg ra ON ur.id = ra.route_id
46
+ ORDER BY ur.registered_at DESC`).all()}getExchangesByRoute(s,n=-1){return this.stmt("exchangesByRoute",`SELECT
47
+ id,
48
+ route_id AS routeId,
49
+ context_id AS contextId,
50
+ correlation_id AS correlationId,
51
+ status,
52
+ started_at AS startedAt,
53
+ completed_at AS completedAt,
54
+ duration_ms AS durationMs,
55
+ error
56
+ FROM exchanges
57
+ WHERE route_id = ?
58
+ ORDER BY started_at DESC
59
+ LIMIT ?`).all(s,n)}getAllExchanges(s=-1){return this.stmt("allExchanges",`SELECT
60
+ id,
61
+ route_id AS routeId,
62
+ context_id AS contextId,
63
+ correlation_id AS correlationId,
64
+ status,
65
+ started_at AS startedAt,
66
+ completed_at AS completedAt,
67
+ duration_ms AS durationMs,
68
+ error
69
+ FROM exchanges
70
+ ORDER BY started_at DESC
71
+ LIMIT ?`).all(s)}getFailedExchanges(s=-1){return this.stmt("failedExchanges",`SELECT
72
+ id,
73
+ route_id AS routeId,
74
+ context_id AS contextId,
75
+ correlation_id AS correlationId,
76
+ status,
77
+ started_at AS startedAt,
78
+ completed_at AS completedAt,
79
+ duration_ms AS durationMs,
80
+ error
81
+ FROM exchanges
82
+ WHERE status = 'failed'
83
+ ORDER BY started_at DESC
84
+ LIMIT ?`).all(s)}getExchangeById(s){return this.stmt("exchangeById",`SELECT
85
+ id,
86
+ route_id AS routeId,
87
+ context_id AS contextId,
88
+ correlation_id AS correlationId,
89
+ status,
90
+ started_at AS startedAt,
91
+ completed_at AS completedAt,
92
+ duration_ms AS durationMs,
93
+ error
94
+ FROM exchanges
95
+ WHERE id = ?
96
+ LIMIT 1`).get(s)??null}getEventsByExchange(s,n){let t=n||s;return this.hasEventIdColumns()?this.stmt("eventsByExchangeIndexed",`SELECT
97
+ id,
98
+ timestamp,
99
+ context_id AS contextId,
100
+ event_name AS eventName,
101
+ details
102
+ FROM events
103
+ WHERE exchange_id = ? OR correlation_id = ?
104
+ ORDER BY id ASC`).all(t,t):this.stmt("eventsByExchangeFallback",`SELECT
105
+ id,
106
+ timestamp,
107
+ context_id AS contextId,
108
+ event_name AS eventName,
109
+ details
110
+ FROM events
111
+ WHERE details LIKE '%' || ? || '%' ESCAPE '\\'
112
+ ORDER BY id ASC`).all(S(t))}hasEventIdColumns(){if(this._hasEventIdColumns!==void 0)return this._hasEventIdColumns;try{let s=this.db.prepare("PRAGMA table_info(events)").all();this._hasEventIdColumns=s.some(n=>n.name==="exchange_id");}catch{this._hasEventIdColumns=false;}return this._hasEventIdColumns}_hasEventIdColumns;getRecentEvents(s){let n=s?.limit??100,t=s?.sinceId??0,r=`
113
+ SELECT
114
+ id,
115
+ timestamp,
116
+ context_id AS contextId,
117
+ event_name AS eventName,
118
+ details
119
+ FROM events
120
+ WHERE id > ?
121
+ `,e=[t];if(s?.eventNameFilter&&(r+=" AND event_name LIKE ? ESCAPE '\\'",e.push(`%${S(s.eventNameFilter)}%`)),s?.routeIdFilter){r+=" AND (event_name LIKE ? ESCAPE '\\' OR details LIKE ? ESCAPE '\\')";let a=S(s.routeIdFilter);e.push(`%${a}%`,`%${a}%`);}return r+=" ORDER BY id DESC LIMIT ?",e.push(n),this.db.prepare(r).all(...e)}getMetrics(){let n=this.stmt("metrics",`SELECT
122
+ (SELECT COUNT(DISTINCT id) FROM routes) AS totalRoutes,
123
+ (SELECT COUNT(*) FROM exchanges) AS totalExchanges,
124
+ (SELECT COUNT(*) FROM exchanges WHERE status = 'completed') AS completedExchanges,
125
+ (SELECT COUNT(*) FROM exchanges WHERE status = 'failed') AS failedExchanges,
126
+ (SELECT COUNT(*) FROM exchanges WHERE status = 'dropped') AS droppedExchanges,
127
+ (SELECT AVG(duration_ms) FROM exchanges
128
+ WHERE started_at >= datetime('now', '-5 minutes')) AS avgDurationMs`).get(),r=this.stmt("percentiles",`SELECT duration_ms
129
+ FROM exchanges
130
+ WHERE duration_ms IS NOT NULL
131
+ AND started_at >= datetime('now', '-5 minutes')
132
+ ORDER BY duration_ms ASC`).all().map(e=>e.duration_ms);return {...n,errorRate:n.totalExchanges>0?n.failedExchanges/n.totalExchanges:0,p90DurationMs:R(r,.9),p95DurationMs:R(r,.95),p99DurationMs:R(r,.99)}}getLiveTrafficBuckets(s=60,n=5){let t=Math.floor(Date.now()/1e3),r=Math.floor(t/n)*n,e=r-(s-1)*n,a=this.stmt("liveTraffic",`SELECT
133
+ CAST((CAST(strftime('%s', started_at) AS INTEGER) - ?) / ? AS INTEGER) AS bucket,
134
+ COUNT(*) AS cnt
135
+ FROM exchanges
136
+ WHERE CAST(strftime('%s', started_at) AS INTEGER) >= ?
137
+ AND CAST(strftime('%s', started_at) AS INTEGER) <= ?
138
+ GROUP BY bucket`).all(e,n,e,r+n),u=new Array(s).fill(0);for(let i of a)i.bucket>=0&&i.bucket<s&&(u[i.bucket]+=i.cnt);return u}getSingleRouteActivity(s,n=12,t=5){let r=Math.floor(Date.now()/1e3),o=Math.floor(r/t)*t-(n-1)*t,u=this.stmt("singleRouteActivity",`SELECT
139
+ CAST((CAST(strftime('%s', started_at) AS INTEGER) - ?) / ? AS INTEGER) AS bucket,
140
+ COUNT(*) AS cnt
141
+ FROM exchanges
142
+ WHERE route_id = ?
143
+ AND CAST(strftime('%s', started_at) AS INTEGER) >= ?
144
+ GROUP BY bucket`).all(o,t,s,o),c=this.stmt("singleRouteErrors",`SELECT COUNT(*) AS cnt
145
+ FROM exchanges
146
+ WHERE route_id = ?
147
+ AND status = 'failed'
148
+ AND CAST(strftime('%s', started_at) AS INTEGER) >= ?`).get(s,o),m=new Array(n).fill(0);for(let g of u)g.bucket>=0&&g.bucket<n&&(m[g.bucket]=g.cnt);return {throughput:m,recentErrors:c?.cnt??0}}getMaxEventId(){return this.db.prepare("SELECT COALESCE(MAX(id), 0) AS maxId FROM events").get().maxId}getExchangeSnapshot(s){try{let t=this.stmt("exchangeSnapshot",`SELECT headers, body, truncated
149
+ FROM exchange_snapshots
150
+ WHERE exchange_id = ?
151
+ LIMIT 1`).get(s);return t?{headers:t.headers,body:t.body,truncated:t.truncated!==0}:null}catch{return null}}getAgents(){if(!this.hasEventIdColumns())return [];let s=this.stmt("agentLifecycle",`SELECT id, timestamp, event_name AS eventName, details, exchange_id AS exchangeId
152
+ FROM events
153
+ WHERE id > ?
154
+ AND (event_name = 'agent:registered'
155
+ OR event_name = 'route:agent:started'
156
+ OR event_name = 'route:agent:finished'
157
+ OR event_name = 'route:agent:error')
158
+ ORDER BY id ASC`).all(this.agentAgg.lastId),n=this.agentAgg.agents,t=(e,o)=>{let a=n.get(e);return a||(a={key:e,source:o,model:null,description:null,runs:new Set,errorCount:0,totalTokens:0,lastRunAt:null},n.set(e,a)),a};for(let e of s){e.id>this.agentAgg.lastId&&(this.agentAgg.lastId=e.id);let o=A(e.details);if(e.eventName==="agent:registered"){let g=d(o.agentId);if(!g)continue;let E=t(g,"registered");E.model=d(o.model)??E.model,E.description=d(o.description)??E.description;continue}let a=e.eventName.split(":").pop(),u=d(o.agentName),i=d(o.routeId)??"",c=u??i;if(!c)continue;let m=t(c,u?"registered":"inline");if(m.model=d(o.model)??m.model,m.lastRunAt=e.timestamp,a==="started"){let g=e.exchangeId??d(o.exchangeId);g&&m.runs.add(g);}else a==="finished"?m.totalTokens+=h(o.totalTokens)??0:a==="error"&&(m.errorCount+=1);}let r=Array.from(n.values()).map(e=>({key:e.key,source:e.source,model:e.model,description:e.description,runCount:e.runs.size,errorCount:e.errorCount,totalTokens:e.totalTokens,lastRunAt:e.lastRunAt}));return r.sort((e,o)=>e.runCount>0!=o.runCount>0?e.runCount>0?-1:1:e.lastRunAt&&o.lastRunAt?e.lastRunAt<o.lastRunAt?1:-1:e.key<o.key?-1:1),r}getAgentRuns(s,n,t=50){if(!this.hasEventIdColumns())return [];let r=(n==="registered"?this.stmt("agentStartedByName",`SELECT id, timestamp, event_name AS eventName, details, exchange_id AS exchangeId
159
+ FROM events
160
+ WHERE event_name = 'route:agent:started'
161
+ AND json_extract(details, '$.agentName') = ?
162
+ ORDER BY id DESC
163
+ LIMIT ?`):this.stmt("agentStartedInline",`SELECT id, timestamp, event_name AS eventName, details, exchange_id AS exchangeId
164
+ FROM events
165
+ WHERE event_name = 'route:agent:started'
166
+ AND json_extract(details, '$.agentName') IS NULL
167
+ AND json_extract(details, '$.routeId') = ?
168
+ ORDER BY id DESC
169
+ LIMIT ?`)).all(s,t*2),e=new Set,o=[];for(let c of r){let m=A(c.details),g=d(m.routeId)??"",E=c.exchangeId??d(m.exchangeId);if(!(!E||e.has(E))&&(e.add(E),o.push({exchangeId:E,routeId:g,correlationId:d(m.correlationId)??"",timestamp:c.timestamp}),o.length>=t))break}if(o.length===0)return [];let a=o.map(()=>"?").join(","),u=this.db.prepare(`SELECT id, route_id AS routeId, context_id AS contextId,
170
+ correlation_id AS correlationId, status,
171
+ started_at AS startedAt, completed_at AS completedAt,
172
+ duration_ms AS durationMs, error
173
+ FROM exchanges WHERE id IN (${a})`).all(...o.map(c=>c.exchangeId)),i=new Map(u.map(c=>[c.id,c]));return o.map(c=>{let m=i.get(c.exchangeId);return m||{id:c.exchangeId,routeId:c.routeId,contextId:"",correlationId:c.correlationId,status:"started",startedAt:c.timestamp,completedAt:null,durationMs:null,error:null}})}getAgentRunInfos(s){let n=new Map;if(!this.hasEventIdColumns()||s.length===0)return n;let t=s.map(()=>"?").join(","),r=this.db.prepare(`SELECT id, timestamp, event_name AS eventName, details, exchange_id AS exchangeId
174
+ FROM events
175
+ WHERE exchange_id IN (${t})
176
+ AND (event_name = 'route:agent:started'
177
+ OR event_name = 'route:agent:finished'
178
+ OR event_name = 'route:agent:error')
179
+ ORDER BY id ASC`).all(...s);for(let e of r){let o=e.exchangeId;if(!o)continue;let a=n.get(o);a||(a={exchangeId:o,model:null,finishReason:null,inputTokens:null,outputTokens:null,totalTokens:null,status:"running"},n.set(o,a));let u=A(e.details),i=e.eventName.split(":").pop();a.model=d(u.model)??a.model,i==="finished"?(a.status=a.status==="error"?"error":"finished",a.finishReason=d(u.finishReason)??a.finishReason,a.inputTokens=h(u.inputTokens)??a.inputTokens,a.outputTokens=h(u.outputTokens)??a.outputTokens,a.totalTokens=h(u.totalTokens)??a.totalTokens):i==="error"&&(a.status="error");}return n}getAgentRunInfo(s){if(!this.hasEventIdColumns())return null;let n=this.stmt("agentRunInfo",`SELECT id, timestamp, event_name AS eventName, details, exchange_id AS exchangeId
180
+ FROM events
181
+ WHERE exchange_id = ?
182
+ AND (event_name = 'route:agent:started'
183
+ OR event_name = 'route:agent:finished'
184
+ OR event_name = 'route:agent:error')
185
+ ORDER BY id ASC`).all(s);if(n.length===0)return null;let t={exchangeId:s,model:null,finishReason:null,inputTokens:null,outputTokens:null,totalTokens:null,status:"running"};for(let r of n){let e=A(r.details),o=r.eventName.split(":").pop();t.model=d(e.model)??t.model,o==="finished"?(t.status=t.status==="error"?"error":"finished",t.finishReason=d(e.finishReason)??t.finishReason,t.inputTokens=h(e.inputTokens)??t.inputTokens,t.outputTokens=h(e.outputTokens)??t.outputTokens,t.totalTokens=h(e.totalTokens)??t.totalTokens):o==="error"&&(t.status="error");}return t}getAgentRunToolCalls(s){if(!this.hasEventIdColumns())return [];let n=this.stmt("agentRunToolCalls",`SELECT id, timestamp, event_name AS eventName, details, exchange_id AS exchangeId
186
+ FROM events
187
+ WHERE exchange_id = ? AND event_name LIKE 'route:agent:tool:%'
188
+ ORDER BY id ASC`).all(s);return T(n)}getTools(){if(!this.hasEventIdColumns())return [];let s=this.stmt("toolLifecycle",`SELECT id, timestamp, event_name AS eventName, details, exchange_id AS exchangeId
189
+ FROM events
190
+ WHERE id > ?
191
+ AND (event_name = 'agent:tool:registered'
192
+ OR event_name = 'route:agent:tool:invoked'
193
+ OR event_name = 'route:agent:tool:error')
194
+ ORDER BY id ASC`).all(this.toolAgg.lastId),n=this.toolAgg.tools,t=(e,o)=>{let a=n.get(e);return a||(a={name:e,source:o,callCount:0,errorCount:0,lastCalledAt:null},n.set(e,a)),a};for(let e of s){e.id>this.toolAgg.lastId&&(this.toolAgg.lastId=e.id);let o=A(e.details);if(e.eventName==="agent:tool:registered"){let c=d(o.toolName);c&&(t(c,"registered").source="registered");continue}let a=d(o.toolName);if(!a)continue;let u=t(a,"observed"),i=e.eventName.split(":").pop();i==="invoked"?(u.callCount+=1,u.lastCalledAt=e.timestamp):i==="error"&&(u.errorCount+=1,u.lastCalledAt=e.timestamp);}let r=Array.from(n.values());return r.sort((e,o)=>e.callCount>0!=o.callCount>0?e.callCount>0?-1:1:e.lastCalledAt&&o.lastCalledAt?e.lastCalledAt<o.lastCalledAt?1:-1:e.name<o.name?-1:1),r}getToolCalls(s,n=100){if(!this.hasEventIdColumns())return [];let t=this.stmt("toolCalls",`SELECT id, timestamp, event_name AS eventName, details, exchange_id AS exchangeId
195
+ FROM events
196
+ WHERE event_name LIKE 'route:agent:tool:%'
197
+ AND json_extract(details, '$.toolName') = ?
198
+ ORDER BY id DESC
199
+ LIMIT ?`).all(s,n*3);t.reverse();let r=T(t);return r.reverse(),r.slice(0,n)}close(){this.db.close();}};function R(l,s){if(l.length===0)return null;let n=s*(l.length-1),t=Math.floor(n),r=Math.ceil(n);return t===r?l[t]:l[t]+(l[r]-l[t])*(n-t)}export{f as a};//# sourceMappingURL=chunk-R3PVIDDT.js.map
200
+ //# sourceMappingURL=chunk-R3PVIDDT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/tui/db.ts"],"names":["parseDetails","raw","v","asString","asNumber","snapshotField","d","field","snap","val","correlateToolCalls","rows","byCall","order","row","suffix","toolCallId","call","toolName","id","escapeLike","s","TelemetryDb","_TelemetryDb","db","key","sql","dbPath","Database","wdb","c","routeId","limit","exchangeId","correlationId","searchId","info","col","options","sinceId","params","escaped","durations","r","percentile","bucketCount","bucketWidthSec","nowSec","snapped","windowStart","result","tRows","eRow","throughput","map","ensure","source","acc","agentName","exId","a","b","agentKey","seen","meta","placeholders","found","m","foundById","e","hit","exchangeIds","name","calls","sorted","p","idx","lo","hi"],"mappings":"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","file":"chunk-R3PVIDDT.js","sourcesContent":["/// <reference types=\"bun-types\" />\nimport type { TelemetryEvent } from \"@routecraft/routecraft\";\nimport type {\n AgentSummary,\n ToolSummary,\n AgentRunInfo,\n ToolCallRow,\n} from \"./types.js\";\n\n/** Exchange row shape from the telemetry SQLite database. */\ninterface TelemetryExchange {\n id: string;\n routeId: string;\n contextId: string;\n correlationId: string;\n status: string;\n startedAt: string;\n completedAt: string | null;\n durationMs: number | null;\n error: string | null;\n}\n\n/** Raw event row used by the agent/tool derivation queries. */\ninterface EventRow {\n id: number;\n timestamp: string;\n eventName: string;\n details: string;\n exchangeId: string | null;\n}\n\n/** Parse an event `details` JSON string, returning {} on failure. */\nfunction parseDetails(raw: string): Record<string, unknown> {\n try {\n const v = JSON.parse(raw) as unknown;\n return v && typeof v === \"object\" ? (v as Record<string, unknown>) : {};\n } catch {\n return {};\n }\n}\n\nfunction asString(v: unknown): string | undefined {\n return typeof v === \"string\" ? v : undefined;\n}\n\nfunction asNumber(v: unknown): number | undefined {\n return typeof v === \"number\" && Number.isFinite(v) ? v : undefined;\n}\n\n/** Read the sensitive `_snapshot` envelope from a tool event's details. */\nfunction snapshotField(\n d: Record<string, unknown>,\n field: \"input\" | \"output\" | \"error\",\n): { present: boolean; json: string | null } {\n const snap = d[\"_snapshot\"];\n if (snap && typeof snap === \"object\" && field in (snap as object)) {\n const val = (snap as Record<string, unknown>)[field];\n return { present: true, json: JSON.stringify(val ?? null) };\n }\n return { present: false, json: null };\n}\n\n/**\n * Correlate a time-ordered list of `agent:tool:*` events into one row per\n * `toolCallId`, merging the invoked event with its later result/error.\n */\nfunction correlateToolCalls(rows: EventRow[]): ToolCallRow[] {\n const byCall = new Map<string, ToolCallRow>();\n const order: string[] = [];\n for (const row of rows) {\n const d = parseDetails(row.details);\n const suffix = row.eventName.split(\":\").pop();\n const toolCallId = asString(d[\"toolCallId\"]) ?? `${row.id}`;\n let call = byCall.get(toolCallId);\n if (!call) {\n call = {\n toolCallId,\n toolName: asString(d[\"toolName\"]) ?? \"?\",\n routeId: asString(d[\"routeId\"]) ?? \"\",\n exchangeId: row.exchangeId ?? asString(d[\"exchangeId\"]) ?? \"\",\n agentName: asString(d[\"agentName\"]) ?? null,\n status: \"invoked\",\n durationMs: null,\n timestamp: row.timestamp,\n hasInput: false,\n hasOutput: false,\n input: null,\n output: null,\n error: null,\n errorName: null,\n };\n byCall.set(toolCallId, call);\n order.push(toolCallId);\n }\n const toolName = asString(d[\"toolName\"]);\n if (toolName) call.toolName = toolName;\n if (suffix === \"invoked\") {\n const snap = snapshotField(d, \"input\");\n call.hasInput = snap.present;\n call.input = snap.json;\n } else if (suffix === \"result\") {\n call.status = \"result\";\n call.durationMs = asNumber(d[\"duration\"]) ?? call.durationMs;\n const snap = snapshotField(d, \"output\");\n call.hasOutput = snap.present;\n call.output = snap.json;\n } else if (suffix === \"error\") {\n call.status = \"error\";\n call.durationMs = asNumber(d[\"duration\"]) ?? call.durationMs;\n call.errorName = asString(d[\"errorName\"]) ?? call.errorName;\n // The full error rides in `_snapshot` (it can echo tool input) and\n // is only persisted when snapshot capture was on. Fall back to the\n // legacy top-level `error` field for events written before the\n // envelope move.\n const snap = snapshotField(d, \"error\");\n if (snap.present) {\n call.error = snap.json;\n } else if (\"error\" in d) {\n call.error = JSON.stringify(d[\"error\"] ?? null);\n }\n }\n }\n return order.map((id) => byCall.get(id)!);\n}\n\n/**\n * Incremental aggregation state for one agent (Agents tab). Aggregates\n * survive across polls so each refresh only parses events newer than\n * `AgentAggState.lastId` instead of rescanning the whole events table.\n */\ninterface AgentAcc {\n key: string;\n source: \"registered\" | \"inline\";\n model: string | null;\n description: string | null;\n runs: Set<string>;\n errorCount: number;\n totalTokens: number;\n lastRunAt: string | null;\n}\n\n/** Incremental aggregation state for one tool (Tools tab). */\ninterface ToolAcc {\n name: string;\n source: \"registered\" | \"observed\";\n callCount: number;\n errorCount: number;\n lastCalledAt: string | null;\n}\n\n/**\n * Minimal type for the bun:sqlite database to avoid pulling bun-types\n * into the CLI's public type surface.\n */\ninterface Database {\n prepare(sql: string): Statement;\n query(sql: string): Statement;\n exec(sql: string): void;\n close(): void;\n}\n\ninterface Statement {\n all(...params: unknown[]): unknown[];\n get(...params: unknown[]): unknown;\n run(...params: unknown[]): unknown;\n}\n\ntype DatabaseConstructor = new (\n filename: string,\n options?: { readonly?: boolean; create?: boolean },\n) => Database;\n\n/**\n * Read-only accessor for the telemetry SQLite database.\n * Used by the TUI to query historical data without affecting the running engine.\n *\n * Use the static `open()` factory instead of the constructor directly.\n */\n/** Escape LIKE metacharacters so they match literally. */\nfunction escapeLike(s: string): string {\n return s.replace(/[%_\\\\]/g, \"\\\\$&\");\n}\n\nexport class TelemetryDb {\n private db: Database;\n private stmtCache = new Map<string, Statement>();\n /** Incremental Agents-tab aggregation: only events with id > lastId are parsed per poll. */\n private agentAgg = { lastId: 0, agents: new Map<string, AgentAcc>() };\n /** Incremental Tools-tab aggregation: only events with id > lastId are parsed per poll. */\n private toolAgg = { lastId: 0, tools: new Map<string, ToolAcc>() };\n\n private constructor(db: Database) {\n this.db = db;\n }\n\n /** Prepare and cache a statement by key. Avoids re-parsing on every call. */\n private stmt(key: string, sql: string): Statement {\n let s = this.stmtCache.get(key);\n if (!s) {\n s = this.db.prepare(sql);\n this.stmtCache.set(key, s);\n }\n return s;\n }\n\n /**\n * Open a telemetry database in read-only mode.\n *\n * Uses dynamic `import(\"bun:sqlite\")` so the module resolves at runtime\n * (the CLI is Bun-only; `bun:sqlite` is a Bun built-in and is not a\n * resolvable spec under Node).\n */\n static async open(dbPath: string): Promise<TelemetryDb> {\n let Database: DatabaseConstructor;\n try {\n const mod = await import(\"bun:sqlite\");\n Database = mod.Database as unknown as DatabaseConstructor;\n } catch {\n throw new Error(\n \"bun:sqlite is not available. The craft CLI requires Bun >= 1.1.0.\",\n );\n }\n\n const db = new Database(dbPath, { readonly: true });\n try {\n db.exec(\"PRAGMA journal_mode = WAL\");\n } catch {\n // Read-only connection cannot change journal mode; safe to ignore\n }\n\n // Ensure indexes exist for TUI query performance.\n // If the DB was created before indexes were added, create them now\n // using a separate writable connection.\n {\n const wdb = new Database(dbPath);\n try {\n wdb\n .prepare(\n \"CREATE INDEX IF NOT EXISTS idx_exchanges_started_at ON exchanges(started_at)\",\n )\n .run();\n wdb\n .prepare(\n \"CREATE INDEX IF NOT EXISTS idx_exchanges_route_started ON exchanges(route_id, started_at)\",\n )\n .run();\n wdb\n .prepare(\n \"CREATE INDEX IF NOT EXISTS idx_exchanges_correlation_id ON exchanges(correlation_id)\",\n )\n .run();\n // Add exchange_id/correlation_id columns to events table if missing\n const cols = wdb.prepare(\"PRAGMA table_info(events)\").all() as Array<{\n name: string;\n }>;\n if (!cols.some((c) => c.name === \"exchange_id\")) {\n wdb.prepare(\"ALTER TABLE events ADD COLUMN exchange_id TEXT\").run();\n wdb\n .prepare(\"ALTER TABLE events ADD COLUMN correlation_id TEXT\")\n .run();\n }\n wdb\n .prepare(\n \"CREATE INDEX IF NOT EXISTS idx_events_exchange_id ON events(exchange_id)\",\n )\n .run();\n wdb\n .prepare(\n \"CREATE INDEX IF NOT EXISTS idx_events_correlation_id ON events(correlation_id)\",\n )\n .run();\n // Ensure exchange_snapshots table exists for older databases\n wdb\n .prepare(\n `CREATE TABLE IF NOT EXISTS exchange_snapshots (\n exchange_id TEXT NOT NULL,\n context_id TEXT NOT NULL,\n headers TEXT NOT NULL,\n body TEXT,\n truncated INTEGER NOT NULL DEFAULT 0,\n captured_at TEXT NOT NULL DEFAULT (datetime('now')),\n PRIMARY KEY (exchange_id, context_id)\n )`,\n )\n .run();\n } catch {\n // Best-effort; the writer will create them on next restart\n } finally {\n wdb.close();\n }\n }\n\n return new TelemetryDb(db);\n }\n\n /**\n * Get a summary of all routes with aggregated metrics.\n * Routes are grouped by ID across all context runs so that restarting\n * the same application does not create duplicate rows.\n */\n getRouteSummary(): Array<{\n id: string;\n status: string;\n totalExchanges: number;\n completedExchanges: number;\n failedExchanges: number;\n droppedExchanges: number;\n avgDurationMs: number | null;\n }> {\n const s = this.stmt(\n \"routeSummary\",\n `WITH unique_routes AS (\n SELECT\n id,\n MAX(registered_at) AS registered_at,\n (SELECT r2.status FROM routes r2\n WHERE r2.id = r.id\n ORDER BY r2.registered_at DESC LIMIT 1) AS status\n FROM routes r\n GROUP BY id\n ),\n exchange_counts AS (\n SELECT\n route_id,\n COUNT(*) AS total,\n SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) AS completed,\n SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) AS failed,\n SUM(CASE WHEN status = 'dropped' THEN 1 ELSE 0 END) AS dropped\n FROM exchanges\n GROUP BY route_id\n ),\n recent_avg AS (\n SELECT route_id, AVG(duration_ms) AS avgDur\n FROM exchanges\n WHERE started_at >= datetime('now', '-5 minutes')\n GROUP BY route_id\n )\n SELECT\n ur.id,\n ur.status,\n COALESCE(ec.total, 0) AS totalExchanges,\n COALESCE(ec.completed, 0) AS completedExchanges,\n COALESCE(ec.failed, 0) AS failedExchanges,\n COALESCE(ec.dropped, 0) AS droppedExchanges,\n ra.avgDur AS avgDurationMs\n FROM unique_routes ur\n LEFT JOIN exchange_counts ec ON ur.id = ec.route_id\n LEFT JOIN recent_avg ra ON ur.id = ra.route_id\n ORDER BY ur.registered_at DESC`,\n );\n return s.all() as Array<{\n id: string;\n status: string;\n totalExchanges: number;\n completedExchanges: number;\n failedExchanges: number;\n droppedExchanges: number;\n avgDurationMs: number | null;\n }>;\n }\n\n /**\n * Get exchanges for a specific route, ordered by most recent first.\n */\n getExchangesByRoute(routeId: string, limit = -1): TelemetryExchange[] {\n const s = this.stmt(\n \"exchangesByRoute\",\n `SELECT\n id,\n route_id AS routeId,\n context_id AS contextId,\n correlation_id AS correlationId,\n status,\n started_at AS startedAt,\n completed_at AS completedAt,\n duration_ms AS durationMs,\n error\n FROM exchanges\n WHERE route_id = ?\n ORDER BY started_at DESC\n LIMIT ?`,\n );\n return s.all(routeId, limit) as TelemetryExchange[];\n }\n\n /**\n * Get all exchanges across all routes, ordered by most recent first.\n */\n getAllExchanges(limit = -1): TelemetryExchange[] {\n const s = this.stmt(\n \"allExchanges\",\n `SELECT\n id,\n route_id AS routeId,\n context_id AS contextId,\n correlation_id AS correlationId,\n status,\n started_at AS startedAt,\n completed_at AS completedAt,\n duration_ms AS durationMs,\n error\n FROM exchanges\n ORDER BY started_at DESC\n LIMIT ?`,\n );\n return s.all(limit) as TelemetryExchange[];\n }\n\n /**\n * Get all failed exchanges across all routes, ordered by most recent first.\n * Shows every failed exchange including child exchanges from split/multicast,\n * without deduplication by correlation chain.\n */\n getFailedExchanges(limit = -1): TelemetryExchange[] {\n const s = this.stmt(\n \"failedExchanges\",\n `SELECT\n id,\n route_id AS routeId,\n context_id AS contextId,\n correlation_id AS correlationId,\n status,\n started_at AS startedAt,\n completed_at AS completedAt,\n duration_ms AS durationMs,\n error\n FROM exchanges\n WHERE status = 'failed'\n ORDER BY started_at DESC\n LIMIT ?`,\n );\n return s.all(limit) as TelemetryExchange[];\n }\n\n /**\n * Get one exchange by id, or null when it is not (yet) recorded.\n * Used by the live detail views to refresh status/duration while the\n * exchange is open.\n */\n getExchangeById(id: string): TelemetryExchange | null {\n const s = this.stmt(\n \"exchangeById\",\n `SELECT\n id,\n route_id AS routeId,\n context_id AS contextId,\n correlation_id AS correlationId,\n status,\n started_at AS startedAt,\n completed_at AS completedAt,\n duration_ms AS durationMs,\n error\n FROM exchanges\n WHERE id = ?\n LIMIT 1`,\n );\n return (s.get(id) as TelemetryExchange | undefined) ?? null;\n }\n\n /**\n * Get events for an exchange and all related exchanges sharing the\n * same correlation ID. Uses indexed exchange_id/correlation_id columns\n * with a fallback to details LIKE for databases created before the\n * column migration.\n */\n getEventsByExchange(\n exchangeId: string,\n correlationId: string,\n ): TelemetryEvent[] {\n const searchId = correlationId || exchangeId;\n\n // Try indexed column lookup first (fast path)\n const hasColumns = this.hasEventIdColumns();\n if (hasColumns) {\n const s = this.stmt(\n \"eventsByExchangeIndexed\",\n `SELECT\n id,\n timestamp,\n context_id AS contextId,\n event_name AS eventName,\n details\n FROM events\n WHERE exchange_id = ? OR correlation_id = ?\n ORDER BY id ASC`,\n );\n return s.all(searchId, searchId) as TelemetryEvent[];\n }\n\n // Fallback: full-text LIKE scan for older databases\n const s = this.stmt(\n \"eventsByExchangeFallback\",\n `SELECT\n id,\n timestamp,\n context_id AS contextId,\n event_name AS eventName,\n details\n FROM events\n WHERE details LIKE '%' || ? || '%' ESCAPE '\\\\'\n ORDER BY id ASC`,\n );\n return s.all(escapeLike(searchId)) as TelemetryEvent[];\n }\n\n /** Check whether the events table has the exchange_id column. */\n private hasEventIdColumns(): boolean {\n if (this._hasEventIdColumns !== undefined) return this._hasEventIdColumns;\n try {\n const info = this.db.prepare(\"PRAGMA table_info(events)\").all() as Array<{\n name: string;\n }>;\n this._hasEventIdColumns = info.some((col) => col.name === \"exchange_id\");\n } catch {\n this._hasEventIdColumns = false;\n }\n return this._hasEventIdColumns;\n }\n private _hasEventIdColumns: boolean | undefined;\n\n /**\n * Get recent events, optionally filtered by event name pattern or route ID.\n */\n getRecentEvents(options?: {\n limit?: number;\n eventNameFilter?: string;\n routeIdFilter?: string;\n sinceId?: number;\n }): TelemetryEvent[] {\n const limit = options?.limit ?? 100;\n const sinceId = options?.sinceId ?? 0;\n\n let sql = `\n SELECT\n id,\n timestamp,\n context_id AS contextId,\n event_name AS eventName,\n details\n FROM events\n WHERE id > ?\n `;\n const params: unknown[] = [sinceId];\n\n if (options?.eventNameFilter) {\n sql += \" AND event_name LIKE ? ESCAPE '\\\\'\";\n params.push(`%${escapeLike(options.eventNameFilter)}%`);\n }\n\n if (options?.routeIdFilter) {\n sql +=\n \" AND (event_name LIKE ? ESCAPE '\\\\' OR details LIKE ? ESCAPE '\\\\')\";\n const escaped = escapeLike(options.routeIdFilter);\n params.push(`%${escaped}%`, `%${escaped}%`);\n }\n\n sql += \" ORDER BY id DESC LIMIT ?\";\n params.push(limit);\n\n const stmt = this.db.prepare(sql);\n return stmt.all(...params) as TelemetryEvent[];\n }\n\n /**\n * Get aggregated metrics for the dashboard, including duration percentiles.\n * Percentiles are computed over exchanges from the last 5 minutes.\n */\n getMetrics(): import(\"./types.js\").Metrics {\n const s = this.stmt(\n \"metrics\",\n `SELECT\n (SELECT COUNT(DISTINCT id) FROM routes) AS totalRoutes,\n (SELECT COUNT(*) FROM exchanges) AS totalExchanges,\n (SELECT COUNT(*) FROM exchanges WHERE status = 'completed') AS completedExchanges,\n (SELECT COUNT(*) FROM exchanges WHERE status = 'failed') AS failedExchanges,\n (SELECT COUNT(*) FROM exchanges WHERE status = 'dropped') AS droppedExchanges,\n (SELECT AVG(duration_ms) FROM exchanges\n WHERE started_at >= datetime('now', '-5 minutes')) AS avgDurationMs`,\n );\n const row = s.get() as {\n totalRoutes: number;\n totalExchanges: number;\n completedExchanges: number;\n failedExchanges: number;\n droppedExchanges: number;\n avgDurationMs: number | null;\n };\n\n // Compute percentiles from recent exchanges with non-null durations\n const pctStmt = this.stmt(\n \"percentiles\",\n `SELECT duration_ms\n FROM exchanges\n WHERE duration_ms IS NOT NULL\n AND started_at >= datetime('now', '-5 minutes')\n ORDER BY duration_ms ASC`,\n );\n const durations = (pctStmt.all() as Array<{ duration_ms: number }>).map(\n (r) => r.duration_ms,\n );\n\n return {\n ...row,\n errorRate:\n row.totalExchanges > 0 ? row.failedExchanges / row.totalExchanges : 0,\n p90DurationMs: percentile(durations, 0.9),\n p95DurationMs: percentile(durations, 0.95),\n p99DurationMs: percentile(durations, 0.99),\n };\n }\n\n /**\n * Get exchange counts for a rolling window using fine-grained buckets.\n * Default: 60 buckets of 5 seconds = 5-minute rolling window.\n * Returns values oldest-first for sparkline rendering.\n */\n getLiveTrafficBuckets(bucketCount = 60, bucketWidthSec = 5): number[] {\n const nowSec = Math.floor(Date.now() / 1000);\n const snapped = Math.floor(nowSec / bucketWidthSec) * bucketWidthSec;\n const windowStart = snapped - (bucketCount - 1) * bucketWidthSec;\n\n const s = this.stmt(\n \"liveTraffic\",\n `SELECT\n CAST((CAST(strftime('%s', started_at) AS INTEGER) - ?) / ? AS INTEGER) AS bucket,\n COUNT(*) AS cnt\n FROM exchanges\n WHERE CAST(strftime('%s', started_at) AS INTEGER) >= ?\n AND CAST(strftime('%s', started_at) AS INTEGER) <= ?\n GROUP BY bucket`,\n );\n const rows = s.all(\n windowStart,\n bucketWidthSec,\n windowStart,\n snapped + bucketWidthSec,\n ) as Array<{ bucket: number; cnt: number }>;\n\n const result = new Array(bucketCount).fill(0) as number[];\n for (const row of rows) {\n if (row.bucket >= 0 && row.bucket < bucketCount) {\n result[row.bucket] += row.cnt;\n }\n }\n return result;\n }\n\n /**\n * Get activity data for a single route: throughput sparkline + recent error count.\n */\n getSingleRouteActivity(\n routeId: string,\n bucketCount = 12,\n bucketWidthSec = 5,\n ): { throughput: number[]; recentErrors: number } {\n const nowSec = Math.floor(Date.now() / 1000);\n const snapped = Math.floor(nowSec / bucketWidthSec) * bucketWidthSec;\n const windowStart = snapped - (bucketCount - 1) * bucketWidthSec;\n\n const tStmt = this.stmt(\n \"singleRouteActivity\",\n `SELECT\n CAST((CAST(strftime('%s', started_at) AS INTEGER) - ?) / ? AS INTEGER) AS bucket,\n COUNT(*) AS cnt\n FROM exchanges\n WHERE route_id = ?\n AND CAST(strftime('%s', started_at) AS INTEGER) >= ?\n GROUP BY bucket`,\n );\n const tRows = tStmt.all(\n windowStart,\n bucketWidthSec,\n routeId,\n windowStart,\n ) as Array<{ bucket: number; cnt: number }>;\n\n const eStmt = this.stmt(\n \"singleRouteErrors\",\n `SELECT COUNT(*) AS cnt\n FROM exchanges\n WHERE route_id = ?\n AND status = 'failed'\n AND CAST(strftime('%s', started_at) AS INTEGER) >= ?`,\n );\n const eRow = eStmt.get(routeId, windowStart) as { cnt: number } | undefined;\n\n const throughput = new Array(bucketCount).fill(0) as number[];\n for (const row of tRows) {\n if (row.bucket >= 0 && row.bucket < bucketCount) {\n throughput[row.bucket] = row.cnt;\n }\n }\n\n return { throughput, recentErrors: eRow?.cnt ?? 0 };\n }\n\n /**\n * Get the maximum event ID (for tailing/polling).\n */\n getMaxEventId(): number {\n const stmt = this.db.prepare(\n \"SELECT COALESCE(MAX(id), 0) AS maxId FROM events\",\n );\n const row = stmt.get() as { maxId: number };\n return row.maxId;\n }\n\n /**\n * Get the exchange snapshot (headers and body) for a given exchange.\n * Returns null if no snapshot was captured.\n */\n getExchangeSnapshot(\n exchangeId: string,\n ): import(\"./types.js\").ExchangeSnapshot | null {\n try {\n const s = this.stmt(\n \"exchangeSnapshot\",\n `SELECT headers, body, truncated\n FROM exchange_snapshots\n WHERE exchange_id = ?\n LIMIT 1`,\n );\n const row = s.get(exchangeId) as\n { headers: string; body: string | null; truncated: number } | undefined;\n if (!row) return null;\n return {\n headers: row.headers,\n body: row.body,\n truncated: row.truncated !== 0,\n };\n } catch {\n // Table may not exist in very old databases\n return null;\n }\n }\n\n // -- Agents tab --\n\n /**\n * List agents derived from registration (`agent:registered`) and\n * lifecycle (`route:agent:started|finished|error`) events. By-name\n * agents are keyed by their registered id; inline agents by their\n * dispatching route id.\n *\n * Aggregation is incremental: each call only reads and parses events\n * newer than the previous call's high-water mark, so the 2s TUI poll\n * stays cheap regardless of how large the events table has grown.\n */\n getAgents(): AgentSummary[] {\n // Agent/tool telemetry postdates the exchange_id column migration, so\n // a legacy database that could not be migrated (read-only file) has\n // no agent events either; degrade to empty instead of erroring on\n // the missing column.\n if (!this.hasEventIdColumns()) return [];\n const rows = this.stmt(\n \"agentLifecycle\",\n `SELECT id, timestamp, event_name AS eventName, details, exchange_id AS exchangeId\n FROM events\n WHERE id > ?\n AND (event_name = 'agent:registered'\n OR event_name = 'route:agent:started'\n OR event_name = 'route:agent:finished'\n OR event_name = 'route:agent:error')\n ORDER BY id ASC`,\n ).all(this.agentAgg.lastId) as EventRow[];\n\n const map = this.agentAgg.agents;\n const ensure = (key: string, source: \"registered\" | \"inline\"): AgentAcc => {\n let acc = map.get(key);\n if (!acc) {\n acc = {\n key,\n source,\n model: null,\n description: null,\n runs: new Set(),\n errorCount: 0,\n totalTokens: 0,\n lastRunAt: null,\n };\n map.set(key, acc);\n }\n return acc;\n };\n\n for (const row of rows) {\n if (row.id > this.agentAgg.lastId) this.agentAgg.lastId = row.id;\n const d = parseDetails(row.details);\n if (row.eventName === \"agent:registered\") {\n const key = asString(d[\"agentId\"]);\n if (!key) continue;\n const acc = ensure(key, \"registered\");\n acc.model = asString(d[\"model\"]) ?? acc.model;\n acc.description = asString(d[\"description\"]) ?? acc.description;\n continue;\n }\n const suffix = row.eventName.split(\":\").pop();\n const agentName = asString(d[\"agentName\"]);\n const routeId = asString(d[\"routeId\"]) ?? \"\";\n const key = agentName ?? routeId;\n if (!key) continue;\n const acc = ensure(key, agentName ? \"registered\" : \"inline\");\n acc.model = asString(d[\"model\"]) ?? acc.model;\n acc.lastRunAt = row.timestamp;\n if (suffix === \"started\") {\n const exId = row.exchangeId ?? asString(d[\"exchangeId\"]);\n if (exId) acc.runs.add(exId);\n } else if (suffix === \"finished\") {\n acc.totalTokens += asNumber(d[\"totalTokens\"]) ?? 0;\n } else if (suffix === \"error\") {\n acc.errorCount += 1;\n }\n }\n\n const result: AgentSummary[] = Array.from(map.values()).map((a) => ({\n key: a.key,\n source: a.source,\n model: a.model,\n description: a.description,\n runCount: a.runs.size,\n errorCount: a.errorCount,\n totalTokens: a.totalTokens,\n lastRunAt: a.lastRunAt,\n }));\n // Active agents (those that have run) first, most-recent first; then\n // registered-but-never-run agents alphabetically.\n result.sort((a, b) => {\n if (a.runCount > 0 !== b.runCount > 0) return a.runCount > 0 ? -1 : 1;\n if (a.lastRunAt && b.lastRunAt) return a.lastRunAt < b.lastRunAt ? 1 : -1;\n return a.key < b.key ? -1 : 1;\n });\n return result;\n }\n\n /**\n * Get the exchanges in which a given agent ran, most recent first.\n * Resolves run exchange ids from `agent:started` events, then joins the\n * `exchanges` table (synthesising a minimal row when the dispatching\n * exchange was not separately recorded).\n */\n getAgentRuns(\n agentKey: string,\n source: \"registered\" | \"inline\",\n limit = 50,\n ): TelemetryExchange[] {\n // Agent/tool telemetry postdates the exchange_id column migration, so\n // a legacy database that could not be migrated (read-only file) has\n // no agent events either; degrade to empty instead of erroring on\n // the missing column.\n if (!this.hasEventIdColumns()) return [];\n // The agent filter runs in SQL (json_extract) so only the selected\n // agent's started events are transferred and parsed. ORDER BY id DESC\n // walks the primary key from the newest row and stops at the LIMIT,\n // so cost tracks recent activity rather than table size. The LIMIT\n // is doubled to absorb duplicate started events for one exchange\n // (deduped below) while still returning `limit` distinct runs.\n const rows = (\n source === \"registered\"\n ? this.stmt(\n \"agentStartedByName\",\n `SELECT id, timestamp, event_name AS eventName, details, exchange_id AS exchangeId\n FROM events\n WHERE event_name = 'route:agent:started'\n AND json_extract(details, '$.agentName') = ?\n ORDER BY id DESC\n LIMIT ?`,\n )\n : this.stmt(\n \"agentStartedInline\",\n `SELECT id, timestamp, event_name AS eventName, details, exchange_id AS exchangeId\n FROM events\n WHERE event_name = 'route:agent:started'\n AND json_extract(details, '$.agentName') IS NULL\n AND json_extract(details, '$.routeId') = ?\n ORDER BY id DESC\n LIMIT ?`,\n )\n ).all(agentKey, limit * 2) as EventRow[];\n\n const seen = new Set<string>();\n const meta: Array<{\n exchangeId: string;\n routeId: string;\n correlationId: string;\n timestamp: string;\n }> = [];\n for (const row of rows) {\n const d = parseDetails(row.details);\n const routeId = asString(d[\"routeId\"]) ?? \"\";\n const exId = row.exchangeId ?? asString(d[\"exchangeId\"]);\n if (!exId || seen.has(exId)) continue;\n seen.add(exId);\n meta.push({\n exchangeId: exId,\n routeId,\n correlationId: asString(d[\"correlationId\"]) ?? \"\",\n timestamp: row.timestamp,\n });\n if (meta.length >= limit) break;\n }\n if (meta.length === 0) return [];\n\n const placeholders = meta.map(() => \"?\").join(\",\");\n const found = this.db\n .prepare(\n `SELECT id, route_id AS routeId, context_id AS contextId,\n correlation_id AS correlationId, status,\n started_at AS startedAt, completed_at AS completedAt,\n duration_ms AS durationMs, error\n FROM exchanges WHERE id IN (${placeholders})`,\n )\n .all(...meta.map((m) => m.exchangeId)) as TelemetryExchange[];\n const foundById = new Map(found.map((e) => [e.id, e]));\n\n return meta.map((m) => {\n const hit = foundById.get(m.exchangeId);\n if (hit) return hit;\n return {\n id: m.exchangeId,\n routeId: m.routeId,\n contextId: \"\",\n correlationId: m.correlationId,\n status: \"started\",\n startedAt: m.timestamp,\n completedAt: null,\n durationMs: null,\n error: null,\n };\n });\n }\n\n /**\n * Batch variant of {@link getAgentRunInfo}: per-run model, finish reason\n * and token usage for a set of run exchanges in one query, keyed by\n * exchange id. Used by the agent runs list to render model/token\n * columns without one query per visible row.\n */\n getAgentRunInfos(exchangeIds: string[]): Map<string, AgentRunInfo> {\n const result = new Map<string, AgentRunInfo>();\n // See getAgents: legacy databases without the exchange_id column\n // cannot contain agent events; degrade to empty.\n if (!this.hasEventIdColumns()) return result;\n if (exchangeIds.length === 0) return result;\n const placeholders = exchangeIds.map(() => \"?\").join(\",\");\n const rows = this.db\n .prepare(\n `SELECT id, timestamp, event_name AS eventName, details, exchange_id AS exchangeId\n FROM events\n WHERE exchange_id IN (${placeholders})\n AND (event_name = 'route:agent:started'\n OR event_name = 'route:agent:finished'\n OR event_name = 'route:agent:error')\n ORDER BY id ASC`,\n )\n .all(...exchangeIds) as EventRow[];\n\n for (const row of rows) {\n const exId = row.exchangeId;\n if (!exId) continue;\n let info = result.get(exId);\n if (!info) {\n info = {\n exchangeId: exId,\n model: null,\n finishReason: null,\n inputTokens: null,\n outputTokens: null,\n totalTokens: null,\n status: \"running\",\n };\n result.set(exId, info);\n }\n const d = parseDetails(row.details);\n const suffix = row.eventName.split(\":\").pop();\n info.model = asString(d[\"model\"]) ?? info.model;\n if (suffix === \"finished\") {\n info.status = info.status === \"error\" ? \"error\" : \"finished\";\n info.finishReason = asString(d[\"finishReason\"]) ?? info.finishReason;\n info.inputTokens = asNumber(d[\"inputTokens\"]) ?? info.inputTokens;\n info.outputTokens = asNumber(d[\"outputTokens\"]) ?? info.outputTokens;\n info.totalTokens = asNumber(d[\"totalTokens\"]) ?? info.totalTokens;\n } else if (suffix === \"error\") {\n info.status = \"error\";\n }\n }\n return result;\n }\n\n /** Per-run agent detail (model, finish reason, tokens) for an exchange. */\n getAgentRunInfo(exchangeId: string): AgentRunInfo | null {\n // Agent/tool telemetry postdates the exchange_id column migration, so\n // a legacy database that could not be migrated (read-only file) has\n // no agent events either; degrade to empty instead of erroring on\n // the missing column.\n if (!this.hasEventIdColumns()) return null;\n const rows = this.stmt(\n \"agentRunInfo\",\n `SELECT id, timestamp, event_name AS eventName, details, exchange_id AS exchangeId\n FROM events\n WHERE exchange_id = ?\n AND (event_name = 'route:agent:started'\n OR event_name = 'route:agent:finished'\n OR event_name = 'route:agent:error')\n ORDER BY id ASC`,\n ).all(exchangeId) as EventRow[];\n if (rows.length === 0) return null;\n\n const info: AgentRunInfo = {\n exchangeId,\n model: null,\n finishReason: null,\n inputTokens: null,\n outputTokens: null,\n totalTokens: null,\n status: \"running\",\n };\n for (const row of rows) {\n const d = parseDetails(row.details);\n const suffix = row.eventName.split(\":\").pop();\n info.model = asString(d[\"model\"]) ?? info.model;\n if (suffix === \"finished\") {\n info.status = info.status === \"error\" ? \"error\" : \"finished\";\n info.finishReason = asString(d[\"finishReason\"]) ?? info.finishReason;\n info.inputTokens = asNumber(d[\"inputTokens\"]) ?? info.inputTokens;\n info.outputTokens = asNumber(d[\"outputTokens\"]) ?? info.outputTokens;\n info.totalTokens = asNumber(d[\"totalTokens\"]) ?? info.totalTokens;\n } else if (suffix === \"error\") {\n info.status = \"error\";\n }\n }\n return info;\n }\n\n /** Ordered tool calls made during a single agent run (exchange). */\n getAgentRunToolCalls(exchangeId: string): ToolCallRow[] {\n // Agent/tool telemetry postdates the exchange_id column migration, so\n // a legacy database that could not be migrated (read-only file) has\n // no agent events either; degrade to empty instead of erroring on\n // the missing column.\n if (!this.hasEventIdColumns()) return [];\n const rows = this.stmt(\n \"agentRunToolCalls\",\n `SELECT id, timestamp, event_name AS eventName, details, exchange_id AS exchangeId\n FROM events\n WHERE exchange_id = ? AND event_name LIKE 'route:agent:tool:%'\n ORDER BY id ASC`,\n ).all(exchangeId) as EventRow[];\n return correlateToolCalls(rows);\n }\n\n // -- Tools tab --\n\n /**\n * List tools derived from registration (`agent:tool:registered`) and\n * invocation (`route:agent:tool:invoked|error`) events.\n *\n * Aggregation is incremental (see {@link getAgents}): each call only\n * parses events newer than the previous call's high-water mark.\n */\n getTools(): ToolSummary[] {\n // Agent/tool telemetry postdates the exchange_id column migration, so\n // a legacy database that could not be migrated (read-only file) has\n // no agent events either; degrade to empty instead of erroring on\n // the missing column.\n if (!this.hasEventIdColumns()) return [];\n const rows = this.stmt(\n \"toolLifecycle\",\n `SELECT id, timestamp, event_name AS eventName, details, exchange_id AS exchangeId\n FROM events\n WHERE id > ?\n AND (event_name = 'agent:tool:registered'\n OR event_name = 'route:agent:tool:invoked'\n OR event_name = 'route:agent:tool:error')\n ORDER BY id ASC`,\n ).all(this.toolAgg.lastId) as EventRow[];\n\n const map = this.toolAgg.tools;\n const ensure = (\n name: string,\n source: \"registered\" | \"observed\",\n ): ToolAcc => {\n let acc = map.get(name);\n if (!acc) {\n acc = { name, source, callCount: 0, errorCount: 0, lastCalledAt: null };\n map.set(name, acc);\n }\n return acc;\n };\n\n for (const row of rows) {\n if (row.id > this.toolAgg.lastId) this.toolAgg.lastId = row.id;\n const d = parseDetails(row.details);\n if (row.eventName === \"agent:tool:registered\") {\n const name = asString(d[\"toolName\"]);\n // Promote: a tool first seen via an invocation (\"observed\") becomes\n // \"registered\" once its registration event arrives.\n if (name) ensure(name, \"registered\").source = \"registered\";\n continue;\n }\n const name = asString(d[\"toolName\"]);\n if (!name) continue;\n const acc = ensure(name, \"observed\");\n const suffix = row.eventName.split(\":\").pop();\n if (suffix === \"invoked\") {\n acc.callCount += 1;\n acc.lastCalledAt = row.timestamp;\n } else if (suffix === \"error\") {\n acc.errorCount += 1;\n acc.lastCalledAt = row.timestamp;\n }\n }\n\n const result: ToolSummary[] = Array.from(map.values());\n result.sort((a, b) => {\n if (a.callCount > 0 !== b.callCount > 0) return a.callCount > 0 ? -1 : 1;\n if (a.lastCalledAt && b.lastCalledAt)\n return a.lastCalledAt < b.lastCalledAt ? 1 : -1;\n return a.name < b.name ? -1 : 1;\n });\n return result;\n }\n\n /**\n * Invocation history for a single tool, most recent call first.\n *\n * The tool filter runs in SQL (json_extract) and the scan walks the\n * primary key from the newest row, stopping at the LIMIT, so cost\n * tracks the tool's recent activity rather than table size. The LIMIT\n * is tripled because each call spans up to three events (invoked,\n * result, error) that the correlation step merges into one row.\n */\n getToolCalls(toolName: string, limit = 100): ToolCallRow[] {\n // Agent/tool telemetry postdates the exchange_id column migration, so\n // a legacy database that could not be migrated (read-only file) has\n // no agent events either; degrade to empty instead of erroring on\n // the missing column.\n if (!this.hasEventIdColumns()) return [];\n const rows = this.stmt(\n \"toolCalls\",\n `SELECT id, timestamp, event_name AS eventName, details, exchange_id AS exchangeId\n FROM events\n WHERE event_name LIKE 'route:agent:tool:%'\n AND json_extract(details, '$.toolName') = ?\n ORDER BY id DESC\n LIMIT ?`,\n ).all(toolName, limit * 3) as EventRow[];\n // Correlation expects chronological order; the query returned newest\n // first so the LIMIT keeps the most recent events.\n rows.reverse();\n const calls = correlateToolCalls(rows);\n calls.reverse();\n return calls.slice(0, limit);\n }\n\n /**\n * Close the database connection.\n */\n close(): void {\n this.db.close();\n }\n}\n\n/**\n * Compute a percentile from a sorted array of numbers using linear interpolation.\n * Returns null if the array is empty.\n */\nfunction percentile(sorted: number[], p: number): number | null {\n if (sorted.length === 0) return null;\n const idx = p * (sorted.length - 1);\n const lo = Math.floor(idx);\n const hi = Math.ceil(idx);\n if (lo === hi) return sorted[lo]!;\n return sorted[lo]! + (sorted[hi]! - sorted[lo]!) * (idx - lo);\n}\n"]}
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env bun
2
- import {a}from'./chunk-R2ZD52FM.js';var n={major:1,minor:1,patch:0},m="https://bun.com/docs/installation",v="https://routecraft.dev/docs/advanced/programmatic-invocation";function d(e=process.versions.bun??null){let o=e;if(!o)return {ok:false,message:`[routecraft] The craft CLI requires Bun. Install Bun from ${m}, or embed @routecraft/routecraft programmatically (see ${v}).`};let s=o.split(/[-+]/)[0]??"",[t,a,u]=s.split("."),r=Number(t),c=Number(a),l=Number(u??"0");return !Number.isFinite(r)||!Number.isFinite(c)||!Number.isFinite(l)?{ok:false,message:`[routecraft] Could not parse Bun version "${o}". Routecraft requires Bun ${f(n)} or later. Upgrade Bun: ${m}.`}:r>n.major||r===n.major&&(c>n.minor||c===n.minor&&l>=n.patch)?{ok:true}:{ok:false,message:`[routecraft] Bun ${o} is not supported. Routecraft requires Bun ${f(n)} or later. Upgrade Bun: ${m}.`}}function f(e){return `${e.major}.${e.minor}.${e.patch}`}var g=d();g.ok||(console.error(g.message),process.exit(1));var{Command:h}=await import('commander'),i=new h;i.name("craft").description("A modern routing framework for TypeScript").version(a).enablePositionalOptions().option("--log-level <level>","Log level (e.g. info, warn, error, silent to disable)").option("--log-file <path>","Write logs to a file instead of stdout").showSuggestionAfterError().showHelpAfterError().exitOverride(e=>{e.code==="commander.unknownCommand"&&process.exit(0);});process.argv.length<=2&&i.help({error:false});i.command("run").description("Run routes from a single TypeScript/JavaScript file").argument("<file>","Path to a file containing routes").argument("[args...]","CLI command and flags to pass through to CLI adapter routes").option("--env <path>","Load environment variables from a .env file (default: .env)").passThroughOptions().action(async(e,o,s)=>{let t=i.opts();t.logLevel!==void 0&&(process.env.LOG_LEVEL=t.logLevel,process.env.CRAFT_LOG_LEVEL=t.logLevel),t.logFile!==void 0&&(process.env.LOG_FILE=t.logFile,process.env.CRAFT_LOG_FILE=t.logFile);let{loadEnvFile:a}=await import('./util-GX4G3K5I.js');s.env!==void 0?a(s.env):a();let{runCommand:u}=await import('./run-3Y3G7OTJ.js'),r=await u(e,o);if(!r.success){r.message&&console.error(r.message);let c=r.code??1;setImmediate(()=>process.exit(c));return}});i.command("tui").description("Launch the Terminal UI to monitor Routecraft execution history").option("--db <path>","Path to the telemetry SQLite database",".routecraft/telemetry.db").action(async e=>{let{resolve:o,isAbsolute:s}=await import('path'),t=s(e.db)?e.db:o(process.cwd(),e.db),{renderTui:a}=await import('./tui/app.js');await a(t);});i.parse();//# sourceMappingURL=index.js.map
2
+ import {a}from'./chunk-QJXOM5GC.js';var n={major:1,minor:1,patch:0},m="https://bun.com/docs/installation",v="https://routecraft.dev/docs/advanced/programmatic-invocation";function d(e=process.versions.bun??null){let o=e;if(!o)return {ok:false,message:`[routecraft] The craft CLI requires Bun. Install Bun from ${m}, or embed @routecraft/routecraft programmatically (see ${v}).`};let s=o.split(/[-+]/)[0]??"",[t,a,u]=s.split("."),r=Number(t),c=Number(a),l=Number(u??"0");return !Number.isFinite(r)||!Number.isFinite(c)||!Number.isFinite(l)?{ok:false,message:`[routecraft] Could not parse Bun version "${o}". Routecraft requires Bun ${f(n)} or later. Upgrade Bun: ${m}.`}:r>n.major||r===n.major&&(c>n.minor||c===n.minor&&l>=n.patch)?{ok:true}:{ok:false,message:`[routecraft] Bun ${o} is not supported. Routecraft requires Bun ${f(n)} or later. Upgrade Bun: ${m}.`}}function f(e){return `${e.major}.${e.minor}.${e.patch}`}var g=d();g.ok||(console.error(g.message),process.exit(1));var{Command:h}=await import('commander'),i=new h;i.name("craft").description("A modern routing framework for TypeScript").version(a).enablePositionalOptions().option("--log-level <level>","Log level (e.g. info, warn, error, silent to disable)").option("--log-file <path>","Write logs to a file instead of stdout").showSuggestionAfterError().showHelpAfterError().exitOverride(e=>{e.code==="commander.unknownCommand"&&process.exit(0);});process.argv.length<=2&&i.help({error:false});i.command("run").description("Run routes from a single TypeScript/JavaScript file").argument("<file>","Path to a file containing routes").argument("[args...]","CLI command and flags to pass through to CLI adapter routes").option("--env <path>","Load environment variables from a .env file (default: .env)").passThroughOptions().action(async(e,o,s)=>{let t=i.opts();t.logLevel!==void 0&&(process.env.LOG_LEVEL=t.logLevel,process.env.CRAFT_LOG_LEVEL=t.logLevel),t.logFile!==void 0&&(process.env.LOG_FILE=t.logFile,process.env.CRAFT_LOG_FILE=t.logFile);let{loadEnvFile:a}=await import('./util-GX4G3K5I.js');s.env!==void 0?a(s.env):a();let{runCommand:u}=await import('./run-6DIAJM2Z.js'),r=await u(e,o);if(!r.success){r.message&&console.error(r.message);let c=r.code??1;setImmediate(()=>process.exit(c));return}});i.command("tui").description("Launch the Terminal UI to monitor Routecraft execution history").option("--db <path>","Path to the telemetry SQLite database",".routecraft/telemetry.db").action(async e=>{let{resolve:o,isAbsolute:s}=await import('path'),t=s(e.db)?e.db:o(process.cwd(),e.db),{renderTui:a}=await import('./tui/app.js');await a(t);});i.parse();//# sourceMappingURL=index.js.map
3
3
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,2 @@
1
+ import {resolve,extname}from'path';import {logger,ContextBuilder,RUNNER_ARGV,shutdownHandler,isRouteBuilder,isRouteDefinition}from'@routecraft/routecraft';var d=[".mjs",".js",".cjs",".ts"];async function h(t,e=[]){let r=resolve(process.cwd(),t),l=extname(r);if(!d.includes(l))return {success:false,code:1,message:`Error: Only the following file types are supported: ${d.join(", ")}`};try{let s=await import(r),n=s.craftConfig;logger.info(`Loading file: ${r}`);let i=new ContextBuilder;n&&(logger.info("Found craftConfig export, applying configuration"),i.with(n));let a=w(i,s.default);if(!a.success)return a;let{context:u}=await i.build();return u.setStore(RUNNER_ARGV,e),shutdownHandler(u),await u.start(),{success:!0}}catch(s){if(s instanceof Error)return logger.error(`Failed to run ${r}: ${s.message}`),{success:false,code:1,message:s.message};let n=typeof s=="object"&&s!==null&&"message"in s?String(s.message):String(s);return logger.error(`Failed to run ${r}: ${n}`),{success:false,code:1,message:n}}}function w(t,e){return e?isRouteBuilder(e)?(t.routes(e),logger.info("Loaded single RouteBuilder from default export"),{success:true}):isRouteDefinition(e)?(t.routes(e),logger.info("Loaded single route from default export"),{success:true}):Array.isArray(e)?e.every(r=>isRouteBuilder(r)||isRouteDefinition(r))?(e.forEach(r=>t.routes(r)),logger.info(`Loaded ${e.length} routes from default export array`),{success:true}):(logger.error("All items in default export array must be RouteDefinition or RouteBuilder"),{success:false,code:1,message:"Invalid items in default export array"}):(logger.error("Invalid default export. Expected: RouteDefinition, RouteBuilder, or array of those."),{success:false,code:1,message:"Invalid default export. Expected: RouteDefinition, RouteBuilder, or array of those."}):(logger.error("No default export found. Expected routes as default export."),{success:false,code:1,message:"No default export found"})}export{h as runCommand};//# sourceMappingURL=run-6DIAJM2Z.js.map
2
+ //# sourceMappingURL=run-6DIAJM2Z.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/run.ts"],"names":["SUPPORTED_EXTENSIONS","runCommand","filePath","cliArgs","absFilePath","resolve","ext","extname","module","craftConfig","logger","contextBuilder","ContextBuilder","configured","configureRoutes","context","RUNNER_ARGV","shutdownHandler","error","message","defaultExport","isRouteBuilder","isRouteDefinition","item","routeOrBuilder"],"mappings":"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","file":"run-6DIAJM2Z.js","sourcesContent":["import { resolve, extname } from \"node:path\";\nimport {\n ContextBuilder,\n type CraftConfig,\n isRouteBuilder,\n isRouteDefinition,\n logger,\n shutdownHandler,\n RUNNER_ARGV,\n type AnyRouteBuilder,\n type RouteDefinition,\n} from \"@routecraft/routecraft\";\n\nconst SUPPORTED_EXTENSIONS = [\".mjs\", \".js\", \".cjs\", \".ts\"] as const;\n\ntype RunResult =\n { success: true } | { success: false; code?: number; message: string };\n\n/**\n * Load a routecraft file, build a context, and start it.\n *\n * Adapter-agnostic: the runner knows nothing about which adapters (CLI, HTTP,\n * cron, etc.) are used. It sets `RUNNER_ARGV` in the context store so that\n * adapters can read remaining CLI tokens if needed.\n *\n * @param filePath - Path to the routecraft file to run\n * @param cliArgs - Remaining CLI arguments after the file path\n */\nexport async function runCommand(\n filePath: string,\n cliArgs: string[] = [],\n): Promise<RunResult> {\n const absFilePath = resolve(process.cwd(), filePath);\n const ext = extname(absFilePath);\n\n // Validate file extension\n if (\n !SUPPORTED_EXTENSIONS.includes(ext as (typeof SUPPORTED_EXTENSIONS)[number])\n ) {\n return {\n success: false,\n code: 1,\n message: `Error: Only the following file types are supported: ${SUPPORTED_EXTENSIONS.join(\", \")}`,\n };\n }\n\n try {\n // Load the module (CLI already set LOG_LEVEL / LOG_FILE from argv in index.ts).\n // Logger uses env first; context will apply craftConfig.log when built (env wins if set).\n const module = await import(absFilePath);\n const craftConfig = module.craftConfig as CraftConfig | undefined;\n\n logger.info(`Loading file: ${absFilePath}`);\n\n // Create context builder\n const contextBuilder = new ContextBuilder();\n\n // Apply craftConfig (routes, plugins, etc.); context applies config.log when built.\n if (craftConfig) {\n logger.info(\"Found craftConfig export, applying configuration\");\n contextBuilder.with(craftConfig);\n }\n\n // Handle routes from the default export\n const configured = configureRoutes(contextBuilder, module.default);\n if (!configured.success) {\n return configured;\n }\n\n // Build and start the context. Adapters handle their own lifecycle.\n // RUNNER_ARGV lets adapters (e.g. CLI) read remaining args without\n // the runner needing to know which adapters are in use.\n const { context } = await contextBuilder.build();\n context.setStore(RUNNER_ARGV, cliArgs);\n shutdownHandler(context);\n await context.start();\n\n return { success: true };\n } catch (error: unknown) {\n if (error instanceof Error) {\n logger.error(`Failed to run ${absFilePath}: ${error.message}`);\n return { success: false, code: 1, message: error.message };\n }\n // Non-Error throws (e.g. Bun's ResolveMessage for a missing package)\n // still carry a message; surface it instead of \"Unknown error\".\n const message =\n typeof error === \"object\" && error !== null && \"message\" in error\n ? String((error as { message: unknown }).message)\n : String(error);\n logger.error(`Failed to run ${absFilePath}: ${message}`);\n return { success: false, code: 1, message };\n }\n}\n\nfunction configureRoutes(\n contextBuilder: InstanceType<typeof ContextBuilder>,\n defaultExport: unknown,\n): RunResult {\n if (!defaultExport) {\n logger.error(\"No default export found. Expected routes as default export.\");\n return { success: false, code: 1, message: \"No default export found\" };\n }\n\n // Handle single RouteBuilder or RouteDefinition (brand-based guards for cross-instance)\n if (isRouteBuilder(defaultExport)) {\n contextBuilder.routes(defaultExport as AnyRouteBuilder);\n logger.info(\"Loaded single RouteBuilder from default export\");\n return { success: true };\n }\n\n if (isRouteDefinition(defaultExport)) {\n contextBuilder.routes(defaultExport as RouteDefinition);\n logger.info(\"Loaded single route from default export\");\n return { success: true };\n }\n\n // Handle array of routes\n if (Array.isArray(defaultExport)) {\n // Check each item, prioritizing RouteBuilder check\n if (\n !defaultExport.every(\n (item) => isRouteBuilder(item) || isRouteDefinition(item),\n )\n ) {\n logger.error(\n \"All items in default export array must be RouteDefinition or RouteBuilder\",\n );\n return {\n success: false,\n code: 1,\n message: \"Invalid items in default export array\",\n };\n }\n\n defaultExport.forEach((routeOrBuilder) =>\n contextBuilder.routes(\n routeOrBuilder as RouteDefinition | AnyRouteBuilder,\n ),\n );\n logger.info(\n `Loaded ${defaultExport.length} routes from default export array`,\n );\n return { success: true };\n }\n\n // Invalid default export\n logger.error(\n \"Invalid default export. Expected: RouteDefinition, RouteBuilder, or array of those.\",\n );\n return {\n success: false,\n code: 1,\n message:\n \"Invalid default export. Expected: RouteDefinition, RouteBuilder, or array of those.\",\n };\n}\n"]}
package/dist/tui/app.d.ts CHANGED
@@ -1,3 +1,16 @@
1
+ import * as react from 'react';
2
+ import { TelemetryDb } from './db.js';
3
+ import '@routecraft/routecraft';
4
+
5
+ /**
6
+ * Root TUI component. Exported for tests; not part of the public CLI
7
+ * surface.
8
+ *
9
+ * @internal
10
+ */
11
+ declare function App({ db }: {
12
+ db: TelemetryDb;
13
+ }): react.JSX.Element;
1
14
  declare function renderTui(dbPath: string): Promise<void>;
2
15
 
3
- export { renderTui };
16
+ export { App, renderTui };