tokentracker-cli 0.42.0 → 0.43.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/dashboard/dist/assets/{ActivityHeatmap-CImSj13A.js → ActivityHeatmap-0IDxIr7i.js} +1 -1
- package/dashboard/dist/assets/{Card-DpjFlMHF.js → Card-hVCKEGdC.js} +1 -1
- package/dashboard/dist/assets/DashboardPage-C3QvJSI2.js +19 -0
- package/dashboard/dist/assets/{DevicePage-BIBlmBPl.js → DevicePage-UVSutySl.js} +1 -1
- package/dashboard/dist/assets/{DialogTitle-BMKzpys0.js → DialogTitle-D0S8c1BO.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-B5-cy4YN.js → FadeIn-anio8NgI.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-BM-sbvJG.js → HeaderGithubStar-wRjA_Fvu.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-C7-NXbFK.js → IpCheckPage-vl0aIVv0.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-ouOFlABY.js → LandingPage-CJO4q92s.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardAvatar-DMXPtCkx.js → LeaderboardAvatar-CjPVgiDo.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardPage-CC4CFYAe.js → LeaderboardPage-DiQn0DqL.js} +3 -3
- package/dashboard/dist/assets/{LeaderboardProfileModal-D-ncIr24.js → LeaderboardProfileModal-yovVjgnB.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardProfilePage-DtDMfVuQ.js → LeaderboardProfilePage-C0vqt4m5.js} +1 -1
- package/dashboard/dist/assets/{LimitsPage-qaD7Reti.js → LimitsPage-8o294HKr.js} +1 -1
- package/dashboard/dist/assets/{LocalOnlyNotice-BCy18fW4.js → LocalOnlyNotice-BQsPhRvE.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-BLPY2ZI-.js → LoginPage-D8hQf3xT.js} +1 -1
- package/dashboard/dist/assets/{PopoverPopup-CpIlpO0j.js → PopoverPopup-rhSAh0x6.js} +1 -1
- package/dashboard/dist/assets/{Select-BOxMknxo.js → Select-ByLRQqzg.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-B0SL3Wb5.js → SelectItemText-CHHy8UA1.js} +1 -1
- package/dashboard/dist/assets/{SettingsPage-De7GbtPf.js → SettingsPage-DGyuX3ua.js} +1 -1
- package/dashboard/dist/assets/SkillsPage-CMsfSZar.js +1 -0
- package/dashboard/dist/assets/{WidgetsPage-BaEYiZZH.js → WidgetsPage-DavgYHA1.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-BYhKuiTb.js → WrappedPage-BARpWxxH.js} +1 -1
- package/dashboard/dist/assets/{agent-logos-DSQgCNll.js → agent-logos-BsEMlmFc.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-Brwh94cN.js → arrow-up-right-Nh5NXsRd.js} +1 -1
- package/dashboard/dist/assets/{download-kFiot71R.js → download-Bz-ad2Zi.js} +1 -1
- package/dashboard/dist/assets/{info-CGx4zWQF.js → info-Edlzr0qR.js} +1 -1
- package/dashboard/dist/assets/main-BfK9LoKV.css +1 -0
- package/dashboard/dist/assets/{main-9Ze1SUIZ.js → main-CGYVeoRd.js} +2 -2
- package/dashboard/dist/assets/{use-limits-display-prefs-Ehri95OP.js → use-limits-display-prefs-Cc82ZSkQ.js} +1 -1
- package/dashboard/dist/assets/{use-native-settings-BWqG5RDr.js → use-native-settings-rTdpowec.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-2ZxmPfoP.js → use-usage-limits-DFjNciSe.js} +1 -1
- package/dashboard/dist/assets/{useCurrency-DQmyhw3i.js → useCurrency-BY5HnhWy.js} +1 -1
- package/dashboard/dist/assets/{useScrollLock-BcLeWqWK.js → useScrollLock-oNpe5Ufe.js} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dashboard/dist/share.html +2 -2
- package/package.json +1 -1
- package/src/lib/local-api.js +31 -10
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/skills-manager.js +134 -41
- package/dashboard/dist/assets/DashboardPage-CZkSRYE8.js +0 -19
- package/dashboard/dist/assets/SkillsPage-jpoYzUBQ.js +0 -1
- package/dashboard/dist/assets/main-DMJJVAOH.css +0 -1
|
@@ -174,13 +174,22 @@ function sanitizePathSegment(value) {
|
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
function sanitizeRelativePath(value) {
|
|
177
|
-
const
|
|
178
|
-
|
|
177
|
+
const input = String(value || "").trim();
|
|
178
|
+
const raw = input.replace(/\\/g, "/");
|
|
179
|
+
if (!raw || raw.includes("\0")) return null;
|
|
180
|
+
if (path.posix.isAbsolute(raw) || path.win32.isAbsolute(input) || path.win32.isAbsolute(raw)) return null;
|
|
179
181
|
const parts = raw.split("/").filter(Boolean);
|
|
180
|
-
if (!parts.length || parts.some((part) => part === "." || part === "..")) return null;
|
|
182
|
+
if (!parts.length || parts.some((part) => part === "." || part === ".." || part.includes(":"))) return null;
|
|
181
183
|
return parts.join("/");
|
|
182
184
|
}
|
|
183
185
|
|
|
186
|
+
function sanitizeLocalSkillPath(value) {
|
|
187
|
+
const safe = sanitizeRelativePath(value);
|
|
188
|
+
if (!safe) return null;
|
|
189
|
+
if (safe.split("/").some((part) => part.startsWith("."))) return null;
|
|
190
|
+
return safe;
|
|
191
|
+
}
|
|
192
|
+
|
|
184
193
|
function installNameFromDirectory(directory) {
|
|
185
194
|
const safe = sanitizeRelativePath(directory);
|
|
186
195
|
if (!safe) return null;
|
|
@@ -256,6 +265,36 @@ function findSkillMarker(dir) {
|
|
|
256
265
|
return null;
|
|
257
266
|
}
|
|
258
267
|
|
|
268
|
+
const MAX_LOCAL_SKILL_SCAN_DEPTH = 3;
|
|
269
|
+
|
|
270
|
+
function scanSkillDirectories(rootDir) {
|
|
271
|
+
const found = [];
|
|
272
|
+
const walk = (dir, relDir = "", depth = 0) => {
|
|
273
|
+
let entries = [];
|
|
274
|
+
try {
|
|
275
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
276
|
+
} catch (_e) {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
280
|
+
for (const entry of entries) {
|
|
281
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
282
|
+
if (!entry.name || entry.name.startsWith(".")) continue;
|
|
283
|
+
const rel = relDir ? `${relDir}/${entry.name}` : entry.name;
|
|
284
|
+
const full = path.join(dir, entry.name);
|
|
285
|
+
if (findSkillMarker(full)) {
|
|
286
|
+
found.push(rel);
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
// Direct symlinked skills are accepted above, but symlinked group folders
|
|
290
|
+
// are not traversed so the scan stays within the target skills tree.
|
|
291
|
+
if (entry.isDirectory() && depth + 1 < MAX_LOCAL_SKILL_SCAN_DEPTH) walk(full, rel, depth + 1);
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
walk(rootDir);
|
|
295
|
+
return found;
|
|
296
|
+
}
|
|
297
|
+
|
|
259
298
|
const HASH_IGNORE = new Set([".git", ".DS_Store", "Thumbs.db", ".gitignore"]);
|
|
260
299
|
|
|
261
300
|
// Stable content fingerprint of a skill directory: walk files in sorted order,
|
|
@@ -531,19 +570,67 @@ function isSymlink(targetPath) {
|
|
|
531
570
|
}
|
|
532
571
|
}
|
|
533
572
|
|
|
573
|
+
function targetSkillPath(baseDir, directory) {
|
|
574
|
+
const safe = sanitizeRelativePath(directory);
|
|
575
|
+
if (!safe) return null;
|
|
576
|
+
const root = path.resolve(baseDir);
|
|
577
|
+
const targetPath = path.resolve(root, safe);
|
|
578
|
+
if (!pathStrictlyWithin(root, targetPath)) return null;
|
|
579
|
+
try {
|
|
580
|
+
const rootStat = fs.lstatSync(root);
|
|
581
|
+
if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) return null;
|
|
582
|
+
} catch (e) {
|
|
583
|
+
if (e?.code !== "ENOENT") return null;
|
|
584
|
+
}
|
|
585
|
+
const parts = safe.split("/");
|
|
586
|
+
let current = root;
|
|
587
|
+
for (let i = 0; i < parts.length - 1; i += 1) {
|
|
588
|
+
current = path.join(current, parts[i]);
|
|
589
|
+
let stat;
|
|
590
|
+
try {
|
|
591
|
+
stat = fs.lstatSync(current);
|
|
592
|
+
} catch (e) {
|
|
593
|
+
if (e?.code === "ENOENT") continue;
|
|
594
|
+
return null;
|
|
595
|
+
}
|
|
596
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) return null;
|
|
597
|
+
}
|
|
598
|
+
return targetPath;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function managedSkillPath(directory) {
|
|
602
|
+
const skillPath = targetSkillPath(ssotDir(), directory);
|
|
603
|
+
if (!skillPath) throw new Error(`Invalid skill directory: ${directory}`);
|
|
604
|
+
return skillPath;
|
|
605
|
+
}
|
|
606
|
+
|
|
534
607
|
function copyDir(source, dest) {
|
|
535
608
|
assertNotNested(source, dest);
|
|
536
609
|
removePath(dest);
|
|
537
610
|
fs.cpSync(source, dest, { recursive: true, force: true });
|
|
538
611
|
}
|
|
539
612
|
|
|
613
|
+
function removeEmptyAncestors(startDir, stopDir) {
|
|
614
|
+
let current = path.resolve(startDir);
|
|
615
|
+
const stop = path.resolve(stopDir);
|
|
616
|
+
while (pathStrictlyWithin(stop, current)) {
|
|
617
|
+
try {
|
|
618
|
+
fs.rmdirSync(current);
|
|
619
|
+
} catch (_e) {
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
current = path.dirname(current);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
540
626
|
function syncSkillToTarget(directory, targetId) {
|
|
541
627
|
const target = TARGETS[targetId];
|
|
542
628
|
if (!target) throw new Error(`Unsupported target: ${targetId}`);
|
|
543
|
-
const source =
|
|
629
|
+
const source = managedSkillPath(directory);
|
|
544
630
|
if (!fs.existsSync(source)) throw new Error(`Managed skill not found: ${directory}`);
|
|
545
631
|
for (const baseDir of targetDirs(target)) {
|
|
546
|
-
const dest =
|
|
632
|
+
const dest = targetSkillPath(baseDir, directory);
|
|
633
|
+
if (!dest) throw new Error(`Invalid skill directory: ${directory}`);
|
|
547
634
|
assertNotNested(source, dest);
|
|
548
635
|
ensureDir(path.dirname(dest));
|
|
549
636
|
removePath(dest);
|
|
@@ -559,7 +646,10 @@ function removeSkillFromTarget(directory, targetId) {
|
|
|
559
646
|
const target = TARGETS[targetId];
|
|
560
647
|
if (!target) return;
|
|
561
648
|
for (const baseDir of targetDirs(target)) {
|
|
562
|
-
|
|
649
|
+
const targetPath = targetSkillPath(baseDir, directory);
|
|
650
|
+
if (!targetPath) continue;
|
|
651
|
+
removePath(targetPath);
|
|
652
|
+
removeEmptyAncestors(path.dirname(targetPath), baseDir);
|
|
563
653
|
}
|
|
564
654
|
}
|
|
565
655
|
|
|
@@ -567,7 +657,8 @@ function scanTargetSkill(directory, targetId) {
|
|
|
567
657
|
const target = TARGETS[targetId];
|
|
568
658
|
if (!target) return false;
|
|
569
659
|
for (const baseDir of targetDirs(target)) {
|
|
570
|
-
const candidate =
|
|
660
|
+
const candidate = targetSkillPath(baseDir, directory);
|
|
661
|
+
if (!candidate) continue;
|
|
571
662
|
if (fs.existsSync(candidate) || isSymlink(candidate)) return true;
|
|
572
663
|
}
|
|
573
664
|
return false;
|
|
@@ -582,7 +673,8 @@ function classifyTargetSkill(directory, targetId) {
|
|
|
582
673
|
if (!target) return "off";
|
|
583
674
|
let state = "off";
|
|
584
675
|
for (const baseDir of targetDirs(target)) {
|
|
585
|
-
const candidate =
|
|
676
|
+
const candidate = targetSkillPath(baseDir, directory);
|
|
677
|
+
if (!candidate) continue;
|
|
586
678
|
if (fs.existsSync(candidate)) return "synced";
|
|
587
679
|
if (isSymlink(candidate)) state = "orphan";
|
|
588
680
|
}
|
|
@@ -612,19 +704,11 @@ function listInstalledSkills() {
|
|
|
612
704
|
const unmanaged = new Map();
|
|
613
705
|
for (const target of Object.values(TARGETS)) {
|
|
614
706
|
for (const dir of targetDirs(target)) {
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
618
|
-
} catch (_e) {
|
|
619
|
-
continue;
|
|
620
|
-
}
|
|
621
|
-
for (const entry of entries) {
|
|
622
|
-
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
623
|
-
const directory = entry.name;
|
|
624
|
-
if (!directory || directory.startsWith(".") || managedDirs.has(directory.toLowerCase())) continue;
|
|
707
|
+
for (const directory of scanSkillDirectories(dir)) {
|
|
708
|
+
if (!directory || managedDirs.has(directory.toLowerCase())) continue;
|
|
625
709
|
const skillPath = findSkillMarker(path.join(dir, directory));
|
|
626
710
|
if (!skillPath) continue;
|
|
627
|
-
const metadata = readSkillMetadata(fs.readFileSync(skillPath, "utf8"), directory);
|
|
711
|
+
const metadata = readSkillMetadata(fs.readFileSync(skillPath, "utf8"), installNameFromDirectory(directory) || directory);
|
|
628
712
|
const key = directory.toLowerCase();
|
|
629
713
|
if (!unmanaged.has(key)) {
|
|
630
714
|
unmanaged.set(key, {
|
|
@@ -670,6 +754,8 @@ async function installSkill(skillInput, targetIds = ["claude", "codex"]) {
|
|
|
670
754
|
};
|
|
671
755
|
if (!skill.repoOwner || !skill.repoName) throw new Error("Missing GitHub repository information");
|
|
672
756
|
const sourceDir = sanitizeRelativePath(skill.directory);
|
|
757
|
+
// GitHub-sourced skills keep the historical flat install name even when
|
|
758
|
+
// sourceDirectory is nested; local nested-skill support uses importLocalSkill().
|
|
673
759
|
const installName = installNameFromDirectory(sourceDir);
|
|
674
760
|
if (!sourceDir || !installName) throw new Error("Invalid skill directory");
|
|
675
761
|
|
|
@@ -695,7 +781,7 @@ async function installSkill(skillInput, targetIds = ["claude", "codex"]) {
|
|
|
695
781
|
);
|
|
696
782
|
if (!files.some((entry) => /(^|\/)SKILL\.md$/i.test(entry.path))) throw new Error("SKILL.md not found in selected directory");
|
|
697
783
|
|
|
698
|
-
const dest =
|
|
784
|
+
const dest = managedSkillPath(installName);
|
|
699
785
|
const temp = path.join(dataDir(), "tmp", `${installName}-${Date.now()}`);
|
|
700
786
|
removePath(temp);
|
|
701
787
|
ensureDir(temp);
|
|
@@ -751,16 +837,18 @@ function uninstallSkill(id) {
|
|
|
751
837
|
const registry = readRegistry();
|
|
752
838
|
const skill = registry.skills.find((entry) => entry.id === id || entry.key === id);
|
|
753
839
|
if (!skill) throw new Error("Managed skill not found");
|
|
840
|
+
const ssotPath = managedSkillPath(skill.directory);
|
|
754
841
|
for (const targetId of Object.keys(TARGETS)) removeSkillFromTarget(skill.directory, targetId);
|
|
755
842
|
// Move SSOT copy into a trash bucket so it can be restored briefly. The
|
|
756
843
|
// registry entry is retained but flagged so restoreSkill can re-link it.
|
|
757
|
-
const ssotPath = path.join(ssotDir(), skill.directory);
|
|
758
844
|
if (fs.existsSync(ssotPath)) {
|
|
759
845
|
ensureDir(trashDir());
|
|
760
846
|
const stamp = Date.now();
|
|
761
|
-
const
|
|
847
|
+
const trashName = `${Buffer.from(String(skill.directory || ""), "utf8").toString("base64url")}-${stamp}`;
|
|
848
|
+
const trashPath = path.join(trashDir(), trashName);
|
|
762
849
|
try {
|
|
763
850
|
fs.renameSync(ssotPath, trashPath);
|
|
851
|
+
removeEmptyAncestors(path.dirname(ssotPath), ssotDir());
|
|
764
852
|
skill.trashedAt = stamp;
|
|
765
853
|
skill.trashedDirectory = path.basename(trashPath);
|
|
766
854
|
skill.previousTargets = skill.targets || [];
|
|
@@ -773,6 +861,7 @@ function uninstallSkill(id) {
|
|
|
773
861
|
return { ok: true, trashed: true, restoreId: skill.id, ttlMs: TRASH_TTL_MS };
|
|
774
862
|
} catch (_e) {
|
|
775
863
|
removePath(ssotPath);
|
|
864
|
+
removeEmptyAncestors(path.dirname(ssotPath), ssotDir());
|
|
776
865
|
}
|
|
777
866
|
}
|
|
778
867
|
registry.skills = registry.skills.filter((entry) => entry.id !== skill.id);
|
|
@@ -808,7 +897,7 @@ function restoreSkill(id) {
|
|
|
808
897
|
throw new Error("Restore window expired");
|
|
809
898
|
}
|
|
810
899
|
const trashPath = path.join(trashDir(), skill.trashedDirectory || "");
|
|
811
|
-
const ssotPath =
|
|
900
|
+
const ssotPath = managedSkillPath(skill.directory);
|
|
812
901
|
if (!fs.existsSync(trashPath)) throw new Error("Trashed copy is missing");
|
|
813
902
|
ensureDir(path.dirname(ssotPath));
|
|
814
903
|
removePath(ssotPath);
|
|
@@ -840,11 +929,12 @@ function setSkillTargets(id, targetIds) {
|
|
|
840
929
|
}
|
|
841
930
|
|
|
842
931
|
function findLocalSkillSource(directory) {
|
|
843
|
-
const
|
|
844
|
-
if (!
|
|
932
|
+
const sourceDir = sanitizeLocalSkillPath(directory);
|
|
933
|
+
if (!sourceDir) return null;
|
|
845
934
|
for (const target of Object.values(TARGETS)) {
|
|
846
935
|
for (const baseDir of targetDirs(target)) {
|
|
847
|
-
const skillPath =
|
|
936
|
+
const skillPath = targetSkillPath(baseDir, sourceDir);
|
|
937
|
+
if (!skillPath) continue;
|
|
848
938
|
if (findSkillMarker(skillPath)) {
|
|
849
939
|
return { path: skillPath, targetId: target.id };
|
|
850
940
|
}
|
|
@@ -854,33 +944,36 @@ function findLocalSkillSource(directory) {
|
|
|
854
944
|
}
|
|
855
945
|
|
|
856
946
|
function importLocalSkill(directory, targetIds = []) {
|
|
857
|
-
const
|
|
858
|
-
if (!
|
|
947
|
+
const sourceDir = sanitizeLocalSkillPath(directory);
|
|
948
|
+
if (!sourceDir) throw new Error("Invalid skill directory");
|
|
859
949
|
const registry = readRegistry();
|
|
860
|
-
const existing = registry.skills.find((entry) => entry.directory.toLowerCase() ===
|
|
950
|
+
const existing = registry.skills.find((entry) => String(entry.directory || "").toLowerCase() === sourceDir.toLowerCase());
|
|
861
951
|
if (existing) {
|
|
952
|
+
if (!String(existing.id || existing.key || "").startsWith("local:")) {
|
|
953
|
+
throw new Error(`Skill directory "${sourceDir}" is already managed by another installed skill`);
|
|
954
|
+
}
|
|
862
955
|
if (!targetIds || !targetIds.length) {
|
|
863
956
|
return { ...existing, managed: true, targets: existing.targets || [] };
|
|
864
957
|
}
|
|
865
958
|
return setSkillTargets(existing.id, targetIds);
|
|
866
959
|
}
|
|
867
960
|
|
|
868
|
-
const source = findLocalSkillSource(
|
|
961
|
+
const source = findLocalSkillSource(sourceDir);
|
|
869
962
|
if (!source) throw new Error("Local skill not found");
|
|
870
963
|
|
|
871
|
-
const dest =
|
|
964
|
+
const dest = managedSkillPath(sourceDir);
|
|
872
965
|
copyDir(source.path, dest);
|
|
873
966
|
const skillMarker = findSkillMarker(dest);
|
|
874
|
-
const metadata = readSkillMetadata(skillMarker ? fs.readFileSync(skillMarker, "utf8") : "",
|
|
875
|
-
const discoveredTargets = Object.keys(TARGETS).filter((targetId) => scanTargetSkill(
|
|
967
|
+
const metadata = readSkillMetadata(skillMarker ? fs.readFileSync(skillMarker, "utf8") : "", installNameFromDirectory(sourceDir));
|
|
968
|
+
const discoveredTargets = Object.keys(TARGETS).filter((targetId) => scanTargetSkill(sourceDir, targetId));
|
|
876
969
|
const selectedTargets = (targetIds.length ? targetIds : discoveredTargets).filter((targetId) => TARGETS[targetId]);
|
|
877
970
|
const skill = {
|
|
878
|
-
id: `local:${
|
|
879
|
-
key: `local:${
|
|
971
|
+
id: `local:${sourceDir}`,
|
|
972
|
+
key: `local:${sourceDir}`,
|
|
880
973
|
name: metadata.name,
|
|
881
974
|
description: metadata.description,
|
|
882
|
-
directory:
|
|
883
|
-
sourceDirectory:
|
|
975
|
+
directory: sourceDir,
|
|
976
|
+
sourceDirectory: sourceDir,
|
|
884
977
|
readmeUrl: null,
|
|
885
978
|
repoOwner: null,
|
|
886
979
|
repoName: null,
|
|
@@ -893,15 +986,15 @@ function importLocalSkill(directory, targetIds = []) {
|
|
|
893
986
|
registry.skills.push(skill);
|
|
894
987
|
saveRegistry(registry);
|
|
895
988
|
for (const targetId of Object.keys(TARGETS)) {
|
|
896
|
-
if (selectedTargets.includes(targetId)) syncSkillToTarget(
|
|
897
|
-
else removeSkillFromTarget(
|
|
989
|
+
if (selectedTargets.includes(targetId)) syncSkillToTarget(sourceDir, targetId);
|
|
990
|
+
else removeSkillFromTarget(sourceDir, targetId);
|
|
898
991
|
}
|
|
899
|
-
appendActivity({ action: "import", name: skill.name, directory:
|
|
992
|
+
appendActivity({ action: "import", name: skill.name, directory: sourceDir, targets: selectedTargets });
|
|
900
993
|
return { ...skill, managed: true, targets: selectedTargets };
|
|
901
994
|
}
|
|
902
995
|
|
|
903
996
|
function deleteLocalSkill(directory, targetIds = []) {
|
|
904
|
-
const installName =
|
|
997
|
+
const installName = sanitizeLocalSkillPath(directory);
|
|
905
998
|
if (!installName) throw new Error("Invalid skill directory");
|
|
906
999
|
const selectedTargets = targetIds.length ? targetIds : Object.keys(TARGETS);
|
|
907
1000
|
for (const targetId of selectedTargets) removeSkillFromTarget(installName, targetId);
|