cc-viewer 1.6.348 → 1.7.0

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.
Files changed (126) hide show
  1. package/README.md +13 -1
  2. package/cli.js +116 -37
  3. package/dist/assets/App-DTkacfRW.js +2 -0
  4. package/dist/assets/{MdxEditorPanel-ualoRFOL.js → MdxEditorPanel-C5Q6uUyH.js} +1 -1
  5. package/dist/assets/Mobile-MoBp9c8k.js +1 -0
  6. package/dist/assets/{_baseUniq-CG81mgN_.js → _baseUniq-D9_E5ZDp.js} +1 -1
  7. package/dist/assets/{arc-C161J4f2.js → arc-D4iFrlhb.js} +1 -1
  8. package/dist/assets/{architectureDiagram-Q4EWVU46-BmbDMdn9.js → architectureDiagram-Q4EWVU46-f1neo7hg.js} +1 -1
  9. package/dist/assets/{blockDiagram-DXYQGD6D-DXpMvdxd.js → blockDiagram-DXYQGD6D-DWi9e9tG.js} +1 -1
  10. package/dist/assets/{c4Diagram-AHTNJAMY-8Mypg0IU.js → c4Diagram-AHTNJAMY-DAdj49A2.js} +1 -1
  11. package/dist/assets/{channel-B2khdmvc.js → channel-B3VthT0m.js} +1 -1
  12. package/dist/assets/{chunk-4BX2VUAB-I61r1g6z.js → chunk-4BX2VUAB-BrPcAHV3.js} +1 -1
  13. package/dist/assets/{chunk-4TB4RGXK-VFPlRE5E.js → chunk-4TB4RGXK-Bvopc1V9.js} +1 -1
  14. package/dist/assets/{chunk-55IACEB6-DvCamg_6.js → chunk-55IACEB6-Juw-7Z3i.js} +1 -1
  15. package/dist/assets/{chunk-EDXVE4YY-Dj4Q8RwR.js → chunk-EDXVE4YY-DVYP9tpB.js} +1 -1
  16. package/dist/assets/{chunk-FMBD7UC4-_bgQuuJh.js → chunk-FMBD7UC4-VQ7VOXl3.js} +1 -1
  17. package/dist/assets/{chunk-OYMX7WX6-3to38LyN.js → chunk-OYMX7WX6-F8Rj4ZUZ.js} +1 -1
  18. package/dist/assets/{chunk-QZHKN3VN-Zl26wKKz.js → chunk-QZHKN3VN-Dn7K3yi9.js} +1 -1
  19. package/dist/assets/{chunk-YZCP3GAM-RLYPrlh0.js → chunk-YZCP3GAM-UjO-KTsn.js} +1 -1
  20. package/dist/assets/classDiagram-6PBFFD2Q-DmSozxoO.js +1 -0
  21. package/dist/assets/classDiagram-v2-HSJHXN6E-DmSozxoO.js +1 -0
  22. package/dist/assets/clone-BQtvy9Ae.js +1 -0
  23. package/dist/assets/{cose-bilkent-S5V4N54A-CsYOjJ0V.js → cose-bilkent-S5V4N54A-BDhNj8rq.js} +1 -1
  24. package/dist/assets/{dagre-KV5264BT-C3YXXaFU.js → dagre-KV5264BT-BzdcDuyt.js} +1 -1
  25. package/dist/assets/{diagram-5BDNPKRD-DUCOg1ld.js → diagram-5BDNPKRD-DP_SX65N.js} +1 -1
  26. package/dist/assets/{diagram-G4DWMVQ6-BtlLl_zX.js → diagram-G4DWMVQ6-B-pBFw8Q.js} +1 -1
  27. package/dist/assets/{diagram-MMDJMWI5-1Qp9xfOk.js → diagram-MMDJMWI5-6XSfN1eN.js} +1 -1
  28. package/dist/assets/{diagram-TYMM5635-Cj7VMp3L.js → diagram-TYMM5635-DJvGxvpE.js} +1 -1
  29. package/dist/assets/{erDiagram-SMLLAGMA-CVZVn541.js → erDiagram-SMLLAGMA-CpqdpGYj.js} +1 -1
  30. package/dist/assets/{flowDiagram-DWJPFMVM-CiU9Fkkv.js → flowDiagram-DWJPFMVM-C9m7wjwI.js} +1 -1
  31. package/dist/assets/{ganttDiagram-T4ZO3ILL-aRaG97ID.js → ganttDiagram-T4ZO3ILL-CU0XOfY3.js} +1 -1
  32. package/dist/assets/{gitGraphDiagram-UUTBAWPF-Cr2LJlvX.js → gitGraphDiagram-UUTBAWPF-ipyGULnP.js} +1 -1
  33. package/dist/assets/{graph-B59lDyMn.js → graph-BR2KmES9.js} +1 -1
  34. package/dist/assets/{index-C1ptnCpq.js → index--wgRW_OB.js} +1 -1
  35. package/dist/assets/index-Ac8vnV3v.js +2 -0
  36. package/dist/assets/{index-CRAWA8PC.js → index-C-be29ey.js} +1 -1
  37. package/dist/assets/{index-CSM1Kw8l.js → index-C16fiVhv.js} +1 -1
  38. package/dist/assets/{index-Ddk-kg22.js → index-D5YruOKD.js} +1 -1
  39. package/dist/assets/{index-BnhNIjbj.js → index-D5tSeMU7.js} +1 -1
  40. package/dist/assets/{index-vFEQq-NB.js → index-DuZQ6FqR.js} +1 -1
  41. package/dist/assets/{index-CO2hZ1s4.js → index-OWzSX2T8.js} +1 -1
  42. package/dist/assets/{infoDiagram-42DDH7IO-CnO22bw8.js → infoDiagram-42DDH7IO-BQWQjfFX.js} +1 -1
  43. package/dist/assets/{ishikawaDiagram-UXIWVN3A-ByNzpScH.js → ishikawaDiagram-UXIWVN3A-Df-KH_Hz.js} +1 -1
  44. package/dist/assets/{journeyDiagram-VCZTEJTY-yaVaCWPq.js → journeyDiagram-VCZTEJTY-CMbB_4G9.js} +1 -1
  45. package/dist/assets/{jszip.min-BdVFP3OW.js → jszip.min-B6k2t9Ch.js} +1 -1
  46. package/dist/assets/{kanban-definition-6JOO6SKY-RIPR_RQ3.js → kanban-definition-6JOO6SKY-CImdlKN3.js} +1 -1
  47. package/dist/assets/{layout-0rzel0v_.js → layout-DQl4aw09.js} +1 -1
  48. package/dist/assets/{linear-sktN0tB6.js → linear-CLD3aPlK.js} +1 -1
  49. package/dist/assets/mermaid.core-CN35dvKJ.js +7 -0
  50. package/dist/assets/{min-rspGOkz_.js → min-_D86henJ.js} +1 -1
  51. package/dist/assets/{mindmap-definition-QFDTVHPH-BsVzZzTz.js → mindmap-definition-QFDTVHPH-2JVcu1nk.js} +1 -1
  52. package/dist/assets/{pieDiagram-DEJITSTG-CsoWsw4f.js → pieDiagram-DEJITSTG-Dik-m5u7.js} +1 -1
  53. package/dist/assets/{quadrantDiagram-34T5L4WZ-CTMKuCAI.js → quadrantDiagram-34T5L4WZ-Bm5l40Jq.js} +1 -1
  54. package/dist/assets/{requirementDiagram-MS252O5E-C67256te.js → requirementDiagram-MS252O5E-Ck9w5rTj.js} +1 -1
  55. package/dist/assets/{sankeyDiagram-XADWPNL6-X8Y7KnW0.js → sankeyDiagram-XADWPNL6-BbGVCFEf.js} +1 -1
  56. package/dist/assets/seqResourceLoaders-Cd_TAXTb.js +2 -0
  57. package/dist/assets/{seqResourceLoaders-B5Zy_CC_.css → seqResourceLoaders-CxuD1CYl.css} +1 -1
  58. package/dist/assets/{sequenceDiagram-FGHM5R23-Cils0TAp.js → sequenceDiagram-FGHM5R23-vjvEdksk.js} +1 -1
  59. package/dist/assets/{stateDiagram-FHFEXIEX-DFnohvpG.js → stateDiagram-FHFEXIEX-DTqMoiB2.js} +1 -1
  60. package/dist/assets/{stateDiagram-v2-QKLJ7IA2-BTX_4o81.js → stateDiagram-v2-QKLJ7IA2-CNbAwnhu.js} +1 -1
  61. package/dist/assets/{timeline-definition-GMOUNBTQ-DrIYFrzZ.js → timeline-definition-GMOUNBTQ-Cw8O-52d.js} +1 -1
  62. package/dist/assets/{vendor-antd-D5HmSDSg.js → vendor-antd-DI7JL-mE.js} +1 -1
  63. package/dist/assets/{vendor-codemirror-C5yuj7ze.js → vendor-codemirror-B9c49dtM.js} +1 -1
  64. package/dist/assets/{vendor-mdxeditor-BcEooTvb.js → vendor-mdxeditor-B6cpBtIE.js} +2 -2
  65. package/dist/assets/{vendor-qrcode-d9LCn0-9.js → vendor-qrcode-Dn90-s-u.js} +1 -1
  66. package/dist/assets/{vendor-virtuoso-K9m4MN6a.js → vendor-virtuoso-CpdSKD_6.js} +1 -1
  67. package/dist/assets/{vennDiagram-DHZGUBPP-lRqBYTr9.js → vennDiagram-DHZGUBPP-DCuqymb7.js} +1 -1
  68. package/dist/assets/{wardley-RL74JXVD-e8YfxpOe.js → wardley-RL74JXVD-BJ20wJUU.js} +1 -1
  69. package/dist/assets/{wardleyDiagram-NUSXRM2D-Bxrg774h.js → wardleyDiagram-NUSXRM2D-Vz6US3qt.js} +1 -1
  70. package/dist/assets/{xychartDiagram-5P7HB3ND-DWhZqhq8.js → xychartDiagram-5P7HB3ND-CzkLKrmi.js} +1 -1
  71. package/dist/index.html +4 -4
  72. package/findcc.js +4 -3
  73. package/package.json +2 -1
  74. package/server/i18n.js +21 -141
  75. package/server/interceptor.js +211 -403
  76. package/server/lib/async-write-queue.js +12 -1
  77. package/server/lib/delta-reconstructor.js +3 -3
  78. package/server/lib/error-report.js +45 -0
  79. package/server/lib/im-log-watcher.js +68 -8
  80. package/server/lib/interceptor-core.js +19 -174
  81. package/server/lib/log-management.js +214 -247
  82. package/server/lib/log-stream.js +59 -108
  83. package/server/lib/log-watcher.js +34 -272
  84. package/server/lib/log-zip.js +148 -0
  85. package/server/lib/session-boundary.js +266 -0
  86. package/server/lib/session-pin-store.js +10 -19
  87. package/server/lib/stats-worker.js +138 -267
  88. package/server/lib/user-prompt-extract.js +250 -0
  89. package/server/lib/v2/adapter.js +1059 -0
  90. package/server/lib/v2/blob-store.js +44 -0
  91. package/server/lib/v2/conversation-store.js +217 -0
  92. package/server/lib/v2/convert-manager.js +79 -0
  93. package/server/lib/v2/convert-worker.js +42 -0
  94. package/server/lib/v2/convert.js +409 -0
  95. package/server/lib/v2/identity.js +204 -0
  96. package/server/lib/v2/journal.js +62 -0
  97. package/server/lib/v2/layout.js +167 -0
  98. package/server/lib/v2/live-feed.js +451 -0
  99. package/server/lib/v2/migrate-prompt.js +54 -0
  100. package/server/lib/v2/replay.js +191 -0
  101. package/server/lib/v2/session-select.js +222 -0
  102. package/server/lib/v2/singleflight.js +104 -0
  103. package/server/lib/v2/v2-writer.js +513 -0
  104. package/server/lib/v2/verify.js +233 -0
  105. package/server/routes/events.js +30 -130
  106. package/server/routes/files-fs.js +4 -2
  107. package/server/routes/im.js +11 -2
  108. package/server/routes/logs.js +194 -152
  109. package/server/routes/preferences.js +0 -3
  110. package/server/routes/project-meta.js +2 -3
  111. package/server/routes/session-pin.js +7 -8
  112. package/server/routes/workspaces.js +44 -9
  113. package/server/server.js +47 -65
  114. package/server/workspace-registry.js +24 -4
  115. package/dist/assets/App-VTo4NVdo.js +0 -2
  116. package/dist/assets/Mobile-CcmYvMm_.js +0 -1
  117. package/dist/assets/classDiagram-6PBFFD2Q-BXnyUgsj.js +0 -1
  118. package/dist/assets/classDiagram-v2-HSJHXN6E-BXnyUgsj.js +0 -1
  119. package/dist/assets/clone-md-Mc1-T.js +0 -1
  120. package/dist/assets/index-DCo650PG.js +0 -2
  121. package/dist/assets/mermaid.core-C2Parj1q.js +0 -7
  122. package/dist/assets/seqResourceLoaders-BVozq68i.js +0 -2
  123. package/server/lib/instance-registry.js +0 -57
  124. package/server/lib/jsonl-archive.js +0 -180
  125. package/server/lib/teammate-detect.js +0 -32
  126. package/server/lib/zip-safety.js +0 -68
