@cjhyy/code-shell-core 0.8.8 → 0.8.10
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/dist/automation/scheduler.js +49 -0
- package/dist/automation/store.d.ts +1 -1
- package/dist/automation/store.js +184 -10
- package/dist/cli/agent-server-stdio.js +7 -0
- package/dist/credentials/store.d.ts +14 -0
- package/dist/credentials/store.js +245 -42
- package/dist/engine/engine.js +81 -27
- package/dist/engine/file-history-hook.js +24 -5
- package/dist/engine/run-finalize.js +8 -6
- package/dist/engine/run-session-open.js +1 -1
- package/dist/engine/run-setup.d.ts +4 -0
- package/dist/engine/run-setup.js +5 -1
- package/dist/engine/run-tooling.js +7 -1
- package/dist/engine/run-types.d.ts +33 -0
- package/dist/engine/run-types.js +21 -0
- package/dist/engine/run-workspace.js +11 -5
- package/dist/engine/turn-loop.js +9 -8
- package/dist/goal/lifecycle.d.ts +2 -0
- package/dist/goal/lifecycle.js +56 -33
- package/dist/index.d.ts +2 -3
- package/dist/index.internal.d.ts +1 -0
- package/dist/index.internal.js +1 -0
- package/dist/index.js +2 -2
- package/dist/links/cli.d.ts +2 -0
- package/dist/links/cli.js +11 -4
- package/dist/model-catalog/index.js +19 -4
- package/dist/model-catalog/save-entry.js +122 -61
- package/dist/model-catalog/types.js +27 -23
- package/dist/panel-apps/installer.js +27 -14
- package/dist/panel-apps/manifest.d.ts +11 -11
- package/dist/panel-apps/manifest.js +3 -1
- package/dist/panel-apps/registry.js +60 -12
- package/dist/plugins/installedPlugins.d.ts +4 -0
- package/dist/plugins/installedPlugins.js +70 -30
- package/dist/plugins/installer/types.d.ts +12 -12
- package/dist/plugins/installer/update.js +37 -38
- package/dist/plugins/knownMarketplaces.d.ts +7 -3
- package/dist/plugins/knownMarketplaces.js +127 -23
- package/dist/plugins/pluginCatalog.js +18 -4
- package/dist/plugins/pluginHookApproval.js +56 -60
- package/dist/plugins/pluginMcpApproval.js +50 -52
- package/dist/profile/catalog-store.js +39 -4
- package/dist/profile/catalog.js +55 -15
- package/dist/profile/store.js +51 -21
- package/dist/prompt/composer.d.ts +8 -0
- package/dist/prompt/composer.js +8 -0
- package/dist/protocol/chat-session-manager.d.ts +9 -0
- package/dist/protocol/chat-session-manager.js +22 -0
- package/dist/protocol/chat-session.d.ts +5 -0
- package/dist/protocol/chat-session.js +1 -0
- package/dist/protocol/server.d.ts +2 -0
- package/dist/protocol/server.js +112 -29
- package/dist/protocol/types.d.ts +20 -0
- package/dist/run/FileRunStore.d.ts +2 -0
- package/dist/run/FileRunStore.js +153 -18
- package/dist/run/Heartbeat.js +63 -4
- package/dist/services/auto-dream.js +39 -17
- package/dist/services/session-memory.js +107 -8
- package/dist/session/file-history.d.ts +63 -2
- package/dist/session/file-history.js +593 -86
- package/dist/session/session-manager.d.ts +4 -1
- package/dist/session/session-manager.js +94 -47
- package/dist/session/transcript.js +33 -3
- package/dist/session/undo-target.d.ts +15 -6
- package/dist/session/undo-target.js +26 -9
- package/dist/settings/manager.d.ts +22 -3
- package/dist/settings/manager.js +185 -50
- package/dist/settings/schema.d.ts +3 -3
- package/dist/sources/adapters/local-files.js +49 -4
- package/dist/sources/catalog.js +64 -18
- package/dist/sources/types.d.ts +3 -3
- package/dist/sources/types.js +7 -4
- package/dist/themes/installer.js +192 -28
- package/dist/tool-system/builtin/add-marketplace.js +21 -1
- package/dist/tool-system/builtin/cron.d.ts +2 -1
- package/dist/tool-system/builtin/cron.js +20 -6
- package/dist/tool-system/builtin/index.js +48 -1
- package/dist/tool-system/builtin/install-capability.d.ts +52 -0
- package/dist/tool-system/builtin/install-capability.js +1057 -0
- package/dist/tool-system/builtin/skill.js +3 -1
- package/dist/tool-system/executor.js +1 -0
- package/dist/tool-system/path-policy.js +25 -33
- package/dist/tool-system/registry.js +5 -0
- package/dist/utils/file-mutex.d.ts +2 -0
- package/dist/utils/file-mutex.js +29 -4
- package/package.json +2 -1
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* Reads the Claude Code-compatible V2 shape plus optional CodeShell integrity
|
|
4
4
|
* fields on each install entry.
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { dirname, join } from "node:path";
|
|
6
|
+
import { chmodSync, closeSync, constants, fstatSync, lstatSync, openSync, readFileSync, } from "node:fs";
|
|
7
|
+
import { join } from "node:path";
|
|
9
8
|
import { homedir } from "node:os";
|
|
9
|
+
import { mutateJsonFile } from "../utils/file-mutex.js";
|
|
10
10
|
const MAX_PLUGIN_KEYS = 2_048;
|
|
11
11
|
const MAX_INSTALLS_PER_KEY = 16;
|
|
12
12
|
const MAX_KEY_LENGTH = 256;
|
|
@@ -16,6 +16,7 @@ const DIGEST_RE = /^[a-f0-9]{64}$/;
|
|
|
16
16
|
const MAX_HOOK_REVIEW_ITEMS = 256;
|
|
17
17
|
const MAX_HOOK_REVIEW_COMMAND_LENGTH = 4_096;
|
|
18
18
|
const MAX_HOOK_REVIEW_MATCHER_LENGTH = 4_096;
|
|
19
|
+
const MAX_INSTALLED_PLUGINS_FILE_BYTES = 16 * 1024 * 1024;
|
|
19
20
|
function userHome() {
|
|
20
21
|
return process.env.HOME ?? homedir();
|
|
21
22
|
}
|
|
@@ -142,52 +143,91 @@ function registryOf(value) {
|
|
|
142
143
|
}
|
|
143
144
|
export function readInstalledPlugins() {
|
|
144
145
|
const path = installedPluginsPath();
|
|
145
|
-
|
|
146
|
-
return { version: 2, plugins: {} };
|
|
146
|
+
let descriptor;
|
|
147
147
|
try {
|
|
148
|
-
const
|
|
148
|
+
const metadata = lstatSync(path);
|
|
149
|
+
if (metadata.isSymbolicLink() ||
|
|
150
|
+
!metadata.isFile() ||
|
|
151
|
+
metadata.size > MAX_INSTALLED_PLUGINS_FILE_BYTES) {
|
|
152
|
+
return { version: 2, plugins: {} };
|
|
153
|
+
}
|
|
154
|
+
descriptor = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
155
|
+
const opened = fstatSync(descriptor);
|
|
156
|
+
if (!opened.isFile() || opened.size > MAX_INSTALLED_PLUGINS_FILE_BYTES) {
|
|
157
|
+
return { version: 2, plugins: {} };
|
|
158
|
+
}
|
|
159
|
+
const parsed = registryOf(JSON.parse(readFileSync(descriptor, "utf-8")));
|
|
149
160
|
if (parsed)
|
|
150
161
|
return parsed;
|
|
151
162
|
}
|
|
152
163
|
catch {
|
|
153
164
|
// Corrupt — treat as empty so the user can re-install.
|
|
154
165
|
}
|
|
166
|
+
finally {
|
|
167
|
+
if (descriptor !== undefined)
|
|
168
|
+
closeSync(descriptor);
|
|
169
|
+
}
|
|
155
170
|
return { version: 2, plugins: {} };
|
|
156
171
|
}
|
|
157
172
|
export function writeInstalledPlugins(data) {
|
|
158
173
|
const path = installedPluginsPath();
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
174
|
+
const normalized = registryOf(data);
|
|
175
|
+
if (!normalized)
|
|
176
|
+
throw new Error("invalid installed plugins registry");
|
|
177
|
+
mutateJsonFile(path, {
|
|
178
|
+
parse: parseInstalledPlugins,
|
|
179
|
+
serialize: (value) => `${JSON.stringify(value, null, 2)}\n`,
|
|
180
|
+
mutation: () => ({ value: normalized }),
|
|
181
|
+
mode: 0o600,
|
|
182
|
+
maxBytes: MAX_INSTALLED_PLUGINS_FILE_BYTES,
|
|
183
|
+
});
|
|
184
|
+
if (process.platform !== "win32")
|
|
185
|
+
chmodSync(path, 0o600);
|
|
186
|
+
}
|
|
187
|
+
function parseInstalledPlugins(raw) {
|
|
188
|
+
if (raw === undefined)
|
|
189
|
+
return { version: 2, plugins: {} };
|
|
190
|
+
const parsed = registryOf(JSON.parse(raw));
|
|
191
|
+
if (!parsed)
|
|
192
|
+
throw new Error("installed plugins registry is corrupt");
|
|
193
|
+
return parsed;
|
|
194
|
+
}
|
|
195
|
+
export function mutateInstalledPlugins(mutation) {
|
|
196
|
+
return mutateJsonFile(installedPluginsPath(), {
|
|
197
|
+
parse: parseInstalledPlugins,
|
|
198
|
+
serialize: (value) => `${JSON.stringify(value, null, 2)}\n`,
|
|
199
|
+
mutation,
|
|
200
|
+
mode: 0o600,
|
|
201
|
+
maxBytes: MAX_INSTALLED_PLUGINS_FILE_BYTES,
|
|
202
|
+
});
|
|
172
203
|
}
|
|
173
204
|
/**
|
|
174
205
|
* Append an install entry for `<plugin>@<marketplace>`. Multiple entries
|
|
175
206
|
* for the same key are allowed (different scopes); MVP only writes scope:"user".
|
|
176
207
|
*/
|
|
177
208
|
export function appendInstallEntry(key, entry) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
209
|
+
if (key.length === 0 || key.length > MAX_KEY_LENGTH || key.includes("\0")) {
|
|
210
|
+
throw new Error("invalid plugin install key");
|
|
211
|
+
}
|
|
212
|
+
const normalized = installEntryOf(entry);
|
|
213
|
+
if (!normalized)
|
|
214
|
+
throw new Error("invalid plugin install entry");
|
|
215
|
+
mutateInstalledPlugins((data) => {
|
|
216
|
+
const list = [...(data.plugins[key] ?? [])];
|
|
217
|
+
if (list.length >= MAX_INSTALLS_PER_KEY)
|
|
218
|
+
throw new Error("too many plugin installs for key");
|
|
219
|
+
list.push(normalized);
|
|
220
|
+
data.plugins[key] = list;
|
|
221
|
+
return { value: data };
|
|
222
|
+
});
|
|
183
223
|
}
|
|
184
224
|
export function removeInstallEntries(key) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
225
|
+
return (mutateInstalledPlugins((data) => {
|
|
226
|
+
if (!Object.prototype.hasOwnProperty.call(data.plugins, key))
|
|
227
|
+
return { result: false };
|
|
228
|
+
delete data.plugins[key];
|
|
229
|
+
return { value: data, result: true };
|
|
230
|
+
}) ?? false);
|
|
191
231
|
}
|
|
192
232
|
export function pluginInstallKey(plugin, marketplace) {
|
|
193
233
|
return `${plugin}@${marketplace}`;
|
|
@@ -29,8 +29,8 @@ export declare const PluginAutomationTemplate: z.ZodObject<{
|
|
|
29
29
|
en?: string | undefined;
|
|
30
30
|
"zh-CN"?: string | undefined;
|
|
31
31
|
};
|
|
32
|
-
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
33
32
|
schedule: string;
|
|
33
|
+
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
34
34
|
description?: string | undefined;
|
|
35
35
|
timezone?: string | undefined;
|
|
36
36
|
}, {
|
|
@@ -79,8 +79,8 @@ export declare const PluginAutomationsManifest: z.ZodEffects<z.ZodObject<{
|
|
|
79
79
|
en?: string | undefined;
|
|
80
80
|
"zh-CN"?: string | undefined;
|
|
81
81
|
};
|
|
82
|
-
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
83
82
|
schedule: string;
|
|
83
|
+
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
84
84
|
description?: string | undefined;
|
|
85
85
|
timezone?: string | undefined;
|
|
86
86
|
}, {
|
|
@@ -108,8 +108,8 @@ export declare const PluginAutomationsManifest: z.ZodEffects<z.ZodObject<{
|
|
|
108
108
|
en?: string | undefined;
|
|
109
109
|
"zh-CN"?: string | undefined;
|
|
110
110
|
};
|
|
111
|
-
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
112
111
|
schedule: string;
|
|
112
|
+
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
113
113
|
description?: string | undefined;
|
|
114
114
|
timezone?: string | undefined;
|
|
115
115
|
}[];
|
|
@@ -140,8 +140,8 @@ export declare const PluginAutomationsManifest: z.ZodEffects<z.ZodObject<{
|
|
|
140
140
|
en?: string | undefined;
|
|
141
141
|
"zh-CN"?: string | undefined;
|
|
142
142
|
};
|
|
143
|
-
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
144
143
|
schedule: string;
|
|
144
|
+
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
145
145
|
description?: string | undefined;
|
|
146
146
|
timezone?: string | undefined;
|
|
147
147
|
}[];
|
|
@@ -249,8 +249,8 @@ export declare const CodeShellPluginOverlay: z.ZodObject<{
|
|
|
249
249
|
en?: string | undefined;
|
|
250
250
|
"zh-CN"?: string | undefined;
|
|
251
251
|
};
|
|
252
|
-
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
253
252
|
schedule: string;
|
|
253
|
+
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
254
254
|
description?: string | undefined;
|
|
255
255
|
timezone?: string | undefined;
|
|
256
256
|
}, {
|
|
@@ -278,8 +278,8 @@ export declare const CodeShellPluginOverlay: z.ZodObject<{
|
|
|
278
278
|
en?: string | undefined;
|
|
279
279
|
"zh-CN"?: string | undefined;
|
|
280
280
|
};
|
|
281
|
-
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
282
281
|
schedule: string;
|
|
282
|
+
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
283
283
|
description?: string | undefined;
|
|
284
284
|
timezone?: string | undefined;
|
|
285
285
|
}[];
|
|
@@ -310,8 +310,8 @@ export declare const CodeShellPluginOverlay: z.ZodObject<{
|
|
|
310
310
|
en?: string | undefined;
|
|
311
311
|
"zh-CN"?: string | undefined;
|
|
312
312
|
};
|
|
313
|
-
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
314
313
|
schedule: string;
|
|
314
|
+
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
315
315
|
description?: string | undefined;
|
|
316
316
|
timezone?: string | undefined;
|
|
317
317
|
}[];
|
|
@@ -345,8 +345,8 @@ export declare const CodeShellPluginOverlay: z.ZodObject<{
|
|
|
345
345
|
en?: string | undefined;
|
|
346
346
|
"zh-CN"?: string | undefined;
|
|
347
347
|
};
|
|
348
|
-
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
349
348
|
schedule: string;
|
|
349
|
+
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
350
350
|
description?: string | undefined;
|
|
351
351
|
timezone?: string | undefined;
|
|
352
352
|
}[];
|
|
@@ -634,8 +634,8 @@ export declare const CanonicalPluginManifest: z.ZodObject<{
|
|
|
634
634
|
en?: string | undefined;
|
|
635
635
|
"zh-CN"?: string | undefined;
|
|
636
636
|
};
|
|
637
|
-
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
638
637
|
schedule: string;
|
|
638
|
+
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
639
639
|
description?: string | undefined;
|
|
640
640
|
timezone?: string | undefined;
|
|
641
641
|
}, {
|
|
@@ -663,8 +663,8 @@ export declare const CanonicalPluginManifest: z.ZodObject<{
|
|
|
663
663
|
en?: string | undefined;
|
|
664
664
|
"zh-CN"?: string | undefined;
|
|
665
665
|
};
|
|
666
|
-
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
667
666
|
schedule: string;
|
|
667
|
+
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
668
668
|
description?: string | undefined;
|
|
669
669
|
timezone?: string | undefined;
|
|
670
670
|
}[];
|
|
@@ -695,8 +695,8 @@ export declare const CanonicalPluginManifest: z.ZodObject<{
|
|
|
695
695
|
en?: string | undefined;
|
|
696
696
|
"zh-CN"?: string | undefined;
|
|
697
697
|
};
|
|
698
|
-
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
699
698
|
schedule: string;
|
|
699
|
+
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
700
700
|
description?: string | undefined;
|
|
701
701
|
timezone?: string | undefined;
|
|
702
702
|
}[];
|
|
@@ -750,8 +750,8 @@ export declare const CanonicalPluginManifest: z.ZodObject<{
|
|
|
750
750
|
en?: string | undefined;
|
|
751
751
|
"zh-CN"?: string | undefined;
|
|
752
752
|
};
|
|
753
|
-
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
754
753
|
schedule: string;
|
|
754
|
+
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
755
755
|
description?: string | undefined;
|
|
756
756
|
timezone?: string | undefined;
|
|
757
757
|
}[];
|
|
@@ -7,7 +7,7 @@ import { pluginInstallDir } from "./paths.js";
|
|
|
7
7
|
import { installPluginFromPath } from "./install.js";
|
|
8
8
|
import { installPluginFromSource } from "./installFromSource.js";
|
|
9
9
|
import { parseSource } from "./parseSource.js";
|
|
10
|
-
import { pluginInstallKey, readInstalledPlugins, removeInstallEntries,
|
|
10
|
+
import { pluginInstallKey, mutateInstalledPlugins, readInstalledPlugins, removeInstallEntries, } from "../installedPlugins.js";
|
|
11
11
|
import { pluginHookInstallRecord } from "../pluginHookIntegrity.js";
|
|
12
12
|
import { pluginMcpInstallRecord } from "../pluginMcpIntegrity.js";
|
|
13
13
|
import { readPluginMcp } from "./loadPluginMcp.js";
|
|
@@ -40,37 +40,37 @@ export async function reinstallAtomic(name, source, doInstall) {
|
|
|
40
40
|
try {
|
|
41
41
|
await doInstall();
|
|
42
42
|
if (savedEntries) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
43
|
+
mutateInstalledPlugins((data) => {
|
|
44
|
+
const installed = data.plugins[key] ?? [];
|
|
45
|
+
let changed = false;
|
|
46
|
+
for (const entry of installed) {
|
|
47
|
+
const next = pluginHookInstallRecord(entry.installPath, savedEntries);
|
|
48
|
+
const nextMcp = pluginMcpInstallRecord(entry.installPath, Object.keys(readPluginMcp(entry.installPath, name)).length > 0, savedEntries);
|
|
49
|
+
if (entry.hookDigest !== next.hookDigest ||
|
|
50
|
+
entry.approvedHookDigest !== next.approvedHookDigest ||
|
|
51
|
+
JSON.stringify(entry.approvedHookSnapshot) !==
|
|
52
|
+
JSON.stringify(next.approvedHookSnapshot) ||
|
|
53
|
+
entry.mcpDigest !== nextMcp.mcpDigest ||
|
|
54
|
+
entry.approvedMcpDigest !== nextMcp.approvedMcpDigest) {
|
|
55
|
+
entry.hookDigest = next.hookDigest;
|
|
56
|
+
if (next.approvedHookDigest)
|
|
57
|
+
entry.approvedHookDigest = next.approvedHookDigest;
|
|
58
|
+
else
|
|
59
|
+
delete entry.approvedHookDigest;
|
|
60
|
+
if (next.approvedHookSnapshot)
|
|
61
|
+
entry.approvedHookSnapshot = next.approvedHookSnapshot;
|
|
62
|
+
else
|
|
63
|
+
delete entry.approvedHookSnapshot;
|
|
64
|
+
entry.mcpDigest = nextMcp.mcpDigest;
|
|
65
|
+
if (nextMcp.approvedMcpDigest)
|
|
66
|
+
entry.approvedMcpDigest = nextMcp.approvedMcpDigest;
|
|
67
|
+
else
|
|
68
|
+
delete entry.approvedMcpDigest;
|
|
69
|
+
changed = true;
|
|
70
|
+
}
|
|
70
71
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
writeInstalledPlugins(data);
|
|
72
|
+
return changed ? { value: data } : {};
|
|
73
|
+
});
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
catch (err) {
|
|
@@ -78,13 +78,12 @@ export async function reinstallAtomic(name, source, doInstall) {
|
|
|
78
78
|
await rm(dir, { recursive: true, force: true });
|
|
79
79
|
await rename(backup, dir);
|
|
80
80
|
if (savedEntries) {
|
|
81
|
-
// Restore
|
|
82
|
-
//
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
writeInstalledPlugins(data);
|
|
81
|
+
// Restore under the registry lock so a concurrent install of a different
|
|
82
|
+
// plugin cannot be lost while this plugin rolls back.
|
|
83
|
+
mutateInstalledPlugins((data) => {
|
|
84
|
+
data.plugins[key] = savedEntries;
|
|
85
|
+
return { value: data };
|
|
86
|
+
});
|
|
88
87
|
}
|
|
89
88
|
throw new PluginInstallError(`update failed; the old version of '${name}' was kept: ` +
|
|
90
89
|
`${err instanceof Error ? err.message : String(err)}. Source: ${source}.`);
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
/** Read/write ~/.code-shell/plugins/known_marketplaces.json. */
|
|
2
|
+
import type { KnownMarketplaces, KnownMarketplace } from "./types.js";
|
|
1
3
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
+
* The single source of truth for what a marketplace name may be.
|
|
5
|
+
*
|
|
6
|
+
* Exported so entry points (AddMarketplace) reject a bad name BEFORE cloning,
|
|
7
|
+
* rather than discovering it at persist time and orphaning the clone.
|
|
4
8
|
*/
|
|
5
|
-
|
|
9
|
+
export declare function isValidMarketplaceName(name: string): boolean;
|
|
6
10
|
export declare function knownMarketplacesPath(): string;
|
|
7
11
|
export declare function readKnownMarketplaces(): KnownMarketplaces;
|
|
8
12
|
export declare function writeKnownMarketplaces(data: KnownMarketplaces): void;
|
|
@@ -1,46 +1,150 @@
|
|
|
1
|
+
/** Read/write ~/.code-shell/plugins/known_marketplaces.json. */
|
|
2
|
+
import { closeSync, constants, fstatSync, lstatSync, openSync, readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { mutateJsonFile } from "../utils/file-mutex.js";
|
|
6
|
+
const MAX_MARKETPLACE_FILE_BYTES = 4 * 1024 * 1024;
|
|
7
|
+
const MAX_MARKETPLACES = 256;
|
|
8
|
+
const MAX_NAME_LENGTH = 128;
|
|
9
|
+
const MAX_PATH_LENGTH = 4_096;
|
|
10
|
+
const MAX_TIMESTAMP_LENGTH = 128;
|
|
11
|
+
const NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
12
|
+
const GITHUB_REPO_RE = /^[A-Za-z0-9][A-Za-z0-9-]{0,38}\/[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/;
|
|
1
13
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
14
|
+
* The single source of truth for what a marketplace name may be.
|
|
15
|
+
*
|
|
16
|
+
* Exported so entry points (AddMarketplace) reject a bad name BEFORE cloning,
|
|
17
|
+
* rather than discovering it at persist time and orphaning the clone.
|
|
4
18
|
*/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
19
|
+
export function isValidMarketplaceName(name) {
|
|
20
|
+
return name.length <= MAX_NAME_LENGTH && NAME_RE.test(name);
|
|
21
|
+
}
|
|
8
22
|
function userHome() {
|
|
9
23
|
return process.env.HOME ?? homedir();
|
|
10
24
|
}
|
|
11
25
|
export function knownMarketplacesPath() {
|
|
12
26
|
return join(userHome(), ".code-shell", "plugins", "known_marketplaces.json");
|
|
13
27
|
}
|
|
28
|
+
function marketplaceOf(value) {
|
|
29
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
30
|
+
return undefined;
|
|
31
|
+
const raw = value;
|
|
32
|
+
if (!raw.source || typeof raw.source !== "object" || Array.isArray(raw.source))
|
|
33
|
+
return undefined;
|
|
34
|
+
const source = raw.source;
|
|
35
|
+
const normalizedSource = source.source === "github" &&
|
|
36
|
+
typeof source.repo === "string" &&
|
|
37
|
+
GITHUB_REPO_RE.test(source.repo)
|
|
38
|
+
? { source: "github", repo: source.repo }
|
|
39
|
+
: source.source === "git" &&
|
|
40
|
+
typeof source.url === "string" &&
|
|
41
|
+
source.url.length > 0 &&
|
|
42
|
+
source.url.length <= MAX_PATH_LENGTH &&
|
|
43
|
+
!source.url.includes("\0")
|
|
44
|
+
? { source: "git", url: source.url }
|
|
45
|
+
: undefined;
|
|
46
|
+
if (!normalizedSource ||
|
|
47
|
+
typeof raw.installLocation !== "string" ||
|
|
48
|
+
raw.installLocation.length === 0 ||
|
|
49
|
+
raw.installLocation.length > MAX_PATH_LENGTH ||
|
|
50
|
+
raw.installLocation.includes("\0") ||
|
|
51
|
+
typeof raw.lastUpdated !== "string" ||
|
|
52
|
+
raw.lastUpdated.length === 0 ||
|
|
53
|
+
raw.lastUpdated.length > MAX_TIMESTAMP_LENGTH) {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
const format = raw.format === "claude-code" || raw.format === "codex" || raw.format === "universal"
|
|
57
|
+
? raw.format
|
|
58
|
+
: undefined;
|
|
59
|
+
if (raw.format !== undefined && !format)
|
|
60
|
+
return undefined;
|
|
61
|
+
return {
|
|
62
|
+
source: normalizedSource,
|
|
63
|
+
installLocation: raw.installLocation,
|
|
64
|
+
lastUpdated: raw.lastUpdated,
|
|
65
|
+
...(format ? { format } : {}),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function parseKnownMarketplaces(raw) {
|
|
69
|
+
if (raw === undefined)
|
|
70
|
+
return Object.create(null);
|
|
71
|
+
const parsed = JSON.parse(raw);
|
|
72
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
73
|
+
throw new Error("known marketplaces registry must be an object");
|
|
74
|
+
}
|
|
75
|
+
const entries = Object.entries(parsed);
|
|
76
|
+
if (entries.length > MAX_MARKETPLACES)
|
|
77
|
+
throw new Error("too many known marketplaces");
|
|
78
|
+
const result = Object.create(null);
|
|
79
|
+
for (const [name, value] of entries) {
|
|
80
|
+
// ISOLATE, don't throw. NAME_RE is stricter than the writers that produced
|
|
81
|
+
// these keys (AddMarketplace accepts any non-empty string, and
|
|
82
|
+
// assertSafePluginName permits spaces/non-ASCII), so a single legacy name
|
|
83
|
+
// like "My Market" used to make the whole registry read as empty AND make
|
|
84
|
+
// every subsequent write throw — unrecoverable, because the read reported
|
|
85
|
+
// nothing for the user to fix. Skipping matches registryOf() in
|
|
86
|
+
// installedPlugins.ts, which is the correct shape for this file family.
|
|
87
|
+
if (name.length > MAX_NAME_LENGTH || !NAME_RE.test(name))
|
|
88
|
+
continue;
|
|
89
|
+
const marketplace = marketplaceOf(value);
|
|
90
|
+
if (!marketplace)
|
|
91
|
+
continue;
|
|
92
|
+
result[name] = marketplace;
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
14
96
|
export function readKnownMarketplaces() {
|
|
15
97
|
const path = knownMarketplacesPath();
|
|
16
|
-
|
|
17
|
-
return {};
|
|
98
|
+
let descriptor;
|
|
18
99
|
try {
|
|
19
|
-
const
|
|
20
|
-
if (
|
|
21
|
-
|
|
100
|
+
const metadata = lstatSync(path);
|
|
101
|
+
if (metadata.isSymbolicLink() ||
|
|
102
|
+
!metadata.isFile() ||
|
|
103
|
+
metadata.size > MAX_MARKETPLACE_FILE_BYTES) {
|
|
104
|
+
return Object.create(null);
|
|
105
|
+
}
|
|
106
|
+
descriptor = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
107
|
+
const opened = fstatSync(descriptor);
|
|
108
|
+
if (!opened.isFile() || opened.size > MAX_MARKETPLACE_FILE_BYTES) {
|
|
109
|
+
return Object.create(null);
|
|
22
110
|
}
|
|
111
|
+
return parseKnownMarketplaces(readFileSync(descriptor, "utf8"));
|
|
23
112
|
}
|
|
24
113
|
catch {
|
|
25
|
-
|
|
114
|
+
return Object.create(null);
|
|
26
115
|
}
|
|
27
|
-
|
|
116
|
+
finally {
|
|
117
|
+
if (descriptor !== undefined)
|
|
118
|
+
closeSync(descriptor);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function mutateKnownMarketplaces(change) {
|
|
122
|
+
return mutateJsonFile(knownMarketplacesPath(), {
|
|
123
|
+
parse: parseKnownMarketplaces,
|
|
124
|
+
serialize: (value) => `${JSON.stringify(value, null, 2)}\n`,
|
|
125
|
+
mutation: change,
|
|
126
|
+
mode: 0o600,
|
|
127
|
+
maxBytes: MAX_MARKETPLACE_FILE_BYTES,
|
|
128
|
+
});
|
|
28
129
|
}
|
|
29
130
|
export function writeKnownMarketplaces(data) {
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
writeFileSync(path, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
131
|
+
const validated = parseKnownMarketplaces(JSON.stringify(data));
|
|
132
|
+
mutateKnownMarketplaces(() => ({ value: validated }));
|
|
33
133
|
}
|
|
34
134
|
export function upsertKnownMarketplace(name, entry) {
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
135
|
+
const marketplace = marketplaceOf(entry);
|
|
136
|
+
if (!NAME_RE.test(name) || !marketplace)
|
|
137
|
+
throw new Error("invalid known marketplace");
|
|
138
|
+
mutateKnownMarketplaces((current) => ({ value: { ...current, [name]: marketplace } }));
|
|
38
139
|
}
|
|
39
140
|
export function removeKnownMarketplace(name) {
|
|
40
|
-
|
|
41
|
-
if (!(name in data))
|
|
141
|
+
if (!NAME_RE.test(name))
|
|
42
142
|
return false;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
143
|
+
return (mutateKnownMarketplaces((current) => {
|
|
144
|
+
if (!Object.prototype.hasOwnProperty.call(current, name))
|
|
145
|
+
return { result: false };
|
|
146
|
+
const next = { ...current };
|
|
147
|
+
delete next[name];
|
|
148
|
+
return { value: next, result: true };
|
|
149
|
+
}) ?? false);
|
|
46
150
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { closeSync, constants, fstatSync, lstatSync, openSync, readFileSync } from "node:fs";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { readInstalledPlugins } from "./installedPlugins.js";
|
|
5
5
|
import { pluginsRoot } from "./installer/paths.js";
|
|
6
6
|
import { CANONICAL_PLUGIN_MANIFEST_FILE, CanonicalPluginManifest, } from "./installer/types.js";
|
|
7
7
|
import { resolveSafePluginPath } from "./pluginInstaller.js";
|
|
8
|
+
const MAX_CANONICAL_MANIFEST_BYTES = 4 * 1024 * 1024;
|
|
8
9
|
/**
|
|
9
10
|
* Stable content revision used to bind a user review to the exact template
|
|
10
11
|
* instantiated later. Install paths and timestamps are intentionally excluded.
|
|
@@ -20,14 +21,27 @@ export function pluginAutomationTemplateRevision(installKey, template) {
|
|
|
20
21
|
}
|
|
21
22
|
function readCanonicalManifest(installPath) {
|
|
22
23
|
const file = join(installPath, CANONICAL_PLUGIN_MANIFEST_FILE);
|
|
23
|
-
|
|
24
|
-
return null;
|
|
24
|
+
let descriptor;
|
|
25
25
|
try {
|
|
26
|
-
|
|
26
|
+
const metadata = lstatSync(file);
|
|
27
|
+
if (metadata.isSymbolicLink() ||
|
|
28
|
+
!metadata.isFile() ||
|
|
29
|
+
metadata.size > MAX_CANONICAL_MANIFEST_BYTES) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
descriptor = openSync(file, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
33
|
+
const opened = fstatSync(descriptor);
|
|
34
|
+
if (!opened.isFile() || opened.size > MAX_CANONICAL_MANIFEST_BYTES)
|
|
35
|
+
return null;
|
|
36
|
+
return CanonicalPluginManifest.parse(JSON.parse(readFileSync(descriptor, "utf-8")));
|
|
27
37
|
}
|
|
28
38
|
catch {
|
|
29
39
|
return null;
|
|
30
40
|
}
|
|
41
|
+
finally {
|
|
42
|
+
if (descriptor !== undefined)
|
|
43
|
+
closeSync(descriptor);
|
|
44
|
+
}
|
|
31
45
|
}
|
|
32
46
|
function identityFromInstallKey(installKey, manifest) {
|
|
33
47
|
const at = installKey.lastIndexOf("@");
|