@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.
- package/dist/client.d.ts +8 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +28 -0
- package/dist/commands/answer.d.ts +3 -0
- package/dist/commands/answer.d.ts.map +1 -0
- package/dist/commands/answer.js +15 -0
- package/dist/commands/debug-format.d.ts +12 -0
- package/dist/commands/debug-format.d.ts.map +1 -0
- package/dist/commands/debug-format.js +262 -0
- package/dist/commands/debug.d.ts +14 -0
- package/dist/commands/debug.d.ts.map +1 -0
- package/dist/commands/debug.js +187 -0
- package/dist/commands/messages.d.ts +4 -0
- package/dist/commands/messages.d.ts.map +1 -0
- package/dist/commands/messages.js +77 -0
- package/dist/commands/presets.d.ts +3 -0
- package/dist/commands/presets.d.ts.map +1 -0
- package/dist/commands/presets.js +14 -0
- package/dist/commands/sessions.d.ts +6 -0
- package/dist/commands/sessions.d.ts.map +1 -0
- package/dist/commands/sessions.js +50 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +277 -0
- package/dist/repl/formatter.d.ts +16 -0
- package/dist/repl/formatter.d.ts.map +1 -0
- package/dist/repl/formatter.js +72 -0
- package/dist/repl/repl.d.ts +3 -0
- package/dist/repl/repl.d.ts.map +1 -0
- package/dist/repl/repl.js +212 -0
- package/dist/repl/session-picker.d.ts +6 -0
- package/dist/repl/session-picker.d.ts.map +1 -0
- package/dist/repl/session-picker.js +60 -0
- package/dist/unwrap.d.ts +4 -0
- package/dist/unwrap.d.ts.map +1 -0
- package/dist/unwrap.js +6 -0
- package/package.json +27 -0
- package/src/client.ts +41 -0
- package/src/commands/answer.ts +27 -0
- package/src/commands/debug-format.ts +299 -0
- package/src/commands/debug.ts +347 -0
- package/src/commands/messages.ts +92 -0
- package/src/commands/presets.ts +22 -0
- package/src/commands/sessions.ts +73 -0
- package/src/main.ts +319 -0
- package/src/repl/formatter.ts +84 -0
- package/src/repl/repl.ts +246 -0
- package/src/repl/session-picker.ts +81 -0
- package/src/unwrap.ts +8 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { formatTable } from '../repl/formatter.js';
|
|
2
|
+
import { unwrap } from '../unwrap.js';
|
|
3
|
+
export async function presetsCommand(client, json) {
|
|
4
|
+
const { presets } = unwrap(await client.call('presets.list', {}));
|
|
5
|
+
if (json) {
|
|
6
|
+
console.log(JSON.stringify(presets, null, 2));
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
if (presets.length === 0) {
|
|
10
|
+
console.log('No presets available.');
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
console.log(formatTable(['ID', 'Name', 'Description'], presets.map(p => [p.id, p.name, p.description ?? ''])));
|
|
14
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { RpcClient } from '@roj-ai/shared/rpc';
|
|
2
|
+
export declare function sessionsListCommand(client: RpcClient, status: 'active' | 'closed' | 'errored' | undefined, json: boolean): Promise<void>;
|
|
3
|
+
export declare function sessionCreateCommand(client: RpcClient, presetId: string, json: boolean): Promise<void>;
|
|
4
|
+
export declare function sessionGetCommand(client: RpcClient, sessionId: string, json: boolean): Promise<void>;
|
|
5
|
+
export declare function sessionCloseCommand(client: RpcClient, sessionId: string, json: boolean): Promise<void>;
|
|
6
|
+
//# sourceMappingURL=sessions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../src/commands/sessions.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAQnD,wBAAsB,mBAAmB,CACxC,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,EACnD,IAAI,EAAE,OAAO,GACX,OAAO,CAAC,IAAI,CAAC,CAsBf;AAED,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAS5G;AAED,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAa1G;AAED,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAS5G"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { formatSessionInfo, formatTable } from '../repl/formatter.js';
|
|
2
|
+
import { unwrap } from '../unwrap.js';
|
|
3
|
+
function isSessionMetadata(data) {
|
|
4
|
+
return typeof data === 'object' && data !== null && 'sessionId' in data && 'presetId' in data;
|
|
5
|
+
}
|
|
6
|
+
export async function sessionsListCommand(client, status, json) {
|
|
7
|
+
const { sessions } = unwrap(await client.call('sessions.list', { status, limit: 50, order: 'desc' }));
|
|
8
|
+
if (json) {
|
|
9
|
+
console.log(JSON.stringify(sessions, null, 2));
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
if (sessions.length === 0) {
|
|
13
|
+
console.log('No sessions found.');
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
console.log(formatTable(['Session ID', 'Preset', 'Status', 'Created'], sessions.filter(isSessionMetadata).map(s => [
|
|
17
|
+
s.sessionId,
|
|
18
|
+
s.presetId,
|
|
19
|
+
s.status,
|
|
20
|
+
new Date(s.createdAt).toLocaleString(),
|
|
21
|
+
])));
|
|
22
|
+
}
|
|
23
|
+
export async function sessionCreateCommand(client, presetId, json) {
|
|
24
|
+
const { sessionId } = unwrap(await client.call('sessions.create', { presetId }));
|
|
25
|
+
if (json) {
|
|
26
|
+
console.log(JSON.stringify({ sessionId }));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
console.log(`Session created: ${sessionId}`);
|
|
30
|
+
}
|
|
31
|
+
export async function sessionGetCommand(client, sessionId, json) {
|
|
32
|
+
const session = unwrap(await client.call('sessions.get', { sessionId }));
|
|
33
|
+
if (json) {
|
|
34
|
+
console.log(JSON.stringify(session, null, 2));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
console.log(formatSessionInfo(session));
|
|
38
|
+
console.log(` Agents: ${session.agentCount}`);
|
|
39
|
+
if (session.closedAt) {
|
|
40
|
+
console.log(` Closed: ${new Date(session.closedAt).toLocaleString()}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export async function sessionCloseCommand(client, sessionId, json) {
|
|
44
|
+
const result = unwrap(await client.call('sessions.close', { sessionId }));
|
|
45
|
+
if (json) {
|
|
46
|
+
console.log(JSON.stringify(result));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
console.log('Session closed.');
|
|
50
|
+
}
|
package/dist/main.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":""}
|
package/dist/main.js
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { SessionId } from '@roj-ai/shared';
|
|
2
|
+
import { RpcError } from '@roj-ai/shared/rpc';
|
|
3
|
+
import { createCliClient } from './client.js';
|
|
4
|
+
import { answerCommand } from './commands/answer.js';
|
|
5
|
+
import { agentCommand, agentsCommand, debugSendCommand, eventsCommand, llmCallCommand, llmCallsCommand, mailboxCommand, metricsCommand, presetAgentsCommand, spawnAgentCommand, timelineCommand, } from './commands/debug.js';
|
|
6
|
+
import { messagesCommand, sendCommand } from './commands/messages.js';
|
|
7
|
+
import { presetsCommand } from './commands/presets.js';
|
|
8
|
+
import { sessionCloseCommand, sessionCreateCommand, sessionGetCommand, sessionsListCommand } from './commands/sessions.js';
|
|
9
|
+
import { startRepl } from './repl/repl.js';
|
|
10
|
+
const USAGE = `Usage:
|
|
11
|
+
roj-cli [options] Start REPL
|
|
12
|
+
roj-cli <command> [args] [options] Non-interactive
|
|
13
|
+
|
|
14
|
+
Options:
|
|
15
|
+
--url <url> Server URL (default: http://localhost:2486, env: ROJ_URL)
|
|
16
|
+
--json Output as JSON (non-interactive only)
|
|
17
|
+
|
|
18
|
+
Commands:
|
|
19
|
+
presets List available presets
|
|
20
|
+
sessions [--status active|closed|errored] List sessions
|
|
21
|
+
session-create <presetId> Create new session
|
|
22
|
+
session-get <sessionId> Get session info
|
|
23
|
+
session-close <sessionId> Close session
|
|
24
|
+
messages <sessionId> Get messages
|
|
25
|
+
send <sessionId> <message> [--wait] Send message
|
|
26
|
+
answer <sessionId> <questionId> <answer> Answer agent question
|
|
27
|
+
|
|
28
|
+
Debug commands:
|
|
29
|
+
events <sessionId> [--type <type>] [--agent <id>] [--limit N] Show domain events
|
|
30
|
+
agents <sessionId> Show agent tree
|
|
31
|
+
agent <sessionId> <agentId> Show agent detail
|
|
32
|
+
mailbox <sessionId> Show global mailbox
|
|
33
|
+
timeline <sessionId> Show execution timeline
|
|
34
|
+
metrics <sessionId> Show session metrics
|
|
35
|
+
llm-calls <sessionId> [--limit N] List LLM calls
|
|
36
|
+
llm-call <sessionId> <callId> Show LLM call detail
|
|
37
|
+
preset-agents <sessionId> Show spawnable agents
|
|
38
|
+
debug-send <sessionId> <agentId> <message> [--from <sender>] Send debug message
|
|
39
|
+
spawn-agent <sessionId> <defName> <parentId> [--message <msg>] Spawn agent manually`;
|
|
40
|
+
function parseArgs(argv) {
|
|
41
|
+
// Skip bun and script path
|
|
42
|
+
const args = argv.slice(2);
|
|
43
|
+
let url = process.env.ROJ_URL ?? 'http://localhost:2486';
|
|
44
|
+
let json = false;
|
|
45
|
+
const positional = [];
|
|
46
|
+
const flags = {};
|
|
47
|
+
for (let i = 0; i < args.length; i++) {
|
|
48
|
+
const arg = args[i];
|
|
49
|
+
if (arg === '--url') {
|
|
50
|
+
url = args[++i];
|
|
51
|
+
}
|
|
52
|
+
else if (arg === '--json') {
|
|
53
|
+
json = true;
|
|
54
|
+
}
|
|
55
|
+
else if (arg === '--help' || arg === '-h') {
|
|
56
|
+
console.log(USAGE);
|
|
57
|
+
process.exit(0);
|
|
58
|
+
}
|
|
59
|
+
else if (arg.startsWith('--')) {
|
|
60
|
+
const key = arg.slice(2);
|
|
61
|
+
const next = args[i + 1];
|
|
62
|
+
if (next && !next.startsWith('--')) {
|
|
63
|
+
flags[key] = next;
|
|
64
|
+
i++;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
flags[key] = true;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
positional.push(arg);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
url,
|
|
76
|
+
json,
|
|
77
|
+
command: positional[0] ?? null,
|
|
78
|
+
positional: positional.slice(1),
|
|
79
|
+
flags,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
async function main() {
|
|
83
|
+
const parsed = parseArgs(process.argv);
|
|
84
|
+
const client = createCliClient(parsed.url);
|
|
85
|
+
// REPL mode if no command
|
|
86
|
+
if (!parsed.command) {
|
|
87
|
+
await startRepl(client, parsed.url);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
switch (parsed.command) {
|
|
91
|
+
case 'presets':
|
|
92
|
+
await presetsCommand(client, parsed.json);
|
|
93
|
+
break;
|
|
94
|
+
case 'sessions': {
|
|
95
|
+
const status = parsed.flags.status;
|
|
96
|
+
await sessionsListCommand(client, typeof status === 'string' ? status : undefined, parsed.json);
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
case 'session-create': {
|
|
100
|
+
const presetId = parsed.positional[0];
|
|
101
|
+
if (!presetId) {
|
|
102
|
+
console.error('Usage: session-create <presetId>');
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
await sessionCreateCommand(client, presetId, parsed.json);
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
case 'session-get': {
|
|
109
|
+
const sessionId = parsed.positional[0];
|
|
110
|
+
if (!sessionId) {
|
|
111
|
+
console.error('Usage: session-get <sessionId>');
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
await sessionGetCommand(client, sessionId, parsed.json);
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
case 'session-close': {
|
|
118
|
+
const sessionId = parsed.positional[0];
|
|
119
|
+
if (!sessionId) {
|
|
120
|
+
console.error('Usage: session-close <sessionId>');
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
await sessionCloseCommand(client, sessionId, parsed.json);
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
case 'messages': {
|
|
127
|
+
const sessionId = parsed.positional[0];
|
|
128
|
+
if (!sessionId) {
|
|
129
|
+
console.error('Usage: messages <sessionId>');
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
await messagesCommand(client, sessionId, parsed.json);
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
case 'send': {
|
|
136
|
+
const sessionId = parsed.positional[0];
|
|
137
|
+
const message = parsed.positional.slice(1).join(' ');
|
|
138
|
+
if (!sessionId || !message) {
|
|
139
|
+
console.error('Usage: send <sessionId> <message> [--wait]');
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
const wait = parsed.flags.wait === true;
|
|
143
|
+
await sendCommand(client, parsed.url, sessionId, message, wait, parsed.json);
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
case 'answer': {
|
|
147
|
+
const sessionId = parsed.positional[0];
|
|
148
|
+
const questionId = parsed.positional[1];
|
|
149
|
+
const answer = parsed.positional.slice(2).join(' ');
|
|
150
|
+
if (!sessionId || !questionId || !answer) {
|
|
151
|
+
console.error('Usage: answer <sessionId> <questionId> <answer>');
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
await answerCommand(client, sessionId, questionId, answer, parsed.json);
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
// Debug commands
|
|
158
|
+
case 'events': {
|
|
159
|
+
const sessionId = parsed.positional[0];
|
|
160
|
+
if (!sessionId) {
|
|
161
|
+
console.error('Usage: events <sessionId> [--type <type>] [--agent <agentId>] [--limit N]');
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
await eventsCommand(client, sessionId, parsed.flags, parsed.json);
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
case 'agents': {
|
|
168
|
+
const sessionId = parsed.positional[0];
|
|
169
|
+
if (!sessionId) {
|
|
170
|
+
console.error('Usage: agents <sessionId>');
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
173
|
+
await agentsCommand(client, sessionId, parsed.json);
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
case 'agent': {
|
|
177
|
+
const sessionId = parsed.positional[0];
|
|
178
|
+
const agentId = parsed.positional[1];
|
|
179
|
+
if (!sessionId || !agentId) {
|
|
180
|
+
console.error('Usage: agent <sessionId> <agentId>');
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
await agentCommand(client, sessionId, agentId, parsed.json);
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
case 'mailbox': {
|
|
187
|
+
const sessionId = parsed.positional[0];
|
|
188
|
+
if (!sessionId) {
|
|
189
|
+
console.error('Usage: mailbox <sessionId>');
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
await mailboxCommand(client, sessionId, parsed.json);
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
case 'timeline': {
|
|
196
|
+
const sessionId = parsed.positional[0];
|
|
197
|
+
if (!sessionId) {
|
|
198
|
+
console.error('Usage: timeline <sessionId>');
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
await timelineCommand(client, sessionId, parsed.json);
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
case 'metrics': {
|
|
205
|
+
const sessionId = parsed.positional[0];
|
|
206
|
+
if (!sessionId) {
|
|
207
|
+
console.error('Usage: metrics <sessionId>');
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
await metricsCommand(client, sessionId, parsed.json);
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
case 'llm-calls': {
|
|
214
|
+
const sessionId = parsed.positional[0];
|
|
215
|
+
if (!sessionId) {
|
|
216
|
+
console.error('Usage: llm-calls <sessionId> [--limit N]');
|
|
217
|
+
process.exit(1);
|
|
218
|
+
}
|
|
219
|
+
await llmCallsCommand(client, sessionId, parsed.flags, parsed.json);
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
case 'llm-call': {
|
|
223
|
+
const sessionId = parsed.positional[0];
|
|
224
|
+
const callId = parsed.positional[1];
|
|
225
|
+
if (!sessionId || !callId) {
|
|
226
|
+
console.error('Usage: llm-call <sessionId> <callId>');
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|
|
229
|
+
await llmCallCommand(client, sessionId, callId, parsed.json);
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
case 'preset-agents': {
|
|
233
|
+
const sessionId = parsed.positional[0];
|
|
234
|
+
if (!sessionId) {
|
|
235
|
+
console.error('Usage: preset-agents <sessionId>');
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
await presetAgentsCommand(client, SessionId(sessionId), parsed.json);
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
case 'debug-send': {
|
|
242
|
+
const sessionId = parsed.positional[0];
|
|
243
|
+
const agentId = parsed.positional[1];
|
|
244
|
+
const message = parsed.positional.slice(2).join(' ');
|
|
245
|
+
if (!sessionId || !agentId || !message) {
|
|
246
|
+
console.error('Usage: debug-send <sessionId> <agentId> <message> [--from <sender>]');
|
|
247
|
+
process.exit(1);
|
|
248
|
+
}
|
|
249
|
+
await debugSendCommand(client, sessionId, agentId, message, parsed.flags, parsed.json);
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
case 'spawn-agent': {
|
|
253
|
+
const sessionId = parsed.positional[0];
|
|
254
|
+
const definitionName = parsed.positional[1];
|
|
255
|
+
const parentId = parsed.positional[2];
|
|
256
|
+
if (!sessionId || !definitionName || !parentId) {
|
|
257
|
+
console.error('Usage: spawn-agent <sessionId> <definitionName> <parentId> [--message <msg>]');
|
|
258
|
+
process.exit(1);
|
|
259
|
+
}
|
|
260
|
+
await spawnAgentCommand(client, sessionId, definitionName, parentId, parsed.flags, parsed.json);
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
default:
|
|
264
|
+
console.error(`Unknown command: ${parsed.command}`);
|
|
265
|
+
console.error(USAGE);
|
|
266
|
+
process.exit(1);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
main().catch((error) => {
|
|
270
|
+
if (error instanceof RpcError) {
|
|
271
|
+
console.error(`RPC Error [${error.error.type}]: ${error.error.message}`);
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
275
|
+
}
|
|
276
|
+
process.exit(1);
|
|
277
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AskUserInputTypeSchema } from '@roj-ai/sdk';
|
|
2
|
+
import type { ChatMessage } from '@roj-ai/shared/rpc';
|
|
3
|
+
export declare function formatAgentMessage(content: string): string;
|
|
4
|
+
export declare function formatUserMessage(content: string): string;
|
|
5
|
+
export declare function formatStatus(status: string): string;
|
|
6
|
+
export declare function formatError(message: string): string;
|
|
7
|
+
export declare function formatQuestion(question: string, inputType: AskUserInputTypeSchema): string;
|
|
8
|
+
export declare function formatChatMessage(msg: ChatMessage): string;
|
|
9
|
+
export declare function formatSessionInfo(session: {
|
|
10
|
+
sessionId: string;
|
|
11
|
+
presetId: string;
|
|
12
|
+
status: string;
|
|
13
|
+
createdAt: number;
|
|
14
|
+
}): string;
|
|
15
|
+
export declare function formatTable(headers: string[], rows: string[][]): string;
|
|
16
|
+
//# sourceMappingURL=formatter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formatter.d.ts","sourceRoot":"","sources":["../../src/repl/formatter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AACzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAYrD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,sBAAsB,GAAG,MAAM,CAuB1F;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,WAAW,GAAG,MAAM,CAW1D;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAG7H;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,MAAM,CAWvE"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// ANSI color codes
|
|
2
|
+
const RESET = '\x1b[0m';
|
|
3
|
+
const BOLD = '\x1b[1m';
|
|
4
|
+
const DIM = '\x1b[2m';
|
|
5
|
+
const CYAN = '\x1b[36m';
|
|
6
|
+
const GREEN = '\x1b[32m';
|
|
7
|
+
const YELLOW = '\x1b[33m';
|
|
8
|
+
const RED = '\x1b[31m';
|
|
9
|
+
const MAGENTA = '\x1b[35m';
|
|
10
|
+
export function formatAgentMessage(content) {
|
|
11
|
+
return `${CYAN}${BOLD}Agent:${RESET} ${content}`;
|
|
12
|
+
}
|
|
13
|
+
export function formatUserMessage(content) {
|
|
14
|
+
return `${GREEN}${BOLD}You:${RESET} ${content}`;
|
|
15
|
+
}
|
|
16
|
+
export function formatStatus(status) {
|
|
17
|
+
return `${DIM}[${status}]${RESET}`;
|
|
18
|
+
}
|
|
19
|
+
export function formatError(message) {
|
|
20
|
+
return `${RED}Error: ${message}${RESET}`;
|
|
21
|
+
}
|
|
22
|
+
export function formatQuestion(question, inputType) {
|
|
23
|
+
const lines = [`${YELLOW}${BOLD}Question:${RESET} ${question}`];
|
|
24
|
+
if (inputType.type === 'single_choice' || inputType.type === 'multi_choice') {
|
|
25
|
+
for (let i = 0; i < inputType.options.length; i++) {
|
|
26
|
+
const opt = inputType.options[i];
|
|
27
|
+
const desc = opt.description ? ` - ${opt.description}` : '';
|
|
28
|
+
lines.push(` ${MAGENTA}${i + 1})${RESET} ${opt.label}${DIM}${desc}${RESET}`);
|
|
29
|
+
}
|
|
30
|
+
if (inputType.type === 'single_choice') {
|
|
31
|
+
lines.push(`${DIM}Enter number to select:${RESET}`);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
lines.push(`${DIM}Enter numbers separated by commas:${RESET}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
else if (inputType.type === 'confirm') {
|
|
38
|
+
const confirmLabel = inputType.confirmLabel ?? 'yes';
|
|
39
|
+
const cancelLabel = inputType.cancelLabel ?? 'no';
|
|
40
|
+
lines.push(`${DIM}(${confirmLabel}/${cancelLabel}):${RESET}`);
|
|
41
|
+
}
|
|
42
|
+
else if (inputType.type === 'rating') {
|
|
43
|
+
lines.push(`${DIM}Enter a number from ${inputType.min} to ${inputType.max}:${RESET}`);
|
|
44
|
+
}
|
|
45
|
+
return lines.join('\n');
|
|
46
|
+
}
|
|
47
|
+
export function formatChatMessage(msg) {
|
|
48
|
+
switch (msg.type) {
|
|
49
|
+
case 'user_message':
|
|
50
|
+
return formatUserMessage(msg.content);
|
|
51
|
+
case 'agent_message':
|
|
52
|
+
return formatAgentMessage(msg.content);
|
|
53
|
+
case 'ask_user': {
|
|
54
|
+
const answered = msg.answered ? `${DIM} (answered: ${JSON.stringify(msg.answer)})${RESET}` : '';
|
|
55
|
+
return formatQuestion(msg.question, msg.inputType) + answered;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export function formatSessionInfo(session) {
|
|
60
|
+
const date = new Date(session.createdAt).toLocaleString();
|
|
61
|
+
return `${BOLD}${session.sessionId}${RESET} ${DIM}[${session.presetId}]${RESET} ${session.status} ${DIM}${date}${RESET}`;
|
|
62
|
+
}
|
|
63
|
+
export function formatTable(headers, rows) {
|
|
64
|
+
const colWidths = headers.map((h, i) => {
|
|
65
|
+
const maxDataWidth = rows.reduce((max, row) => Math.max(max, (row[i] ?? '').length), 0);
|
|
66
|
+
return Math.max(h.length, maxDataWidth);
|
|
67
|
+
});
|
|
68
|
+
const headerLine = headers.map((h, i) => h.padEnd(colWidths[i])).join(' ');
|
|
69
|
+
const separator = colWidths.map(w => '─'.repeat(w)).join('──');
|
|
70
|
+
const dataLines = rows.map(row => row.map((cell, i) => (cell ?? '').padEnd(colWidths[i])).join(' '));
|
|
71
|
+
return [headerLine, separator, ...dataLines].join('\n');
|
|
72
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repl.d.ts","sourceRoot":"","sources":["../../src/repl/repl.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAUnD,wBAAsB,SAAS,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA0HjF"}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { AgentId, ChatMessageId } from '@roj-ai/shared';
|
|
2
|
+
function isChatMessage(msg) {
|
|
3
|
+
return typeof msg === 'object' && msg !== null && 'type' in msg && 'timestamp' in msg;
|
|
4
|
+
}
|
|
5
|
+
import * as readline from 'node:readline';
|
|
6
|
+
import { connectWebSocket } from '../client.js';
|
|
7
|
+
import { unwrap } from '../unwrap.js';
|
|
8
|
+
import { formatAgentMessage, formatChatMessage, formatError, formatQuestion, formatStatus } from './formatter.js';
|
|
9
|
+
import { createNewSession, pickSession } from './session-picker.js';
|
|
10
|
+
const DIM = '\x1b[2m';
|
|
11
|
+
const RESET = '\x1b[0m';
|
|
12
|
+
export async function startRepl(client, baseUrl) {
|
|
13
|
+
const rl = readline.createInterface({
|
|
14
|
+
input: process.stdin,
|
|
15
|
+
output: process.stdout,
|
|
16
|
+
});
|
|
17
|
+
console.log(`${DIM}Connected to ${baseUrl}${RESET}`);
|
|
18
|
+
console.log(`${DIM}Commands: /quit, /sessions, /new, /messages, /help${RESET}`);
|
|
19
|
+
let sessionId = await pickSession(client, rl);
|
|
20
|
+
let entryAgentId = await resolveEntryAgentId(client, sessionId);
|
|
21
|
+
await loadAndDisplayMessages(client, sessionId);
|
|
22
|
+
let pendingQuestion = null;
|
|
23
|
+
const handlers = {
|
|
24
|
+
agentMessage: (payload) => {
|
|
25
|
+
if (typeof payload !== 'object' || payload === null)
|
|
26
|
+
return;
|
|
27
|
+
const input = payload;
|
|
28
|
+
if (typeof input.content !== 'string')
|
|
29
|
+
return;
|
|
30
|
+
readline.clearLine(process.stdout, 0);
|
|
31
|
+
readline.cursorTo(process.stdout, 0);
|
|
32
|
+
console.log(formatAgentMessage(input.content));
|
|
33
|
+
rl.prompt();
|
|
34
|
+
},
|
|
35
|
+
askUser: (payload) => {
|
|
36
|
+
if (typeof payload !== 'object' || payload === null)
|
|
37
|
+
return;
|
|
38
|
+
const input = payload;
|
|
39
|
+
if (typeof input.questionId !== 'string' || typeof input.question !== 'string')
|
|
40
|
+
return;
|
|
41
|
+
readline.clearLine(process.stdout, 0);
|
|
42
|
+
readline.cursorTo(process.stdout, 0);
|
|
43
|
+
console.log(formatQuestion(input.question, input.inputType));
|
|
44
|
+
pendingQuestion = { questionId: input.questionId, inputType: input.inputType };
|
|
45
|
+
rl.prompt();
|
|
46
|
+
},
|
|
47
|
+
agentStatus: (payload) => {
|
|
48
|
+
if (typeof payload !== 'object' || payload === null)
|
|
49
|
+
return;
|
|
50
|
+
const input = payload;
|
|
51
|
+
if (typeof input.status !== 'string')
|
|
52
|
+
return;
|
|
53
|
+
const statusLabels = {
|
|
54
|
+
idle: 'Agent idle',
|
|
55
|
+
thinking: 'Agent thinking...',
|
|
56
|
+
responding: 'Agent responding...',
|
|
57
|
+
waiting_for_user: 'Waiting for your input',
|
|
58
|
+
error: 'Agent error',
|
|
59
|
+
};
|
|
60
|
+
readline.clearLine(process.stdout, 0);
|
|
61
|
+
readline.cursorTo(process.stdout, 0);
|
|
62
|
+
console.log(formatStatus(statusLabels[input.status] ?? input.status));
|
|
63
|
+
rl.prompt();
|
|
64
|
+
},
|
|
65
|
+
error: (payload) => {
|
|
66
|
+
if (typeof payload !== 'object' || payload === null)
|
|
67
|
+
return;
|
|
68
|
+
const input = payload;
|
|
69
|
+
if (typeof input.message !== 'string')
|
|
70
|
+
return;
|
|
71
|
+
readline.clearLine(process.stdout, 0);
|
|
72
|
+
readline.cursorTo(process.stdout, 0);
|
|
73
|
+
console.log(formatError(input.message));
|
|
74
|
+
rl.prompt();
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
let connection = connectWebSocket(baseUrl, sessionId, handlers);
|
|
78
|
+
await connection.connect();
|
|
79
|
+
rl.setPrompt('> ');
|
|
80
|
+
rl.prompt();
|
|
81
|
+
rl.on('line', async (line) => {
|
|
82
|
+
const input = line.trim();
|
|
83
|
+
if (!input) {
|
|
84
|
+
rl.prompt();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (input.startsWith('/')) {
|
|
88
|
+
const handled = await handleSlashCommand(input, client, baseUrl, rl, sessionId, connection);
|
|
89
|
+
if (handled === 'quit') {
|
|
90
|
+
await connection.disconnect();
|
|
91
|
+
rl.close();
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (handled !== false) {
|
|
95
|
+
// Session switch
|
|
96
|
+
await connection.disconnect();
|
|
97
|
+
sessionId = handled;
|
|
98
|
+
entryAgentId = await resolveEntryAgentId(client, sessionId);
|
|
99
|
+
connection = connectWebSocket(baseUrl, sessionId, handlers);
|
|
100
|
+
await connection.connect();
|
|
101
|
+
await loadAndDisplayMessages(client, sessionId);
|
|
102
|
+
}
|
|
103
|
+
rl.prompt();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
// Handle pending question answer
|
|
107
|
+
if (pendingQuestion && entryAgentId) {
|
|
108
|
+
const answer = parseAnswer(input, pendingQuestion.inputType);
|
|
109
|
+
unwrap(await client.call('user-chat.answerQuestion', {
|
|
110
|
+
sessionId,
|
|
111
|
+
agentId: AgentId(entryAgentId),
|
|
112
|
+
questionId: ChatMessageId(pendingQuestion.questionId),
|
|
113
|
+
answer,
|
|
114
|
+
}));
|
|
115
|
+
pendingQuestion = null;
|
|
116
|
+
rl.prompt();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
// Send message
|
|
120
|
+
if (entryAgentId) {
|
|
121
|
+
unwrap(await client.call('user-chat.sendMessage', { sessionId, agentId: AgentId(entryAgentId), content: input }));
|
|
122
|
+
}
|
|
123
|
+
rl.prompt();
|
|
124
|
+
});
|
|
125
|
+
rl.on('close', async () => {
|
|
126
|
+
await connection.disconnect();
|
|
127
|
+
process.exit(0);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
async function resolveEntryAgentId(client, sessionId) {
|
|
131
|
+
const session = unwrap(await client.call('sessions.get', { sessionId }));
|
|
132
|
+
return session.entryAgentId;
|
|
133
|
+
}
|
|
134
|
+
async function loadAndDisplayMessages(client, sessionId) {
|
|
135
|
+
const { messages: rawMessages } = unwrap(await client.call('user-chat.getMessages', { sessionId }));
|
|
136
|
+
const messages = rawMessages.filter(isChatMessage);
|
|
137
|
+
if (messages.length > 0) {
|
|
138
|
+
console.log();
|
|
139
|
+
console.log(`${DIM}--- Message history ---${RESET}`);
|
|
140
|
+
for (const msg of messages) {
|
|
141
|
+
console.log(formatChatMessage(msg));
|
|
142
|
+
console.log();
|
|
143
|
+
}
|
|
144
|
+
console.log(`${DIM}--- End of history ---${RESET}`);
|
|
145
|
+
console.log();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
async function handleSlashCommand(input, client, _baseUrl, rl, currentSessionId, _connection) {
|
|
149
|
+
const [cmd] = input.split(/\s+/);
|
|
150
|
+
switch (cmd) {
|
|
151
|
+
case '/quit':
|
|
152
|
+
case '/exit':
|
|
153
|
+
case '/q':
|
|
154
|
+
return 'quit';
|
|
155
|
+
case '/sessions': {
|
|
156
|
+
const newSessionId = await pickSession(client, rl);
|
|
157
|
+
if (newSessionId !== currentSessionId) {
|
|
158
|
+
return newSessionId;
|
|
159
|
+
}
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
case '/new': {
|
|
163
|
+
const newSessionId = await createNewSession(client, rl);
|
|
164
|
+
return newSessionId;
|
|
165
|
+
}
|
|
166
|
+
case '/messages': {
|
|
167
|
+
await loadAndDisplayMessages(client, currentSessionId);
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
case '/help':
|
|
171
|
+
console.log(`${DIM}Commands:${RESET}`);
|
|
172
|
+
console.log(` /quit, /exit, /q - Exit the REPL`);
|
|
173
|
+
console.log(` /sessions - Switch to another session`);
|
|
174
|
+
console.log(` /new - Create a new session`);
|
|
175
|
+
console.log(` /messages - Show message history`);
|
|
176
|
+
console.log(` /help - Show this help`);
|
|
177
|
+
return false;
|
|
178
|
+
default:
|
|
179
|
+
console.log(`Unknown command: ${cmd}. Type /help for available commands.`);
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function parseAnswer(input, inputType) {
|
|
184
|
+
switch (inputType.type) {
|
|
185
|
+
case 'text':
|
|
186
|
+
return input;
|
|
187
|
+
case 'single_choice': {
|
|
188
|
+
const index = parseInt(input, 10) - 1;
|
|
189
|
+
if (index >= 0 && index < inputType.options.length) {
|
|
190
|
+
return inputType.options[index].value;
|
|
191
|
+
}
|
|
192
|
+
return input;
|
|
193
|
+
}
|
|
194
|
+
case 'multi_choice': {
|
|
195
|
+
const indices = input.split(',').map(s => parseInt(s.trim(), 10) - 1);
|
|
196
|
+
return indices
|
|
197
|
+
.filter(i => i >= 0 && i < inputType.options.length)
|
|
198
|
+
.map(i => inputType.options[i].value);
|
|
199
|
+
}
|
|
200
|
+
case 'confirm': {
|
|
201
|
+
const lower = input.toLowerCase();
|
|
202
|
+
return lower === 'y' || lower === 'yes' || lower === '1' || lower === 'true';
|
|
203
|
+
}
|
|
204
|
+
case 'rating': {
|
|
205
|
+
const num = parseInt(input, 10);
|
|
206
|
+
if (num >= inputType.min && num <= inputType.max) {
|
|
207
|
+
return num;
|
|
208
|
+
}
|
|
209
|
+
return parseInt(input, 10);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|