Initial commit
This commit is contained in:
@@ -110,7 +110,7 @@ pub static SKILL_PROVIDER_DEFINITIONS: LazyLock<Vec<SkillProviderDefinition>> =
|
||||
},
|
||||
SkillProviderDefinition {
|
||||
provider: SkillProvider::Warp,
|
||||
skills_path: PathBuf::from(".warp").join("skills"),
|
||||
skills_path: PathBuf::from(".warp-core").join("skills"),
|
||||
},
|
||||
SkillProviderDefinition {
|
||||
provider: SkillProvider::Claude,
|
||||
|
||||
@@ -47,21 +47,20 @@ pub fn test_preview_config_dir_migration() -> Builder {
|
||||
.add_named_assertion(
|
||||
"new directory exists and is a real directory",
|
||||
|_app, _window_id| {
|
||||
let new_dir = home_dir().join(".warp-preview");
|
||||
let new_dir = home_dir().join(".warp-core-preview");
|
||||
|
||||
if !new_dir.is_dir() {
|
||||
return AssertionOutcome::failure(
|
||||
".warp-preview should be a directory".to_string(),
|
||||
".warp-core-preview should be a directory".to_string(),
|
||||
);
|
||||
}
|
||||
// It should be a real directory, not a symlink.
|
||||
let is_symlink = new_dir
|
||||
.symlink_metadata()
|
||||
.map(|m| m.file_type().is_symlink())
|
||||
.unwrap_or(false);
|
||||
if is_symlink {
|
||||
return AssertionOutcome::failure(
|
||||
".warp-preview should not itself be a symlink".to_string(),
|
||||
".warp-core-preview should not itself be a symlink".to_string(),
|
||||
);
|
||||
}
|
||||
AssertionOutcome::Success
|
||||
@@ -71,8 +70,8 @@ pub fn test_preview_config_dir_migration() -> Builder {
|
||||
"expected entries are symlinks pointing to old dir",
|
||||
|_app, _window_id| {
|
||||
let home = home_dir();
|
||||
let old_dir = home.join(".warp");
|
||||
let new_dir = home.join(".warp-preview");
|
||||
let old_dir = home.join(".warp-core");
|
||||
let new_dir = home.join(".warp-core-preview");
|
||||
|
||||
for name in ["keybindings.yaml", "themes", "workflows", ".mcp.json"] {
|
||||
let link = new_dir.join(name);
|
||||
@@ -110,7 +109,7 @@ pub fn test_preview_config_dir_migration() -> Builder {
|
||||
},
|
||||
)
|
||||
.add_named_assertion("excluded files were not symlinked", |_app, _window_id| {
|
||||
let new_dir = home_dir().join(".warp-preview");
|
||||
let new_dir = home_dir().join(".warp-core-preview");
|
||||
|
||||
for name in [".DS_Store", "._somefile", "settings.toml"] {
|
||||
if new_dir.join(name).exists() {
|
||||
|
||||
@@ -40,7 +40,7 @@ pub trait LanguageServerCandidate: Send + Sync {
|
||||
self.is_installed_on_path(executor).await
|
||||
}
|
||||
|
||||
/// Attempts to install the server into the `.warp/` directory.
|
||||
/// Attempts to install the server into the `.warp-core/` directory.
|
||||
///
|
||||
/// The executor provides the user's PATH environment variable, which may be needed
|
||||
/// for servers that rely on external tools (e.g. gopls needs `go`).
|
||||
|
||||
@@ -112,22 +112,20 @@ pub fn parse_uname_output(output: &str) -> Result<RemotePlatform> {
|
||||
|
||||
/// Returns the remote directory where the binary is installed, keyed by channel.
|
||||
///
|
||||
/// - stable: `~/.warp/remote-server`
|
||||
/// - preview: `~/.warp-preview/remote-server`
|
||||
/// - dev: `~/.warp-dev/remote-server`
|
||||
/// - local: `~/.warp-local/remote-server`
|
||||
/// - integration: `~/.warp-dev/remote-server`
|
||||
/// - warp-oss: `~/.warp-oss/remote-server`
|
||||
/// - stable: `~/.warp-core/remote-server`
|
||||
/// - preview: `~/.warp-core-preview/remote-server`
|
||||
/// - dev: `~/.warp-core-dev/remote-server`
|
||||
/// - local: `~/.warp-core-local/remote-server`
|
||||
/// - integration: `~/.warp-core-dev/remote-server`
|
||||
/// - warp-core-oss: `~/.warp-core-oss/remote-server`
|
||||
pub fn remote_server_dir() -> String {
|
||||
let warp_dir = match ChannelState::channel() {
|
||||
Channel::Stable => ".warp",
|
||||
Channel::Preview => ".warp-preview",
|
||||
Channel::Dev | Channel::Integration => ".warp-dev",
|
||||
Channel::Local => ".warp-local",
|
||||
Channel::Stable => ".warp-core",
|
||||
Channel::Preview => ".warp-core-preview",
|
||||
Channel::Dev | Channel::Integration => ".warp-core-dev",
|
||||
Channel::Local => ".warp-core-local",
|
||||
Channel::Oss => {
|
||||
// TODO(alokedesai): need to figure out how remote server works with warp-oss
|
||||
// For now, return what Dev returns.
|
||||
".warp-dev"
|
||||
".warp-core-dev"
|
||||
}
|
||||
};
|
||||
format!("~/{warp_dir}/remote-server")
|
||||
|
||||
@@ -224,7 +224,7 @@ pub struct RunAgentArgs {
|
||||
///
|
||||
/// Format: `skill_name`, `repo:skill_name`, or `org/repo:skill_name`
|
||||
///
|
||||
/// Skills are searched in `.agents/skills/`, `.warp/skills/`, `.claude/skills/`, and `.codex/skills/` directories.
|
||||
/// Skills are searched in `.agents/skills/`, `.warp-core/skills/`, `.claude/skills/`, and `.codex/skills/` directories.
|
||||
/// If a repo is specified, searches only that repo. If org is also specified,
|
||||
/// validates the repo's git remote matches the expected org.
|
||||
///
|
||||
@@ -357,7 +357,7 @@ pub struct RunCloudArgs {
|
||||
///
|
||||
/// Format: `skill_name`, `repo:skill_name`, or `org/repo:skill_name`
|
||||
///
|
||||
/// Skills are searched in `.agents/skills/`, `.warp/skills/`, `.claude/skills/`, and `.codex/skills/` directories.
|
||||
/// Skills are searched in `.agents/skills/`, `.warp-core/skills/`, `.claude/skills/`, and `.codex/skills/` directories.
|
||||
/// If a repo is specified, searches only that repo. If org is also specified,
|
||||
/// validates the repo's git remote matches the expected org.
|
||||
///
|
||||
|
||||
@@ -111,7 +111,7 @@ pub struct CreateScheduleArgs {
|
||||
///
|
||||
/// Format: `repo:skill_name` or `org/repo:skill_name`
|
||||
///
|
||||
/// Skills are searched in `.agents/skills/`, `.warp/skills/`, `.claude/skills/`, and `.codex/skills/` directories.
|
||||
/// Skills are searched in `.agents/skills/`, `.warp-core/skills/`, `.claude/skills/`, and `.codex/skills/` directories.
|
||||
/// The skill is resolved at runtime in the agent's cloud environment.
|
||||
///
|
||||
/// When used with --prompt, the skill provides the base context and the prompt is the user task.
|
||||
@@ -186,7 +186,7 @@ pub struct UpdateScheduleArgs {
|
||||
///
|
||||
/// Format: `skill_name`, `repo:skill_name`, or `org/repo:skill_name`
|
||||
///
|
||||
/// Skills are searched in `.agents/skills/`, `.warp/skills/`, `.claude/skills/`, and `.codex/skills/` directories.
|
||||
/// Skills are searched in `.agents/skills/`, `.warp-core/skills/`, `.claude/skills/`, and `.codex/skills/` directories.
|
||||
/// The skill is resolved at runtime in the agent's cloud environment.
|
||||
#[arg(long = "skill", value_name = "SPEC", conflicts_with = "remove_skill")]
|
||||
pub skill: Option<SkillSpec>,
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::{fmt, str::FromStr};
|
||||
/// A skill specifier that can reference a skill in a specific repo or search the current directory.
|
||||
///
|
||||
/// The skill identifier (after the optional `repo:` or `org/repo:` prefix) can be either:
|
||||
/// - A **simple skill name** - searched across skill directories with precedence (`.agents/skills/`, `.warp/skills/`, `.claude/skills/`, `.codex/skills/`)
|
||||
/// - A **simple skill name** - searched across skill directories with precedence (`.agents/skills/`, `.warp-core/skills/`, `.claude/skills/`, `.codex/skills/`)
|
||||
/// - A **full path to SKILL.md** - resolved directly without precedence
|
||||
///
|
||||
/// # Formats
|
||||
@@ -19,7 +19,7 @@ use std::{fmt, str::FromStr};
|
||||
///
|
||||
/// Simple skill names (searched with directory precedence):
|
||||
/// ```ignore
|
||||
/// code-review // searches .agents/skills/, .warp/skills/, .claude/skills/, .codex/skills/
|
||||
/// code-review // searches .agents/skills/, .warp-core/skills/, .claude/skills/, .codex/skills/
|
||||
/// warp-internal:code-review // searches in "warp-internal" repo
|
||||
/// warpdotdev/warp-internal:code-review // searches in specific org/repo
|
||||
/// ```
|
||||
@@ -38,7 +38,7 @@ pub struct SkillSpec {
|
||||
pub repo: Option<String>,
|
||||
/// The skill identifier - either a simple name or a full path to SKILL.md.
|
||||
///
|
||||
/// - **Simple name** (e.g., `"code-review"`): Searched across `.agents/skills/`, `.warp/skills/`, `.claude/skills/`, `.codex/skills/`
|
||||
/// - **Simple name** (e.g., `"code-review"`): Searched across `.agents/skills/`, `.warp-core/skills/`, `.claude/skills/`, `.codex/skills/`
|
||||
/// in precedence order. The name is used to construct paths like `.claude/skills/code-review/SKILL.md`.
|
||||
///
|
||||
/// - **Full path** (e.g., `".claude/skills/code-review/SKILL.md"`): Resolved directly without precedence.
|
||||
@@ -87,7 +87,7 @@ impl SkillSpec {
|
||||
/// - `deploy`
|
||||
///
|
||||
/// Full paths are resolved directly, while simple names are searched across
|
||||
/// skill directories in precedence order (`.agents/skills/`, `.warp/skills/`, `.claude/skills/`, `.codex/skills/`).
|
||||
/// skill directories in precedence order (`.agents/skills/`, `.warp-core/skills/`, `.claude/skills/`, `.codex/skills/`).
|
||||
///
|
||||
/// Uses cross-platform path semantics via [`std::path::Path`].
|
||||
pub fn is_full_path(&self) -> bool {
|
||||
|
||||
@@ -24,11 +24,15 @@ use crate::{
|
||||
AppId,
|
||||
};
|
||||
|
||||
/// The name of the directory in which to put non-global Warp-specific files.
|
||||
/// The name of the directory in which to put non-global Warp Core-specific files.
|
||||
///
|
||||
/// This should be used, for example, as the base directory under which
|
||||
/// repository workflows would be stored (in "./.warp/workflows").
|
||||
pub const WARP_CONFIG_DIR: &str = ".warp";
|
||||
/// repository workflows would be stored (in "./.warp-core/workflows").
|
||||
pub const WARP_CONFIG_DIR: &str = ".warp-core";
|
||||
|
||||
/// The legacy config directory name used by Warp before the rename to Warp Core.
|
||||
/// Used for auto-migration on first launch.
|
||||
pub const LEGACY_WARP_CONFIG_DIR: &str = ".warp";
|
||||
|
||||
/// The name of the folder that stores Warp execution logs and network logs.
|
||||
/// This is currently only used on Windows to maintain backwards compatibility.
|
||||
@@ -59,15 +63,142 @@ pub fn warp_home_config_dir_name() -> String {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the home-relative Warp config directory for the current channel and data profile.
|
||||
/// Returns the home-relative Warp Core config directory for the current channel and data profile.
|
||||
///
|
||||
/// Unlike [`data_dir`] and [`config_local_dir`] on non-macOS platforms, this intentionally keeps
|
||||
/// Warp-authored, user-facing config under a `.warp*` directory in the home directory instead of
|
||||
/// user-facing config under a `.warp-core*` directory in the home directory instead of
|
||||
/// using the platform XDG/AppData project directories.
|
||||
pub fn warp_home_config_dir() -> Option<PathBuf> {
|
||||
dirs::home_dir().map(|home_dir| home_dir.join(warp_home_config_dir_name()))
|
||||
}
|
||||
|
||||
/// Returns the legacy `~/.warp*` config directory path for the current channel,
|
||||
/// used to detect and migrate data from a previous Warp installation.
|
||||
pub fn legacy_warp_home_config_dir() -> Option<PathBuf> {
|
||||
let base = LEGACY_WARP_CONFIG_DIR;
|
||||
let dir_name = match ChannelState::channel() {
|
||||
Channel::Stable | Channel::Preview => base.to_owned(),
|
||||
Channel::Oss => format!("{base}-oss"),
|
||||
Channel::Dev => format!("{base}-dev"),
|
||||
Channel::Integration => format!("{base}-integration"),
|
||||
Channel::Local => format!("{base}-local"),
|
||||
};
|
||||
let dir_name = if let Some(data_profile) = ChannelState::data_profile() {
|
||||
format!("{dir_name}-{data_profile}")
|
||||
} else {
|
||||
dir_name
|
||||
};
|
||||
dirs::home_dir().map(|home_dir| home_dir.join(dir_name))
|
||||
}
|
||||
|
||||
/// Migrates the legacy `~/.warp*` config directory to `~/.warp-core*` if needed.
|
||||
///
|
||||
/// This runs once on first launch after the rename. It creates symlinks from the
|
||||
/// old directory entries into the new directory, preserving access to existing
|
||||
/// configuration (keybindings, themes, workflows, skills, etc.).
|
||||
///
|
||||
/// This is a no-op if:
|
||||
/// - The new directory already exists.
|
||||
/// - The old directory does not exist.
|
||||
pub fn migrate_legacy_config_dir_if_needed() {
|
||||
let Some(old_dir) = legacy_warp_home_config_dir() else {
|
||||
return;
|
||||
};
|
||||
let Some(new_dir) = warp_home_config_dir() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if new_dir.exists() || !old_dir.exists() {
|
||||
return;
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::symlink;
|
||||
|
||||
if let Err(err) = std::fs::create_dir(&new_dir) {
|
||||
if err.kind() != std::io::ErrorKind::AlreadyExists {
|
||||
log::warn!(
|
||||
"Failed to create config directory {}: {err}",
|
||||
new_dir.display()
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let entries = match std::fs::read_dir(&old_dir) {
|
||||
Ok(entries) => entries,
|
||||
Err(err) => {
|
||||
log::warn!(
|
||||
"Failed to read legacy config directory {}: {err}",
|
||||
old_dir.display()
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut migrated = 0u32;
|
||||
for entry in entries.flatten() {
|
||||
let name = entry.file_name();
|
||||
let name_str = name.to_string_lossy();
|
||||
|
||||
if name_str == ".DS_Store" || name_str.starts_with("._") {
|
||||
continue;
|
||||
}
|
||||
|
||||
let target = old_dir.join(&name);
|
||||
let link = new_dir.join(&name);
|
||||
|
||||
if let Err(err) = symlink(&target, &link) {
|
||||
log::warn!(
|
||||
"Failed to symlink {} -> {}: {err}",
|
||||
link.display(),
|
||||
target.display()
|
||||
);
|
||||
} else {
|
||||
migrated += 1;
|
||||
}
|
||||
}
|
||||
|
||||
log::info!(
|
||||
"Migrated legacy config directory: created {migrated} symlinks in {}",
|
||||
new_dir.display()
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
fn copy_dir_recursive(src: &Path, dst: &Path) -> std::io::Result<()> {
|
||||
std::fs::create_dir_all(dst)?;
|
||||
for entry in std::fs::read_dir(src)? {
|
||||
let entry = entry?;
|
||||
let ty = entry.file_type()?;
|
||||
let dest = dst.join(entry.file_name());
|
||||
if ty.is_dir() {
|
||||
copy_dir_recursive(&entry.path(), &dest)?;
|
||||
} else {
|
||||
std::fs::copy(entry.path(), dest)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
if let Err(err) = copy_dir_recursive(&old_dir, &new_dir) {
|
||||
log::warn!(
|
||||
"Failed to copy legacy config directory {} to {}: {err}",
|
||||
old_dir.display(),
|
||||
new_dir.display()
|
||||
);
|
||||
} else {
|
||||
log::info!(
|
||||
"Migrated legacy config directory {} to {}",
|
||||
old_dir.display(),
|
||||
new_dir.display()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn warp_home_skills_dir() -> Option<PathBuf> {
|
||||
warp_home_config_dir().map(|warp_config_dir| warp_config_dir.join("skills"))
|
||||
}
|
||||
@@ -78,8 +209,8 @@ pub fn warp_home_mcp_config_file_path() -> Option<PathBuf> {
|
||||
|
||||
/// Returns the macOS config directory name for the current channel.
|
||||
///
|
||||
/// Stable uses `.warp`, while other channels include a channel suffix
|
||||
/// (e.g., `.warp-dev`, `.warp-local`).
|
||||
/// Stable uses `.warp-core`, while other channels include a channel suffix
|
||||
/// (e.g., `.warp-core-dev`, `.warp-core-local`).
|
||||
///
|
||||
/// These suffixes are persisted on disk as directory names and must not be
|
||||
/// changed once established, or existing user data will be orphaned.
|
||||
@@ -275,7 +406,7 @@ pub fn app_group_container_path() -> Option<PathBuf> {
|
||||
|
||||
let fm = NSFileManager::defaultManager();
|
||||
// Keep in sync with Entitlements.plist
|
||||
let group_id = format!("{}.dev.warp", crate::macos::APPLE_TEAM_ID);
|
||||
let group_id = format!("{}.dev.warpcore", crate::macos::APPLE_TEAM_ID);
|
||||
let group_id = NSString::from_str(&group_id);
|
||||
// containerURLForSecurityApplicationGroupIdentifier always returns a value on macOS (unlike iOS).
|
||||
// We have to double-check that the path points to a directory we can actually use. In addition to
|
||||
|
||||
@@ -8,7 +8,7 @@ fn test_data_dir_path() {
|
||||
// ChannelState, by default, is configured for Channel::Oss.
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(target_os = "macos")] {
|
||||
assert_eq!(data_dir(), home_dir.join(".warp-oss"));
|
||||
assert_eq!(data_dir(), home_dir.join(".warp-core-oss"));
|
||||
} else if #[cfg(target_os = "linux")] {
|
||||
assert_eq!(data_dir(), home_dir.join(".local/share/warp-oss"));
|
||||
} else if #[cfg(windows)] {
|
||||
@@ -25,7 +25,7 @@ fn test_config_local_dir_path() {
|
||||
// ChannelState, by default, is configured for Channel::Oss.
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(target_os = "macos")] {
|
||||
assert_eq!(config_local_dir(), home_dir.join(".warp-oss"));
|
||||
assert_eq!(config_local_dir(), home_dir.join(".warp-core-oss"));
|
||||
} else if #[cfg(target_os = "linux")] {
|
||||
assert_eq!(config_local_dir(), home_dir.join(".config/warp-oss"));
|
||||
} else if #[cfg(windows)] {
|
||||
@@ -40,8 +40,8 @@ fn test_config_local_dir_path() {
|
||||
fn test_warp_home_config_dir_path() {
|
||||
let home_dir = home_dir().expect("Should be able to compute home directory");
|
||||
let expected_dir_name = match ChannelState::data_profile() {
|
||||
Some(data_profile) => format!(".warp-oss-{data_profile}"),
|
||||
None => ".warp-oss".to_string(),
|
||||
Some(data_profile) => format!(".warp-core-oss-{data_profile}"),
|
||||
None => ".warp-core-oss".to_string(),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
|
||||
@@ -654,7 +654,7 @@ pub enum FeatureFlag {
|
||||
|
||||
/// Enables platform skills support (--skill flag) for agent runs.
|
||||
///
|
||||
/// Skills are loaded from `.agents/skills/`, `.warp/skills/`, `.claude/skills/`, and `.codex/skills/`
|
||||
/// Skills are loaded from `.agents/skills/`, `.warp-core/skills/`, `.claude/skills/`, and `.codex/skills/`
|
||||
/// directories to provide base prompts for agent runs.
|
||||
OzPlatformSkills,
|
||||
/// Enables Oz identity federation commands.
|
||||
|
||||
Reference in New Issue
Block a user