cc-viewer 1.6.345 → 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 (142) hide show
  1. package/README.md +14 -2
  2. package/cli.js +116 -37
  3. package/dist/assets/App-DTkacfRW.js +2 -0
  4. package/dist/assets/App-dYPa5-df.css +1 -0
  5. package/dist/assets/{MdxEditorPanel-C8ITEOzT.js → MdxEditorPanel-C5Q6uUyH.js} +1 -1
  6. package/dist/assets/Mobile-MoBp9c8k.js +1 -0
  7. package/dist/assets/{_baseUniq-CG81mgN_.js → _baseUniq-D9_E5ZDp.js} +1 -1
  8. package/dist/assets/{arc-C161J4f2.js → arc-D4iFrlhb.js} +1 -1
  9. package/dist/assets/{architectureDiagram-Q4EWVU46-BmbDMdn9.js → architectureDiagram-Q4EWVU46-f1neo7hg.js} +1 -1
  10. package/dist/assets/{blockDiagram-DXYQGD6D-DXpMvdxd.js → blockDiagram-DXYQGD6D-DWi9e9tG.js} +1 -1
  11. package/dist/assets/{c4Diagram-AHTNJAMY-8Mypg0IU.js → c4Diagram-AHTNJAMY-DAdj49A2.js} +1 -1
  12. package/dist/assets/{channel-B2khdmvc.js → channel-B3VthT0m.js} +1 -1
  13. package/dist/assets/{chunk-4BX2VUAB-I61r1g6z.js → chunk-4BX2VUAB-BrPcAHV3.js} +1 -1
  14. package/dist/assets/{chunk-4TB4RGXK-VFPlRE5E.js → chunk-4TB4RGXK-Bvopc1V9.js} +1 -1
  15. package/dist/assets/{chunk-55IACEB6-DvCamg_6.js → chunk-55IACEB6-Juw-7Z3i.js} +1 -1
  16. package/dist/assets/{chunk-EDXVE4YY-Dj4Q8RwR.js → chunk-EDXVE4YY-DVYP9tpB.js} +1 -1
  17. package/dist/assets/{chunk-FMBD7UC4-_bgQuuJh.js → chunk-FMBD7UC4-VQ7VOXl3.js} +1 -1
  18. package/dist/assets/{chunk-OYMX7WX6-3to38LyN.js → chunk-OYMX7WX6-F8Rj4ZUZ.js} +1 -1
  19. package/dist/assets/{chunk-QZHKN3VN-Zl26wKKz.js → chunk-QZHKN3VN-Dn7K3yi9.js} +1 -1
  20. package/dist/assets/{chunk-YZCP3GAM-RLYPrlh0.js → chunk-YZCP3GAM-UjO-KTsn.js} +1 -1
  21. package/dist/assets/classDiagram-6PBFFD2Q-DmSozxoO.js +1 -0
  22. package/dist/assets/classDiagram-v2-HSJHXN6E-DmSozxoO.js +1 -0
  23. package/dist/assets/clone-BQtvy9Ae.js +1 -0
  24. package/dist/assets/{cose-bilkent-S5V4N54A-CsYOjJ0V.js → cose-bilkent-S5V4N54A-BDhNj8rq.js} +1 -1
  25. package/dist/assets/{dagre-KV5264BT-C3YXXaFU.js → dagre-KV5264BT-BzdcDuyt.js} +1 -1
  26. package/dist/assets/{diagram-5BDNPKRD-DUCOg1ld.js → diagram-5BDNPKRD-DP_SX65N.js} +1 -1
  27. package/dist/assets/{diagram-G4DWMVQ6-BtlLl_zX.js → diagram-G4DWMVQ6-B-pBFw8Q.js} +1 -1
  28. package/dist/assets/{diagram-MMDJMWI5-1Qp9xfOk.js → diagram-MMDJMWI5-6XSfN1eN.js} +1 -1
  29. package/dist/assets/{diagram-TYMM5635-Cj7VMp3L.js → diagram-TYMM5635-DJvGxvpE.js} +1 -1
  30. package/dist/assets/{erDiagram-SMLLAGMA-CVZVn541.js → erDiagram-SMLLAGMA-CpqdpGYj.js} +1 -1
  31. package/dist/assets/{flowDiagram-DWJPFMVM-CiU9Fkkv.js → flowDiagram-DWJPFMVM-C9m7wjwI.js} +1 -1
  32. package/dist/assets/{ganttDiagram-T4ZO3ILL-aRaG97ID.js → ganttDiagram-T4ZO3ILL-CU0XOfY3.js} +1 -1
  33. package/dist/assets/{gitGraphDiagram-UUTBAWPF-Cr2LJlvX.js → gitGraphDiagram-UUTBAWPF-ipyGULnP.js} +1 -1
  34. package/dist/assets/{graph-B59lDyMn.js → graph-BR2KmES9.js} +1 -1
  35. package/dist/assets/{index-C1ptnCpq.js → index--wgRW_OB.js} +1 -1
  36. package/dist/assets/index-Ac8vnV3v.js +2 -0
  37. package/dist/assets/{index-CRAWA8PC.js → index-C-be29ey.js} +1 -1
  38. package/dist/assets/{index-CSM1Kw8l.js → index-C16fiVhv.js} +1 -1
  39. package/dist/assets/{index-Ddk-kg22.js → index-D5YruOKD.js} +1 -1
  40. package/dist/assets/{index-BnhNIjbj.js → index-D5tSeMU7.js} +1 -1
  41. package/dist/assets/{index-vFEQq-NB.js → index-DuZQ6FqR.js} +1 -1
  42. package/dist/assets/{index-CO2hZ1s4.js → index-OWzSX2T8.js} +1 -1
  43. package/dist/assets/{infoDiagram-42DDH7IO-CnO22bw8.js → infoDiagram-42DDH7IO-BQWQjfFX.js} +1 -1
  44. package/dist/assets/{ishikawaDiagram-UXIWVN3A-ByNzpScH.js → ishikawaDiagram-UXIWVN3A-Df-KH_Hz.js} +1 -1
  45. package/dist/assets/{journeyDiagram-VCZTEJTY-yaVaCWPq.js → journeyDiagram-VCZTEJTY-CMbB_4G9.js} +1 -1
  46. package/dist/assets/{jszip.min-BdVFP3OW.js → jszip.min-B6k2t9Ch.js} +1 -1
  47. package/dist/assets/{kanban-definition-6JOO6SKY-RIPR_RQ3.js → kanban-definition-6JOO6SKY-CImdlKN3.js} +1 -1
  48. package/dist/assets/{layout-0rzel0v_.js → layout-DQl4aw09.js} +1 -1
  49. package/dist/assets/{linear-sktN0tB6.js → linear-CLD3aPlK.js} +1 -1
  50. package/dist/assets/mermaid.core-CN35dvKJ.js +7 -0
  51. package/dist/assets/{min-rspGOkz_.js → min-_D86henJ.js} +1 -1
  52. package/dist/assets/{mindmap-definition-QFDTVHPH-BsVzZzTz.js → mindmap-definition-QFDTVHPH-2JVcu1nk.js} +1 -1
  53. package/dist/assets/{pieDiagram-DEJITSTG-CsoWsw4f.js → pieDiagram-DEJITSTG-Dik-m5u7.js} +1 -1
  54. package/dist/assets/{quadrantDiagram-34T5L4WZ-CTMKuCAI.js → quadrantDiagram-34T5L4WZ-Bm5l40Jq.js} +1 -1
  55. package/dist/assets/{requirementDiagram-MS252O5E-C67256te.js → requirementDiagram-MS252O5E-Ck9w5rTj.js} +1 -1
  56. package/dist/assets/{sankeyDiagram-XADWPNL6-X8Y7KnW0.js → sankeyDiagram-XADWPNL6-BbGVCFEf.js} +1 -1
  57. package/dist/assets/seqResourceLoaders-Cd_TAXTb.js +2 -0
  58. package/dist/assets/{seqResourceLoaders-B3TSSS6T.css → seqResourceLoaders-CxuD1CYl.css} +1 -1
  59. package/dist/assets/{sequenceDiagram-FGHM5R23-Cils0TAp.js → sequenceDiagram-FGHM5R23-vjvEdksk.js} +1 -1
  60. package/dist/assets/{stateDiagram-FHFEXIEX-DFnohvpG.js → stateDiagram-FHFEXIEX-DTqMoiB2.js} +1 -1
  61. package/dist/assets/{stateDiagram-v2-QKLJ7IA2-BTX_4o81.js → stateDiagram-v2-QKLJ7IA2-CNbAwnhu.js} +1 -1
  62. package/dist/assets/{timeline-definition-GMOUNBTQ-DrIYFrzZ.js → timeline-definition-GMOUNBTQ-Cw8O-52d.js} +1 -1
  63. package/dist/assets/{vendor-antd-D5HmSDSg.js → vendor-antd-DI7JL-mE.js} +1 -1
  64. package/dist/assets/{vendor-codemirror-C5yuj7ze.js → vendor-codemirror-B9c49dtM.js} +1 -1
  65. package/dist/assets/{vendor-mdxeditor-BcEooTvb.js → vendor-mdxeditor-B6cpBtIE.js} +2 -2
  66. package/dist/assets/{vendor-qrcode-d9LCn0-9.js → vendor-qrcode-Dn90-s-u.js} +1 -1
  67. package/dist/assets/{vendor-virtuoso-K9m4MN6a.js → vendor-virtuoso-CpdSKD_6.js} +1 -1
  68. package/dist/assets/{vennDiagram-DHZGUBPP-lRqBYTr9.js → vennDiagram-DHZGUBPP-DCuqymb7.js} +1 -1
  69. package/dist/assets/{wardley-RL74JXVD-e8YfxpOe.js → wardley-RL74JXVD-BJ20wJUU.js} +1 -1
  70. package/dist/assets/{wardleyDiagram-NUSXRM2D-Bxrg774h.js → wardleyDiagram-NUSXRM2D-Vz6US3qt.js} +1 -1
  71. package/dist/assets/{xychartDiagram-5P7HB3ND-DWhZqhq8.js → xychartDiagram-5P7HB3ND-CzkLKrmi.js} +1 -1
  72. package/dist/index.html +4 -4
  73. package/findcc.js +79 -7
  74. package/package.json +2 -1
  75. package/server/i18n.js +21 -141
  76. package/server/interceptor.js +211 -403
  77. package/server/lib/adapters/dingtalk-adapter.js +16 -2
  78. package/server/lib/adapters/discord-adapter.js +28 -6
  79. package/server/lib/adapters/feishu-adapter.js +50 -4
  80. package/server/lib/adapters/wecom-adapter.js +24 -0
  81. package/server/lib/async-write-queue.js +12 -1
  82. package/server/lib/create_system_prompt.js +13 -7
  83. package/server/lib/delta-reconstructor.js +3 -3
  84. package/server/lib/error-report.js +45 -0
  85. package/server/lib/im-bridge-core.js +71 -4
  86. package/server/lib/im-lock.js +16 -2
  87. package/server/lib/im-log-watcher.js +68 -8
  88. package/server/lib/im-process-manager.js +7 -1
  89. package/server/lib/interceptor-core.js +19 -174
  90. package/server/lib/log-management.js +214 -247
  91. package/server/lib/log-stream.js +59 -108
  92. package/server/lib/log-watcher.js +34 -272
  93. package/server/lib/log-zip.js +148 -0
  94. package/server/lib/model-system-prompts.js +10 -6
  95. package/server/lib/session-boundary.js +266 -0
  96. package/server/lib/session-pin-store.js +10 -19
  97. package/server/lib/spawn-model-resolver.js +109 -0
  98. package/server/lib/stats-worker.js +138 -267
  99. package/server/lib/system-prompt-files.js +7 -3
  100. package/server/lib/system-prompt-render.js +79 -0
  101. package/server/lib/user-prompt-extract.js +250 -0
  102. package/server/lib/v2/adapter.js +1059 -0
  103. package/server/lib/v2/blob-store.js +44 -0
  104. package/server/lib/v2/conversation-store.js +217 -0
  105. package/server/lib/v2/convert-manager.js +79 -0
  106. package/server/lib/v2/convert-worker.js +42 -0
  107. package/server/lib/v2/convert.js +409 -0
  108. package/server/lib/v2/identity.js +204 -0
  109. package/server/lib/v2/journal.js +62 -0
  110. package/server/lib/v2/layout.js +167 -0
  111. package/server/lib/v2/live-feed.js +451 -0
  112. package/server/lib/v2/migrate-prompt.js +54 -0
  113. package/server/lib/v2/replay.js +191 -0
  114. package/server/lib/v2/session-select.js +222 -0
  115. package/server/lib/v2/singleflight.js +104 -0
  116. package/server/lib/v2/v2-writer.js +513 -0
  117. package/server/lib/v2/verify.js +233 -0
  118. package/server/pty-manager.js +115 -36
  119. package/server/routes/dingtalk.js +9 -3
  120. package/server/routes/events.js +30 -130
  121. package/server/routes/files-fs.js +4 -2
  122. package/server/routes/im.js +67 -20
  123. package/server/routes/logs.js +194 -152
  124. package/server/routes/preferences.js +0 -3
  125. package/server/routes/project-meta.js +2 -3
  126. package/server/routes/session-pin.js +7 -8
  127. package/server/routes/workspaces.js +44 -9
  128. package/server/server.js +59 -66
  129. package/server/workspace-registry.js +24 -4
  130. package/dist/assets/App-BjHJ9KJ6.css +0 -1
  131. package/dist/assets/App-CCmR0aki.js +0 -2
  132. package/dist/assets/Mobile-DCHg97f1.js +0 -1
  133. package/dist/assets/classDiagram-6PBFFD2Q-BXnyUgsj.js +0 -1
  134. package/dist/assets/classDiagram-v2-HSJHXN6E-BXnyUgsj.js +0 -1
  135. package/dist/assets/clone-md-Mc1-T.js +0 -1
  136. package/dist/assets/index-CfPXhStV.js +0 -2
  137. package/dist/assets/mermaid.core-C2Parj1q.js +0 -7
  138. package/dist/assets/seqResourceLoaders-CAYxnMSH.js +0 -2
  139. package/server/lib/instance-registry.js +0 -57
  140. package/server/lib/jsonl-archive.js +0 -180
  141. package/server/lib/teammate-detect.js +0 -32
  142. package/server/lib/zip-safety.js +0 -68
