@baseline-studio/cli 2.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/bin/baseline ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require('../dist/index.js');
@@ -0,0 +1 @@
1
+ export declare function context(name?: string): Promise<void>;
@@ -0,0 +1,212 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.context = context;
4
+ const readline_1 = require("readline");
5
+ const fs_1 = require("fs");
6
+ const path_1 = require("path");
7
+ const js_yaml_1 = require("js-yaml");
8
+ const config_js_1 = require("../config.js");
9
+ let rlClosed = false;
10
+ function ask(rl, question) {
11
+ if (rlClosed)
12
+ return Promise.resolve("");
13
+ return new Promise((resolve) => {
14
+ try {
15
+ rl.question(question, resolve);
16
+ }
17
+ catch {
18
+ resolve("");
19
+ }
20
+ });
21
+ }
22
+ async function context(name) {
23
+ if (name) {
24
+ await contextAdd(name);
25
+ }
26
+ else {
27
+ await contextRefresh();
28
+ }
29
+ }
30
+ /** Re-run context prompts for existing files */
31
+ async function contextRefresh() {
32
+ const config = (0, config_js_1.readConfig)();
33
+ const cwd = process.cwd();
34
+ const contextDir = (0, path_1.join)(cwd, config.client.contextPath || "./context");
35
+ // Load prompts from local skills (they were pulled from core via update)
36
+ const skillsDir = (0, path_1.join)(cwd, "skills");
37
+ const prompts = loadPromptsFromLocal(cwd);
38
+ if (Object.keys(prompts).length === 0) {
39
+ console.error(" Error: No context prompts found. Run `baseline update` first.\n");
40
+ process.exit(1);
41
+ }
42
+ const rl = (0, readline_1.createInterface)({ input: process.stdin, output: process.stdout });
43
+ rl.on("close", () => { rlClosed = true; });
44
+ console.log(`\n Baseline Context — Update Context Files`);
45
+ console.log(` ────────────────────────────────────────\n`);
46
+ console.log(` Existing answers shown in [brackets]. Press Enter to keep them.\n`);
47
+ let filesUpdated = 0;
48
+ for (const [ctxFile, prompt] of Object.entries(prompts)) {
49
+ const fullPath = (0, path_1.join)(contextDir, ctxFile);
50
+ const existingContent = (0, fs_1.existsSync)(fullPath) ? (0, fs_1.readFileSync)(fullPath, "utf-8") : "";
51
+ const existingAnswers = parseExistingAnswers(existingContent, prompt.questions);
52
+ console.log(` ── ${prompt.title} ──\n`);
53
+ const answers = [];
54
+ for (let i = 0; i < prompt.questions.length; i++) {
55
+ const q = prompt.questions[i];
56
+ const existing = existingAnswers[i] || "";
57
+ const hint = existing ? ` [${truncate(existing, 60)}]` : "";
58
+ const answer = await ask(rl, ` ${q}${hint}\n > `);
59
+ const final = answer.trim() || existing;
60
+ if (final) {
61
+ answers.push(`**${q}**\n${final}`);
62
+ }
63
+ console.log();
64
+ }
65
+ (0, fs_1.mkdirSync)((0, path_1.dirname)(fullPath), { recursive: true });
66
+ if (answers.length > 0) {
67
+ (0, fs_1.writeFileSync)(fullPath, `# ${prompt.title}\n\n${answers.join("\n\n")}\n`);
68
+ filesUpdated++;
69
+ }
70
+ else if (!(0, fs_1.existsSync)(fullPath)) {
71
+ (0, fs_1.writeFileSync)(fullPath, `# ${prompt.title}\n\n<!-- Add your content here -->\n`);
72
+ }
73
+ }
74
+ rl.close();
75
+ console.log(` ────────────────────────────────────────`);
76
+ console.log(` Updated ${filesUpdated} context files.\n`);
77
+ }
78
+ /** Create a new context file and wire it into context.yaml */
79
+ async function contextAdd(name) {
80
+ const config = (0, config_js_1.readConfig)();
81
+ const cwd = process.cwd();
82
+ const contextDir = (0, path_1.join)(cwd, config.client.contextPath || "./context");
83
+ const skillsDir = (0, path_1.join)(cwd, "skills");
84
+ // Normalize name
85
+ const fileName = name.endsWith(".md") ? name : `${name}.md`;
86
+ const filePath = (0, path_1.join)(contextDir, "extended", fileName);
87
+ if ((0, fs_1.existsSync)(filePath)) {
88
+ console.error(`\n Error: context/extended/${fileName} already exists.\n`);
89
+ process.exit(1);
90
+ }
91
+ // Get available skills
92
+ const skills = [];
93
+ if ((0, fs_1.existsSync)(skillsDir)) {
94
+ for (const entry of (0, fs_1.readdirSync)(skillsDir)) {
95
+ const manifestPath = (0, path_1.join)(skillsDir, entry, "manifest.yaml");
96
+ if ((0, fs_1.existsSync)(manifestPath)) {
97
+ skills.push(entry);
98
+ }
99
+ }
100
+ }
101
+ skills.sort();
102
+ const rl = (0, readline_1.createInterface)({ input: process.stdin, output: process.stdout });
103
+ rl.on("close", () => { rlClosed = true; });
104
+ console.log(`\n Baseline Context — Add New File`);
105
+ console.log(` ───────────────────────────────\n`);
106
+ console.log(` Creating: context/extended/${fileName}\n`);
107
+ // Ask for a title
108
+ const title = await ask(rl, ` Title for this context file: `);
109
+ const fileTitle = title.trim() || name.replace(/\.md$/, "").replace(/-/g, " ");
110
+ console.log();
111
+ // Ask which skills should use this context
112
+ console.log(` Which skills should use this context file?`);
113
+ console.log(` Available skills:\n`);
114
+ for (let i = 0; i < skills.length; i++) {
115
+ console.log(` ${i + 1}. ${skills[i]}`);
116
+ }
117
+ console.log();
118
+ const selection = await ask(rl, ` Enter skill numbers (comma-separated) or "all":\n > `);
119
+ let selectedSkills;
120
+ if (selection.trim().toLowerCase() === "all") {
121
+ selectedSkills = [...skills];
122
+ }
123
+ else {
124
+ const indices = selection
125
+ .split(",")
126
+ .map((s) => parseInt(s.trim(), 10) - 1)
127
+ .filter((i) => i >= 0 && i < skills.length);
128
+ selectedSkills = indices.map((i) => skills[i]);
129
+ }
130
+ // Create the file
131
+ (0, fs_1.mkdirSync)((0, path_1.dirname)(filePath), { recursive: true });
132
+ (0, fs_1.writeFileSync)(filePath, `# ${fileTitle}\n\n<!-- Add your content here -->\n`);
133
+ // Update context.yaml
134
+ const contextYamlPath = (0, path_1.join)(contextDir, "context.yaml");
135
+ let contextYaml = { core: ["identity.md", "voice.md"], extended: {} };
136
+ if ((0, fs_1.existsSync)(contextYamlPath)) {
137
+ const parsed = (0, js_yaml_1.load)((0, fs_1.readFileSync)(contextYamlPath, "utf-8"));
138
+ if (parsed)
139
+ contextYaml = parsed;
140
+ if (!contextYaml.extended)
141
+ contextYaml.extended = {};
142
+ }
143
+ if (selectedSkills.length > 0) {
144
+ contextYaml.extended[fileName] = selectedSkills.sort();
145
+ }
146
+ // Write context.yaml back as formatted YAML
147
+ let yamlOut = "# Maps context files to skills. Merged with skill manifests during execution.\n";
148
+ yamlOut += "core:\n";
149
+ for (const c of contextYaml.core || ["identity.md", "voice.md"]) {
150
+ yamlOut += ` - ${c}\n`;
151
+ }
152
+ yamlOut += "extended:\n";
153
+ const entries = Object.entries(contextYaml.extended || {}).sort(([a], [b]) => a.localeCompare(b));
154
+ for (const [file, skillList] of entries) {
155
+ yamlOut += ` ${file}:\n`;
156
+ for (const s of skillList) {
157
+ yamlOut += ` - ${s}\n`;
158
+ }
159
+ }
160
+ (0, fs_1.writeFileSync)(contextYamlPath, yamlOut);
161
+ rl.close();
162
+ console.log(`\n ───────────────────────────────`);
163
+ console.log(` Created context/extended/${fileName}`);
164
+ if (selectedSkills.length > 0) {
165
+ console.log(` Wired to: ${selectedSkills.join(", ")}`);
166
+ }
167
+ console.log(` Updated context/context.yaml\n`);
168
+ }
169
+ /** Load context prompts from the local context-prompts.yaml or skill manifests */
170
+ function loadPromptsFromLocal(cwd) {
171
+ // First try context-prompts.yaml bundled with the skills
172
+ // Since we don't bundle context-prompts.yaml in client repos,
173
+ // we fetch it from the core repo via config
174
+ const config = (0, config_js_1.readConfig)();
175
+ const { cloneAtTag, getLatestTag } = require("../git.js");
176
+ const latest = getLatestTag(config.coreRepo);
177
+ if (!latest)
178
+ return {};
179
+ const tmpDir = cloneAtTag(config.coreRepo, latest);
180
+ const promptsPath = (0, path_1.join)(tmpDir, "context-prompts.yaml");
181
+ let prompts = {};
182
+ if ((0, fs_1.existsSync)(promptsPath)) {
183
+ prompts = (0, js_yaml_1.load)((0, fs_1.readFileSync)(promptsPath, "utf-8"));
184
+ }
185
+ const { rmSync } = require("fs");
186
+ rmSync(tmpDir, { recursive: true });
187
+ return prompts;
188
+ }
189
+ /** Parse existing answers from a context file */
190
+ function parseExistingAnswers(content, questions) {
191
+ const answers = [];
192
+ for (const q of questions) {
193
+ const marker = `**${q}**`;
194
+ const idx = content.indexOf(marker);
195
+ if (idx === -1) {
196
+ answers.push("");
197
+ continue;
198
+ }
199
+ const afterMarker = content.slice(idx + marker.length).trimStart();
200
+ // Find the next question marker or end of file
201
+ const nextMarkerIdx = afterMarker.indexOf("**");
202
+ const answerText = nextMarkerIdx > 0
203
+ ? afterMarker.slice(0, nextMarkerIdx).trim()
204
+ : afterMarker.trim();
205
+ answers.push(answerText);
206
+ }
207
+ return answers;
208
+ }
209
+ function truncate(str, max) {
210
+ const oneLine = str.replace(/\n/g, " ").trim();
211
+ return oneLine.length > max ? oneLine.slice(0, max - 3) + "..." : oneLine;
212
+ }
@@ -0,0 +1 @@
1
+ export declare function init(): Promise<void>;
@@ -0,0 +1,343 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.init = init;
4
+ const readline_1 = require("readline");
5
+ const fs_1 = require("fs");
6
+ const path_1 = require("path");
7
+ const js_yaml_1 = require("js-yaml");
8
+ const git_js_1 = require("../git.js");
9
+ const child_process_1 = require("child_process");
10
+ const SYNC_DIRS = ["skills", "frameworks", "scripts", "cli"];
11
+ let rlClosed = false;
12
+ function ask(rl, question) {
13
+ if (rlClosed)
14
+ return Promise.resolve("");
15
+ return new Promise((resolve) => {
16
+ try {
17
+ rl.question(question, resolve);
18
+ }
19
+ catch {
20
+ resolve("");
21
+ }
22
+ });
23
+ }
24
+ async function init() {
25
+ const rl = (0, readline_1.createInterface)({ input: process.stdin, output: process.stdout });
26
+ rl.on("close", () => { rlClosed = true; });
27
+ console.log(`\n Baseline System — New Client Setup`);
28
+ console.log(` ───────────────────────────────────\n`);
29
+ // 1. Gather basic info
30
+ const clientName = await ask(rl, " Client name: ");
31
+ const folderName = await ask(rl, ` Folder name (${clientName.toLowerCase().replace(/\s+/g, "-")}-system): `);
32
+ const folder = folderName.trim() ||
33
+ `${clientName.toLowerCase().replace(/\s+/g, "-")}-system`;
34
+ const coreRepo = await ask(rl, " Core repo (TrentM6/baseline-core): ");
35
+ const repo = coreRepo.trim() || "TrentM6/baseline-core";
36
+ const destDir = (0, path_1.join)(process.cwd(), folder);
37
+ if ((0, fs_1.existsSync)(destDir)) {
38
+ console.error(`\n Error: ${folder} already exists.\n`);
39
+ rl.close();
40
+ process.exit(1);
41
+ }
42
+ // 2. Fetch latest from core
43
+ console.log(`\n Fetching latest from ${repo}...`);
44
+ const latest = (0, git_js_1.getLatestTag)(repo);
45
+ if (!latest) {
46
+ console.error(" Could not determine latest version.\n");
47
+ rl.close();
48
+ process.exit(1);
49
+ }
50
+ console.log(` Using v${latest}\n`);
51
+ const tmpDir = (0, git_js_1.cloneAtTag)(repo, latest);
52
+ // 3. Create folder structure
53
+ (0, fs_1.mkdirSync)((0, path_1.join)(destDir, "context", "core"), { recursive: true });
54
+ (0, fs_1.mkdirSync)((0, path_1.join)(destDir, "context", "extended"), { recursive: true });
55
+ // 4. Copy skills, frameworks, scripts, cli
56
+ for (const dir of SYNC_DIRS) {
57
+ const srcDir = (0, path_1.join)(tmpDir, dir);
58
+ if ((0, fs_1.existsSync)(srcDir)) {
59
+ (0, fs_1.cpSync)(srcDir, (0, path_1.join)(destDir, dir), { recursive: true });
60
+ }
61
+ }
62
+ // Remove CLI source files (clients only need bin/, dist/, package.json)
63
+ const cliCleanup = ["src", "tsconfig.json", "package-lock.json"];
64
+ for (const item of cliCleanup) {
65
+ const p = (0, path_1.join)(destDir, "cli", item);
66
+ if ((0, fs_1.existsSync)(p))
67
+ (0, fs_1.rmSync)(p, { recursive: true });
68
+ }
69
+ // 5. Collect unique context file paths from manifests
70
+ const contextFiles = collectContextPaths(tmpDir);
71
+ // 6. Load context-prompts.yaml from core
72
+ const promptsPath = (0, path_1.join)(tmpDir, "context-prompts.yaml");
73
+ let prompts = {};
74
+ if ((0, fs_1.existsSync)(promptsPath)) {
75
+ prompts = (0, js_yaml_1.load)((0, fs_1.readFileSync)(promptsPath, "utf-8"));
76
+ }
77
+ // 7. Ask questions and write context files
78
+ console.log(" Let's set up your context files.\n");
79
+ console.log(" (Press Enter to skip any question)\n");
80
+ for (const ctxFile of contextFiles) {
81
+ const prompt = prompts[ctxFile];
82
+ if (!prompt) {
83
+ // Create empty template for files without prompts
84
+ const fullPath = (0, path_1.join)(destDir, "context", ctxFile);
85
+ (0, fs_1.mkdirSync)((0, path_1.dirname)(fullPath), { recursive: true });
86
+ (0, fs_1.writeFileSync)(fullPath, `# ${ctxFile}\n\n<!-- Add your content here -->\n`);
87
+ continue;
88
+ }
89
+ console.log(` ── ${prompt.title} ──\n`);
90
+ const answers = [];
91
+ for (const q of prompt.questions) {
92
+ const answer = await ask(rl, ` ${q}\n > `);
93
+ if (answer.trim()) {
94
+ answers.push(`**${q}**\n${answer.trim()}`);
95
+ }
96
+ console.log();
97
+ }
98
+ const fullPath = (0, path_1.join)(destDir, "context", ctxFile);
99
+ (0, fs_1.mkdirSync)((0, path_1.dirname)(fullPath), { recursive: true });
100
+ if (answers.length > 0) {
101
+ (0, fs_1.writeFileSync)(fullPath, `# ${prompt.title}\n\n${answers.join("\n\n")}\n`);
102
+ }
103
+ else {
104
+ (0, fs_1.writeFileSync)(fullPath, `# ${prompt.title}\n\n<!-- Add your content here -->\n`);
105
+ }
106
+ }
107
+ // 8. Create context.yaml
108
+ const contextYaml = buildContextYaml(tmpDir, contextFiles);
109
+ (0, fs_1.writeFileSync)((0, path_1.join)(destDir, "context", "context.yaml"), contextYaml);
110
+ // 9. Create baseline.config.json
111
+ const config = {
112
+ version: latest,
113
+ coreRepo: repo,
114
+ lastUpdated: new Date().toISOString(),
115
+ client: {
116
+ name: clientName,
117
+ contextPath: "./context",
118
+ },
119
+ };
120
+ (0, fs_1.writeFileSync)((0, path_1.join)(destDir, "baseline.config.json"), JSON.stringify(config, null, 2) + "\n");
121
+ // 10. Create CLAUDE.md
122
+ const templatePath = (0, path_1.join)(tmpDir, "claude-template.md");
123
+ if ((0, fs_1.existsSync)(templatePath)) {
124
+ let template = (0, fs_1.readFileSync)(templatePath, "utf-8");
125
+ template = template.replace(/\{client_name\}/g, clientName);
126
+ (0, fs_1.writeFileSync)((0, path_1.join)(destDir, "CLAUDE.md"), template);
127
+ }
128
+ else {
129
+ (0, fs_1.writeFileSync)((0, path_1.join)(destDir, "CLAUDE.md"), generateClaudeMd(clientName));
130
+ }
131
+ // 11. Create root package.json for local CLI access
132
+ const rootPkg = {
133
+ name: `${clientName.toLowerCase().replace(/\s+/g, "-")}-system`,
134
+ version: "1.0.0",
135
+ private: true,
136
+ description: `${clientName} Baseline System`,
137
+ dependencies: {
138
+ "baseline-cli": "file:cli",
139
+ },
140
+ };
141
+ (0, fs_1.writeFileSync)((0, path_1.join)(destDir, "package.json"), JSON.stringify(rootPkg, null, 2) + "\n");
142
+ // 12. Install CLI locally so npx baseline works
143
+ console.log(`\n Installing CLI...`);
144
+ (0, child_process_1.execSync)("npm install --silent", { cwd: destDir, stdio: "pipe" });
145
+ // 13. Create .gitignore
146
+ (0, fs_1.writeFileSync)((0, path_1.join)(destDir, ".gitignore"), "node_modules/\n.DS_Store\n");
147
+ // 14. Initialize git repo
148
+ (0, child_process_1.execSync)("git init", { cwd: destDir, stdio: "pipe" });
149
+ (0, child_process_1.execSync)("git add -A", { cwd: destDir, stdio: "pipe" });
150
+ (0, child_process_1.execSync)(`git commit -m "Initialize ${clientName} Baseline System (v${latest})"`, { cwd: destDir, stdio: "pipe" });
151
+ // Clean up
152
+ (0, fs_1.rmSync)(tmpDir, { recursive: true });
153
+ rl.close();
154
+ console.log(` ───────────────────────────────────`);
155
+ console.log(` ${clientName} system created at ./${folder}`);
156
+ console.log(` Version: v${latest}`);
157
+ console.log(` Skills: ${SYNC_DIRS.filter((d) => d !== "cli").map((d) => (0, fs_1.existsSync)((0, path_1.join)(destDir, d)) ? (0, fs_1.readdirSync)((0, path_1.join)(destDir, d)).filter((f) => !f.startsWith(".") && !f.startsWith("_")).length : 0).join(" | ")}`);
158
+ console.log(`\n Next steps:`);
159
+ console.log(` cd ${folder}`);
160
+ console.log(` Edit context/ files to add more detail`);
161
+ console.log(` Run \`npx baseline status\` to check for updates\n`);
162
+ }
163
+ /** Scan all skill manifests and return unique context file paths (relative to context/) */
164
+ function collectContextPaths(coreDir) {
165
+ const skillsDir = (0, path_1.join)(coreDir, "skills");
166
+ if (!(0, fs_1.existsSync)(skillsDir))
167
+ return [];
168
+ const paths = new Set();
169
+ // Always include core files
170
+ paths.add("core/identity.md");
171
+ paths.add("core/voice.md");
172
+ for (const skill of (0, fs_1.readdirSync)(skillsDir)) {
173
+ const manifestPath = (0, path_1.join)(skillsDir, skill, "manifest.yaml");
174
+ if (!(0, fs_1.existsSync)(manifestPath))
175
+ continue;
176
+ try {
177
+ const manifest = (0, js_yaml_1.load)((0, fs_1.readFileSync)(manifestPath, "utf-8"));
178
+ if (!manifest?.context)
179
+ continue;
180
+ for (const section of ["core", "extended"]) {
181
+ const entries = manifest.context[section];
182
+ if (!entries)
183
+ continue;
184
+ for (const ctxPath of entries) {
185
+ const match = ctxPath.match(/context\/\{client\}\/(.+)/);
186
+ if (match)
187
+ paths.add(match[1]);
188
+ }
189
+ }
190
+ }
191
+ catch {
192
+ // Skip unparseable manifests
193
+ }
194
+ }
195
+ // Sort: core first, then extended
196
+ return [...paths].sort((a, b) => {
197
+ if (a.startsWith("core/") && !b.startsWith("core/"))
198
+ return -1;
199
+ if (!a.startsWith("core/") && b.startsWith("core/"))
200
+ return 1;
201
+ return a.localeCompare(b);
202
+ });
203
+ }
204
+ /** Build context.yaml mapping context files to skills */
205
+ function buildContextYaml(coreDir, contextFiles) {
206
+ const skillsDir = (0, path_1.join)(coreDir, "skills");
207
+ const extendedMap = new Map();
208
+ if ((0, fs_1.existsSync)(skillsDir)) {
209
+ for (const skill of (0, fs_1.readdirSync)(skillsDir)) {
210
+ const manifestPath = (0, path_1.join)(skillsDir, skill, "manifest.yaml");
211
+ if (!(0, fs_1.existsSync)(manifestPath))
212
+ continue;
213
+ try {
214
+ const manifest = (0, js_yaml_1.load)((0, fs_1.readFileSync)(manifestPath, "utf-8"));
215
+ if (!manifest?.context?.extended)
216
+ continue;
217
+ for (const ctxPath of manifest.context.extended) {
218
+ const match = ctxPath.match(/context\/\{client\}\/extended\/(.+)/);
219
+ if (!match)
220
+ continue;
221
+ const file = match[1];
222
+ if (!extendedMap.has(file))
223
+ extendedMap.set(file, []);
224
+ extendedMap.get(file).push(skill);
225
+ }
226
+ }
227
+ catch {
228
+ // Skip
229
+ }
230
+ }
231
+ }
232
+ let yaml = "# Maps context files to skills. Merged with skill manifests during execution.\n";
233
+ yaml += "core:\n";
234
+ yaml += " - identity.md # loaded by all skills\n";
235
+ yaml += " - voice.md # loaded by all skills\n";
236
+ yaml += "extended:\n";
237
+ for (const [file, skills] of [...extendedMap.entries()].sort()) {
238
+ yaml += ` ${file}:\n`;
239
+ for (const s of skills.sort()) {
240
+ yaml += ` - ${s}\n`;
241
+ }
242
+ }
243
+ return yaml;
244
+ }
245
+ /** Generate a CLAUDE.md for the client if no template exists in core */
246
+ function generateClaudeMd(clientName) {
247
+ return `# ${clientName} — Baseline System
248
+
249
+ > This file is automatically loaded at the start of every Claude Code session. It enforces consistent skill execution.
250
+
251
+ ---
252
+
253
+ ## What This Project Is
254
+
255
+ The Baseline System is a complete AI system for product work with four components:
256
+
257
+ 1. **Skills** (\`skills/\`) — Domain expertise (12 universal skills)
258
+ 2. **Context** (\`context/\`) — Business-specific knowledge
259
+ 3. **Frameworks** (\`frameworks/\`) — Reusable methodologies
260
+ 4. **Scripts** (\`scripts/\`) — Delivery to external tools
261
+
262
+ Skills provide methodology. Context personalizes output. Frameworks provide reusable patterns. Scripts deliver to tools.
263
+
264
+ ---
265
+
266
+ ## Skill Execution Protocol
267
+
268
+ When a user invokes any skill — by name, by file path, or by describing a task that maps to a skill — you MUST follow this exact sequence. Do not skip steps.
269
+
270
+ ### Step 1: Identify the Skill
271
+
272
+ Match the user's request to a skill using this table:
273
+
274
+ | Task | Skill |
275
+ |------|-------|
276
+ | Strategic decisions, roadmaps, prioritization | \`strategic-advisory\` |
277
+ | User research, interviews, synthesis | \`research-synthesis\` |
278
+ | PRDs, specs, briefs, stakeholder updates | \`product-communications\` |
279
+ | Interface design, wireframes, UI copy | \`ux-design\` |
280
+ | Metrics, dashboards, A/B tests | \`product-analytics\` |
281
+ | Coded prototypes, demos, POCs | \`prototyping\` |
282
+ | Planning, tracking, sprints | \`project-management\` |
283
+ | User guides, help center, release notes | \`technical-documentation\` |
284
+ | Presentations, graphics, diagrams | \`brand-design\` |
285
+ | LinkedIn content, website copy, campaigns | \`marketing\` |
286
+ | Outreach, proposals, discovery calls | \`sales\` |
287
+ | Creating new skills | \`skill-building\` |
288
+
289
+ ### Step 2: Read the Manifest
290
+
291
+ Every skill has a \`manifest.yaml\` in its folder that lists every file the skill needs. Read it:
292
+
293
+ \`\`\`
294
+ skills/[skill-name]/manifest.yaml
295
+ \`\`\`
296
+
297
+ This is the source of truth for what files to load. Do not guess or rely on file paths embedded in markdown prose.
298
+
299
+ ### Step 3: Load All Files Listed in the Manifest
300
+
301
+ The manifest has three sections. Load them in this order:
302
+
303
+ 1. **\`always_load\`** — Read every file in this list. These are the skill file and framework files. Always load all of them.
304
+
305
+ 2. **\`context\`** — These paths use \`{client}\` as a placeholder. Replace \`{client}\` with the context folder path for this project. Read every file under \`core\` and \`extended\`. If a context file does not exist, skip it and continue — not all context files may be populated.
306
+
307
+ 3. **\`references\`** — These are detailed reference materials. Load them when the task benefits from detailed guidance (e.g., document templates when writing a PRD, content playbooks when creating a campaign). For straightforward tasks, you may skip references.
308
+
309
+ **Do not skip files.** Do not summarize file names instead of reading them. Treat every path in the manifest as a load instruction.
310
+
311
+ ### Step 4: Execute the Skill's Workflow
312
+
313
+ After loading all files, follow the workflow defined in the skill file and the workflow orchestration framework:
314
+
315
+ 1. **Plan** — Present your plan to the user. Wait for approval before proceeding.
316
+ 2. **Clarify** — Ask the skill's clarifying questions. Do not proceed with missing information.
317
+ 3. **Execute** — Do the domain-specific work using the skill's methodology and loaded context.
318
+ 4. **Validate** — Run the skill's quality checks. If any fail, apply error recovery and re-validate.
319
+
320
+ ### Step 5: Deliver (If Requested)
321
+
322
+ If the user wants output delivered to an external tool, read the relevant script from \`scripts/\` and follow its instructions.
323
+
324
+ ---
325
+
326
+ ## Session Management
327
+
328
+ - Scope each session to one major task. Multi-task sessions degrade output quality.
329
+ - After completing a major deliverable, recommend starting a fresh session.
330
+ - If the conversation has gone through 3+ revision cycles, proactively suggest a session break.
331
+
332
+ ---
333
+
334
+ ## Anti-Patterns — Do Not
335
+
336
+ - **Skip reading the manifest** — Always read \`manifest.yaml\` first. It is the source of truth for what files a skill needs.
337
+ - **Acknowledge file paths without reading them** — If the manifest lists a file, read it.
338
+ - **Skip the Plan step** — Always present a plan and wait for approval.
339
+ - **Proceed without clarifying** — Ask the skill's questions before executing.
340
+ - **Skip quality checks** — Run every check defined by the skill.
341
+ - **Overload the session** — One major task per session. Recommend fresh sessions after milestones.
342
+ `;
343
+ }
@@ -0,0 +1 @@
1
+ export declare function status(): void;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.status = status;
4
+ const config_js_1 = require("../config.js");
5
+ const git_js_1 = require("../git.js");
6
+ function status() {
7
+ const config = (0, config_js_1.readConfig)();
8
+ console.log(`\n Baseline System`);
9
+ console.log(` ───────────────────────────`);
10
+ console.log(` Client: ${config.client.name}`);
11
+ console.log(` Version: v${config.version}`);
12
+ console.log(` Core repo: ${config.coreRepo}`);
13
+ console.log(` Last updated: ${config.lastUpdated}`);
14
+ console.log(`\n Checking for updates...`);
15
+ const latest = (0, git_js_1.getLatestTag)(config.coreRepo);
16
+ if (!latest) {
17
+ console.log(` Could not determine latest version.\n`);
18
+ return;
19
+ }
20
+ if ((0, git_js_1.isNewer)(latest, config.version)) {
21
+ console.log(` Update available: v${config.version} → v${latest}`);
22
+ console.log(` Run \`baseline update\` to pull the latest.\n`);
23
+ }
24
+ else {
25
+ console.log(` Up to date (v${config.version}).\n`);
26
+ }
27
+ }
@@ -0,0 +1 @@
1
+ export declare function update(): void;
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.update = update;
4
+ const fs_1 = require("fs");
5
+ const path_1 = require("path");
6
+ const config_js_1 = require("../config.js");
7
+ const git_js_1 = require("../git.js");
8
+ const js_yaml_1 = require("js-yaml");
9
+ const child_process_1 = require("child_process");
10
+ const SYNC_DIRS = ["skills", "frameworks", "scripts", "cli"];
11
+ function update() {
12
+ const config = (0, config_js_1.readConfig)();
13
+ const cwd = process.cwd();
14
+ console.log(`\n Checking for updates...`);
15
+ const latest = (0, git_js_1.getLatestTag)(config.coreRepo);
16
+ if (!latest) {
17
+ console.log(` Could not determine latest version.\n`);
18
+ return;
19
+ }
20
+ if (!(0, git_js_1.isNewer)(latest, config.version)) {
21
+ console.log(` Already up to date (v${config.version}).\n`);
22
+ return;
23
+ }
24
+ console.log(` Updating v${config.version} → v${latest}...`);
25
+ // Clone the latest tag to a temp directory
26
+ const tmpDir = (0, git_js_1.cloneAtTag)(config.coreRepo, latest);
27
+ // Sync each directory: full replace
28
+ const stats = { skills: 0, frameworks: 0, scripts: 0 };
29
+ for (const dir of SYNC_DIRS) {
30
+ const srcDir = (0, path_1.join)(tmpDir, dir);
31
+ const destDir = (0, path_1.join)(cwd, dir);
32
+ if (!(0, fs_1.existsSync)(srcDir))
33
+ continue;
34
+ // Count items for summary (skip cli in counts)
35
+ if (dir === "skills") {
36
+ stats.skills = (0, fs_1.readdirSync)(srcDir).filter((f) => (0, fs_1.statSync)((0, path_1.join)(srcDir, f)).isDirectory()).length;
37
+ }
38
+ else if (dir !== "cli") {
39
+ stats[dir] = (0, fs_1.readdirSync)(srcDir).filter((f) => f.endsWith(".md") || (0, fs_1.statSync)((0, path_1.join)(srcDir, f)).isDirectory()).length;
40
+ }
41
+ // Full replace
42
+ if ((0, fs_1.existsSync)(destDir)) {
43
+ (0, fs_1.rmSync)(destDir, { recursive: true });
44
+ }
45
+ (0, fs_1.cpSync)(srcDir, destDir, { recursive: true });
46
+ }
47
+ // Remove CLI source files (clients only need bin/, dist/, package.json)
48
+ const cliCleanup = ["src", "tsconfig.json", "package-lock.json"];
49
+ for (const item of cliCleanup) {
50
+ const p = (0, path_1.join)(cwd, "cli", item);
51
+ if ((0, fs_1.existsSync)(p))
52
+ (0, fs_1.rmSync)(p, { recursive: true });
53
+ }
54
+ // Re-install CLI dependencies after update
55
+ if ((0, fs_1.existsSync)((0, path_1.join)(cwd, "package.json"))) {
56
+ try {
57
+ (0, child_process_1.execSync)("npm install --silent", { cwd, stdio: "pipe" });
58
+ }
59
+ catch {
60
+ // Non-fatal — CLI still works from previous install
61
+ }
62
+ }
63
+ // Check for missing context files
64
+ const contextPath = config.client.contextPath || "./context";
65
+ checkMissingContext(tmpDir, (0, path_1.join)(cwd, contextPath));
66
+ // Clean up temp dir
67
+ (0, fs_1.rmSync)(tmpDir, { recursive: true });
68
+ // Update config
69
+ config.version = latest;
70
+ config.lastUpdated = new Date().toISOString();
71
+ (0, config_js_1.writeConfig)(config);
72
+ console.log(`\n Updated v${config.version} successfully.`);
73
+ console.log(` Skills: ${stats.skills} | Frameworks: ${stats.frameworks} | Scripts: ${stats.scripts}`);
74
+ console.log();
75
+ }
76
+ function checkMissingContext(coreDir, contextDir) {
77
+ const skillsDir = (0, path_1.join)(coreDir, "skills");
78
+ if (!(0, fs_1.existsSync)(skillsDir))
79
+ return;
80
+ const missingMap = new Map();
81
+ for (const skill of (0, fs_1.readdirSync)(skillsDir)) {
82
+ const manifestPath = (0, path_1.join)(skillsDir, skill, "manifest.yaml");
83
+ if (!(0, fs_1.existsSync)(manifestPath))
84
+ continue;
85
+ try {
86
+ const manifest = (0, js_yaml_1.load)((0, fs_1.readFileSync)(manifestPath, "utf-8"));
87
+ if (!manifest?.context?.extended)
88
+ continue;
89
+ for (const ctxPath of manifest.context.extended) {
90
+ const match = ctxPath.match(/context\/\{client\}\/(.+)/);
91
+ if (!match)
92
+ continue;
93
+ const relPath = match[1];
94
+ const fullPath = (0, path_1.join)(contextDir, relPath);
95
+ if (!(0, fs_1.existsSync)(fullPath)) {
96
+ if (!missingMap.has(relPath))
97
+ missingMap.set(relPath, []);
98
+ missingMap.get(relPath).push(skill);
99
+ }
100
+ }
101
+ }
102
+ catch {
103
+ // Skip unparseable manifests
104
+ }
105
+ }
106
+ if (missingMap.size > 0) {
107
+ console.log(`\n Missing context files:`);
108
+ for (const [file, skills] of missingMap) {
109
+ console.log(` → ${file} (used by ${skills.join(", ")})`);
110
+ }
111
+ console.log(` Create these files to get the most out of these skills.`);
112
+ }
113
+ }
@@ -0,0 +1,11 @@
1
+ export interface BaselineConfig {
2
+ version: string;
3
+ coreRepo: string;
4
+ lastUpdated: string;
5
+ client: {
6
+ name: string;
7
+ contextPath: string;
8
+ };
9
+ }
10
+ export declare function readConfig(): BaselineConfig;
11
+ export declare function writeConfig(config: BaselineConfig): void;
package/dist/config.js ADDED
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readConfig = readConfig;
4
+ exports.writeConfig = writeConfig;
5
+ const fs_1 = require("fs");
6
+ const path_1 = require("path");
7
+ const CONFIG_FILE = "baseline.config.json";
8
+ function readConfig() {
9
+ const configPath = (0, path_1.join)(process.cwd(), CONFIG_FILE);
10
+ try {
11
+ return JSON.parse((0, fs_1.readFileSync)(configPath, "utf-8"));
12
+ }
13
+ catch {
14
+ console.error(`Error: ${CONFIG_FILE} not found in current directory.`);
15
+ console.error("Run this command from your baseline system root.");
16
+ process.exit(1);
17
+ }
18
+ }
19
+ function writeConfig(config) {
20
+ const configPath = (0, path_1.join)(process.cwd(), CONFIG_FILE);
21
+ (0, fs_1.writeFileSync)(configPath, JSON.stringify(config, null, 2) + "\n");
22
+ }
package/dist/git.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ export declare function getLatestTag(coreRepo: string): string | null;
2
+ export declare function fetchAndExtract(coreRepo: string, tag: string, destDir: string): void;
3
+ /** Clone a specific tag to a temp dir and return the path */
4
+ export declare function cloneAtTag(coreRepo: string, tag: string): string;
5
+ export declare function isNewer(latest: string, current: string): boolean;
package/dist/git.js ADDED
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getLatestTag = getLatestTag;
4
+ exports.fetchAndExtract = fetchAndExtract;
5
+ exports.cloneAtTag = cloneAtTag;
6
+ exports.isNewer = isNewer;
7
+ const child_process_1 = require("child_process");
8
+ const semver_1 = require("semver");
9
+ function getLatestTag(coreRepo) {
10
+ try {
11
+ const repoUrl = `https://github.com/${coreRepo}.git`;
12
+ const output = (0, child_process_1.execSync)(`git ls-remote --tags --sort=-v:refname ${repoUrl}`, {
13
+ encoding: "utf-8",
14
+ timeout: 15000,
15
+ });
16
+ for (const line of output.trim().split("\n")) {
17
+ if (!line)
18
+ continue;
19
+ const ref = line.split("\t")[1];
20
+ if (!ref || ref.endsWith("^{}"))
21
+ continue;
22
+ const tag = ref.replace("refs/tags/", "");
23
+ const version = tag.startsWith("v") ? tag.slice(1) : tag;
24
+ if ((0, semver_1.valid)(version))
25
+ return version;
26
+ }
27
+ return null;
28
+ }
29
+ catch {
30
+ console.error("Error: Could not reach baseline-core repo.");
31
+ console.error("Check your network connection and repo access.");
32
+ process.exit(1);
33
+ }
34
+ }
35
+ function fetchAndExtract(coreRepo, tag, destDir) {
36
+ const repoUrl = `https://github.com/${coreRepo}.git`;
37
+ const vTag = `v${tag}`;
38
+ (0, child_process_1.execSync)(`git archive --remote=${repoUrl} ${vTag} 2>/dev/null || git clone --depth 1 --branch ${vTag} ${repoUrl} /tmp/baseline-core-fetch`, { encoding: "utf-8", timeout: 60000, stdio: "pipe" });
39
+ }
40
+ /** Clone a specific tag to a temp dir and return the path */
41
+ function cloneAtTag(coreRepo, tag) {
42
+ const repoUrl = `https://github.com/${coreRepo}.git`;
43
+ const vTag = `v${tag}`;
44
+ const tmpDir = `/tmp/baseline-core-${Date.now()}`;
45
+ (0, child_process_1.execSync)(`git clone --depth 1 --branch ${vTag} ${repoUrl} ${tmpDir}`, {
46
+ encoding: "utf-8",
47
+ timeout: 60000,
48
+ stdio: "pipe",
49
+ });
50
+ return tmpDir;
51
+ }
52
+ function isNewer(latest, current) {
53
+ return (0, semver_1.compare)(latest, current) > 0;
54
+ }
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const commander_1 = require("commander");
4
+ const status_js_1 = require("./commands/status.js");
5
+ const update_js_1 = require("./commands/update.js");
6
+ const init_js_1 = require("./commands/init.js");
7
+ const context_js_1 = require("./commands/context.js");
8
+ const program = new commander_1.Command();
9
+ program
10
+ .name("baseline")
11
+ .description("Distribute and update the Baseline System")
12
+ .version("2.2.0");
13
+ program
14
+ .command("status")
15
+ .description("Show current version and check for updates")
16
+ .action(status_js_1.status);
17
+ program
18
+ .command("update")
19
+ .description("Pull latest skills, frameworks, and scripts from baseline-core")
20
+ .action(update_js_1.update);
21
+ program
22
+ .command("init")
23
+ .description("Set up a new client system with guided onboarding")
24
+ .action(init_js_1.init);
25
+ const ctxCmd = program
26
+ .command("context")
27
+ .description("Manage context files")
28
+ .action(() => (0, context_js_1.context)());
29
+ ctxCmd
30
+ .command("add <name>")
31
+ .description("Create a new context file and wire it to skills")
32
+ .action((name) => (0, context_js_1.context)(name));
33
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@baseline-studio/cli",
3
+ "version": "2.2.0",
4
+ "description": "CLI for distributing and updating the Baseline System — an AI-powered workflow system for product teams",
5
+ "keywords": [
6
+ "baseline",
7
+ "ai-system",
8
+ "cli",
9
+ "workflow",
10
+ "product-management",
11
+ "claude-code"
12
+ ],
13
+ "homepage": "https://github.com/TrentM6/baseline-core#readme",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/TrentM6/baseline-core.git",
17
+ "directory": "cli"
18
+ },
19
+ "bugs": {
20
+ "url": "https://github.com/TrentM6/baseline-core/issues"
21
+ },
22
+ "license": "MIT",
23
+ "author": "Baseline Studio <trent@baselinestudio.design>",
24
+ "bin": {
25
+ "baseline": "./bin/baseline"
26
+ },
27
+ "files": [
28
+ "bin/",
29
+ "dist/"
30
+ ],
31
+ "scripts": {
32
+ "build": "tsc",
33
+ "dev": "tsc --watch",
34
+ "prepublishOnly": "npm run build"
35
+ },
36
+ "engines": {
37
+ "node": ">=18.0.0"
38
+ },
39
+ "dependencies": {
40
+ "commander": "^12.0.0",
41
+ "js-yaml": "^4.1.0",
42
+ "semver": "^7.6.0"
43
+ },
44
+ "devDependencies": {
45
+ "@types/js-yaml": "^4.0.0",
46
+ "@types/node": "^20.0.0",
47
+ "@types/semver": "^7.5.0",
48
+ "typescript": "^5.4.0"
49
+ }
50
+ }