@friedbotstudio/create-baseline 0.1.0 → 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/README.md
CHANGED
|
@@ -106,8 +106,13 @@ npx @friedbotstudio/create-baseline ./your-project --dry-run
|
|
|
106
106
|
|
|
107
107
|
# Skip the install-time PlantUML jar download
|
|
108
108
|
npx @friedbotstudio/create-baseline ./your-project --no-plantuml
|
|
109
|
+
|
|
110
|
+
# Materialize a security-hardened target/.npmrc (opt-in)
|
|
111
|
+
npx @friedbotstudio/create-baseline ./your-project --with-npmrc
|
|
109
112
|
```
|
|
110
113
|
|
|
114
|
+
By default the scaffolder writes only inside `.claude/`, plus `CLAUDE.md`, `.mcp.json`, and `docs/init/seed.md`. Pass `--with-npmrc` to also drop `ignore-scripts=true` + `min-release-age=7` into `target/.npmrc`. Those defaults blunt the npm post-install-hook attack class and delay consumption of fresh malicious publishes. An existing `target/.npmrc` is preserved verbatim. Operators who already set these defaults in `~/.npmrc` don't need the flag.
|
|
115
|
+
|
|
111
116
|
### Doctor
|
|
112
117
|
|
|
113
118
|
```bash
|
package/bin/cli.js
CHANGED
|
@@ -43,6 +43,11 @@ PlantUML jar (~19 MB, fetched at install time from upstream):
|
|
|
43
43
|
--no-plantuml Skip the jar download entirely.
|
|
44
44
|
--require-plantuml Treat jar fetch failure (network/sha256) as fatal (exit 4).
|
|
45
45
|
|
|
46
|
+
npm posture (off by default):
|
|
47
|
+
--with-npmrc Materialize a security-hardened target/.npmrc
|
|
48
|
+
(ignore-scripts=true, min-release-age=7). Existing
|
|
49
|
+
target/.npmrc is preserved verbatim.
|
|
50
|
+
|
|
46
51
|
Misc:
|
|
47
52
|
--help, -h Show this message.
|
|
48
53
|
--version Print version.
|
|
@@ -97,6 +102,7 @@ async function main(argv) {
|
|
|
97
102
|
'dry-run': { type: 'boolean' },
|
|
98
103
|
'no-plantuml': { type: 'boolean' },
|
|
99
104
|
'require-plantuml': { type: 'boolean' },
|
|
105
|
+
'with-npmrc': { type: 'boolean' },
|
|
100
106
|
strict: { type: 'boolean' },
|
|
101
107
|
},
|
|
102
108
|
strict: true,
|
|
@@ -209,10 +215,10 @@ async function main(argv) {
|
|
|
209
215
|
}
|
|
210
216
|
} else if (values.force) {
|
|
211
217
|
if (dryRun) io.log(`Would force-install into ${target}`);
|
|
212
|
-
else await forceInstall(templateDir, target);
|
|
218
|
+
else await forceInstall(templateDir, target, { withNpmrc: !!values['with-npmrc'] });
|
|
213
219
|
} else {
|
|
214
220
|
if (dryRun) io.log(`Would fresh-install into ${target}`);
|
|
215
|
-
else await freshInstall(templateDir, target);
|
|
221
|
+
else await freshInstall(templateDir, target, { withNpmrc: !!values['with-npmrc'] });
|
|
216
222
|
}
|
|
217
223
|
} catch (err) {
|
|
218
224
|
io.error(`install failed: ${err.message}`);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 2,
|
|
3
|
-
"generated_at": "2026-05-
|
|
3
|
+
"generated_at": "2026-05-14T19:28:02.322Z",
|
|
4
4
|
"files": {
|
|
5
5
|
".claude/agents/swarm-worker.md": "1735a220f268c9765cb22e0567b728803f2edd7776cbde51dd017a9f062ae41f",
|
|
6
6
|
".claude/bin/LICENSE": "a8dcf2775ab71a58c7d4cc935e3a8e9974e87bb7d6082ee25ef52f8140be8e07",
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
".claude/hooks/env_guard.sh": "76ffc98fa9a2709526715132981699e46043183e2b1d043a05744a40384d1361",
|
|
16
16
|
".claude/hooks/git_commit_guard.sh": "1a78799f35cae7b52ee29d8adcddc5cd29427822c223a3be6e0d181f1e8e71c2",
|
|
17
17
|
".claude/hooks/harness_continuation.sh": "81b40138a66b24d93fcd8fde57f2f21804667952f466d84c2af7c9307c75b117",
|
|
18
|
-
".claude/hooks/lib/__pycache__/resume_writer.cpython-314.pyc": "4c9afd22f475848f4007111bf325b406924faef16a6e90e963399febf3075cab",
|
|
19
18
|
".claude/hooks/lib/common.sh": "2c72b867acf08550f62f80c6b57024d35edd74260a9fd7ac6c709e71fd812319",
|
|
20
19
|
".claude/hooks/lib/resume_writer.py": "9592fb72de966cb797e51322fbf0160a6784de34934bd6587768689a34bd03b5",
|
|
21
20
|
".claude/hooks/lint_runner.sh": "267e08dbd9af246c0af124c32ad4aeac48dfe20ac926244ed89855deb7ff7940",
|
|
@@ -210,5 +209,6 @@
|
|
|
210
209
|
"triage": "baseline",
|
|
211
210
|
"verify": "baseline"
|
|
212
211
|
}
|
|
213
|
-
}
|
|
212
|
+
},
|
|
213
|
+
"build_id": "gha-25880800910"
|
|
214
214
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@friedbotstudio/create-baseline",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Zero-dependency Node CLI scaffolder that materializes the Claude Code baseline (hooks, skills, commands, MCP servers, governance docs) into a target project. Run via `npx @friedbotstudio/create-baseline <target>`.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"build:site": "eleventy",
|
|
23
23
|
"dev:site": "eleventy --serve --port=4321",
|
|
24
24
|
"publish:check": "bash scripts/publish-check.sh",
|
|
25
|
-
"publish:precheck": "npm
|
|
25
|
+
"publish:precheck": "npm pack --dry-run",
|
|
26
26
|
"publish:files-diff": "node scripts/check-files-diff.mjs",
|
|
27
27
|
"publish:smoke": "node scripts/smoke-tarball.mjs",
|
|
28
28
|
"release": "semantic-release"
|
|
@@ -38,6 +38,11 @@
|
|
|
38
38
|
"type": "git",
|
|
39
39
|
"url": "git+https://github.com/friedbotstudio/baseline.git"
|
|
40
40
|
},
|
|
41
|
+
"author": {
|
|
42
|
+
"name": "Friedbot Studio Pvt Ltd",
|
|
43
|
+
"email": "hello@friedbotstudio.com"
|
|
44
|
+
},
|
|
45
|
+
"homepage": "https://baseline.friedbotstudio.com",
|
|
41
46
|
"devDependencies": {
|
|
42
47
|
"@11ty/eleventy": "3.1.5",
|
|
43
48
|
"@semantic-release/changelog": "6.0.3",
|
package/src/cli/install.js
CHANGED
|
@@ -12,6 +12,15 @@ const NPMRC_TEMPLATE_PATH = join(PACKAGE_ROOT, 'src/.npmrc.template');
|
|
|
12
12
|
|
|
13
13
|
export const NEVER_TOUCH = Object.freeze(['.claude/project.json']);
|
|
14
14
|
export const SPECIAL_MERGE = Object.freeze(['.mcp.json']);
|
|
15
|
+
// Files present in the shipped template that must NOT be cp'd to target. These
|
|
16
|
+
// are reference artifacts the CLI consults from templateDir (or that ship for
|
|
17
|
+
// inspection-time provenance), never materialized at consumer project root.
|
|
18
|
+
// `manifest.json`: the shipped sha256 table. The CLI's runtime manifest lives
|
|
19
|
+
// at `target/.claude/.baseline-manifest.json` (written by writeBaselineManifest);
|
|
20
|
+
// `target/manifest.json` would be a confusing duplicate. Keep the file in the
|
|
21
|
+
// published tarball so anyone inspecting `node_modules/<pkg>/obj/template/` can
|
|
22
|
+
// see what shipped, but exclude it from the fresh/force install copy.
|
|
23
|
+
export const COPY_EXCLUDE = Object.freeze(['manifest.json']);
|
|
15
24
|
|
|
16
25
|
async function listFiles(root, base = root, acc = []) {
|
|
17
26
|
for (const entry of await readdir(root, { withFileTypes: true })) {
|
|
@@ -37,6 +46,7 @@ function makeFilter(opts) {
|
|
|
37
46
|
return (src, _dest) => {
|
|
38
47
|
const rel = relative(opts.templateRoot, src).split(sep).join('/');
|
|
39
48
|
if (rel === '') return true;
|
|
49
|
+
if (COPY_EXCLUDE.includes(rel)) return false;
|
|
40
50
|
if (NEVER_TOUCH.includes(rel) && opts.skipNeverTouch) return false;
|
|
41
51
|
if (SPECIAL_MERGE.includes(rel) && opts.skipSpecialMerge) return false;
|
|
42
52
|
return true;
|
|
@@ -76,18 +86,18 @@ async function materializeNpmrc(target) {
|
|
|
76
86
|
await writeFile(dst, bytes);
|
|
77
87
|
}
|
|
78
88
|
|
|
79
|
-
export async function freshInstall(templateDir, target) {
|
|
89
|
+
export async function freshInstall(templateDir, target, opts = {}) {
|
|
80
90
|
const filter = makeFilter({ templateRoot: templateDir, skipNeverTouch: false, skipSpecialMerge: true });
|
|
81
91
|
await cp(templateDir, target, { recursive: true, force: false, filter });
|
|
82
92
|
await applySpecialAndNeverTouch(templateDir, target);
|
|
83
|
-
await materializeNpmrc(target);
|
|
93
|
+
if (opts.withNpmrc === true) await materializeNpmrc(target);
|
|
84
94
|
await writeBaselineManifest(target);
|
|
85
95
|
}
|
|
86
96
|
|
|
87
|
-
export async function forceInstall(templateDir, target) {
|
|
97
|
+
export async function forceInstall(templateDir, target, opts = {}) {
|
|
88
98
|
const filter = makeFilter({ templateRoot: templateDir, skipNeverTouch: true, skipSpecialMerge: true });
|
|
89
99
|
await cp(templateDir, target, { recursive: true, force: true, filter });
|
|
90
100
|
await applySpecialAndNeverTouch(templateDir, target);
|
|
91
|
-
await materializeNpmrc(target);
|
|
101
|
+
if (opts.withNpmrc === true) await materializeNpmrc(target);
|
|
92
102
|
await writeBaselineManifest(target);
|
|
93
103
|
}
|
|
Binary file
|