@popoverai/dotrequirements 0.11.0 → 0.12.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/dist/cli.js CHANGED
@@ -10,6 +10,14 @@ import { mcpSetupCommand } from './commands/mcp-setup.js';
10
10
  import { loginCommand } from './commands/login.js';
11
11
  import { logoutCommand } from './commands/logout.js';
12
12
  import { loadEnvFile } from './utils/env.js';
13
+ import { readFileSync } from 'fs';
14
+ import { fileURLToPath } from 'url';
15
+ import { dirname, join } from 'path';
16
+ // Read version from package.json
17
+ const __filename = fileURLToPath(import.meta.url);
18
+ const __dirname = dirname(__filename);
19
+ const packageJson = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf-8'));
20
+ const VERSION = packageJson.version;
13
21
  // Load environment variables from .env.local if it exists
14
22
  loadEnvFile();
15
23
  /**
@@ -36,7 +44,7 @@ const program = new Command();
36
44
  program
37
45
  .name('dotrequirements')
38
46
  .description('Requirements tracking CLI with test harness and MCP server')
39
- .version('0.1.0');
47
+ .version(VERSION);
40
48
  program
41
49
  .command('init')
42
50
  .description('Initialize a new dotrequirements project')
@@ -165,7 +165,8 @@ async function createNewProjectFlow(cwd, envPath, options) {
165
165
  return;
166
166
  }
167
167
  // Reload env file so newly written credentials are available to subsequent commands
168
- loadEnvFile(cwd);
168
+ // Use force=true to overwrite any previously cached values
169
+ loadEnvFile(cwd, true);
169
170
  // 4. Create .requirements/ directory
170
171
  await createRequirementsDirectory(cwd, projectId);
171
172
  // 5. Update .gitignore
@@ -242,7 +243,8 @@ async function connectExistingProjectFlow(cwd, envPath) {
242
243
  return;
243
244
  }
244
245
  // Reload env file so newly written credentials are available to pullCommand
245
- loadEnvFile(cwd);
246
+ // Use force=true to overwrite any previously cached values
247
+ loadEnvFile(cwd, true);
246
248
  // Create .requirements/ directory
247
249
  await createRequirementsDirectory(cwd, projectId);
248
250
  // Update .gitignore
@@ -106,6 +106,7 @@ export async function mcpSetupCommand() {
106
106
  message: 'Which AI assistant are you using?',
107
107
  choices: [
108
108
  { title: 'Claude Code', value: 'claude-code' },
109
+ { title: 'Claude Desktop', value: 'claude-desktop' },
109
110
  { title: 'Cursor', value: 'cursor' },
110
111
  { title: 'Google Antigravity', value: 'antigravity' },
111
112
  { title: 'OpenAI Codex', value: 'codex' },
@@ -122,6 +123,9 @@ export async function mcpSetupCommand() {
122
123
  case 'claude-code':
123
124
  await setupClaudeCode();
124
125
  break;
126
+ case 'claude-desktop':
127
+ await setupClaudeDesktop();
128
+ break;
125
129
  case 'cursor':
126
130
  await setupCursor();
127
131
  break;
@@ -174,6 +178,78 @@ async function setupClaudeCode() {
174
178
  await installAllSkills('claude-code');
175
179
  }
176
180
  }
181
+ async function setupClaudeDesktop() {
182
+ console.log('\nšŸ“¦ Configuring MCP for Claude Desktop...\n');
183
+ // Determine config path based on OS
184
+ const platform = process.platform;
185
+ let configPath;
186
+ let configDir;
187
+ if (platform === 'darwin') {
188
+ configDir = join(homedir(), 'Library', 'Application Support', 'Claude');
189
+ configPath = join(configDir, 'claude_desktop_config.json');
190
+ }
191
+ else if (platform === 'win32') {
192
+ const appData = process.env.APPDATA || join(homedir(), 'AppData', 'Roaming');
193
+ configDir = join(appData, 'Claude');
194
+ configPath = join(configDir, 'claude_desktop_config.json');
195
+ }
196
+ else {
197
+ console.error('āŒ Claude Desktop is only supported on macOS and Windows.');
198
+ console.log('\nFor Linux, please manually configure the MCP server.');
199
+ return;
200
+ }
201
+ const mcpServerConfig = {
202
+ command: 'npx',
203
+ args: ['-y', '@popoverai/dotrequirements', 'mcp'],
204
+ env: {
205
+ REQUIREMENTS_DIR: process.cwd(),
206
+ },
207
+ };
208
+ try {
209
+ if (!existsSync(configDir)) {
210
+ console.log('Creating Claude Desktop config directory...');
211
+ mkdirSync(configDir, { recursive: true });
212
+ }
213
+ let config = {};
214
+ if (existsSync(configPath)) {
215
+ try {
216
+ const existingContent = readFileSync(configPath, 'utf-8');
217
+ if (existingContent.trim()) {
218
+ config = JSON.parse(existingContent);
219
+ }
220
+ }
221
+ catch (parseError) {
222
+ console.log('āš ļø Existing config file is invalid, creating fresh config...');
223
+ config = {};
224
+ }
225
+ }
226
+ if (!config.mcpServers) {
227
+ config.mcpServers = {};
228
+ }
229
+ config.mcpServers['dotrequirements'] = mcpServerConfig;
230
+ writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
231
+ console.log(`āœ… MCP configured successfully!`);
232
+ console.log(` Config file: ${configPath}`);
233
+ console.log(` REQUIREMENTS_DIR: ${process.cwd()}\n`);
234
+ console.log('āš ļø Important: Quit and restart Claude Desktop for changes to take effect.');
235
+ console.log(' (Cmd+Q on macOS, not just close the window)\n');
236
+ console.log('You can now ask Claude Desktop to help write requirements!\n');
237
+ // Claude Desktop doesn't use skills like Claude Code, so we skip that step
238
+ }
239
+ catch (error) {
240
+ console.error('āŒ Failed to configure MCP automatically.');
241
+ console.log(`\nError: ${error instanceof Error ? error.message : String(error)}`);
242
+ console.log('\nYou can manually edit the config file at:');
243
+ console.log(` ${configPath}\n`);
244
+ console.log('Add the following configuration:');
245
+ const manualConfig = {
246
+ mcpServers: {
247
+ dotrequirements: mcpServerConfig,
248
+ },
249
+ };
250
+ console.log(JSON.stringify(manualConfig, null, 2));
251
+ }
252
+ }
177
253
  async function setupCursor() {
178
254
  console.log('\nšŸ“¦ Configuring MCP for Cursor (project-specific)...\n');
179
255
  const configPath = join(process.cwd(), '.cursor', 'mcp.json');
@@ -151,14 +151,17 @@ function extractDocumentIdFromFile(filePath) {
151
151
  const content = fs.readFileSync(filePath, 'utf-8');
152
152
  // Match document.id in YAML frontmatter - handles both inline and nested formats
153
153
  // Inline: document: { id: "abc123", ... }
154
- // Nested:
154
+ // Nested (id: can appear at any position within the indented document block):
155
155
  // document:
156
+ // title: Some Title
157
+ // defaultPrefix: REQ
156
158
  // id: abc123
157
159
  const inlineMatch = content.match(/document:\s*\{[^}]*id:\s*["']?([^"',}\s]+)/);
158
160
  if (inlineMatch) {
159
161
  return inlineMatch[1];
160
162
  }
161
- const nestedMatch = content.match(/document:\s*\n\s+id:\s*["']?([^"'\s\n]+)/);
163
+ // Match id: anywhere within the document block (allows other properties before it)
164
+ const nestedMatch = content.match(/document:\s*\n(?:[ \t]+[^\n]*\n)*?[ \t]+id:\s*["']?([^"'\s\n]+)/);
162
165
  if (nestedMatch) {
163
166
  return nestedMatch[1];
164
167
  }
package/dist/mcp/index.js CHANGED
@@ -9,6 +9,14 @@ import { glob } from 'glob';
9
9
  import { loadConvexConfig, getRequirementCoverage as queryRequirementCoverage, getProjectCoverage as queryProjectCoverage, } from './convexClient.js';
10
10
  import { discoverProjects, resolveProject, } from '../utils/project-discovery.js';
11
11
  import { isLocalOnlyProject, CLOUD_FEATURES_REQUIRE_AUTH_MESSAGE } from '../utils/local-project.js';
12
+ import { readFileSync } from 'fs';
13
+ import { fileURLToPath } from 'url';
14
+ import { dirname, join } from 'path';
15
+ // Read version from package.json
16
+ const __filename = fileURLToPath(import.meta.url);
17
+ const __dirname = dirname(__filename);
18
+ const packageJson = JSON.parse(readFileSync(join(__dirname, '../../package.json'), 'utf-8'));
19
+ const VERSION = packageJson.version;
12
20
  // Get project paths from environment variables
13
21
  // Supports both PROJ_* pattern (Antigravity) and REQUIREMENTS_DIR fallback
14
22
  function getProjectPathsFromEnv() {
@@ -331,7 +339,7 @@ const prompts = [
331
339
  // Create server
332
340
  const server = new Server({
333
341
  name: 'dotrequirements',
334
- version: '0.1.0',
342
+ version: VERSION,
335
343
  }, {
336
344
  capabilities: {
337
345
  tools: {},
@@ -1,8 +1,10 @@
1
1
  /**
2
2
  * Load environment variables from .env.local file
3
- * Merges with existing process.env (process.env takes precedence)
3
+ * Merges with existing process.env (process.env takes precedence by default)
4
+ * @param cwd - Directory containing .env.local (defaults to process.cwd())
5
+ * @param force - If true, overwrite existing process.env values (used when reloading after init)
4
6
  */
