@davvidbaker/flambe-cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/flambe.mjs +13 -0
- package/package.json +20 -0
- package/src/cli.mjs +427 -0
- package/src/client.mjs +549 -0
- package/src/env.mjs +21 -0
- package/src/local/agents.mjs +24 -0
- package/src/local/http.mjs +391 -0
- package/src/local/serve.mjs +61 -0
- package/src/local/store.mjs +739 -0
- package/src/queue.mjs +161 -0
- package/test/client.test.mjs +951 -0
- package/test/local.test.mjs +136 -0
- package/test/queue.test.mjs +135 -0
package/bin/flambe.mjs
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { run } from '../src/cli.mjs';
|
|
4
|
+
import { loadProjectEnv } from '../src/env.mjs';
|
|
5
|
+
|
|
6
|
+
loadProjectEnv();
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
await run(process.argv.slice(2));
|
|
10
|
+
} catch (error) {
|
|
11
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
12
|
+
process.exitCode = 1;
|
|
13
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@davvidbaker/flambe-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI for streaming coding-agent activity into Flambe",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"flambe": "./bin/flambe.mjs"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "node --test"
|
|
11
|
+
},
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=22"
|
|
14
|
+
},
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/davvidbaker/flambe.git"
|
|
19
|
+
}
|
|
20
|
+
}
|
package/src/cli.mjs
ADDED
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
|
+
|
|
5
|
+
import { clientFromEnv, clientFromHostEnv } from './client.mjs';
|
|
6
|
+
|
|
7
|
+
// `node:sqlite` still prints an ExperimentalWarning on import in some Node 22 releases.
|
|
8
|
+
// Only serve/export need it, so load those modules on demand and keep every other
|
|
9
|
+
// command's stderr clean for reducer notes.
|
|
10
|
+
const loadServe = () => import('./local/serve.mjs');
|
|
11
|
+
const loadStore = () => import('./local/store.mjs');
|
|
12
|
+
|
|
13
|
+
function defaultDbPath() {
|
|
14
|
+
return join(homedir(), '.flambe', 'local.sqlite');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const usage = `Usage:
|
|
18
|
+
flambe start <activity name> [--description <text>] [--thread <id>] [--parent <id> | --root] [--category <id>...] [--started-at <ISO-8601>]
|
|
19
|
+
flambe end <activity-id> [message] [--force]
|
|
20
|
+
flambe suspend <activity-id> [message]
|
|
21
|
+
flambe resume <activity-id> [message]
|
|
22
|
+
flambe status [--active | --suspended] [--json]
|
|
23
|
+
flambe threads [--json]
|
|
24
|
+
flambe categories [--json]
|
|
25
|
+
flambe ping
|
|
26
|
+
flambe message <text> [--activity <id>] [--json]
|
|
27
|
+
flambe observe <kind> <value> [--unit <unit>] [--on <YYYY-MM-DD>] [--payload <json>] [--at <ISO-8601>]
|
|
28
|
+
flambe serve [--port <n>] [--db <path>] [--static <dir>]
|
|
29
|
+
flambe export [file] [--db <path>]
|
|
30
|
+
flambe import <file> [--url <url>] [--token <token>]
|
|
31
|
+
|
|
32
|
+
Configuration:
|
|
33
|
+
Loads .env from the current working directory.
|
|
34
|
+
Existing shell environment variables take precedence.
|
|
35
|
+
|
|
36
|
+
Required values:
|
|
37
|
+
FLAMBE_URL
|
|
38
|
+
FLAMBE_API_TOKEN
|
|
39
|
+
FLAMBE_TRACE_ID
|
|
40
|
+
|
|
41
|
+
Optional:
|
|
42
|
+
FLAMBE_AGENT_ID Stable ID for this running agent; enables named flame lanes.
|
|
43
|
+
Derived from Codex, Cursor, or Claude Code session env when unset.
|
|
44
|
+
FLAMBE_AGENT_NAME Display name for this agent's lane. Do not put this in .env.
|
|
45
|
+
FLAMBE_QUEUE_PATH Local offline queue path (default: ~/.flambe/event-queue.json)
|
|
46
|
+
FLAMBE_LOCAL_DB SQLite path for flambe serve / export (default: ~/.flambe/local.sqlite)
|
|
47
|
+
|
|
48
|
+
serve / export do not need FLAMBE_URL. import needs FLAMBE_URL and FLAMBE_API_TOKEN (the production instance).`;
|
|
49
|
+
|
|
50
|
+
function parseStart(args) {
|
|
51
|
+
const nameParts = [];
|
|
52
|
+
let description;
|
|
53
|
+
let threadId;
|
|
54
|
+
let parentId;
|
|
55
|
+
let startedAt;
|
|
56
|
+
const categoryIds = [];
|
|
57
|
+
|
|
58
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
59
|
+
const arg = args[index];
|
|
60
|
+
if (arg === '--description') {
|
|
61
|
+
description = args[index + 1];
|
|
62
|
+
if (description === undefined) throw new Error('--description requires a value');
|
|
63
|
+
index += 1;
|
|
64
|
+
} else if (arg === '--thread') {
|
|
65
|
+
threadId = args[index + 1];
|
|
66
|
+
if (threadId === undefined) throw new Error('--thread requires a value');
|
|
67
|
+
index += 1;
|
|
68
|
+
} else if (arg === '--parent') {
|
|
69
|
+
if (parentId !== undefined) throw new Error('--parent and --root cannot be combined');
|
|
70
|
+
parentId = args[index + 1];
|
|
71
|
+
if (parentId === undefined) throw new Error('--parent requires a value');
|
|
72
|
+
index += 1;
|
|
73
|
+
} else if (arg === '--root') {
|
|
74
|
+
if (parentId !== undefined) throw new Error('--parent and --root cannot be combined');
|
|
75
|
+
parentId = null;
|
|
76
|
+
} else if (arg === '--category') {
|
|
77
|
+
const categoryId = args[index + 1];
|
|
78
|
+
if (categoryId === undefined) throw new Error('--category requires a value');
|
|
79
|
+
categoryIds.push(categoryId);
|
|
80
|
+
index += 1;
|
|
81
|
+
} else if (arg === '--started-at') {
|
|
82
|
+
startedAt = args[index + 1];
|
|
83
|
+
if (startedAt === undefined) throw new Error('--started-at requires a value');
|
|
84
|
+
index += 1;
|
|
85
|
+
} else if (arg.startsWith('--')) {
|
|
86
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
87
|
+
} else {
|
|
88
|
+
nameParts.push(arg);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
name: nameParts.join(' '),
|
|
94
|
+
description,
|
|
95
|
+
threadId,
|
|
96
|
+
...(parentId === undefined ? {} : { parentId }),
|
|
97
|
+
categoryIds,
|
|
98
|
+
startedAt,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function parseEnd(args) {
|
|
103
|
+
let force = false;
|
|
104
|
+
const positional = [];
|
|
105
|
+
|
|
106
|
+
for (const arg of args) {
|
|
107
|
+
if (arg === '--force') force = true;
|
|
108
|
+
else if (arg.startsWith('--')) throw new Error(`Unknown option: ${arg}`);
|
|
109
|
+
else positional.push(arg);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const [activityId, ...messageParts] = positional;
|
|
113
|
+
return {
|
|
114
|
+
activityId,
|
|
115
|
+
message: messageParts.join(' ') || undefined,
|
|
116
|
+
force,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function parseStatus(args) {
|
|
121
|
+
let activeOnly = false;
|
|
122
|
+
let suspendedOnly = false;
|
|
123
|
+
let json = false;
|
|
124
|
+
|
|
125
|
+
for (const arg of args) {
|
|
126
|
+
if (arg === '--active') activeOnly = true;
|
|
127
|
+
else if (arg === '--suspended') suspendedOnly = true;
|
|
128
|
+
else if (arg === '--json') json = true;
|
|
129
|
+
else throw new Error(`Unknown option: ${arg}`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (activeOnly && suspendedOnly) throw new Error('--active and --suspended cannot be used together');
|
|
133
|
+
return { activeOnly, suspendedOnly, json };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function parseObserve(args) {
|
|
137
|
+
const positional = [];
|
|
138
|
+
let unit;
|
|
139
|
+
let observedOn;
|
|
140
|
+
let payload;
|
|
141
|
+
let at;
|
|
142
|
+
|
|
143
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
144
|
+
const arg = args[index];
|
|
145
|
+
if (arg === '--unit') {
|
|
146
|
+
unit = args[index + 1];
|
|
147
|
+
if (unit === undefined) throw new Error('--unit requires a value');
|
|
148
|
+
index += 1;
|
|
149
|
+
} else if (arg === '--on') {
|
|
150
|
+
observedOn = args[index + 1];
|
|
151
|
+
if (observedOn === undefined) throw new Error('--on requires a value');
|
|
152
|
+
index += 1;
|
|
153
|
+
} else if (arg === '--payload') {
|
|
154
|
+
const raw = args[index + 1];
|
|
155
|
+
if (raw === undefined) throw new Error('--payload requires a JSON object');
|
|
156
|
+
try {
|
|
157
|
+
payload = JSON.parse(raw);
|
|
158
|
+
} catch {
|
|
159
|
+
throw new Error('--payload must be valid JSON');
|
|
160
|
+
}
|
|
161
|
+
if (payload === null || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
162
|
+
throw new Error('--payload must be a JSON object');
|
|
163
|
+
}
|
|
164
|
+
index += 1;
|
|
165
|
+
} else if (arg === '--at') {
|
|
166
|
+
at = args[index + 1];
|
|
167
|
+
if (at === undefined) throw new Error('--at requires a value');
|
|
168
|
+
index += 1;
|
|
169
|
+
} else if (arg.startsWith('--')) {
|
|
170
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
171
|
+
} else {
|
|
172
|
+
positional.push(arg);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const [kind, valueText, ...rest] = positional;
|
|
177
|
+
if (!kind || valueText === undefined || rest.length > 0) {
|
|
178
|
+
throw new Error('Usage: flambe observe <kind> <value> [--unit <unit>] [--on <YYYY-MM-DD>] [--payload <json>] [--at <ISO-8601>]');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const value = Number(valueText);
|
|
182
|
+
if (!Number.isFinite(value)) throw new Error('value must be a finite number');
|
|
183
|
+
if (observedOn !== undefined && !/^\d{4}-\d{2}-\d{2}$/.test(observedOn)) {
|
|
184
|
+
throw new Error('--on must be YYYY-MM-DD');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
kind,
|
|
189
|
+
value,
|
|
190
|
+
unit,
|
|
191
|
+
observedOn,
|
|
192
|
+
payload,
|
|
193
|
+
at,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function parseJson(args) {
|
|
198
|
+
if (args.length === 0) return false;
|
|
199
|
+
if (args.length === 1 && args[0] === '--json') return true;
|
|
200
|
+
throw new Error(`Unknown option: ${args[0]}`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function takeOption(args, name) {
|
|
204
|
+
const index = args.indexOf(name);
|
|
205
|
+
if (index === -1) return { value: undefined, rest: args };
|
|
206
|
+
const value = args[index + 1];
|
|
207
|
+
if (value === undefined) throw new Error(`${name} requires a value`);
|
|
208
|
+
return { value, rest: [...args.slice(0, index), ...args.slice(index + 2)] };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function parseServe(args, env) {
|
|
212
|
+
let rest = args;
|
|
213
|
+
let port;
|
|
214
|
+
let dbPath;
|
|
215
|
+
let staticDir;
|
|
216
|
+
({ value: port, rest } = takeOption(rest, '--port'));
|
|
217
|
+
({ value: dbPath, rest } = takeOption(rest, '--db'));
|
|
218
|
+
({ value: staticDir, rest } = takeOption(rest, '--static'));
|
|
219
|
+
if (rest.length > 0) throw new Error(`Unknown option: ${rest[0]}`);
|
|
220
|
+
return {
|
|
221
|
+
port: port ? Number(port) : 4001,
|
|
222
|
+
dbPath: dbPath ?? env.FLAMBE_LOCAL_DB ?? defaultDbPath(),
|
|
223
|
+
staticDir,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function parseExport(args, env) {
|
|
228
|
+
let rest = args;
|
|
229
|
+
let dbPath;
|
|
230
|
+
({ value: dbPath, rest } = takeOption(rest, '--db'));
|
|
231
|
+
const positional = rest.filter(arg => !arg.startsWith('--'));
|
|
232
|
+
const unknown = rest.filter(arg => arg.startsWith('--'));
|
|
233
|
+
if (unknown.length > 0) throw new Error(`Unknown option: ${unknown[0]}`);
|
|
234
|
+
if (positional.length > 1) throw new Error('Usage: flambe export [file] [--db <path>]');
|
|
235
|
+
return {
|
|
236
|
+
file: positional[0] ?? 'flambe-export.json',
|
|
237
|
+
dbPath: dbPath ?? env.FLAMBE_LOCAL_DB ?? defaultDbPath(),
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function parseMessage(args) {
|
|
242
|
+
let rest = args;
|
|
243
|
+
let activityId;
|
|
244
|
+
({ value: activityId, rest } = takeOption(rest, '--activity'));
|
|
245
|
+
const json = rest.includes('--json');
|
|
246
|
+
rest = rest.filter(arg => arg !== '--json');
|
|
247
|
+
const unknown = rest.filter(arg => arg.startsWith('--'));
|
|
248
|
+
if (unknown.length > 0) throw new Error(`Unknown option: ${unknown[0]}`);
|
|
249
|
+
const text = rest.join(' ').trim();
|
|
250
|
+
if (!text) throw new Error('Usage: flambe message <text> [--activity <id>] [--json]');
|
|
251
|
+
return { text, activityId, json };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function parseImport(args) {
|
|
255
|
+
let rest = args;
|
|
256
|
+
let url;
|
|
257
|
+
let token;
|
|
258
|
+
({ value: url, rest } = takeOption(rest, '--url'));
|
|
259
|
+
({ value: token, rest } = takeOption(rest, '--token'));
|
|
260
|
+
const positional = rest.filter(arg => !arg.startsWith('--'));
|
|
261
|
+
const unknown = rest.filter(arg => arg.startsWith('--'));
|
|
262
|
+
if (unknown.length > 0) throw new Error(`Unknown option: ${unknown[0]}`);
|
|
263
|
+
if (positional.length !== 1) throw new Error('Usage: flambe import <file> [--url <url>] [--token <token>]');
|
|
264
|
+
return { file: positional[0], url, token };
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function formatAction(action) {
|
|
268
|
+
if (action.type === 'create_child') return `created child ${action.activity_id} under ${action.parent_activity_id}`;
|
|
269
|
+
if (action.type === 'update_activity') return `updated activity ${action.activity_id}`;
|
|
270
|
+
return JSON.stringify(action);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function formatReducerNote(note) {
|
|
274
|
+
if (note.type === 'closed_descendants') {
|
|
275
|
+
const list = note.closedDescendants.map(item => `${item.activityId} (${item.activityName})`).join(', ');
|
|
276
|
+
return `reducer closed open descendants of ${note.activityId}: ${list}\n`;
|
|
277
|
+
}
|
|
278
|
+
return `reducer: ${JSON.stringify(note)}\n`;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export async function run(argv, { env = process.env, stdout = process.stdout, stderr = process.stderr, client } = {}) {
|
|
282
|
+
const [command, ...args] = argv;
|
|
283
|
+
|
|
284
|
+
if (!command || command === 'help' || command === '--help' || command === '-h') {
|
|
285
|
+
stdout.write(`${usage}\n`);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (command === 'serve') {
|
|
290
|
+
const options = parseServe(args, env);
|
|
291
|
+
const { listenLocal, printServeBanner } = await loadServe();
|
|
292
|
+
const listening = await listenLocal(options);
|
|
293
|
+
printServeBanner(listening, stdout);
|
|
294
|
+
await new Promise((resolveClose, reject) => {
|
|
295
|
+
listening.server.on('close', resolveClose);
|
|
296
|
+
listening.server.on('error', reject);
|
|
297
|
+
});
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (command === 'export') {
|
|
302
|
+
const { file, dbPath } = parseExport(args, env);
|
|
303
|
+
const { LocalStore } = await loadStore();
|
|
304
|
+
const store = new LocalStore(dbPath);
|
|
305
|
+
try {
|
|
306
|
+
const bundle = store.exportBundle();
|
|
307
|
+
const outPath = resolve(file);
|
|
308
|
+
writeFileSync(outPath, `${JSON.stringify(bundle, null, 2)}\n`);
|
|
309
|
+
stdout.write(`${outPath}\n`);
|
|
310
|
+
} finally {
|
|
311
|
+
store.close();
|
|
312
|
+
}
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (command === 'import') {
|
|
317
|
+
const { file, url, token } = parseImport(args);
|
|
318
|
+
const bundle = JSON.parse(readFileSync(resolve(file), 'utf8'));
|
|
319
|
+
const flambe = client ?? clientFromHostEnv({
|
|
320
|
+
...env,
|
|
321
|
+
...(url ? { FLAMBE_URL: url } : {}),
|
|
322
|
+
...(token ? { FLAMBE_API_TOKEN: token } : {}),
|
|
323
|
+
});
|
|
324
|
+
const result = await flambe.importBundle(bundle);
|
|
325
|
+
stdout.write(`${JSON.stringify(result.data ?? result)}\n`);
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Reducer notes go to stderr so stdout stays a machine-friendly id.
|
|
330
|
+
const onReducerNote = note => stderr.write(formatReducerNote(note));
|
|
331
|
+
const flambe = client ?? clientFromEnv(env, { onReducerNote });
|
|
332
|
+
flambe.onReducerNote ??= onReducerNote;
|
|
333
|
+
await flambe.flushQueue?.();
|
|
334
|
+
|
|
335
|
+
if (command === 'start') {
|
|
336
|
+
const activityId = await flambe.start(parseStart(args));
|
|
337
|
+
stdout.write(`${activityId}\n`);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (command === 'end') {
|
|
342
|
+
const { activityId, message, force } = parseEnd(args);
|
|
343
|
+
if (!activityId) throw new Error('Usage: flambe end <activity-id> [message] [--force]');
|
|
344
|
+
const eventId = await flambe.end({ activityId, message, force });
|
|
345
|
+
stdout.write(`${eventId}\n`);
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (command === 'suspend' || command === 'resume') {
|
|
350
|
+
const [activityId, ...messageParts] = args;
|
|
351
|
+
if (!activityId) throw new Error(`Usage: flambe ${command} <activity-id> [message]`);
|
|
352
|
+
const eventId = await flambe[command]({ activityId, message: messageParts.join(' ') || undefined });
|
|
353
|
+
stdout.write(`${eventId}\n`);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (command === 'status') {
|
|
358
|
+
const { activeOnly, suspendedOnly, json } = parseStatus(args);
|
|
359
|
+
const status = await flambe.status({ activeOnly, suspendedOnly });
|
|
360
|
+
|
|
361
|
+
if (json) {
|
|
362
|
+
stdout.write(`${JSON.stringify(status)}\n`);
|
|
363
|
+
} else {
|
|
364
|
+
for (const activity of status.activities) {
|
|
365
|
+
stdout.write(`${activity.id}\t${activity.path.join(' > ')}\t${activity.threadId}\t${activity.threadName ?? ''}\t${activity.categoryIds.join(',')}\t${activity.latestEvent.phase}\t${activity.latestEvent.timestamp}\n`);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (command === 'threads') {
|
|
373
|
+
const json = parseJson(args);
|
|
374
|
+
const threads = await flambe.threads();
|
|
375
|
+
if (json) stdout.write(`${JSON.stringify(threads)}\n`);
|
|
376
|
+
else {
|
|
377
|
+
for (const thread of threads) {
|
|
378
|
+
stdout.write(`${thread.id}\t${thread.rank}\t${thread.name}${thread.default ? '\tdefault' : ''}\n`);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (command === 'categories') {
|
|
385
|
+
const json = parseJson(args);
|
|
386
|
+
const categories = await flambe.categories();
|
|
387
|
+
if (json) stdout.write(`${JSON.stringify(categories)}\n`);
|
|
388
|
+
else {
|
|
389
|
+
for (const category of categories) {
|
|
390
|
+
stdout.write(`${category.id}\t${category.name}\t${category.color_background}\t${category.color_text}\n`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (command === 'ping') {
|
|
397
|
+
const trace = await flambe.getTrace();
|
|
398
|
+
stdout.write(`${trace.id}\t${trace.name}\n`);
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (command === 'observe') {
|
|
403
|
+
const observationId = await flambe.observe(parseObserve(args));
|
|
404
|
+
stdout.write(`${observationId}\n`);
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (command === 'message') {
|
|
409
|
+
const { text, activityId, json } = parseMessage(args);
|
|
410
|
+
const decision = await flambe.message({ activityId, text });
|
|
411
|
+
if (json) {
|
|
412
|
+
stdout.write(`${JSON.stringify(decision)}\n`);
|
|
413
|
+
} else {
|
|
414
|
+
stdout.write(`activity\t${decision.activityId}\n`);
|
|
415
|
+
stdout.write(`assessment\t${decision.assessment}\n`);
|
|
416
|
+
stdout.write(`direction\t${decision.direction ?? '-'}\n`);
|
|
417
|
+
stdout.write(`reply\t${decision.reply ?? '-'}\n`);
|
|
418
|
+
const applied = (decision.actions_applied ?? []).filter(action => action.type !== 'no_op');
|
|
419
|
+
if (applied.length > 0) {
|
|
420
|
+
stdout.write(`actions\t${applied.map(formatAction).join('; ')}\n`);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
throw new Error(`Unknown command: ${command}\n\n${usage}`);
|
|
427
|
+
}
|