planmode 0.1.5 → 0.2.1
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 +43 -0
- package/dist/index.js +1183 -193
- package/dist/mcp.d.ts +2 -0
- package/dist/mcp.js +2435 -0
- package/package.json +6 -3
- package/src/commands/doctor.ts +43 -0
- package/src/commands/init.ts +17 -36
- package/src/commands/mcp.ts +39 -0
- package/src/commands/publish.ts +3 -191
- package/src/commands/record.ts +76 -0
- package/src/commands/snapshot.ts +46 -0
- package/src/commands/test.ts +45 -0
- package/src/index.ts +11 -1
- package/src/lib/doctor.ts +123 -0
- package/src/lib/init.ts +71 -0
- package/src/lib/installer.ts +20 -1
- package/src/lib/logger.ts +74 -11
- package/src/lib/publisher.ts +203 -0
- package/src/lib/recorder.ts +195 -0
- package/src/lib/snapshot.ts +348 -0
- package/src/lib/templates.ts +60 -0
- package/src/lib/tester.ts +162 -0
- package/src/mcp.ts +853 -0
- package/src/types/index.ts +2 -0
- package/tsup.config.ts +1 -1
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "planmode",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "The open source package manager for AI plans, rules, and prompts.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"planmode": "./dist/index.js"
|
|
7
|
+
"planmode": "./dist/index.js",
|
|
8
|
+
"planmode-mcp": "./dist/mcp.js"
|
|
8
9
|
},
|
|
9
10
|
"scripts": {
|
|
10
11
|
"build": "tsup",
|
|
@@ -15,10 +16,12 @@
|
|
|
15
16
|
"typecheck": "tsc --noEmit"
|
|
16
17
|
},
|
|
17
18
|
"dependencies": {
|
|
19
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
18
20
|
"commander": "^13.1.0",
|
|
19
21
|
"handlebars": "^4.7.8",
|
|
20
22
|
"simple-git": "^3.27.0",
|
|
21
|
-
"yaml": "^2.7.0"
|
|
23
|
+
"yaml": "^2.7.0",
|
|
24
|
+
"zod": "^4.3.6"
|
|
22
25
|
},
|
|
23
26
|
"devDependencies": {
|
|
24
27
|
"@types/node": "^22.13.0",
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { runDoctor } from "../lib/doctor.js";
|
|
3
|
+
import { logger } from "../lib/logger.js";
|
|
4
|
+
|
|
5
|
+
export const doctorCommand = new Command("doctor")
|
|
6
|
+
.description("Check project health: verify installed packages, imports, and file integrity")
|
|
7
|
+
.action(() => {
|
|
8
|
+
const result = runDoctor();
|
|
9
|
+
|
|
10
|
+
logger.blank();
|
|
11
|
+
logger.bold(`Checked ${result.packagesChecked} package(s)`);
|
|
12
|
+
logger.blank();
|
|
13
|
+
|
|
14
|
+
if (result.issues.length === 0) {
|
|
15
|
+
logger.success("Everything looks good. No issues found.");
|
|
16
|
+
logger.blank();
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const errors = result.issues.filter((i) => i.severity === "error");
|
|
21
|
+
const warnings = result.issues.filter((i) => i.severity === "warning");
|
|
22
|
+
|
|
23
|
+
for (const issue of errors) {
|
|
24
|
+
logger.error(issue.message);
|
|
25
|
+
if (issue.fix) logger.dim(` Fix: ${issue.fix}`);
|
|
26
|
+
}
|
|
27
|
+
for (const issue of warnings) {
|
|
28
|
+
logger.warn(issue.message);
|
|
29
|
+
if (issue.fix) logger.dim(` Fix: ${issue.fix}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
logger.blank();
|
|
33
|
+
if (errors.length > 0) {
|
|
34
|
+
logger.error(`${errors.length} error(s), ${warnings.length} warning(s)`);
|
|
35
|
+
} else {
|
|
36
|
+
logger.warn(`${warnings.length} warning(s)`);
|
|
37
|
+
}
|
|
38
|
+
logger.blank();
|
|
39
|
+
|
|
40
|
+
if (errors.length > 0) {
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
});
|
package/src/commands/init.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { stringify } from "yaml";
|
|
5
2
|
import { logger } from "../lib/logger.js";
|
|
3
|
+
import { createPackage } from "../lib/init.js";
|
|
6
4
|
import type { PackageType, Category } from "../types/index.js";
|
|
7
5
|
|
|
8
6
|
async function prompt(question: string): Promise<string> {
|
|
@@ -35,48 +33,31 @@ export const initCommand = new Command("init")
|
|
|
35
33
|
|
|
36
34
|
const description = await prompt("Description: ");
|
|
37
35
|
const author = await prompt("Author (GitHub username): ");
|
|
38
|
-
const license = await prompt("License [MIT]: ") || "MIT";
|
|
36
|
+
const license = (await prompt("License [MIT]: ")) || "MIT";
|
|
39
37
|
const tagsInput = await prompt("Tags (comma-separated): ");
|
|
40
|
-
const tags = tagsInput
|
|
41
|
-
|
|
38
|
+
const tags = tagsInput
|
|
39
|
+
? tagsInput.split(",").map((t) => t.trim().toLowerCase())
|
|
40
|
+
: [];
|
|
41
|
+
const category =
|
|
42
|
+
((await prompt(
|
|
43
|
+
"Category (frontend/backend/devops/database/testing/mobile/ai-ml/security/other) [other]: ",
|
|
44
|
+
)) || "other") as Category;
|
|
42
45
|
|
|
43
|
-
|
|
44
|
-
const manifest: Record<string, unknown> = {
|
|
46
|
+
const result = createPackage({
|
|
45
47
|
name,
|
|
46
|
-
version: "1.0.0",
|
|
47
48
|
type,
|
|
48
49
|
description,
|
|
49
50
|
author,
|
|
50
51
|
license,
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
manifest["category"] = category;
|
|
55
|
-
|
|
56
|
-
const contentFile = `${type}.md`;
|
|
57
|
-
manifest["content_file"] = contentFile;
|
|
58
|
-
|
|
59
|
-
// Write planmode.yaml
|
|
60
|
-
const yamlContent = stringify(manifest);
|
|
61
|
-
fs.writeFileSync(path.join(process.cwd(), "planmode.yaml"), yamlContent, "utf-8");
|
|
62
|
-
logger.success("Created planmode.yaml");
|
|
63
|
-
|
|
64
|
-
// Write stub content file
|
|
65
|
-
const stubs: Record<string, string> = {
|
|
66
|
-
plan: `# ${name}\n\n1. First step\n2. Second step\n3. Third step\n`,
|
|
67
|
-
rule: `- Rule one\n- Rule two\n- Rule three\n`,
|
|
68
|
-
prompt: `Write your prompt here.\n\nUse {{variable_name}} for template variables.\n`,
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
fs.writeFileSync(
|
|
72
|
-
path.join(process.cwd(), contentFile),
|
|
73
|
-
stubs[type] ?? stubs["plan"]!,
|
|
74
|
-
"utf-8",
|
|
75
|
-
);
|
|
76
|
-
logger.success(`Created ${contentFile}`);
|
|
52
|
+
tags,
|
|
53
|
+
category,
|
|
54
|
+
});
|
|
77
55
|
|
|
56
|
+
logger.success(`Created ${result.files.join(", ")}`);
|
|
78
57
|
logger.blank();
|
|
79
|
-
logger.info(
|
|
58
|
+
logger.info(
|
|
59
|
+
`Edit ${result.files[1]}, then run \`planmode publish\` when ready.`,
|
|
60
|
+
);
|
|
80
61
|
logger.blank();
|
|
81
62
|
} catch (err) {
|
|
82
63
|
logger.error((err as Error).message);
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { execSync } from "node:child_process";
|
|
3
|
+
import { logger } from "../lib/logger.js";
|
|
4
|
+
|
|
5
|
+
export const mcpCommand = new Command("mcp")
|
|
6
|
+
.description("Manage MCP server registration with Claude Code");
|
|
7
|
+
|
|
8
|
+
mcpCommand
|
|
9
|
+
.command("setup")
|
|
10
|
+
.description("Register the planmode MCP server with Claude Code")
|
|
11
|
+
.action(() => {
|
|
12
|
+
try {
|
|
13
|
+
execSync("claude mcp add --transport stdio planmode -- planmode-mcp", {
|
|
14
|
+
stdio: "inherit",
|
|
15
|
+
});
|
|
16
|
+
logger.success("Planmode MCP server registered with Claude Code.");
|
|
17
|
+
logger.dim("Claude Code can now use planmode tools directly.");
|
|
18
|
+
} catch (err) {
|
|
19
|
+
logger.error(
|
|
20
|
+
"Failed to register MCP server. Make sure Claude Code CLI is installed.",
|
|
21
|
+
);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
mcpCommand
|
|
27
|
+
.command("remove")
|
|
28
|
+
.description("Remove the planmode MCP server from Claude Code")
|
|
29
|
+
.action(() => {
|
|
30
|
+
try {
|
|
31
|
+
execSync("claude mcp remove planmode", { stdio: "inherit" });
|
|
32
|
+
logger.success("Planmode MCP server removed from Claude Code.");
|
|
33
|
+
} catch (err) {
|
|
34
|
+
logger.error(
|
|
35
|
+
"Failed to remove MCP server. Make sure Claude Code CLI is installed.",
|
|
36
|
+
);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
});
|
package/src/commands/publish.ts
CHANGED
|
@@ -1,201 +1,13 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import {
|
|
3
|
-
import { getGitHubToken } from "../lib/config.js";
|
|
4
|
-
import { getRemoteUrl, getHeadSha, createTag, pushTag } from "../lib/git.js";
|
|
2
|
+
import { publishPackage } from "../lib/publisher.js";
|
|
5
3
|
import { logger } from "../lib/logger.js";
|
|
6
4
|
|
|
7
5
|
export const publishCommand = new Command("publish")
|
|
8
6
|
.description("Publish the current directory as a package to the registry")
|
|
9
7
|
.action(async () => {
|
|
10
8
|
try {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
// Check auth
|
|
14
|
-
const token = getGitHubToken();
|
|
15
|
-
if (!token) {
|
|
16
|
-
logger.error("Not authenticated. Run `planmode login` first.");
|
|
17
|
-
process.exit(1);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// Read and validate manifest
|
|
21
|
-
logger.info("Reading planmode.yaml...");
|
|
22
|
-
const manifest = readManifest(cwd);
|
|
23
|
-
const errors = validateManifest(manifest, true);
|
|
24
|
-
if (errors.length > 0) {
|
|
25
|
-
logger.error("Invalid manifest:");
|
|
26
|
-
for (const err of errors) {
|
|
27
|
-
console.log(` - ${err}`);
|
|
28
|
-
}
|
|
29
|
-
process.exit(1);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Check git remote
|
|
33
|
-
const remoteUrl = await getRemoteUrl(cwd);
|
|
34
|
-
if (!remoteUrl) {
|
|
35
|
-
logger.error("No git remote found. Push your code to GitHub first.");
|
|
36
|
-
process.exit(1);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const sha = await getHeadSha(cwd);
|
|
40
|
-
const tag = `v${manifest.version}`;
|
|
41
|
-
|
|
42
|
-
// Create and push tag
|
|
43
|
-
logger.info(`Creating tag ${tag}...`);
|
|
44
|
-
try {
|
|
45
|
-
await createTag(cwd, tag);
|
|
46
|
-
} catch {
|
|
47
|
-
logger.dim(`Tag ${tag} already exists, using existing`);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
try {
|
|
51
|
-
await pushTag(cwd, tag);
|
|
52
|
-
logger.success(`Pushed tag ${tag}`);
|
|
53
|
-
} catch {
|
|
54
|
-
logger.dim(`Tag ${tag} already pushed`);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Fork registry and create PR via GitHub API
|
|
58
|
-
logger.info("Submitting to registry...");
|
|
59
|
-
|
|
60
|
-
const headers = {
|
|
61
|
-
Authorization: `Bearer ${token}`,
|
|
62
|
-
Accept: "application/vnd.github.v3+json",
|
|
63
|
-
"User-Agent": "planmode-cli",
|
|
64
|
-
"Content-Type": "application/json",
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
// Fork the registry repo (idempotent)
|
|
68
|
-
await fetch("https://api.github.com/repos/kaihannonen/planmode.org/forks", {
|
|
69
|
-
method: "POST",
|
|
70
|
-
headers,
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
// Get authenticated user
|
|
74
|
-
const userRes = await fetch("https://api.github.com/user", { headers });
|
|
75
|
-
const user = (await userRes.json()) as { login: string };
|
|
76
|
-
|
|
77
|
-
// Create metadata files content
|
|
78
|
-
const metadataContent = JSON.stringify(
|
|
79
|
-
{
|
|
80
|
-
name: manifest.name,
|
|
81
|
-
description: manifest.description,
|
|
82
|
-
author: manifest.author,
|
|
83
|
-
license: manifest.license,
|
|
84
|
-
repository: remoteUrl
|
|
85
|
-
.replace(/^https?:\/\//, "")
|
|
86
|
-
.replace(/\.git$/, ""),
|
|
87
|
-
category: manifest.category ?? "other",
|
|
88
|
-
tags: manifest.tags ?? [],
|
|
89
|
-
type: manifest.type,
|
|
90
|
-
models: manifest.models ?? [],
|
|
91
|
-
latest_version: manifest.version,
|
|
92
|
-
versions: [manifest.version],
|
|
93
|
-
downloads: 0,
|
|
94
|
-
created_at: new Date().toISOString(),
|
|
95
|
-
updated_at: new Date().toISOString(),
|
|
96
|
-
dependencies: manifest.dependencies,
|
|
97
|
-
variables: manifest.variables,
|
|
98
|
-
},
|
|
99
|
-
null,
|
|
100
|
-
2,
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
const versionContent = JSON.stringify(
|
|
104
|
-
{
|
|
105
|
-
version: manifest.version,
|
|
106
|
-
published_at: new Date().toISOString(),
|
|
107
|
-
source: {
|
|
108
|
-
repository: remoteUrl
|
|
109
|
-
.replace(/^https?:\/\//, "")
|
|
110
|
-
.replace(/\.git$/, ""),
|
|
111
|
-
tag,
|
|
112
|
-
sha,
|
|
113
|
-
},
|
|
114
|
-
files: ["planmode.yaml", manifest.content_file ?? "inline"],
|
|
115
|
-
content_hash: `sha256:${sha.slice(0, 16)}`,
|
|
116
|
-
},
|
|
117
|
-
null,
|
|
118
|
-
2,
|
|
119
|
-
);
|
|
120
|
-
|
|
121
|
-
// Create branch on fork
|
|
122
|
-
const branchName = `add-${manifest.name}-${manifest.version}`;
|
|
123
|
-
|
|
124
|
-
// Get main branch ref
|
|
125
|
-
const refRes = await fetch(
|
|
126
|
-
`https://api.github.com/repos/${user.login}/planmode.org/git/ref/heads/main`,
|
|
127
|
-
{ headers },
|
|
128
|
-
);
|
|
129
|
-
|
|
130
|
-
if (!refRes.ok) {
|
|
131
|
-
logger.error("Failed to access registry fork. Make sure the fork exists.");
|
|
132
|
-
process.exit(1);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const refData = (await refRes.json()) as { object: { sha: string } };
|
|
136
|
-
const baseSha = refData.object.sha;
|
|
137
|
-
|
|
138
|
-
// Create branch
|
|
139
|
-
await fetch(`https://api.github.com/repos/${user.login}/planmode.org/git/refs`, {
|
|
140
|
-
method: "POST",
|
|
141
|
-
headers,
|
|
142
|
-
body: JSON.stringify({
|
|
143
|
-
ref: `refs/heads/${branchName}`,
|
|
144
|
-
sha: baseSha,
|
|
145
|
-
}),
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
// Create metadata.json
|
|
149
|
-
await fetch(
|
|
150
|
-
`https://api.github.com/repos/${user.login}/planmode.org/contents/registry/packages/${manifest.name}/metadata.json`,
|
|
151
|
-
{
|
|
152
|
-
method: "PUT",
|
|
153
|
-
headers,
|
|
154
|
-
body: JSON.stringify({
|
|
155
|
-
message: `Add ${manifest.name}@${manifest.version}`,
|
|
156
|
-
content: Buffer.from(metadataContent).toString("base64"),
|
|
157
|
-
branch: branchName,
|
|
158
|
-
}),
|
|
159
|
-
},
|
|
160
|
-
);
|
|
161
|
-
|
|
162
|
-
// Create version file
|
|
163
|
-
await fetch(
|
|
164
|
-
`https://api.github.com/repos/${user.login}/planmode.org/contents/registry/packages/${manifest.name}/versions/${manifest.version}.json`,
|
|
165
|
-
{
|
|
166
|
-
method: "PUT",
|
|
167
|
-
headers,
|
|
168
|
-
body: JSON.stringify({
|
|
169
|
-
message: `Add ${manifest.name}@${manifest.version} version metadata`,
|
|
170
|
-
content: Buffer.from(versionContent).toString("base64"),
|
|
171
|
-
branch: branchName,
|
|
172
|
-
}),
|
|
173
|
-
},
|
|
174
|
-
);
|
|
175
|
-
|
|
176
|
-
// Create PR
|
|
177
|
-
const prRes = await fetch("https://api.github.com/repos/kaihannonen/planmode.org/pulls", {
|
|
178
|
-
method: "POST",
|
|
179
|
-
headers,
|
|
180
|
-
body: JSON.stringify({
|
|
181
|
-
title: `Add ${manifest.name}@${manifest.version}`,
|
|
182
|
-
head: `${user.login}:${branchName}`,
|
|
183
|
-
base: "main",
|
|
184
|
-
body: `## New package: ${manifest.name}\n\n- **Type:** ${manifest.type}\n- **Version:** ${manifest.version}\n- **Description:** ${manifest.description}\n- **Author:** ${manifest.author}\n\nSubmitted via \`planmode publish\`.`,
|
|
185
|
-
}),
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
if (prRes.ok) {
|
|
189
|
-
const pr = (await prRes.json()) as { html_url: string };
|
|
190
|
-
logger.blank();
|
|
191
|
-
logger.success(`Published ${manifest.name}@${manifest.version}`);
|
|
192
|
-
logger.info(`PR: ${pr.html_url}`);
|
|
193
|
-
} else {
|
|
194
|
-
const err = await prRes.text();
|
|
195
|
-
logger.error(`Failed to create PR: ${err}`);
|
|
196
|
-
process.exit(1);
|
|
197
|
-
}
|
|
198
|
-
|
|
9
|
+
logger.blank();
|
|
10
|
+
const result = await publishPackage();
|
|
199
11
|
logger.blank();
|
|
200
12
|
} catch (err) {
|
|
201
13
|
logger.error((err as Error).message);
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { startRecordingAsync, stopRecording, isRecording } from "../lib/recorder.js";
|
|
5
|
+
import { logger } from "../lib/logger.js";
|
|
6
|
+
|
|
7
|
+
export const recordCommand = new Command("record")
|
|
8
|
+
.description("Record git activity and generate a plan from commits");
|
|
9
|
+
|
|
10
|
+
recordCommand
|
|
11
|
+
.command("start")
|
|
12
|
+
.description("Start recording — saves current HEAD as the starting point")
|
|
13
|
+
.action(async () => {
|
|
14
|
+
try {
|
|
15
|
+
logger.blank();
|
|
16
|
+
const sha = await startRecordingAsync();
|
|
17
|
+
logger.success(`Recording started at ${sha.slice(0, 7)}`);
|
|
18
|
+
logger.dim("Work normally. When done, run `planmode record stop` to generate a plan.");
|
|
19
|
+
logger.blank();
|
|
20
|
+
} catch (err) {
|
|
21
|
+
logger.error((err as Error).message);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
recordCommand
|
|
27
|
+
.command("stop")
|
|
28
|
+
.description("Stop recording and generate a plan from commits since start")
|
|
29
|
+
.option("--name <name>", "Package name (auto-inferred if not provided)")
|
|
30
|
+
.option("--author <author>", "Author GitHub username")
|
|
31
|
+
.option("--dir <dir>", "Output directory for the generated package (default: current directory)")
|
|
32
|
+
.action(async (options: { name?: string; author?: string; dir?: string }) => {
|
|
33
|
+
try {
|
|
34
|
+
logger.blank();
|
|
35
|
+
logger.info("Analyzing commits...");
|
|
36
|
+
|
|
37
|
+
const result = await stopRecording(process.cwd(), {
|
|
38
|
+
name: options.name,
|
|
39
|
+
author: options.author,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Write to output directory
|
|
43
|
+
const outDir = options.dir ?? process.cwd();
|
|
44
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
45
|
+
|
|
46
|
+
fs.writeFileSync(path.join(outDir, "planmode.yaml"), result.manifestContent, "utf-8");
|
|
47
|
+
fs.writeFileSync(path.join(outDir, "plan.md"), result.planContent, "utf-8");
|
|
48
|
+
|
|
49
|
+
logger.success(`Generated plan from ${result.totalCommits} commit(s) (${result.totalFilesChanged} files changed)`);
|
|
50
|
+
logger.blank();
|
|
51
|
+
|
|
52
|
+
for (let i = 0; i < result.steps.length; i++) {
|
|
53
|
+
const step = result.steps[i]!;
|
|
54
|
+
logger.dim(` ${i + 1}. ${step.title} (${step.filesChanged.length} files)`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
logger.blank();
|
|
58
|
+
logger.success("Created planmode.yaml and plan.md");
|
|
59
|
+
logger.dim("Edit the generated plan, then run `planmode test` to validate and `planmode publish` when ready.");
|
|
60
|
+
logger.blank();
|
|
61
|
+
} catch (err) {
|
|
62
|
+
logger.error((err as Error).message);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
recordCommand
|
|
68
|
+
.command("status")
|
|
69
|
+
.description("Check if a recording is in progress")
|
|
70
|
+
.action(() => {
|
|
71
|
+
if (isRecording()) {
|
|
72
|
+
logger.info("Recording is in progress. Run `planmode record stop` to generate a plan.");
|
|
73
|
+
} else {
|
|
74
|
+
logger.info("No recording in progress.");
|
|
75
|
+
}
|
|
76
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { takeSnapshot } from "../lib/snapshot.js";
|
|
5
|
+
import { logger } from "../lib/logger.js";
|
|
6
|
+
|
|
7
|
+
export const snapshotCommand = new Command("snapshot")
|
|
8
|
+
.description("Analyze the current project and generate a plan that recreates this setup")
|
|
9
|
+
.option("--name <name>", "Package name (auto-inferred from project)")
|
|
10
|
+
.option("--author <author>", "Author GitHub username")
|
|
11
|
+
.option("--dir <dir>", "Output directory for the generated package (default: current directory)")
|
|
12
|
+
.action((options: { name?: string; author?: string; dir?: string }) => {
|
|
13
|
+
try {
|
|
14
|
+
logger.blank();
|
|
15
|
+
logger.info("Analyzing project...");
|
|
16
|
+
|
|
17
|
+
const result = takeSnapshot(process.cwd(), {
|
|
18
|
+
name: options.name,
|
|
19
|
+
author: options.author,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Write to output directory
|
|
23
|
+
const outDir = options.dir ?? process.cwd();
|
|
24
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
25
|
+
|
|
26
|
+
fs.writeFileSync(path.join(outDir, "planmode.yaml"), result.manifestContent, "utf-8");
|
|
27
|
+
fs.writeFileSync(path.join(outDir, "plan.md"), result.planContent, "utf-8");
|
|
28
|
+
|
|
29
|
+
logger.blank();
|
|
30
|
+
logger.success(`Snapshot: ${result.data.name}`);
|
|
31
|
+
if (result.data.framework) {
|
|
32
|
+
logger.dim(` Framework: ${result.data.framework}`);
|
|
33
|
+
}
|
|
34
|
+
logger.dim(` Dependencies: ${Object.keys(result.data.dependencies).length}`);
|
|
35
|
+
logger.dim(` Dev dependencies: ${Object.keys(result.data.devDependencies).length}`);
|
|
36
|
+
logger.dim(` Tools detected: ${result.data.detectedTools.map((t) => t.name).join(", ") || "none"}`);
|
|
37
|
+
|
|
38
|
+
logger.blank();
|
|
39
|
+
logger.success("Created planmode.yaml and plan.md");
|
|
40
|
+
logger.dim("Edit the generated plan, then run `planmode test` to validate and `planmode publish` when ready.");
|
|
41
|
+
logger.blank();
|
|
42
|
+
} catch (err) {
|
|
43
|
+
logger.error((err as Error).message);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { testPackage } from "../lib/tester.js";
|
|
3
|
+
import { logger } from "../lib/logger.js";
|
|
4
|
+
|
|
5
|
+
export const testCommand = new Command("test")
|
|
6
|
+
.description("Test the current package before publishing: validate manifest, render templates, check dependencies")
|
|
7
|
+
.action(async () => {
|
|
8
|
+
try {
|
|
9
|
+
logger.blank();
|
|
10
|
+
logger.bold("Testing package...");
|
|
11
|
+
logger.blank();
|
|
12
|
+
|
|
13
|
+
const result = await testPackage();
|
|
14
|
+
|
|
15
|
+
for (const check of result.checks) {
|
|
16
|
+
if (check.passed) {
|
|
17
|
+
logger.success(check.name);
|
|
18
|
+
} else {
|
|
19
|
+
const issue = result.issues.find((i) => i.check === check.name);
|
|
20
|
+
if (issue?.severity === "error") {
|
|
21
|
+
logger.error(`${check.name}: ${issue.message}`);
|
|
22
|
+
} else if (issue) {
|
|
23
|
+
logger.warn(`${check.name}: ${issue.message}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
logger.blank();
|
|
29
|
+
if (result.passed) {
|
|
30
|
+
logger.success(`All checks passed. Ready to publish.`);
|
|
31
|
+
} else {
|
|
32
|
+
const errors = result.issues.filter((i) => i.severity === "error");
|
|
33
|
+
const warnings = result.issues.filter((i) => i.severity === "warning");
|
|
34
|
+
logger.error(`${errors.length} error(s), ${warnings.length} warning(s). Fix errors before publishing.`);
|
|
35
|
+
}
|
|
36
|
+
logger.blank();
|
|
37
|
+
|
|
38
|
+
if (!result.passed) {
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
} catch (err) {
|
|
42
|
+
logger.error((err as Error).message);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -9,13 +9,18 @@ import { listCommand } from "./commands/list.js";
|
|
|
9
9
|
import { infoCommand } from "./commands/info.js";
|
|
10
10
|
import { initCommand } from "./commands/init.js";
|
|
11
11
|
import { loginCommand } from "./commands/login.js";
|
|
12
|
+
import { mcpCommand } from "./commands/mcp.js";
|
|
13
|
+
import { doctorCommand } from "./commands/doctor.js";
|
|
14
|
+
import { testCommand } from "./commands/test.js";
|
|
15
|
+
import { recordCommand } from "./commands/record.js";
|
|
16
|
+
import { snapshotCommand } from "./commands/snapshot.js";
|
|
12
17
|
|
|
13
18
|
const program = new Command();
|
|
14
19
|
|
|
15
20
|
program
|
|
16
21
|
.name("planmode")
|
|
17
22
|
.description("The open source package manager for AI plans, rules, and prompts.")
|
|
18
|
-
.version("0.1
|
|
23
|
+
.version("0.2.1");
|
|
19
24
|
|
|
20
25
|
program.addCommand(installCommand);
|
|
21
26
|
program.addCommand(uninstallCommand);
|
|
@@ -27,5 +32,10 @@ program.addCommand(listCommand);
|
|
|
27
32
|
program.addCommand(infoCommand);
|
|
28
33
|
program.addCommand(initCommand);
|
|
29
34
|
program.addCommand(loginCommand);
|
|
35
|
+
program.addCommand(mcpCommand);
|
|
36
|
+
program.addCommand(doctorCommand);
|
|
37
|
+
program.addCommand(testCommand);
|
|
38
|
+
program.addCommand(recordCommand);
|
|
39
|
+
program.addCommand(snapshotCommand);
|
|
30
40
|
|
|
31
41
|
program.parse();
|