plain-forge 1.0.1 → 1.0.2
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 -34
- package/bin/cli.mjs +83 -35
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,73 +19,82 @@ Each phase is **incremental**, not a single long questionnaire. plain-forge walk
|
|
|
19
19
|
|
|
20
20
|
## Getting Started
|
|
21
21
|
|
|
22
|
-
plain-forge ships as a set of skills that plug into your AI coding tool of choice. Install it once, then invoke `forge-plain` (or `add-feature` to add a feature to an existing ***plain project) from any project.
|
|
22
|
+
plain-forge ships as a set of skills, rules, and docs that plug into your AI coding tool of choice. Install it once, then invoke `forge-plain` (or `add-feature` to add a feature to an existing ***plain project) from any project.
|
|
23
23
|
|
|
24
|
-
### Install with
|
|
24
|
+
### Install with `npx plain-forge install` (recommended)
|
|
25
25
|
|
|
26
|
-
The
|
|
26
|
+
The primary install path. Works for every supported runtime and is the only installer that ships **all** plain-forge content (skills, rules, **and** docs) — the other methods below are limited or agent-specific.
|
|
27
27
|
|
|
28
28
|
```bash
|
|
29
|
-
npx
|
|
29
|
+
npx plain-forge install
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
If you only use one runtime, pass `--agent` to target just that one (you can repeat the flag to pick several):
|
|
32
|
+
This prompts you to pick an agent and a scope using an arrow-key menu. You can also pass both flags non-interactively:
|
|
35
33
|
|
|
36
34
|
```bash
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
npx plain-forge install --agent claude --scope project
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Agent options:**
|
|
39
|
+
|
|
40
|
+
| `--agent` | Installs into | Use when |
|
|
41
|
+
|-----------|---------------|----------|
|
|
42
|
+
| `claude` | `.claude/` | You use Claude Code |
|
|
43
|
+
| `codex` | `.codex/` | You use the OpenAI Codex CLI |
|
|
44
|
+
| `forgecode` | `.forgecode/` | You use ForgeCode |
|
|
45
|
+
| `universal` | `.agents/` | You want a runtime-neutral layout that any agent reading from `.agents/` can pick up |
|
|
39
46
|
|
|
40
|
-
|
|
41
|
-
npx skills add Codeplain-ai/plain-forge --skill '*' --agent codex
|
|
47
|
+
**Scope options:**
|
|
42
48
|
|
|
43
|
-
|
|
44
|
-
|
|
49
|
+
| `--scope` | Installs into | Use when |
|
|
50
|
+
|-----------|---------------|----------|
|
|
51
|
+
| `project` | `./<agent-dir>/` in the current working directory | You want plain-forge in just this project |
|
|
52
|
+
| `global` | `~/<agent-dir>/` in your home directory | You want plain-forge available in every project on the machine |
|
|
45
53
|
|
|
46
|
-
|
|
47
|
-
|
|
54
|
+
Each install writes three subfolders under the chosen directory:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
<agent-dir>/
|
|
58
|
+
skills/ # every plain-forge skill
|
|
59
|
+
rules/ # spec-writing rules (loaded as workspace instructions)
|
|
60
|
+
docs/ # shared reference docs
|
|
48
61
|
```
|
|
49
62
|
|
|
50
|
-
|
|
63
|
+
Re-running `npx plain-forge install` overwrites the destination silently, so it doubles as the upgrade path — `npx plain-forge@latest install` pulls the newest release every time.
|
|
51
64
|
|
|
52
|
-
###
|
|
65
|
+
### Alternative install paths (skills only — no rules or docs)
|
|
53
66
|
|
|
54
|
-
|
|
67
|
+
These work but only install the skill files. Rules and docs do **not** travel with them, so use them only if you have a reason not to use `npx plain-forge install`.
|
|
55
68
|
|
|
56
|
-
|
|
69
|
+
#### `npx skills` CLI
|
|
57
70
|
|
|
58
|
-
```
|
|
59
|
-
|
|
71
|
+
```bash
|
|
72
|
+
npx skills add Codeplain-ai/plain-forge --skill '*' --agent claude-code
|
|
60
73
|
```
|
|
61
74
|
|
|
62
|
-
|
|
75
|
+
Replace `--agent claude-code` with `codex` or `opencode` to target a different runtime, or repeat the flag for several at once.
|
|
63
76
|
|
|
64
|
-
|
|
65
|
-
/plugin install plain-forge@plain-forge
|
|
66
|
-
```
|
|
77
|
+
#### Claude Code native plugin flow
|
|
67
78
|
|
|
68
|
-
|
|
79
|
+
Requires the [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code). Inside a Claude Code session, run the following **three commands** one after the other:
|
|
69
80
|
|
|
70
81
|
```text
|
|
82
|
+
/plugin marketplace add Codeplain-ai/plain-forge
|
|
83
|
+
/plugin install plain-forge@plain-forge
|
|
71
84
|
/reload-plugins
|
|
72
85
|
```
|
|
73
86
|
|
|
74
|
-
Without the reload
|
|
87
|
+
Without the reload the skills won't appear in the current session.
|
|
75
88
|
|
|
76
|
-
|
|
89
|
+
#### Codex native plugin flow
|
|
77
90
|
|
|
78
|
-
Requires the [OpenAI Codex CLI](https://developers.openai.com/codex/cli/reference)
|
|
79
|
-
|
|
80
|
-
**1.** From your shell, register this repository as a Codex marketplace:
|
|
91
|
+
Requires the [OpenAI Codex CLI](https://developers.openai.com/codex/cli/reference). From your shell:
|
|
81
92
|
|
|
82
93
|
```bash
|
|
83
94
|
codex plugin marketplace add Codeplain-ai/plain-forge
|
|
84
95
|
```
|
|
85
96
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
Once the plugin is installed, all plain-forge skills become available in your Codex sessions.
|
|
97
|
+
Then, inside Codex, open the plugin directory, pick the `plain-forge` marketplace, and install the plugin from there. (Codex's CLI does not currently expose a `codex plugin install` equivalent.)
|
|
89
98
|
|
|
90
99
|
## Usage
|
|
91
100
|
|
package/bin/cli.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import readline from "node:readline
|
|
5
|
+
import readline from "node:readline";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
|
|
8
8
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -23,49 +23,96 @@ function usage() {
|
|
|
23
23
|
Options:
|
|
24
24
|
--agent <claude|codex|forgecode|universal> Target agent layout
|
|
25
25
|
--scope <project|global> Install into cwd or $HOME
|
|
26
|
-
--skill <name> Install only the named skill (repeatable; default: all)
|
|
27
26
|
-h, --help Show this help
|
|
28
27
|
|
|
29
28
|
Examples:
|
|
30
29
|
plain-forge install --agent claude --scope project
|
|
31
30
|
plain-forge install --agent universal --scope global
|
|
32
|
-
plain-forge install --agent claude --skill add-functional-spec --skill add-concept
|
|
33
31
|
|
|
34
32
|
Missing flags are prompted interactively.`);
|
|
35
33
|
}
|
|
36
34
|
|
|
37
35
|
function parseArgs(argv) {
|
|
38
|
-
const out = { _: []
|
|
36
|
+
const out = { _: [] };
|
|
39
37
|
for (let i = 0; i < argv.length; i++) {
|
|
40
38
|
const a = argv[i];
|
|
41
39
|
if (a === "--agent") out.agent = argv[++i];
|
|
42
40
|
else if (a === "--scope") out.scope = argv[++i];
|
|
43
|
-
else if (a === "--skill") out.skills.push(argv[++i]);
|
|
44
41
|
else if (a === "-h" || a === "--help") out.help = true;
|
|
45
42
|
else out._.push(a);
|
|
46
43
|
}
|
|
47
44
|
return out;
|
|
48
45
|
}
|
|
49
46
|
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
rl.close();
|
|
47
|
+
function promptChoice(question, choices) {
|
|
48
|
+
const input = process.stdin;
|
|
49
|
+
const output = process.stdout;
|
|
50
|
+
if (!input.isTTY) {
|
|
51
|
+
return Promise.reject(
|
|
52
|
+
new Error(
|
|
53
|
+
`cannot prompt for "${question}" — stdin is not a TTY. Pass the value as a flag instead.`,
|
|
54
|
+
),
|
|
55
|
+
);
|
|
60
56
|
}
|
|
57
|
+
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
let index = 0;
|
|
60
|
+
let rendered = 0;
|
|
61
|
+
|
|
62
|
+
const render = () => {
|
|
63
|
+
if (rendered > 0) {
|
|
64
|
+
readline.moveCursor(output, 0, -rendered);
|
|
65
|
+
readline.clearScreenDown(output);
|
|
66
|
+
}
|
|
67
|
+
output.write(`? ${question} (use arrow keys, enter to select)\n`);
|
|
68
|
+
for (let i = 0; i < choices.length; i++) {
|
|
69
|
+
const pointer = i === index ? "\x1b[36m>\x1b[0m" : " ";
|
|
70
|
+
const label = i === index ? `\x1b[36m${choices[i]}\x1b[0m` : choices[i];
|
|
71
|
+
output.write(` ${pointer} ${label}\n`);
|
|
72
|
+
}
|
|
73
|
+
rendered = choices.length + 1;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const cleanup = () => {
|
|
77
|
+
input.removeListener("keypress", onKey);
|
|
78
|
+
input.setRawMode(false);
|
|
79
|
+
input.pause();
|
|
80
|
+
output.write("\x1b[?25h"); // show cursor
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const onKey = (_str, key) => {
|
|
84
|
+
if (!key) return;
|
|
85
|
+
if (key.name === "up" || (key.ctrl && key.name === "p")) {
|
|
86
|
+
index = (index - 1 + choices.length) % choices.length;
|
|
87
|
+
render();
|
|
88
|
+
} else if (key.name === "down" || (key.ctrl && key.name === "n")) {
|
|
89
|
+
index = (index + 1) % choices.length;
|
|
90
|
+
render();
|
|
91
|
+
} else if (key.name === "return") {
|
|
92
|
+
cleanup();
|
|
93
|
+
output.write(` \x1b[32m${choices[index]}\x1b[0m\n`);
|
|
94
|
+
resolve(choices[index]);
|
|
95
|
+
} else if (key.name === "escape" || (key.ctrl && key.name === "c")) {
|
|
96
|
+
cleanup();
|
|
97
|
+
output.write("\n");
|
|
98
|
+
reject(new Error("cancelled"));
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
readline.emitKeypressEvents(input);
|
|
103
|
+
input.setRawMode(true);
|
|
104
|
+
input.resume();
|
|
105
|
+
output.write("\x1b[?25l"); // hide cursor
|
|
106
|
+
input.on("keypress", onKey);
|
|
107
|
+
render();
|
|
108
|
+
});
|
|
61
109
|
}
|
|
62
110
|
|
|
63
|
-
function copyTree(srcDir, destDir
|
|
111
|
+
function copyTree(srcDir, destDir) {
|
|
64
112
|
if (!fs.existsSync(srcDir)) return 0;
|
|
65
113
|
fs.mkdirSync(destDir, { recursive: true });
|
|
66
114
|
let count = 0;
|
|
67
115
|
for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
|
|
68
|
-
if (filterNames && !filterNames.has(entry.name)) continue;
|
|
69
116
|
const src = path.join(srcDir, entry.name);
|
|
70
117
|
const dest = path.join(destDir, entry.name);
|
|
71
118
|
if (entry.isDirectory()) {
|
|
@@ -82,7 +129,9 @@ async function cmdInstall(args) {
|
|
|
82
129
|
let agent = args.agent;
|
|
83
130
|
if (!agent) agent = await promptChoice("Which agent?", Object.keys(AGENTS));
|
|
84
131
|
if (!Object.hasOwn(AGENTS, agent)) {
|
|
85
|
-
console.error(
|
|
132
|
+
console.error(
|
|
133
|
+
`unknown agent "${agent}". valid: ${Object.keys(AGENTS).join(", ")}`,
|
|
134
|
+
);
|
|
86
135
|
process.exit(2);
|
|
87
136
|
}
|
|
88
137
|
|
|
@@ -96,22 +145,18 @@ async function cmdInstall(args) {
|
|
|
96
145
|
const root = scope === "global" ? os.homedir() : process.cwd();
|
|
97
146
|
const baseDir = path.join(root, AGENTS[agent]);
|
|
98
147
|
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const skillsCount = copyTree(skillsSrc, path.join(baseDir, "skills"), skillFilter);
|
|
113
|
-
const rulesCount = copyTree(path.join(forgeDir, "rules"), path.join(baseDir, "rules"), null);
|
|
114
|
-
const docsCount = copyTree(path.join(forgeDir, "docs"), path.join(baseDir, "docs"), null);
|
|
148
|
+
const skillsCount = copyTree(
|
|
149
|
+
path.join(forgeDir, "skills"),
|
|
150
|
+
path.join(baseDir, "skills"),
|
|
151
|
+
);
|
|
152
|
+
const rulesCount = copyTree(
|
|
153
|
+
path.join(forgeDir, "rules"),
|
|
154
|
+
path.join(baseDir, "rules"),
|
|
155
|
+
);
|
|
156
|
+
const docsCount = copyTree(
|
|
157
|
+
path.join(forgeDir, "docs"),
|
|
158
|
+
path.join(baseDir, "docs"),
|
|
159
|
+
);
|
|
115
160
|
|
|
116
161
|
console.log(`installed into ${baseDir}`);
|
|
117
162
|
console.log(` skills: ${skillsCount}`);
|
|
@@ -138,6 +183,9 @@ async function main() {
|
|
|
138
183
|
}
|
|
139
184
|
|
|
140
185
|
main().catch((err) => {
|
|
141
|
-
|
|
186
|
+
if (err instanceof Error && err.message === "cancelled") {
|
|
187
|
+
process.exit(130);
|
|
188
|
+
}
|
|
189
|
+
console.error(err instanceof Error ? (err.stack ?? err.message) : err);
|
|
142
190
|
process.exit(1);
|
|
143
191
|
});
|