@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 +9 -1
- package/dist/commands/init.js +4 -2
- package/dist/commands/mcp-setup.js +76 -0
- package/dist/commands/pull.js +5 -2
- package/dist/mcp/index.js +9 -1
- package/dist/utils/env.d.ts +4 -2
- package/dist/utils/env.js +6 -4
- package/package.json +1 -1
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(
|
|
47
|
+
.version(VERSION);
|
|
40
48
|
program
|
|
41
49
|
.command('init')
|
|
42
50
|
.description('Initialize a new dotrequirements project')
|
package/dist/commands/init.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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');
|
package/dist/commands/pull.js
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
342
|
+
version: VERSION,
|
|
335
343
|
}, {
|
|
336
344
|
capabilities: {
|
|
337
345
|
tools: {},
|
package/dist/utils/env.d.ts
CHANGED
|
@@ -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
|
-
//
|
|
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
|
}
|