package/findcc.js CHANGED
@@ -1,4 +1,4 @@
1
- import { resolve, join } from 'node:path';
1
+ import { resolve, join, sep } from 'node:path';
2
2
  import { fileURLToPath } from 'node:url';
3
3
  import { existsSync, realpathSync, readFileSync } from 'node:fs';
4
4
  import { homedir, tmpdir, arch } from 'node:os';
@@ -22,11 +22,42 @@ const NODE_MODULES = resolve(__dirname, '..');
22
22
  * Claude Code's config from ~/.claude/ to a custom location.
23
23
  * @returns {string} absolute path to the Claude config directory
24
24
  */
25
+ // ████████ Test isolation barrier L1c helper — DO NOT REMOVE (2026-07-12 data loss) ████████
26
+ // A ccv-hosted shell exports CCV_LOG_DIR=<real user data dir> (and possibly CLAUDE_CONFIG_DIR)
27
+ // into every child process — the claude pty, its Bash tool, any nested shell. A direct
28
+ // `node --test <file>` run there inherits those vars and sails through the explicit-value fast
29
+ // paths below, handing the test process the REAL user directories; test fixtures/cleanup then
30
+ // delete real user data (confirmed 2026-07-12: a pty-manager fixture's finally-rmSync wiped the
31
+ // user's global system_prompt/ model prompts). Policy: a test process may only ever target
32
+ // disposable temp directories. This helper decides whether an explicit dir qualifies.
33
+ function isDisposableTmpPath(p) {
34
+ const roots = new Set();
35
+ const t = resolve(tmpdir());
36
+ roots.add(t);
37
+ try { roots.add(realpathSync(t)); } catch { /* keep the unresolved form */ }
38
+ if (process.platform !== 'win32') { roots.add('/tmp'); roots.add('/private/tmp'); }
39
+ const forms = new Set([resolve(p)]);
40
+ try { forms.add(realpathSync(resolve(p))); } catch { /* path may not exist yet */ }
41
+ for (const f of forms) {
42
+ for (const r of roots) {
43
+ if (f === r || f.startsWith(r + sep)) return true;
44
+ }
45
+ }
46
+ return false;
47
+ }
48
+
25
49
  export function getClaudeConfigDir() {
26
50
  const envDir = process.env.CLAUDE_CONFIG_DIR;
27
51
  if (envDir && typeof envDir === 'string' && envDir.trim()) {
28
52
  const raw = envDir.trim();
29
- return raw.startsWith('~/') ? join(homedir(), raw.slice(2)) : resolve(raw);
53
+ const resolved = raw.startsWith('~/') ? join(homedir(), raw.slice(2)) : resolve(raw);
54
+ // ████ L1d: in test context an explicit CLAUDE_CONFIG_DIR must still be a throwaway dir —
55
+ // an inherited real config dir would re-open the 2026-06-06 updater CACHE_DIR hole. ████
56
+ if (process.env.NODE_TEST_CONTEXT && !isDisposableTmpPath(resolved)) {
57
+ console.warn(`[findcc] L1d test-isolation barrier: CLAUDE_CONFIG_DIR="${raw}" is not under the OS temp dir — forcing a private guard config dir (tests may only target disposable temp dirs)`);
58
+ return join(tmpdir(), 'cc-viewer-test', `guard-cfg-${process.pid}-${threadId}`);
59
+ }
60
+ return resolved;
30
61
  }
31
62
  // ████████ Test isolation barrier L1b — DO NOT REMOVE (prevents data-loss regressions, 2026-06-06) ████████
32
63
  // CCV_LOG_DIR=tmp only redirects LOG_DIR; it does not cover this function: updater.js's
@@ -69,7 +100,21 @@ function resolveLogDir() {
69
100
  return join(tmpdir(), 'cc-viewer-test', `${process.pid}-${threadId}`);
70
101
  }
71
102
  const expanded = raw.startsWith('~/') ? join(homedir(), raw.slice(2)) : raw;
72
- return resolve(expanded);
103
+ const resolved = resolve(expanded);
104
+ // ████████ Test isolation barrier L1c — DO NOT REMOVE (2026-07-12 data loss) ████████
105
+ // The NODE_TEST_CONTEXT guard below only covers the no-CCV_LOG_DIR case; this explicit-value
106
+ // fast path used to accept ANY inherited dir. Inside a ccv-hosted shell CCV_LOG_DIR points at
107
+ // the real ~/.claude/cc-viewer, so a direct `node --test <file>` there resolved LOG_DIR to
108
+ // real user data — test cleanup then deleted it (2026-07-12: the user's global system_prompt/
109
+ // entries were wiped this way, twice). Tests may only target disposable temp dirs: anything
110
+ // outside the OS temp root is forced to the private guard dir. Use CCV_LOG_DIR=tmp or a
111
+ // mkdtemp path in tests. Unit test: test/logdir-test-guard.test.js.
112
+ if (process.env.NODE_TEST_CONTEXT && !isDisposableTmpPath(resolved)) {
113
+ console.warn(`[findcc] L1c test-isolation barrier: CCV_LOG_DIR="${raw}" is not under the OS temp dir — forcing a private guard LOG_DIR (tests may only target disposable temp dirs; use CCV_LOG_DIR=tmp or a mkdtemp path)`);
114
+ return join(tmpdir(), 'cc-viewer-test', `guard-${process.pid}-${threadId}`);
115
+ }
116
+ // ████████████████████████████████████████████████████████████████████████████
117
+ return resolved;
73
118
  }
