@i14ks/ccv 0.12.2 → 0.13.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 (27) hide show
  1. package/bundle/ccv.mjs +210 -77
  2. package/package.json +1 -1
  3. package/web/assets/{codemirror-D11GWMsB.js → codemirror-DAgbW3uz.js} +1 -1
  4. package/web/assets/{index-Iwl1w0_V.js → index-1j2ILa-W.js} +1 -1
  5. package/web/assets/{index-J2jvoYvL.js → index-BI8B49yl.js} +1 -1
  6. package/web/assets/{index-Bw0U50QY.js → index-BO3Em_j_.js} +1 -1
  7. package/web/assets/{index-CmCj0IE6.js → index-BTNpZcr3.js} +1 -1
  8. package/web/assets/{index-BIhkivIg.js → index-B_KTK-Ue.js} +1 -1
  9. package/web/assets/{index-4S0cl_O2.js → index-Bjh8yEB4.js} +1 -1
  10. package/web/assets/{index-D57ZXpBD.js → index-C0igZOa9.js} +1 -1
  11. package/web/assets/{index-CZF-u-hv.js → index-C11bbOpw.js} +1 -1
  12. package/web/assets/{index-xSo7_4Td.js → index-CBTNRCgC.js} +1 -1
  13. package/web/assets/{index-09MC_4AF.js → index-CFzKsiua.js} +1 -1
  14. package/web/assets/{index-Dn5EQ5YI.js → index-CIpZI228.js} +1 -1
  15. package/web/assets/index-CWCyiPjz.js +132 -0
  16. package/web/assets/{index-Czu0PbLS.js → index-Cd-2HW_i.js} +1 -1
  17. package/web/assets/{index-BDuAzkOQ.js → index-CquqZqWN.js} +1 -1
  18. package/web/assets/{index-BSEf83Uc.js → index-CzYpffog.js} +1 -1
  19. package/web/assets/{index-DO1FY4r1.js → index-D-pPtz2I.js} +1 -1
  20. package/web/assets/{index-DdCJlB8h.js → index-DDJ5NYOk.js} +1 -1
  21. package/web/assets/{index-Bg0VD211.js → index-DPG5pWPG.js} +1 -1
  22. package/web/assets/{index-DABElhgt.js → index-DmfDW_VA.js} +1 -1
  23. package/web/assets/{index-ZstREAcb.js → index-XkZY-Wpw.js} +1 -1
  24. package/web/assets/index-s-BhjU3m.css +1 -0
  25. package/web/index.html +2 -2
  26. package/web/assets/index-9Toz507b.js +0 -132
  27. package/web/assets/index-D5y0Nk74.css +0 -1
package/bundle/ccv.mjs CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  // dist/ccv.js
4
4
  import { spawn as spawn2 } from "node:child_process";
5
- import { readFile as readFile14 } from "node:fs/promises";
6
- import path19 from "node:path";
5
+ import { readFile as readFile15 } from "node:fs/promises";
6
+ import path20 from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
8
  import { parseArgs } from "node:util";
9
9
 
