Files
moltbot/src/cli/program/register.migrate.ts
pashpashpash 027ea5f08b Isolate Codex app-server state per agent (#74556)
* fix(codex): isolate app-server home per agent

* fix(codex): isolate native Codex assets per agent

* fix(channels): mark inbound system events untrusted

* fix(doctor): warn on personal Codex agent skills

* test(doctor): cover personal Codex agent skills warning

* fix(codex): forward auth profiles to harness runs

* fix(codex): preserve auto auth for harness runs

* fix(codex): auto-select harness auth profiles

* test(codex): type harness auth mock

* feat(codex): select migrated skills

* fix(codex): satisfy migration selection lint

* docs: add codex isolation changelog
2026-05-01 04:49:02 +09:00

151 lines
5.8 KiB
TypeScript

import type { Command } from "commander";
import {
migrateApplyCommand,
migrateDefaultCommand,
migrateListCommand,
migratePlanCommand,
} from "../../commands/migrate.js";
import { defaultRuntime } from "../../runtime.js";
import { theme } from "../../terminal/theme.js";
import { runCommandWithRuntime } from "../cli-utils.js";
import { formatHelpExamples } from "../help-format.js";
function collectMigrationSkill(value: string, previous: string[] | undefined): string[] {
return [...(previous ?? []), value];
}
function readMigrationSkills(value: unknown): string[] | undefined {
if (!Array.isArray(value)) {
return undefined;
}
const skills = value
.filter((item): item is string => typeof item === "string")
.map((item) => item.trim())
.filter((item) => item.length > 0);
return skills.length > 0 ? skills : undefined;
}
function addMigrationSkillOption(command: Command): Command {
return command.option(
"--skill <name>",
"Select one skill to migrate by name or item id; repeat for multiple skills",
collectMigrationSkill,
);
}
function addMigrationOptions(command: Command): Command {
return addMigrationSkillOption(
command
.option("--from <path>", "Source directory to migrate from")
.option("--include-secrets", "Import supported credentials and secrets", false)
.option("--overwrite", "Overwrite conflicting target files after item-level backups", false)
.option("--json", "Output JSON", false),
);
}
export function registerMigrateCommand(program: Command) {
const migrate = program
.command("migrate")
.description("Import state from another agent system")
.argument("[provider]", "Migration provider id, for example hermes")
.option("--from <path>", "Source directory to migrate from")
.option("--include-secrets", "Import supported credentials and secrets", false)
.option("--overwrite", "Overwrite conflicting target files after item-level backups", false)
.option("--dry-run", "Preview only; do not apply changes", false)
.option("--yes", "Apply without prompting after preview", false)
.option(
"--skill <name>",
"Select one skill to migrate by name or item id; repeat for multiple skills",
collectMigrationSkill,
)
.option("--backup-output <path>", "Pre-migration backup archive path or directory")
.option("--no-backup", "Skip the pre-migration OpenClaw backup")
.option("--force", "Allow dangerous options such as --no-backup", false)
.option("--json", "Output JSON", false)
.addHelpText(
"after",
() =>
`\n${theme.heading("Examples:")}\n${formatHelpExamples([
["openclaw migrate list", "Show available migration providers."],
["openclaw migrate hermes", "Preview Hermes migration, then prompt before applying."],
["openclaw migrate hermes --dry-run", "Preview Hermes migration only."],
[
"openclaw migrate apply hermes --yes",
"Apply Hermes migration non-interactively after writing a verified backup.",
],
[
"openclaw migrate apply hermes --include-secrets --yes",
"Include supported credentials in the migration.",
],
])}`,
)
.action(async (provider, opts) => {
await runCommandWithRuntime(defaultRuntime, async () => {
await migrateDefaultCommand(defaultRuntime, {
provider: provider as string | undefined,
source: opts.from as string | undefined,
includeSecrets: Boolean(opts.includeSecrets),
overwrite: Boolean(opts.overwrite),
skills: readMigrationSkills(opts.skill),
dryRun: Boolean(opts.dryRun),
yes: Boolean(opts.yes),
backupOutput: opts.backupOutput as string | undefined,
noBackup: opts.backup === false,
force: Boolean(opts.force),
json: Boolean(opts.json),
});
});
});
migrate
.command("list")
.description("List migration providers")
.option("--json", "Output JSON", false)
.action(async (opts) => {
await runCommandWithRuntime(defaultRuntime, async () => {
await migrateListCommand(defaultRuntime, { json: Boolean(opts.json) });
});
});
addMigrationOptions(
migrate
.command("plan <provider>")
.description("Preview a migration without changing OpenClaw state"),
).action(async (provider, opts) => {
await runCommandWithRuntime(defaultRuntime, async () => {
await migratePlanCommand(defaultRuntime, {
provider: provider as string,
source: opts.from as string | undefined,
includeSecrets: Boolean(opts.includeSecrets),
overwrite: Boolean(opts.overwrite),
skills: readMigrationSkills(opts.skill),
json: Boolean(opts.json),
});
});
});
addMigrationOptions(
migrate.command("apply <provider>").description("Apply a migration after a verified backup"),
)
.option("--yes", "Apply without prompting", false)
.option("--backup-output <path>", "Pre-migration backup archive path or directory")
.option("--no-backup", "Skip the pre-migration OpenClaw backup")
.option("--force", "Allow dangerous options such as --no-backup", false)
.action(async (provider, opts) => {
await runCommandWithRuntime(defaultRuntime, async () => {
await migrateApplyCommand(defaultRuntime, {
provider: provider as string,
source: opts.from as string | undefined,
includeSecrets: Boolean(opts.includeSecrets),
overwrite: Boolean(opts.overwrite),
skills: readMigrationSkills(opts.skill),
yes: Boolean(opts.yes),
backupOutput: opts.backupOutput as string | undefined,
noBackup: opts.backup === false,
force: Boolean(opts.force),
json: Boolean(opts.json),
});
});
});
}