@i14ks/ccv 0.10.0 → 0.12.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 +423 -121
- package/package.json +1 -1
- package/web/assets/{codemirror-DKsnpugS.js → codemirror-Byu5BOry.js} +1 -1
- package/web/assets/{index-C3yVGgV2.js → index-BednlsnR.js} +1 -1
- package/web/assets/{index-CiZ3hw4e.js → index-BhTNEiv0.js} +1 -1
- package/web/assets/{index-nV6tsVmQ.js → index-BiJkY5lu.js} +1 -1
- package/web/assets/{index-CNmvdMxt.js → index-Buinfinc.js} +1 -1
- package/web/assets/{index-CZMbzOzb.js → index-CBtC705w.js} +1 -1
- package/web/assets/{index-ClY-NTKp.js → index-CNdrabZC.js} +1 -1
- package/web/assets/{index-u3kUuJkV.js → index-CPLRJruN.js} +1 -1
- package/web/assets/{index-AUibZMHd.js → index-CQlHFJzv.js} +1 -1
- package/web/assets/{index-CT4Ig6AX.js → index-CmXpz6wO.js} +1 -1
- package/web/assets/{index-MFoRbG31.js → index-CsBX1VvC.js} +1 -1
- package/web/assets/{index-BwqyDurx.js → index-CzPnlrTh.js} +1 -1
- package/web/assets/index-D5y0Nk74.css +1 -0
- package/web/assets/{index-BCiq-pRA.js → index-DG-5rv4t.js} +1 -1
- package/web/assets/{index-vmz_RdEe.js → index-DJN6swZl.js} +1 -1
- package/web/assets/{index-B7Kjuy7B.js → index-DJPI4hL4.js} +1 -1
- package/web/assets/{index-NZmn_bzd.js → index-DM4oBLe8.js} +1 -1
- package/web/assets/{index-BaxFm2-f.js → index-DNppE7jj.js} +1 -1
- package/web/assets/index-DO6gKWcT.js +132 -0
- package/web/assets/{index-Cw4Jg8_d.js → index-DSzfAcOE.js} +1 -1
- package/web/assets/{index-yzGIJ5xe.js → index-DXYp3jsl.js} +1 -1
- package/web/assets/{index-cSZKYcZu.js → index-rekQBGDD.js} +1 -1
- package/web/index.html +2 -2
- package/web/assets/index-BVL_B_hC.js +0 -132
- package/web/assets/index-c0zvhlqR.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 readFile14 } from "node:fs/promises";
|
|
6
|
+
import path19 from "node:path";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import { parseArgs } from "node:util";
|
|
9
9
|
|
|
@@ -146,9 +146,9 @@ function envDefaults() {
|
|
|
146
146
|
// ../host/dist/server.js
|
|
147
147
|
import { createServer } from "node:http";
|
|
148
148
|
import { createReadStream, existsSync as existsSync3, statSync as statSync3 } from "node:fs";
|
|
149
|
-
import
|
|
149
|
+
import path18 from "node:path";
|
|
150
150
|
import { homedir as homedir5 } from "node:os";
|
|
151
|
-
import { randomUUID as
|
|
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";
|
|
154
154
|
|
|
@@ -3003,9 +3003,9 @@ function parsePatch(raw) {
|
|
|
3003
3003
|
body = "";
|
|
3004
3004
|
}
|
|
3005
3005
|
total += body.length;
|
|
3006
|
-
const
|
|
3006
|
+
const path20 = status === "deleted" ? oldPath : newPath || oldPath;
|
|
3007
3007
|
files.push({
|
|
3008
|
-
path:
|
|
3008
|
+
path: path20,
|
|
3009
3009
|
oldPath: status === "renamed" ? oldPath : "",
|
|
3010
3010
|
status,
|
|
3011
3011
|
added: count(body, "+"),
|
|
@@ -3017,10 +3017,10 @@ function parsePatch(raw) {
|
|
|
3017
3017
|
return { files, truncated };
|
|
3018
3018
|
}
|
|
3019
3019
|
function stripPrefix(value) {
|
|
3020
|
-
const
|
|
3021
|
-
if (
|
|
3020
|
+
const path20 = unquote(value.trim());
|
|
3021
|
+
if (path20 === "/dev/null")
|
|
3022
3022
|
return "";
|
|
3023
|
-
return
|
|
3023
|
+
return path20.replace(/^[ab]\//, "");
|
|
3024
3024
|
}
|
|
3025
3025
|
function count(body, mark) {
|
|
3026
3026
|
let n = 0;
|
|
@@ -3552,7 +3552,7 @@ import { mkdir as mkdir3, readFile as readFile6, writeFile as writeFile4 } from
|
|
|
3552
3552
|
import path9 from "node:path";
|
|
3553
3553
|
import { listSessions as listSessions2 } from "@anthropic-ai/claude-agent-sdk";
|
|
3554
3554
|
var WorkspaceRegistry = class {
|
|
3555
|
-
#
|
|
3555
|
+
#entries = [];
|
|
3556
3556
|
#storePath;
|
|
3557
3557
|
constructor(dataDir) {
|
|
3558
3558
|
this.#storePath = path9.join(dataDir, "workspaces.json");
|
|
@@ -3561,52 +3561,91 @@ var WorkspaceRegistry = class {
|
|
|
3561
3561
|
try {
|
|
3562
3562
|
const raw = await readFile6(this.#storePath, "utf8");
|
|
3563
3563
|
const parsed = JSON.parse(raw);
|
|
3564
|
-
if (Array.isArray(parsed)) {
|
|
3565
|
-
this.#
|
|
3564
|
+
if (!Array.isArray(parsed)) {
|
|
3565
|
+
this.#entries = [];
|
|
3566
|
+
return;
|
|
3566
3567
|
}
|
|
3568
|
+
this.#entries = parsed.map((item) => {
|
|
3569
|
+
if (typeof item === "string")
|
|
3570
|
+
return { path: item };
|
|
3571
|
+
if (isEntry(item))
|
|
3572
|
+
return item;
|
|
3573
|
+
return null;
|
|
3574
|
+
}).filter((e) => e !== null && existsSync2(e.path));
|
|
3567
3575
|
} catch {
|
|
3568
|
-
this.#
|
|
3576
|
+
this.#entries = [];
|
|
3569
3577
|
}
|
|
3570
3578
|
}
|
|
3571
3579
|
async #persist() {
|
|
3572
3580
|
await mkdir3(path9.dirname(this.#storePath), { recursive: true });
|
|
3573
|
-
await writeFile4(this.#storePath, JSON.stringify(this.#
|
|
3581
|
+
await writeFile4(this.#storePath, JSON.stringify(this.#entries, null, 2), "utf8");
|
|
3574
3582
|
}
|
|
3583
|
+
/** Активные проекты. */
|
|
3575
3584
|
list() {
|
|
3576
|
-
return this.#
|
|
3585
|
+
return this.#entries.filter((e) => !e.archivedAt).map((e) => toInfo(e));
|
|
3586
|
+
}
|
|
3587
|
+
/** Закрытые проекты — архив, симметричный `list()`. */
|
|
3588
|
+
listArchived() {
|
|
3589
|
+
return this.#entries.filter((e) => e.archivedAt).map((e) => toInfo(e));
|
|
3577
3590
|
}
|
|
3578
3591
|
find(id) {
|
|
3579
|
-
|
|
3592
|
+
const entry = this.#entries.find((e) => workspaceIdFromPath(e.path) === id);
|
|
3593
|
+
return entry ? toInfo(entry) : void 0;
|
|
3580
3594
|
}
|
|
3581
3595
|
async open(rawPath) {
|
|
3582
3596
|
const absolute = path9.resolve(rawPath);
|
|
3583
3597
|
if (!existsSync2(absolute)) {
|
|
3584
3598
|
throw new Error(`\u041A\u0430\u0442\u0430\u043B\u043E\u0433 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D: ${absolute}`);
|
|
3585
3599
|
}
|
|
3586
|
-
|
|
3587
|
-
|
|
3600
|
+
const existing = this.#entries.find((e) => e.path === absolute);
|
|
3601
|
+
if (!existing) {
|
|
3602
|
+
this.#entries.unshift({ path: absolute });
|
|
3603
|
+
await this.#persist();
|
|
3604
|
+
} else if (existing.archivedAt) {
|
|
3605
|
+
delete existing.archivedAt;
|
|
3588
3606
|
await this.#persist();
|
|
3589
3607
|
}
|
|
3590
|
-
return toInfo(absolute);
|
|
3608
|
+
return toInfo({ path: absolute });
|
|
3591
3609
|
}
|
|
3592
3610
|
/**
|
|
3593
|
-
* Убрать проекты из списка
|
|
3611
|
+
* Убрать проекты из списка открытых — в архив, а не с концами.
|
|
3594
3612
|
*
|
|
3595
|
-
* Только реестр: каталог, его файлы и транскрипты сессий остаются на
|
|
3596
|
-
*
|
|
3597
|
-
*
|
|
3598
|
-
*
|
|
3613
|
+
* Только реестр: каталог, его файлы и транскрипты сессий остаются на месте.
|
|
3614
|
+
* Запись при этом не удаляется, а помечается временем закрытия — проект
|
|
3615
|
+
* возвращается в активный список через `restore()`, без повторного поиска
|
|
3616
|
+
* каталога. Пачкой, а не по одному, потому что список чистят разом и
|
|
3617
|
+
* переписывать файл на каждый проект незачем.
|
|
3599
3618
|
*/
|
|
3600
3619
|
async close(ids) {
|
|
3601
3620
|
const doomed = new Set(ids);
|
|
3602
|
-
const closed =
|
|
3621
|
+
const closed = [];
|
|
3622
|
+
const at = Date.now();
|
|
3623
|
+
for (const entry of this.#entries) {
|
|
3624
|
+
if (entry.archivedAt || !doomed.has(workspaceIdFromPath(entry.path)))
|
|
3625
|
+
continue;
|
|
3626
|
+
entry.archivedAt = at;
|
|
3627
|
+
closed.push(toInfo(entry));
|
|
3628
|
+
}
|
|
3603
3629
|
if (closed.length === 0)
|
|
3604
3630
|
return [];
|
|
3605
|
-
const gone = new Set(closed.map((w) => w.path));
|
|
3606
|
-
this.#paths = this.#paths.filter((p) => !gone.has(p));
|
|
3607
3631
|
await this.#persist();
|
|
3608
3632
|
return closed;
|
|
3609
3633
|
}
|
|
3634
|
+
/** Вернуть проекты из архива в активный список. */
|
|
3635
|
+
async restore(ids) {
|
|
3636
|
+
const wanted = new Set(ids);
|
|
3637
|
+
const restored = [];
|
|
3638
|
+
for (const entry of this.#entries) {
|
|
3639
|
+
if (!entry.archivedAt || !wanted.has(workspaceIdFromPath(entry.path)))
|
|
3640
|
+
continue;
|
|
3641
|
+
delete entry.archivedAt;
|
|
3642
|
+
restored.push(toInfo(entry));
|
|
3643
|
+
}
|
|
3644
|
+
if (restored.length === 0)
|
|
3645
|
+
return [];
|
|
3646
|
+
await this.#persist();
|
|
3647
|
+
return restored;
|
|
3648
|
+
}
|
|
3610
3649
|
/**
|
|
3611
3650
|
* Создать каталог проекта и открыть его.
|
|
3612
3651
|
*
|
|
@@ -3705,14 +3744,19 @@ function claudeMdStub(name) {
|
|
|
3705
3744
|
function readmeStub(name) {
|
|
3706
3745
|
return [`# ${name}`, "", "<!-- \u041E\u0434\u043D\u0430-\u0434\u0432\u0435 \u0444\u0440\u0430\u0437\u044B: \u0447\u0442\u043E \u044D\u0442\u043E \u0438 \u0437\u0430\u0447\u0435\u043C. -->", ""].join("\n");
|
|
3707
3746
|
}
|
|
3708
|
-
function toInfo(
|
|
3747
|
+
function toInfo(entry) {
|
|
3748
|
+
const { path: absolute, archivedAt } = entry;
|
|
3709
3749
|
return {
|
|
3710
3750
|
id: workspaceIdFromPath(absolute),
|
|
3711
3751
|
path: absolute,
|
|
3712
3752
|
name: path9.basename(absolute) || absolute,
|
|
3713
|
-
hasClaudeMd: existsSync2(path9.join(absolute, "CLAUDE.md"))
|
|
3753
|
+
hasClaudeMd: existsSync2(path9.join(absolute, "CLAUDE.md")),
|
|
3754
|
+
...archivedAt ? { archivedAt } : {}
|
|
3714
3755
|
};
|
|
3715
3756
|
}
|
|
3757
|
+
function isEntry(value) {
|
|
3758
|
+
return typeof value === "object" && value !== null && typeof value.path === "string" && (value.archivedAt === void 0 || typeof value.archivedAt === "number");
|
|
3759
|
+
}
|
|
3716
3760
|
|
|
3717
3761
|
// ../host/dist/archive.js
|
|
3718
3762
|
import { mkdir as mkdir4, readFile as readFile7, writeFile as writeFile5 } from "node:fs/promises";
|
|
@@ -3912,38 +3956,234 @@ var SessionPins = class {
|
|
|
3912
3956
|
await this.#persist();
|
|
3913
3957
|
return changed;
|
|
3914
3958
|
}
|
|
3959
|
+
async #persist() {
|
|
3960
|
+
const file = { pinned: Object.fromEntries(this.#pinned) };
|
|
3961
|
+
await mkdir5(path11.dirname(this.#storePath), { recursive: true });
|
|
3962
|
+
await writeFile6(this.#storePath, JSON.stringify(file, null, 2), "utf8");
|
|
3963
|
+
}
|
|
3964
|
+
};
|
|
3965
|
+
function isRecord2(value) {
|
|
3966
|
+
return typeof value === "object" && value !== null && typeof value.workspaceId === "string" && typeof value.at === "number";
|
|
3967
|
+
}
|
|
3968
|
+
|
|
3969
|
+
// ../host/dist/folders.js
|
|
3970
|
+
import { mkdir as mkdir6, readFile as readFile9, writeFile as writeFile7 } from "node:fs/promises";
|
|
3971
|
+
import path12 from "node:path";
|
|
3972
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
3973
|
+
var Folders = class {
|
|
3974
|
+
#workspaceFolders = /* @__PURE__ */ new Map();
|
|
3975
|
+
#sessionFolders = /* @__PURE__ */ new Map();
|
|
3976
|
+
#pinnedFolders = /* @__PURE__ */ new Map();
|
|
3977
|
+
#workspaceAssignments = /* @__PURE__ */ new Map();
|
|
3978
|
+
#sessionAssignments = /* @__PURE__ */ new Map();
|
|
3979
|
+
#pinnedAssignments = /* @__PURE__ */ new Map();
|
|
3980
|
+
#storePath;
|
|
3981
|
+
constructor(dataDir) {
|
|
3982
|
+
this.#storePath = path12.join(dataDir, "folders.json");
|
|
3983
|
+
}
|
|
3984
|
+
async load() {
|
|
3985
|
+
try {
|
|
3986
|
+
const raw = await readFile9(this.#storePath, "utf8");
|
|
3987
|
+
const parsed = JSON.parse(raw);
|
|
3988
|
+
this.#workspaceFolders = new Map(Object.entries(parsed.workspaceFolders ?? {}).filter(([, r]) => isWorkspaceFolder(r)));
|
|
3989
|
+
this.#sessionFolders = new Map(Object.entries(parsed.sessionFolders ?? {}).filter(([, r]) => isSessionFolder(r)));
|
|
3990
|
+
this.#pinnedFolders = new Map(Object.entries(parsed.pinnedFolders ?? {}).filter(([, r]) => isPinnedFolder(r)));
|
|
3991
|
+
this.#workspaceAssignments = new Map(Object.entries(parsed.workspaceAssignments ?? {}).filter((entry) => typeof entry[1] === "string"));
|
|
3992
|
+
this.#sessionAssignments = new Map(Object.entries(parsed.sessionAssignments ?? {}).filter((entry) => typeof entry[1] === "string"));
|
|
3993
|
+
this.#pinnedAssignments = new Map(Object.entries(parsed.pinnedAssignments ?? {}).filter((entry) => typeof entry[1] === "string"));
|
|
3994
|
+
} catch {
|
|
3995
|
+
this.#workspaceFolders = /* @__PURE__ */ new Map();
|
|
3996
|
+
this.#sessionFolders = /* @__PURE__ */ new Map();
|
|
3997
|
+
this.#pinnedFolders = /* @__PURE__ */ new Map();
|
|
3998
|
+
this.#workspaceAssignments = /* @__PURE__ */ new Map();
|
|
3999
|
+
this.#sessionAssignments = /* @__PURE__ */ new Map();
|
|
4000
|
+
this.#pinnedAssignments = /* @__PURE__ */ new Map();
|
|
4001
|
+
}
|
|
4002
|
+
}
|
|
4003
|
+
/** Все папки — для кадра `folder.list`. */
|
|
4004
|
+
list() {
|
|
4005
|
+
return {
|
|
4006
|
+
workspaceFolders: [...this.#workspaceFolders].map(([id, r]) => ({ id, name: r.name })),
|
|
4007
|
+
sessionFolders: [...this.#sessionFolders].map(([id, r]) => ({
|
|
4008
|
+
id,
|
|
4009
|
+
name: r.name,
|
|
4010
|
+
workspaceId: r.workspaceId
|
|
4011
|
+
})),
|
|
4012
|
+
pinnedFolders: [...this.#pinnedFolders].map(([id, r]) => ({ id, name: r.name }))
|
|
4013
|
+
};
|
|
4014
|
+
}
|
|
4015
|
+
async createWorkspaceFolder(name) {
|
|
4016
|
+
const id = randomUUID2();
|
|
4017
|
+
this.#workspaceFolders.set(id, { name });
|
|
4018
|
+
await this.#persist();
|
|
4019
|
+
return id;
|
|
4020
|
+
}
|
|
4021
|
+
async createSessionFolder(workspaceId, name) {
|
|
4022
|
+
const id = randomUUID2();
|
|
4023
|
+
this.#sessionFolders.set(id, { name, workspaceId });
|
|
4024
|
+
await this.#persist();
|
|
4025
|
+
return id;
|
|
4026
|
+
}
|
|
4027
|
+
async createPinnedFolder(name) {
|
|
4028
|
+
const id = randomUUID2();
|
|
4029
|
+
this.#pinnedFolders.set(id, { name });
|
|
4030
|
+
await this.#persist();
|
|
4031
|
+
return id;
|
|
4032
|
+
}
|
|
4033
|
+
/** Переименовать папку любого вида — id уникален между обоими видами. */
|
|
4034
|
+
async rename(folderId, name) {
|
|
4035
|
+
const ws = this.#workspaceFolders.get(folderId);
|
|
4036
|
+
if (ws) {
|
|
4037
|
+
this.#workspaceFolders.set(folderId, { ...ws, name });
|
|
4038
|
+
await this.#persist();
|
|
4039
|
+
return true;
|
|
4040
|
+
}
|
|
4041
|
+
const sess = this.#sessionFolders.get(folderId);
|
|
4042
|
+
if (sess) {
|
|
4043
|
+
this.#sessionFolders.set(folderId, { ...sess, name });
|
|
4044
|
+
await this.#persist();
|
|
4045
|
+
return true;
|
|
4046
|
+
}
|
|
4047
|
+
const pinned = this.#pinnedFolders.get(folderId);
|
|
4048
|
+
if (pinned) {
|
|
4049
|
+
this.#pinnedFolders.set(folderId, { ...pinned, name });
|
|
4050
|
+
await this.#persist();
|
|
4051
|
+
return true;
|
|
4052
|
+
}
|
|
4053
|
+
return false;
|
|
4054
|
+
}
|
|
3915
4055
|
/**
|
|
3916
|
-
*
|
|
4056
|
+
* Удалить папку. Элементы внутри неё не трогаются — только перестают на неё
|
|
4057
|
+
* указывать, то есть становятся вне папок.
|
|
3917
4058
|
*
|
|
3918
|
-
*
|
|
3919
|
-
*
|
|
4059
|
+
* Возвращает, какого вида была удалённая папка (и, для папки сессий, её
|
|
4060
|
+
* проект) — вызывающему это нужно, чтобы разослать обновление только туда,
|
|
4061
|
+
* где папка была видна, а не всем спискам сразу.
|
|
3920
4062
|
*/
|
|
3921
|
-
async
|
|
3922
|
-
let
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
this.#
|
|
3927
|
-
|
|
4063
|
+
async delete(folderId) {
|
|
4064
|
+
let removed = null;
|
|
4065
|
+
if (this.#workspaceFolders.delete(folderId)) {
|
|
4066
|
+
removed = { scope: "workspace" };
|
|
4067
|
+
} else {
|
|
4068
|
+
const sess = this.#sessionFolders.get(folderId);
|
|
4069
|
+
if (sess && this.#sessionFolders.delete(folderId)) {
|
|
4070
|
+
removed = { scope: "session", workspaceId: sess.workspaceId };
|
|
4071
|
+
} else if (this.#pinnedFolders.delete(folderId)) {
|
|
4072
|
+
removed = { scope: "pinned" };
|
|
4073
|
+
}
|
|
3928
4074
|
}
|
|
3929
|
-
if (
|
|
4075
|
+
if (!removed)
|
|
4076
|
+
return null;
|
|
4077
|
+
for (const [id, assigned] of this.#workspaceAssignments) {
|
|
4078
|
+
if (assigned === folderId)
|
|
4079
|
+
this.#workspaceAssignments.delete(id);
|
|
4080
|
+
}
|
|
4081
|
+
for (const [id, assigned] of this.#sessionAssignments) {
|
|
4082
|
+
if (assigned === folderId)
|
|
4083
|
+
this.#sessionAssignments.delete(id);
|
|
4084
|
+
}
|
|
4085
|
+
for (const [id, assigned] of this.#pinnedAssignments) {
|
|
4086
|
+
if (assigned === folderId)
|
|
4087
|
+
this.#pinnedAssignments.delete(id);
|
|
4088
|
+
}
|
|
4089
|
+
await this.#persist();
|
|
4090
|
+
return removed;
|
|
4091
|
+
}
|
|
4092
|
+
/** Разложить проект по папке или вынуть из неё (`folderId: null`). */
|
|
4093
|
+
async assignWorkspace(workspaceId, folderId) {
|
|
4094
|
+
if (folderId === null) {
|
|
4095
|
+
if (!this.#workspaceAssignments.delete(workspaceId))
|
|
4096
|
+
return false;
|
|
3930
4097
|
await this.#persist();
|
|
3931
|
-
|
|
4098
|
+
return true;
|
|
4099
|
+
}
|
|
4100
|
+
if (!this.#workspaceFolders.has(folderId)) {
|
|
4101
|
+
throw new Error(`\u041F\u0430\u043F\u043A\u0430 \u043F\u0440\u043E\u0435\u043A\u0442\u043E\u0432 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u0430: ${folderId}`);
|
|
4102
|
+
}
|
|
4103
|
+
this.#workspaceAssignments.set(workspaceId, folderId);
|
|
4104
|
+
await this.#persist();
|
|
4105
|
+
return true;
|
|
4106
|
+
}
|
|
4107
|
+
/**
|
|
4108
|
+
* Разложить сессию по папке или вынуть из неё.
|
|
4109
|
+
*
|
|
4110
|
+
* Папка сессии привязана к проекту — раскладывать сессию в папку чужого
|
|
4111
|
+
* проекта нельзя, иначе список одного проекта показывал бы группу, для
|
|
4112
|
+
* которой в нём нет ни одной другой строки.
|
|
4113
|
+
*/
|
|
4114
|
+
async assignSession(sessionId, workspaceId, folderId) {
|
|
4115
|
+
if (folderId === null) {
|
|
4116
|
+
if (!this.#sessionAssignments.delete(sessionId))
|
|
4117
|
+
return false;
|
|
4118
|
+
await this.#persist();
|
|
4119
|
+
return true;
|
|
4120
|
+
}
|
|
4121
|
+
const folder = this.#sessionFolders.get(folderId);
|
|
4122
|
+
if (!folder)
|
|
4123
|
+
throw new Error(`\u041F\u0430\u043F\u043A\u0430 \u0441\u0435\u0441\u0441\u0438\u0439 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u0430: ${folderId}`);
|
|
4124
|
+
if (folder.workspaceId !== workspaceId) {
|
|
4125
|
+
throw new Error("\u041F\u0430\u043F\u043A\u0430 \u0441\u0435\u0441\u0441\u0438\u0439 \u043F\u0440\u0438\u043D\u0430\u0434\u043B\u0435\u0436\u0438\u0442 \u0434\u0440\u0443\u0433\u043E\u043C\u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0443");
|
|
4126
|
+
}
|
|
4127
|
+
this.#sessionAssignments.set(sessionId, folderId);
|
|
4128
|
+
await this.#persist();
|
|
4129
|
+
return true;
|
|
4130
|
+
}
|
|
4131
|
+
/**
|
|
4132
|
+
* Разложить закреплённую сессию по папке закреплённых или вынуть из неё.
|
|
4133
|
+
*
|
|
4134
|
+
* Без проверки проекта — в отличие от `assignSession`, папка закреплённых
|
|
4135
|
+
* общая на все проекты сразу, и сессия любого из них может в неё попасть.
|
|
4136
|
+
*/
|
|
4137
|
+
async assignPinnedSession(sessionId, folderId) {
|
|
4138
|
+
if (folderId === null) {
|
|
4139
|
+
if (!this.#pinnedAssignments.delete(sessionId))
|
|
4140
|
+
return false;
|
|
4141
|
+
await this.#persist();
|
|
4142
|
+
return true;
|
|
4143
|
+
}
|
|
4144
|
+
if (!this.#pinnedFolders.has(folderId)) {
|
|
4145
|
+
throw new Error(`\u041F\u0430\u043F\u043A\u0430 \u0437\u0430\u043A\u0440\u0435\u043F\u043B\u0451\u043D\u043D\u044B\u0445 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u0430: ${folderId}`);
|
|
4146
|
+
}
|
|
4147
|
+
this.#pinnedAssignments.set(sessionId, folderId);
|
|
4148
|
+
await this.#persist();
|
|
4149
|
+
return true;
|
|
4150
|
+
}
|
|
4151
|
+
folderIdForWorkspace(workspaceId) {
|
|
4152
|
+
return this.#workspaceAssignments.get(workspaceId);
|
|
4153
|
+
}
|
|
4154
|
+
folderIdForSession(sessionId) {
|
|
4155
|
+
return this.#sessionAssignments.get(sessionId);
|
|
4156
|
+
}
|
|
4157
|
+
folderIdForPinnedSession(sessionId) {
|
|
4158
|
+
return this.#pinnedAssignments.get(sessionId);
|
|
3932
4159
|
}
|
|
3933
4160
|
async #persist() {
|
|
3934
|
-
const file = {
|
|
3935
|
-
|
|
3936
|
-
|
|
4161
|
+
const file = {
|
|
4162
|
+
workspaceFolders: Object.fromEntries(this.#workspaceFolders),
|
|
4163
|
+
sessionFolders: Object.fromEntries(this.#sessionFolders),
|
|
4164
|
+
pinnedFolders: Object.fromEntries(this.#pinnedFolders),
|
|
4165
|
+
workspaceAssignments: Object.fromEntries(this.#workspaceAssignments),
|
|
4166
|
+
sessionAssignments: Object.fromEntries(this.#sessionAssignments),
|
|
4167
|
+
pinnedAssignments: Object.fromEntries(this.#pinnedAssignments)
|
|
4168
|
+
};
|
|
4169
|
+
await mkdir6(path12.dirname(this.#storePath), { recursive: true });
|
|
4170
|
+
await writeFile7(this.#storePath, JSON.stringify(file, null, 2), "utf8");
|
|
3937
4171
|
}
|
|
3938
4172
|
};
|
|
3939
|
-
function
|
|
3940
|
-
return typeof value === "object" && value !== null && typeof value.
|
|
4173
|
+
function isWorkspaceFolder(value) {
|
|
4174
|
+
return typeof value === "object" && value !== null && typeof value.name === "string";
|
|
4175
|
+
}
|
|
4176
|
+
function isSessionFolder(value) {
|
|
4177
|
+
return typeof value === "object" && value !== null && typeof value.name === "string" && typeof value.workspaceId === "string";
|
|
4178
|
+
}
|
|
4179
|
+
function isPinnedFolder(value) {
|
|
4180
|
+
return typeof value === "object" && value !== null && typeof value.name === "string";
|
|
3941
4181
|
}
|
|
3942
4182
|
|
|
3943
4183
|
// ../host/dist/app-settings.js
|
|
3944
|
-
import { mkdir as
|
|
4184
|
+
import { mkdir as mkdir7, readFile as readFile10, writeFile as writeFile8 } from "node:fs/promises";
|
|
3945
4185
|
import { homedir as homedir3 } from "node:os";
|
|
3946
|
-
import
|
|
4186
|
+
import path13 from "node:path";
|
|
3947
4187
|
var DEFAULT_AUTO_ARCHIVE_AFTER_MS = 2 * 24 * 60 * 60 * 1e3;
|
|
3948
4188
|
var MIN_AUTO_ARCHIVE_AFTER_MS = 60 * 60 * 1e3;
|
|
3949
4189
|
var MIN_AUTO_SAVE_AFTER_PAUSE_MS = 500;
|
|
@@ -3978,11 +4218,11 @@ var AppSettingsStore = class {
|
|
|
3978
4218
|
#settings = { ...DEFAULT_APP_SETTINGS };
|
|
3979
4219
|
#storePath;
|
|
3980
4220
|
constructor(dataDir) {
|
|
3981
|
-
this.#storePath =
|
|
4221
|
+
this.#storePath = path13.join(dataDir, "app-settings.json");
|
|
3982
4222
|
}
|
|
3983
4223
|
async load() {
|
|
3984
4224
|
try {
|
|
3985
|
-
const raw = await
|
|
4225
|
+
const raw = await readFile10(this.#storePath, "utf8");
|
|
3986
4226
|
this.#settings = sanitize(JSON.parse(raw), DEFAULT_APP_SETTINGS);
|
|
3987
4227
|
} catch {
|
|
3988
4228
|
this.#settings = { ...DEFAULT_APP_SETTINGS };
|
|
@@ -4002,8 +4242,8 @@ var AppSettingsStore = class {
|
|
|
4002
4242
|
async update(patch) {
|
|
4003
4243
|
const next = sanitize(patch, this.#settings);
|
|
4004
4244
|
this.#settings = next;
|
|
4005
|
-
await
|
|
4006
|
-
await
|
|
4245
|
+
await mkdir7(path13.dirname(this.#storePath), { recursive: true });
|
|
4246
|
+
await writeFile8(this.#storePath, JSON.stringify(next, null, 2), "utf8");
|
|
4007
4247
|
return this.current;
|
|
4008
4248
|
}
|
|
4009
4249
|
};
|
|
@@ -4041,7 +4281,7 @@ function sanitize(value, fallback) {
|
|
|
4041
4281
|
return result;
|
|
4042
4282
|
}
|
|
4043
4283
|
function claudeHome() {
|
|
4044
|
-
return
|
|
4284
|
+
return path13.join(homedir3(), ".claude");
|
|
4045
4285
|
}
|
|
4046
4286
|
function writableRoots(mode, cwd, workspacePaths) {
|
|
4047
4287
|
if (mode === "all")
|
|
@@ -4051,7 +4291,7 @@ function writableRoots(mode, cwd, workspacePaths) {
|
|
|
4051
4291
|
return [...roots];
|
|
4052
4292
|
for (const workspace of [cwd, ...workspacePaths]) {
|
|
4053
4293
|
roots.add(workspace);
|
|
4054
|
-
const parent =
|
|
4294
|
+
const parent = path13.dirname(workspace);
|
|
4055
4295
|
if (parent !== workspace)
|
|
4056
4296
|
roots.add(parent);
|
|
4057
4297
|
}
|
|
@@ -4059,9 +4299,9 @@ function writableRoots(mode, cwd, workspacePaths) {
|
|
|
4059
4299
|
}
|
|
4060
4300
|
|
|
4061
4301
|
// ../host/dist/plugins.js
|
|
4062
|
-
import { readFile as
|
|
4302
|
+
import { readFile as readFile11 } from "node:fs/promises";
|
|
4063
4303
|
import { homedir as homedir4 } from "node:os";
|
|
4064
|
-
import
|
|
4304
|
+
import path14 from "node:path";
|
|
4065
4305
|
var TIMEOUT_MS2 = 12e4;
|
|
4066
4306
|
var SAFE_ARG = /^[A-Za-z0-9][A-Za-z0-9._@/-]*$/;
|
|
4067
4307
|
var PluginCli = class {
|
|
@@ -4160,10 +4400,10 @@ async function readCatalog(markets, installed) {
|
|
|
4160
4400
|
return lists.flat().sort((a, b) => a.name.localeCompare(b.name));
|
|
4161
4401
|
}
|
|
4162
4402
|
async function readMarketplace(market, installed) {
|
|
4163
|
-
const root = market.installLocation ||
|
|
4403
|
+
const root = market.installLocation || path14.join(homedir4(), ".claude", "plugins", "marketplaces", market.name);
|
|
4164
4404
|
let manifest;
|
|
4165
4405
|
try {
|
|
4166
|
-
const text = await
|
|
4406
|
+
const text = await readFile11(path14.join(root, ".claude-plugin", "marketplace.json"), "utf8");
|
|
4167
4407
|
manifest = JSON.parse(text);
|
|
4168
4408
|
} catch {
|
|
4169
4409
|
return [];
|
|
@@ -4404,8 +4644,8 @@ function describe4(error) {
|
|
|
4404
4644
|
|
|
4405
4645
|
// ../host/dist/relay-link.js
|
|
4406
4646
|
import { EventEmitter as EventEmitter3 } from "node:events";
|
|
4407
|
-
import { readFile as
|
|
4408
|
-
import
|
|
4647
|
+
import { readFile as readFile12, writeFile as writeFile9, rm as rm3, mkdir as mkdir8 } from "node:fs/promises";
|
|
4648
|
+
import path15 from "node:path";
|
|
4409
4649
|
import { hostname } from "node:os";
|
|
4410
4650
|
import WebSocket from "ws";
|
|
4411
4651
|
|
|
@@ -4460,7 +4700,7 @@ var RelayLink = class extends EventEmitter3 {
|
|
|
4460
4700
|
#log;
|
|
4461
4701
|
constructor(options2) {
|
|
4462
4702
|
super();
|
|
4463
|
-
this.#file =
|
|
4703
|
+
this.#file = path15.join(options2.dataDir, "relay.json");
|
|
4464
4704
|
this.#defaultUrl = options2.defaultUrl.replace(/\/+$/, "");
|
|
4465
4705
|
this.#log = options2.log ?? ((msg) => console.log(`[relay] ${msg}`));
|
|
4466
4706
|
}
|
|
@@ -4541,7 +4781,7 @@ var RelayLink = class extends EventEmitter3 {
|
|
|
4541
4781
|
}
|
|
4542
4782
|
async #read() {
|
|
4543
4783
|
try {
|
|
4544
|
-
const raw = await
|
|
4784
|
+
const raw = await readFile12(this.#file, "utf8");
|
|
4545
4785
|
const parsed = JSON.parse(raw);
|
|
4546
4786
|
if (typeof parsed !== "object" || parsed === null)
|
|
4547
4787
|
return null;
|
|
@@ -4554,8 +4794,8 @@ var RelayLink = class extends EventEmitter3 {
|
|
|
4554
4794
|
}
|
|
4555
4795
|
}
|
|
4556
4796
|
async #write(credentials) {
|
|
4557
|
-
await
|
|
4558
|
-
await
|
|
4797
|
+
await mkdir8(path15.dirname(this.#file), { recursive: true });
|
|
4798
|
+
await writeFile9(this.#file, `${JSON.stringify(credentials, null, 2)}
|
|
4559
4799
|
`, {
|
|
4560
4800
|
encoding: "utf8",
|
|
4561
4801
|
mode: 384
|
|
@@ -4812,8 +5052,8 @@ function humanize(error) {
|
|
|
4812
5052
|
// ../host/dist/terminal.js
|
|
4813
5053
|
import { EventEmitter as EventEmitter4 } from "node:events";
|
|
4814
5054
|
import { access as access2, constants as constants2 } from "node:fs/promises";
|
|
4815
|
-
import
|
|
4816
|
-
import { randomUUID as
|
|
5055
|
+
import path16 from "node:path";
|
|
5056
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
4817
5057
|
var SCROLLBACK_BYTES = 256 * 1024;
|
|
4818
5058
|
var FLUSH_MS = 12;
|
|
4819
5059
|
var MAX_TERMINALS = 24;
|
|
@@ -4850,7 +5090,7 @@ var TerminalHub = class extends EventEmitter4 {
|
|
|
4850
5090
|
env: environment()
|
|
4851
5091
|
});
|
|
4852
5092
|
const info = {
|
|
4853
|
-
id:
|
|
5093
|
+
id: randomUUID3(),
|
|
4854
5094
|
workspaceId: options2.workspaceId,
|
|
4855
5095
|
title: shell.title,
|
|
4856
5096
|
shell: shell.file,
|
|
@@ -4982,22 +5222,22 @@ async function defaultShell() {
|
|
|
4982
5222
|
if (pwsh)
|
|
4983
5223
|
return { file: pwsh, args: [], title: "pwsh" };
|
|
4984
5224
|
const system = process.env.SystemRoot ?? "C:\\Windows";
|
|
4985
|
-
const powershell =
|
|
5225
|
+
const powershell = path16.join(system, "System32/WindowsPowerShell/v1.0/powershell.exe");
|
|
4986
5226
|
if (await exists2(powershell))
|
|
4987
5227
|
return { file: powershell, args: [], title: "powershell" };
|
|
4988
5228
|
return { file: process.env.COMSPEC ?? "cmd.exe", args: [], title: "cmd" };
|
|
4989
5229
|
}
|
|
4990
5230
|
const shell = process.env.SHELL;
|
|
4991
5231
|
if (shell && await exists2(shell))
|
|
4992
|
-
return { file: shell, args: [], title:
|
|
5232
|
+
return { file: shell, args: [], title: path16.basename(shell) };
|
|
4993
5233
|
if (await exists2("/bin/bash"))
|
|
4994
5234
|
return { file: "/bin/bash", args: [], title: "bash" };
|
|
4995
5235
|
return { file: "/bin/sh", args: [], title: "sh" };
|
|
4996
5236
|
}
|
|
4997
5237
|
async function findOnPath(name) {
|
|
4998
|
-
const dirs = (process.env.PATH ?? "").split(
|
|
5238
|
+
const dirs = (process.env.PATH ?? "").split(path16.delimiter).filter(Boolean);
|
|
4999
5239
|
for (const dir of dirs) {
|
|
5000
|
-
const candidate =
|
|
5240
|
+
const candidate = path16.join(dir, name);
|
|
5001
5241
|
if (await exists2(candidate))
|
|
5002
5242
|
return candidate;
|
|
5003
5243
|
}
|
|
@@ -5008,10 +5248,10 @@ function exists2(file) {
|
|
|
5008
5248
|
}
|
|
5009
5249
|
|
|
5010
5250
|
// ../host/dist/mcp-config.js
|
|
5011
|
-
import { readFile as
|
|
5012
|
-
import
|
|
5251
|
+
import { readFile as readFile13, writeFile as writeFile10, mkdir as mkdir9 } from "node:fs/promises";
|
|
5252
|
+
import path17 from "node:path";
|
|
5013
5253
|
var MCP_FILE = ".mcp.json";
|
|
5014
|
-
var LOCAL_SETTINGS2 =
|
|
5254
|
+
var LOCAL_SETTINGS2 = path17.join(".claude", "settings.local.json");
|
|
5015
5255
|
var queues = /* @__PURE__ */ new Map();
|
|
5016
5256
|
function serialize(cwd, work) {
|
|
5017
5257
|
const previous = queues.get(cwd) ?? Promise.resolve();
|
|
@@ -5020,7 +5260,7 @@ function serialize(cwd, work) {
|
|
|
5020
5260
|
return next;
|
|
5021
5261
|
}
|
|
5022
5262
|
async function readProjectServers(cwd) {
|
|
5023
|
-
const file = await readJson(
|
|
5263
|
+
const file = await readJson(path17.join(cwd, MCP_FILE));
|
|
5024
5264
|
const servers = file?.mcpServers;
|
|
5025
5265
|
if (!servers || typeof servers !== "object")
|
|
5026
5266
|
return {};
|
|
@@ -5028,23 +5268,23 @@ async function readProjectServers(cwd) {
|
|
|
5028
5268
|
}
|
|
5029
5269
|
function addProjectServer(cwd, name, config) {
|
|
5030
5270
|
return serialize(cwd, async () => {
|
|
5031
|
-
const file = await readJson(
|
|
5271
|
+
const file = await readJson(path17.join(cwd, MCP_FILE)) ?? {};
|
|
5032
5272
|
const servers = file.mcpServers ?? {};
|
|
5033
5273
|
servers[name] = config;
|
|
5034
5274
|
file.mcpServers = servers;
|
|
5035
|
-
await writeJson(
|
|
5275
|
+
await writeJson(path17.join(cwd, MCP_FILE), file);
|
|
5036
5276
|
await approve(cwd, name);
|
|
5037
5277
|
});
|
|
5038
5278
|
}
|
|
5039
5279
|
function removeProjectServer(cwd, name) {
|
|
5040
5280
|
return serialize(cwd, async () => {
|
|
5041
|
-
const file = await readJson(
|
|
5281
|
+
const file = await readJson(path17.join(cwd, MCP_FILE));
|
|
5042
5282
|
const servers = file?.mcpServers;
|
|
5043
5283
|
if (!servers || !(name in servers))
|
|
5044
5284
|
return false;
|
|
5045
5285
|
delete servers[name];
|
|
5046
|
-
await writeJson(
|
|
5047
|
-
const settingsPath =
|
|
5286
|
+
await writeJson(path17.join(cwd, MCP_FILE), file);
|
|
5287
|
+
const settingsPath = path17.join(cwd, LOCAL_SETTINGS2);
|
|
5048
5288
|
const settings = await readJson(settingsPath);
|
|
5049
5289
|
const enabled = settings?.enabledMcpjsonServers;
|
|
5050
5290
|
if (settings && Array.isArray(enabled) && enabled.includes(name)) {
|
|
@@ -5055,7 +5295,7 @@ function removeProjectServer(cwd, name) {
|
|
|
5055
5295
|
});
|
|
5056
5296
|
}
|
|
5057
5297
|
async function approve(cwd, name) {
|
|
5058
|
-
const file =
|
|
5298
|
+
const file = path17.join(cwd, LOCAL_SETTINGS2);
|
|
5059
5299
|
const settings = await readJson(file) ?? {};
|
|
5060
5300
|
const enabled = Array.isArray(settings.enabledMcpjsonServers) ? settings.enabledMcpjsonServers : [];
|
|
5061
5301
|
if (!enabled.includes(name))
|
|
@@ -5094,21 +5334,21 @@ function describeConfig(config) {
|
|
|
5094
5334
|
}
|
|
5095
5335
|
async function readJson(file) {
|
|
5096
5336
|
try {
|
|
5097
|
-
const raw = await
|
|
5337
|
+
const raw = await readFile13(file, "utf8");
|
|
5098
5338
|
const parsed = JSON.parse(raw);
|
|
5099
5339
|
return parsed && typeof parsed === "object" ? parsed : null;
|
|
5100
5340
|
} catch (error) {
|
|
5101
5341
|
if (error.code === "ENOENT")
|
|
5102
5342
|
return null;
|
|
5103
5343
|
if (error instanceof SyntaxError) {
|
|
5104
|
-
throw new Error(`\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0440\u0430\u0437\u043E\u0431\u0440\u0430\u0442\u044C ${
|
|
5344
|
+
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}`);
|
|
5105
5345
|
}
|
|
5106
5346
|
throw error;
|
|
5107
5347
|
}
|
|
5108
5348
|
}
|
|
5109
5349
|
async function writeJson(file, data) {
|
|
5110
|
-
await
|
|
5111
|
-
await
|
|
5350
|
+
await mkdir9(path17.dirname(file), { recursive: true });
|
|
5351
|
+
await writeFile10(file, `${JSON.stringify(data, null, 2)}
|
|
5112
5352
|
`, "utf8");
|
|
5113
5353
|
}
|
|
5114
5354
|
|
|
@@ -5122,6 +5362,7 @@ var HostServer = class {
|
|
|
5122
5362
|
#workspaces;
|
|
5123
5363
|
#archive;
|
|
5124
5364
|
#pins;
|
|
5365
|
+
#folders;
|
|
5125
5366
|
#settings;
|
|
5126
5367
|
#finder;
|
|
5127
5368
|
#sessions = /* @__PURE__ */ new Map();
|
|
@@ -5139,7 +5380,7 @@ var HostServer = class {
|
|
|
5139
5380
|
#clients = /* @__PURE__ */ new Set();
|
|
5140
5381
|
/** Удалённые вкладки по их id в relay — чтобы адресовать ответ именно той. */
|
|
5141
5382
|
#remotes = /* @__PURE__ */ new Map();
|
|
5142
|
-
#hostId =
|
|
5383
|
+
#hostId = randomUUID4();
|
|
5143
5384
|
#plugins = new PluginCli();
|
|
5144
5385
|
#auth = new AuthCli();
|
|
5145
5386
|
#relay;
|
|
@@ -5159,6 +5400,7 @@ var HostServer = class {
|
|
|
5159
5400
|
this.#workspaces = new WorkspaceRegistry(options2.dataDir);
|
|
5160
5401
|
this.#archive = new SessionArchive(options2.dataDir);
|
|
5161
5402
|
this.#pins = new SessionPins(options2.dataDir);
|
|
5403
|
+
this.#folders = new Folders(options2.dataDir);
|
|
5162
5404
|
this.#settings = new AppSettingsStore(options2.dataDir);
|
|
5163
5405
|
this.#finder = new ProjectFinder(this.#workspaces);
|
|
5164
5406
|
this.#relay = new RelayLink({ dataDir: options2.dataDir, defaultUrl: options2.relayUrl });
|
|
@@ -5176,6 +5418,7 @@ var HostServer = class {
|
|
|
5176
5418
|
await this.#workspaces.load();
|
|
5177
5419
|
await this.#archive.load();
|
|
5178
5420
|
await this.#pins.load();
|
|
5421
|
+
await this.#folders.load();
|
|
5179
5422
|
await this.#settings.load();
|
|
5180
5423
|
await this.#github.load();
|
|
5181
5424
|
await warmClaudeLauncher();
|
|
@@ -5325,6 +5568,27 @@ var HostServer = class {
|
|
|
5325
5568
|
client.send(frame);
|
|
5326
5569
|
}
|
|
5327
5570
|
}
|
|
5571
|
+
/** Активные и архивные проекты разом, с папкой каждого — для `workspace.list`. */
|
|
5572
|
+
#workspaceLists() {
|
|
5573
|
+
const withFolder = (w) => {
|
|
5574
|
+
const folderId = this.#folders.folderIdForWorkspace(w.id);
|
|
5575
|
+
return folderId ? { ...w, folderId } : w;
|
|
5576
|
+
};
|
|
5577
|
+
return {
|
|
5578
|
+
workspaces: this.#workspaces.list().map(withFolder),
|
|
5579
|
+
archived: this.#workspaces.listArchived().map(withFolder)
|
|
5580
|
+
};
|
|
5581
|
+
}
|
|
5582
|
+
#broadcastWorkspaces() {
|
|
5583
|
+
this.#broadcast({ type: "workspace.list", ...this.#workspaceLists() });
|
|
5584
|
+
}
|
|
5585
|
+
#sendFolders(client) {
|
|
5586
|
+
const frame = { type: "folder.list", ...this.#folders.list() };
|
|
5587
|
+
if (client)
|
|
5588
|
+
this.#send(client, frame);
|
|
5589
|
+
else
|
|
5590
|
+
this.#broadcast(frame);
|
|
5591
|
+
}
|
|
5328
5592
|
async #handleFrame(socket, frame) {
|
|
5329
5593
|
switch (frame.type) {
|
|
5330
5594
|
case "hello":
|
|
@@ -5335,10 +5599,8 @@ var HostServer = class {
|
|
|
5335
5599
|
hostVersion: this.#options.version,
|
|
5336
5600
|
platform: process.platform
|
|
5337
5601
|
});
|
|
5338
|
-
this.#send(socket, {
|
|
5339
|
-
|
|
5340
|
-
workspaces: this.#workspaces.list()
|
|
5341
|
-
});
|
|
5602
|
+
this.#send(socket, { type: "workspace.list", ...this.#workspaceLists() });
|
|
5603
|
+
this.#sendFolders(socket);
|
|
5342
5604
|
this.#auth.status().then((status) => this.#send(socket, { type: "auth.status", status })).catch(() => void 0);
|
|
5343
5605
|
this.#send(socket, { type: "relay.state", state: this.#relay.state });
|
|
5344
5606
|
return;
|
|
@@ -5346,17 +5608,11 @@ var HostServer = class {
|
|
|
5346
5608
|
this.#send(socket, { type: "pong", nonce: frame.nonce });
|
|
5347
5609
|
return;
|
|
5348
5610
|
case "workspace.list":
|
|
5349
|
-
this.#send(socket, {
|
|
5350
|
-
type: "workspace.list",
|
|
5351
|
-
workspaces: this.#workspaces.list()
|
|
5352
|
-
});
|
|
5611
|
+
this.#send(socket, { type: "workspace.list", ...this.#workspaceLists() });
|
|
5353
5612
|
return;
|
|
5354
5613
|
case "workspace.open":
|
|
5355
5614
|
await this.#workspaces.open(frame.path);
|
|
5356
|
-
this.#
|
|
5357
|
-
type: "workspace.list",
|
|
5358
|
-
workspaces: this.#workspaces.list()
|
|
5359
|
-
});
|
|
5615
|
+
this.#broadcastWorkspaces();
|
|
5360
5616
|
await this.#closeFinder();
|
|
5361
5617
|
return;
|
|
5362
5618
|
case "workspace.close": {
|
|
@@ -5369,17 +5625,14 @@ var HostServer = class {
|
|
|
5369
5625
|
continue;
|
|
5370
5626
|
await this.#dropSession(session.id, "workspace closed");
|
|
5371
5627
|
}
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
});
|
|
5381
|
-
if (unpinned)
|
|
5382
|
-
await this.#sendPinned();
|
|
5628
|
+
this.#broadcastWorkspaces();
|
|
5629
|
+
return;
|
|
5630
|
+
}
|
|
5631
|
+
case "workspace.restore": {
|
|
5632
|
+
const restored = await this.#workspaces.restore(frame.workspaceIds);
|
|
5633
|
+
if (restored.length === 0)
|
|
5634
|
+
return;
|
|
5635
|
+
this.#broadcastWorkspaces();
|
|
5383
5636
|
return;
|
|
5384
5637
|
}
|
|
5385
5638
|
case "workspace.find": {
|
|
@@ -5401,10 +5654,7 @@ var HostServer = class {
|
|
|
5401
5654
|
return;
|
|
5402
5655
|
case "workspace.create": {
|
|
5403
5656
|
const { workspace, warnings } = await this.#workspaces.create(frame.parent, frame.name, frame.init);
|
|
5404
|
-
this.#
|
|
5405
|
-
type: "workspace.list",
|
|
5406
|
-
workspaces: this.#workspaces.list()
|
|
5407
|
-
});
|
|
5657
|
+
this.#broadcastWorkspaces();
|
|
5408
5658
|
this.#send(socket, { type: "workspace.created", workspace, warnings });
|
|
5409
5659
|
await this.#closeFinder();
|
|
5410
5660
|
return;
|
|
@@ -5466,6 +5716,51 @@ var HostServer = class {
|
|
|
5466
5716
|
case "session.pins":
|
|
5467
5717
|
await this.#sendPinned();
|
|
5468
5718
|
return;
|
|
5719
|
+
case "folder.create": {
|
|
5720
|
+
if (frame.scope === "session") {
|
|
5721
|
+
if (!frame.workspaceId)
|
|
5722
|
+
throw new Error("\u0414\u043B\u044F \u043F\u0430\u043F\u043A\u0438 \u0441\u0435\u0441\u0441\u0438\u0439 \u043D\u0443\u0436\u0435\u043D workspaceId");
|
|
5723
|
+
await this.#folders.createSessionFolder(frame.workspaceId, frame.name);
|
|
5724
|
+
} else if (frame.scope === "pinned") {
|
|
5725
|
+
await this.#folders.createPinnedFolder(frame.name);
|
|
5726
|
+
} else {
|
|
5727
|
+
await this.#folders.createWorkspaceFolder(frame.name);
|
|
5728
|
+
}
|
|
5729
|
+
this.#sendFolders();
|
|
5730
|
+
return;
|
|
5731
|
+
}
|
|
5732
|
+
case "folder.rename":
|
|
5733
|
+
if (await this.#folders.rename(frame.folderId, frame.name))
|
|
5734
|
+
this.#sendFolders();
|
|
5735
|
+
return;
|
|
5736
|
+
case "folder.delete": {
|
|
5737
|
+
const removed = await this.#folders.delete(frame.folderId);
|
|
5738
|
+
if (!removed)
|
|
5739
|
+
return;
|
|
5740
|
+
this.#sendFolders();
|
|
5741
|
+
if (removed.scope === "workspace")
|
|
5742
|
+
this.#broadcastWorkspaces();
|
|
5743
|
+
else if (removed.scope === "session")
|
|
5744
|
+
await this.#sendSessionList(removed.workspaceId);
|
|
5745
|
+
else
|
|
5746
|
+
await this.#sendPinned();
|
|
5747
|
+
return;
|
|
5748
|
+
}
|
|
5749
|
+
case "folder.assign": {
|
|
5750
|
+
if (frame.scope === "session") {
|
|
5751
|
+
if (!frame.workspaceId)
|
|
5752
|
+
throw new Error("\u0414\u043B\u044F \u043F\u0430\u043F\u043A\u0438 \u0441\u0435\u0441\u0441\u0438\u0439 \u043D\u0443\u0436\u0435\u043D workspaceId");
|
|
5753
|
+
await this.#folders.assignSession(frame.itemId, frame.workspaceId, frame.folderId);
|
|
5754
|
+
await this.#sendSessionList(frame.workspaceId);
|
|
5755
|
+
} else if (frame.scope === "pinned") {
|
|
5756
|
+
await this.#folders.assignPinnedSession(frame.itemId, frame.folderId);
|
|
5757
|
+
await this.#sendPinned();
|
|
5758
|
+
} else {
|
|
5759
|
+
await this.#folders.assignWorkspace(frame.itemId, frame.folderId);
|
|
5760
|
+
this.#broadcastWorkspaces();
|
|
5761
|
+
}
|
|
5762
|
+
return;
|
|
5763
|
+
}
|
|
5469
5764
|
case "session.rename":
|
|
5470
5765
|
await this.#renameSession(frame.sessionId, frame.title, frame.workspaceId);
|
|
5471
5766
|
return;
|
|
@@ -6532,7 +6827,14 @@ var HostServer = class {
|
|
|
6532
6827
|
const archived = [];
|
|
6533
6828
|
for (const base of [...live, ...historical].sort((a, b) => b.updatedAt - a.updatedAt)) {
|
|
6534
6829
|
const pin = this.#pins.find(base.claudeSessionId ?? base.id);
|
|
6535
|
-
const
|
|
6830
|
+
const folderId = this.#folders.folderIdForSession(base.claudeSessionId ?? base.id);
|
|
6831
|
+
const pinnedFolderId = this.#folders.folderIdForPinnedSession(base.claudeSessionId ?? base.id);
|
|
6832
|
+
const summary = {
|
|
6833
|
+
...base,
|
|
6834
|
+
...pin ? { pinned: true, pinnedAt: pin.at } : {},
|
|
6835
|
+
...folderId ? { folderId } : {},
|
|
6836
|
+
...pinnedFolderId ? { pinnedFolderId } : {}
|
|
6837
|
+
};
|
|
6536
6838
|
const record = this.#archive.find(summary.claudeSessionId ?? summary.id);
|
|
6537
6839
|
if (!record) {
|
|
6538
6840
|
sessions.push(summary);
|
|
@@ -6570,8 +6872,8 @@ var HostServer = class {
|
|
|
6570
6872
|
return;
|
|
6571
6873
|
}
|
|
6572
6874
|
const url2 = new URL(req.url ?? "/", "http://localhost");
|
|
6573
|
-
const requested =
|
|
6574
|
-
const target = requested.startsWith(webRoot) && existsSync3(requested) && statSync3(requested).isFile() ? requested :
|
|
6875
|
+
const requested = path18.join(webRoot, path18.normalize(url2.pathname));
|
|
6876
|
+
const target = requested.startsWith(webRoot) && existsSync3(requested) && statSync3(requested).isFile() ? requested : path18.join(webRoot, "index.html");
|
|
6575
6877
|
if (!existsSync3(target)) {
|
|
6576
6878
|
res.writeHead(404).end("not found");
|
|
6577
6879
|
return;
|
|
@@ -6597,12 +6899,12 @@ function contentType(file) {
|
|
|
6597
6899
|
".svg": "image/svg+xml",
|
|
6598
6900
|
".woff2": "font/woff2"
|
|
6599
6901
|
};
|
|
6600
|
-
return types[
|
|
6902
|
+
return types[path18.extname(file)] ?? "application/octet-stream";
|
|
6601
6903
|
}
|
|
6602
6904
|
|
|
6603
6905
|
// dist/ccv.js
|
|
6604
|
-
var HERE =
|
|
6605
|
-
var PACKAGE_ROOT =
|
|
6906
|
+
var HERE = path19.dirname(fileURLToPath(import.meta.url));
|
|
6907
|
+
var PACKAGE_ROOT = path19.resolve(HERE, "..");
|
|
6606
6908
|
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.
|
|
6607
6909
|
|
|
6608
6910
|
\u0418\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u043D\u0438\u0435:
|
|
@@ -6669,7 +6971,7 @@ var server = new HostServer({
|
|
|
6669
6971
|
dataDir: defaults.dataDir,
|
|
6670
6972
|
// UI лежит в пакете рядом с бандлом — это и есть разница между установленной
|
|
6671
6973
|
// утилитой и запуском из репозитория, где сборка живёт в `apps/web/dist`.
|
|
6672
|
-
webRoot: process.env.CCV_WEB_ROOT ??
|
|
6974
|
+
webRoot: process.env.CCV_WEB_ROOT ?? path19.join(PACKAGE_ROOT, "web"),
|
|
6673
6975
|
version,
|
|
6674
6976
|
relayUrl: defaults.relayUrl
|
|
6675
6977
|
});
|
|
@@ -6698,7 +7000,7 @@ for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
|
6698
7000
|
}
|
|
6699
7001
|
async function packageVersion() {
|
|
6700
7002
|
try {
|
|
6701
|
-
const text = await
|
|
7003
|
+
const text = await readFile14(path19.join(PACKAGE_ROOT, "package.json"), "utf8");
|
|
6702
7004
|
return JSON.parse(text).version ?? "0.0.0";
|
|
6703
7005
|
} catch {
|
|
6704
7006
|
return "0.0.0";
|