@dawn0608/dsh-skill-manager 0.1.6 → 0.2.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/README.md +5 -29
- package/README.zh.md +5 -29
- package/lib/THIRD_PARTY_NOTICES.md +0 -28
- package/lib/client.js +106 -102
- package/lib/client.js.map +4 -4
- package/lib/index.js +44 -179
- package/lib/index.js.map +4 -4
- package/package.json +3 -7
- package/lib/install-sync.js +0 -7693
- package/lib/install-sync.js.map +0 -7
- package/lib/sync-daemon.js +0 -8804
- package/lib/sync-daemon.js.map +0 -7
package/lib/index.js
CHANGED
|
@@ -7413,10 +7413,10 @@ var require_dist = __commonJS({
|
|
|
7413
7413
|
|
|
7414
7414
|
// src/index.js
|
|
7415
7415
|
import { homedir } from "node:os";
|
|
7416
|
-
import { join as
|
|
7416
|
+
import { join as join4, resolve as resolve2 } from "node:path";
|
|
7417
7417
|
|
|
7418
7418
|
// src/host.js
|
|
7419
|
-
import { isAbsolute as isAbsolute2, join as
|
|
7419
|
+
import { isAbsolute as isAbsolute2, join as join3, normalize } from "node:path";
|
|
7420
7420
|
|
|
7421
7421
|
// src/host-bundle.js
|
|
7422
7422
|
var import_yaml = __toESM(require_dist(), 1);
|
|
@@ -7586,7 +7586,7 @@ import {
|
|
|
7586
7586
|
unlinkSync,
|
|
7587
7587
|
writeFileSync
|
|
7588
7588
|
} from "node:fs";
|
|
7589
|
-
import { dirname, join as join2 } from "node:path";
|
|
7589
|
+
import { dirname, join as join2, resolve, toNamespacedPath } from "node:path";
|
|
7590
7590
|
import { randomUUID } from "node:crypto";
|
|
7591
7591
|
function stat(path) {
|
|
7592
7592
|
try {
|
|
@@ -7599,6 +7599,13 @@ function stat(path) {
|
|
|
7599
7599
|
function identity(stats) {
|
|
7600
7600
|
return `${stats.dev}:${stats.ino}:${stats.birthtimeNs}`;
|
|
7601
7601
|
}
|
|
7602
|
+
function linkTargets(path, expected) {
|
|
7603
|
+
const target = resolve(dirname(path), readlinkSync(path));
|
|
7604
|
+
return process.platform === "win32" ? toNamespacedPath(target).toLowerCase() === toNamespacedPath(resolve(expected)).toLowerCase() : target === resolve(expected);
|
|
7605
|
+
}
|
|
7606
|
+
function directoryLink(target, path) {
|
|
7607
|
+
symlinkSync(resolve(target), path, process.platform === "win32" ? "junction" : "dir");
|
|
7608
|
+
}
|
|
7602
7609
|
function safeDirectory(path, create) {
|
|
7603
7610
|
let stats = stat(path);
|
|
7604
7611
|
if (!stats && create) {
|
|
@@ -7685,19 +7692,30 @@ function createSkillStore(home) {
|
|
|
7685
7692
|
function takeLock() {
|
|
7686
7693
|
for (let generation = 0; ; generation += 1) {
|
|
7687
7694
|
const path = join2(manager, `.write-lock-${generation}`);
|
|
7695
|
+
const temp = join2(manager, `.lock-${randomUUID()}`);
|
|
7696
|
+
writeFileSync(temp, JSON.stringify({ pid: process.pid, nonce: randomUUID() }), { flag: "wx", mode: 384 });
|
|
7697
|
+
const token = identity(stat(temp));
|
|
7688
7698
|
try {
|
|
7689
|
-
|
|
7690
|
-
|
|
7699
|
+
linkSync(temp, path);
|
|
7700
|
+
if (!sameEntry(path, token)) throw new ManagerError("conflict");
|
|
7691
7701
|
return () => unlinkOwned(path, token);
|
|
7692
7702
|
} catch (error) {
|
|
7693
7703
|
if (error.code !== "EEXIST") throw error;
|
|
7704
|
+
} finally {
|
|
7705
|
+
unlinkSync(temp);
|
|
7694
7706
|
}
|
|
7695
7707
|
const stats = stat(path);
|
|
7696
|
-
if (!stats) throw new ManagerError("conflict");
|
|
7697
|
-
|
|
7698
|
-
|
|
7699
|
-
|
|
7700
|
-
|
|
7708
|
+
if (!stats?.isFile()) throw new ManagerError("conflict");
|
|
7709
|
+
let owner;
|
|
7710
|
+
try {
|
|
7711
|
+
owner = JSON.parse(readRegularFile(path, 1024, identity(stats)));
|
|
7712
|
+
} catch {
|
|
7713
|
+
throw new ManagerError("conflict");
|
|
7714
|
+
}
|
|
7715
|
+
const pid = owner?.pid;
|
|
7716
|
+
if (!Number.isSafeInteger(pid) || pid < 1 || pid > 2147483647 || !MANAGED_ID.test(owner?.nonce ?? "")) {
|
|
7717
|
+
throw new ManagerError("conflict");
|
|
7718
|
+
}
|
|
7701
7719
|
try {
|
|
7702
7720
|
process.kill(pid, 0);
|
|
7703
7721
|
} catch (error) {
|
|
@@ -7713,6 +7731,9 @@ function createSkillStore(home) {
|
|
|
7713
7731
|
try {
|
|
7714
7732
|
if (!roots()) throw new ManagerError("conflict");
|
|
7715
7733
|
return operation();
|
|
7734
|
+
} catch (error) {
|
|
7735
|
+
if (["EBUSY", "EACCES", "EPERM"].includes(error.code)) throw new ManagerError("conflict");
|
|
7736
|
+
throw error;
|
|
7716
7737
|
} finally {
|
|
7717
7738
|
release();
|
|
7718
7739
|
}
|
|
@@ -7732,13 +7753,13 @@ function createSkillStore(home) {
|
|
|
7732
7753
|
if (!activation) return false;
|
|
7733
7754
|
if (!stats?.isSymbolicLink() || activation.link !== null && identity(stats) !== activation.link) return false;
|
|
7734
7755
|
const anchor = anchorPath(activation);
|
|
7735
|
-
return
|
|
7756
|
+
return linkTargets(paths.active, anchor) && stat(anchor)?.isSymbolicLink() === true && linkTargets(anchor, paths.bundle);
|
|
7736
7757
|
}
|
|
7737
7758
|
function discardAnchor(paths, activation) {
|
|
7738
7759
|
if (!activation) return;
|
|
7739
7760
|
const anchor = anchorPath(activation);
|
|
7740
7761
|
const stats = stat(anchor);
|
|
7741
|
-
if (stats?.isSymbolicLink() &&
|
|
7762
|
+
if (stats?.isSymbolicLink() && linkTargets(anchor, paths.bundle)) unlinkOwned(anchor, identity(stats));
|
|
7742
7763
|
}
|
|
7743
7764
|
function lookup(name, id) {
|
|
7744
7765
|
const paths = pathsFor(name);
|
|
@@ -7804,7 +7825,7 @@ function createSkillStore(home) {
|
|
|
7804
7825
|
assertFree(skills, [name, `${name}.md`]);
|
|
7805
7826
|
const activation = { anchor: randomUUID(), link: null };
|
|
7806
7827
|
const anchor = anchorPath(activation);
|
|
7807
|
-
|
|
7828
|
+
directoryLink(paths.bundle, anchor);
|
|
7808
7829
|
let prepared;
|
|
7809
7830
|
try {
|
|
7810
7831
|
prepared = writeRecord(paths.record, { ...record.data, activation }, record.token);
|
|
@@ -7814,7 +7835,7 @@ function createSkillStore(home) {
|
|
|
7814
7835
|
}
|
|
7815
7836
|
discardAnchor(paths, record.data.activation);
|
|
7816
7837
|
try {
|
|
7817
|
-
|
|
7838
|
+
directoryLink(anchor, paths.active);
|
|
7818
7839
|
} catch (error) {
|
|
7819
7840
|
if (error.code === "EEXIST") throw new ManagerError("conflict");
|
|
7820
7841
|
throw error;
|
|
@@ -7929,142 +7950,6 @@ function createSkillStore(home) {
|
|
|
7929
7950
|
return { install, list, read, remove, setEnabled };
|
|
7930
7951
|
}
|
|
7931
7952
|
|
|
7932
|
-
// src/shared-sync.js
|
|
7933
|
-
import { constants as constants3, watchFile, unwatchFile } from "node:fs";
|
|
7934
|
-
import { lstat as lstat2, open as open2, readdir, realpath as realpath2 } from "node:fs/promises";
|
|
7935
|
-
import { createConnection } from "node:net";
|
|
7936
|
-
import { dirname as dirname2, join as join3, resolve } from "node:path";
|
|
7937
|
-
var syncDirectory = (home) => join3(home, "skill-manager", "shared-sync");
|
|
7938
|
-
var syncSocket = (home) => join3(syncDirectory(home), "control.sock");
|
|
7939
|
-
var syncStateFile = (home) => join3(syncDirectory(home), "state.json");
|
|
7940
|
-
var MAX_STATE_BYTES = 2 * 1024 * 1024;
|
|
7941
|
-
var disabled = { modelInvocable: false, userInvocable: false };
|
|
7942
|
-
async function regularFile(path, limit) {
|
|
7943
|
-
var _stack = [];
|
|
7944
|
-
try {
|
|
7945
|
-
const before = await lstat2(path);
|
|
7946
|
-
if (!before.isFile() || before.size > limit) throw new ManagerError("invalid");
|
|
7947
|
-
const file = __using(_stack, await open2(path, constants3.O_RDONLY | (constants3.O_NOFOLLOW ?? 0) | (constants3.O_NONBLOCK ?? 0)), true);
|
|
7948
|
-
const stat2 = await file.stat();
|
|
7949
|
-
if (!stat2.isFile() || stat2.dev !== before.dev || stat2.ino !== before.ino || stat2.size > limit) {
|
|
7950
|
-
throw new ManagerError("conflict");
|
|
7951
|
-
}
|
|
7952
|
-
const bytes = Buffer.alloc(stat2.size + 1);
|
|
7953
|
-
const { bytesRead } = await file.read(bytes);
|
|
7954
|
-
if (bytesRead !== stat2.size) throw new ManagerError("conflict");
|
|
7955
|
-
return bytes.subarray(0, bytesRead);
|
|
7956
|
-
} catch (_) {
|
|
7957
|
-
var _error = _, _hasError = true;
|
|
7958
|
-
} finally {
|
|
7959
|
-
var _promise = __callDispose(_stack, _error, _hasError);
|
|
7960
|
-
_promise && await _promise;
|
|
7961
|
-
}
|
|
7962
|
-
}
|
|
7963
|
-
async function sharedSkills(agentsHome) {
|
|
7964
|
-
const root = await realpath2(join3(agentsHome, "skills")).catch((error) => {
|
|
7965
|
-
if (error.code !== "ENOENT") throw error;
|
|
7966
|
-
});
|
|
7967
|
-
const skills = /* @__PURE__ */ new Map();
|
|
7968
|
-
if (!root) return skills;
|
|
7969
|
-
for (const entry of await readdir(root, { withFileTypes: true })) {
|
|
7970
|
-
if (!entry.isDirectory() || !isManagedName(entry.name) || entry.name === "synced" || entry.name === "anthropic-skills") continue;
|
|
7971
|
-
const path = join3(root, entry.name, "SKILL.md");
|
|
7972
|
-
try {
|
|
7973
|
-
if (await realpath2(path) !== path) continue;
|
|
7974
|
-
const parsed = parseSkillFile(await regularFile(path, MAX_BUNDLE_BYTES));
|
|
7975
|
-
if (parsed.name === entry.name) skills.set(path, { path, name: parsed.name, description: parsed.description });
|
|
7976
|
-
} catch (error) {
|
|
7977
|
-
if (error.code !== "ENOENT" && error.code !== "invalid") throw error;
|
|
7978
|
-
}
|
|
7979
|
-
}
|
|
7980
|
-
return skills;
|
|
7981
|
-
}
|
|
7982
|
-
async function readSyncState(home) {
|
|
7983
|
-
try {
|
|
7984
|
-
const value = JSON.parse(await regularFile(syncStateFile(home), MAX_STATE_BYTES));
|
|
7985
|
-
if (value.version !== 1 || !isRecord(value.entries)) return null;
|
|
7986
|
-
return value;
|
|
7987
|
-
} catch (error) {
|
|
7988
|
-
if (error.code === "ENOENT") return null;
|
|
7989
|
-
throw new ManagerError("unavailable");
|
|
7990
|
-
}
|
|
7991
|
-
}
|
|
7992
|
-
function requestSync(home, message) {
|
|
7993
|
-
return new Promise((resolve3, reject) => {
|
|
7994
|
-
const socket = createConnection(syncSocket(home));
|
|
7995
|
-
let data = "";
|
|
7996
|
-
const fail = () => {
|
|
7997
|
-
socket.destroy();
|
|
7998
|
-
reject(new ManagerError("unavailable"));
|
|
7999
|
-
};
|
|
8000
|
-
socket.setEncoding("utf8");
|
|
8001
|
-
socket.setTimeout(9e3, fail);
|
|
8002
|
-
socket.on("error", fail);
|
|
8003
|
-
socket.on("connect", () => socket.write(JSON.stringify(message) + "\n"));
|
|
8004
|
-
socket.on("data", (chunk) => {
|
|
8005
|
-
data += chunk;
|
|
8006
|
-
if (data.length > 8192) return fail();
|
|
8007
|
-
if (!data.includes("\n")) return;
|
|
8008
|
-
socket.destroy();
|
|
8009
|
-
try {
|
|
8010
|
-
const reply = JSON.parse(data);
|
|
8011
|
-
if (!reply.ok) return reject(new ManagerError(reply.error === "conflict" ? "conflict" : "unavailable"));
|
|
8012
|
-
resolve3(reply.result);
|
|
8013
|
-
} catch {
|
|
8014
|
-
fail();
|
|
8015
|
-
}
|
|
8016
|
-
});
|
|
8017
|
-
socket.on("end", fail);
|
|
8018
|
-
});
|
|
8019
|
-
}
|
|
8020
|
-
function sharedPolicyEnabled(entry) {
|
|
8021
|
-
if (!entry) return void 0;
|
|
8022
|
-
if (typeof entry.policyEnabled === "boolean") return entry.policyEnabled;
|
|
8023
|
-
if (entry.pending && (entry.codex === false || entry.claude === "off")) return false;
|
|
8024
|
-
return entry.enabled;
|
|
8025
|
-
}
|
|
8026
|
-
function registerSharedPolicy(skills, home, agentsHome) {
|
|
8027
|
-
if (typeof skills.registerProvider !== "function") return () => {
|
|
8028
|
-
};
|
|
8029
|
-
let invalidate;
|
|
8030
|
-
const dispose = skills.registerProvider((control) => {
|
|
8031
|
-
invalidate = control.invalidate;
|
|
8032
|
-
return {
|
|
8033
|
-
name: "skill-manager-shared-policy",
|
|
8034
|
-
async list() {
|
|
8035
|
-
const state = await readSyncState(home);
|
|
8036
|
-
if (!state || !Object.values(state.entries).some((entry) => sharedPolicyEnabled(entry) === false)) return [];
|
|
8037
|
-
return [...(await sharedSkills(agentsHome)).values()].filter((skill) => sharedPolicyEnabled(state.entries[skill.path]) === false).map((skill) => ({
|
|
8038
|
-
...skill,
|
|
8039
|
-
provider: "skill-manager-shared-policy",
|
|
8040
|
-
source: "user-agents",
|
|
8041
|
-
// Immediately ahead of the default user-agents provider. Project and user-dsh win.
|
|
8042
|
-
rank: 499,
|
|
8043
|
-
invocation: disabled,
|
|
8044
|
-
locator: skill.path,
|
|
8045
|
-
resourceBase: { kind: "directory", path: dirname2(skill.path) }
|
|
8046
|
-
}));
|
|
8047
|
-
},
|
|
8048
|
-
async get(candidate) {
|
|
8049
|
-
try {
|
|
8050
|
-
if (await realpath2(candidate.path) !== candidate.path) return void 0;
|
|
8051
|
-
const { name, description, body } = parseSkillFile(await regularFile(candidate.path, MAX_BUNDLE_BYTES));
|
|
8052
|
-
return { ...candidate, name, description, content: body };
|
|
8053
|
-
} catch (error) {
|
|
8054
|
-
if (error.code === "ENOENT" || error.code === "invalid") return void 0;
|
|
8055
|
-
throw error;
|
|
8056
|
-
}
|
|
8057
|
-
}
|
|
8058
|
-
};
|
|
8059
|
-
});
|
|
8060
|
-
const path = syncStateFile(home);
|
|
8061
|
-
watchFile(path, { persistent: false, interval: 500 }, invalidate);
|
|
8062
|
-
return () => {
|
|
8063
|
-
unwatchFile(path, invalidate);
|
|
8064
|
-
dispose();
|
|
8065
|
-
};
|
|
8066
|
-
}
|
|
8067
|
-
|
|
8068
7953
|
// src/host.js
|
|
8069
7954
|
var ROUTE = "/api/skill-manager";
|
|
8070
7955
|
var MAX_REQUEST_BYTES = 12 * 1024 * 1024;
|
|
@@ -8105,7 +7990,7 @@ function parseId(id) {
|
|
|
8105
7990
|
function skillProvenance(skill, provenance) {
|
|
8106
7991
|
return skill.source === "bundled" ? { origin: "builtin" } : provenance(skill);
|
|
8107
7992
|
}
|
|
8108
|
-
function catalogRow(skill, provenance
|
|
7993
|
+
function catalogRow(skill, provenance) {
|
|
8109
7994
|
const origin = skillProvenance(skill, provenance)?.origin;
|
|
8110
7995
|
return {
|
|
8111
7996
|
id: `catalog:${skill.name}`,
|
|
@@ -8113,7 +7998,7 @@ function catalogRow(skill, provenance, shared) {
|
|
|
8113
7998
|
description: skill.description,
|
|
8114
7999
|
group: SOURCES.get(skill.source) ?? "local",
|
|
8115
8000
|
source: SOURCES.has(skill.source) ? skill.source : "other",
|
|
8116
|
-
enabled:
|
|
8001
|
+
enabled: null,
|
|
8117
8002
|
shadowed: false,
|
|
8118
8003
|
...origin && { origin }
|
|
8119
8004
|
};
|
|
@@ -8122,7 +8007,7 @@ function matchesManaged(skill, managed) {
|
|
|
8122
8007
|
if (!skill || skill.source !== "user-dsh" || typeof skill.path !== "string") return false;
|
|
8123
8008
|
if (!managed.enabled && !managed.activeMissing) return false;
|
|
8124
8009
|
const path = normalize(skill.path);
|
|
8125
|
-
return path === managed.resolvedFile || path ===
|
|
8010
|
+
return path === managed.resolvedFile || path === join3(managed.bundle, "SKILL.md") || path === join3(managed.active, "SKILL.md");
|
|
8126
8011
|
}
|
|
8127
8012
|
function createSkillManager({ home, agentsHome, skills, workspaceRegistry, agentPresets }) {
|
|
8128
8013
|
const store = createSkillStore(home);
|
|
@@ -8156,10 +8041,9 @@ function createSkillManager({ home, agentsHome, skills, workspaceRegistry, agent
|
|
|
8156
8041
|
}
|
|
8157
8042
|
async function list(options) {
|
|
8158
8043
|
const workspaces = workspaceRegistry.list().map(({ id, title }) => ({ id, title }));
|
|
8159
|
-
const [catalog, provenance
|
|
8044
|
+
const [catalog, provenance] = await Promise.all([
|
|
8160
8045
|
snapshot(options),
|
|
8161
|
-
readProvenance(agentsHome)
|
|
8162
|
-
readSyncState(home).catch(() => ({ error: "unavailable" }))
|
|
8046
|
+
readProvenance(agentsHome)
|
|
8163
8047
|
]);
|
|
8164
8048
|
const managed = store.list();
|
|
8165
8049
|
const byName = new Map(catalog.skills.map((skill) => [skill.name, skill]));
|
|
@@ -8178,17 +8062,12 @@ function createSkillManager({ home, agentsHome, skills, workspaceRegistry, agent
|
|
|
8178
8062
|
shadowed: skill.enabled && Boolean(match) && !ownWinner
|
|
8179
8063
|
});
|
|
8180
8064
|
}
|
|
8181
|
-
rows.push(...[...byName.values()].map((skill) => catalogRow(skill, provenance
|
|
8065
|
+
rows.push(...[...byName.values()].map((skill) => catalogRow(skill, provenance)));
|
|
8182
8066
|
rows.sort((left, right) => left.name.localeCompare(right.name) || Number(right.group === "imported") - Number(left.group === "imported"));
|
|
8183
|
-
const syncError = sync?.error ?? Object.values(sync?.entries ?? {}).find((entry) => entry.error)?.error;
|
|
8184
|
-
const stopped = sync && !sync.error && (!Number.isFinite(Date.parse(sync.health?.lastAttemptAt)) || Date.now() - Date.parse(sync.health.lastAttemptAt) > 15e3);
|
|
8185
|
-
const healthError = stopped ? "sharedStopped" : sync?.health?.status === "unavailable" ? "sharedUnavailable" : null;
|
|
8186
8067
|
return {
|
|
8187
|
-
...sync?.health && { syncHealth: sync.health },
|
|
8188
8068
|
skills: rows,
|
|
8189
8069
|
workspaces,
|
|
8190
|
-
complete: catalog.complete && managed.complete
|
|
8191
|
-
...(healthError || syncError) && { syncError: healthError ?? (syncError === "conflict" ? "sharedConflict" : "sharedUnavailable") }
|
|
8070
|
+
complete: catalog.complete && managed.complete
|
|
8192
8071
|
};
|
|
8193
8072
|
}
|
|
8194
8073
|
async function detail(id, options) {
|
|
@@ -8235,17 +8114,6 @@ function createSkillManager({ home, agentsHome, skills, workspaceRegistry, agent
|
|
|
8235
8114
|
case "setEnabled":
|
|
8236
8115
|
case "remove": {
|
|
8237
8116
|
const { kind, name, installId } = parseId(payload.id);
|
|
8238
|
-
if (kind === "catalog" && action === "setEnabled") {
|
|
8239
|
-
if (typeof payload.enabled !== "boolean") throw new ManagerError("invalid");
|
|
8240
|
-
return scoped(options, async (scopedOptions) => {
|
|
8241
|
-
const skill = await load(name, scopedOptions);
|
|
8242
|
-
const sync = await readSyncState(home);
|
|
8243
|
-
if (skill?.source !== "user-agents" || !sync?.entries[skill.path]) throw new ManagerError("forbidden");
|
|
8244
|
-
signal?.throwIfAborted();
|
|
8245
|
-
await requestSync(home, { type: "set", path: skill.path, enabled: payload.enabled });
|
|
8246
|
-
return null;
|
|
8247
|
-
});
|
|
8248
|
-
}
|
|
8249
8117
|
if (kind !== "managed") throw new ManagerError("forbidden");
|
|
8250
8118
|
signal?.throwIfAborted();
|
|
8251
8119
|
if (action === "setEnabled") {
|
|
@@ -8323,8 +8191,8 @@ function createFetchHandler(manager) {
|
|
|
8323
8191
|
var inject = ["agentPresets", "connection", "skills", "workspaceRegistry"];
|
|
8324
8192
|
function resolveHome(configured, fallback) {
|
|
8325
8193
|
return configured?.trim() ? resolve2(
|
|
8326
|
-
configured === "~" ? homedir() : /^~[/\\]/.test(configured) ?
|
|
8327
|
-
) :
|
|
8194
|
+
configured === "~" ? homedir() : /^~[/\\]/.test(configured) ? join4(homedir(), configured.slice(2)) : configured
|
|
8195
|
+
) : join4(homedir(), fallback);
|
|
8328
8196
|
}
|
|
8329
8197
|
function apply(ctx) {
|
|
8330
8198
|
const home = resolveHome(process.env.DSH_HOME, ".dsh");
|
|
@@ -8336,9 +8204,6 @@ function apply(ctx) {
|
|
|
8336
8204
|
workspaceRegistry: ctx.workspaceRegistry,
|
|
8337
8205
|
agentPresets: ctx.agentPresets
|
|
8338
8206
|
});
|
|
8339
|
-
if (typeof ctx.skills.registerProvider === "function") {
|
|
8340
|
-
ctx.effect(() => registerSharedPolicy(ctx.skills, home, agentsHome), "skill-manager: shared invocation policy");
|
|
8341
|
-
}
|
|
8342
8207
|
ctx.effect(() => ctx.connection.fetch.register({
|
|
8343
8208
|
path: ROUTE,
|
|
8344
8209
|
methods: ["POST"],
|