@esotech/contextuate 2.1.0 → 2.1.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.
Files changed (48) hide show
  1. package/dist/commands/doctor.d.ts +6 -0
  2. package/dist/commands/doctor.js +131 -0
  3. package/dist/commands/init.d.ts +0 -2
  4. package/dist/commands/init.js +164 -228
  5. package/dist/commands/install.js +8 -24
  6. package/dist/index.js +7 -0
  7. package/dist/templates/agents/aegis.md +1 -1
  8. package/dist/templates/agents/archon.md +116 -8
  9. package/dist/templates/agents/atlas.md +17 -17
  10. package/dist/templates/agents/canvas.md +1 -1
  11. package/dist/templates/agents/chronicle.md +1 -1
  12. package/dist/templates/agents/chronos.md +1 -1
  13. package/dist/templates/agents/cipher.md +1 -1
  14. package/dist/templates/agents/crucible.md +1 -1
  15. package/dist/templates/agents/echo.md +1 -1
  16. package/dist/templates/agents/forge.md +1 -1
  17. package/dist/templates/agents/ledger.md +34 -3
  18. package/dist/templates/agents/meridian.md +1 -1
  19. package/dist/templates/agents/nexus.md +1 -1
  20. package/dist/templates/agents/pythia.md +1 -1
  21. package/dist/templates/agents/scribe.md +1 -1
  22. package/dist/templates/agents/sentinel.md +1 -1
  23. package/dist/templates/agents/thoth.md +1 -1
  24. package/dist/templates/agents/unity.md +1 -1
  25. package/dist/templates/agents/vox.md +1 -1
  26. package/dist/templates/agents/weaver.md +1 -1
  27. package/dist/templates/{skills → commands}/consult.md +1 -1
  28. package/dist/templates/commands/orchestrate.md +49 -0
  29. package/dist/templates/framework-agents/documentation-expert.md +3 -3
  30. package/dist/templates/framework-agents/tools-expert.md +3 -3
  31. package/dist/templates/templates/contextuate.md +1 -1
  32. package/dist/templates/tools/agent-creator.md +4 -4
  33. package/dist/templates/tools/standards-detector.md +1 -1
  34. package/dist/utils/tools.d.ts +60 -0
  35. package/dist/utils/tools.js +260 -0
  36. package/package.json +2 -2
  37. package/dist/templates/skills/orchestrate.md +0 -173
  38. package/dist/templates/skills/pythia.md +0 -37
  39. package/dist/templates/templates/standards/go.standards.md +0 -167
  40. package/dist/templates/templates/standards/java.standards.md +0 -167
  41. package/dist/templates/templates/standards/javascript.standards.md +0 -292
  42. package/dist/templates/templates/standards/php.standards.md +0 -181
  43. package/dist/templates/templates/standards/python.standards.md +0 -175
  44. package/dist/templates/tools/agent-creator.tool.md +0 -252
  45. package/dist/templates/tools/quickref.tool.md +0 -216
  46. package/dist/templates/tools/spawn.tool.md +0 -31
  47. package/dist/templates/tools/standards-detector.tool.md +0 -301
  48. package/dist/templates/version.json +0 -8
@@ -9,6 +9,7 @@ const chalk_1 = __importDefault(require("chalk"));
9
9
  const fs_extra_1 = __importDefault(require("fs-extra"));
10
10
  const path_1 = __importDefault(require("path"));
11
11
  const fs_1 = require("fs");
12
+ const tools_1 = require("../utils/tools");
12
13
  // Get package version dynamically
