@popoverai/dotrequirements 0.13.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +92 -48
- package/dist/cli.js +1 -10
- package/dist/commands/init.js +166 -226
- package/dist/commands/link.d.ts +9 -10
- package/dist/commands/link.js +81 -106
- package/dist/commands/mcp-setup.js +77 -94
- package/dist/commands/pull.d.ts +1 -0
- package/dist/commands/pull.js +70 -53
- package/dist/commands/push.js +4 -13
- package/dist/config.js +0 -4
- package/dist/harness/cache.d.ts +0 -5
- package/dist/harness/cache.js +0 -48
- package/dist/harness/convexReporting.js +7 -14
- package/dist/harness/finalize.js +7 -12
- package/dist/harness/prepare.js +7 -9
- package/dist/mcp/convexClient.d.ts +9 -1
- package/dist/mcp/convexClient.js +15 -35
- package/dist/mcp/index.js +112 -152
- package/dist/mcp/requirements.d.ts +10 -0
- package/dist/mcp/requirements.js +15 -1
- package/dist/templates/context-file-section.md +59 -0
- package/dist/utils/context-file.d.ts +38 -0
- package/dist/utils/context-file.js +94 -0
- package/dist/utils/env.d.ts +0 -13
- package/dist/utils/env.js +0 -19
- package/dist/utils/gitignore.d.ts +2 -2
- package/dist/utils/gitignore.js +4 -4
- package/dist/utils/oauth-flow.d.ts +0 -1
- package/dist/utils/oauth-flow.js +0 -9
- package/dist/utils/project-discovery.d.ts +3 -5
- package/dist/utils/project-discovery.js +18 -42
- package/dist/utils/project-selector.d.ts +17 -3
- package/dist/utils/project-selector.js +35 -3
- package/dist/utils/project-settings.d.ts +56 -0
- package/dist/utils/project-settings.js +126 -0
- package/dist/utils/templates.d.ts +0 -24
- package/dist/utils/templates.js +0 -39
- package/package.json +1 -1
- package/dist/harness/localReporting.d.ts +0 -6
- package/dist/harness/localReporting.js +0 -49
- package/dist/templates/antigravity-gemini.md +0 -3
- package/dist/templates/antigravity-overview-rule.md +0 -3
- package/dist/templates/antigravity-test-rule.md +0 -3
- package/dist/templates/behavioral-core.md +0 -25
- package/dist/templates/claude-code-overview-skill.md +0 -6
- package/dist/templates/claude-code-skill.md +0 -6
- package/dist/templates/claude-code-test-skill.md +0 -6
- package/dist/templates/codex-agents.md +0 -3
- package/dist/templates/codex-overview-agents.md +0 -3
- package/dist/templates/codex-test-agents.md +0 -3
- package/dist/templates/cursor-overview-rule.mdc +0 -5
- package/dist/templates/cursor-rule.mdc +0 -5
- package/dist/templates/cursor-test-rule.mdc +0 -5
- package/dist/templates/overview-core.md +0 -27
- package/dist/templates/test-writing-core.md +0 -72
- package/dist/utils/detect-existing-project.d.ts +0 -5
- package/dist/utils/detect-existing-project.js +0 -34
package/dist/commands/link.js
CHANGED
|
@@ -1,74 +1,81 @@
|
|
|
1
|
-
import
|
|
2
|
-
import * as path from 'path';
|
|
3
|
-
import { promptConfirm } from '../utils/prompts.js';
|
|
1
|
+
import { promptConfirm, promptChoice } from '../utils/prompts.js';
|
|
4
2
|
import { pullCommand } from './pull.js';
|
|
5
|
-
import { hasValidTokens, getTokens } from '../utils/token-storage.js';
|
|
6
3
|
import { executeOAuthFlow } from '../utils/oauth-flow.js';
|
|
7
4
|
import { ConvexHttpClient } from 'convex/browser';
|
|
8
5
|
import { getConvexUrl } from '../config.js';
|
|
9
6
|
import { brand } from '../utils/brand.js';
|
|
10
|
-
import { selectTeam, selectProject, getOrCreateProjectSecret } from '../utils/project-selector.js';
|
|
7
|
+
import { selectTeam, selectProject, getOrCreateProjectSecret, promptExpiryDays } from '../utils/project-selector.js';
|
|
8
|
+
import { findProjectRoot, readProjectSettings, writeProjectSettings, } from '../utils/project-settings.js';
|
|
11
9
|
/**
|
|
12
|
-
* Link command - connect local
|
|
10
|
+
* Link command - connect or reconnect local project to cloud.
|
|
13
11
|
*
|
|
14
|
-
*
|
|
15
|
-
* 1
|
|
16
|
-
* 2
|
|
17
|
-
* 3
|
|
18
|
-
* 4
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* 8. Optionally pull requirements
|
|
12
|
+
* Requirements:
|
|
13
|
+
* - LINK-1: Requires existing .requirements/ folder
|
|
14
|
+
* - LINK-2: Offers refresh when credentials exist
|
|
15
|
+
* - LINK-3: Uses inline OAuth (no persistent tokens)
|
|
16
|
+
* - LINK-4, LINK-5: Team and project selection
|
|
17
|
+
* - LINK-6: Saves credentials to project-settings.json
|
|
18
|
+
* - LINK-7: Optionally pulls requirements
|
|
19
|
+
* - LINK-8: Handles network errors gracefully
|
|
23
20
|
*/
|
|
24
21
|
export async function linkCommand() {
|
|
25
22
|
try {
|
|
26
|
-
console.log(`Linking to
|
|
23
|
+
console.log(`Linking to ${brand} cloud...\n`);
|
|
27
24
|
const cwd = process.cwd();
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
25
|
+
// LINK-1: Check if .requirements/ folder exists
|
|
26
|
+
const projectRoot = findProjectRoot(cwd);
|
|
27
|
+
if (!projectRoot || projectRoot !== cwd) {
|
|
28
|
+
console.log('No .requirements/ folder found.');
|
|
29
|
+
console.log('Run "dotrequirements init" to initialize a project first.\n');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
// LINK-2: Check for existing credentials and offer refresh
|
|
33
|
+
let existingCredentials;
|
|
34
|
+
try {
|
|
35
|
+
existingCredentials = readProjectSettings(projectRoot);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// Invalid settings - treat as no credentials
|
|
39
|
+
existingCredentials = undefined;
|
|
40
|
+
}
|
|
41
|
+
if (existingCredentials) {
|
|
42
|
+
const shouldContinue = await handleExistingCredentials(existingCredentials);
|
|
43
|
+
if (!shouldContinue) {
|
|
46
44
|
return;
|
|
47
45
|
}
|
|
48
46
|
}
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
// LINK-3: Inline OAuth (no persistent tokens)
|
|
48
|
+
let oauthResult;
|
|
49
|
+
try {
|
|
50
|
+
oauthResult = await executeOAuthFlow();
|
|
51
|
+
console.log();
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
console.error('\nAuthentication failed:', error instanceof Error ? error.message : String(error));
|
|
55
|
+
console.log('Please try again later.\n');
|
|
56
|
+
return;
|
|
53
57
|
}
|
|
58
|
+
// Set up authenticated client using the session token
|
|
54
59
|
const client = new ConvexHttpClient(getConvexUrl());
|
|
55
|
-
client.setAuth(
|
|
56
|
-
//
|
|
57
|
-
const
|
|
58
|
-
if (!
|
|
59
|
-
console.log('
|
|
60
|
+
client.setAuth(oauthResult.accessToken);
|
|
61
|
+
// LINK-4: Select team
|
|
62
|
+
const selectedTeam = await selectTeam(client);
|
|
63
|
+
if (!selectedTeam) {
|
|
64
|
+
console.log('Link cancelled.');
|
|
60
65
|
return;
|
|
61
66
|
}
|
|
62
|
-
//
|
|
63
|
-
const selectedProject = await selectProject(client,
|
|
67
|
+
// LINK-5: Select project
|
|
68
|
+
const selectedProject = await selectProject(client, selectedTeam.teamId);
|
|
64
69
|
if (!selectedProject) {
|
|
65
|
-
|
|
70
|
+
// selectProject already shows message about no projects
|
|
66
71
|
return;
|
|
67
72
|
}
|
|
68
|
-
//
|
|
73
|
+
// AUTHZ-3: Prompt for secret expiry (paid tier only)
|
|
74
|
+
const expiryDays = await promptExpiryDays(selectedTeam.tier);
|
|
75
|
+
// LINK-5.3: Get or create secret for this project
|
|
69
76
|
console.log('\nGetting project credentials...');
|
|
70
|
-
const secretResult = await getOrCreateProjectSecret(client, selectedProject.projectId);
|
|
71
|
-
const
|
|
77
|
+
const secretResult = await getOrCreateProjectSecret(client, selectedProject.projectId, expiryDays);
|
|
78
|
+
const projectId = selectedProject.projectSlug || selectedProject.projectId;
|
|
72
79
|
const secret = secretResult.secret;
|
|
73
80
|
if (secretResult.created) {
|
|
74
81
|
console.log('✓ Created new project secret');
|
|
@@ -76,23 +83,23 @@ export async function linkCommand() {
|
|
|
76
83
|
else {
|
|
77
84
|
console.log('✓ Retrieved existing project secret');
|
|
78
85
|
}
|
|
79
|
-
// 6
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
// 7. Optionally pull requirements
|
|
86
|
+
// LINK-6: Write credentials to project-settings.json
|
|
87
|
+
writeProjectSettings(projectRoot, { projectId, projectSecret: secret });
|
|
88
|
+
console.log('✓ Wrote credentials to .requirements/project-settings.json');
|
|
89
|
+
// LINK-7: Optionally pull requirements
|
|
85
90
|
const shouldPull = await promptConfirm('Pull requirements from cloud now?', true);
|
|
86
91
|
if (shouldPull) {
|
|
87
92
|
console.log('\nPulling requirements...');
|
|
88
|
-
await pullCommand({ project:
|
|
93
|
+
await pullCommand({ project: projectId });
|
|
89
94
|
}
|
|
95
|
+
// LINK-7.2: Success message
|
|
90
96
|
console.log(`\n✓ Successfully linked to project: ${selectedProject.projectName}`);
|
|
91
|
-
console.log(` Project ID: ${
|
|
97
|
+
console.log(` Project ID: ${projectId}`);
|
|
92
98
|
console.log('\nDocumentation: https://docs.dotrequirements.io/getting-started');
|
|
93
99
|
}
|
|
94
100
|
catch (error) {
|
|
95
101
|
console.error(`\n✗ Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
102
|
+
// LINK-8: Network error guidance
|
|
96
103
|
if (error instanceof Error && error.message.includes('fetch')) {
|
|
97
104
|
console.error('\nCheck your internet connection and try again.');
|
|
98
105
|
console.error('If the problem persists, visit https://status.convex.dev');
|
|
@@ -101,56 +108,24 @@ export async function linkCommand() {
|
|
|
101
108
|
}
|
|
102
109
|
}
|
|
103
110
|
/**
|
|
104
|
-
*
|
|
105
|
-
* Returns true if
|
|
106
|
-
* Returns false if user
|
|
111
|
+
* LINK-2: Handle existing credentials - offer refresh or connect to different project
|
|
112
|
+
* Returns true if user wants to continue (either refresh or different project)
|
|
113
|
+
* Returns false if user cancels
|
|
107
114
|
*/
|
|
108
|
-
async function
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
];
|
|
114
|
-
if (
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const hasProjectSecret = existingContent.includes('DOTREQUIREMENTS_PROJECT_SECRET');
|
|
118
|
-
if (hasProjectId || hasProjectSecret) {
|
|
119
|
-
const shouldUpdate = await promptConfirm(`.env.local already contains ${brand} configuration. Update it?`, true);
|
|
120
|
-
if (!shouldUpdate) {
|
|
121
|
-
console.log('Aborted.');
|
|
122
|
-
return false;
|
|
123
|
-
}
|
|
124
|
-
// Remove old dotrequirements lines
|
|
125
|
-
const lines = existingContent.split('\n');
|
|
126
|
-
const filteredLines = lines.filter(line => !line.includes('DOTREQUIREMENTS_PROJECT_ID') &&
|
|
127
|
-
!line.includes('DOTREQUIREMENTS_PROJECT_SECRET') &&
|
|
128
|
-
!(line.trim() === '# dotrequirements configuration') &&
|
|
129
|
-
!(line.trim() === '# dot•requirements configuration'));
|
|
130
|
-
const updatedContent = filteredLines.join('\n').trimEnd() + '\n\n' + newEnvLines.join('\n') + '\n';
|
|
131
|
-
fs.writeFileSync(envPath, updatedContent);
|
|
132
|
-
console.log(`✓ Updated ${brand} configuration in .env.local`);
|
|
133
|
-
return true;
|
|
134
|
-
}
|
|
135
|
-
else {
|
|
136
|
-
const shouldAppend = await promptConfirm(`.env.local already exists. Add ${brand} configuration to it?`, true);
|
|
137
|
-
if (!shouldAppend) {
|
|
138
|
-
console.log('\nSkipped writing to .env.local. Please add these values manually:');
|
|
139
|
-
console.log(` DOTREQUIREMENTS_PROJECT_ID=${projectId}`);
|
|
140
|
-
console.log(` DOTREQUIREMENTS_PROJECT_SECRET=${secret}\n`);
|
|
141
|
-
return true;
|
|
142
|
-
}
|
|
143
|
-
const appendContent = '\n' + newEnvLines.join('\n') + '\n';
|
|
144
|
-
fs.appendFileSync(envPath, appendContent);
|
|
145
|
-
console.log(`✓ Added ${brand} configuration to .env.local`);
|
|
146
|
-
return true;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
else {
|
|
150
|
-
const envContent = newEnvLines.join('\n') + '\n';
|
|
151
|
-
fs.writeFileSync(envPath, envContent);
|
|
152
|
-
console.log('✓ Wrote configuration to .env.local');
|
|
153
|
-
return true;
|
|
115
|
+
async function handleExistingCredentials(credentials) {
|
|
116
|
+
console.log(`Currently linked to: ${credentials.projectId}\n`);
|
|
117
|
+
const choice = await promptChoice('What would you like to do?', [
|
|
118
|
+
{ title: 'Refresh credentials for this project', value: 'refresh' },
|
|
119
|
+
{ title: 'Connect to a different project', value: 'different' },
|
|
120
|
+
]);
|
|
121
|
+
if (!choice) {
|
|
122
|
+
console.log('Link cancelled.');
|
|
123
|
+
return false;
|
|
154
124
|
}
|
|
125
|
+
// Both options continue with OAuth flow
|
|
126
|
+
// For 'refresh', we'll match on the same project after team/project selection
|
|
127
|
+
// For 'different', user selects any project
|
|
128
|
+
// The flow is the same - user authenticates and selects team/project
|
|
129
|
+
return true;
|
|
155
130
|
}
|
|
156
131
|
//# sourceMappingURL=link.js.map
|
|
@@ -1,98 +1,71 @@
|
|
|
1
1
|
import { execSync } from 'child_process';
|
|
2
2
|
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
3
3
|
import { homedir } from 'os';
|
|
4
|
-
import { join
|
|
4
|
+
import { join } from 'path';
|
|
5
5
|
import prompts from 'prompts';
|
|
6
|
-
import {
|
|
7
|
-
import { loadPlatformTemplate, loadTestWritingTemplate, loadOverviewTemplate } from '../utils/templates.js';
|
|
6
|
+
import { loadTemplate } from '../utils/templates.js';
|
|
8
7
|
import { brand } from '../utils/brand.js';
|
|
8
|
+
import { getContextFileName, getContextFilePath, findDotrequirementsSection, appendOrUpdateSection, } from '../utils/context-file.js';
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
10
|
+
* Load the context file section template
|
|
11
11
|
*/
|
|
12
|
-
|
|
13
|
-
'
|
|
14
|
-
'cursor': '.cursor/skills',
|
|
15
|
-
'antigravity': '.agent/skills',
|
|
16
|
-
'codex': '.codex/skills',
|
|
17
|
-
'github-copilot': '.github/skills',
|
|
18
|
-
};
|
|
19
|
-
/**
|
|
20
|
-
* Load skill content by name
|
|
21
|
-
*/
|
|
22
|
-
function loadSkillContent(skillName) {
|
|
23
|
-
switch (skillName) {
|
|
24
|
-
case 'dotreq-requirements':
|
|
25
|
-
return loadPlatformTemplate('claude-code-skill.md');
|
|
26
|
-
case 'dotreq-tests':
|
|
27
|
-
return loadTestWritingTemplate('claude-code-test-skill.md');
|
|
28
|
-
case 'dotreq-overview':
|
|
29
|
-
return loadOverviewTemplate('claude-code-overview-skill.md');
|
|
30
|
-
}
|
|
12
|
+
function loadContextFileSection() {
|
|
13
|
+
return loadTemplate('context-file-section.md');
|
|
31
14
|
}
|
|
32
15
|
/**
|
|
33
|
-
* Install
|
|
16
|
+
* Install workflow guidance to the appropriate context file for a platform
|
|
34
17
|
*/
|
|
35
|
-
async function
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
mkdirSync(skillDir, { recursive: true });
|
|
18
|
+
async function installContextFileSection(platform) {
|
|
19
|
+
const contextFilePath = await getContextFilePath(platform);
|
|
20
|
+
if (!contextFilePath) {
|
|
21
|
+
console.error(`\n❌ Could not determine context file path for ${platform}`);
|
|
22
|
+
return;
|
|
41
23
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
24
|
+
const fileName = getContextFileName(platform);
|
|
25
|
+
const sectionContent = loadContextFileSection();
|
|
26
|
+
// Check if file exists and has existing section
|
|
27
|
+
let existingSection = null;
|
|
28
|
+
if (existsSync(contextFilePath)) {
|
|
29
|
+
const content = readFileSync(contextFilePath, 'utf-8');
|
|
30
|
+
const section = findDotrequirementsSection(content);
|
|
31
|
+
if (section) {
|
|
32
|
+
existingSection = section.content;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// Prompt user
|
|
36
|
+
const action = existingSection ? 'Update' : existsSync(contextFilePath) ? 'Add to' : 'Create';
|
|
49
37
|
const response = await prompts({
|
|
50
38
|
type: 'confirm',
|
|
51
39
|
name: 'install',
|
|
52
|
-
message:
|
|
40
|
+
message: `${action} ${fileName} with dotrequirements workflow guidance?`,
|
|
53
41
|
initial: true,
|
|
54
42
|
});
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Install all three skills to a platform's skills directory
|
|
59
|
-
*/
|
|
60
|
-
async function installAllSkills(platform, customPath) {
|
|
61
|
-
const gitDir = await findUp('.git', { type: 'directory' });
|
|
62
|
-
if (!gitDir) {
|
|
63
|
-
console.error('\n❌ Could not find git root directory.');
|
|
64
|
-
console.log(' Make sure you are inside a git repository.');
|
|
43
|
+
if (!response.install) {
|
|
44
|
+
console.log('');
|
|
65
45
|
return;
|
|
66
46
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
];
|
|
79
|
-
for (const skill of skills) {
|
|
80
|
-
const skillPath = join(gitRoot, baseDir, skill.name, 'SKILL.md');
|
|
81
|
-
const shouldInstall = await promptForGuidanceInstall(skillPath);
|
|
82
|
-
if (shouldInstall) {
|
|
83
|
-
try {
|
|
84
|
-
await installSkill(skill.name, baseDir, gitRoot);
|
|
85
|
-
console.log(`✅ ${skill.label} guidance installed\n`);
|
|
86
|
-
}
|
|
87
|
-
catch (error) {
|
|
88
|
-
console.error(`❌ Failed to install ${skill.label} guidance`);
|
|
89
|
-
console.log(` Error: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
else {
|
|
93
|
-
console.log('');
|
|
47
|
+
// If updating, show what will change
|
|
48
|
+
if (existingSection) {
|
|
49
|
+
const confirmUpdate = await prompts({
|
|
50
|
+
type: 'confirm',
|
|
51
|
+
name: 'confirm',
|
|
52
|
+
message: 'Existing dotrequirements section found. Replace it?',
|
|
53
|
+
initial: true,
|
|
54
|
+
});
|
|
55
|
+
if (!confirmUpdate.confirm) {
|
|
56
|
+
console.log(' Skipped.\n');
|
|
57
|
+
return;
|
|
94
58
|
}
|
|
95
59
|
}
|
|
60
|
+
try {
|
|
61
|
+
const result = appendOrUpdateSection(contextFilePath, sectionContent);
|
|
62
|
+
const actionVerb = result.action === 'created' ? 'Created' : result.action === 'appended' ? 'Added to' : 'Updated';
|
|
63
|
+
console.log(`✅ ${actionVerb} ${contextFilePath}\n`);
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
console.error(`❌ Failed to update ${fileName}`);
|
|
67
|
+
console.log(` Error: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
68
|
+
}
|
|
96
69
|
}
|
|
97
70
|
/**
|
|
98
71
|
* MCP setup command - configures the MCP server for AI assistants
|
|
@@ -175,7 +148,7 @@ async function setupClaudeCode() {
|
|
|
175
148
|
if (mcpConfigured) {
|
|
176
149
|
console.log('\n⚠️ Important: Restart Claude Code for changes to take effect.');
|
|
177
150
|
console.log('\nYou can now ask your AI assistant to help write requirements!\n');
|
|
178
|
-
await
|
|
151
|
+
await installContextFileSection('claude-code');
|
|
179
152
|
}
|
|
180
153
|
}
|
|
181
154
|
async function setupClaudeDesktop() {
|
|
@@ -285,7 +258,7 @@ async function setupCursor() {
|
|
|
285
258
|
console.log(` Config file: ${configPath}\n`);
|
|
286
259
|
console.log('⚠️ Important: Reload or restart Cursor for changes to take effect.');
|
|
287
260
|
console.log('\nYou can now ask your AI assistant to help write requirements!\n');
|
|
288
|
-
await
|
|
261
|
+
await installContextFileSection('cursor');
|
|
289
262
|
}
|
|
290
263
|
catch (error) {
|
|
291
264
|
console.error('❌ Failed to configure MCP automatically.');
|
|
@@ -379,7 +352,7 @@ async function setupAntigravity() {
|
|
|
379
352
|
else {
|
|
380
353
|
console.log('');
|
|
381
354
|
}
|
|
382
|
-
await
|
|
355
|
+
await installContextFileSection('antigravity');
|
|
383
356
|
}
|
|
384
357
|
catch (error) {
|
|
385
358
|
console.error('❌ Failed to configure MCP automatically.');
|
|
@@ -396,7 +369,7 @@ async function setupCodex() {
|
|
|
396
369
|
console.log('\n✅ MCP configured successfully for Codex!');
|
|
397
370
|
console.log('\n⚠️ Important: Restart Codex for changes to take effect.');
|
|
398
371
|
console.log('\nYou can now ask your AI assistant to help write requirements!\n');
|
|
399
|
-
await
|
|
372
|
+
await installContextFileSection('codex');
|
|
400
373
|
}
|
|
401
374
|
catch (error) {
|
|
402
375
|
console.error('\n❌ Failed to configure MCP automatically.');
|
|
@@ -413,24 +386,27 @@ async function setupGitHubCopilot() {
|
|
|
413
386
|
console.log('ℹ️ GitHub Copilot discovers MCP servers from your IDE configuration.');
|
|
414
387
|
console.log(' Please configure the MCP server in VS Code settings or GitHub settings.\n');
|
|
415
388
|
console.log(' MCP server command: npx -y @popoverai/dotrequirements mcp\n');
|
|
416
|
-
await
|
|
389
|
+
await installContextFileSection('github-copilot');
|
|
417
390
|
}
|
|
418
391
|
async function setupOther() {
|
|
419
|
-
console.log('\n📦 Custom
|
|
420
|
-
console.log('ℹ️
|
|
421
|
-
console.log(' Common
|
|
422
|
-
console.log(' - .
|
|
423
|
-
console.log(' - .
|
|
424
|
-
console.log(' - .
|
|
425
|
-
console.log(' - .codex/skills/ (Codex)');
|
|
426
|
-
console.log(' - .github/skills/ (GitHub Copilot)\n');
|
|
392
|
+
console.log('\n📦 Custom Context File Setup\n');
|
|
393
|
+
console.log('ℹ️ Workflow guidance is added to a context file at your project root.');
|
|
394
|
+
console.log(' Common file names by platform:');
|
|
395
|
+
console.log(' - CLAUDE.md (Claude Code)');
|
|
396
|
+
console.log(' - AGENTS.md (Cursor, Codex, GitHub Copilot)');
|
|
397
|
+
console.log(' - GEMINI.md (Antigravity)\n');
|
|
427
398
|
const response = await prompts({
|
|
428
|
-
type: '
|
|
429
|
-
name: '
|
|
430
|
-
message: '
|
|
431
|
-
|
|
399
|
+
type: 'select',
|
|
400
|
+
name: 'file',
|
|
401
|
+
message: 'Which context file should we update?',
|
|
402
|
+
choices: [
|
|
403
|
+
{ title: 'AGENTS.md (most compatible)', value: 'AGENTS.md' },
|
|
404
|
+
{ title: 'CLAUDE.md', value: 'CLAUDE.md' },
|
|
405
|
+
{ title: 'GEMINI.md', value: 'GEMINI.md' },
|
|
406
|
+
],
|
|
407
|
+
initial: 0,
|
|
432
408
|
});
|
|
433
|
-
if (!response.
|
|
409
|
+
if (!response.file) {
|
|
434
410
|
console.log('\nSetup cancelled.');
|
|
435
411
|
return;
|
|
436
412
|
}
|
|
@@ -438,6 +414,13 @@ async function setupOther() {
|
|
|
438
414
|
console.log('Configure your AI assistant to run the MCP server:');
|
|
439
415
|
console.log(' Command: npx');
|
|
440
416
|
console.log(' Args: -y @popoverai/dotrequirements mcp\n');
|
|
441
|
-
|
|
417
|
+
// Determine platform based on file choice for the install function
|
|
418
|
+
const platformMap = {
|
|
419
|
+
'CLAUDE.md': 'claude-code',
|
|
420
|
+
'AGENTS.md': 'cursor', // Any AGENTS.md platform works
|
|
421
|
+
'GEMINI.md': 'antigravity',
|
|
422
|
+
};
|
|
423
|
+
const platform = platformMap[response.file];
|
|
424
|
+
await installContextFileSection(platform);
|
|
442
425
|
}
|
|
443
426
|
//# sourceMappingURL=mcp-setup.js.map
|
package/dist/commands/pull.d.ts
CHANGED
package/dist/commands/pull.js
CHANGED
|
@@ -4,64 +4,80 @@ import { ConvexHttpClient } from 'convex/browser';
|
|
|
4
4
|
import { getConvexUrl } from '../config.js';
|
|
5
5
|
import { buildRequirementsFile } from '../schema/index.js';
|
|
6
6
|
import { api } from '../convex.js';
|
|
7
|
-
import { isLocalOnlyProject, CLOUD_FEATURES_REQUIRE_AUTH_MESSAGE } from '../utils/local-project.js';
|
|
8
7
|
import { brand } from '../utils/brand.js';
|
|
9
8
|
import { findRequirementsFiles } from '../mcp/requirements.js';
|
|
9
|
+
import { getProjectCredentials } from '../utils/project-settings.js';
|
|
10
10
|
export async function pullCommand(options) {
|
|
11
11
|
try {
|
|
12
|
-
console.log(`Syncing requirements from ${brand} cloud...`);
|
|
13
12
|
// Connect to Convex
|
|
14
13
|
const convexUrl = getConvexUrl();
|
|
15
14
|
const client = new ConvexHttpClient(convexUrl);
|
|
16
|
-
|
|
17
|
-
let
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
15
|
+
let documents;
|
|
16
|
+
let projectName;
|
|
17
|
+
let isShareTokenFlow = false;
|
|
18
|
+
// SHARE-TOKEN-CLI-1: Handle share token flow (no project config needed)
|
|
19
|
+
if (options.share) {
|
|
20
|
+
isShareTokenFlow = true;
|
|
21
|
+
console.log(`Pulling requirements with share token...`);
|
|
22
|
+
try {
|
|
23
|
+
// SHARE-TOKEN-CLI-1.1: Pass only projectSecret, no projectSlug
|
|
24
|
+
// The authzQueryReadOnly wrapper will resolve project from the token
|
|
25
|
+
const result = await client.query(api.documents.queries.exportProjectForCli, {
|
|
26
|
+
projectAuth: {
|
|
27
|
+
projectSecret: options.share,
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
documents = result.documents;
|
|
31
|
+
projectName = result.projectName;
|
|
32
|
+
// SHARE-TOKEN-CLI-1.2: Display project name in output
|
|
33
|
+
console.log(`\nProject: "${projectName}"`);
|
|
25
34
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
35
|
+
catch (error) {
|
|
36
|
+
// SHARE-TOKEN-CLI-2: Handle invalid/revoked tokens gracefully
|
|
37
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
38
|
+
if (errorMessage.includes('Invalid share token')) {
|
|
39
|
+
throw new Error('Invalid share token. The token may be incorrect or has been revoked.\n' +
|
|
40
|
+
'Please request a new share token from your team.');
|
|
41
|
+
}
|
|
42
|
+
throw error;
|
|
29
43
|
}
|
|
30
44
|
}
|
|
31
|
-
// Load project secret for authentication
|
|
32
|
-
const projectSecret = process.env.DOTREQUIREMENTS_PROJECT_SECRET;
|
|
33
|
-
if (!projectSecret) {
|
|
34
|
-
throw new Error('DOTREQUIREMENTS_PROJECT_SECRET not found. Either:\n' +
|
|
35
|
-
' 1. Run "dotrequirements init" or "dotrequirements login" to set up authentication, or\n' +
|
|
36
|
-
' 2. Set DOTREQUIREMENTS_PROJECT_SECRET in .env.local');
|
|
37
|
-
}
|
|
38
|
-
// Fetch documents
|
|
39
|
-
let documents;
|
|
40
|
-
if (options.document) {
|
|
41
|
-
// Fetch specific document
|
|
42
|
-
const doc = await client.query(api.documents.queries.exportForCli, {
|
|
43
|
-
projectAuth: {
|
|
44
|
-
projectSlug: projectId,
|
|
45
|
-
projectSecret,
|
|
46
|
-
},
|
|
47
|
-
target: { type: 'document', id: options.document },
|
|
48
|
-
documentId: options.document,
|
|
49
|
-
});
|
|
50
|
-
documents = doc ? [doc] : [];
|
|
51
|
-
}
|
|
52
|
-
else if (projectId) {
|
|
53
|
-
// Fetch all documents for project
|
|
54
|
-
// SYNC-ARCH-3.1: Use projectSlug (not projectId) since env stores the slug
|
|
55
|
-
documents = await client.query(api.documents.queries.exportProjectForCli, {
|
|
56
|
-
projectAuth: {
|
|
57
|
-
projectSlug: projectId,
|
|
58
|
-
projectSecret,
|
|
59
|
-
},
|
|
60
|
-
target: { type: 'project', slug: projectId },
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
45
|
else {
|
|
64
|
-
|
|
46
|
+
console.log(`Syncing requirements from ${brand} cloud...`);
|
|
47
|
+
// Get project credentials from .requirements/project-settings.json
|
|
48
|
+
// This throws helpful errors if project not found or not connected to cloud
|
|
49
|
+
const credentials = getProjectCredentials();
|
|
50
|
+
// Allow --project flag to override, but still need secret from settings
|
|
51
|
+
const projectId = options.project ?? credentials.projectId;
|
|
52
|
+
const projectSecret = credentials.projectSecret;
|
|
53
|
+
// Fetch documents
|
|
54
|
+
if (options.document) {
|
|
55
|
+
// Fetch specific document
|
|
56
|
+
const doc = await client.query(api.documents.queries.exportForCli, {
|
|
57
|
+
projectAuth: {
|
|
58
|
+
projectSlug: projectId,
|
|
59
|
+
projectSecret,
|
|
60
|
+
},
|
|
61
|
+
target: { type: 'document', id: options.document },
|
|
62
|
+
documentId: options.document,
|
|
63
|
+
});
|
|
64
|
+
documents = doc ? [doc] : [];
|
|
65
|
+
}
|
|
66
|
+
else if (projectId) {
|
|
67
|
+
// Fetch all documents for project
|
|
68
|
+
// SYNC-ARCH-3.1: Use projectSlug (not projectId) since env stores the slug
|
|
69
|
+
const result = await client.query(api.documents.queries.exportProjectForCli, {
|
|
70
|
+
projectAuth: {
|
|
71
|
+
projectSlug: projectId,
|
|
72
|
+
projectSecret,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
documents = result.documents;
|
|
76
|
+
projectName = result.projectName;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
throw new Error('Either --project or --document must be specified');
|
|
80
|
+
}
|
|
65
81
|
}
|
|
66
82
|
if (documents.length === 0) {
|
|
67
83
|
console.log('No documents found.');
|
|
@@ -98,6 +114,13 @@ export async function pullCommand(options) {
|
|
|
98
114
|
console.log(`✓ Synced: ${fileName} (version ${doc.version})`);
|
|
99
115
|
}
|
|
100
116
|
console.log(`\nSuccessfully synced ${documents.length} document(s) to .requirements/`);
|
|
117
|
+
// SHARE-TOKEN-CLI-3: Show next steps for share token users
|
|
118
|
+
if (isShareTokenFlow) {
|
|
119
|
+
console.log('\n─────────────────────────────────────────────────────────');
|
|
120
|
+
console.log('Next steps to get full access (push, coverage):');
|
|
121
|
+
console.log(' Run: dotrequirements link');
|
|
122
|
+
console.log('─────────────────────────────────────────────────────────');
|
|
123
|
+
}
|
|
101
124
|
}
|
|
102
125
|
catch (error) {
|
|
103
126
|
// Re-throw to let the CLI error handler display it cleanly
|
|
@@ -113,12 +136,6 @@ function sanitizeFileName(title) {
|
|
|
113
136
|
.replace(/[^a-z0-9]+/g, '-')
|
|
114
137
|
.replace(/^-+|-+$/g, '');
|
|
115
138
|
}
|
|
116
|
-
/**
|
|
117
|
-
* Load project ID from process.env (already loaded from .env.local by CLI)
|
|
118
|
-
*/
|
|
119
|
-
function loadProjectIdFromEnv() {
|
|
120
|
-
return process.env.DOTREQUIREMENTS_PROJECT_ID;
|
|
121
|
-
}
|
|
122
139
|
/**
|
|
123
140
|
* Build an index mapping document IDs to their file paths.
|
|
124
141
|
* Scans existing .requirements.md files across the entire workspace and extracts document.id from frontmatter.
|