pi-agent-toolkit 0.1.2 → 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/dist/index.js +375 -229
- package/package.json +5 -3
package/dist/index.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
5
|
+
import { dirname as dirname3, resolve as resolve5 } from "path";
|
|
6
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4
7
|
import { defineCommand, runMain } from "citty";
|
|
5
8
|
|
|
6
9
|
// src/commands/install.ts
|
|
@@ -185,8 +188,7 @@ var extensions = [
|
|
|
185
188
|
group: "tools",
|
|
186
189
|
description: "Intercepts pip/python calls and redirects to uv",
|
|
187
190
|
method: "copy",
|
|
188
|
-
source: "extensions/uv.ts"
|
|
189
|
-
recommends: ["intercepted-commands"]
|
|
191
|
+
source: "extensions/uv.ts"
|
|
190
192
|
},
|
|
191
193
|
{
|
|
192
194
|
name: "execute-command",
|
|
@@ -541,13 +543,15 @@ function getByCategory(category) {
|
|
|
541
543
|
return registry.filter((c) => c.category === category);
|
|
542
544
|
}
|
|
543
545
|
function getExtensionGroups() {
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
546
|
+
return getByCategory("extensions").reduce(
|
|
547
|
+
(acc, ext) => {
|
|
548
|
+
const key = ext.group ?? "tools";
|
|
549
|
+
if (!acc[key]) acc[key] = [];
|
|
550
|
+
acc[key].push(ext);
|
|
551
|
+
return acc;
|
|
552
|
+
},
|
|
553
|
+
{}
|
|
554
|
+
);
|
|
551
555
|
}
|
|
552
556
|
var GROUP_LABELS = {
|
|
553
557
|
safety: "Safety",
|
|
@@ -561,19 +565,21 @@ var GROUP_LABELS = {
|
|
|
561
565
|
|
|
562
566
|
// src/lib/warnings.ts
|
|
563
567
|
import * as p from "@clack/prompts";
|
|
564
|
-
|
|
568
|
+
function findMissingRecommendations(selected) {
|
|
565
569
|
const selectedNames = new Set(selected.map((c) => c.name));
|
|
566
570
|
const warnings = [];
|
|
567
571
|
for (const component of selected) {
|
|
568
572
|
if (!component.recommends) continue;
|
|
569
573
|
for (const rec of component.recommends) {
|
|
570
574
|
if (!selectedNames.has(rec)) {
|
|
571
|
-
warnings.push(
|
|
572
|
-
`${component.name} works best with ${rec}, which you didn't select.`
|
|
573
|
-
);
|
|
575
|
+
warnings.push(`${component.name} works best with ${rec}, which you didn't select.`);
|
|
574
576
|
}
|
|
575
577
|
}
|
|
576
578
|
}
|
|
579
|
+
return warnings;
|
|
580
|
+
}
|
|
581
|
+
async function checkRecommendations(selected) {
|
|
582
|
+
const warnings = findMissingRecommendations(selected);
|
|
577
583
|
if (warnings.length === 0) return true;
|
|
578
584
|
p.log.warn("Some selected components have recommendations:");
|
|
579
585
|
for (const warning of warnings) {
|
|
@@ -594,12 +600,11 @@ async function checkRecommendations(selected) {
|
|
|
594
600
|
import {
|
|
595
601
|
cpSync,
|
|
596
602
|
existsSync as existsSync2,
|
|
597
|
-
mkdirSync as mkdirSync2,
|
|
598
|
-
symlinkSync,
|
|
599
603
|
lstatSync,
|
|
600
|
-
|
|
604
|
+
mkdirSync as mkdirSync2,
|
|
601
605
|
readdirSync,
|
|
602
606
|
statSync,
|
|
607
|
+
symlinkSync,
|
|
603
608
|
unlinkSync
|
|
604
609
|
} from "fs";
|
|
605
610
|
import { resolve as resolve2, basename } from "path";
|
|
@@ -635,51 +640,64 @@ function emptyManifest() {
|
|
|
635
640
|
updatedAt: ""
|
|
636
641
|
};
|
|
637
642
|
}
|
|
638
|
-
function readManifest() {
|
|
639
|
-
if (!existsSync(
|
|
643
|
+
function readManifest(path = MANIFEST_PATH) {
|
|
644
|
+
if (!existsSync(path)) return emptyManifest();
|
|
640
645
|
try {
|
|
641
|
-
const raw = readFileSync(
|
|
642
|
-
|
|
646
|
+
const raw = readFileSync(path, "utf-8");
|
|
647
|
+
const parsed = JSON.parse(raw);
|
|
648
|
+
const defaults = emptyManifest();
|
|
649
|
+
const installed = parsed.installed ?? {};
|
|
650
|
+
return {
|
|
651
|
+
version: parsed.version ?? defaults.version,
|
|
652
|
+
installed: {
|
|
653
|
+
extensions: installed.extensions ?? defaults.installed.extensions,
|
|
654
|
+
skills: {
|
|
655
|
+
bundled: installed.skills?.bundled ?? defaults.installed.skills.bundled,
|
|
656
|
+
external: installed.skills?.external ?? defaults.installed.skills.external
|
|
657
|
+
},
|
|
658
|
+
packages: installed.packages ?? defaults.installed.packages,
|
|
659
|
+
configs: installed.configs ?? defaults.installed.configs
|
|
660
|
+
},
|
|
661
|
+
installedAt: parsed.installedAt ?? defaults.installedAt,
|
|
662
|
+
updatedAt: parsed.updatedAt ?? defaults.updatedAt
|
|
663
|
+
};
|
|
643
664
|
} catch {
|
|
644
665
|
return emptyManifest();
|
|
645
666
|
}
|
|
646
667
|
}
|
|
647
|
-
function writeManifest(manifest) {
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
writeFileSync(MANIFEST_PATH, JSON.stringify(manifest, null, 2) + "\n");
|
|
668
|
+
function writeManifest(manifest, path = MANIFEST_PATH) {
|
|
669
|
+
mkdirSync(dirname2(path), { recursive: true });
|
|
670
|
+
writeFileSync(path, JSON.stringify(manifest, null, 2) + "\n");
|
|
651
671
|
}
|
|
652
|
-
function
|
|
653
|
-
|
|
672
|
+
function getList(manifest, category) {
|
|
673
|
+
switch (category) {
|
|
674
|
+
case "extensions":
|
|
675
|
+
return manifest.installed.extensions;
|
|
676
|
+
case "skills-bundled":
|
|
677
|
+
return manifest.installed.skills.bundled;
|
|
678
|
+
case "skills-external":
|
|
679
|
+
return manifest.installed.skills.external;
|
|
680
|
+
case "packages":
|
|
681
|
+
return manifest.installed.packages;
|
|
682
|
+
case "configs":
|
|
683
|
+
return manifest.installed.configs;
|
|
684
|
+
default:
|
|
685
|
+
return null;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
function recordInstall(names, category, cliVersion, path = MANIFEST_PATH) {
|
|
689
|
+
const manifest = readManifest(path);
|
|
654
690
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
655
691
|
if (!manifest.installedAt) manifest.installedAt = now;
|
|
656
692
|
manifest.updatedAt = now;
|
|
657
693
|
manifest.version = cliVersion;
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
manifest.installed.extensions.push(name);
|
|
663
|
-
break;
|
|
664
|
-
case "skills-bundled":
|
|
665
|
-
if (!manifest.installed.skills.bundled.includes(name))
|
|
666
|
-
manifest.installed.skills.bundled.push(name);
|
|
667
|
-
break;
|
|
668
|
-
case "skills-external":
|
|
669
|
-
if (!manifest.installed.skills.external.includes(name))
|
|
670
|
-
manifest.installed.skills.external.push(name);
|
|
671
|
-
break;
|
|
672
|
-
case "packages":
|
|
673
|
-
if (!manifest.installed.packages.includes(name))
|
|
674
|
-
manifest.installed.packages.push(name);
|
|
675
|
-
break;
|
|
676
|
-
case "configs":
|
|
677
|
-
if (!manifest.installed.configs.includes(name))
|
|
678
|
-
manifest.installed.configs.push(name);
|
|
679
|
-
break;
|
|
694
|
+
const list2 = getList(manifest, category);
|
|
695
|
+
if (list2) {
|
|
696
|
+
for (const name of names) {
|
|
697
|
+
if (!list2.includes(name)) list2.push(name);
|
|
680
698
|
}
|
|
681
699
|
}
|
|
682
|
-
writeManifest(manifest);
|
|
700
|
+
writeManifest(manifest, path);
|
|
683
701
|
}
|
|
684
702
|
|
|
685
703
|
// src/lib/installer.ts
|
|
@@ -712,42 +730,24 @@ function resolveTarget(component) {
|
|
|
712
730
|
return resolve2(PI_AGENT_DIR, component.name);
|
|
713
731
|
}
|
|
714
732
|
}
|
|
715
|
-
function copyComponent(source, target
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
if (isDirectory) {
|
|
719
|
-
cpSync(source, target, { recursive: true });
|
|
720
|
-
} else {
|
|
721
|
-
cpSync(source, target);
|
|
722
|
-
}
|
|
733
|
+
function copyComponent(source, target) {
|
|
734
|
+
mkdirSync2(resolve2(target, ".."), { recursive: true });
|
|
735
|
+
cpSync(source, target, { recursive: true });
|
|
723
736
|
}
|
|
724
737
|
function linkComponent(source, target) {
|
|
725
738
|
const targetDir = resolve2(target, "..");
|
|
726
739
|
mkdirSync2(targetDir, { recursive: true });
|
|
727
|
-
if (
|
|
728
|
-
unlinkSync(target);
|
|
729
|
-
}
|
|
740
|
+
if (targetExists(target)) unlinkSync(target);
|
|
730
741
|
symlinkSync(source, target);
|
|
731
742
|
}
|
|
732
|
-
function
|
|
743
|
+
function targetExists(path) {
|
|
733
744
|
try {
|
|
734
745
|
lstatSync(path);
|
|
735
|
-
return
|
|
746
|
+
return true;
|
|
736
747
|
} catch {
|
|
737
748
|
return false;
|
|
738
749
|
}
|
|
739
750
|
}
|
|
740
|
-
function targetExists(target) {
|
|
741
|
-
if (isSymlink(target)) {
|
|
742
|
-
try {
|
|
743
|
-
readlinkSync(target);
|
|
744
|
-
return true;
|
|
745
|
-
} catch {
|
|
746
|
-
return false;
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
return existsSync2(target);
|
|
750
|
-
}
|
|
751
751
|
function installLocal(component, options) {
|
|
752
752
|
const source = resolveSource(component, options);
|
|
753
753
|
const target = resolveTarget(component);
|
|
@@ -762,7 +762,7 @@ function installLocal(component, options) {
|
|
|
762
762
|
linkComponent(source, target);
|
|
763
763
|
return { success: true, message: "linked" };
|
|
764
764
|
} else {
|
|
765
|
-
copyComponent(source, target
|
|
765
|
+
copyComponent(source, target);
|
|
766
766
|
return { success: true, message: "copied" };
|
|
767
767
|
}
|
|
768
768
|
} catch (err) {
|
|
@@ -810,10 +810,10 @@ async function installComponents(components, options) {
|
|
|
810
810
|
mkdirSync2(PI_EXTENSIONS_DIR, { recursive: true });
|
|
811
811
|
mkdirSync2(PI_SKILLS_DIR, { recursive: true });
|
|
812
812
|
mkdirSync2(AGENTS_SKILLS_DIR, { recursive: true });
|
|
813
|
-
const
|
|
813
|
+
const spinner3 = p2.spinner();
|
|
814
814
|
const results = [];
|
|
815
815
|
for (const component of components) {
|
|
816
|
-
|
|
816
|
+
spinner3.start(`Installing ${component.name}...`);
|
|
817
817
|
let result;
|
|
818
818
|
switch (component.method) {
|
|
819
819
|
case "copy":
|
|
@@ -831,9 +831,9 @@ async function installComponents(components, options) {
|
|
|
831
831
|
}
|
|
832
832
|
results.push({ name: component.name, ...result });
|
|
833
833
|
if (result.success) {
|
|
834
|
-
|
|
834
|
+
spinner3.stop(`${component.name}: ${result.message}`);
|
|
835
835
|
} else {
|
|
836
|
-
|
|
836
|
+
spinner3.stop(`${component.name}: FAILED - ${result.message}`);
|
|
837
837
|
}
|
|
838
838
|
}
|
|
839
839
|
const successByCategory = /* @__PURE__ */ new Map();
|
|
@@ -841,19 +841,17 @@ async function installComponents(components, options) {
|
|
|
841
841
|
if (!r.success) continue;
|
|
842
842
|
const component = components.find((c) => c.name === r.name);
|
|
843
843
|
if (!component) continue;
|
|
844
|
-
const
|
|
845
|
-
|
|
846
|
-
successByCategory.
|
|
844
|
+
const list2 = successByCategory.get(component.category) ?? [];
|
|
845
|
+
list2.push(r.name);
|
|
846
|
+
successByCategory.set(component.category, list2);
|
|
847
847
|
}
|
|
848
848
|
for (const [category, names] of successByCategory) {
|
|
849
849
|
recordInstall(names, category, options.cliVersion);
|
|
850
850
|
}
|
|
851
851
|
const succeeded = results.filter((r) => r.success).length;
|
|
852
|
-
const failed = results.
|
|
852
|
+
const failed = results.length - succeeded;
|
|
853
853
|
if (failed > 0) {
|
|
854
|
-
p2.log.warn(
|
|
855
|
-
`Installed ${succeeded}/${results.length} components. ${failed} failed:`
|
|
856
|
-
);
|
|
854
|
+
p2.log.warn(`Installed ${succeeded}/${results.length} components. ${failed} failed:`);
|
|
857
855
|
for (const r of results.filter((r2) => !r2.success)) {
|
|
858
856
|
p2.log.error(` ${r.name}: ${r.message}`);
|
|
859
857
|
}
|
|
@@ -868,130 +866,82 @@ function installExtensionDeps() {
|
|
|
868
866
|
for (const entry of entries) {
|
|
869
867
|
const fullPath = resolve2(extDir, entry);
|
|
870
868
|
try {
|
|
871
|
-
if (statSync(fullPath).isDirectory()
|
|
872
|
-
|
|
873
|
-
execSync("npm install --silent", { cwd: fullPath, stdio: "pipe" });
|
|
869
|
+
if (!statSync(fullPath).isDirectory() || !existsSync2(resolve2(fullPath, "package.json"))) {
|
|
870
|
+
continue;
|
|
874
871
|
}
|
|
875
872
|
} catch {
|
|
873
|
+
continue;
|
|
874
|
+
}
|
|
875
|
+
try {
|
|
876
|
+
p2.log.info(`Installing dependencies for ${entry}...`);
|
|
877
|
+
execSync("npm install --silent", { cwd: fullPath, stdio: "pipe" });
|
|
878
|
+
} catch (err) {
|
|
879
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
880
|
+
p2.log.error(`Failed to install dependencies for ${entry}: ${msg}`);
|
|
876
881
|
}
|
|
877
882
|
}
|
|
878
883
|
}
|
|
879
884
|
|
|
880
885
|
// src/commands/install.ts
|
|
881
|
-
function
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
const groups = getExtensionGroups();
|
|
891
|
-
const extensionOptions = [];
|
|
892
|
-
for (const group of Object.keys(GROUP_LABELS)) {
|
|
893
|
-
const components = groups[group];
|
|
894
|
-
if (!components?.length) continue;
|
|
895
|
-
extensionOptions.push({
|
|
896
|
-
value: `__separator_${group}`,
|
|
897
|
-
label: pc.dim(`--- ${GROUP_LABELS[group]} ---`),
|
|
898
|
-
hint: ""
|
|
899
|
-
});
|
|
900
|
-
extensionOptions.push(...componentOptions(components));
|
|
901
|
-
}
|
|
902
|
-
const extResult = await p3.multiselect({
|
|
903
|
-
message: "Select extensions to install:",
|
|
904
|
-
options: extensionOptions.filter((o) => !o.value.startsWith("__separator")),
|
|
886
|
+
async function selectComponents(message, components) {
|
|
887
|
+
if (components.length === 0) return [];
|
|
888
|
+
const result = await p3.multiselect({
|
|
889
|
+
message,
|
|
890
|
+
options: components.map((c) => ({
|
|
891
|
+
value: c.name,
|
|
892
|
+
label: c.name,
|
|
893
|
+
hint: c.description
|
|
894
|
+
})),
|
|
905
895
|
required: false
|
|
906
896
|
});
|
|
907
|
-
if (p3.isCancel(
|
|
897
|
+
if (p3.isCancel(result)) {
|
|
908
898
|
p3.cancel("Installation cancelled.");
|
|
909
899
|
process.exit(0);
|
|
910
900
|
}
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
if (p3.isCancel(skillResult)) {
|
|
920
|
-
p3.cancel("Installation cancelled.");
|
|
921
|
-
process.exit(0);
|
|
922
|
-
}
|
|
923
|
-
selected.push(...skillResult);
|
|
924
|
-
}
|
|
925
|
-
const externalSkills2 = getByCategory("skills-external");
|
|
926
|
-
if (externalSkills2.length > 0) {
|
|
927
|
-
const extSkillResult = await p3.multiselect({
|
|
901
|
+
return result;
|
|
902
|
+
}
|
|
903
|
+
async function interactivePicker() {
|
|
904
|
+
const selected = [];
|
|
905
|
+
const steps = [
|
|
906
|
+
{ message: "Select extensions to install:", components: getByCategory("extensions") },
|
|
907
|
+
{ message: "Select bundled skills to install:", components: getByCategory("skills-bundled") },
|
|
908
|
+
{
|
|
928
909
|
message: "Select external skills to install (fetched from source repos):",
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
}
|
|
936
|
-
selected.push(...extSkillResult);
|
|
937
|
-
}
|
|
938
|
-
const pkgs = getByCategory("packages");
|
|
939
|
-
if (pkgs.length > 0) {
|
|
940
|
-
const pkgResult = await p3.multiselect({
|
|
941
|
-
message: "Select pi packages to install:",
|
|
942
|
-
options: componentOptions(pkgs),
|
|
943
|
-
required: false
|
|
944
|
-
});
|
|
945
|
-
if (p3.isCancel(pkgResult)) {
|
|
946
|
-
p3.cancel("Installation cancelled.");
|
|
947
|
-
process.exit(0);
|
|
948
|
-
}
|
|
949
|
-
selected.push(...pkgResult);
|
|
950
|
-
}
|
|
951
|
-
const configs2 = getByCategory("configs");
|
|
952
|
-
if (configs2.length > 0) {
|
|
953
|
-
const configResult = await p3.multiselect({
|
|
954
|
-
message: "Select starter configs to install (copied as templates, won't overwrite existing):",
|
|
955
|
-
options: componentOptions(configs2),
|
|
956
|
-
required: false
|
|
957
|
-
});
|
|
958
|
-
if (p3.isCancel(configResult)) {
|
|
959
|
-
p3.cancel("Installation cancelled.");
|
|
960
|
-
process.exit(0);
|
|
910
|
+
components: getByCategory("skills-external")
|
|
911
|
+
},
|
|
912
|
+
{ message: "Select pi packages to install:", components: getByCategory("packages") },
|
|
913
|
+
{
|
|
914
|
+
message: "Select starter configs (copied as templates, won't overwrite existing):",
|
|
915
|
+
components: getByCategory("configs")
|
|
961
916
|
}
|
|
962
|
-
|
|
917
|
+
];
|
|
918
|
+
for (const step of steps) {
|
|
919
|
+
const names = await selectComponents(step.message, step.components);
|
|
920
|
+
selected.push(...names);
|
|
963
921
|
}
|
|
964
922
|
return registry.filter((c) => selected.includes(c.name));
|
|
965
923
|
}
|
|
966
924
|
function resolveFromFlags(args) {
|
|
967
|
-
const names = /* @__PURE__ */ new Set(
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
for (const name of args.skills) names.add(name);
|
|
973
|
-
}
|
|
974
|
-
if (args.packages) {
|
|
975
|
-
for (const name of args.packages) names.add(name);
|
|
976
|
-
}
|
|
925
|
+
const names = /* @__PURE__ */ new Set([
|
|
926
|
+
...args.extensions ?? [],
|
|
927
|
+
...args.skills ?? [],
|
|
928
|
+
...args.packages ?? []
|
|
929
|
+
]);
|
|
977
930
|
const resolved = [];
|
|
978
931
|
const notFound = [];
|
|
979
932
|
for (const name of names) {
|
|
980
933
|
const component = registry.find((c) => c.name === name);
|
|
981
|
-
if (component)
|
|
982
|
-
|
|
983
|
-
} else {
|
|
984
|
-
notFound.push(name);
|
|
985
|
-
}
|
|
934
|
+
if (component) resolved.push(component);
|
|
935
|
+
else notFound.push(name);
|
|
986
936
|
}
|
|
987
937
|
if (notFound.length > 0) {
|
|
988
938
|
p3.log.warn(`Unknown components: ${notFound.join(", ")}`);
|
|
989
|
-
p3.log.info('Run "pi-toolkit list" to see available components.');
|
|
939
|
+
p3.log.info('Run "pi-agent-toolkit list" to see available components.');
|
|
990
940
|
}
|
|
991
941
|
return resolved;
|
|
992
942
|
}
|
|
993
943
|
async function runInstall(args) {
|
|
994
|
-
p3.intro(pc.bold("pi-toolkit install"));
|
|
944
|
+
p3.intro(pc.bold("pi-agent-toolkit install"));
|
|
995
945
|
if (args.link && !args.repoPath) {
|
|
996
946
|
p3.log.error("--link requires --repo-path to be set.");
|
|
997
947
|
p3.log.info("Example: pi-toolkit install --link --repo-path ~/Code/pi-toolkit");
|
|
@@ -1011,18 +961,14 @@ async function runInstall(args) {
|
|
|
1011
961
|
p3.outro("Done.");
|
|
1012
962
|
return;
|
|
1013
963
|
}
|
|
1014
|
-
const
|
|
1015
|
-
|
|
1016
|
-
(c) => c.category
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
const
|
|
1021
|
-
|
|
1022
|
-
if (skillCount) parts.push(`${skillCount} skill${skillCount > 1 ? "s" : ""}`);
|
|
1023
|
-
if (pkgCount) parts.push(`${pkgCount} package${pkgCount > 1 ? "s" : ""}`);
|
|
1024
|
-
if (cfgCount) parts.push(`${cfgCount} config${cfgCount > 1 ? "s" : ""}`);
|
|
1025
|
-
p3.log.info(`Will install: ${parts.join(", ")}`);
|
|
964
|
+
const counts = [
|
|
965
|
+
["extension", components.filter((c) => c.category === "extensions").length],
|
|
966
|
+
["skill", components.filter((c) => c.category.startsWith("skills-")).length],
|
|
967
|
+
["package", components.filter((c) => c.category === "packages").length],
|
|
968
|
+
["config", components.filter((c) => c.category === "configs").length]
|
|
969
|
+
];
|
|
970
|
+
const summary = counts.filter(([, n]) => n > 0).map(([label, n]) => `${n} ${label}${n > 1 ? "s" : ""}`).join(", ");
|
|
971
|
+
p3.log.info(`Will install: ${summary}`);
|
|
1026
972
|
if (args.link) {
|
|
1027
973
|
p3.log.info(`Mode: symlink (repo: ${args.repoPath})`);
|
|
1028
974
|
}
|
|
@@ -1054,7 +1000,7 @@ async function runInstall(args) {
|
|
|
1054
1000
|
import pc2 from "picocolors";
|
|
1055
1001
|
function runList() {
|
|
1056
1002
|
console.log();
|
|
1057
|
-
console.log(pc2.bold("pi-toolkit: available components"));
|
|
1003
|
+
console.log(pc2.bold("pi-agent-toolkit: available components"));
|
|
1058
1004
|
console.log();
|
|
1059
1005
|
console.log(pc2.bold(pc2.cyan("Extensions")));
|
|
1060
1006
|
const groups = getExtensionGroups();
|
|
@@ -1103,7 +1049,7 @@ function runList() {
|
|
|
1103
1049
|
}
|
|
1104
1050
|
|
|
1105
1051
|
// src/commands/status.ts
|
|
1106
|
-
import { existsSync as existsSync3, lstatSync as lstatSync2, readlinkSync
|
|
1052
|
+
import { existsSync as existsSync3, lstatSync as lstatSync2, readlinkSync } from "fs";
|
|
1107
1053
|
import { resolve as resolve3, basename as basename2 } from "path";
|
|
1108
1054
|
import pc3 from "picocolors";
|
|
1109
1055
|
function expectedPath(component) {
|
|
@@ -1121,8 +1067,7 @@ function expectedPath(component) {
|
|
|
1121
1067
|
case "skills-external": {
|
|
1122
1068
|
const piPath = resolve3(PI_SKILLS_DIR, component.name);
|
|
1123
1069
|
const agentsPath = resolve3(AGENTS_SKILLS_DIR, component.name);
|
|
1124
|
-
if (existsSync3(piPath)
|
|
1125
|
-
if (existsSync3(agentsPath) || isSymlink2(agentsPath)) return agentsPath;
|
|
1070
|
+
if (existsSync3(piPath)) return piPath;
|
|
1126
1071
|
return agentsPath;
|
|
1127
1072
|
}
|
|
1128
1073
|
case "packages":
|
|
@@ -1136,44 +1081,36 @@ function expectedPath(component) {
|
|
|
1136
1081
|
return null;
|
|
1137
1082
|
}
|
|
1138
1083
|
}
|
|
1139
|
-
function
|
|
1084
|
+
function checkFile(path) {
|
|
1140
1085
|
try {
|
|
1141
|
-
|
|
1086
|
+
const stats = lstatSync2(path);
|
|
1087
|
+
if (stats.isSymbolicLink()) {
|
|
1088
|
+
const target = readlinkSync(path);
|
|
1089
|
+
const dangling = !existsSync3(path);
|
|
1090
|
+
return {
|
|
1091
|
+
exists: !dangling,
|
|
1092
|
+
detail: dangling ? `dangling symlink -> ${target}` : `symlink -> ${target}`
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
return { exists: true };
|
|
1142
1096
|
} catch {
|
|
1143
|
-
return false;
|
|
1097
|
+
return { exists: false };
|
|
1144
1098
|
}
|
|
1145
1099
|
}
|
|
1146
|
-
function checkFile(path) {
|
|
1147
|
-
if (isSymlink2(path)) {
|
|
1148
|
-
const target = readlinkSync2(path);
|
|
1149
|
-
const dangling = !existsSync3(path);
|
|
1150
|
-
return {
|
|
1151
|
-
exists: !dangling,
|
|
1152
|
-
detail: dangling ? `dangling symlink -> ${target}` : `symlink -> ${target}`
|
|
1153
|
-
};
|
|
1154
|
-
}
|
|
1155
|
-
return { exists: existsSync3(path) };
|
|
1156
|
-
}
|
|
1157
1100
|
function runStatus() {
|
|
1158
1101
|
const manifest = readManifest();
|
|
1159
1102
|
console.log();
|
|
1160
|
-
console.log(pc3.bold("pi-toolkit status"));
|
|
1103
|
+
console.log(pc3.bold("pi-agent-toolkit status"));
|
|
1161
1104
|
console.log();
|
|
1162
1105
|
if (!manifest.installedAt) {
|
|
1163
1106
|
console.log(pc3.dim("No pi-toolkit manifest found. Nothing has been installed yet."));
|
|
1164
|
-
console.log(pc3.dim('Run "pi-toolkit install" to get started.'));
|
|
1107
|
+
console.log(pc3.dim('Run "pi-agent-toolkit install" to get started.'));
|
|
1165
1108
|
console.log();
|
|
1166
1109
|
return;
|
|
1167
1110
|
}
|
|
1168
|
-
console.log(
|
|
1169
|
-
|
|
1170
|
-
);
|
|
1171
|
-
console.log(
|
|
1172
|
-
`${pc3.dim("Installed at:")} ${manifest.installedAt}`
|
|
1173
|
-
);
|
|
1174
|
-
console.log(
|
|
1175
|
-
`${pc3.dim("Updated at:")} ${manifest.updatedAt}`
|
|
1176
|
-
);
|
|
1111
|
+
console.log(`${pc3.dim("CLI version:")} ${manifest.version || "unknown"}`);
|
|
1112
|
+
console.log(`${pc3.dim("Installed at:")} ${manifest.installedAt}`);
|
|
1113
|
+
console.log(`${pc3.dim("Updated at:")} ${manifest.updatedAt}`);
|
|
1177
1114
|
console.log();
|
|
1178
1115
|
const installedNames = /* @__PURE__ */ new Set([
|
|
1179
1116
|
...manifest.installed.extensions,
|
|
@@ -1241,21 +1178,205 @@ function runStatus() {
|
|
|
1241
1178
|
}
|
|
1242
1179
|
const missing = entries.filter((e) => e.status === "missing");
|
|
1243
1180
|
if (missing.length > 0) {
|
|
1244
|
-
console.log(
|
|
1245
|
-
pc3.yellow(
|
|
1246
|
-
`${missing.length} component(s) in manifest but missing from disk:`
|
|
1247
|
-
)
|
|
1248
|
-
);
|
|
1181
|
+
console.log(pc3.yellow(`${missing.length} component(s) in manifest but missing from disk:`));
|
|
1249
1182
|
for (const m of missing) {
|
|
1250
1183
|
console.log(pc3.yellow(` - ${m.name}`));
|
|
1251
1184
|
}
|
|
1252
|
-
console.log(pc3.dim('Re-run "pi-toolkit install" to restore them.'));
|
|
1185
|
+
console.log(pc3.dim('Re-run "pi-agent-toolkit install" to restore them.'));
|
|
1253
1186
|
console.log();
|
|
1254
1187
|
}
|
|
1255
1188
|
}
|
|
1256
1189
|
|
|
1190
|
+
// src/commands/sync.ts
|
|
1191
|
+
import {
|
|
1192
|
+
cpSync as cpSync2,
|
|
1193
|
+
existsSync as existsSync4,
|
|
1194
|
+
lstatSync as lstatSync3,
|
|
1195
|
+
mkdirSync as mkdirSync3,
|
|
1196
|
+
readdirSync as readdirSync2,
|
|
1197
|
+
rmSync,
|
|
1198
|
+
statSync as statSync2,
|
|
1199
|
+
symlinkSync as symlinkSync2,
|
|
1200
|
+
unlinkSync as unlinkSync2
|
|
1201
|
+
} from "fs";
|
|
1202
|
+
import { resolve as resolve4 } from "path";
|
|
1203
|
+
import * as p4 from "@clack/prompts";
|
|
1204
|
+
import pc4 from "picocolors";
|
|
1205
|
+
function getExternalSkillNames() {
|
|
1206
|
+
return new Set(getByCategory("skills-external").map((c) => c.name));
|
|
1207
|
+
}
|
|
1208
|
+
function isSymlink(path) {
|
|
1209
|
+
try {
|
|
1210
|
+
return lstatSync3(path).isSymbolicLink();
|
|
1211
|
+
} catch {
|
|
1212
|
+
return false;
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
function findUnmanaged(scanDir, targetDir, category, skipNames) {
|
|
1216
|
+
if (!existsSync4(scanDir)) return [];
|
|
1217
|
+
const items = [];
|
|
1218
|
+
const entries = readdirSync2(scanDir);
|
|
1219
|
+
for (const entry of entries) {
|
|
1220
|
+
const fullPath = resolve4(scanDir, entry);
|
|
1221
|
+
if (isSymlink(fullPath)) continue;
|
|
1222
|
+
if (skipNames.has(entry) || skipNames.has(entry.replace(/\.ts$/, ""))) continue;
|
|
1223
|
+
if (entry === "node_modules" || entry.startsWith(".")) continue;
|
|
1224
|
+
let isDir;
|
|
1225
|
+
try {
|
|
1226
|
+
isDir = statSync2(fullPath).isDirectory();
|
|
1227
|
+
} catch {
|
|
1228
|
+
continue;
|
|
1229
|
+
}
|
|
1230
|
+
if (category === "extensions") {
|
|
1231
|
+
if (!isDir && !entry.endsWith(".ts")) continue;
|
|
1232
|
+
} else {
|
|
1233
|
+
if (!isDir) continue;
|
|
1234
|
+
}
|
|
1235
|
+
items.push({
|
|
1236
|
+
name: entry,
|
|
1237
|
+
sourcePath: fullPath,
|
|
1238
|
+
targetDir,
|
|
1239
|
+
category,
|
|
1240
|
+
isDirectory: isDir
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1243
|
+
return items;
|
|
1244
|
+
}
|
|
1245
|
+
function absorbItem(item) {
|
|
1246
|
+
const targetPath = resolve4(item.targetDir, item.name);
|
|
1247
|
+
try {
|
|
1248
|
+
mkdirSync3(item.targetDir, { recursive: true });
|
|
1249
|
+
if (item.isDirectory) {
|
|
1250
|
+
cpSync2(item.sourcePath, targetPath, { recursive: true });
|
|
1251
|
+
rmSync(item.sourcePath, { recursive: true, force: true });
|
|
1252
|
+
} else {
|
|
1253
|
+
cpSync2(item.sourcePath, targetPath);
|
|
1254
|
+
unlinkSync2(item.sourcePath);
|
|
1255
|
+
}
|
|
1256
|
+
symlinkSync2(targetPath, item.sourcePath);
|
|
1257
|
+
return { success: true, message: "absorbed and symlinked" };
|
|
1258
|
+
} catch (err) {
|
|
1259
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1260
|
+
return { success: false, message: msg };
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
async function runSync(options) {
|
|
1264
|
+
p4.intro(pc4.bold("pi-agent-toolkit sync"));
|
|
1265
|
+
const repoPath = resolve4(options.repoPath);
|
|
1266
|
+
const dotfilesPath = resolve4(repoPath, "dotfiles");
|
|
1267
|
+
if (!existsSync4(dotfilesPath)) {
|
|
1268
|
+
p4.log.error(`dotfiles/ not found at ${dotfilesPath}`);
|
|
1269
|
+
p4.log.info("Make sure --repo-path points to your pi-toolkit repo clone.");
|
|
1270
|
+
process.exit(1);
|
|
1271
|
+
}
|
|
1272
|
+
p4.log.info(`Repo: ${repoPath}`);
|
|
1273
|
+
p4.log.info("Scanning for unmanaged extensions and skills...");
|
|
1274
|
+
const externalSkills2 = getExternalSkillNames();
|
|
1275
|
+
const knownExtensions = /* @__PURE__ */ new Set();
|
|
1276
|
+
const extDir = resolve4(dotfilesPath, "extensions");
|
|
1277
|
+
if (existsSync4(extDir)) {
|
|
1278
|
+
for (const entry of readdirSync2(extDir)) {
|
|
1279
|
+
knownExtensions.add(entry);
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
const knownAgentSkills = /* @__PURE__ */ new Set();
|
|
1283
|
+
const agentSkillsDir = resolve4(dotfilesPath, "agent-skills");
|
|
1284
|
+
if (existsSync4(agentSkillsDir)) {
|
|
1285
|
+
for (const entry of readdirSync2(agentSkillsDir)) {
|
|
1286
|
+
knownAgentSkills.add(entry);
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
const knownGlobalSkills = /* @__PURE__ */ new Set();
|
|
1290
|
+
const globalSkillsDir = resolve4(dotfilesPath, "global-skills");
|
|
1291
|
+
if (existsSync4(globalSkillsDir)) {
|
|
1292
|
+
for (const entry of readdirSync2(globalSkillsDir)) {
|
|
1293
|
+
knownGlobalSkills.add(entry);
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
const found = [
|
|
1297
|
+
...findUnmanaged(
|
|
1298
|
+
PI_EXTENSIONS_DIR,
|
|
1299
|
+
resolve4(dotfilesPath, "extensions"),
|
|
1300
|
+
"extensions",
|
|
1301
|
+
knownExtensions
|
|
1302
|
+
),
|
|
1303
|
+
...findUnmanaged(
|
|
1304
|
+
PI_SKILLS_DIR,
|
|
1305
|
+
resolve4(dotfilesPath, "agent-skills"),
|
|
1306
|
+
"agent-skills",
|
|
1307
|
+
/* @__PURE__ */ new Set([...knownAgentSkills, ...externalSkills2])
|
|
1308
|
+
),
|
|
1309
|
+
...findUnmanaged(
|
|
1310
|
+
AGENTS_SKILLS_DIR,
|
|
1311
|
+
resolve4(dotfilesPath, "global-skills"),
|
|
1312
|
+
"global-skills",
|
|
1313
|
+
/* @__PURE__ */ new Set([...knownGlobalSkills, ...externalSkills2])
|
|
1314
|
+
)
|
|
1315
|
+
];
|
|
1316
|
+
if (found.length === 0) {
|
|
1317
|
+
p4.log.success("No unmanaged extensions or skills found. Everything is in sync.");
|
|
1318
|
+
p4.outro("Done.");
|
|
1319
|
+
return;
|
|
1320
|
+
}
|
|
1321
|
+
p4.log.info(`Found ${found.length} unmanaged item(s):`);
|
|
1322
|
+
for (const item of found) {
|
|
1323
|
+
const suffix = item.isDirectory ? "/" : "";
|
|
1324
|
+
p4.log.message(` ${pc4.yellow(item.name + suffix)} ${pc4.dim(`(${item.category})`)}`);
|
|
1325
|
+
}
|
|
1326
|
+
let toAbsorb;
|
|
1327
|
+
if (options.all) {
|
|
1328
|
+
toAbsorb = found;
|
|
1329
|
+
} else {
|
|
1330
|
+
const selected = await p4.multiselect({
|
|
1331
|
+
message: "Select items to absorb into the repo:",
|
|
1332
|
+
options: found.map((item) => ({
|
|
1333
|
+
value: item.name,
|
|
1334
|
+
label: item.name + (item.isDirectory ? "/" : ""),
|
|
1335
|
+
hint: `${item.category} -> dotfiles/${item.category}/${item.name}`
|
|
1336
|
+
})),
|
|
1337
|
+
required: false
|
|
1338
|
+
});
|
|
1339
|
+
if (p4.isCancel(selected)) {
|
|
1340
|
+
p4.cancel("Sync cancelled.");
|
|
1341
|
+
process.exit(0);
|
|
1342
|
+
}
|
|
1343
|
+
const selectedNames = new Set(selected);
|
|
1344
|
+
toAbsorb = found.filter((item) => selectedNames.has(item.name));
|
|
1345
|
+
}
|
|
1346
|
+
if (toAbsorb.length === 0) {
|
|
1347
|
+
p4.log.warn("Nothing selected to absorb.");
|
|
1348
|
+
p4.outro("Done.");
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
const spinner3 = p4.spinner();
|
|
1352
|
+
let succeeded = 0;
|
|
1353
|
+
let failed = 0;
|
|
1354
|
+
for (const item of toAbsorb) {
|
|
1355
|
+
spinner3.start(`Absorbing ${item.name}...`);
|
|
1356
|
+
const result = absorbItem(item);
|
|
1357
|
+
if (result.success) {
|
|
1358
|
+
spinner3.stop(`${item.name}: ${result.message}`);
|
|
1359
|
+
succeeded++;
|
|
1360
|
+
} else {
|
|
1361
|
+
spinner3.stop(`${item.name}: FAILED - ${result.message}`);
|
|
1362
|
+
failed++;
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
if (failed > 0) {
|
|
1366
|
+
p4.log.warn(`Absorbed ${succeeded}/${toAbsorb.length}. ${failed} failed.`);
|
|
1367
|
+
} else {
|
|
1368
|
+
p4.log.success(`All ${succeeded} item(s) absorbed into the repo.`);
|
|
1369
|
+
}
|
|
1370
|
+
p4.outro(
|
|
1371
|
+
pc4.green("Next steps: review the new files in dotfiles/, add to registry.ts, then commit.")
|
|
1372
|
+
);
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1257
1375
|
// src/index.ts
|
|
1258
|
-
var
|
|
1376
|
+
var __dirname2 = dirname3(fileURLToPath2(import.meta.url));
|
|
1377
|
+
var CLI_VERSION = JSON.parse(
|
|
1378
|
+
readFileSync2(resolve5(__dirname2, "..", "package.json"), "utf-8")
|
|
1379
|
+
).version;
|
|
1259
1380
|
var install = defineCommand({
|
|
1260
1381
|
meta: {
|
|
1261
1382
|
name: "install",
|
|
@@ -1329,6 +1450,30 @@ var status = defineCommand({
|
|
|
1329
1450
|
runStatus();
|
|
1330
1451
|
}
|
|
1331
1452
|
});
|
|
1453
|
+
var sync = defineCommand({
|
|
1454
|
+
meta: {
|
|
1455
|
+
name: "sync",
|
|
1456
|
+
description: "Absorb unmanaged extensions and skills from pi into the repo"
|
|
1457
|
+
},
|
|
1458
|
+
args: {
|
|
1459
|
+
"repo-path": {
|
|
1460
|
+
type: "string",
|
|
1461
|
+
description: "Path to local pi-toolkit repo clone",
|
|
1462
|
+
required: true
|
|
1463
|
+
},
|
|
1464
|
+
all: {
|
|
1465
|
+
type: "boolean",
|
|
1466
|
+
description: "Absorb all unmanaged items without prompting",
|
|
1467
|
+
default: false
|
|
1468
|
+
}
|
|
1469
|
+
},
|
|
1470
|
+
run({ args }) {
|
|
1471
|
+
return runSync({
|
|
1472
|
+
repoPath: args["repo-path"],
|
|
1473
|
+
all: args.all
|
|
1474
|
+
});
|
|
1475
|
+
}
|
|
1476
|
+
});
|
|
1332
1477
|
var main = defineCommand({
|
|
1333
1478
|
meta: {
|
|
1334
1479
|
name: "pi-agent-toolkit",
|
|
@@ -1338,7 +1483,8 @@ var main = defineCommand({
|
|
|
1338
1483
|
subCommands: {
|
|
1339
1484
|
install,
|
|
1340
1485
|
list,
|
|
1341
|
-
status
|
|
1486
|
+
status,
|
|
1487
|
+
sync
|
|
1342
1488
|
}
|
|
1343
1489
|
});
|
|
1344
1490
|
runMain(main);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-agent-toolkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "CLI to selectively install curated extensions, skills, and configs for the pi coding agent",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi",
|
|
@@ -27,11 +27,13 @@
|
|
|
27
27
|
"scripts": {
|
|
28
28
|
"build": "tsup",
|
|
29
29
|
"dev": "tsup --watch",
|
|
30
|
+
"test": "node --experimental-strip-types --test src/**/*.test.ts",
|
|
31
|
+
"typecheck": "tsc",
|
|
30
32
|
"prepack": "npm run build"
|
|
31
33
|
},
|
|
32
34
|
"dependencies": {
|
|
33
|
-
"@clack/prompts": "^0.
|
|
34
|
-
"citty": "^0.1
|
|
35
|
+
"@clack/prompts": "^0.11.0",
|
|
36
|
+
"citty": "^0.2.1",
|
|
35
37
|
"picocolors": "^1.1.1"
|
|
36
38
|
},
|
|
37
39
|
"devDependencies": {
|