@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.
- package/CHANGELOG.md +8 -0
- package/dist/build-info.json +3 -3
- package/dist/harness-runtime/DD-ZCODE.md +13 -0
- package/dist/harness-runtime/bin/dd-opencode.mjs +3 -3
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +14 -1
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +36 -1
- package/dist/harness-runtime/lib/dd-zcode.mjs +7 -3
- package/dist/harness-runtime/lib/opencode-plugin.mjs +3 -1
- package/dist/services/controller-child-observer.js +99 -0
- package/dist/services/controller-fanout.js +9 -2
- package/dist/services/run-controller.js +83 -8
- package/dist/services/stage-lifecycle.js +2 -1
- package/dist/services/stage-report-renderer.js +13 -0
- package/dist/services/vnext-fanout.js +66 -1
- package/dist/services/vnext-protocolize.js +60 -2
- package/package.json +1 -1
- package/dist/cli/command-metadata.js +0 -144
- package/dist/cli/command-option-descriptions.js +0 -186
- package/dist/cli/completion-cli.js +0 -70
- package/dist/cli/completion.js +0 -57
- package/dist/cli/error-output.js +0 -52
- package/dist/cli/executor-onboarding.js +0 -71
- package/dist/cli/harnesses.js +0 -84
- package/dist/cli/models.js +0 -39
- package/dist/cli/operator-profiles.js +0 -61
- package/dist/cli/output-options.js +0 -35
- package/dist/cli/response-file.js +0 -69
- package/dist/cli/setup.js +0 -147
- package/dist/services/cli-upgrade-allowlist.js +0 -27
- package/dist/services/discovery/agy-discovery.js +0 -2
- package/dist/services/discovery/codex-discovery.js +0 -2
- package/dist/services/discovery/droid-discovery.js +0 -2
- package/dist/services/discovery/grok-discovery.js +0 -2
- package/dist/services/discovery/opencode-discovery.js +0 -2
- package/dist/services/discovery/static.js +0 -31
- package/dist/services/discovery/zcode-discovery.js +0 -2
- package/dist/services/executor-onboarding.js +0 -56
- package/dist/services/harness-discovery.js +0 -29
- package/dist/services/harness-doctor.js +0 -75
- package/dist/services/harness-probe-effects.js +0 -51
- package/dist/services/harness-version-probe.js +0 -100
- package/dist/services/hybrid-contracts.js +0 -455
- package/dist/services/installation-lock.js +0 -92
- package/dist/services/model-catalog.js +0 -109
- package/dist/services/model-discovery.js +0 -46
- package/dist/services/operator-profiles.js +0 -236
- package/dist/services/release-archive-producer.js +0 -52
- package/dist/services/release-archive.js +0 -143
- package/dist/services/release-cache.js +0 -142
- package/dist/services/release-compatibility.js +0 -18
- package/dist/services/release-download.js +0 -67
- package/dist/services/release-envelope.js +0 -104
- package/dist/services/release-manifest.js +0 -45
- package/dist/services/release-payload.js +0 -114
- package/dist/services/release-producer.js +0 -63
- package/dist/services/release-provenance.js +0 -160
- package/dist/services/release-registry.js +0 -66
- package/dist/services/release-source.js +0 -194
- 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
|
-
}
|
package/dist/cli/completion.js
DELETED
|
@@ -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
|
-
}
|
package/dist/cli/error-output.js
DELETED
|
@@ -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
|
-
}
|
package/dist/cli/harnesses.js
DELETED
|
@@ -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
|
-
}
|
package/dist/cli/models.js
DELETED
|
@@ -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
|
-
}
|