@popoverai/dotrequirements 0.13.0 → 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 (53) hide show
  1. package/README.md +47 -47
  2. package/dist/cli.js +0 -10
  3. package/dist/commands/init.js +166 -226
  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 +7 -29
  8. package/dist/commands/push.js +4 -13
  9. package/dist/harness/cache.d.ts +0 -5
  10. package/dist/harness/cache.js +0 -48
  11. package/dist/harness/convexReporting.js +7 -14
  12. package/dist/harness/finalize.js +7 -12
  13. package/dist/harness/prepare.js +7 -9
  14. package/dist/mcp/convexClient.d.ts +5 -1
  15. package/dist/mcp/convexClient.js +14 -34
  16. package/dist/mcp/index.js +12 -73
  17. package/dist/templates/context-file-section.md +59 -0
  18. package/dist/utils/context-file.d.ts +38 -0
  19. package/dist/utils/context-file.js +94 -0
  20. package/dist/utils/env.d.ts +0 -13
  21. package/dist/utils/env.js +0 -19
  22. package/dist/utils/gitignore.d.ts +2 -2
  23. package/dist/utils/gitignore.js +4 -4
  24. package/dist/utils/oauth-flow.d.ts +0 -1
  25. package/dist/utils/oauth-flow.js +0 -9
  26. package/dist/utils/project-discovery.d.ts +3 -5
  27. package/dist/utils/project-discovery.js +18 -42
  28. package/dist/utils/project-selector.d.ts +17 -3
  29. package/dist/utils/project-selector.js +35 -3
  30. package/dist/utils/project-settings.d.ts +47 -0
  31. package/dist/utils/project-settings.js +110 -0
  32. package/dist/utils/templates.d.ts +0 -24
  33. package/dist/utils/templates.js +0 -39
  34. package/package.json +1 -1
  35. package/dist/harness/localReporting.d.ts +0 -6
  36. package/dist/harness/localReporting.js +0 -49
  37. package/dist/templates/antigravity-gemini.md +0 -3
  38. package/dist/templates/antigravity-overview-rule.md +0 -3
  39. package/dist/templates/antigravity-test-rule.md +0 -3
  40. package/dist/templates/behavioral-core.md +0 -25
  41. package/dist/templates/claude-code-overview-skill.md +0 -6
  42. package/dist/templates/claude-code-skill.md +0 -6
  43. package/dist/templates/claude-code-test-skill.md +0 -6
  44. package/dist/templates/codex-agents.md +0 -3
  45. package/dist/templates/codex-overview-agents.md +0 -3
  46. package/dist/templates/codex-test-agents.md +0 -3
  47. package/dist/templates/cursor-overview-rule.mdc +0 -5
  48. package/dist/templates/cursor-rule.mdc +0 -5
  49. package/dist/templates/cursor-test-rule.mdc +0 -5
  50. package/dist/templates/overview-core.md +0 -27
  51. package/dist/templates/test-writing-core.md +0 -72
  52. package/dist/utils/detect-existing-project.d.ts +0 -5
  53. 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,18 +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
- // AUTH-24.0: Ensure user has a default team if they don't have any
72
- // This handles new users who haven't joined via invite
136
+ // INIT-18.0: Ensure user has a default team if they don't have any
73
137
  await client.mutation(api.teams.mutations.ensureDefaultTeam, {});
74
- // INIT-13.0: Query all teams for the authenticated user
138
+ // Query all teams for the authenticated user
75
139
  const teams = await client.query(api.teams.queries.listForUserWithUsage);
