Skip to content
Merged
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
2 changes: 1 addition & 1 deletion src/agent-device-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ export function createAgentDeviceClient(
command: DaemonCommandName,
options: InternalRequestOptions = {},
): Promise<T> => {
const request = await prepareDaemonCommandRequest(command, options);
const request = await prepareDaemonCommandRequest(command, options, host);
return (await execute(
request.command,
request.positionals,
Expand Down
2 changes: 1 addition & 1 deletion src/cli/commands/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export function renderProxyStartup(
startup: ProxyStartup,
options: { useColor?: boolean } = {},
): string {
const useColor = options.useColor ?? supportsColor();
const useColor = options.useColor ?? supportsColor(process.env);
const checkmark = formatProxyOutputValue('✓', 'green', useColor);
const proxyBaseUrl = formatProxyOutputValue(startup.proxyBaseUrl, 'cyan', useColor);
const daemonBaseUrl = formatProxyOutputValue('<tunnel URL>', 'cyan', useColor);
Expand Down
4 changes: 2 additions & 2 deletions src/cli/commands/screenshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export const diffCommand: ClientCommandHandler = async ({ positionals, flags, cl
flags,
env: host.env,
});
await writeCommandOutput(flags, result, () => formatSnapshotDiffText(result));
await writeCommandOutput(flags, result, () => formatSnapshotDiffText(result, host.env));
return true;
}

Expand Down Expand Up @@ -104,7 +104,7 @@ export const diffCommand: ClientCommandHandler = async ({ positionals, flags, cl
});

await writeCommandOutput(flags, result, () =>
formatScreenshotDiffText(result as ScreenshotDiffResult),
formatScreenshotDiffText(result as ScreenshotDiffResult, host),
);
return true;
};
Expand Down
4 changes: 2 additions & 2 deletions src/cli/replay-test/progress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ function formatReplayTestLiveProgressLine(
): string {
const title = event.title?.trim();
const file = path.basename(event.file);
const useColor = supportsColor(process.stderr);
const useColor = supportsColor(process.env, process.stderr);
const spinner = formatReplayTestProgressSpinner(spinnerFrame, { useColor });
const shardSuffix = formatReplayTestProgressShardSuffix(event, { useColor });
const stepSuffix = formatReplayTestLiveProgressStepSuffix(event, { useColor });
Expand Down Expand Up @@ -223,7 +223,7 @@ function replayTestProgressFailureContextLines(event: ReplayTestResult): string[
}

function formatReplayTestCaseSummaryLine(event: ReplayTestResult): string {
const useColor = supportsColor(process.stderr);
const useColor = supportsColor(process.env, process.stderr);
const statusLabel = formatReplayTestProgressStatusLabel(event);
const name = formatReplayTestProgressName(event);
const shardSuffix = formatReplayTestProgressShardSuffix(event, { useColor });
Expand Down
4 changes: 2 additions & 2 deletions src/cli/replay-test/reporters/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ function renderWarningsSection(

function formatReplayTestSummaryLine(data: ReplaySuiteResult, flakyCount: number): string {
const durationMs = typeof data.durationMs === 'number' ? data.durationMs : undefined;
const useColor = supportsColor();
const useColor = supportsColor(process.env);
const passed = formatReplaySummaryPassed(data.passed, { useColor });
const total = formatReplaySummaryTotal(data.total, { useColor });
const failedSuffix = data.failed > 0 ? `, ${data.failed} failed` : '';
Expand All @@ -169,7 +169,7 @@ function formatReplayDuration(durationMs: number, options: { useColor?: boolean
}

function replayFlakyStatusIcon(): string {
const useColor = supportsColor();
const useColor = supportsColor(process.env);
return useColor ? colorize('✓', 'yellow') : '✓';
}

Expand Down
18 changes: 14 additions & 4 deletions src/commands/batch/projection.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { HostEnvironment } from '@agent-device/kernel/source-value';
import { PUBLIC_COMMANDS } from '@agent-device/command-registry/catalog';
import {
STRUCTURED_BATCH_COMMAND_NAMES,
Expand Down Expand Up @@ -27,23 +28,32 @@ type PrepareDaemonCommandRequest = (
command: string,
input: CommandInput,
stepNumber: number,
host: HostEnvironment,
) => Promise<DaemonCommandRequest>;

type PrepareBatchStep = (
command: string,
input: CommandInput,
stepNumber: number,
) => Promise<DaemonCommandRequest>;

export function createBatchDaemonWriter(
prepareDaemonCommandRequest: PrepareDaemonCommandRequest,
): AsyncDaemonWriter {
return async (input) =>
return async (input, host) =>
request(PUBLIC_COMMANDS.batch, [], {
...input,
batchSteps: await readBatchDaemonSteps(input.steps, prepareDaemonCommandRequest),
batchSteps: await readBatchDaemonSteps(input.steps, (command, input, stepNumber) =>
prepareDaemonCommandRequest(command, input, stepNumber, host),
),
batchOnError: input.onError,
batchMaxSteps: input.maxSteps,
});
}

async function readBatchDaemonSteps(
steps: unknown,
prepareDaemonCommandRequest: PrepareDaemonCommandRequest,
prepareDaemonCommandRequest: PrepareBatchStep,
): Promise<DaemonBatchStep[]> {
if (!Array.isArray(steps) || steps.length === 0) {
throw new AppError('INVALID_ARGS', 'batch requires a non-empty steps array.');
Expand All @@ -60,7 +70,7 @@ async function readBatchDaemonSteps(
async function readBatchDaemonStep(
step: unknown,
stepNumber: number,
prepareDaemonCommandRequest: PrepareDaemonCommandRequest,
prepareDaemonCommandRequest: PrepareBatchStep,
): Promise<DaemonBatchStep> {
const record = readBatchStepRecord(step, stepNumber);
const command = readBatchStepCommand(record, stepNumber);
Expand Down
6 changes: 5 additions & 1 deletion src/commands/cli-grammar/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { HostEnvironment } from '@agent-device/kernel/source-value';
import type {
CliFlags,
CommandFlags,
Expand Down Expand Up @@ -97,6 +98,9 @@ export type DaemonWriter = (input: CommandInput) => DaemonCommandRequest;
* `runFlow` includes on demand. The registry awaits whichever kind a command declares, so the
* asynchrony stays inside the one writer that needs it.
*/
export type AsyncDaemonWriter = (input: CommandInput) => Promise<DaemonCommandRequest>;
export type AsyncDaemonWriter = (
input: CommandInput,
host: HostEnvironment,
) => Promise<DaemonCommandRequest>;

export type AnyDaemonWriter = DaemonWriter | AsyncDaemonWriter;
9 changes: 7 additions & 2 deletions src/commands/command-projection.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { HostEnvironment } from '@agent-device/kernel/source-value';
import { createBatchDaemonWriter } from './batch/index.ts';
import type { AnyDaemonWriter, CommandInput, DaemonCommandRequest } from './cli-grammar/types.ts';
import { findCommandMetadata } from './command-metadata.ts';
Expand All @@ -17,6 +18,7 @@ async function prepareBatchDaemonCommandRequest(
command: string,
input: CommandInput,
stepNumber: number,
host: HostEnvironment,
): Promise<DaemonCommandRequest> {
const writer = (daemonWriters as Readonly<Record<string, AnyDaemonWriter>>)[command];
if (!writer) {
Expand All @@ -31,6 +33,7 @@ async function prepareBatchDaemonCommandRequest(
writer,
metadata,
metadata.readInput(input) as CommandInput,
host,
);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
Expand All @@ -46,22 +49,24 @@ async function prepareBatchDaemonCommandRequest(
export async function prepareDaemonCommandRequest(
command: DaemonCommandName,
input: CommandInput,
host: HostEnvironment,
): Promise<DaemonCommandRequest> {
const writer = daemonWriters[command];
if (!writer) {
throw new Error(`Missing daemon writer for command: ${command}`);
}
refuseRetiredCommonInput(input as Record<string, unknown>);
const metadata = findCommandMetadata(command);
return await prepareRequestWithMetadataFlags(writer, metadata, input);
return await prepareRequestWithMetadataFlags(writer, metadata, input, host);
}

async function prepareRequestWithMetadataFlags(
writer: AnyDaemonWriter,
metadata: ReturnType<typeof findCommandMetadata>,
input: CommandInput,
host: HostEnvironment,
): Promise<DaemonCommandRequest> {
const request = await writer(input);
const request = await writer(input, host);
return {
...request,
...(metadata ? { metadataFlags: readMetadataCommandFlags(metadata, request.options) } : {}),
Expand Down
10 changes: 5 additions & 5 deletions src/commands/output/color.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ test('supportsColor uses the stream TTY state by default', () => {
delete process.env.FORCE_COLOR;
delete process.env.NO_COLOR;
try {
assert.equal(supportsColor({ isTTY: true }), true);
assert.equal(supportsColor({ isTTY: false }), false);
assert.equal(supportsColor(process.env, { isTTY: true }), true);
assert.equal(supportsColor(process.env, { isTTY: false }), false);
} finally {
if (typeof originalForceColor === 'string') process.env.FORCE_COLOR = originalForceColor;
else delete process.env.FORCE_COLOR;
Expand All @@ -25,14 +25,14 @@ test('supportsColor honors explicit environment overrides', () => {
try {
process.env.FORCE_COLOR = '0';
delete process.env.NO_COLOR;
assert.equal(supportsColor({ isTTY: true }), false);
assert.equal(supportsColor(process.env, { isTTY: true }), false);

process.env.FORCE_COLOR = '1';
assert.equal(supportsColor({ isTTY: false }), true);
assert.equal(supportsColor(process.env, { isTTY: false }), true);

delete process.env.FORCE_COLOR;
process.env.NO_COLOR = '1';
assert.equal(supportsColor({ isTTY: true }), false);
assert.equal(supportsColor(process.env, { isTTY: true }), false);
} finally {
if (typeof originalForceColor === 'string') process.env.FORCE_COLOR = originalForceColor;
else delete process.env.FORCE_COLOR;
Expand Down
7 changes: 4 additions & 3 deletions src/commands/output/color.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import type { EnvMap } from '@agent-device/kernel/source-value';
import { styleText } from 'node:util';

export function supportsColor(stream: { isTTY?: boolean } = process.stdout): boolean {
const forceColor = process.env.FORCE_COLOR;
export function supportsColor(env: EnvMap, stream: { isTTY?: boolean } = process.stdout): boolean {
const forceColor = env.FORCE_COLOR;
if (typeof forceColor === 'string') {
return forceColor !== '0';
}
if (typeof process.env.NO_COLOR === 'string') {
if (typeof env.NO_COLOR === 'string') {
return false;
}
return Boolean(stream.isTTY);
Expand Down
Loading
Loading