@compaction/cli 0.1.2 → 0.1.4
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 +1 -1
- package/dist/cli/commands/compact.js +6 -0
- package/dist/cli/commands/init.js +184 -157
- package/dist/cli/commands/run.js +5 -0
- package/dist/cli/index.js +1 -1
- package/dist/cli/onboarding/InitTui.d.ts +52 -0
- package/dist/cli/onboarding/InitTui.js +153 -0
- package/dist/cli/onboarding/model.d.ts +39 -0
- package/dist/cli/onboarding/model.js +77 -0
- package/dist/cli/onboarding/should-use-tui.d.ts +25 -0
- package/dist/cli/onboarding/should-use-tui.js +35 -0
- package/dist/cli/onboarding/wordmark.d.ts +28 -0
- package/dist/cli/onboarding/wordmark.js +72 -0
- package/dist/core/aggregate-format.d.ts +3 -1
- package/dist/core/aggregate-format.js +18 -0
- package/dist/core/report-generator.d.ts +13 -0
- package/dist/core/report-generator.js +16 -0
- package/dist/core/run-aggregator.d.ts +19 -0
- package/dist/core/run-aggregator.js +68 -3
- package/dist/core/session-aggregate.d.ts +43 -0
- package/dist/core/session-aggregate.js +89 -7
- package/dist/core/types.d.ts +22 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -66,7 +66,7 @@ npx @compaction/cli --help # no install at all
|
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
The installed binary is `compaction`. After a global install, sanity-check with
|
|
69
|
-
`compaction --version` (→ `0.1.
|
|
69
|
+
`compaction --version` (→ `0.1.4`) and `compaction --help`.
|
|
70
70
|
|
|
71
71
|
### curl one-liner
|
|
72
72
|
|
|
@@ -2,6 +2,7 @@ import path from "node:path";
|
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import { Option } from "commander";
|
|
4
4
|
import { writeJsonArtifact, writeTextArtifact } from "../../core/artifact-writer.js";
|
|
5
|
+
import { withTraceFingerprint } from "../../core/report-generator.js";
|
|
5
6
|
import { describeTokenAccounting } from "../../core/token-accounting.js";
|
|
6
7
|
import { parseTraceFile } from "../../core/trace-parser.js";
|
|
7
8
|
import { buildRunLabelsFile, hasAnyLabel } from "../../core/run-labels.js";
|
|
@@ -123,6 +124,11 @@ export function registerCompactCommand(program) {
|
|
|
123
124
|
// hand-assembled bundle — the eval consumes the identical PolicyMiddlewareResult.
|
|
124
125
|
const policyResult = applyCompactionPolicy({ trace, compactSkillInjections: approveSkillInjectionPolicy });
|
|
125
126
|
const artifacts = await writeCompactionArtifacts(trace, options.out, runId, policyResult, undefined, { reviewerType, reviewSummaryPresent, approveSkillInjectionPolicy });
|
|
127
|
+
// Attribution/integrity (V0.2 savings-evidence): re-persist report.json with the
|
|
128
|
+
// content-addressed trace fingerprint so `aggregate`/`summary` can attribute each saving to a
|
|
129
|
+
// verifiable run and de-duplicate the same captured run if it is rolled up twice. Additive,
|
|
130
|
+
// backward-compatible; does not change any existing report field/label.
|
|
131
|
+
await writeJsonArtifact(options.out, "report.json", withTraceFingerprint(artifacts.report, trace));
|
|
126
132
|
console.log(chalk.cyan("compaction compact"));
|
|
127
133
|
console.log(artifacts.consoleReport);
|
|
128
134
|
// Operator-visible only when the approval gesture was given; default output is unchanged.
|
|
@@ -1,216 +1,243 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import path from "node:path";
|
|
2
5
|
import chalk from "chalk";
|
|
3
6
|
import { defaultProjectsDir, discoverClaudeCodeSessions } from "../../core/adapters/claude-code-discovery.js";
|
|
7
|
+
import { BRAND_HEX, VALUE_PROMISE, STATUS_PILLS, PRIMARY, SECONDARY, FREE_FLOW, HONESTY_LINES, FOOTER_LINES, ALL_WORKFLOW_KEYS } from "../onboarding/model.js";
|
|
8
|
+
import { decideInteractiveTuiFromProcess } from "../onboarding/should-use-tui.js";
|
|
4
9
|
/**
|
|
5
10
|
* `compaction init` — first-run, value-led terminal onboarding.
|
|
6
11
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
12
|
+
* In a real interactive terminal this launches a graphical Ink TUI (a workflow
|
|
13
|
+
* chooser). Everywhere else — pipes, CI, NO_COLOR, dumb terminals, the test
|
|
14
|
+
* runner, `--static`, or `--path <focus>` — it prints the byte-stable static
|
|
15
|
+
* screen. Both surfaces share ONE model (`../onboarding/model`) so they always
|
|
16
|
+
* feature the same workflows, the same free follow-ups, and the same honest
|
|
17
|
+
* claim labels.
|
|
10
18
|
*
|
|
11
|
-
* Invariants (non-negotiable, hold on
|
|
19
|
+
* Invariants (non-negotiable, hold on EVERY path — static and TUI):
|
|
12
20
|
* - writes nothing
|
|
13
21
|
* - makes no network call
|
|
14
22
|
* - reads no provider credential
|
|
15
23
|
* - needs no repo access
|
|
16
|
-
*
|
|
17
|
-
* `capture provider-usage` (or enters operator-run measurement records).
|
|
24
|
+
* The TUI is a chooser, not an agent: no text input, no model call, no upload.
|
|
18
25
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
26
|
+
* Free-CLI scope: onboarding features ONLY the free, local-first commands
|
|
27
|
+
* (capture / import / analyze / spend / summary / feedback). The optimization &
|
|
28
|
+
* verification engine commands (recommend / compact / inspect / approve /
|
|
29
|
+
* apply-context) are the opt-in Compaction API — NOT in the free CLI — and so
|
|
30
|
+
* are deliberately NOT featured here.
|
|
24
31
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
32
|
+
* Honesty rails: token counts are provider-reported when usage metadata is
|
|
33
|
+
* present, otherwise a labeled local estimate (chars/4); cost is a price-table
|
|
34
|
+
* estimate; nothing is called a saving until it is measured. No billing-confirmed,
|
|
35
|
+
* no semantic-preservation, no hosted-API-live, no auto-apply, no demo-first.
|
|
28
36
|
*/
|
|
29
|
-
//
|
|
30
|
-
// terminal
|
|
31
|
-
const
|
|
37
|
+
// Brand accent. chalk.hex degrades gracefully: on a non-color / non-truecolor
|
|
38
|
+
// terminal chalk down-samples or no-ops, so the text is never lost.
|
|
39
|
+
const ACCENT = chalk.hex(BRAND_HEX);
|
|
40
|
+
// Spaced wordmark reads as a premium brand mark at any terminal width.
|
|
41
|
+
const WORDMARK = ACCENT.bold("C O M P A C T I O N");
|
|
32
42
|
const RULE = chalk.dim("─".repeat(58));
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
["compact", "the before/after token delta, and which family produced it"],
|
|
41
|
-
["inspect", "safety evidence — source pointers, hashes, recoverable originals"],
|
|
42
|
-
["approve", "emit a compacted context for your next call. No auto-apply."]
|
|
43
|
-
];
|
|
44
|
-
// Real-workflow paths only. compaction operates on the user's OWN agent
|
|
45
|
-
// workflow; there is no demo / synthetic first-value path here. Demo fixtures
|
|
46
|
-
// exist solely for tests, package smoke, and explicitly-labeled docs.
|
|
47
|
-
const PRIMARY = {
|
|
48
|
-
key: "claude-code",
|
|
49
|
-
title: "Claude Code",
|
|
50
|
-
command: "compaction capture claude-code --discover"
|
|
51
|
-
};
|
|
52
|
-
const SECONDARY = [
|
|
53
|
-
{
|
|
54
|
-
key: "codex",
|
|
55
|
-
title: "Codex",
|
|
56
|
-
command: "codex exec --json '<task>' > run.jsonl && compaction import run.jsonl --source codex-exec-jsonl --out ./my-trace"
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
key: "openai-agents",
|
|
60
|
-
title: "OpenAI Agents",
|
|
61
|
-
command: "compaction capture openai-agents --out ./my-trace -- <your-command>"
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
key: "import",
|
|
65
|
-
title: "Local trace",
|
|
66
|
-
command: "compaction import --list-sources # then: import <file> --source <source> --out ./my-trace"
|
|
43
|
+
async function readPackageVersion() {
|
|
44
|
+
try {
|
|
45
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
46
|
+
// dist/cli/commands -> dist -> package root
|
|
47
|
+
const pkgPath = path.resolve(here, "..", "..", "..", "package.json");
|
|
48
|
+
const pkg = JSON.parse(await readFile(pkgPath, "utf8"));
|
|
49
|
+
return typeof pkg.version === "string" ? pkg.version : "unknown";
|
|
67
50
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
51
|
+
catch {
|
|
52
|
+
return "unknown";
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function detectClaudeCode(projectsDir) {
|
|
56
|
+
let claudeDetected = false;
|
|
57
|
+
let sessionCount = 0;
|
|
58
|
+
if (existsSync(projectsDir)) {
|
|
59
|
+
try {
|
|
60
|
+
const discovery = await discoverClaudeCodeSessions({ projectsDir });
|
|
61
|
+
sessionCount = discovery.sessions.length;
|
|
62
|
+
claudeDetected = sessionCount > 0;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
claudeDetected = false; // detection is best-effort; onboarding never fails on it
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return { claudeDetected, sessionCount, projectsDir };
|
|
69
|
+
}
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
// Static screen (the universal fallback). Output is intentionally byte-stable:
|
|
72
|
+
// scripts, snapshots, and smoke tests depend on it.
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
76
74
|
function header(text) {
|
|
77
75
|
return chalk.bold(text);
|
|
78
76
|
}
|
|
79
|
-
function loopBlock() {
|
|
80
|
-
const width = Math.max(...LOOP.map(([verb]) => verb.length));
|
|
81
|
-
return LOOP.map(([verb, desc]) => ` ${chalk.cyan(verb.padEnd(width))} ${chalk.dim(desc)}`);
|
|
82
|
-
}
|
|
83
77
|
function primaryBlock(sessionCount) {
|
|
84
|
-
const tag = chalk.green(`
|
|
78
|
+
const tag = chalk.green(` ✓ ${sessionCount} session(s) found`);
|
|
85
79
|
return [
|
|
86
|
-
`${chalk.bold("▸ " + PRIMARY.title)}${chalk.dim(" · recommended")}${tag}`,
|
|
87
|
-
` ${
|
|
80
|
+
`${chalk.bold("▸ " + PRIMARY.title)}${chalk.dim(" · recommended first run")}${tag}`,
|
|
81
|
+
` ${ACCENT(PRIMARY.command)}`
|
|
88
82
|
];
|
|
89
83
|
}
|
|
90
|
-
function secondaryBlock() {
|
|
91
|
-
const lines = [chalk.dim("Other workflows")];
|
|
92
|
-
for (const p of SECONDARY) {
|
|
93
|
-
lines.push(` ${chalk.dim(p.title)}`);
|
|
94
|
-
lines.push(` ${chalk.cyan(p.command)}`);
|
|
95
|
-
}
|
|
96
|
-
return lines;
|
|
97
|
-
}
|
|
98
84
|
function emptyStateBlock(projectsDir) {
|
|
99
|
-
// Calm, premium no-sessions state: explain, show how to create one, and give
|
|
100
|
-
// the exact rerun command. The secondary real paths follow below.
|
|
101
85
|
return [
|
|
102
|
-
`${chalk.bold("▸ " + PRIMARY.title)}${chalk.dim(" · recommended")}`,
|
|
86
|
+
`${chalk.bold("▸ " + PRIMARY.title)}${chalk.dim(" · recommended first run")}`,
|
|
103
87
|
chalk.dim(` No sessions found under ${projectsDir}.`),
|
|
104
88
|
chalk.dim(" Start or run a Claude Code session in any project, then re-run:"),
|
|
105
|
-
` ${
|
|
89
|
+
` ${ACCENT("compaction capture claude-code --discover")}`
|
|
106
90
|
];
|
|
107
91
|
}
|
|
92
|
+
function supportedBlock() {
|
|
93
|
+
const summary = "Claude Code · OpenAI Agents SDK · Codex / local import · local trace import";
|
|
94
|
+
const lines = [header("Supported workflows"), ` ${chalk.dim(summary)}`, ""];
|
|
95
|
+
for (const p of SECONDARY) {
|
|
96
|
+
lines.push(` ${chalk.bold(p.title)}`);
|
|
97
|
+
lines.push(` ${ACCENT(p.command)}`);
|
|
98
|
+
}
|
|
99
|
+
return lines;
|
|
100
|
+
}
|
|
108
101
|
function afterCaptureBlock() {
|
|
102
|
+
const width = Math.max(...FREE_FLOW.map((f) => f.command.length));
|
|
109
103
|
return [
|
|
110
104
|
header("Then, on the trace you captured"),
|
|
111
105
|
"",
|
|
112
|
-
...
|
|
106
|
+
...FREE_FLOW.map((f) => ` ${ACCENT(f.command.padEnd(width))} ${chalk.dim(f.desc)}`),
|
|
107
|
+
"",
|
|
108
|
+
chalk.dim(` ${HONESTY_LINES[0]}`),
|
|
109
|
+
chalk.dim(` ${HONESTY_LINES[1]}`),
|
|
110
|
+
chalk.dim(` ${HONESTY_LINES[2]}`),
|
|
113
111
|
"",
|
|
114
|
-
chalk.dim(
|
|
115
|
-
chalk.dim(
|
|
116
|
-
chalk.dim(" (provider-reported vs estimated — never an estimate as reported),"),
|
|
117
|
-
chalk.dim(" RISK LEVEL, safety + source_recoverability, the APPROVAL REQUIREMENT"),
|
|
118
|
-
chalk.dim(" (--approve-in-workflow-use; no auto-apply), and the next action.")
|
|
112
|
+
chalk.dim(` ${HONESTY_LINES[3]}`),
|
|
113
|
+
chalk.dim(` ${HONESTY_LINES[4]}`)
|
|
119
114
|
];
|
|
120
115
|
}
|
|
121
116
|
function footerBlock() {
|
|
122
|
-
return
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
117
|
+
return FOOTER_LINES.map((l) => chalk.dim(l));
|
|
118
|
+
}
|
|
119
|
+
function buildStaticScreen(version, focus, det) {
|
|
120
|
+
const lines = [];
|
|
121
|
+
// 1. Wordmark + status + value promise.
|
|
122
|
+
lines.push("");
|
|
123
|
+
lines.push(` ${WORDMARK}`);
|
|
124
|
+
lines.push("");
|
|
125
|
+
lines.push(` ${chalk.dim(`v${version} · ${STATUS_PILLS.join(" · ")}`)}`);
|
|
126
|
+
lines.push("");
|
|
127
|
+
lines.push(` ${VALUE_PROMISE}`);
|
|
128
|
+
lines.push("");
|
|
129
|
+
lines.push(RULE);
|
|
130
|
+
lines.push("");
|
|
131
|
+
// 2. Start here — primary path (Claude Code) or a focused secondary path.
|
|
132
|
+
if (!focus || focus === "claude-code") {
|
|
133
|
+
lines.push(header("Start here"));
|
|
134
|
+
lines.push("");
|
|
135
|
+
if (det.claudeDetected) {
|
|
136
|
+
lines.push(...primaryBlock(det.sessionCount));
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
lines.push(...emptyStateBlock(det.projectsDir));
|
|
140
|
+
}
|
|
141
|
+
lines.push("");
|
|
142
|
+
if (!focus) {
|
|
143
|
+
lines.push(...supportedBlock());
|
|
144
|
+
lines.push("");
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
const p = SECONDARY.find((s) => s.key === focus);
|
|
149
|
+
lines.push(header("Start here"));
|
|
150
|
+
lines.push("");
|
|
151
|
+
lines.push(` ${chalk.bold(p.title)}`);
|
|
152
|
+
lines.push(` ${ACCENT(p.command)}`);
|
|
153
|
+
lines.push("");
|
|
154
|
+
}
|
|
155
|
+
lines.push(RULE);
|
|
156
|
+
lines.push("");
|
|
157
|
+
// 3. The free follow-up sequence + honest labeling + opt-in-API positioning.
|
|
158
|
+
lines.push(...afterCaptureBlock());
|
|
159
|
+
lines.push("");
|
|
160
|
+
lines.push(RULE);
|
|
161
|
+
lines.push("");
|
|
162
|
+
// 4. Local-first footer.
|
|
163
|
+
lines.push(...footerBlock());
|
|
164
|
+
lines.push("");
|
|
165
|
+
return lines.join("\n");
|
|
166
|
+
}
|
|
167
|
+
// After the TUI exits with a selection, print a clean, copy-ready next-command
|
|
168
|
+
// block to the scrollback so the user can paste and run it.
|
|
169
|
+
function printSelection(card) {
|
|
170
|
+
const out = [
|
|
171
|
+
"",
|
|
172
|
+
`${chalk.bold("▸ " + card.title)}`,
|
|
173
|
+
chalk.dim(` ${card.blurb}`),
|
|
174
|
+
"",
|
|
175
|
+
chalk.dim(" Run this next:"),
|
|
176
|
+
` ${ACCENT(card.command)}`,
|
|
177
|
+
"",
|
|
178
|
+
chalk.dim(` Then: ${FREE_FLOW.map((f) => f.command.replace("compaction ", "")).join(" · ")}`),
|
|
179
|
+
chalk.dim(" Local-first: no network, no credentials, no upload. Nothing is called a"),
|
|
180
|
+
chalk.dim(" saving until it is measured."),
|
|
181
|
+
""
|
|
126
182
|
];
|
|
183
|
+
console.log(out.join("\n"));
|
|
127
184
|
}
|
|
128
185
|
export function registerInitCommand(program) {
|
|
129
186
|
program
|
|
130
187
|
.command("init")
|
|
131
|
-
.description("First-run onboarding:
|
|
132
|
-
"
|
|
133
|
-
|
|
188
|
+
.description("First-run onboarding: an interactive workflow chooser in a real terminal (TUI), or the " +
|
|
189
|
+
"value promise + exact next commands as a static screen elsewhere. Guidance + one read-only " +
|
|
190
|
+
"local detection only — writes nothing, no network, no credentials.")
|
|
191
|
+
.option("--path <path>", "Focus one input path: claude-code | openai-agents | codex | import")
|
|
134
192
|
.option("--projects-dir <dir>", "Claude Code projects directory to detect against (default: the standard local location)")
|
|
193
|
+
.option("--interactive", "prefer the interactive TUI; if no TTY is available, show the static screen and why")
|
|
194
|
+
.option("--static", "always show the plain static screen (no TUI)")
|
|
135
195
|
.action(async (options) => {
|
|
136
196
|
// Validate --path early so the error stays a clean one-liner.
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
console.error(`unknown --path '${options.path}'. Valid: ${allKeys.join(" | ")}`);
|
|
197
|
+
if (options.path && !ALL_WORKFLOW_KEYS.includes(options.path)) {
|
|
198
|
+
console.error(`unknown --path '${options.path}'. Valid: ${ALL_WORKFLOW_KEYS.join(" | ")}`);
|
|
140
199
|
process.exitCode = 1;
|
|
141
200
|
return;
|
|
142
201
|
}
|
|
202
|
+
// Version read at runtime so it always matches the installed package.
|
|
203
|
+
const version = await readPackageVersion();
|
|
143
204
|
// Read-only local detection (no writes, no network).
|
|
144
205
|
const projectsDir = options.projectsDir ?? defaultProjectsDir();
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
catch {
|
|
154
|
-
claudeDetected = false; // detection is best-effort; onboarding never fails on it
|
|
155
|
-
}
|
|
156
|
-
}
|
|
206
|
+
const det = await detectClaudeCode(projectsDir);
|
|
207
|
+
// Decide static vs interactive. A focused path (--path) or --static always
|
|
208
|
+
// serves static; --interactive forces the TUI iff a TTY is present;
|
|
209
|
+
// otherwise auto-detect.
|
|
210
|
+
// Note: --interactive cannot conjure a TTY (raw-mode keyboard nav needs
|
|
211
|
+
// one), so it does not override the TTY check; it only documents intent.
|
|
212
|
+
// When asked for but unavailable, we fall back to static with a reason.
|
|
157
213
|
const focus = options.path;
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
lines.push("");
|
|
174
|
-
lines.push(RULE);
|
|
175
|
-
lines.push("");
|
|
176
|
-
// 3. Start here — primary path (Claude Code) or a focused secondary path.
|
|
177
|
-
if (!focus || focus === "claude-code") {
|
|
178
|
-
lines.push(header("Start here"));
|
|
179
|
-
lines.push("");
|
|
180
|
-
if (claudeDetected) {
|
|
181
|
-
lines.push(...primaryBlock(sessionCount));
|
|
214
|
+
const ttyDecision = decideInteractiveTuiFromProcess();
|
|
215
|
+
const wantTui = !focus && !options.static && ttyDecision.useTui;
|
|
216
|
+
if (wantTui) {
|
|
217
|
+
// Dynamic import keeps Ink/React off every non-TTY code path.
|
|
218
|
+
const { runInitTui } = await import("../onboarding/InitTui.js");
|
|
219
|
+
const result = await runInitTui({
|
|
220
|
+
version,
|
|
221
|
+
detection: {
|
|
222
|
+
detected: det.claudeDetected,
|
|
223
|
+
sessionCount: det.sessionCount,
|
|
224
|
+
projectsDir: det.projectsDir
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
if (result.action === "select") {
|
|
228
|
+
printSelection(result.card);
|
|
182
229
|
}
|
|
183
230
|
else {
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
lines.push("");
|
|
187
|
-
// 4. Secondary real-workflow paths (compact, secondary styling).
|
|
188
|
-
// Only on the default (unfocused) view.
|
|
189
|
-
if (!focus) {
|
|
190
|
-
lines.push(...secondaryBlock());
|
|
191
|
-
lines.push("");
|
|
231
|
+
console.log(chalk.dim("\nNo workflow selected. Run `compaction init` anytime.\n"));
|
|
192
232
|
}
|
|
233
|
+
return;
|
|
193
234
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
lines.push("");
|
|
199
|
-
lines.push(` ${chalk.bold(p.title)}`);
|
|
200
|
-
lines.push(` ${chalk.cyan(p.command)}`);
|
|
201
|
-
lines.push("");
|
|
235
|
+
// If the user explicitly asked for --interactive but we are not in a TTY,
|
|
236
|
+
// tell them why we fell back, then serve the static screen.
|
|
237
|
+
if (options.interactive && !ttyDecision.useTui) {
|
|
238
|
+
console.error(chalk.dim(`(interactive TUI unavailable here: ${ttyDecision.reason}; showing static screen)`));
|
|
202
239
|
}
|
|
203
|
-
|
|
204
|
-
lines.push("");
|
|
205
|
-
// 5. The follow-up sequence + what to expect.
|
|
206
|
-
lines.push(...afterCaptureBlock());
|
|
207
|
-
lines.push("");
|
|
208
|
-
lines.push(RULE);
|
|
209
|
-
lines.push("");
|
|
210
|
-
// 6. Local-first footer.
|
|
211
|
-
lines.push(...footerBlock());
|
|
212
|
-
lines.push("");
|
|
213
|
-
console.log(lines.join("\n"));
|
|
240
|
+
console.log(buildStaticScreen(version, focus, det));
|
|
214
241
|
});
|
|
215
242
|
}
|
|
216
243
|
//# sourceMappingURL=init.js.map
|
package/dist/cli/commands/run.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { parseRunCommand, executeLocalCommand, persistLocalCommandRun } from "../../core/command-runner.js";
|
|
3
3
|
import { writeJsonArtifact } from "../../core/artifact-writer.js";
|
|
4
|
+
import { withTraceFingerprint } from "../../core/report-generator.js";
|
|
4
5
|
import { localCommandRunToAgentTrace } from "../../core/run-trace-converter.js";
|
|
5
6
|
import { runEngineCommand } from "../engine-degrade.js";
|
|
6
7
|
export function registerRunCommand(program) {
|
|
@@ -23,6 +24,10 @@ export function registerRunCommand(program) {
|
|
|
23
24
|
const trace = localCommandRunToAgentTrace(run);
|
|
24
25
|
const tracePath = await writeJsonArtifact(persistedRun.outputDirectory, "trace.json", trace);
|
|
25
26
|
const artifacts = await writeCompactionArtifacts(trace, persistedRun.outputDirectory, run.runId);
|
|
27
|
+
// Attribution/integrity (V0.2 savings-evidence): re-persist report.json with the content-addressed
|
|
28
|
+
// trace fingerprint so `aggregate`/`summary` attribute each saving to a verifiable run and
|
|
29
|
+
// de-duplicate a re-counted run. Additive; no existing report field/label changes.
|
|
30
|
+
await writeJsonArtifact(persistedRun.outputDirectory, "report.json", withTraceFingerprint(artifacts.report, trace));
|
|
26
31
|
console.log(`Exit code: ${run.exitCode ?? "none"}`);
|
|
27
32
|
console.log(`Signal: ${run.signal ?? "none"}`);
|
|
28
33
|
console.log(`Duration: ${run.durationMs} ms`);
|
package/dist/cli/index.js
CHANGED
|
@@ -27,7 +27,7 @@ const program = new Command();
|
|
|
27
27
|
program
|
|
28
28
|
.name("compaction")
|
|
29
29
|
.description("CLI-first trace compaction tools for AI agent workflows.")
|
|
30
|
-
.version("0.1.
|
|
30
|
+
.version("0.1.4");
|
|
31
31
|
registerAnalyzeCommand(program);
|
|
32
32
|
registerAdapterCommand(program);
|
|
33
33
|
registerAggregateCommand(program);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { type WorkflowCard } from "./model.js";
|
|
3
|
+
/**
|
|
4
|
+
* Interactive first-run TUI for `compaction init` (Ink/React) — a minimal,
|
|
5
|
+
* premium, left-aligned, dark-terminal-first workflow chooser.
|
|
6
|
+
*
|
|
7
|
+
* Progressive enhancement gated behind a real TTY (see `decideInteractiveTui`):
|
|
8
|
+
* it NEVER runs in pipes/CI/NO_COLOR/dumb terminals or the test runner — those
|
|
9
|
+
* get the static screen — so it may freely rely on color and Unicode.
|
|
10
|
+
*
|
|
11
|
+
* The big COMPACTION wordmark is the visual hero; everything below is quiet and
|
|
12
|
+
* compact. Palette is restrained Compaction-blue only (no teal/cyan). The full
|
|
13
|
+
* UI is short and rendered into the alternate screen buffer (see `runInitTui`)
|
|
14
|
+
* so resizing repaints in place and never duplicates the wordmark.
|
|
15
|
+
*
|
|
16
|
+
* Invariants (identical to the static path): writes nothing, no network call,
|
|
17
|
+
* no credential read, features ONLY the free local-first workflows. It is a
|
|
18
|
+
* chooser, not an agent: no text input, no model call, nothing uploaded.
|
|
19
|
+
*/
|
|
20
|
+
export interface InitTuiDetection {
|
|
21
|
+
detected: boolean;
|
|
22
|
+
sessionCount: number;
|
|
23
|
+
projectsDir: string;
|
|
24
|
+
}
|
|
25
|
+
export type InitTuiResult = {
|
|
26
|
+
action: "select";
|
|
27
|
+
card: WorkflowCard;
|
|
28
|
+
} | {
|
|
29
|
+
action: "quit";
|
|
30
|
+
};
|
|
31
|
+
interface AppProps {
|
|
32
|
+
version: string;
|
|
33
|
+
detection: InitTuiDetection;
|
|
34
|
+
onDone: (result: InitTuiResult) => void;
|
|
35
|
+
}
|
|
36
|
+
export declare function App({ version, detection, onDone }: AppProps): React.ReactElement;
|
|
37
|
+
/**
|
|
38
|
+
* Render the TUI and resolve with the user's choice. Resolves to `{action:"quit"}`
|
|
39
|
+
* if the user exits without selecting. The caller prints the chosen command to
|
|
40
|
+
* the scrollback after this resolves.
|
|
41
|
+
*
|
|
42
|
+
* Renders into the terminal's ALTERNATE SCREEN BUFFER (like vim/less/fzf): no
|
|
43
|
+
* scrollback, so a reflow can never push a stale frame up into history. Resize
|
|
44
|
+
* redraw is handled INSIDE the component (reactive terminal size → re-render),
|
|
45
|
+
* so Ink owns the clear/repaint and the screen stays visible across resizes. On
|
|
46
|
+
* exit the screen is restored and the selected next-command prints normally.
|
|
47
|
+
*/
|
|
48
|
+
export declare function runInitTui(props: {
|
|
49
|
+
version: string;
|
|
50
|
+
detection: InitTuiDetection;
|
|
51
|
+
}): Promise<InitTuiResult>;
|
|
52
|
+
export {};
|