@davvidbaker/flambe-cli 0.1.0 → 0.1.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/README.md +63 -0
- package/package.json +17 -3
- package/src/agentNames.mjs +35 -0
- package/src/cli.mjs +124 -10
- package/src/client.mjs +359 -41
- package/src/local/agents.mjs +18 -12
- package/src/local/commands.mjs +291 -0
- package/src/local/http.mjs +116 -20
- package/src/local/serve.mjs +2 -1
- package/src/local/store.mjs +262 -39
- package/src/questions.mjs +48 -0
- package/src/queue.mjs +15 -3
- package/src/runtime.mjs +23 -0
- package/test/client.test.mjs +0 -951
- package/test/local.test.mjs +0 -136
- package/test/queue.test.mjs +0 -135
package/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# @davvidbaker/flambe-cli
|
|
2
|
+
|
|
3
|
+
Zero-dependency Node 22 CLI that streams coding-agent work into a Flambe trace.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install -g @davvidbaker/flambe-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
That puts `flambe` on `PATH`. One-off:
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npx @davvidbaker/flambe-cli --help
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
From this checkout (no npm):
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
node cli/bin/flambe.mjs --help
|
|
21
|
+
# or
|
|
22
|
+
cd cli && npm link
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Cursor Cloud and other repos
|
|
26
|
+
|
|
27
|
+
1. Install the CLI in the environment (or at the start of an agent session):
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
npm install -g @davvidbaker/flambe-cli
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
2. Set Cloud secrets / env: `FLAMBE_URL`, `FLAMBE_API_TOKEN`, `FLAMBE_TRACE_ID`.
|
|
34
|
+
Optional: `FLAMBE_THREAD` (name, unique slug, or id) is sent on `start` when
|
|
35
|
+
`--thread` is omitted. Do not list threads and pick one by project name.
|
|
36
|
+
Copy the `flambe-cli` skill from this repo (`.codex/skills/flambe-cli`) so
|
|
37
|
+
agents know when to `start` / `end`.
|
|
38
|
+
|
|
39
|
+
3. Set `FLAMBE_AGENT_PLATFORM` to the product (`Cursor Cloud`) as a Cloud
|
|
40
|
+
secret; it is shared by every agent there. Agents do not need a name: the
|
|
41
|
+
reducer assigns one on first contact and the CLI remembers it
|
|
42
|
+
(`flambe whoami`). Set `FLAMBE_AGENT_NAME` only to override. Do not put
|
|
43
|
+
`FLAMBE_AGENT_ID` or `FLAMBE_AGENT_NAME` in `.env`.
|
|
44
|
+
|
|
45
|
+
CI publishes this package from `cli/` via npm trusted publishing when
|
|
46
|
+
`cli/package.json` on `main` changes. Local `npm publish` needs access to
|
|
47
|
+
`@davvidbaker`.
|
|
48
|
+
|
|
49
|
+
## Configure
|
|
50
|
+
|
|
51
|
+
Create a `.env` in the project working directory (or set the same names in the
|
|
52
|
+
shell; shell wins):
|
|
53
|
+
|
|
54
|
+
```dotenv
|
|
55
|
+
FLAMBE_URL=https://your-app.fly.dev
|
|
56
|
+
FLAMBE_API_TOKEN=flb_...
|
|
57
|
+
FLAMBE_TRACE_ID=1
|
|
58
|
+
# FLAMBE_THREAD=flambe
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Mint a token in Flambe Settings → API tokens. See the
|
|
62
|
+
[repository README](https://github.com/davvidbaker/flambe#connect-coding-agents)
|
|
63
|
+
for commands (`start`, `end`, `message`, `observe`, local `serve`).
|
package/package.json
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@davvidbaker/flambe-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "CLI for streaming coding-agent activity into Flambe",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"flambe": "./bin/flambe.mjs"
|
|
8
8
|
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"src",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
9
14
|
"scripts": {
|
|
10
|
-
"test": "node --test"
|
|
15
|
+
"test": "node --test",
|
|
16
|
+
"prepublishOnly": "npm test"
|
|
11
17
|
},
|
|
12
18
|
"engines": {
|
|
13
19
|
"node": ">=22"
|
|
@@ -15,6 +21,14 @@
|
|
|
15
21
|
"license": "MIT",
|
|
16
22
|
"repository": {
|
|
17
23
|
"type": "git",
|
|
18
|
-
"url": "git+https://github.com/davvidbaker/flambe.git"
|
|
24
|
+
"url": "git+https://github.com/davvidbaker/flambe.git",
|
|
25
|
+
"directory": "cli"
|
|
26
|
+
},
|
|
27
|
+
"homepage": "https://github.com/davvidbaker/flambe/tree/main/cli",
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/davvidbaker/flambe/issues"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
19
33
|
}
|
|
20
34
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { randomUUID } from 'node:crypto';
|
|
5
|
+
|
|
6
|
+
// Names the reducer assigned to agents on this machine, keyed by agent id (ADR-012).
|
|
7
|
+
// Read synchronously because identity is resolved while the client is constructed.
|
|
8
|
+
|
|
9
|
+
export function defaultAgentNamesPath() {
|
|
10
|
+
return join(homedir(), '.flambe', 'agent-names.json');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function readAgentNames(path = defaultAgentNamesPath()) {
|
|
14
|
+
try {
|
|
15
|
+
const parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
16
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
|
|
17
|
+
} catch {
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function rememberedAgentName(agentId, path = defaultAgentNamesPath()) {
|
|
23
|
+
if (!agentId) return undefined;
|
|
24
|
+
const name = readAgentNames(path)[agentId];
|
|
25
|
+
return typeof name === 'string' && name.trim() ? name : undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function rememberAgentName(agentId, name, path = defaultAgentNamesPath()) {
|
|
29
|
+
if (!agentId || !name) return;
|
|
30
|
+
const names = { ...readAgentNames(path), [agentId]: name };
|
|
31
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
32
|
+
const tmp = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
33
|
+
writeFileSync(tmp, `${JSON.stringify(names, null, 2)}\n`);
|
|
34
|
+
renameSync(tmp, path);
|
|
35
|
+
}
|
package/src/cli.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync } from 'node:fs';
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { join, resolve } from 'node:path';
|
|
4
4
|
|
|
5
5
|
import { clientFromEnv, clientFromHostEnv } from './client.mjs';
|
|
6
|
+
import { clearQuestion, readOpenQuestion, rememberQuestion } from './questions.mjs';
|
|
6
7
|
|
|
7
8
|
// `node:sqlite` still prints an ExperimentalWarning on import in some Node 22 releases.
|
|
8
9
|
// Only serve/export need it, so load those modules on demand and keep every other
|
|
@@ -15,14 +16,16 @@ function defaultDbPath() {
|
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
const usage = `Usage:
|
|
18
|
-
flambe start <activity name> [--description <text>] [--thread <id>] [--parent <id> | --root] [--category <id>...] [--started-at <ISO-8601>]
|
|
19
|
+
flambe start <activity name> [--description <text>] [--thread <id|name>] [--parent <id> | --root] [--category <id>...] [--started-at <ISO-8601>]
|
|
19
20
|
flambe end <activity-id> [message] [--force]
|
|
20
21
|
flambe suspend <activity-id> [message]
|
|
21
22
|
flambe resume <activity-id> [message]
|
|
22
|
-
flambe
|
|
23
|
+
flambe plan <activity name> [--start <ISO-8601>] [--end <ISO-8601>] [--description <text>] [--weight <n>]
|
|
24
|
+
flambe status [--active | --suspended] [--unstarted] [--json]
|
|
23
25
|
flambe threads [--json]
|
|
24
26
|
flambe categories [--json]
|
|
25
27
|
flambe ping
|
|
28
|
+
flambe whoami [--json]
|
|
26
29
|
flambe message <text> [--activity <id>] [--json]
|
|
27
30
|
flambe observe <kind> <value> [--unit <unit>] [--on <YYYY-MM-DD>] [--payload <json>] [--at <ISO-8601>]
|
|
28
31
|
flambe serve [--port <n>] [--db <path>] [--static <dir>]
|
|
@@ -41,9 +44,17 @@ Required values:
|
|
|
41
44
|
Optional:
|
|
42
45
|
FLAMBE_AGENT_ID Stable ID for this running agent; enables named flame lanes.
|
|
43
46
|
Derived from Codex, Cursor, or Claude Code session env when unset.
|
|
44
|
-
FLAMBE_AGENT_NAME
|
|
47
|
+
FLAMBE_AGENT_NAME Override the lane name. Unset, the reducer names the agent and the
|
|
48
|
+
CLI remembers it in ~/.flambe/agent-names.json. Do not put this in .env.
|
|
49
|
+
FLAMBE_AGENT_PLATFORM Product the agent runs on, shared by many agents ("Cursor Cloud",
|
|
50
|
+
"Codex"). Derived from the session type when unset.
|
|
51
|
+
FLAMBE_AGENT_NAMES_PATH Where remembered agent names live.
|
|
52
|
+
FLAMBE_THREAD Thread name, unique slug, or id for start when --thread is omitted
|
|
53
|
+
(overridden by --thread). 'flambe' matches 'flambé🔥'. The reducer
|
|
54
|
+
still keeps a child on its parent's thread.
|
|
45
55
|
FLAMBE_QUEUE_PATH Local offline queue path (default: ~/.flambe/event-queue.json)
|
|
46
56
|
FLAMBE_LOCAL_DB SQLite path for flambe serve / export (default: ~/.flambe/local.sqlite)
|
|
57
|
+
FLAMBE_RUNTIME_MODE local_self_contained restricts CLI requests to numeric loopback; cloud preserves hosted access
|
|
47
58
|
|
|
48
59
|
serve / export do not need FLAMBE_URL. import needs FLAMBE_URL and FLAMBE_API_TOKEN (the production instance).`;
|
|
49
60
|
|
|
@@ -117,20 +128,55 @@ function parseEnd(args) {
|
|
|
117
128
|
};
|
|
118
129
|
}
|
|
119
130
|
|
|
131
|
+
function parsePlan(args) {
|
|
132
|
+
const nameParts = [];
|
|
133
|
+
let description;
|
|
134
|
+
let weight;
|
|
135
|
+
let start;
|
|
136
|
+
let end;
|
|
137
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
138
|
+
const arg = args[index];
|
|
139
|
+
if (arg === '--description') {
|
|
140
|
+
description = args[index + 1];
|
|
141
|
+
if (description === undefined) throw new Error('--description requires a value');
|
|
142
|
+
index += 1;
|
|
143
|
+
} else if (arg === '--weight') {
|
|
144
|
+
weight = Number(args[index + 1]);
|
|
145
|
+
if (!Number.isInteger(weight)) throw new Error('--weight requires an integer');
|
|
146
|
+
index += 1;
|
|
147
|
+
} else if (arg === '--start' || arg === '--end') {
|
|
148
|
+
const raw = args[index + 1];
|
|
149
|
+
if (raw === undefined) throw new Error(`${arg} requires an ISO-8601 timestamp`);
|
|
150
|
+
const parsed = Date.parse(raw);
|
|
151
|
+
if (!Number.isFinite(parsed)) throw new Error(`${arg} must be a valid ISO-8601 timestamp`);
|
|
152
|
+
if (arg === '--start') start = parsed;
|
|
153
|
+
else end = parsed;
|
|
154
|
+
index += 1;
|
|
155
|
+
} else if (arg.startsWith('--')) {
|
|
156
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
157
|
+
} else nameParts.push(arg);
|
|
158
|
+
}
|
|
159
|
+
const name = nameParts.join(' ').trim();
|
|
160
|
+
if (!name) throw new Error('Usage: flambe plan <activity name> [--start <ISO-8601>] [--end <ISO-8601>]');
|
|
161
|
+
return { name, description, weight, scheduledStart: start, scheduledEnd: end };
|
|
162
|
+
}
|
|
163
|
+
|
|
120
164
|
function parseStatus(args) {
|
|
121
165
|
let activeOnly = false;
|
|
122
166
|
let suspendedOnly = false;
|
|
167
|
+
let includeUnstarted = false;
|
|
123
168
|
let json = false;
|
|
124
169
|
|
|
125
170
|
for (const arg of args) {
|
|
126
171
|
if (arg === '--active') activeOnly = true;
|
|
127
172
|
else if (arg === '--suspended') suspendedOnly = true;
|
|
173
|
+
else if (arg === '--unstarted') includeUnstarted = true;
|
|
128
174
|
else if (arg === '--json') json = true;
|
|
129
175
|
else throw new Error(`Unknown option: ${arg}`);
|
|
130
176
|
}
|
|
131
177
|
|
|
132
178
|
if (activeOnly && suspendedOnly) throw new Error('--active and --suspended cannot be used together');
|
|
133
|
-
return { activeOnly, suspendedOnly, json };
|
|
179
|
+
return { activeOnly, suspendedOnly, includeUnstarted, json };
|
|
134
180
|
}
|
|
135
181
|
|
|
136
182
|
function parseObserve(args) {
|
|
@@ -267,6 +313,14 @@ function parseImport(args) {
|
|
|
267
313
|
function formatAction(action) {
|
|
268
314
|
if (action.type === 'create_child') return `created child ${action.activity_id} under ${action.parent_activity_id}`;
|
|
269
315
|
if (action.type === 'update_activity') return `updated activity ${action.activity_id}`;
|
|
316
|
+
if (action.type === 'reparent') return `reparented ${action.activity_id} under ${action.parent_activity_id}`;
|
|
317
|
+
if (action.type === 'rename') return `renamed ${action.activity_id} to ${action.name}`;
|
|
318
|
+
if (action.type === 'ask') return `asked about ${action.activity_id}`;
|
|
319
|
+
if (action.type === 'resume_existing') return `resumed existing ${action.activity_id}`;
|
|
320
|
+
if (action.type === 'resume_ancestor') return `resumed ancestor ${action.activity_id}`;
|
|
321
|
+
if (action.type === 'no_op') return `left ${action.activity_id} as it was`;
|
|
322
|
+
if (action.type === 'keep') return 'kept the recorded structure';
|
|
323
|
+
if (action.type === 'skipped') return 'recorded the proposal; model review skipped';
|
|
270
324
|
return JSON.stringify(action);
|
|
271
325
|
}
|
|
272
326
|
|
|
@@ -275,10 +329,31 @@ function formatReducerNote(note) {
|
|
|
275
329
|
const list = note.closedDescendants.map(item => `${item.activityId} (${item.activityName})`).join(', ');
|
|
276
330
|
return `reducer closed open descendants of ${note.activityId}: ${list}\n`;
|
|
277
331
|
}
|
|
332
|
+
if (note.type === 'agent_named') {
|
|
333
|
+
return `reducer named this agent "${note.name}"; the CLI will use it from now on\n`;
|
|
334
|
+
}
|
|
335
|
+
if (note.type === 'command_result') {
|
|
336
|
+
const lines = [];
|
|
337
|
+
if (note.direction) lines.push(`direction\t${note.direction}\n`);
|
|
338
|
+
if (note.reply) lines.push(`reply\t${note.reply}\n`);
|
|
339
|
+
if (note.actionsApplied?.length) {
|
|
340
|
+
lines.push(`actions\t${note.actionsApplied.map(formatAction).join('; ')}\n`);
|
|
341
|
+
}
|
|
342
|
+
return lines.join('');
|
|
343
|
+
}
|
|
344
|
+
if (note.type === 'start_reduced') {
|
|
345
|
+
const parts = [];
|
|
346
|
+
if (note.parentSource === 'inferred') parts.push(`nested ${note.activityId} under ${note.parentId}`);
|
|
347
|
+
if (note.threadSource === 'parent' && note.requestedThreadId !== undefined && note.requestedThreadId !== note.threadId) {
|
|
348
|
+
parts.push(`kept it on the parent's thread ${note.threadId}, not ${note.requestedThreadId}`);
|
|
349
|
+
}
|
|
350
|
+
if (note.categoriesSource === 'parent') parts.push('inherited the parent\'s categories');
|
|
351
|
+
return parts.length > 0 ? `reducer ${parts.join('; ')}\n` : '';
|
|
352
|
+
}
|
|
278
353
|
return `reducer: ${JSON.stringify(note)}\n`;
|
|
279
354
|
}
|
|
280
355
|
|
|
281
|
-
export async function run(argv, { env = process.env, stdout = process.stdout, stderr = process.stderr, client } = {}) {
|
|
356
|
+
export async function run(argv, { env = process.env, stdout = process.stdout, stderr = process.stderr, client, questionsPath } = {}) {
|
|
282
357
|
const [command, ...args] = argv;
|
|
283
358
|
|
|
284
359
|
if (!command || command === 'help' || command === '--help' || command === '-h') {
|
|
@@ -300,6 +375,9 @@ export async function run(argv, { env = process.env, stdout = process.stdout, st
|
|
|
300
375
|
|
|
301
376
|
if (command === 'export') {
|
|
302
377
|
const { file, dbPath } = parseExport(args, env);
|
|
378
|
+
if (!existsSync(dbPath)) {
|
|
379
|
+
throw new Error(`Local database does not exist: ${dbPath}`);
|
|
380
|
+
}
|
|
303
381
|
const { LocalStore } = await loadStore();
|
|
304
382
|
const store = new LocalStore(dbPath);
|
|
305
383
|
try {
|
|
@@ -327,11 +405,32 @@ export async function run(argv, { env = process.env, stdout = process.stdout, st
|
|
|
327
405
|
}
|
|
328
406
|
|
|
329
407
|
// Reducer notes go to stderr so stdout stays a machine-friendly id.
|
|
330
|
-
const
|
|
408
|
+
const questionFile = questionsPath ?? (client ? undefined : env.FLAMBE_QUESTIONS_PATH);
|
|
409
|
+
const persistQuestions = Boolean(questionsPath) || !client;
|
|
410
|
+
const traceId = env.FLAMBE_TRACE_ID;
|
|
411
|
+
const pendingQuestion = !persistQuestions || command === 'message'
|
|
412
|
+
? undefined
|
|
413
|
+
: readOpenQuestion(traceId, questionFile);
|
|
414
|
+
if (pendingQuestion) {
|
|
415
|
+
stderr.write(`reducer still waiting on activity ${pendingQuestion.activityId}: ${pendingQuestion.reply}\n`);
|
|
416
|
+
}
|
|
417
|
+
const onReducerNote = note => {
|
|
418
|
+
const text = formatReducerNote(note);
|
|
419
|
+
if (text) stderr.write(text);
|
|
420
|
+
if (persistQuestions && note.type === 'command_result' && note.asked) {
|
|
421
|
+
rememberQuestion(traceId, { activityId: note.activityId, reply: note.reply }, questionFile);
|
|
422
|
+
}
|
|
423
|
+
};
|
|
331
424
|
const flambe = client ?? clientFromEnv(env, { onReducerNote });
|
|
332
425
|
flambe.onReducerNote ??= onReducerNote;
|
|
333
426
|
await flambe.flushQueue?.();
|
|
334
427
|
|
|
428
|
+
if (command === 'plan') {
|
|
429
|
+
const activityId = await flambe.plan(parsePlan(args));
|
|
430
|
+
stdout.write(`${activityId}\n`);
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
|
|
335
434
|
if (command === 'start') {
|
|
336
435
|
const activityId = await flambe.start(parseStart(args));
|
|
337
436
|
stdout.write(`${activityId}\n`);
|
|
@@ -355,14 +454,20 @@ export async function run(argv, { env = process.env, stdout = process.stdout, st
|
|
|
355
454
|
}
|
|
356
455
|
|
|
357
456
|
if (command === 'status') {
|
|
358
|
-
const { activeOnly, suspendedOnly, json } = parseStatus(args);
|
|
359
|
-
const status = await flambe.status({
|
|
457
|
+
const { activeOnly, suspendedOnly, includeUnstarted, json } = parseStatus(args);
|
|
458
|
+
const status = await flambe.status({
|
|
459
|
+
activeOnly,
|
|
460
|
+
suspendedOnly,
|
|
461
|
+
...(includeUnstarted ? { includeUnstarted } : {}),
|
|
462
|
+
});
|
|
360
463
|
|
|
361
464
|
if (json) {
|
|
362
465
|
stdout.write(`${JSON.stringify(status)}\n`);
|
|
363
466
|
} else {
|
|
364
467
|
for (const activity of status.activities) {
|
|
365
|
-
|
|
468
|
+
const phase = activity.latestEvent?.phase ?? activity.status;
|
|
469
|
+
const when = activity.latestEvent?.timestamp ?? '';
|
|
470
|
+
stdout.write(`${activity.id}\t${activity.path.join(' > ')}\t${activity.threadId}\t${activity.threadName ?? ''}\t${activity.categoryIds.join(',')}\t${phase}\t${when}\n`);
|
|
366
471
|
}
|
|
367
472
|
}
|
|
368
473
|
|
|
@@ -399,6 +504,14 @@ export async function run(argv, { env = process.env, stdout = process.stdout, st
|
|
|
399
504
|
return;
|
|
400
505
|
}
|
|
401
506
|
|
|
507
|
+
if (command === 'whoami') {
|
|
508
|
+
const json = parseJson(args);
|
|
509
|
+
const me = await flambe.whoami();
|
|
510
|
+
if (json) stdout.write(`${JSON.stringify(me)}\n`);
|
|
511
|
+
else stdout.write(`${me.agentId}\t${me.name}\t${me.platform ?? ''}\n`);
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
|
|
402
515
|
if (command === 'observe') {
|
|
403
516
|
const observationId = await flambe.observe(parseObserve(args));
|
|
404
517
|
stdout.write(`${observationId}\n`);
|
|
@@ -408,6 +521,7 @@ export async function run(argv, { env = process.env, stdout = process.stdout, st
|
|
|
408
521
|
if (command === 'message') {
|
|
409
522
|
const { text, activityId, json } = parseMessage(args);
|
|
410
523
|
const decision = await flambe.message({ activityId, text });
|
|
524
|
+
if (persistQuestions) clearQuestion(traceId, questionFile);
|
|
411
525
|
if (json) {
|
|
412
526
|
stdout.write(`${JSON.stringify(decision)}\n`);
|
|
413
527
|
} else {
|