theclawbay 0.5.1 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,6 +4,7 @@ const core_1 = require("@oclif/core");
4
4
  const promises_1 = require("node:readline/promises");
5
5
  const base_command_1 = require("../lib/base-command");
6
6
  const errors_1 = require("../lib/errors");
7
+ const app_shell_1 = require("../lib/shared/app-shell");
7
8
  const tui_1 = require("../lib/shared/tui");
8
9
  const codex_profiles_1 = require("../lib/clients/codex-profiles");
9
10
  const fsx_1 = require("../lib/shared/fsx");
@@ -18,6 +19,7 @@ function kindGlyph(profile) {
18
19
  class CodexCommand extends base_command_1.BaseCommand {
19
20
  async run() {
20
21
  await this.runSafe(async () => {
22
+ app_shell_1.AppShell.printHeaderOnce("Switch your Codex account");
21
23
  const { args, flags } = await this.parse(CodexCommand);
22
24
  const nowIso = new Date().toISOString();
23
25
  const rawAction = (args.action ?? "status").toLowerCase();
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const format_1 = require("../lib/shared/format");
4
4
  const base_command_1 = require("../lib/base-command");
5
+ const app_shell_1 = require("../lib/shared/app-shell");
5
6
  const tui_1 = require("../lib/shared/tui");
6
7
  const core_1 = require("@oclif/core");
7
8
  const config_1 = require("../lib/managed/config");
@@ -74,6 +75,8 @@ class LogoutCommand extends base_command_1.BaseCommand {
74
75
  await this.runSafe(async () => {
75
76
  const { flags } = await this.parse(LogoutCommand);
76
77
  const debugOutput = flags["debug-output"];
78
+ if (!debugOutput)
79
+ app_shell_1.AppShell.printHeaderOnce("Revert The Claw Bay on this machine");
77
80
  let managed = null;
78
81
  try {
79
82
  managed = await (0, config_1.readManagedConfig)();
@@ -38,6 +38,7 @@ const paths_1 = require("../lib/config/paths");
38
38
  const providers_1 = require("../lib/shared/providers");
39
39
  const openclaw_1 = require("../lib/clients/openclaw");
40
40
  const node_path_1 = __importDefault(require("node:path"));
41
+ const app_shell_1 = require("../lib/shared/app-shell");
41
42
  const tui_1 = require("../lib/shared/tui");
42
43
  function logSetupCompactSummary(params) {
43
44
  const configured = params.setupClients
@@ -76,18 +77,78 @@ function logSetupCompactSummary(params) {
76
77
  for (const note of params.notes) {
77
78
  lines.push((0, tui_1.statusLine)("info", note));
78
79
  }
79
- for (const line of (0, tui_1.renderBox)({ title: "Setup complete", lines })) {
80
+ params.log("");
81
+ for (const line of (0, tui_1.renderBox)({ title: `${tui_1.glyph.tick} Setup complete`, accent: "coral", lines })) {
80
82
  params.log(line);
81
83
  }
82
84
  }
85
+ // Convert the chosen slice of Codex history while painting a live bar with an
86
+ // ETA, then leave a single permanent summary line. This replaces the old silent
87
+ // full-tree pass that could appear to hang for hours.
88
+ async function convertConversationsWithProgress(plan, log) {
89
+ const total = plan.files.length;
90
+ const isTTY = process.stdout.isTTY;
91
+ const painter = isTTY ? (0, tui_1.createFramePainter)(process.stdout) : null;
92
+ let latest = null;
93
+ let frameIndex = 0;
94
+ const draw = () => {
95
+ if (!painter || !latest)
96
+ return;
97
+ const p = latest;
98
+ const spinner = tui_1.tint.accent(tui_1.SPINNER_FRAMES[frameIndex % tui_1.SPINNER_FRAMES.length] ?? "");
99
+ const rate = p.elapsedMs > 0 ? (p.processed / (p.elapsedMs / 1000)) : 0;
100
+ const bar = (0, tui_1.gauge)({ value: p.processed, max: p.total, width: 26, color: tui_1.tint.sea });
101
+ painter.paint([
102
+ "",
103
+ ...(0, tui_1.renderBox)({
104
+ title: "Converting conversations",
105
+ accent: "sea",
106
+ lines: [
107
+ `${spinner} ${tui_1.tint.foam("Keeping your Codex history visible under The Claw Bay")}`,
108
+ "",
109
+ `${bar} ${tui_1.tint.dim(`${(0, tui_1.formatCount)(p.processed)} / ${(0, tui_1.formatCount)(p.total)}`)}`,
110
+ tui_1.tint.dim(`${(0, tui_1.formatCount)(p.rewritten)} updated · ${(0, tui_1.formatEta)(p.etaSeconds)} left · ${rate >= 1 ? `${Math.round(rate)}/s` : "…"}`),
111
+ ],
112
+ }),
113
+ ]);
114
+ };
115
+ const timer = isTTY ? setInterval(() => { frameIndex += 1; draw(); }, 90) : null;
116
+ if (timer && typeof timer.unref === "function")
117
+ timer.unref();
118
+ let result;
119
+ try {
120
+ result = await (0, codex_history_migration_1.migrateSessionProviders)({
121
+ codexHome: paths_1.codexDir,
122
+ migrationStateFile: codex_1.MIGRATION_STATE_FILE,
123
+ neutralizeSources: codex_1.HISTORY_PROVIDER_NEUTRALIZE_SOURCES,
124
+ files: plan.files,
125
+ onProgress: (progress) => {
126
+ latest = progress;
127
+ draw();
128
+ },
129
+ });
130
+ }
131
+ finally {
132
+ if (timer)
133
+ clearInterval(timer);
134
+ if (painter)
135
+ painter.clear();
136
+ }
137
+ const converted = result.rewritten;
138
+ const summary = converted > 0
139
+ ? `Converted ${(0, tui_1.formatCount)(converted)} of ${(0, tui_1.formatCount)(total)} conversation${total === 1 ? "" : "s"}`
140
+ : `Checked ${(0, tui_1.formatCount)(total)} conversation${total === 1 ? "" : "s"} — nothing needed converting`;
141
+ if (isTTY)
142
+ log(` ${tui_1.tint.green(tui_1.glyph.tick)} ${tui_1.tint.foam(summary)}`);
143
+ return result;
144
+ }
83
145
  class SetupCommand extends base_command_1.BaseCommand {
84
146
  async run() {
85
147
  await this.runSafe(async () => {
86
148
  const { flags } = await this.parse(SetupCommand);
87
149
  const debugOutput = flags["debug-output"];
88
- if (!debugOutput && process.stdout.isTTY) {
89
- this.log((0, tui_1.banner)("Connect your coding tools to The Claw Bay relay"));
90
- this.log("");
150
+ if (!debugOutput) {
151
+ app_shell_1.AppShell.printHeaderOnce("Connect your coding tools to The Claw Bay relay");
91
152
  }
92
153
  let managed = null;
93
154
  try {
@@ -296,14 +357,15 @@ class SetupCommand extends base_command_1.BaseCommand {
296
357
  selectedClientIds: selectedSetupClients,
297
358
  log: (message) => this.log(message),
298
359
  });
299
- const migrateCodexConversations = await (0, codex_1.resolveCodexConversationMigrationSelection)({
360
+ if (flags["migrate-conversations"] !== undefined && !selectedSetupClients.has("codex")) {
361
+ throw new Error("--migrate-conversations requires Codex to be selected for setup.");
362
+ }
363
+ const migrationPlan = await (0, codex_1.resolveCodexConversationMigrationPlan)({
300
364
  codexSelected: selectedSetupClients.has("codex"),
301
365
  flagValue: flags["migrate-conversations"],
302
366
  skipPrompt: flags.yes,
303
367
  });
304
- if (flags["migrate-conversations"] !== undefined && !selectedSetupClients.has("codex")) {
305
- throw new Error("--migrate-conversations requires Codex to be selected for setup.");
306
- }
368
+ const migrateCodexConversations = migrationPlan.enabled;
307
369
  const linkFreshDeviceSession = async () => {
308
370
  deviceLabel = await (0, backend_1.resolveDeviceLabel)({
309
371
  flagValue: flags["device-name"],
@@ -490,11 +552,9 @@ class SetupCommand extends base_command_1.BaseCommand {
490
552
  apiKey: authCredential,
491
553
  });
492
554
  if (migrateCodexConversations) {
493
- sessionMigration = await (0, codex_history_migration_1.migrateSessionProviders)({
494
- codexHome: paths_1.codexDir,
495
- migrationStateFile: codex_1.MIGRATION_STATE_FILE,
496
- neutralizeSources: codex_1.HISTORY_PROVIDER_NEUTRALIZE_SOURCES,
497
- });
555
+ // Hand off from the checklist spinner to the live conversion bar.
556
+ progress.stop();
557
+ sessionMigration = await convertConversationsWithProgress(migrationPlan, (m) => this.log(m));
498
558
  }
499
559
  authSeedCleanup = await (0, codex_auth_seeding_1.cleanupSeededCodexAuth)({
500
560
  codexHome: paths_1.codexDir,
@@ -618,7 +678,8 @@ class SetupCommand extends base_command_1.BaseCommand {
618
678
  throw error;
619
679
  }
620
680
  if (!debugOutput && process.stdout.isTTY) {
621
- progress.succeed("Setup complete");
681
+ // Finalize the last live step; the summary box below is the headline.
682
+ progress.stop();
622
683
  }
623
684
  else {
624
685
  this.log("Setup complete");
@@ -5,6 +5,8 @@ const base_command_1 = require("../lib/base-command");
5
5
  const api_key_1 = require("../lib/managed/api-key");
6
6
  const config_1 = require("../lib/managed/config");
7
7
  const errors_1 = require("../lib/managed/errors");
8
+ const app_shell_1 = require("../lib/shared/app-shell");
9
+ const tui_1 = require("../lib/shared/tui");
8
10
  const DEFAULT_BACKEND_URL = "https://theclawbay.com";
9
11
  function formatPercent(value) {
10
12
  if (typeof value !== "number" || !Number.isFinite(value))
@@ -62,6 +64,26 @@ function printWindow(log, label, window, showCosts) {
62
64
  log(" Status: limit reached");
63
65
  }
64
66
  }
67
+ // A framed usage window with a coral/teal meter, sized to the terminal.
68
+ function renderWindowBox(label, window, showCosts) {
69
+ const usedPercent = window.progressPercentUsed ?? window.percentUsed ?? 0;
70
+ const meterColor = window.limitReached ? tui_1.tint.red : usedPercent >= 80 ? tui_1.tint.yellow : tui_1.tint.sea;
71
+ const lines = [
72
+ `${(0, tui_1.gauge)({ value: usedPercent, max: 100, width: 30, color: meterColor })} ${tui_1.tint.dim("used")}`,
73
+ "",
74
+ `${tui_1.tint.dim("Remaining")} ${tui_1.tint.foam(formatPercent(window.percentRemaining))}`,
75
+ `${tui_1.tint.dim("Requests")} ${tui_1.tint.foam(String(window.requestCount))} total ${tui_1.tint.green(`${window.successCount} ok`)} ${window.failedCount > 0 ? tui_1.tint.red(`${window.failedCount} failed`) : tui_1.tint.dim("0 failed")}`,
76
+ `${tui_1.tint.dim("Resets in")} ${tui_1.tint.foam(formatDuration(window.secondsUntilReset))} ${tui_1.tint.dim(`(${formatTimestamp(window.windowEnd)})`)}`,
77
+ ];
78
+ if (showCosts) {
79
+ lines.push(`${tui_1.tint.dim("Cost")} ${tui_1.tint.foam(formatUsd(window.estimatedCostUsdUsed))} ${tui_1.tint.dim("of")} ${tui_1.tint.foam(formatUsd(window.costUsdLimit))} ${tui_1.tint.dim(`(${formatUsd(window.costUsdRemaining)} left)`)}`);
80
+ }
81
+ if (window.limitReached) {
82
+ lines.push("");
83
+ lines.push((0, tui_1.statusLine)("warn", tui_1.tint.yellow("Limit reached for this window")));
84
+ }
85
+ return (0, tui_1.renderBox)({ title: label, accent: window.limitReached ? "danger" : "sea", lines });
86
+ }
65
87
  class UsageCommand extends base_command_1.BaseCommand {
66
88
  async run() {
67
89
  await this.runSafe(async () => {
@@ -99,19 +121,42 @@ class UsageCommand extends base_command_1.BaseCommand {
99
121
  return;
100
122
  }
101
123
  const showCosts = body.usageLimitPresentation !== "percent_only";
102
- this.log("The Claw Bay usage");
103
- this.log(`Observed: ${formatTimestamp(body.observedAt)}`);
104
- this.log(`Auth scope: ${body.pooled ? "shared pooled usage" : body.mode}`);
105
- if (body.mode === "direct" && typeof body.planMultiplier === "number" && Number.isFinite(body.planMultiplier)) {
106
- this.log(`Plan multiplier: ${body.planMultiplier}x`);
124
+ if (!process.stdout.isTTY) {
125
+ this.log("The Claw Bay usage");
126
+ this.log(`Observed: ${formatTimestamp(body.observedAt)}`);
127
+ this.log(`Auth scope: ${body.pooled ? "shared pooled usage" : body.mode}`);
128
+ if (body.mode === "direct" && typeof body.planMultiplier === "number" && Number.isFinite(body.planMultiplier)) {
129
+ this.log(`Plan multiplier: ${body.planMultiplier}x`);
130
+ }
131
+ if (body.anyLimitReached) {
132
+ this.log("Status: at least one usage window has been reached");
133
+ }
134
+ this.log("");
135
+ printWindow((message) => this.log(message), "5-hour window", body.usage.fiveHour, showCosts);
136
+ this.log("");
137
+ printWindow((message) => this.log(message), "Weekly window", body.usage.weekly, showCosts);
138
+ return;
107
139
  }
108
- if (body.anyLimitReached) {
109
- this.log("Status: at least one usage window has been reached");
140
+ app_shell_1.AppShell.printHeaderOnce("Your pooled Claw Bay usage");
141
+ const scope = body.pooled ? "shared pooled usage" : body.mode;
142
+ const overviewLines = [
143
+ `${tui_1.tint.dim("Scope")} ${tui_1.tint.foam(scope)}`,
144
+ `${tui_1.tint.dim("Observed")} ${tui_1.tint.foam(formatTimestamp(body.observedAt))}`,
145
+ ];
146
+ if (body.mode === "direct" && typeof body.planMultiplier === "number" && Number.isFinite(body.planMultiplier)) {
147
+ overviewLines.push(`${tui_1.tint.dim("Plan")} ${tui_1.tint.foam(`${body.planMultiplier}x multiplier`)}`);
110
148
  }
149
+ overviewLines.push(body.anyLimitReached
150
+ ? (0, tui_1.statusLine)("warn", tui_1.tint.yellow("At least one usage window has been reached"))
151
+ : (0, tui_1.statusLine)("ok", tui_1.tint.foam("Within limits on all windows")));
152
+ for (const line of (0, tui_1.renderBox)({ title: `${tui_1.glyph.claw} Usage`, accent: "coral", lines: overviewLines }))
153
+ this.log(line);
111
154
  this.log("");
112
- printWindow((message) => this.log(message), "5-hour window", body.usage.fiveHour, showCosts);
155
+ for (const line of renderWindowBox("5-hour window", body.usage.fiveHour, showCosts))
156
+ this.log(line);
113
157
  this.log("");
114
- printWindow((message) => this.log(message), "Weekly window", body.usage.weekly, showCosts);
158
+ for (const line of renderWindowBox("Weekly window", body.usage.weekly, showCosts))
159
+ this.log(line);
115
160
  });
116
161
  }
117
162
  }
@@ -1,13 +1,7 @@
1
1
  import { Command } from "@oclif/core";
2
- type ProgressHandle = {
3
- update(message: string): void;
4
- succeed(message?: string): void;
5
- fail(message?: string): void;
6
- stop(): void;
7
- };
2
+ import { ProgressHandle } from "./shared/app-shell";
8
3
  export declare abstract class BaseCommand extends Command {
9
4
  protected runSafe(action: () => Promise<void>): Promise<void>;
10
5
  private handleError;
11
6
  protected createProgressHandle(enabled: boolean): ProgressHandle;
12
7
  }
13
- export {};
@@ -4,7 +4,7 @@ exports.BaseCommand = void 0;
4
4
  const core_1 = require("@oclif/core");
5
5
  const errors_1 = require("./errors");
6
6
  const update_check_1 = require("./update-check");
7
- const tui_1 = require("./shared/tui");
7
+ const app_shell_1 = require("./shared/app-shell");
8
8
  class BaseCommand extends core_1.Command {
9
9
  async runSafe(action) {
10
10
  try {
@@ -20,72 +20,19 @@ class BaseCommand extends core_1.Command {
20
20
  }
21
21
  }
22
22
  handleError(error) {
23
- if (error instanceof errors_1.ClawBayError) {
24
- this.error(error.message);
25
- }
26
- if (error instanceof Error) {
27
- this.error(error.message);
28
- }
29
- this.error(String(error));
23
+ // Always hand oclif a non-empty string. A blank/undefined message would make
24
+ // oclif throw its own "first argument must be a string" TypeError, which then
25
+ // masks the real failure with a confusing message.
26
+ const message = error instanceof errors_1.ClawBayError || error instanceof Error
27
+ ? error.message
28
+ : String(error);
29
+ this.error(typeof message === "string" && message.trim().length > 0 ? message : String(error));
30
30
  }
31
31
  createProgressHandle(enabled) {
32
- if (!enabled || !process.stdout.isTTY) {
33
- return {
34
- update() { },
35
- succeed() { },
36
- fail() { },
37
- stop() { },
38
- };
39
- }
40
- const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
41
- let message = "";
42
- let frameIndex = 0;
43
- let timer = null;
44
- const render = (prefix) => {
45
- process.stdout.write(`\r\x1b[2K${prefix} ${message}`);
46
- };
47
- const spinnerFrame = () => tui_1.tint.accent(frames[frameIndex] ?? frames[0] ?? "•");
48
- const ensureTimer = () => {
49
- if (timer)
50
- return;
51
- timer = setInterval(() => {
52
- frameIndex = (frameIndex + 1) % frames.length;
53
- render(spinnerFrame());
54
- }, 80);
55
- if (typeof timer.unref === "function")
56
- timer.unref();
57
- };
58
- const clearTimer = () => {
59
- if (!timer)
60
- return;
61
- clearInterval(timer);
62
- timer = null;
63
- };
64
- return {
65
- update(nextMessage) {
66
- message = nextMessage;
67
- ensureTimer();
68
- render(spinnerFrame());
69
- },
70
- succeed(nextMessage) {
71
- if (nextMessage)
72
- message = tui_1.tint.bold(nextMessage);
73
- clearTimer();
74
- render(tui_1.tint.green(tui_1.glyph.tick));
75
- process.stdout.write("\n");
76
- },
77
- fail(nextMessage) {
78
- if (nextMessage)
79
- message = nextMessage;
80
- clearTimer();
81
- render(tui_1.tint.red(tui_1.glyph.cross));
82
- process.stdout.write("\n");
83
- },
84
- stop() {
85
- clearTimer();
86
- process.stdout.write("\r\x1b[2K");
87
- },
88
- };
32
+ // Backed by the persistent AppShell: each completed step commits a lasting
33
+ // checklist line while only the active step animates, so the surface never
34
+ // wipes itself between phases.
35
+ return app_shell_1.AppShell.progress(enabled);
89
36
  }
90
37
  }
91
38
  exports.BaseCommand = BaseCommand;
@@ -16,12 +16,17 @@ export type CodexFeatureFlagSnapshot = {
16
16
  export declare function removeProviderTable(source: string, providerId: string): string;
17
17
  export declare function restoreManagedCodexFeatureFlags(source: string): Promise<string>;
18
18
  export declare function ensureManagedCodexFeatureFlags(source: string): Promise<string>;
19
- export declare function promptForCodexConversationMigration(): Promise<boolean>;
20
- export declare function resolveCodexConversationMigrationSelection(params: {
19
+ export type CodexMigrationPlan = {
20
+ enabled: boolean;
21
+ limit: number | null;
22
+ files: string[];
23
+ total: number;
24
+ };
25
+ export declare function resolveCodexConversationMigrationPlan(params: {
21
26
  codexSelected: boolean;
22
27
  flagValue: boolean | undefined;
23
28
  skipPrompt: boolean;
24
- }): Promise<boolean>;
29
+ }): Promise<CodexMigrationPlan>;
25
30
  export declare function detectCodexClient(): Promise<boolean>;
26
31
  export declare function writeCodexConfig(params: {
27
32
  backendUrl: string;
@@ -7,8 +7,7 @@ exports.CODEX_MODEL_AUTO_COMPACT_TOKEN_LIMIT = exports.CODEX_MODEL_CONTEXT_WINDO
7
7
  exports.removeProviderTable = removeProviderTable;
8
8
  exports.restoreManagedCodexFeatureFlags = restoreManagedCodexFeatureFlags;
9
9
  exports.ensureManagedCodexFeatureFlags = ensureManagedCodexFeatureFlags;
10
- exports.promptForCodexConversationMigration = promptForCodexConversationMigration;
11
- exports.resolveCodexConversationMigrationSelection = resolveCodexConversationMigrationSelection;
10
+ exports.resolveCodexConversationMigrationPlan = resolveCodexConversationMigrationPlan;
12
11
  exports.detectCodexClient = detectCodexClient;
13
12
  exports.writeCodexConfig = writeCodexConfig;
14
13
  const node_path_1 = __importDefault(require("node:path"));
@@ -16,9 +15,10 @@ const paths_1 = require("../config/paths");
16
15
  const providers_1 = require("../shared/providers");
17
16
  const fsx_1 = require("../shared/fsx");
18
17
  const toml_1 = require("../shared/toml");
19
- const promises_1 = require("node:readline/promises");
18
+ const codex_history_migration_1 = require("../codex-history-migration");
19
+ const prompts_1 = require("../shared/prompts");
20
20
  const platform_1 = require("../shared/platform");
21
- const promises_2 = __importDefault(require("node:fs/promises"));
21
+ const promises_1 = __importDefault(require("node:fs/promises"));
22
22
  const urls_1 = require("../shared/urls");
23
23
  // Extracted from src/commands/setup.ts (theclawbay CLI restructure).
24
24
  // Code motion only: bodies are verbatim from the original file.
@@ -104,31 +104,27 @@ async function ensureManagedCodexFeatureFlags(source) {
104
104
  next = (0, toml_1.setTopLevelTableKey)(next, "features", "apps_mcp_gateway", "true");
105
105
  return next;
106
106
  }
107
- async function promptForCodexConversationMigration() {
108
- if (!process.stdin.isTTY || !process.stdout.isTTY)
109
- return false;
110
- const rl = (0, promises_1.createInterface)({
111
- input: process.stdin,
112
- output: process.stdout,
113
- });
114
- try {
115
- const answer = await rl.question("\nConvert old Codex conversations so they stay visible under The Claw Bay? [y/N] ");
116
- const normalized = answer.trim().toLowerCase();
117
- return normalized === "y" || normalized === "yes";
118
- }
119
- finally {
120
- rl.close();
121
- process.stdin.resume();
122
- }
123
- }
124
- async function resolveCodexConversationMigrationSelection(params) {
107
+ const DISABLED_MIGRATION_PLAN = { enabled: false, limit: null, files: [], total: 0 };
108
+ async function resolveCodexConversationMigrationPlan(params) {
125
109
  if (!params.codexSelected)
126
- return false;
127
- if (params.flagValue !== undefined)
128
- return params.flagValue;
110
+ return DISABLED_MIGRATION_PLAN;
111
+ if (params.flagValue === false)
112
+ return DISABLED_MIGRATION_PLAN;
113
+ const scope = await (0, codex_history_migration_1.buildConversationScope)(paths_1.codexDir);
114
+ if (scope.total === 0)
115
+ return DISABLED_MIGRATION_PLAN;
116
+ // `--migrate-conversations` keeps its historical meaning: a full pass.
117
+ if (params.flagValue === true) {
118
+ return { enabled: true, limit: null, files: scope.paths, total: scope.total };
119
+ }
120
+ // `--yes` / non-interactive still defaults to leaving history untouched.
129
121
  if (params.skipPrompt || !process.stdin.isTTY || !process.stdout.isTTY)
130
- return false;
131
- return promptForCodexConversationMigration();
122
+ return DISABLED_MIGRATION_PLAN;
123
+ const choice = await (0, prompts_1.promptConversationScope)({ total: scope.total, options: scope.options });
124
+ if (!choice)
125
+ return DISABLED_MIGRATION_PLAN;
126
+ const files = choice.limit === null ? scope.paths : scope.paths.slice(0, choice.limit);
127
+ return { enabled: files.length > 0, limit: choice.limit, files, total: scope.total };
132
128
  }
133
129
  async function detectCodexClient() {
134
130
  if ((0, platform_1.hasCommand)("codex"))
@@ -147,10 +143,10 @@ async function detectCodexClient() {
147
143
  }
148
144
  async function writeCodexConfig(params) {
149
145
  const configPath = node_path_1.default.join(paths_1.codexDir, "config.toml");
150
- await promises_2.default.mkdir(paths_1.codexDir, { recursive: true });
146
+ await promises_1.default.mkdir(paths_1.codexDir, { recursive: true });
151
147
  let existing = "";
152
148
  try {
153
- existing = await promises_2.default.readFile(configPath, "utf8");
149
+ existing = await promises_1.default.readFile(configPath, "utf8");
154
150
  }
155
151
  catch (error) {
156
152
  const err = error;
@@ -183,6 +179,6 @@ async function writeCodexConfig(params) {
183
179
  exports.MANAGED_END,
184
180
  ]);
185
181
  next = (0, fsx_1.appendManagedBlock)(next, managedBlock.trimEnd().split("\n"));
186
- await promises_2.default.writeFile(configPath, next, "utf8");
182
+ await promises_1.default.writeFile(configPath, next, "utf8");
187
183
  return configPath;
188
184
  }
@@ -11,10 +11,36 @@ export type StateDbProviderMigrationResult = {
11
11
  failed: string[];
12
12
  warning?: string;
13
13
  };
14
+ export type SessionCandidate = {
15
+ path: string;
16
+ size: number;
17
+ mtimeMs: number;
18
+ };
19
+ export type ConversationScopeOption = {
20
+ limit: number | null;
21
+ count: number;
22
+ estSeconds: number;
23
+ };
24
+ export type ConversationScope = {
25
+ total: number;
26
+ paths: string[];
27
+ options: ConversationScopeOption[];
28
+ };
29
+ export type MigrationProgress = {
30
+ processed: number;
31
+ total: number;
32
+ rewritten: number;
33
+ elapsedMs: number;
34
+ etaSeconds: number;
35
+ currentFile: string;
36
+ };
37
+ export declare function buildConversationScope(codexHome: string): Promise<ConversationScope>;
14
38
  export declare function migrateSessionProviders(params: {
15
39
  codexHome: string;
16
40
  migrationStateFile: string;
17
41
  neutralizeSources: Set<string>;
42
+ files?: string[];
43
+ onProgress?: (progress: MigrationProgress) => void;
18
44
  }): Promise<SessionProviderMigrationResult>;
19
45
  export declare function migrateStateDbProviders(params: {
20
46
  codexHome: string;