@roj-ai/cli 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/client.d.ts +8 -0
  2. package/dist/client.d.ts.map +1 -0
  3. package/dist/client.js +28 -0
  4. package/dist/commands/answer.d.ts +3 -0
  5. package/dist/commands/answer.d.ts.map +1 -0
  6. package/dist/commands/answer.js +15 -0
  7. package/dist/commands/debug-format.d.ts +12 -0
  8. package/dist/commands/debug-format.d.ts.map +1 -0
  9. package/dist/commands/debug-format.js +262 -0
  10. package/dist/commands/debug.d.ts +14 -0
  11. package/dist/commands/debug.d.ts.map +1 -0
  12. package/dist/commands/debug.js +187 -0
  13. package/dist/commands/messages.d.ts +4 -0
  14. package/dist/commands/messages.d.ts.map +1 -0
  15. package/dist/commands/messages.js +77 -0
  16. package/dist/commands/presets.d.ts +3 -0
  17. package/dist/commands/presets.d.ts.map +1 -0
  18. package/dist/commands/presets.js +14 -0
  19. package/dist/commands/sessions.d.ts +6 -0
  20. package/dist/commands/sessions.d.ts.map +1 -0
  21. package/dist/commands/sessions.js +50 -0
  22. package/dist/main.d.ts +2 -0
  23. package/dist/main.d.ts.map +1 -0
  24. package/dist/main.js +277 -0
  25. package/dist/repl/formatter.d.ts +16 -0
  26. package/dist/repl/formatter.d.ts.map +1 -0
  27. package/dist/repl/formatter.js +72 -0
  28. package/dist/repl/repl.d.ts +3 -0
  29. package/dist/repl/repl.d.ts.map +1 -0
  30. package/dist/repl/repl.js +212 -0
  31. package/dist/repl/session-picker.d.ts +6 -0
  32. package/dist/repl/session-picker.d.ts.map +1 -0
  33. package/dist/repl/session-picker.js +60 -0
  34. package/dist/unwrap.d.ts +4 -0
  35. package/dist/unwrap.d.ts.map +1 -0
  36. package/dist/unwrap.js +6 -0
  37. package/package.json +27 -0
  38. package/src/client.ts +41 -0
  39. package/src/commands/answer.ts +27 -0
  40. package/src/commands/debug-format.ts +299 -0
  41. package/src/commands/debug.ts +347 -0
  42. package/src/commands/messages.ts +92 -0
  43. package/src/commands/presets.ts +22 -0
  44. package/src/commands/sessions.ts +73 -0
  45. package/src/main.ts +319 -0
  46. package/src/repl/formatter.ts +84 -0
  47. package/src/repl/repl.ts +246 -0
  48. package/src/repl/session-picker.ts +81 -0
  49. package/src/unwrap.ts +8 -0
