@popoverai/dotrequirements 0.12.1 → 0.14.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.
Files changed (61) hide show
  1. package/README.md +66 -61
  2. package/dist/cli.js +0 -10
  3. package/dist/commands/init.js +167 -224
  4. package/dist/commands/link.d.ts +9 -10
  5. package/dist/commands/link.js +81 -106
  6. package/dist/commands/mcp-setup.js +77 -94
  7. package/dist/commands/pull.js +17 -45
  8. package/dist/commands/push.js +9 -24
  9. package/dist/convex.d.ts +3 -0
  10. package/dist/convex.js +3 -0
  11. package/dist/harness/cache.d.ts +0 -5
  12. package/dist/harness/cache.js +0 -48
  13. package/dist/harness/convexReporting.js +7 -14
  14. package/dist/harness/finalize.d.ts +8 -2
  15. package/dist/harness/finalize.js +48 -31
  16. package/dist/harness/prepare.js +7 -9
  17. package/dist/mcp/convexClient.d.ts +5 -1
  18. package/dist/mcp/convexClient.js +14 -34
  19. package/dist/mcp/index.js +25 -78
  20. package/dist/schema/conversions.d.ts +2 -2
  21. package/dist/schema/conversions.js +2 -3
  22. package/dist/schema/schemas.d.ts +14 -37
  23. package/dist/schema/schemas.js +7 -10
  24. package/dist/schema/test-schema.js +1 -1
  25. package/dist/templates/context-file-section.md +59 -0
  26. package/dist/utils/context-file.d.ts +38 -0
  27. package/dist/utils/context-file.js +94 -0
  28. package/dist/utils/env.d.ts +0 -13
  29. package/dist/utils/env.js +0 -19
  30. package/dist/utils/gitignore.d.ts +2 -2
  31. package/dist/utils/gitignore.js +4 -4
  32. package/dist/utils/oauth-flow.d.ts +0 -1
  33. package/dist/utils/oauth-flow.js +0 -9
  34. package/dist/utils/project-discovery.d.ts +3 -5
  35. package/dist/utils/project-discovery.js +18 -42
  36. package/dist/utils/project-selector.d.ts +17 -3
  37. package/dist/utils/project-selector.js +37 -3
  38. package/dist/utils/project-settings.d.ts +47 -0
  39. package/dist/utils/project-settings.js +110 -0
  40. package/dist/utils/templates.d.ts +0 -24
  41. package/dist/utils/templates.js +0 -39
  42. package/package.json +1 -1
  43. package/dist/harness/localReporting.d.ts +0 -6
  44. package/dist/harness/localReporting.js +0 -49
  45. package/dist/templates/antigravity-gemini.md +0 -3
  46. package/dist/templates/antigravity-overview-rule.md +0 -3
  47. package/dist/templates/antigravity-test-rule.md +0 -3
  48. package/dist/templates/behavioral-core.md +0 -25
  49. package/dist/templates/claude-code-overview-skill.md +0 -6
  50. package/dist/templates/claude-code-skill.md +0 -6
  51. package/dist/templates/claude-code-test-skill.md +0 -6
  52. package/dist/templates/codex-agents.md +0 -3
  53. package/dist/templates/codex-overview-agents.md +0 -3
  54. package/dist/templates/codex-test-agents.md +0 -3
  55. package/dist/templates/cursor-overview-rule.mdc +0 -5
  56. package/dist/templates/cursor-rule.mdc +0 -5
  57. package/dist/templates/cursor-test-rule.mdc +0 -5
  58. package/dist/templates/overview-core.md +0 -27
  59. package/dist/templates/test-writing-core.md +0 -72
  60. package/dist/utils/detect-existing-project.d.ts +0 -5
  61. package/dist/utils/detect-existing-project.js +0 -34
@@ -6,52 +6,48 @@ import { generateRequirementsReadme } from '../templates/requirements-readme.js'
6
6
  import { generateExampleRequirements } from '../templates/example-requirements.js';
