Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ acpx --format text codex 'summarize your findings'
acpx --format json codex exec 'review changed files'
acpx --format json --json-strict codex exec 'machine-safe JSON only'
acpx --format quiet codex 'final recommendation only'
acpx --suppress-reads codex exec 'show tool activity without dumping file bodies'

acpx --timeout 90 codex 'investigate intermittent test timeout'
acpx --ttl 30 codex 'keep queue owner alive for quick follow-ups'
Expand Down Expand Up @@ -257,8 +258,16 @@ acpx --format json --json-strict codex exec 'review this PR'

# quiet: final assistant text only
acpx --format quiet codex 'give me a 3-line summary'

# suppress read payloads while keeping the selected output format
acpx --suppress-reads codex exec 'inspect the repo and report tool usage'
```

- `text`: human-readable stream with assistant text and tool updates
- `json`: raw ACP NDJSON stream for automation
- `quiet`: final assistant text only
- `--suppress-reads`: replace raw read-file contents with `[read output suppressed]` in `text` and `json` output

JSON events include a stable envelope for correlation:

```json
Expand Down
7 changes: 7 additions & 0 deletions docs/CLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ All global options:
| `--approve-reads` | Auto-approve reads/searches, prompt for others | Default permission mode. |
| `--deny-all` | Deny all permissions | Permission mode `deny-all`. |
| `--format <fmt>` | Output format | `text` (default), `json`, `quiet`. |
| `--suppress-reads` | Suppress read file contents | Replaces raw read payloads with `[read output suppressed]`. |
| `--json-strict` | Strict JSON mode | Requires `--format json`; suppresses non-JSON stderr output. |
| `--non-interactive-permissions <policy>` | Non-TTY prompt policy | `deny` (default) or `fail` when approval prompt cannot be shown. |
| `--timeout <seconds>` | Max wait time for agent response | Must be positive. Decimal seconds allowed. |
Expand Down Expand Up @@ -424,6 +425,12 @@ When a prompt is already in flight for a session, `acpx` uses a per-session queu
- `json`: one raw ACP JSON-RPC message per line
- `quiet`: concatenated assistant text only

When `--suppress-reads` is enabled:

- `text`: read-like tool outputs render as `[read output suppressed]`
- `json`: ACP `fs/read_text_file` responses and read-like tool-call outputs replace raw file contents with `[read output suppressed]`
- `quiet`: unchanged, because quiet mode only prints assistant text

ACP message examples:

