mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-08-28 05:27:41 +00:00
Merge remote-tracking branch 'origin/main'
# Conflicts: # lib/crates/fabro-server/src/lib.rs # lib/crates/fabro-server/src/serve.rs # lib/crates/fabro-server/src/server.rs
This commit is contained in:
commit
37cd8ff45f
188 changed files with 3753 additions and 3798 deletions
88
Cargo.lock
generated
88
Cargo.lock
generated
|
|
@ -1505,7 +1505,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fabro-agent"
|
||||
version = "0.211.0-nightly.1"
|
||||
version = "0.212.0-nightly.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
|
|
@ -1541,9 +1541,10 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fabro-api"
|
||||
version = "0.211.0-nightly.1"
|
||||
version = "0.212.0-nightly.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"fabro-config",
|
||||
"fabro-types",
|
||||
"openapiv3",
|
||||
"prettyplease",
|
||||
|
|
@ -1560,7 +1561,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fabro-auth"
|
||||
version = "0.211.0-nightly.1"
|
||||
version = "0.212.0-nightly.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
|
|
@ -1581,7 +1582,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fabro-checkpoint"
|
||||
version = "0.211.0-nightly.1"
|
||||
version = "0.212.0-nightly.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"fabro-store",
|
||||
|
|
@ -1596,7 +1597,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fabro-cli"
|
||||
version = "0.211.0-nightly.1"
|
||||
version = "0.212.0-nightly.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assert_cmd",
|
||||
|
|
@ -1686,7 +1687,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fabro-client"
|
||||
version = "0.211.0-nightly.1"
|
||||
version = "0.212.0-nightly.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
|
|
@ -1713,7 +1714,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fabro-config"
|
||||
version = "0.211.0-nightly.1"
|
||||
version = "0.212.0-nightly.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
|
|
@ -1726,6 +1727,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_json",
|
||||
"strsim 0.11.1",
|
||||
"temp-env",
|
||||
"tempfile",
|
||||
"thiserror 2.0.18",
|
||||
"toml 0.8.23",
|
||||
|
|
@ -1735,7 +1737,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fabro-core"
|
||||
version = "0.211.0-nightly.1"
|
||||
version = "0.212.0-nightly.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"fabro-types",
|
||||
|
|
@ -1750,7 +1752,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fabro-devcontainer"
|
||||
version = "0.211.0-nightly.1"
|
||||
version = "0.212.0-nightly.0"
|
||||
dependencies = [
|
||||
"fabro-http",
|
||||
"fabro-util",
|
||||
|
|
@ -1766,7 +1768,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fabro-github"
|
||||
version = "0.211.0-nightly.1"
|
||||
version = "0.212.0-nightly.0"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"chrono",
|
||||
|
|
@ -1782,7 +1784,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fabro-graphviz"
|
||||
version = "0.211.0-nightly.1"
|
||||
version = "0.212.0-nightly.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"fabro-types",
|
||||
|
|
@ -1796,7 +1798,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fabro-hooks"
|
||||
version = "0.211.0-nightly.1"
|
||||
version = "0.212.0-nightly.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"fabro-agent",
|
||||
|
|
@ -1819,7 +1821,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fabro-http"
|
||||
version = "0.211.0-nightly.1"
|
||||
version = "0.212.0-nightly.0"
|
||||
dependencies = [
|
||||
"http",
|
||||
"reqwest 0.13.2",
|
||||
|
|
@ -1828,7 +1830,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fabro-install"
|
||||
version = "0.211.0-nightly.1"
|
||||
version = "0.212.0-nightly.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
|
|
@ -1842,7 +1844,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fabro-interview"
|
||||
version = "0.211.0-nightly.1"
|
||||
version = "0.212.0-nightly.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"dialoguer",
|
||||
|
|
@ -1856,7 +1858,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fabro-llm"
|
||||
version = "0.211.0-nightly.1"
|
||||
version = "0.212.0-nightly.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
|
|
@ -1886,7 +1888,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fabro-macros"
|
||||
version = "0.211.0-nightly.1"
|
||||
version = "0.212.0-nightly.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -1895,7 +1897,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fabro-mcp"
|
||||
version = "0.211.0-nightly.1"
|
||||
version = "0.212.0-nightly.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"fabro-config",
|
||||
|
|
@ -1911,7 +1913,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fabro-model"
|
||||
version = "0.211.0-nightly.1"
|
||||
version = "0.212.0-nightly.0"
|
||||
dependencies = [
|
||||
"insta",
|
||||
"serde",
|
||||
|
|
@ -1921,7 +1923,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fabro-oauth"
|
||||
version = "0.211.0-nightly.1"
|
||||
version = "0.212.0-nightly.0"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"base64",
|
||||
|
|
@ -1940,7 +1942,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fabro-proc"
|
||||
version = "0.211.0-nightly.1"
|
||||
version = "0.212.0-nightly.0"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
|
|
@ -1949,7 +1951,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fabro-retro"
|
||||
version = "0.211.0-nightly.1"
|
||||
version = "0.212.0-nightly.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
|
|
@ -1967,7 +1969,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fabro-sandbox"
|
||||
version = "0.211.0-nightly.1"
|
||||
version = "0.212.0-nightly.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
|
|
@ -1999,7 +2001,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fabro-server"
|
||||
version = "0.211.0-nightly.1"
|
||||
version = "0.212.0-nightly.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
|
|
@ -2050,6 +2052,7 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
"rand 0.9.4",
|
||||
"regex",
|
||||
"reqwest 0.12.28",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
|
@ -2076,7 +2079,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fabro-slack"
|
||||
version = "0.211.0-nightly.1"
|
||||
version = "0.212.0-nightly.0"
|
||||
dependencies = [
|
||||
"fabro-http",
|
||||
"fabro-interview",
|
||||
|
|
@ -2095,14 +2098,14 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fabro-spa"
|
||||
version = "0.211.0-nightly.1"
|
||||
version = "0.212.0-nightly.0"
|
||||
dependencies = [
|
||||
"rust-embed",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fabro-store"
|
||||
version = "0.211.0-nightly.1"
|
||||
version = "0.212.0-nightly.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
|
|
@ -2128,7 +2131,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fabro-telemetry"
|
||||
version = "0.211.0-nightly.1"
|
||||
version = "0.212.0-nightly.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
|
|
@ -2153,7 +2156,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fabro-template"
|
||||
version = "0.211.0-nightly.1"
|
||||
version = "0.212.0-nightly.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"fabro-util",
|
||||
|
|
@ -2165,7 +2168,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fabro-test"
|
||||
version = "0.211.0-nightly.1"
|
||||
version = "0.212.0-nightly.0"
|
||||
dependencies = [
|
||||
"assert_cmd",
|
||||
"axum",
|
||||
|
|
@ -2187,7 +2190,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fabro-tracker"
|
||||
version = "0.211.0-nightly.1"
|
||||
version = "0.212.0-nightly.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"fabro-github",
|
||||
|
|
@ -2200,7 +2203,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fabro-types"
|
||||
version = "0.211.0-nightly.1"
|
||||
version = "0.212.0-nightly.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
|
|
@ -2221,7 +2224,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fabro-util"
|
||||
version = "0.211.0-nightly.1"
|
||||
version = "0.212.0-nightly.0"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"anyhow",
|
||||
|
|
@ -2243,7 +2246,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fabro-validate"
|
||||
version = "0.211.0-nightly.1"
|
||||
version = "0.212.0-nightly.0"
|
||||
dependencies = [
|
||||
"fabro-graphviz",
|
||||
"fabro-model",
|
||||
|
|
@ -2253,7 +2256,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fabro-vault"
|
||||
version = "0.211.0-nightly.1"
|
||||
version = "0.212.0-nightly.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"serde",
|
||||
|
|
@ -2264,7 +2267,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fabro-workflow"
|
||||
version = "0.211.0-nightly.1"
|
||||
version = "0.212.0-nightly.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assert_cmd",
|
||||
|
|
@ -6421,6 +6424,15 @@ dependencies = [
|
|||
"xattr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "temp-env"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96374855068f47402c3121c6eed88d29cb1de8f3ab27090e273e420bdabcf050"
|
||||
dependencies = [
|
||||
"parking_lot",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.26.0"
|
||||
|
|
@ -6900,7 +6912,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "twin-github"
|
||||
version = "0.211.0-nightly.1"
|
||||
version = "0.212.0-nightly.0"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"base64",
|
||||
|
|
@ -6919,7 +6931,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "twin-openai"
|
||||
version = "0.211.0-nightly.1"
|
||||
version = "0.212.0-nightly.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-stream",
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ resolver = "2"
|
|||
|
||||
[workspace.package]
|
||||
edition = "2021"
|
||||
version = "0.211.0-nightly.1"
|
||||
version = "0.212.0-nightly.0"
|
||||
license = "MIT"
|
||||
|
||||
[workspace.dependencies]
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
import type { PaginationMeta } from "@qltysh/fabro-api-client";
|
||||
|
||||
/**
|
||||
* Opaque settings payload returned by `/api/v1/runs/:id/settings`. Mirrors the
|
||||
* v2 `SettingsFile` shape in `lib/crates/fabro-types/src/settings/tree.rs`,
|
||||
* with secret-bearing subtrees dropped before serialization. Treated as a
|
||||
* loose JSON object on the web side — consumers only render it.
|
||||
* Opaque persisted `SettingsLayer` payload returned by `/api/v1/runs/:id/settings`.
|
||||
* Treated as a loose JSON object on the web side — consumers only render it.
|
||||
*/
|
||||
export type RunSettings = Record<string, unknown>;
|
||||
export type RunSettingsLayer = Record<string, unknown>;
|
||||
|
||||
export interface WorkflowScheduleSummary {
|
||||
expression: string;
|
||||
|
|
@ -35,6 +33,6 @@ export interface WorkflowDetailResponse {
|
|||
slug: string;
|
||||
description: string;
|
||||
filename: string;
|
||||
settings: RunSettings;
|
||||
settings: RunSettingsLayer;
|
||||
graph: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,14 +6,14 @@ import { apiJson } from "../api";
|
|||
import { isVisibleStage } from "../data/runs";
|
||||
import { formatDurationSecs } from "../lib/format";
|
||||
import type { PaginatedRunStageList } from "@qltysh/fabro-api-client";
|
||||
import type { RunSettings } from "../lib/workflow-api";
|
||||
import type { RunSettingsLayer } from "../lib/workflow-api";
|
||||
|
||||
export const handle = { wide: true };
|
||||
|
||||
export async function loader({ request, params }: any) {
|
||||
const [{ data: apiStages }, settings] = await Promise.all([
|
||||
apiJson<PaginatedRunStageList>(`/runs/${params.id}/stages`, { request }),
|
||||
apiJson<RunSettings>(`/runs/${params.id}/settings`, { request }),
|
||||
apiJson<RunSettingsLayer>(`/runs/${params.id}/settings`, { request }),
|
||||
]);
|
||||
const stages: Stage[] = apiStages.filter((s) => isVisibleStage(s.id)).map((s) => ({
|
||||
id: s.id,
|
||||
|
|
|
|||
|
|
@ -1,13 +1,7 @@
|
|||
import type { ServerSettings } from "@qltysh/fabro-api-client";
|
||||
import { apiJson } from "../api";
|
||||
import { CollapsibleFile } from "../components/collapsible-file";
|
||||
|
||||
/**
|
||||
* Opaque server settings payload returned by `/api/v1/settings`. Mirrors the
|
||||
* v2 `SettingsFile` shape with secret-bearing subtrees dropped before
|
||||
* serialization. The UI only renders it as JSON.
|
||||
*/
|
||||
type ServerSettings = Record<string, unknown>;
|
||||
|
||||
export function meta({}: any) {
|
||||
return [{ title: "Settings — Fabro" }];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,24 @@
|
|||
import { ChevronRightIcon } from "@heroicons/react/20/solid";
|
||||
import { Link, Outlet, useLocation, useParams } from "react-router";
|
||||
import { apiJsonOrNull } from "../api";
|
||||
import type { RunSettings, WorkflowDetailResponse as ApiWorkflowDetail } from "../lib/workflow-api";
|
||||
import type {
|
||||
RunSettingsLayer,
|
||||
WorkflowDetailResponse as ApiWorkflowDetail,
|
||||
} from "../lib/workflow-api";
|
||||
|
||||
export interface WorkflowEntry {
|
||||
name: string;
|
||||
slug: string;
|
||||
description: string;
|
||||
filename: string;
|
||||
settings: RunSettings;
|
||||
settings: RunSettingsLayer;
|
||||
graph: string;
|
||||
}
|
||||
|
||||
// Static sample data used by the `workflow-definition` index route for the
|
||||
// hardcoded showcase workflows. Shape mirrors the v2 `SettingsFile` JSON
|
||||
// returned by `/api/v1/runs/:id/settings` (see the Rust
|
||||
// `fabro_types::settings::SettingsFile` type). Fields are opaque to the
|
||||
// `RunSettings` TypeScript type, which is a bare `Record<string, unknown>`.
|
||||
// hardcoded showcase workflows. Shape mirrors the persisted `SettingsLayer`
|
||||
// JSON returned by `/api/v1/runs/:id/settings`. Fields are opaque to the
|
||||
// `RunSettingsLayer` TypeScript type, which is a bare `Record<string, unknown>`.
|
||||
export const workflowData: Record<string, WorkflowEntry> = {
|
||||
fix_build: {
|
||||
name: "Fix Build",
|
||||
|
|
|
|||
|
|
@ -3,9 +3,16 @@ set -euo pipefail
|
|||
|
||||
cd "$(dirname "$0")/../.."
|
||||
|
||||
symbol_allowlist=(
|
||||
server_symbol_allowlist=(
|
||||
"lib/crates/fabro-cli/src/local_server.rs"
|
||||
"lib/crates/fabro-cli/src/commands/run/runner.rs"
|
||||
"lib/crates/fabro-cli/src/commands/pr/mod.rs"
|
||||
"lib/crates/fabro-cli/src/commands/pr/create.rs"
|
||||
)
|
||||
|
||||
storage_allowlist=(
|
||||
"lib/crates/fabro-cli/src/commands/install.rs"
|
||||
"lib/crates/fabro-cli/src/commands/uninstall.rs"
|
||||
"lib/crates/fabro-cli/src/commands/run/runner.rs"
|
||||
"lib/crates/fabro-cli/src/commands/pr/mod.rs"
|
||||
"lib/crates/fabro-cli/src/commands/pr/create.rs"
|
||||
|
|
@ -49,11 +56,19 @@ fail=0
|
|||
|
||||
while IFS= read -r path; do
|
||||
[[ -z "$path" ]] && continue
|
||||
if ! in_array "$path" "${symbol_allowlist[@]}"; then
|
||||
if ! in_array "$path" "${server_symbol_allowlist[@]}"; then
|
||||
echo "boundary check failed: gated server symbol used outside allowlist: $path" >&2
|
||||
fail=1
|
||||
fi
|
||||
done < <(find_matches 'fabro_config::resolve_server_from_file|fabro_config::resolve_server\b|Storage::new')
|
||||
done < <(find_matches 'fabro_config::resolve_server_from_file|fabro_config::resolve_server\b|fabro_config::ServerSettings::from_layer\b|fabro_config::ServerSettings::resolve\b|ServerSettings::from_layer\b|ServerSettings::resolve\b')
|
||||
|
||||
while IFS= read -r path; do
|
||||
[[ -z "$path" ]] && continue
|
||||
if ! in_array "$path" "${storage_allowlist[@]}"; then
|
||||
echo "boundary check failed: Storage::new used outside allowlist: $path" >&2
|
||||
fail=1
|
||||
fi
|
||||
done < <(find_matches 'Storage::new')
|
||||
|
||||
while IFS= read -r path; do
|
||||
[[ -z "$path" ]] && continue
|
||||
|
|
|
|||
|
|
@ -20,7 +20,8 @@ while IFS= read -r match; do
|
|||
|
||||
case "$path:$line" in
|
||||
"lib/crates/fabro-telemetry/src/spawn.rs:std::env::set_var(key, value);" | \
|
||||
"lib/crates/fabro-telemetry/src/spawn.rs:std::env::remove_var(key);")
|
||||
"lib/crates/fabro-telemetry/src/spawn.rs:std::env::remove_var(key);" | \
|
||||
'lib/crates/fabro-server/src/install.rs:std::env::set_var("FABRO_TEST_IN_MEMORY_STORE", "1");')
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@
|
|||
|
||||
| Path | Direct dependency | Why it still exists | Suggested handling |
|
||||
| --- | --- | --- | --- |
|
||||
| `lib/crates/fabro-cli/src/commands/store/dump.rs` test module | `event::{Event, append_event}` | Unit tests synthesize workflow events directly. | Low priority; keep until a lighter-weight event fixture helper exists. |
|
||||
| `lib/crates/fabro-cli/src/commands/dump.rs` test module | `event::{Event, append_event}` | Unit tests synthesize workflow events directly. | Low priority; keep until a lighter-weight event fixture helper exists. |
|
||||
| `lib/crates/fabro-cli/src/commands/run/wait.rs` test module | `outcome::StageStatus`, `records::Conclusion`, `run_status::RunStatusRecord` | Output tests construct workflow-owned records directly. | Replace with shared fixture builders once status/conclusion DTOs move out. |
|
||||
| `lib/crates/fabro-cli/src/commands/run/run_progress/mod.rs` test module | `event::{Event, RunNoticeLevel, to_run_event, to_run_event_at}`, `outcome::billed_model_usage_from_llm` | Progress tests build engine events directly. | Replace with shared event fixture helpers after event DTO extraction. |
|
||||
| `lib/crates/fabro-cli/src/commands/run/run_progress/event.rs` test module | `event::{Event, to_run_event}` | Event rendering tests depend on engine event constructors. | Replace with shared event fixture helpers after event DTO extraction. |
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ These paths are local runtime state, not canonical event projections.
|
|||
These names are still real, but they are no longer live scratch files by default:
|
||||
|
||||
- Metadata branch files such as `run.json`, `start.json`, `checkpoint.json`, and `retro.json`
|
||||
- `fabro store dump` exports such as `run.json`, `start.json`, `status.json`, `checkpoint.json`, `conclusion.json`, `retro.json`, `events.jsonl`, and per-node prompt/response/status/stdout/stderr files
|
||||
- `fabro dump` exports such as `run.json`, `start.json`, `status.json`, `checkpoint.json`, `conclusion.json`, `retro.json`, `events.jsonl`, and per-node prompt/response/status/stdout/stderr files
|
||||
- Retro-agent temp uploads named `progress.jsonl`, `checkpoint.json`, `run.json`, and `start.json` inside the retro sandbox
|
||||
|
||||
## Notes
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ When an agent or prompt node finishes, Fabro captures its response text and prod
|
|||
|
||||
## Response capture
|
||||
|
||||
After an agent or prompt node completes, Fabro captures the full response text and persists it to `stages/{node_id}@{visit}/response.md` in metadata snapshots and `fabro store dump` output. It also writes the final outcome (status, context updates, routing directives) to `stages/{node_id}@{visit}/status.json`.
|
||||
After an agent or prompt node completes, Fabro captures the full response text and persists it to `stages/{node_id}@{visit}/response.md` in metadata snapshots and `fabro dump` output. It also writes the final outcome (status, context updates, routing directives) to `stages/{node_id}@{visit}/status.json`.
|
||||
|
||||
## Context updates
|
||||
|
||||
|
|
@ -92,7 +92,7 @@ review -> approve [label="Approve"]
|
|||
|
||||
## Output logging
|
||||
|
||||
Fabro writes several files per stage to `stages/{node_id}@{visit}/` in metadata snapshots and `fabro store dump` output:
|
||||
Fabro writes several files per stage to `stages/{node_id}@{visit}/` in metadata snapshots and `fabro dump` output:
|
||||
|
||||
| File | Contents |
|
||||
|---|---|
|
||||
|
|
|
|||
|
|
@ -295,4 +295,4 @@ Use prompt nodes for analysis, classification, and summarization tasks where too
|
|||
|
||||
## Prompt logging
|
||||
|
||||
Fabro persists the assembled prompt to `stages/{node_id}@{visit}/prompt.md` in metadata snapshots and `fabro store dump` output for every agent and prompt stage. This includes the preamble (if any) and the expanded prompt text. Use these files for debugging when an agent behaves unexpectedly.
|
||||
Fabro persists the assembled prompt to `stages/{node_id}@{visit}/prompt.md` in metadata snapshots and `fabro dump` output for every agent and prompt stage. This includes the preamble (if any) and the expanded prompt text. Use these files for debugging when an agent behaves unexpectedly.
|
||||
|
|
|
|||
|
|
@ -1376,7 +1376,7 @@ paths:
|
|||
operationId: retrieveRunSettings
|
||||
tags: [Run Internals]
|
||||
summary: Retrieve Run Settings
|
||||
description: Returns the structured settings used to launch this run.
|
||||
description: Returns the persisted `SettingsLayer` used to launch this run.
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/RunId"
|
||||
responses:
|
||||
|
|
@ -1385,7 +1385,7 @@ paths:
|
|||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/RunSettings"
|
||||
$ref: "#/components/schemas/RunSettingsLayer"
|
||||
"404":
|
||||
description: Run not found
|
||||
content:
|
||||
|
|
@ -2012,22 +2012,11 @@ paths:
|
|||
tags: [Settings]
|
||||
summary: Retrieve Server Settings
|
||||
description: >
|
||||
Returns the server settings view selected by the optional `view` query
|
||||
parameter. `view=layer` (the default) returns the current sparse
|
||||
redacted `SettingsLayer` payload. `view=resolved` returns the server's
|
||||
dense resolved settings payload after applying the same redaction
|
||||
policy.
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/SettingsView"
|
||||
Returns the server's current in-memory settings view as the typed
|
||||
`ServerSettings` payload.
|
||||
responses:
|
||||
"200":
|
||||
description: Server settings
|
||||
headers:
|
||||
X-Fabro-Settings-View:
|
||||
description: Present with value `resolved` when the response body is the dense resolved settings view.
|
||||
schema:
|
||||
type: string
|
||||
enum: [resolved]
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
|
|
@ -2069,16 +2058,6 @@ components:
|
|||
type: string
|
||||
example: nightly-build
|
||||
|
||||
SettingsView:
|
||||
name: view
|
||||
in: query
|
||||
required: false
|
||||
description: Selects the server settings representation to return.
|
||||
schema:
|
||||
type: string
|
||||
enum: [layer, resolved]
|
||||
default: layer
|
||||
|
||||
StageId:
|
||||
name: stageId
|
||||
in: path
|
||||
|
|
@ -5148,32 +5127,328 @@ components:
|
|||
# ── Settings Schemas ─────────────────────────────────────────────────
|
||||
|
||||
ServerSettings:
|
||||
description: |
|
||||
Redacted server settings payload.
|
||||
|
||||
The `/api/v1/settings` endpoint supports two response shapes:
|
||||
|
||||
- `view=layer` (default): the sparse redacted `SettingsLayer` shape
|
||||
- `view=resolved`: the dense resolved `Settings` shape
|
||||
|
||||
Both views drop the same exact operational path:
|
||||
|
||||
- `server.listen`
|
||||
|
||||
For non-redacted `InterpString` fields, the wire payload preserves the
|
||||
unresolved source/template string rather than any environment-resolved
|
||||
secret value.
|
||||
description: Current in-memory server settings view.
|
||||
type: object
|
||||
additionalProperties: true
|
||||
required: [server, features]
|
||||
properties:
|
||||
server:
|
||||
$ref: "#/components/schemas/ServerNamespace"
|
||||
features:
|
||||
$ref: "#/components/schemas/FeaturesNamespace"
|
||||
|
||||
RunSettings:
|
||||
ServerNamespace:
|
||||
type: object
|
||||
required:
|
||||
- listen
|
||||
- api
|
||||
- web
|
||||
- auth
|
||||
- ip_allowlist
|
||||
- storage
|
||||
- artifacts
|
||||
- slatedb
|
||||
- scheduler
|
||||
- logging
|
||||
- integrations
|
||||
properties:
|
||||
listen:
|
||||
$ref: "#/components/schemas/ServerListenSettings"
|
||||
api:
|
||||
$ref: "#/components/schemas/ServerApiSettings"
|
||||
web:
|
||||
$ref: "#/components/schemas/ServerWebSettings"
|
||||
auth:
|
||||
$ref: "#/components/schemas/ServerAuthSettings"
|
||||
ip_allowlist:
|
||||
$ref: "#/components/schemas/ServerIpAllowlistSettings"
|
||||
storage:
|
||||
$ref: "#/components/schemas/ServerStorageSettings"
|
||||
artifacts:
|
||||
$ref: "#/components/schemas/ServerArtifactsSettings"
|
||||
slatedb:
|
||||
$ref: "#/components/schemas/ServerSlateDbSettings"
|
||||
scheduler:
|
||||
$ref: "#/components/schemas/ServerSchedulerSettings"
|
||||
logging:
|
||||
$ref: "#/components/schemas/ServerLoggingSettings"
|
||||
integrations:
|
||||
$ref: "#/components/schemas/ServerIntegrationsSettings"
|
||||
|
||||
FeaturesNamespace:
|
||||
type: object
|
||||
required: [session_sandboxes]
|
||||
properties:
|
||||
session_sandboxes:
|
||||
type: boolean
|
||||
|
||||
ServerListenSettings:
|
||||
oneOf:
|
||||
- $ref: "#/components/schemas/ServerListenTcpSettings"
|
||||
- $ref: "#/components/schemas/ServerListenUnixSettings"
|
||||
|
||||
ServerListenTcpSettings:
|
||||
type: object
|
||||
required: [type, address]
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
enum: [tcp]
|
||||
address:
|
||||
type: string
|
||||
|
||||
ServerListenUnixSettings:
|
||||
type: object
|
||||
required: [type, path]
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
enum: [unix]
|
||||
path:
|
||||
type: string
|
||||
|
||||
ServerApiSettings:
|
||||
type: object
|
||||
required: [url]
|
||||
properties:
|
||||
url:
|
||||
type: ["string", "null"]
|
||||
|
||||
ServerWebSettings:
|
||||
type: object
|
||||
required: [enabled, url]
|
||||
properties:
|
||||
enabled:
|
||||
type: boolean
|
||||
url:
|
||||
type: string
|
||||
|
||||
ServerAuthSettings:
|
||||
type: object
|
||||
required: [methods, github]
|
||||
properties:
|
||||
methods:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/ServerAuthMethod"
|
||||
github:
|
||||
$ref: "#/components/schemas/ServerAuthGithubSettings"
|
||||
|
||||
ServerAuthMethod:
|
||||
type: string
|
||||
enum: [dev-token, github]
|
||||
|
||||
ServerAuthGithubSettings:
|
||||
type: object
|
||||
required: [allowed_usernames]
|
||||
properties:
|
||||
allowed_usernames:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
|
||||
ServerIpAllowlistSettings:
|
||||
type: object
|
||||
required: [entries, trusted_proxy_count]
|
||||
properties:
|
||||
entries:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/IpAllowEntry"
|
||||
trusted_proxy_count:
|
||||
type: integer
|
||||
|
||||
ServerIpAllowlistOverrideSettings:
|
||||
type: object
|
||||
required: [entries, trusted_proxy_count]
|
||||
properties:
|
||||
entries:
|
||||
type: ["array", "null"]
|
||||
items:
|
||||
$ref: "#/components/schemas/IpAllowEntry"
|
||||
trusted_proxy_count:
|
||||
type: ["integer", "null"]
|
||||
|
||||
IpAllowEntry:
|
||||
oneOf:
|
||||
- $ref: "#/components/schemas/LiteralIpAllowEntry"
|
||||
- $ref: "#/components/schemas/GitHubMetaHooksEntry"
|
||||
|
||||
LiteralIpAllowEntry:
|
||||
type: object
|
||||
required: [Literal]
|
||||
properties:
|
||||
Literal:
|
||||
type: string
|
||||
|
||||
GitHubMetaHooksEntry:
|
||||
type: string
|
||||
enum: [GitHubMetaHooks]
|
||||
|
||||
ServerStorageSettings:
|
||||
type: object
|
||||
required: [root]
|
||||
properties:
|
||||
root:
|
||||
type: string
|
||||
|
||||
ServerArtifactsSettings:
|
||||
type: object
|
||||
required: [prefix, store]
|
||||
properties:
|
||||
prefix:
|
||||
type: string
|
||||
store:
|
||||
$ref: "#/components/schemas/ObjectStoreSettings"
|
||||
|
||||
ServerSlateDbSettings:
|
||||
type: object
|
||||
required: [prefix, store, flush_interval, disk_cache]
|
||||
properties:
|
||||
prefix:
|
||||
type: string
|
||||
store:
|
||||
$ref: "#/components/schemas/ObjectStoreSettings"
|
||||
flush_interval:
|
||||
type: string
|
||||
disk_cache:
|
||||
type: boolean
|
||||
|
||||
ObjectStoreSettings:
|
||||
oneOf:
|
||||
- $ref: "#/components/schemas/ObjectStoreLocalSettings"
|
||||
- $ref: "#/components/schemas/ObjectStoreS3Settings"
|
||||
|
||||
ObjectStoreLocalSettings:
|
||||
type: object
|
||||
required: [type, root]
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
enum: [local]
|
||||
root:
|
||||
type: string
|
||||
|
||||
ObjectStoreS3Settings:
|
||||
type: object
|
||||
required: [type, bucket, region, endpoint, path_style]
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
enum: [s3]
|
||||
bucket:
|
||||
type: string
|
||||
region:
|
||||
type: string
|
||||
endpoint:
|
||||
type: ["string", "null"]
|
||||
path_style:
|
||||
type: boolean
|
||||
|
||||
ServerSchedulerSettings:
|
||||
type: object
|
||||
required: [max_concurrent_runs]
|
||||
properties:
|
||||
max_concurrent_runs:
|
||||
type: integer
|
||||
|
||||
ServerLoggingSettings:
|
||||
type: object
|
||||
required: [level]
|
||||
properties:
|
||||
level:
|
||||
type: ["string", "null"]
|
||||
|
||||
ServerIntegrationsSettings:
|
||||
type: object
|
||||
required: [github, slack, discord, teams]
|
||||
properties:
|
||||
github:
|
||||
$ref: "#/components/schemas/GithubIntegrationSettings"
|
||||
slack:
|
||||
$ref: "#/components/schemas/SlackIntegrationSettings"
|
||||
discord:
|
||||
$ref: "#/components/schemas/DiscordIntegrationSettings"
|
||||
teams:
|
||||
$ref: "#/components/schemas/TeamsIntegrationSettings"
|
||||
|
||||
GithubIntegrationSettings:
|
||||
type: object
|
||||
required:
|
||||
- enabled
|
||||
- strategy
|
||||
- app_id
|
||||
- client_id
|
||||
- slug
|
||||
- permissions
|
||||
- webhooks
|
||||
properties:
|
||||
enabled:
|
||||
type: boolean
|
||||
strategy:
|
||||
$ref: "#/components/schemas/GithubIntegrationStrategy"
|
||||
app_id:
|
||||
type: ["string", "null"]
|
||||
client_id:
|
||||
type: ["string", "null"]
|
||||
slug:
|
||||
type: ["string", "null"]
|
||||
permissions:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
webhooks:
|
||||
oneOf:
|
||||
- $ref: "#/components/schemas/IntegrationWebhooksSettings"
|
||||
- type: "null"
|
||||
|
||||
GithubIntegrationStrategy:
|
||||
type: string
|
||||
enum: [token, app]
|
||||
|
||||
SlackIntegrationSettings:
|
||||
type: object
|
||||
required: [enabled, default_channel]
|
||||
properties:
|
||||
enabled:
|
||||
type: boolean
|
||||
default_channel:
|
||||
type: ["string", "null"]
|
||||
|
||||
DiscordIntegrationSettings:
|
||||
type: object
|
||||
required: [enabled]
|
||||
properties:
|
||||
enabled:
|
||||
type: boolean
|
||||
|
||||
TeamsIntegrationSettings:
|
||||
type: object
|
||||
required: [enabled]
|
||||
properties:
|
||||
enabled:
|
||||
type: boolean
|
||||
|
||||
IntegrationWebhooksSettings:
|
||||
type: object
|
||||
required: [strategy, ip_allowlist]
|
||||
properties:
|
||||
strategy:
|
||||
oneOf:
|
||||
- $ref: "#/components/schemas/WebhookStrategy"
|
||||
- type: "null"
|
||||
ip_allowlist:
|
||||
oneOf:
|
||||
- $ref: "#/components/schemas/ServerIpAllowlistOverrideSettings"
|
||||
- type: "null"
|
||||
|
||||
WebhookStrategy:
|
||||
type: string
|
||||
enum: [tailscale_funnel, server_url]
|
||||
|
||||
RunSettingsLayer:
|
||||
description: |
|
||||
The merged, persisted v2 `[run]` subtree for a specific run, serialized
|
||||
as the wrapping `SettingsFile` shape (so `settings.run.*` holds the run
|
||||
config). Matches `fabro_types::settings::SettingsFile` minus secret
|
||||
subtrees, identical to ServerSettings' redaction rules.
|
||||
|
||||
See `lib/crates/fabro-types/src/settings/run.rs` for the full type.
|
||||
The persisted `SettingsLayer` used for a specific run, serialized as-is.
|
||||
This matches the stored run manifest shape rather than a resolved view.
|
||||
type: object
|
||||
additionalProperties: true
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
---
|
||||
title: "Store dump export command"
|
||||
title: "Dump export command"
|
||||
date: "2026-03-29"
|
||||
---
|
||||
|
||||
## `fabro store dump`
|
||||
## `fabro dump`
|
||||
|
||||
A new `fabro store dump` command exports the contents of the run store to a human-readable format for debugging and inspection. This is useful for diagnosing issues with run state, verifying data integrity after migrations, or extracting run data for external analysis.
|
||||
A new `fabro dump` command exports the contents of the run store to a human-readable format for debugging and inspection. This is useful for diagnosing issues with run state, verifying data integrity after migrations, or extracting run data for external analysis.
|
||||
|
||||
```bash
|
||||
fabro store dump <run-id>
|
||||
fabro dump <run-id>
|
||||
```
|
||||
|
||||
## More
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ jq '{from: .properties.from_node, to: .properties.to_node, label: .properties.la
|
|||
<(fabro logs 01JKXYZ...) | head
|
||||
```
|
||||
|
||||
If you need files on disk for offline analysis, `fabro store dump` exports `events.jsonl` plus run-state projections.
|
||||
If you need files on disk for offline analysis, `fabro dump` exports `events.jsonl` plus run-state projections.
|
||||
|
||||
## Event categories
|
||||
|
||||
|
|
@ -132,6 +132,6 @@ Post-run analysis surfaces include:
|
|||
|---|---|
|
||||
| `fabro logs <RUN>` | Full event envelope stream as NDJSON |
|
||||
| `fabro inspect <RUN>` | Current durable run state, including run/start/checkpoint/conclusion records |
|
||||
| `fabro store dump --output <DIR> <RUN>` | Exported `events.jsonl` plus reconstructed JSON and node files |
|
||||
| `fabro dump --output <DIR> <RUN>` | Exported `events.jsonl` plus reconstructed JSON and node files |
|
||||
|
||||
See [retros](/execution/retros), [stages](/api-reference/run-internals/list-run-stages), and [turns](/api-reference/run-internals/list-stage-turns) for higher-level analysis views built on top of this event stream.
|
||||
|
|
|
|||
|
|
@ -143,4 +143,4 @@ Retros are also available via the REST API. See the [list retros](/api-reference
|
|||
|
||||
## Storage
|
||||
|
||||
Retros are stored in durable run state. If you need files on disk, `fabro store dump` materializes retro text under `stages/retro/` alongside `run.json`, stage files, and the rest of the exported run data.
|
||||
Retros are stored in durable run state. If you need files on disk, `fabro dump` materializes retro text under `stages/retro/` alongside `run.json`, stage files, and the rest of the exported run data.
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -947,13 +947,13 @@ fabro secret rm ANTHROPIC_API_KEY
|
|||
|
||||
---
|
||||
|
||||
## `fabro store dump`
|
||||
## `fabro dump`
|
||||
|
||||
Export the contents of a run's store-backed state to a directory for debugging and inspection.
|
||||
Export the contents of a run's durable state to a directory for debugging and inspection.
|
||||
|
||||
```bash
|
||||
fabro store dump <RUN>
|
||||
fabro store dump abc123 -o ./debug-output
|
||||
fabro dump <RUN>
|
||||
fabro dump abc123 -o ./debug-output
|
||||
```
|
||||
|
||||
| Argument / Flag | Description |
|
||||
|
|
|
|||
|
|
@ -30,18 +30,18 @@ These paths are local runtime state and caches, not the canonical run state.
|
|||
- **`runtime/`** — Local runtime files. Today this is mainly materialized blob payloads under `runtime/blobs/`.
|
||||
- **`nodes/{manager_node}_{visit}/child/`** — Nested scratch directories for manager-loop child workflows.
|
||||
|
||||
Large durable values, event streams, checkpoints, diffs, conclusions, and retros are no longer projected into live scratch by default. Use `fabro logs`, `fabro inspect`, the API, or `fabro store dump` for those surfaces.
|
||||
Large durable values, event streams, checkpoints, diffs, conclusions, and retros are no longer projected into live scratch by default. Use `fabro logs`, `fabro inspect`, the API, or `fabro dump` for those surfaces.
|
||||
|
||||
## Reconstructed and export-only layouts
|
||||
|
||||
Reconstructed metadata branches and `fabro store dump` exports now use the same core layout:
|
||||
Reconstructed metadata branches and `fabro dump` exports now use the same core layout:
|
||||
|
||||
- `run.json` for the current projection snapshot, including the current checkpoint
|
||||
- `graph.fabro` for workflow source
|
||||
- `stages/retro/*.md` for retro prompt/response text
|
||||
- `stages/{node_id}@{visit}/...` for per-stage prompt, response, status, diff, stdout, and stderr files
|
||||
|
||||
`fabro store dump` adds export-only history surfaces on top of that shared layout:
|
||||
`fabro dump` adds export-only history surfaces on top of that shared layout:
|
||||
|
||||
- `events.jsonl` for the durable event stream
|
||||
- `checkpoints/*.json` for checkpoint history snapshots
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ wildcard_imports = "warn"
|
|||
|
||||
[dependencies]
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
fabro-config = { path = "../fabro-config" }
|
||||
fabro-types = { path = "../fabro-types" }
|
||||
progenitor-client = "0.13"
|
||||
regress = "0.10"
|
||||
|
|
|
|||
|
|
@ -171,6 +171,137 @@ fn main() {
|
|||
"fabro_types::status::RunControlAction",
|
||||
&[],
|
||||
),
|
||||
(
|
||||
"RunStatusRecord",
|
||||
"fabro_types::status::RunStatusRecord",
|
||||
&[],
|
||||
),
|
||||
("ServerSettings", "fabro_config::ServerSettings", &[]),
|
||||
(
|
||||
"ServerNamespace",
|
||||
"fabro_types::settings::ServerNamespace",
|
||||
&[],
|
||||
),
|
||||
(
|
||||
"FeaturesNamespace",
|
||||
"fabro_types::settings::FeaturesNamespace",
|
||||
&[],
|
||||
),
|
||||
(
|
||||
"ServerListenSettings",
|
||||
"fabro_types::settings::server::ServerListenSettings",
|
||||
&[],
|
||||
),
|
||||
(
|
||||
"ServerApiSettings",
|
||||
"fabro_types::settings::server::ServerApiSettings",
|
||||
&[],
|
||||
),
|
||||
(
|
||||
"ServerWebSettings",
|
||||
"fabro_types::settings::server::ServerWebSettings",
|
||||
&[],
|
||||
),
|
||||
(
|
||||
"ServerAuthSettings",
|
||||
"fabro_types::settings::server::ServerAuthSettings",
|
||||
&[],
|
||||
),
|
||||
(
|
||||
"ServerAuthMethod",
|
||||
"fabro_types::settings::server::ServerAuthMethod",
|
||||
&[],
|
||||
),
|
||||
(
|
||||
"ServerAuthGithubSettings",
|
||||
"fabro_types::settings::server::ServerAuthGithubSettings",
|
||||
&[],
|
||||
),
|
||||
(
|
||||
"ServerIpAllowlistSettings",
|
||||
"fabro_types::settings::server::ServerIpAllowlistSettings",
|
||||
&[],
|
||||
),
|
||||
(
|
||||
"ServerIpAllowlistOverrideSettings",
|
||||
"fabro_types::settings::server::ServerIpAllowlistOverrideSettings",
|
||||
&[],
|
||||
),
|
||||
(
|
||||
"IpAllowEntry",
|
||||
"fabro_types::settings::server::IpAllowEntry",
|
||||
&[],
|
||||
),
|
||||
(
|
||||
"ServerStorageSettings",
|
||||
"fabro_types::settings::server::ServerStorageSettings",
|
||||
&[],
|
||||
),
|
||||
(
|
||||
"ServerArtifactsSettings",
|
||||
"fabro_types::settings::server::ServerArtifactsSettings",
|
||||
&[],
|
||||
),
|
||||
(
|
||||
"ServerSlateDbSettings",
|
||||
"fabro_types::settings::server::ServerSlateDbSettings",
|
||||
&[],
|
||||
),
|
||||
(
|
||||
"ObjectStoreSettings",
|
||||
"fabro_types::settings::server::ObjectStoreSettings",
|
||||
&[],
|
||||
),
|
||||
(
|
||||
"ServerSchedulerSettings",
|
||||
"fabro_types::settings::server::ServerSchedulerSettings",
|
||||
&[],
|
||||
),
|
||||
(
|
||||
"ServerLoggingSettings",
|
||||
"fabro_types::settings::server::ServerLoggingSettings",
|
||||
&[],
|
||||
),
|
||||
(
|
||||
"ServerIntegrationsSettings",
|
||||
"fabro_types::settings::server::ServerIntegrationsSettings",
|
||||
&[],
|
||||
),
|
||||
(
|
||||
"GithubIntegrationSettings",
|
||||
"fabro_types::settings::server::GithubIntegrationSettings",
|
||||
&[],
|
||||
),
|
||||
(
|
||||
"GithubIntegrationStrategy",
|
||||
"fabro_types::settings::server::GithubIntegrationStrategy",
|
||||
&[],
|
||||
),
|
||||
(
|
||||
"SlackIntegrationSettings",
|
||||
"fabro_types::settings::server::SlackIntegrationSettings",
|
||||
&[],
|
||||
),
|
||||
(
|
||||
"DiscordIntegrationSettings",
|
||||
"fabro_types::settings::server::DiscordIntegrationSettings",
|
||||
&[],
|
||||
),
|
||||
(
|
||||
"TeamsIntegrationSettings",
|
||||
"fabro_types::settings::server::TeamsIntegrationSettings",
|
||||
&[],
|
||||
),
|
||||
(
|
||||
"IntegrationWebhooksSettings",
|
||||
"fabro_types::settings::server::IntegrationWebhooksSettings",
|
||||
&[],
|
||||
),
|
||||
(
|
||||
"WebhookStrategy",
|
||||
"fabro_types::settings::server::WebhookStrategy",
|
||||
&[],
|
||||
),
|
||||
];
|
||||
for (name, path, impls) in replacements {
|
||||
settings.with_replacement(*name, *path, impls.iter().copied());
|
||||
|
|
|
|||
|
|
@ -14,6 +14,17 @@ mod generated {
|
|||
include!(concat!(env!("OUT_DIR"), "/codegen.rs"));
|
||||
}
|
||||
pub mod types {
|
||||
pub use fabro_config::ServerSettings;
|
||||
pub use fabro_types::settings::server::{
|
||||
DiscordIntegrationSettings, GithubIntegrationSettings, GithubIntegrationStrategy,
|
||||
IntegrationWebhooksSettings, IpAllowEntry, ObjectStoreSettings, ServerApiSettings,
|
||||
ServerArtifactsSettings, ServerAuthGithubSettings, ServerAuthMethod, ServerAuthSettings,
|
||||
ServerIntegrationsSettings, ServerIpAllowlistOverrideSettings, ServerIpAllowlistSettings,
|
||||
ServerListenSettings, ServerLoggingSettings, ServerSchedulerSettings,
|
||||
ServerSlateDbSettings, ServerStorageSettings, ServerWebSettings, SlackIntegrationSettings,
|
||||
TeamsIntegrationSettings, WebhookStrategy,
|
||||
};
|
||||
pub use fabro_types::settings::{FeaturesNamespace, ServerNamespace};
|
||||
pub use fabro_types::status::{
|
||||
BlockedReason, FailureReason, RunControlAction, RunStatus, SuccessReason, TerminalStatus,
|
||||
};
|
||||
|
|
|
|||
78
lib/crates/fabro-api/tests/server_settings_round_trip.rs
Normal file
78
lib/crates/fabro-api/tests/server_settings_round_trip.rs
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
use std::any::{TypeId, type_name};
|
||||
|
||||
use fabro_api::types::{
|
||||
FeaturesNamespace as ApiFeaturesNamespace, ObjectStoreSettings as ApiObjectStoreSettings,
|
||||
ServerNamespace as ApiServerNamespace, ServerSettings as ApiServerSettings,
|
||||
};
|
||||
use fabro_config::{ServerSettings, parse_settings_layer};
|
||||
use fabro_types::settings::server::ObjectStoreSettings;
|
||||
use fabro_types::settings::{FeaturesNamespace, ServerNamespace};
|
||||
|
||||
#[test]
|
||||
fn server_settings_family_reuses_domain_types() {
|
||||
assert_same_type::<ApiServerSettings, ServerSettings>();
|
||||
assert_same_type::<ApiServerNamespace, ServerNamespace>();
|
||||
assert_same_type::<ApiFeaturesNamespace, FeaturesNamespace>();
|
||||
assert_same_type::<ApiObjectStoreSettings, ObjectStoreSettings>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn server_settings_json_matches_openapi_shape() {
|
||||
let layer = parse_settings_layer(
|
||||
r#"
|
||||
_version = 1
|
||||
|
||||
[server.listen]
|
||||
type = "tcp"
|
||||
address = "127.0.0.1:32276"
|
||||
|
||||
[server.api]
|
||||
url = "https://api.fabro.example.com"
|
||||
|
||||
[server.web]
|
||||
enabled = true
|
||||
url = "https://fabro.example.com"
|
||||
|
||||
[server.auth]
|
||||
methods = ["dev-token", "github"]
|
||||
|
||||
[server.auth.github]
|
||||
allowed_usernames = ["alice"]
|
||||
|
||||
[server.storage]
|
||||
root = "/srv/fabro"
|
||||
|
||||
[server.integrations.github]
|
||||
enabled = true
|
||||
strategy = "app"
|
||||
app_id = "12345"
|
||||
client_id = "Iv1.abcdef"
|
||||
slug = "fabro-dev"
|
||||
|
||||
[features]
|
||||
session_sandboxes = true
|
||||
"#,
|
||||
)
|
||||
.expect("settings fixture should parse");
|
||||
let settings = ServerSettings::from_layer(&layer).expect("settings should resolve");
|
||||
|
||||
let json = serde_json::to_value(&settings).expect("server settings should serialize");
|
||||
assert_eq!(json["server"]["listen"]["type"], "tcp");
|
||||
assert_eq!(json["server"]["listen"]["address"], "127.0.0.1:32276");
|
||||
assert_eq!(json["server"]["storage"]["root"], "/srv/fabro");
|
||||
assert_eq!(json["features"]["session_sandboxes"], true);
|
||||
|
||||
let round_trip: ApiServerSettings =
|
||||
serde_json::from_value(json).expect("server settings should deserialize");
|
||||
assert_eq!(round_trip, settings);
|
||||
}
|
||||
|
||||
fn assert_same_type<T: 'static, U: 'static>() {
|
||||
assert_eq!(
|
||||
TypeId::of::<T>(),
|
||||
TypeId::of::<U>(),
|
||||
"{} should be the same type as {}",
|
||||
type_name::<T>(),
|
||||
type_name::<U>()
|
||||
);
|
||||
}
|
||||
|
|
@ -514,7 +514,7 @@ pub(crate) struct InspectArgs {
|
|||
}
|
||||
|
||||
#[derive(Args)]
|
||||
pub(crate) struct StoreDumpArgs {
|
||||
pub(crate) struct DumpArgs {
|
||||
#[command(flatten)]
|
||||
pub(crate) server: ServerTargetArgs,
|
||||
|
||||
|
|
@ -712,13 +712,6 @@ pub(crate) struct SystemEventsArgs {
|
|||
pub(crate) struct SettingsArgs {
|
||||
#[command(flatten)]
|
||||
pub(crate) target: ServerTargetArgs,
|
||||
|
||||
/// Show only locally resolved settings and skip the server call
|
||||
#[arg(long, conflicts_with = "server")]
|
||||
pub(crate) local: bool,
|
||||
|
||||
/// Optional workflow name, .fabro path, or .toml run config to overlay
|
||||
pub(crate) workflow: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
|
|
@ -1008,8 +1001,8 @@ pub(crate) enum Commands {
|
|||
Parse(ParseArgs),
|
||||
/// Inspect and copy run artifacts (screenshots, reports, traces)
|
||||
Artifact(ArtifactNamespace),
|
||||
/// Export store-backed run state for debugging
|
||||
Store(StoreNamespace),
|
||||
/// Export a run's durable state to a directory
|
||||
Dump(DumpArgs),
|
||||
#[command(flatten)]
|
||||
RunsCmd(RunsCommands),
|
||||
/// List and test LLM models
|
||||
|
|
@ -1092,9 +1085,7 @@ impl Commands {
|
|||
ArtifactCommand::List(_) => "artifact list",
|
||||
ArtifactCommand::Cp(_) => "artifact cp",
|
||||
},
|
||||
Self::Store(ns) => match &ns.command {
|
||||
StoreCommand::Dump(_) => "store dump",
|
||||
},
|
||||
Self::Dump(_) => "dump",
|
||||
Self::Exec(_) => "exec",
|
||||
Self::RunCmd(cmd) => cmd.name(),
|
||||
Self::Preflight(_) => "preflight",
|
||||
|
|
@ -1206,18 +1197,6 @@ pub(crate) enum ArtifactCommand {
|
|||
Cp(ArtifactCpArgs),
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
pub(crate) struct StoreNamespace {
|
||||
#[command(subcommand)]
|
||||
pub(crate) command: StoreCommand,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub(crate) enum StoreCommand {
|
||||
/// Export a run's durable state to a directory
|
||||
Dump(StoreDumpArgs),
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
pub(crate) struct SecretNamespace {
|
||||
#[command(flatten)]
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@ use std::path::{Path, PathBuf};
|
|||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{Context as _, Result, bail};
|
||||
use fabro_config::UserSettings;
|
||||
use fabro_config::merge::combine_files;
|
||||
use fabro_types::settings::SettingsLayer;
|
||||
use fabro_types::settings::cli::CliLayer;
|
||||
use fabro_types::settings::{CliSettings, SettingsLayer};
|
||||
use fabro_util::printer::Printer;
|
||||
use tokio::sync::OnceCell;
|
||||
|
||||
|
|
@ -33,24 +34,19 @@ pub(crate) struct CommandContext {
|
|||
cwd: PathBuf,
|
||||
base_config_path: PathBuf,
|
||||
machine_settings: SettingsLayer,
|
||||
cli_settings: CliSettings,
|
||||
user_settings: UserSettings,
|
||||
server_mode: ServerMode,
|
||||
server: OnceCell<Arc<Client>>,
|
||||
}
|
||||
|
||||
impl CommandContext {
|
||||
pub(crate) fn base(
|
||||
printer: Printer,
|
||||
cli_settings: CliSettings,
|
||||
cli_layer: &CliLayer,
|
||||
) -> Result<Self> {
|
||||
Self::new(printer, ServerMode::None, cli_settings, cli_layer)
|
||||
pub(crate) fn base(printer: Printer, cli_layer: &CliLayer) -> Result<Self> {
|
||||
Self::new(printer, ServerMode::None, cli_layer)
|
||||
}
|
||||
|
||||
pub(crate) fn for_target(
|
||||
args: &ServerTargetArgs,
|
||||
printer: Printer,
|
||||
cli_settings: CliSettings,
|
||||
cli_layer: &CliLayer,
|
||||
) -> Result<Self> {
|
||||
Self::new(
|
||||
|
|
@ -58,7 +54,6 @@ impl CommandContext {
|
|||
ServerMode::ByTarget {
|
||||
target_override: args.server.clone(),
|
||||
},
|
||||
cli_settings,
|
||||
cli_layer,
|
||||
)
|
||||
}
|
||||
|
|
@ -66,7 +61,6 @@ impl CommandContext {
|
|||
pub(crate) fn for_connection(
|
||||
args: &ServerConnectionArgs,
|
||||
printer: Printer,
|
||||
cli_settings: CliSettings,
|
||||
cli_layer: &CliLayer,
|
||||
) -> Result<Self> {
|
||||
Self::new(
|
||||
|
|
@ -75,17 +69,11 @@ impl CommandContext {
|
|||
target_override: args.target.server.clone(),
|
||||
storage_dir_override: args.storage_dir.clone_path(),
|
||||
},
|
||||
cli_settings,
|
||||
cli_layer,
|
||||
)
|
||||
}
|
||||
|
||||
fn new(
|
||||
printer: Printer,
|
||||
server_mode: ServerMode,
|
||||
cli_settings: CliSettings,
|
||||
cli_layer: &CliLayer,
|
||||
) -> Result<Self> {
|
||||
fn new(printer: Printer, server_mode: ServerMode, cli_layer: &CliLayer) -> Result<Self> {
|
||||
let cwd = std::env::current_dir().context("Failed to get current directory")?;
|
||||
let base_config_path = user_config::active_settings_path(None);
|
||||
let disk_settings = match &server_mode {
|
||||
|
|
@ -99,13 +87,14 @@ impl CommandContext {
|
|||
cli: Some(cli_layer.clone()),
|
||||
..SettingsLayer::default()
|
||||
});
|
||||
let user_settings = fabro_config::UserSettings::from_layer(&machine_settings)?;
|
||||
|
||||
Ok(Self {
|
||||
printer,
|
||||
cwd,
|
||||
base_config_path,
|
||||
machine_settings,
|
||||
cli_settings,
|
||||
user_settings,
|
||||
server_mode,
|
||||
server: OnceCell::new(),
|
||||
})
|
||||
|
|
@ -123,16 +112,12 @@ impl CommandContext {
|
|||
&self.cwd
|
||||
}
|
||||
|
||||
pub(crate) fn base_config_path(&self) -> &Path {
|
||||
&self.base_config_path
|
||||
}
|
||||
|
||||
pub(crate) fn machine_settings(&self) -> &SettingsLayer {
|
||||
&self.machine_settings
|
||||
}
|
||||
|
||||
pub(crate) fn cli_settings(&self) -> &CliSettings {
|
||||
&self.cli_settings
|
||||
pub(crate) fn user_settings(&self) -> &UserSettings {
|
||||
&self.user_settings
|
||||
}
|
||||
|
||||
pub(crate) async fn server(&self) -> Result<Arc<Client>> {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::{Context, Result, bail};
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
|
||||
|
|
@ -16,7 +16,7 @@ use crate::shared::{print_json_pretty, split_run_path};
|
|||
|
||||
pub(super) async fn cp_command(
|
||||
args: &ArtifactCpArgs,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
|
|
@ -26,7 +26,6 @@ pub(super) async fn cp_command(
|
|||
run_id_selector,
|
||||
args.node.as_deref(),
|
||||
args.retry,
|
||||
cli,
|
||||
cli_layer,
|
||||
printer,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use anyhow::Result;
|
||||
use cli_table::format::{Border, Justify, Separator};
|
||||
use cli_table::{Cell, CellStruct, Style, Table};
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_util::terminal::Styles;
|
||||
|
|
@ -10,7 +10,7 @@ use crate::args::ArtifactListArgs;
|
|||
|
||||
pub(super) async fn list_command(
|
||||
args: &ArtifactListArgs,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
|
|
@ -19,7 +19,6 @@ pub(super) async fn list_command(
|
|||
&args.run_id,
|
||||
args.node.as_deref(),
|
||||
args.retry,
|
||||
cli,
|
||||
cli_layer,
|
||||
printer,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ mod cp;
|
|||
mod list;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::CliLayer;
|
||||
use fabro_types::{RunId, StageId};
|
||||
use fabro_util::printer::Printer;
|
||||
|
|
@ -26,11 +26,10 @@ pub(super) async fn resolve_artifacts(
|
|||
run_selector: &str,
|
||||
node: Option<&str>,
|
||||
retry: Option<u32>,
|
||||
cli: &CliSettings,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<(RunId, Client, Vec<ArtifactEntry>)> {
|
||||
let ctx = CommandContext::for_target(server, printer, cli.clone(), cli_layer)?;
|
||||
let ctx = CommandContext::for_target(server, printer, cli_layer)?;
|
||||
let client = ctx.server().await?;
|
||||
let run_id = client.resolve_run(run_selector).await?.run_id;
|
||||
let mut entries = Vec::new();
|
||||
|
|
@ -65,7 +64,7 @@ pub(super) async fn resolve_artifacts(
|
|||
|
||||
pub(crate) async fn dispatch(
|
||||
ns: ArtifactNamespace,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ use anyhow::{Context as _, Result, bail};
|
|||
use chrono::{DateTime, Utc};
|
||||
use fabro_client::{AuthEntry, AuthStore, StoredSubject};
|
||||
use fabro_http::header::CONTENT_TYPE;
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::cli::CliLayer;
|
||||
use fabro_util::browser;
|
||||
use fabro_util::printer::Printer;
|
||||
|
|
@ -36,7 +35,6 @@ struct CliTokenSubject {
|
|||
|
||||
pub(super) async fn login_command(
|
||||
args: AuthLoginArgs,
|
||||
cli: &CliSettings,
|
||||
cli_layer: &CliLayer,
|
||||
process_local_json: bool,
|
||||
printer: Printer,
|
||||
|
|
@ -45,7 +43,7 @@ pub(super) async fn login_command(
|
|||
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
let _ = (args, cli, cli_layer, printer);
|
||||
let _ = (args, cli_layer, printer);
|
||||
bail!(
|
||||
"CLI OAuth login is not supported on Windows in this release. Use WSL, or use a dev-token server."
|
||||
);
|
||||
|
|
@ -53,7 +51,7 @@ pub(super) async fn login_command(
|
|||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let ctx = CommandContext::base(printer, cli.clone(), cli_layer)?;
|
||||
let ctx = CommandContext::base(printer, cli_layer)?;
|
||||
let target = user_config::resolve_server_target(&args.server, ctx.machine_settings())?;
|
||||
let web_url = browser_origin(&target)?;
|
||||
let pkce = fabro_oauth::generate_pkce();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
use anyhow::{Result, bail};
|
||||
use fabro_client::{AuthEntry, AuthStore};
|
||||
use fabro_http::header::AUTHORIZATION;
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::cli::CliLayer;
|
||||
use fabro_util::printer::Printer;
|
||||
|
||||
|
|
@ -12,14 +11,13 @@ use crate::user_config::ServerTarget;
|
|||
|
||||
pub(super) async fn logout_command(
|
||||
args: AuthLogoutArgs,
|
||||
cli: &CliSettings,
|
||||
cli_layer: &CliLayer,
|
||||
process_local_json: bool,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
require_no_json_override(process_local_json)?;
|
||||
|
||||
let ctx = CommandContext::base(printer, cli.clone(), cli_layer)?;
|
||||
let ctx = CommandContext::base(printer, cli_layer)?;
|
||||
let store = AuthStore::default();
|
||||
if args.all {
|
||||
let entries = store.list()?;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ mod logout;
|
|||
mod status;
|
||||
|
||||
use anyhow::Result;
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::cli::CliLayer;
|
||||
use fabro_util::printer::Printer;
|
||||
|
||||
|
|
@ -11,20 +10,19 @@ use crate::args::{AuthCommand, AuthNamespace};
|
|||
|
||||
pub(crate) async fn dispatch(
|
||||
ns: AuthNamespace,
|
||||
cli: &CliSettings,
|
||||
cli_layer: &CliLayer,
|
||||
process_local_json: bool,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
match ns.command {
|
||||
AuthCommand::Login(args) => {
|
||||
login::login_command(args, cli, cli_layer, process_local_json, printer).await
|
||||
login::login_command(args, cli_layer, process_local_json, printer).await
|
||||
}
|
||||
AuthCommand::Logout(args) => {
|
||||
logout::logout_command(args, cli, cli_layer, process_local_json, printer).await
|
||||
logout::logout_command(args, cli_layer, process_local_json, printer).await
|
||||
}
|
||||
AuthCommand::Status(args) => {
|
||||
status::status_command(&args, cli, cli_layer, process_local_json, printer)
|
||||
status::status_command(&args, cli_layer, process_local_json, printer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
use anyhow::Result;
|
||||
use chrono::{DateTime, Utc};
|
||||
use fabro_client::{AuthEntry, AuthStore};
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::cli::CliLayer;
|
||||
use fabro_util::dev_token::{read_dev_token_file, validate_dev_token_format};
|
||||
use fabro_util::printer::Printer;
|
||||
|
|
@ -43,12 +42,11 @@ struct StatusOutput {
|
|||
|
||||
pub(super) fn status_command(
|
||||
args: &AuthStatusArgs,
|
||||
cli: &CliSettings,
|
||||
cli_layer: &CliLayer,
|
||||
process_local_json: bool,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let ctx = CommandContext::base(printer, cli.clone(), cli_layer)?;
|
||||
let ctx = CommandContext::base(printer, cli_layer)?;
|
||||
let store = AuthStore::default();
|
||||
let now = Utc::now();
|
||||
let rows = if args.server.as_deref().is_some() {
|
||||
|
|
|
|||
|
|
@ -8,155 +8,46 @@
|
|||
)]
|
||||
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
use fabro_config::effective_settings::{
|
||||
EffectiveSettingsLayers, EffectiveSettingsMode, materialize_settings_layer,
|
||||
};
|
||||
use fabro_config::{load_settings_project, project};
|
||||
use fabro_api::types::ServerSettings;
|
||||
use fabro_config::UserSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_types::settings::{CliSettings, SettingsLayer};
|
||||
use fabro_util::printer::Printer;
|
||||
use serde_json::json;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::args::SettingsArgs;
|
||||
use crate::command_context::CommandContext;
|
||||
use crate::shared::print_json_pretty;
|
||||
use crate::user_config;
|
||||
|
||||
fn config_layers(
|
||||
ctx: &CommandContext,
|
||||
workflow: Option<&Path>,
|
||||
) -> anyhow::Result<EffectiveSettingsLayers> {
|
||||
let cwd = ctx.cwd();
|
||||
let (workflow_layer, project_layer) = match workflow {
|
||||
Some(path) => workflow_and_project_layers(path, cwd)?,
|
||||
None => (SettingsLayer::default(), load_settings_project(cwd)?),
|
||||
};
|
||||
let user_layer =
|
||||
user_config::load_settings_with_config_and_storage_dir(Some(ctx.base_config_path()), None)?;
|
||||
Ok(EffectiveSettingsLayers::new(
|
||||
SettingsLayer::default(),
|
||||
workflow_layer,
|
||||
project_layer,
|
||||
user_layer,
|
||||
))
|
||||
}
|
||||
|
||||
fn workflow_and_project_layers(
|
||||
path: &Path,
|
||||
cwd: &Path,
|
||||
) -> anyhow::Result<(SettingsLayer, SettingsLayer)> {
|
||||
let resolution = project::resolve_workflow_path(path, cwd)?;
|
||||
if resolution.workflow_config.is_none() && !resolution.resolved_workflow_path.is_file() {
|
||||
anyhow::bail!(
|
||||
"Workflow not found: {}",
|
||||
resolution.resolved_workflow_path.display()
|
||||
);
|
||||
}
|
||||
|
||||
let workflow_layer = resolution.workflow_config.unwrap_or_default();
|
||||
let project_layer = project::discover_project_config(
|
||||
resolution
|
||||
.resolved_workflow_path
|
||||
.parent()
|
||||
.unwrap_or_else(|| Path::new(".")),
|
||||
)?
|
||||
.map(|(_, config)| config)
|
||||
.unwrap_or_default();
|
||||
|
||||
Ok((workflow_layer, project_layer))
|
||||
}
|
||||
|
||||
fn strip_nulls(value: &mut serde_json::Value) {
|
||||
match value {
|
||||
serde_json::Value::Object(map) => {
|
||||
for child in map.values_mut() {
|
||||
strip_nulls(child);
|
||||
}
|
||||
map.retain(|_, child| !child.is_null());
|
||||
}
|
||||
serde_json::Value::Array(values) => {
|
||||
for child in values {
|
||||
strip_nulls(child);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn local_settings_value(
|
||||
args: &SettingsArgs,
|
||||
cli: &CliSettings,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> anyhow::Result<serde_json::Value> {
|
||||
let base_ctx = CommandContext::base(printer, cli.clone(), cli_layer)?;
|
||||
let layers = config_layers(&base_ctx, args.workflow.as_deref())?;
|
||||
let local_settings =
|
||||
materialize_settings_layer(layers, None, EffectiveSettingsMode::LocalOnly)?;
|
||||
let mut value = resolve_local_settings_value(&local_settings)?;
|
||||
strip_nulls(&mut value);
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
fn render_resolve_errors(errors: Vec<fabro_config::ResolveError>) -> anyhow::Error {
|
||||
anyhow::anyhow!(
|
||||
"failed to resolve local settings:\n{}",
|
||||
errors
|
||||
.into_iter()
|
||||
.map(|error| error.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
)
|
||||
}
|
||||
|
||||
fn resolve_local_settings_value(file: &SettingsLayer) -> anyhow::Result<serde_json::Value> {
|
||||
let file = fabro_config::apply_builtin_defaults(file.clone());
|
||||
|
||||
let project = fabro_config::resolve_project_from_file(&file).map_err(render_resolve_errors)?;
|
||||
let workflow =
|
||||
fabro_config::resolve_workflow_from_file(&file).map_err(render_resolve_errors)?;
|
||||
let run = fabro_config::resolve_run_from_file(&file).map_err(render_resolve_errors)?;
|
||||
let cli = fabro_config::resolve_cli_from_file(&file).map_err(render_resolve_errors)?;
|
||||
let features =
|
||||
fabro_config::resolve_features_from_file(&file).map_err(render_resolve_errors)?;
|
||||
|
||||
Ok(json!({
|
||||
"project": project,
|
||||
"workflow": workflow,
|
||||
"run": run,
|
||||
"cli": cli,
|
||||
"features": features,
|
||||
}))
|
||||
#[derive(Serialize)]
|
||||
struct RenderedConfig {
|
||||
user: UserSettings,
|
||||
server: ServerSettings,
|
||||
}
|
||||
|
||||
async fn rendered_config(
|
||||
args: &SettingsArgs,
|
||||
cli: &CliSettings,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> anyhow::Result<serde_json::Value> {
|
||||
if args.local {
|
||||
return local_settings_value(args, cli, cli_layer, printer);
|
||||
}
|
||||
if args.workflow.is_some() {
|
||||
anyhow::bail!("WORKFLOW requires --local; use `fabro settings --local WORKFLOW`");
|
||||
}
|
||||
let ctx = CommandContext::for_target(&args.target, printer, cli.clone(), cli_layer)?;
|
||||
ctx.server()
|
||||
let ctx = CommandContext::for_target(&args.target, printer, cli_layer)?;
|
||||
let user = fabro_config::UserSettings::resolve()?;
|
||||
let server = ctx
|
||||
.server()
|
||||
.await?
|
||||
.retrieve_resolved_server_settings()
|
||||
.await
|
||||
.await?;
|
||||
serde_json::to_value(RenderedConfig { user, server }).map_err(Into::into)
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(
|
||||
args: &SettingsArgs,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> anyhow::Result<()> {
|
||||
let config = Box::pin(rendered_config(args, cli, cli_layer, printer)).await?;
|
||||
let config = Box::pin(rendered_config(args, cli_layer, printer)).await?;
|
||||
if cli.output.format == OutputFormat::Json {
|
||||
print_json_pretty(&config)?;
|
||||
return Ok(());
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use std::path::PathBuf;
|
|||
use anyhow::Result;
|
||||
use fabro_api::types as api_types;
|
||||
use fabro_config::user::active_settings_path;
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
pub(crate) use fabro_util::check_report::{
|
||||
CheckDetail, CheckReport, CheckResult, CheckSection, CheckStatus,
|
||||
|
|
@ -143,7 +143,7 @@ fn render_report(report: &CheckReport, styles: &Styles, verbose: bool, printer:
|
|||
pub(crate) async fn run_doctor(
|
||||
args: &DoctorArgs,
|
||||
verbose: bool,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<i32, anyhow::Error> {
|
||||
|
|
@ -179,7 +179,7 @@ pub(crate) async fn run_doctor(
|
|||
}],
|
||||
};
|
||||
|
||||
let ctx = match CommandContext::for_target(&args.target, printer, cli.clone(), cli_layer) {
|
||||
let ctx = match CommandContext::for_target(&args.target, printer, cli_layer) {
|
||||
Ok(ctx) => ctx,
|
||||
Err(err) => {
|
||||
report.sections.push(CheckSection {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#![expect(
|
||||
clippy::disallowed_methods,
|
||||
reason = "CLI `store dump` command: sync file I/O for dump outputs"
|
||||
reason = "CLI `dump` command: sync file I/O for dump outputs"
|
||||
)]
|
||||
|
||||
use std::io::ErrorKind;
|
||||
|
|
@ -11,28 +11,28 @@ use bytes::Bytes;
|
|||
#[cfg(test)]
|
||||
use fabro_store::{ArtifactStore, RunDatabase};
|
||||
use fabro_store::{EventEnvelope, RunProjection, StageId};
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_types::{RunBlobId, RunId};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_workflow::run_dump::RunDump;
|
||||
use futures::future::BoxFuture;
|
||||
#[cfg(test)]
|
||||
use serde::de::DeserializeOwned;
|
||||
use tokio::task::spawn_blocking;
|
||||
|
||||
use super::run_export::StoreRunExport;
|
||||
use crate::args::StoreDumpArgs;
|
||||
use crate::args::DumpArgs;
|
||||
use crate::command_context::CommandContext;
|
||||
use crate::server_client::Client;
|
||||
use crate::shared::{absolute_or_current, print_json_pretty};
|
||||
|
||||
pub(crate) async fn dump_command(
|
||||
args: &StoreDumpArgs,
|
||||
cli: &CliSettings,
|
||||
pub(crate) async fn run(
|
||||
args: &DumpArgs,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli.clone(), cli_layer)?;
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
|
||||
let client = ctx.server().await?;
|
||||
let run_id = client.resolve_run(&args.run).await?.run_id;
|
||||
let state = client.get_run_state(&run_id).await?;
|
||||
|
|
@ -222,7 +222,7 @@ async fn export_run_from_source(
|
|||
.with_context(|| format!("failed to create {}", staging_parent.display()))?;
|
||||
|
||||
let staging_dir = tempfile::Builder::new()
|
||||
.prefix(".fabro-store-dump-")
|
||||
.prefix(".fabro-dump-")
|
||||
.tempdir_in(staging_parent)
|
||||
.with_context(|| {
|
||||
format!(
|
||||
|
|
@ -248,7 +248,7 @@ async fn write_run_dump(
|
|||
output_dir: &Path,
|
||||
) -> Result<usize> {
|
||||
let events = source.list_events().await?;
|
||||
let mut dump = StoreRunExport::from_store_state_and_events(state, &events)?;
|
||||
let mut dump = RunDump::from_store_state_and_events(state, &events)?;
|
||||
|
||||
dump.hydrate_referenced_blobs_with_reader(|blob_id| source.read_blob(blob_id))
|
||||
.await?;
|
||||
|
|
@ -15,7 +15,7 @@ use fabro_llm::types::{
|
|||
use fabro_mcp::config::{McpServerSettings, McpTransport};
|
||||
use fabro_types::settings::cli::OutputFormat as SettingsOutputFormat;
|
||||
use fabro_types::settings::run::McpEntryLayer;
|
||||
use fabro_types::settings::{CliSettings, InterpString};
|
||||
use fabro_types::settings::{CliNamespace, InterpString};
|
||||
use fabro_util::exit::{ErrorExt, ExitClass};
|
||||
use fabro_util::printer::Printer;
|
||||
use futures::stream;
|
||||
|
|
@ -358,7 +358,7 @@ impl ProviderAdapter for AuthenticatedFabroServerAdapter {
|
|||
|
||||
pub(crate) async fn execute(
|
||||
mut args: ExecArgs,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
_printer: Printer,
|
||||
) -> AnyResult<()> {
|
||||
use fabro_agent::cli::PermissionLevel as AgentPermissionLevel;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ use fabro_api::types;
|
|||
use fabro_config::load::load_settings_user;
|
||||
use fabro_config::user::active_settings_path;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_types::settings::{CliSettings, SettingsLayer};
|
||||
use fabro_types::settings::{CliNamespace, SettingsLayer};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_util::terminal::Styles;
|
||||
use tracing::debug;
|
||||
|
|
@ -28,7 +28,7 @@ use crate::shared::{absolute_or_current, print_diagnostics, print_json_pretty, r
|
|||
pub(crate) async fn run(
|
||||
args: &GraphArgs,
|
||||
styles: &Styles,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
process_local_json: bool,
|
||||
printer: Printer,
|
||||
|
|
@ -37,7 +37,7 @@ pub(crate) async fn run(
|
|||
require_no_json_override(process_local_json)?;
|
||||
}
|
||||
|
||||
let ctx = CommandContext::for_target(&args.target, printer, cli.clone(), cli_layer)?;
|
||||
let ctx = CommandContext::for_target(&args.target, printer, cli_layer)?;
|
||||
let built = build_run_manifest(ManifestBuildInput {
|
||||
workflow: args.workflow.clone(),
|
||||
cwd: ctx.cwd().to_path_buf(),
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ use fabro_auth::{AuthCredential, AuthMethod, codex_oauth_config, credential_id_f
|
|||
use fabro_config::bind::Bind;
|
||||
use fabro_config::daemon::ServerDaemon;
|
||||
use fabro_config::user::{SETTINGS_CONFIG_FILENAME, default_storage_dir};
|
||||
use fabro_config::{ResolveError, Storage, envfile};
|
||||
use fabro_config::{Storage, envfile};
|
||||
use fabro_install::{
|
||||
InstallListenConfig, PendingSettingsWrite, merge_server_settings as merge_server_settings_impl,
|
||||
persist_install_outputs_direct, write_github_app_settings, write_token_settings,
|
||||
|
|
@ -34,7 +34,7 @@ use fabro_server::serve;
|
|||
use fabro_store::ArtifactStore;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_types::settings::server::ServerAuthMethod;
|
||||
use fabro_types::settings::{CliSettings, SettingsLayer};
|
||||
use fabro_types::settings::{CliNamespace, SettingsLayer};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_util::terminal::Styles;
|
||||
use fabro_util::version::FABRO_VERSION;
|
||||
|
|
@ -1262,24 +1262,12 @@ fn persist_github_install_changes(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn render_server_resolve_errors(errors: Vec<ResolveError>) -> anyhow::Error {
|
||||
anyhow::anyhow!(
|
||||
"failed to resolve server settings:\n{}",
|
||||
errors
|
||||
.into_iter()
|
||||
.map(|error| error.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
)
|
||||
}
|
||||
|
||||
async fn write_artifact_store_metadata(
|
||||
settings: &SettingsLayer,
|
||||
fabro_version: &str,
|
||||
) -> Result<()> {
|
||||
let resolved =
|
||||
fabro_config::resolve_server_from_file(settings).map_err(render_server_resolve_errors)?;
|
||||
let (object_store, prefix) = serve::build_artifact_object_store(&resolved)?;
|
||||
let resolved = fabro_config::ServerSettings::from_layer(settings)?;
|
||||
let (object_store, prefix) = serve::build_artifact_object_store(&resolved.server)?;
|
||||
let artifact_store = ArtifactStore::new(object_store, prefix);
|
||||
artifact_store.write_metadata(fabro_version).await?;
|
||||
Ok(())
|
||||
|
|
@ -1421,7 +1409,7 @@ where
|
|||
pub(crate) async fn execute(
|
||||
args: &InstallArgs,
|
||||
command: Option<InstallCommand>,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
process_local_json: bool,
|
||||
printer: Printer,
|
||||
|
|
@ -1437,7 +1425,7 @@ pub(crate) async fn execute(
|
|||
async fn run_install_github_command(
|
||||
args: &InstallArgs,
|
||||
github_args: &InstallGithubArgs,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
process_local_json: bool,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
|
|
@ -1600,7 +1588,7 @@ async fn run_install_github_inner(
|
|||
|
||||
pub(crate) async fn run_install(
|
||||
args: &InstallArgs,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
process_local_json: bool,
|
||||
printer: Printer,
|
||||
|
|
@ -1626,7 +1614,7 @@ pub(crate) async fn run_install(
|
|||
|
||||
async fn run_install_inner(
|
||||
args: &InstallArgs,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
|
|
@ -1802,8 +1790,7 @@ async fn run_install_inner(
|
|||
.context("failed to parse generated settings.toml")?,
|
||||
args.storage_dir.as_deref(),
|
||||
);
|
||||
fabro_config::resolve_server_from_file(&install_settings)
|
||||
.map_err(render_server_resolve_errors)?;
|
||||
fabro_config::ServerSettings::from_layer(&install_settings)?;
|
||||
|
||||
// Secrets and auth material
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ pub(crate) mod artifact;
|
|||
pub(crate) mod auth;
|
||||
pub(crate) mod config;
|
||||
pub(crate) mod doctor;
|
||||
pub(crate) mod dump;
|
||||
pub(crate) mod exec;
|
||||
pub(crate) mod graph;
|
||||
pub(crate) mod install;
|
||||
|
|
@ -10,6 +11,7 @@ pub(crate) mod parse;
|
|||
pub(crate) mod pr;
|
||||
pub(crate) mod preflight;
|
||||
pub(crate) mod provider;
|
||||
pub(crate) mod rebuild;
|
||||
pub(crate) mod render_graph;
|
||||
pub(crate) mod repo;
|
||||
pub(crate) mod run;
|
||||
|
|
@ -17,7 +19,6 @@ pub(crate) mod runs;
|
|||
pub(crate) mod sandbox;
|
||||
pub(crate) mod secret;
|
||||
pub(crate) mod server;
|
||||
pub(crate) mod store;
|
||||
pub(crate) mod system;
|
||||
pub(crate) mod uninstall;
|
||||
pub(crate) mod upgrade;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use cli_table::format::{Border, Justify, Separator};
|
|||
use cli_table::{Cell, CellStruct, Color, Style, Table};
|
||||
use fabro_api::types as api_types;
|
||||
use fabro_model::{Catalog, Model, Provider};
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_util::terminal::Styles;
|
||||
|
|
@ -42,7 +42,7 @@ struct ModelTestOutput {
|
|||
|
||||
pub(crate) async fn execute(
|
||||
command: Option<ModelsCommand>,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
|
|
@ -51,7 +51,7 @@ pub(crate) async fn execute(
|
|||
ModelsCommand::List(args) => &args.target,
|
||||
ModelsCommand::Test(args) => &args.target,
|
||||
};
|
||||
let ctx = CommandContext::for_target(target_args, printer, cli.clone(), cli_layer)?;
|
||||
let ctx = CommandContext::for_target(target_args, printer, cli_layer)?;
|
||||
let server = ctx.server().await?;
|
||||
|
||||
run_models(command, &server, cli.output.format == OutputFormat::Json).await
|
||||
|
|
|
|||
|
|
@ -11,13 +11,13 @@ use std::io::Write;
|
|||
|
||||
use fabro_config::project::resolve_workflow;
|
||||
use fabro_graphviz::parser::parse_ast;
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_util::printer::Printer;
|
||||
|
||||
use crate::args::ParseArgs;
|
||||
use crate::shared::read_workflow_file;
|
||||
|
||||
pub(crate) fn run(args: &ParseArgs, _cli: &CliSettings, _printer: Printer) -> anyhow::Result<()> {
|
||||
pub(crate) fn run(args: &ParseArgs, _cli: &CliNamespace, _printer: Printer) -> anyhow::Result<()> {
|
||||
let stdout = std::io::stdout();
|
||||
run_to(args, stdout.lock())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use anyhow::Result;
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
use tracing::info;
|
||||
|
|
@ -9,14 +9,14 @@ use crate::shared::print_json_pretty;
|
|||
|
||||
pub(super) async fn close_command(
|
||||
args: PrCloseArgs,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let (record, _run_id) =
|
||||
super::load_pr_record(&args.server, &args.run_id, cli, cli_layer, printer).await?;
|
||||
super::load_pr_record(&args.server, &args.run_id, cli_layer, printer).await?;
|
||||
|
||||
let creds = super::load_github_credentials_required(cli, cli_layer, printer)?;
|
||||
let creds = super::load_github_credentials_required(cli_layer, printer)?;
|
||||
|
||||
fabro_github::close_pull_request(
|
||||
&creds,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use fabro_auth::configured_providers_from_process_env;
|
|||
use fabro_config::Storage;
|
||||
use fabro_model::Catalog;
|
||||
use fabro_sandbox::daytona::detect_repo_info;
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_vault::Vault;
|
||||
|
|
@ -16,7 +16,7 @@ use tracing::info;
|
|||
|
||||
use crate::args::PrCreateArgs;
|
||||
use crate::command_context::CommandContext;
|
||||
use crate::commands::store::rebuild::rebuild_run_store;
|
||||
use crate::commands::rebuild::rebuild_run_store;
|
||||
use crate::shared::print_json_pretty;
|
||||
use crate::shared::repo::ensure_matching_repo_origin;
|
||||
use crate::user_config;
|
||||
|
|
@ -27,11 +27,11 @@ use crate::user_config;
|
|||
)]
|
||||
pub(super) async fn create_command(
|
||||
args: PrCreateArgs,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli.clone(), cli_layer)?;
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
|
||||
let client = ctx.server().await?;
|
||||
let run_id = client.resolve_run(&args.run_id).await?.run_id;
|
||||
let events = client.list_run_events(&run_id, None, None).await?;
|
||||
|
|
@ -86,7 +86,7 @@ pub(super) async fn create_command(
|
|||
let (owner, repo) = fabro_github::parse_github_owner_repo(&https_url)
|
||||
.map_err(|err| anyhow::anyhow!("{err}"))?;
|
||||
|
||||
let creds = super::load_github_credentials_required(cli, cli_layer, printer)?;
|
||||
let creds = super::load_github_credentials_required(cli_layer, printer)?;
|
||||
|
||||
let branch_found = fabro_github::branch_exists(
|
||||
&creds,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use anyhow::Result;
|
||||
use cli_table::format::{Border, Separator};
|
||||
use cli_table::{Cell, CellStruct, Color, Style, Table};
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_util::terminal::Styles;
|
||||
|
|
@ -25,11 +25,11 @@ struct PrRow {
|
|||
|
||||
pub(super) async fn list_command(
|
||||
args: PrListArgs,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli.clone(), cli_layer)?;
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
|
||||
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
|
||||
|
||||
let mut entries = Vec::new();
|
||||
|
|
@ -50,7 +50,7 @@ pub(super) async fn list_command(
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
let creds = super::load_github_credentials_required(cli, cli_layer, printer)?;
|
||||
let creds = super::load_github_credentials_required(cli_layer, printer)?;
|
||||
|
||||
let futures: Vec<_> = entries
|
||||
.iter()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use anyhow::Result;
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
use tracing::info;
|
||||
|
|
@ -9,14 +9,14 @@ use crate::shared::print_json_pretty;
|
|||
|
||||
pub(super) async fn merge_command(
|
||||
args: PrMergeArgs,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let (record, _run_id) =
|
||||
super::load_pr_record(&args.server, &args.run_id, cli, cli_layer, printer).await?;
|
||||
super::load_pr_record(&args.server, &args.run_id, cli_layer, printer).await?;
|
||||
|
||||
let creds = super::load_github_credentials_required(cli, cli_layer, printer)?;
|
||||
let creds = super::load_github_credentials_required(cli_layer, printer)?;
|
||||
|
||||
fabro_github::merge_pull_request(
|
||||
&creds,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use fabro_config::Storage;
|
|||
use fabro_github::GitHubCredentials;
|
||||
use fabro_types::PullRequestRecord;
|
||||
use fabro_types::settings::cli::CliLayer;
|
||||
use fabro_types::settings::{CliSettings, InterpString};
|
||||
use fabro_types::settings::{CliNamespace, InterpString};
|
||||
use fabro_util::printer::Printer;
|
||||
|
||||
use crate::args::{PrCommand, PrNamespace, ServerTargetArgs};
|
||||
|
|
@ -22,7 +22,7 @@ const GITHUB_CREDENTIALS_REQUIRED: &str =
|
|||
|
||||
pub(crate) async fn dispatch(
|
||||
ns: PrNamespace,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
|
|
@ -42,28 +42,19 @@ pub(crate) async fn dispatch(
|
|||
reason = "boundary-exempt(pr-api): remove with follow-up #1 when PR ops move server-side"
|
||||
)]
|
||||
fn load_github_credentials_required(
|
||||
cli: &CliSettings,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<GitHubCredentials> {
|
||||
let ctx = CommandContext::base(printer, cli.clone(), cli_layer)?;
|
||||
let server_settings =
|
||||
fabro_config::resolve_server_from_file(ctx.machine_settings()).map_err(|errors| {
|
||||
anyhow!(
|
||||
"failed to resolve server settings:\n{}",
|
||||
errors
|
||||
.into_iter()
|
||||
.map(|error| error.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
)
|
||||
})?;
|
||||
let ctx = CommandContext::base(printer, cli_layer)?;
|
||||
let server_settings = fabro_config::ServerSettings::from_layer(ctx.machine_settings())
|
||||
.map_err(anyhow::Error::from)?;
|
||||
let vault = user_config::storage_dir(ctx.machine_settings())
|
||||
.ok()
|
||||
.and_then(|dir| fabro_vault::Vault::load(Storage::new(&dir).secrets_path()).ok());
|
||||
let creds = build_github_credentials(
|
||||
server_settings.integrations.github.strategy,
|
||||
server_settings.server.integrations.github.strategy,
|
||||
server_settings
|
||||
.server
|
||||
.integrations
|
||||
.github
|
||||
.app_id
|
||||
|
|
@ -79,11 +70,10 @@ fn load_github_credentials_required(
|
|||
pub(crate) async fn load_pr_record(
|
||||
server: &ServerTargetArgs,
|
||||
run_id: &str,
|
||||
cli: &CliSettings,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<(PullRequestRecord, fabro_types::RunId)> {
|
||||
let ctx = CommandContext::for_target(server, printer, cli.clone(), cli_layer)?;
|
||||
let ctx = CommandContext::for_target(server, printer, cli_layer)?;
|
||||
let client = ctx.server().await?;
|
||||
let run_id = client.resolve_run(run_id).await?.run_id;
|
||||
let state = client.get_run_state(&run_id).await?;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use anyhow::Result;
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
use tracing::info;
|
||||
|
|
@ -9,14 +9,14 @@ use crate::shared::print_json_pretty;
|
|||
|
||||
pub(super) async fn view_command(
|
||||
args: PrViewArgs,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let (record, _run_id) =
|
||||
super::load_pr_record(&args.server, &args.run_id, cli, cli_layer, printer).await?;
|
||||
super::load_pr_record(&args.server, &args.run_id, cli_layer, printer).await?;
|
||||
|
||||
let creds = super::load_github_credentials_required(cli, cli_layer, printer)?;
|
||||
let creds = super::load_github_credentials_required(cli_layer, printer)?;
|
||||
|
||||
let detail = fabro_github::get_pull_request(
|
||||
&creds,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use anyhow::bail;
|
||||
use fabro_config::load::load_settings_user;
|
||||
use fabro_config::user::active_settings_path;
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat, OutputVerbosity};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_util::terminal::Styles;
|
||||
|
|
@ -17,12 +17,12 @@ use crate::shared::print_json_pretty;
|
|||
|
||||
pub(crate) async fn execute(
|
||||
mut args: PreflightArgs,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> anyhow::Result<()> {
|
||||
let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr()));
|
||||
let ctx = CommandContext::for_target(&args.target, printer, cli.clone(), cli_layer)?;
|
||||
let ctx = CommandContext::for_target(&args.target, printer, cli_layer)?;
|
||||
args.verbose = args.verbose || cli.output.verbosity == OutputVerbosity::Verbose;
|
||||
|
||||
let manifest = build_run_manifest(ManifestBuildInput {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
use anyhow::Result;
|
||||
use fabro_api::types;
|
||||
use fabro_auth::credential_id_for;
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::cli::CliLayer;
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_util::terminal::Styles;
|
||||
|
|
@ -12,14 +11,13 @@ use crate::shared::provider_auth;
|
|||
|
||||
pub(super) async fn login_command(
|
||||
args: ProviderLoginArgs,
|
||||
cli: &CliSettings,
|
||||
cli_layer: &CliLayer,
|
||||
process_local_json: bool,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
require_no_json_override(process_local_json)?;
|
||||
let s = Styles::detect_stderr();
|
||||
let ctx = CommandContext::for_target(&args.target, printer, cli.clone(), cli_layer)?;
|
||||
let ctx = CommandContext::for_target(&args.target, printer, cli_layer)?;
|
||||
let server = ctx.server().await?;
|
||||
let credential = if args.api_key_stdin {
|
||||
provider_auth::authenticate_provider_with_api_key_source(
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
mod login;
|
||||
|
||||
use anyhow::Result;
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::cli::CliLayer;
|
||||
use fabro_util::printer::Printer;
|
||||
|
||||
|
|
@ -9,14 +8,13 @@ use crate::args::{ProviderCommand, ProviderNamespace};
|
|||
|
||||
pub(crate) async fn dispatch(
|
||||
ns: ProviderNamespace,
|
||||
cli: &CliSettings,
|
||||
cli_layer: &CliLayer,
|
||||
process_local_json: bool,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
match ns.command {
|
||||
ProviderCommand::Login(args) => {
|
||||
login::login_command(args, cli, cli_layer, process_local_json, printer).await
|
||||
login::login_command(args, cli_layer, process_local_json, printer).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
use anyhow::{Context, Result, bail};
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
use fabro_util::printer::Printer;
|
||||
|
||||
pub(crate) fn run_deinit(cli: &CliSettings, printer: Printer) -> Result<Vec<String>> {
|
||||
pub(crate) fn run_deinit(cli: &CliNamespace, printer: Printer) -> Result<Vec<String>> {
|
||||
let repo_root = super::init::git_repo_root()?;
|
||||
let mut removed = Vec::new();
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::{Context, Result, bail};
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
use tokio::process::Command as TokioCommand;
|
||||
|
|
@ -36,7 +36,7 @@ pub(super) fn git_repo_root() -> Result<PathBuf> {
|
|||
|
||||
pub(crate) async fn run_init(
|
||||
args: &RepoInitArgs,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<Vec<String>> {
|
||||
|
|
@ -151,7 +151,7 @@ draft = true
|
|||
}
|
||||
|
||||
if cli.output.format != OutputFormat::Json {
|
||||
check_github_app_installation(&args.target, cli, cli_layer, printer).await;
|
||||
check_github_app_installation(&args.target, cli_layer, printer).await;
|
||||
}
|
||||
|
||||
Ok(created)
|
||||
|
|
@ -159,7 +159,6 @@ draft = true
|
|||
|
||||
async fn check_github_app_installation(
|
||||
target: &ServerTargetArgs,
|
||||
cli: &CliSettings,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) {
|
||||
|
|
@ -200,7 +199,7 @@ async fn check_github_app_installation(
|
|||
return; // Not a GitHub repo — skip silently
|
||||
};
|
||||
|
||||
let ctx = match CommandContext::for_target(target, printer, cli.clone(), cli_layer) {
|
||||
let ctx = match CommandContext::for_target(target, printer, cli_layer) {
|
||||
Ok(ctx) => ctx,
|
||||
Err(err) => {
|
||||
fabro_util::printerr!(
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ pub(crate) mod deinit;
|
|||
pub(crate) mod init;
|
||||
|
||||
use anyhow::Result;
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
|
||||
|
|
@ -11,7 +11,7 @@ use crate::shared::print_json_pretty;
|
|||
|
||||
pub(crate) async fn dispatch(
|
||||
ns: RepoNamespace,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ use anyhow::Result;
|
|||
use fabro_api::types;
|
||||
use fabro_interview::{AnswerValue, ConsoleInterviewer, Question, QuestionOption, QuestionType};
|
||||
use fabro_store::EventEnvelope;
|
||||
use fabro_types::settings::cli::OutputVerbosity;
|
||||
use fabro_types::settings::run::ApprovalMode;
|
||||
use fabro_types::{EventBody, RunId};
|
||||
use fabro_util::json::normalize_json_value;
|
||||
|
|
@ -49,6 +48,7 @@ pub(crate) async fn attach_run(
|
|||
kill_on_detach: bool,
|
||||
styles: &'static Styles,
|
||||
json_output: bool,
|
||||
live_verbose: bool,
|
||||
) -> Result<ExitCode> {
|
||||
let inferred_storage_dir = infer_storage_dir(run_dir);
|
||||
let inferred_run_id = infer_run_id(run_dir);
|
||||
|
|
@ -63,6 +63,7 @@ pub(crate) async fn attach_run(
|
|||
kill_on_detach,
|
||||
styles,
|
||||
json_output,
|
||||
live_verbose,
|
||||
Printer::Default,
|
||||
))
|
||||
.await;
|
||||
|
|
@ -79,6 +80,7 @@ pub(crate) async fn attach_run_with_client(
|
|||
kill_on_detach: bool,
|
||||
styles: &'static Styles,
|
||||
json_output: bool,
|
||||
live_verbose: bool,
|
||||
printer: Printer,
|
||||
) -> Result<ExitCode> {
|
||||
let state = client.get_run_state(run_id).await?;
|
||||
|
|
@ -86,10 +88,6 @@ pub(crate) async fn attach_run_with_client(
|
|||
fabro_config::resolve_run_from_file(&record.settings)
|
||||
.is_ok_and(|settings| settings.execution.approval == ApprovalMode::Auto)
|
||||
});
|
||||
let verbose = state.spec.as_ref().is_some_and(|record| {
|
||||
fabro_config::resolve_cli_from_file(&record.settings)
|
||||
.is_ok_and(|settings| settings.output.verbosity == OutputVerbosity::Verbose)
|
||||
});
|
||||
let events = client.list_run_events(run_id, None, None).await?;
|
||||
let replay_events = events.clone();
|
||||
let next_seq = events.last().map_or(1, |event| event.seq.saturating_add(1));
|
||||
|
|
@ -98,7 +96,7 @@ pub(crate) async fn attach_run_with_client(
|
|||
|
||||
if state_is_terminal(&state) || initial_exit_code.is_some() {
|
||||
return replay_run_with_client(
|
||||
verbose,
|
||||
live_verbose,
|
||||
events,
|
||||
initial_exit_code
|
||||
.or(state_exit_code)
|
||||
|
|
@ -116,7 +114,7 @@ pub(crate) async fn attach_run_with_client(
|
|||
styles,
|
||||
AttachOptions {
|
||||
auto_approve,
|
||||
verbose,
|
||||
verbose: live_verbose,
|
||||
kill_on_detach,
|
||||
json_output,
|
||||
},
|
||||
|
|
@ -544,6 +542,7 @@ mod tests {
|
|||
false,
|
||||
no_color_styles(),
|
||||
false,
|
||||
false,
|
||||
))
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use anyhow::Result;
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat, OutputVerbosity};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_util::terminal::Styles;
|
||||
|
|
@ -11,17 +11,17 @@ use crate::user_config::load_settings_with_storage_dir;
|
|||
|
||||
pub(crate) async fn execute(
|
||||
mut args: RunArgs,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr()));
|
||||
let ctx = CommandContext::for_target(&args.target, printer, cli.clone(), cli_layer)?;
|
||||
let ctx = CommandContext::for_target(&args.target, printer, cli_layer)?;
|
||||
let cli_defaults = load_settings_with_storage_dir(None)?;
|
||||
args.verbose = args.verbose || cli.output.verbosity == OutputVerbosity::Verbose;
|
||||
|
||||
let quiet = args.detach;
|
||||
let prevent_idle_sleep = ctx.cli_settings().exec.prevent_idle_sleep;
|
||||
let prevent_idle_sleep = ctx.user_settings().cli.exec.prevent_idle_sleep;
|
||||
let created_run = Box::pin(super::create::create_run(
|
||||
&ctx,
|
||||
&args,
|
||||
|
|
@ -64,6 +64,7 @@ pub(crate) async fn execute(
|
|||
true,
|
||||
styles,
|
||||
json,
|
||||
ctx.user_settings().cli.output.verbosity == OutputVerbosity::Verbose,
|
||||
printer,
|
||||
))
|
||||
.await?;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::{Context, Result, bail};
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
use tokio::fs;
|
||||
|
|
@ -28,7 +28,7 @@ enum CopyDirection {
|
|||
|
||||
pub(crate) async fn cp_command(
|
||||
args: CpArgs,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
|
|
@ -41,8 +41,7 @@ pub(crate) async fn cp_command(
|
|||
local_path,
|
||||
} => {
|
||||
let (client, run_id) =
|
||||
resolve_client_and_run_id(&args.server, &run_prefix, cli, cli_layer, printer)
|
||||
.await?;
|
||||
resolve_client_and_run_id(&args.server, &run_prefix, cli_layer, printer).await?;
|
||||
|
||||
let file_count = if args.recursive {
|
||||
Some(download_recursive(&client, &run_id, &remote_path, &local_path).await?)
|
||||
|
|
@ -73,8 +72,7 @@ pub(crate) async fn cp_command(
|
|||
remote_path,
|
||||
} => {
|
||||
let (client, run_id) =
|
||||
resolve_client_and_run_id(&args.server, &run_prefix, cli, cli_layer, printer)
|
||||
.await?;
|
||||
resolve_client_and_run_id(&args.server, &run_prefix, cli_layer, printer).await?;
|
||||
|
||||
let file_count = if args.recursive {
|
||||
Some(upload_recursive(&client, &run_id, &local_path, &remote_path).await?)
|
||||
|
|
@ -128,11 +126,10 @@ fn parse_direction(src: &str, dst: &str) -> Result<CopyDirection> {
|
|||
async fn resolve_client_and_run_id(
|
||||
server: &ServerTargetArgs,
|
||||
run_prefix: &str,
|
||||
cli: &CliSettings,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<(Client, fabro_types::RunId)> {
|
||||
let ctx = CommandContext::for_target(server, printer, cli.clone(), cli_layer)?;
|
||||
let ctx = CommandContext::for_target(server, printer, cli_layer)?;
|
||||
let client = ctx.server().await?;
|
||||
let run_id = client.resolve_run(run_prefix).await?.run_id;
|
||||
Ok((client.clone_for_reuse(), run_id))
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
use std::io::{self, IsTerminal, Write};
|
||||
|
||||
use anyhow::{Context, Result, bail};
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
use tracing::{debug, info};
|
||||
|
|
@ -22,12 +22,12 @@ use crate::shared::print_json_pretty;
|
|||
|
||||
pub(crate) async fn run(
|
||||
args: DiffArgs,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
info!(run_id = %args.run, "Showing diff");
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli.clone(), cli_layer)?;
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
|
||||
let client = ctx.server().await?;
|
||||
let run_id = client.resolve_run(&args.run).await?.run_id;
|
||||
let state = client.get_run_state(&run_id).await?;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use anyhow::{Context, Result};
|
||||
use fabro_checkpoint::git::Store;
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_util::terminal::Styles;
|
||||
|
|
@ -9,19 +9,19 @@ use git2::Repository;
|
|||
|
||||
use crate::args::ForkArgs;
|
||||
use crate::command_context::CommandContext;
|
||||
use crate::commands::store::rebuild::rebuild_run_store;
|
||||
use crate::commands::rebuild::rebuild_run_store;
|
||||
use crate::shared::print_json_pretty;
|
||||
use crate::shared::repo::ensure_matching_repo_origin;
|
||||
|
||||
pub(crate) async fn run(
|
||||
args: &ForkArgs,
|
||||
styles: &Styles,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let repo = Repository::discover(".").context("not in a git repository")?;
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli.clone(), cli_layer)?;
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
|
||||
let client = ctx.server().await?;
|
||||
let run_id = client.resolve_run(&args.run_id).await?.run_id;
|
||||
let state = client.get_run_state(&run_id).await?;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ use std::time::Duration;
|
|||
|
||||
use anyhow::{Context, Result, bail};
|
||||
use chrono::{DateTime, Utc};
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::json::normalize_json_value;
|
||||
use fabro_util::printer::Printer;
|
||||
|
|
@ -32,11 +32,11 @@ const FOLLOW_TERMINAL_GRACE: Duration = Duration::from_millis(500);
|
|||
pub(crate) async fn run(
|
||||
args: &LogsArgs,
|
||||
styles: &Styles,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli.clone(), cli_layer)?;
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
|
||||
let client = ctx.server().await?;
|
||||
let run_id = client.resolve_run(&args.run).await?.run_id;
|
||||
info!(run_id = %run_id, "Showing logs");
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use anyhow::Result;
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat, OutputVerbosity};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_util::terminal::Styles;
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ pub(crate) mod wait;
|
|||
|
||||
pub(crate) async fn dispatch(
|
||||
cmd: RunCommands,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
_process_local_json: bool,
|
||||
printer: Printer,
|
||||
|
|
@ -39,7 +39,7 @@ pub(crate) async fn dispatch(
|
|||
RunCommands::Create(args) => {
|
||||
let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr()));
|
||||
let cli_defaults = load_settings_with_storage_dir(None)?;
|
||||
let ctx = CommandContext::for_target(&args.target, printer, cli.clone(), cli_layer)?;
|
||||
let ctx = CommandContext::for_target(&args.target, printer, cli_layer)?;
|
||||
let created_run = Box::pin(create::create_run(
|
||||
&ctx,
|
||||
&args,
|
||||
|
|
@ -57,7 +57,7 @@ pub(crate) async fn dispatch(
|
|||
Ok(())
|
||||
}
|
||||
RunCommands::Start(StartArgs { server, run }) => {
|
||||
let ctx = CommandContext::for_target(&server, printer, cli.clone(), cli_layer)?;
|
||||
let ctx = CommandContext::for_target(&server, printer, cli_layer)?;
|
||||
let client = ctx.server().await?;
|
||||
let run_id = client.resolve_run(&run).await?.run_id;
|
||||
start::start_run_with_client(client.as_ref(), &run_id, false).await?;
|
||||
|
|
@ -68,7 +68,7 @@ pub(crate) async fn dispatch(
|
|||
}
|
||||
RunCommands::Attach(AttachArgs { server, run }) => {
|
||||
let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr()));
|
||||
let ctx = CommandContext::for_target(&server, printer, cli.clone(), cli_layer)?;
|
||||
let ctx = CommandContext::for_target(&server, printer, cli_layer)?;
|
||||
let client = ctx.server().await?;
|
||||
let run_id = client.resolve_run(&run).await?.run_id;
|
||||
let exit_code = Box::pin(attach::attach_run_with_client(
|
||||
|
|
@ -77,6 +77,7 @@ pub(crate) async fn dispatch(
|
|||
false,
|
||||
styles,
|
||||
cli.output.format == OutputFormat::Json,
|
||||
ctx.user_settings().cli.output.verbosity == OutputVerbosity::Verbose,
|
||||
printer,
|
||||
))
|
||||
.await?;
|
||||
|
|
@ -112,9 +113,8 @@ pub(crate) async fn dispatch(
|
|||
let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr()));
|
||||
#[cfg(feature = "sleep_inhibitor")]
|
||||
let _sleep_guard = {
|
||||
let ctx =
|
||||
CommandContext::for_target(&args.server, printer, cli.clone(), cli_layer)?;
|
||||
crate::sleep_inhibitor::guard(ctx.cli_settings().exec.prevent_idle_sleep)
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
|
||||
crate::sleep_inhibitor::guard(ctx.user_settings().cli.exec.prevent_idle_sleep)
|
||||
};
|
||||
Box::pin(resume::resume_command(
|
||||
args, styles, cli, cli_layer, printer,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use anyhow::{Context, Result};
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
use tracing::info;
|
||||
|
|
@ -10,12 +10,12 @@ use crate::shared::print_json_pretty;
|
|||
|
||||
pub(crate) async fn run(
|
||||
args: PreviewArgs,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
process_local_json: bool,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli.clone(), cli_layer)?;
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
|
||||
let client = ctx.server().await?;
|
||||
let run_id = client.resolve_run(&args.run).await?.run_id;
|
||||
let expires_in_secs =
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat, OutputVerbosity};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_util::terminal::Styles;
|
||||
|
||||
|
|
@ -15,11 +15,11 @@ use crate::shared::print_json_pretty;
|
|||
pub(crate) async fn resume_command(
|
||||
args: ResumeArgs,
|
||||
styles: &'static Styles,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> anyhow::Result<()> {
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli.clone(), cli_layer)?;
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
|
||||
let client = ctx.server().await?;
|
||||
let run_id = client.resolve_run(&args.run).await?.run_id;
|
||||
|
||||
|
|
@ -39,6 +39,7 @@ pub(crate) async fn resume_command(
|
|||
true,
|
||||
styles,
|
||||
json,
|
||||
ctx.user_settings().cli.output.verbosity == OutputVerbosity::Verbose,
|
||||
printer,
|
||||
))
|
||||
.await?;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use cli_table::format::{Border, Separator};
|
|||
use cli_table::{Cell, CellStruct, Color, Style, Table};
|
||||
use fabro_checkpoint::git::Store;
|
||||
use fabro_types::run_event::{CheckpointCompletedProps, RunRewoundProps, RunSubmittedProps};
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_types::{EventBody, RunEvent};
|
||||
use fabro_util::printer::Printer;
|
||||
|
|
@ -17,7 +17,7 @@ use serde::Serialize;
|
|||
|
||||
use crate::args::RewindArgs;
|
||||
use crate::command_context::CommandContext;
|
||||
use crate::commands::store::rebuild::rebuild_run_store;
|
||||
use crate::commands::rebuild::rebuild_run_store;
|
||||
use crate::server_client::Client;
|
||||
use crate::shared::repo::ensure_matching_repo_origin;
|
||||
use crate::shared::{color_if, print_json_pretty};
|
||||
|
|
@ -33,12 +33,12 @@ pub(crate) struct TimelineEntryJson {
|
|||
pub(crate) async fn run(
|
||||
args: &RewindArgs,
|
||||
styles: &Styles,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let repo = Repository::discover(".").context("not in a git repository")?;
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli.clone(), cli_layer)?;
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
|
||||
let client = ctx.server().await?;
|
||||
let run_id = client.resolve_run(&args.run_id).await?.run_id;
|
||||
let state = client.get_run_state(&run_id).await?;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
std::io::BufReader; not on a Tokio path"
|
||||
)]
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::io::{BufRead as StdBufRead, BufReader as StdBufReader};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
|
@ -107,6 +108,7 @@ pub(crate) async fn execute(
|
|||
artifact_sink,
|
||||
run_control: Some(run_control),
|
||||
github_app,
|
||||
github_permissions: HashMap::new(),
|
||||
vault,
|
||||
on_node: None,
|
||||
registry_override: None,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use anyhow::{Result, bail};
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
use tracing::info;
|
||||
|
|
@ -10,7 +10,7 @@ use crate::shared::print_json_pretty;
|
|||
|
||||
pub(crate) async fn run(
|
||||
args: SshArgs,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
process_local_json: bool,
|
||||
printer: Printer,
|
||||
|
|
@ -19,7 +19,7 @@ pub(crate) async fn run(
|
|||
require_no_json_override(process_local_json)?;
|
||||
}
|
||||
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli.clone(), cli_layer)?;
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
|
||||
let client = ctx.server().await?;
|
||||
let run_id = client.resolve_run(&args.run).await?.run_id;
|
||||
let ssh = client.create_run_ssh_access(&run_id, args.ttl).await?;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use std::io::Write;
|
|||
|
||||
use anyhow::{Result, bail};
|
||||
use fabro_types::RunId;
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_util::terminal::Styles;
|
||||
|
|
@ -27,11 +27,11 @@ use crate::shared::{format_duration_ms, format_usd_micros, run_status_kind};
|
|||
pub(crate) async fn run(
|
||||
args: &WaitArgs,
|
||||
styles: &Styles,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli.clone(), cli_layer)?;
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
|
||||
let client = ctx.server().await?;
|
||||
let run_id = client.resolve_run(&args.run).await?.run_id;
|
||||
info!(run_id = %run_id, "Waiting for run to complete");
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use anyhow::{Result, bail};
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
|
||||
|
|
@ -11,11 +11,11 @@ use crate::shared::print_json_pretty;
|
|||
|
||||
pub(crate) async fn archive_command(
|
||||
args: &RunsArchiveArgs,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli.clone(), cli_layer)?;
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
|
||||
run_bulk(
|
||||
Action::Archive,
|
||||
&args.runs,
|
||||
|
|
@ -28,11 +28,11 @@ pub(crate) async fn archive_command(
|
|||
|
||||
pub(crate) async fn unarchive_command(
|
||||
args: &RunsUnarchiveArgs,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli.clone(), cli_layer)?;
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
|
||||
run_bulk(
|
||||
Action::Unarchive,
|
||||
&args.runs,
|
||||
|
|
@ -66,7 +66,7 @@ async fn run_bulk(
|
|||
action: Action,
|
||||
identifiers: &[String],
|
||||
client: &server_client::Client,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let json = cli.output.format == OutputFormat::Json;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
use anyhow::Result;
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::cli::CliLayer;
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_workflow::run_status::RunStatus;
|
||||
|
|
@ -21,13 +20,8 @@ pub(crate) struct InspectOutput {
|
|||
pub sandbox: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
pub(crate) async fn run(
|
||||
args: &InspectArgs,
|
||||
cli: &CliSettings,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli.clone(), cli_layer)?;
|
||||
pub(crate) async fn run(args: &InspectArgs, cli_layer: &CliLayer, printer: Printer) -> Result<()> {
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
|
||||
let client = ctx.server().await?;
|
||||
let run = ServerRunSummaryInfo::from_summary(client.resolve_run(&args.run).await?);
|
||||
let run_id = run.run_id();
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use anyhow::Result;
|
|||
use chrono::Utc;
|
||||
use cli_table::format::{Border, Separator};
|
||||
use cli_table::{Cell, CellStruct, Color, Style, Table};
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_util::terminal::Styles;
|
||||
|
|
@ -20,11 +20,11 @@ use crate::shared::{color_if, format_duration_ms, run_status_kind, tilde_path};
|
|||
pub(crate) async fn list_command(
|
||||
args: &RunsListArgs,
|
||||
styles: &Styles,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli.clone(), cli_layer)?;
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
|
||||
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
|
||||
let label_filters = parse_label_filters(&args.filter.label);
|
||||
let filtered = filter_server_runs(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use anyhow::Result;
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::CliLayer;
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_util::terminal::Styles;
|
||||
|
|
@ -13,7 +13,7 @@ pub(crate) mod rm;
|
|||
|
||||
pub(crate) async fn dispatch(
|
||||
cmd: RunsCommands,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
|
|
@ -23,7 +23,7 @@ pub(crate) async fn dispatch(
|
|||
list::list_command(&args, &styles, cli, cli_layer, printer).await
|
||||
}
|
||||
RunsCommands::Rm(args) => rm::remove_command(&args, cli, cli_layer, printer).await,
|
||||
RunsCommands::Inspect(args) => inspect::run(&args, cli, cli_layer, printer).await,
|
||||
RunsCommands::Inspect(args) => inspect::run(&args, cli_layer, printer).await,
|
||||
RunsCommands::Archive(args) => {
|
||||
archive::archive_command(&args, cli, cli_layer, printer).await
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use anyhow::{Result, bail};
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
|
||||
|
|
@ -11,18 +11,18 @@ use crate::shared::print_json_pretty;
|
|||
|
||||
pub(crate) async fn remove_command(
|
||||
args: &RunsRemoveArgs,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli.clone(), cli_layer)?;
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
|
||||
remove_from(args, ctx.server().await?.as_ref(), cli, printer).await
|
||||
}
|
||||
|
||||
async fn remove_from(
|
||||
args: &RunsRemoveArgs,
|
||||
client: &server_client::Client,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let json = cli.output.format == OutputFormat::Json;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use anyhow::Result;
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::CliLayer;
|
||||
use fabro_util::printer::Printer;
|
||||
|
||||
|
|
@ -7,7 +7,7 @@ use crate::args::SandboxCommand;
|
|||
|
||||
pub(crate) async fn dispatch(
|
||||
command: SandboxCommand,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
process_local_json: bool,
|
||||
printer: Printer,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use anyhow::Result;
|
|||
use chrono::{DateTime, Utc};
|
||||
use cli_table::format::{Border, Separator};
|
||||
use cli_table::{Cell, CellStruct, Style, Table};
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_util::terminal::Styles;
|
||||
|
|
@ -25,7 +25,7 @@ fn format_age(dt: DateTime<Utc>, now: DateTime<Utc>) -> String {
|
|||
pub(super) async fn list_command(
|
||||
client: &Client,
|
||||
_args: &SecretListArgs,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let secrets = client.list_secrets().await?;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ mod rm;
|
|||
mod set;
|
||||
|
||||
use anyhow::Result;
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::CliLayer;
|
||||
use fabro_util::printer::Printer;
|
||||
|
||||
|
|
@ -12,11 +12,11 @@ use crate::command_context::CommandContext;
|
|||
|
||||
pub(crate) async fn dispatch(
|
||||
ns: SecretNamespace,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let ctx = CommandContext::for_target(&ns.target, printer, cli.clone(), cli_layer)?;
|
||||
let ctx = CommandContext::for_target(&ns.target, printer, cli_layer)?;
|
||||
let server = ctx.server().await?;
|
||||
match ns.command {
|
||||
SecretCommand::List(args) => list::list_command(&server, &args, cli, printer).await,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use anyhow::Result;
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
use fabro_util::printer::Printer;
|
||||
|
||||
|
|
@ -10,7 +10,7 @@ use crate::shared::print_json_pretty;
|
|||
pub(super) async fn rm_command(
|
||||
client: &Client,
|
||||
args: &SecretRmArgs,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
client.delete_secret_by_name(&args.key).await?;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use std::io::{IsTerminal, Read as _};
|
|||
|
||||
use anyhow::{Context as _, Result, bail};
|
||||
use fabro_api::types;
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
use fabro_util::printer::Printer;
|
||||
use tokio::task::spawn_blocking;
|
||||
|
|
@ -60,7 +60,7 @@ async fn resolve_value(args: &SecretSetArgs) -> Result<String> {
|
|||
pub(super) async fn set_command(
|
||||
client: &Client,
|
||||
args: &SecretSetArgs,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let value = resolve_value(args).await?;
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
pub(crate) mod dump;
|
||||
pub(crate) mod rebuild;
|
||||
mod run_export;
|
||||
|
||||
use anyhow::Result;
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::cli::CliLayer;
|
||||
use fabro_util::printer::Printer;
|
||||
|
||||
use crate::args::{StoreCommand, StoreNamespace};
|
||||
|
||||
pub(crate) async fn dispatch(
|
||||
ns: StoreNamespace,
|
||||
cli: &CliSettings,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
match ns.command {
|
||||
StoreCommand::Dump(args) => dump::dump_command(&args, cli, cli_layer, printer).await,
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
pub(super) use fabro_workflow::run_dump::RunDump as StoreRunExport;
|
||||
|
|
@ -3,7 +3,7 @@ use chrono::{DateTime, Utc};
|
|||
use cli_table::format::{Border, Justify, Separator};
|
||||
use cli_table::{Cell, CellStruct, Style, Table};
|
||||
use fabro_api::types;
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
|
||||
|
|
@ -13,11 +13,11 @@ use crate::shared::{format_size, print_json_pretty};
|
|||
|
||||
pub(super) async fn df_command(
|
||||
args: &DfArgs,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let ctx = CommandContext::for_connection(&args.connection, printer, cli.clone(), cli_layer)?;
|
||||
let ctx = CommandContext::for_connection(&args.connection, printer, cli_layer)?;
|
||||
let server = ctx.server().await?;
|
||||
let json = cli.output.format == OutputFormat::Json;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use anyhow::Result;
|
||||
use fabro_client::sse;
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
use futures::StreamExt;
|
||||
|
|
@ -10,11 +10,11 @@ use crate::command_context::CommandContext;
|
|||
|
||||
pub(super) async fn events_command(
|
||||
args: &SystemEventsArgs,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let ctx = CommandContext::for_connection(&args.connection, printer, cli.clone(), cli_layer)?;
|
||||
let ctx = CommandContext::for_connection(&args.connection, printer, cli_layer)?;
|
||||
let server = ctx.server().await?;
|
||||
let mut stream = server.attach_events(&args.run_ids).await?;
|
||||
let mut pending = Vec::new();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use anyhow::Result;
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
|
||||
|
|
@ -9,11 +9,11 @@ use crate::shared::print_json_pretty;
|
|||
|
||||
pub(super) async fn info_command(
|
||||
args: &SystemInfoArgs,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let ctx = CommandContext::for_connection(&args.connection, printer, cli.clone(), cli_layer)?;
|
||||
let ctx = CommandContext::for_connection(&args.connection, printer, cli_layer)?;
|
||||
let server = ctx.server().await?;
|
||||
let response = server.get_system_info().await?;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ mod info;
|
|||
mod prune;
|
||||
|
||||
use anyhow::Result;
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::CliLayer;
|
||||
use fabro_util::printer::Printer;
|
||||
pub(crate) use prune::parse_duration;
|
||||
|
|
@ -13,7 +13,7 @@ use crate::args::{SystemCommand, SystemNamespace};
|
|||
|
||||
pub(crate) async fn dispatch(
|
||||
ns: SystemNamespace,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::collections::HashMap;
|
|||
|
||||
use anyhow::{Context, Result, bail};
|
||||
use fabro_api::types;
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
use tracing::{debug, info};
|
||||
|
|
@ -13,11 +13,11 @@ use crate::shared::{format_size, print_json_pretty};
|
|||
|
||||
pub(super) async fn prune_command(
|
||||
args: &RunsPruneArgs,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let ctx = CommandContext::for_connection(&args.connection, printer, cli.clone(), cli_layer)?;
|
||||
let ctx = CommandContext::for_connection(&args.connection, printer, cli_layer)?;
|
||||
let server = ctx.server().await?;
|
||||
let response = server
|
||||
.prune_runs(types::PruneRunsRequest {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ use std::time::Duration;
|
|||
use anyhow::{Context, Result};
|
||||
use fabro_config::Storage;
|
||||
use fabro_config::daemon::ServerDaemon;
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
use fabro_util::Home;
|
||||
use fabro_util::printer::Printer;
|
||||
|
|
@ -45,7 +45,7 @@ struct Inventory {
|
|||
)]
|
||||
pub(crate) async fn run_uninstall(
|
||||
args: &UninstallArgs,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let json = cli.output.format == OutputFormat::Json;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ use std::io::{IsTerminal, Write};
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::{Context, Result, bail};
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
use fabro_util::printer::Printer;
|
||||
use semver::Version;
|
||||
|
|
@ -436,7 +436,7 @@ impl UpgradeCheckState {
|
|||
|
||||
pub(crate) async fn run_upgrade(
|
||||
args: UpgradeArgs,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let current_exe = std::env::current_exe()
|
||||
|
|
@ -599,7 +599,7 @@ pub(crate) async fn run_upgrade(
|
|||
|
||||
fn run_upgrade_brew(
|
||||
args: &UpgradeArgs,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
printer: Printer,
|
||||
channel: BrewChannel,
|
||||
) -> Result<()> {
|
||||
|
|
@ -1153,7 +1153,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn run_upgrade_brew_refuses_by_default() {
|
||||
let cli = CliSettings::default();
|
||||
let cli = CliNamespace::default();
|
||||
let err = run_upgrade_brew(
|
||||
&brew_args(None, false, false, false),
|
||||
&cli,
|
||||
|
|
@ -1169,7 +1169,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn run_upgrade_brew_dry_run_returns_ok() {
|
||||
let cli = CliSettings::default();
|
||||
let cli = CliNamespace::default();
|
||||
let result = run_upgrade_brew(
|
||||
&brew_args(None, false, false, true),
|
||||
&cli,
|
||||
|
|
@ -1181,7 +1181,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn run_upgrade_brew_rejects_version_flag() {
|
||||
let cli = CliSettings::default();
|
||||
let cli = CliNamespace::default();
|
||||
let err = run_upgrade_brew(
|
||||
&brew_args(Some("0.1.0"), false, false, false),
|
||||
&cli,
|
||||
|
|
@ -1194,7 +1194,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn run_upgrade_brew_rejects_prerelease_flag() {
|
||||
let cli = CliSettings::default();
|
||||
let cli = CliNamespace::default();
|
||||
let err = run_upgrade_brew(
|
||||
&brew_args(None, true, false, false),
|
||||
&cli,
|
||||
|
|
@ -1207,7 +1207,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn run_upgrade_brew_rejects_force_flag() {
|
||||
let cli = CliSettings::default();
|
||||
let cli = CliNamespace::default();
|
||||
let err = run_upgrade_brew(
|
||||
&brew_args(None, false, true, false),
|
||||
&cli,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use anyhow::bail;
|
|||
use fabro_config::load::load_settings_user;
|
||||
use fabro_config::user::active_settings_path;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_types::settings::{CliSettings, SettingsLayer};
|
||||
use fabro_types::settings::{CliNamespace, SettingsLayer};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_util::terminal::Styles;
|
||||
|
||||
|
|
@ -15,11 +15,11 @@ use crate::shared::{print_diagnostics, print_json_pretty, relative_path};
|
|||
pub(crate) async fn run(
|
||||
args: &ValidateArgs,
|
||||
styles: &Styles,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> anyhow::Result<()> {
|
||||
let ctx = CommandContext::for_target(&args.target, printer, cli.clone(), cli_layer)?;
|
||||
let ctx = CommandContext::for_target(&args.target, printer, cli_layer)?;
|
||||
let built = build_run_manifest(ManifestBuildInput {
|
||||
workflow: args.workflow.clone(),
|
||||
cwd: ctx.cwd().to_path_buf(),
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
use std::io::IsTerminal;
|
||||
|
||||
use anyhow::Result;
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
use serde_json::{Map, Value, json};
|
||||
|
|
@ -18,12 +18,12 @@ use crate::user_config::{self, ServerTarget};
|
|||
|
||||
pub(crate) async fn version_command(
|
||||
args: &VersionArgs,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let client = client_info();
|
||||
let ctx = CommandContext::for_target(&args.target, printer, cli.clone(), cli_layer)?;
|
||||
let ctx = CommandContext::for_target(&args.target, printer, cli_layer)?;
|
||||
let server_target = user_config::resolve_server_target(&args.target, ctx.machine_settings())?;
|
||||
let server_address = format_server_target(&server_target);
|
||||
let server_info = match ctx.server().await {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use std::path::Path;
|
|||
|
||||
use anyhow::{Context, Result, bail};
|
||||
use fabro_config::project::{discover_project_config, resolve_fabro_root};
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
use fabro_util::printer::Printer;
|
||||
|
||||
|
|
@ -16,7 +16,7 @@ use crate::shared::{print_json_pretty, relative_path};
|
|||
|
||||
pub(super) fn create_command(
|
||||
args: &WorkflowCreateArgs,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let cwd = std::env::current_dir()?;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use fabro_config::project::{
|
|||
WorkflowInfo, WorkflowSource, discover_project_config, list_workflows_detailed,
|
||||
resolve_fabro_root,
|
||||
};
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_util::terminal::Styles;
|
||||
|
|
@ -17,7 +17,7 @@ const GOAL_MAX_LEN: usize = 60;
|
|||
|
||||
pub(super) fn list_command(
|
||||
_args: &WorkflowListArgs,
|
||||
cli: &CliSettings,
|
||||
cli: &CliNamespace,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let styles = Styles::detect_stderr();
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@ mod create;
|
|||
mod list;
|
||||
|
||||
use anyhow::Result;
|
||||
use fabro_types::settings::CliSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_util::printer::Printer;
|
||||
|
||||
use crate::args::{WorkflowCommand, WorkflowNamespace};
|
||||
|
||||
pub(crate) fn dispatch(ns: WorkflowNamespace, cli: &CliSettings, printer: Printer) -> Result<()> {
|
||||
pub(crate) fn dispatch(ns: WorkflowNamespace, cli: &CliNamespace, printer: Printer) -> Result<()> {
|
||||
match ns.command {
|
||||
WorkflowCommand::List(args) => list::list_command(&args, cli, printer),
|
||||
WorkflowCommand::Create(args) => create::create_command(&args, cli, printer),
|
||||
|
|
|
|||
|
|
@ -34,8 +34,8 @@ pub(crate) fn bind_request(
|
|||
}
|
||||
|
||||
pub(crate) fn auth_methods(settings: &SettingsLayer) -> Vec<ServerAuthMethod> {
|
||||
fabro_config::resolve_server_from_file(settings)
|
||||
.map(|resolved| resolved.auth.methods)
|
||||
fabro_config::ServerSettings::from_layer(settings)
|
||||
.map(|resolved| resolved.server.auth.methods)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -173,9 +173,9 @@ async fn main_inner() -> (String, Result<()>) {
|
|||
cli: Some(cli_layer.clone()),
|
||||
..SettingsLayer::default()
|
||||
});
|
||||
let cli_settings = match user_config::resolve_cli_settings(&combined_settings) {
|
||||
Ok(cli_settings) => cli_settings,
|
||||
Err(err) => return (command_name, Err(err)),
|
||||
let cli_settings = match fabro_config::UserSettings::from_layer(&combined_settings) {
|
||||
Ok(settings) => settings.cli,
|
||||
Err(err) => return (command_name, Err(err.into())),
|
||||
};
|
||||
let printer = printer_from_verbosity(cli_settings.output.verbosity);
|
||||
|
||||
|
|
@ -247,8 +247,8 @@ async fn main_inner() -> (String, Result<()>) {
|
|||
Commands::Artifact(ns) => {
|
||||
commands::artifact::dispatch(ns, &cli_settings, &cli_layer, printer).await?;
|
||||
}
|
||||
Commands::Store(ns) => {
|
||||
commands::store::dispatch(ns, &cli_settings, &cli_layer, printer).await?;
|
||||
Commands::Dump(args) => {
|
||||
commands::dump::run(&args, &cli_settings, &cli_layer, printer).await?;
|
||||
}
|
||||
Commands::RunsCmd(cmd) => {
|
||||
commands::runs::dispatch(cmd, &cli_settings, &cli_layer, printer).await?;
|
||||
|
|
@ -318,14 +318,7 @@ async fn main_inner() -> (String, Result<()>) {
|
|||
commands::uninstall::run_uninstall(&args, &cli_settings, printer).await?;
|
||||
}
|
||||
Commands::Auth(ns) => {
|
||||
commands::auth::dispatch(
|
||||
ns,
|
||||
&cli_settings,
|
||||
&cli_layer,
|
||||
process_local_json,
|
||||
printer,
|
||||
)
|
||||
.await?;
|
||||
commands::auth::dispatch(ns, &cli_layer, process_local_json, printer).await?;
|
||||
}
|
||||
Commands::Pr(ns) => {
|
||||
Box::pin(commands::pr::dispatch(
|
||||
|
|
@ -353,14 +346,7 @@ async fn main_inner() -> (String, Result<()>) {
|
|||
commands::upgrade::run_upgrade(args, &cli_settings, printer).await?;
|
||||
}
|
||||
Commands::Provider(ns) => {
|
||||
commands::provider::dispatch(
|
||||
ns,
|
||||
&cli_settings,
|
||||
&cli_layer,
|
||||
process_local_json,
|
||||
printer,
|
||||
)
|
||||
.await?;
|
||||
commands::provider::dispatch(ns, &cli_layer, process_local_json, printer).await?;
|
||||
}
|
||||
Commands::Sandbox { command } => {
|
||||
commands::sandbox::dispatch(
|
||||
|
|
@ -519,7 +505,7 @@ async fn prepare_server_bootstrap(
|
|||
mod tests {
|
||||
use args::{
|
||||
AuthCommand, AuthNamespace, Commands, InstallGitHubStrategyArg, ModelsCommand,
|
||||
ProviderCommand, ProviderNamespace, StoreCommand, StoreNamespace,
|
||||
ProviderCommand, ProviderNamespace,
|
||||
};
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
|
|
@ -954,13 +940,11 @@ level = "warn"
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn parse_store_dump_command() {
|
||||
let cli = Cli::try_parse_from(["fabro", "store", "dump", "ABC123", "-o", "./out"])
|
||||
.expect("should parse");
|
||||
fn parse_dump_command() {
|
||||
let cli =
|
||||
Cli::try_parse_from(["fabro", "dump", "ABC123", "-o", "./out"]).expect("should parse");
|
||||
match *cli.command.unwrap() {
|
||||
Commands::Store(StoreNamespace {
|
||||
command: StoreCommand::Dump(args),
|
||||
}) => {
|
||||
Commands::Dump(args) => {
|
||||
assert_eq!(args.run, "ABC123");
|
||||
assert_eq!(args.output, std::path::PathBuf::from("./out"));
|
||||
}
|
||||
|
|
@ -1077,36 +1061,22 @@ level = "warn"
|
|||
assert_eq!(cli.command.as_ref().unwrap().name(), "settings");
|
||||
match *cli.command.unwrap() {
|
||||
Commands::Settings(args) => {
|
||||
assert!(!args.local);
|
||||
assert!(args.target.server.is_none());
|
||||
assert!(args.workflow.is_none());
|
||||
}
|
||||
_ => panic!("unexpected command variant"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_settings_with_workflow() {
|
||||
let cli = Cli::try_parse_from(["fabro", "settings", "demo"]).expect("should parse");
|
||||
match *cli.command.unwrap() {
|
||||
Commands::Settings(args) => {
|
||||
assert_eq!(args.workflow, Some(std::path::PathBuf::from("demo")));
|
||||
}
|
||||
_ => panic!("unexpected command variant"),
|
||||
}
|
||||
fn parse_settings_rejects_workflow_argument() {
|
||||
let result = Cli::try_parse_from(["fabro", "settings", "demo"]);
|
||||
assert!(result.is_err(), "should reject settings workflow argument");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_settings_local_mode() {
|
||||
let cli =
|
||||
Cli::try_parse_from(["fabro", "settings", "--local", "demo"]).expect("should parse");
|
||||
match *cli.command.unwrap() {
|
||||
Commands::Settings(args) => {
|
||||
assert!(args.local);
|
||||
assert_eq!(args.workflow, Some(std::path::PathBuf::from("demo")));
|
||||
}
|
||||
_ => panic!("unexpected command variant"),
|
||||
}
|
||||
fn parse_settings_rejects_local_flag() {
|
||||
let result = Cli::try_parse_from(["fabro", "settings", "--local"]);
|
||||
assert!(result.is_err(), "should reject settings --local");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use anyhow::Result;
|
|||
pub(crate) use fabro_client::ServerTarget;
|
||||
pub(crate) use fabro_config::user::*;
|
||||
use fabro_types::settings::cli::CliTargetSettings;
|
||||
use fabro_types::settings::{CliSettings, SettingsLayer};
|
||||
use fabro_types::settings::{CliNamespace, SettingsLayer};
|
||||
use fabro_util::version::FABRO_VERSION;
|
||||
use tracing::debug;
|
||||
|
||||
|
|
@ -30,41 +30,9 @@ pub(crate) fn load_settings_with_config_and_storage_dir(
|
|||
Ok(apply_storage_dir_override(layer, storage_dir))
|
||||
}
|
||||
|
||||
fn render_resolve_errors(errors: Vec<fabro_config::ResolveError>) -> anyhow::Error {
|
||||
anyhow::anyhow!(
|
||||
"failed to resolve cli settings:\n{}",
|
||||
errors
|
||||
.into_iter()
|
||||
.map(|error| error.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_cli_settings(file: &SettingsLayer) -> anyhow::Result<CliSettings> {
|
||||
fabro_config::resolve_cli_from_file(file).map_err(render_resolve_errors)
|
||||
}
|
||||
|
||||
pub(crate) fn apply_storage_dir_override(
|
||||
mut layer: SettingsLayer,
|
||||
storage_dir: Option<&Path>,
|
||||
) -> SettingsLayer {
|
||||
use fabro_types::settings::interp::InterpString;
|
||||
use fabro_types::settings::server::{ServerLayer, ServerStorageLayer};
|
||||
if let Some(dir) = storage_dir {
|
||||
let server = layer.server.get_or_insert_with(ServerLayer::default);
|
||||
let storage = server
|
||||
.storage
|
||||
.get_or_insert_with(ServerStorageLayer::default);
|
||||
storage.root = Some(InterpString::parse(&dir.display().to_string()));
|
||||
}
|
||||
|
||||
layer
|
||||
}
|
||||
|
||||
/// Pull the resolved CLI target configuration out of `[cli.target]`.
|
||||
/// Returns either an http(s) URL or a unix socket path.
|
||||
fn cli_target_from_settings(settings: &CliSettings) -> Option<String> {
|
||||
fn cli_target_from_settings(settings: &CliNamespace) -> Option<String> {
|
||||
let target = settings.target.as_ref()?;
|
||||
match target {
|
||||
CliTargetSettings::Http { url } => Some(url.as_source()),
|
||||
|
|
@ -73,8 +41,8 @@ fn cli_target_from_settings(settings: &CliSettings) -> Option<String> {
|
|||
}
|
||||
|
||||
fn configured_server_target(settings: &SettingsLayer) -> Result<Option<ServerTarget>> {
|
||||
let cli_settings = resolve_cli_settings(settings)?;
|
||||
let Some(value) = cli_target_from_settings(&cli_settings) else {
|
||||
let user_settings = fabro_config::UserSettings::from_layer(settings)?;
|
||||
let Some(value) = cli_target_from_settings(&user_settings.cli) else {
|
||||
return Ok(None);
|
||||
};
|
||||
parse_server_target(&value).map(Some)
|
||||
|
|
|
|||
|
|
@ -40,93 +40,10 @@ fn parse_settings(stdout: &[u8]) -> serde_json::Value {
|
|||
serde_yaml::from_slice(stdout).expect("stdout should be valid YAML settings")
|
||||
}
|
||||
|
||||
fn parse_settings_json(stdout: &[u8]) -> serde_json::Value {
|
||||
serde_json::from_slice(stdout).expect("stdout should be valid JSON settings")
|
||||
}
|
||||
|
||||
fn run_goal_inline(settings: &serde_json::Value) -> Option<&str> {
|
||||
let goal = settings.get("run")?.get("goal")?;
|
||||
(goal.get("type")?.as_str() == Some("inline"))
|
||||
.then(|| goal.get("value")?.as_str())
|
||||
.flatten()
|
||||
}
|
||||
|
||||
fn run_model_name(settings: &serde_json::Value) -> Option<&str> {
|
||||
settings.get("run")?.get("model")?.get("name")?.as_str()
|
||||
}
|
||||
|
||||
fn run_model_provider(settings: &serde_json::Value) -> Option<&str> {
|
||||
settings.get("run")?.get("model")?.get("provider")?.as_str()
|
||||
}
|
||||
|
||||
fn run_inputs(settings: &serde_json::Value) -> &serde_json::Map<String, serde_json::Value> {
|
||||
settings
|
||||
.get("run")
|
||||
.and_then(|run| run.get("inputs"))
|
||||
.and_then(serde_json::Value::as_object)
|
||||
.expect("run.inputs")
|
||||
}
|
||||
|
||||
fn run_sandbox(settings: &serde_json::Value) -> &serde_json::Value {
|
||||
settings
|
||||
.get("run")
|
||||
.and_then(|run| run.get("sandbox"))
|
||||
.expect("run.sandbox")
|
||||
}
|
||||
|
||||
fn run_checkpoint(settings: &serde_json::Value) -> &serde_json::Value {
|
||||
settings
|
||||
.get("run")
|
||||
.and_then(|run| run.get("checkpoint"))
|
||||
.expect("run.checkpoint")
|
||||
}
|
||||
|
||||
fn run_hooks(settings: &serde_json::Value) -> &[serde_json::Value] {
|
||||
settings
|
||||
.get("run")
|
||||
.and_then(|run| run.get("hooks"))
|
||||
.and_then(serde_json::Value::as_array)
|
||||
.expect("run.hooks")
|
||||
}
|
||||
|
||||
fn run_agent_mcps(settings: &serde_json::Value) -> &serde_json::Map<String, serde_json::Value> {
|
||||
settings
|
||||
.get("run")
|
||||
.and_then(|run| run.get("agent"))
|
||||
.and_then(|agent| agent.get("mcps"))
|
||||
.and_then(serde_json::Value::as_object)
|
||||
.expect("run.agent.mcps")
|
||||
}
|
||||
|
||||
fn auto_approve_enabled(settings: &serde_json::Value) -> bool {
|
||||
settings
|
||||
.get("run")
|
||||
.and_then(|run| run.get("execution"))
|
||||
.and_then(|execution| execution.get("approval"))
|
||||
.and_then(serde_json::Value::as_str)
|
||||
== Some("auto")
|
||||
}
|
||||
|
||||
fn run_prepare_commands(settings: &serde_json::Value) -> Vec<String> {
|
||||
settings
|
||||
.get("run")
|
||||
.and_then(|run| run.get("prepare"))
|
||||
.and_then(|prepare| prepare.get("commands"))
|
||||
.and_then(serde_json::Value::as_array)
|
||||
.expect("run.prepare.commands")
|
||||
.iter()
|
||||
.map(|value| {
|
||||
value
|
||||
.as_str()
|
||||
.expect("command should be a string")
|
||||
.to_string()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn server_storage_root(settings: &serde_json::Value) -> &str {
|
||||
settings
|
||||
.get("server")
|
||||
.and_then(|server| server.get("server"))
|
||||
.and_then(|server| server.get("storage"))
|
||||
.and_then(|storage| storage.get("root"))
|
||||
.and_then(serde_json::Value::as_str)
|
||||
|
|
@ -157,7 +74,7 @@ shared = "server"
|
|||
}
|
||||
|
||||
fn resolved_server_settings_fixture() -> serde_json::Value {
|
||||
let settings = fabro_config::resolve(&server_settings_layer_fixture())
|
||||
let settings = fabro_config::ServerSettings::from_layer(&server_settings_layer_fixture())
|
||||
.expect("server settings fixture should resolve");
|
||||
serde_json::to_value(settings).expect("resolved settings payload should serialize")
|
||||
}
|
||||
|
|
@ -389,155 +306,6 @@ script = "workflow-setup"
|
|||
// Tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn settings_local_merges_cli_and_project_defaults() {
|
||||
let context = test_context!();
|
||||
let project = setup_settings_fixture(&context);
|
||||
|
||||
let output = context
|
||||
.settings()
|
||||
.arg("--local")
|
||||
.current_dir(project.path())
|
||||
.assert()
|
||||
.success()
|
||||
.get_output()
|
||||
.stdout
|
||||
.clone();
|
||||
|
||||
let cfg = parse_settings(&output);
|
||||
assert!(cfg.get("_version").is_none());
|
||||
assert_eq!(cfg["project"]["directory"].as_str(), Some("."));
|
||||
assert_eq!(cfg["workflow"]["graph"].as_str(), Some("workflow.fabro"));
|
||||
assert_eq!(cfg["run"]["execution"]["approval"].as_str(), Some("prompt"));
|
||||
assert_eq!(cfg["run"]["sandbox"]["provider"].as_str(), Some("daytona"));
|
||||
assert_eq!(run_model_name(&cfg), Some("project-model"));
|
||||
assert_eq!(run_model_provider(&cfg), Some("openai"));
|
||||
assert_eq!(run_goal_inline(&cfg), None);
|
||||
|
||||
// v2 R22: run.inputs replaces the inherited map wholesale rather than
|
||||
// merging by key, so the project layer wipes out the CLI layer's inputs.
|
||||
let vars = run_inputs(&cfg);
|
||||
assert_eq!(
|
||||
vars.get("project_only").and_then(serde_json::Value::as_str),
|
||||
Some("1")
|
||||
);
|
||||
assert_eq!(
|
||||
vars.get("shared").and_then(serde_json::Value::as_str),
|
||||
Some("project")
|
||||
);
|
||||
assert!(
|
||||
!vars.contains_key("cli_only"),
|
||||
"run.inputs should replace across layers, not merge by key"
|
||||
);
|
||||
|
||||
// v2 R71: provider-native maps such as run.sandbox.daytona.labels remain
|
||||
// sticky merge-by-key, so CLI labels persist under the project layer.
|
||||
let sandbox = run_sandbox(&cfg);
|
||||
let labels = &sandbox["daytona"]["labels"];
|
||||
assert_eq!(labels["cli_only"].as_str(), Some("1"));
|
||||
assert_eq!(labels["shared"].as_str(), Some("cli"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn settings_local_workflow_name_applies_run_overlay_and_deep_merges() {
|
||||
let context = test_context!();
|
||||
let project = setup_settings_fixture(&context);
|
||||
|
||||
let output = context
|
||||
.settings()
|
||||
.current_dir(project.path())
|
||||
.args(["--local", "demo"])
|
||||
.assert()
|
||||
.success()
|
||||
.get_output()
|
||||
.stdout
|
||||
.clone();
|
||||
|
||||
let cfg = parse_settings(&output);
|
||||
assert_eq!(run_goal_inline(&cfg), Some("demo goal"));
|
||||
assert_eq!(run_model_name(&cfg), Some("run-model"));
|
||||
assert_eq!(run_model_provider(&cfg), Some("anthropic"));
|
||||
|
||||
// v2 R22: run.inputs replaces wholesale, so the workflow layer wins
|
||||
// over project and cli.
|
||||
let vars = run_inputs(&cfg);
|
||||
assert_eq!(vars.get("run_only").and_then(|v| v.as_str()), Some("1"));
|
||||
assert_eq!(vars.get("shared").and_then(|v| v.as_str()), Some("run"));
|
||||
|
||||
// checkpoint.exclude_globs is a security/policy list: replace by default.
|
||||
let checkpoint = run_checkpoint(&cfg);
|
||||
assert_eq!(
|
||||
checkpoint["exclude_globs"],
|
||||
serde_json::json!(["run-only", "shared"])
|
||||
);
|
||||
|
||||
// Hooks: id-based replacement. The "shared" hook appears in both cli and
|
||||
// workflow layers and resolves to the workflow entry; project and run-only
|
||||
// contribute the other two ids.
|
||||
let hooks = run_hooks(&cfg);
|
||||
assert!(hooks.len() >= 2);
|
||||
let shared_hook = hooks
|
||||
.iter()
|
||||
.find(|hook| hook["name"].as_str() == Some("shared"))
|
||||
.expect("shared hook");
|
||||
assert_eq!(shared_hook["command"].as_str(), Some("echo run"));
|
||||
assert!(
|
||||
hooks
|
||||
.iter()
|
||||
.any(|hook| hook["name"].as_str() == Some("run-only"))
|
||||
);
|
||||
|
||||
let mcps = run_agent_mcps(&cfg);
|
||||
let shared = mcps.get("shared").expect("shared mcp");
|
||||
assert_eq!(shared["transport"]["type"].as_str(), Some("stdio"));
|
||||
assert_eq!(
|
||||
shared["transport"]["command"],
|
||||
serde_json::json!(["echo", "run"])
|
||||
);
|
||||
assert!(mcps.contains_key("run_only"));
|
||||
|
||||
// run.sandbox.daytona.labels stays sticky merge-by-key per R71.
|
||||
let sandbox = run_sandbox(&cfg);
|
||||
let labels = &sandbox["daytona"]["labels"];
|
||||
assert_eq!(labels["run_only"].as_str(), Some("1"));
|
||||
assert_eq!(labels["shared"].as_str(), Some("run"));
|
||||
|
||||
// run.sandbox.env stays sticky merge-by-key per R71.
|
||||
let env = &sandbox["env"];
|
||||
assert_eq!(env["CLI_ONLY"].as_str(), Some("1"));
|
||||
assert_eq!(env["RUN_ONLY"].as_str(), Some("1"));
|
||||
assert_eq!(env["SHARED"].as_str(), Some("run"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn settings_local_explicit_workflow_path_uses_workflow_project_layers() {
|
||||
let mut context = test_context!();
|
||||
let (project, _storage_dir) = setup_external_workflow_fixture(&mut context);
|
||||
let cwd = tempfile::tempdir().unwrap();
|
||||
let workflow = project.path().join("workflow.toml");
|
||||
|
||||
// Remove FABRO_STORAGE_DIR so the CLI uses storage_dir from settings.toml
|
||||
let output = context
|
||||
.settings()
|
||||
.env_remove("FABRO_STORAGE_DIR")
|
||||
.current_dir(cwd.path())
|
||||
.args(["--local", workflow.to_str().unwrap()])
|
||||
.assert()
|
||||
.success()
|
||||
.get_output()
|
||||
.stdout
|
||||
.clone();
|
||||
|
||||
let cfg = parse_settings(&output);
|
||||
assert!(auto_approve_enabled(&cfg));
|
||||
// v2 R30: run.prepare.steps replaces the whole ordered list across layers.
|
||||
// The highest-precedence layer (workflow) wins.
|
||||
assert_eq!(run_prepare_commands(&cfg), vec![
|
||||
"workflow-setup".to_string()
|
||||
]);
|
||||
assert_eq!(run_sandbox(&cfg)["preserve"].as_bool(), Some(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_explicit_workflow_path_uses_project_config_relative_to_workflow() {
|
||||
let mut context = test_context!();
|
||||
|
|
@ -608,254 +376,6 @@ fn create_explicit_workflow_path_uses_project_config_relative_to_workflow() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn settings_fabro_path_matches_ambient_defaults() {
|
||||
let context = test_context!();
|
||||
let project = setup_settings_fixture(&context);
|
||||
|
||||
let ambient = context
|
||||
.settings()
|
||||
.arg("--local")
|
||||
.current_dir(project.path())
|
||||
.assert()
|
||||
.success()
|
||||
.get_output()
|
||||
.stdout
|
||||
.clone();
|
||||
let graph = context
|
||||
.settings()
|
||||
.current_dir(project.path())
|
||||
.args(["--local", "standalone.fabro"])
|
||||
.assert()
|
||||
.success()
|
||||
.get_output()
|
||||
.stdout
|
||||
.clone();
|
||||
|
||||
assert_eq!(parse_settings(&graph), parse_settings(&ambient));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn settings_missing_run_config_errors() {
|
||||
let context = test_context!();
|
||||
let project = setup_settings_fixture(&context);
|
||||
|
||||
let mut cmd = context.settings();
|
||||
cmd.current_dir(project.path());
|
||||
cmd.args(["--local", "missing.toml"]);
|
||||
let output = cmd.output().expect("command should execute");
|
||||
assert!(!output.status.success());
|
||||
assert!(String::from_utf8_lossy(&output.stdout).trim().is_empty());
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
assert!(
|
||||
stderr.contains("workflow not found:"),
|
||||
"stderr should report missing workflow path, got:\n{stderr}"
|
||||
);
|
||||
assert!(
|
||||
stderr.contains("missing.toml"),
|
||||
"stderr should include missing workflow filename, got:\n{stderr}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn settings_legacy_cli_config_is_silently_ignored() {
|
||||
let context = test_context!();
|
||||
let project = tempfile::tempdir().unwrap();
|
||||
|
||||
context.write_home(
|
||||
".fabro/cli.toml",
|
||||
r#"
|
||||
_version = 1
|
||||
|
||||
[cli.output]
|
||||
verbosity = "verbose"
|
||||
|
||||
[run.model]
|
||||
name = "legacy-model"
|
||||
"#,
|
||||
);
|
||||
|
||||
let assert = context
|
||||
.settings()
|
||||
.arg("--local")
|
||||
.current_dir(project.path())
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
assert!(
|
||||
assert.get_output().stderr.is_empty(),
|
||||
"settings should not warn about legacy config files: {}",
|
||||
String::from_utf8_lossy(&assert.get_output().stderr)
|
||||
);
|
||||
|
||||
let cfg = parse_settings(&assert.get_output().stdout);
|
||||
assert_eq!(cfg["cli"]["output"]["verbosity"].as_str(), Some("normal"));
|
||||
assert!(
|
||||
cfg["run"]["model"].get("name").is_none(),
|
||||
"resolved dense settings should omit an unset run.model.name"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn settings_legacy_user_config_is_silently_ignored() {
|
||||
let context = test_context!();
|
||||
let project = tempfile::tempdir().unwrap();
|
||||
|
||||
context.write_home(
|
||||
".fabro/user.toml",
|
||||
r#"
|
||||
_version = 1
|
||||
|
||||
[cli.output]
|
||||
verbosity = "verbose"
|
||||
|
||||
[run.model]
|
||||
name = "legacy-model"
|
||||
"#,
|
||||
);
|
||||
|
||||
let assert = context
|
||||
.settings()
|
||||
.arg("--local")
|
||||
.current_dir(project.path())
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
assert!(
|
||||
assert.get_output().stderr.is_empty(),
|
||||
"settings should not warn about legacy config files: {}",
|
||||
String::from_utf8_lossy(&assert.get_output().stderr)
|
||||
);
|
||||
|
||||
let cfg = parse_settings(&assert.get_output().stdout);
|
||||
assert_eq!(cfg["cli"]["output"]["verbosity"].as_str(), Some("normal"));
|
||||
assert!(
|
||||
cfg["run"]["model"].get("name").is_none(),
|
||||
"resolved dense settings should omit an unset run.model.name"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn settings_legacy_server_config_is_silently_ignored() {
|
||||
let context = test_context!();
|
||||
let project = tempfile::tempdir().unwrap();
|
||||
|
||||
context.write_home(
|
||||
".fabro/server.toml",
|
||||
r#"
|
||||
_version = 1
|
||||
|
||||
[cli.output]
|
||||
verbosity = "verbose"
|
||||
|
||||
[run.model]
|
||||
name = "legacy-model"
|
||||
"#,
|
||||
);
|
||||
|
||||
let assert = context
|
||||
.settings()
|
||||
.arg("--local")
|
||||
.current_dir(project.path())
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
assert!(
|
||||
assert.get_output().stderr.is_empty(),
|
||||
"settings should not warn about legacy config files: {}",
|
||||
String::from_utf8_lossy(&assert.get_output().stderr)
|
||||
);
|
||||
|
||||
let cfg = parse_settings(&assert.get_output().stdout);
|
||||
assert_eq!(cfg["cli"]["output"]["verbosity"].as_str(), Some("normal"));
|
||||
assert!(
|
||||
cfg["run"]["model"].get("name").is_none(),
|
||||
"resolved dense settings should omit an unset run.model.name"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn settings_user_config_wins_over_legacy_cli_config() {
|
||||
let context = test_context!();
|
||||
let project = setup_settings_fixture(&context);
|
||||
context.write_home(
|
||||
".fabro/cli.toml",
|
||||
r#"
|
||||
_version = 1
|
||||
|
||||
[run.model]
|
||||
name = "legacy-model"
|
||||
|
||||
[run.inputs]
|
||||
shared = "legacy"
|
||||
"#,
|
||||
);
|
||||
|
||||
let assert = context
|
||||
.settings()
|
||||
.arg("--local")
|
||||
.current_dir(project.path())
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
assert!(
|
||||
assert.get_output().stderr.is_empty(),
|
||||
"settings should not warn about legacy config files: {}",
|
||||
String::from_utf8_lossy(&assert.get_output().stderr)
|
||||
);
|
||||
|
||||
let cfg = parse_settings(&assert.get_output().stdout);
|
||||
assert_eq!(run_model_name(&cfg), Some("project-model"));
|
||||
let vars = run_inputs(&cfg);
|
||||
assert_eq!(
|
||||
vars.get("shared").and_then(serde_json::Value::as_str),
|
||||
Some("project")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn settings_uses_fabro_home_for_home_config_resolution() {
|
||||
let context = test_context!();
|
||||
let fabro_home = tempfile::tempdir().unwrap();
|
||||
|
||||
std::fs::write(
|
||||
fabro_home.path().join("settings.toml"),
|
||||
r#"
|
||||
_version = 1
|
||||
|
||||
[cli.output]
|
||||
verbosity = "verbose"
|
||||
|
||||
[run.model]
|
||||
name = "from-fabro-home"
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let output = context
|
||||
.settings()
|
||||
.args(["--local", "--json"])
|
||||
.env("FABRO_HOME", fabro_home.path())
|
||||
.env_remove("FABRO_STORAGE_DIR")
|
||||
.output()
|
||||
.expect("command should execute");
|
||||
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"settings command failed:\nstdout:\n{}\nstderr:\n{}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr),
|
||||
);
|
||||
|
||||
let cfg = parse_settings_json(&output.stdout);
|
||||
assert!(cfg.get("_version").is_none());
|
||||
assert_eq!(cfg["cli"]["output"]["verbosity"].as_str(), Some("verbose"));
|
||||
assert_eq!(
|
||||
cfg["run"]["model"]["name"].as_str(),
|
||||
Some("from-fabro-home")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn settings_rejects_server_url_flag() {
|
||||
let context = test_context!();
|
||||
|
|
@ -883,32 +403,27 @@ fn settings_rejects_storage_dir_flag() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn settings_rejects_local_and_server_combination() {
|
||||
fn settings_rejects_local_flag() {
|
||||
let context = test_context!();
|
||||
context
|
||||
.settings()
|
||||
.args(["--local", "--server", "https://cli.example.com"])
|
||||
.arg("--local")
|
||||
.assert()
|
||||
.failure()
|
||||
.stderr(predicate::str::contains(
|
||||
"the argument '--local' cannot be used with '--server <SERVER>'",
|
||||
"unexpected argument '--local' found",
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn settings_rejects_workflow_without_local() {
|
||||
fn settings_rejects_workflow_argument() {
|
||||
let context = test_context!();
|
||||
let project = setup_settings_fixture(&context);
|
||||
|
||||
context
|
||||
.settings()
|
||||
.current_dir(project.path())
|
||||
.arg("demo")
|
||||
.assert()
|
||||
.failure()
|
||||
.stderr(predicate::str::contains(
|
||||
"WORKFLOW requires --local; use `fabro settings --local WORKFLOW`",
|
||||
));
|
||||
.stderr(predicate::str::contains("unexpected argument 'demo' found"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -918,12 +433,9 @@ fn settings_fetches_server_resolved_settings() {
|
|||
let server = MockServer::start();
|
||||
let server_settings = resolved_server_settings_fixture();
|
||||
let mock = server.mock(|when, then| {
|
||||
when.method("GET")
|
||||
.path("/api/v1/settings")
|
||||
.query_param("view", "resolved");
|
||||
when.method("GET").path("/api/v1/settings");
|
||||
then.status(200)
|
||||
.header("Content-Type", "application/json")
|
||||
.header("X-Fabro-Settings-View", "resolved")
|
||||
.body(server_settings_body(&server_settings));
|
||||
});
|
||||
context.write_home(
|
||||
|
|
@ -962,30 +474,25 @@ shared = "cli"
|
|||
|
||||
mock.assert();
|
||||
let cfg = parse_settings(&output);
|
||||
assert!(cfg.get("_version").is_none());
|
||||
assert_eq!(cfg["project"]["directory"].as_str(), Some("."));
|
||||
assert_eq!(cfg["workflow"]["graph"].as_str(), Some("workflow.fabro"));
|
||||
assert_eq!(cfg["run"]["execution"]["approval"].as_str(), Some("prompt"));
|
||||
assert_eq!(run_model_name(&cfg), Some("server-model"));
|
||||
assert_eq!(run_model_provider(&cfg), Some("openai"));
|
||||
assert_eq!(
|
||||
cfg["user"]["cli"]["output"]["verbosity"].as_str(),
|
||||
Some("verbose")
|
||||
);
|
||||
assert_eq!(
|
||||
cfg["user"]["features"]["session_sandboxes"].as_bool(),
|
||||
Some(false)
|
||||
);
|
||||
assert_eq!(
|
||||
cfg["server"]["server"]["auth"]["methods"][0].as_str(),
|
||||
Some("dev-token")
|
||||
);
|
||||
assert_eq!(server_storage_root(&cfg), "/srv/fabro-server");
|
||||
assert_eq!(cfg["cli"]["output"]["verbosity"].as_str(), Some("normal"));
|
||||
|
||||
// Server-backed mode now returns the selected server's own dense resolved
|
||||
// settings; local project/user overlays are not merged into the output.
|
||||
let vars = run_inputs(&cfg);
|
||||
assert_eq!(
|
||||
vars.get("server_only").and_then(serde_json::Value::as_str),
|
||||
Some("1")
|
||||
);
|
||||
assert_eq!(
|
||||
vars.get("shared").and_then(serde_json::Value::as_str),
|
||||
Some("server")
|
||||
);
|
||||
assert!(
|
||||
!vars.contains_key("project_only"),
|
||||
"server-backed settings output must not include local workflow/project overlays"
|
||||
cfg["server"]["server"]["artifacts"]["store"]["type"].as_str(),
|
||||
Some("local")
|
||||
);
|
||||
assert!(cfg.get("run").is_none());
|
||||
assert!(cfg.get("project").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -994,21 +501,16 @@ fn settings_cli_server_target_overrides_configured_server_target() {
|
|||
let project = setup_settings_fixture(&context);
|
||||
let configured_server = MockServer::start();
|
||||
let configured_mock = configured_server.mock(|when, then| {
|
||||
when.method("GET")
|
||||
.path("/api/v1/settings")
|
||||
.query_param("view", "resolved");
|
||||
when.method("GET").path("/api/v1/settings");
|
||||
then.status(500)
|
||||
.body("configured-server-should-not-be-used");
|
||||
});
|
||||
let cli_server = MockServer::start();
|
||||
let cli_server_settings = resolved_server_settings_fixture();
|
||||
let cli_mock = cli_server.mock(|when, then| {
|
||||
when.method("GET")
|
||||
.path("/api/v1/settings")
|
||||
.query_param("view", "resolved");
|
||||
when.method("GET").path("/api/v1/settings");
|
||||
then.status(200)
|
||||
.header("Content-Type", "application/json")
|
||||
.header("X-Fabro-Settings-View", "resolved")
|
||||
.body(server_settings_body(&cli_server_settings));
|
||||
});
|
||||
context.write_home(
|
||||
|
|
@ -1044,46 +546,6 @@ verbosity = "verbose"
|
|||
assert_eq!(server_storage_root(&cfg), "/srv/fabro-server");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn settings_errors_when_server_lacks_resolved_view_marker() {
|
||||
let context = test_context!();
|
||||
let project = setup_settings_fixture(&context);
|
||||
let server = MockServer::start();
|
||||
let server_settings = resolved_server_settings_fixture();
|
||||
let mock = server.mock(|when, then| {
|
||||
when.method("GET")
|
||||
.path("/api/v1/settings")
|
||||
.query_param("view", "resolved");
|
||||
then.status(200)
|
||||
.header("Content-Type", "application/json")
|
||||
.body(server_settings_body(&server_settings));
|
||||
});
|
||||
context.write_home(
|
||||
".fabro/settings.toml",
|
||||
format!(
|
||||
r#"
|
||||
_version = 1
|
||||
|
||||
[cli.target]
|
||||
type = "http"
|
||||
url = "{}/api/v1"
|
||||
"#,
|
||||
server.base_url()
|
||||
),
|
||||
);
|
||||
|
||||
context
|
||||
.settings()
|
||||
.current_dir(project.path())
|
||||
.assert()
|
||||
.failure()
|
||||
.stderr(predicate::str::contains(
|
||||
"server does not support resolved settings view; upgrade the server or use --local",
|
||||
));
|
||||
|
||||
mock.assert();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn settings_unreachable_http_target_fails_clearly() {
|
||||
let context = test_context!();
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use crate::support::{fabro_json_snapshot, unique_run_id};
|
|||
|
||||
fn resolved_run(
|
||||
settings: &fabro_types::settings::SettingsLayer,
|
||||
) -> fabro_types::settings::RunSettings {
|
||||
) -> fabro_types::settings::RunNamespace {
|
||||
fabro_config::resolve_run_from_file(settings).expect("run settings should resolve")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,14 +16,14 @@ use crate::support::{LightweightCli, unique_run_id};
|
|||
fn help() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.command();
|
||||
cmd.args(["store", "dump", "--help"]);
|
||||
cmd.args(["dump", "--help"]);
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Export a run's durable state to a directory
|
||||
|
||||
Usage: fabro store dump [OPTIONS] --output <OUTPUT> <RUN>
|
||||
Usage: fabro dump [OPTIONS] --output <OUTPUT> <RUN>
|
||||
|
||||
Arguments:
|
||||
<RUN> Run ID prefix or workflow name
|
||||
|
|
@ -42,7 +42,7 @@ fn help() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn store_dump_accepts_server_target_from_separate_home() {
|
||||
fn dump_accepts_server_target_from_separate_home() {
|
||||
let context = test_context!();
|
||||
let run = setup_completed_dry_run(&context);
|
||||
let cli = LightweightCli::new();
|
||||
|
|
@ -51,7 +51,6 @@ fn store_dump_accepts_server_target_from_separate_home() {
|
|||
|
||||
let mut cmd = cli.command();
|
||||
cmd.args([
|
||||
"store",
|
||||
"dump",
|
||||
"--server",
|
||||
&server,
|
||||
|
|
@ -63,10 +62,10 @@ fn store_dump_accepts_server_target_from_separate_home() {
|
|||
cmd.env("FABRO_DEV_TOKEN", dev_token);
|
||||
}
|
||||
|
||||
let output = cmd.output().expect("store dump should execute");
|
||||
let output = cmd.output().expect("dump should execute");
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"store dump via remote server target failed\nstdout:\n{}\nstderr:\n{}",
|
||||
"dump via remote server target failed\nstdout:\n{}\nstderr:\n{}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
|
|
@ -74,7 +73,7 @@ fn store_dump_accepts_server_target_from_separate_home() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn store_dump_exports_large_command_output_backed_by_blob_refs() {
|
||||
fn dump_exports_large_command_output_backed_by_blob_refs() {
|
||||
let context = test_context!();
|
||||
let workflow = context.temp_dir.join("large-output.fabro");
|
||||
fs::write(
|
||||
|
|
@ -130,17 +129,11 @@ fn store_dump_exports_large_command_output_backed_by_blob_refs() {
|
|||
|
||||
let output_dir = context.temp_dir.join("export");
|
||||
let mut dump_cmd = context.command();
|
||||
dump_cmd.args([
|
||||
"store",
|
||||
"dump",
|
||||
"--output",
|
||||
output_dir.to_str().unwrap(),
|
||||
&run_id,
|
||||
]);
|
||||
let dump_output = dump_cmd.output().expect("store dump should execute");
|
||||
dump_cmd.args(["dump", "--output", output_dir.to_str().unwrap(), &run_id]);
|
||||
let dump_output = dump_cmd.output().expect("dump should execute");
|
||||
assert!(
|
||||
dump_output.status.success(),
|
||||
"store dump failed\nstdout:\n{}\nstderr:\n{}",
|
||||
"dump failed\nstdout:\n{}\nstderr:\n{}",
|
||||
String::from_utf8_lossy(&dump_output.stdout),
|
||||
String::from_utf8_lossy(&dump_output.stderr)
|
||||
);
|
||||
|
|
@ -153,7 +146,7 @@ fn store_dump_exports_large_command_output_backed_by_blob_refs() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn store_dump_exports_blob_refs_and_artifacts_together() {
|
||||
fn dump_exports_blob_refs_and_artifacts_together() {
|
||||
let context = test_context!();
|
||||
let workspace_dir = context.temp_dir.join("mixed-export");
|
||||
fs::create_dir_all(&workspace_dir).unwrap();
|
||||
|
|
@ -233,17 +226,11 @@ include = ["assets/**"]
|
|||
|
||||
let output_dir = context.temp_dir.join("export-mixed");
|
||||
let mut dump_cmd = context.command();
|
||||
dump_cmd.args([
|
||||
"store",
|
||||
"dump",
|
||||
"--output",
|
||||
output_dir.to_str().unwrap(),
|
||||
&run_id,
|
||||
]);
|
||||
let dump_output = dump_cmd.output().expect("store dump should execute");
|
||||
dump_cmd.args(["dump", "--output", output_dir.to_str().unwrap(), &run_id]);
|
||||
let dump_output = dump_cmd.output().expect("dump should execute");
|
||||
assert!(
|
||||
dump_output.status.success(),
|
||||
"store dump failed\nstdout:\n{}\nstderr:\n{}",
|
||||
"dump failed\nstdout:\n{}\nstderr:\n{}",
|
||||
String::from_utf8_lossy(&dump_output.stdout),
|
||||
String::from_utf8_lossy(&dump_output.stderr)
|
||||
);
|
||||
|
|
@ -260,14 +247,13 @@ include = ["assets/**"]
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn store_dump_exports_completed_run_snapshot() {
|
||||
fn dump_exports_completed_run_snapshot() {
|
||||
let context = test_context!();
|
||||
let run = setup_completed_dry_run(&context);
|
||||
let output_dir = context.temp_dir.join("export");
|
||||
|
||||
let mut cmd = context.command();
|
||||
cmd.args([
|
||||
"store",
|
||||
"dump",
|
||||
"--output",
|
||||
output_dir.to_str().unwrap(),
|
||||
|
|
@ -298,7 +284,7 @@ fn store_dump_exports_completed_run_snapshot() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn store_dump_rejects_non_empty_output_dir() {
|
||||
fn dump_rejects_non_empty_output_dir() {
|
||||
let context = test_context!();
|
||||
let run = setup_completed_dry_run(&context);
|
||||
let output_dir = context.temp_dir.join("nonempty");
|
||||
|
|
@ -307,7 +293,6 @@ fn store_dump_rejects_non_empty_output_dir() {
|
|||
|
||||
let mut cmd = context.command();
|
||||
cmd.args([
|
||||
"store",
|
||||
"dump",
|
||||
"--output",
|
||||
output_dir.to_str().unwrap(),
|
||||
|
|
@ -25,7 +25,7 @@ fn help() {
|
|||
validate Validate a workflow
|
||||
graph Render a workflow graph as SVG
|
||||
artifact Inspect and copy run artifacts (screenshots, reports, traces)
|
||||
store Export store-backed run state for debugging
|
||||
dump Export a run's durable state to a directory
|
||||
rm Remove one or more workflow runs
|
||||
inspect Show detailed information about a workflow run
|
||||
archive Mark terminal runs as archived (reviewed, no further action needed). Archived runs are hidden from default listings
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ mod diff;
|
|||
mod discord;
|
||||
mod docs;
|
||||
mod doctor;
|
||||
mod dump;
|
||||
mod exec;
|
||||
mod fabro;
|
||||
mod fork;
|
||||
|
|
@ -53,8 +54,6 @@ mod server_start;
|
|||
mod server_status;
|
||||
mod server_stop;
|
||||
mod start;
|
||||
mod store;
|
||||
mod store_dump;
|
||||
pub(crate) mod support;
|
||||
mod system;
|
||||
mod system_df;
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
use fabro_test::{fabro_snapshot, test_context};
|
||||
|
||||
#[test]
|
||||
fn help() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.command();
|
||||
cmd.args(["store", "--help"]);
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Export store-backed run state for debugging
|
||||
|
||||
Usage: fabro store [OPTIONS] <COMMAND>
|
||||
|
||||
Commands:
|
||||
dump Export a run's durable state to a directory
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
|
||||
Options:
|
||||
--json Output as JSON [env: FABRO_JSON=]
|
||||
--debug Enable DEBUG-level logging (default is INFO) [env: FABRO_DEBUG=]
|
||||
--no-upgrade-check Disable automatic upgrade check [env: FABRO_NO_UPGRADE_CHECK=true]
|
||||
--quiet Suppress non-essential output [env: FABRO_QUIET=]
|
||||
--verbose Enable verbose output [env: FABRO_VERBOSE=]
|
||||
-h, --help Print help
|
||||
----- stderr -----
|
||||
");
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue