dsh-packycode-api 1.0.1 → 1.0.4
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/AGENTS.md +49 -1
- package/CHANGELOG.md +15 -0
- package/README.en.md +161 -0
- package/README.md +131 -71
- package/docs/assets/dsh-packycode-mascot.png +0 -0
- package/icon-subscription.webp +0 -0
- package/lib/client.js +260 -7
- package/lib/index.js +414 -7
- package/package.json +5 -1
package/lib/index.js
CHANGED
|
@@ -8,6 +8,8 @@ import { homedir, tmpdir } from "node:os";
|
|
|
8
8
|
import { spawn } from "node:child_process";
|
|
9
9
|
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
10
10
|
import { credentialKey } from "@deepseek-ai/dsh-credentials";
|
|
11
|
+
import { readFile, readdir, realpath } from "node:fs/promises";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
11
13
|
import { createProvider } from "@earendil-works/pi-ai";
|
|
12
14
|
import { anthropicMessagesApi } from "@earendil-works/pi-ai/api/anthropic-messages.lazy";
|
|
13
15
|
import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completions.lazy";
|
|
@@ -67,7 +69,7 @@ const DEFAULT_REASONING_LEVEL = "high";
|
|
|
67
69
|
* @module dsh-packycode-api/balance
|
|
68
70
|
*/
|
|
69
71
|
const record = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
70
|
-
const clone = (value) => structuredClone(value);
|
|
72
|
+
const clone$1 = (value) => structuredClone(value);
|
|
71
73
|
function number(value) {
|
|
72
74
|
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
73
75
|
if (typeof value === "string" && value.trim() !== "") {
|
|
@@ -274,7 +276,7 @@ function createBalanceReader({ listKeys, getHosts, fetchImpl = fetch, now = Date
|
|
|
274
276
|
return Object.freeze({
|
|
275
277
|
async read({ force = false, signal } = {}) {
|
|
276
278
|
signal?.throwIfAborted();
|
|
277
|
-
if (!force && cached !== void 0 && now() - cached.fetchedAt < ttlMs) return clone(cached);
|
|
279
|
+
if (!force && cached !== void 0 && now() - cached.fetchedAt < ttlMs) return clone$1(cached);
|
|
278
280
|
const createPending = (requestSignal) => {
|
|
279
281
|
const observedGeneration = generation;
|
|
280
282
|
return load(requestSignal).then((value) => {
|
|
@@ -282,7 +284,7 @@ function createBalanceReader({ listKeys, getHosts, fetchImpl = fetch, now = Date
|
|
|
282
284
|
return value;
|
|
283
285
|
});
|
|
284
286
|
};
|
|
285
|
-
if (force) return clone(await createPending(signal));
|
|
287
|
+
if (force) return clone$1(await createPending(signal));
|
|
286
288
|
const pending = inFlight ?? createPending();
|
|
287
289
|
if (inFlight === void 0) {
|
|
288
290
|
inFlight = pending;
|
|
@@ -291,7 +293,7 @@ function createBalanceReader({ listKeys, getHosts, fetchImpl = fetch, now = Date
|
|
|
291
293
|
};
|
|
292
294
|
pending.then(clear, clear);
|
|
293
295
|
}
|
|
294
|
-
return clone(await pending);
|
|
296
|
+
return clone$1(await pending);
|
|
295
297
|
},
|
|
296
298
|
invalidate() {
|
|
297
299
|
generation += 1;
|
|
@@ -1819,6 +1821,20 @@ var PackyCodeKeyRing = class {
|
|
|
1819
1821
|
};
|
|
1820
1822
|
}));
|
|
1821
1823
|
}
|
|
1824
|
+
async rename(id, label) {
|
|
1825
|
+
const name = text(label).trim().slice(0, 60);
|
|
1826
|
+
if (name === "") throw new Error("That PackyCode key name cannot be empty");
|
|
1827
|
+
return projection(await this.#write((state) => {
|
|
1828
|
+
if (!state.keys.some((candidate) => candidate.id === id)) throw new Error("Unknown PackyCode key");
|
|
1829
|
+
return {
|
|
1830
|
+
activeId: state.activeId,
|
|
1831
|
+
keys: state.keys.map((candidate) => candidate.id === id ? {
|
|
1832
|
+
...candidate,
|
|
1833
|
+
label: name
|
|
1834
|
+
} : candidate)
|
|
1835
|
+
};
|
|
1836
|
+
}));
|
|
1837
|
+
}
|
|
1822
1838
|
async remove(id) {
|
|
1823
1839
|
return projection(await this.#write((state) => {
|
|
1824
1840
|
const remaining = state.keys.filter((candidate) => candidate.id !== id);
|
|
@@ -1946,6 +1962,383 @@ function capClaudeMode(requested, ceiling) {
|
|
|
1946
1962
|
return RANK[requested] > RANK[ceiling] ? ceiling : requested;
|
|
1947
1963
|
}
|
|
1948
1964
|
//#endregion
|
|
1965
|
+
//#region src/plugin-version.js
|
|
1966
|
+
/**
|
|
1967
|
+
* Version self-knowledge for the settings panel.
|
|
1968
|
+
*
|
|
1969
|
+
* The running version comes from this package. The latest version comes from
|
|
1970
|
+
* the npm registry. An in-place update is only offered for a registry install,
|
|
1971
|
+
* and it installs that exact version with the host's own pnpm — never through
|
|
1972
|
+
* `dsh plugin --profile desktop`, which the desktop app refuses. Registry
|
|
1973
|
+
* bodies and package-manager output stay host-side.
|
|
1974
|
+
*
|
|
1975
|
+
* @module dsh-packycode-api/plugin-version
|
|
1976
|
+
*/
|
|
1977
|
+
const PACKAGE_NAME = "dsh-packycode-api";
|
|
1978
|
+
const NPM_REGISTRY_LATEST_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
1979
|
+
const DEFAULT_VERSION_TTL_MS = 5 * 6e4;
|
|
1980
|
+
const DEFAULT_VERSION_TIMEOUT_MS = 1e4;
|
|
1981
|
+
const DEFAULT_UPDATE_TIMEOUT_MS = 18e4;
|
|
1982
|
+
const clone = (value) => structuredClone(value);
|
|
1983
|
+
/**
|
|
1984
|
+
* Resolve fetch at call time. A sibling plugin may wrap `globalThis.fetch`
|
|
1985
|
+
* and later dismantle that wrapper; a binding captured during `apply()` would
|
|
1986
|
+
* keep the dead wrapper for the process lifetime.
|
|
1987
|
+
*/
|
|
1988
|
+
function ambientFetch(input, init) {
|
|
1989
|
+
const headers = new Headers(init?.headers ?? (input instanceof Request ? input.headers : void 0));
|
|
1990
|
+
if (!headers.has("accept-encoding")) headers.set("accept-encoding", "identity");
|
|
1991
|
+
return globalThis.fetch(input, {
|
|
1992
|
+
...init,
|
|
1993
|
+
headers
|
|
1994
|
+
});
|
|
1995
|
+
}
|
|
1996
|
+
/** One caller's cancellation must not abort the shared registry read. */
|
|
1997
|
+
function waitForSharedRequest(promise, signal) {
|
|
1998
|
+
if (signal === void 0) return promise;
|
|
1999
|
+
signal.throwIfAborted();
|
|
2000
|
+
return new Promise((resolve, reject) => {
|
|
2001
|
+
const onAbort = () => reject(signal.reason);
|
|
2002
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
2003
|
+
promise.then((value) => {
|
|
2004
|
+
signal.removeEventListener("abort", onAbort);
|
|
2005
|
+
resolve(value);
|
|
2006
|
+
}, (error) => {
|
|
2007
|
+
signal.removeEventListener("abort", onAbort);
|
|
2008
|
+
reject(error);
|
|
2009
|
+
});
|
|
2010
|
+
});
|
|
2011
|
+
}
|
|
2012
|
+
/** Parse a strict `x.y.z[-pre][+build]` version; anything else is undefined. */
|
|
2013
|
+
function parseSemver(value) {
|
|
2014
|
+
if (typeof value !== "string") return void 0;
|
|
2015
|
+
const match = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/u.exec(value.trim());
|
|
2016
|
+
if (match === null) return void 0;
|
|
2017
|
+
return {
|
|
2018
|
+
major: Number(match[1]),
|
|
2019
|
+
minor: Number(match[2]),
|
|
2020
|
+
patch: Number(match[3]),
|
|
2021
|
+
pre: match[4]
|
|
2022
|
+
};
|
|
2023
|
+
}
|
|
2024
|
+
/** Compare two versions: -1/0/1, or undefined when either side is not semver. */
|
|
2025
|
+
function compareSemver(a, b) {
|
|
2026
|
+
const left = parseSemver(a);
|
|
2027
|
+
const right = parseSemver(b);
|
|
2028
|
+
if (left === void 0 || right === void 0) return void 0;
|
|
2029
|
+
for (const key of [
|
|
2030
|
+
"major",
|
|
2031
|
+
"minor",
|
|
2032
|
+
"patch"
|
|
2033
|
+
]) if (left[key] !== right[key]) return left[key] < right[key] ? -1 : 1;
|
|
2034
|
+
if (left.pre === right.pre) return 0;
|
|
2035
|
+
if (left.pre === void 0) return 1;
|
|
2036
|
+
if (right.pre === void 0) return -1;
|
|
2037
|
+
return left.pre < right.pre ? -1 : 1;
|
|
2038
|
+
}
|
|
2039
|
+
/**
|
|
2040
|
+
* Classify how the running package reached the profile. Only registry specs
|
|
2041
|
+
* may be updated in place; local checkouts belong to the owner's iteration.
|
|
2042
|
+
*/
|
|
2043
|
+
function classifySpec(spec) {
|
|
2044
|
+
if (typeof spec !== "string" || spec === "") return "unknown";
|
|
2045
|
+
if (/^(?:link|file):/u.test(spec)) return "link";
|
|
2046
|
+
if (/^(?:\d|[~^*])/u.test(spec)) return "npm";
|
|
2047
|
+
return "unknown";
|
|
2048
|
+
}
|
|
2049
|
+
const readManifest = async (path) => {
|
|
2050
|
+
try {
|
|
2051
|
+
const parsed = JSON.parse(await readFile(path, "utf8"));
|
|
2052
|
+
return parsed !== null && typeof parsed === "object" ? parsed : void 0;
|
|
2053
|
+
} catch {
|
|
2054
|
+
return;
|
|
2055
|
+
}
|
|
2056
|
+
};
|
|
2057
|
+
/**
|
|
2058
|
+
* A zero exit only proves the command ran. pnpm can skip a too-new version
|
|
2059
|
+
* and still exit zero, so the landed manifest is what makes the answer true.
|
|
2060
|
+
*/
|
|
2061
|
+
async function confirmInstalled({ profileDir, expected, settleDelayMs, attempts = 3 }) {
|
|
2062
|
+
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
2063
|
+
if ((await readManifest(join(profileDir, "node_modules", "dsh-packycode-api", "package.json")))?.version === expected) return;
|
|
2064
|
+
if (attempt < attempts) await new Promise((resolve) => {
|
|
2065
|
+
setTimeout(resolve, settleDelayMs);
|
|
2066
|
+
});
|
|
2067
|
+
}
|
|
2068
|
+
throw new Error("PackyCode plugin update did not install the requested version");
|
|
2069
|
+
}
|
|
2070
|
+
/**
|
|
2071
|
+
* Find the profile that owns this running installation. pnpm symlinks resolve
|
|
2072
|
+
* to the same realpath on both sides. When no realpath matches, fall back to
|
|
2073
|
+
* the only profile that declares the dependency at all.
|
|
2074
|
+
*/
|
|
2075
|
+
async function findInstall({ dshHome, ownPackageJsonUrl }) {
|
|
2076
|
+
let ownReal;
|
|
2077
|
+
try {
|
|
2078
|
+
ownReal = await realpath(fileURLToPath(ownPackageJsonUrl));
|
|
2079
|
+
} catch {
|
|
2080
|
+
ownReal = void 0;
|
|
2081
|
+
}
|
|
2082
|
+
let entries = [];
|
|
2083
|
+
try {
|
|
2084
|
+
entries = await readdir(join(dshHome, "profiles"), { withFileTypes: true });
|
|
2085
|
+
} catch {
|
|
2086
|
+
entries = [];
|
|
2087
|
+
}
|
|
2088
|
+
const declared = [];
|
|
2089
|
+
for (const entry of entries) {
|
|
2090
|
+
if (!entry.isDirectory()) continue;
|
|
2091
|
+
const profileDir = join(dshHome, "profiles", entry.name);
|
|
2092
|
+
const spec = (await readManifest(join(profileDir, "package.json")))?.dependencies?.[PACKAGE_NAME];
|
|
2093
|
+
if (typeof spec !== "string") continue;
|
|
2094
|
+
declared.push({
|
|
2095
|
+
profile: entry.name,
|
|
2096
|
+
spec
|
|
2097
|
+
});
|
|
2098
|
+
if (ownReal === void 0) continue;
|
|
2099
|
+
try {
|
|
2100
|
+
if (await realpath(join(profileDir, "node_modules", "dsh-packycode-api", "package.json")) === ownReal) return {
|
|
2101
|
+
kind: classifySpec(spec),
|
|
2102
|
+
profile: entry.name
|
|
2103
|
+
};
|
|
2104
|
+
} catch {}
|
|
2105
|
+
}
|
|
2106
|
+
if (declared.length === 1) return {
|
|
2107
|
+
kind: classifySpec(declared[0].spec),
|
|
2108
|
+
profile: declared[0].profile
|
|
2109
|
+
};
|
|
2110
|
+
return { kind: "unknown" };
|
|
2111
|
+
}
|
|
2112
|
+
const isExistingPath = (value) => typeof value === "string" && value !== "" && existsSync(value);
|
|
2113
|
+
/** Anchored to the file name so a directory that merely contains the word cannot match. */
|
|
2114
|
+
const PNPM_SCRIPT = /[\\/]pnpm\.(?:mjs|cjs|js)$/u;
|
|
2115
|
+
const dshRuntimeRoot = (value) => isExistingPath(value) && existsSync(join(value, "node_modules", "@deepseek-ai", "dsh", "package.json")) ? value : void 0;
|
|
2116
|
+
/**
|
|
2117
|
+
* Locate the package manager that owns this profile.
|
|
2118
|
+
*
|
|
2119
|
+
* `dsh plugin --profile desktop` is refused by the desktop app, so the update
|
|
2120
|
+
* installs with pnpm directly. The desktop host puts the bundled pnpm in its
|
|
2121
|
+
* own arguments; nothing is guessed from the entry script.
|
|
2122
|
+
*/
|
|
2123
|
+
function resolvePnpm({ argv } = {}) {
|
|
2124
|
+
const entries = Array.isArray(argv) ? argv : [];
|
|
2125
|
+
for (const entry of entries) if (isExistingPath(entry) && PNPM_SCRIPT.test(entry)) return entry;
|
|
2126
|
+
for (const entry of entries) {
|
|
2127
|
+
const root = dshRuntimeRoot(entry);
|
|
2128
|
+
if (root === void 0) continue;
|
|
2129
|
+
const script = join(root, "..", "pnpm", "bin", "pnpm.mjs");
|
|
2130
|
+
if (existsSync(script)) return script;
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
function resolveLauncher({ argv, execPath }) {
|
|
2134
|
+
const pnpm = resolvePnpm({ argv });
|
|
2135
|
+
if (pnpm === void 0) return {
|
|
2136
|
+
command: "pnpm",
|
|
2137
|
+
prefix: []
|
|
2138
|
+
};
|
|
2139
|
+
return {
|
|
2140
|
+
command: execPath,
|
|
2141
|
+
prefix: [pnpm]
|
|
2142
|
+
};
|
|
2143
|
+
}
|
|
2144
|
+
/** Run pnpm host-side. Its output is never returned to the browser. */
|
|
2145
|
+
function spawnRunCommand({ command, args, cwd }, { signal, timeoutMs }) {
|
|
2146
|
+
return new Promise((resolve, reject) => {
|
|
2147
|
+
if (signal?.aborted) {
|
|
2148
|
+
reject(signal.reason instanceof Error ? signal.reason : /* @__PURE__ */ new Error("PackyCode plugin update aborted"));
|
|
2149
|
+
return;
|
|
2150
|
+
}
|
|
2151
|
+
const controller = new AbortController();
|
|
2152
|
+
const onAbort = () => controller.abort(signal.reason);
|
|
2153
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2154
|
+
const timer = setTimeout(() => controller.abort(/* @__PURE__ */ new Error("PackyCode plugin update timed out")), timeoutMs);
|
|
2155
|
+
let settled = false;
|
|
2156
|
+
const settle = (callback, value) => {
|
|
2157
|
+
if (settled) return;
|
|
2158
|
+
settled = true;
|
|
2159
|
+
clearTimeout(timer);
|
|
2160
|
+
signal?.removeEventListener("abort", onAbort);
|
|
2161
|
+
callback(value);
|
|
2162
|
+
};
|
|
2163
|
+
let child;
|
|
2164
|
+
try {
|
|
2165
|
+
child = spawn(command, args, {
|
|
2166
|
+
stdio: [
|
|
2167
|
+
"ignore",
|
|
2168
|
+
"pipe",
|
|
2169
|
+
"pipe"
|
|
2170
|
+
],
|
|
2171
|
+
signal: controller.signal,
|
|
2172
|
+
...cwd === void 0 ? {} : { cwd }
|
|
2173
|
+
});
|
|
2174
|
+
} catch (error) {
|
|
2175
|
+
settle(reject, error);
|
|
2176
|
+
return;
|
|
2177
|
+
}
|
|
2178
|
+
let stdout = "";
|
|
2179
|
+
let stderr = "";
|
|
2180
|
+
child.stdout?.on("data", (chunk) => {
|
|
2181
|
+
stdout = (stdout + chunk).slice(-8192);
|
|
2182
|
+
});
|
|
2183
|
+
child.stderr?.on("data", (chunk) => {
|
|
2184
|
+
stderr = (stderr + chunk).slice(-8192);
|
|
2185
|
+
});
|
|
2186
|
+
child.on("error", (error) => settle(reject, error));
|
|
2187
|
+
child.on("close", (code) => {
|
|
2188
|
+
if (controller.signal.aborted) {
|
|
2189
|
+
const reason = controller.signal.reason;
|
|
2190
|
+
settle(reject, reason instanceof Error ? reason : /* @__PURE__ */ new Error("PackyCode plugin update aborted"));
|
|
2191
|
+
return;
|
|
2192
|
+
}
|
|
2193
|
+
settle(resolve, {
|
|
2194
|
+
code: code ?? 1,
|
|
2195
|
+
stdout,
|
|
2196
|
+
stderr
|
|
2197
|
+
});
|
|
2198
|
+
});
|
|
2199
|
+
});
|
|
2200
|
+
}
|
|
2201
|
+
/**
|
|
2202
|
+
* The running version, the latest registry version, which profile installed
|
|
2203
|
+
* this copy, and registry-spec updates against that profile.
|
|
2204
|
+
*/
|
|
2205
|
+
function createPackyCodePluginManager({ fetchImpl = ambientFetch, runCommand = spawnRunCommand, execPath = process.execPath, argv = process.argv, env = process.env, ownPackageJsonUrl = new URL("../package.json", import.meta.url), registryUrl = NPM_REGISTRY_LATEST_URL, now = Date.now, ttlMs = DEFAULT_VERSION_TTL_MS, timeoutMs = DEFAULT_VERSION_TIMEOUT_MS, updateTimeoutMs = DEFAULT_UPDATE_TIMEOUT_MS, settleDelayMs = 250 } = {}) {
|
|
2206
|
+
if (typeof fetchImpl !== "function") throw new Error("PackyCode plugin manager requires fetch");
|
|
2207
|
+
if (typeof runCommand !== "function") throw new Error("PackyCode plugin manager requires runCommand");
|
|
2208
|
+
const dshHome = typeof env.DSH_HOME === "string" && env.DSH_HOME !== "" ? env.DSH_HOME : join(homedir(), ".dsh");
|
|
2209
|
+
const launcher = resolveLauncher({
|
|
2210
|
+
argv,
|
|
2211
|
+
execPath
|
|
2212
|
+
});
|
|
2213
|
+
let cached;
|
|
2214
|
+
let inFlight;
|
|
2215
|
+
let generation = 0;
|
|
2216
|
+
let updating = false;
|
|
2217
|
+
const fetchLatest = async (signal) => {
|
|
2218
|
+
const timeoutSignal = AbortSignal.timeout(timeoutMs);
|
|
2219
|
+
const requestSignal = signal === void 0 ? timeoutSignal : AbortSignal.any([signal, timeoutSignal]);
|
|
2220
|
+
let response;
|
|
2221
|
+
try {
|
|
2222
|
+
response = await fetchImpl(registryUrl, {
|
|
2223
|
+
headers: { Accept: "application/json" },
|
|
2224
|
+
redirect: "error",
|
|
2225
|
+
signal: requestSignal
|
|
2226
|
+
});
|
|
2227
|
+
} catch (error) {
|
|
2228
|
+
if (signal?.aborted) throw error;
|
|
2229
|
+
throw new Error("PackyCode plugin version check failed");
|
|
2230
|
+
}
|
|
2231
|
+
if (!response.ok) throw new Error("PackyCode plugin version check failed");
|
|
2232
|
+
let version;
|
|
2233
|
+
try {
|
|
2234
|
+
version = (await response.json())?.version;
|
|
2235
|
+
} catch {
|
|
2236
|
+
version = void 0;
|
|
2237
|
+
}
|
|
2238
|
+
if (parseSemver(version) === void 0) throw new Error("PackyCode plugin version check failed");
|
|
2239
|
+
return version;
|
|
2240
|
+
};
|
|
2241
|
+
const readOwnVersion = async () => {
|
|
2242
|
+
const version = (await readManifest(fileURLToPath(ownPackageJsonUrl)))?.version;
|
|
2243
|
+
if (parseSemver(version) === void 0) throw new Error("PackyCode plugin version is unavailable");
|
|
2244
|
+
return version;
|
|
2245
|
+
};
|
|
2246
|
+
const load = async (signal) => {
|
|
2247
|
+
signal?.throwIfAborted();
|
|
2248
|
+
const [current, install, latest] = await Promise.all([
|
|
2249
|
+
readOwnVersion(),
|
|
2250
|
+
findInstall({
|
|
2251
|
+
dshHome,
|
|
2252
|
+
ownPackageJsonUrl
|
|
2253
|
+
}),
|
|
2254
|
+
fetchLatest(signal)
|
|
2255
|
+
]);
|
|
2256
|
+
return {
|
|
2257
|
+
current,
|
|
2258
|
+
latest,
|
|
2259
|
+
updateAvailable: compareSemver(latest, current) === 1,
|
|
2260
|
+
install,
|
|
2261
|
+
fetchedAt: now()
|
|
2262
|
+
};
|
|
2263
|
+
};
|
|
2264
|
+
return Object.freeze({
|
|
2265
|
+
async read({ force = false, signal } = {}) {
|
|
2266
|
+
signal?.throwIfAborted();
|
|
2267
|
+
if (!force && cached !== void 0 && now() - cached.fetchedAt < ttlMs) return clone(cached);
|
|
2268
|
+
const createPending = (requestSignal) => {
|
|
2269
|
+
const observedGeneration = generation;
|
|
2270
|
+
return load(requestSignal).then((value) => {
|
|
2271
|
+
if (generation === observedGeneration) cached = value;
|
|
2272
|
+
return value;
|
|
2273
|
+
});
|
|
2274
|
+
};
|
|
2275
|
+
if (force) return clone(await createPending(signal));
|
|
2276
|
+
const pending = inFlight ?? createPending();
|
|
2277
|
+
if (inFlight === void 0) {
|
|
2278
|
+
inFlight = pending;
|
|
2279
|
+
const clear = () => {
|
|
2280
|
+
if (inFlight === pending) inFlight = void 0;
|
|
2281
|
+
};
|
|
2282
|
+
pending.then(clear, clear);
|
|
2283
|
+
}
|
|
2284
|
+
return clone(await waitForSharedRequest(pending, signal));
|
|
2285
|
+
},
|
|
2286
|
+
async update({ signal } = {}) {
|
|
2287
|
+
signal?.throwIfAborted();
|
|
2288
|
+
if (updating) throw new Error("PackyCode plugin update is already running");
|
|
2289
|
+
updating = true;
|
|
2290
|
+
try {
|
|
2291
|
+
const install = await findInstall({
|
|
2292
|
+
dshHome,
|
|
2293
|
+
ownPackageJsonUrl
|
|
2294
|
+
});
|
|
2295
|
+
if (install.kind === "link") throw new Error("PackyCode plugin is installed from a local checkout");
|
|
2296
|
+
if (install.kind !== "npm" || install.profile === void 0) throw new Error("PackyCode plugin update could not find the owning profile");
|
|
2297
|
+
const latest = await fetchLatest(signal);
|
|
2298
|
+
const profileDir = join(dshHome, "profiles", install.profile);
|
|
2299
|
+
let result;
|
|
2300
|
+
try {
|
|
2301
|
+
result = await runCommand({
|
|
2302
|
+
command: launcher.command,
|
|
2303
|
+
args: [
|
|
2304
|
+
...launcher.prefix,
|
|
2305
|
+
"add",
|
|
2306
|
+
"--save-exact",
|
|
2307
|
+
`--config.minimumReleaseAge=0`,
|
|
2308
|
+
`${PACKAGE_NAME}@${latest}`
|
|
2309
|
+
],
|
|
2310
|
+
cwd: profileDir
|
|
2311
|
+
}, {
|
|
2312
|
+
signal,
|
|
2313
|
+
timeoutMs: updateTimeoutMs
|
|
2314
|
+
});
|
|
2315
|
+
} catch (error) {
|
|
2316
|
+
if (signal?.aborted) throw error;
|
|
2317
|
+
throw new Error("PackyCode plugin update failed");
|
|
2318
|
+
}
|
|
2319
|
+
if (result.code !== 0) throw new Error("PackyCode plugin update failed");
|
|
2320
|
+
await confirmInstalled({
|
|
2321
|
+
profileDir,
|
|
2322
|
+
expected: latest,
|
|
2323
|
+
settleDelayMs
|
|
2324
|
+
});
|
|
2325
|
+
generation += 1;
|
|
2326
|
+
cached = void 0;
|
|
2327
|
+
return {
|
|
2328
|
+
version: latest,
|
|
2329
|
+
profile: install.profile
|
|
2330
|
+
};
|
|
2331
|
+
} finally {
|
|
2332
|
+
updating = false;
|
|
2333
|
+
}
|
|
2334
|
+
},
|
|
2335
|
+
invalidate() {
|
|
2336
|
+
generation += 1;
|
|
2337
|
+
cached = void 0;
|
|
2338
|
+
}
|
|
2339
|
+
});
|
|
2340
|
+
}
|
|
2341
|
+
//#endregion
|
|
1949
2342
|
//#region src/catalog.js
|
|
1950
2343
|
/**
|
|
1951
2344
|
* The built-in PackyCode model catalog.
|
|
@@ -2354,11 +2747,15 @@ function normalizeHost(value, fallback) {
|
|
|
2354
2747
|
* Claude Code CLI manager (optional: without it the CLI endpoints refuse).
|
|
2355
2748
|
* @returns a handler for `connection.rpc.handle(CHANNEL, handler)`.
|
|
2356
2749
|
*/
|
|
2357
|
-
function createPackyCodeRpc({ ring, balance, hosts, preference, claudeCli }) {
|
|
2750
|
+
function createPackyCodeRpc({ ring, balance, hosts, preference, claudeCli, pluginManager }) {
|
|
2358
2751
|
const requireCli = () => {
|
|
2359
2752
|
if (claudeCli === void 0) throw new Error("PackyCode Claude Code CLI support is unavailable in this runtime");
|
|
2360
2753
|
return claudeCli;
|
|
2361
2754
|
};
|
|
2755
|
+
const requireManager = () => {
|
|
2756
|
+
if (pluginManager === void 0) throw new Error("PackyCode plugin version is unavailable");
|
|
2757
|
+
return pluginManager;
|
|
2758
|
+
};
|
|
2362
2759
|
const status = async () => ({
|
|
2363
2760
|
...await ring.list(),
|
|
2364
2761
|
hosts: hosts(),
|
|
@@ -2368,7 +2765,7 @@ function createPackyCodeRpc({ ring, balance, hosts, preference, claudeCli }) {
|
|
|
2368
2765
|
statusHost: DEFAULT_STATUS_HOST
|
|
2369
2766
|
}
|
|
2370
2767
|
});
|
|
2371
|
-
return async (endpoint, payload) => {
|
|
2768
|
+
return async (endpoint, payload, signal) => {
|
|
2372
2769
|
try {
|
|
2373
2770
|
switch (endpoint) {
|
|
2374
2771
|
case "status": return ok(await status());
|
|
@@ -2376,6 +2773,10 @@ function createPackyCodeRpc({ ring, balance, hosts, preference, claudeCli }) {
|
|
|
2376
2773
|
await ring.add(payload?.key, payload?.label);
|
|
2377
2774
|
balance.invalidate();
|
|
2378
2775
|
return ok(await status());
|
|
2776
|
+
case "keys/rename":
|
|
2777
|
+
await ring.rename(String(payload?.id ?? ""), payload?.label);
|
|
2778
|
+
balance.invalidate();
|
|
2779
|
+
return ok(await status());
|
|
2379
2780
|
case "keys/remove":
|
|
2380
2781
|
await ring.remove(String(payload?.id ?? ""));
|
|
2381
2782
|
balance.invalidate();
|
|
@@ -2413,6 +2814,11 @@ function createPackyCodeRpc({ ring, balance, hosts, preference, claudeCli }) {
|
|
|
2413
2814
|
cli.install();
|
|
2414
2815
|
return ok(await cli.status());
|
|
2415
2816
|
}
|
|
2817
|
+
case "plugin/version": return ok(await requireManager().read({
|
|
2818
|
+
force: payload?.force === true,
|
|
2819
|
+
signal
|
|
2820
|
+
}));
|
|
2821
|
+
case "plugin/update": return ok(await requireManager().update({ signal }));
|
|
2416
2822
|
default: return failed("Unknown PackyCode endpoint");
|
|
2417
2823
|
}
|
|
2418
2824
|
} catch (error) {
|
|
@@ -2703,7 +3109,8 @@ function apply(ctx, config) {
|
|
|
2703
3109
|
balance,
|
|
2704
3110
|
hosts,
|
|
2705
3111
|
preference,
|
|
2706
|
-
claudeCli
|
|
3112
|
+
claudeCli,
|
|
3113
|
+
pluginManager: createPackyCodePluginManager()
|
|
2707
3114
|
});
|
|
2708
3115
|
/**
|
|
2709
3116
|
* Connection registers a channel through `owner.webServer`, read on its own
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-packycode-api",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"packageManager": "pnpm@10.32.1",
|
|
5
5
|
"description": "Use a PackyCode (PackyAPI) API key in DeepSeek Harness through a built-in multi-protocol model route with key rotation and balance display",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./lib/index.js",
|
|
8
|
+
"icon": "./icon-subscription.webp",
|
|
8
9
|
"exports": {
|
|
9
10
|
".": "./lib/index.js",
|
|
10
11
|
"./client": "./lib/client.js",
|
|
@@ -12,9 +13,12 @@
|
|
|
12
13
|
"./package.json": "./package.json"
|
|
13
14
|
},
|
|
14
15
|
"files": [
|
|
16
|
+
"icon-subscription.webp",
|
|
15
17
|
"lib/*.js",
|
|
16
18
|
"cordis.patch.yml",
|
|
17
19
|
"README.md",
|
|
20
|
+
"README.en.md",
|
|
21
|
+
"docs/assets/dsh-packycode-mascot.png",
|
|
18
22
|
"AGENTS.md",
|
|
19
23
|
"CHANGELOG.md",
|
|
20
24
|
"SECURITY.md",
|