mirror of
https://github.com/openai/codex.git
synced 2026-11-05 20:10:33 +00:00
default external imports without a source picker
This commit is contained in:
@@ -239,8 +239,8 @@ Example with notification opt-out:
|
||||
- `windowsSandbox/setupStart` — start Windows sandbox setup for the selected mode (`elevated` or `unelevated`); accepts an optional absolute `cwd` to target setup for a specific workspace, returns `{ started: true }` immediately, and later emits `windowsSandbox/setupCompleted`.
|
||||
- `feedback/upload` — submit a feedback report (classification + optional reason/logs, conversation_id, and optional `extraLogFiles` attachments array); returns the tracking thread id.
|
||||
- `config/read` — fetch the effective config on disk after resolving config layering, including opaque `desktop` values stored in `config.toml`.
|
||||
- `externalAgentConfig/detect` — detect migratable external-agent artifacts with `includeHome` and optional `cwds`. Pass `source: "cursor"` to detect Cursor artifacts; omitted, `null`, or unrecognized values retain the default Claude Code behavior. Each detected item includes `cwd` (`null` for home), and multi-item migrations may additionally include structured `details` with plugin ids, skill names, session metadata, or other artifact names.
|
||||
- `externalAgentConfig/import` — apply selected external-agent migration items by passing explicit `migrationItems` with `cwd` (`null` for home) and any `details` returned by detect. Pass the same `source` used for detection so the server reads from the matching product; `source: "cursor"` selects Cursor, while omitted, `null`, or unrecognized values retain the default Claude Code behavior. The response acknowledges the synchronous import phase with an `importId`. Expected migration failures are reported as per-item failures rather than JSON-RPC errors, so the server still returns that `importId` and emits `externalAgentConfig/import/completed` with the same ID once all synchronous and background work finishes. The completion notification contains type-level `itemTypeResults` with successes and failures, including raw failure messages for the client to report separately.
|
||||
- `externalAgentConfig/detect` — detect migratable external-agent artifacts with `includeHome`, optional `cwds`, and an optional source selector. Omitted, `null`, or unrecognized source values retain the default behavior. Each detected item includes `cwd` (`null` for home), and multi-item migrations may additionally include structured `details` with plugin ids, skill names, session metadata, or other artifact names.
|
||||
- `externalAgentConfig/import` — apply selected external-agent migration items by passing explicit `migrationItems` with `cwd` (`null` for home) and any `details` returned by detect. Pass the same optional source used for detection so the server reads from the matching source; omitted, `null`, or unrecognized values retain the default behavior. The response acknowledges the synchronous import phase with an `importId`. Expected migration failures are reported as per-item failures rather than JSON-RPC errors, so the server still returns that `importId` and emits `externalAgentConfig/import/completed` with the same ID once all synchronous and background work finishes. The completion notification contains type-level `itemTypeResults` with successes and failures, including raw failure messages for the client to report separately.
|
||||
- `config/value/write` — write a single config key/value to the user's config.toml on disk; dotted paths such as `desktop.someKey` use the same generic write surface.
|
||||
- `config/batchWrite` — apply multiple config edits atomically to the user's config.toml on disk, with optional `reloadUserConfig: true` to hot-reload loaded threads, including multiple `desktop.*` edits.
|
||||
- `configRequirements/read` — fetch loaded requirements constraints from `requirements.toml` and/or MDM (or `null` if none are configured), including allow-lists (`allowedApprovalPolicies`, `allowedSandboxModes`, `allowedWebSearchModes`), the layered permission-profile allow map (`allowedPermissionProfiles`), the managed permission-profile default (`defaultPermissions`), lifecycle hook lockdown (`allowManagedHooksOnly`), remote-control policy (`allowRemoteControl`; `false` force-disables remote control while `true` or `null` preserves existing behavior), computer use policy (`computerUse`), pinned feature values (`featureRequirements`), managed lifecycle hooks (`hooks`), `enforceResidency`, managed new-thread defaults (`models.newThread.model`, `models.newThread.modelReasoningEffort`, and `models.newThread.serviceTier`), and `network` constraints such as canonical domain/socket permissions plus `managedAllowedDomainsOnly` and `dangerFullAccessDenylistOnly`.
|
||||
|
||||
@@ -51,9 +51,6 @@ fn assert_import_response(response: ExternalAgentConfigImportResponse) -> String
|
||||
response.import_id
|
||||
}
|
||||
|
||||
#[path = "external_agent_config_source_cu.rs"]
|
||||
mod source_cu_tests;
|
||||
|
||||
#[tokio::test]
|
||||
async fn external_agent_config_import_sends_completion_notification_for_sync_only_import()
|
||||
-> Result<()> {
|
||||
|
||||
@@ -1,332 +0,0 @@
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[tokio::test]
|
||||
async fn external_agent_config_imports_source_cu_repo_artifacts() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let project_root = codex_home.path().join("repo");
|
||||
let source_dir = project_root.join(".cursor");
|
||||
let nested_scope = project_root.join("backend");
|
||||
std::fs::create_dir_all(project_root.join(".git"))?;
|
||||
std::fs::create_dir_all(source_dir.join("skills/release"))?;
|
||||
std::fs::create_dir_all(source_dir.join("commands"))?;
|
||||
std::fs::create_dir_all(source_dir.join("agents"))?;
|
||||
std::fs::create_dir_all(source_dir.join("rules/team"))?;
|
||||
std::fs::create_dir_all(nested_scope.join(".cursor/rules"))?;
|
||||
std::fs::write(
|
||||
source_dir.join("mcp.json"),
|
||||
r#"{"mcpServers":{"docs":{"command":"docs-server","args":["--stdio"]}}}"#,
|
||||
)?;
|
||||
std::fs::write(
|
||||
source_dir.join("skills/release/SKILL.md"),
|
||||
"---\nname: release\ndescription: Release with Cursor\n---\n\nUse Cursor to release. Advance the database cursor after each page.\n",
|
||||
)?;
|
||||
std::fs::write(
|
||||
source_dir.join("commands/review.md"),
|
||||
"Review the change with Cursor. Keep the text cursor visible.\n",
|
||||
)?;
|
||||
std::fs::write(
|
||||
source_dir.join("agents/researcher.md"),
|
||||
"---\nname: researcher\ndescription: Cursor research role\n---\nResearch with Cursor.\n",
|
||||
)?;
|
||||
std::fs::write(
|
||||
project_root.join(".cursorrules"),
|
||||
"Use Cursor for repository work. Keep the text cursor visible.\n",
|
||||
)?;
|
||||
std::fs::write(
|
||||
source_dir.join("rules/always.mdc"),
|
||||
"---\ndescription: Always apply\nalwaysApply: true # YAML comments are valid\n---\nRun checks in Cursor.\n",
|
||||
)?;
|
||||
std::fs::write(
|
||||
source_dir.join("rules/team/database.mdc"),
|
||||
"---\ndescription: Database guidance\nalwaysApply: true\n---\nDocument database cursor behavior.\n",
|
||||
)?;
|
||||
std::fs::write(
|
||||
source_dir.join("rules/scoped.mdc"),
|
||||
"---\ndescription: TypeScript only\nglobs: '*.ts'\nalwaysApply: false\n---\nUse TypeScript.\n",
|
||||
)?;
|
||||
std::fs::write(
|
||||
nested_scope.join(".cursor/rules/always.mdc"),
|
||||
"---\ndescription: Backend guidance\nalwaysApply: true\n---\nUse Cursor for backend work.\n",
|
||||
)?;
|
||||
|
||||
let home_dir = codex_home.path().display().to_string();
|
||||
let mut mcp = TestAppServer::builder()
|
||||
.with_codex_home(codex_home.path())
|
||||
.with_env_overrides(&[("HOME", Some(home_dir.as_str()))])
|
||||
.build()
|
||||
.await?;
|
||||
timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
let request_id = mcp
|
||||
.send_raw_request(
|
||||
"externalAgentConfig/detect",
|
||||
Some(serde_json::json!({
|
||||
"includeHome": false,
|
||||
"cwds": [&project_root],
|
||||
"source": "cursor"
|
||||
})),
|
||||
)
|
||||
.await?;
|
||||
let response: JSONRPCResponse = timeout(
|
||||
DEFAULT_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??;
|
||||
let detected: ExternalAgentConfigDetectResponse = to_response(response)?;
|
||||
assert_eq!(
|
||||
detected
|
||||
.items
|
||||
.iter()
|
||||
.map(|item| item.item_type)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
ExternalAgentConfigMigrationItemType::McpServerConfig,
|
||||
ExternalAgentConfigMigrationItemType::Skills,
|
||||
ExternalAgentConfigMigrationItemType::Commands,
|
||||
ExternalAgentConfigMigrationItemType::Subagents,
|
||||
ExternalAgentConfigMigrationItemType::AgentsMd,
|
||||
ExternalAgentConfigMigrationItemType::AgentsMd,
|
||||
]
|
||||
);
|
||||
|
||||
let request_id = mcp
|
||||
.send_raw_request(
|
||||
"externalAgentConfig/import",
|
||||
Some(serde_json::json!({
|
||||
"migrationItems": detected.items,
|
||||
"source": "cursor"
|
||||
})),
|
||||
)
|
||||
.await?;
|
||||
let response: JSONRPCResponse = timeout(
|
||||
DEFAULT_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??;
|
||||
let response: ExternalAgentConfigImportResponse = to_response(response)?;
|
||||
let import_id = assert_import_response(response);
|
||||
let notification = timeout(
|
||||
DEFAULT_TIMEOUT,
|
||||
mcp.read_stream_until_notification_message("externalAgentConfig/import/completed"),
|
||||
)
|
||||
.await??;
|
||||
let completed: ExternalAgentConfigImportCompletedNotification =
|
||||
serde_json::from_value(notification.params.expect("completed params"))?;
|
||||
assert_eq!(completed.import_id, import_id);
|
||||
assert!(
|
||||
completed
|
||||
.item_type_results
|
||||
.iter()
|
||||
.all(|result| result.failures.is_empty())
|
||||
);
|
||||
|
||||
let config = std::fs::read_to_string(project_root.join(".codex/config.toml"))?;
|
||||
assert!(config.contains("[mcp_servers.docs]"));
|
||||
assert!(config.contains("command = \"docs-server\""));
|
||||
let skill = std::fs::read_to_string(project_root.join(".agents/skills/release/SKILL.md"))?;
|
||||
assert!(skill.contains("Release with Codex"));
|
||||
assert!(skill.contains("database cursor"));
|
||||
let command = std::fs::read_to_string(
|
||||
project_root.join(".agents/skills/source-command-review/SKILL.md"),
|
||||
)?;
|
||||
assert!(command.contains("Review the change with Codex"));
|
||||
assert!(command.contains("Migrated source command `review`"));
|
||||
assert!(command.contains("text cursor"));
|
||||
let subagent = std::fs::read_to_string(project_root.join(".codex/agents/researcher.toml"))?;
|
||||
assert!(subagent.contains("Codex research role"));
|
||||
let agents_md = std::fs::read_to_string(project_root.join("AGENTS.md"))?;
|
||||
assert!(agents_md.contains("Use Codex for repository work."));
|
||||
assert!(agents_md.contains("Run checks in Codex."));
|
||||
assert!(agents_md.contains("Document database cursor behavior."));
|
||||
assert!(agents_md.contains("text cursor"));
|
||||
assert!(!agents_md.contains("Use TypeScript."));
|
||||
assert!(!agents_md.contains("alwaysApply"));
|
||||
let nested_agents_md = std::fs::read_to_string(nested_scope.join("AGENTS.md"))?;
|
||||
assert!(nested_agents_md.contains("Use Codex for backend work."));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn external_agent_config_imports_source_cu_home_artifacts() -> Result<()> {
|
||||
let root = TempDir::new()?;
|
||||
let home = root.path().join("home");
|
||||
let codex_home = home.join(".codex");
|
||||
let source_dir = home.join(".cursor");
|
||||
std::fs::create_dir_all(&codex_home)?;
|
||||
std::fs::create_dir_all(source_dir.join("skills/home-release"))?;
|
||||
std::fs::create_dir_all(source_dir.join("commands"))?;
|
||||
std::fs::create_dir_all(source_dir.join("agents"))?;
|
||||
std::fs::create_dir_all(source_dir.join("rules"))?;
|
||||
std::fs::write(
|
||||
source_dir.join("mcp.json"),
|
||||
r#"{"mcpServers":{"home-docs":{"command":"home-docs-server"}}}"#,
|
||||
)?;
|
||||
std::fs::write(
|
||||
source_dir.join("skills/home-release/SKILL.md"),
|
||||
"---\nname: home-release\ndescription: Release with Cursor\n---\n\nUse Cursor for home releases.\n",
|
||||
)?;
|
||||
std::fs::write(
|
||||
source_dir.join("commands/home-review.md"),
|
||||
"Review home changes with Cursor.\n",
|
||||
)?;
|
||||
std::fs::write(
|
||||
source_dir.join("agents/home-researcher.md"),
|
||||
"---\nname: home-researcher\ndescription: Research with Cursor\n---\nResearch with Cursor.\n",
|
||||
)?;
|
||||
std::fs::write(
|
||||
source_dir.join("rules/always.mdc"),
|
||||
"---\ndescription: Home guidance\nalwaysApply: true\n---\nUse Cursor for home work.\n",
|
||||
)?;
|
||||
|
||||
let home_dir = home.display().to_string();
|
||||
let mut mcp = TestAppServer::builder()
|
||||
.with_codex_home(&codex_home)
|
||||
.with_env_overrides(&[("HOME", Some(home_dir.as_str()))])
|
||||
.build()
|
||||
.await?;
|
||||
timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
let request_id = mcp
|
||||
.send_raw_request(
|
||||
"externalAgentConfig/detect",
|
||||
Some(serde_json::json!({
|
||||
"includeHome": true,
|
||||
"cwds": [],
|
||||
"source": "cursor"
|
||||
})),
|
||||
)
|
||||
.await?;
|
||||
let response: JSONRPCResponse = timeout(
|
||||
DEFAULT_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??;
|
||||
let detected: ExternalAgentConfigDetectResponse = to_response(response)?;
|
||||
assert_eq!(
|
||||
detected
|
||||
.items
|
||||
.iter()
|
||||
.map(|item| item.item_type)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
ExternalAgentConfigMigrationItemType::McpServerConfig,
|
||||
ExternalAgentConfigMigrationItemType::Skills,
|
||||
ExternalAgentConfigMigrationItemType::Commands,
|
||||
ExternalAgentConfigMigrationItemType::Subagents,
|
||||
ExternalAgentConfigMigrationItemType::AgentsMd,
|
||||
]
|
||||
);
|
||||
|
||||
let request_id = mcp
|
||||
.send_raw_request(
|
||||
"externalAgentConfig/import",
|
||||
Some(serde_json::json!({
|
||||
"migrationItems": detected.items,
|
||||
"source": "cursor"
|
||||
})),
|
||||
)
|
||||
.await?;
|
||||
let response: JSONRPCResponse = timeout(
|
||||
DEFAULT_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??;
|
||||
let response: ExternalAgentConfigImportResponse = to_response(response)?;
|
||||
let import_id = assert_import_response(response);
|
||||
let notification = timeout(
|
||||
DEFAULT_TIMEOUT,
|
||||
mcp.read_stream_until_notification_message("externalAgentConfig/import/completed"),
|
||||
)
|
||||
.await??;
|
||||
let completed: ExternalAgentConfigImportCompletedNotification =
|
||||
serde_json::from_value(notification.params.expect("completed params"))?;
|
||||
assert_eq!(completed.import_id, import_id);
|
||||
assert!(
|
||||
completed
|
||||
.item_type_results
|
||||
.iter()
|
||||
.all(|result| result.failures.is_empty())
|
||||
);
|
||||
|
||||
let config = std::fs::read_to_string(codex_home.join("config.toml"))?;
|
||||
assert!(config.contains("[mcp_servers.home-docs]"));
|
||||
let skill = std::fs::read_to_string(home.join(".agents/skills/home-release/SKILL.md"))?;
|
||||
assert!(skill.contains("Release with Codex"));
|
||||
assert!(skill.contains("Use Codex for home releases."));
|
||||
let command =
|
||||
std::fs::read_to_string(home.join(".agents/skills/source-command-home-review/SKILL.md"))?;
|
||||
assert!(command.contains("Review home changes with Codex."));
|
||||
let subagent = std::fs::read_to_string(codex_home.join("agents/home-researcher.toml"))?;
|
||||
assert!(subagent.contains("Research with Codex"));
|
||||
let agents_md = std::fs::read_to_string(codex_home.join("AGENTS.md"))?;
|
||||
assert!(agents_md.contains("Use Codex for home work."));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn source_cu_import_does_not_accept_source_cl_sessions() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let project_root = codex_home.path().join("repo");
|
||||
let session_path = external_agent_home(codex_home.path()).join("projects/repo/session.jsonl");
|
||||
std::fs::create_dir_all(session_path.parent().expect("session parent"))?;
|
||||
std::fs::write(&session_path, "{}\n")?;
|
||||
|
||||
let home_dir = codex_home.path().display().to_string();
|
||||
let mut mcp = TestAppServer::builder()
|
||||
.with_codex_home(codex_home.path())
|
||||
.with_env_overrides(&[("HOME", Some(home_dir.as_str()))])
|
||||
.build()
|
||||
.await?;
|
||||
timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
let request_id = mcp
|
||||
.send_raw_request(
|
||||
"externalAgentConfig/import",
|
||||
Some(serde_json::json!({
|
||||
"source": "cursor",
|
||||
"migrationItems": [{
|
||||
"itemType": "SESSIONS",
|
||||
"description": "Migrate recent sessions",
|
||||
"cwd": null,
|
||||
"details": {
|
||||
"sessions": [{
|
||||
"path": session_path,
|
||||
"cwd": project_root,
|
||||
"title": "wrong source"
|
||||
}]
|
||||
}
|
||||
}]
|
||||
})),
|
||||
)
|
||||
.await?;
|
||||
let response: JSONRPCResponse = timeout(
|
||||
DEFAULT_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??;
|
||||
let response: ExternalAgentConfigImportResponse = to_response(response)?;
|
||||
let import_id = assert_import_response(response);
|
||||
let notification = timeout(
|
||||
DEFAULT_TIMEOUT,
|
||||
mcp.read_stream_until_notification_message("externalAgentConfig/import/completed"),
|
||||
)
|
||||
.await??;
|
||||
let completed: ExternalAgentConfigImportCompletedNotification =
|
||||
serde_json::from_value(notification.params.expect("completed params"))?;
|
||||
|
||||
assert_eq!(completed.import_id, import_id);
|
||||
assert_eq!(completed.item_type_results.len(), 1);
|
||||
let result = &completed.item_type_results[0];
|
||||
assert_eq!(
|
||||
result.item_type,
|
||||
ExternalAgentConfigMigrationItemType::Sessions
|
||||
);
|
||||
assert!(result.successes.is_empty());
|
||||
assert_eq!(result.failures.len(), 1);
|
||||
assert_eq!(result.failures[0].failure_stage, "session_missing");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -136,7 +136,8 @@ use uuid::Uuid;
|
||||
|
||||
const JSONRPC_INVALID_REQUEST: i64 = -32600;
|
||||
const JSONRPC_METHOD_NOT_FOUND: i64 = -32601;
|
||||
pub(crate) const EXTERNAL_AGENT_CONFIG_IMPORT_IN_PROGRESS_MESSAGE: &str = "A previous external agent import is still running. Wait for it to finish before importing again.";
|
||||
pub(crate) const EXTERNAL_AGENT_CONFIG_IMPORT_IN_PROGRESS_MESSAGE: &str =
|
||||
"A previous Claude Code import is still running. Wait for it to finish before importing again.";
|
||||
const THREAD_SETTINGS_UPDATE_METHOD: &str = "thread/settings/update";
|
||||
|
||||
fn bootstrap_request_error(context: &'static str, err: TypedRequestError) -> color_eyre::Report {
|
||||
@@ -397,13 +398,12 @@ impl AppServerSession {
|
||||
self.client
|
||||
.request_typed(ClientRequest::ExternalAgentConfigDetect { request_id, params })
|
||||
.await
|
||||
.wrap_err("externalAgentConfig/detect failed during external agent import")
|
||||
.wrap_err("externalAgentConfig/detect failed during Claude Code import")
|
||||
}
|
||||
|
||||
pub(crate) async fn external_agent_config_import(
|
||||
&mut self,
|
||||
migration_items: Vec<ExternalAgentConfigMigrationItem>,
|
||||
source: Option<String>,
|
||||
) -> Result<()> {
|
||||
// Mark the import active before sending the request so a fast completion notification
|
||||
// cannot arrive before the TUI records it.
|
||||
@@ -420,11 +420,11 @@ impl AppServerSession {
|
||||
request_id,
|
||||
params: ExternalAgentConfigImportParams {
|
||||
migration_items,
|
||||
source,
|
||||
source: None,
|
||||
},
|
||||
})
|
||||
.await
|
||||
.wrap_err("externalAgentConfig/import failed during external agent import");
|
||||
.wrap_err("externalAgentConfig/import failed during Claude Code import");
|
||||
match response {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
|
||||
@@ -78,14 +78,15 @@ impl WidgetRef for &ExternalAgentConfigMigrationScreen {
|
||||
let error_height = u16::from(self.error.is_some());
|
||||
let intro_lines = match self.view {
|
||||
MigrationView::Summary => vec![
|
||||
Line::from("Bring over supported setup from another coding agent."),
|
||||
Line::from("Bring over your setup, current project, and recent chats."),
|
||||
Line::from("Codex may add files to your current project folder."),
|
||||
Line::from("Your existing setup will not be changed."),
|
||||
Line::from("Your existing Claude Code setup will not be changed."),
|
||||
Line::from("Standard Claude Chat data cannot be imported."),
|
||||
],
|
||||
MigrationView::Customize => vec![
|
||||
Line::from("Choose items to import."),
|
||||
Line::from("Choose the Claude Code items to import."),
|
||||
Line::from("Codex may add files to your current project folder."),
|
||||
Line::from("Your existing setup will not be changed."),
|
||||
Line::from("Your existing Claude Code setup will not be changed."),
|
||||
],
|
||||
};
|
||||
let intro_height = intro_lines.len() as u16;
|
||||
@@ -118,7 +119,7 @@ impl WidgetRef for &ExternalAgentConfigMigrationScreen {
|
||||
.areas(inner_area);
|
||||
|
||||
let title = match self.view {
|
||||
MigrationView::Summary => "Import setup",
|
||||
MigrationView::Summary => "Import from Claude Code",
|
||||
MigrationView::Customize => "Choose what to import",
|
||||
};
|
||||
let heading = Line::from(vec!["> ".into(), title.bold()]);
|
||||
|
||||
@@ -4,8 +4,6 @@ use crate::external_agent_config_migration::ExternalAgentConfigMigrationOutcome;
|
||||
use crate::external_agent_config_migration::run_external_agent_config_migration_prompt;
|
||||
use crate::external_agent_config_migration_model::external_agent_config_migration_item_count;
|
||||
use crate::external_agent_config_migration_model::external_agent_config_migration_type_label;
|
||||
use crate::external_agent_config_migration_source::ExternalAgentConfigMigrationSource;
|
||||
use crate::external_agent_config_migration_source::run_external_agent_config_source_prompt;
|
||||
use crate::legacy_core::config::Config;
|
||||
use crate::tui;
|
||||
use codex_app_server_protocol::ExternalAgentConfigDetectParams;
|
||||
@@ -16,9 +14,9 @@ use ratatui::prelude::Stylize as _;
|
||||
use ratatui::text::Line;
|
||||
|
||||
pub(crate) const EXTERNAL_AGENT_CONFIG_MIGRATION_NO_ITEMS_MESSAGE: &str =
|
||||
"No compatible setup was found to import.";
|
||||
pub(crate) const EXTERNAL_AGENT_CONFIG_MIGRATION_REMOTE_UNAVAILABLE_MESSAGE: &str = "Import from other apps is unavailable in remote sessions. Start Codex locally and run /import.";
|
||||
pub(crate) const EXTERNAL_AGENT_CONFIG_MIGRATION_DAEMON_UNAVAILABLE_MESSAGE: &str = "Import from other apps is unavailable while Codex is connected to the local app-server daemon. Stop the daemon, restart Codex, and run /import.";
|
||||
"No Claude Code setup was found to import.";
|
||||
pub(crate) const EXTERNAL_AGENT_CONFIG_MIGRATION_REMOTE_UNAVAILABLE_MESSAGE: &str = "Import from Claude Code is unavailable in remote sessions. Start Codex locally and run /import.";
|
||||
pub(crate) const EXTERNAL_AGENT_CONFIG_MIGRATION_DAEMON_UNAVAILABLE_MESSAGE: &str = "Import from Claude Code is unavailable while Codex is connected to the local app-server daemon. Stop the daemon, restart Codex, and run /import.";
|
||||
|
||||
pub(crate) enum ExternalAgentConfigMigrationFlowOutcome {
|
||||
Started(Vec<Line<'static>>),
|
||||
@@ -26,11 +24,6 @@ pub(crate) enum ExternalAgentConfigMigrationFlowOutcome {
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
struct DetectedExternalAgentConfigSource {
|
||||
source: ExternalAgentConfigMigrationSource,
|
||||
items: Vec<ExternalAgentConfigMigrationItem>,
|
||||
}
|
||||
|
||||
fn external_agent_config_migration_started_lines(
|
||||
selected_items: &[ExternalAgentConfigMigrationItem],
|
||||
remaining_item_count: usize,
|
||||
@@ -96,7 +89,7 @@ fn external_agent_config_migration_started_lines(
|
||||
let mut lines = vec![
|
||||
vec![
|
||||
"• ".dim(),
|
||||
"Import started.".cyan(),
|
||||
"Claude Code import started.".cyan(),
|
||||
" You can keep working while it finishes.".into(),
|
||||
]
|
||||
.into(),
|
||||
@@ -152,7 +145,7 @@ pub(crate) fn external_agent_config_migration_finished_lines(
|
||||
let mut lines = vec![
|
||||
vec![
|
||||
"• ".dim(),
|
||||
"Import finished: ".into(),
|
||||
"Claude Code import finished: ".into(),
|
||||
format!("{imported_count} imported").green(),
|
||||
", ".into(),
|
||||
failed_count,
|
||||
@@ -219,65 +212,28 @@ pub(crate) async fn handle_external_agent_config_migration_prompt(
|
||||
}
|
||||
|
||||
let cwd = config.cwd.to_path_buf();
|
||||
let mut detected_sources = Vec::new();
|
||||
let mut detection_errors = Vec::new();
|
||||
for source in ExternalAgentConfigMigrationSource::ALL {
|
||||
let response = match app_server
|
||||
.external_agent_config_detect(ExternalAgentConfigDetectParams {
|
||||
include_home: true,
|
||||
cwds: Some(vec![cwd.clone()]),
|
||||
source: Some(source.api_source().to_string()),
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(response) => response,
|
||||
Err(err) => {
|
||||
tracing::warn!(
|
||||
error = %err,
|
||||
cwd = %cwd.display(),
|
||||
source = source.label(),
|
||||
"failed to detect external agent config migrations"
|
||||
);
|
||||
detection_errors.push(format!("{}: {err}", source.label()));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if !response.items.is_empty() {
|
||||
detected_sources.push(DetectedExternalAgentConfigSource {
|
||||
source,
|
||||
items: response.items,
|
||||
});
|
||||
let detected_items = match app_server
|
||||
.external_agent_config_detect(ExternalAgentConfigDetectParams {
|
||||
include_home: true,
|
||||
cwds: Some(vec![cwd.clone()]),
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(response) => response.items,
|
||||
Err(err) => {
|
||||
tracing::warn!(
|
||||
error = %err,
|
||||
cwd = %cwd.display(),
|
||||
"failed to detect external agent config migrations"
|
||||
);
|
||||
return Err(format!("Could not check for Claude Code setup: {err}"));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if detected_sources.is_empty() {
|
||||
if !detection_errors.is_empty() {
|
||||
return Err(format!(
|
||||
"Could not check for importable setup: {}",
|
||||
detection_errors.join("; ")
|
||||
));
|
||||
}
|
||||
if detected_items.is_empty() {
|
||||
return Ok(ExternalAgentConfigMigrationFlowOutcome::NoItems);
|
||||
}
|
||||
|
||||
let selected_source = if detected_sources.len() == 1 {
|
||||
detected_sources[0].source
|
||||
} else {
|
||||
let sources = detected_sources
|
||||
.iter()
|
||||
.map(|detected| detected.source)
|
||||
.collect::<Vec<_>>();
|
||||
let Some(source) = run_external_agent_config_source_prompt(tui, &sources).await else {
|
||||
return Ok(ExternalAgentConfigMigrationFlowOutcome::Cancelled);
|
||||
};
|
||||
source
|
||||
};
|
||||
let detected_items = detected_sources
|
||||
.into_iter()
|
||||
.find(|detected| detected.source == selected_source)
|
||||
.map(|detected| detected.items)
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut selected_items = detected_items.clone();
|
||||
let mut error: Option<String> = None;
|
||||
|
||||
@@ -292,13 +248,7 @@ pub(crate) async fn handle_external_agent_config_migration_prompt(
|
||||
{
|
||||
ExternalAgentConfigMigrationOutcome::Proceed(items) => {
|
||||
selected_items = items.clone();
|
||||
match app_server
|
||||
.external_agent_config_import(
|
||||
items,
|
||||
Some(selected_source.api_source().to_string()),
|
||||
)
|
||||
.await
|
||||
{
|
||||
match app_server.external_agent_config_import(items).await {
|
||||
Ok(()) => {
|
||||
let remaining_item_count =
|
||||
detected_items.len().saturating_sub(selected_items.len());
|
||||
|
||||
@@ -173,7 +173,7 @@ fn external_agent_config_migration_status_lines_use_semantic_colors() {
|
||||
vec![
|
||||
Line::from(vec![
|
||||
"• ".dim(),
|
||||
"Import started.".cyan(),
|
||||
"Claude Code import started.".cyan(),
|
||||
" You can keep working while it finishes.".into(),
|
||||
]),
|
||||
Line::from(vec![
|
||||
@@ -227,7 +227,7 @@ fn external_agent_config_migration_status_lines_use_semantic_colors() {
|
||||
vec![
|
||||
Line::from(vec![
|
||||
"• ".dim(),
|
||||
"Import finished: ".into(),
|
||||
"Claude Code import finished: ".into(),
|
||||
"2 imported".green(),
|
||||
", ".into(),
|
||||
"1 failed".red(),
|
||||
|
||||
@@ -79,7 +79,7 @@ pub(crate) fn external_agent_config_migration_item_label(
|
||||
item: &ExternalAgentConfigMigrationItem,
|
||||
) -> &'static str {
|
||||
match item.item_type {
|
||||
ExternalAgentConfigMigrationItemType::AgentsMd => "Instructions",
|
||||
ExternalAgentConfigMigrationItemType::AgentsMd => "Instructions (CLAUDE.md -> AGENTS.md)",
|
||||
ExternalAgentConfigMigrationItemType::Config => "Settings (settings.json -> config.toml)",
|
||||
ExternalAgentConfigMigrationItemType::Skills => "Skills",
|
||||
ExternalAgentConfigMigrationItemType::Plugins => "Plugins",
|
||||
|
||||
@@ -1,200 +0,0 @@
|
||||
use crate::key_hint;
|
||||
use crate::render::Insets;
|
||||
use crate::render::renderable::ColumnRenderable;
|
||||
use crate::render::renderable::Renderable;
|
||||
use crate::render::renderable::RenderableExt as _;
|
||||
use crate::tui::FrameRequester;
|
||||
use crate::tui::Tui;
|
||||
use crate::tui::TuiEvent;
|
||||
use crossterm::event::KeyCode;
|
||||
use crossterm::event::KeyEvent;
|
||||
use crossterm::event::KeyEventKind;
|
||||
use crossterm::event::KeyModifiers;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::prelude::Stylize as _;
|
||||
use ratatui::prelude::Widget;
|
||||
use ratatui::text::Line;
|
||||
use ratatui::text::Span;
|
||||
use ratatui::widgets::Clear;
|
||||
use ratatui::widgets::WidgetRef;
|
||||
use tokio_stream::StreamExt;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum ExternalAgentConfigMigrationSource {
|
||||
Cl,
|
||||
Cu,
|
||||
}
|
||||
|
||||
impl ExternalAgentConfigMigrationSource {
|
||||
pub(crate) const ALL: [Self; 2] = [Self::Cl, Self::Cu];
|
||||
|
||||
pub(crate) fn api_source(self) -> &'static str {
|
||||
match self {
|
||||
Self::Cl => "claude-code",
|
||||
Self::Cu => "cursor",
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn label(self) -> &'static str {
|
||||
match self {
|
||||
Self::Cl => "Claude Code",
|
||||
Self::Cu => "Cursor",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn run_external_agent_config_source_prompt(
|
||||
tui: &mut Tui,
|
||||
sources: &[ExternalAgentConfigMigrationSource],
|
||||
) -> Option<ExternalAgentConfigMigrationSource> {
|
||||
let mut screen = ExternalAgentConfigSourceScreen::new(tui.frame_requester(), sources);
|
||||
let _ = tui.draw(u16::MAX, |frame| {
|
||||
frame.render_widget_ref(&screen, frame.area());
|
||||
});
|
||||
|
||||
let events = tui.event_stream();
|
||||
tokio::pin!(events);
|
||||
|
||||
while !screen.is_done() {
|
||||
if let Some(event) = events.next().await {
|
||||
match event {
|
||||
TuiEvent::Key(key_event) => screen.handle_key(key_event),
|
||||
TuiEvent::Paste(_) => {}
|
||||
TuiEvent::Draw | TuiEvent::Resize => {
|
||||
let _ = tui.draw(u16::MAX, |frame| {
|
||||
frame.render_widget_ref(&screen, frame.area());
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
screen.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
screen.selection()
|
||||
}
|
||||
|
||||
struct ExternalAgentConfigSourceScreen {
|
||||
request_frame: FrameRequester,
|
||||
sources: Vec<ExternalAgentConfigMigrationSource>,
|
||||
highlighted_idx: usize,
|
||||
selection: Option<ExternalAgentConfigMigrationSource>,
|
||||
done: bool,
|
||||
}
|
||||
|
||||
impl ExternalAgentConfigSourceScreen {
|
||||
fn new(request_frame: FrameRequester, sources: &[ExternalAgentConfigMigrationSource]) -> Self {
|
||||
Self {
|
||||
request_frame,
|
||||
sources: sources.to_vec(),
|
||||
highlighted_idx: 0,
|
||||
selection: None,
|
||||
done: sources.is_empty(),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_key(&mut self, key_event: KeyEvent) {
|
||||
if key_event.kind == KeyEventKind::Release || self.sources.is_empty() {
|
||||
return;
|
||||
}
|
||||
if key_event.modifiers.contains(KeyModifiers::CONTROL)
|
||||
&& matches!(key_event.code, KeyCode::Char('c') | KeyCode::Char('d'))
|
||||
{
|
||||
self.cancel();
|
||||
return;
|
||||
}
|
||||
match key_event.code {
|
||||
KeyCode::Up | KeyCode::Char('k') => self.move_highlight(-1),
|
||||
KeyCode::Down | KeyCode::Char('j') => self.move_highlight(1),
|
||||
KeyCode::Char(character) if character.is_ascii_digit() => {
|
||||
let Some(index) = character
|
||||
.to_digit(10)
|
||||
.and_then(|index| index.checked_sub(1))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
self.select(index as usize);
|
||||
}
|
||||
KeyCode::Enter => self.select(self.highlighted_idx),
|
||||
KeyCode::Esc => self.cancel(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn move_highlight(&mut self, delta: isize) {
|
||||
self.highlighted_idx = self
|
||||
.highlighted_idx
|
||||
.saturating_add_signed(delta)
|
||||
.min(self.sources.len().saturating_sub(1));
|
||||
self.request_frame.schedule_frame();
|
||||
}
|
||||
|
||||
fn select(&mut self, index: usize) {
|
||||
let Some(source) = self.sources.get(index).copied() else {
|
||||
return;
|
||||
};
|
||||
self.selection = Some(source);
|
||||
self.done = true;
|
||||
self.request_frame.schedule_frame();
|
||||
}
|
||||
|
||||
fn cancel(&mut self) {
|
||||
self.selection = None;
|
||||
self.done = true;
|
||||
self.request_frame.schedule_frame();
|
||||
}
|
||||
|
||||
fn is_done(&self) -> bool {
|
||||
self.done
|
||||
}
|
||||
|
||||
fn selection(&self) -> Option<ExternalAgentConfigMigrationSource> {
|
||||
self.selection
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetRef for &ExternalAgentConfigSourceScreen {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
Clear.render(area, buf);
|
||||
let mut column = ColumnRenderable::new();
|
||||
column.push("");
|
||||
column.push("Choose an import source".bold());
|
||||
column.push("");
|
||||
column.push(
|
||||
Line::from("Select the app whose setup you want to import.".dim()).inset(Insets::tlbr(
|
||||
/*top*/ 0, /*left*/ 2, /*bottom*/ 0, /*right*/ 0,
|
||||
)),
|
||||
);
|
||||
column.push("");
|
||||
for (index, source) in self.sources.iter().enumerate() {
|
||||
let prefix = if index == self.highlighted_idx {
|
||||
"›"
|
||||
} else {
|
||||
" "
|
||||
};
|
||||
let label = Span::from(format!("{prefix} {}. {}", index + 1, source.label()));
|
||||
column.push(Line::from(if index == self.highlighted_idx {
|
||||
label.cyan()
|
||||
} else {
|
||||
label
|
||||
}));
|
||||
}
|
||||
column.push("");
|
||||
column.push(
|
||||
Line::from(vec![
|
||||
"Press ".dim(),
|
||||
key_hint::plain(KeyCode::Enter).into(),
|
||||
" to continue".dim(),
|
||||
])
|
||||
.inset(Insets::tlbr(
|
||||
/*top*/ 0, /*left*/ 2, /*bottom*/ 0, /*right*/ 0,
|
||||
)),
|
||||
);
|
||||
column.render(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "external_agent_config_migration_source_tests.rs"]
|
||||
mod tests;
|
||||
@@ -1,42 +0,0 @@
|
||||
use super::*;
|
||||
use crate::test_backend::VT100Backend;
|
||||
use crossterm::event::KeyEvent;
|
||||
use crossterm::event::KeyModifiers;
|
||||
use pretty_assertions::assert_eq;
|
||||
use ratatui::Terminal;
|
||||
|
||||
fn new_screen() -> ExternalAgentConfigSourceScreen {
|
||||
ExternalAgentConfigSourceScreen::new(
|
||||
FrameRequester::test_dummy(),
|
||||
&ExternalAgentConfigMigrationSource::ALL,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_agent_config_source_prompt_snapshot() {
|
||||
let screen = new_screen();
|
||||
let mut terminal =
|
||||
Terminal::new(VT100Backend::new(/*width*/ 80, /*height*/ 12)).expect("terminal");
|
||||
terminal
|
||||
.draw(|frame| frame.render_widget_ref(&screen, frame.area()))
|
||||
.expect("render source prompt");
|
||||
insta::assert_snapshot!("external_agent_config_source_prompt", terminal.backend());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_agent_config_source_prompt_selects_highlighted_source() {
|
||||
let mut screen = new_screen();
|
||||
screen.handle_key(KeyEvent::new(KeyCode::Down, KeyModifiers::NONE));
|
||||
screen.handle_key(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
|
||||
assert_eq!(
|
||||
screen.selection(),
|
||||
Some(ExternalAgentConfigMigrationSource::Cu)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_agent_config_source_prompt_can_cancel() {
|
||||
let mut screen = new_screen();
|
||||
screen.handle_key(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));
|
||||
assert_eq!(screen.selection(), None);
|
||||
}
|
||||
@@ -116,7 +116,6 @@ mod exec_command;
|
||||
mod external_agent_config_migration;
|
||||
mod external_agent_config_migration_flow;
|
||||
mod external_agent_config_migration_model;
|
||||
mod external_agent_config_migration_source;
|
||||
mod external_editor;
|
||||
mod file_search;
|
||||
mod frames;
|
||||
|
||||
+3
-3
@@ -4,9 +4,9 @@ expression: rendered
|
||||
---
|
||||
|
||||
> Choose what to import
|
||||
Choose items to import.
|
||||
Choose the Claude Code items to import.
|
||||
Codex may add files to your current project folder.
|
||||
Your existing setup will not be changed.
|
||||
Your existing Claude Code setup will not be changed.
|
||||
Home
|
||||
› [x] Settings (settings.json -> config.toml)
|
||||
Import /Users/alex/.claude/settings.json into /Users/alex/.codex/conf…
|
||||
@@ -21,7 +21,7 @@ expression: rendered
|
||||
• team-marketplace: asana
|
||||
• debug: sample
|
||||
• +1 more marketplaces
|
||||
[x] Instructions
|
||||
[x] Instructions (CLAUDE.md -> AGENTS.md)
|
||||
Import CLAUDE.md to AGENTS.md
|
||||
|
||||
Selected 4 of 4 items.
|
||||
|
||||
+3
-3
@@ -4,9 +4,9 @@ expression: rendered
|
||||
---
|
||||
|
||||
> Choose what to import
|
||||
Choose items to import.
|
||||
Choose the Claude Code items to import.
|
||||
Codex may add files to your current project folder.
|
||||
Your existing setup will not be changed.
|
||||
Your existing Claude Code setup will not be changed.
|
||||
Home
|
||||
[x] Settings (settings.json -> config.toml)
|
||||
Import /Users/alex/.claude/settings.json into /Users/alex/.codex/conf…
|
||||
@@ -21,7 +21,7 @@ expression: rendered
|
||||
• team-marketplace: asana
|
||||
• debug: sample
|
||||
• +1 more marketplaces
|
||||
[x] Instructions
|
||||
[x] Instructions (CLAUDE.md -> AGENTS.md)
|
||||
Import CLAUDE.md to AGENTS.md
|
||||
|
||||
Selected 4 of 4 items.
|
||||
|
||||
+3
-3
@@ -4,9 +4,9 @@ expression: rendered
|
||||
---
|
||||
|
||||
> Choose what to import
|
||||
Choose items to import.
|
||||
Choose the Claude Code items to import.
|
||||
Codex may add files to your current project folder.
|
||||
Your existing setup will not be changed.
|
||||
Your existing Claude Code setup will not be changed.
|
||||
Home
|
||||
[x] Settings (settings.json -> config.toml)
|
||||
Import /Users/alex/.claude/settings.json into /Users/alex/.codex/conf…
|
||||
@@ -21,7 +21,7 @@ expression: rendered
|
||||
• team-marketplace: asana
|
||||
• debug: sample
|
||||
• +1 more marketplaces
|
||||
[x] Instructions
|
||||
[x] Instructions (CLAUDE.md -> AGENTS.md)
|
||||
Import CLAUDE.md to AGENTS.md
|
||||
|
||||
Selected 4 of 4 items.
|
||||
|
||||
+3
-3
@@ -4,9 +4,9 @@ expression: rendered
|
||||
---
|
||||
|
||||
> Choose what to import
|
||||
Choose items to import.
|
||||
Choose the Claude Code items to import.
|
||||
Codex may add files to your current project folder.
|
||||
Your existing setup will not be changed.
|
||||
Your existing Claude Code setup will not be changed.
|
||||
Home
|
||||
› [x] Settings (settings.json -> config.toml)
|
||||
Import /Users/alex/.claude/settings.json into /Users/alex/.codex/conf…
|
||||
@@ -21,7 +21,7 @@ expression: rendered
|
||||
• team-marketplace: asana
|
||||
• debug: sample
|
||||
• +1 more marketplaces
|
||||
[x] Instructions
|
||||
[x] Instructions (CLAUDE.md -> AGENTS.md)
|
||||
Import CLAUDE.md to AGENTS.md
|
||||
|
||||
Selected 4 of 4 items.
|
||||
|
||||
+4
-3
@@ -3,10 +3,11 @@ source: tui/src/external_agent_config_migration.rs
|
||||
expression: rendered
|
||||
---
|
||||
|
||||
> Import setup
|
||||
Bring over supported setup from another coding agent.
|
||||
> Import from Claude Code
|
||||
Bring over your setup, current project, and recent chats.
|
||||
Codex may add files to your current project folder.
|
||||
Your existing setup will not be changed.
|
||||
Your existing Claude Code setup will not be changed.
|
||||
Standard Claude Chat data cannot be imported.
|
||||
[x] Tools & setup
|
||||
Settings, instructions, integrations, agents, commands, and skills
|
||||
Importing: Settings 1
|
||||
|
||||
+4
-3
@@ -3,10 +3,11 @@ source: tui/src/external_agent_config_migration.rs
|
||||
expression: rendered
|
||||
---
|
||||
|
||||
> Import setup
|
||||
Bring over supported setup from another coding agent.
|
||||
> Import from Claude Code
|
||||
Bring over your setup, current project, and recent chats.
|
||||
Codex may add files to your current project folder.
|
||||
Your existing setup will not be changed.
|
||||
Your existing Claude Code setup will not be changed.
|
||||
Standard Claude Chat data cannot be imported.
|
||||
[x] Tools & setup
|
||||
Settings, instructions, integrations, agents, commands, and skills
|
||||
Importing: Settings 1
|
||||
|
||||
+8
-8
@@ -2,7 +2,7 @@
|
||||
source: tui/src/external_agent_config_migration_flow_tests.rs
|
||||
expression: messages
|
||||
---
|
||||
• Import started. You can keep working while it finishes.
|
||||
• Claude Code import started. You can keep working while it finishes.
|
||||
Imported setup will apply to new chats.
|
||||
Importing:
|
||||
Settings: 1
|
||||
@@ -10,7 +10,7 @@ expression: messages
|
||||
MCP servers: 2 — docs, issues
|
||||
Chat sessions: 3 — Alpha rollout, Beta review, Gamma notes
|
||||
Plugins: 2 — formatter, reviewer
|
||||
• Import started. You can keep working while it finishes.
|
||||
• Claude Code import started. You can keep working while it finishes.
|
||||
Imported setup will apply to new chats.
|
||||
Importing:
|
||||
Settings: 1
|
||||
@@ -19,7 +19,7 @@ expression: messages
|
||||
Chat sessions: 3 — Alpha rollout, Beta review, Gamma notes
|
||||
Plugins: 2 — formatter, reviewer
|
||||
1 additional item remains. After it finishes, run /import again to review it.
|
||||
• Import started. You can keep working while it finishes.
|
||||
• Claude Code import started. You can keep working while it finishes.
|
||||
Imported setup will apply to new chats.
|
||||
Importing:
|
||||
Settings: 1
|
||||
@@ -28,12 +28,12 @@ expression: messages
|
||||
Chat sessions: 3 — Alpha rollout, Beta review, Gamma notes
|
||||
Plugins: 2 — formatter, reviewer
|
||||
2 additional items remain. After it finishes, run /import again to review them.
|
||||
• Import finished: 2 imported, 1 failed.
|
||||
• Claude Code import finished: 2 imported, 1 failed.
|
||||
Results by type:
|
||||
Settings: 1 imported, 0 failed
|
||||
Plugins: 1 imported, 1 failed
|
||||
Run /import again to check for additional items.
|
||||
No compatible setup was found to import.
|
||||
Import from other apps is unavailable in remote sessions. Start Codex locally and run /import.
|
||||
Import from other apps is unavailable while Codex is connected to the local app-server daemon. Stop the daemon, restart Codex, and run /import.
|
||||
A previous external agent import is still running. Wait for it to finish before importing again.
|
||||
No Claude Code setup was found to import.
|
||||
Import from Claude Code is unavailable in remote sessions. Start Codex locally and run /import.
|
||||
Import from Claude Code is unavailable while Codex is connected to the local app-server daemon. Stop the daemon, restart Codex, and run /import.
|
||||
A previous Claude Code import is still running. Wait for it to finish before importing again.
|
||||
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
---
|
||||
source: tui/src/external_agent_config_migration_source_tests.rs
|
||||
expression: terminal.backend()
|
||||
---
|
||||
|
||||
Choose an import source
|
||||
|
||||
Select the app whose setup you want to import.
|
||||
|
||||
› 1. Claude Code
|
||||
2. Cursor
|
||||
|
||||
Press enter to continue
|
||||
Reference in New Issue
Block a user