5
- export declare function loadEnvFile(cwd?: string): void;
7
+ export declare function loadEnvFile(cwd?: string, force?: boolean): void;
6
8
  /**
7
9
  * Get project credentials from environment
8
10
  * Returns undefined if not found
package/dist/utils/env.js CHANGED
@@ -2,9 +2,11 @@ import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  /**
4
4
  * Load environment variables from .env.local file
5
- * Merges with existing process.env (process.env takes precedence)
5
+ * Merges with existing process.env (process.env takes precedence by default)
6
+ * @param cwd - Directory containing .env.local (defaults to process.cwd())
7
+ * @param force - If true, overwrite existing process.env values (used when reloading after init)
6
8
  */
7
- export function loadEnvFile(cwd = process.cwd()) {
9
+ export function loadEnvFile(cwd = process.cwd(), force = false) {
8
10
  const envPath = path.join(cwd, '.env.local');
9
11
  if (!fs.existsSync(envPath)) {
10
12
  return;
@@ -22,8 +24,8 @@ export function loadEnvFile(cwd = process.cwd()) {
22
24
  if (match) {
23
25
  const key = match[1].trim();
24
26
  const value = match[2].trim();
25
- // Only set if not already in process.env
26
- if (!process.env[key]) {
27
+ // Set if force=true, or if not already in process.env
28
+ if (force || !process.env[key]) {
27
29
  process.env[key] = value;
28
30
  }
29
31
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@popoverai/dotrequirements",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "Requirements tracking CLI, test harness, and MCP server",
5
5
  "type": "module",
6
6
  "bin": {