76
140
  if (teams.length === 0) {
77
141
  console.log('\nYou are not a member of any teams.');
@@ -79,20 +143,21 @@ async function selectTeam(client) {
79
143
  console.log('After creating your team, run "dotrequirements init" again.\n');
80
144
  process.exit(0);
81
145
  }
82
- // 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
83
147
  const teamChoices = teams.map((team) => {
84
148
  const limitDisplay = team.projectLimit === -1
85
149
  ? 'unlimited'
86
150
  : `${team.projectCount}/${team.projectLimit}`;
87
- // INIT-13.2: Mark teams at capacity
151
+ // INIT-18.3: Mark teams at capacity
88
152
  const capacityMarker = team.isAtCapacity ? ' [at capacity]' : '';
89
153
  return {
90
154
  title: `${team.name} (${team.tierName} - ${limitDisplay} projects)${capacityMarker}`,
91
155
  value: team.teamId,
92
- disabled: team.isAtCapacity, // Make at-capacity teams unselectable
156
+ disabled: team.isAtCapacity,
93
157
  };
94
158
  });
95
- // 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)
96
161
  teamChoices.push({
97
162
  title: 'Create new team',
98
163
  value: 'CREATE_NEW_TEAM',
@@ -100,14 +165,12 @@ async function selectTeam(client) {
100
165
  });
101
166
  const selectedTeamId = await promptChoice('Select a team for this project:', teamChoices);
102
167
  if (!selectedTeamId) {
103
- // User cancelled
104
168
  return undefined;
105
169
  }
106
- // 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)
107
171
  const selectedTeam = teams.find((t) => t.teamId === selectedTeamId);
108
172
  if (selectedTeam && selectedTeam.isAtCapacity) {
109
173
  console.error('\n✗ This team is at its project limit. Please select a different team or create a new one.\n');
110
- // Retry selection
111
174
  return await selectTeam(client);
112
175
  }
113
176
  // INIT-15: Handle "Create new team" selection
@@ -116,122 +179,78 @@ async function selectTeam(client) {
116
179
  console.log('After creating your team, run "dotrequirements init" again.\n');
117
180
  process.exit(0);
118
181
  }
119
- // INIT-13.4: Return selected team ID
120
- return selectedTeamId;
182
+ return {
183
+ teamId: selectedTeamId,
184
+ tier: selectedTeam.tier,
185
+ };
121
186
  }
122
187
  /**
123
- * Create a new project flow (INIT-3, INIT-4, INIT-13)
188
+ * INIT-7: Choose between creating new project or connecting to existing
124
189
  */
125
- async function createNewProjectFlow(cwd, envPath, options) {
126
- // 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
127
209
  const projectName = await detectProjectName({ name: options.name });
128
210
  console.log(`Project name: ${projectName}\n`);
129
- // 2. Check if user is authenticated
130
- const isAuthenticated = await hasValidTokens();
131
- let projectId; // Can be slug (new) or Convex ID (old/local)
132
- let secret;
133
- if (isAuthenticated) {
134
- // INIT-4: Authenticated user - create cloud project
135
- const tokens = await getTokens();
136
- if (!tokens) {
137
- throw new Error('Authentication tokens not found');
138
- }
139
- const client = new ConvexHttpClient(getConvexUrl());
140
- client.setAuth(tokens.accessToken);
141
- // INIT-13: Select team for the project (returns null if no teams exist, undefined if cancelled)
142
- const selectedTeamId = await selectTeam(client);
143
- if (selectedTeamId === undefined) {
144
- // User cancelled (note: "Create new team" exits in selectTeam)
145
- console.log('Aborted.');
146
- return;
147
- }
148
- console.log('Creating cloud project...');
149
- const result = await client.mutation(api.projects.mutations.create, {
150
- name: projectName,
151
- teamId: selectedTeamId ?? undefined, // null becomes undefined for auto-create
152
- });
153
- // Use slug as the user-facing project identifier
154
- projectId = result.slug;
155
- secret = result.secret;
156
- console.log(`✓ Cloud project created: ${projectId}`);
157
- }
158
- else {
159
- // INIT-3: Unauthenticated user - create local-only project with placeholder credentials
160
- console.log('Initializing local project...');
161
- projectId = LOCAL_PROJECT_CREDENTIALS.projectId;
162
- secret = LOCAL_PROJECT_CREDENTIALS.secret;
163
- console.log(`✓ Project initialized locally`);
164
- }
165
- // 3. Write .env.local
166
- const shouldContinue = await writeEnvFile(envPath, projectId, secret);
167
- if (!shouldContinue) {
168
- // User declined to continue - abort init
169
- return;
170
- }
171
- // Reload env file so newly written credentials are available to subsequent commands
172
- // Use force=true to overwrite any previously cached values
173
- loadEnvFile(cwd, true);
174
- // 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)
175
230
  await createRequirementsDirectory(cwd, projectId);
176
- // 5. Update .gitignore
231
+ // INIT-5.2: Update .gitignore
177
232
  ensureGitignore(cwd);
178
233
  console.log('✓ Updated .gitignore\n');
179
- // 6. Show next steps
234
+ // INIT-4.3: Show guidance
180
235
  console.log('Next steps:');
181
236
  console.log(' 1. Create requirements in .requirements/ directory');
182
237
  console.log(' 2. Add the test harness to your test setup (see README)');
183
- if (!isAuthenticated) {
184
- // INIT-3.1: Local-only guidance
185
- console.log(' 3. Run "dotrequirements login" for cloud features (push/pull, coverage, AI)');
186
- }
187
238
  }
188
239
  /**
189
- * Connect to existing project flow
240
+ * INIT-6, INIT-16: Connect to an existing project
190
241
  */
191
- async function connectExistingProjectFlow(cwd, envPath) {
192
- // INIT-6.0: Check if user is authenticated, prompt to log in if not
193
- let isAuthenticated = await hasValidTokens();
194
- if (!isAuthenticated) {
195
- const shouldLogin = await promptConfirm('You need to authenticate to connect to an existing project. Log in now?', true);
196
- if (!shouldLogin) {
197
- console.log('Aborted.');
198
- return;
199
- }
200
- // INIT-6.1: Initiate OAuth login flow inline
201
- try {
202
- console.log();
203
- await executeOAuthFlow();
204
- console.log();
205
- isAuthenticated = true;
206
- }
207
- catch (error) {
208
- console.error('\nAuthentication failed:', error instanceof Error ? error.message : String(error));
209
- console.log('Please try running "dotrequirements login" separately.\n');
210
- return;
211
- }
212
- }
213
- // Set up authenticated client
214
- const tokens = await getTokens();
215
- if (!tokens) {
216
- throw new Error('Authentication tokens not found');
217
- }
218
- const client = new ConvexHttpClient(getConvexUrl());
219
- client.setAuth(tokens.accessToken);
220
- // INIT-6.2: Select team
221
- const selectedTeamId = await selectTeamFromList(client);
222
- if (!selectedTeamId) {
223
- console.log('Aborted.');
224
- return;
225
- }
226
- // INIT-6.3: Select project
227
- 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);
228
245
  if (!selectedProject) {
229
- console.log('Aborted.');
246
+ console.log('Initialization cancelled.');
230
247
  return;
231
248
  }
232
- // 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
233
252
  console.log('\nGetting project credentials...');
234
- const secretResult = await getOrCreateProjectSecret(client, selectedProject.projectId);
253
+ const secretResult = await getOrCreateProjectSecret(client, selectedProject.projectId, expiryDays);
235
254
  const projectId = selectedProject.projectSlug || selectedProject.projectId;
236
255
  const secret = secretResult.secret;
237
256
  if (secretResult.created) {
@@ -240,118 +259,39 @@ async function connectExistingProjectFlow(cwd, envPath) {
240
259
  else {
241
260
  console.log('✓ Retrieved existing project secret');
242
261
  }
243
- // Write .env.local
244
- const shouldContinue = await writeEnvFile(envPath, projectId, secret);
245
- if (!shouldContinue) {
246
- // User declined to continue - abort init
247
- return;
248
- }
249
- // Reload env file so newly written credentials are available to pullCommand
250
- // Use force=true to overwrite any previously cached values
251
- loadEnvFile(cwd, true);
252
- // 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
253
266
  await createRequirementsDirectory(cwd, projectId);
254
267
  // Update .gitignore
255
268
  ensureGitignore(cwd);
256
269
  console.log('✓ Updated .gitignore\n');
257
- // Pull existing requirements from cloud
270
+ // INIT-6.2: Pull existing requirements from cloud
258
271
  console.log('Pulling requirements from cloud...');
259
272
  await pullCommand({ project: projectId });
260
273
  console.log(`\n✓ Successfully connected to project: ${selectedProject.projectName}`);
261
274
  console.log(` Project ID: ${projectId}`);
275
+ // INIT-6.3: Show guidance
262
276
  console.log('\nNext steps:');
263
277
  console.log(' 1. Review the requirements in .requirements/');
264
278
  console.log(' 2. Add the test harness to your test setup (see README)');
265
279
  }
266
280
  /**
267
- * Write .env.local file with confirmation if it already exists
268
- * Returns true if credentials were written or user chose to continue with existing
269
- * Returns false if user aborted
270
- */
271
- async function writeEnvFile(envPath, projectId, secret) {
272
- const newEnvLines = [
273
- '# dot•requirements configuration',
274
- `DOTREQUIREMENTS_PROJECT_ID=${projectId}`,
275
- `DOTREQUIREMENTS_PROJECT_SECRET=${secret}`,
276
- ];
277
- if (fs.existsSync(envPath)) {
278
- // Read existing content
279
- const existingContent = fs.readFileSync(envPath, 'utf-8');
280
- // Check if dotrequirements variables already exist
281
- const hasProjectId = existingContent.includes('DOTREQUIREMENTS_PROJECT_ID');
282
- const hasProjectSecret = existingContent.includes('DOTREQUIREMENTS_PROJECT_SECRET');
283
- if (hasProjectId || hasProjectSecret) {
284
- // INIT-2.0: Ask if user wants to update existing values
285
- const shouldUpdate = await promptConfirm(`.env.local already contains ${brand} configuration. Update it?`, false);
286
- if (!shouldUpdate) {
287
- // INIT-2.1: Ask if user wants to continue with existing credentials
288
- const shouldContinue = await promptConfirm('Continue with existing credentials?', true);
289
- if (!shouldContinue) {
290
- // INIT-2.3: User declined to continue - abort
291
- console.log('Aborted.');
292
- return false;
293
- }
294
- // INIT-2.2: User wants to continue with existing credentials
295
- console.log('Continuing with existing credentials...');
296
- return true;
297
- }
298
- // Remove old dotrequirements lines
299
- const lines = existingContent.split('\n');
300
- const filteredLines = lines.filter(line => !line.includes('DOTREQUIREMENTS_PROJECT_ID') &&
301
- !line.includes('DOTREQUIREMENTS_PROJECT_SECRET') &&
302
- !(line.trim() === '# dotrequirements configuration') &&
303
- !(line.trim() === '# dot•requirements configuration'));
304
- // Append new configuration
305
- const updatedContent = filteredLines.join('\n').trimEnd() + '\n\n' + newEnvLines.join('\n') + '\n';
306
- fs.writeFileSync(envPath, updatedContent);
307
- console.log(`✓ Updated ${brand} configuration in .env.local`);
308
- return true;
309
- }
310
- else {
311
- // File exists but doesn't have dotrequirements config - just append
312
- const shouldAppend = await promptConfirm(`.env.local already exists. Add ${brand} configuration to it?`, true);
313
- if (!shouldAppend) {
314
- console.log('\nSkipped writing to .env.local. Please add these values manually:');
315
- console.log(` DOTREQUIREMENTS_PROJECT_ID=${projectId}`);
316
- console.log(` DOTREQUIREMENTS_PROJECT_SECRET=${secret}\n`);
317
- return true; // Continue with init even though we didn't write the file
318
- }
319
- // Append to existing file
320
- const appendContent = '\n' + newEnvLines.join('\n') + '\n';
321
- fs.appendFileSync(envPath, appendContent);
322
- console.log(`✓ Added ${brand} configuration to .env.local`);
323
- return true;
324
- }
325
- }
326
- else {
327
- // File doesn't exist - create it
328
- const envContent = newEnvLines.join('\n') + '\n';
329
- fs.writeFileSync(envPath, envContent);
330
- console.log('✓ Wrote configuration to .env.local');
331
- return true;
332
- }
333
- }
334
- /**
335
- * Create .requirements/ directory with confirmation if it already exists
281
+ * INIT-5: Create .requirements/ directory with README and example
336
282
  */
337
283
  async function createRequirementsDirectory(cwd, projectId) {
338
284
  const requirementsDir = path.join(cwd, '.requirements');
339
- if (fs.existsSync(requirementsDir)) {
340
- const shouldOverwrite = await promptConfirm('.requirements/ directory already exists. Continue?', true);
341
- if (!shouldOverwrite) {
342
- console.log('Skipped creating .requirements/ directory');
343
- return;
344
- }
345
- }
346
- else {
285
+ // Directory may already exist from writeProjectSettings
286
+ if (!fs.existsSync(requirementsDir)) {
347
287
  fs.mkdirSync(requirementsDir, { recursive: true });
348
288
  console.log('✓ Created .requirements/ directory');
349
289
  }
350
- // Always create/update README
290
+ // INIT-5.0: Always create/update README
351
291
  const readmePath = path.join(requirementsDir, 'README.md');
352
292
  fs.writeFileSync(readmePath, generateRequirementsReadme());
353
293
  console.log('✓ Created .requirements/README.md');
354
- // Create example.requirements.md (INIT-5.1)
294
+ // INIT-5.1: Create example.requirements.md
355
295
  const examplePath = path.join(requirementsDir, 'example.requirements.md');
356
296
  if (!fs.existsSync(examplePath)) {
357
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