@deksden-com/dd-flow-cli 0.9.0-beta.136 → 0.9.0-beta.137

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.
Files changed (59) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/build-info.json +3 -3
  3. package/dist/harness-runtime/DD-ZCODE.md +13 -0
  4. package/dist/harness-runtime/bin/dd-opencode.mjs +3 -3
  5. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +14 -1
  6. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +36 -1
  7. package/dist/harness-runtime/lib/dd-zcode.mjs +7 -3
  8. package/dist/harness-runtime/lib/opencode-plugin.mjs +3 -1
  9. package/dist/services/controller-child-observer.js +99 -0
  10. package/dist/services/controller-fanout.js +9 -2
  11. package/dist/services/run-controller.js +83 -8
  12. package/dist/services/stage-lifecycle.js +2 -1
  13. package/dist/services/stage-report-renderer.js +13 -0
  14. package/dist/services/vnext-fanout.js +66 -1
  15. package/dist/services/vnext-protocolize.js +60 -2
  16. package/package.json +1 -1
  17. package/dist/cli/command-metadata.js +0 -144
  18. package/dist/cli/command-option-descriptions.js +0 -186
  19. package/dist/cli/completion-cli.js +0 -70
  20. package/dist/cli/completion.js +0 -57
  21. package/dist/cli/error-output.js +0 -52
  22. package/dist/cli/executor-onboarding.js +0 -71
  23. package/dist/cli/harnesses.js +0 -84
  24. package/dist/cli/models.js +0 -39
  25. package/dist/cli/operator-profiles.js +0 -61
  26. package/dist/cli/output-options.js +0 -35
  27. package/dist/cli/response-file.js +0 -69
  28. package/dist/cli/setup.js +0 -147
  29. package/dist/services/cli-upgrade-allowlist.js +0 -27
  30. package/dist/services/discovery/agy-discovery.js +0 -2
  31. package/dist/services/discovery/codex-discovery.js +0 -2
  32. package/dist/services/discovery/droid-discovery.js +0 -2
  33. package/dist/services/discovery/grok-discovery.js +0 -2
  34. package/dist/services/discovery/opencode-discovery.js +0 -2
  35. package/dist/services/discovery/static.js +0 -31
  36. package/dist/services/discovery/zcode-discovery.js +0 -2
  37. package/dist/services/executor-onboarding.js +0 -56
  38. package/dist/services/harness-discovery.js +0 -29
  39. package/dist/services/harness-doctor.js +0 -75
  40. package/dist/services/harness-probe-effects.js +0 -51
  41. package/dist/services/harness-version-probe.js +0 -100
  42. package/dist/services/hybrid-contracts.js +0 -455
  43. package/dist/services/installation-lock.js +0 -92
  44. package/dist/services/model-catalog.js +0 -109
  45. package/dist/services/model-discovery.js +0 -46
  46. package/dist/services/operator-profiles.js +0 -236
  47. package/dist/services/release-archive-producer.js +0 -52
  48. package/dist/services/release-archive.js +0 -143
  49. package/dist/services/release-cache.js +0 -142
  50. package/dist/services/release-compatibility.js +0 -18
  51. package/dist/services/release-download.js +0 -67
  52. package/dist/services/release-envelope.js +0 -104
  53. package/dist/services/release-manifest.js +0 -45
  54. package/dist/services/release-payload.js +0 -114
  55. package/dist/services/release-producer.js +0 -63
  56. package/dist/services/release-provenance.js +0 -160
  57. package/dist/services/release-registry.js +0 -66
  58. package/dist/services/release-source.js +0 -194
  59. package/dist/services/setup.js +0 -377
