mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
[codex] show external import result counts (#29567)
## What changed - Show per-type import counts in the `/import` review UI and started message. - Render completion results as a multi-line summary with total imported/failed counts and one row per import type. - Add snapshot coverage for the updated review and completion output. <img width="537" height="322" alt="Screenshot 2026-06-23 at 9 41 20 PM" src="https://github.com/user-attachments/assets/166542eb-2097-4b2b-8130-8f6fd8c680ce" /> ## Why The TUI previously only reported that Claude Code import started or finished. Users could not see how many items of each type were selected or how many actually imported versus failed.
This commit is contained in:
committed by
GitHub
Unverified
parent
69b76e9d07
commit
3694b48a82
@@ -1723,6 +1723,13 @@
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"skills": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/definitions/SkillMigration"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"subagents": {
|
||||
"default": [],
|
||||
"items": {
|
||||
@@ -3407,6 +3414,17 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SkillMigration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SkillsConfigWriteParams": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
|
||||
+18
@@ -12392,6 +12392,13 @@
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"skills": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/definitions/v2/SkillMigration"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"subagents": {
|
||||
"default": [],
|
||||
"items": {
|
||||
@@ -16338,6 +16345,17 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SkillMigration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SkillScope": {
|
||||
"enum": [
|
||||
"user",
|
||||
|
||||
+18
@@ -8796,6 +8796,13 @@
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"skills": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/definitions/SkillMigration"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"subagents": {
|
||||
"default": [],
|
||||
"items": {
|
||||
@@ -14117,6 +14124,17 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SkillMigration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SkillScope": {
|
||||
"enum": [
|
||||
"user",
|
||||
|
||||
+18
@@ -117,6 +117,13 @@
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"skills": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/definitions/SkillMigration"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"subagents": {
|
||||
"default": [],
|
||||
"items": {
|
||||
@@ -166,6 +173,17 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SkillMigration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SubagentMigration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
|
||||
+18
@@ -117,6 +117,13 @@
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"skills": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/definitions/SkillMigration"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"subagents": {
|
||||
"default": [],
|
||||
"items": {
|
||||
@@ -166,6 +173,17 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SkillMigration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SubagentMigration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { HookMigration } from "./HookMigration";
|
||||
import type { McpServerMigration } from "./McpServerMigration";
|
||||
import type { PluginsMigration } from "./PluginsMigration";
|
||||
import type { SessionMigration } from "./SessionMigration";
|
||||
import type { SkillMigration } from "./SkillMigration";
|
||||
import type { SubagentMigration } from "./SubagentMigration";
|
||||
|
||||
export type MigrationDetails = { plugins: Array<PluginsMigration>, sessions: Array<SessionMigration>, mcpServers: Array<McpServerMigration>, hooks: Array<HookMigration>, subagents: Array<SubagentMigration>, commands: Array<CommandMigration>, };
|
||||
export type MigrationDetails = { plugins: Array<PluginsMigration>, skills: Array<SkillMigration>, sessions: Array<SessionMigration>, mcpServers: Array<McpServerMigration>, hooks: Array<HookMigration>, subagents: Array<SubagentMigration>, commands: Array<CommandMigration>, };
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type SkillMigration = { name: string, };
|
||||
@@ -363,6 +363,7 @@ export type { SkillDependencies } from "./SkillDependencies";
|
||||
export type { SkillErrorInfo } from "./SkillErrorInfo";
|
||||
export type { SkillInterface } from "./SkillInterface";
|
||||
export type { SkillMetadata } from "./SkillMetadata";
|
||||
export type { SkillMigration } from "./SkillMigration";
|
||||
export type { SkillScope } from "./SkillScope";
|
||||
export type { SkillSummary } from "./SkillSummary";
|
||||
export type { SkillToolDependency } from "./SkillToolDependency";
|
||||
|
||||
@@ -574,6 +574,13 @@ pub struct PluginsMigration {
|
||||
pub plugin_names: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct SkillMigration {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
@@ -618,6 +625,8 @@ pub struct MigrationDetails {
|
||||
#[serde(default)]
|
||||
pub plugins: Vec<PluginsMigration>,
|
||||
#[serde(default)]
|
||||
pub skills: Vec<SkillMigration>,
|
||||
#[serde(default)]
|
||||
pub sessions: Vec<SessionMigration>,
|
||||
#[serde(default)]
|
||||
pub mcp_servers: Vec<McpServerMigration>,
|
||||
|
||||
@@ -238,8 +238,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`; each detected item includes `cwd` (`null` for home), and plugin/session migration items may additionally include structured `details` grouping plugin ids or session metadata.
|
||||
- `externalAgentConfig/import` — apply selected external-agent migration items by passing explicit `migrationItems` with `cwd` (`null` for home) and any plugin/session `details` returned by detect. Callers may pass `source` to identify the product that initiated the import; omitted or `null` means unspecified. 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` and optional `cwds`; 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. Callers may pass `source` to identify the product that initiated the import; omitted or `null` means unspecified. 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`, and `network` constraints such as canonical domain/socket permissions plus `managedAllowedDomainsOnly` and `dangerFullAccessDenylistOnly`.
|
||||
|
||||
@@ -72,6 +72,7 @@ pub(crate) struct NamedMigration {
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub(crate) struct MigrationDetails {
|
||||
pub plugins: Vec<PluginsMigration>,
|
||||
pub skills: Vec<NamedMigration>,
|
||||
pub sessions: Vec<ExternalAgentSessionMigration>,
|
||||
pub mcp_servers: Vec<NamedMigration>,
|
||||
pub hooks: Vec<NamedMigration>,
|
||||
@@ -570,7 +571,8 @@ impl ExternalAgentConfigService {
|
||||
|| self.home_target_skills_dir(),
|
||||
|repo_root| repo_root.join(".agents").join("skills"),
|
||||
);
|
||||
let skills_count = count_missing_subdirectories(&source_skills, &target_skills)?;
|
||||
let skill_names = missing_subdirectory_names(&source_skills, &target_skills)?;
|
||||
let skills_count = skill_names.len();
|
||||
if skills_count > 0 {
|
||||
items.push(ExternalAgentConfigMigrationItem {
|
||||
item_type: ExternalAgentConfigMigrationItemType::Skills,
|
||||
@@ -580,7 +582,10 @@ impl ExternalAgentConfigService {
|
||||
target_skills.display()
|
||||
),
|
||||
cwd: cwd.clone(),
|
||||
details: None,
|
||||
details: Some(MigrationDetails {
|
||||
skills: named_migrations(skill_names),
|
||||
..Default::default()
|
||||
}),
|
||||
});
|
||||
emit_migration_metric(
|
||||
EXTERNAL_AGENT_CONFIG_DETECT_METRIC,
|
||||
@@ -1569,13 +1574,16 @@ fn collect_subdirectory_names(path: &Path) -> io::Result<HashSet<OsString>> {
|
||||
Ok(names)
|
||||
}
|
||||
|
||||
fn count_missing_subdirectories(source: &Path, target: &Path) -> io::Result<usize> {
|
||||
fn missing_subdirectory_names(source: &Path, target: &Path) -> io::Result<Vec<String>> {
|
||||
let source_names = collect_subdirectory_names(source)?;
|
||||
let target_names = collect_subdirectory_names(target)?;
|
||||
Ok(source_names
|
||||
.iter()
|
||||
.filter(|name| !target_names.contains(*name))
|
||||
.count())
|
||||
let mut missing_names = source_names
|
||||
.into_iter()
|
||||
.filter(|name| !target_names.contains(name))
|
||||
.map(|name| name.to_string_lossy().into_owned())
|
||||
.collect::<Vec<_>>();
|
||||
missing_names.sort();
|
||||
Ok(missing_names)
|
||||
}
|
||||
|
||||
fn is_missing_or_empty_text_file(path: &Path) -> io::Result<bool> {
|
||||
|
||||
@@ -113,7 +113,10 @@ async fn detect_home_lists_config_skills_and_agents_md() {
|
||||
agents_skills.display()
|
||||
),
|
||||
cwd: None,
|
||||
details: None,
|
||||
details: Some(MigrationDetails {
|
||||
skills: named_migrations(vec!["skill-a".to_string()]),
|
||||
..Default::default()
|
||||
}),
|
||||
},
|
||||
ExternalAgentConfigMigrationItem {
|
||||
item_type: ExternalAgentConfigMigrationItemType::AgentsMd,
|
||||
@@ -305,7 +308,10 @@ async fn detect_repo_still_reports_non_plugin_items_when_home_config_is_invalid(
|
||||
repo_root.join(".agents").join("skills").display()
|
||||
),
|
||||
cwd: Some(repo_root.clone()),
|
||||
details: None,
|
||||
details: Some(MigrationDetails {
|
||||
skills: named_migrations(vec!["skill-a".to_string()]),
|
||||
..Default::default()
|
||||
}),
|
||||
},
|
||||
ExternalAgentConfigMigrationItem {
|
||||
item_type: ExternalAgentConfigMigrationItemType::AgentsMd,
|
||||
|
||||
@@ -38,6 +38,7 @@ use codex_app_server_protocol::McpServerMigration;
|
||||
use codex_app_server_protocol::MigrationDetails;
|
||||
use codex_app_server_protocol::PluginsMigration;
|
||||
use codex_app_server_protocol::ServerNotification;
|
||||
use codex_app_server_protocol::SkillMigration;
|
||||
use codex_arg0::Arg0DispatchPaths;
|
||||
use codex_core::ThreadManager;
|
||||
use codex_external_agent_sessions::ExternalAgentSessionMigration as CoreSessionMigration;
|
||||
@@ -161,6 +162,11 @@ impl ExternalAgentConfigRequestProcessor {
|
||||
plugin_names: plugin.plugin_names,
|
||||
})
|
||||
.collect(),
|
||||
skills: details
|
||||
.skills
|
||||
.into_iter()
|
||||
.map(|skill| SkillMigration { name: skill.name })
|
||||
.collect(),
|
||||
sessions: details
|
||||
.sessions
|
||||
.into_iter()
|
||||
@@ -490,6 +496,11 @@ impl ExternalAgentConfigRequestProcessor {
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
skills: details
|
||||
.skills
|
||||
.into_iter()
|
||||
.map(|skill| CoreNamedMigration { name: skill.name })
|
||||
.collect(),
|
||||
sessions: details
|
||||
.sessions
|
||||
.into_iter()
|
||||
|
||||
@@ -103,7 +103,7 @@ impl App {
|
||||
);
|
||||
return;
|
||||
}
|
||||
ServerNotification::ExternalAgentConfigImportCompleted(_) => {
|
||||
ServerNotification::ExternalAgentConfigImportCompleted(notification) => {
|
||||
let should_report_completion =
|
||||
app_server_client.consume_external_agent_config_import_completion();
|
||||
if let Err(err) = self.refresh_in_memory_config_from_disk().await {
|
||||
@@ -117,10 +117,8 @@ impl App {
|
||||
self.chat_widget.submit_op(AppCommand::reload_user_config());
|
||||
self.fetch_plugins_list(app_server_client, cwd);
|
||||
if should_report_completion {
|
||||
self.chat_widget.add_info_message(
|
||||
crate::external_agent_config_migration_flow::EXTERNAL_AGENT_CONFIG_MIGRATION_FINISHED_MESSAGE
|
||||
.to_string(),
|
||||
/*hint*/ None,
|
||||
self.chat_widget.add_plain_history_lines(
|
||||
crate::external_agent_config_migration_flow::external_agent_config_migration_finished_lines(notification),
|
||||
);
|
||||
}
|
||||
return;
|
||||
|
||||
@@ -119,8 +119,8 @@ impl App {
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(ExternalAgentConfigMigrationFlowOutcome::Started(message)) => {
|
||||
self.chat_widget.add_info_message(message, /*hint*/ None);
|
||||
Ok(ExternalAgentConfigMigrationFlowOutcome::Started(lines)) => {
|
||||
self.chat_widget.add_plain_history_lines(lines);
|
||||
}
|
||||
Ok(ExternalAgentConfigMigrationFlowOutcome::NoItems) => {
|
||||
self.chat_widget.add_info_message(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::diff_render::display_path_for;
|
||||
use crate::external_agent_config_migration_model::ExternalAgentConfigMigrationGroupModel;
|
||||
use crate::external_agent_config_migration_model::external_agent_config_migration_count_summary;
|
||||
use crate::external_agent_config_migration_model::external_agent_config_migration_groups;
|
||||
use crate::external_agent_config_migration_model::external_agent_config_migration_item_detail;
|
||||
use crate::external_agent_config_migration_model::external_agent_config_migration_item_label;
|
||||
@@ -599,6 +600,13 @@ impl ExternalAgentConfigMigrationScreen {
|
||||
.iter()
|
||||
.enumerate()
|
||||
.flat_map(|(idx, group)| {
|
||||
let selected_items = group
|
||||
.item_indices
|
||||
.iter()
|
||||
.filter_map(|idx| self.items.get(*idx))
|
||||
.filter(|item| item.enabled)
|
||||
.map(|item| &item.item);
|
||||
let count_summary = external_agent_config_migration_count_summary(selected_items);
|
||||
[
|
||||
RenderLineEntry {
|
||||
item_idx: Some(idx),
|
||||
@@ -614,6 +622,15 @@ impl ExternalAgentConfigMigrationScreen {
|
||||
kind: RenderLineKind::ItemDetail,
|
||||
line: Line::from(format!(" {}", group.description)),
|
||||
},
|
||||
RenderLineEntry {
|
||||
item_idx: None,
|
||||
kind: RenderLineKind::ItemDetail,
|
||||
line: Line::from(if count_summary.is_empty() {
|
||||
" Importing: none".to_string()
|
||||
} else {
|
||||
format!(" Importing: {count_summary}")
|
||||
}),
|
||||
},
|
||||
]
|
||||
})
|
||||
.collect()
|
||||
|
||||
@@ -2,29 +2,185 @@ use crate::app_server_session::AppServerSession;
|
||||
use crate::app_server_session::EXTERNAL_AGENT_CONFIG_IMPORT_IN_PROGRESS_MESSAGE;
|
||||
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::legacy_core::config::Config;
|
||||
use crate::tui;
|
||||
use codex_app_server_protocol::ExternalAgentConfigDetectParams;
|
||||
use codex_app_server_protocol::ExternalAgentConfigImportCompletedNotification;
|
||||
use codex_app_server_protocol::ExternalAgentConfigMigrationItem;
|
||||
use codex_app_server_protocol::ExternalAgentConfigMigrationItemType;
|
||||
use ratatui::prelude::Stylize as _;
|
||||
use ratatui::text::Line;
|
||||
|
||||
pub(crate) const EXTERNAL_AGENT_CONFIG_MIGRATION_FINISHED_MESSAGE: &str =
|
||||
"Claude Code import finished. Run /import again to check for additional items.";
|
||||
pub(crate) const EXTERNAL_AGENT_CONFIG_MIGRATION_NO_ITEMS_MESSAGE: &str =
|
||||
"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(String),
|
||||
Started(Vec<Line<'static>>),
|
||||
NoItems,
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
fn external_agent_config_migration_success_message(remaining_item_count: usize) -> String {
|
||||
let message = "Claude Code import started. You can keep working while it finishes. Imported setup will apply to new chats.";
|
||||
match remaining_items_handoff(remaining_item_count) {
|
||||
Some(remaining_items_handoff) => format!("{message} {remaining_items_handoff}"),
|
||||
None => message.to_string(),
|
||||
fn external_agent_config_migration_started_lines(
|
||||
selected_items: &[ExternalAgentConfigMigrationItem],
|
||||
remaining_item_count: usize,
|
||||
) -> Vec<Line<'static>> {
|
||||
let mut import_summaries =
|
||||
Vec::<(ExternalAgentConfigMigrationItemType, usize, Vec<&str>)>::new();
|
||||
for item in selected_items {
|
||||
let names = item
|
||||
.details
|
||||
.as_ref()
|
||||
.map_or_else(Vec::new, |details| match item.item_type {
|
||||
ExternalAgentConfigMigrationItemType::Plugins => details
|
||||
.plugins
|
||||
.iter()
|
||||
.flat_map(|plugin_group| plugin_group.plugin_names.iter())
|
||||
.map(String::as_str)
|
||||
.collect(),
|
||||
ExternalAgentConfigMigrationItemType::Skills => details
|
||||
.skills
|
||||
.iter()
|
||||
.map(|skill| skill.name.as_str())
|
||||
.collect(),
|
||||
ExternalAgentConfigMigrationItemType::McpServerConfig => details
|
||||
.mcp_servers
|
||||
.iter()
|
||||
.map(|server| server.name.as_str())
|
||||
.collect(),
|
||||
ExternalAgentConfigMigrationItemType::Subagents => details
|
||||
.subagents
|
||||
.iter()
|
||||
.map(|agent| agent.name.as_str())
|
||||
.collect(),
|
||||
ExternalAgentConfigMigrationItemType::Hooks => details
|
||||
.hooks
|
||||
.iter()
|
||||
.map(|hook| hook.name.as_str())
|
||||
.collect(),
|
||||
ExternalAgentConfigMigrationItemType::Commands => details
|
||||
.commands
|
||||
.iter()
|
||||
.map(|command| command.name.as_str())
|
||||
.collect(),
|
||||
ExternalAgentConfigMigrationItemType::Sessions => details
|
||||
.sessions
|
||||
.iter()
|
||||
.filter_map(|session| session.title.as_deref())
|
||||
.collect(),
|
||||
ExternalAgentConfigMigrationItemType::AgentsMd
|
||||
| ExternalAgentConfigMigrationItemType::Config => Vec::new(),
|
||||
});
|
||||
let count = external_agent_config_migration_item_count(item);
|
||||
if let Some((_, type_count, type_names)) = import_summaries
|
||||
.iter_mut()
|
||||
.find(|(item_type, _, _)| *item_type == item.item_type)
|
||||
{
|
||||
*type_count += count;
|
||||
type_names.extend(names);
|
||||
} else {
|
||||
import_summaries.push((item.item_type, count, names));
|
||||
}
|
||||
}
|
||||
|
||||
let mut lines = vec![
|
||||
vec![
|
||||
"• ".dim(),
|
||||
"Claude Code import started.".cyan(),
|
||||
" You can keep working while it finishes.".into(),
|
||||
]
|
||||
.into(),
|
||||
vec![" ".into(), "Imported setup will apply to new chats.".dim()].into(),
|
||||
vec![" ".into(), "Importing:".cyan().bold()].into(),
|
||||
];
|
||||
lines.extend(
|
||||
import_summaries
|
||||
.into_iter()
|
||||
.map(|(item_type, count, names)| {
|
||||
let mut line = vec![
|
||||
" ".into(),
|
||||
external_agent_config_migration_type_label(item_type).cyan(),
|
||||
": ".into(),
|
||||
count.to_string().green(),
|
||||
];
|
||||
if !names.is_empty() {
|
||||
let shown_names = names.iter().take(3).copied().collect::<Vec<_>>();
|
||||
let mut name_summary = shown_names.join(", ");
|
||||
if names.len() > shown_names.len() {
|
||||
name_summary
|
||||
.push_str(&format!(", +{} more", names.len() - shown_names.len()));
|
||||
}
|
||||
line.extend([" — ".dim(), name_summary.into()]);
|
||||
}
|
||||
line.into()
|
||||
}),
|
||||
);
|
||||
if let Some(remaining_items_handoff) = remaining_items_handoff(remaining_item_count) {
|
||||
lines.push(vec![" ".into(), remaining_items_handoff.dim()].into());
|
||||
}
|
||||
lines
|
||||
}
|
||||
|
||||
pub(crate) fn external_agent_config_migration_finished_lines(
|
||||
notification: &ExternalAgentConfigImportCompletedNotification,
|
||||
) -> Vec<Line<'static>> {
|
||||
let imported_count = notification
|
||||
.item_type_results
|
||||
.iter()
|
||||
.map(|type_result| type_result.successes.len())
|
||||
.sum::<usize>();
|
||||
let failed_count = notification
|
||||
.item_type_results
|
||||
.iter()
|
||||
.map(|type_result| type_result.failures.len())
|
||||
.sum::<usize>();
|
||||
let failed_count = if failed_count == 0 {
|
||||
format!("{failed_count} failed").green()
|
||||
} else {
|
||||
format!("{failed_count} failed").red()
|
||||
};
|
||||
let mut lines = vec![
|
||||
vec![
|
||||
"• ".dim(),
|
||||
"Claude Code import finished: ".into(),
|
||||
format!("{imported_count} imported").green(),
|
||||
", ".into(),
|
||||
failed_count,
|
||||
".".into(),
|
||||
]
|
||||
.into(),
|
||||
];
|
||||
if !notification.item_type_results.is_empty() {
|
||||
lines.push(vec![" ".into(), "Results by type:".cyan().bold()].into());
|
||||
lines.extend(notification.item_type_results.iter().map(|type_result| {
|
||||
let failed_count = format!("{} failed", type_result.failures.len());
|
||||
let failed_count = if type_result.failures.is_empty() {
|
||||
failed_count.green()
|
||||
} else {
|
||||
failed_count.red()
|
||||
};
|
||||
vec![
|
||||
" ".into(),
|
||||
external_agent_config_migration_type_label(type_result.item_type).cyan(),
|
||||
": ".into(),
|
||||
format!("{} imported", type_result.successes.len()).green(),
|
||||
", ".into(),
|
||||
failed_count,
|
||||
]
|
||||
.into()
|
||||
}));
|
||||
}
|
||||
lines.push(
|
||||
vec![
|
||||
" ".into(),
|
||||
"Run /import again to check for additional items.".dim(),
|
||||
]
|
||||
.into(),
|
||||
);
|
||||
lines
|
||||
}
|
||||
|
||||
fn remaining_items_handoff(remaining_item_count: usize) -> Option<String> {
|
||||
@@ -96,10 +252,12 @@ pub(crate) async fn handle_external_agent_config_migration_prompt(
|
||||
Ok(()) => {
|
||||
let remaining_item_count =
|
||||
detected_items.len().saturating_sub(selected_items.len());
|
||||
let success_message =
|
||||
external_agent_config_migration_success_message(remaining_item_count);
|
||||
let started_lines = external_agent_config_migration_started_lines(
|
||||
&selected_items,
|
||||
remaining_item_count,
|
||||
);
|
||||
return Ok(ExternalAgentConfigMigrationFlowOutcome::Started(
|
||||
success_message,
|
||||
started_lines,
|
||||
));
|
||||
}
|
||||
Err(err) => {
|
||||
|
||||
@@ -1,21 +1,259 @@
|
||||
use super::*;
|
||||
use codex_app_server_protocol::ExternalAgentConfigImportItemTypeFailure;
|
||||
use codex_app_server_protocol::ExternalAgentConfigImportItemTypeSuccess;
|
||||
use codex_app_server_protocol::ExternalAgentConfigImportTypeResult;
|
||||
use codex_app_server_protocol::ExternalAgentConfigMigrationItemType;
|
||||
use codex_app_server_protocol::McpServerMigration;
|
||||
use codex_app_server_protocol::MigrationDetails;
|
||||
use codex_app_server_protocol::PluginsMigration;
|
||||
use codex_app_server_protocol::SessionMigration;
|
||||
use codex_app_server_protocol::SkillMigration;
|
||||
use pretty_assertions::assert_eq;
|
||||
use ratatui::text::Line;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn selected_items() -> Vec<ExternalAgentConfigMigrationItem> {
|
||||
vec![
|
||||
ExternalAgentConfigMigrationItem {
|
||||
item_type: ExternalAgentConfigMigrationItemType::Config,
|
||||
description: "Import settings".to_string(),
|
||||
cwd: None,
|
||||
details: None,
|
||||
},
|
||||
ExternalAgentConfigMigrationItem {
|
||||
item_type: ExternalAgentConfigMigrationItemType::Skills,
|
||||
description: "Import skills".to_string(),
|
||||
cwd: None,
|
||||
details: Some(MigrationDetails {
|
||||
skills: vec![
|
||||
SkillMigration {
|
||||
name: "triage".to_string(),
|
||||
},
|
||||
SkillMigration {
|
||||
name: "release-notes".to_string(),
|
||||
},
|
||||
SkillMigration {
|
||||
name: "risk-check".to_string(),
|
||||
},
|
||||
SkillMigration {
|
||||
name: "incident-review".to_string(),
|
||||
},
|
||||
],
|
||||
..Default::default()
|
||||
}),
|
||||
},
|
||||
ExternalAgentConfigMigrationItem {
|
||||
item_type: ExternalAgentConfigMigrationItemType::McpServerConfig,
|
||||
description: "Import MCP servers".to_string(),
|
||||
cwd: None,
|
||||
details: Some(MigrationDetails {
|
||||
mcp_servers: vec![
|
||||
McpServerMigration {
|
||||
name: "docs".to_string(),
|
||||
},
|
||||
McpServerMigration {
|
||||
name: "issues".to_string(),
|
||||
},
|
||||
],
|
||||
..Default::default()
|
||||
}),
|
||||
},
|
||||
ExternalAgentConfigMigrationItem {
|
||||
item_type: ExternalAgentConfigMigrationItemType::Sessions,
|
||||
description: "Import chat sessions".to_string(),
|
||||
cwd: None,
|
||||
details: Some(MigrationDetails {
|
||||
sessions: vec![
|
||||
SessionMigration {
|
||||
path: PathBuf::from("/sessions/alpha.jsonl"),
|
||||
cwd: PathBuf::from("/workspace/project"),
|
||||
title: Some("Alpha rollout".to_string()),
|
||||
},
|
||||
SessionMigration {
|
||||
path: PathBuf::from("/sessions/beta.jsonl"),
|
||||
cwd: PathBuf::from("/workspace/project"),
|
||||
title: Some("Beta review".to_string()),
|
||||
},
|
||||
SessionMigration {
|
||||
path: PathBuf::from("/sessions/gamma.jsonl"),
|
||||
cwd: PathBuf::from("/workspace/project"),
|
||||
title: Some("Gamma notes".to_string()),
|
||||
},
|
||||
],
|
||||
..Default::default()
|
||||
}),
|
||||
},
|
||||
ExternalAgentConfigMigrationItem {
|
||||
item_type: ExternalAgentConfigMigrationItemType::Plugins,
|
||||
description: "Import plugins".to_string(),
|
||||
cwd: None,
|
||||
details: Some(MigrationDetails {
|
||||
plugins: vec![PluginsMigration {
|
||||
marketplace_name: "example".to_string(),
|
||||
plugin_names: vec!["formatter".to_string(), "reviewer".to_string()],
|
||||
}],
|
||||
..Default::default()
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn completed_notification() -> ExternalAgentConfigImportCompletedNotification {
|
||||
ExternalAgentConfigImportCompletedNotification {
|
||||
import_id: "import-1".to_string(),
|
||||
item_type_results: vec![
|
||||
ExternalAgentConfigImportTypeResult {
|
||||
item_type: ExternalAgentConfigMigrationItemType::Config,
|
||||
successes: vec![ExternalAgentConfigImportItemTypeSuccess {
|
||||
item_type: ExternalAgentConfigMigrationItemType::Config,
|
||||
cwd: None,
|
||||
source: Some("settings.json".to_string()),
|
||||
target: Some("config.toml".to_string()),
|
||||
}],
|
||||
failures: Vec::new(),
|
||||
},
|
||||
ExternalAgentConfigImportTypeResult {
|
||||
item_type: ExternalAgentConfigMigrationItemType::Plugins,
|
||||
successes: vec![ExternalAgentConfigImportItemTypeSuccess {
|
||||
item_type: ExternalAgentConfigMigrationItemType::Plugins,
|
||||
cwd: None,
|
||||
source: Some("formatter@example".to_string()),
|
||||
target: Some("formatter@example".to_string()),
|
||||
}],
|
||||
failures: vec![ExternalAgentConfigImportItemTypeFailure {
|
||||
item_type: ExternalAgentConfigMigrationItemType::Plugins,
|
||||
error_type: Some("plugin_install_failed".to_string()),
|
||||
failure_stage: "plugin_import".to_string(),
|
||||
message: "install failed".to_string(),
|
||||
cwd: Some(PathBuf::from("/workspace/project")),
|
||||
source: Some("deployer@example".to_string()),
|
||||
}],
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_agent_config_migration_messages_snapshot() {
|
||||
let cases = [0, 1, 2];
|
||||
|
||||
let messages = cases
|
||||
.map(external_agent_config_migration_success_message)
|
||||
let selected_items = selected_items();
|
||||
let completed_notification = completed_notification();
|
||||
let messages = [0, 1, 2]
|
||||
.into_iter()
|
||||
.flat_map(|remaining_item_count| {
|
||||
external_agent_config_migration_started_lines(&selected_items, remaining_item_count)
|
||||
})
|
||||
.chain(external_agent_config_migration_finished_lines(
|
||||
&completed_notification,
|
||||
))
|
||||
.chain([
|
||||
EXTERNAL_AGENT_CONFIG_MIGRATION_FINISHED_MESSAGE.to_string(),
|
||||
EXTERNAL_AGENT_CONFIG_MIGRATION_NO_ITEMS_MESSAGE.to_string(),
|
||||
EXTERNAL_AGENT_CONFIG_MIGRATION_REMOTE_UNAVAILABLE_MESSAGE.to_string(),
|
||||
EXTERNAL_AGENT_CONFIG_MIGRATION_DAEMON_UNAVAILABLE_MESSAGE.to_string(),
|
||||
EXTERNAL_AGENT_CONFIG_IMPORT_IN_PROGRESS_MESSAGE.to_string(),
|
||||
Line::from(EXTERNAL_AGENT_CONFIG_MIGRATION_NO_ITEMS_MESSAGE),
|
||||
Line::from(EXTERNAL_AGENT_CONFIG_MIGRATION_REMOTE_UNAVAILABLE_MESSAGE),
|
||||
Line::from(EXTERNAL_AGENT_CONFIG_MIGRATION_DAEMON_UNAVAILABLE_MESSAGE),
|
||||
Line::from(EXTERNAL_AGENT_CONFIG_IMPORT_IN_PROGRESS_MESSAGE),
|
||||
])
|
||||
.map(|line| {
|
||||
line.spans
|
||||
.into_iter()
|
||||
.map(|span| span.content.into_owned())
|
||||
.collect::<String>()
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
|
||||
insta::assert_snapshot!("external_agent_config_migration_messages", messages);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_agent_config_migration_status_lines_use_semantic_colors() {
|
||||
assert_eq!(
|
||||
external_agent_config_migration_started_lines(
|
||||
&selected_items(),
|
||||
/*remaining_item_count*/ 0,
|
||||
),
|
||||
vec![
|
||||
Line::from(vec![
|
||||
"• ".dim(),
|
||||
"Claude Code import started.".cyan(),
|
||||
" You can keep working while it finishes.".into(),
|
||||
]),
|
||||
Line::from(vec![
|
||||
" ".into(),
|
||||
"Imported setup will apply to new chats.".dim(),
|
||||
]),
|
||||
Line::from(vec![" ".into(), "Importing:".cyan().bold()]),
|
||||
Line::from(vec![
|
||||
" ".into(),
|
||||
"Settings".cyan(),
|
||||
": ".into(),
|
||||
"1".green(),
|
||||
]),
|
||||
Line::from(vec![
|
||||
" ".into(),
|
||||
"Skills".cyan(),
|
||||
": ".into(),
|
||||
"4".green(),
|
||||
" — ".dim(),
|
||||
"triage, release-notes, risk-check, +1 more".into(),
|
||||
]),
|
||||
Line::from(vec![
|
||||
" ".into(),
|
||||
"MCP servers".cyan(),
|
||||
": ".into(),
|
||||
"2".green(),
|
||||
" — ".dim(),
|
||||
"docs, issues".into(),
|
||||
]),
|
||||
Line::from(vec![
|
||||
" ".into(),
|
||||
"Chat sessions".cyan(),
|
||||
": ".into(),
|
||||
"3".green(),
|
||||
" — ".dim(),
|
||||
"Alpha rollout, Beta review, Gamma notes".into(),
|
||||
]),
|
||||
Line::from(vec![
|
||||
" ".into(),
|
||||
"Plugins".cyan(),
|
||||
": ".into(),
|
||||
"2".green(),
|
||||
" — ".dim(),
|
||||
"formatter, reviewer".into(),
|
||||
]),
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
external_agent_config_migration_finished_lines(&completed_notification()),
|
||||
vec![
|
||||
Line::from(vec![
|
||||
"• ".dim(),
|
||||
"Claude Code import finished: ".into(),
|
||||
"2 imported".green(),
|
||||
", ".into(),
|
||||
"1 failed".red(),
|
||||
".".into(),
|
||||
]),
|
||||
Line::from(vec![" ".into(), "Results by type:".cyan().bold()]),
|
||||
Line::from(vec![
|
||||
" ".into(),
|
||||
"Settings".cyan(),
|
||||
": ".into(),
|
||||
"1 imported".green(),
|
||||
", ".into(),
|
||||
"0 failed".green(),
|
||||
]),
|
||||
Line::from(vec![
|
||||
" ".into(),
|
||||
"Plugins".cyan(),
|
||||
": ".into(),
|
||||
"1 imported".green(),
|
||||
", ".into(),
|
||||
"1 failed".red(),
|
||||
]),
|
||||
Line::from(vec![
|
||||
" ".into(),
|
||||
"Run /import again to check for additional items.".dim(),
|
||||
]),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -91,12 +91,107 @@ pub(crate) fn external_agent_config_migration_item_label(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn external_agent_config_migration_type_label(
|
||||
item_type: ExternalAgentConfigMigrationItemType,
|
||||
) -> &'static str {
|
||||
match item_type {
|
||||
ExternalAgentConfigMigrationItemType::AgentsMd => "Instructions",
|
||||
ExternalAgentConfigMigrationItemType::Config => "Settings",
|
||||
ExternalAgentConfigMigrationItemType::Skills => "Skills",
|
||||
ExternalAgentConfigMigrationItemType::Plugins => "Plugins",
|
||||
ExternalAgentConfigMigrationItemType::McpServerConfig => "MCP servers",
|
||||
ExternalAgentConfigMigrationItemType::Subagents => "Agents",
|
||||
ExternalAgentConfigMigrationItemType::Hooks => "Hooks",
|
||||
ExternalAgentConfigMigrationItemType::Commands => "Slash commands",
|
||||
ExternalAgentConfigMigrationItemType::Sessions => "Chat sessions",
|
||||
}
|
||||
}
|
||||
|
||||
/// Summarizes the concrete objects represented by selected migration items.
|
||||
///
|
||||
/// Most detected item types carry the objects they will import in `details`; types without
|
||||
/// details represent one importable file or source directory per migration item.
|
||||
pub(crate) fn external_agent_config_migration_count_summary<'a>(
|
||||
items: impl IntoIterator<Item = &'a ExternalAgentConfigMigrationItem>,
|
||||
) -> String {
|
||||
let mut counts = Vec::<(ExternalAgentConfigMigrationItemType, usize)>::new();
|
||||
for item in items {
|
||||
let count = external_agent_config_migration_item_count(item);
|
||||
if let Some((_, type_count)) = counts
|
||||
.iter_mut()
|
||||
.find(|(item_type, _)| *item_type == item.item_type)
|
||||
{
|
||||
*type_count += count;
|
||||
} else {
|
||||
counts.push((item.item_type, count));
|
||||
}
|
||||
}
|
||||
|
||||
counts
|
||||
.into_iter()
|
||||
.map(|(item_type, count)| {
|
||||
format!(
|
||||
"{} {count}",
|
||||
external_agent_config_migration_type_label(item_type)
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
}
|
||||
|
||||
pub(crate) fn external_agent_config_migration_item_count(
|
||||
item: &ExternalAgentConfigMigrationItem,
|
||||
) -> usize {
|
||||
match item.item_type {
|
||||
ExternalAgentConfigMigrationItemType::Plugins => {
|
||||
item.details.as_ref().map_or(1, |details| {
|
||||
details
|
||||
.plugins
|
||||
.iter()
|
||||
.map(|plugin_group| plugin_group.plugin_names.len())
|
||||
.sum()
|
||||
})
|
||||
}
|
||||
ExternalAgentConfigMigrationItemType::McpServerConfig => item
|
||||
.details
|
||||
.as_ref()
|
||||
.map_or(1, |details| details.mcp_servers.len()),
|
||||
ExternalAgentConfigMigrationItemType::Subagents => item
|
||||
.details
|
||||
.as_ref()
|
||||
.map_or(1, |details| details.subagents.len()),
|
||||
ExternalAgentConfigMigrationItemType::Hooks => item
|
||||
.details
|
||||
.as_ref()
|
||||
.map_or(1, |details| details.hooks.len()),
|
||||
ExternalAgentConfigMigrationItemType::Commands => item
|
||||
.details
|
||||
.as_ref()
|
||||
.map_or(1, |details| details.commands.len()),
|
||||
ExternalAgentConfigMigrationItemType::Sessions => item
|
||||
.details
|
||||
.as_ref()
|
||||
.map_or(1, |details| details.sessions.len()),
|
||||
ExternalAgentConfigMigrationItemType::Skills => item
|
||||
.details
|
||||
.as_ref()
|
||||
.map_or(1, |details| details.skills.len()),
|
||||
ExternalAgentConfigMigrationItemType::AgentsMd
|
||||
| ExternalAgentConfigMigrationItemType::Config => 1,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn external_agent_config_migration_item_detail(
|
||||
item: &ExternalAgentConfigMigrationItem,
|
||||
) -> Option<String> {
|
||||
let details = item.details.as_ref()?;
|
||||
match item.item_type {
|
||||
ExternalAgentConfigMigrationItemType::Plugins => None,
|
||||
ExternalAgentConfigMigrationItemType::Skills => Some(format_counted_details(
|
||||
"skill",
|
||||
details.skills.len(),
|
||||
details.skills.iter().map(|skill| skill.name.as_str()),
|
||||
)),
|
||||
ExternalAgentConfigMigrationItemType::McpServerConfig => Some(format_counted_details(
|
||||
"MCP server",
|
||||
details.mcp_servers.len(),
|
||||
@@ -129,8 +224,7 @@ pub(crate) fn external_agent_config_migration_item_detail(
|
||||
.filter_map(|session| session.title.as_deref()),
|
||||
)),
|
||||
ExternalAgentConfigMigrationItemType::AgentsMd
|
||||
| ExternalAgentConfigMigrationItemType::Config
|
||||
| ExternalAgentConfigMigrationItemType::Skills => None,
|
||||
| ExternalAgentConfigMigrationItemType::Config => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
@@ -10,10 +10,13 @@ expression: rendered
|
||||
Standard Claude Chat data cannot be imported.
|
||||
[x] Tools & setup
|
||||
Settings, instructions, integrations, agents, commands, and skills
|
||||
Importing: Settings 1
|
||||
[x] Current project
|
||||
Add Codex files alongside your existing project files
|
||||
Importing: Plugins 6, Instructions 1
|
||||
[x] Chat sessions (1)
|
||||
Last 30 days of chats
|
||||
Importing: Chat sessions 1
|
||||
|
||||
Selected 4 of 4 items.
|
||||
› 1. Import selected
|
||||
|
||||
+3
@@ -10,10 +10,13 @@ expression: rendered
|
||||
Standard Claude Chat data cannot be imported.
|
||||
[x] Tools & setup
|
||||
Settings, instructions, integrations, agents, commands, and skills
|
||||
Importing: Settings 1
|
||||
[x] Current project
|
||||
Add Codex files alongside your existing project files
|
||||
Importing: Plugins 6, Instructions 1
|
||||
[x] Chat sessions (1)
|
||||
Last 30 days of chats
|
||||
Importing: Chat sessions 1
|
||||
|
||||
Selected 4 of 4 items.
|
||||
› 1. Import selected
|
||||
|
||||
+31
-4
@@ -2,10 +2,37 @@
|
||||
source: tui/src/external_agent_config_migration_flow_tests.rs
|
||||
expression: messages
|
||||
---
|
||||
Claude Code import started. You can keep working while it finishes. Imported setup will apply to new chats.
|
||||
Claude Code import started. You can keep working while it finishes. Imported setup will apply to new chats. 1 additional item remains. After it finishes, run /import again to review it.
|
||||
Claude Code import started. You can keep working while it finishes. Imported setup will apply to new chats. 2 additional items remain. After it finishes, run /import again to review them.
|
||||
Claude Code import finished. Run /import again to check for additional items.
|
||||
• Claude Code import started. You can keep working while it finishes.
|
||||
Imported setup will apply to new chats.
|
||||
Importing:
|
||||
Settings: 1
|
||||
Skills: 4 — triage, release-notes, risk-check, +1 more
|
||||
MCP servers: 2 — docs, issues
|
||||
Chat sessions: 3 — Alpha rollout, Beta review, Gamma notes
|
||||
Plugins: 2 — formatter, reviewer
|
||||
• Claude Code import started. You can keep working while it finishes.
|
||||
Imported setup will apply to new chats.
|
||||
Importing:
|
||||
Settings: 1
|
||||
Skills: 4 — triage, release-notes, risk-check, +1 more
|
||||
MCP servers: 2 — docs, issues
|
||||
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.
|
||||
• Claude Code import started. You can keep working while it finishes.
|
||||
Imported setup will apply to new chats.
|
||||
Importing:
|
||||
Settings: 1
|
||||
Skills: 4 — triage, release-notes, risk-check, +1 more
|
||||
MCP servers: 2 — docs, issues
|
||||
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.
|
||||
• 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 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.
|
||||
|
||||
Reference in New Issue
Block a user