@i14ks/ccv 0.12.3 → 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.
- package/bundle/ccv.mjs +206 -76
- package/package.json +1 -1
- package/web/assets/{codemirror-D11GWMsB.js → codemirror-DAgbW3uz.js} +1 -1
- package/web/assets/{index-Iwl1w0_V.js → index-1j2ILa-W.js} +1 -1
- package/web/assets/{index-J2jvoYvL.js → index-BI8B49yl.js} +1 -1
- package/web/assets/{index-Bw0U50QY.js → index-BO3Em_j_.js} +1 -1
- package/web/assets/{index-CmCj0IE6.js → index-BTNpZcr3.js} +1 -1
- package/web/assets/{index-BIhkivIg.js → index-B_KTK-Ue.js} +1 -1
- package/web/assets/{index-4S0cl_O2.js → index-Bjh8yEB4.js} +1 -1
- package/web/assets/{index-D57ZXpBD.js → index-C0igZOa9.js} +1 -1
- package/web/assets/{index-CZF-u-hv.js → index-C11bbOpw.js} +1 -1
- package/web/assets/{index-xSo7_4Td.js → index-CBTNRCgC.js} +1 -1
- package/web/assets/{index-09MC_4AF.js → index-CFzKsiua.js} +1 -1
- package/web/assets/{index-Dn5EQ5YI.js → index-CIpZI228.js} +1 -1
- package/web/assets/index-CWCyiPjz.js +132 -0
- package/web/assets/{index-Czu0PbLS.js → index-Cd-2HW_i.js} +1 -1
- package/web/assets/{index-BDuAzkOQ.js → index-CquqZqWN.js} +1 -1
- package/web/assets/{index-BSEf83Uc.js → index-CzYpffog.js} +1 -1
- package/web/assets/{index-DO1FY4r1.js → index-D-pPtz2I.js} +1 -1
- package/web/assets/{index-DdCJlB8h.js → index-DDJ5NYOk.js} +1 -1
- package/web/assets/{index-Bg0VD211.js → index-DPG5pWPG.js} +1 -1
- package/web/assets/{index-DABElhgt.js → index-DmfDW_VA.js} +1 -1
- package/web/assets/{index-ZstREAcb.js → index-XkZY-Wpw.js} +1 -1
- package/web/assets/index-s-BhjU3m.css +1 -0
- package/web/index.html +2 -2
- package/web/assets/index-9Toz507b.js +0 -132
- 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
|
|
6
|
-
import
|
|
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
|
|
149
|
-
import
|
|
150
|
-
import { homedir as
|
|
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);
|
|
@@ -2421,6 +2427,27 @@ function toUsageState(raw) {
|
|
|
2421
2427
|
fetchedAt: Date.now()
|
|
2422
2428
|
};
|
|
2423
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
|
+
}
|
|
2424
2451
|
function toUsageWindow(raw) {
|
|
2425
2452
|
if (!raw || typeof raw.utilization !== "number")
|
|
2426
2453
|
return null;
|
|
@@ -3021,9 +3048,9 @@ function parsePatch(raw) {
|
|
|
3021
3048
|
body = "";
|
|
3022
3049
|
}
|
|
3023
3050
|
total += body.length;
|
|
3024
|
-
const
|
|
3051
|
+
const path21 = status === "deleted" ? oldPath : newPath || oldPath;
|
|
3025
3052
|
files.push({
|
|
3026
|
-
path:
|
|
3053
|
+
path: path21,
|
|
3027
3054
|
oldPath: status === "renamed" ? oldPath : "",
|
|
3028
3055
|
status,
|
|
3029
3056
|
added: count(body, "+"),
|
|
@@ -3035,10 +3062,10 @@ function parsePatch(raw) {
|
|
|
3035
3062
|
return { files, truncated };
|
|
3036
3063
|
}
|
|
3037
3064
|
function stripPrefix(value) {
|
|
3038
|
-
const
|
|
3039
|
-
if (
|
|
3065
|
+
const path21 = unquote(value.trim());
|
|
3066
|
+
if (path21 === "/dev/null")
|
|
3040
3067
|
return "";
|
|
3041
|
-
return
|
|
3068
|
+
return path21.replace(/^[ab]\//, "");
|
|
3042
3069
|
}
|
|
3043
3070
|
function count(body, mark) {
|
|
3044
3071
|
let n = 0;
|
|
@@ -3280,7 +3307,7 @@ function describe3(error) {
|
|
|
3280
3307
|
}
|
|
3281
3308
|
|
|
3282
3309
|
// ../host/dist/finder.js
|
|
3283
|
-
import { existsSync, statSync } from "node:fs";
|
|
3310
|
+
import { existsSync as existsSync2, statSync } from "node:fs";
|
|
3284
3311
|
import { readdir as readdir2 } from "node:fs/promises";
|
|
3285
3312
|
import os2 from "node:os";
|
|
3286
3313
|
import path8 from "node:path";
|
|
@@ -3468,7 +3495,7 @@ var ProjectFinder = class {
|
|
|
3468
3495
|
}
|
|
3469
3496
|
};
|
|
3470
3497
|
function usable(dir) {
|
|
3471
|
-
return !contains(os2.tmpdir(), dir) &&
|
|
3498
|
+
return !contains(os2.tmpdir(), dir) && existsSync2(dir);
|
|
3472
3499
|
}
|
|
3473
3500
|
function tokens(query2) {
|
|
3474
3501
|
return query2.toLowerCase().split(/[^a-z0-9а-яё]+/i).filter(Boolean);
|
|
@@ -3565,7 +3592,7 @@ function fromText(text) {
|
|
|
3565
3592
|
}
|
|
3566
3593
|
|
|
3567
3594
|
// ../host/dist/workspaces.js
|
|
3568
|
-
import { existsSync as
|
|
3595
|
+
import { existsSync as existsSync3, statSync as statSync2 } from "node:fs";
|
|
3569
3596
|
import { mkdir as mkdir3, readFile as readFile6, writeFile as writeFile4 } from "node:fs/promises";
|
|
3570
3597
|
import path9 from "node:path";
|
|
3571
3598
|
import { listSessions as listSessions2 } from "@anthropic-ai/claude-agent-sdk";
|
|
@@ -3589,7 +3616,7 @@ var WorkspaceRegistry = class {
|
|
|
3589
3616
|
if (isEntry(item))
|
|
3590
3617
|
return item;
|
|
3591
3618
|
return null;
|
|
3592
|
-
}).filter((e) => e !== null &&
|
|
3619
|
+
}).filter((e) => e !== null && existsSync3(e.path));
|
|
3593
3620
|
} catch {
|
|
3594
3621
|
this.#entries = [];
|
|
3595
3622
|
}
|
|
@@ -3612,7 +3639,7 @@ var WorkspaceRegistry = class {
|
|
|
3612
3639
|
}
|
|
3613
3640
|
async open(rawPath) {
|
|
3614
3641
|
const absolute = path9.resolve(rawPath);
|
|
3615
|
-
if (!
|
|
3642
|
+
if (!existsSync3(absolute)) {
|
|
3616
3643
|
throw new Error(`\u041A\u0430\u0442\u0430\u043B\u043E\u0433 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D: ${absolute}`);
|
|
3617
3644
|
}
|
|
3618
3645
|
const existing = this.#entries.find((e) => e.path === absolute);
|
|
@@ -3679,14 +3706,14 @@ var WorkspaceRegistry = class {
|
|
|
3679
3706
|
const name = nameRaw.trim();
|
|
3680
3707
|
requireName(name);
|
|
3681
3708
|
const parent = path9.resolve(parentRaw.trim());
|
|
3682
|
-
if (!
|
|
3709
|
+
if (!existsSync3(parent)) {
|
|
3683
3710
|
throw new Error(`\u041A\u0430\u0442\u0430\u043B\u043E\u0433 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D: ${parent}`);
|
|
3684
3711
|
}
|
|
3685
3712
|
if (!statSync2(parent).isDirectory()) {
|
|
3686
3713
|
throw new Error(`\u042D\u0442\u043E \u043D\u0435 \u043A\u0430\u0442\u0430\u043B\u043E\u0433: ${parent}`);
|
|
3687
3714
|
}
|
|
3688
3715
|
const target = path9.join(parent, name);
|
|
3689
|
-
if (
|
|
3716
|
+
if (existsSync3(target)) {
|
|
3690
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`);
|
|
3691
3718
|
}
|
|
3692
3719
|
await mkdir3(target);
|
|
@@ -3768,7 +3795,7 @@ function toInfo(entry) {
|
|
|
3768
3795
|
id: workspaceIdFromPath(absolute),
|
|
3769
3796
|
path: absolute,
|
|
3770
3797
|
name: path9.basename(absolute) || absolute,
|
|
3771
|
-
hasClaudeMd:
|
|
3798
|
+
hasClaudeMd: existsSync3(path9.join(absolute, "CLAUDE.md")),
|
|
3772
3799
|
...archivedAt ? { archivedAt } : {}
|
|
3773
3800
|
};
|
|
3774
3801
|
}
|
|
@@ -3984,9 +4011,79 @@ function isRecord2(value) {
|
|
|
3984
4011
|
return typeof value === "object" && value !== null && typeof value.workspaceId === "string" && typeof value.at === "number";
|
|
3985
4012
|
}
|
|
3986
4013
|
|
|
3987
|
-
// ../host/dist/
|
|
4014
|
+
// ../host/dist/order.js
|
|
3988
4015
|
import { mkdir as mkdir6, readFile as readFile9, writeFile as writeFile7 } from "node:fs/promises";
|
|
3989
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";
|
|
3990
4087
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
3991
4088
|
var Folders = class {
|
|
3992
4089
|
#workspaceFolders = /* @__PURE__ */ new Map();
|
|
@@ -3997,11 +4094,11 @@ var Folders = class {
|
|
|
3997
4094
|
#pinnedAssignments = /* @__PURE__ */ new Map();
|
|
3998
4095
|
#storePath;
|
|
3999
4096
|
constructor(dataDir) {
|
|
4000
|
-
this.#storePath =
|
|
4097
|
+
this.#storePath = path13.join(dataDir, "folders.json");
|
|
4001
4098
|
}
|
|
4002
4099
|
async load() {
|
|
4003
4100
|
try {
|
|
4004
|
-
const raw = await
|
|
4101
|
+
const raw = await readFile10(this.#storePath, "utf8");
|
|
4005
4102
|
const parsed = JSON.parse(raw);
|
|
4006
4103
|
this.#workspaceFolders = new Map(Object.entries(parsed.workspaceFolders ?? {}).filter(([, r]) => isWorkspaceFolder(r)));
|
|
4007
4104
|
this.#sessionFolders = new Map(Object.entries(parsed.sessionFolders ?? {}).filter(([, r]) => isSessionFolder(r)));
|
|
@@ -4184,8 +4281,8 @@ var Folders = class {
|
|
|
4184
4281
|
sessionAssignments: Object.fromEntries(this.#sessionAssignments),
|
|
4185
4282
|
pinnedAssignments: Object.fromEntries(this.#pinnedAssignments)
|
|
4186
4283
|
};
|
|
4187
|
-
await
|
|
4188
|
-
await
|
|
4284
|
+
await mkdir7(path13.dirname(this.#storePath), { recursive: true });
|
|
4285
|
+
await writeFile8(this.#storePath, JSON.stringify(file, null, 2), "utf8");
|
|
4189
4286
|
}
|
|
4190
4287
|
};
|
|
4191
4288
|
function isWorkspaceFolder(value) {
|
|
@@ -4199,9 +4296,9 @@ function isPinnedFolder(value) {
|
|
|
4199
4296
|
}
|
|
4200
4297
|
|
|
4201
4298
|
// ../host/dist/app-settings.js
|
|
4202
|
-
import { mkdir as
|
|
4203
|
-
import { homedir as
|
|
4204
|
-
import
|
|
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";
|
|
4205
4302
|
var DEFAULT_AUTO_ARCHIVE_AFTER_MS = 2 * 24 * 60 * 60 * 1e3;
|
|
4206
4303
|
var MIN_AUTO_ARCHIVE_AFTER_MS = 60 * 60 * 1e3;
|
|
4207
4304
|
var MIN_AUTO_SAVE_AFTER_PAUSE_MS = 500;
|
|
@@ -4230,18 +4327,20 @@ var DEFAULT_APP_SETTINGS = {
|
|
|
4230
4327
|
// Терминал по умолчанию закрыт — его включают, а не выключают: приложение,
|
|
4231
4328
|
// открытое в браузере, не обязано с первого запуска уметь запускать на этой
|
|
4232
4329
|
// машине что угодно.
|
|
4233
|
-
terminalEnabled: false
|
|
4330
|
+
terminalEnabled: false,
|
|
4331
|
+
// Выключено по умолчанию: списки идут в том порядке, в каком их расставили.
|
|
4332
|
+
liveSessionsFirst: false
|
|
4234
4333
|
};
|
|
4235
4334
|
var AppSettingsStore = class {
|
|
4236
4335
|
#settings = { ...DEFAULT_APP_SETTINGS };
|
|
4237
4336
|
#storePath;
|
|
4238
4337
|
constructor(dataDir) {
|
|
4239
|
-
this.#storePath =
|
|
4338
|
+
this.#storePath = path14.join(dataDir, "app-settings.json");
|
|
4240
4339
|
}
|
|
4241
4340
|
async load() {
|
|
4242
4341
|
try {
|
|
4243
|
-
const raw = await
|
|
4244
|
-
this.#settings =
|
|
4342
|
+
const raw = await readFile11(this.#storePath, "utf8");
|
|
4343
|
+
this.#settings = sanitize2(JSON.parse(raw), DEFAULT_APP_SETTINGS);
|
|
4245
4344
|
} catch {
|
|
4246
4345
|
this.#settings = { ...DEFAULT_APP_SETTINGS };
|
|
4247
4346
|
}
|
|
@@ -4258,14 +4357,14 @@ var AppSettingsStore = class {
|
|
|
4258
4357
|
* всё равно получит в ответ то, что реально записано, и покажет именно это.
|
|
4259
4358
|
*/
|
|
4260
4359
|
async update(patch) {
|
|
4261
|
-
const next =
|
|
4360
|
+
const next = sanitize2(patch, this.#settings);
|
|
4262
4361
|
this.#settings = next;
|
|
4263
|
-
await
|
|
4264
|
-
await
|
|
4362
|
+
await mkdir8(path14.dirname(this.#storePath), { recursive: true });
|
|
4363
|
+
await writeFile9(this.#storePath, JSON.stringify(next, null, 2), "utf8");
|
|
4265
4364
|
return this.current;
|
|
4266
4365
|
}
|
|
4267
4366
|
};
|
|
4268
|
-
function
|
|
4367
|
+
function sanitize2(value, fallback) {
|
|
4269
4368
|
const result = { ...fallback };
|
|
4270
4369
|
if (typeof value !== "object" || value === null)
|
|
4271
4370
|
return result;
|
|
@@ -4296,10 +4395,13 @@ function sanitize(value, fallback) {
|
|
|
4296
4395
|
if (typeof record.terminalEnabled === "boolean") {
|
|
4297
4396
|
result.terminalEnabled = record.terminalEnabled;
|
|
4298
4397
|
}
|
|
4398
|
+
if (typeof record.liveSessionsFirst === "boolean") {
|
|
4399
|
+
result.liveSessionsFirst = record.liveSessionsFirst;
|
|
4400
|
+
}
|
|
4299
4401
|
return result;
|
|
4300
4402
|
}
|
|
4301
4403
|
function claudeHome() {
|
|
4302
|
-
return
|
|
4404
|
+
return path14.join(homedir4(), ".claude");
|
|
4303
4405
|
}
|
|
4304
4406
|
function writableRoots(mode, cwd, workspacePaths) {
|
|
4305
4407
|
if (mode === "all")
|
|
@@ -4309,7 +4411,7 @@ function writableRoots(mode, cwd, workspacePaths) {
|
|
|
4309
4411
|
return [...roots];
|
|
4310
4412
|
for (const workspace of [cwd, ...workspacePaths]) {
|
|
4311
4413
|
roots.add(workspace);
|
|
4312
|
-
const parent =
|
|
4414
|
+
const parent = path14.dirname(workspace);
|
|
4313
4415
|
if (parent !== workspace)
|
|
4314
4416
|
roots.add(parent);
|
|
4315
4417
|
}
|
|
@@ -4317,9 +4419,9 @@ function writableRoots(mode, cwd, workspacePaths) {
|
|
|
4317
4419
|
}
|
|
4318
4420
|
|
|
4319
4421
|
// ../host/dist/plugins.js
|
|
4320
|
-
import { readFile as
|
|
4321
|
-
import { homedir as
|
|
4322
|
-
import
|
|
4422
|
+
import { readFile as readFile12 } from "node:fs/promises";
|
|
4423
|
+
import { homedir as homedir5 } from "node:os";
|
|
4424
|
+
import path15 from "node:path";
|
|
4323
4425
|
var TIMEOUT_MS2 = 12e4;
|
|
4324
4426
|
var SAFE_ARG = /^[A-Za-z0-9][A-Za-z0-9._@/-]*$/;
|
|
4325
4427
|
var PluginCli = class {
|
|
@@ -4418,10 +4520,10 @@ async function readCatalog(markets, installed) {
|
|
|
4418
4520
|
return lists.flat().sort((a, b) => a.name.localeCompare(b.name));
|
|
4419
4521
|
}
|
|
4420
4522
|
async function readMarketplace(market, installed) {
|
|
4421
|
-
const root = market.installLocation ||
|
|
4523
|
+
const root = market.installLocation || path15.join(homedir5(), ".claude", "plugins", "marketplaces", market.name);
|
|
4422
4524
|
let manifest;
|
|
4423
4525
|
try {
|
|
4424
|
-
const text = await
|
|
4526
|
+
const text = await readFile12(path15.join(root, ".claude-plugin", "marketplace.json"), "utf8");
|
|
4425
4527
|
manifest = JSON.parse(text);
|
|
4426
4528
|
} catch {
|
|
4427
4529
|
return [];
|
|
@@ -4662,8 +4764,8 @@ function describe4(error) {
|
|
|
4662
4764
|
|
|
4663
4765
|
// ../host/dist/relay-link.js
|
|
4664
4766
|
import { EventEmitter as EventEmitter3 } from "node:events";
|
|
4665
|
-
import { readFile as
|
|
4666
|
-
import
|
|
4767
|
+
import { readFile as readFile13, writeFile as writeFile10, rm as rm3, mkdir as mkdir9 } from "node:fs/promises";
|
|
4768
|
+
import path16 from "node:path";
|
|
4667
4769
|
import { hostname } from "node:os";
|
|
4668
4770
|
import WebSocket from "ws";
|
|
4669
4771
|
|
|
@@ -4718,7 +4820,7 @@ var RelayLink = class extends EventEmitter3 {
|
|
|
4718
4820
|
#log;
|
|
4719
4821
|
constructor(options2) {
|
|
4720
4822
|
super();
|
|
4721
|
-
this.#file =
|
|
4823
|
+
this.#file = path16.join(options2.dataDir, "relay.json");
|
|
4722
4824
|
this.#defaultUrl = options2.defaultUrl.replace(/\/+$/, "");
|
|
4723
4825
|
this.#log = options2.log ?? ((msg) => console.log(`[relay] ${msg}`));
|
|
4724
4826
|
}
|
|
@@ -4799,7 +4901,7 @@ var RelayLink = class extends EventEmitter3 {
|
|
|
4799
4901
|
}
|
|
4800
4902
|
async #read() {
|
|
4801
4903
|
try {
|
|
4802
|
-
const raw = await
|
|
4904
|
+
const raw = await readFile13(this.#file, "utf8");
|
|
4803
4905
|
const parsed = JSON.parse(raw);
|
|
4804
4906
|
if (typeof parsed !== "object" || parsed === null)
|
|
4805
4907
|
return null;
|
|
@@ -4812,8 +4914,8 @@ var RelayLink = class extends EventEmitter3 {
|
|
|
4812
4914
|
}
|
|
4813
4915
|
}
|
|
4814
4916
|
async #write(credentials) {
|
|
4815
|
-
await
|
|
4816
|
-
await
|
|
4917
|
+
await mkdir9(path16.dirname(this.#file), { recursive: true });
|
|
4918
|
+
await writeFile10(this.#file, `${JSON.stringify(credentials, null, 2)}
|
|
4817
4919
|
`, {
|
|
4818
4920
|
encoding: "utf8",
|
|
4819
4921
|
mode: 384
|
|
@@ -5070,7 +5172,7 @@ function humanize(error) {
|
|
|
5070
5172
|
// ../host/dist/terminal.js
|
|
5071
5173
|
import { EventEmitter as EventEmitter4 } from "node:events";
|
|
5072
5174
|
import { access as access2, constants as constants2 } from "node:fs/promises";
|
|
5073
|
-
import
|
|
5175
|
+
import path17 from "node:path";
|
|
5074
5176
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
5075
5177
|
var SCROLLBACK_BYTES = 256 * 1024;
|
|
5076
5178
|
var FLUSH_MS = 12;
|
|
@@ -5240,22 +5342,22 @@ async function defaultShell() {
|
|
|
5240
5342
|
if (pwsh)
|
|
5241
5343
|
return { file: pwsh, args: [], title: "pwsh" };
|
|
5242
5344
|
const system = process.env.SystemRoot ?? "C:\\Windows";
|
|
5243
|
-
const powershell =
|
|
5345
|
+
const powershell = path17.join(system, "System32/WindowsPowerShell/v1.0/powershell.exe");
|
|
5244
5346
|
if (await exists2(powershell))
|
|
5245
5347
|
return { file: powershell, args: [], title: "powershell" };
|
|
5246
5348
|
return { file: process.env.COMSPEC ?? "cmd.exe", args: [], title: "cmd" };
|
|
5247
5349
|
}
|
|
5248
5350
|
const shell = process.env.SHELL;
|
|
5249
5351
|
if (shell && await exists2(shell))
|
|
5250
|
-
return { file: shell, args: [], title:
|
|
5352
|
+
return { file: shell, args: [], title: path17.basename(shell) };
|
|
5251
5353
|
if (await exists2("/bin/bash"))
|
|
5252
5354
|
return { file: "/bin/bash", args: [], title: "bash" };
|
|
5253
5355
|
return { file: "/bin/sh", args: [], title: "sh" };
|
|
5254
5356
|
}
|
|
5255
5357
|
async function findOnPath(name) {
|
|
5256
|
-
const dirs = (process.env.PATH ?? "").split(
|
|
5358
|
+
const dirs = (process.env.PATH ?? "").split(path17.delimiter).filter(Boolean);
|
|
5257
5359
|
for (const dir of dirs) {
|
|
5258
|
-
const candidate =
|
|
5360
|
+
const candidate = path17.join(dir, name);
|
|
5259
5361
|
if (await exists2(candidate))
|
|
5260
5362
|
return candidate;
|
|
5261
5363
|
}
|
|
@@ -5266,10 +5368,10 @@ function exists2(file) {
|
|
|
5266
5368
|
}
|
|
5267
5369
|
|
|
5268
5370
|
// ../host/dist/mcp-config.js
|
|
5269
|
-
import { readFile as
|
|
5270
|
-
import
|
|
5371
|
+
import { readFile as readFile14, writeFile as writeFile11, mkdir as mkdir10 } from "node:fs/promises";
|
|
5372
|
+
import path18 from "node:path";
|
|
5271
5373
|
var MCP_FILE = ".mcp.json";
|
|
5272
|
-
var LOCAL_SETTINGS2 =
|
|
5374
|
+
var LOCAL_SETTINGS2 = path18.join(".claude", "settings.local.json");
|
|
5273
5375
|
var queues = /* @__PURE__ */ new Map();
|
|
5274
5376
|
function serialize(cwd, work) {
|
|
5275
5377
|
const previous = queues.get(cwd) ?? Promise.resolve();
|
|
@@ -5278,7 +5380,7 @@ function serialize(cwd, work) {
|
|
|
5278
5380
|
return next;
|
|
5279
5381
|
}
|
|
5280
5382
|
async function readProjectServers(cwd) {
|
|
5281
|
-
const file = await readJson(
|
|
5383
|
+
const file = await readJson(path18.join(cwd, MCP_FILE));
|
|
5282
5384
|
const servers = file?.mcpServers;
|
|
5283
5385
|
if (!servers || typeof servers !== "object")
|
|
5284
5386
|
return {};
|
|
@@ -5286,23 +5388,23 @@ async function readProjectServers(cwd) {
|
|
|
5286
5388
|
}
|
|
5287
5389
|
function addProjectServer(cwd, name, config) {
|
|
5288
5390
|
return serialize(cwd, async () => {
|
|
5289
|
-
const file = await readJson(
|
|
5391
|
+
const file = await readJson(path18.join(cwd, MCP_FILE)) ?? {};
|
|
5290
5392
|
const servers = file.mcpServers ?? {};
|
|
5291
5393
|
servers[name] = config;
|
|
5292
5394
|
file.mcpServers = servers;
|
|
5293
|
-
await writeJson(
|
|
5395
|
+
await writeJson(path18.join(cwd, MCP_FILE), file);
|
|
5294
5396
|
await approve(cwd, name);
|
|
5295
5397
|
});
|
|
5296
5398
|
}
|
|
5297
5399
|
function removeProjectServer(cwd, name) {
|
|
5298
5400
|
return serialize(cwd, async () => {
|
|
5299
|
-
const file = await readJson(
|
|
5401
|
+
const file = await readJson(path18.join(cwd, MCP_FILE));
|
|
5300
5402
|
const servers = file?.mcpServers;
|
|
5301
5403
|
if (!servers || !(name in servers))
|
|
5302
5404
|
return false;
|
|
5303
5405
|
delete servers[name];
|
|
5304
|
-
await writeJson(
|
|
5305
|
-
const settingsPath =
|
|
5406
|
+
await writeJson(path18.join(cwd, MCP_FILE), file);
|
|
5407
|
+
const settingsPath = path18.join(cwd, LOCAL_SETTINGS2);
|
|
5306
5408
|
const settings = await readJson(settingsPath);
|
|
5307
5409
|
const enabled = settings?.enabledMcpjsonServers;
|
|
5308
5410
|
if (settings && Array.isArray(enabled) && enabled.includes(name)) {
|
|
@@ -5313,7 +5415,7 @@ function removeProjectServer(cwd, name) {
|
|
|
5313
5415
|
});
|
|
5314
5416
|
}
|
|
5315
5417
|
async function approve(cwd, name) {
|
|
5316
|
-
const file =
|
|
5418
|
+
const file = path18.join(cwd, LOCAL_SETTINGS2);
|
|
5317
5419
|
const settings = await readJson(file) ?? {};
|
|
5318
5420
|
const enabled = Array.isArray(settings.enabledMcpjsonServers) ? settings.enabledMcpjsonServers : [];
|
|
5319
5421
|
if (!enabled.includes(name))
|
|
@@ -5352,21 +5454,21 @@ function describeConfig(config) {
|
|
|
5352
5454
|
}
|
|
5353
5455
|
async function readJson(file) {
|
|
5354
5456
|
try {
|
|
5355
|
-
const raw = await
|
|
5457
|
+
const raw = await readFile14(file, "utf8");
|
|
5356
5458
|
const parsed = JSON.parse(raw);
|
|
5357
5459
|
return parsed && typeof parsed === "object" ? parsed : null;
|
|
5358
5460
|
} catch (error) {
|
|
5359
5461
|
if (error.code === "ENOENT")
|
|
5360
5462
|
return null;
|
|
5361
5463
|
if (error instanceof SyntaxError) {
|
|
5362
|
-
throw new Error(`\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0440\u0430\u0437\u043E\u0431\u0440\u0430\u0442\u044C ${
|
|
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}`);
|
|
5363
5465
|
}
|
|
5364
5466
|
throw error;
|
|
5365
5467
|
}
|
|
5366
5468
|
}
|
|
5367
5469
|
async function writeJson(file, data) {
|
|
5368
|
-
await
|
|
5369
|
-
await
|
|
5470
|
+
await mkdir10(path18.dirname(file), { recursive: true });
|
|
5471
|
+
await writeFile11(file, `${JSON.stringify(data, null, 2)}
|
|
5370
5472
|
`, "utf8");
|
|
5371
5473
|
}
|
|
5372
5474
|
|
|
@@ -5380,6 +5482,7 @@ var HostServer = class {
|
|
|
5380
5482
|
#workspaces;
|
|
5381
5483
|
#archive;
|
|
5382
5484
|
#pins;
|
|
5485
|
+
#order;
|
|
5383
5486
|
#folders;
|
|
5384
5487
|
#settings;
|
|
5385
5488
|
#finder;
|
|
@@ -5418,6 +5521,7 @@ var HostServer = class {
|
|
|
5418
5521
|
this.#workspaces = new WorkspaceRegistry(options2.dataDir);
|
|
5419
5522
|
this.#archive = new SessionArchive(options2.dataDir);
|
|
5420
5523
|
this.#pins = new SessionPins(options2.dataDir);
|
|
5524
|
+
this.#order = new ListOrderStore(options2.dataDir);
|
|
5421
5525
|
this.#folders = new Folders(options2.dataDir);
|
|
5422
5526
|
this.#settings = new AppSettingsStore(options2.dataDir);
|
|
5423
5527
|
this.#finder = new ProjectFinder(this.#workspaces);
|
|
@@ -5436,6 +5540,7 @@ var HostServer = class {
|
|
|
5436
5540
|
await this.#workspaces.load();
|
|
5437
5541
|
await this.#archive.load();
|
|
5438
5542
|
await this.#pins.load();
|
|
5543
|
+
await this.#order.load();
|
|
5439
5544
|
await this.#folders.load();
|
|
5440
5545
|
await this.#settings.load();
|
|
5441
5546
|
await this.#github.load();
|
|
@@ -5619,6 +5724,8 @@ var HostServer = class {
|
|
|
5619
5724
|
});
|
|
5620
5725
|
this.#send(socket, { type: "workspace.list", ...this.#workspaceLists() });
|
|
5621
5726
|
this.#sendFolders(socket);
|
|
5727
|
+
this.#send(socket, { type: "order.state", order: this.#order.current });
|
|
5728
|
+
this.#sendLive(socket);
|
|
5622
5729
|
this.#auth.status().then((status) => this.#send(socket, { type: "auth.status", status })).catch(() => void 0);
|
|
5623
5730
|
this.#send(socket, { type: "relay.state", state: this.#relay.state });
|
|
5624
5731
|
return;
|
|
@@ -5734,6 +5841,13 @@ var HostServer = class {
|
|
|
5734
5841
|
case "session.pins":
|
|
5735
5842
|
await this.#sendPinned();
|
|
5736
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;
|
|
5737
5851
|
case "folder.create": {
|
|
5738
5852
|
if (frame.scope === "session") {
|
|
5739
5853
|
if (!frame.workspaceId)
|
|
@@ -6375,8 +6489,23 @@ var HostServer = class {
|
|
|
6375
6489
|
await session.close(reason);
|
|
6376
6490
|
this.#sessions.delete(sessionId);
|
|
6377
6491
|
this.#broadcast({ type: "session.closed", sessionId, reason });
|
|
6492
|
+
this.#sendLive();
|
|
6378
6493
|
return true;
|
|
6379
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
|
+
}
|
|
6380
6509
|
/**
|
|
6381
6510
|
* Разослать список сессий проекта всем окнам.
|
|
6382
6511
|
*
|
|
@@ -6438,6 +6567,7 @@ var HostServer = class {
|
|
|
6438
6567
|
this.#wire(session);
|
|
6439
6568
|
this.#sessions.set(session.id, session);
|
|
6440
6569
|
session.start();
|
|
6570
|
+
this.#sendLive();
|
|
6441
6571
|
this.#send(socket, { type: "session.summary", session: session.summary() });
|
|
6442
6572
|
}
|
|
6443
6573
|
/**
|
|
@@ -6551,7 +6681,7 @@ var HostServer = class {
|
|
|
6551
6681
|
*/
|
|
6552
6682
|
#roots() {
|
|
6553
6683
|
const roots = this.#finder.roots();
|
|
6554
|
-
return roots.length > 0 ? roots : [
|
|
6684
|
+
return roots.length > 0 ? roots : [homedir6()];
|
|
6555
6685
|
}
|
|
6556
6686
|
/**
|
|
6557
6687
|
* Где сессии этого каталога разрешено писать без вопроса.
|
|
@@ -6884,15 +7014,15 @@ var HostServer = class {
|
|
|
6884
7014
|
}
|
|
6885
7015
|
#serveStatic(req, res) {
|
|
6886
7016
|
const webRoot = this.#options.webRoot;
|
|
6887
|
-
if (!webRoot || !
|
|
7017
|
+
if (!webRoot || !existsSync4(webRoot)) {
|
|
6888
7018
|
res.writeHead(200, { "content-type": "text/plain; charset=utf-8" });
|
|
6889
7019
|
res.end("CCV host is running. UI is served by the Vite dev server.\n");
|
|
6890
7020
|
return;
|
|
6891
7021
|
}
|
|
6892
7022
|
const url2 = new URL(req.url ?? "/", "http://localhost");
|
|
6893
|
-
const requested =
|
|
6894
|
-
const target = requested.startsWith(webRoot) &&
|
|
6895
|
-
if (!
|
|
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)) {
|
|
6896
7026
|
res.writeHead(404).end("not found");
|
|
6897
7027
|
return;
|
|
6898
7028
|
}
|
|
@@ -6917,12 +7047,12 @@ function contentType(file) {
|
|
|
6917
7047
|
".svg": "image/svg+xml",
|
|
6918
7048
|
".woff2": "font/woff2"
|
|
6919
7049
|
};
|
|
6920
|
-
return types[
|
|
7050
|
+
return types[path19.extname(file)] ?? "application/octet-stream";
|
|
6921
7051
|
}
|
|
6922
7052
|
|
|
6923
7053
|
// dist/ccv.js
|
|
6924
|
-
var HERE =
|
|
6925
|
-
var PACKAGE_ROOT =
|
|
7054
|
+
var HERE = path20.dirname(fileURLToPath(import.meta.url));
|
|
7055
|
+
var PACKAGE_ROOT = path20.resolve(HERE, "..");
|
|
6926
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.
|
|
6927
7057
|
|
|
6928
7058
|
\u0418\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u043D\u0438\u0435:
|
|
@@ -6989,7 +7119,7 @@ var server = new HostServer({
|
|
|
6989
7119
|
dataDir: defaults.dataDir,
|
|
6990
7120
|
// UI лежит в пакете рядом с бандлом — это и есть разница между установленной
|
|
6991
7121
|
// утилитой и запуском из репозитория, где сборка живёт в `apps/web/dist`.
|
|
6992
|
-
webRoot: process.env.CCV_WEB_ROOT ??
|
|
7122
|
+
webRoot: process.env.CCV_WEB_ROOT ?? path20.join(PACKAGE_ROOT, "web"),
|
|
6993
7123
|
version,
|
|
6994
7124
|
relayUrl: defaults.relayUrl
|
|
6995
7125
|
});
|
|
@@ -7018,7 +7148,7 @@ for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
|
7018
7148
|
}
|
|
7019
7149
|
async function packageVersion() {
|
|
7020
7150
|
try {
|
|
7021
|
-
const text = await
|
|
7151
|
+
const text = await readFile15(path20.join(PACKAGE_ROOT, "package.json"), "utf8");
|
|
7022
7152
|
return JSON.parse(text).version ?? "0.0.0";
|
|
7023
7153
|
} catch {
|
|
7024
7154
|
return "0.0.0";
|