@@ -1,70 +0,0 @@
1
- import { completionCandidates } from "./completion.js";
2
- import { prepareCommandRoute } from "./command-inputs.js";
3
- import { parseOutputOptions } from "./output-options.js";
4
- import { writePreparationError } from "./error-output.js";
5
- import { AppError, errorRecord } from "../shared/errors.js";
6
- import { prepareResponseDestination, writeResponseFile } from "./response-file.js";
7
- export function completionScript(shell) {
8
- if (shell === "bash")
9
- return `# dd-flow completion: bash
10
- _dd_flow_complete() {
11
- local candidate
12
- COMPREPLY=()
13
- while IFS= read -r candidate; do
14
- [ -n "$candidate" ] && COMPREPLY+=("$candidate")
15
- done < <("${"$"}{COMP_WORDS[0]}" completion candidates -- "${"$"}{COMP_WORDS[@]:1:COMP_CWORD}")
16
- }
17
- complete -o default -F _dd_flow_complete dd-flow
18
- `;
19
- if (shell === "zsh")
20
- return `#compdef dd-flow
21
- _dd_flow_complete() {
22
- local -a replies
23
- replies=("${"$"}{(@f)$("${"$"}{words[1]}" completion candidates -- "${"$"}{(@)words[2,CURRENT]}")}")
24
- if (( ${"$"}{#replies} )) && [[ -n "$replies[1]" ]]; then
25
- compadd -a replies
26
- else
27
- _files
28
- fi
29
- }
30
- compdef _dd_flow_complete dd-flow
31
- `;
32
- if (shell === "fish")
33
- return `function __dd_flow_candidates
34
- set -l tokens (commandline -opc)
35
- set -l prefix (commandline -ct)
36
- set -l arguments
37
- if test (count $tokens) -gt 1
38
- set arguments $tokens[2..-1]
39
- end
40
- $tokens[1] completion candidates -- $arguments "$prefix"
41
- end
42
- complete -c dd-flow -a '(__dd_flow_candidates)'
43
- `;
44
- throw new AppError("usage", "Supported completion shells: bash, zsh, fish", 2, { effect: "no_effect" });
45
- }
46
- /** Completion is grammar-only: never constructs an AppContext or writes a home. */
47
- export function runCompletionCli(args, io = { stdout: process.stdout, stderr: process.stderr }) {
48
- let json = false;
49
- try {
50
- const output = parseOutputOptions(args);
51
- json = output.json;
52
- if (output.responseFile)
53
- prepareResponseDestination(output.responseFile);
54
- const route = prepareCommandRoute(output.args);
55
- if (route.key === "completion candidates") {
56
- const candidates = completionCandidates(route.parsed.positional);
57
- io.stdout.write(json ? `${JSON.stringify(writeResponseFile({ candidates }, output))}\n` : candidates.map(value => `${value}\n`).join(""));
58
- }
59
- else {
60
- const script = completionScript(route.command);
61
- io.stdout.write(json ? `${JSON.stringify(writeResponseFile({ shell: route.command, script }, output))}\n` : script);
62
- }
63
- return 0;
64
- }
65
- catch (error) {
66
- const record = errorRecord(error);
67
- writePreparationError(io, args, record);
68
- return record.exitCode;
69
- }
70
- }
@@ -1,57 +0,0 @@
1
- import { commandInputs } from "./command-inputs.js";
2
- import { globalOptionDocumentation, optionsForCommandTree } from "./command-metadata.js";
3
- const globalSwitches = globalOptionDocumentation.filter(option => option.kind === "switch").map(option => `--${option.name}`);
4
- const globalValues = globalOptionDocumentation.filter(option => option.kind === "value").map(option => `--${option.name}`);
5
- /** Read-only completion grammar. Last word is the cursor's unfinished token. */
6
- export function completionCandidates(words) {
7
- const prefix = words.at(-1) ?? "";
8
- const completed = words.slice(0, -1);
9
- if (completed.includes("--"))
10
- return [];
11
- const positions = [];
12
- let awaiting;
13
- let routeKey;
14
- const keys = Object.keys(commandInputs).filter(key => !commandInputs[key].retired);
15
- const select = () => keys.filter(key => key.split(" ").every((part, index) => positions[index] === part))
16
- .sort((left, right) => right.split(" ").length - left.split(" ").length)[0];
17
- for (const word of completed) {
18
- if (awaiting) {
19
- awaiting = undefined;
20
- continue;
21
- }
22
- if (word.startsWith("--")) {
23
- const name = word.slice(2).split("=")[0];
24
- const input = routeKey ? commandInputs[routeKey] : undefined;
25
- const values = input?.values ?? [...new Set(Object.values(commandInputs).flatMap(item => item.values))];
26
- if (!word.includes("=") && (values.includes(name) || globalValues.includes(`--${name}`)))
27
- awaiting = name;
28
- continue;
29
- }
30
- if (word === "-h")
31
- continue;
32
- positions.push(word);
33
- routeKey = select();
34
- }
35
- const input = routeKey ? commandInputs[routeKey] : undefined;
36
- let candidates;
37
- const inline = prefix.match(/^--([^=]+)=(.*)$/);
38
- if (awaiting || inline) {
39
- const option = awaiting ?? inline[1];
40
- candidates = option === "option" ? optionsForCommandTree(routeKey ?? positions.join(" ")).map(item => item.name)
41
- : [...(input?.enums[option] ?? [])];
42
- if (option === "option" && (inline?.[2] ?? prefix).startsWith("--"))
43
- candidates = candidates.map(name => `--${name}`);
44
- if (inline)
45
- return candidates.filter(value => value.startsWith(inline[2])).map(value => `--${option}=${value}`).sort();
46
- }
47
- else if (prefix.startsWith("-")) {
48
- candidates = [...globalSwitches, ...globalValues, ...(input ? [...input.values, ...input.switches].map(name => `--${name}`) : [])];
49
- }
50
- else {
51
- candidates = keys.filter(key => {
52
- const tokens = key.split(" ");
53
- return positions.every((part, index) => tokens[index] === part) && tokens.length > positions.length;
54
- }).map(key => key.split(" ")[positions.length]);
55
- }
56
- return [...new Set(candidates)].filter(value => value.startsWith(prefix)).sort();
57
- }
@@ -1,52 +0,0 @@
1
- import { hasHelpIntent } from "./help.js";
2
- import { commandHelpTopic } from "./command-metadata.js";
3
- import { writeJson } from "../shared/json.js";
4
- export function writePreparationError(io, args, error) {
5
- const literal = args.indexOf("--");
6
- const options = literal < 0 ? args : args.slice(0, literal);
7
- const payload = { ok: false, error };
8
- const help = hasHelpIntent(args);
9
- let json = false;
10
- for (let index = 0; index < options.length; index++) {
11
- if (help && options[index] === "--option") {
12
- index++;
13
- continue;
14
- }
15
- if (options[index] === "--json")
16
- json = true;
17
- }
18
- // Retain the hook's machine-only contract even when transport validation fails.
19
- if (json || args.slice(0, 3).join(" ") === "codex hook handle")
20
- writeJson(io.stderr, payload);
21
- else
22
- io.stderr.write(renderHumanError(payload, args));
23
- }
24
- export function renderHumanError(payload, args) {
25
- const lines = [`dd-flow: ${payload.error.message}`];
26
- const details = recordObject(payload.error.details);
27
- const compatibility = recordObject(details?.compatibility);
28
- const remediation = recordObject(details?.remediation);
29
- if (compatibility) {
30
- lines.push(`Compatibility: ${String(compatibility.verdict ?? "unknown")} (${String(compatibility.engine_resolution ?? "unknown")})`);
31
- if (compatibility.blocked_operation)
32
- lines.push(`Blocked operation: ${String(compatibility.blocked_operation)}`);
33
- if (compatibility.install_hint)
34
- lines.push(`Install compatible engine: ${String(compatibility.install_hint)}`);
35
- }
36
- if (remediation?.mb_upgrade_mode)
37
- lines.push(`Upgrade mode: ${String(remediation.mb_upgrade_mode)}`);
38
- if (details?.preferred_command)
39
- lines.push(`Preferred command: ${String(details.preferred_command)}`);
40
- const related = Array.isArray(details?.related_commands) ? details.related_commands.map(String) : [];
41
- if (related.length > 0) {
42
- lines.push("", "Related commands:");
43
- for (const command of related)
44
- lines.push(` ${command}`);
45
- }
46
- const topic = commandHelpTopic(args, "");
47
- lines.push(`See: dd-flow${topic ? ` ${topic}` : ""} --help`);
48
- return `${lines.join("\n")}\n`;
49
- }
50
- function recordObject(value) {
51
- return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
52
- }
@@ -1,71 +0,0 @@
1
- import { createInterface } from "node:readline";
2
- import { AppError } from "../shared/errors.js";
3
- import { prepareOperatorProfile, prepareProfileCreation, readOperatorProfileInput } from "../services/operator-profiles.js";
4
- /** One explicit choice; prompts go to stderr so stdout remains machine-readable. */
5
- export async function askExecutorChoice(settings, io) {
6
- const ids = settings.profiles.map(entry => entry.id);
7
- io.stderr.write(`Executor profiles: ${ids.length ? ids.join(", ") : "none configured"}\n`);
8
- io.stderr.write("Choose a profile ID/alias, file:/absolute/profile.json, create, or skip. No model is selected automatically.\n");
9
- const terminal = createInterface({ input: io.stdin, output: io.stderr });
10
- const lines = terminal[Symbol.asyncIterator]();
11
- const cancelled = () => new AppError("onboarding_cancelled", "Executor onboarding was cancelled", 1, { effect: "no_effect" });
12
- terminal.on("SIGINT", () => terminal.close());
13
- const ask = async (prompt) => {
14
- io.stderr.write(prompt);
15
- const line = await lines.next();
16
- if (line.done)
17
- throw cancelled();
18
- const answer = line.value.trim();
19
- if (answer.length > 4096)
20
- throw new AppError("usage", "Executor choice exceeds the input limit", 2, { effect: "no_effect" });
21
- return answer;
22
- };
23
- try {
24
- const answer = await ask("Executor: ");
25
- if (answer === "skip")
26
- return { skip: true };
27
- if (answer === "create") {
28
- io.stderr.write("Enter explicit profile settings; this does not probe providers or verify model access. Nothing is saved by this dialog.\n");
29
- const input = { schema_id: "dd-flow/agent-profile@1" };
30
- for (const [key, prompt] of [
31
- ["id", "Profile ID: "], ["harness", "Harness (codex/zcode/agy/opencode/grok/droid): "],
32
- ["provider", "Provider ID: "], ["model", "Model ID: "], ["reasoning", "Reasoning setting: "],
33
- ["mode", "Mode setting: "], ["permission", "Permission (allow/deny): "]
34
- ])
35
- input[key] = await ask(prompt);
36
- const profile = prepareOperatorProfile(input);
37
- if (settings.profiles.some(entry => entry.id === profile.id) || Object.hasOwn(settings.aliases, profile.id)) {
38
- throw new AppError("profile_id_conflict", "New profile ID collides with an existing profile or alias", 2, { effect: "no_effect" });
39
- }
40
- if (await ask("Use this profile for this operation? Type yes: ") !== "yes")
41
- throw cancelled();
42
- const save = await ask("Save this profile globally during installation? Type yes or no: ");
43
- if (save !== "yes" && save !== "no")
44
- throw cancelled();
45
- if (save === "yes") {
46
- const defaultChoice = await ask("Set it as global default? Type yes or no: ");
47
- if (defaultChoice !== "yes" && defaultChoice !== "no")
48
- throw cancelled();
49
- return { profile, profileCreation: prepareProfileCreation(settings, profile, defaultChoice === "yes") };
50
- }
51
- return { profile };
52
- }
53
- if (answer.startsWith("file:")) {
54
- const file = answer.slice(5);
55
- if (!file.startsWith("/") && !/^[A-Za-z]:[\\/]/.test(file))
56
- throw new AppError("usage", "Profile file must be an absolute path", 2, { effect: "no_effect" });
57
- return { profile: readOperatorProfileInput(file) };
58
- }
59
- if (!answer)
60
- throw new AppError("agent_profile_missing", "An explicit executor choice is required", 1, { effect: "no_effect" });
61
- return { profileId: answer };
62
- }
63
- catch (error) {
64
- if (error instanceof AppError)
65
- throw error;
66
- throw cancelled();
67
- }
68
- finally {
69
- terminal.close();
70
- }
71
- }
@@ -1,84 +0,0 @@
1
- import { prepareCommandRoute } from "./command-inputs.js";
2
- import { writePreparationError } from "./error-output.js";
3
- import { parseOutputOptions } from "./output-options.js";
4
- import { optionalOption, optionalPositiveNumber } from "./argument-values.js";
5
- import { discoverHarnesses, harnessCatalog } from "../services/harness-discovery.js";
6
- import { AppError, errorRecord } from "../shared/errors.js";
7
- import { probeHarnessVersion } from "../services/harness-version-probe.js";
8
- import { probeHarnessDoctor } from "../services/harness-doctor.js";
9
- import { prepareResponseDestination, writeResponseFile } from "./response-file.js";
10
- import path from "node:path";
11
- import { validateModelScope } from "../services/model-discovery.js";
12
- /** Home-free ingress: process diagnostics require explicit effect admission. */
13
- export async function runHarnessesCli(args, env, io = { stdout: process.stdout, stderr: process.stderr }) {
14
- let json = false;
15
- try {
16
- const output = parseOutputOptions(args);
17
- json = output.json;
18
- if (output.responseFile)
19
- prepareResponseDestination(output.responseFile);
20
- const route = prepareCommandRoute(output.args);
21
- const family = route.key === "harnesses show" ? route.parsed.positional[0] : optionalOption(route.parsed, "harness");
22
- if (family && !harnessCatalog.entries.some(entry => entry.id === family))
23
- throw new AppError("usage", "Unknown harness; see dd-flow harnesses --help", 2, { effect: "no_effect" });
24
- const provider = optionalOption(route.parsed, "provider"), account = optionalOption(route.parsed, "account-sha256");
25
- const observationFile = optionalOption(route.parsed, "model-observation-file");
26
- let modelScope;
27
- if (provider !== undefined || account !== undefined || observationFile !== undefined) {
28
- if (!provider || !account)
29
- throw new AppError("usage", "Doctor model observations require both --provider and --account-sha256", 2, { effect: "no_effect", help: "harnesses doctor" });
30
- modelScope = { harness: family, provider, account_sha256: account };
31
- validateModelScope(modelScope);
32
- if (observationFile) {
33
- if (output.responseFile && path.resolve(observationFile) === path.resolve(output.responseFile))
34
- throw new AppError("usage", "Model observation and command response destinations must differ", 2, { effect: "no_effect" });
35
- prepareResponseDestination(observationFile);
36
- }
37
- }
38
- const discovery = discoverHarnesses({ env, ...(family ? { harness: family } : {}),
39
- ...(optionalOption(route.parsed, "executable") ? { executable: optionalOption(route.parsed, "executable") } : {}) });
40
- let probe;
41
- let modelObservation;
42
- let observationPublication;
43
- if (route.key === "harnesses doctor") {
44
- const selected = discovery.entries[0]?.candidates.find(candidate => candidate.selected);
45
- if (!selected)
46
- throw new AppError("harness_selection_required", "Doctor requires one installed executable; use --executable for ambiguous installations", 1, { effect: "no_effect" });
47
- const stateDir = optionalOption(route.parsed, "state-dir"), nativeExecutable = optionalOption(route.parsed, "native-executable");
48
- const timeoutMs = optionalPositiveNumber(route.parsed, "timeout-ms", true);
49
- probe = await probeHarnessDoctor({ harness: family, executable: selected.path, env,
50
- ...(stateDir !== undefined ? { stateDir } : {}), ...(nativeExecutable !== undefined ? { nativeExecutable } : {}),
51
- ...(timeoutMs !== undefined ? { timeoutMs } : {}),
52
- scope: { processes: route.parsed.options.has("allow-processes"), services: route.parsed.options.has("allow-services"),
53
- credentials: route.parsed.options.has("allow-credentials"), writes: route.parsed.options.has("allow-writes") } });
54
- if (modelScope) {
55
- modelObservation = { scope: modelScope, observed_at: new Date().toISOString(),
56
- status: probe.reported_models.length ? "listed" : "unavailable", complete: false, models: [...probe.reported_models] };
57
- if (observationFile) {
58
- const published = writeResponseFile(modelObservation, { responseFile: observationFile });
59
- observationPublication = { path: observationFile, status: published.response_file_error ? "unknown" : "published",
60
- ...(published.response_file_error ? { error: published.response_file_error } : {}) };
61
- }
62
- }
63
- }
64
- else if (route.key === "harnesses probe-version") {
65
- const selected = discovery.entries[0]?.candidates.find(candidate => candidate.selected);
66
- if (!selected)
67
- throw new AppError("harness_selection_required", "Version probe requires one installed executable; use --executable for ambiguous installations", 1, { effect: "no_effect" });
68
- probe = await probeHarnessVersion({ harness: family, executable: selected.path, env,
69
- ...(optionalPositiveNumber(route.parsed, "timeout-ms", true) === undefined ? {} : { timeoutMs: optionalPositiveNumber(route.parsed, "timeout-ms", true) }),
70
- scope: { processes: route.parsed.options.has("allow-processes"), services: false,
71
- credentials: route.parsed.options.has("allow-credentials"), writes: route.parsed.options.has("allow-writes") } });
72
- }
73
- const result = { ok: true, action: route.key.replaceAll(" ", "_"), catalog: harnessCatalog, discovery, ...(probe ? { probe } : {}),
74
- ...(modelObservation ? { model_observation: modelObservation, observation_scope_binding: "operator_declared_unverified" } : {}),
75
- ...(observationPublication ? { model_observation_publication: observationPublication } : {}) };
76
- io.stdout.write(`${JSON.stringify(writeResponseFile(result, output), null, json ? undefined : 2)}\n`);
77
- return 0;
78
- }
79
- catch (error) {
80
- const record = errorRecord(error);
81
- writePreparationError(io, args, record);
82
- return record.exitCode;
83
- }
84
- }
@@ -1,39 +0,0 @@
1
- import { prepareCommandRoute } from "./command-inputs.js";
2
- import { optionalOption } from "./argument-values.js";
3
- import { parseOutputOptions } from "./output-options.js";
4
- import { prepareResponseDestination, writeResponseFile } from "./response-file.js";
5
- import { listOperatorModels } from "../services/model-catalog.js";
6
- import { AppError, errorRecord } from "../shared/errors.js";
7
- import { writePreparationError } from "./error-output.js";
8
- /** Model listing never opens an AppContext, starts a probe or reads credentials. */
9
- export function runModelsCli(args, io = process) {
10
- let json = false;
11
- try {
12
- const output = parseOutputOptions(args);
13
- json = output.json;
14
- if (output.responseFile)
15
- prepareResponseDestination(output.responseFile);
16
- const route = prepareCommandRoute(output.args);
17
- if (!["models list", "models show"].includes(route.key))
18
- throw new AppError("usage", "Unknown models command; see dd-flow models --help", 2, { effect: "no_effect" });
19
- const input = {};
20
- for (const [option, field] of [["harness", "harness"], ["provider", "provider"], ["account-sha256", "accountSha256"],
21
- ["seed-file", "seedFile"], ["observation-file", "observationFile"]]) {
22
- const value = optionalOption(route.parsed, option);
23
- if (value !== undefined)
24
- input[field] = value;
25
- }
26
- if (route.key === "models show")
27
- input.model = route.parsed.positional[0];
28
- const models = listOperatorModels(input);
29
- if (route.key === "models show" && models.entries.length === 0)
30
- throw new AppError("model_not_found", "Model is absent from the selected seed and scoped observation", 1, { effect: "no_effect", help: "models" });
31
- io.stdout.write(`${JSON.stringify(writeResponseFile({ ok: true, action: route.key.replaceAll(" ", "_"), ...models }, output), null, json ? undefined : 2)}\n`);
32
- return 0;
33
- }
34
- catch (error) {
35
- const record = errorRecord(error);
36
- writePreparationError(io, args, record);
37
- return record.exitCode;
38
- }
39
- }
@@ -1,61 +0,0 @@
1
- import { requiredOption, optionalOption } from "./argument-values.js";
2
- import { AppError } from "../shared/errors.js";
3
- import { errorRecord } from "../shared/errors.js";
4
- import { prepareCommandRoute } from "./command-inputs.js";
5
- import { parseOutputOptions } from "./output-options.js";
6
- import { writePreparationError } from "./error-output.js";
7
- import { resolveDdFlowHome } from "../storage/paths.js";
8
- import { prepareResponseDestination, writeResponseFile } from "./response-file.js";
9
- import { readOperatorProfiles, resolveOperatorProfile, mutateOperatorProfiles, readOperatorProfileInput } from "../services/operator-profiles.js";
10
- /** Home-only entrypoint; does not create AppContext, project registration or runtime DB. */
11
- export function dispatchOperatorProfiles(home, route) {
12
- const action = route.command, parsed = route.parsed;
13
- if (action === "list")
14
- return { ok: true, action: "profiles_list", settings: readOperatorProfiles(home) };
15
- if (action === "show") {
16
- const settings = readOperatorProfiles(home), id = parsed.positional[0];
17
- return { ok: true, action: "profiles_show", selected: id, revision: settings.revision, profile: resolveOperatorProfile(settings, id) };
18
- }
19
- let mutation;
20
- if (action === "create")
21
- mutation = { action, profile: readOperatorProfileInput(requiredOption(parsed, "file")), setDefault: parsed.options.has("set-default") };
22
- else if (action === "edit")
23
- mutation = { action, profile: readOperatorProfileInput(requiredOption(parsed, "file")) };
24
- else if (action === "remove")
25
- mutation = { action, id: parsed.positional[0] };
26
- else if (action === "alias") {
27
- const target = optionalOption(parsed, "target"), remove = parsed.options.has("remove");
28
- if (Boolean(target) === remove)
29
- throw new AppError("usage", "Use exactly one of --target ID or --remove; see dd-flow profiles alias --help", 2, { effect: "no_effect" });
30
- mutation = { action, alias: parsed.positional[0], target: target ?? null };
31
- }
32
- else if (action === "set-default") {
33
- const id = parsed.positional[0], clear = parsed.options.has("clear");
34
- if (Boolean(id) === clear)
35
- throw new AppError("usage", "Select one profile ID or --clear; see dd-flow profiles set-default --help", 2, { effect: "no_effect" });
36
- mutation = { action, id: id ?? null };
37
- }
38
- else
39
- throw new AppError("usage", "Unknown profiles command; use dd-flow profiles --help", 2, { effect: "no_effect" });
40
- const settings = mutateOperatorProfiles(home, requiredOption(parsed, "expected-revision"), mutation);
41
- return { ok: true, action: `profiles_${action}`, effect: "committed", settings };
42
- }
43
- /** Lightweight executable ingress; never imports the project/runtime business router. */
44
- export function runOperatorProfilesCli(args, env, io = { stdout: process.stdout, stderr: process.stderr }) {
45
- let json = false;
46
- try {
47
- const output = parseOutputOptions(args);
48
- json = output.json;
49
- if (output.responseFile)
50
- prepareResponseDestination(output.responseFile);
51
- const route = prepareCommandRoute(output.args);
52
- const result = dispatchOperatorProfiles(resolveDdFlowHome(env), route);
53
- io.stdout.write(`${JSON.stringify(writeResponseFile(result, output), null, json ? undefined : 2)}\n`);
54
- return 0;
55
- }
56
- catch (error) {
57
- const record = errorRecord(error);
58
- writePreparationError(io, args, record);
59
- return record.exitCode;
60
- }
61
- }
@@ -1,35 +0,0 @@
1
- import { AppError } from "../shared/errors.js";
2
- /** Shared execution transport grammar. Help resolves before this function. */
3
- export function parseOutputOptions(args) {
4
- const stripped = [];
5
- let json = false, progressJsonl = false, responseFile = null;
6
- for (let index = 0; index < args.length; index++) {
7
- const arg = args[index];
8
- if (arg === "--") {
9
- stripped.push(...args.slice(index));
10
- break;
11
- }
12
- if (arg === "--json") {
13
- json = true;
14
- continue;
15
- }
16
- if (arg === "--progress-jsonl") {
17
- progressJsonl = true;
18
- continue;
19
- }
20
- if (arg === "--response-file" || arg.startsWith("--response-file=")) {
21
- if (responseFile !== null)
22
- throw new AppError("usage", "--response-file may be supplied only once", 2);
23
- const file = arg === "--response-file" ? args[++index] : arg.slice("--response-file=".length);
24
- if (!file?.trim() || file.startsWith("--"))
25
- throw new AppError("usage", "--response-file requires a path", 2);
26
- responseFile = file;
27
- continue;
28
- }
29
- stripped.push(arg);
30
- }
31
- if (responseFile && !json)
32
- throw new AppError("usage", "--response-file requires --json", 2);
33
- const hookJsonOnly = stripped[0] === "codex" && stripped[1] === "hook" && stripped[2] === "handle";
34
- return { json: json || hookJsonOnly, progressJsonl, args: stripped, hookJsonOnly, responseFile };
35
- }
@@ -1,69 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import { randomUUID } from "node:crypto";
4
- import { AppError } from "../shared/errors.js";
5
- /** Preflight does not create a parent, destination or runtime home. */
6
- export function prepareResponseDestination(value) {
7
- const file = path.resolve(value);
8
- try {
9
- const inspect = (target) => {
10
- try {
11
- return fs.lstatSync(target);
12
- }
13
- catch (error) {
14
- if (error.code === "ENOENT")
15
- return null;
16
- throw error;
17
- }
18
- };
19
- let parent = path.dirname(file);
20
- let parentStat = inspect(parent);
21
- while (!parentStat) {
22
- const next = path.dirname(parent);
23
- if (next === parent)
24
- break;
25
- parent = next;
26
- parentStat = inspect(parent);
27
- }
28
- const targetStat = inspect(file);
29
- if (!parentStat?.isDirectory() || targetStat && !targetStat.isFile())
30
- throw new Error();
31
- }
32
- catch {
33
- throw new AppError("response_file_invalid", "Response destination must be a regular file under a directory", 2, { parameter: "response-file", effect: "no_effect" });
34
- }
35
- }
36
- /** Optional publication failure never changes the accepted operation outcome. */
37
- export function writeResponseFile(payload, output) {
38
- if (!output.responseFile)
39
- return payload;
40
- const file = path.resolve(output.responseFile), temporary = `${file}.tmp-${randomUUID()}`;
41
- const visible = payload && typeof payload === "object" && !Array.isArray(payload) ? { ...payload } : { ok: true, result: payload };
42
- let ownsTemporary = false;
43
- try {
44
- prepareResponseDestination(file);
45
- fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
46
- const fd = fs.openSync(temporary, "wx", 0o600);
47
- ownsTemporary = true;
48
- try {
49
- fs.writeFileSync(fd, `${JSON.stringify(payload, null, 2)}\n`);
50
- fs.fsyncSync(fd);
51
- }
52
- finally {
53
- fs.closeSync(fd);
54
- }
55
- fs.renameSync(temporary, file);
56
- }
57
- catch {
58
- return { ...visible, response_file: file, response_file_error: "Response publication failed; reconcile the accepted operation before retrying" };
59
- }
60
- finally {
61
- try {
62
- if (ownsTemporary && fs.existsSync(temporary))
63
- fs.unlinkSync(temporary);
64
- }
65
- catch { /* Own temporary only; never mask the operation outcome. */ }
66
- }
67
- delete visible.worker_prompt_markdown;
68
- return { ...visible, response_file: file };
69
- }