13
14
  function getPackageVersion() {
14
15
  try {
@@ -19,7 +20,7 @@ function getPackageVersion() {
19
20
  return '0.0.0';
20
21
  }
21
22
  }
22
- // Get package info for version.json
23
+ // Get package info for contextuate.json
23
24
  function getPackageInfo() {
24
25
  try {
25
26
  const packageJson = JSON.parse((0, fs_1.readFileSync)(path_1.default.join(__dirname, '../../package.json'), 'utf-8'));
@@ -70,10 +71,45 @@ function fuzzyMatchPlatform(input) {
70
71
  }
71
72
  return null;
72
73
  }
74
+ // Get template source directory
75
+ function getTemplateSource() {
76
+ let templateSource = path_1.default.join(__dirname, '../templates');
77
+ // Handle ts-node vs compiled paths
78
+ if (path_1.default.basename(path_1.default.join(__dirname, '..')) === 'src') {
79
+ templateSource = path_1.default.join(__dirname, '../../src/templates');
80
+ }
81
+ else if (path_1.default.basename(__dirname) === 'commands') {
82
+ templateSource = path_1.default.join(__dirname, '../templates');
83
+ }
84
+ if (!fs_extra_1.default.existsSync(templateSource)) {
85
+ templateSource = path_1.default.join(__dirname, '../../templates');
86
+ }
87
+ return templateSource;
88
+ }
89
+ // Discover available agents from template source
90
+ async function discoverAgents(templateSource) {
91
+ const agentDir = path_1.default.join(templateSource, 'agents');
92
+ if (!fs_extra_1.default.existsSync(agentDir)) {
93
+ return [];
94
+ }
95
+ const files = await fs_extra_1.default.readdir(agentDir);
96
+ return files
97
+ .filter(f => f.endsWith('.md'))
98
+ .map(f => f.replace('.md', ''));
99
+ }
100
+ // Discover available skills from template source
101
+ async function discoverSkills(templateSource) {
102
+ const commandsDir = path_1.default.join(templateSource, 'commands');
103
+ if (!fs_extra_1.default.existsSync(commandsDir)) {
104
+ return [];
105
+ }
106
+ const files = await fs_extra_1.default.readdir(commandsDir);
107
+ return files
108
+ .filter(f => f.endsWith('.md'))
109
+ .map(f => f.replace('.md', ''));
110
+ }
73
111
  async function initCommand(platformArgs, options) {
74
112
  // Handle both old signature (no args) and new signature (with variadic args)
75
- // When called with no args: first param is options object
76
- // When called with args: first param is array, second param is options
77
113
  let platforms = [];
78
114
  let opts = {};
79
115
  if (Array.isArray(platformArgs)) {
@@ -81,14 +117,12 @@ async function initCommand(platformArgs, options) {
81
117
  opts = options || {};
82
118
  }
83
119
  else {
84
- // Old signature - first param is actually options
85
120
  opts = platformArgs || {};
86
121
  platforms = [];
87
122
  }
88
123
  console.log(chalk_1.default.blue('╔════════════════════════════════════════╗'));
89
124
  console.log(chalk_1.default.blue('║ Contextuate Installer ║'));
90
125
  console.log(chalk_1.default.blue('║ AI Context Framework ║'));
91
- console.log(chalk_1.default.blue('║ ║'));
92
126
  console.log(chalk_1.default.blue('╚════════════════════════════════════════╝'));
93
127
  console.log('');
94
128
  try {
@@ -113,18 +147,23 @@ async function initCommand(platformArgs, options) {
113
147
  }
114
148
  }
115
149
  else if (!hasMarker && nonInteractive) {
116
- console.log(chalk_1.default.yellow('[WARN] No project markers found (.git, package.json, etc.) - continuing anyway in non-interactive mode'));
150
+ console.log(chalk_1.default.yellow('[WARN] No project markers found - continuing anyway'));
117
151
  }
152
+ // Get template source
153
+ const templateSource = getTemplateSource();
154
+ if (!fs_extra_1.default.existsSync(templateSource)) {
155
+ console.error(chalk_1.default.red(`[ERROR] Could not find template source at ${templateSource}`));
156
+ return;
157
+ }
158
+ // Determine selected platforms
118
159
  let selectedPlatforms = [];
119
- // Non-interactive mode - process CLI arguments
120
160
  if (nonInteractive) {
121
- // Check for "all" argument
161
+ // Non-interactive: parse CLI arguments
122
162
  if (platforms.some(arg => arg.toLowerCase() === 'all')) {
123
163
  selectedPlatforms = PLATFORMS;
124
164
  console.log(chalk_1.default.blue('[INFO] Installing all platforms'));
125
165
  }
126
166
  else {
127
- // Fuzzy match each platform argument
128
167
  for (const arg of platforms) {
129
168
  const matchedId = fuzzyMatchPlatform(arg);
130
169
  if (matchedId) {
@@ -146,126 +185,46 @@ async function initCommand(platformArgs, options) {
146
185
  }
147
186
  }
148
187
  else {
149
- // Interactive mode - ask about platform selection
150
- const { platforms } = await inquirer_1.default.prompt([
151
- {
152
- type: 'checkbox',
153
- name: 'platforms',
154
- message: 'Select the platforms to install:',
155
- choices: [
156
- { name: 'Select All', value: 'all' },
157
- new inquirer_1.default.Separator(),
158
- ...PLATFORMS.map(p => ({
159
- name: p.name,
160
- value: p.id,
161
- checked: false,
162
- }))
163
- ],
164
- validate: (answer) => {
165
- if (answer.length < 1) {
166
- return 'You must select at least one platform.';
167
- }
168
- return true;
169
- },
170
- },
171
- ]);
172
- if (platforms.includes('all')) {
173
- selectedPlatforms = PLATFORMS;
188
+ // Interactive: default to Claude Code (opinionated)
189
+ const claudePlatform = PLATFORMS.find(p => p.id === 'claude');
190
+ if (claudePlatform) {
191
+ selectedPlatforms = [claudePlatform];
192
+ console.log(chalk_1.default.blue(`[INFO] Installing for ${claudePlatform.name} (default)`));
174
193
  }
175
- else {
176
- selectedPlatforms = PLATFORMS.filter(p => platforms.includes(p.id));
177
- }
178
- }
179
- // Handle agent installation
180
- let selectedAgents = [];
181
- // Dynamically discover available agents from template source
182
- let agentTemplateDir = path_1.default.join(__dirname, '../templates/agents');
183
- // Handle ts-node vs compiled paths
184
- if (path_1.default.basename(path_1.default.join(__dirname, '..')) === 'src') {
185
- agentTemplateDir = path_1.default.join(__dirname, '../../src/templates/agents');
186
- }
187
- else if (path_1.default.basename(__dirname) === 'commands') {
188
- agentTemplateDir = path_1.default.join(__dirname, '../templates/agents');
189
- }
190
- if (!fs_extra_1.default.existsSync(agentTemplateDir)) {
191
- agentTemplateDir = path_1.default.join(__dirname, '../../templates/agents');
192
- }
193
- let availableAgents = [];
194
- if (fs_extra_1.default.existsSync(agentTemplateDir)) {
195
- const agentFiles = await fs_extra_1.default.readdir(agentTemplateDir);
196
- availableAgents = agentFiles
197
- .filter(f => f.endsWith('.md'))
198
- .map(f => f.replace('.md', ''));
199
- }
200
- if (opts.agents && opts.agents.length > 0) {
201
- // Non-interactive agent selection via --agents flag
202
- if (opts.agents.includes('none')) {
203
- // Explicitly skip agent installation
204
- console.log(chalk_1.default.blue('[INFO] Skipping agent installation (--agents none)'));
205
- }
206
- else if (opts.agents.includes('all')) {
207
- selectedAgents = availableAgents;
208
- console.log(chalk_1.default.blue('[INFO] Installing all agents'));
209
- }
210
- else {
211
- // Match specific agents
212
- for (const agentArg of opts.agents) {
213
- const normalized = agentArg.toLowerCase().trim();
214
- const matched = availableAgents.find(a => a.toLowerCase() === normalized);
215
- if (matched) {
216
- selectedAgents.push(matched);
217
- console.log(chalk_1.default.green(`[OK] Matched agent "${agentArg}" to ${matched}`));
218
- }
219
- else {
220
- console.log(chalk_1.default.yellow(`[WARN] Could not match agent "${agentArg}" - skipping`));
221
- }
222
- }
223
- if (selectedAgents.length === 0 && opts.agents.length > 0) {
224
- console.log(chalk_1.default.yellow('[WARN] No valid agents matched. Available agents:'));
225
- availableAgents.forEach(a => console.log(` - ${a}`));
226
- }
227
- }
228
- }
229
- else if (!nonInteractive) {
230
- // Interactive mode - ask about agent installation
231
- const { installAgents } = await inquirer_1.default.prompt([
194
+ // Ask if they want additional platforms
195
+ const { addMorePlatforms } = await inquirer_1.default.prompt([
232
196
  {
233
197
  type: 'confirm',
234
- name: 'installAgents',
235
- message: 'Would you like to install pre-built AI agents?',
236
- default: true,
198
+ name: 'addMorePlatforms',
199
+ message: 'Would you like to add other platforms?',
200
+ default: false,
237
201
  },
238
202
  ]);
239
- if (installAgents && availableAgents.length > 0) {
240
- const { agents } = await inquirer_1.default.prompt([
203
+ if (addMorePlatforms) {
204
+ const otherPlatforms = PLATFORMS.filter(p => p.id !== 'claude');
205
+ const { additionalPlatforms } = await inquirer_1.default.prompt([
241
206
  {
242
207
  type: 'checkbox',
243
- name: 'agents',
244
- message: 'Select agents to install:',
245
- choices: [
246
- { name: 'Select All', value: 'all' },
247
- new inquirer_1.default.Separator(),
248
- ...availableAgents.map(agent => ({
249
- name: agent.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '),
250
- value: agent,
251
- checked: false,
252
- }))
253
- ],
208
+ name: 'additionalPlatforms',
209
+ message: 'Select additional platforms:',
210
+ choices: otherPlatforms.map(p => ({
211
+ name: p.name,
212
+ value: p.id,
213
+ checked: false,
214
+ })),
254
215
  },
255
216
  ]);
256
- if (agents.includes('all')) {
257
- selectedAgents = availableAgents;
258
- }
259
- else {
260
- selectedAgents = agents;
217
+ for (const platformId of additionalPlatforms) {
218
+ const platform = PLATFORMS.find(p => p.id === platformId);
219
+ if (platform) {
220
+ selectedPlatforms.push(platform);
221
+ }
261
222
  }
262
223
  }
263
224
  }
264
- else {
265
- // Non-interactive mode without --agents flag: default to all agents
266
- selectedAgents = availableAgents;
267
- console.log(chalk_1.default.blue('[INFO] Installing all agents (default in non-interactive mode)'));
268
- }
225
+ // Discover all agents and skills
226
+ const availableAgents = await discoverAgents(templateSource);
227
+ const availableSkills = await discoverSkills(templateSource);
269
228
  console.log('');
270
229
  console.log(chalk_1.default.blue('[INFO] Installing Contextuate framework...'));
271
230
  console.log('');
@@ -287,170 +246,142 @@ async function initCommand(platformArgs, options) {
287
246
  await fs_extra_1.default.ensureDir(dir);
288
247
  console.log(chalk_1.default.green(`[OK] Created directory: ${dir}`));
289
248
  }
290
- // Cleanup legacy template directories if they exist
291
- const legacyDirs = [
292
- 'docs/ai/.contextuate/templates',
293
- ];
249
+ // Cleanup legacy directories
250
+ const legacyDirs = ['docs/ai/.contextuate/templates'];
294
251
  for (const dir of legacyDirs) {
295
252
  if (fs_extra_1.default.existsSync(dir)) {
296
253
  await fs_extra_1.default.remove(dir);
297
254
  console.log(chalk_1.default.yellow(`[CLEANUP] Removed legacy directory: ${dir}`));
298
255
  }
299
256
  }
257
+ // Cleanup legacy version.json (replaced by contextuate.json)
258
+ const legacyVersionJson = 'docs/ai/.contextuate/version.json';
259
+ if (fs_extra_1.default.existsSync(legacyVersionJson)) {
260
+ await fs_extra_1.default.remove(legacyVersionJson);
261
+ console.log(chalk_1.default.yellow(`[CLEANUP] Removed legacy version.json (migrated to contextuate.json)`));
262
+ }
300
263
  console.log('');
301
- // 2. Copy templates
264
+ // 2. Detect tools
265
+ console.log(chalk_1.default.blue('[INFO] Checking for optional tools...'));
266
+ let tools = (0, tools_1.detectTools)();
267
+ (0, tools_1.printToolStatus)(tools);
268
+ // Offer to install missing tools
269
+ tools = await (0, tools_1.promptInstallTools)(tools, nonInteractive);
270
+ console.log('');
271
+ // 3. Copy framework files
302
272
  console.log(chalk_1.default.blue('[INFO] Installing framework files...'));
303
- // In development (ts-node), this is ../../src/templates relative to src/commands/init.ts
304
- // In production (dist), this is ../templates relative to dist/commands/init.js
305
- let templateSource = path_1.default.join(__dirname, '../templates');
306
- // If running from src/commands (ts-node), we need to go up one more level if structure is src/commands/init.ts
307
- if (path_1.default.basename(path_1.default.join(__dirname, '..')) === 'src') {
308
- templateSource = path_1.default.join(__dirname, '../../src/templates');
309
- }
310
- else if (path_1.default.basename(__dirname) === 'commands') {
311
- // dist/commands/init.js -> dist/templates
312
- templateSource = path_1.default.join(__dirname, '../templates');
313
- }
314
- // Fallback/Verify
315
- if (!fs_extra_1.default.existsSync(templateSource)) {
316
- // Try one level up just in case
317
- templateSource = path_1.default.join(__dirname, '../../templates');
318
- }
319
- if (!fs_extra_1.default.existsSync(templateSource)) {
320
- console.error(chalk_1.default.red(`[ERROR] Could not find template source at ${templateSource}`));
321
- return;
322
- }
323
273
  const installDir = 'docs/ai/.contextuate';
324
- // Helper to copy files
325
- const copyFile = async (src, dest) => {
326
- // Resolve absolute paths to check for equality
274
+ // Helper to copy and process files
275
+ const copyFile = async (src, dest, processTemplates = false) => {
327
276
  const absSrc = path_1.default.resolve(src);
328
277
  const absDest = path_1.default.resolve(dest);
329
- if (absSrc === absDest) {
278
+ if (absSrc === absDest)
330
279
  return;
331
- }
332
280
  if (!fs_extra_1.default.existsSync(src)) {
333
281
  console.log(chalk_1.default.red(`[ERROR] Source file missing: ${src}`));
334
282
  return;
335
283
  }
336
- if (fs_extra_1.default.existsSync(dest) && !opts.force) {
337
- console.log(chalk_1.default.yellow(`[WARN] Skipped (exists): ${dest}`));
284
+ // Only context.md is protected from overwrites
285
+ const isContextMd = path_1.default.basename(dest) === 'context.md';
286
+ if (isContextMd && fs_extra_1.default.existsSync(dest)) {
287
+ console.log(chalk_1.default.yellow(`[PRESERVE] Kept existing: ${dest}`));
338
288
  return;
339
289
  }
340
- await fs_extra_1.default.copy(src, dest);
290
+ let content = await fs_extra_1.default.readFile(src, 'utf-8');
291
+ // Process template variables if needed
292
+ if (processTemplates && dest.endsWith('.md')) {
293
+ content = (0, tools_1.processTemplateVariables)(content, tools);
294
+ }
295
+ await fs_extra_1.default.ensureDir(path_1.default.dirname(dest));
296
+ await fs_extra_1.default.writeFile(dest, content);
341
297
  console.log(chalk_1.default.green(`[OK] Created: ${dest}`));
342
298
  };
343
- // Copy core engine files only to .contextuate
344
- // Generate version.json dynamically from package.json
345
- const pkgInfo = getPackageInfo();
346
- const versionData = {
347
- name: pkgInfo.name,
348
- version: pkgInfo.version,
349
- description: pkgInfo.description,
350
- repository: pkgInfo.repository,
351
- installed: new Date().toISOString(),
352
- updated: new Date().toISOString()
353
- };
354
- await fs_extra_1.default.writeFile(path_1.default.join(installDir, 'version.json'), JSON.stringify(versionData, null, 2));
355
- console.log(chalk_1.default.green(`[OK] Created: ${path_1.default.join(installDir, 'version.json')}`));
356
- await copyFile(path_1.default.join(templateSource, 'README.md'), path_1.default.join(installDir, 'README.md'));
357
- // Copy directories - only core framework files
358
- const copyDirContents = async (srcSubDir, destDir) => {
299
+ // Copy directories with template processing
300
+ const copyDirContents = async (srcSubDir, destDir, processTemplates = false) => {
359
301
  const srcDir = path_1.default.join(templateSource, srcSubDir);
360
302
  if (fs_extra_1.default.existsSync(srcDir)) {
361
303
  await fs_extra_1.default.ensureDir(destDir);
362
304
  const files = await fs_extra_1.default.readdir(srcDir);
363
305
  for (const file of files) {
364
- await copyFile(path_1.default.join(srcDir, file), path_1.default.join(destDir, file));
306
+ await copyFile(path_1.default.join(srcDir, file), path_1.default.join(destDir, file), processTemplates);
365
307
  }
366
308
  }
367
309
  };
368
- // Copy core standards, tools, and framework agents to .contextuate (engine files)
310
+ // Copy README
311
+ await copyFile(path_1.default.join(templateSource, 'README.md'), path_1.default.join(installDir, 'README.md'));
312
+ // Copy core standards, tools, and framework agents
369
313
  await copyDirContents('standards', path_1.default.join(installDir, 'standards'));
370
314
  await copyDirContents('tools', path_1.default.join(installDir, 'tools'));
371
315
  await copyDirContents('framework-agents', path_1.default.join(installDir, 'agents'));
372
316
  console.log(chalk_1.default.green('[OK] Copied framework files'));
373
317
  console.log('');
374
- // 3. Install selected agents to docs/ai/agents/
375
- if (selectedAgents.length > 0) {
376
- console.log(chalk_1.default.blue('[INFO] Installing selected agents...'));
377
- for (const agent of selectedAgents) {
378
- const agentFile = `${agent}.md`;
379
- const srcPath = path_1.default.join(templateSource, 'agents', agentFile);
380
- const destPath = path_1.default.join('docs/ai/agents', agentFile);
381
- await copyFile(srcPath, destPath);
318
+ // 4. Install ALL agents (opinionated - no prompt)
319
+ if (availableAgents.length > 0) {
320
+ console.log(chalk_1.default.blue(`[INFO] Installing agents (${availableAgents.length} agents)...`));
321
+ for (const agent of availableAgents) {
322
+ const srcPath = path_1.default.join(templateSource, 'agents', `${agent}.md`);
323
+ const destPath = path_1.default.join('docs/ai/agents', `${agent}.md`);
324
+ await copyFile(srcPath, destPath, true); // Process templates
382
325
  }
383
- console.log(chalk_1.default.green(`[OK] Installed ${selectedAgents.length} agent(s) to docs/ai/agents/`));
326
+ console.log(chalk_1.default.green(`[OK] Installed ${availableAgents.length} agent(s)`));
384
327
  console.log('');
385
328
  }
386
- // 4. Setup project context files
329
+ // 5. Install ALL skills (opinionated - no prompt)
330
+ if (availableSkills.length > 0) {
331
+ console.log(chalk_1.default.blue(`[INFO] Installing skills (${availableSkills.length} skills)...`));
332
+ for (const skill of availableSkills) {
333
+ const srcPath = path_1.default.join(templateSource, 'commands', `${skill}.md`);
334
+ const destPath = path_1.default.join('docs/ai/commands', `${skill}.md`);
335
+ await copyFile(srcPath, destPath, true); // Process templates
336
+ }
337
+ console.log(chalk_1.default.green(`[OK] Installed ${availableSkills.length} skill(s)`));
338
+ console.log('');
339
+ }
340
+ // 6. Setup project context files
387
341
  console.log(chalk_1.default.blue('[INFO] Setting up project context...'));
388
- // Copy contextuate.md (main entry point) directly from templates to docs/ai/.contextuate/ (protected)
389
342
  await copyFile(path_1.default.join(templateSource, 'templates/contextuate.md'), 'docs/ai/.contextuate/contextuate.md');
390
- // Copy context.md (user customizable) directly from templates to docs/ai/
391
- // IMPORTANT: Never overwrite context.md even with --force, as it contains project-specific context
392
- const contextMdPath = 'docs/ai/context.md';
393
- if (fs_extra_1.default.existsSync(contextMdPath)) {
394
- console.log(chalk_1.default.yellow(`[WARN] Preserved (project context): ${contextMdPath}`));
395
- }
396
- else {
397
- await copyFile(path_1.default.join(templateSource, 'templates/context.md'), contextMdPath);
398
- }
343
+ await copyFile(path_1.default.join(templateSource, 'templates/context.md'), 'docs/ai/context.md');
399
344
  console.log('');
400
- // 5. Generate jump files for selected platforms
401
- console.log(chalk_1.default.blue('[INFO] Generating platform jump files...'));
345
+ // 7. Generate platform files
346
+ console.log(chalk_1.default.blue('[INFO] Generating platform files...'));
402
347
  for (const platform of selectedPlatforms) {
403
348
  if (platform.ensureDir) {
404
349
  await fs_extra_1.default.ensureDir(platform.ensureDir);
405
350
  }
406
- // Copy directly from template source, not from .contextuate
407
351
  await copyFile(path_1.default.join(templateSource, platform.src), platform.dest);
408
352
  }
409
353
  console.log('');
410
- // 6. Create platform-specific symlinks (only for platforms that need them)
354
+ // 8. Create symlinks for platforms that need them
411
355
  const platformsWithSymlinks = selectedPlatforms.filter(p => p.symlinks);
412
356
  if (platformsWithSymlinks.length > 0) {
413
357
  console.log(chalk_1.default.blue('[INFO] Creating platform symlinks...'));
414
- // Helper to create symlinks
415
358
  const createSymlink = async (target, linkPath) => {
416
359
  const linkDir = path_1.default.dirname(linkPath);
417
360
  await fs_extra_1.default.ensureDir(linkDir);
418
- // Calculate relative path from link location to target
419
361
  const relativeTarget = path_1.default.relative(linkDir, target);
420
- // Check if symlink already exists
421
362
  try {
422
363
  const existingLink = await fs_extra_1.default.readlink(linkPath);
423
364
  if (existingLink === relativeTarget) {
424
- console.log(chalk_1.default.yellow(`[WARN] Symlink exists: ${linkPath} -> ${relativeTarget}`));
365
+ console.log(chalk_1.default.yellow(`[SKIP] Symlink exists: ${linkPath}`));
425
366
  return;
426
367
  }
427
- // Remove existing symlink if it points elsewhere
428
368
  await fs_extra_1.default.remove(linkPath);
429
369
  }
430
370
  catch {
431
- // Not a symlink or doesn't exist
432
371
  if (fs_extra_1.default.existsSync(linkPath)) {
433
- if (opts.force) {
434
- await fs_extra_1.default.remove(linkPath);
435
- }
436
- else {
437
- console.log(chalk_1.default.yellow(`[WARN] Skipped (path exists): ${linkPath}`));
438
- return;
439
- }
372
+ await fs_extra_1.default.remove(linkPath);
440
373
  }
441
374
  }
442
375
  await fs_extra_1.default.ensureSymlink(relativeTarget, linkPath);
443
376
  console.log(chalk_1.default.green(`[OK] Symlink: ${linkPath} -> ${relativeTarget}`));
444
377
  };
445
- // Create symlinks for Claude Code
378
+ // Claude Code symlinks
446
379
  if (selectedPlatforms.some(p => p.id === 'claude')) {
447
380
  const symlinks = [
448
381
  { target: 'docs/ai/commands', link: '.claude/commands' },
449
382
  { target: 'docs/ai/agents', link: '.claude/agents' },
450
383
  { target: 'docs/ai/hooks', link: '.claude/hooks' },
451
384
  { target: 'docs/ai/skills', link: '.claude/skills' },
452
- // Link .contextuate so relative paths from agents work correctly
453
- // (e.g., ../.contextuate/agents/base.md resolves properly)
454
385
  { target: 'docs/ai/.contextuate', link: '.claude/.contextuate' },
455
386
  ];
456
387
  for (const symlink of symlinks) {
@@ -459,6 +390,20 @@ async function initCommand(platformArgs, options) {
459
390
  }
460
391
  console.log('');
461
392
  }
393
+ // 9. Create contextuate.json
394
+ console.log(chalk_1.default.blue('[INFO] Creating contextuate.json...'));
395
+ const pkgInfo = getPackageInfo();
396
+ const now = new Date().toISOString();
397
+ const config = {
398
+ ...pkgInfo,
399
+ initialized: now,
400
+ updated: now,
401
+ tools
402
+ };
403
+ await (0, tools_1.writeContextuateConfig)(installDir, config);
404
+ console.log(chalk_1.default.green('[OK] Created contextuate.json'));
405
+ console.log('');
406
+ // Summary
462
407
  console.log(chalk_1.default.green('╔════════════════════════════════════════╗'));
463
408
  console.log(chalk_1.default.green('║ Installation Complete! ║'));
464
409
  console.log(chalk_1.default.green('╚════════════════════════════════════════╝'));
@@ -468,44 +413,35 @@ async function initCommand(platformArgs, options) {
468
413
  console.log(` - ${chalk_1.default.cyan(platform.name)} (${platform.dest})`);
469
414
  }
470
415
  console.log('');
471
- if (selectedAgents.length > 0) {
472
- console.log('Installed agents:');
473
- for (const agent of selectedAgents) {
474
- console.log(` - ${chalk_1.default.cyan(agent)} (docs/ai/agents/${agent}.md)`);
475
- }
476
- console.log('');
477
- }
478
- console.log('Next steps:');
416
+ console.log(`Installed: ${chalk_1.default.cyan(availableAgents.length)} agents, ${chalk_1.default.cyan(availableSkills.length)} skills`);
479
417
  console.log('');
480
- console.log(` 1. Edit ${chalk_1.default.blue('docs/ai/context.md')} with your project details`);
481
- if (selectedAgents.length > 0) {
482
- console.log(` 2. Review installed agents in ${chalk_1.default.blue('docs/ai/agents/')}`);
483
- console.log(` 3. Add custom agents or quickrefs as needed`);
418
+ // Tool status summary
419
+ if (tools.ripgrep.installed) {
420
+ console.log(`Search tool: ${chalk_1.default.green('ripgrep')} (fast)`);
484
421
  }
485
422
  else {
486
- console.log(` 2. Create custom agents in ${chalk_1.default.blue('docs/ai/agents/')}`);
487
- console.log(` 3. Add quickrefs in ${chalk_1.default.blue('docs/ai/quickrefs/')}`);
423
+ console.log(`Search tool: ${chalk_1.default.yellow('grep')} (fallback - consider installing ripgrep)`);
488
424
  }
489
425
  console.log('');
426
+ console.log('Next steps:');
490
427
  console.log('');
491
- console.log('Documentation: https://contextuate.md');
428
+ console.log(` 1. Edit ${chalk_1.default.blue('docs/ai/context.md')} with your project details`);
429
+ console.log(` 2. Review agents in ${chalk_1.default.blue('docs/ai/agents/')}`);
430
+ console.log(` 3. Run ${chalk_1.default.blue('contextuate doctor')} to check configuration`);
492
431
  console.log('');
493
- console.log(chalk_1.default.gray('Contextuate Framework'));
494
- console.log(chalk_1.default.gray('Created by Alexander Conroy (@geilt)'));
432
+ console.log('Documentation: https://contextuate.md');
495
433
  console.log('');
496
434
  }
497
435
  catch (error) {
498
436
  if (error.isTtyError) {
499
- // Prompt couldn't be rendered in the current environment
500
437
  console.error(chalk_1.default.red('[ERROR] Prompt could not be rendered in the current environment'));
501
438
  }
502
439
  else if (error.name === 'ExitPromptError' || error.message.includes('User force closed the prompt')) {
503
440
  console.log('');
504
- console.log(chalk_1.default.yellow('👋 Installation cancelled by user.'));
441
+ console.log(chalk_1.default.yellow('Installation cancelled by user.'));
505
442
  process.exit(0);
506
443
  }
507
444
  else {
508
- // Something else went wrong
509
445
  console.error(chalk_1.default.red('[ERROR] An unexpected error occurred:'), error);
510
446
  process.exit(1);
511
447
  }
@@ -60,10 +60,10 @@ async function discoverTemplates() {
60
60
  .filter(f => f.endsWith('.md'))
61
61
  .map(f => f.replace('.md', ''));
62
62
  }
63
- // Discover skills
64
- const skillsDir = path_1.default.join(templateSource, 'skills');
65
- if (fs_extra_1.default.existsSync(skillsDir)) {
66
- const files = await fs_extra_1.default.readdir(skillsDir);
63
+ // Discover skills (stored in commands/ directory)
64
+ const commandsDir = path_1.default.join(templateSource, 'commands');
65
+ if (fs_extra_1.default.existsSync(commandsDir)) {
66
+ const files = await fs_extra_1.default.readdir(commandsDir);
67
67
  result.skills = files
68
68
  .filter(f => f.endsWith('.md'))
69
69
  .map(f => f.replace('.md', ''));
@@ -188,7 +188,7 @@ async function installTools(names, force) {
188
188
  return installed;
189
189
  }
190
190
  // Install skills (slash commands)
191
- // Skills are installed to docs/ai/skills/ and symlinked to docs/ai/commands/ for Claude Code
191
+ // Skills are installed directly to docs/ai/commands/ for Claude Code
192
192
  async function installSkills(names, force) {
193
193
  const templateSource = getTemplateSource();
194
194
  const templates = await discoverTemplates();
@@ -197,26 +197,10 @@ async function installSkills(names, force) {
197
197
  const normalized = name.toLowerCase().trim().replace(/^\//, ''); // Remove leading slash if present
198
198
  const matched = templates.skills.find(s => s.toLowerCase() === normalized);
199
199
  if (matched) {
200
- const src = path_1.default.join(templateSource, 'skills', `${matched}.md`);
201
- const skillDest = path_1.default.join('docs/ai/skills', `${matched}.md`);
202
- const commandDest = path_1.default.join('docs/ai/commands', `${matched}.md`);
203
- // Install skill file to skills/
204
- if (await copyFile(src, skillDest, force)) {
200
+ const src = path_1.default.join(templateSource, 'commands', `${matched}.md`);
201
+ const dest = path_1.default.join('docs/ai/commands', `${matched}.md`);
202
+ if (await copyFile(src, dest, force)) {
205
203
  installed++;
206
- // Create symlink in commands/ for Claude Code to pick up
207
- await fs_extra_1.default.ensureDir(path_1.default.dirname(commandDest));
208
- const symlinkTarget = path_1.default.relative(path_1.default.dirname(commandDest), skillDest);
209
- if (fs_extra_1.default.existsSync(commandDest)) {
210
- if (force) {
211
- await fs_extra_1.default.remove(commandDest);
212
- }
213
- else {
214
- console.log(chalk_1.default.yellow(`[SKIP] Symlink already exists: ${commandDest}`));
215
- continue;
216
- }
217
- }
218
- await fs_extra_1.default.symlink(symlinkTarget, commandDest);
219
- console.log(chalk_1.default.green(`[OK] Symlinked: ${commandDest} -> ${symlinkTarget}`));
220
204
  }
221
205
  }
222
206
  else {
package/dist/index.js CHANGED
@@ -9,6 +9,7 @@ const create_1 = require("./commands/create");
9
9
  const index_1 = require("./commands/index");
10
10
  const context_1 = require("./commands/context");
11
11
  const install_1 = require("./commands/install");
12
+ const doctor_1 = require("./commands/doctor");
12
13
  const monitor_1 = require("./commands/monitor");
13
14
  const claude_1 = require("./commands/claude");
14
15
  const fs_1 = require("fs");
@@ -54,6 +55,12 @@ program
54
55
  .command('add-context')
55
56
  .description('Interactively add files to docs/ai/context.md')
56
57
  .action(context_1.addContextCommand);
58
+ program
59
+ .command('doctor')
60
+ .description('Check and fix Contextuate configuration')
61
+ .option('--fix', 'Attempt to fix issues and update template variables')
62
+ .option('--install', 'Offer to install missing tools')
63
+ .action(doctor_1.doctorCommand);
57
64
  program
58
65
  .command('run')
59
66
  .description('Run an agent definition')