@epoch-agent/core 0.21.0 → 0.22.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/dist/index.d.ts +97 -1
- package/dist/index.js +846 -616
- package/package.json +5 -3
package/dist/index.js
CHANGED
|
@@ -6296,6 +6296,7 @@ var ManifestSchema = z.object({
|
|
|
6296
6296
|
epochVersion: z.string().min(1).optional(),
|
|
6297
6297
|
homepage: z.string().min(1).optional(),
|
|
6298
6298
|
keywords: z.array(z.string().min(1)).optional(),
|
|
6299
|
+
dependencies: z.array(z.string().min(1)).optional().catch(void 0),
|
|
6299
6300
|
displayName: z.string().min(1).optional(),
|
|
6300
6301
|
category: z.string().min(1).optional(),
|
|
6301
6302
|
icon: z.string().min(1).optional(),
|
|
@@ -6338,6 +6339,23 @@ function readPluginManifest(dir) {
|
|
|
6338
6339
|
})
|
|
6339
6340
|
});
|
|
6340
6341
|
}
|
|
6342
|
+
const rawDependencies = parsed?.dependencies;
|
|
6343
|
+
if (rawDependencies !== void 0 && !Array.isArray(rawDependencies)) {
|
|
6344
|
+
issues.push({
|
|
6345
|
+
path: `${path}.dependencies`,
|
|
6346
|
+
message: t("plugin_manifest.bad_dependencies", { raw: JSON.stringify(rawDependencies) })
|
|
6347
|
+
});
|
|
6348
|
+
}
|
|
6349
|
+
if (Array.isArray(rawDependencies)) {
|
|
6350
|
+
for (const item of rawDependencies) {
|
|
6351
|
+
if (typeof item !== "string" || !isValidPluginName(item)) {
|
|
6352
|
+
issues.push({
|
|
6353
|
+
path: `${path}.dependencies`,
|
|
6354
|
+
message: t("plugin_manifest.bad_dependency_name", { name: String(item) })
|
|
6355
|
+
});
|
|
6356
|
+
}
|
|
6357
|
+
}
|
|
6358
|
+
}
|
|
6341
6359
|
const checked = parseLenient(ManifestSchema.optional(), parsed, void 0);
|
|
6342
6360
|
if (!checked.value) {
|
|
6343
6361
|
issues.push(
|
|
@@ -6639,6 +6657,7 @@ function loadPlugins(opts) {
|
|
|
6639
6657
|
const state = readPluginRecords(statePath);
|
|
6640
6658
|
const out = noPlugins();
|
|
6641
6659
|
out.issues.push(...state.issues);
|
|
6660
|
+
const declared = /* @__PURE__ */ new Map();
|
|
6642
6661
|
const epochVersion = opts.epochVersion ?? currentEpochVersion();
|
|
6643
6662
|
for (const record of state.records) {
|
|
6644
6663
|
const outcome = admit(record, epochVersion);
|
|
@@ -6657,6 +6676,21 @@ function loadPlugins(opts) {
|
|
|
6657
6676
|
continue;
|
|
6658
6677
|
}
|
|
6659
6678
|
collect(record, out);
|
|
6679
|
+
if (outcome.manifest?.dependencies?.length) {
|
|
6680
|
+
declared.set(record.name, outcome.manifest.dependencies);
|
|
6681
|
+
}
|
|
6682
|
+
}
|
|
6683
|
+
const present = new Set(out.loaded.map((plugin) => plugin.name));
|
|
6684
|
+
for (const [name, dependencies] of declared) {
|
|
6685
|
+
if (!present.has(name)) continue;
|
|
6686
|
+
for (const dependency of dependencies) {
|
|
6687
|
+
if (present.has(dependency)) continue;
|
|
6688
|
+
diags?.warn(
|
|
6689
|
+
MODULE,
|
|
6690
|
+
t("plugin_load.dependency_missing", { name, dependency }),
|
|
6691
|
+
"plugin-dependency-missing"
|
|
6692
|
+
);
|
|
6693
|
+
}
|
|
6660
6694
|
}
|
|
6661
6695
|
for (const detail of issueDetails(out.issues)) {
|
|
6662
6696
|
diags?.warn(MODULE, detail, "plugin-invalid");
|
|
@@ -6683,7 +6717,7 @@ function admit(record, epochVersion) {
|
|
|
6683
6717
|
if (!version.ok) {
|
|
6684
6718
|
return { reason: version.reason, code: "plugin-version-mismatch" };
|
|
6685
6719
|
}
|
|
6686
|
-
return {};
|
|
6720
|
+
return { manifest: manifest.manifest };
|
|
6687
6721
|
}
|
|
6688
6722
|
function collect(record, out) {
|
|
6689
6723
|
const { name, path: dir } = record;
|
|
@@ -7029,708 +7063,874 @@ function firstLine3(err) {
|
|
|
7029
7063
|
const message = err instanceof Error ? err.message : String(err);
|
|
7030
7064
|
return message.split("\n")[0] ?? message;
|
|
7031
7065
|
}
|
|
7032
|
-
var
|
|
7033
|
-
var
|
|
7034
|
-
|
|
7035
|
-
|
|
7036
|
-
|
|
7037
|
-
|
|
7038
|
-
|
|
7039
|
-
|
|
7040
|
-
|
|
7041
|
-
|
|
7042
|
-
|
|
7043
|
-
|
|
7044
|
-
|
|
7045
|
-
|
|
7046
|
-
|
|
7047
|
-
|
|
7048
|
-
|
|
7049
|
-
|
|
7050
|
-
|
|
7051
|
-
|
|
7052
|
-
|
|
7053
|
-
|
|
7054
|
-
|
|
7055
|
-
}
|
|
7056
|
-
|
|
7057
|
-
|
|
7058
|
-
|
|
7059
|
-
|
|
7060
|
-
|
|
7061
|
-
|
|
7062
|
-
|
|
7063
|
-
|
|
7064
|
-
|
|
7065
|
-
|
|
7066
|
-
|
|
7067
|
-
|
|
7068
|
-
|
|
7069
|
-
|
|
7070
|
-
|
|
7071
|
-
|
|
7072
|
-
|
|
7073
|
-
|
|
7074
|
-
|
|
7075
|
-
|
|
7076
|
-
|
|
7077
|
-
|
|
7078
|
-
|
|
7079
|
-
|
|
7080
|
-
|
|
7081
|
-
const
|
|
7082
|
-
|
|
7083
|
-
|
|
7084
|
-
|
|
7085
|
-
|
|
7086
|
-
|
|
7087
|
-
function isArchiveUrl(url) {
|
|
7088
|
-
return isWirePluginArchiveName(url);
|
|
7066
|
+
var MARKETPLACE_FILE = "epoch-marketplace.json";
|
|
7067
|
+
var NAME_PATTERN = PLUGIN_NAME_PATTERN;
|
|
7068
|
+
var EntryCategoryInput = z.union([z.string().min(1), z.array(z.string().min(1))]).optional().catch(void 0);
|
|
7069
|
+
var EntryShape = z.object({
|
|
7070
|
+
name: z.string().regex(NAME_PATTERN),
|
|
7071
|
+
description: z.string().min(1).optional(),
|
|
7072
|
+
source: z.string().min(1),
|
|
7073
|
+
category: EntryCategoryInput,
|
|
7074
|
+
keywords: z.array(z.string().min(1)).optional(),
|
|
7075
|
+
author: PluginAuthorSchema.optional(),
|
|
7076
|
+
downloads: z.number().int().nonnegative().optional(),
|
|
7077
|
+
likes: z.number().int().nonnegative().optional(),
|
|
7078
|
+
displayName: z.string().min(1).optional(),
|
|
7079
|
+
icon: z.string().min(1).optional(),
|
|
7080
|
+
version: z.string().min(1).optional(),
|
|
7081
|
+
changelog: z.string().min(1).optional(),
|
|
7082
|
+
size: z.number().int().positive().optional(),
|
|
7083
|
+
homepage: z.string().min(1).optional(),
|
|
7084
|
+
visibility: z.enum(PLUGIN_VISIBILITY).optional().catch(void 0),
|
|
7085
|
+
status: z.enum(["listed", "reviewing", "rejected", "offline"]).optional().catch(void 0)
|
|
7086
|
+
});
|
|
7087
|
+
var EntrySchema = EntryShape.transform(({ category, ...rest }) => {
|
|
7088
|
+
const picked = category === void 0 ? [] : typeof category === "string" ? [category] : category;
|
|
7089
|
+
return { ...rest, ...picked.length === 0 ? {} : { category: [...picked] } };
|
|
7090
|
+
});
|
|
7091
|
+
var MARKETPLACE_DISPLAY_FIELDS = [
|
|
7092
|
+
"status",
|
|
7093
|
+
"downloads",
|
|
7094
|
+
"likes",
|
|
7095
|
+
"author",
|
|
7096
|
+
"category",
|
|
7097
|
+
"visibility",
|
|
7098
|
+
"icon",
|
|
7099
|
+
"homepage",
|
|
7100
|
+
"size"
|
|
7101
|
+
];
|
|
7102
|
+
var DisplaySchema = z.object({
|
|
7103
|
+
fields: z.array(z.string().min(1)).optional()
|
|
7104
|
+
});
|
|
7105
|
+
var CategoryNodeSchema = z.lazy(
|
|
7106
|
+
() => z.object({
|
|
7107
|
+
id: z.string().min(1),
|
|
7108
|
+
label: z.string().min(1).optional(),
|
|
7109
|
+
children: z.array(CategoryNodeSchema).optional()
|
|
7110
|
+
})
|
|
7111
|
+
);
|
|
7112
|
+
var CategoriesSchema = z.array(CategoryNodeSchema).refine(hasUniqueCategoryIds, { message: "category ids must be unique across the whole tree" }).optional().catch(void 0);
|
|
7113
|
+
function hasUniqueCategoryIds(nodes) {
|
|
7114
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7115
|
+
const walk3 = (list) => list.every((node) => {
|
|
7116
|
+
if (seen.has(node.id)) return false;
|
|
7117
|
+
seen.add(node.id);
|
|
7118
|
+
return node.children === void 0 || walk3(node.children);
|
|
7119
|
+
});
|
|
7120
|
+
return walk3(nodes);
|
|
7089
7121
|
}
|
|
7090
|
-
|
|
7091
|
-
|
|
7092
|
-
|
|
7093
|
-
|
|
7094
|
-
|
|
7095
|
-
|
|
7096
|
-
|
|
7122
|
+
var CatalogSchema = z.object({
|
|
7123
|
+
name: z.string().regex(NAME_PATTERN),
|
|
7124
|
+
description: z.string().min(1).optional(),
|
|
7125
|
+
owner: z.object({ name: z.string().min(1), url: z.string().min(1).optional() }).optional(),
|
|
7126
|
+
display: DisplaySchema.optional(),
|
|
7127
|
+
categories: CategoriesSchema,
|
|
7128
|
+
plugins: z.array(EntrySchema)
|
|
7129
|
+
});
|
|
7130
|
+
var RecordSchema2 = z.object({
|
|
7131
|
+
name: z.string().regex(NAME_PATTERN),
|
|
7132
|
+
source: z.string().min(1),
|
|
7133
|
+
addedAt: z.number(),
|
|
7134
|
+
catalog: CatalogSchema
|
|
7135
|
+
});
|
|
7136
|
+
var StateSchema2 = z.object({
|
|
7137
|
+
version: z.literal(1).optional(),
|
|
7138
|
+
marketplaces: z.array(RecordSchema2)
|
|
7139
|
+
});
|
|
7140
|
+
function projectEntryForDisplay(entry, display) {
|
|
7141
|
+
const allowed = display?.fields;
|
|
7142
|
+
if (allowed === void 0) return entry;
|
|
7143
|
+
const out = { ...entry };
|
|
7144
|
+
for (const field of MARKETPLACE_DISPLAY_FIELDS) {
|
|
7145
|
+
if (!displayAllows(display, field)) delete out[field];
|
|
7146
|
+
}
|
|
7147
|
+
return out;
|
|
7097
7148
|
}
|
|
7098
|
-
function
|
|
7099
|
-
|
|
7149
|
+
function displayAllows(display, field) {
|
|
7150
|
+
const allowed = display?.fields;
|
|
7151
|
+
if (allowed === void 0) return true;
|
|
7152
|
+
return allowed.includes(field);
|
|
7100
7153
|
}
|
|
7101
|
-
|
|
7102
|
-
|
|
7103
|
-
|
|
7104
|
-
|
|
7154
|
+
function readMarketplaces(statePath) {
|
|
7155
|
+
if (!existsSync(statePath)) return { records: [], issues: [] };
|
|
7156
|
+
let raw;
|
|
7157
|
+
try {
|
|
7158
|
+
raw = JSON.parse(readFileSync(statePath, "utf-8"));
|
|
7159
|
+
} catch (err) {
|
|
7105
7160
|
return {
|
|
7106
|
-
|
|
7107
|
-
|
|
7161
|
+
records: [],
|
|
7162
|
+
issues: [
|
|
7163
|
+
{ path: statePath, message: t("plugin_marketplace.bad_json", { reason: first(err) }) }
|
|
7164
|
+
]
|
|
7108
7165
|
};
|
|
7109
7166
|
}
|
|
7110
|
-
|
|
7111
|
-
|
|
7167
|
+
const checked = parseLenient(StateSchema2.optional(), raw, void 0);
|
|
7168
|
+
if (!checked.value) {
|
|
7169
|
+
return {
|
|
7170
|
+
records: [],
|
|
7171
|
+
issues: checked.issues.map((i) => ({
|
|
7172
|
+
path: statePath,
|
|
7173
|
+
message: t("plugin_marketplace.issue", { path: i.path, message: i.message })
|
|
7174
|
+
}))
|
|
7175
|
+
};
|
|
7176
|
+
}
|
|
7177
|
+
return { records: checked.value.marketplaces, issues: [] };
|
|
7178
|
+
}
|
|
7179
|
+
async function mutate(statePath, fn) {
|
|
7180
|
+
const release = await acquireStateLock(statePath);
|
|
7112
7181
|
try {
|
|
7113
|
-
|
|
7182
|
+
const next = fn(readMarketplaces(statePath).records);
|
|
7183
|
+
if (!next) return false;
|
|
7184
|
+
writeStateFile(statePath, JSON.stringify({ version: 1, marketplaces: next }, null, 2));
|
|
7185
|
+
return true;
|
|
7114
7186
|
} finally {
|
|
7115
|
-
|
|
7187
|
+
release();
|
|
7116
7188
|
}
|
|
7117
7189
|
}
|
|
7118
|
-
async function
|
|
7119
|
-
const fetched = await
|
|
7120
|
-
if (!fetched.ok) return
|
|
7121
|
-
const
|
|
7122
|
-
|
|
7123
|
-
|
|
7124
|
-
|
|
7125
|
-
|
|
7126
|
-
return { ok: false, reason: t("plugin_install.manifest_unusable", { detail: `
|
|
7127
|
-
${detail}` }) };
|
|
7128
|
-
}
|
|
7129
|
-
const taken = new PluginStore(opts.statePath).read().records.find((r) => r.name === manifest.name);
|
|
7130
|
-
const conflict = taken && taken.name !== opts.replace ? taken : void 0;
|
|
7131
|
-
const current = opts.epochVersion ?? currentEpochVersion();
|
|
7132
|
-
const version = checkEpochVersion(manifest.epochVersion, current);
|
|
7133
|
-
const versionUnmet = !version.ok && manifest.epochVersion ? { required: manifest.epochVersion, current, reason: version.reason } : void 0;
|
|
7134
|
-
const preview3 = {
|
|
7135
|
-
manifest,
|
|
7136
|
-
source: resolved,
|
|
7137
|
-
inventory: scanPluginDir(readDir, manifest.name),
|
|
7138
|
-
...conflict ? { conflict } : {},
|
|
7139
|
-
...versionUnmet ? { versionUnmet } : {}
|
|
7190
|
+
async function addMarketplace(source, opts) {
|
|
7191
|
+
const fetched = await fetchCatalog(source);
|
|
7192
|
+
if (!fetched.ok) return fetched;
|
|
7193
|
+
const record = {
|
|
7194
|
+
name: fetched.catalog.name,
|
|
7195
|
+
source: source.trim(),
|
|
7196
|
+
addedAt: Date.now(),
|
|
7197
|
+
catalog: fetched.catalog
|
|
7140
7198
|
};
|
|
7141
|
-
|
|
7199
|
+
const written = await mutate(
|
|
7200
|
+
opts.statePath,
|
|
7201
|
+
(records) => records.some((r) => r.name === record.name) ? void 0 : [...records, record].sort((a, b) => a.name.localeCompare(b.name))
|
|
7202
|
+
);
|
|
7203
|
+
return written ? { ok: true, record } : {
|
|
7204
|
+
ok: false,
|
|
7205
|
+
reason: t("plugin_marketplace.already_added", { name: record.name })
|
|
7206
|
+
};
|
|
7207
|
+
}
|
|
7208
|
+
async function removeMarketplace(name, opts) {
|
|
7209
|
+
const written = await mutate(
|
|
7210
|
+
opts.statePath,
|
|
7211
|
+
(records) => records.some((r) => r.name === name) ? records.filter((r) => r.name !== name) : void 0
|
|
7212
|
+
);
|
|
7213
|
+
return written ? { ok: true } : { ok: false, reason: t("plugin_marketplace.not_added", { name }) };
|
|
7214
|
+
}
|
|
7215
|
+
async function updateMarketplace(name, opts) {
|
|
7216
|
+
const existing = readMarketplaces(opts.statePath).records.find((r) => r.name === name);
|
|
7217
|
+
if (!existing) return { ok: false, reason: t("plugin_marketplace.not_added", { name }) };
|
|
7218
|
+
const fetched = await fetchCatalog(existing.source);
|
|
7219
|
+
if (!fetched.ok) return fetched;
|
|
7220
|
+
if (fetched.catalog.name !== name) {
|
|
7142
7221
|
return {
|
|
7143
7222
|
ok: false,
|
|
7144
|
-
|
|
7145
|
-
|
|
7223
|
+
reason: t("plugin_marketplace.renamed", {
|
|
7224
|
+
source: existing.source,
|
|
7225
|
+
name: fetched.catalog.name
|
|
7226
|
+
})
|
|
7146
7227
|
};
|
|
7147
7228
|
}
|
|
7148
|
-
|
|
7229
|
+
const record = { ...existing, catalog: fetched.catalog };
|
|
7230
|
+
await mutate(opts.statePath, (records) => records.map((r) => r.name === name ? record : r));
|
|
7231
|
+
return { ok: true, record };
|
|
7232
|
+
}
|
|
7233
|
+
function searchMarketplaces(keyword, statePath) {
|
|
7234
|
+
const needle = keyword.trim().toLowerCase();
|
|
7235
|
+
const hits = [];
|
|
7236
|
+
for (const record of readMarketplaces(statePath).records) {
|
|
7237
|
+
for (const entry of record.catalog.plugins) {
|
|
7238
|
+
const haystack = [
|
|
7239
|
+
entry.name,
|
|
7240
|
+
entry.description ?? "",
|
|
7241
|
+
...entry.category ?? [],
|
|
7242
|
+
entry.author?.name ?? "",
|
|
7243
|
+
...entry.keywords ?? []
|
|
7244
|
+
].join(" ").toLowerCase();
|
|
7245
|
+
if (needle && !haystack.includes(needle)) continue;
|
|
7246
|
+
hits.push({
|
|
7247
|
+
marketplace: record.name,
|
|
7248
|
+
entry: projectEntryForDisplay(entry, record.catalog.display),
|
|
7249
|
+
ref: `${record.name}/${entry.name}`
|
|
7250
|
+
});
|
|
7251
|
+
}
|
|
7252
|
+
}
|
|
7253
|
+
return hits;
|
|
7254
|
+
}
|
|
7255
|
+
function resolveMarketplaceRef(input, statePath) {
|
|
7256
|
+
const parts = input.trim().split("/");
|
|
7257
|
+
if (parts.length !== 2) return void 0;
|
|
7258
|
+
const [market = "", plugin = ""] = parts;
|
|
7259
|
+
if (!NAME_PATTERN.test(market) || !NAME_PATTERN.test(plugin)) return void 0;
|
|
7260
|
+
const record = readMarketplaces(statePath).records.find((r) => r.name === market);
|
|
7261
|
+
if (!record) {
|
|
7149
7262
|
return {
|
|
7150
7263
|
ok: false,
|
|
7151
|
-
|
|
7152
|
-
reason: t("plugin_install.already_installed", {
|
|
7153
|
-
name: conflict.name,
|
|
7154
|
-
version: conflict.version,
|
|
7155
|
-
source: conflict.source
|
|
7156
|
-
})
|
|
7264
|
+
reason: t("plugin_marketplace.market_not_added", { market, input })
|
|
7157
7265
|
};
|
|
7158
7266
|
}
|
|
7159
|
-
|
|
7160
|
-
|
|
7161
|
-
|
|
7162
|
-
|
|
7163
|
-
|
|
7164
|
-
|
|
7165
|
-
preview: preview3,
|
|
7166
|
-
reason: t("plugin_install.raced", { name: manifest.name })
|
|
7167
|
-
};
|
|
7168
|
-
}
|
|
7169
|
-
async function commit(source, staging, preview3, opts) {
|
|
7170
|
-
const name = preview3.manifest.name;
|
|
7171
|
-
const dest = join(opts.pluginsDir, name);
|
|
7172
|
-
removeInstallDest(dest);
|
|
7173
|
-
const linked = source.type === "local";
|
|
7174
|
-
if (linked) {
|
|
7175
|
-
try {
|
|
7176
|
-
symlinkSync(source.target, dest, "junction");
|
|
7177
|
-
} catch {
|
|
7178
|
-
opts.onProgress?.(t("plugin_install.symlink_failed"));
|
|
7179
|
-
cpSync(source.target, dest, { recursive: true });
|
|
7180
|
-
return write(false);
|
|
7181
|
-
}
|
|
7182
|
-
} else {
|
|
7183
|
-
renameSync(pluginRoot(source, staging), dest);
|
|
7267
|
+
const entry = record.catalog.plugins.find((p) => p.name === plugin);
|
|
7268
|
+
if (!entry) {
|
|
7269
|
+
return {
|
|
7270
|
+
ok: false,
|
|
7271
|
+
reason: t("plugin_marketplace.plugin_not_in_market", { market, plugin })
|
|
7272
|
+
};
|
|
7184
7273
|
}
|
|
7185
|
-
|
|
7186
|
-
|
|
7187
|
-
|
|
7188
|
-
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
|
|
7192
|
-
|
|
7193
|
-
|
|
7194
|
-
enabled: true,
|
|
7195
|
-
installedAt: Date.now(),
|
|
7196
|
-
...opts.marketplace ? { marketplace: opts.marketplace } : {},
|
|
7197
|
-
...source.sha256 ? { checksum: `sha256:${source.sha256}` } : {}
|
|
7274
|
+
const source = resolveEntrySource(entry.source, record.source);
|
|
7275
|
+
if (!source) {
|
|
7276
|
+
return {
|
|
7277
|
+
ok: false,
|
|
7278
|
+
reason: t("plugin_marketplace.entry_unresolvable", {
|
|
7279
|
+
market,
|
|
7280
|
+
plugin,
|
|
7281
|
+
source: entry.source
|
|
7282
|
+
})
|
|
7198
7283
|
};
|
|
7199
|
-
return new PluginStore(opts.statePath).mutate((records) => {
|
|
7200
|
-
const taken = records.some((r) => r.name === name);
|
|
7201
|
-
if (taken && opts.replace !== name) return void 0;
|
|
7202
|
-
return {
|
|
7203
|
-
records: taken ? records.map((r) => r.name === name ? record : r) : [...records, record],
|
|
7204
|
-
result: record
|
|
7205
|
-
};
|
|
7206
|
-
});
|
|
7207
7284
|
}
|
|
7285
|
+
return { ok: true, ref: { marketplace: market, source } };
|
|
7208
7286
|
}
|
|
7209
|
-
function
|
|
7210
|
-
|
|
7211
|
-
|
|
7212
|
-
|
|
7213
|
-
|
|
7214
|
-
|
|
7215
|
-
if (
|
|
7216
|
-
|
|
7217
|
-
|
|
7287
|
+
function resolveEntrySource(entry, marketSource) {
|
|
7288
|
+
const spec = entry.trim();
|
|
7289
|
+
if (!spec.startsWith(".") && !spec.startsWith("/")) return spec;
|
|
7290
|
+
const rel = spec.replace(/^\.\//, "").replace(/^\//, "");
|
|
7291
|
+
if (rel === "" || rel.split("/").includes("..")) return void 0;
|
|
7292
|
+
const market = marketSource.trim();
|
|
7293
|
+
if (market.startsWith("github:") || market.startsWith("git+")) {
|
|
7294
|
+
const [base = "", fragment] = market.split("#", 2);
|
|
7295
|
+
const prefix = fragment?.startsWith("subdir=") ? `${fragment.slice("subdir=".length)}/` : "";
|
|
7296
|
+
return `${base}#subdir=${prefix}${rel}`;
|
|
7218
7297
|
}
|
|
7219
|
-
|
|
7220
|
-
|
|
7221
|
-
} catch {
|
|
7222
|
-
rmdirSync(dest);
|
|
7298
|
+
if (market.startsWith("https://") || market.startsWith("http://")) {
|
|
7299
|
+
return `${market.replace(/\/[^/]*$/, "")}/${rel}`;
|
|
7223
7300
|
}
|
|
7301
|
+
return join(marketDir(market), rel);
|
|
7224
7302
|
}
|
|
7225
|
-
|
|
7226
|
-
|
|
7227
|
-
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
}
|
|
7233
|
-
async function fetchLocalArchive(source, staging) {
|
|
7234
|
-
if (!existsSync(source.target)) {
|
|
7235
|
-
return { ok: false, reason: t("plugin_install.archive_missing", { path: source.target }) };
|
|
7303
|
+
function findMarketplaceEntryByName(name, statePath) {
|
|
7304
|
+
for (const record of readMarketplaces(statePath).records) {
|
|
7305
|
+
const entry = record.catalog.plugins.find((plugin) => plugin.name === name);
|
|
7306
|
+
if (!entry) continue;
|
|
7307
|
+
const source = resolveEntrySource(entry.source, record.source);
|
|
7308
|
+
if (!source) continue;
|
|
7309
|
+
return { marketplace: record.name, source };
|
|
7236
7310
|
}
|
|
7237
|
-
|
|
7238
|
-
return extractArchive(source.target, staging);
|
|
7311
|
+
return void 0;
|
|
7239
7312
|
}
|
|
7240
|
-
async function
|
|
7241
|
-
const
|
|
7242
|
-
if (!
|
|
7243
|
-
|
|
7244
|
-
|
|
7245
|
-
|
|
7246
|
-
|
|
7247
|
-
|
|
7248
|
-
let answer;
|
|
7313
|
+
async function fetchCatalog(source) {
|
|
7314
|
+
const input = source.trim();
|
|
7315
|
+
if (!input) return { ok: false, reason: t("plugin_marketplace.source_empty") };
|
|
7316
|
+
const local = localCatalogPath(input);
|
|
7317
|
+
if (local) {
|
|
7318
|
+
if (!existsSync(local)) {
|
|
7319
|
+
return { ok: false, reason: t("plugin_marketplace.local_missing", { path: local }) };
|
|
7320
|
+
}
|
|
7249
7321
|
try {
|
|
7250
|
-
|
|
7322
|
+
return parseCatalog(readFileSync(local, "utf-8"), local);
|
|
7251
7323
|
} catch (err) {
|
|
7252
|
-
return {
|
|
7253
|
-
ok: false,
|
|
7254
|
-
reason: t("plugin_install.host_fetch_threw", {
|
|
7255
|
-
reason: (err instanceof Error ? err.message : String(err)).split("\n")[0] ?? ""
|
|
7256
|
-
})
|
|
7257
|
-
};
|
|
7258
|
-
}
|
|
7259
|
-
if (!answer.ok) {
|
|
7260
|
-
return {
|
|
7261
|
-
ok: false,
|
|
7262
|
-
reason: answer.reason ?? t("plugin_install.host_fetch_failed", { target: source.target })
|
|
7263
|
-
};
|
|
7324
|
+
return { ok: false, reason: t("plugin_marketplace.read_failed", { reason: first(err) }) };
|
|
7264
7325
|
}
|
|
7265
|
-
|
|
7326
|
+
}
|
|
7327
|
+
const url = catalogUrl(input);
|
|
7328
|
+
if (!url) {
|
|
7329
|
+
return {
|
|
7330
|
+
ok: false,
|
|
7331
|
+
reason: t("plugin_marketplace.bad_url", { input, file: MARKETPLACE_FILE })
|
|
7332
|
+
};
|
|
7333
|
+
}
|
|
7334
|
+
try {
|
|
7335
|
+
const response = await fetch(url, { redirect: "follow" });
|
|
7336
|
+
if (!response.ok) {
|
|
7337
|
+
const vars = { url, status: response.status };
|
|
7266
7338
|
return {
|
|
7267
7339
|
ok: false,
|
|
7268
|
-
reason: t("
|
|
7340
|
+
reason: looksPrivate(response.status) ? t("marketplace.fetch_failed_private", vars) : t("marketplace.fetch_failed", vars)
|
|
7269
7341
|
};
|
|
7270
7342
|
}
|
|
7271
|
-
return
|
|
7272
|
-
})
|
|
7273
|
-
}
|
|
7274
|
-
function fetchLocal(source) {
|
|
7275
|
-
if (!existsSync(source.target)) {
|
|
7276
|
-
return { ok: false, reason: t("plugin_install.local_missing", { path: source.target }) };
|
|
7343
|
+
return parseCatalog(await response.text(), url);
|
|
7344
|
+
} catch (err) {
|
|
7345
|
+
return { ok: false, reason: t("marketplace.fetch_error", { url, reason: first(err) }) };
|
|
7277
7346
|
}
|
|
7278
|
-
return { ok: true };
|
|
7279
7347
|
}
|
|
7280
|
-
|
|
7281
|
-
|
|
7282
|
-
|
|
7283
|
-
|
|
7284
|
-
|
|
7348
|
+
function looksPrivate(status) {
|
|
7349
|
+
return status === 401 || status === 403 || status === 404;
|
|
7350
|
+
}
|
|
7351
|
+
function catalogUrl(input) {
|
|
7352
|
+
if (input.startsWith("github:")) {
|
|
7353
|
+
const slug = input.slice("github:".length);
|
|
7354
|
+
if (!/^[\w.-]+\/[\w.-]+$/.test(slug)) return void 0;
|
|
7355
|
+
return `https://raw.githubusercontent.com/${slug}/HEAD/${MARKETPLACE_FILE}`;
|
|
7285
7356
|
}
|
|
7286
|
-
|
|
7287
|
-
return
|
|
7357
|
+
if (!input.startsWith("https://") && !input.startsWith("http://")) return void 0;
|
|
7358
|
+
return input.endsWith(".json") ? input : `${input.replace(/\/+$/, "")}/${MARKETPLACE_FILE}`;
|
|
7288
7359
|
}
|
|
7289
|
-
|
|
7290
|
-
|
|
7291
|
-
|
|
7292
|
-
|
|
7293
|
-
|
|
7294
|
-
"npm",
|
|
7295
|
-
["pack", source.target, "--pack-destination", dl, "--silent"],
|
|
7296
|
-
{ cwd: dl, windowsShell: true }
|
|
7297
|
-
);
|
|
7298
|
-
if (!packed.ok) {
|
|
7299
|
-
return { ok: false, reason: t("plugin_install.npm_pack_failed", { detail: packed.detail }) };
|
|
7300
|
-
}
|
|
7301
|
-
const tarball = packed.stdout.trim().split("\n").pop()?.trim();
|
|
7302
|
-
if (!tarball) return { ok: false, reason: t("plugin_install.npm_no_tarball") };
|
|
7303
|
-
return extractArchive(isAbsolute(tarball) ? tarball : join(dl, tarball), staging);
|
|
7304
|
-
});
|
|
7360
|
+
function localCatalogPath(input) {
|
|
7361
|
+
if (/^[a-z][a-z0-9+.-]+:/i.test(input) || input.startsWith("//")) return void 0;
|
|
7362
|
+
const abs = isAbsolute(input) ? input : resolve(input);
|
|
7363
|
+
if (existsSync(abs) && statSync(abs).isDirectory()) return join(abs, MARKETPLACE_FILE);
|
|
7364
|
+
return abs;
|
|
7305
7365
|
}
|
|
7306
|
-
|
|
7307
|
-
|
|
7308
|
-
|
|
7309
|
-
|
|
7366
|
+
function marketDir(market) {
|
|
7367
|
+
const abs = isAbsolute(market) ? market : resolve(market);
|
|
7368
|
+
if (existsSync(abs) && statSync(abs).isDirectory()) return abs;
|
|
7369
|
+
return abs.replace(/[/\\][^/\\]*$/, "");
|
|
7370
|
+
}
|
|
7371
|
+
function parseCatalog(body2, where) {
|
|
7372
|
+
let raw;
|
|
7310
7373
|
try {
|
|
7311
|
-
|
|
7312
|
-
if (!response.ok) {
|
|
7313
|
-
return {
|
|
7314
|
-
ok: false,
|
|
7315
|
-
reason: t("plugin_install.download_http", {
|
|
7316
|
-
status: response.status,
|
|
7317
|
-
statusText: response.statusText
|
|
7318
|
-
})
|
|
7319
|
-
};
|
|
7320
|
-
}
|
|
7321
|
-
bytes = Buffer.from(await response.arrayBuffer());
|
|
7374
|
+
raw = JSON.parse(body2);
|
|
7322
7375
|
} catch (err) {
|
|
7323
7376
|
return {
|
|
7324
7377
|
ok: false,
|
|
7325
|
-
reason: t("
|
|
7326
|
-
message: err instanceof Error ? err.message : String(err)
|
|
7327
|
-
})
|
|
7378
|
+
reason: t("plugin_marketplace.catalog_bad_json", { where, reason: first(err) })
|
|
7328
7379
|
};
|
|
7329
7380
|
}
|
|
7330
|
-
|
|
7331
|
-
|
|
7332
|
-
|
|
7333
|
-
|
|
7334
|
-
ok: false,
|
|
7335
|
-
reason: [
|
|
7336
|
-
t("plugin_install.sha_mismatch"),
|
|
7337
|
-
t("plugin_install.sha_expected", { expected: source.sha256 }),
|
|
7338
|
-
t("plugin_install.sha_actual", { actual })
|
|
7339
|
-
].join("\n")
|
|
7340
|
-
};
|
|
7341
|
-
}
|
|
7381
|
+
const checked = parseLenient(CatalogSchema.optional(), raw, void 0);
|
|
7382
|
+
if (!checked.value) {
|
|
7383
|
+
const detail = checked.issues.map((i) => t("plugin_marketplace.issue", { path: i.path, message: i.message })).join(t("plugin_marketplace.catalog_issue_sep"));
|
|
7384
|
+
return { ok: false, reason: t("plugin_marketplace.catalog_invalid", { where, detail }) };
|
|
7342
7385
|
}
|
|
7343
|
-
return
|
|
7344
|
-
const archive = join(dl, `download${archiveExt(source.target)}`);
|
|
7345
|
-
writeFileSync(archive, bytes);
|
|
7346
|
-
return extractArchive(archive, staging);
|
|
7347
|
-
});
|
|
7386
|
+
return { ok: true, catalog: checked.value };
|
|
7348
7387
|
}
|
|
7349
|
-
function
|
|
7350
|
-
const
|
|
7351
|
-
|
|
7352
|
-
if (path.endsWith(".tar.gz")) return ".tar.gz";
|
|
7353
|
-
return ".zip";
|
|
7388
|
+
function first(err) {
|
|
7389
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
7390
|
+
return message.split("\n")[0] ?? t("plugin_marketplace.unknown_error");
|
|
7354
7391
|
}
|
|
7355
|
-
|
|
7356
|
-
|
|
7357
|
-
|
|
7358
|
-
|
|
7359
|
-
|
|
7360
|
-
|
|
7392
|
+
var NPM_SPEC = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*(?:@[a-zA-Z0-9.^~*+-]+)?$/;
|
|
7393
|
+
var REMOTE_URL = /^https?:\/\/[^\s'"\\^|<>]+$/;
|
|
7394
|
+
function resolveSource(raw) {
|
|
7395
|
+
const input = raw.trim();
|
|
7396
|
+
if (!input) return void 0;
|
|
7397
|
+
if (input.startsWith("github:")) {
|
|
7398
|
+
const [slug = "", fragment] = input.slice("github:".length).split("#", 2);
|
|
7399
|
+
if (!/^[\w.-]+\/[\w.-]+$/.test(slug)) return void 0;
|
|
7400
|
+
const sub = subdirOf(fragment);
|
|
7401
|
+
if (!sub.ok) return void 0;
|
|
7402
|
+
return {
|
|
7403
|
+
type: "git",
|
|
7404
|
+
raw: input,
|
|
7405
|
+
target: `https://github.com/${slug}.git`,
|
|
7406
|
+
...sub.subdir ? { subdir: sub.subdir } : {}
|
|
7407
|
+
};
|
|
7408
|
+
}
|
|
7409
|
+
if (input.startsWith("git+")) {
|
|
7410
|
+
const [url = "", fragment] = input.slice("git+".length).split("#", 2);
|
|
7411
|
+
if (!REMOTE_URL.test(url)) return void 0;
|
|
7412
|
+
const sub = subdirOf(fragment);
|
|
7413
|
+
if (!sub.ok) return void 0;
|
|
7414
|
+
return { type: "git", raw: input, target: url, ...sub.subdir ? { subdir: sub.subdir } : {} };
|
|
7415
|
+
}
|
|
7416
|
+
if (input.startsWith("npm:")) {
|
|
7417
|
+
const pkg = input.slice("npm:".length);
|
|
7418
|
+
if (!NPM_SPEC.test(pkg)) return void 0;
|
|
7419
|
+
return { type: "npm", raw: input, target: pkg };
|
|
7420
|
+
}
|
|
7421
|
+
if (input.startsWith("host:")) {
|
|
7422
|
+
const opaque = input.slice("host:".length).trim();
|
|
7423
|
+
if (!opaque) return void 0;
|
|
7424
|
+
return { type: "host", raw: input, target: opaque };
|
|
7425
|
+
}
|
|
7426
|
+
if (input.startsWith("https://") || input.startsWith("http://")) {
|
|
7427
|
+
const [url = "", fragment] = input.split("#", 2);
|
|
7428
|
+
if (!REMOTE_URL.test(url)) return void 0;
|
|
7429
|
+
const sha = fragment?.startsWith("sha256=") ? fragment.slice("sha256=".length) : void 0;
|
|
7430
|
+
if (isArchiveUrl(url)) {
|
|
7431
|
+
if (sha !== void 0 && !/^[a-f0-9]{64}$/i.test(sha)) return void 0;
|
|
7432
|
+
return {
|
|
7433
|
+
type: "archive",
|
|
7434
|
+
raw: input,
|
|
7435
|
+
target: url,
|
|
7436
|
+
...sha ? { sha256: sha.toLowerCase() } : {}
|
|
7437
|
+
};
|
|
7438
|
+
}
|
|
7439
|
+
return { type: "git", raw: input, target: url };
|
|
7440
|
+
}
|
|
7441
|
+
const abs = isAbsolute(input) ? input : resolve(input);
|
|
7442
|
+
if (isArchiveUrl(abs)) {
|
|
7443
|
+
return { type: "archive", raw: input, target: abs, localFile: true };
|
|
7361
7444
|
}
|
|
7445
|
+
return { type: "local", raw: input, target: abs };
|
|
7362
7446
|
}
|
|
7363
|
-
|
|
7364
|
-
|
|
7365
|
-
var PLUGIN_ROOT_ENV = "EPOCH_PLUGIN_ROOT";
|
|
7366
|
-
function mentionsPluginRoot(text) {
|
|
7367
|
-
return text.includes(PLUGIN_ROOT_TOKEN);
|
|
7447
|
+
function isArchiveUrl(url) {
|
|
7448
|
+
return isWirePluginArchiveName(url);
|
|
7368
7449
|
}
|
|
7369
|
-
function
|
|
7370
|
-
|
|
7450
|
+
function subdirOf(fragment) {
|
|
7451
|
+
if (fragment === void 0 || fragment === "") return { ok: true };
|
|
7452
|
+
if (!fragment.startsWith("subdir=")) return { ok: false };
|
|
7453
|
+
const rel = fragment.slice("subdir=".length).replace(/\/+$/, "");
|
|
7454
|
+
if (!/^[\w.-]+(?:\/[\w.-]+)*$/.test(rel)) return { ok: false };
|
|
7455
|
+
if (rel.split("/").includes("..")) return { ok: false };
|
|
7456
|
+
return { ok: true, subdir: rel };
|
|
7371
7457
|
}
|
|
7372
|
-
|
|
7373
|
-
|
|
7374
|
-
const hit = records.find((r) => r.name === name);
|
|
7375
|
-
if (!hit) return void 0;
|
|
7376
|
-
return { records: records.filter((r) => r.name !== name), result: hit };
|
|
7377
|
-
});
|
|
7378
|
-
if (!removed) return { ok: false, reason: t("plugin_lifecycle.not_installed", { name }) };
|
|
7379
|
-
if (removed.linked) {
|
|
7380
|
-
rmSync(removed.path, { force: true });
|
|
7381
|
-
return { ok: true, message: t("plugin_lifecycle.uninstalled_link", { name }) };
|
|
7382
|
-
}
|
|
7383
|
-
rmSync(removed.path, { recursive: true, force: true });
|
|
7384
|
-
return { ok: true, message: t("plugin_lifecycle.uninstalled", { name }) };
|
|
7458
|
+
function pluginRoot(source, staging) {
|
|
7459
|
+
return source.subdir ? join(staging, source.subdir) : staging;
|
|
7385
7460
|
}
|
|
7386
|
-
|
|
7387
|
-
|
|
7388
|
-
|
|
7389
|
-
|
|
7461
|
+
// src/plugin/install.ts
|
|
7462
|
+
async function installPlugin(source, opts) {
|
|
7463
|
+
const resolved = resolveSource(source);
|
|
7464
|
+
if (!resolved) {
|
|
7390
7465
|
return {
|
|
7391
|
-
|
|
7392
|
-
|
|
7466
|
+
ok: false,
|
|
7467
|
+
reason: t("plugin_install.bad_source", { source })
|
|
7393
7468
|
};
|
|
7394
|
-
});
|
|
7395
|
-
if (changed === void 0) {
|
|
7396
|
-
return { ok: false, reason: t("plugin_lifecycle.not_installed", { name }) };
|
|
7397
7469
|
}
|
|
7398
|
-
|
|
7399
|
-
|
|
7400
|
-
|
|
7401
|
-
|
|
7402
|
-
}
|
|
7403
|
-
}
|
|
7404
|
-
async function updatePlugin(name, opts) {
|
|
7405
|
-
const existing = new PluginStore(opts.statePath).read().records.find((r) => r.name === name);
|
|
7406
|
-
if (!existing) return { ok: false, reason: t("plugin_lifecycle.not_installed", { name }) };
|
|
7407
|
-
if (existing.linked) {
|
|
7408
|
-
return {
|
|
7409
|
-
ok: true,
|
|
7410
|
-
message: t("plugin_lifecycle.linked_no_update", { name, source: existing.source })
|
|
7411
|
-
};
|
|
7470
|
+
mkdirSync(opts.pluginsDir, { recursive: true });
|
|
7471
|
+
const staging = join(opts.pluginsDir, `.staging-${randomUUID().slice(0, 8)}`);
|
|
7472
|
+
try {
|
|
7473
|
+
return await runInstall(resolved, staging, opts);
|
|
7474
|
+
} finally {
|
|
7475
|
+
rmSync(staging, { recursive: true, force: true });
|
|
7412
7476
|
}
|
|
7413
|
-
return installPlugin(existing.source, {
|
|
7414
|
-
...opts,
|
|
7415
|
-
replace: name,
|
|
7416
|
-
...existing.marketplace ? { marketplace: existing.marketplace } : {}
|
|
7417
|
-
});
|
|
7418
|
-
}
|
|
7419
|
-
var MARKETPLACE_FILE = "epoch-marketplace.json";
|
|
7420
|
-
var NAME_PATTERN = PLUGIN_NAME_PATTERN;
|
|
7421
|
-
var EntryCategoryInput = z.union([z.string().min(1), z.array(z.string().min(1))]).optional().catch(void 0);
|
|
7422
|
-
var EntryShape = z.object({
|
|
7423
|
-
name: z.string().regex(NAME_PATTERN),
|
|
7424
|
-
description: z.string().min(1).optional(),
|
|
7425
|
-
source: z.string().min(1),
|
|
7426
|
-
category: EntryCategoryInput,
|
|
7427
|
-
keywords: z.array(z.string().min(1)).optional(),
|
|
7428
|
-
author: PluginAuthorSchema.optional(),
|
|
7429
|
-
downloads: z.number().int().nonnegative().optional(),
|
|
7430
|
-
likes: z.number().int().nonnegative().optional(),
|
|
7431
|
-
displayName: z.string().min(1).optional(),
|
|
7432
|
-
icon: z.string().min(1).optional(),
|
|
7433
|
-
version: z.string().min(1).optional(),
|
|
7434
|
-
changelog: z.string().min(1).optional(),
|
|
7435
|
-
size: z.number().int().positive().optional(),
|
|
7436
|
-
homepage: z.string().min(1).optional(),
|
|
7437
|
-
visibility: z.enum(PLUGIN_VISIBILITY).optional().catch(void 0),
|
|
7438
|
-
status: z.enum(["listed", "reviewing", "rejected", "offline"]).optional().catch(void 0)
|
|
7439
|
-
});
|
|
7440
|
-
var EntrySchema = EntryShape.transform(({ category, ...rest }) => {
|
|
7441
|
-
const picked = category === void 0 ? [] : typeof category === "string" ? [category] : category;
|
|
7442
|
-
return { ...rest, ...picked.length === 0 ? {} : { category: [...picked] } };
|
|
7443
|
-
});
|
|
7444
|
-
var MARKETPLACE_DISPLAY_FIELDS = [
|
|
7445
|
-
"status",
|
|
7446
|
-
"downloads",
|
|
7447
|
-
"likes",
|
|
7448
|
-
"author",
|
|
7449
|
-
"category",
|
|
7450
|
-
"visibility",
|
|
7451
|
-
"icon",
|
|
7452
|
-
"homepage",
|
|
7453
|
-
"size"
|
|
7454
|
-
];
|
|
7455
|
-
var DisplaySchema = z.object({
|
|
7456
|
-
fields: z.array(z.string().min(1)).optional()
|
|
7457
|
-
});
|
|
7458
|
-
var CategoryNodeSchema = z.lazy(
|
|
7459
|
-
() => z.object({
|
|
7460
|
-
id: z.string().min(1),
|
|
7461
|
-
label: z.string().min(1).optional(),
|
|
7462
|
-
children: z.array(CategoryNodeSchema).optional()
|
|
7463
|
-
})
|
|
7464
|
-
);
|
|
7465
|
-
var CategoriesSchema = z.array(CategoryNodeSchema).refine(hasUniqueCategoryIds, { message: "category ids must be unique across the whole tree" }).optional().catch(void 0);
|
|
7466
|
-
function hasUniqueCategoryIds(nodes) {
|
|
7467
|
-
const seen = /* @__PURE__ */ new Set();
|
|
7468
|
-
const walk3 = (list) => list.every((node) => {
|
|
7469
|
-
if (seen.has(node.id)) return false;
|
|
7470
|
-
seen.add(node.id);
|
|
7471
|
-
return node.children === void 0 || walk3(node.children);
|
|
7472
|
-
});
|
|
7473
|
-
return walk3(nodes);
|
|
7474
7477
|
}
|
|
7475
|
-
|
|
7476
|
-
|
|
7477
|
-
|
|
7478
|
-
|
|
7479
|
-
|
|
7480
|
-
|
|
7481
|
-
|
|
7482
|
-
|
|
7483
|
-
|
|
7484
|
-
|
|
7485
|
-
|
|
7486
|
-
addedAt: z.number(),
|
|
7487
|
-
catalog: CatalogSchema
|
|
7488
|
-
});
|
|
7489
|
-
var StateSchema2 = z.object({
|
|
7490
|
-
version: z.literal(1).optional(),
|
|
7491
|
-
marketplaces: z.array(RecordSchema2)
|
|
7492
|
-
});
|
|
7493
|
-
function projectEntryForDisplay(entry, display) {
|
|
7494
|
-
const allowed = display?.fields;
|
|
7495
|
-
if (allowed === void 0) return entry;
|
|
7496
|
-
const out = { ...entry };
|
|
7497
|
-
for (const field of MARKETPLACE_DISPLAY_FIELDS) {
|
|
7498
|
-
if (!displayAllows(display, field)) delete out[field];
|
|
7478
|
+
async function runInstall(resolved, staging, opts) {
|
|
7479
|
+
const rootStaging = join(staging, "root");
|
|
7480
|
+
const fetched = await fetchInto(resolved, rootStaging, opts);
|
|
7481
|
+
if (!fetched.ok) return { ok: false, reason: fetched.reason };
|
|
7482
|
+
const readDir = resolved.type === "local" ? resolved.target : pluginRoot(resolved, rootStaging);
|
|
7483
|
+
const read = readPluginManifest(readDir);
|
|
7484
|
+
const manifest = read.manifest;
|
|
7485
|
+
if (!manifest) {
|
|
7486
|
+
const detail = read.issues.map((i) => t("plugin_install.issue_line", { path: i.path, message: i.message })).join("\n");
|
|
7487
|
+
return { ok: false, reason: t("plugin_install.manifest_unusable", { detail: `
|
|
7488
|
+
${detail}` }) };
|
|
7499
7489
|
}
|
|
7500
|
-
|
|
7501
|
-
|
|
7502
|
-
|
|
7503
|
-
const
|
|
7504
|
-
|
|
7505
|
-
|
|
7506
|
-
|
|
7507
|
-
|
|
7508
|
-
|
|
7509
|
-
|
|
7510
|
-
|
|
7511
|
-
|
|
7512
|
-
|
|
7490
|
+
const taken = new PluginStore(opts.statePath).read().records.find((r) => r.name === manifest.name);
|
|
7491
|
+
const conflict = taken && taken.name !== opts.replace ? taken : void 0;
|
|
7492
|
+
const current = opts.epochVersion ?? currentEpochVersion();
|
|
7493
|
+
const version = checkEpochVersion(manifest.epochVersion, current);
|
|
7494
|
+
const versionUnmet = !version.ok && manifest.epochVersion ? { required: manifest.epochVersion, current, reason: version.reason } : void 0;
|
|
7495
|
+
const closure = await resolveClosure({
|
|
7496
|
+
root: manifest.name,
|
|
7497
|
+
declared: manifest.dependencies ?? [],
|
|
7498
|
+
installed: new Set(new PluginStore(opts.statePath).read().records.map((r) => r.name)),
|
|
7499
|
+
staging,
|
|
7500
|
+
opts
|
|
7501
|
+
});
|
|
7502
|
+
const preview3 = {
|
|
7503
|
+
manifest,
|
|
7504
|
+
source: resolved,
|
|
7505
|
+
inventory: scanPluginDir(readDir, manifest.name),
|
|
7506
|
+
...conflict ? { conflict } : {},
|
|
7507
|
+
...versionUnmet ? { versionUnmet } : {},
|
|
7508
|
+
...closure.report.length > 0 ? { dependencies: closure.report } : {}
|
|
7509
|
+
};
|
|
7510
|
+
if (opts.replace !== void 0 && manifest.name !== opts.replace) {
|
|
7513
7511
|
return {
|
|
7514
|
-
|
|
7515
|
-
|
|
7516
|
-
|
|
7517
|
-
]
|
|
7512
|
+
ok: false,
|
|
7513
|
+
preview: preview3,
|
|
7514
|
+
reason: t("plugin_install.renamed", { found: manifest.name, expected: opts.replace })
|
|
7518
7515
|
};
|
|
7519
7516
|
}
|
|
7520
|
-
|
|
7521
|
-
if (!checked.value) {
|
|
7517
|
+
if (conflict) {
|
|
7522
7518
|
return {
|
|
7523
|
-
|
|
7524
|
-
|
|
7525
|
-
|
|
7526
|
-
|
|
7527
|
-
|
|
7519
|
+
ok: false,
|
|
7520
|
+
preview: preview3,
|
|
7521
|
+
reason: t("plugin_install.already_installed", {
|
|
7522
|
+
name: conflict.name,
|
|
7523
|
+
version: conflict.version,
|
|
7524
|
+
source: conflict.source
|
|
7525
|
+
})
|
|
7528
7526
|
};
|
|
7529
7527
|
}
|
|
7530
|
-
|
|
7531
|
-
}
|
|
7532
|
-
async function mutate(statePath, fn) {
|
|
7533
|
-
const release = await acquireStateLock(statePath);
|
|
7534
|
-
try {
|
|
7535
|
-
const next = fn(readMarketplaces(statePath).records);
|
|
7536
|
-
if (!next) return false;
|
|
7537
|
-
writeStateFile(statePath, JSON.stringify({ version: 1, marketplaces: next }, null, 2));
|
|
7538
|
-
return true;
|
|
7539
|
-
} finally {
|
|
7540
|
-
release();
|
|
7528
|
+
if (opts.confirm && !await opts.confirm(preview3)) {
|
|
7529
|
+
return { ok: false, reason: t("plugin.install_cancelled"), cancelled: true, preview: preview3 };
|
|
7541
7530
|
}
|
|
7542
|
-
|
|
7543
|
-
|
|
7544
|
-
|
|
7545
|
-
|
|
7546
|
-
|
|
7547
|
-
|
|
7548
|
-
|
|
7549
|
-
|
|
7550
|
-
|
|
7551
|
-
|
|
7552
|
-
|
|
7553
|
-
|
|
7554
|
-
|
|
7531
|
+
const records = await commitAll(
|
|
7532
|
+
[
|
|
7533
|
+
{
|
|
7534
|
+
name: manifest.name,
|
|
7535
|
+
version: manifest.version,
|
|
7536
|
+
source: resolved,
|
|
7537
|
+
stagingDir: rootStaging,
|
|
7538
|
+
...opts.marketplace ? { marketplace: opts.marketplace } : {}
|
|
7539
|
+
},
|
|
7540
|
+
...closure.plans.map((plan) => ({
|
|
7541
|
+
name: plan.name,
|
|
7542
|
+
version: plan.manifest.version,
|
|
7543
|
+
source: plan.source,
|
|
7544
|
+
stagingDir: plan.stagingDir,
|
|
7545
|
+
...plan.marketplace ? { marketplace: plan.marketplace } : {}
|
|
7546
|
+
}))
|
|
7547
|
+
],
|
|
7548
|
+
opts
|
|
7555
7549
|
);
|
|
7556
|
-
|
|
7550
|
+
const record = records?.find((r) => r.name === manifest.name);
|
|
7551
|
+
return record ? { ok: true, record, preview: preview3 } : {
|
|
7557
7552
|
ok: false,
|
|
7558
|
-
|
|
7553
|
+
preview: preview3,
|
|
7554
|
+
reason: t("plugin_install.raced", { name: manifest.name })
|
|
7559
7555
|
};
|
|
7560
7556
|
}
|
|
7561
|
-
|
|
7562
|
-
|
|
7563
|
-
|
|
7564
|
-
|
|
7565
|
-
|
|
7566
|
-
|
|
7567
|
-
|
|
7568
|
-
|
|
7569
|
-
|
|
7570
|
-
|
|
7571
|
-
|
|
7572
|
-
|
|
7573
|
-
|
|
7574
|
-
|
|
7575
|
-
|
|
7576
|
-
|
|
7577
|
-
|
|
7578
|
-
|
|
7579
|
-
|
|
7580
|
-
|
|
7557
|
+
var DEPENDENCY_LIMIT = 32;
|
|
7558
|
+
async function commitAll(entries2, opts) {
|
|
7559
|
+
const records = [];
|
|
7560
|
+
for (const entry of entries2) {
|
|
7561
|
+
const dest = join(opts.pluginsDir, entry.name);
|
|
7562
|
+
removeInstallDest(dest);
|
|
7563
|
+
let linked = entry.source.type === "local";
|
|
7564
|
+
if (linked) {
|
|
7565
|
+
try {
|
|
7566
|
+
symlinkSync(entry.source.target, dest, "junction");
|
|
7567
|
+
} catch {
|
|
7568
|
+
opts.onProgress?.(t("plugin_install.symlink_failed"));
|
|
7569
|
+
cpSync(entry.source.target, dest, { recursive: true });
|
|
7570
|
+
linked = false;
|
|
7571
|
+
}
|
|
7572
|
+
} else {
|
|
7573
|
+
renameSync(pluginRoot(entry.source, entry.stagingDir), dest);
|
|
7574
|
+
}
|
|
7575
|
+
records.push({
|
|
7576
|
+
name: entry.name,
|
|
7577
|
+
version: entry.version,
|
|
7578
|
+
source: entry.source.raw,
|
|
7579
|
+
sourceType: entry.source.type,
|
|
7580
|
+
path: dest,
|
|
7581
|
+
linked,
|
|
7582
|
+
enabled: true,
|
|
7583
|
+
installedAt: Date.now(),
|
|
7584
|
+
...entry.marketplace ? { marketplace: entry.marketplace } : {},
|
|
7585
|
+
...entry.source.sha256 ? { checksum: `sha256:${entry.source.sha256}` } : {}
|
|
7586
|
+
});
|
|
7581
7587
|
}
|
|
7582
|
-
|
|
7583
|
-
|
|
7584
|
-
|
|
7588
|
+
return new PluginStore(opts.statePath).mutate((existing) => {
|
|
7589
|
+
for (const record of records) {
|
|
7590
|
+
const taken = existing.some((r) => r.name === record.name);
|
|
7591
|
+
if (taken && opts.replace !== record.name) return void 0;
|
|
7592
|
+
}
|
|
7593
|
+
let next = existing;
|
|
7594
|
+
for (const record of records) {
|
|
7595
|
+
const taken = next.some((r) => r.name === record.name);
|
|
7596
|
+
next = taken ? next.map((r) => r.name === record.name ? record : r) : [...next, record];
|
|
7597
|
+
}
|
|
7598
|
+
return { records: next, result: records };
|
|
7599
|
+
});
|
|
7585
7600
|
}
|
|
7586
|
-
function
|
|
7587
|
-
const
|
|
7588
|
-
const
|
|
7589
|
-
|
|
7590
|
-
|
|
7591
|
-
|
|
7592
|
-
|
|
7593
|
-
|
|
7594
|
-
|
|
7595
|
-
|
|
7596
|
-
|
|
7597
|
-
|
|
7598
|
-
|
|
7599
|
-
|
|
7600
|
-
|
|
7601
|
-
|
|
7602
|
-
|
|
7601
|
+
async function resolveClosure(input) {
|
|
7602
|
+
const { opts, staging } = input;
|
|
7603
|
+
const seen = /* @__PURE__ */ new Set([input.root]);
|
|
7604
|
+
const queue = input.declared.map((name) => ({
|
|
7605
|
+
name,
|
|
7606
|
+
requiredBy: input.root
|
|
7607
|
+
}));
|
|
7608
|
+
const report3 = [];
|
|
7609
|
+
const plans = [];
|
|
7610
|
+
while (queue.length > 0) {
|
|
7611
|
+
const next = queue.shift();
|
|
7612
|
+
if (!next) break;
|
|
7613
|
+
const { name, requiredBy } = next;
|
|
7614
|
+
if (seen.has(name)) continue;
|
|
7615
|
+
seen.add(name);
|
|
7616
|
+
if (input.installed.has(name)) {
|
|
7617
|
+
report3.push({ name, status: "installed", requiredBy });
|
|
7618
|
+
continue;
|
|
7619
|
+
}
|
|
7620
|
+
if (plans.length >= DEPENDENCY_LIMIT) {
|
|
7621
|
+
report3.push({
|
|
7622
|
+
name,
|
|
7623
|
+
status: "unresolved",
|
|
7624
|
+
requiredBy,
|
|
7625
|
+
reason: t("plugin_install.dep_too_many", { limit: DEPENDENCY_LIMIT, root: input.root })
|
|
7626
|
+
});
|
|
7627
|
+
continue;
|
|
7628
|
+
}
|
|
7629
|
+
const located = await locateDependency(name, opts);
|
|
7630
|
+
if (!located) {
|
|
7631
|
+
report3.push({
|
|
7632
|
+
name,
|
|
7633
|
+
status: "unresolved",
|
|
7634
|
+
requiredBy,
|
|
7635
|
+
reason: t("plugin_install.dep_no_source", { name })
|
|
7636
|
+
});
|
|
7637
|
+
continue;
|
|
7638
|
+
}
|
|
7639
|
+
const source = resolveSource(located.source);
|
|
7640
|
+
if (!source) {
|
|
7641
|
+
report3.push({
|
|
7642
|
+
name,
|
|
7643
|
+
status: "unresolved",
|
|
7644
|
+
requiredBy,
|
|
7645
|
+
source: located.source,
|
|
7646
|
+
reason: t("plugin_install.dep_bad_source", { name, source: located.source })
|
|
7647
|
+
});
|
|
7648
|
+
continue;
|
|
7649
|
+
}
|
|
7650
|
+
opts.onProgress?.(t("plugin_install.dep_fetching", { name, source: located.source }));
|
|
7651
|
+
const stagingDir = join(staging, `dep-${plans.length}`);
|
|
7652
|
+
const fetched = await fetchInto(source, stagingDir, opts);
|
|
7653
|
+
if (!fetched.ok) {
|
|
7654
|
+
report3.push({
|
|
7655
|
+
name,
|
|
7656
|
+
status: "unresolved",
|
|
7657
|
+
requiredBy,
|
|
7658
|
+
source: located.source,
|
|
7659
|
+
reason: fetched.reason
|
|
7603
7660
|
});
|
|
7661
|
+
continue;
|
|
7662
|
+
}
|
|
7663
|
+
const readDir = source.type === "local" ? source.target : pluginRoot(source, stagingDir);
|
|
7664
|
+
const read = readPluginManifest(readDir);
|
|
7665
|
+
const manifest = read.manifest;
|
|
7666
|
+
if (!manifest) {
|
|
7667
|
+
report3.push({
|
|
7668
|
+
name,
|
|
7669
|
+
status: "unresolved",
|
|
7670
|
+
requiredBy,
|
|
7671
|
+
source: located.source,
|
|
7672
|
+
reason: t("plugin_install.dep_bad_manifest", {
|
|
7673
|
+
name,
|
|
7674
|
+
detail: read.issues.map((issue) => issue.message).join("; ")
|
|
7675
|
+
})
|
|
7676
|
+
});
|
|
7677
|
+
continue;
|
|
7678
|
+
}
|
|
7679
|
+
if (manifest.name !== name) {
|
|
7680
|
+
report3.push({
|
|
7681
|
+
name,
|
|
7682
|
+
status: "unresolved",
|
|
7683
|
+
requiredBy,
|
|
7684
|
+
source: located.source,
|
|
7685
|
+
reason: t("plugin_install.dep_name_mismatch", { name, found: manifest.name })
|
|
7686
|
+
});
|
|
7687
|
+
continue;
|
|
7688
|
+
}
|
|
7689
|
+
const current = opts.epochVersion ?? currentEpochVersion();
|
|
7690
|
+
const version = checkEpochVersion(manifest.epochVersion, current);
|
|
7691
|
+
const versionUnmet = !version.ok && manifest.epochVersion ? { required: manifest.epochVersion, current, reason: version.reason } : void 0;
|
|
7692
|
+
report3.push({
|
|
7693
|
+
name,
|
|
7694
|
+
status: "plan",
|
|
7695
|
+
requiredBy,
|
|
7696
|
+
source: located.source,
|
|
7697
|
+
manifest,
|
|
7698
|
+
inventory: scanPluginDir(readDir, manifest.name),
|
|
7699
|
+
...versionUnmet ? { versionUnmet } : {}
|
|
7700
|
+
});
|
|
7701
|
+
plans.push({
|
|
7702
|
+
name: manifest.name,
|
|
7703
|
+
source,
|
|
7704
|
+
stagingDir,
|
|
7705
|
+
manifest,
|
|
7706
|
+
...located.marketplace ? { marketplace: located.marketplace } : {}
|
|
7707
|
+
});
|
|
7708
|
+
for (const nested of manifest.dependencies ?? []) {
|
|
7709
|
+
queue.push({ name: nested, requiredBy: manifest.name });
|
|
7604
7710
|
}
|
|
7605
7711
|
}
|
|
7606
|
-
return
|
|
7712
|
+
return { report: report3, plans };
|
|
7607
7713
|
}
|
|
7608
|
-
function
|
|
7609
|
-
const
|
|
7610
|
-
if (
|
|
7611
|
-
|
|
7612
|
-
|
|
7613
|
-
|
|
7614
|
-
|
|
7615
|
-
|
|
7616
|
-
|
|
7617
|
-
reason: t("plugin_marketplace.market_not_added", { market, input })
|
|
7618
|
-
};
|
|
7714
|
+
async function locateDependency(name, opts) {
|
|
7715
|
+
const local = opts.marketplacesStatePath ? findMarketplaceEntryByName(name, opts.marketplacesStatePath) : void 0;
|
|
7716
|
+
if (local) return { source: local.source, marketplace: local.marketplace };
|
|
7717
|
+
if (!opts.resolveDependency) return void 0;
|
|
7718
|
+
try {
|
|
7719
|
+
const answer = await opts.resolveDependency(name);
|
|
7720
|
+
return answer ? { source: answer } : void 0;
|
|
7721
|
+
} catch {
|
|
7722
|
+
return void 0;
|
|
7619
7723
|
}
|
|
7620
|
-
|
|
7621
|
-
|
|
7622
|
-
|
|
7623
|
-
|
|
7624
|
-
|
|
7625
|
-
|
|
7724
|
+
}
|
|
7725
|
+
function removeInstallDest(dest) {
|
|
7726
|
+
let link = false;
|
|
7727
|
+
try {
|
|
7728
|
+
link = lstatSync(dest).isSymbolicLink();
|
|
7729
|
+
} catch {
|
|
7626
7730
|
}
|
|
7627
|
-
|
|
7628
|
-
|
|
7629
|
-
return
|
|
7630
|
-
|
|
7631
|
-
|
|
7632
|
-
|
|
7633
|
-
|
|
7634
|
-
|
|
7635
|
-
})
|
|
7636
|
-
};
|
|
7731
|
+
if (!link) {
|
|
7732
|
+
rmSync(dest, { recursive: true, force: true });
|
|
7733
|
+
return;
|
|
7734
|
+
}
|
|
7735
|
+
try {
|
|
7736
|
+
unlinkSync(dest);
|
|
7737
|
+
} catch {
|
|
7738
|
+
rmdirSync(dest);
|
|
7637
7739
|
}
|
|
7638
|
-
return { ok: true, ref: { marketplace: market, source } };
|
|
7639
7740
|
}
|
|
7640
|
-
function
|
|
7641
|
-
|
|
7642
|
-
if (
|
|
7643
|
-
|
|
7644
|
-
if (
|
|
7645
|
-
|
|
7646
|
-
|
|
7647
|
-
|
|
7648
|
-
|
|
7649
|
-
|
|
7650
|
-
|
|
7651
|
-
if (market.startsWith("https://") || market.startsWith("http://")) {
|
|
7652
|
-
return `${market.replace(/\/[^/]*$/, "")}/${rel}`;
|
|
7741
|
+
async function fetchInto(source, staging, opts) {
|
|
7742
|
+
if (source.type === "local") return fetchLocal(source);
|
|
7743
|
+
if (source.type === "git") return fetchGit(source, staging, opts);
|
|
7744
|
+
if (source.type === "npm") return fetchNpm(source, staging, opts);
|
|
7745
|
+
if (source.type === "host") return fetchFromHost(source, staging, opts);
|
|
7746
|
+
if (source.localFile) return fetchLocalArchive(source, staging);
|
|
7747
|
+
return fetchArchive(source, staging, opts);
|
|
7748
|
+
}
|
|
7749
|
+
async function fetchLocalArchive(source, staging) {
|
|
7750
|
+
if (!existsSync(source.target)) {
|
|
7751
|
+
return { ok: false, reason: t("plugin_install.archive_missing", { path: source.target }) };
|
|
7653
7752
|
}
|
|
7654
|
-
|
|
7753
|
+
mkdirSync(staging, { recursive: true });
|
|
7754
|
+
return extractArchive(source.target, staging);
|
|
7655
7755
|
}
|
|
7656
|
-
async function
|
|
7657
|
-
const
|
|
7658
|
-
if (!
|
|
7659
|
-
|
|
7660
|
-
|
|
7661
|
-
|
|
7662
|
-
|
|
7663
|
-
|
|
7756
|
+
async function fetchFromHost(source, staging, opts) {
|
|
7757
|
+
const ask = opts.fetchFromHost;
|
|
7758
|
+
if (!ask) {
|
|
7759
|
+
return { ok: false, reason: t("plugin_install.host_fetch_unavailable") };
|
|
7760
|
+
}
|
|
7761
|
+
opts.onProgress?.(t("plugin_install.host_fetching", { target: source.target }));
|
|
7762
|
+
mkdirSync(staging, { recursive: true });
|
|
7763
|
+
return withDownloadDir(async (dl) => {
|
|
7764
|
+
let answer;
|
|
7664
7765
|
try {
|
|
7665
|
-
|
|
7766
|
+
answer = await ask({ target: source.target, destDir: dl });
|
|
7666
7767
|
} catch (err) {
|
|
7667
|
-
return {
|
|
7768
|
+
return {
|
|
7769
|
+
ok: false,
|
|
7770
|
+
reason: t("plugin_install.host_fetch_threw", {
|
|
7771
|
+
reason: (err instanceof Error ? err.message : String(err)).split("\n")[0] ?? ""
|
|
7772
|
+
})
|
|
7773
|
+
};
|
|
7774
|
+
}
|
|
7775
|
+
if (!answer.ok) {
|
|
7776
|
+
return {
|
|
7777
|
+
ok: false,
|
|
7778
|
+
reason: answer.reason ?? t("plugin_install.host_fetch_failed", { target: source.target })
|
|
7779
|
+
};
|
|
7780
|
+
}
|
|
7781
|
+
if (!existsSync(answer.archivePath)) {
|
|
7782
|
+
return {
|
|
7783
|
+
ok: false,
|
|
7784
|
+
reason: t("plugin_install.host_fetch_no_file", { path: answer.archivePath })
|
|
7785
|
+
};
|
|
7668
7786
|
}
|
|
7787
|
+
return extractArchive(answer.archivePath, staging);
|
|
7788
|
+
});
|
|
7789
|
+
}
|
|
7790
|
+
function fetchLocal(source) {
|
|
7791
|
+
if (!existsSync(source.target)) {
|
|
7792
|
+
return { ok: false, reason: t("plugin_install.local_missing", { path: source.target }) };
|
|
7669
7793
|
}
|
|
7670
|
-
|
|
7671
|
-
|
|
7672
|
-
|
|
7673
|
-
|
|
7674
|
-
|
|
7675
|
-
|
|
7794
|
+
return { ok: true };
|
|
7795
|
+
}
|
|
7796
|
+
async function fetchGit(source, staging, opts) {
|
|
7797
|
+
opts.onProgress?.(t("plugin_install.cloning", { target: source.target }));
|
|
7798
|
+
const result = await runCommand("git", ["clone", "--depth", "1", source.target, staging]);
|
|
7799
|
+
if (!result.ok) {
|
|
7800
|
+
return { ok: false, reason: t("plugin_install.clone_failed", { detail: result.detail }) };
|
|
7676
7801
|
}
|
|
7802
|
+
rmSync(join(staging, ".git"), { recursive: true, force: true });
|
|
7803
|
+
return { ok: true };
|
|
7804
|
+
}
|
|
7805
|
+
async function fetchNpm(source, staging, opts) {
|
|
7806
|
+
opts.onProgress?.(t("plugin_install.npm_fetching", { target: source.target }));
|
|
7807
|
+
mkdirSync(staging, { recursive: true });
|
|
7808
|
+
return withDownloadDir(async (dl) => {
|
|
7809
|
+
const packed = await runCommand(
|
|
7810
|
+
"npm",
|
|
7811
|
+
["pack", source.target, "--pack-destination", dl, "--silent"],
|
|
7812
|
+
{ cwd: dl, windowsShell: true }
|
|
7813
|
+
);
|
|
7814
|
+
if (!packed.ok) {
|
|
7815
|
+
return { ok: false, reason: t("plugin_install.npm_pack_failed", { detail: packed.detail }) };
|
|
7816
|
+
}
|
|
7817
|
+
const tarball = packed.stdout.trim().split("\n").pop()?.trim();
|
|
7818
|
+
if (!tarball) return { ok: false, reason: t("plugin_install.npm_no_tarball") };
|
|
7819
|
+
return extractArchive(isAbsolute(tarball) ? tarball : join(dl, tarball), staging);
|
|
7820
|
+
});
|
|
7821
|
+
}
|
|
7822
|
+
async function fetchArchive(source, staging, opts) {
|
|
7823
|
+
opts.onProgress?.(t("plugin_install.downloading", { target: source.target }));
|
|
7824
|
+
mkdirSync(staging, { recursive: true });
|
|
7825
|
+
let bytes;
|
|
7677
7826
|
try {
|
|
7678
|
-
const response = await fetch(
|
|
7827
|
+
const response = await fetch(source.target, { redirect: "follow" });
|
|
7679
7828
|
if (!response.ok) {
|
|
7680
|
-
const vars = { url, status: response.status };
|
|
7681
7829
|
return {
|
|
7682
7830
|
ok: false,
|
|
7683
|
-
reason:
|
|
7831
|
+
reason: t("plugin_install.download_http", {
|
|
7832
|
+
status: response.status,
|
|
7833
|
+
statusText: response.statusText
|
|
7834
|
+
})
|
|
7684
7835
|
};
|
|
7685
7836
|
}
|
|
7686
|
-
|
|
7837
|
+
bytes = Buffer.from(await response.arrayBuffer());
|
|
7687
7838
|
} catch (err) {
|
|
7688
|
-
return {
|
|
7839
|
+
return {
|
|
7840
|
+
ok: false,
|
|
7841
|
+
reason: t("plugin_install.download_failed", {
|
|
7842
|
+
message: err instanceof Error ? err.message : String(err)
|
|
7843
|
+
})
|
|
7844
|
+
};
|
|
7845
|
+
}
|
|
7846
|
+
if (source.sha256) {
|
|
7847
|
+
const actual = createHash("sha256").update(bytes).digest("hex");
|
|
7848
|
+
if (actual !== source.sha256) {
|
|
7849
|
+
return {
|
|
7850
|
+
ok: false,
|
|
7851
|
+
reason: [
|
|
7852
|
+
t("plugin_install.sha_mismatch"),
|
|
7853
|
+
t("plugin_install.sha_expected", { expected: source.sha256 }),
|
|
7854
|
+
t("plugin_install.sha_actual", { actual })
|
|
7855
|
+
].join("\n")
|
|
7856
|
+
};
|
|
7857
|
+
}
|
|
7689
7858
|
}
|
|
7859
|
+
return withDownloadDir(async (dl) => {
|
|
7860
|
+
const archive = join(dl, `download${archiveExt(source.target)}`);
|
|
7861
|
+
writeFileSync(archive, bytes);
|
|
7862
|
+
return extractArchive(archive, staging);
|
|
7863
|
+
});
|
|
7690
7864
|
}
|
|
7691
|
-
function
|
|
7692
|
-
|
|
7865
|
+
function archiveExt(url) {
|
|
7866
|
+
const path = (url.split("?")[0] ?? "").toLowerCase();
|
|
7867
|
+
if (path.endsWith(".tgz")) return ".tgz";
|
|
7868
|
+
if (path.endsWith(".tar.gz")) return ".tar.gz";
|
|
7869
|
+
return ".zip";
|
|
7693
7870
|
}
|
|
7694
|
-
function
|
|
7695
|
-
|
|
7696
|
-
|
|
7697
|
-
|
|
7698
|
-
|
|
7871
|
+
async function withDownloadDir(fn) {
|
|
7872
|
+
const dir = mkdtempSync(join(tmpdir(), "epoch-plugin-dl-"));
|
|
7873
|
+
try {
|
|
7874
|
+
return await fn(dir);
|
|
7875
|
+
} finally {
|
|
7876
|
+
rmSync(dir, { recursive: true, force: true });
|
|
7699
7877
|
}
|
|
7700
|
-
if (!input.startsWith("https://") && !input.startsWith("http://")) return void 0;
|
|
7701
|
-
return input.endsWith(".json") ? input : `${input.replace(/\/+$/, "")}/${MARKETPLACE_FILE}`;
|
|
7702
7878
|
}
|
|
7703
|
-
|
|
7704
|
-
|
|
7705
|
-
|
|
7706
|
-
|
|
7707
|
-
return
|
|
7879
|
+
// src/plugin/root-token.ts
|
|
7880
|
+
var PLUGIN_ROOT_TOKEN = "${EPOCH_PLUGIN_ROOT}";
|
|
7881
|
+
var PLUGIN_ROOT_ENV = "EPOCH_PLUGIN_ROOT";
|
|
7882
|
+
function mentionsPluginRoot(text) {
|
|
7883
|
+
return text.includes(PLUGIN_ROOT_TOKEN);
|
|
7708
7884
|
}
|
|
7709
|
-
function
|
|
7710
|
-
|
|
7711
|
-
if (existsSync(abs) && statSync(abs).isDirectory()) return abs;
|
|
7712
|
-
return abs.replace(/[/\\][^/\\]*$/, "");
|
|
7885
|
+
function substitutePluginRoot(text, root) {
|
|
7886
|
+
return text.split(PLUGIN_ROOT_TOKEN).join(root);
|
|
7713
7887
|
}
|
|
7714
|
-
function
|
|
7715
|
-
|
|
7716
|
-
|
|
7717
|
-
|
|
7718
|
-
|
|
7888
|
+
async function uninstallPlugin(name, opts) {
|
|
7889
|
+
const removed = await new PluginStore(opts.statePath).mutate((records) => {
|
|
7890
|
+
const hit = records.find((r) => r.name === name);
|
|
7891
|
+
if (!hit) return void 0;
|
|
7892
|
+
return { records: records.filter((r) => r.name !== name), result: hit };
|
|
7893
|
+
});
|
|
7894
|
+
if (!removed) return { ok: false, reason: t("plugin_lifecycle.not_installed", { name }) };
|
|
7895
|
+
if (removed.linked) {
|
|
7896
|
+
rmSync(removed.path, { force: true });
|
|
7897
|
+
return { ok: true, message: t("plugin_lifecycle.uninstalled_link", { name }) };
|
|
7898
|
+
}
|
|
7899
|
+
rmSync(removed.path, { recursive: true, force: true });
|
|
7900
|
+
return { ok: true, message: t("plugin_lifecycle.uninstalled", { name }) };
|
|
7901
|
+
}
|
|
7902
|
+
async function setPluginEnabled(name, enabled, opts) {
|
|
7903
|
+
const changed = await new PluginStore(opts.statePath).mutate((records) => {
|
|
7904
|
+
const hit = records.find((r) => r.name === name);
|
|
7905
|
+
if (!hit) return void 0;
|
|
7719
7906
|
return {
|
|
7720
|
-
|
|
7721
|
-
|
|
7907
|
+
records: records.map((r) => r.name === name ? { ...r, enabled } : r),
|
|
7908
|
+
result: hit.enabled !== enabled
|
|
7722
7909
|
};
|
|
7910
|
+
});
|
|
7911
|
+
if (changed === void 0) {
|
|
7912
|
+
return { ok: false, reason: t("plugin_lifecycle.not_installed", { name }) };
|
|
7723
7913
|
}
|
|
7724
|
-
const
|
|
7725
|
-
|
|
7726
|
-
|
|
7727
|
-
|
|
7728
|
-
}
|
|
7729
|
-
return { ok: true, catalog: checked.value };
|
|
7914
|
+
const verb = enabled ? t("plugin_lifecycle.verb_enable") : t("plugin_lifecycle.verb_disable");
|
|
7915
|
+
return {
|
|
7916
|
+
ok: true,
|
|
7917
|
+
message: changed ? t("plugin_lifecycle.toggled", { verb, name }) : t("plugin_lifecycle.already", { name, verb })
|
|
7918
|
+
};
|
|
7730
7919
|
}
|
|
7731
|
-
function
|
|
7732
|
-
const
|
|
7733
|
-
|
|
7920
|
+
async function updatePlugin(name, opts) {
|
|
7921
|
+
const existing = new PluginStore(opts.statePath).read().records.find((r) => r.name === name);
|
|
7922
|
+
if (!existing) return { ok: false, reason: t("plugin_lifecycle.not_installed", { name }) };
|
|
7923
|
+
if (existing.linked) {
|
|
7924
|
+
return {
|
|
7925
|
+
ok: true,
|
|
7926
|
+
message: t("plugin_lifecycle.linked_no_update", { name, source: existing.source })
|
|
7927
|
+
};
|
|
7928
|
+
}
|
|
7929
|
+
return installPlugin(existing.source, {
|
|
7930
|
+
...opts,
|
|
7931
|
+
replace: name,
|
|
7932
|
+
...existing.marketplace ? { marketplace: existing.marketplace } : {}
|
|
7933
|
+
});
|
|
7734
7934
|
}
|
|
7735
7935
|
var TMP_SUFFIX = ".epoch-rewind";
|
|
7736
7936
|
function planFor(entry, current) {
|
|
@@ -7778,7 +7978,7 @@ async function applyRewind(store, sessionId, manifest, apply, skippedConflicts)
|
|
|
7778
7978
|
} catch (err) {
|
|
7779
7979
|
return { ...empty, failed: t("rewind.stage_failed", { message: messageOf(err) }) };
|
|
7780
7980
|
}
|
|
7781
|
-
return
|
|
7981
|
+
return commitAll2(staged, skippedConflicts);
|
|
7782
7982
|
}
|
|
7783
7983
|
async function stageAll(store, sessionId, manifest, todo, byPath) {
|
|
7784
7984
|
const staged = [];
|
|
@@ -7806,7 +8006,7 @@ async function stageAll(store, sessionId, manifest, todo, byPath) {
|
|
|
7806
8006
|
}
|
|
7807
8007
|
return staged;
|
|
7808
8008
|
}
|
|
7809
|
-
async function
|
|
8009
|
+
async function commitAll2(staged, skippedConflicts) {
|
|
7810
8010
|
const outcome = {
|
|
7811
8011
|
restored: [],
|
|
7812
8012
|
deleted: [],
|
|
@@ -13371,6 +13571,10 @@ var DEFAULT_MODELS = {
|
|
|
13371
13571
|
groq: "llama-3.3-70b-versatile",
|
|
13372
13572
|
mistral: "ministral-8b-latest",
|
|
13373
13573
|
moonshotai: "kimi-latest",
|
|
13574
|
+
zhipu: "glm-4.5-flash",
|
|
13575
|
+
dashscope: "qwen-flash",
|
|
13576
|
+
bytedance: "doubao-seed-2.0-mini",
|
|
13577
|
+
qianfan: "ernie-speed-128k",
|
|
13374
13578
|
cohere: "command-r7b-12-2024",
|
|
13375
13579
|
xai: "grok-vision-beta",
|
|
13376
13580
|
perplexity: "sonar",
|
|
@@ -13783,6 +13987,10 @@ var PACKAGES = {
|
|
|
13783
13987
|
groq: "@ai-sdk/groq",
|
|
13784
13988
|
mistral: "@ai-sdk/mistral",
|
|
13785
13989
|
moonshotai: "@ai-sdk/moonshotai",
|
|
13990
|
+
zhipu: "@ai-sdk/openai-compatible",
|
|
13991
|
+
dashscope: "@ai-sdk/alibaba",
|
|
13992
|
+
bytedance: "@ai-sdk/openai-compatible",
|
|
13993
|
+
qianfan: "@ai-sdk/openai-compatible",
|
|
13786
13994
|
cohere: "@ai-sdk/cohere",
|
|
13787
13995
|
xai: "@ai-sdk/xai",
|
|
13788
13996
|
perplexity: "@ai-sdk/perplexity",
|
|
@@ -13792,6 +14000,17 @@ var PACKAGES = {
|
|
|
13792
14000
|
ollama: "ollama-ai-provider-v2",
|
|
13793
14001
|
"openai-compatible": "@ai-sdk/openai-compatible"
|
|
13794
14002
|
};
|
|
14003
|
+
function openaiCompatibleFactory(name, defaultBaseUrl) {
|
|
14004
|
+
return async ({ apiKey, baseUrl }) => {
|
|
14005
|
+
const { createOpenAICompatible } = await import('@ai-sdk/openai-compatible');
|
|
14006
|
+
return createOpenAICompatible({
|
|
14007
|
+
apiKey,
|
|
14008
|
+
baseURL: baseUrl || defaultBaseUrl,
|
|
14009
|
+
name,
|
|
14010
|
+
includeUsage: true
|
|
14011
|
+
});
|
|
14012
|
+
};
|
|
14013
|
+
}
|
|
13795
14014
|
var LOADERS = {
|
|
13796
14015
|
openai: async ({ apiKey, baseUrl }) => {
|
|
13797
14016
|
const { createOpenAI } = await import('@ai-sdk/openai');
|
|
@@ -13823,6 +14042,17 @@ var LOADERS = {
|
|
|
13823
14042
|
const { createMoonshotAI } = await import('@ai-sdk/moonshotai');
|
|
13824
14043
|
return createMoonshotAI({ apiKey });
|
|
13825
14044
|
},
|
|
14045
|
+
zhipu: openaiCompatibleFactory("zhipu", "https://open.bigmodel.cn/api/paas/v4"),
|
|
14046
|
+
dashscope: async ({ apiKey, baseUrl }) => {
|
|
14047
|
+
const { createAlibaba } = await import('@ai-sdk/alibaba');
|
|
14048
|
+
const provider = createAlibaba({
|
|
14049
|
+
apiKey,
|
|
14050
|
+
baseURL: baseUrl || "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
|
14051
|
+
});
|
|
14052
|
+
return (model) => provider.languageModel(model);
|
|
14053
|
+
},
|
|
14054
|
+
bytedance: openaiCompatibleFactory("bytedance", "https://ark.cn-beijing.volces.com/api/v3"),
|
|
14055
|
+
qianfan: openaiCompatibleFactory("qianfan", "https://qianfan.baidubce.com/v2"),
|
|
13826
14056
|
cohere: async ({ apiKey }) => {
|
|
13827
14057
|
const { createCohere } = await import('@ai-sdk/cohere');
|
|
13828
14058
|
return createCohere({ apiKey });
|
|
@@ -13851,15 +14081,7 @@ var LOADERS = {
|
|
|
13851
14081
|
const { createOllama } = await import('ollama-ai-provider-v2');
|
|
13852
14082
|
return createOllama();
|
|
13853
14083
|
},
|
|
13854
|
-
"openai-compatible":
|
|
13855
|
-
const { createOpenAICompatible } = await import('@ai-sdk/openai-compatible');
|
|
13856
|
-
return createOpenAICompatible({
|
|
13857
|
-
apiKey,
|
|
13858
|
-
baseURL: baseUrl || "http://localhost:8080/v1",
|
|
13859
|
-
name: "custom",
|
|
13860
|
-
includeUsage: true
|
|
13861
|
-
});
|
|
13862
|
-
}
|
|
14084
|
+
"openai-compatible": openaiCompatibleFactory("custom", "http://localhost:8080/v1")
|
|
13863
14085
|
};
|
|
13864
14086
|
var ProviderSdkMissingError = class extends Error {
|
|
13865
14087
|
constructor(providerType, packageName, cause) {
|
|
@@ -14491,6 +14713,10 @@ var STATIC_MODEL_CATALOG = {
|
|
|
14491
14713
|
],
|
|
14492
14714
|
mistral: ["mistral-large-latest", "mistral-small-latest", "codestral-latest"],
|
|
14493
14715
|
moonshotai: ["kimi-latest"],
|
|
14716
|
+
zhipu: ["glm-4.6", "glm-4.5-flash", "glm-4.5-air", "glm-4.5"],
|
|
14717
|
+
dashscope: ["qwen3-max", "qwen-plus", "qwen-flash", "qwen-turbo", "qwen-max-latest"],
|
|
14718
|
+
bytedance: ["doubao-seed-2.1-pro", "doubao-seed-2.0-mini", "doubao-seed-1.6-flash"],
|
|
14719
|
+
qianfan: ["ernie-5.0", "ernie-speed-128k", "ernie-4.0-turbo-8k"],
|
|
14494
14720
|
cohere: ["command-r-plus-04-2024", "command-r7b-12-2024", "command-a-03-2025"],
|
|
14495
14721
|
xai: ["grok-vision-beta"],
|
|
14496
14722
|
perplexity: ["sonar", "sonar-pro", "sonar-reasoning"],
|
|
@@ -14506,6 +14732,10 @@ var PROVIDER_DISCOVERY = {
|
|
|
14506
14732
|
groq: { baseUrl: "https://api.groq.com/openai/v1", auth: "bearer" },
|
|
14507
14733
|
mistral: { baseUrl: "https://api.mistral.ai/v1", auth: "bearer" },
|
|
14508
14734
|
moonshotai: { baseUrl: "https://api.moonshot.ai/v1", auth: "bearer" },
|
|
14735
|
+
zhipu: { baseUrl: "https://open.bigmodel.cn/api/paas/v4", auth: "bearer" },
|
|
14736
|
+
dashscope: { baseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1", auth: "bearer" },
|
|
14737
|
+
bytedance: { baseUrl: "https://ark.cn-beijing.volces.com/api/v3", auth: "bearer" },
|
|
14738
|
+
qianfan: { baseUrl: "https://qianfan.baidubce.com/v2", auth: "bearer" },
|
|
14509
14739
|
xai: { baseUrl: "https://api.x.ai/v1", auth: "bearer" },
|
|
14510
14740
|
perplexity: { baseUrl: "https://api.perplexity.ai", auth: "bearer" },
|
|
14511
14741
|
togetherai: { baseUrl: "https://api.together.xyz/v1", auth: "bearer" }
|
|
@@ -14756,7 +14986,7 @@ function writeUserYaml(path, key, value) {
|
|
|
14756
14986
|
const after = asMapping(parseYaml(next));
|
|
14757
14987
|
const intact = after !== null && (dot < 0 ? onlyChanged(before, after, key, value) : onlyChangedNested(before, after, key.slice(0, dot), key.slice(dot + 1), value));
|
|
14758
14988
|
if (!intact) return { ok: false, reason: "unwritable-file" };
|
|
14759
|
-
return
|
|
14989
|
+
return commit(path, next, 384, value);
|
|
14760
14990
|
}
|
|
14761
14991
|
function writeSettingsJson(path, key, value) {
|
|
14762
14992
|
let before = {};
|
|
@@ -14776,7 +15006,7 @@ function writeSettingsJson(path, key, value) {
|
|
|
14776
15006
|
}
|
|
14777
15007
|
const body2 = `${JSON.stringify({ ...before, [key]: value }, null, 2)}
|
|
14778
15008
|
`;
|
|
14779
|
-
return
|
|
15009
|
+
return commit(path, body2, 420, value);
|
|
14780
15010
|
}
|
|
14781
15011
|
function scalarText(value) {
|
|
14782
15012
|
return dump(value, { lineWidth: -1 }).trimEnd();
|
|
@@ -14833,7 +15063,7 @@ function sameKeysAndValues(before, after, except) {
|
|
|
14833
15063
|
function sameData(a, b) {
|
|
14834
15064
|
return JSON.stringify(a ?? null) === JSON.stringify(b ?? null);
|
|
14835
15065
|
}
|
|
14836
|
-
function
|
|
15066
|
+
function commit(path, body2, createMode, value) {
|
|
14837
15067
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
14838
15068
|
try {
|
|
14839
15069
|
mkdirSync(dirname(path), { recursive: true });
|
|
@@ -19965,7 +20195,7 @@ function parseSimpleToml(content, file) {
|
|
|
19965
20195
|
const at = `${file}:${i + 1}`;
|
|
19966
20196
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
19967
20197
|
if (trimmed === "[[rule]]") {
|
|
19968
|
-
if (current)
|
|
20198
|
+
if (current) commit2(current, file, rules, issues);
|
|
19969
20199
|
current = { raw: {}, line: i + 1 };
|
|
19970
20200
|
continue;
|
|
19971
20201
|
}
|
|
@@ -19988,7 +20218,7 @@ function parseSimpleToml(content, file) {
|
|
|
19988
20218
|
current.raw[kv.key] = kv.value;
|
|
19989
20219
|
}
|
|
19990
20220
|
}
|
|
19991
|
-
if (current)
|
|
20221
|
+
if (current) commit2(current, file, rules, issues);
|
|
19992
20222
|
return { rules, issues };
|
|
19993
20223
|
}
|
|
19994
20224
|
function readKeyValue(line) {
|
|
@@ -20002,7 +20232,7 @@ function readKeyValue(line) {
|
|
|
20002
20232
|
const sq = rawVal.match(/^'(.*)'$/);
|
|
20003
20233
|
return { key, value: sq && sq[1] ? sq[1] : rawVal };
|
|
20004
20234
|
}
|
|
20005
|
-
function
|
|
20235
|
+
function commit2(draft, file, rules, issues) {
|
|
20006
20236
|
const at = `${file}:${draft.line}`;
|
|
20007
20237
|
issues.push(...unknownKeyIssues(POLICY_RULE_KEYS, draft.raw, at));
|
|
20008
20238
|
const raw = { ...draft.raw };
|
|
@@ -22490,7 +22720,7 @@ async function importSkills(source, opts) {
|
|
|
22490
22720
|
detail: t("skill.import_stale", void 0, opts.lang)
|
|
22491
22721
|
};
|
|
22492
22722
|
}
|
|
22493
|
-
return { ok: true, ...
|
|
22723
|
+
return { ok: true, ...commit3(preview3, staged, opts.skillsDir, opts.lang) };
|
|
22494
22724
|
});
|
|
22495
22725
|
}
|
|
22496
22726
|
async function withStaged(source, opts, fn) {
|
|
@@ -22609,7 +22839,7 @@ function digest(entries2, staged) {
|
|
|
22609
22839
|
}
|
|
22610
22840
|
return hash.digest("hex").slice(0, 32);
|
|
22611
22841
|
}
|
|
22612
|
-
function
|
|
22842
|
+
function commit3(preview3, staged, skillsDir, lang) {
|
|
22613
22843
|
const byName = new Map(staged.map((s) => [s.name, s.dir]));
|
|
22614
22844
|
const imported = [];
|
|
22615
22845
|
const skipped = [];
|