@davvidbaker/flambe-cli 0.1.3 → 0.1.5
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 +67 -1
- package/bin/flambe.mjs +2 -1
- package/package.json +10 -3
- package/scripts/prepare-skills.mjs +12 -0
- package/skills/flambe-cli/SKILL.md +129 -0
- package/src/cli.mjs +59 -4
- package/src/client.mjs +15 -2
- package/src/codex-hook.mjs +237 -0
- package/src/codex-setup.mjs +104 -0
- package/src/local/commands.mjs +6 -4
- package/src/local/history.mjs +98 -0
- package/src/local/http.mjs +36 -10
- package/src/local/mcp.mjs +92 -0
- package/src/local/reducer-provider.mjs +106 -0
- package/src/local/reducer-review.mjs +179 -0
- package/src/local/reducer.mjs +74 -0
- package/src/local/serve.mjs +15 -5
- package/src/local/store.mjs +40 -7
- package/src/local/work-ai-review.mjs +94 -0
- package/src/mcp-tools.json +582 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @davvidbaker/flambe-cli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Node 22 CLI that streams coding-agent work into a Flambe trace.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -61,3 +61,69 @@ FLAMBE_TRACE_ID=1
|
|
|
61
61
|
Mint a token in Flambe Settings → API tokens. See the
|
|
62
62
|
[repository README](https://github.com/davvidbaker/flambe#connect-coding-agents)
|
|
63
63
|
for commands (`start`, `end`, `message`, `observe`, local `serve`).
|
|
64
|
+
|
|
65
|
+
## Single Burner AI review
|
|
66
|
+
|
|
67
|
+
When running `flambe serve`, optional AI review is configured in that server
|
|
68
|
+
process with `FLAMBE_LOCAL_REDUCER_MODE=off|agent|api|local`. It is off by default;
|
|
69
|
+
provider failures never reroute work to Cloud. See the
|
|
70
|
+
[Single Burner configuration guide](../single-burner/README.md#optional-ai-reducer)
|
|
71
|
+
for model, endpoint, credential, timeout, and data-boundary settings. Client
|
|
72
|
+
processes only need their normal local Flambe connection settings.
|
|
73
|
+
|
|
74
|
+
In `agent` mode, your current work AI reads the review returned by `flambe message`
|
|
75
|
+
and submits its decision with `flambe decide <review-id> --file <decision.json>`.
|
|
76
|
+
No separate model or inference API key is required.
|
|
77
|
+
|
|
78
|
+
## Local Codex / Single Burner hooks
|
|
79
|
+
|
|
80
|
+
With Single Burner running, put its printed connection values in the target
|
|
81
|
+
project's `.env`, including `FLAMBE_RUNTIME_MODE=local_self_contained`. From
|
|
82
|
+
that project root run:
|
|
83
|
+
|
|
84
|
+
```sh
|
|
85
|
+
flambe setup codex
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
This installs project-scoped Codex hooks and the bundled `flambe-cli` skill.
|
|
89
|
+
It preserves unrelated hooks, backs up an existing `hooks.json` once, and keeps
|
|
90
|
+
an existing custom skill rather than overwriting it. Restart local Codex, open
|
|
91
|
+
`/hooks`, and review/trust the new definitions. Hook loading remains subject to
|
|
92
|
+
project trust and workspace policy. No ChatGPT plugin, Platform organization,
|
|
93
|
+
tunnel, or model API key is needed for this local route.
|
|
94
|
+
|
|
95
|
+
Keep `flambe` available on PATH to the Codex process. Hooks use `flambe.cmd` on
|
|
96
|
+
Windows and `flambe` on macOS/Linux, with no machine-specific checkout paths.
|
|
97
|
+
If you installed the CLI while Codex was already open, restart the app so it can
|
|
98
|
+
see the updated PATH.
|
|
99
|
+
|
|
100
|
+
- Session start, resume, prompts, subagent start, and compaction restore context.
|
|
101
|
+
- Tool hooks use a small cache, refresh local status at most once per 30 seconds
|
|
102
|
+
during ordinary work, and request a fresh stack check every ten tool calls or
|
|
103
|
+
90 seconds while tools continue. Explicit lifecycle checkpoints reset the
|
|
104
|
+
call counter; a timer does not run while the agent is idle.
|
|
105
|
+
- Stop requests at most one reconciliation continuation after substantial tool
|
|
106
|
+
work without a verified checkpoint. It does not automatically close parents.
|
|
107
|
+
- Interrupt records a pending local reconciliation reminder; it does not mutate
|
|
108
|
+
activities or prevent interruption.
|
|
109
|
+
|
|
110
|
+
The agent still names/nests activities, decides when work changed, and submits
|
|
111
|
+
requested reducer decisions. Hooks only send status requests to the configured
|
|
112
|
+
numeric-loopback server. They do not call a model, inspect transcripts, or
|
|
113
|
+
queue activity mutations. Missing configuration, an unavailable server, or an
|
|
114
|
+
unidentifiable agent produces bounded feedback and permits work to continue.
|
|
115
|
+
The local cache contains counters/fingerprints, not prompts, command text,
|
|
116
|
+
credentials, or activity names. See [ADR-023](../docs/ADR-023-portable-local-codex-hooks.md).
|
|
117
|
+
|
|
118
|
+
```sh
|
|
119
|
+
flambe setup codex --check
|
|
120
|
+
flambe setup codex --remove
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`--check` inspects installed files and local connection configuration; it cannot
|
|
124
|
+
confirm a live server, Codex trust, or workspace policy.
|
|
125
|
+
`--remove` retains skills and unrelated hooks. For a live check, restart Codex,
|
|
126
|
+
confirm these hooks are trusted in `/hooks`, then ask it to track a short local
|
|
127
|
+
activity, request/submit an agent-mode review, and end or suspend the activity.
|
|
128
|
+
Verify that the chart and that agent's lane reflect the result. A fresh install
|
|
129
|
+
on the work laptop still needs this native Codex verification.
|
package/bin/flambe.mjs
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
import { run } from '../src/cli.mjs';
|
|
4
4
|
import { loadProjectEnv } from '../src/env.mjs';
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
// Hook input selects its installed project root; do not preload a nested .env.
|
|
7
|
+
if (process.argv[2] !== 'hook') loadProjectEnv();
|
|
7
8
|
|
|
8
9
|
try {
|
|
9
10
|
await run(process.argv.slice(2));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@davvidbaker/flambe-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "CLI for streaming coding-agent activity into Flambe",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,11 +9,14 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"bin",
|
|
11
11
|
"src",
|
|
12
|
-
"README.md"
|
|
12
|
+
"README.md",
|
|
13
|
+
"skills",
|
|
14
|
+
"scripts/prepare-skills.mjs"
|
|
13
15
|
],
|
|
14
16
|
"scripts": {
|
|
15
17
|
"test": "node --test",
|
|
16
|
-
"prepublishOnly": "npm test"
|
|
18
|
+
"prepublishOnly": "npm test",
|
|
19
|
+
"prepack": "node scripts/prepare-skills.mjs"
|
|
17
20
|
},
|
|
18
21
|
"engines": {
|
|
19
22
|
"node": ">=22"
|
|
@@ -30,5 +33,9 @@
|
|
|
30
33
|
},
|
|
31
34
|
"publishConfig": {
|
|
32
35
|
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@modelcontextprotocol/node": "2.1.1",
|
|
39
|
+
"@modelcontextprotocol/server": "2.3.1"
|
|
33
40
|
}
|
|
34
41
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {copyFileSync, existsSync, mkdirSync} from 'node:fs';
|
|
2
|
+
import {fileURLToPath} from 'node:url';
|
|
3
|
+
import {dirname} from 'node:path';
|
|
4
|
+
|
|
5
|
+
const source = fileURLToPath(new URL('../../.codex/skills/flambe-cli/SKILL.md', import.meta.url));
|
|
6
|
+
const target = fileURLToPath(new URL('../skills/flambe-cli/SKILL.md', import.meta.url));
|
|
7
|
+
if (existsSync(source)) {
|
|
8
|
+
mkdirSync(dirname(target), {recursive: true});
|
|
9
|
+
copyFileSync(source, target);
|
|
10
|
+
} else if (!existsSync(target)) {
|
|
11
|
+
throw new Error('The CLI package is missing its canonical flambe-cli skill.');
|
|
12
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: flambe-cli
|
|
3
|
+
description: Track coding-agent work in a Flambe trace with the flambe CLI. Use when logging substantial or multi-step work in a project configured with FLAMBE_URL, FLAMBE_API_TOKEN, and FLAMBE_TRACE_ID.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Flambe CLI
|
|
7
|
+
|
|
8
|
+
Log semantic work units as a live flame chart — not commands, file opens, or hidden reasoning. Skip trivial questions. If `FLAMBE_URL`, `FLAMBE_API_TOKEN`, or `FLAMBE_TRACE_ID` is set neither in `.env` nor in the shell environment (Cloud Agent secrets arrive as environment variables), skip silently.
|
|
9
|
+
|
|
10
|
+
Needs Node 22+ and `flambe` on `PATH`. If it is missing, `npm install -g @davvidbaker/flambe-cli`; in this repo without a global install, use `node cli/bin/flambe.mjs`. You do not need to pick a name: the reducer names each agent on its first request, the CLI remembers it (`~/.flambe/agent-names.json`) and prints `reducer named this agent "X"` on stderr once. `flambe whoami` shows your id, name, and platform. `FLAMBE_AGENT_PLATFORM` is the product you run on (`"Cursor Cloud"`, `"Codex"`; derived from the session when unset) and is shared by many agents; it is not your name. Set `FLAMBE_AGENT_NAME` only to override; never put `FLAMBE_AGENT_ID`, `FLAMBE_AGENT_NAME`, or `FLAMBE_AGENT_PLATFORM` in `.env`.
|
|
11
|
+
|
|
12
|
+
## Think in a stack
|
|
13
|
+
|
|
14
|
+
Always treat attention as a **call stack**, not a flat to-do list.
|
|
15
|
+
|
|
16
|
+
You will often produce some progress on a parent goal, then go down a rabbit hole about one specific thing (a bug, a prerequisite, a design question, a test failure). That digression is a **push**: start a nested activity under the still-running parent. When the rabbit hole is done, **pop**: end the nested activity with the outcome, and continue the parent where you left off.
|
|
17
|
+
|
|
18
|
+
- Leave parents running while you dig. Ending the parent when you dive deeper flattens the chart and loses the “come back up” story.
|
|
19
|
+
- Nested steps belong under the workstream they serve — not as siblings of the top objective.
|
|
20
|
+
- When attention moves to a true sibling of the current leaf, end the leaf first, then start the sibling under the same parent.
|
|
21
|
+
- Use `--root` only for a genuinely new top-level workstream, not for every digression.
|
|
22
|
+
- Keep the active stack truthful as work changes. Do not plan a detailed chart up front or reconstruct one afterward.
|
|
23
|
+
|
|
24
|
+
## Local Codex hooks
|
|
25
|
+
|
|
26
|
+
For a Single Burner project, run `flambe setup codex` from its root after setting
|
|
27
|
+
its local connection values in `.env`. Setup merges project hooks into
|
|
28
|
+
`.codex/hooks.json` and installs this skill if it is missing. Existing custom
|
|
29
|
+
skills and unrelated hooks are retained. Restart local Codex and review/trust
|
|
30
|
+
these hooks with `/hooks`; installed files alone do not prove they are running.
|
|
31
|
+
Use `flambe setup codex --check` to inspect the files, and `--remove` to remove
|
|
32
|
+
only Flambe's own hook handlers. Keep `flambe` on PATH; Windows hooks use
|
|
33
|
+
`flambe.cmd` so PowerShell script policy does not select `flambe.ps1`.
|
|
34
|
+
|
|
35
|
+
Hooks restore workflow context, request periodic stack checks, and can request
|
|
36
|
+
one reconciliation continuation before a substantive turn ends. They make no
|
|
37
|
+
inference calls or semantic activity changes. You still start/nest/end the real
|
|
38
|
+
work and complete requested work-AI reviews. A turn ending does not mean its
|
|
39
|
+
parent task ended. Missing servers or identity produce feedback without blocking
|
|
40
|
+
ordinary work. Hooks require `local_self_contained`; they do not route to Cloud.
|
|
41
|
+
|
|
42
|
+
## Commands
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
flambe start "Fix USERPROFILE resolution" # prints id; nests under the current active activity
|
|
46
|
+
flambe start "Add Windows support" --root # new top-level workstream
|
|
47
|
+
flambe end "$ID" "Resolved USERPROFILE on Windows"
|
|
48
|
+
flambe suspend "$ID" "Waiting for product decision" # only when explicitly tabling work
|
|
49
|
+
flambe resume "$ID" "Decision received"
|
|
50
|
+
flambe observe carbon 312.4 --unit gCO2eq/kWh --on 2026-09-04 --payload '{"source":"us-ba-mean"}'
|
|
51
|
+
flambe message "Auth fix needs the session module refactored too; widening scope" # ask the Reducer Agent
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`observe` writes a user overlay (carbon, mood, …), not an activity. Same `kind` + `--on` date upserts and merges payload keys. Omit `--on` to always insert.
|
|
55
|
+
|
|
56
|
+
**255-character hard limit on activity names and `--description`.** The column is `varchar(255)`. A longer value returns a 500 and can wedge the offline queue. Count before `start`. Prefer a short concrete name; put detail in the end/suspend/resume message (those are unbounded).
|
|
57
|
+
|
|
58
|
+
Reuse this conversation's activity id when you already have one. Run `flambe status --active --json` only to find a matching open activity from this session; ignore unrelated active work. Do not fetch threads or categories to decide placement; that is the reducer's job.
|
|
59
|
+
|
|
60
|
+
Every command is a proposal; the reducer owns the stack. Without `--parent`/`--root`, `start` nests under **your own** newest active activity (other agents' work is never your parent), puts a child on its parent's thread, and gives it the parent's categories; a new root gets its thread and categories chosen by the reducer a few seconds later. `FLAMBE_THREAD` (name, unique slug, or id) is sent as `--thread` when that flag is omitted; do not list threads and pick one by project name. Pass `--thread` / `--parent` / `--category` only when you are certain. Offline `offline-…` ids from `start` are valid for `end`. Do not retry queued operations. Offline results carry no immediate reducer output; nothing is dropped. Local `serve` always applies deterministic rules, including duplicate reuse, ancestor resumption, and descendant closure. Optional review is off by default; an explicitly configured server can ask the current work AI to review or call a separate model.
|
|
61
|
+
|
|
62
|
+
## Read every result; the reducer may rewrite
|
|
63
|
+
|
|
64
|
+
Any command (`start`, `end`, `suspend`, `resume`, `message`) may come back with `direction`, `reply`, and `actions_applied`, not only `message`. The reducer records what you proposed and then corrects the structure around it when needed: re-parent, rename, treat your `start` as a resume of an already-open same-name activity, resume a suspended ancestor before nesting under it, or close open descendants when you end a parent (`--force`). The CLI prints each rewrite on stderr (`reducer nested 43 under 42`, `actions`). The returned state is the truth: keep using the ids it gives you, do not undo a rewrite, and do not re-issue the proposal.
|
|
65
|
+
|
|
66
|
+
The reducer may answer a proposal with a question in `reply`, usually when a name does not fit its ancestors. Answer before your next step: rename the activity, or reply with `flambe message`. The CLI reminds you on the next command if a question is still open; it never refuses.
|
|
67
|
+
|
|
68
|
+
A returned `direction` is binding: act on it before doing more work. On `pause`, `stop`, or `escalate`, stop and surface the reply to David rather than deciding yourself. `direction -` or none means continue as you were. A model-judged `start` can take a few seconds; wait for it.
|
|
69
|
+
|
|
70
|
+
## Review with the AI doing the work
|
|
71
|
+
|
|
72
|
+
Review only at meaningful scope/approach decisions or when placement is uncertain.
|
|
73
|
+
Routine lifecycle tracking uses deterministic local rules. Do not review every
|
|
74
|
+
start/end, and do not request full history for routine bookkeeping. Work-AI
|
|
75
|
+
review context contains the current path and this agent's open work. Compact
|
|
76
|
+
lifecycle replies mark their state scope; omitted activities are not ended.
|
|
77
|
+
|
|
78
|
+
When a local result contains `review` and `model_review.status: awaiting_decision`,
|
|
79
|
+
you are the reviewer. This is the current work AI evaluating its own context,
|
|
80
|
+
not independent approval. Do not start another model, ask for provider keys, or
|
|
81
|
+
claim a separate reviewer approved your work.
|
|
82
|
+
|
|
83
|
+
Read `review.instructions`, `review.context`, and `review.decision_schema`.
|
|
84
|
+
Evaluate the user's intent, current path, and proposed scope. Choose concise
|
|
85
|
+
assessment, direction, reply, rationale, and at most one permitted action.
|
|
86
|
+
Write that decision as JSON and submit it with:
|
|
87
|
+
|
|
88
|
+
```sh
|
|
89
|
+
flambe decide <review.review_id> --file <decision.json>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`flambe message` returns the review JSON in this mode. Flagged starts normally
|
|
93
|
+
print only a small review-available notice on stderr; request review only when
|
|
94
|
+
scope or placement needs judgment. They include full review JSON only when the
|
|
95
|
+
server explicitly enables automatic start reviews. Stdout remains the recorded id. Keep the same local
|
|
96
|
+
connection, trace, and stable agent id for both steps. Ordinary
|
|
97
|
+
lifecycle tracking continues to use start/end/suspend/resume.
|
|
98
|
+
|
|
99
|
+
Complete a requested review before continuing substantive work. Follow the
|
|
100
|
+
returned direction after submission; escalate unresolved human judgment with no
|
|
101
|
+
actions and ask the user. Do not repeat the recorded start to request review.
|
|
102
|
+
On `REDUCER_STATE_CHANGED` or `REVIEW_EXPIRED`, request a fresh review with
|
|
103
|
+
`flambe message` for that activity, reassess it, and submit against the new id.
|
|
104
|
+
Do not resubmit the old decision blindly. Identical retries of a completed
|
|
105
|
+
submission return a receipt; `replayed: true` means no new action was applied.
|
|
106
|
+
If the server lacks work-AI review support, report that instead of changing instances.
|
|
107
|
+
|
|
108
|
+
## End of work
|
|
109
|
+
|
|
110
|
+
When the work you were doing is finished, check limbo and scheduled activities before you stop. Run `flambe status --unstarted --json`. If something there is worth doing next, propose that candidate with `flambe message` and begin it only if the reply says continue. Do not begin it unasked, and do not ask David in chat.
|
|
111
|
+
|
|
112
|
+
## Ask the reducer before you drift
|
|
113
|
+
|
|
114
|
+
`flambe message "<update>"` is your own channel: it sends your update plus the live flame (open and suspended work, the path to the current activity, and ended work from the last 21 days) and answers with `assessment`, `direction`, `reply`, and at most one stack change. Message when you are about to:
|
|
115
|
+
|
|
116
|
+
- widen scope beyond the activity you started
|
|
117
|
+
- change approach after the plan stalled
|
|
118
|
+
- end a root workstream
|
|
119
|
+
- continue while suspecting the work has drifted from what David asked
|
|
120
|
+
- answer a question the reducer asked
|
|
121
|
+
|
|
122
|
+
Do not message for routine progress, and never instead of `start`/`end`. Defaults to your newest active activity; pass `--activity <id>` to target another. Needs a reachable server with model review configured (not queued). Local self-contained `serve` has optional review, off by default, including an `agent` mode that returns review context for you to evaluate. On `REDUCER_NOT_CONFIGURED`, do not retry or switch to a remote server; surface unresolved judgment to the user and continue deterministic logging. A lifecycle result with `model_review.status: failed` still recorded the activity: keep its returned id/state, surface the review failure, and do not repeat the start or retarget another instance.
|
|
123
|
+
|
|
124
|
+
## What to record
|
|
125
|
+
|
|
126
|
+
- Start a new activity when the goal or scope meaningfully changes (a specific implementation, a discovered prerequisite, a concrete bug, focused tests, verification) — that is a stack push.
|
|
127
|
+
- Do not start an activity for committing or pushing. Those are wrap-up of the current leaf; mention them in the end-message if useful. Exception: the assigned work *is* getting code onto a remote.
|
|
128
|
+
- Name the concrete action and object (`Fix USERPROFILE config resolution`), not `Investigate` / `Implement` / `Work on tests`. Stay well under 255 characters; do not compensate with a long name or `--description`.
|
|
129
|
+
- End-message: the outcome, not `done`. That records a resolution. Omitting the message is a plain end. No secrets, tokens, or transcripts. Ending the leaf is the pop; then resume the parent mentally and in the log.
|
package/src/cli.mjs
CHANGED
|
@@ -27,6 +27,8 @@ const usage = `Usage:
|
|
|
27
27
|
flambe ping
|
|
28
28
|
flambe whoami [--json]
|
|
29
29
|
flambe message <text> [--activity <id>] [--json]
|
|
30
|
+
flambe decide <review-id> --file <decision.json>
|
|
31
|
+
flambe setup codex [--check | --remove]
|
|
30
32
|
flambe observe <kind> <value> [--unit <unit>] [--on <YYYY-MM-DD>] [--payload <json>] [--at <ISO-8601>]
|
|
31
33
|
flambe serve [--port <n>] [--db <path>] [--static <dir>]
|
|
32
34
|
flambe export [file] [--db <path>]
|
|
@@ -55,6 +57,7 @@ Optional:
|
|
|
55
57
|
FLAMBE_QUEUE_PATH Local offline queue path (default: ~/.flambe/event-queue.json)
|
|
56
58
|
FLAMBE_LOCAL_DB SQLite path for flambe serve / export (default: ~/.flambe/local.sqlite)
|
|
57
59
|
FLAMBE_RUNTIME_MODE local_self_contained restricts CLI requests to numeric loopback; cloud preserves hosted access
|
|
60
|
+
FLAMBE_LOCAL_REDUCER_MODE off (default), agent, api, or local; see the Single Burner README
|
|
58
61
|
|
|
59
62
|
serve / export do not need FLAMBE_URL. import needs FLAMBE_URL and FLAMBE_API_TOKEN (the production instance).`;
|
|
60
63
|
|
|
@@ -336,6 +339,11 @@ function formatReducerNote(note) {
|
|
|
336
339
|
const lines = [];
|
|
337
340
|
if (note.direction) lines.push(`direction\t${note.direction}\n`);
|
|
338
341
|
if (note.reply) lines.push(`reply\t${note.reply}\n`);
|
|
342
|
+
if (note.modelReview?.status === 'available') lines.push(`review available\tRequest flambe message --activity ${note.activityId} when scope judgment is needed.\n`);
|
|
343
|
+
if (note.review) lines.push(`review\t${JSON.stringify(note.review)}\n`);
|
|
344
|
+
if (note.modelReview?.status === 'failed') {
|
|
345
|
+
lines.push(`model review\t${note.modelReview.code}: ${note.modelReview.message} Recorded work was preserved.\n`);
|
|
346
|
+
}
|
|
339
347
|
if (note.actionsApplied?.length) {
|
|
340
348
|
lines.push(`actions\t${note.actionsApplied.map(formatAction).join('; ')}\n`);
|
|
341
349
|
}
|
|
@@ -353,9 +361,43 @@ function formatReducerNote(note) {
|
|
|
353
361
|
return `reducer: ${JSON.stringify(note)}\n`;
|
|
354
362
|
}
|
|
355
363
|
|
|
356
|
-
export async function run(argv, { env = process.env, stdout = process.stdout, stderr = process.stderr, client, questionsPath } = {}) {
|
|
364
|
+
export async function run(argv, { env = process.env, stdout = process.stdout, stderr = process.stderr, stdin = process.stdin, client, questionsPath } = {}) {
|
|
357
365
|
const [command, ...args] = argv;
|
|
358
366
|
|
|
367
|
+
if (command === 'hook' && args.length === 1 && args[0] === 'codex') {
|
|
368
|
+
const {readHookInput, runCodexHook} = await import('./codex-hook.mjs');
|
|
369
|
+
let output;
|
|
370
|
+
try { output = await runCodexHook(await readHookInput(stdin), {env}); }
|
|
371
|
+
catch { output = {systemMessage: 'Single Burner hook input could not be read; work may continue.'}; }
|
|
372
|
+
stdout.write(`${JSON.stringify(output)}\n`);
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (command === 'setup') {
|
|
377
|
+
if (args[0] !== 'codex' || args.length > 2
|
|
378
|
+
|| (args[1] !== undefined && !['--check', '--remove'].includes(args[1]))) {
|
|
379
|
+
throw new Error('Usage: flambe setup codex [--check | --remove]');
|
|
380
|
+
}
|
|
381
|
+
const {setupCodex} = await import('./codex-setup.mjs');
|
|
382
|
+
const action = args[1] === '--check' ? 'check' : args[1] === '--remove' ? 'remove' : 'install';
|
|
383
|
+
const result = setupCodex({action});
|
|
384
|
+
let localConnectionConfigured = false;
|
|
385
|
+
try {
|
|
386
|
+
localConnectionConfigured = env.FLAMBE_RUNTIME_MODE === 'local_self_contained' && Boolean(clientFromEnv(env));
|
|
387
|
+
} catch { /* Only report whether the config is usable; never echo credentials. */ }
|
|
388
|
+
if (action === 'check') stdout.write(`${JSON.stringify({...result, localConnectionConfigured})}\n`);
|
|
389
|
+
else if (action === 'remove') stdout.write(`Single Burner Codex hooks ${result.removed ? 'removed' : 'were not installed'}. Existing skills and unrelated hooks were retained.\n`);
|
|
390
|
+
else {
|
|
391
|
+
stdout.write(`Single Burner Codex hooks installed in ${result.root}\n`);
|
|
392
|
+
stdout.write(result.skillInstalled ? 'Installed the flambe-cli skill.\n' : 'Kept the existing flambe-cli skill.\n');
|
|
393
|
+
if (!result.skillMatches) stdout.write('The existing skill differs from the bundled version; review it for current work-AI review instructions.\n');
|
|
394
|
+
if (!localConnectionConfigured) stdout.write('Copy Single Burner connection values into this project .env, including FLAMBE_RUNTIME_MODE=local_self_contained, before using the hooks.\n');
|
|
395
|
+
stdout.write('Restart the local Codex session. Review and trust these project hooks with /hooks before relying on them. Workspace policy can restrict hooks.\n');
|
|
396
|
+
stdout.write('Run flambe setup codex --check to inspect installed files and connection configuration. This does not confirm Codex trust or live hook execution.\n');
|
|
397
|
+
}
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
|
|
359
401
|
if (!command || command === 'help' || command === '--help' || command === '-h') {
|
|
360
402
|
stdout.write(`${usage}\n`);
|
|
361
403
|
return;
|
|
@@ -364,7 +406,7 @@ export async function run(argv, { env = process.env, stdout = process.stdout, st
|
|
|
364
406
|
if (command === 'serve') {
|
|
365
407
|
const options = parseServe(args, env);
|
|
366
408
|
const { listenLocal, printServeBanner } = await loadServe();
|
|
367
|
-
const listening = await listenLocal(options);
|
|
409
|
+
const listening = await listenLocal({ ...options, reducer: { env } });
|
|
368
410
|
printServeBanner(listening, stdout);
|
|
369
411
|
await new Promise((resolveClose, reject) => {
|
|
370
412
|
listening.server.on('close', resolveClose);
|
|
@@ -518,11 +560,24 @@ export async function run(argv, { env = process.env, stdout = process.stdout, st
|
|
|
518
560
|
return;
|
|
519
561
|
}
|
|
520
562
|
|
|
563
|
+
if (command === 'decide') {
|
|
564
|
+
const { value: file, rest } = takeOption(args, '--file');
|
|
565
|
+
if (!file || rest.length !== 1 || rest[0].startsWith('--')) {
|
|
566
|
+
throw new Error('Usage: flambe decide <review-id> --file <decision.json>');
|
|
567
|
+
}
|
|
568
|
+
const content = readFileSync(resolve(file), 'utf8');
|
|
569
|
+
if (Buffer.byteLength(content) > 65_536) throw new Error('Decision file exceeds the size limit');
|
|
570
|
+
const result = await flambe.decide({ reviewId: rest[0], decision: JSON.parse(content) });
|
|
571
|
+
if (persistQuestions && !(result.actions_applied ?? []).some(a => a.type === 'ask')) clearQuestion(traceId, questionFile);
|
|
572
|
+
stdout.write(`${JSON.stringify(result)}\n`);
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
|
|
521
576
|
if (command === 'message') {
|
|
522
577
|
const { text, activityId, json } = parseMessage(args);
|
|
523
578
|
const decision = await flambe.message({ activityId, text });
|
|
524
|
-
if (persistQuestions) clearQuestion(traceId, questionFile);
|
|
525
|
-
if (json) {
|
|
579
|
+
if (persistQuestions && !decision.review) clearQuestion(traceId, questionFile);
|
|
580
|
+
if (json || decision.review) {
|
|
526
581
|
stdout.write(`${JSON.stringify(decision)}\n`);
|
|
527
582
|
} else {
|
|
528
583
|
stdout.write(`activity\t${decision.activityId}\n`);
|
package/src/client.mjs
CHANGED
|
@@ -155,6 +155,7 @@ export class FlambeClient {
|
|
|
155
155
|
|| REQUIRED_AGENT_COMMANDS.some(command => !commands.includes(command))) {
|
|
156
156
|
throw new Error('Flambe API returned an invalid agent-command capability document');
|
|
157
157
|
}
|
|
158
|
+
this.agentCommandDetails = payload.data;
|
|
158
159
|
return true;
|
|
159
160
|
})
|
|
160
161
|
.catch(error => {
|
|
@@ -636,13 +637,15 @@ export class FlambeClient {
|
|
|
636
637
|
const actions = result.actions_applied ?? [];
|
|
637
638
|
const direction = result.direction ?? null;
|
|
638
639
|
const reply = result.reply ?? null;
|
|
639
|
-
if (!direction && !reply && actions.length === 0) return;
|
|
640
|
+
if (!direction && !reply && actions.length === 0 && !result.model_review) return;
|
|
640
641
|
this.onReducerNote?.({
|
|
641
642
|
type: 'command_result',
|
|
642
643
|
activityId: result.activity_id,
|
|
643
644
|
direction,
|
|
644
645
|
reply,
|
|
645
646
|
actionsApplied: actions,
|
|
647
|
+
...(result.model_review ? { modelReview: result.model_review } : {}),
|
|
648
|
+
...(result.review ? { review: result.review } : {}),
|
|
646
649
|
asked: actions.some(action => action.type === 'ask'),
|
|
647
650
|
});
|
|
648
651
|
}
|
|
@@ -699,6 +702,16 @@ export class FlambeClient {
|
|
|
699
702
|
return this.request('/api/imports', { method: 'POST', body: bundle });
|
|
700
703
|
}
|
|
701
704
|
|
|
705
|
+
/** Work-AI decisions are never queued or sent to a substitute connection. */
|
|
706
|
+
async decide({ reviewId, decision }) {
|
|
707
|
+
if (!(await this.supportsAgentCommands()) || !this.agentCommandDetails?.reducer?.work_ai_reviews) {
|
|
708
|
+
throw new Error('This connection does not support work-AI reviews. Configure agent reducer mode on Single Burner.');
|
|
709
|
+
}
|
|
710
|
+
const result = await this.agentCommand('decide', { ...this.commandIdentity(), review_id: reviewId, decision });
|
|
711
|
+
this.reportCommandResult(result);
|
|
712
|
+
return result;
|
|
713
|
+
}
|
|
714
|
+
|
|
702
715
|
/**
|
|
703
716
|
* Send a free-text update to the Reducer Agent. It answers with assessment /
|
|
704
717
|
* direction / reply and, as the single writer for the stack (ADR-011), may apply at
|
|
@@ -726,7 +739,7 @@ export class FlambeClient {
|
|
|
726
739
|
message: text.trim(),
|
|
727
740
|
});
|
|
728
741
|
const decision = { activityId: result.activity_id ?? resolvedActivityId, ...result };
|
|
729
|
-
this.reportCommandResult(result);
|
|
742
|
+
if (!result.review) this.reportCommandResult(result);
|
|
730
743
|
return decision;
|
|
731
744
|
}
|
|
732
745
|
|