74
119
  // Test isolation barrier: in node:test environment (NODE_TEST_CONTEXT is auto-injected by the
75
120
  // test runner and inherited by spawned child processes via spread env), if CCV_LOG_DIR is not
@@ -93,13 +138,13 @@ export let LOG_DIR = resolveLogDir();
93
138
  * will automatically see the updated value.
94
139
  */
95
140
  export function setLogDir(dir) {
96
- if (!dir || typeof dir !== 'string') return;
141
+ if (!dir || typeof dir !== 'string') return false;
97
142
  const raw = dir.trim();
98
- if (!raw) return;
143
+ if (!raw) return false;
99
144
  const resolved = resolve(raw.startsWith('~/') ? join(homedir(), raw.slice(2)) : raw);
100
145
  // Security: restrict to home directory or /tmp to prevent writes to system directories
101
146
  const home = homedir();
102
- if (!resolved.startsWith(home) && !resolved.startsWith('/tmp/')) return;
147
+ if (!resolved.startsWith(home) && !resolved.startsWith('/tmp/')) return false;
103
148
  LOG_DIR = resolved;
104
149
  // workspace registry file location changes with LOG_DIR; the allowlist cache (including
105
150
  // registered workspaces) must be invalidated. Lazy import to avoid circular dependency
@@ -107,6 +152,7 @@ export function setLogDir(dir) {
107
152
  import('./server/lib/file-access-policy.js')
108
153
  .then(m => m.bumpWorkspacesVersion?.())
109
154
  .catch(() => { /* CLI-only entry points may not have the policy module loaded; side-effect free */ });
155
+ return true;
110
156
  }
111
157
 
112
158
  // npm package name candidates (priority order)
@@ -234,10 +280,16 @@ export function resolveNpmClaudePath() {
234
280
  const match = normReal.match(/(.*node_modules\/@[^/]+\/[^/]+)\//);
235
281
  if (match) {
236
282
  const packageDir = match[1];
283
+ // Claude Code 1.x: cli.js (injected with interceptor)
237
284
  const cliPath = join(packageDir, CLI_ENTRY);
238
285
  if (existsSync(cliPath)) {
239
286
  return cliPath;
240
287
  }
288
+ // Claude Code 2.x layout ships no cli.js — the package's bin/ binary IS
289
+ // the entry point; without this fallback the resolver returns null and
290
+ // the launch dies with no claude at all.
291
+ const binPath = findNpmBinFallback(packageDir);
292
+ if (binPath) return binPath;
241
293
  }
242
294
  }
243
295
  } catch { }
@@ -255,16 +307,36 @@ export function resolveNpmClaudePath() {
255
307
  const globalRoot = getGlobalNodeModulesDir();
256
308
  if (globalRoot) {
257
309
  for (const packageName of PACKAGES) {
258
- const cliPath = join(globalRoot, packageName, CLI_ENTRY);
310
+ const pkgDir = join(globalRoot, packageName);
311
+ // Claude Code 1.x: cli.js
312
+ const cliPath = join(pkgDir, CLI_ENTRY);
259
313
  if (existsSync(cliPath)) {
260
314
  return cliPath;
261
315
  }
316
+ // Claude Code 2.x: no cli.js, fall back to the package's bin/ entry.
317
+ const binPath = findNpmBinFallback(pkgDir);
318
+ if (binPath) return binPath;
262
319
  }
263
320
  }
264
321
 
265
322
  return null;
266
323
  }
267
324
 
325
+ // Claude Code 2.x npm packages ship no cli.js — bin/claude(.exe) is the entry point.
326
+ // `claude.exe` is checked FIRST even on POSIX: 2.1.x's package.json maps
327
+ // `bin: {"claude": "bin/claude.exe"}` on every platform (verified on macOS 2.1.207,
328
+ // where bin/claude.exe is the platform-native binary hard-linked into bin/).
329
+ // Returns the first existing bin candidate under `<pkgDir>/bin`, or null (1.x layout).
330
+ function findNpmBinFallback(pkgDir) {
331
+ const binDir = join(pkgDir, 'bin');
332
+ const names = process.platform === 'win32' ? ['claude.exe', 'claude.cmd'] : ['claude.exe', 'claude'];
333
+ for (const name of names) {
334
+ const binPath = join(binDir, name);
335
+ if (existsSync(binPath)) return binPath;
336
+ }
337
+ return null;
338
+ }
339
+
268
340
  /**
269
341
  * 从 which/where 的原始输出中挑出能直接 CreateProcess/exec 的候选行。
270
342
  * Windows 的 `where` 会列出 PATH 中全部同名匹配——npm 全局安装时第一行往往是给
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-viewer",
3
- "version": "1.6.345",
3
+ "version": "1.7.0",
4
4
  "description": "Claude Code logging, visualization, and management toolkit — launch a web viewer alongside Claude Code with full request/response tracing, proxy, and mobile support",
5
5
  "license": "MIT",
6
6
  "main": "server.js",
@@ -20,6 +20,7 @@
20
20
  "build:sourcemap": "CCV_SOURCEMAP=1 node build.js",
21
21
  "start": "node server.js",
22
22
  "lint:control-bytes": "node scripts/check-no-control-bytes.js",
23
+ "verify:wire-v2": "node cli.js verify",
23
24
  "pretest": "npm run lint:control-bytes",
24
25
  "pretest:cli": "npm run lint:control-bytes",
25
26
  "pretest:coverage": "npm run lint:control-bytes",
package/server/i18n.js CHANGED
@@ -223,8 +223,8 @@ const i18nData = {
223
223
  "uk": "\nДля видалення виконайте: ccv --uninstall"
224
224
  },
225
225
  "cli.help": {
226
- "zh": "CC Viewer CLI — Claude Code 的 Web Viewer 前端\n\n用法: ccv [选项] [命令] [提示词]\n\n默认启动交互式会话 + Web Viewer,所有参数透传给 claude。\n\n参数:\n prompt 你的提示词\n\nCCV 专属选项:\n -SDK, --sdk 使用 Agent SDK 模式(无终端,结构化交互)\n -logger 安装/修复 Claude Code Hook\n --uninstall 移除 CC Viewer 集成\n --d --dangerously-skip-permissions 的快捷方式\n --ad --allow-dangerously-skip-permissions 的快捷方式\n --log-dir <path> 自定义 JSONL 日志存储目录\n --no-open 启动时不自动打开浏览器\n --user-name <name> 自定义显示名称\n --user-avatar <path|url> 自定义头像图片(文件路径或 URL)\n --usePassword[=<pwd>] 启用密码登录(远程访问需密码或 token;不带值则随机生成 6 位(2 字母 + 4 数字))\n --pid[=<name>] 锁定实例 id:让本 ccv 的「仅展示当前会话」视图按实例隔离(下次用同一 id 复用)\n\nClaude 选项(透传):\n -c, --continue 继续当前目录最近的对话\n -r, --resume [value] 通过会话 ID 恢复对话,或打开交互式选择器\n -p, --print 打印响应后退出(适用于管道)\n -d, --debug [filter] 启用调试模式\n -w, --worktree [name] 为此会话创建新的 git worktree\n --model <model> 当前会话使用的模型\n --effort <level> 努力级别 (low, medium, high, max)\n --dangerously-skip-permissions 跳过所有权限检查\n --allowedTools <tools...> 允许的工具列表\n --disallowedTools <tools...> 禁止的工具列表\n --system-prompt <prompt> 会话系统提示词\n --append-system-prompt <prompt> 追加系统提示词\n --mcp-config <configs...> 加载 MCP 服务器配置\n --permission-mode <mode> 权限模式\n --add-dir <directories...> 额外允许访问的目录\n --max-budget-usd <amount> API 调用最大预算(仅 --print)\n --output-format <format> 输出格式(仅 --print): text, json, stream-json\n --json-schema <schema> 结构化输出的 JSON Schema\n --verbose 详细模式\n -h, --help 显示帮助\n -v, --version 显示版本\n\n 更多选项请参考: claude --help\n\n示例:\n ccv 启动交互模式\n ccv -c 继续上次对话\n ccv -r 恢复对话\n ccv -p \"hello\" 打印模式\n ccv --model opus 指定模型\n ccv --d 跳过权限确认(快捷方式)\n ccv --ad 允许跳过权限(不立即启用,可通过 Shift+Tab 切换)\n ccv -c --model sonnet --effort max 组合使用\n ccv -SDK Agent SDK 模式(无终端)\n ccv -SDK --d SDK 模式 + 跳过权限\n ccv -logger 安装/修复 Hook\n ccv --log-dir ~/my-logs 使用自定义日志目录\n ccv --no-open 启动时不打开浏览器\n ccv --user-name \"Alice\" --user-avatar ./avatar.png 自定义用户身份\n ccv --log-dir /tmp/test --no-open 组合 CCV 选项",
227
- "en": "CC Viewer CLI — Web Viewer frontend for Claude Code\n\nUsage: ccv [options] [command] [prompt]\n\nStarts an interactive session with Web Viewer by default. All args are passed\nthrough to claude.\n\nArguments:\n prompt Your prompt\n\nCCV Options:\n -SDK, --sdk Use Agent SDK mode (no terminal, structured interaction)\n -logger Install/repair Claude Code hook\n --uninstall Remove CC Viewer integration\n --d Shortcut for --dangerously-skip-permissions\n --ad Shortcut for --allow-dangerously-skip-permissions\n --log-dir <path> Custom JSONL log storage directory\n --no-open Don't auto-open browser on startup\n --user-name <name> Custom display name for this session\n --user-avatar <path|url> Custom avatar image (file path or URL)\n --usePassword[=<pwd>] Enable password login (remote needs password or token; bare flag → random 6-char: 2 letters + 4 digits)\n --pid[=<name>] Lock an instance id so this ccv's current-session view is isolated per instance (reuse the same id next run)\n\nClaude Options (passed through):\n -c, --continue Continue the most recent conversation\n -r, --resume [value] Resume a conversation by session ID, or open picker\n -p, --print Print response and exit (useful for pipes)\n -d, --debug [filter] Enable debug mode\n -w, --worktree [name] Create a new git worktree for this session\n --model <model> Model for the current session\n --effort <level> Effort level (low, medium, high, max)\n --dangerously-skip-permissions Bypass all permission checks\n --allowedTools <tools...> Allowed tool names\n --disallowedTools <tools...> Disallowed tool names\n --system-prompt <prompt> System prompt for the session\n --append-system-prompt <prompt> Append to default system prompt\n --mcp-config <configs...> Load MCP servers from JSON files or strings\n --permission-mode <mode> Permission mode for the session\n --add-dir <directories...> Additional directories to allow access to\n --max-budget-usd <amount> Max dollar amount for API calls (--print only)\n --output-format <format> Output format (--print only): text, json, stream-json\n --json-schema <schema> JSON Schema for structured output\n --verbose Enable verbose mode\n -h, --help Show help\n -v, --version Show version\n\n For all options, see: claude --help\n\nExamples:\n ccv Start interactive mode\n ccv -c Continue last conversation\n ccv -r Resume a conversation\n ccv -p \"hello\" Print mode\n ccv --model opus Specify model\n ccv --d Skip permission prompts (shortcut)\n ccv --ad Allow bypass mode (not active, toggle via Shift+Tab)\n ccv -c --model sonnet --effort max Combine options\n ccv -SDK Agent SDK mode (no terminal)\n ccv -SDK --d SDK mode + bypass permissions\n ccv -logger Install/repair hook\n ccv --log-dir ~/my-logs Use custom log directory\n ccv --no-open Start without opening browser\n ccv --user-name \"Alice\" --user-avatar ./avatar.png Custom user profile\n ccv --log-dir /tmp/test --no-open Combine CCV options",
226
+ "zh": "CC Viewer CLI — Claude Code 的 Web Viewer 前端\n\n用法: ccv [选项] [命令] [提示词]\n\n默认启动交互式会话 + Web Viewer,所有参数透传给 claude。\n\n参数:\n prompt 你的提示词\n\nCCV 专属选项:\n -SDK, --sdk 使用 Agent SDK 模式(无终端,结构化交互)\n -logger 安装/修复 Claude Code Hook\n --uninstall 移除 CC Viewer 集成\n --d --dangerously-skip-permissions 的快捷方式\n --ad --allow-dangerously-skip-permissions 的快捷方式\n --log-dir <path> 自定义 JSONL 日志存储目录\n --no-open 启动时不自动打开浏览器\n --user-name <name> 自定义显示名称\n --user-avatar <path|url> 自定义头像图片(文件路径或 URL)\n --usePassword[=<pwd>] 启用密码登录(远程访问需密码或 token;不带值则随机生成 6 位(2 字母 + 4 数字))\n\nClaude 选项(透传):\n -c, --continue 继续当前目录最近的对话\n -r, --resume [value] 通过会话 ID 恢复对话,或打开交互式选择器\n -p, --print 打印响应后退出(适用于管道)\n -d, --debug [filter] 启用调试模式\n -w, --worktree [name] 为此会话创建新的 git worktree\n --model <model> 当前会话使用的模型\n --effort <level> 努力级别 (low, medium, high, max)\n --dangerously-skip-permissions 跳过所有权限检查\n --allowedTools <tools...> 允许的工具列表\n --disallowedTools <tools...> 禁止的工具列表\n --system-prompt <prompt> 会话系统提示词\n --append-system-prompt <prompt> 追加系统提示词\n --mcp-config <configs...> 加载 MCP 服务器配置\n --permission-mode <mode> 权限模式\n --add-dir <directories...> 额外允许访问的目录\n --max-budget-usd <amount> API 调用最大预算(仅 --print)\n --output-format <format> 输出格式(仅 --print): text, json, stream-json\n --json-schema <schema> 结构化输出的 JSON Schema\n --verbose 详细模式\n -h, --help 显示帮助\n -v, --version 显示版本\n\n 更多选项请参考: claude --help\n\n示例:\n ccv 启动交互模式\n ccv -c 继续上次对话\n ccv -r 恢复对话\n ccv -p \"hello\" 打印模式\n ccv --model opus 指定模型\n ccv --d 跳过权限确认(快捷方式)\n ccv --ad 允许跳过权限(不立即启用,可通过 Shift+Tab 切换)\n ccv -c --model sonnet --effort max 组合使用\n ccv -SDK Agent SDK 模式(无终端)\n ccv -SDK --d SDK 模式 + 跳过权限\n ccv -logger 安装/修复 Hook\n ccv --log-dir ~/my-logs 使用自定义日志目录\n ccv --no-open 启动时不打开浏览器\n ccv --user-name \"Alice\" --user-avatar ./avatar.png 自定义用户身份\n ccv --log-dir /tmp/test --no-open 组合 CCV 选项",
227
+ "en": "CC Viewer CLI — Web Viewer frontend for Claude Code\n\nUsage: ccv [options] [command] [prompt]\n\nStarts an interactive session with Web Viewer by default. All args are passed\nthrough to claude.\n\nArguments:\n prompt Your prompt\n\nCCV Options:\n -SDK, --sdk Use Agent SDK mode (no terminal, structured interaction)\n -logger Install/repair Claude Code hook\n --uninstall Remove CC Viewer integration\n --d Shortcut for --dangerously-skip-permissions\n --ad Shortcut for --allow-dangerously-skip-permissions\n --log-dir <path> Custom JSONL log storage directory\n --no-open Don't auto-open browser on startup\n --user-name <name> Custom display name for this session\n --user-avatar <path|url> Custom avatar image (file path or URL)\n --usePassword[=<pwd>] Enable password login (remote needs password or token; bare flag → random 6-char: 2 letters + 4 digits)\n\nClaude Options (passed through):\n -c, --continue Continue the most recent conversation\n -r, --resume [value] Resume a conversation by session ID, or open picker\n -p, --print Print response and exit (useful for pipes)\n -d, --debug [filter] Enable debug mode\n -w, --worktree [name] Create a new git worktree for this session\n --model <model> Model for the current session\n --effort <level> Effort level (low, medium, high, max)\n --dangerously-skip-permissions Bypass all permission checks\n --allowedTools <tools...> Allowed tool names\n --disallowedTools <tools...> Disallowed tool names\n --system-prompt <prompt> System prompt for the session\n --append-system-prompt <prompt> Append to default system prompt\n --mcp-config <configs...> Load MCP servers from JSON files or strings\n --permission-mode <mode> Permission mode for the session\n --add-dir <directories...> Additional directories to allow access to\n --max-budget-usd <amount> Max dollar amount for API calls (--print only)\n --output-format <format> Output format (--print only): text, json, stream-json\n --json-schema <schema> JSON Schema for structured output\n --verbose Enable verbose mode\n -h, --help Show help\n -v, --version Show version\n\n For all options, see: claude --help\n\nExamples:\n ccv Start interactive mode\n ccv -c Continue last conversation\n ccv -r Resume a conversation\n ccv -p \"hello\" Print mode\n ccv --model opus Specify model\n ccv --d Skip permission prompts (shortcut)\n ccv --ad Allow bypass mode (not active, toggle via Shift+Tab)\n ccv -c --model sonnet --effort max Combine options\n ccv -SDK Agent SDK mode (no terminal)\n ccv -SDK --d SDK mode + bypass permissions\n ccv -logger Install/repair hook\n ccv --log-dir ~/my-logs Use custom log directory\n ccv --no-open Start without opening browser\n ccv --user-name \"Alice\" --user-avatar ./avatar.png Custom user profile\n ccv --log-dir /tmp/test --no-open Combine CCV options",
228
228
  "zh-TW": "CC Viewer CLI — Claude Code 的 Web Viewer 前端\n\n用法: ccv [選項] [命令] [提示詞]\n\n預設啟動互動式會話 + Web Viewer,所有參數透傳給 claude。\n\n參數:\n prompt 你的提示詞\n\nCCV 專屬選項:\n -SDK, --sdk 使用 Agent SDK 模式(無終端,結構化互動)\n -logger 安裝/修復 Claude Code Hook\n --uninstall 移除 CC Viewer 整合\n --d --dangerously-skip-permissions 的快捷方式\n --ad --allow-dangerously-skip-permissions 的快捷方式\n --log-dir <path> 自訂 JSONL 日誌儲存目錄\n --no-open 啟動時不自動開啟瀏覽器\n --user-name <name> 自訂顯示名稱\n --user-avatar <path|url> 自訂頭像圖片(檔案路徑或 URL)\n\nClaude 選項(透傳):\n -c, --continue 繼續當前目錄最近的對話\n -r, --resume [value] 透過會話 ID 恢復對話,或開啟互動式選擇器\n -p, --print 列印回應後退出(適用於管道)\n -d, --debug [filter] 啟用除錯模式\n -w, --worktree [name] 為此會話建立新的 git worktree\n --model <model> 當前會話使用的模型\n --effort <level> 努力級別 (low, medium, high, max)\n --dangerously-skip-permissions 跳過所有權限檢查\n --allowedTools <tools...> 允許的工具列表\n --disallowedTools <tools...> 禁止的工具列表\n --system-prompt <prompt> 會話系統提示詞\n --append-system-prompt <prompt> 追加系統提示詞\n --mcp-config <configs...> 載入 MCP 伺服器設定\n --permission-mode <mode> 權限模式\n --add-dir <directories...> 額外允許存取的目錄\n --max-budget-usd <amount> API 呼叫最大預算(僅 --print)\n --output-format <format> 輸出格式(僅 --print): text, json, stream-json\n --json-schema <schema> 結構化輸出的 JSON Schema\n --verbose 詳細模式\n -h, --help 顯示說明\n -v, --version 顯示版本\n\n 更多選項請參考: claude --help\n\n範例:\n ccv 啟動互動模式\n ccv -c 繼續上次對話\n ccv -r 恢復對話\n ccv -p \"hello\" 列印模式\n ccv --model opus 指定模型\n ccv --d 跳過權限確認(快捷方式)\n ccv --ad 允許跳過權限(不立即啟用,可透過 Shift+Tab 切換)\n ccv -c --model sonnet --effort max 組合使用\n ccv -SDK Agent SDK 模式(無終端)\n ccv -SDK --d SDK 模式 + 跳過權限\n ccv -logger 安裝/修復 Hook\n ccv --log-dir ~/my-logs 使用自訂日誌目錄\n ccv --no-open 啟動時不開啟瀏覽器\n ccv --user-name \"Alice\" --user-avatar ./avatar.png 自訂使用者身份\n ccv --log-dir /tmp/test --no-open 組合 CCV 選項"
229
229
  },
230
230
  "cli.cMode.notFound": {
@@ -2355,26 +2355,6 @@ const i18nData = {
2355
2355
  "tr": "Araç Kullanım İstatistikleri",
2356
2356
  "uk": "Статистика використання інструментів"
2357
2357
  },
2358
- "ui.mergeLogs": {
2359
- "zh": "合并日志",
2360
- "en": "Merge Logs",
2361
- "zh-TW": "合併日誌",
2362
- "ko": "로그 병합",
2363
- "ja": "ログを結合",
2364
- "de": "Logs zusammenführen",
2365
- "es": "Fusionar logs",
2366
- "fr": "Fusionner les logs",
2367
- "it": "Unisci log",
2368
- "da": "Flet logs",
2369
- "pl": "Scal logi",
2370
- "ru": "Объединить логи",
2371
- "ar": "دمج السجلات",
2372
- "no": "Slå sammen logger",
2373
- "pt-BR": "Mesclar logs",
2374
- "th": "รวมบันทึก",
2375
- "tr": "Logları birleştir",
2376
- "uk": "Об'єднати логи"
2377
- },
2378
2358
  "ui.openLog": {
2379
2359
  "zh": "打开",
2380
2360
  "en": "Open",
@@ -2395,66 +2375,6 @@ const i18nData = {
2395
2375
  "tr": "Aç",
2396
2376
  "uk": "Відкрити"
2397
2377
  },
2398
- "ui.mergeNotConsecutive": {
2399
- "zh": "请选择连续的日志文件",
2400
- "en": "Please select consecutive log files",
2401
- "zh-TW": "請選擇連續的日誌檔案",
2402
- "ko": "연속된 로그 파일을 선택하세요",
2403
- "ja": "連続したログファイルを選択してください",
2404
- "de": "Bitte wählen Sie aufeinanderfolgende Log-Dateien",
2405
- "es": "Seleccione archivos de log consecutivos",
2406
- "fr": "Veuillez sélectionner des fichiers de log consécutifs",
2407
- "it": "Seleziona file di log consecutivi",
2408
- "da": "Vælg venligst sammenhængende logfiler",
2409
- "pl": "Wybierz kolejne pliki logów",
2410
- "ru": "Выберите последовательные файлы логов",
2411
- "ar": "يرجى اختيار ملفات سجلات متتالية",
2412
- "no": "Velg sammenhengende loggfiler",
2413
- "pt-BR": "Selecione arquivos de log consecutivos",
2414
- "th": "กรุณาเลือกไฟล์บันทึกที่ต่อเนื่องกัน",
2415
- "tr": "Lütfen ardışık log dosyalarını seçin",
2416
- "uk": "Виберіть послідовні файли логів"
2417
- },
2418
- "ui.mergeTooLarge": {
2419
- "zh": "选中的日志文件总大小超过 400MB",
2420
- "en": "Selected log files exceed 400MB",
2421
- "zh-TW": "選中的日誌檔案總大小超過 400MB",
2422
- "ko": "선택한 로그 파일의 총 크기가 400MB를 초과합니다",
2423
- "ja": "選択したログファイルの合計サイズが 400MB を超えています",
2424
- "de": "Ausgewählte Log-Dateien überschreiten 400MB",
2425
- "es": "Los archivos de log seleccionados superan los 400MB",
2426
- "fr": "Les fichiers de log sélectionnés dépassent 400 Mo",
2427
- "it": "I file di log selezionati superano i 400MB",
2428
- "da": "Valgte logfiler overstiger 400MB",
2429
- "pl": "Wybrane pliki logów przekraczają 400MB",
2430
- "ru": "Выбранные файлы логов превышают 400МБ",
2431
- "ar": "ملفات السجلات المحددة تتجاوز 400 ميجابايت",
2432
- "no": "Valgte loggfiler overstiger 400MB",
2433
- "pt-BR": "Arquivos de log selecionados excedem 400MB",
2434
- "th": "ไฟล์บันทึกที่เลือกมีขนาดรวมเกิน 400MB",
2435
- "tr": "Seçilen log dosyaları 400MB'ı aşıyor",
2436
- "uk": "Вибрані файли логів перевищують 400МБ"
2437
- },
2438
- "ui.mergeSuccess": {
2439
- "zh": "日志合并成功",
2440
- "en": "Logs merged successfully",
2441
- "zh-TW": "日誌合併成功",
2442
- "ko": "로그 병합 성공",
2443
- "ja": "ログの結合に成功しました",
2444
- "de": "Logs erfolgreich zusammengeführt",
2445
- "es": "Logs fusionados correctamente",
2446
- "fr": "Logs fusionnés avec succès",
2447
- "it": "Log uniti con successo",
2448
- "da": "Logs flettet",
2449
- "pl": "Logi scalone pomyślnie",
2450
- "ru": "Логи успешно объединены",
2451
- "ar": "تم دمج السجلات بنجاح",
2452
- "no": "Logger slått sammen",
2453
- "pt-BR": "Logs mesclados com sucesso",
2454
- "th": "รวมบันทึกสำเร็จ",
2455
- "tr": "Loglar başarıyla birleştirildi",
2456
- "uk": "Логи успішно об'єднано"
2457
- },
2458
2378
  "ui.askQuestion": {
2459
2379
  "zh": "提问",
2460
2380
  "en": "Question",
@@ -2935,65 +2855,25 @@ const i18nData = {
2935
2855
  "tr": "Hata: --im bir platform kimliği gerektirir (örn. dingtalk)",
2936
2856
  "uk": "Помилка: --im потребує id платформи (напр. dingtalk)"
2937
2857
  },
2938
- "cli.pidInvalid": {
2939
- "zh": "错误: --pid 需要一个非空的实例名(如 --pid=alpha)",
2940
- "en": "Error: --pid requires a non-empty instance name (e.g. --pid=alpha)",
2941
- "zh-TW": "錯誤: --pid 需要一個非空的實例名稱(如 --pid=alpha)",
2942
- "ko": "오류: --pid에는 비어 있지 않은 인스턴스 이름이 필요합니다 (예: --pid=alpha)",
2943
- "ja": "エラー: --pid には空でないインスタンス名が必要です (例: --pid=alpha)",
2944
- "de": "Fehler: --pid erfordert einen nicht leeren Instanznamen (z. B. --pid=alpha)",
2945
- "es": "Error: --pid requiere un nombre de instancia no vacío (p. ej. --pid=alpha)",
2946
- "fr": "Erreur : --pid nécessite un nom d'instance non vide (p. ex. --pid=alpha)",
2947
- "it": "Errore: --pid richiede un nome di istanza non vuoto (es. --pid=alpha)",
2948
- "da": "Fejl: --pid kræver et ikke-tomt instansnavn (f.eks. --pid=alpha)",
2949
- "pl": "Błąd: --pid wymaga niepustej nazwy instancji (np. --pid=alpha)",
2950
- "ru": "Ошибка: --pid требует непустого имени экземпляра (например, --pid=alpha)",
2951
- "ar": "خطأ: --pid يتطلب اسم نسخة غير فارغ (مثل --pid=alpha)",
2952
- "no": "Feil: --pid krever et ikke-tomt instansnavn (f.eks. --pid=alpha)",
2953
- "pt-BR": "Erro: --pid requer um nome de instância não vazio (ex.: --pid=alpha)",
2954
- "th": "ข้อผิดพลาด: --pid ต้องการชื่ออินสแตนซ์ที่ไม่ว่าง (เช่น --pid=alpha)",
2955
- "tr": "Hata: --pid boş olmayan bir örnek adı gerektirir (örn. --pid=alpha)",
2956
- "uk": "Помилка: --pid потребує непорожнього імені екземпляра (напр. --pid=alpha)"
2957
- },
2958
- "cli.instanceId": {
2959
- "zh": "实例 PID: {id}",
2960
- "en": "Instance PID: {id}",
2961
- "zh-TW": "實例 PID: {id}",
2962
- "ko": "인스턴스 PID: {id}",
2963
- "ja": "インスタンス PID: {id}",
2964
- "de": "Instanz-PID: {id}",
2965
- "es": "PID de instancia: {id}",
2966
- "fr": "PID d'instance : {id}",
2967
- "it": "PID istanza: {id}",
2968
- "da": "Instans-PID: {id}",
2969
- "pl": "PID instancji: {id}",
2970
- "ru": "PID экземпляра: {id}",
2971
- "ar": "PID النسخة: {id}",
2972
- "no": "Instans-PID: {id}",
2973
- "pt-BR": "PID da instância: {id}",
2974
- "th": "PID อินสแตนซ์: {id}",
2975
- "tr": "Örnek PID: {id}",
2976
- "uk": "PID екземпляра: {id}"
2977
- },
2978
- "cli.instanceHistory": {
2979
- "zh": "该项目历史 PID: {ids}",
2980
- "en": "Past PIDs for this project: {ids}",
2981
- "zh-TW": "此專案歷史 PID: {ids}",
2982
- "ko": "이 프로젝트의 이전 PID: {ids}",
2983
- "ja": "このプロジェクトの過去の PID: {ids}",
2984
- "de": "Frühere PIDs für dieses Projekt: {ids}",
2985
- "es": "PIDs anteriores de este proyecto: {ids}",
2986
- "fr": "PID précédents pour ce projet : {ids}",
2987
- "it": "PID precedenti per questo progetto: {ids}",
2988
- "da": "Tidligere PID'er for dette projekt: {ids}",
2989
- "pl": "Poprzednie PID-y tego projektu: {ids}",
2990
- "ru": "Прежние PID этого проекта: {ids}",
2991
- "ar": "معرّفات PID السابقة لهذا المشروع: {ids}",
2992
- "no": "Tidligere PID-er for dette prosjektet: {ids}",
2993
- "pt-BR": "PIDs anteriores deste projeto: {ids}",
2994
- "th": "PID ก่อนหน้าของโปรเจกต์นี้: {ids}",
2995
- "tr": "Bu projenin önceki PID'leri: {ids}",
2996
- "uk": "Попередні PID цього проєкту: {ids}"
2858
+ "cli.v1LogsFound": {
2859
+ "zh": "发现 {count} 个项目存在未迁移的旧格式日志,可运行 ccv convert <项目> 或 ccv convert --all 迁移",
2860
+ "en": "{count} project(s) have unmigrated legacy logs run `ccv convert <project>` or `ccv convert --all`",
2861
+ "zh-TW": "發現 {count} 個專案存在未遷移的舊格式日誌,可執行 ccv convert <專案> 或 ccv convert --all 遷移",
2862
+ "ko": "{count}개 프로젝트에 마이그레이션되지 않은 이전 형식 로그가 있습니다. `ccv convert <프로젝트>` 또는 `ccv convert --all`을 실행하세요",
2863
+ "ja": "{count} 件のプロジェクトに未移行の旧形式ログがあります。`ccv convert <project>` または `ccv convert --all` を実行してください",
2864
+ "de": "{count} Projekt(e) haben nicht migrierte alte Logs `ccv convert <Projekt>` oder `ccv convert --all` ausführen",
2865
+ "es": "{count} proyecto(s) tienen registros antiguos sin migrar ejecute `ccv convert <proyecto>` o `ccv convert --all`",
2866
+ "fr": "{count} projet(s) ont des journaux anciens non migrés exécutez `ccv convert <projet>` ou `ccv convert --all`",
2867
+ "it": "{count} progetto/i con log in formato precedente non migrati esegui `ccv convert <progetto>` o `ccv convert --all`",
2868
+ "da": "{count} projekt(er) har ikke-migrerede ældre logfiler — kør `ccv convert <projekt>` eller `ccv convert --all`",
2869
+ "pl": "{count} projekt(ów) ma niezmigrowane starsze logi uruchom `ccv convert <projekt>` lub `ccv convert --all`",
2870
+ "ru": "В {count} проект(ах) есть неперенесённые логи старого формата — выполните `ccv convert <проект>` или `ccv convert --all`",
2871
+ "ar": "{count} مشروع(ات) بها سجلات قديمة غير مُرحّلة شغّل `ccv convert <project>` أو `ccv convert --all`",
2872
+ "no": "{count} prosjekt(er) har umigrerte eldre logger kjør `ccv convert <prosjekt>` eller `ccv convert --all`",
2873
+ "pt-BR": "{count} projeto(s) têm logs antigos não migrados execute `ccv convert <projeto>` ou `ccv convert --all`",
2874
+ "th": "มี {count} โปรเจกต์ที่มีบันทึกรูปแบบเก่ายังไม่ย้าย รัน `ccv convert <project>` หรือ `ccv convert --all`",
2875
+ "tr": "{count} projede taşınmamış eski günlükler var `ccv convert <proje>` veya `ccv convert --all` çalıştırın",
2876
+ "uk": "{count} проєкт(ів) мають неперенесені старі логи виконайте `ccv convert <проєкт>` або `ccv convert --all`"
2997
2877
  },
2998
2878
  "cli.imUnknownPlatform": {
2999
2879
  "zh": "错误: 未知的 IM 平台: {id}",