plain-forge 1.0.12 → 1.0.13
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 +32 -23
- package/bin/cli.mjs +105 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -95,6 +95,27 @@ Each deprecated file is confirmed individually before it's deleted — you'll se
|
|
|
95
95
|
|
|
96
96
|
Installs that predate the manifest (anyone who installed before this feature existed) have no manifest to read. `update` still finds them by their skill footprint: if the `forge-plain`, `add-feature`, `debug-specs`, and `load-plain-reference` skills are all present in an agent directory, it's treated as a plain-forge install. Such installs are refreshed without pruning (overwrite-only), and gain a manifest going forward so later updates can prune.
|
|
97
97
|
|
|
98
|
+
#### Removing an install
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
npx plain-forge uninstall
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`uninstall` reads the install manifest (`<agent-dir>/.plain-forge/manifest.json`) and deletes **exactly** the files plain-forge wrote, then the manifest itself, then any directory left empty (including the agent directory). Your own skills and third-party content are never in the manifest, so they are never touched.
|
|
105
|
+
|
|
106
|
+
By default it removes **every** agent layout in the **project** scope (the current folder). Narrow it with flags:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
npx plain-forge uninstall --agent claude --scope global
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
| Flag | Default | Values |
|
|
113
|
+
|------|---------|--------|
|
|
114
|
+
| `--agent` | `*` (all agents) | `claude`, `codex`, `forgecode`, `universal`, or `*` |
|
|
115
|
+
| `--scope` | `project` | `project` (cwd) or `global` (home directory) |
|
|
116
|
+
|
|
117
|
+
If an install has **no manifest** (e.g. one that predates manifests), `uninstall` cannot tell which files are plain-forge's, so it refuses to delete anything: it prints an error, lists the directories to clean up by hand, and exits non-zero. Refresh such an install with `update` first (which writes a manifest going forward), then `uninstall`.
|
|
118
|
+
|
|
98
119
|
## Usage
|
|
99
120
|
|
|
100
121
|
### Prerequisites
|
|
@@ -154,36 +175,24 @@ Hit a bug in the rendered app, a failing test, or behavior that doesn't match wh
|
|
|
154
175
|
|
|
155
176
|
## Repository Structure
|
|
156
177
|
|
|
157
|
-
plain-forge keeps a single canonical source of truth under `forge
|
|
178
|
+
plain-forge keeps a single canonical source of truth under `forge/`. The `plain-forge install` CLI copies that content straight into whichever agent directory you choose — there is no build step and no generated, committed output to keep in sync.
|
|
158
179
|
|
|
159
180
|
```
|
|
160
|
-
forge/ # canonical,
|
|
181
|
+
forge/ # canonical content, copied verbatim on install
|
|
161
182
|
skills/ # all skills used during spec writing
|
|
162
|
-
rules/ #
|
|
163
|
-
|
|
164
|
-
runtimes/ # per-runtime adapters
|
|
165
|
-
claude/
|
|
166
|
-
build.ts # generates .claude/ + .claude-plugin/ from forge/
|
|
167
|
-
templates/ # Claude-specific files: settings.json, hook script, plugin manifests
|
|
168
|
-
codex/
|
|
169
|
-
build.ts # generates .codex-plugin/ and .agents/plugins/ (manifest points at forge/skills/)
|
|
170
|
-
templates/ # Codex-specific files: plugin.json, marketplace catalog
|
|
171
|
-
opencode/
|
|
172
|
-
build.ts # generates .opencode/ from forge/
|
|
173
|
-
templates/ # OpenCode-specific files: package.json, .gitignore
|
|
183
|
+
rules/ # spec-writing rules (installed as workspace instructions)
|
|
174
184
|
|
|
175
185
|
bin/
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
#
|
|
180
|
-
|
|
181
|
-
.
|
|
182
|
-
.codex-plugin/ # Codex plugin manifest (its "skills" field points at forge/skills/)
|
|
183
|
-
.agents/plugins/ # Codex marketplace catalog
|
|
184
|
-
.opencode/ # OpenCode plugin layout
|
|
186
|
+
cli.mjs # the `plain-forge` CLI — `install` and `update` commands
|
|
187
|
+
|
|
188
|
+
test/
|
|
189
|
+
cli.test.mjs # tests for the install / update CLI
|
|
190
|
+
|
|
191
|
+
package.json # ships only `bin/cli.mjs` and `forge/` to npm
|
|
185
192
|
```
|
|
186
193
|
|
|
194
|
+
On `install`, the CLI reads `forge/skills` and `forge/rules` and writes them into the chosen agent directory (`.claude/`, `.codex/`, `.forgecode/`, or `.agents/`), recording every file it wrote in `<agent-dir>/.plain-forge/manifest.json` so `update` can later refresh and prune precisely.
|
|
195
|
+
|
|
187
196
|
## Available Skills
|
|
188
197
|
|
|
189
198
|
### Core Workflow
|
package/bin/cli.mjs
CHANGED
|
@@ -78,8 +78,9 @@ function usage() {
|
|
|
78
78
|
console.log(`Usage: plain-forge <command> [options]
|
|
79
79
|
|
|
80
80
|
Commands:
|
|
81
|
-
install
|
|
82
|
-
update
|
|
81
|
+
install Install plain-forge into an agent directory
|
|
82
|
+
update Refresh every existing plain-forge install in cwd and $HOME
|
|
83
|
+
uninstall Remove a plain-forge install using its manifest
|
|
83
84
|
|
|
84
85
|
Install options:
|
|
85
86
|
--agent <claude|codex|forgecode|universal> Target agent layout
|
|
@@ -90,16 +91,25 @@ Update options:
|
|
|
90
91
|
-y, --yes Remove deprecated files without
|
|
91
92
|
confirming each one
|
|
92
93
|
|
|
94
|
+
Uninstall options:
|
|
95
|
+
--agent <claude|codex|forgecode|universal|*> Which install to remove
|
|
96
|
+
(default: * — every agent)
|
|
97
|
+
--scope <project|global> Where to look (default: project)
|
|
98
|
+
|
|
93
99
|
Examples:
|
|
94
100
|
plain-forge install --agent claude --scope project
|
|
95
101
|
plain-forge install --agent universal --scope global
|
|
96
102
|
plain-forge update
|
|
97
103
|
plain-forge update --yes
|
|
104
|
+
plain-forge uninstall
|
|
105
|
+
plain-forge uninstall --agent claude --scope global
|
|
98
106
|
|
|
99
107
|
"install" fails if plain-forge is already installed at the target — use
|
|
100
108
|
"update" to refresh it. Missing install flags are prompted interactively.
|
|
101
109
|
"update" auto-detects installs and prunes only files plain-forge wrote
|
|
102
|
-
(confirming each removal), leaving your own and third-party skills untouched
|
|
110
|
+
(confirming each removal), leaving your own and third-party skills untouched.
|
|
111
|
+
"uninstall" deletes exactly the files recorded in the install manifest, then
|
|
112
|
+
the manifest itself; an install with no manifest is reported and left in place.`);
|
|
103
113
|
}
|
|
104
114
|
|
|
105
115
|
function parseArgs(argv) {
|
|
@@ -488,6 +498,95 @@ async function cmdUpdate(args) {
|
|
|
488
498
|
}
|
|
489
499
|
}
|
|
490
500
|
|
|
501
|
+
async function cmdUninstall(args) {
|
|
502
|
+
printBanner();
|
|
503
|
+
|
|
504
|
+
const scope = args.scope ?? "project";
|
|
505
|
+
if (!SCOPES.includes(scope)) {
|
|
506
|
+
console.error(`unknown scope "${scope}". valid: ${SCOPES.join(", ")}`);
|
|
507
|
+
process.exit(2);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Default agent is "*" — every agent layout. A named agent narrows to one.
|
|
511
|
+
const agentArg = args.agent ?? "*";
|
|
512
|
+
let agents;
|
|
513
|
+
if (agentArg === "*" || agentArg === "all") {
|
|
514
|
+
agents = Object.keys(AGENTS);
|
|
515
|
+
} else if (Object.hasOwn(AGENTS, agentArg)) {
|
|
516
|
+
agents = [agentArg];
|
|
517
|
+
} else {
|
|
518
|
+
console.error(
|
|
519
|
+
`unknown agent "${agentArg}". valid: ${Object.keys(AGENTS).join(", ")}, or "*" for all`,
|
|
520
|
+
);
|
|
521
|
+
process.exit(2);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const root = scope === "global" ? os.homedir() : process.cwd();
|
|
525
|
+
|
|
526
|
+
let found = 0;
|
|
527
|
+
let removed = 0;
|
|
528
|
+
let hadError = false;
|
|
529
|
+
|
|
530
|
+
for (const agent of agents) {
|
|
531
|
+
const baseDir = path.join(root, AGENTS[agent]);
|
|
532
|
+
if (!fs.existsSync(baseDir)) continue;
|
|
533
|
+
|
|
534
|
+
const manifest = readManifest(baseDir);
|
|
535
|
+
const legacy = !manifest && hasForgeSignature(baseDir);
|
|
536
|
+
if (!manifest && !legacy) continue; // not a plain-forge install
|
|
537
|
+
found++;
|
|
538
|
+
|
|
539
|
+
// No manifest → we have no record of which files are ours, so deleting
|
|
540
|
+
// would risk touching the user's own content. Refuse and point at the
|
|
541
|
+
// directories to clean by hand.
|
|
542
|
+
if (!manifest) {
|
|
543
|
+
hadError = true;
|
|
544
|
+
console.error(`cannot uninstall ${agent} (${scope}) — ${baseDir}`);
|
|
545
|
+
console.error(
|
|
546
|
+
` the install manifest (${MANIFEST_REL}) is missing, so automatic deletion is not supported.`,
|
|
547
|
+
);
|
|
548
|
+
console.error(` please remove plain-forge's files manually from:`);
|
|
549
|
+
for (const dir of CONTENT_DIRS) {
|
|
550
|
+
const p = path.join(baseDir, dir);
|
|
551
|
+
if (fs.existsSync(p)) console.error(` ${p}`);
|
|
552
|
+
}
|
|
553
|
+
console.error("");
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
let deleted = 0;
|
|
558
|
+
let failed = 0;
|
|
559
|
+
for (const rel of manifest.files) {
|
|
560
|
+
if (deleteForgeFile(baseDir, rel)) deleted++;
|
|
561
|
+
else failed++;
|
|
562
|
+
}
|
|
563
|
+
// Finally remove the manifest itself, then prune the now-empty .plain-forge
|
|
564
|
+
// directory and the agent directory if nothing else remains in it.
|
|
565
|
+
fs.rmSync(manifestPathFor(baseDir), { force: true });
|
|
566
|
+
removeEmptyDirsUpward(
|
|
567
|
+
path.join(baseDir, path.dirname(MANIFEST_REL)),
|
|
568
|
+
baseDir,
|
|
569
|
+
);
|
|
570
|
+
removeEmptyDirsUpward(baseDir, root);
|
|
571
|
+
|
|
572
|
+
console.log(`uninstalled ${agent} (${scope}) from ${baseDir}`);
|
|
573
|
+
console.log(
|
|
574
|
+
` removed ${deleted} file(s)${failed ? `, ${failed} could not be removed` : ""} + manifest`,
|
|
575
|
+
);
|
|
576
|
+
console.log();
|
|
577
|
+
removed++;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
if (found === 0) {
|
|
581
|
+
console.log(`no plain-forge installation found in ${root} (scope: ${scope}).`);
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
if (removed > 0) {
|
|
585
|
+
console.log(`uninstalled ${removed} installation(s).`);
|
|
586
|
+
}
|
|
587
|
+
if (hadError) process.exit(1);
|
|
588
|
+
}
|
|
589
|
+
|
|
491
590
|
function printNextSteps(agent) {
|
|
492
591
|
const bold = (s) => `\x1b[1;97m${s}\x1b[0m`;
|
|
493
592
|
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
@@ -548,6 +647,9 @@ async function main() {
|
|
|
548
647
|
case "update":
|
|
549
648
|
await cmdUpdate(args);
|
|
550
649
|
break;
|
|
650
|
+
case "uninstall":
|
|
651
|
+
await cmdUninstall(args);
|
|
652
|
+
break;
|
|
551
653
|
default:
|
|
552
654
|
console.error(`unknown command "${cmd}"`);
|
|
553
655
|
usage();
|