docs: document cli shared helpers

This commit is contained in:
Peter Steinberger
2026-06-03 18:23:23 -04:00
parent 90b8b41c41
commit 4f4cd2e8ae
7 changed files with 29 additions and 0 deletions

View File

@@ -1,7 +1,9 @@
// CLI dotenv loader that preserves workspace overrides before global runtime fallbacks.
import path from "node:path";
import { resolveStateDir } from "../config/paths.js";
import { loadGlobalRuntimeDotEnvFiles, loadWorkspaceDotEnvFile } from "../infra/dotenv.js";
/** Load `.env` files for normal CLI commands without overriding existing process env. */
export function loadCliDotEnv(opts?: { quiet?: boolean }) {
const quiet = opts?.quiet ?? true;
const cwdEnvPath = path.join(process.cwd(), ".env");

View File

@@ -1,8 +1,10 @@
// Minimal dotenv loader for gateway-dispatched CLI commands.
import fs from "node:fs";
import path from "node:path";
import { resolveStateDir } from "../config/paths.js";
import { loadGlobalRuntimeDotEnvFiles } from "../infra/dotenv-global.js";
/** Load only the env files needed before dispatching a command through the gateway. */
export async function loadGatewayDispatchCliDotEnv(opts?: { quiet?: boolean }) {
const quiet = opts?.quiet ?? true;
const cwdEnvPath = path.join(process.cwd(), ".env");

View File

@@ -1,8 +1,10 @@
// Helpers for recording npm plugin installs with optional exact-version pinning metadata.
import {
buildNpmResolutionFields,
type NpmSpecResolution as NpmResolutionMetadata,
} from "../infra/install-source-utils.js";
/** Choose the install-record spec for an npm package, optionally pinning to the resolved version. */
export function resolvePinnedNpmSpec(params: {
rawSpec: string;
pin: boolean;
@@ -24,6 +26,7 @@ export function resolvePinnedNpmSpec(params: {
};
}
/** Convert npm resolver metadata into persisted install-record fields. */
export function mapNpmResolutionMetadata(resolution?: NpmResolutionMetadata): {
resolvedName?: string;
resolvedVersion?: string;
@@ -35,6 +38,7 @@ export function mapNpmResolutionMetadata(resolution?: NpmResolutionMetadata): {
return buildNpmResolutionFields(resolution);
}
/** Build the npm section of a plugin install record. */
export function buildNpmInstallRecordFields(params: {
spec: string;
installPath: string;
@@ -61,6 +65,7 @@ export function buildNpmInstallRecordFields(params: {
};
}
/** Resolve and log npm pinning decisions before constructing the persisted install record. */
export function resolvePinnedNpmInstallRecord(params: {
rawSpec: string;
pin: boolean;
@@ -84,6 +89,7 @@ export function resolvePinnedNpmInstallRecord(params: {
});
}
/** CLI adapter for npm install-record pinning with styled warning output. */
export function resolvePinnedNpmInstallRecordForCli(
rawSpec: string,
pin: boolean,
@@ -104,6 +110,7 @@ export function resolvePinnedNpmInstallRecordForCli(
});
}
/** Emit any user-facing notice or warning from npm pin resolution. */
export function logPinnedNpmSpecMessages(
pinInfo: { pinWarning?: string; pinNotice?: string },
log: (message: string) => void,

View File

@@ -1,8 +1,10 @@
// Duration parser shared by CLI flags, command directives, and config-backed timing values.
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
} from "@openclaw/normalization-core/string-coerce";
/** Options for choosing the unit used by bare numeric duration values. */
export type DurationMsParseOptions = {
defaultUnit?: "ms" | "s" | "m" | "h" | "d";
};
@@ -29,6 +31,7 @@ function roundSafeDurationMs(raw: string, value: number): number {
return ms;
}
/** Parse a non-negative duration into milliseconds, supporting single and composite units. */
export function parseDurationMs(raw: string, opts?: DurationMsParseOptions): number {
const trimmed = normalizeLowercaseStringOrEmpty(normalizeOptionalString(raw) ?? "");
if (!trimmed) {

View File

@@ -1,3 +1,4 @@
// Terminal progress reporter used by long-running CLI commands.
import { spinner } from "@clack/prompts";
import {
createOscProgressController,
@@ -12,6 +13,7 @@ import { theme } from "../../packages/terminal-core/src/theme.js";
import { resolveTimerTimeoutMs } from "../shared/number-coercion.js";
const DEFAULT_DELAY_MS = 0;
// Only one active progress renderer may own the terminal line at a time.
let activeProgress = 0;
type ProgressOptions = {
@@ -24,6 +26,7 @@ type ProgressOptions = {
fallback?: "spinner" | "line" | "log" | "none";
};
/** Minimal progress API exposed to CLI work callbacks. */
export type ProgressReporter = {
setLabel: (label: string) => void;
setPercent: (percent: number) => void;
@@ -31,12 +34,14 @@ export type ProgressReporter = {
done: () => void;
};
/** Completed/total progress update shape used by totals-based commands. */
export type ProgressTotalsUpdate = {
completed: number;
total: number;
label?: string;
};
/** Decide whether the interactive spinner is safe for the current terminal state. */
export function shouldUseInteractiveProgressSpinner(params: {
fallback?: ProgressOptions["fallback"];
streamIsTty?: boolean;
@@ -53,6 +58,7 @@ const noopReporter: ProgressReporter = {
done: () => {},
};
/** Create a no-op, spinner, line, log, and OSC-capable progress reporter. */
export function createCliProgress(options: ProgressOptions): ProgressReporter {
if (options.enabled === false) {
return noopReporter;
@@ -78,6 +84,7 @@ export function createCliProgress(options: ProgressOptions): ProgressReporter {
});
const allowLine = isTty && options.fallback === "line";
if (isTty && stdinIsRaw && (options.fallback === undefined || options.fallback === "spinner")) {
// Raw stdin usually means an interactive prompt owns cursor movement.
return noopReporter;
}
@@ -223,6 +230,7 @@ export function createCliProgress(options: ProgressOptions): ProgressReporter {
return { setLabel, setPercent, tick, done };
}
/** Run async work with a progress reporter that is always stopped in finally. */
export async function withProgress<T>(
options: ProgressOptions,
work: (progress: ProgressReporter) => Promise<T>,
@@ -235,6 +243,7 @@ export async function withProgress<T>(
}
}
/** Run async work with a progress reporter plus a completed/total update adapter. */
export async function withProgressTotals<T>(
options: ProgressOptions,
work: (update: (update: ProgressTotalsUpdate) => void, progress: ProgressReporter) => Promise<T>,

View File

@@ -1,3 +1,4 @@
// Shared scanner for forwarding root CLI options while subcommands inspect their own args.
import { FLAG_TERMINATOR } from "../infra/cli-root-options.js";
import { forwardConsumedCliRootOption } from "./root-option-forward.js";
@@ -8,6 +9,7 @@ type CliRootOptionVisitResult =
| { kind: "handled"; consumedNext?: boolean }
| { kind: "error"; error: string };
/** Walk argv once, letting callers consume custom flags before forwarding root options. */
export function scanCliRootOptions(
argv: string[],
visit: (params: {
@@ -29,6 +31,7 @@ export function scanCliRootOptions(
continue;
}
if (arg === FLAG_TERMINATOR) {
// `--` ends root-option handling; everything after it belongs to the target command.
out.push(arg, ...args.slice(i + 1));
break;
}

View File

@@ -1,7 +1,10 @@
// CLI-facing TCP port parser wrapper.
import { parseTcpPort } from "../../infra/tcp-port.js";
/** Re-export the canonical TCP port parser and limit for CLI callers. */
export { MAX_TCP_PORT, parseTcpPort } from "../../infra/tcp-port.js";
/** Parse a TCP port from unknown CLI/config input, returning null for invalid values. */
export function parsePort(raw: unknown): number | null {
return parseTcpPort(raw);
}