@@ -0,0 +1,347 @@
1
+ import type { DomainEvent, LLMCallLogEntry, SessionId } from '@roj-ai/sdk'
2
+ import type { GetAgentDetailResponse, ProjectionEvent } from '@roj-ai/shared'
3
+ import {
4
+ type AgentDetailProjectionState,
5
+ AgentId,
6
+ type AgentRegistryState,
7
+ type AgentTreeProjectionState,
8
+ applyEventToAgentDetail,
9
+ applyEventToAgentRegistry,
10
+ applyEventToAgentTree,
11
+ applyEventToMailbox,
12
+ applyEventToMetrics,
13
+ applyEventToTimeline,
14
+ buildAgentTreeFromProjection,
15
+ createAgentDetailProjectionState,
16
+ createAgentRegistryState,
17
+ createAgentTreeProjectionState,
18
+ createMailboxState,
19
+ createMetricsState,
20
+ createTimelineState,
21
+ getAgentDetail,
22
+ getMailboxMessages,
23
+ getTimelineItems,
24
+ isDomainEvent,
25
+ metricsStateToResponse,
26
+ } from '@roj-ai/shared'
27
+ import type { RpcClient, RpcOutput } from '@roj-ai/shared/rpc'
28
+ import { formatTable } from '../repl/formatter.js'
29
+ import { unwrap } from '../unwrap.js'
30
+ import {
31
+ formatAgentDetail,
32
+ formatAgentTree,
33
+ formatEventRow,
34
+ formatLLMCallDetail,
35
+ formatMailboxMessage,
36
+ formatMetrics,
37
+ formatTimelineItem,
38
+ truncate,
39
+ } from './debug-format.js'
40
+
41
+ // ============================================================================
42
+ // Helpers
43
+ // ============================================================================
44
+
45
+ function isLLMCallLogEntry(data: unknown): data is LLMCallLogEntry {
46
+ return typeof data === 'object' && data !== null && 'id' in data && 'status' in data && 'request' in data
47
+ }
48
+
49
+ interface LoadedProjections {
50
+ registry: AgentRegistryState
51
+ agentTree: AgentTreeProjectionState
52
+ agentDetail: AgentDetailProjectionState
53
+ events: DomainEvent[]
54
+ }
55
+
56
+ async function loadProjections(client: RpcClient, sessionId: string): Promise<LoadedProjections> {
57
+ const { events: rawEvents } = unwrap(await client.call('sessions.getEvents', { sessionId, limit: 10000 }))
58
+ const events = rawEvents.filter(isDomainEvent)
59
+
60
+ let registry = createAgentRegistryState()
61
+ let agentTree = createAgentTreeProjectionState()
62
+ let agentDetail = createAgentDetailProjectionState()
63
+
64
+ for (const event of events) {
65
+ const e = event as ProjectionEvent
66
+ registry = applyEventToAgentRegistry(registry, e)
67
+ agentTree = applyEventToAgentTree(agentTree, e)
68
+ agentDetail = applyEventToAgentDetail(agentDetail, e)
69
+ }
70
+
71
+ return { registry, agentTree, agentDetail, events }
72
+ }
73
+
74
+ // ============================================================================
75
+ // Direct RPC Commands
76
+ // ============================================================================
77
+
78
+ export async function eventsCommand(
79
+ client: RpcClient,
80
+ sessionId: string,
81
+ flags: Record<string, string | true>,
82
+ json: boolean,
83
+ ): Promise<void> {
84
+ const limit = typeof flags.limit === 'string' ? parseInt(flags.limit, 10) : undefined
85
+ const type = typeof flags.type === 'string' ? flags.type : undefined
86
+ const agentId = typeof flags.agent === 'string' ? flags.agent : undefined
87
+
88
+ const { events: rawEvents, total } = unwrap(await client.call('sessions.getEvents', { sessionId, limit, type, agentId }))
89
+ const events = rawEvents.filter(isDomainEvent)
90
+
91
+ if (json) {
92
+ console.log(JSON.stringify({ events, total }, null, 2))
93
+ return
94
+ }
95
+
96
+ if (events.length === 0) {
97
+ console.log('No events found.')
98
+ return
99
+ }
100
+
101
+ console.log(formatTable(
102
+ ['#', 'Type', 'Agent', 'Time'],
103
+ events.map((e, i) => formatEventRow(e, i)),
104
+ ))
105
+ console.log(`\nTotal: ${total} events`)
106
+ }
107
+
108
+ export async function llmCallsCommand(
109
+ client: RpcClient,
110
+ sessionId: string,
111
+ flags: Record<string, string | true>,
112
+ json: boolean,
113
+ ): Promise<void> {
114
+ const limit = typeof flags.limit === 'string' ? parseInt(flags.limit, 10) : undefined
115
+
116
+ const { calls, total } = unwrap(await client.call('llm.getCalls', { sessionId, limit }))
117
+
118
+ if (json) {
119
+ console.log(JSON.stringify({ calls, total }, null, 2))
120
+ return
121
+ }
122
+
123
+ if (calls.length === 0) {
124
+ console.log('No LLM calls found.')
125
+ return
126
+ }
127
+
128
+ const typedCalls = calls.filter(isLLMCallLogEntry)
129
+ console.log(formatTable(
130
+ ['Time', 'Status', 'Model', 'Duration', 'Tokens', 'Cost', 'Agent'],
131
+ typedCalls.map((c) => {
132
+ const time = new Date(c.createdAt).toLocaleTimeString()
133
+ const duration = c.durationMs !== undefined ? `${(c.durationMs / 1000).toFixed(1)}s` : '-'
134
+ const tokens = c.metrics ? `${c.metrics.promptTokens}/${c.metrics.completionTokens}` : '-'
135
+ const cost = c.metrics?.cost !== undefined ? `$${c.metrics.cost.toFixed(4)}` : '-'
136
+ return [time, c.status, c.request.model, duration, tokens, cost, String(c.agentId)]
137
+ }),
138
+ ))
139
+ console.log(`\nTotal: ${total} calls`)
140
+ }
141
+
142
+ export async function llmCallCommand(
143
+ client: RpcClient,
144
+ sessionId: string,
145
+ callId: string,
146
+ json: boolean,
147
+ ): Promise<void> {
148
+ const callData = unwrap(await client.call('llm.getCall', { sessionId, callId }))
149
+
150
+ if (json) {
151
+ console.log(JSON.stringify(callData, null, 2))
152
+ return
153
+ }
154
+
155
+ if (!isLLMCallLogEntry(callData)) {
156
+ console.error('Invalid LLM call data.')
157
+ return
158
+ }
159
+
160
+ console.log(formatLLMCallDetail(callData))
161
+ }
162
+
163
+ export async function presetAgentsCommand(
164
+ client: RpcClient,
165
+ sessionId: SessionId,
166
+ json: boolean,
167
+ ): Promise<void> {
168
+ const result = unwrap(await client.call('presets.getAgents', { sessionId }))
169
+ const agents = result.agents
170
+
171
+ if (json) {
172
+ console.log(JSON.stringify(agents, null, 2))
173
+ return
174
+ }
175
+
176
+ if (agents.length === 0) {
177
+ console.log('No agent definitions found.')
178
+ return
179
+ }
180
+
181
+ type PresetAgent = RpcOutput<'presets.getAgents'>['agents'][number]
182
+ console.log(formatTable(
183
+ ['Name', 'Spawnable By', 'Has Input Schema'],
184
+ agents.map((a: PresetAgent) => [a.name, a.spawnableBy.join(', '), a.hasInputSchema ? 'yes' : 'no']),
185
+ ))
186
+ }
187
+
188
+ export async function debugSendCommand(
189
+ client: RpcClient,
190
+ sessionId: string,
191
+ agentId: string,
192
+ content: string,
193
+ flags: Record<string, string | true>,
194
+ json: boolean,
195
+ ): Promise<void> {
196
+ const { messageId } = unwrap(await client.call('user-chat.sendMessage', { sessionId, agentId: AgentId(agentId), content }))
197
+
198
+ if (json) {
199
+ console.log(JSON.stringify({ messageId }))
200
+ return
201
+ }
202
+
203
+ console.log(`Message sent: ${messageId}`)
204
+ }
205
+
206
+ export async function spawnAgentCommand(
207
+ client: RpcClient,
208
+ sessionId: string,
209
+ definitionName: string,
210
+ parentId: string,
211
+ flags: Record<string, string | true>,
212
+ json: boolean,
213
+ ): Promise<void> {
214
+ const message = typeof flags.message === 'string' ? flags.message : undefined
215
+ const { agentId } = unwrap(await client.call('agents.spawn', { sessionId, definitionName, parentId: AgentId(parentId), message }))
216
+
217
+ if (json) {
218
+ console.log(JSON.stringify({ agentId }))
219
+ return
220
+ }
221
+
222
+ console.log(`Agent spawned: ${agentId}`)
223
+ }
224
+
225
+ // ============================================================================
226
+ // Event-sourced Projection Commands
227
+ // ============================================================================
228
+
229
+ export async function agentsCommand(
230
+ client: RpcClient,
231
+ sessionId: string,
232
+ json: boolean,
233
+ ): Promise<void> {
234
+ const { agentTree: agentTreeState } = await loadProjections(client, sessionId)
235
+ const tree = buildAgentTreeFromProjection(agentTreeState)
236
+
237
+ if (json) {
238
+ console.log(JSON.stringify(tree, null, 2))
239
+ return
240
+ }
241
+
242
+ if (tree.length === 0) {
243
+ console.log('No agents.')
244
+ return
245
+ }
246
+
247
+ console.log(formatAgentTree(tree))
248
+ }
249
+
250
+ export async function agentCommand(
251
+ client: RpcClient,
252
+ sessionId: string,
253
+ agentId: string,
254
+ json: boolean,
255
+ ): Promise<void> {
256
+ const { agentDetail: agentDetailState } = await loadProjections(client, sessionId)
257
+ const detail = getAgentDetail(agentDetailState, AgentId(agentId))
258
+
259
+ if (!detail) {
260
+ console.error(`Agent not found: ${agentId}`)
261
+ process.exit(1)
262
+ }
263
+
264
+ if (json) {
265
+ console.log(JSON.stringify(detail, null, 2))
266
+ return
267
+ }
268
+
269
+ console.log(formatAgentDetail(detail))
270
+ }
271
+
272
+ export async function mailboxCommand(
273
+ client: RpcClient,
274
+ sessionId: string,
275
+ json: boolean,
276
+ ): Promise<void> {
277
+ const { registry, events } = await loadProjections(client, sessionId)
278
+
279
+ let mailboxState = createMailboxState()
280
+ for (const event of events) {
281
+ mailboxState = applyEventToMailbox(mailboxState, event as ProjectionEvent, registry)
282
+ }
283
+ const messages = getMailboxMessages(mailboxState)
284
+
285
+ if (json) {
286
+ console.log(JSON.stringify(messages, null, 2))
287
+ return
288
+ }
289
+
290
+ if (messages.length === 0) {
291
+ console.log('No mailbox messages.')
292
+ return
293
+ }
294
+
295
+ for (const msg of messages) {
296
+ console.log(formatMailboxMessage(msg))
297
+ }
298
+ }
299
+
300
+ export async function timelineCommand(
301
+ client: RpcClient,
302
+ sessionId: string,
303
+ json: boolean,
304
+ ): Promise<void> {
305
+ const { registry, events } = await loadProjections(client, sessionId)
306
+
307
+ let timelineState = createTimelineState()
308
+ for (const event of events) {
309
+ timelineState = applyEventToTimeline(timelineState, event as ProjectionEvent, registry)
310
+ }
311
+ const items = getTimelineItems(timelineState, registry)
312
+
313
+ if (json) {
314
+ console.log(JSON.stringify(items, null, 2))
315
+ return
316
+ }
317
+
318
+ if (items.length === 0) {
319
+ console.log('No timeline items.')
320
+ return
321
+ }
322
+
323
+ for (const item of items) {
324
+ console.log(formatTimelineItem(item))
325
+ }
326
+ }
327
+
328
+ export async function metricsCommand(
329
+ client: RpcClient,
330
+ sessionId: string,
331
+ json: boolean,
332
+ ): Promise<void> {
333
+ const { registry, events } = await loadProjections(client, sessionId)
334
+
335
+ let metricsState = createMetricsState()
336
+ for (const event of events) {
337
+ metricsState = applyEventToMetrics(metricsState, event as ProjectionEvent)
338
+ }
339
+ const metrics = metricsStateToResponse(metricsState, registry.count)
340
+
341
+ if (json) {
342
+ console.log(JSON.stringify(metrics, null, 2))
343
+ return
344
+ }
345
+
346
+ console.log(formatMetrics(metrics))
347
+ }
@@ -0,0 +1,92 @@
1
+ import type { ChatMessage } from '@roj-ai/sdk'
2
+ import { AgentId } from '@roj-ai/shared'
3
+ import type { RpcClient } from '@roj-ai/shared/rpc'
4
+ import { connectWebSocket } from '../client.js'
5
+ import { formatChatMessage } from '../repl/formatter.js'
6
+ import { unwrap } from '../unwrap.js'
7
+
8
+ function isChatMessage(msg: unknown): msg is ChatMessage {
9
+ return typeof msg === 'object' && msg !== null && 'type' in msg && 'timestamp' in msg
10
+ }
11
+
12
+ export async function messagesCommand(client: RpcClient, sessionId: string, json: boolean): Promise<void> {
13
+ const { messages: rawMessages } = unwrap(await client.call('user-chat.getMessages', { sessionId }))
14
+ const messages = rawMessages.filter(isChatMessage)
15
+
16
+ if (json) {
17
+ console.log(JSON.stringify(messages, null, 2))
18
+ return
19
+ }
20
+
21
+ if (messages.length === 0) {
22
+ console.log('No messages.')
23
+ return
24
+ }
25
+
26
+ for (const msg of messages) {
27
+ console.log(formatChatMessage(msg))
28
+ console.log()
29
+ }
30
+ }
31
+
32
+ export async function sendCommand(
33
+ client: RpcClient,
34
+ baseUrl: string,
35
+ sessionId: string,
36
+ content: string,
37
+ wait: boolean,
38
+ json: boolean,
39
+ ): Promise<void> {
40
+ const session = unwrap(await client.call('sessions.get', { sessionId }))
41
+ if (!session.entryAgentId) {
42
+ console.error('No entry agent found for session.')
43
+ process.exit(1)
44
+ }
45
+ const { messageId } = unwrap(await client.call('user-chat.sendMessage', { sessionId, agentId: AgentId(session.entryAgentId), content }))
46
+
47
+ if (!wait) {
48
+ if (json) {
49
+ console.log(JSON.stringify({ messageId }))
50
+ } else {
51
+ console.log(`Message sent: ${messageId}`)
52
+ }
53
+ return
54
+ }
55
+
56
+ // Wait for agent response via WebSocket
57
+ const responsePromise = new Promise<string>((resolve, reject) => {
58
+ const timeout = setTimeout(() => {
59
+ connection.disconnect()
60
+ reject(new Error('Timeout waiting for agent response'))
61
+ }, 120_000)
62
+
63
+ const connection = connectWebSocket(baseUrl, sessionId, {
64
+ agentMessage: (payload) => {
65
+ if (typeof payload !== 'object' || payload === null) return
66
+ const input = payload as Record<string, unknown>
67
+ if (typeof input.content !== 'string') return
68
+ clearTimeout(timeout)
69
+ connection.disconnect()
70
+ resolve(input.content)
71
+ },
72
+ error: (payload) => {
73
+ if (typeof payload !== 'object' || payload === null) return
74
+ const input = payload as Record<string, unknown>
75
+ if (typeof input.message !== 'string') return
76
+ clearTimeout(timeout)
77
+ connection.disconnect()
78
+ reject(new Error(input.message))
79
+ },
80
+ })
81
+
82
+ connection.connect().catch(reject)
83
+ })
84
+
85
+ const response = await responsePromise
86
+
87
+ if (json) {
88
+ console.log(JSON.stringify({ messageId, response }))
89
+ } else {
90
+ console.log(response)
91
+ }
92
+ }
@@ -0,0 +1,22 @@
1
+ import type { RpcClient } from '@roj-ai/shared/rpc'
2
+ import { formatTable } from '../repl/formatter.js'
3
+ import { unwrap } from '../unwrap.js'
4
+
5
+ export async function presetsCommand(client: RpcClient, json: boolean): Promise<void> {
6
+ const { presets } = unwrap(await client.call('presets.list', {}))
7
+
8
+ if (json) {
9
+ console.log(JSON.stringify(presets, null, 2))
10
+ return
11
+ }
12
+
13
+ if (presets.length === 0) {
14
+ console.log('No presets available.')
15
+ return
16
+ }
17
+
18
+ console.log(formatTable(
19
+ ['ID', 'Name', 'Description'],
20
+ presets.map(p => [p.id, p.name, p.description ?? '']),
21
+ ))
22
+ }
@@ -0,0 +1,73 @@
1
+ import type { SessionMetadata } from '@roj-ai/sdk'
2
+ import type { RpcClient } from '@roj-ai/shared/rpc'
3
+ import { formatSessionInfo, formatTable } from '../repl/formatter.js'
4
+ import { unwrap } from '../unwrap.js'
5
+
6
+ function isSessionMetadata(data: unknown): data is SessionMetadata {
7
+ return typeof data === 'object' && data !== null && 'sessionId' in data && 'presetId' in data
8
+ }
9
+
10
+ export async function sessionsListCommand(
11
+ client: RpcClient,
12
+ status: 'active' | 'closed' | 'errored' | undefined,
13
+ json: boolean,
14
+ ): Promise<void> {
15
+ const { sessions } = unwrap(await client.call('sessions.list', { status, limit: 50, order: 'desc' }))
16
+
17
+ if (json) {
18
+ console.log(JSON.stringify(sessions, null, 2))
19
+ return
20
+ }
21
+
22
+ if (sessions.length === 0) {
23
+ console.log('No sessions found.')
24
+ return
25
+ }
26
+
27
+ console.log(formatTable(
28
+ ['Session ID', 'Preset', 'Status', 'Created'],
29
+ sessions.filter(isSessionMetadata).map(s => [
30
+ s.sessionId,
31
+ s.presetId,
32
+ s.status,
33
+ new Date(s.createdAt).toLocaleString(),
34
+ ]),
35
+ ))
36
+ }
37
+
38
+ export async function sessionCreateCommand(client: RpcClient, presetId: string, json: boolean): Promise<void> {
39
+ const { sessionId } = unwrap(await client.call('sessions.create', { presetId }))
40
+
41
+ if (json) {
42
+ console.log(JSON.stringify({ sessionId }))
43
+ return
44
+ }
45
+
46
+ console.log(`Session created: ${sessionId}`)
47
+ }
48
+
49
+ export async function sessionGetCommand(client: RpcClient, sessionId: string, json: boolean): Promise<void> {
50
+ const session = unwrap(await client.call('sessions.get', { sessionId }))
51
+
52
+ if (json) {
53
+ console.log(JSON.stringify(session, null, 2))
54
+ return
55
+ }
56
+
57
+ console.log(formatSessionInfo(session))
58
+ console.log(` Agents: ${session.agentCount}`)
59
+ if (session.closedAt) {
60
+ console.log(` Closed: ${new Date(session.closedAt).toLocaleString()}`)
61
+ }
62
+ }
63
+
64
+ export async function sessionCloseCommand(client: RpcClient, sessionId: string, json: boolean): Promise<void> {
65
+ const result = unwrap(await client.call('sessions.close', { sessionId }))
66
+
67
+ if (json) {
68
+ console.log(JSON.stringify(result))
69
+ return
70
+ }
71
+
72
+ console.log('Session closed.')
73
+ }