@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-14T15:52:13.061Z",
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.1.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 publish --dry-run",
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",
@@ -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
  }