feat: use builtin profile by default
This commit is contained in:
+118
-96
@@ -4,13 +4,13 @@ use std::process::ExitCode;
|
||||
|
||||
use clap::Parser;
|
||||
use manifest::{NixProfileResolver, PodManifest, PodManifestConfig, ProfileSelector, paths};
|
||||
use pod::{Pod, PodController, PodFactory, PromptLoader};
|
||||
use pod::{Pod, PodController, PromptLoader};
|
||||
use session_store::{FsStore, PodMetadataStore, SegmentId, Store};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(
|
||||
name = "insomnia-pod",
|
||||
about = "Spawn a Pod process from manifest layers or a single manifest file"
|
||||
about = "Spawn a Pod process from a Nix profile or a single manifest file"
|
||||
)]
|
||||
struct Cli {
|
||||
/// Nix profile to evaluate. Accepts an explicit path, `path:<path>`, a
|
||||
@@ -29,19 +29,20 @@ struct Cli {
|
||||
#[arg(long, value_name = "NAME", requires = "profile", conflicts_with_all = ["pod", "session", "adopt"])]
|
||||
profile_pod_name: Option<String>,
|
||||
|
||||
/// Manifest TOML to use directly, without loading user, project, or
|
||||
/// overlay layers.
|
||||
/// Manifest TOML to use directly as a one-file compatibility/debug input.
|
||||
/// This bypasses profile discovery but still applies builtin defaults and
|
||||
/// the same required-field validation boundary.
|
||||
#[arg(long, value_name = "PATH", conflicts_with_all = ["project", "overlay"])]
|
||||
manifest: Option<PathBuf>,
|
||||
|
||||
/// Start the project-manifest walk from this directory. When
|
||||
/// omitted, the factory walks up from the current working
|
||||
/// directory looking for `.insomnia/manifest.toml`.
|
||||
/// Deprecated manifest-cascade project root flag. Ambient project/user
|
||||
/// manifest discovery has been removed; configure/select a profile instead.
|
||||
#[arg(long, value_name = "PATH")]
|
||||
project: Option<PathBuf>,
|
||||
|
||||
/// Inline TOML string applied as the highest-priority overlay
|
||||
/// layer. Example: `--overlay 'pod.name = "dbg"'`.
|
||||
/// Inline TOML override applied to the implicit default profile. This is
|
||||
/// retained for launchers that need to supply restore/session-time values;
|
||||
/// it does not re-enable user/project manifest discovery.
|
||||
#[arg(long, value_name = "TOML")]
|
||||
overlay: Option<String>,
|
||||
|
||||
@@ -94,7 +95,7 @@ fn resolve_manifest_with_user_manifest_env(
|
||||
|
||||
fn resolve_manifest_with_user_manifest_env_and_profile_loader<F>(
|
||||
cli: &Cli,
|
||||
user_manifest_env: Option<OsString>,
|
||||
_user_manifest_env: Option<OsString>,
|
||||
load_profile_fn: F,
|
||||
) -> Result<(PodManifest, PromptLoader), String>
|
||||
where
|
||||
@@ -105,29 +106,31 @@ where
|
||||
return load_profile_fn(&selector, cli.profile_pod_name.as_deref());
|
||||
}
|
||||
|
||||
let user_manifest = paths::user_manifest_path_from_env(user_manifest_env);
|
||||
|
||||
if let Some(path) = &cli.manifest {
|
||||
if user_manifest.is_some() {
|
||||
return Err(format!(
|
||||
"--manifest cannot be used when {} is set",
|
||||
paths::USER_MANIFEST_ENV
|
||||
));
|
||||
}
|
||||
return load_single_manifest(path, cli.pod.as_deref());
|
||||
}
|
||||
|
||||
let factory = build_factory_with_user_manifest_path(cli, user_manifest)?;
|
||||
factory
|
||||
.resolve()
|
||||
.map_err(|e| format!("failed to resolve manifest cascade: {e}"))
|
||||
if cli.project.is_some() {
|
||||
return Err(
|
||||
"--project is no longer supported; normal startup uses profile discovery/default, \
|
||||
and --manifest <PATH> is the only one-file manifest mode"
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
let selector = ProfileSelector::Default;
|
||||
let (manifest, loader) = load_profile_fn(&selector, None)?;
|
||||
let manifest = apply_cli_overrides_to_manifest(manifest, cli)?;
|
||||
Ok((manifest, loader))
|
||||
}
|
||||
|
||||
fn load_profile(
|
||||
selector: &ProfileSelector,
|
||||
pod_name_override: Option<&str>,
|
||||
) -> Result<(PodManifest, PromptLoader), String> {
|
||||
let resolver = NixProfileResolver::new();
|
||||
let cwd = std::env::current_dir()
|
||||
.map_err(|e| format!("failed to resolve current directory for profile: {e}"))?;
|
||||
let resolver = NixProfileResolver::new().with_workspace_base(cwd);
|
||||
let mut resolved = resolver.resolve(selector).map_err(|e| {
|
||||
format!(
|
||||
"failed to resolve profile {}: {e}",
|
||||
@@ -146,28 +149,32 @@ fn load_single_manifest(
|
||||
) -> Result<(PodManifest, PromptLoader), String> {
|
||||
let toml = std::fs::read_to_string(path)
|
||||
.map_err(|e| format!("failed to read manifest {}: {e}", path.display()))?;
|
||||
let manifest = match pod_name_override {
|
||||
Some(pod_name) => match PodManifest::from_toml(&toml) {
|
||||
Ok(mut manifest) => {
|
||||
manifest.pod.name = pod_name.to_string();
|
||||
manifest
|
||||
}
|
||||
Err(_) => {
|
||||
let base = PodManifestConfig::from_toml(&toml)
|
||||
.map_err(|e| format!("failed to parse manifest {}: {e}", path.display()))?;
|
||||
let overlay = PodManifestConfig::from_toml(&pod_name_overlay_toml(pod_name))
|
||||
.expect("pod name overlay TOML is generated");
|
||||
PodManifest::try_from(base.merge(overlay)).map_err(|e| {
|
||||
format!(
|
||||
"failed to resolve manifest {} with --pod: {e}",
|
||||
path.display()
|
||||
)
|
||||
})?
|
||||
}
|
||||
},
|
||||
None => PodManifest::from_toml(&toml)
|
||||
.map_err(|e| format!("failed to parse manifest {}: {e}", path.display()))?,
|
||||
let absolute_path = if path.is_absolute() {
|
||||
path.to_path_buf()
|
||||
} else {
|
||||
std::env::current_dir()
|
||||
.map_err(|e| format!("failed to resolve current directory: {e}"))?
|
||||
.join(path)
|
||||
};
|
||||
let base_dir = absolute_path.parent().ok_or_else(|| {
|
||||
format!(
|
||||
"manifest path {} has no parent directory",
|
||||
absolute_path.display()
|
||||
)
|
||||
})?;
|
||||
let mut config = PodManifestConfig::builtin_defaults().merge(
|
||||
PodManifestConfig::from_toml(&toml)
|
||||
.map_err(|e| format!("failed to parse manifest {}: {e}", path.display()))?
|
||||
.resolve_paths(base_dir),
|
||||
);
|
||||
if let Some(pod_name) = pod_name_override {
|
||||
config = config.merge(
|
||||
PodManifestConfig::from_toml(&pod_name_overlay_toml(pod_name))
|
||||
.expect("pod name overlay TOML is generated"),
|
||||
);
|
||||
}
|
||||
let manifest = PodManifest::try_from(config)
|
||||
.map_err(|e| format!("failed to resolve manifest {}: {e}", path.display()))?;
|
||||
Ok((manifest, PromptLoader::builtins_only()))
|
||||
}
|
||||
|
||||
@@ -179,43 +186,33 @@ fn pod_name_overlay_toml(pod_name: &str) -> String {
|
||||
toml::to_string(&toml::Value::Table(root)).expect("pod name overlay serialisation cannot fail")
|
||||
}
|
||||
|
||||
fn build_factory_with_user_manifest_path(
|
||||
fn manifest_to_config(manifest: &PodManifest) -> Result<PodManifestConfig, String> {
|
||||
let value = serde_json::to_value(manifest)
|
||||
.map_err(|e| format!("failed to serialise resolved manifest for overlay: {e}"))?;
|
||||
serde_json::from_value(value)
|
||||
.map_err(|e| format!("failed to convert resolved manifest for overlay: {e}"))
|
||||
}
|
||||
|
||||
fn apply_cli_overrides_to_manifest(
|
||||
mut manifest: PodManifest,
|
||||
cli: &Cli,
|
||||
user_manifest: Option<PathBuf>,
|
||||
) -> Result<PodFactory, String> {
|
||||
let mut factory = PodFactory::new();
|
||||
|
||||
factory = match user_manifest {
|
||||
Some(path) => factory
|
||||
.with_user_manifest(path)
|
||||
.map_err(|e| format!("failed to load user manifest: {e}"))?,
|
||||
None => factory
|
||||
.with_user_manifest_auto()
|
||||
.map_err(|e| format!("failed to auto-load user manifest: {e}"))?,
|
||||
};
|
||||
|
||||
factory = match &cli.project {
|
||||
Some(path) => factory
|
||||
.with_project_manifest_from(path)
|
||||
.map_err(|e| format!("failed to load project manifest: {e}"))?,
|
||||
None => factory
|
||||
.with_project_manifest_auto()
|
||||
.map_err(|e| format!("failed to auto-load project manifest: {e}"))?,
|
||||
};
|
||||
|
||||
) -> Result<PodManifest, String> {
|
||||
let profile = manifest.profile.clone();
|
||||
if let Some(overlay) = cli.overlay.as_deref() {
|
||||
factory = factory
|
||||
.with_overlay_toml(overlay)
|
||||
.map_err(|e| format!("failed to parse overlay TOML: {e}"))?;
|
||||
let base_dir = std::env::current_dir()
|
||||
.map_err(|e| format!("failed to resolve current directory for overlay: {e}"))?;
|
||||
let overlay = PodManifestConfig::from_toml(overlay)
|
||||
.map_err(|e| format!("failed to parse overlay TOML: {e}"))?
|
||||
.resolve_paths(&base_dir);
|
||||
let config = manifest_to_config(&manifest)?.merge(overlay);
|
||||
manifest = PodManifest::try_from(config)
|
||||
.map_err(|e| format!("failed to resolve default profile overlay: {e}"))?;
|
||||
manifest.profile = profile;
|
||||
}
|
||||
|
||||
if let Some(pod_name) = cli.pod.as_deref() {
|
||||
factory = factory
|
||||
.with_overlay_toml(&pod_name_overlay_toml(pod_name))
|
||||
.map_err(|e| format!("failed to apply --pod overlay: {e}"))?;
|
||||
manifest.pod.name = pod_name.to_string();
|
||||
}
|
||||
|
||||
Ok(factory)
|
||||
Ok(manifest)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
@@ -442,18 +439,20 @@ permission = "write"
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn manifest_conflicts_with_user_manifest_env_when_env_is_non_empty() {
|
||||
fn manifest_ignores_non_empty_user_manifest_env() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let manifest = tmp.path().join("manifest.toml");
|
||||
write(&manifest, &manifest_toml("single", tmp.path()));
|
||||
let cli = Cli::try_parse_from(["insomnia-pod", "--manifest", manifest.to_str().unwrap()])
|
||||
.unwrap();
|
||||
|
||||
let err = resolve_manifest_with_user_manifest_env(&cli, Some(OsString::from("user.toml")))
|
||||
.unwrap_err();
|
||||
let (manifest, loader) =
|
||||
resolve_manifest_with_user_manifest_env(&cli, Some(OsString::from("user.toml")))
|
||||
.unwrap();
|
||||
|
||||
assert!(err.contains("--manifest cannot be used"));
|
||||
assert!(err.contains(paths::USER_MANIFEST_ENV));
|
||||
assert_eq!(manifest.pod.name, "single");
|
||||
assert!(loader.user_dir().is_none());
|
||||
assert!(loader.workspace_dir().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -549,26 +548,38 @@ permission = "write"
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn user_manifest_env_overrides_auto_user_manifest_path() {
|
||||
fn normal_startup_uses_default_profile_and_ignores_user_manifest_env() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let user_manifest = tmp.path().join("custom-user.toml");
|
||||
write(&user_manifest, &manifest_toml("from-env", tmp.path()));
|
||||
let no_project_root = tmp.path().join("no-project");
|
||||
std::fs::create_dir_all(&no_project_root).unwrap();
|
||||
let cli = Cli::try_parse_from([
|
||||
"insomnia-pod",
|
||||
"--project",
|
||||
no_project_root.to_str().unwrap(),
|
||||
])
|
||||
.unwrap();
|
||||
let cli = Cli::try_parse_from(["insomnia-pod"]).unwrap();
|
||||
let mut called = false;
|
||||
|
||||
let (manifest, _loader) = resolve_manifest_with_user_manifest_env(
|
||||
let (manifest, _loader) = resolve_manifest_with_user_manifest_env_and_profile_loader(
|
||||
&cli,
|
||||
Some(user_manifest.as_os_str().to_os_string()),
|
||||
Some(OsString::from("ignored-user-manifest.toml")),
|
||||
|selector, pod_name| {
|
||||
called = true;
|
||||
assert_eq!(selector, &ProfileSelector::Default);
|
||||
assert_eq!(pod_name, None);
|
||||
let manifest =
|
||||
PodManifest::from_toml(&manifest_toml("from-default-profile", tmp.path()))
|
||||
.unwrap();
|
||||
Ok((manifest, PromptLoader::builtins_only()))
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(manifest.pod.name, "from-env");
|
||||
assert!(called);
|
||||
assert_eq!(manifest.pod.name, "from-default-profile");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn project_flag_no_longer_enables_ambient_manifest_cascade() {
|
||||
let cli = Cli::try_parse_from(["insomnia-pod", "--project", "."]).unwrap();
|
||||
let err = resolve_manifest_with_user_manifest_env_and_profile_loader(&cli, None, |_, _| {
|
||||
panic!("default profile loader must not run when deprecated --project is present")
|
||||
})
|
||||
.unwrap_err();
|
||||
assert!(err.contains("--project is no longer supported"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -605,7 +616,17 @@ permission = "write"
|
||||
let manifest = tmp.path().join("manifest.toml");
|
||||
write(
|
||||
&manifest,
|
||||
&manifest_toml("unused", tmp.path()).replace("name = \"unused\"\n", ""),
|
||||
r#"
|
||||
[pod]
|
||||
|
||||
[model]
|
||||
scheme = "anthropic"
|
||||
model_id = "test-model"
|
||||
|
||||
[[scope.allow]]
|
||||
target = "."
|
||||
permission = "write"
|
||||
"#,
|
||||
);
|
||||
let cli = Cli::try_parse_from([
|
||||
"insomnia-pod",
|
||||
@@ -619,6 +640,7 @@ permission = "write"
|
||||
let (manifest, _loader) = resolve_manifest_with_user_manifest_env(&cli, None).unwrap();
|
||||
|
||||
assert_eq!(manifest.pod.name, "from-flag");
|
||||
assert_eq!(manifest.scope.allow[0].target, tmp.path());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user