```json
Expand Down
4 changes: 3 additions & 1 deletion skills/acpx/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Core capabilities:
- Local agent process checks via `status`
- Stable ACP client methods for filesystem and terminal requests
- Stable ACP `authenticate` handshake via env/config credentials
- Structured streaming output (`text`, `json`, `quiet`)
- Structured streaming output (`text`, `json`, `quiet`) with optional `--suppress-reads`
- Built-in agent registry plus raw `--agent` escape hatch

## Install
Expand Down Expand Up @@ -192,6 +192,7 @@ Behavior:
- `--approve-reads`: auto-approve reads/searches, prompt for writes (default mode)
- `--deny-all`: deny all permission requests
- `--format <fmt>`: output format (`text`, `json`, `quiet`)
- `--suppress-reads`: suppress raw read-file contents while preserving the selected format
- `--timeout <seconds>`: max wait time (positive number)
- `--ttl <seconds>`: queue owner idle TTL before shutdown (default `300`, `0` disables TTL)
- `--verbose`: verbose ACP/debug logs to stderr
Expand Down Expand Up @@ -266,6 +267,7 @@ Use `--format <fmt>`:
- `text` (default): human-readable stream with updates/tool status and done line
- `json`: NDJSON event stream (good for automation)
- `quiet`: final assistant text only
- `--suppress-reads`: replace raw read-file contents with `[read output suppressed]` in `text` and `json` output

Example automation:

Expand Down
28 changes: 23 additions & 5 deletions src/cli-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,17 @@ function resolveCompatibleConfigId(
export { parseAllowedTools, parseMaxTurns, parseTtlSeconds };
export { formatPromptSessionBannerLine } from "./cli/output-render.js";

function resolveRequestedOutputPolicy(globalFlags: {
format: OutputFormat;
jsonStrict?: boolean;
suppressReads?: boolean;
}): OutputPolicy {
return {
...resolveOutputPolicy(globalFlags.format, globalFlags.jsonStrict === true),
suppressReads: globalFlags.suppressReads === true,
};
}

type SessionModule = typeof import("./session.js");
type OutputModule = typeof import("./output.js");
type OutputRenderModule = typeof import("./cli/output-render.js");
Expand Down Expand Up @@ -254,7 +265,7 @@ async function handlePrompt(
config: ResolvedAcpxConfig,
): Promise<void> {
const globalFlags = resolveGlobalFlags(command, config);
const outputPolicy = resolveOutputPolicy(globalFlags.format, globalFlags.jsonStrict === true);
const outputPolicy = resolveRequestedOutputPolicy(globalFlags);
const permissionMode = resolvePermissionMode(globalFlags, config.defaultPermissions);
const prompt = await readPrompt(promptParts, flags.file, globalFlags.cwd);
const agent = resolveAgentInvocation(explicitAgentName, globalFlags, config);
Expand All @@ -273,6 +284,7 @@ async function handlePrompt(
jsonContext: {
sessionId: record.acpxRecordId,
},
suppressReads: outputPolicy.suppressReads,
});

await printPromptSessionBanner(record, agent.cwd, outputPolicy.format, outputPolicy.jsonStrict);
Expand Down Expand Up @@ -317,7 +329,7 @@ async function handleExec(
): Promise<void> {
if (config.disableExec) {
const globalFlags = resolveGlobalFlags(command, config);
const outputPolicy = resolveOutputPolicy(globalFlags.format, globalFlags.jsonStrict === true);
const outputPolicy = resolveRequestedOutputPolicy(globalFlags);
if (outputPolicy.format === "json") {
process.stdout.write(
`${JSON.stringify({
Expand All @@ -341,14 +353,16 @@ async function handleExec(
}

const globalFlags = resolveGlobalFlags(command, config);
const outputPolicy = resolveOutputPolicy(globalFlags.format, globalFlags.jsonStrict === true);
const outputPolicy = resolveRequestedOutputPolicy(globalFlags);
const permissionMode = resolvePermissionMode(globalFlags, config.defaultPermissions);
const prompt = await readPrompt(promptParts, flags.file, globalFlags.cwd);
const [{ createOutputFormatter }, { runOnce }] = await Promise.all([
loadOutputModule(),
loadSessionModule(),
]);
const outputFormatter = createOutputFormatter(outputPolicy.format);
const outputFormatter = createOutputFormatter(outputPolicy.format, {
suppressReads: outputPolicy.suppressReads,
});
const agent = resolveAgentInvocation(explicitAgentName, globalFlags, config);

const result = await runOnce({
Expand Down Expand Up @@ -1471,6 +1485,7 @@ async function emitJsonErrorEvent(error: NormalizedOutputError): Promise<void> {
jsonContext: {
sessionId: "unknown",
},
suppressReads: false,
});
formatter.onError(error);
formatter.flush();
Expand Down Expand Up @@ -1538,7 +1553,10 @@ export async function main(argv: string[] = process.argv): Promise<void> {
const config = await loadResolvedConfig(detectInitialCwd(argv.slice(2)));
const requestedJsonStrict = detectJsonStrict(argv.slice(2));
const requestedOutputFormat = detectRequestedOutputFormat(argv.slice(2), config.format);
const requestedOutputPolicy = resolveOutputPolicy(requestedOutputFormat, requestedJsonStrict);
const requestedOutputPolicy = {
...resolveOutputPolicy(requestedOutputFormat, requestedJsonStrict),
suppressReads: argv.slice(2).some((token) => token === "--suppress-reads"),
};
const builtInAgents = listBuiltInAgents(config.agents);

const program = new Command();
Expand Down
4 changes: 4 additions & 0 deletions src/cli/flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export type GlobalFlags = PermissionFlags & {
authPolicy?: AuthPolicy;
nonInteractivePermissions: NonInteractivePermissionPolicy;
jsonStrict?: boolean;
suppressReads?: boolean;
timeout?: number;
ttl: number;
verbose?: boolean;
Expand Down Expand Up @@ -193,6 +194,7 @@ export function addGlobalFlags(command: Command): Command {
parseNonInteractivePermissionPolicy,
)
.option("--format <fmt>", "Output format: text, json, quiet", parseOutputFormat)
.option("--suppress-reads", "Suppress raw read-file contents in output")
.option("--model <id>", "Agent model id")
.option(
"--allowed-tools <list>",
Expand Down Expand Up @@ -278,6 +280,7 @@ export function resolveGlobalFlags(command: Command, config: ResolvedAcpxConfig)
authPolicy: opts.authPolicy ?? config.authPolicy,
nonInteractivePermissions: opts.nonInteractivePermissions ?? config.nonInteractivePermissions,
jsonStrict,
suppressReads: opts.suppressReads === true,
timeout: opts.timeout ?? config.timeoutMs,
ttl: opts.ttl ?? config.ttlMs ?? DEFAULT_QUEUE_OWNER_TTL_MS,
verbose,
Expand All @@ -295,6 +298,7 @@ export function resolveOutputPolicy(format: OutputFormat, jsonStrict: boolean):
return {
format,
jsonStrict,
suppressReads: false,
suppressNonJsonStderr: jsonStrict,
queueErrorAlreadyEmitted: format !== "quiet",
suppressSdkConsoleErrors: jsonStrict,
Expand Down
Loading