planmode 0.2.2 → 0.3.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 +1720 -758
- package/dist/mcp.js +315 -155
- package/package.json +2 -1
- package/src/commands/doctor.ts +46 -14
- package/src/commands/init.ts +95 -47
- package/src/commands/install.ts +17 -2
- package/src/commands/interactive.ts +449 -0
- package/src/commands/login.ts +50 -23
- package/src/commands/publish.ts +15 -3
- package/src/commands/record.ts +32 -8
- package/src/commands/run.ts +6 -15
- package/src/commands/search.ts +89 -18
- package/src/commands/snapshot.ts +33 -9
- package/src/commands/test.ts +43 -13
- package/src/commands/update.ts +57 -15
- package/src/index.ts +9 -2
- package/src/lib/installer.ts +57 -29
- package/src/lib/prompts.ts +159 -0
- package/src/lib/publisher.ts +176 -144
package/dist/mcp.js
CHANGED
|
@@ -35,33 +35,33 @@ var logger = {
|
|
|
35
35
|
return capturing;
|
|
36
36
|
},
|
|
37
37
|
info(msg) {
|
|
38
|
-
const
|
|
38
|
+
const text2 = `info ${msg}`;
|
|
39
39
|
if (capturing) {
|
|
40
|
-
captured.push(stripAnsi(
|
|
40
|
+
captured.push(stripAnsi(text2));
|
|
41
41
|
} else {
|
|
42
42
|
console.log(`${CYAN}info${RESET} ${msg}`);
|
|
43
43
|
}
|
|
44
44
|
},
|
|
45
45
|
success(msg) {
|
|
46
|
-
const
|
|
46
|
+
const text2 = `\u2713 ${msg}`;
|
|
47
47
|
if (capturing) {
|
|
48
|
-
captured.push(stripAnsi(
|
|
48
|
+
captured.push(stripAnsi(text2));
|
|
49
49
|
} else {
|
|
50
50
|
console.log(`${GREEN}\u2713${RESET} ${msg}`);
|
|
51
51
|
}
|
|
52
52
|
},
|
|
53
53
|
warn(msg) {
|
|
54
|
-
const
|
|
54
|
+
const text2 = `warn ${msg}`;
|
|
55
55
|
if (capturing) {
|
|
56
|
-
captured.push(stripAnsi(
|
|
56
|
+
captured.push(stripAnsi(text2));
|
|
57
57
|
} else {
|
|
58
58
|
console.log(`${YELLOW}warn${RESET} ${msg}`);
|
|
59
59
|
}
|
|
60
60
|
},
|
|
61
61
|
error(msg) {
|
|
62
|
-
const
|
|
62
|
+
const text2 = `error ${msg}`;
|
|
63
63
|
if (capturing) {
|
|
64
|
-
captured.push(stripAnsi(
|
|
64
|
+
captured.push(stripAnsi(text2));
|
|
65
65
|
} else {
|
|
66
66
|
console.error(`${RED}error${RESET} ${msg}`);
|
|
67
67
|
}
|
|
@@ -660,6 +660,118 @@ function trackDownload(packageName) {
|
|
|
660
660
|
});
|
|
661
661
|
}
|
|
662
662
|
|
|
663
|
+
// src/lib/prompts.ts
|
|
664
|
+
import * as p from "@clack/prompts";
|
|
665
|
+
function isInteractive() {
|
|
666
|
+
return Boolean(process.stdin.isTTY) && !process.env.CI;
|
|
667
|
+
}
|
|
668
|
+
function handleCancel(value) {
|
|
669
|
+
if (p.isCancel(value)) {
|
|
670
|
+
p.cancel("Cancelled.");
|
|
671
|
+
process.exit(0);
|
|
672
|
+
}
|
|
673
|
+
return value;
|
|
674
|
+
}
|
|
675
|
+
async function promptForVariable(name, def) {
|
|
676
|
+
switch (def.type) {
|
|
677
|
+
case "enum": {
|
|
678
|
+
const value = await p.select({
|
|
679
|
+
message: def.description || name,
|
|
680
|
+
options: (def.options ?? []).map((opt) => ({
|
|
681
|
+
value: opt,
|
|
682
|
+
label: opt
|
|
683
|
+
})),
|
|
684
|
+
initialValue: def.default !== void 0 ? String(def.default) : void 0
|
|
685
|
+
});
|
|
686
|
+
return handleCancel(value);
|
|
687
|
+
}
|
|
688
|
+
case "boolean": {
|
|
689
|
+
const value = await p.confirm({
|
|
690
|
+
message: def.description || name,
|
|
691
|
+
initialValue: def.default !== void 0 ? Boolean(def.default) : false
|
|
692
|
+
});
|
|
693
|
+
return handleCancel(value);
|
|
694
|
+
}
|
|
695
|
+
case "number": {
|
|
696
|
+
const value = await p.text({
|
|
697
|
+
message: def.description || name,
|
|
698
|
+
placeholder: def.default !== void 0 ? String(def.default) : void 0,
|
|
699
|
+
defaultValue: def.default !== void 0 ? String(def.default) : void 0,
|
|
700
|
+
validate(input) {
|
|
701
|
+
if (isNaN(Number(input))) return "Must be a number";
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
return Number(handleCancel(value));
|
|
705
|
+
}
|
|
706
|
+
case "string":
|
|
707
|
+
default: {
|
|
708
|
+
const value = await p.text({
|
|
709
|
+
message: def.description || name,
|
|
710
|
+
placeholder: def.default !== void 0 ? String(def.default) : void 0,
|
|
711
|
+
defaultValue: def.default !== void 0 ? String(def.default) : void 0,
|
|
712
|
+
validate(input) {
|
|
713
|
+
if (def.required && !input) return `${name} is required`;
|
|
714
|
+
}
|
|
715
|
+
});
|
|
716
|
+
return handleCancel(value);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
async function promptForVariables(variableDefs, provided, noInput = false) {
|
|
721
|
+
const values = {};
|
|
722
|
+
for (const [name, def] of Object.entries(variableDefs)) {
|
|
723
|
+
if (def.type === "resolved") continue;
|
|
724
|
+
if (provided[name] !== void 0) {
|
|
725
|
+
values[name] = coerceValue2(provided[name], def);
|
|
726
|
+
} else if (def.default !== void 0) {
|
|
727
|
+
if (isInteractive() && !noInput) {
|
|
728
|
+
values[name] = await promptForVariable(name, def);
|
|
729
|
+
} else {
|
|
730
|
+
values[name] = def.default;
|
|
731
|
+
}
|
|
732
|
+
} else if (def.required) {
|
|
733
|
+
if (isInteractive() && !noInput) {
|
|
734
|
+
values[name] = await promptForVariable(name, def);
|
|
735
|
+
} else {
|
|
736
|
+
throw new Error(`Missing required variable: ${name} -- ${def.description}`);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
return values;
|
|
741
|
+
}
|
|
742
|
+
function coerceValue2(raw, def) {
|
|
743
|
+
switch (def.type) {
|
|
744
|
+
case "number":
|
|
745
|
+
return Number(raw);
|
|
746
|
+
case "boolean":
|
|
747
|
+
return raw === "true" || raw === "1" || raw === "yes";
|
|
748
|
+
case "enum":
|
|
749
|
+
if (def.options && !def.options.includes(raw)) {
|
|
750
|
+
throw new Error(
|
|
751
|
+
`Invalid value "${raw}" for enum variable. Options: ${def.options.join(", ")}`
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
return raw;
|
|
755
|
+
default:
|
|
756
|
+
return raw;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
async function withSpinner(message, fn, successMessage) {
|
|
760
|
+
if (!isInteractive()) {
|
|
761
|
+
return fn();
|
|
762
|
+
}
|
|
763
|
+
const s = p.spinner();
|
|
764
|
+
s.start(message);
|
|
765
|
+
try {
|
|
766
|
+
const result = await fn();
|
|
767
|
+
s.stop(successMessage ?? message);
|
|
768
|
+
return result;
|
|
769
|
+
} catch (err) {
|
|
770
|
+
s.stop(`Failed: ${message}`);
|
|
771
|
+
throw err;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
663
775
|
// src/lib/installer.ts
|
|
664
776
|
function getInstallDir(type) {
|
|
665
777
|
switch (type) {
|
|
@@ -679,38 +791,60 @@ function contentHash(content) {
|
|
|
679
791
|
}
|
|
680
792
|
async function installPackage(packageName, options = {}) {
|
|
681
793
|
const projectDir = options.projectDir ?? process.cwd();
|
|
794
|
+
const interactive = options.interactive ?? (isInteractive() && !options.noInput);
|
|
682
795
|
const locked = getLockedVersion(packageName, projectDir);
|
|
683
796
|
if (locked && !options.version) {
|
|
684
797
|
logger.dim(`${packageName}@${locked.version} already installed`);
|
|
685
798
|
return;
|
|
686
799
|
}
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
const
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
)
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
800
|
+
const resolveAndFetch = async () => {
|
|
801
|
+
const { version: version2, metadata: metadata2 } = await resolveVersion(packageName, options.version);
|
|
802
|
+
const versionMeta2 = await fetchVersionMetadata(packageName, version2);
|
|
803
|
+
return { version: version2, metadata: metadata2, versionMeta: versionMeta2 };
|
|
804
|
+
};
|
|
805
|
+
const { version, metadata, versionMeta } = interactive ? await withSpinner(
|
|
806
|
+
`Resolving ${packageName}...`,
|
|
807
|
+
resolveAndFetch,
|
|
808
|
+
`Resolved ${packageName}`
|
|
809
|
+
) : await (async () => {
|
|
810
|
+
logger.info(`Resolving ${packageName}...`);
|
|
811
|
+
return resolveAndFetch();
|
|
812
|
+
})();
|
|
813
|
+
const fetchContent = async () => {
|
|
814
|
+
const basePath = versionMeta.source.path ? `${versionMeta.source.path}/` : "";
|
|
815
|
+
const manifestRaw = await fetchFileAtTag(
|
|
703
816
|
versionMeta.source.repository,
|
|
704
817
|
versionMeta.source.tag,
|
|
705
|
-
`${basePath}
|
|
818
|
+
`${basePath}planmode.yaml`
|
|
706
819
|
);
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
820
|
+
const manifest2 = parseManifest(manifestRaw);
|
|
821
|
+
let content2;
|
|
822
|
+
if (manifest2.content) {
|
|
823
|
+
content2 = manifest2.content;
|
|
824
|
+
} else if (manifest2.content_file) {
|
|
825
|
+
content2 = await fetchFileAtTag(
|
|
826
|
+
versionMeta.source.repository,
|
|
827
|
+
versionMeta.source.tag,
|
|
828
|
+
`${basePath}${manifest2.content_file}`
|
|
829
|
+
);
|
|
830
|
+
} else {
|
|
831
|
+
throw new Error("Package has no content or content_file");
|
|
832
|
+
}
|
|
833
|
+
return { manifest: manifest2, content: content2 };
|
|
834
|
+
};
|
|
835
|
+
const { manifest, content: rawContent } = interactive ? await withSpinner(
|
|
836
|
+
`Fetching ${packageName}@${version}...`,
|
|
837
|
+
fetchContent,
|
|
838
|
+
`Fetched ${packageName}@${version}`
|
|
839
|
+
) : await (async () => {
|
|
840
|
+
logger.info(`Fetching ${packageName}@${version}...`);
|
|
841
|
+
return fetchContent();
|
|
842
|
+
})();
|
|
843
|
+
let content = rawContent;
|
|
710
844
|
if (manifest.variables && Object.keys(manifest.variables).length > 0) {
|
|
711
845
|
const provided = options.variables ?? {};
|
|
712
|
-
if (
|
|
713
|
-
const values =
|
|
846
|
+
if (interactive) {
|
|
847
|
+
const values = await promptForVariables(manifest.variables, provided, false);
|
|
714
848
|
content = renderTemplate(content, values);
|
|
715
849
|
} else {
|
|
716
850
|
const values = collectVariableValues(manifest.variables, provided);
|
|
@@ -772,7 +906,8 @@ async function installPackage(packageName, options = {}) {
|
|
|
772
906
|
await installPackage(name, {
|
|
773
907
|
version: range === "*" ? void 0 : range,
|
|
774
908
|
projectDir,
|
|
775
|
-
noInput: options.noInput
|
|
909
|
+
noInput: options.noInput,
|
|
910
|
+
interactive: options.interactive
|
|
776
911
|
});
|
|
777
912
|
}
|
|
778
913
|
}
|
|
@@ -919,139 +1054,151 @@ function createPackage(options) {
|
|
|
919
1054
|
// src/lib/publisher.ts
|
|
920
1055
|
async function publishPackage(options = {}) {
|
|
921
1056
|
const cwd = options.projectDir ?? process.cwd();
|
|
1057
|
+
const interactive = options.interactive ?? false;
|
|
922
1058
|
const token = options.token ?? getGitHubToken();
|
|
923
1059
|
if (!token) {
|
|
924
1060
|
throw new Error("Not authenticated. Run `planmode login` first.");
|
|
925
1061
|
}
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
1062
|
+
const doValidate = async () => {
|
|
1063
|
+
const manifest2 = readManifest(cwd);
|
|
1064
|
+
const errors = validateManifest(manifest2, true);
|
|
1065
|
+
if (errors.length > 0) {
|
|
1066
|
+
throw new Error(`Invalid manifest:
|
|
931
1067
|
${errors.map((e) => ` - ${e}`).join("\n")}`);
|
|
932
|
-
|
|
1068
|
+
}
|
|
1069
|
+
return manifest2;
|
|
1070
|
+
};
|
|
1071
|
+
const manifest = interactive ? await withSpinner("Validating manifest...", doValidate, "Manifest valid") : await (async () => {
|
|
1072
|
+
logger.info("Reading planmode.yaml...");
|
|
1073
|
+
return doValidate();
|
|
1074
|
+
})();
|
|
933
1075
|
const remoteUrl = await getRemoteUrl(cwd);
|
|
934
1076
|
if (!remoteUrl) {
|
|
935
1077
|
throw new Error("No git remote found. Push your code to GitHub first.");
|
|
936
1078
|
}
|
|
937
1079
|
const sha = await getHeadSha(cwd);
|
|
938
1080
|
const tag = `v${manifest.version}`;
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
1081
|
+
const doTag = async () => {
|
|
1082
|
+
try {
|
|
1083
|
+
await createTag(cwd, tag);
|
|
1084
|
+
} catch {
|
|
1085
|
+
}
|
|
1086
|
+
try {
|
|
1087
|
+
await pushTag(cwd, tag);
|
|
1088
|
+
} catch {
|
|
1089
|
+
}
|
|
1090
|
+
};
|
|
1091
|
+
if (interactive) {
|
|
1092
|
+
await withSpinner(`Creating tag ${tag}...`, doTag, `Tag ${tag} ready`);
|
|
1093
|
+
} else {
|
|
1094
|
+
logger.info(`Creating tag ${tag}...`);
|
|
1095
|
+
await doTag();
|
|
947
1096
|
logger.success(`Pushed tag ${tag}`);
|
|
948
|
-
} catch {
|
|
949
|
-
logger.dim(`Tag ${tag} already pushed`);
|
|
950
1097
|
}
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
repository: repoPath,
|
|
975
|
-
category: manifest.category ?? "other",
|
|
976
|
-
tags: manifest.tags ?? [],
|
|
977
|
-
type: manifest.type,
|
|
978
|
-
models: manifest.models ?? [],
|
|
979
|
-
latest_version: manifest.version,
|
|
980
|
-
versions: [manifest.version],
|
|
981
|
-
downloads: 0,
|
|
982
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
983
|
-
updated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
984
|
-
dependencies: manifest.dependencies,
|
|
985
|
-
variables: manifest.variables
|
|
986
|
-
},
|
|
987
|
-
null,
|
|
988
|
-
2
|
|
989
|
-
);
|
|
990
|
-
const versionContent = JSON.stringify(
|
|
991
|
-
{
|
|
992
|
-
version: manifest.version,
|
|
993
|
-
published_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
994
|
-
source: {
|
|
1098
|
+
const doSubmit = async () => {
|
|
1099
|
+
const headers = {
|
|
1100
|
+
Authorization: `Bearer ${token}`,
|
|
1101
|
+
Accept: "application/vnd.github.v3+json",
|
|
1102
|
+
"User-Agent": "planmode-cli",
|
|
1103
|
+
"Content-Type": "application/json"
|
|
1104
|
+
};
|
|
1105
|
+
await fetch("https://api.github.com/repos/kaihannonen/planmode.org/forks", {
|
|
1106
|
+
method: "POST",
|
|
1107
|
+
headers
|
|
1108
|
+
});
|
|
1109
|
+
const userRes = await fetch("https://api.github.com/user", { headers });
|
|
1110
|
+
if (!userRes.ok) {
|
|
1111
|
+
throw new Error("Failed to authenticate with GitHub. Check your token.");
|
|
1112
|
+
}
|
|
1113
|
+
const user = await userRes.json();
|
|
1114
|
+
const repoPath = remoteUrl.replace(/^https?:\/\//, "").replace(/\.git$/, "");
|
|
1115
|
+
const metadataContent = JSON.stringify(
|
|
1116
|
+
{
|
|
1117
|
+
name: manifest.name,
|
|
1118
|
+
description: manifest.description,
|
|
1119
|
+
author: manifest.author,
|
|
1120
|
+
license: manifest.license,
|
|
995
1121
|
repository: repoPath,
|
|
996
|
-
|
|
997
|
-
|
|
1122
|
+
category: manifest.category ?? "other",
|
|
1123
|
+
tags: manifest.tags ?? [],
|
|
1124
|
+
type: manifest.type,
|
|
1125
|
+
models: manifest.models ?? [],
|
|
1126
|
+
latest_version: manifest.version,
|
|
1127
|
+
versions: [manifest.version],
|
|
1128
|
+
downloads: 0,
|
|
1129
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1130
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1131
|
+
dependencies: manifest.dependencies,
|
|
1132
|
+
variables: manifest.variables
|
|
998
1133
|
},
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1134
|
+
null,
|
|
1135
|
+
2
|
|
1136
|
+
);
|
|
1137
|
+
const versionContent = JSON.stringify(
|
|
1138
|
+
{
|
|
1139
|
+
version: manifest.version,
|
|
1140
|
+
published_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1141
|
+
source: {
|
|
1142
|
+
repository: repoPath,
|
|
1143
|
+
tag,
|
|
1144
|
+
sha
|
|
1145
|
+
},
|
|
1146
|
+
files: ["planmode.yaml", manifest.content_file ?? "inline"],
|
|
1147
|
+
content_hash: `sha256:${sha.slice(0, 16)}`
|
|
1148
|
+
},
|
|
1149
|
+
null,
|
|
1150
|
+
2
|
|
1151
|
+
);
|
|
1152
|
+
const branchName = `add-${manifest.name}-${manifest.version}`;
|
|
1153
|
+
const refRes = await fetch(
|
|
1154
|
+
`https://api.github.com/repos/${user.login}/planmode.org/git/ref/heads/main`,
|
|
1155
|
+
{ headers }
|
|
1156
|
+
);
|
|
1157
|
+
if (!refRes.ok) {
|
|
1158
|
+
throw new Error("Failed to access registry fork. Make sure the fork exists.");
|
|
1159
|
+
}
|
|
1160
|
+
const refData = await refRes.json();
|
|
1161
|
+
const baseSha = refData.object.sha;
|
|
1162
|
+
await fetch(`https://api.github.com/repos/${user.login}/planmode.org/git/refs`, {
|
|
1163
|
+
method: "POST",
|
|
1027
1164
|
headers,
|
|
1028
1165
|
body: JSON.stringify({
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
branch: branchName
|
|
1166
|
+
ref: `refs/heads/${branchName}`,
|
|
1167
|
+
sha: baseSha
|
|
1032
1168
|
})
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1169
|
+
});
|
|
1170
|
+
await fetch(
|
|
1171
|
+
`https://api.github.com/repos/${user.login}/planmode.org/contents/registry/packages/${manifest.name}/metadata.json`,
|
|
1172
|
+
{
|
|
1173
|
+
method: "PUT",
|
|
1174
|
+
headers,
|
|
1175
|
+
body: JSON.stringify({
|
|
1176
|
+
message: `Add ${manifest.name}@${manifest.version}`,
|
|
1177
|
+
content: Buffer.from(metadataContent).toString("base64"),
|
|
1178
|
+
branch: branchName
|
|
1179
|
+
})
|
|
1180
|
+
}
|
|
1181
|
+
);
|
|
1182
|
+
await fetch(
|
|
1183
|
+
`https://api.github.com/repos/${user.login}/planmode.org/contents/registry/packages/${manifest.name}/versions/${manifest.version}.json`,
|
|
1184
|
+
{
|
|
1185
|
+
method: "PUT",
|
|
1186
|
+
headers,
|
|
1187
|
+
body: JSON.stringify({
|
|
1188
|
+
message: `Add ${manifest.name}@${manifest.version} version metadata`,
|
|
1189
|
+
content: Buffer.from(versionContent).toString("base64"),
|
|
1190
|
+
branch: branchName
|
|
1191
|
+
})
|
|
1192
|
+
}
|
|
1193
|
+
);
|
|
1194
|
+
const prRes = await fetch("https://api.github.com/repos/kaihannonen/planmode.org/pulls", {
|
|
1195
|
+
method: "POST",
|
|
1039
1196
|
headers,
|
|
1040
1197
|
body: JSON.stringify({
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
}
|
|
1046
|
-
);
|
|
1047
|
-
const prRes = await fetch("https://api.github.com/repos/kaihannonen/planmode.org/pulls", {
|
|
1048
|
-
method: "POST",
|
|
1049
|
-
headers,
|
|
1050
|
-
body: JSON.stringify({
|
|
1051
|
-
title: `Add ${manifest.name}@${manifest.version}`,
|
|
1052
|
-
head: `${user.login}:${branchName}`,
|
|
1053
|
-
base: "main",
|
|
1054
|
-
body: `## New package: ${manifest.name}
|
|
1198
|
+
title: `Add ${manifest.name}@${manifest.version}`,
|
|
1199
|
+
head: `${user.login}:${branchName}`,
|
|
1200
|
+
base: "main",
|
|
1201
|
+
body: `## New package: ${manifest.name}
|
|
1055
1202
|
|
|
1056
1203
|
- **Type:** ${manifest.type}
|
|
1057
1204
|
- **Version:** ${manifest.version}
|
|
@@ -1059,17 +1206,30 @@ ${errors.map((e) => ` - ${e}`).join("\n")}`);
|
|
|
1059
1206
|
- **Author:** ${manifest.author}
|
|
1060
1207
|
|
|
1061
1208
|
Submitted via \`planmode publish\`.`
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1209
|
+
})
|
|
1210
|
+
});
|
|
1211
|
+
if (!prRes.ok) {
|
|
1212
|
+
const err = await prRes.text();
|
|
1213
|
+
throw new Error(`Failed to create PR: ${err}`);
|
|
1214
|
+
}
|
|
1215
|
+
const pr = await prRes.json();
|
|
1216
|
+
return pr.html_url;
|
|
1217
|
+
};
|
|
1218
|
+
let prUrl;
|
|
1219
|
+
if (interactive) {
|
|
1220
|
+
prUrl = await withSpinner(
|
|
1221
|
+
"Submitting to registry...",
|
|
1222
|
+
doSubmit,
|
|
1223
|
+
"Submitted to registry"
|
|
1224
|
+
);
|
|
1225
|
+
} else {
|
|
1226
|
+
logger.info("Submitting to registry...");
|
|
1227
|
+
prUrl = await doSubmit();
|
|
1228
|
+
logger.success(`Published ${manifest.name}@${manifest.version}`);
|
|
1229
|
+
logger.info(`PR: ${prUrl}`);
|
|
1067
1230
|
}
|
|
1068
|
-
const pr = await prRes.json();
|
|
1069
|
-
logger.success(`Published ${manifest.name}@${manifest.version}`);
|
|
1070
|
-
logger.info(`PR: ${pr.html_url}`);
|
|
1071
1231
|
return {
|
|
1072
|
-
prUrl
|
|
1232
|
+
prUrl,
|
|
1073
1233
|
packageName: manifest.name,
|
|
1074
1234
|
version: manifest.version
|
|
1075
1235
|
};
|
|
@@ -1714,9 +1874,9 @@ async function withCaptureAsync(fn) {
|
|
|
1714
1874
|
throw Object.assign(err, { capturedMessages: messages });
|
|
1715
1875
|
}
|
|
1716
1876
|
}
|
|
1717
|
-
function textResult(
|
|
1877
|
+
function textResult(text2, isError = false) {
|
|
1718
1878
|
return {
|
|
1719
|
-
content: [{ type: "text", text }],
|
|
1879
|
+
content: [{ type: "text", text: text2 }],
|
|
1720
1880
|
isError
|
|
1721
1881
|
};
|
|
1722
1882
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "planmode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "The open source package manager for AI plans, rules, and prompts.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"typecheck": "tsc --noEmit"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
+
"@clack/prompts": "^0.9.1",
|
|
19
20
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
20
21
|
"commander": "^13.1.0",
|
|
21
22
|
"handlebars": "^4.7.8",
|
package/src/commands/doctor.ts
CHANGED
|
@@ -1,19 +1,35 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
+
import * as p from "@clack/prompts";
|
|
2
3
|
import { runDoctor } from "../lib/doctor.js";
|
|
3
4
|
import { logger } from "../lib/logger.js";
|
|
5
|
+
import { isInteractive } from "../lib/prompts.js";
|
|
4
6
|
|
|
5
7
|
export const doctorCommand = new Command("doctor")
|
|
6
8
|
.description("Check project health: verify installed packages, imports, and file integrity")
|
|
7
9
|
.action(() => {
|
|
10
|
+
const interactive = isInteractive();
|
|
8
11
|
const result = runDoctor();
|
|
9
12
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
if (interactive) {
|
|
14
|
+
p.intro("Health check");
|
|
15
|
+
} else {
|
|
16
|
+
logger.blank();
|
|
17
|
+
}
|
|
13
18
|
|
|
14
|
-
if (
|
|
15
|
-
|
|
19
|
+
if (interactive) {
|
|
20
|
+
p.log.info(`Checked ${result.packagesChecked} package(s)`);
|
|
21
|
+
} else {
|
|
22
|
+
logger.bold(`Checked ${result.packagesChecked} package(s)`);
|
|
16
23
|
logger.blank();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (result.issues.length === 0) {
|
|
27
|
+
if (interactive) {
|
|
28
|
+
p.outro("Everything looks good. No issues found.");
|
|
29
|
+
} else {
|
|
30
|
+
logger.success("Everything looks good. No issues found.");
|
|
31
|
+
logger.blank();
|
|
32
|
+
}
|
|
17
33
|
return;
|
|
18
34
|
}
|
|
19
35
|
|
|
@@ -21,21 +37,37 @@ export const doctorCommand = new Command("doctor")
|
|
|
21
37
|
const warnings = result.issues.filter((i) => i.severity === "warning");
|
|
22
38
|
|
|
23
39
|
for (const issue of errors) {
|
|
24
|
-
|
|
25
|
-
|
|
40
|
+
if (interactive) {
|
|
41
|
+
p.log.error(issue.message);
|
|
42
|
+
} else {
|
|
43
|
+
logger.error(issue.message);
|
|
44
|
+
if (issue.fix) logger.dim(` Fix: ${issue.fix}`);
|
|
45
|
+
}
|
|
26
46
|
}
|
|
27
47
|
for (const issue of warnings) {
|
|
28
|
-
|
|
29
|
-
|
|
48
|
+
if (interactive) {
|
|
49
|
+
p.log.warn(issue.message);
|
|
50
|
+
} else {
|
|
51
|
+
logger.warn(issue.message);
|
|
52
|
+
if (issue.fix) logger.dim(` Fix: ${issue.fix}`);
|
|
53
|
+
}
|
|
30
54
|
}
|
|
31
55
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
56
|
+
if (interactive) {
|
|
57
|
+
if (errors.length > 0) {
|
|
58
|
+
p.outro(`${errors.length} error(s), ${warnings.length} warning(s)`);
|
|
59
|
+
} else {
|
|
60
|
+
p.outro(`${warnings.length} warning(s)`);
|
|
61
|
+
}
|
|
35
62
|
} else {
|
|
36
|
-
logger.
|
|
63
|
+
logger.blank();
|
|
64
|
+
if (errors.length > 0) {
|
|
65
|
+
logger.error(`${errors.length} error(s), ${warnings.length} warning(s)`);
|
|
66
|
+
} else {
|
|
67
|
+
logger.warn(`${warnings.length} warning(s)`);
|
|
68
|
+
}
|
|
69
|
+
logger.blank();
|
|
37
70
|
}
|
|
38
|
-
logger.blank();
|
|
39
71
|
|
|
40
72
|
if (errors.length > 0) {
|
|
41
73
|
process.exit(1);
|