@@ -145,9 +145,9 @@ function envDefaults() {
145
145
 
146
146
  // ../host/dist/server.js
147
147
  import { createServer } from "node:http";
148
- import { createReadStream, existsSync as existsSync3, statSync as statSync3 } from "node:fs";
149
- import path18 from "node:path";
150
- import { homedir as homedir5 } from "node:os";
148
+ import { createReadStream, existsSync as existsSync4, statSync as statSync3 } from "node:fs";
149
+ import path19 from "node:path";
150
+ import { homedir as homedir6 } from "node:os";
151
151
  import { randomUUID as randomUUID4 } from "node:crypto";
152
152
  import { WebSocketServer } from "ws";
153
153
  import { renameSession as renameSession2 } from "@anthropic-ai/claude-agent-sdk";
@@ -164,6 +164,8 @@ function workspaceIdFromPath(absolutePath) {
164
164
  // ../host/dist/session.js
165
165
  import { EventEmitter } from "node:events";
166
166
  import { randomUUID } from "node:crypto";
167
+ import { existsSync, writeFileSync } from "node:fs";
168
+ import { homedir as homedir3 } from "node:os";
167
169
  import path5 from "node:path";
168
170
  import { query, renameSession } from "@anthropic-ai/claude-agent-sdk";
169
171
 
@@ -1109,6 +1111,7 @@ function previewAnnotations(questions, answers) {
1109
1111
  return annotations;
1110
1112
  }
1111
1113
  var USAGE_MIN_INTERVAL_MS = 2e4;
1114
+ var USAGE_TURN_INTERVAL_MS = 12e4;
1112
1115
  var CONTEXT_MIN_INTERVAL_MS = 5e3;
1113
1116
  var ClaudeSession = class extends EventEmitter {
1114
1117
  id;
@@ -1864,6 +1867,7 @@ var ClaudeSession = class extends EventEmitter {
1864
1867
  const raw = await q.usage_EXPERIMENTAL_MAY_CHANGE_DO_NOT_RELY_ON_THIS_API_YET();
1865
1868
  this.#usage = toUsageState(raw);
1866
1869
  this.emit("usage", this.#usage);
1870
+ writeFinisherSnapshot(this.#usage, this.#claudeSessionId, this.cwd);
1867
1871
  } catch {
1868
1872
  } finally {
1869
1873
  this.#usageAt = Date.now();
@@ -2172,6 +2176,8 @@ var ClaudeSession = class extends EventEmitter {
2172
2176
  break;
2173
2177
  case "user":
2174
2178
  this.#handleToolResults(message);
2179
+ if (Date.now() - this.#usageAt >= USAGE_TURN_INTERVAL_MS)
2180
+ void this.refreshUsage();
2175
2181
  break;
2176
2182
  case "stream_event":
2177
2183
  this.#handleStreamEvent(message);
@@ -2291,7 +2297,7 @@ var ClaudeSession = class extends EventEmitter {
2291
2297
  this.#capabilities = {
2292
2298
  ...this.#capabilities ?? this.#baseCapabilities(),
2293
2299
  commands: init.commands.map(toSlashCommand),
2294
- models: init.models.map((m) => ({
2300
+ models: init.models.filter((m) => isPlainModel(m.value)).map((m) => ({
2295
2301
  value: m.value,
2296
2302
  resolvedModel: m.resolvedModel ?? null,
2297
2303
  displayName: m.displayName,
@@ -2399,6 +2405,9 @@ function isApiErrorMessage(message) {
2399
2405
  function apiErrorReason(status) {
2400
2406
  return status === void 0 ? "\u0437\u0430\u043F\u0440\u043E\u0441 \u043A API \u043D\u0435 \u0434\u043E\u0448\u0451\u043B" : `API \u043E\u0442\u0432\u0435\u0442\u0438\u043B ${status}`;
2401
2407
  }
2408
+ function isPlainModel(value) {
2409
+ return /^[a-z]+$/.test(value);
2410
+ }
2402
2411
  function toSlashCommand(command) {
2403
2412
  return {
2404
2413
  name: command.name,
@@ -2418,6 +2427,27 @@ function toUsageState(raw) {
2418
2427
  fetchedAt: Date.now()
2419
2428
  };
2420
2429
  }
2430
+ function writeFinisherSnapshot(usage, sessionId, cwd) {
2431
+ if (!usage.available)
2432
+ return;
2433
+ const dir = path5.join(homedir3(), ".claude", "finisher");
2434
+ if (!existsSync(dir))
2435
+ return;
2436
+ const snapshot = {
2437
+ updated_at: Date.now(),
2438
+ session_id: sessionId,
2439
+ cwd,
2440
+ rate_limits: {
2441
+ five_hour: usage.fiveHour?.utilization ?? null,
2442
+ seven_day: usage.sevenDay?.utilization ?? null,
2443
+ spend_limit: null
2444
+ }
2445
+ };
2446
+ try {
2447
+ writeFileSync(path5.join(dir, "usage-cache.json"), JSON.stringify(snapshot, null, 2));
2448
+ } catch {
2449
+ }
2450
+ }
2421
2451
  function toUsageWindow(raw) {
2422
2452
  if (!raw || typeof raw.utilization !== "number")
2423
2453
  return null;
@@ -3018,9 +3048,9 @@ function parsePatch(raw) {
3018
3048
  body = "";
3019
3049
  }
3020
3050
  total += body.length;
3021
- const path20 = status === "deleted" ? oldPath : newPath || oldPath;
3051
+ const path21 = status === "deleted" ? oldPath : newPath || oldPath;
3022
3052
  files.push({
3023
- path: path20,
3053
+ path: path21,
3024
3054
  oldPath: status === "renamed" ? oldPath : "",
3025
3055
  status,
3026
3056
  added: count(body, "+"),
@@ -3032,10 +3062,10 @@ function parsePatch(raw) {
3032
3062
  return { files, truncated };
3033
3063
  }
3034
3064
  function stripPrefix(value) {
3035
- const path20 = unquote(value.trim());
3036
- if (path20 === "/dev/null")
3065
+ const path21 = unquote(value.trim());
3066
+ if (path21 === "/dev/null")
3037
3067
  return "";
3038
- return path20.replace(/^[ab]\//, "");
3068
+ return path21.replace(/^[ab]\//, "");
3039
3069
  }
3040
3070
  function count(body, mark) {
3041
3071
  let n = 0;
@@ -3277,7 +3307,7 @@ function describe3(error) {
3277
3307
  }
3278
3308
 
3279
3309
  // ../host/dist/finder.js
3280
- import { existsSync, statSync } from "node:fs";
3310
+ import { existsSync as existsSync2, statSync } from "node:fs";
3281
3311
  import { readdir as readdir2 } from "node:fs/promises";
3282
3312
  import os2 from "node:os";
3283
3313
  import path8 from "node:path";
@@ -3465,7 +3495,7 @@ var ProjectFinder = class {
3465
3495
  }
3466
3496
  };
3467
3497
  function usable(dir) {
3468
- return !contains(os2.tmpdir(), dir) && existsSync(dir);
3498
+ return !contains(os2.tmpdir(), dir) && existsSync2(dir);
3469
3499
  }
3470
3500
  function tokens(query2) {
3471
3501
  return query2.toLowerCase().split(/[^a-z0-9а-яё]+/i).filter(Boolean);
@@ -3562,7 +3592,7 @@ function fromText(text) {
3562
3592
  }
3563
3593
 
3564
3594
  // ../host/dist/workspaces.js
3565
- import { existsSync as existsSync2, statSync as statSync2 } from "node:fs";
3595
+ import { existsSync as existsSync3, statSync as statSync2 } from "node:fs";
3566
3596
  import { mkdir as mkdir3, readFile as readFile6, writeFile as writeFile4 } from "node:fs/promises";
3567
3597
  import path9 from "node:path";
3568
3598
  import { listSessions as listSessions2 } from "@anthropic-ai/claude-agent-sdk";
@@ -3586,7 +3616,7 @@ var WorkspaceRegistry = class {
3586
3616
  if (isEntry(item))
3587
3617
  return item;
3588
3618
  return null;
3589
- }).filter((e) => e !== null && existsSync2(e.path));
3619
+ }).filter((e) => e !== null && existsSync3(e.path));
3590
3620
  } catch {
3591
3621
  this.#entries = [];
3592
3622
  }
@@ -3609,7 +3639,7 @@ var WorkspaceRegistry = class {
3609
3639
  }
3610
3640
  async open(rawPath) {
3611
3641
  const absolute = path9.resolve(rawPath);
3612
- if (!existsSync2(absolute)) {
3642
+ if (!existsSync3(absolute)) {
3613
3643
  throw new Error(`\u041A\u0430\u0442\u0430\u043B\u043E\u0433 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D: ${absolute}`);
3614
3644
  }
3615
3645
  const existing = this.#entries.find((e) => e.path === absolute);
@@ -3676,14 +3706,14 @@ var WorkspaceRegistry = class {
3676
3706
  const name = nameRaw.trim();
3677
3707
  requireName(name);
3678
3708
  const parent = path9.resolve(parentRaw.trim());
3679
- if (!existsSync2(parent)) {
3709
+ if (!existsSync3(parent)) {
3680
3710
  throw new Error(`\u041A\u0430\u0442\u0430\u043B\u043E\u0433 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D: ${parent}`);
3681
3711
  }
3682
3712
  if (!statSync2(parent).isDirectory()) {
3683
3713
  throw new Error(`\u042D\u0442\u043E \u043D\u0435 \u043A\u0430\u0442\u0430\u043B\u043E\u0433: ${parent}`);
3684
3714
  }
3685
3715
  const target = path9.join(parent, name);
3686
- if (existsSync2(target)) {
3716
+ if (existsSync3(target)) {
3687
3717
  throw new Error(`\u0423\u0436\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442: ${target} \u2014 \u043E\u0442\u043A\u0440\u043E\u0439\u0442\u0435 \u0435\u0433\u043E \u043A\u0430\u043A \u043F\u0440\u043E\u0435\u043A\u0442`);
3688
3718
  }
3689
3719
  await mkdir3(target);
@@ -3765,7 +3795,7 @@ function toInfo(entry) {
3765
3795
  id: workspaceIdFromPath(absolute),
3766
3796
  path: absolute,
3767
3797
  name: path9.basename(absolute) || absolute,
3768
- hasClaudeMd: existsSync2(path9.join(absolute, "CLAUDE.md")),
3798
+ hasClaudeMd: existsSync3(path9.join(absolute, "CLAUDE.md")),
3769
3799
  ...archivedAt ? { archivedAt } : {}
3770
3800
  };
3771
3801
  }
@@ -3981,9 +4011,79 @@ function isRecord2(value) {
3981
4011
  return typeof value === "object" && value !== null && typeof value.workspaceId === "string" && typeof value.at === "number";
3982
4012
  }
3983
4013
 
3984
- // ../host/dist/folders.js
4014
+ // ../host/dist/order.js
3985
4015
  import { mkdir as mkdir6, readFile as readFile9, writeFile as writeFile7 } from "node:fs/promises";
3986
4016
  import path12 from "node:path";
4017
+ var ListOrderStore = class {
4018
+ #order = emptyOrder();
4019
+ #storePath;
4020
+ constructor(dataDir) {
4021
+ this.#storePath = path12.join(dataDir, "list-order.json");
4022
+ }
4023
+ async load() {
4024
+ try {
4025
+ const raw = await readFile9(this.#storePath, "utf8");
4026
+ this.#order = sanitize(JSON.parse(raw));
4027
+ } catch {
4028
+ this.#order = emptyOrder();
4029
+ }
4030
+ }
4031
+ get current() {
4032
+ return {
4033
+ workspaces: [...this.#order.workspaces],
4034
+ sessions: Object.fromEntries(Object.entries(this.#order.sessions).map(([k, v]) => [k, [...v]])),
4035
+ pinned: [...this.#order.pinned],
4036
+ live: [...this.#order.live]
4037
+ };
4038
+ }
4039
+ /**
4040
+ * Записать порядок одного списка. `ids === null` — сбросить его: список
4041
+ * вернётся к обычному порядку.
4042
+ */
4043
+ async set(scope, ids, workspaceId) {
4044
+ const clean = ids === null ? null : dedupe(ids);
4045
+ if (scope === "sessions") {
4046
+ if (!workspaceId)
4047
+ throw new Error("\u0414\u043B\u044F \u043F\u043E\u0440\u044F\u0434\u043A\u0430 \u0441\u0435\u0441\u0441\u0438\u0439 \u043D\u0443\u0436\u0435\u043D workspaceId");
4048
+ if (clean === null || clean.length === 0)
4049
+ delete this.#order.sessions[workspaceId];
4050
+ else
4051
+ this.#order.sessions[workspaceId] = clean;
4052
+ } else {
4053
+ this.#order[scope] = clean ?? [];
4054
+ }
4055
+ await mkdir6(path12.dirname(this.#storePath), { recursive: true });
4056
+ await writeFile7(this.#storePath, JSON.stringify(this.#order, null, 2), "utf8");
4057
+ }
4058
+ };
4059
+ function emptyOrder() {
4060
+ return { workspaces: [], sessions: {}, pinned: [], live: [] };
4061
+ }
4062
+ function dedupe(ids) {
4063
+ return [...new Set(ids.filter((id) => typeof id === "string" && id !== ""))];
4064
+ }
4065
+ function sanitize(value) {
4066
+ const result = emptyOrder();
4067
+ if (typeof value !== "object" || value === null)
4068
+ return result;
4069
+ const record = value;
4070
+ const list = (v) => Array.isArray(v) ? dedupe(v) : [];
4071
+ result.workspaces = list(record.workspaces);
4072
+ result.pinned = list(record.pinned);
4073
+ result.live = list(record.live);
4074
+ if (typeof record.sessions === "object" && record.sessions !== null) {
4075
+ for (const [workspaceId, ids] of Object.entries(record.sessions)) {
4076
+ const clean = list(ids);
4077
+ if (clean.length > 0)
4078
+ result.sessions[workspaceId] = clean;
4079
+ }
4080
+ }
4081
+ return result;
4082
+ }
4083
+
4084
+ // ../host/dist/folders.js
4085
+ import { mkdir as mkdir7, readFile as readFile10, writeFile as writeFile8 } from "node:fs/promises";
4086
+ import path13 from "node:path";
3987
4087
  import { randomUUID as randomUUID2 } from "node:crypto";
3988
4088
  var Folders = class {
3989
4089
  #workspaceFolders = /* @__PURE__ */ new Map();
@@ -3994,11 +4094,11 @@ var Folders = class {
3994
4094
  #pinnedAssignments = /* @__PURE__ */ new Map();
3995
4095
  #storePath;
3996
4096
  constructor(dataDir) {
3997
- this.#storePath = path12.join(dataDir, "folders.json");
4097
+ this.#storePath = path13.join(dataDir, "folders.json");
3998
4098
  }
3999
4099
  async load() {
4000
4100
  try {
4001
- const raw = await readFile9(this.#storePath, "utf8");
4101
+ const raw = await readFile10(this.#storePath, "utf8");
4002
4102
  const parsed = JSON.parse(raw);
4003
4103
  this.#workspaceFolders = new Map(Object.entries(parsed.workspaceFolders ?? {}).filter(([, r]) => isWorkspaceFolder(r)));
4004
4104
  this.#sessionFolders = new Map(Object.entries(parsed.sessionFolders ?? {}).filter(([, r]) => isSessionFolder(r)));
@@ -4181,8 +4281,8 @@ var Folders = class {
4181
4281
  sessionAssignments: Object.fromEntries(this.#sessionAssignments),
4182
4282
  pinnedAssignments: Object.fromEntries(this.#pinnedAssignments)
4183
4283
  };
4184
- await mkdir6(path12.dirname(this.#storePath), { recursive: true });
4185
- await writeFile7(this.#storePath, JSON.stringify(file, null, 2), "utf8");
4284
+ await mkdir7(path13.dirname(this.#storePath), { recursive: true });
4285
+ await writeFile8(this.#storePath, JSON.stringify(file, null, 2), "utf8");
4186
4286
  }
4187
4287
  };
4188
4288
  function isWorkspaceFolder(value) {
@@ -4196,9 +4296,9 @@ function isPinnedFolder(value) {
4196
4296
  }
4197
4297
 
4198
4298
  // ../host/dist/app-settings.js
4199
- import { mkdir as mkdir7, readFile as readFile10, writeFile as writeFile8 } from "node:fs/promises";
4200
- import { homedir as homedir3 } from "node:os";
4201
- import path13 from "node:path";
4299
+ import { mkdir as mkdir8, readFile as readFile11, writeFile as writeFile9 } from "node:fs/promises";
4300
+ import { homedir as homedir4 } from "node:os";
4301
+ import path14 from "node:path";
4202
4302
  var DEFAULT_AUTO_ARCHIVE_AFTER_MS = 2 * 24 * 60 * 60 * 1e3;
4203
4303
  var MIN_AUTO_ARCHIVE_AFTER_MS = 60 * 60 * 1e3;
4204
4304
  var MIN_AUTO_SAVE_AFTER_PAUSE_MS = 500;
@@ -4227,18 +4327,20 @@ var DEFAULT_APP_SETTINGS = {
4227
4327
  // Терминал по умолчанию закрыт — его включают, а не выключают: приложение,
4228
4328
  // открытое в браузере, не обязано с первого запуска уметь запускать на этой
4229
4329
  // машине что угодно.
4230
- terminalEnabled: false
4330
+ terminalEnabled: false,
4331
+ // Выключено по умолчанию: списки идут в том порядке, в каком их расставили.
4332
+ liveSessionsFirst: false
4231
4333
  };
4232
4334
  var AppSettingsStore = class {
4233
4335
  #settings = { ...DEFAULT_APP_SETTINGS };
4234
4336
  #storePath;
4235
4337
  constructor(dataDir) {
4236
- this.#storePath = path13.join(dataDir, "app-settings.json");
4338
+ this.#storePath = path14.join(dataDir, "app-settings.json");
4237
4339
  }
4238
4340
  async load() {
4239
4341
  try {
4240
- const raw = await readFile10(this.#storePath, "utf8");
4241
- this.#settings = sanitize(JSON.parse(raw), DEFAULT_APP_SETTINGS);
4342
+ const raw = await readFile11(this.#storePath, "utf8");
4343
+ this.#settings = sanitize2(JSON.parse(raw), DEFAULT_APP_SETTINGS);
4242
4344
  } catch {
4243
4345
  this.#settings = { ...DEFAULT_APP_SETTINGS };
4244
4346
  }
@@ -4255,14 +4357,14 @@ var AppSettingsStore = class {
4255
4357
  * всё равно получит в ответ то, что реально записано, и покажет именно это.
4256
4358
  */
4257
4359
  async update(patch) {
4258
- const next = sanitize(patch, this.#settings);
4360
+ const next = sanitize2(patch, this.#settings);
4259
4361
  this.#settings = next;
4260
- await mkdir7(path13.dirname(this.#storePath), { recursive: true });
4261
- await writeFile8(this.#storePath, JSON.stringify(next, null, 2), "utf8");
4362
+ await mkdir8(path14.dirname(this.#storePath), { recursive: true });
4363
+ await writeFile9(this.#storePath, JSON.stringify(next, null, 2), "utf8");
4262
4364
  return this.current;
4263
4365
  }
4264
4366
  };
4265
- function sanitize(value, fallback) {
4367
+ function sanitize2(value, fallback) {
4266
4368
  const result = { ...fallback };
4267
4369
  if (typeof value !== "object" || value === null)
4268
4370
  return result;
@@ -4293,10 +4395,13 @@ function sanitize(value, fallback) {
4293
4395
  if (typeof record.terminalEnabled === "boolean") {
4294
4396
  result.terminalEnabled = record.terminalEnabled;
4295
4397
  }
4398
+ if (typeof record.liveSessionsFirst === "boolean") {
4399
+ result.liveSessionsFirst = record.liveSessionsFirst;
4400
+ }
4296
4401
  return result;
4297
4402
  }
4298
4403
  function claudeHome() {
4299
- return path13.join(homedir3(), ".claude");
4404
+ return path14.join(homedir4(), ".claude");
4300
4405
  }
4301
4406
  function writableRoots(mode, cwd, workspacePaths) {
4302
4407
  if (mode === "all")
@@ -4306,7 +4411,7 @@ function writableRoots(mode, cwd, workspacePaths) {
4306
4411
  return [...roots];
4307
4412
  for (const workspace of [cwd, ...workspacePaths]) {
4308
4413
  roots.add(workspace);
4309
- const parent = path13.dirname(workspace);
4414
+ const parent = path14.dirname(workspace);
4310
4415
  if (parent !== workspace)
4311
4416
  roots.add(parent);
4312
4417
  }
@@ -4314,9 +4419,9 @@ function writableRoots(mode, cwd, workspacePaths) {
4314
4419
  }
4315
4420
 
4316
4421
  // ../host/dist/plugins.js
4317
- import { readFile as readFile11 } from "node:fs/promises";
4318
- import { homedir as homedir4 } from "node:os";
4319
- import path14 from "node:path";
4422
+ import { readFile as readFile12 } from "node:fs/promises";
4423
+ import { homedir as homedir5 } from "node:os";
4424
+ import path15 from "node:path";
4320
4425
  var TIMEOUT_MS2 = 12e4;
4321
4426
  var SAFE_ARG = /^[A-Za-z0-9][A-Za-z0-9._@/-]*$/;
4322
4427
  var PluginCli = class {
@@ -4415,10 +4520,10 @@ async function readCatalog(markets, installed) {
4415
4520
  return lists.flat().sort((a, b) => a.name.localeCompare(b.name));
4416
4521
  }
4417
4522
  async function readMarketplace(market, installed) {
4418
- const root = market.installLocation || path14.join(homedir4(), ".claude", "plugins", "marketplaces", market.name);
4523
+ const root = market.installLocation || path15.join(homedir5(), ".claude", "plugins", "marketplaces", market.name);
4419
4524
  let manifest;
4420
4525
  try {
4421
- const text = await readFile11(path14.join(root, ".claude-plugin", "marketplace.json"), "utf8");
4526
+ const text = await readFile12(path15.join(root, ".claude-plugin", "marketplace.json"), "utf8");
4422
4527
  manifest = JSON.parse(text);
4423
4528
  } catch {
4424
4529
  return [];
@@ -4659,8 +4764,8 @@ function describe4(error) {
4659
4764
 
4660
4765
  // ../host/dist/relay-link.js
4661
4766
  import { EventEmitter as EventEmitter3 } from "node:events";
4662
- import { readFile as readFile12, writeFile as writeFile9, rm as rm3, mkdir as mkdir8 } from "node:fs/promises";
4663
- import path15 from "node:path";
4767
+ import { readFile as readFile13, writeFile as writeFile10, rm as rm3, mkdir as mkdir9 } from "node:fs/promises";
4768
+ import path16 from "node:path";
4664
4769
  import { hostname } from "node:os";
4665
4770
  import WebSocket from "ws";
4666
4771
 
@@ -4715,7 +4820,7 @@ var RelayLink = class extends EventEmitter3 {
4715
4820
  #log;
4716
4821
  constructor(options2) {
4717
4822
  super();
4718
- this.#file = path15.join(options2.dataDir, "relay.json");
4823
+ this.#file = path16.join(options2.dataDir, "relay.json");
4719
4824
  this.#defaultUrl = options2.defaultUrl.replace(/\/+$/, "");
4720
4825
  this.#log = options2.log ?? ((msg) => console.log(`[relay] ${msg}`));
4721
4826
  }
@@ -4796,7 +4901,7 @@ var RelayLink = class extends EventEmitter3 {
4796
4901
  }
4797
4902
  async #read() {
4798
4903
  try {
4799
- const raw = await readFile12(this.#file, "utf8");
4904
+ const raw = await readFile13(this.#file, "utf8");
4800
4905
  const parsed = JSON.parse(raw);
4801
4906
  if (typeof parsed !== "object" || parsed === null)
4802
4907
  return null;
@@ -4809,8 +4914,8 @@ var RelayLink = class extends EventEmitter3 {
4809
4914
  }
4810
4915
  }
4811
4916
  async #write(credentials) {
4812
- await mkdir8(path15.dirname(this.#file), { recursive: true });
4813
- await writeFile9(this.#file, `${JSON.stringify(credentials, null, 2)}
4917
+ await mkdir9(path16.dirname(this.#file), { recursive: true });
4918
+ await writeFile10(this.#file, `${JSON.stringify(credentials, null, 2)}
4814
4919
  `, {
4815
4920
  encoding: "utf8",
4816
4921
  mode: 384
@@ -5067,7 +5172,7 @@ function humanize(error) {
5067
5172
  // ../host/dist/terminal.js
5068
5173
  import { EventEmitter as EventEmitter4 } from "node:events";
5069
5174
  import { access as access2, constants as constants2 } from "node:fs/promises";
5070
- import path16 from "node:path";
5175
+ import path17 from "node:path";
5071
5176
  import { randomUUID as randomUUID3 } from "node:crypto";
5072
5177
  var SCROLLBACK_BYTES = 256 * 1024;
5073
5178
  var FLUSH_MS = 12;
@@ -5237,22 +5342,22 @@ async function defaultShell() {
5237
5342
  if (pwsh)
5238
5343
  return { file: pwsh, args: [], title: "pwsh" };
5239
5344
  const system = process.env.SystemRoot ?? "C:\\Windows";
5240
- const powershell = path16.join(system, "System32/WindowsPowerShell/v1.0/powershell.exe");
5345
+ const powershell = path17.join(system, "System32/WindowsPowerShell/v1.0/powershell.exe");
5241
5346
  if (await exists2(powershell))
5242
5347
  return { file: powershell, args: [], title: "powershell" };
5243
5348
  return { file: process.env.COMSPEC ?? "cmd.exe", args: [], title: "cmd" };
5244
5349
  }
5245
5350
  const shell = process.env.SHELL;
5246
5351
  if (shell && await exists2(shell))
5247
- return { file: shell, args: [], title: path16.basename(shell) };
5352
+ return { file: shell, args: [], title: path17.basename(shell) };
5248
5353
  if (await exists2("/bin/bash"))
5249
5354
  return { file: "/bin/bash", args: [], title: "bash" };
5250
5355
  return { file: "/bin/sh", args: [], title: "sh" };
5251
5356
  }
5252
5357
  async function findOnPath(name) {
5253
- const dirs = (process.env.PATH ?? "").split(path16.delimiter).filter(Boolean);
5358
+ const dirs = (process.env.PATH ?? "").split(path17.delimiter).filter(Boolean);
5254
5359
  for (const dir of dirs) {
5255
- const candidate = path16.join(dir, name);
5360
+ const candidate = path17.join(dir, name);
5256
5361
  if (await exists2(candidate))
5257
5362
  return candidate;
5258
5363
  }
@@ -5263,10 +5368,10 @@ function exists2(file) {
5263
5368
  }
5264
5369
 
5265
5370
  // ../host/dist/mcp-config.js
5266
- import { readFile as readFile13, writeFile as writeFile10, mkdir as mkdir9 } from "node:fs/promises";
5267
- import path17 from "node:path";
5371
+ import { readFile as readFile14, writeFile as writeFile11, mkdir as mkdir10 } from "node:fs/promises";
5372
+ import path18 from "node:path";
5268
5373
  var MCP_FILE = ".mcp.json";
5269
- var LOCAL_SETTINGS2 = path17.join(".claude", "settings.local.json");
5374
+ var LOCAL_SETTINGS2 = path18.join(".claude", "settings.local.json");
5270
5375
  var queues = /* @__PURE__ */ new Map();
5271
5376
  function serialize(cwd, work) {
5272
5377
  const previous = queues.get(cwd) ?? Promise.resolve();
@@ -5275,7 +5380,7 @@ function serialize(cwd, work) {
5275
5380
  return next;
5276
5381
  }
5277
5382
  async function readProjectServers(cwd) {
5278
- const file = await readJson(path17.join(cwd, MCP_FILE));
5383
+ const file = await readJson(path18.join(cwd, MCP_FILE));
5279
5384
  const servers = file?.mcpServers;
5280
5385
  if (!servers || typeof servers !== "object")
5281
5386
  return {};
@@ -5283,23 +5388,23 @@ async function readProjectServers(cwd) {
5283
5388
  }
5284
5389
  function addProjectServer(cwd, name, config) {
5285
5390
  return serialize(cwd, async () => {
5286
- const file = await readJson(path17.join(cwd, MCP_FILE)) ?? {};
5391
+ const file = await readJson(path18.join(cwd, MCP_FILE)) ?? {};
5287
5392
  const servers = file.mcpServers ?? {};
5288
5393
  servers[name] = config;
5289
5394
  file.mcpServers = servers;
5290
- await writeJson(path17.join(cwd, MCP_FILE), file);
5395
+ await writeJson(path18.join(cwd, MCP_FILE), file);
5291
5396
  await approve(cwd, name);
5292
5397
  });
5293
5398
  }
5294
5399
  function removeProjectServer(cwd, name) {
5295
5400
  return serialize(cwd, async () => {
5296
- const file = await readJson(path17.join(cwd, MCP_FILE));
5401
+ const file = await readJson(path18.join(cwd, MCP_FILE));
5297
5402
  const servers = file?.mcpServers;
5298
5403
  if (!servers || !(name in servers))
5299
5404
  return false;
5300
5405
  delete servers[name];
5301
- await writeJson(path17.join(cwd, MCP_FILE), file);
5302
- const settingsPath = path17.join(cwd, LOCAL_SETTINGS2);
5406
+ await writeJson(path18.join(cwd, MCP_FILE), file);
5407
+ const settingsPath = path18.join(cwd, LOCAL_SETTINGS2);
5303
5408
  const settings = await readJson(settingsPath);
5304
5409
  const enabled = settings?.enabledMcpjsonServers;
5305
5410
  if (settings && Array.isArray(enabled) && enabled.includes(name)) {
@@ -5310,7 +5415,7 @@ function removeProjectServer(cwd, name) {
5310
5415
  });
5311
5416
  }
5312
5417
  async function approve(cwd, name) {
5313
- const file = path17.join(cwd, LOCAL_SETTINGS2);
5418
+ const file = path18.join(cwd, LOCAL_SETTINGS2);
5314
5419
  const settings = await readJson(file) ?? {};
5315
5420
  const enabled = Array.isArray(settings.enabledMcpjsonServers) ? settings.enabledMcpjsonServers : [];
5316
5421
  if (!enabled.includes(name))
@@ -5349,21 +5454,21 @@ function describeConfig(config) {
5349
5454
  }
5350
5455
  async function readJson(file) {
5351
5456
  try {
5352
- const raw = await readFile13(file, "utf8");
5457
+ const raw = await readFile14(file, "utf8");
5353
5458
  const parsed = JSON.parse(raw);
5354
5459
  return parsed && typeof parsed === "object" ? parsed : null;
5355
5460
  } catch (error) {
5356
5461
  if (error.code === "ENOENT")
5357
5462
  return null;
5358
5463
  if (error instanceof SyntaxError) {
5359
- throw new Error(`\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0440\u0430\u0437\u043E\u0431\u0440\u0430\u0442\u044C ${path17.basename(file)}: ${error.message}`);
5464
+ throw new Error(`\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0440\u0430\u0437\u043E\u0431\u0440\u0430\u0442\u044C ${path18.basename(file)}: ${error.message}`);
5360
5465
  }
5361
5466
  throw error;
5362
5467
  }
5363
5468
  }
5364
5469
  async function writeJson(file, data) {
5365
- await mkdir9(path17.dirname(file), { recursive: true });
5366
- await writeFile10(file, `${JSON.stringify(data, null, 2)}
5470
+ await mkdir10(path18.dirname(file), { recursive: true });
5471
+ await writeFile11(file, `${JSON.stringify(data, null, 2)}
5367
5472
  `, "utf8");
5368
5473
  }
5369
5474
 
@@ -5377,6 +5482,7 @@ var HostServer = class {
5377
5482
  #workspaces;
5378
5483
  #archive;
5379
5484
  #pins;
5485
+ #order;
5380
5486
  #folders;
5381
5487
  #settings;
5382
5488
  #finder;
@@ -5415,6 +5521,7 @@ var HostServer = class {
5415
5521
  this.#workspaces = new WorkspaceRegistry(options2.dataDir);
5416
5522
  this.#archive = new SessionArchive(options2.dataDir);
5417
5523
  this.#pins = new SessionPins(options2.dataDir);
5524
+ this.#order = new ListOrderStore(options2.dataDir);
5418
5525
  this.#folders = new Folders(options2.dataDir);
5419
5526
  this.#settings = new AppSettingsStore(options2.dataDir);
5420
5527
  this.#finder = new ProjectFinder(this.#workspaces);
@@ -5433,6 +5540,7 @@ var HostServer = class {
5433
5540
  await this.#workspaces.load();
5434
5541
  await this.#archive.load();
5435
5542
  await this.#pins.load();
5543
+ await this.#order.load();
5436
5544
  await this.#folders.load();
5437
5545
  await this.#settings.load();
5438
5546
  await this.#github.load();
@@ -5616,6 +5724,8 @@ var HostServer = class {
5616
5724
  });
5617
5725
  this.#send(socket, { type: "workspace.list", ...this.#workspaceLists() });
5618
5726
  this.#sendFolders(socket);
5727
+ this.#send(socket, { type: "order.state", order: this.#order.current });
5728
+ this.#sendLive(socket);
5619
5729
  this.#auth.status().then((status) => this.#send(socket, { type: "auth.status", status })).catch(() => void 0);
5620
5730
  this.#send(socket, { type: "relay.state", state: this.#relay.state });
5621
5731
  return;
@@ -5731,6 +5841,13 @@ var HostServer = class {
5731
5841
  case "session.pins":
5732
5842
  await this.#sendPinned();
5733
5843
  return;
5844
+ case "order.get":
5845
+ this.#send(socket, { type: "order.state", order: this.#order.current });
5846
+ return;
5847
+ case "order.set":
5848
+ await this.#order.set(frame.scope, frame.ids, frame.workspaceId);
5849
+ this.#broadcast({ type: "order.state", order: this.#order.current });
5850
+ return;
5734
5851
  case "folder.create": {
5735
5852
  if (frame.scope === "session") {
5736
5853
  if (!frame.workspaceId)
@@ -6372,8 +6489,23 @@ var HostServer = class {
6372
6489
  await session.close(reason);
6373
6490
  this.#sessions.delete(sessionId);
6374
6491
  this.#broadcast({ type: "session.closed", sessionId, reason });
6492
+ this.#sendLive();
6375
6493
  return true;
6376
6494
  }
6495
+ /**
6496
+ * Живые сессии всех проектов — вкладке запущенных.
6497
+ *
6498
+ * Без `socket` — всем окнам: состав живых поменялся. Боковые сессии и сессия
6499
+ * поиска проекта в список не идут, их пользователь не заводил.
6500
+ */
6501
+ #sendLive(socket) {
6502
+ const sessions = [...this.#sessions.values()].filter((s) => s !== this.#finderSession).map((s) => s.summary()).filter((s) => !s.parentSessionId);
6503
+ const frame = { type: "session.live", sessions };
6504
+ if (socket)
6505
+ this.#send(socket, frame);
6506
+ else
6507
+ this.#broadcast(frame);
6508
+ }
6377
6509
  /**
6378
6510
  * Разослать список сессий проекта всем окнам.
6379
6511
  *
@@ -6435,6 +6567,7 @@ var HostServer = class {
6435
6567
  this.#wire(session);
6436
6568
  this.#sessions.set(session.id, session);
6437
6569
  session.start();
6570
+ this.#sendLive();
6438
6571
  this.#send(socket, { type: "session.summary", session: session.summary() });
6439
6572
  }
6440
6573
  /**
@@ -6548,7 +6681,7 @@ var HostServer = class {
6548
6681
  */
6549
6682
  #roots() {
6550
6683
  const roots = this.#finder.roots();
6551
- return roots.length > 0 ? roots : [homedir5()];
6684
+ return roots.length > 0 ? roots : [homedir6()];
6552
6685
  }
6553
6686
  /**
6554
6687
  * Где сессии этого каталога разрешено писать без вопроса.
@@ -6881,15 +7014,15 @@ var HostServer = class {
6881
7014
  }
6882
7015
  #serveStatic(req, res) {
6883
7016
  const webRoot = this.#options.webRoot;
6884
- if (!webRoot || !existsSync3(webRoot)) {
7017
+ if (!webRoot || !existsSync4(webRoot)) {
6885
7018
  res.writeHead(200, { "content-type": "text/plain; charset=utf-8" });
6886
7019
  res.end("CCV host is running. UI is served by the Vite dev server.\n");
6887
7020
  return;
6888
7021
  }
6889
7022
  const url2 = new URL(req.url ?? "/", "http://localhost");
6890
- const requested = path18.join(webRoot, path18.normalize(url2.pathname));
6891
- const target = requested.startsWith(webRoot) && existsSync3(requested) && statSync3(requested).isFile() ? requested : path18.join(webRoot, "index.html");
6892
- if (!existsSync3(target)) {
7023
+ const requested = path19.join(webRoot, path19.normalize(url2.pathname));
7024
+ const target = requested.startsWith(webRoot) && existsSync4(requested) && statSync3(requested).isFile() ? requested : path19.join(webRoot, "index.html");
7025
+ if (!existsSync4(target)) {
6893
7026
  res.writeHead(404).end("not found");
6894
7027
  return;
6895
7028
  }
@@ -6914,12 +7047,12 @@ function contentType(file) {
6914
7047
  ".svg": "image/svg+xml",
6915
7048
  ".woff2": "font/woff2"
6916
7049
  };
6917
- return types[path18.extname(file)] ?? "application/octet-stream";
7050
+ return types[path19.extname(file)] ?? "application/octet-stream";
6918
7051
  }
6919
7052
 
6920
7053
  // dist/ccv.js
6921
- var HERE = path19.dirname(fileURLToPath(import.meta.url));
6922
- var PACKAGE_ROOT = path19.resolve(HERE, "..");
7054
+ var HERE = path20.dirname(fileURLToPath(import.meta.url));
7055
+ var PACKAGE_ROOT = path20.resolve(HERE, "..");
6923
7056
  var USAGE = `ccv \u2014 Claude Code Visual: \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 \u0445\u043E\u0441\u0442 Claude Code \u0441 \u0438\u043D\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u043E\u043C \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435.
6924
7057
 
6925
7058
  \u0418\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u043D\u0438\u0435:
@@ -6986,7 +7119,7 @@ var server = new HostServer({
6986
7119
  dataDir: defaults.dataDir,
6987
7120
  // UI лежит в пакете рядом с бандлом — это и есть разница между установленной
6988
7121
  // утилитой и запуском из репозитория, где сборка живёт в `apps/web/dist`.
6989
- webRoot: process.env.CCV_WEB_ROOT ?? path19.join(PACKAGE_ROOT, "web"),
7122
+ webRoot: process.env.CCV_WEB_ROOT ?? path20.join(PACKAGE_ROOT, "web"),
6990
7123
  version,
6991
7124
  relayUrl: defaults.relayUrl
6992
7125
  });
@@ -7015,7 +7148,7 @@ for (const signal of ["SIGINT", "SIGTERM"]) {
7015
7148
  }
7016
7149
  async function packageVersion() {
7017
7150
  try {
7018
- const text = await readFile14(path19.join(PACKAGE_ROOT, "package.json"), "utf8");
7151
+ const text = await readFile15(path20.join(PACKAGE_ROOT, "package.json"), "utf8");
7019
7152
  return JSON.parse(text).version ?? "0.0.0";
7020
7153
  } catch {
7021
7154
  return "0.0.0";