package/README.md CHANGED
@@ -52,11 +52,23 @@ Once started in programming mode, the web page opens automatically.
52
52
 
53
53
  cc-viewer also ships as a native desktop app: [download page](https://github.com/weiesky/cc-viewer/releases)
54
54
 
55
+ ### Upgrading to 1.7.0 (log format v2)
56
+
57
+ Since 1.7.0, logs are stored in a per-session directory format (wire-format v2) instead of single `.jsonl` files — roughly 90% smaller on disk. Existing v1 `.jsonl` files are never modified or deleted; the log dialog lists v2 sessions by default, and a small “View legacy (v1) logs” entry (shown while old files exist) opens a v1 view where they can be viewed, migrated, or deleted. On startup, cc-viewer offers one-click migration when legacy logs are found (strongly recommended when continuing an old conversation with `claude -c`, whose first half lives in the old files). You can also migrate from the terminal:
58
+
59
+ ```bash
60
+ ccv convert <project> # migrate one project
61
+ ccv convert --all # migrate every project
62
+ ccv verify <v1-file> # check a v1 file against its converted sessions
63
+ ```
64
+
65
+ A session that fails golden verification is held in `sessions-quarantine/` for inspection instead of failing the whole migration — the other sessions still migrate.
66
+
55
67
  ### Logger mode
56
68
 
57
69
  If you still prefer the native claude tool or the VS Code extension, use this mode.
58
70
 
59
- In this mode, launching `claude` will automatically start a logging process that records request logs to \~/.claude/cc-viewer/*yourproject*/date.jsonl
71
+ In this mode, launching `claude` will automatically start a logging process that records request logs to per-session directories under \~/.claude/cc-viewer/*yourproject*/sessions/ (wire-format v2)
60
72
 
61
73
  Enable logger mode:
62
74
 
package/cli.js CHANGED
@@ -222,6 +222,9 @@ function removeCliJsInjection() {
222
222
 
223
223
  async function runProxyCommand(args) {
224
224
  try {
225
+ // P2 detection channel ①(shell-hook 形态):`claude -c` 经 hook 变成
226
+ // `ccv run -- claude --ccv-internal -c …`,在 proxy/server 模块加载前打标。
227
+ markContinueEnv(args);
225
228
  // Dynamic import to avoid side effects when just installing
226
229
  const { startProxy } = await import('./server/proxy.js');
227
230
  const proxyPort = await startProxy();
@@ -306,16 +309,41 @@ async function runProxyCommand(args) {
306
309
 
307
310
  // ensureHooks() extracted to server/lib/ensure-hooks.js (shared with electron/tab-worker.js)
308
311
 
309
- // Print the `--pid` instance id + this project's previously-used ids in the startup banner,
310
- // so the user can recall which id to reuse (with `-c`) next time. No-op without `--pid`.
311
- function printInstanceBanner(serverMod) {
312
+ // P2 (-c migration guidance, detection channel ①): the interceptor resolves
313
+ // CCV_CLAUDE_CONTINUE at prompt time, so setting it before the server module
314
+ // loads is sufficient — claude's own -c semantics are untouched.
315
+ function markContinueEnv(claudeArgs) {
316
+ if (!Array.isArray(claudeArgs)) return;
317
+ if (claudeArgs.some((a) => a === '-c' || a === '--continue' || a === '-r' || a === '--resume')) {
318
+ process.env.CCV_CLAUDE_CONTINUE = '1';
319
+ }
320
+ // Explicit `-r`/`--resume` targets a session of the user's choosing, while
321
+ // folder adoption always targets the LATEST main session — so a resume must
322
+ // keep its pre-adoption behavior (own folder). The CONTINUE env above still
323
+ // marks the launch for the migrate prompt.
324
+ if (claudeArgs.some((a) => a === '-r' || a === '--resume')) {
325
+ process.env.CCV_CLAUDE_RESUME = '1';
326
+ }
327
+ // `--fork-session` continues/resumes but mints a NEW session id on purpose —
328
+ // the interceptor reads this before the server module loads so `-c` folder
329
+ // adoption is suppressed (claude's own semantics are untouched).
330
+ if (claudeArgs.includes('--fork-session')) {
331
+ process.env.CCV_CLAUDE_FORK_SESSION = '1';
332
+ }
333
+ }
334
+
335
+ // 1.7.0: legacy v1 logs still present → one localized banner line pointing at
336
+ // `ccv convert` (headless users never see the web migrate prompt).
337
+ async function printMigrationBanner() {
312
338
  try {
313
- const id = serverMod.getInstanceId && serverMod.getInstanceId();
314
- if (!id) return;
315
- console.log(` ${t('cli.instanceId', { id })}`);
316
- const known = (serverMod.getKnownInstances && serverMod.getKnownInstances()) || [];
317
- const others = known.filter((x) => x !== id);
318
- if (others.length) console.log(` ${t('cli.instanceHistory', { ids: others.join(', ') })}`);
339
+ const { LOG_DIR } = await import('./findcc.js');
340
+ const { listConvertibleProjects } = await import('./server/lib/v2/convert.js');
341
+ const { migrationStatus } = await import('./server/lib/v2/migrate-prompt.js');
342
+ const pending = listConvertibleProjects(LOG_DIR)
343
+ .filter((p) => migrationStatus(LOG_DIR, p).pending);
344
+ if (pending.length > 0) {
345
+ console.log(` ${t('cli.v1LogsFound', { count: pending.length })}`);
346
+ }
319
347
  } catch { /* banner is best-effort */ }
320
348
  }
321
349
 
@@ -440,7 +468,7 @@ async function runCliMode(extraClaudeArgs = [], cwd, noOpen = false) {
440
468
  if (_auth.password === '') console.error(` ${t('server.passwordEmptyWarn')}`);
441
469
  else console.log(` ${t('server.passwordActive', { password: _auth.password })}`);
442
470
  }
443
- printInstanceBanner(serverMod);
471
+ await printMigrationBanner();
444
472
 
445
473
  // 5. 注册退出处理(hardened:watchdog 5s 强退 + 连按 Ctrl+C 立退,
446
474
  // 防 Windows 上 ConPTY kill / IM teardown 挂住导致"Ctrl+C 完全无反应")
@@ -623,7 +651,7 @@ async function runSdkMode(extraClaudeArgs = [], cwd, noOpen = false) {
623
651
  if (_auth.password === '') console.error(` ${t('server.passwordEmptyWarn')}`);
624
652
  else console.log(` ${t('server.passwordActive', { password: _auth.password })}`);
625
653
  }
626
- printInstanceBanner(serverMod);
654
+ await printMigrationBanner();
627
655
 
628
656
  // 注册退出处理(hardened,与 PTY 模式同款三层防御)
629
657
  const cleanup = createHardenedCleanup({
@@ -649,9 +677,9 @@ const logDirIdx = args.indexOf('--log-dir');
649
677
  if (logDirIdx !== -1) {
650
678
  const logDirVal = args[logDirIdx + 1];
651
679
  if (logDirVal && !logDirVal.startsWith('-')) {
652
- const prevDir = LOG_DIR;
653
- setLogDir(logDirVal);
654
- if (LOG_DIR === prevDir) {
680
+ // setLogDir returns false only on a rejected path; comparing LOG_DIR
681
+ // before/after mis-rejected a --log-dir equal to the current default.
682
+ if (!setLogDir(logDirVal)) {
655
683
  console.error(`Error: --log-dir path rejected (must be under home directory or /tmp/): ${logDirVal}`);
656
684
  process.exit(1);
657
685
  }
@@ -721,29 +749,6 @@ if (usePwdIdx !== -1) {
721
749
  args.splice(usePwdIdx, 1);
722
750
  }
723
751
 
724
- // Extract --pid[=<name>] / --pid <name> — instance id for per-instance session-pin isolation.
725
- // NB: "pid" here is an instance *id* LABEL (user-chosen, e.g. alpha/beta), NOT an OS process id —
726
- // it only keys the session-pin file `.session-pin.<id>.json`; unrelated to process.pid.
727
- // ccv-owned (NEVER forwarded to claude — an unknown --pid would otherwise crash claude): sets
728
- // CCV_INSTANCE_ID and splices out. Sanitized to a filesystem-safe token (it becomes part of a
729
- // `.session-pin.<id>.json` filename → guard against path traversal / invalid names).
730
- const pidIdx = args.findIndex((a) => a === '--pid' || a.startsWith('--pid='));
731
- if (pidIdx !== -1) {
732
- const arg = args[pidIdx];
733
- let rawVal;
734
- if (arg.startsWith('--pid=')) {
735
- rawVal = arg.slice('--pid='.length);
736
- args.splice(pidIdx, 1);
737
- } else {
738
- rawVal = args[pidIdx + 1];
739
- if (rawVal && !rawVal.startsWith('-')) args.splice(pidIdx, 2);
740
- else { console.error(t('cli.pidInvalid')); process.exit(1); }
741
- }
742
- const sanitized = (rawVal || '').replace(/[^a-zA-Z0-9_\-.]/g, '_');
743
- if (!sanitized) { console.error(t('cli.pidInvalid')); process.exit(1); }
744
- process.env.CCV_INSTANCE_ID = sanitized;
745
- }
746
-
747
752
  // Extract --im <platformId> — 启动一个独立常驻 IM worker:工作目录 IM_<id>/、绑 127.0.0.1、
748
753
  // skip-permissions、全局唯一锁。必须在动态 import 之前提取(runImMode 会在 import server.js 前设 env)。
749
754
  let imPlatform = null;
@@ -917,12 +922,85 @@ if (imPlatform) {
917
922
  console.error('IM mode error:', err);
918
923
  process.exit(1);
919
924
  });
925
+ } else if (args[0] === 'verify') {
926
+ // wire-v2 S4: dual-write consistency check (docs/refactor/WIRE_FORMAT_V2_PLAN.md S4)
927
+ // Usage: ccv verify <v1-log.jsonl> [more.jsonl ...] — compares each v1 file
928
+ // against its project's v2 session dirs; exit 0 = zero diffs.
929
+ (async () => {
930
+ const files = args.slice(1).filter(a => a.endsWith('.jsonl'));
931
+ if (files.length === 0) {
932
+ console.error('usage: ccv verify <v1-log.jsonl> [more.jsonl ...]');
933
+ process.exit(2);
934
+ }
935
+ const { verifyV1File, renderReport } = await import('./server/lib/v2/verify.js');
936
+ let allOk = true;
937
+ for (const f of files) {
938
+ const report = await verifyV1File(resolve(f));
939
+ console.log(renderReport(report));
940
+ if (!report.ok) allOk = false;
941
+ }
942
+ process.exit(allOk ? 0 : 1);
943
+ })().catch(err => {
944
+ console.error('verify error:', err);
945
+ process.exit(1);
946
+ });
947
+ } else if (args[0] === 'convert') {
948
+ // wire-v2 S8: offline v1→v2 migration (docs/refactor/WIRE_FORMAT_V2_PLAN.md S8)
949
+ // Usage: ccv convert <project> [more-projects ...] | ccv convert --all
950
+ // [--log-dir <dir>] — chronological per project, file-level resume,
951
+ // staging + golden verify + promote. Strictly additive: v1 files untouched.
952
+ (async () => {
953
+ const { convertProject, listConvertibleProjects, QUARANTINE_DIR_NAME } = await import('./server/lib/v2/convert.js');
954
+ const rest = args.slice(1);
955
+ const dirIdx = rest.indexOf('--log-dir');
956
+ let logDir = LOG_DIR;
957
+ if (dirIdx !== -1) {
958
+ logDir = resolve(rest[dirIdx + 1] || '');
959
+ rest.splice(dirIdx, 2);
960
+ }
961
+ const all = rest.includes('--all');
962
+ const projects = all ? listConvertibleProjects(logDir) : rest.filter(a => !a.startsWith('-'));
963
+ if (projects.length === 0) {
964
+ console.error('usage: ccv convert <project> [more-projects ...] | ccv convert --all [--log-dir <dir>]');
965
+ process.exit(2);
966
+ }
967
+ let allOk = true;
968
+ for (const project of projects) {
969
+ console.error(`converting ${project} (${logDir}) ...`);
970
+ try {
971
+ const state = await convertProject(logDir, project, {
972
+ onProgress: (p) => {
973
+ if (p.phase === 'done') return;
974
+ console.error(` [${p.phase}] ${p.file} (${p.fileIndex}/${p.filesTotal}) entries=${p.entries} sessions=${p.sessionsConverted} skipped=${p.sessionsSkipped}`);
975
+ },
976
+ });
977
+ const quarantined = state.sessionsQuarantined || 0;
978
+ console.error(` ${project}: ${state.status} — ${state.sessionsConverted} sessions converted, ${state.sessionsSkipped} skipped${quarantined ? `, ${quarantined} quarantined` : ''}, ${state.entries} entries`);
979
+ if (quarantined > 0) {
980
+ // Non-blocking: the good sessions promoted; only the flagged ones are held.
981
+ console.error(` WARNING: ${quarantined} session(s) failed golden verify and were held in ${QUARANTINE_DIR_NAME}/ (not promoted). Suspect sessions:`);
982
+ for (const q of state.quarantined || []) {
983
+ console.error(` - ${q.sessionId}${q.reasons && q.reasons.length ? ` (${q.reasons.slice(0, 5).join(', ')})` : ''}`);
984
+ }
985
+ }
986
+ if (state.status !== 'done') allOk = false;
987
+ } catch (err) {
988
+ console.error(` ${project}: FAILED — ${err.message}`);
989
+ allOk = false;
990
+ }
991
+ }
992
+ process.exit(allOk ? 0 : 1);
993
+ })().catch(err => {
994
+ console.error('convert error:', err);
995
+ process.exit(1);
996
+ });
920
997
  } else if (args[0] === 'run') {
921
998
  runProxyCommand(args);
922
999
  } else if (args.includes('-SDK') || args.includes('--sdk')) {
923
1000
  // SDK 模式(显式 -SDK 切换)
924
1001
  const claudeArgs = args.filter(a => a !== '-SDK' && a !== '--sdk')
925
1002
  .map(a => a === '--d' ? '--dangerously-skip-permissions' : a === '--ad' ? '--allow-dangerously-skip-permissions' : a);
1003
+ markContinueEnv(claudeArgs);
926
1004
  runSdkMode(claudeArgs, process.cwd(), noOpen).catch(err => {
927
1005
  console.error('SDK mode error:', err);
928
1006
  process.exit(1);
@@ -930,6 +1008,7 @@ if (imPlatform) {
930
1008
  } else {
931
1009
  // PTY 模式(默认)
932
1010
  const claudeArgs = args.map(a => a === '--d' ? '--dangerously-skip-permissions' : a === '--ad' ? '--allow-dangerously-skip-permissions' : a);
1011
+ markContinueEnv(claudeArgs);
933
1012
  runCliMode(claudeArgs, process.cwd(), noOpen).catch(err => {
934
1013
  console.error('CLI mode error:', err);
935
1014
  process.exit(1);