7
7
  import { promptChoice, promptConfirm } from '../utils/prompts.js';
8
8
  import { pullCommand } from './pull.js';
9
- import { LOCAL_PROJECT_CREDENTIALS } from '../utils/local-project.js';
10
- import { loadEnvFile } from '../utils/env.js';
11
- import { hasValidTokens, getTokens } from '../utils/token-storage.js';
12
9
  import { executeOAuthFlow } from '../utils/oauth-flow.js';
13
10
  import { ConvexHttpClient } from 'convex/browser';
14
11
  import { getConvexUrl } from '../config.js';
15
12
  import { api } from '../convex.js';
16
13
  import { brand } from '../utils/brand.js';
17
- import { selectTeam as selectTeamFromList, selectProject, getOrCreateProjectSecret } from '../utils/project-selector.js';
14
+ import { selectProject, getOrCreateProjectSecret, promptExpiryDays } from '../utils/project-selector.js';
15
+ import { writeProjectSettings, findProjectRoot } from '../utils/project-settings.js';
18
16
  export async function initCommand(options = {}) {
19
17
  try {
20
18
  console.log(`Initializing ${brand} project...\n`);
21
19
  const cwd = process.cwd();
22
- const envPath = path.join(cwd, '.env.local');
23
- // 1. Pre-flight check: already initialized?
24
- if (fs.existsSync(envPath)) {
25
- const content = fs.readFileSync(envPath, 'utf-8');
26
- if (content.includes('DOTREQUIREMENTS_PROJECT_ID')) {
27
- const shouldReinit = await promptConfirm('Project already initialized. Reinitialize?', false);
28
- if (!shouldReinit) {
29
- console.log('Aborted.');
30
- return;
31
- }
32
- }
20
+ // INIT-2: Check if .requirements/ folder already exists
21
+ const existingProject = findProjectRoot(cwd);
22
+ if (existingProject === cwd) {
23
+ console.log('This project is already initialized.');
24
+ console.log('Use "dotrequirements link" to connect or reconnect to cloud.\n');
25
+ return;
33
26
  }
34
- // 2. Ask user to choose between creating new or connecting to existing
35
- const flow = await promptChoice('What would you like to do?', [
36
- { title: 'Create a new project', value: 'create' },
37
- { title: 'Connect to an existing project', value: 'connect' },
27
+ // INIT-1: Ask user whether to authenticate
28
+ const authChoice = await promptChoice('Do you want to authenticate before initializing a project? (Recommended)', [
29
+ { title: 'Yes', value: 'yes' },
30
+ { title: 'No', value: 'no' },
38
31
  ]);
39
- if (!flow) {
40
- console.log('Aborted.');
32
+ if (!authChoice) {
33
+ // INIT-1.3: User cancelled
34
+ console.log('Initialization cancelled.');
41
35
  return;
42
36
  }
43
- // 3. Execute the appropriate flow
44
- if (flow === 'create') {
45
- await createNewProjectFlow(cwd, envPath, options);
37
+ if (authChoice === 'no') {
38
+ // INIT-3: Local-only workflow
39
+ await localOnlyFlow(cwd);
46
40
  }
47
41
  else {
48
- await connectExistingProjectFlow(cwd, envPath);
42
+ // INIT-17+: Authenticated workflow
43
+ await authenticatedFlow(cwd, options);
49
44
  }
50
45
  console.log('\nDocumentation: https://docs.dotrequirements.io/getting-started');
51
46
  console.log('By using this service, you agree to our Terms: https://app.dotrequirements.io/terms');
52
47
  }
53
48
  catch (error) {
54
49
  console.error(`\n✗ Error: ${error instanceof Error ? error.message : String(error)}`);
50
+ // INIT-11: Network error guidance
55
51
  if (error instanceof Error && error.message.includes('fetch')) {
56
52
  console.error('\nCheck your internet connection and try again.');
57
53
  console.error('If the problem persists, visit https://status.convex.dev');
@@ -60,15 +56,86 @@ export async function initCommand(options = {}) {
60
56
  }
61
57
  }
62
58
  /**
63
- * INIT-13: Prompt user to select a team for the new project
59
+ * INIT-3: Local-only workflow - creates folder structure without credentials
60
+ */
61
+ async function localOnlyFlow(cwd) {
62
+ console.log('\nInitializing local project...');
63
+ // INIT-3.0: Create .requirements/ directory
64
+ const requirementsDir = path.join(cwd, '.requirements');
65
+ if (!fs.existsSync(requirementsDir)) {
66
+ fs.mkdirSync(requirementsDir, { recursive: true });
67
+ console.log('✓ Created .requirements/ directory');
68
+ }
69
+ // Create README and example files
70
+ const readmePath = path.join(requirementsDir, 'README.md');
71
+ fs.writeFileSync(readmePath, generateRequirementsReadme());
72
+ console.log('✓ Created .requirements/README.md');
73
+ const examplePath = path.join(requirementsDir, 'example.requirements.md');
74
+ if (!fs.existsSync(examplePath)) {
75
+ fs.writeFileSync(examplePath, generateExampleRequirements('local'));
76
+ console.log('✓ Created .requirements/example.requirements.md');
77
+ }
78
+ // INIT-5.2: Update .gitignore
79
+ ensureGitignore(cwd);
80
+ console.log('✓ Updated .gitignore\n');
81
+ // INIT-3.1: No credentials written (local-only)
82
+ // INIT-3.2, INIT-3.3: Show guidance
83
+ console.log('Next steps:');
84
+ console.log(' 1. Create requirements in .requirements/ directory');
85
+ console.log(' 2. Add the test harness to your test setup (see README)');
86
+ console.log(' 3. Run "dotrequirements link" to connect to cloud features');
87
+ }
88
+ /**
89
+ * INIT-17, INIT-18, INIT-7, INIT-4, INIT-6: Authenticated workflow
90
+ */
91
+ async function authenticatedFlow(cwd, options) {
92
+ // INIT-17: Inline OAuth (no persistent tokens)
93
+ let oauthResult;
94
+ try {
95
+ console.log();
96
+ oauthResult = await executeOAuthFlow();
97
+ console.log();
98
+ }
99
+ catch (error) {
100
+ // INIT-17.3: OAuth failed
101
+ console.error('\nAuthentication failed:', error instanceof Error ? error.message : String(error));
102
+ console.log('Please try again later.\n');
103
+ return;
104
+ }
105
+ // Set up authenticated client using the session token
106
+ const client = new ConvexHttpClient(getConvexUrl());
107
+ client.setAuth(oauthResult.accessToken);
108
+ // INIT-18: Select team
109
+ const selectedTeam = await selectTeam(client);
110
+ if (selectedTeam === undefined) {
111
+ // User cancelled
112
+ console.log('Initialization cancelled.');
113
+ return;
114
+ }
115
+ // INIT-7: Choose between create and connect
116
+ const projectChoice = await chooseProjectAction(client, selectedTeam.teamId);
117
+ if (!projectChoice) {
118
+ console.log('Initialization cancelled.');
119
+ return;
120
+ }
121
+ if (projectChoice === 'create') {
122
+ await createNewProjectFlow(cwd, client, selectedTeam, options);
123
+ }
124
+ else {
125
+ await connectExistingProjectFlow(cwd, client, selectedTeam);
126
+ }
127
+ }
128
+ /**
129
+ * INIT-18: Prompt user to select a team after OAuth
64
130
  * Returns:
65
- * - teamId string if user selected a team
66
- * - null if no teams exist (auto-create flow)
131
+ * - { teamId, tier } if user selected a team
67
132
  * - undefined if user cancelled
68
133
  */
69
134
  async function selectTeam(client) {
70
135
  console.log('Fetching your teams...\n');
71
- // INIT-13.0: Query all teams for the authenticated user
136
+ // INIT-18.0: Ensure user has a default team if they don't have any
137
+ await client.mutation(api.teams.mutations.ensureDefaultTeam, {});
138
+ // Query all teams for the authenticated user
72
139
  const teams = await client.query(api.teams.queries.listForUserWithUsage);
73
140
  if (teams.length === 0) {
74
141
  console.log('\nYou are not a member of any teams.');
@@ -76,20 +143,21 @@ async function selectTeam(client) {
76
143
  console.log('After creating your team, run "dotrequirements init" again.\n');
77
144
  process.exit(0);
78
145
  }
79
- // INIT-13.1 & INIT-13.3: Build team selection options with usage display
146
+ // INIT-18.1, INIT-18.2, INIT-18.3: Build team selection options with usage display
80
147
  const teamChoices = teams.map((team) => {
81
148
  const limitDisplay = team.projectLimit === -1
82
149
  ? 'unlimited'
83
150
  : `${team.projectCount}/${team.projectLimit}`;
84
- // INIT-13.2: Mark teams at capacity
151
+ // INIT-18.3: Mark teams at capacity
85
152
  const capacityMarker = team.isAtCapacity ? ' [at capacity]' : '';
86
153
  return {
87
154
  title: `${team.name} (${team.tierName} - ${limitDisplay} projects)${capacityMarker}`,
88
155
  value: team.teamId,
89
- disabled: team.isAtCapacity, // Make at-capacity teams unselectable
156
+ disabled: team.isAtCapacity,
90
157
  };
91
158
  });
92
- // INIT-13.3: Add "Create new team" option
159
+ // INIT-18.4: Add "Create new team" option if user can create one
160
+ // (Check if user is already admin of a free team - simplified to always show for now)
93
161
  teamChoices.push({
94
162
  title: 'Create new team',
95
163
  value: 'CREATE_NEW_TEAM',
@@ -97,14 +165,12 @@ async function selectTeam(client) {
97
165
  });
98
166
  const selectedTeamId = await promptChoice('Select a team for this project:', teamChoices);
99
167
  if (!selectedTeamId) {
100
- // User cancelled
101
168
  return undefined;
102
169
  }
103
- // INIT-14: Handle selection of at-capacity team (should not happen due to disabled flag, but defensive check)
170
+ // INIT-14: Handle selection of at-capacity team (defensive check)
104
171
  const selectedTeam = teams.find((t) => t.teamId === selectedTeamId);
105
172
  if (selectedTeam && selectedTeam.isAtCapacity) {
106
173
  console.error('\n✗ This team is at its project limit. Please select a different team or create a new one.\n');
107
- // Retry selection
108
174
  return await selectTeam(client);
109
175
  }
110
176
  // INIT-15: Handle "Create new team" selection
@@ -113,122 +179,78 @@ async function selectTeam(client) {
113
179
  console.log('After creating your team, run "dotrequirements init" again.\n');
114
180
  process.exit(0);
115
181
  }
116
- // INIT-13.4: Return selected team ID
117
- return selectedTeamId;
182
+ return {
183
+ teamId: selectedTeamId,
184
+ tier: selectedTeam.tier,
185
+ };
118
186
  }
119
187
  /**
120
- * Create a new project flow (INIT-3, INIT-4, INIT-13)
188
+ * INIT-7: Choose between creating new project or connecting to existing
121
189
  */
122
- async function createNewProjectFlow(cwd, envPath, options) {
123
- // 1. Detect/prompt for project name
190
+ async function chooseProjectAction(client, teamId) {
191
+ // INIT-7.3: Check if team has existing projects the user can access
192
+ const projectsWithSecrets = await client.query(api.projectSecrets.queries.listOwnSecretsForTeam, { target: { type: 'team', id: teamId } });
193
+ if (projectsWithSecrets.length === 0) {
194
+ // No existing projects, only offer create
195
+ const confirmed = await promptConfirm('This team has no projects. Create a new one?', true);
196
+ return confirmed ? 'create' : undefined;
197
+ }
198
+ // INIT-7.0: Offer both options
199
+ return await promptChoice('What would you like to do?', [
200
+ { title: 'Create a new project', value: 'create' },
201
+ { title: 'Connect to an existing project', value: 'connect' },
202
+ ]);
203
+ }
204
+ /**
205
+ * INIT-4: Create a new project in DotRequirements Cloud
206
+ */
207
+ async function createNewProjectFlow(cwd, client, team, options) {
208
+ // INIT-4.0: Detect/prompt for project name
124
209
  const projectName = await detectProjectName({ name: options.name });
125
210
  console.log(`Project name: ${projectName}\n`);
126
- // 2. Check if user is authenticated
127
- const isAuthenticated = await hasValidTokens();
128
- let projectId; // Can be slug (new) or Convex ID (old/local)
129
- let secret;
130
- if (isAuthenticated) {
131
- // INIT-4: Authenticated user - create cloud project
132
- const tokens = await getTokens();
133
- if (!tokens) {
134
- throw new Error('Authentication tokens not found');
135
- }
136
- const client = new ConvexHttpClient(getConvexUrl());
137
- client.setAuth(tokens.accessToken);
138
- // INIT-13: Select team for the project (returns null if no teams exist, undefined if cancelled)
139
- const selectedTeamId = await selectTeam(client);
140
- if (selectedTeamId === undefined) {
141
- // User cancelled (note: "Create new team" exits in selectTeam)
142
- console.log('Aborted.');
143
- return;
144
- }
145
- console.log('Creating cloud project...');
146
- const result = await client.mutation(api.projects.mutations.create, {
147
- name: projectName,
148
- teamId: selectedTeamId ?? undefined, // null becomes undefined for auto-create
149
- });
150
- // Use slug as the user-facing project identifier
151
- projectId = result.slug;
152
- secret = result.secret;
153
- console.log(`✓ Cloud project created: ${projectId}`);
154
- }
155
- else {
156
- // INIT-3: Unauthenticated user - create local-only project with placeholder credentials
157
- console.log('Initializing local project...');
158
- projectId = LOCAL_PROJECT_CREDENTIALS.projectId;
159
- secret = LOCAL_PROJECT_CREDENTIALS.secret;
160
- console.log(`✓ Project initialized locally`);
161
- }
162
- // 3. Write .env.local
163
- const shouldContinue = await writeEnvFile(envPath, projectId, secret);
164
- if (!shouldContinue) {
165
- // User declined to continue - abort init
166
- return;
167
- }
168
- // Reload env file so newly written credentials are available to subsequent commands
169
- // Use force=true to overwrite any previously cached values
170
- loadEnvFile(cwd, true);
171
- // 4. Create .requirements/ directory
211
+ console.log('Creating cloud project...');
212
+ // INIT-4.1: Create project in cloud (no longer auto-creates secret)
213
+ const result = await client.mutation(api.projects.mutations.create, {
214
+ name: projectName,
215
+ teamId: team.teamId,
216
+ });
217
+ const projectId = result.slug;
218
+ console.log(`✓ Cloud project created: ${projectId}`);
219
+ // AUTHZ-3: Prompt for secret expiry (paid tier only)
220
+ const expiryDays = await promptExpiryDays(team.tier);
221
+ // Get secret for the newly created project
222
+ console.log('Getting project credentials...');
223
+ const secretResult = await getOrCreateProjectSecret(client, result.projectId, expiryDays);
224
+ const secret = secretResult.secret;
225
+ console.log('✓ Created project secret');
226
+ // INIT-4.2: Write credentials to project-settings.json
227
+ writeProjectSettings(cwd, { projectId, projectSecret: secret });
228
+ console.log('✓ Wrote credentials to .requirements/project-settings.json');
229
+ // INIT-5.0, INIT-5.1: Create directory structure (if not already created by writeProjectSettings)
172
230
  await createRequirementsDirectory(cwd, projectId);
173
- // 5. Update .gitignore
231
+ // INIT-5.2: Update .gitignore
174
232
  ensureGitignore(cwd);
175
233
  console.log('✓ Updated .gitignore\n');
176
- // 6. Show next steps
234
+ // INIT-4.3: Show guidance
177
235
  console.log('Next steps:');
178
236
  console.log(' 1. Create requirements in .requirements/ directory');
179
237
  console.log(' 2. Add the test harness to your test setup (see README)');
180
- if (!isAuthenticated) {
181
- // INIT-3.1: Local-only guidance
182
- console.log(' 3. Run "dotrequirements login" for cloud features (push/pull, coverage, AI)');
183
- }
184
238
  }
185
239
  /**
186
- * Connect to existing project flow
240
+ * INIT-6, INIT-16: Connect to an existing project
187
241
  */
188
- async function connectExistingProjectFlow(cwd, envPath) {
189
- // INIT-6.0: Check if user is authenticated, prompt to log in if not
190
- let isAuthenticated = await hasValidTokens();
191
- if (!isAuthenticated) {
192
- const shouldLogin = await promptConfirm('You need to authenticate to connect to an existing project. Log in now?', true);
193
- if (!shouldLogin) {
194
- console.log('Aborted.');
195
- return;
196
- }
197
- // INIT-6.1: Initiate OAuth login flow inline
198
- try {
199
- console.log();
200
- await executeOAuthFlow();
201
- console.log();
202
- isAuthenticated = true;
203
- }
204
- catch (error) {
205
- console.error('\nAuthentication failed:', error instanceof Error ? error.message : String(error));
206
- console.log('Please try running "dotrequirements login" separately.\n');
207
- return;
208
- }
209
- }
210
- // Set up authenticated client
211
- const tokens = await getTokens();
212
- if (!tokens) {
213
- throw new Error('Authentication tokens not found');
214
- }
215
- const client = new ConvexHttpClient(getConvexUrl());
216
- client.setAuth(tokens.accessToken);
217
- // INIT-6.2: Select team
218
- const selectedTeamId = await selectTeamFromList(client);
219
- if (!selectedTeamId) {
220
- console.log('Aborted.');
221
- return;
222
- }
223
- // INIT-6.3: Select project
224
- const selectedProject = await selectProject(client, selectedTeamId);
242
+ async function connectExistingProjectFlow(cwd, client, team) {
243
+ // INIT-6.0, INIT-16.0: Select project from team
244
+ const selectedProject = await selectProject(client, team.teamId);
225
245
  if (!selectedProject) {
226
- console.log('Aborted.');
246
+ console.log('Initialization cancelled.');
227
247
  return;
228
248
  }
229
- // INIT-6.4: Get or create secret for this project
249
+ // AUTHZ-3: Prompt for secret expiry (paid tier only, if creating new secret)
250
+ const expiryDays = await promptExpiryDays(team.tier);
251
+ // INIT-16.1: Get or create secret for this project
230
252
  console.log('\nGetting project credentials...');
231
- const secretResult = await getOrCreateProjectSecret(client, selectedProject.projectId);
253
+ const secretResult = await getOrCreateProjectSecret(client, selectedProject.projectId, expiryDays);
232
254
  const projectId = selectedProject.projectSlug || selectedProject.projectId;
233
255
  const secret = secretResult.secret;
234
256
  if (secretResult.created) {
@@ -237,118 +259,39 @@ async function connectExistingProjectFlow(cwd, envPath) {
237
259
  else {
238
260
  console.log('✓ Retrieved existing project secret');
239
261
  }
240
- // Write .env.local
241
- const shouldContinue = await writeEnvFile(envPath, projectId, secret);
242
- if (!shouldContinue) {
243
- // User declined to continue - abort init
244
- return;
245
- }
246
- // Reload env file so newly written credentials are available to pullCommand
247
- // Use force=true to overwrite any previously cached values
248
- loadEnvFile(cwd, true);
249
- // Create .requirements/ directory
262
+ // INIT-6.1: Write credentials to project-settings.json
263
+ writeProjectSettings(cwd, { projectId, projectSecret: secret });
264
+ console.log('✓ Wrote credentials to .requirements/project-settings.json');
265
+ // Create directory structure
250
266
  await createRequirementsDirectory(cwd, projectId);
251
267
  // Update .gitignore
252
268
  ensureGitignore(cwd);
253
269
  console.log('✓ Updated .gitignore\n');
254
- // Pull existing requirements from cloud
270
+ // INIT-6.2: Pull existing requirements from cloud
255
271
  console.log('Pulling requirements from cloud...');
256
272
  await pullCommand({ project: projectId });
257
273
  console.log(`\n✓ Successfully connected to project: ${selectedProject.projectName}`);
258
274
  console.log(` Project ID: ${projectId}`);
275
+ // INIT-6.3: Show guidance
259
276
  console.log('\nNext steps:');
260
277
  console.log(' 1. Review the requirements in .requirements/');
261
278
  console.log(' 2. Add the test harness to your test setup (see README)');
262
279
  }
263
280
  /**
264
- * Write .env.local file with confirmation if it already exists
265
- * Returns true if credentials were written or user chose to continue with existing
266
- * Returns false if user aborted
267
- */
268
- async function writeEnvFile(envPath, projectId, secret) {
269
- const newEnvLines = [
270
- '# dot•requirements configuration',
271
- `DOTREQUIREMENTS_PROJECT_ID=${projectId}`,
272
- `DOTREQUIREMENTS_PROJECT_SECRET=${secret}`,
273
- ];
274
- if (fs.existsSync(envPath)) {
275
- // Read existing content
276
- const existingContent = fs.readFileSync(envPath, 'utf-8');
277
- // Check if dotrequirements variables already exist
278
- const hasProjectId = existingContent.includes('DOTREQUIREMENTS_PROJECT_ID');
279
- const hasProjectSecret = existingContent.includes('DOTREQUIREMENTS_PROJECT_SECRET');
280
- if (hasProjectId || hasProjectSecret) {
281
- // INIT-2.0: Ask if user wants to update existing values
282
- const shouldUpdate = await promptConfirm(`.env.local already contains ${brand} configuration. Update it?`, false);
283
- if (!shouldUpdate) {
284
- // INIT-2.1: Ask if user wants to continue with existing credentials
285
- const shouldContinue = await promptConfirm('Continue with existing credentials?', true);
286
- if (!shouldContinue) {
287
- // INIT-2.3: User declined to continue - abort
288
- console.log('Aborted.');
289
- return false;
290
- }
291
- // INIT-2.2: User wants to continue with existing credentials
292
- console.log('Continuing with existing credentials...');
293
- return true;
294
- }
295
- // Remove old dotrequirements lines
296
- const lines = existingContent.split('\n');
297
- const filteredLines = lines.filter(line => !line.includes('DOTREQUIREMENTS_PROJECT_ID') &&
298
- !line.includes('DOTREQUIREMENTS_PROJECT_SECRET') &&
299
- !(line.trim() === '# dotrequirements configuration') &&
300
- !(line.trim() === '# dot•requirements configuration'));
301
- // Append new configuration
302
- const updatedContent = filteredLines.join('\n').trimEnd() + '\n\n' + newEnvLines.join('\n') + '\n';
303
- fs.writeFileSync(envPath, updatedContent);
304
- console.log(`✓ Updated ${brand} configuration in .env.local`);
305
- return true;
306
- }
307
- else {
308
- // File exists but doesn't have dotrequirements config - just append
309
- const shouldAppend = await promptConfirm(`.env.local already exists. Add ${brand} configuration to it?`, true);
310
- if (!shouldAppend) {
311
- console.log('\nSkipped writing to .env.local. Please add these values manually:');
312
- console.log(` DOTREQUIREMENTS_PROJECT_ID=${projectId}`);
313
- console.log(` DOTREQUIREMENTS_PROJECT_SECRET=${secret}\n`);
314
- return true; // Continue with init even though we didn't write the file
315
- }
316
- // Append to existing file
317
- const appendContent = '\n' + newEnvLines.join('\n') + '\n';
318
- fs.appendFileSync(envPath, appendContent);
319
- console.log(`✓ Added ${brand} configuration to .env.local`);
320
- return true;
321
- }
322
- }
323
- else {
324
- // File doesn't exist - create it
325
- const envContent = newEnvLines.join('\n') + '\n';
326
- fs.writeFileSync(envPath, envContent);
327
- console.log('✓ Wrote configuration to .env.local');
328
- return true;
329
- }
330
- }
331
- /**
332
- * Create .requirements/ directory with confirmation if it already exists
281
+ * INIT-5: Create .requirements/ directory with README and example
333
282
  */
334
283
  async function createRequirementsDirectory(cwd, projectId) {
335
284
  const requirementsDir = path.join(cwd, '.requirements');
336
- if (fs.existsSync(requirementsDir)) {
337
- const shouldOverwrite = await promptConfirm('.requirements/ directory already exists. Continue?', true);
338
- if (!shouldOverwrite) {
339
- console.log('Skipped creating .requirements/ directory');
340
- return;
341
- }
342
- }
343
- else {
285
+ // Directory may already exist from writeProjectSettings
286
+ if (!fs.existsSync(requirementsDir)) {
344
287
  fs.mkdirSync(requirementsDir, { recursive: true });
345
288
  console.log('✓ Created .requirements/ directory');
346
289
  }
347
- // Always create/update README
290
+ // INIT-5.0: Always create/update README
348
291
  const readmePath = path.join(requirementsDir, 'README.md');
349
292
  fs.writeFileSync(readmePath, generateRequirementsReadme());
350
293
  console.log('✓ Created .requirements/README.md');
351
- // Create example.requirements.md (INIT-5.1)
294
+ // INIT-5.1: Create example.requirements.md
352
295
  const examplePath = path.join(requirementsDir, 'example.requirements.md');
353
296
  if (!fs.existsSync(examplePath)) {
354
297
  fs.writeFileSync(examplePath, generateExampleRequirements(projectId));
@@ -1,15 +1,14 @@
1
1
  /**
2
- * Link command - connect local environment to an existing project.
2
+ * Link command - connect or reconnect local project to cloud.
3
3
  *
4
- * Flow:
5
- * 1. Check authentication (prompt to login if not)
6
- * 2. Fetch user's teams with usage info
7
- * 3. Let user select a team
8
- * 4. Fetch projects for that team
9
- * 5. Let user select a project
10
- * 6. Get or create user's secret for that project
11
- * 7. Write credentials to .env.local
12
- * 8. Optionally pull requirements
4
+ * Requirements:
5
+ * - LINK-1: Requires existing .requirements/ folder
6
+ * - LINK-2: Offers refresh when credentials exist
7
+ * - LINK-3: Uses inline OAuth (no persistent tokens)
8
+ * - LINK-4, LINK-5: Team and project selection
9
+ * - LINK-6: Saves credentials to project-settings.json
10
+ * - LINK-7: Optionally pulls requirements
11
+ * - LINK-8: Handles network errors gracefully
13
12
  */
14
13
  export declare function linkCommand(): Promise<void>;
15
14
  //# sourceMappingURL=link.d.ts.map