@popoverai/dotrequirements 0.11.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 (127) hide show
  1. package/README.md +478 -0
  2. package/dist/cli.d.ts +3 -0
  3. package/dist/cli.js +82 -0
  4. package/dist/commands/init.d.ts +6 -0
  5. package/dist/commands/init.js +355 -0
  6. package/dist/commands/link.d.ts +15 -0
  7. package/dist/commands/link.js +156 -0
  8. package/dist/commands/login.d.ts +12 -0
  9. package/dist/commands/login.js +117 -0
  10. package/dist/commands/logout.d.ts +5 -0
  11. package/dist/commands/logout.js +17 -0
  12. package/dist/commands/mcp-setup.d.ts +5 -0
  13. package/dist/commands/mcp-setup.js +367 -0
  14. package/dist/commands/mcp.d.ts +6 -0
  15. package/dist/commands/mcp.js +10 -0
  16. package/dist/commands/pull.d.ts +7 -0
  17. package/dist/commands/pull.js +171 -0
  18. package/dist/commands/push.d.ts +11 -0
  19. package/dist/commands/push.js +310 -0
  20. package/dist/commands/test.d.ts +6 -0
  21. package/dist/commands/test.js +78 -0
  22. package/dist/config.d.ts +5 -0
  23. package/dist/config.js +16 -0
  24. package/dist/convex.d.ts +56 -0
  25. package/dist/convex.js +58 -0
  26. package/dist/harness/cache.d.ts +135 -0
  27. package/dist/harness/cache.js +342 -0
  28. package/dist/harness/convexReporting.d.ts +15 -0
  29. package/dist/harness/convexReporting.js +136 -0
  30. package/dist/harness/coverageCache.d.ts +30 -0
  31. package/dist/harness/coverageCache.js +70 -0
  32. package/dist/harness/finalize.d.ts +48 -0
  33. package/dist/harness/finalize.js +299 -0
  34. package/dist/harness/index.d.ts +70 -0
  35. package/dist/harness/index.js +103 -0
  36. package/dist/harness/localReporting.d.ts +6 -0
  37. package/dist/harness/localReporting.js +49 -0
  38. package/dist/harness/prepare.d.ts +41 -0
  39. package/dist/harness/prepare.js +83 -0
  40. package/dist/harness/requirementsLoader.d.ts +45 -0
  41. package/dist/harness/requirementsLoader.js +201 -0
  42. package/dist/harness/tracking.d.ts +49 -0
  43. package/dist/harness/tracking.js +179 -0
  44. package/dist/harness/types.d.ts +12 -0
  45. package/dist/harness/types.js +6 -0
  46. package/dist/mcp/convexClient.d.ts +43 -0
  47. package/dist/mcp/convexClient.js +101 -0
  48. package/dist/mcp/grep.d.ts +24 -0
  49. package/dist/mcp/grep.js +261 -0
  50. package/dist/mcp/index.d.ts +3 -0
  51. package/dist/mcp/index.js +1758 -0
  52. package/dist/mcp/requirements.d.ts +47 -0
  53. package/dist/mcp/requirements.js +141 -0
  54. package/dist/mcp/testCodeExtractor.d.ts +22 -0
  55. package/dist/mcp/testCodeExtractor.js +152 -0
  56. package/dist/mcp/types.d.ts +27 -0
  57. package/dist/mcp/types.js +2 -0
  58. package/dist/schema/browser.d.ts +12 -0
  59. package/dist/schema/browser.js +24 -0
  60. package/dist/schema/builder.d.ts +25 -0
  61. package/dist/schema/builder.js +125 -0
  62. package/dist/schema/conversions.d.ts +69 -0
  63. package/dist/schema/conversions.js +201 -0
  64. package/dist/schema/index.d.ts +14 -0
  65. package/dist/schema/index.js +24 -0
  66. package/dist/schema/parser-core.d.ts +61 -0
  67. package/dist/schema/parser-core.js +247 -0
  68. package/dist/schema/parser.d.ts +44 -0
  69. package/dist/schema/parser.js +295 -0
  70. package/dist/schema/resolver.d.ts +66 -0
  71. package/dist/schema/resolver.js +185 -0
  72. package/dist/schema/schemas.d.ts +312 -0
  73. package/dist/schema/schemas.js +258 -0
  74. package/dist/schema/test-schema.d.ts +5 -0
  75. package/dist/schema/test-schema.js +81 -0
  76. package/dist/templates/antigravity-gemini.md +3 -0
  77. package/dist/templates/antigravity-overview-rule.md +3 -0
  78. package/dist/templates/antigravity-test-rule.md +3 -0
  79. package/dist/templates/behavioral-core.md +25 -0
  80. package/dist/templates/claude-code-overview-skill.md +6 -0
  81. package/dist/templates/claude-code-skill.md +6 -0
  82. package/dist/templates/claude-code-test-skill.md +6 -0
  83. package/dist/templates/codex-agents.md +3 -0
  84. package/dist/templates/codex-overview-agents.md +3 -0
  85. package/dist/templates/codex-test-agents.md +3 -0
  86. package/dist/templates/cursor-overview-rule.mdc +5 -0
  87. package/dist/templates/cursor-rule.mdc +5 -0
  88. package/dist/templates/cursor-test-rule.mdc +5 -0
  89. package/dist/templates/example-requirements.d.ts +8 -0
  90. package/dist/templates/example-requirements.js +88 -0
  91. package/dist/templates/example-requirements.ts +88 -0
  92. package/dist/templates/overview-core.md +27 -0
  93. package/dist/templates/requirements-readme.d.ts +5 -0
  94. package/dist/templates/requirements-readme.js +31 -0
  95. package/dist/templates/requirements-readme.ts +30 -0
  96. package/dist/templates/test-writing-core.md +72 -0
  97. package/dist/utils/brand.d.ts +5 -0
  98. package/dist/utils/brand.js +8 -0
  99. package/dist/utils/browser-launch.d.ts +19 -0
  100. package/dist/utils/browser-launch.js +36 -0
  101. package/dist/utils/detect-existing-project.d.ts +5 -0
  102. package/dist/utils/detect-existing-project.js +34 -0
  103. package/dist/utils/env.d.ts +19 -0
  104. package/dist/utils/env.js +56 -0
  105. package/dist/utils/gitignore.d.ts +7 -0
  106. package/dist/utils/gitignore.js +29 -0
  107. package/dist/utils/local-project.d.ts +31 -0
  108. package/dist/utils/local-project.js +33 -0
  109. package/dist/utils/oauth-callback-server.d.ts +28 -0
  110. package/dist/utils/oauth-callback-server.js +156 -0
  111. package/dist/utils/oauth-flow.d.ts +22 -0
  112. package/dist/utils/oauth-flow.js +120 -0
  113. package/dist/utils/project-discovery.d.ts +57 -0
  114. package/dist/utils/project-discovery.js +146 -0
  115. package/dist/utils/project-name.d.ts +8 -0
  116. package/dist/utils/project-name.js +48 -0
  117. package/dist/utils/project-selector.d.ts +25 -0
  118. package/dist/utils/project-selector.js +69 -0
  119. package/dist/utils/prompts.d.ts +33 -0
  120. package/dist/utils/prompts.js +60 -0
  121. package/dist/utils/templates.d.ts +29 -0
  122. package/dist/utils/templates.js +67 -0
  123. package/dist/utils/token-refresh.d.ts +24 -0
  124. package/dist/utils/token-refresh.js +69 -0
  125. package/dist/utils/token-storage.d.ts +31 -0
  126. package/dist/utils/token-storage.js +57 -0
  127. package/package.json +82 -0
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Utilities for detecting and working with local-only projects.
3
+ *
4
+ * Local-only projects use placeholder credentials and have no cloud features.
5
+ */
6
+ /**
7
+ * Check if a project is local-only (no cloud features).
8
+ *
9
+ * Local-only projects have placeholder credentials:
10
+ * - DOTREQUIREMENTS_PROJECT_ID=local
11
+ * - DOTREQUIREMENTS_PROJECT_SECRET=local
12
+ *
13
+ * These projects can use local features (test harness, MCP local operations)
14
+ * but cannot use cloud features (coverage reporting, push/pull, AI features).
15
+ *
16
+ * @param projectId - The project ID to check
17
+ * @returns true if the project is local-only, false otherwise
18
+ */
19
+ export declare function isLocalOnlyProject(projectId: string | undefined): boolean;
20
+ /**
21
+ * Placeholder credentials for local-only projects.
22
+ */
23
+ export declare const LOCAL_PROJECT_CREDENTIALS: {
24
+ readonly projectId: "local";
25
+ readonly secret: "local";
26
+ };
27
+ /**
28
+ * Error message for when cloud features are attempted on local-only projects.
29
+ */
30
+ export declare const CLOUD_FEATURES_REQUIRE_AUTH_MESSAGE = "Cloud features require authentication. Run `dotrequirements login` to enable cloud features.";
31
+ //# sourceMappingURL=local-project.d.ts.map
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Utilities for detecting and working with local-only projects.
3
+ *
4
+ * Local-only projects use placeholder credentials and have no cloud features.
5
+ */
6
+ /**
7
+ * Check if a project is local-only (no cloud features).
8
+ *
9
+ * Local-only projects have placeholder credentials:
10
+ * - DOTREQUIREMENTS_PROJECT_ID=local
11
+ * - DOTREQUIREMENTS_PROJECT_SECRET=local
12
+ *
13
+ * These projects can use local features (test harness, MCP local operations)
14
+ * but cannot use cloud features (coverage reporting, push/pull, AI features).
15
+ *
16
+ * @param projectId - The project ID to check
17
+ * @returns true if the project is local-only, false otherwise
18
+ */
19
+ export function isLocalOnlyProject(projectId) {
20
+ return projectId === 'local';
21
+ }
22
+ /**
23
+ * Placeholder credentials for local-only projects.
24
+ */
25
+ export const LOCAL_PROJECT_CREDENTIALS = {
26
+ projectId: 'local',
27
+ secret: 'local',
28
+ };
29
+ /**
30
+ * Error message for when cloud features are attempted on local-only projects.
31
+ */
32
+ export const CLOUD_FEATURES_REQUIRE_AUTH_MESSAGE = 'Cloud features require authentication. Run `dotrequirements login` to enable cloud features.';
33
+ //# sourceMappingURL=local-project.js.map
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Local HTTP server for OAuth callback handling.
3
+ *
4
+ * Creates a temporary server on localhost to receive the OAuth redirect.
5
+ * Implements AUTH-5: Local callback server for OAuth redirect
6
+ */
7
+ import * as http from 'http';
8
+ export interface CallbackResult {
9
+ code: string;
10
+ state: string;
11
+ }
12
+ export interface CallbackServerOptions {
13
+ port?: number;
14
+ timeoutMs?: number;
15
+ }
16
+ /**
17
+ * Start a local HTTP server and wait for OAuth callback
18
+ *
19
+ * Returns the authorization code and state from the callback URL
20
+ */
21
+ export declare function startCallbackServer(options?: CallbackServerOptions): Promise<CallbackResult>;
22
+ /**
23
+ * Get the callback URL for the OAuth flow
24
+ *
25
+ * Must be called after starting the server to get the actual port
26
+ */
27
+ export declare function getCallbackUrl(server: http.Server): string;
28
+ //# sourceMappingURL=oauth-callback-server.d.ts.map
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Local HTTP server for OAuth callback handling.
3
+ *
4
+ * Creates a temporary server on localhost to receive the OAuth redirect.
5
+ * Implements AUTH-5: Local callback server for OAuth redirect
6
+ */
7
+ import * as http from 'http';
8
+ import { URL } from 'url';
9
+ /**
10
+ * Start a local HTTP server and wait for OAuth callback
11
+ *
12
+ * Returns the authorization code and state from the callback URL
13
+ */
14
+ export async function startCallbackServer(options = {}) {
15
+ const { port = 0, timeoutMs = 120000 } = options; // Default 2 minute timeout
16
+ return new Promise((resolve, reject) => {
17
+ let timeout;
18
+ const server = http.createServer((req, res) => {
19
+ // Only handle GET requests to the callback path
20
+ if (req.method !== 'GET' || !req.url) {
21
+ res.writeHead(404);
22
+ res.end('Not Found');
23
+ return;
24
+ }
25
+ try {
26
+ // Parse the callback URL
27
+ const address = server.address();
28
+ const port = address && typeof address !== 'string' ? address.port : 3000;
29
+ const callbackUrl = new URL(req.url, `http://localhost:${port}`);
30
+ const code = callbackUrl.searchParams.get('code');
31
+ const state = callbackUrl.searchParams.get('state');
32
+ const error = callbackUrl.searchParams.get('error');
33
+ const errorDescription = callbackUrl.searchParams.get('error_description');
34
+ // Handle OAuth errors
35
+ if (error) {
36
+ const message = errorDescription || error;
37
+ res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
38
+ res.end(`
39
+ <!DOCTYPE html>
40
+ <html>
41
+ <head>
42
+ <meta charset="utf-8">
43
+ <title>Authentication Failed</title>
44
+ <style>
45
+ body { font-family: system-ui; max-width: 600px; margin: 100px auto; padding: 20px; }
46
+ .error { color: #c00; }
47
+ </style>
48
+ </head>
49
+ <body>
50
+ <h1 class="error">Authentication Failed</h1>
51
+ <p>${escapeHtml(message)}</p>
52
+ <p>You can close this window and try again.</p>
53
+ </body>
54
+ </html>
55
+ `);
56
+ clearTimeout(timeout);
57
+ server.close();
58
+ reject(new Error(`OAuth error: ${message}`));
59
+ return;
60
+ }
61
+ // Validate required parameters
62
+ if (!code || !state) {
63
+ res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
64
+ res.end(`
65
+ <!DOCTYPE html>
66
+ <html>
67
+ <head>
68
+ <meta charset="utf-8">
69
+ <title>Invalid Callback</title>
70
+ <style>
71
+ body { font-family: system-ui; max-width: 600px; margin: 100px auto; padding: 20px; }
72
+ .error { color: #c00; }
73
+ </style>
74
+ </head>
75
+ <body>
76
+ <h1 class="error">Invalid Callback</h1>
77
+ <p>Missing required parameters (code or state).</p>
78
+ <p>You can close this window and try again.</p>
79
+ </body>
80
+ </html>
81
+ `);
82
+ clearTimeout(timeout);
83
+ server.close();
84
+ reject(new Error('Invalid callback: missing code or state'));
85
+ return;
86
+ }
87
+ // Success response
88
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
89
+ res.end(`
90
+ <!DOCTYPE html>
91
+ <html>
92
+ <head>
93
+ <meta charset="utf-8">
94
+ <title>Authentication Successful</title>
95
+ <style>
96
+ body { font-family: system-ui; max-width: 600px; margin: 100px auto; padding: 20px; }
97
+ .success { color: #0a0; }
98
+ </style>
99
+ </head>
100
+ <body>
101
+ <h1 class="success">✓ Authentication Successful</h1>
102
+ <p>You can close this window and return to the terminal.</p>
103
+ </body>
104
+ </html>
105
+ `);
106
+ // Close server and resolve with callback data
107
+ clearTimeout(timeout);
108
+ server.close();
109
+ resolve({ code, state });
110
+ }
111
+ catch (err) {
112
+ res.writeHead(500, { 'Content-Type': 'text/plain' });
113
+ res.end('Internal Server Error');
114
+ clearTimeout(timeout);
115
+ server.close();
116
+ reject(err);
117
+ }
118
+ });
119
+ // Set timeout
120
+ timeout = setTimeout(() => {
121
+ server.close();
122
+ reject(new Error('OAuth callback timeout: no response received within 2 minutes'));
123
+ }, timeoutMs);
124
+ // Handle server errors
125
+ server.on('error', (err) => {
126
+ clearTimeout(timeout);
127
+ reject(err);
128
+ });
129
+ // Start listening on specified port (0 = random available port)
130
+ server.listen(port, 'localhost');
131
+ });
132
+ }
133
+ /**
134
+ * Get the callback URL for the OAuth flow
135
+ *
136
+ * Must be called after starting the server to get the actual port
137
+ */
138
+ export function getCallbackUrl(server) {
139
+ const address = server.address();
140
+ if (!address || typeof address === 'string') {
141
+ throw new Error('Server is not listening on a network address');
142
+ }
143
+ return `http://localhost:${address.port}/callback`;
144
+ }
145
+ /**
146
+ * Simple HTML escaping to prevent XSS in error messages
147
+ */
148
+ function escapeHtml(str) {
149
+ return str
150
+ .replace(/&/g, '&amp;')
151
+ .replace(/</g, '&lt;')
152
+ .replace(/>/g, '&gt;')
153
+ .replace(/"/g, '&quot;')
154
+ .replace(/'/g, '&#039;');
155
+ }
156
+ //# sourceMappingURL=oauth-callback-server.js.map
@@ -0,0 +1,22 @@
1
+ /**
2
+ * OAuth flow orchestration for WorkOS AuthKit.
3
+ *
4
+ * Coordinates browser launch, callback server, and token exchange.
5
+ * Implements AUTH-24: OAuth flow orchestration
6
+ */
7
+ export interface OAuthResult {
8
+ accessToken: string;
9
+ refreshToken: string;
10
+ expiresAt: number;
11
+ }
12
+ /**
13
+ * Execute the full OAuth flow:
14
+ * 1. Start local callback server
15
+ * 2. Generate authorization URL with PKCE
16
+ * 3. Open browser for user authentication
17
+ * 4. Wait for callback with authorization code
18
+ * 5. Exchange code for tokens via WorkOS SDK
19
+ * 6. Store tokens securely
20
+ */
21
+ export declare function executeOAuthFlow(): Promise<OAuthResult>;
22
+ //# sourceMappingURL=oauth-flow.d.ts.map
@@ -0,0 +1,120 @@
1
+ /**
2
+ * OAuth flow orchestration for WorkOS AuthKit.
3
+ *
4
+ * Coordinates browser launch, callback server, and token exchange.
5
+ * Implements AUTH-24: OAuth flow orchestration
6
+ */
7
+ import { randomBytes } from 'crypto';
8
+ import { ConvexHttpClient } from 'convex/browser';
9
+ import { startCallbackServer } from './oauth-callback-server.js';
10
+ import { openBrowserWithFallback } from './browser-launch.js';
11
+ import { storeTokens } from './token-storage.js';
12
+ import { getConvexUrl } from '../config.js';
13
+ import { api } from '../convex.js';
14
+ const WORKOS_CLIENT_ID = 'client_01KCM6WPYX201P7DRV5DHF4TR7';
15
+ const WORKOS_AUTHORIZE_URL = 'https://api.workos.com/user_management/authorize';
16
+ /**
17
+ * Execute the full OAuth flow:
18
+ * 1. Start local callback server
19
+ * 2. Generate authorization URL with PKCE
20
+ * 3. Open browser for user authentication
21
+ * 4. Wait for callback with authorization code
22
+ * 5. Exchange code for tokens via WorkOS SDK
23
+ * 6. Store tokens securely
24
+ */
25
+ export async function executeOAuthFlow() {
26
+ // Generate PKCE code verifier and challenge
27
+ const codeVerifier = generateCodeVerifier();
28
+ const codeChallenge = await generateCodeChallenge(codeVerifier);
29
+ const state = generateState();
30
+ console.log('Starting authentication flow...');
31
+ // Start local callback server on port 3010 (WorkOS requires pre-configured redirect URI)
32
+ const redirectUri = 'http://localhost:3010/callback';
33
+ const callbackPromise = startCallbackServer({ port: 3010, timeoutMs: 120000 });
34
+ // Build authorization URL
35
+ const authUrl = buildAuthorizationUrl({
36
+ clientId: WORKOS_CLIENT_ID,
37
+ redirectUri,
38
+ state,
39
+ codeChallenge,
40
+ });
41
+ // Open browser for user authentication
42
+ await openBrowserWithFallback(authUrl);
43
+ console.log('Waiting for authentication in browser...');
44
+ // Wait for OAuth callback
45
+ const { code, state: returnedState } = await callbackPromise;
46
+ // Verify state to prevent CSRF attacks
47
+ if (state !== returnedState) {
48
+ throw new Error('Invalid state parameter: possible CSRF attack');
49
+ }
50
+ console.log('Authentication successful, exchanging code for tokens...');
51
+ // Exchange authorization code for tokens via Convex action (server-side WorkOS call)
52
+ const client = new ConvexHttpClient(getConvexUrl());
53
+ const result = await client.action(api.lib.authActions.exchangeAuthorizationCode, {
54
+ code,
55
+ codeVerifier,
56
+ });
57
+ if (!result.success) {
58
+ throw new Error(`Token exchange failed: ${result.error}`);
59
+ }
60
+ const { accessToken, refreshToken, expiresAt } = result;
61
+ // Store tokens securely
62
+ const tokens = {
63
+ accessToken,
64
+ refreshToken,
65
+ expiresAt,
66
+ };
67
+ await storeTokens(tokens);
68
+ console.log('✓ Authentication complete!');
69
+ return {
70
+ accessToken,
71
+ refreshToken,
72
+ expiresAt,
73
+ };
74
+ }
75
+ /**
76
+ * Build the WorkOS authorization URL
77
+ */
78
+ function buildAuthorizationUrl(params) {
79
+ const url = new URL(WORKOS_AUTHORIZE_URL);
80
+ url.searchParams.set('client_id', params.clientId);
81
+ url.searchParams.set('redirect_uri', params.redirectUri);
82
+ url.searchParams.set('response_type', 'code');
83
+ url.searchParams.set('state', params.state);
84
+ url.searchParams.set('code_challenge', params.codeChallenge);
85
+ url.searchParams.set('code_challenge_method', 'S256');
86
+ url.searchParams.set('scope', 'openid profile email offline_access');
87
+ url.searchParams.set('provider', 'authkit');
88
+ return url.toString();
89
+ }
90
+ /**
91
+ * Generate a random code verifier for PKCE
92
+ */
93
+ function generateCodeVerifier() {
94
+ return base64UrlEncode(randomBytes(32));
95
+ }
96
+ /**
97
+ * Generate code challenge from verifier using SHA-256
98
+ */
99
+ async function generateCodeChallenge(verifier) {
100
+ const { createHash } = await import('crypto');
101
+ const hash = createHash('sha256').update(verifier).digest();
102
+ return base64UrlEncode(hash);
103
+ }
104
+ /**
105
+ * Generate a random state parameter for CSRF protection
106
+ */
107
+ function generateState() {
108
+ return base64UrlEncode(randomBytes(16));
109
+ }
110
+ /**
111
+ * Base64 URL-safe encoding (without padding)
112
+ */
113
+ function base64UrlEncode(buffer) {
114
+ return buffer
115
+ .toString('base64')
116
+ .replace(/\+/g, '-')
117
+ .replace(/\//g, '_')
118
+ .replace(/=/g, '');
119
+ }
120
+ //# sourceMappingURL=oauth-flow.js.map
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Represents a configured dotrequirements project
3
+ */
4
+ export interface DotreqProject {
5
+ /** Absolute path to the project root directory */
6
+ path: string;
7
+ /** Project ID from .env.local */
8
+ projectId: string;
9
+ /** Project secret from .env.local */
10
+ projectSecret: string;
11
+ }
12
+ /**
13
+ * Result of project discovery - either a single project or multiple projects
14
+ */
15
+ export type ProjectDiscoveryResult = {
16
+ type: 'none';
17
+ message: string;
18
+ } | {
19
+ type: 'single';
20
+ project: DotreqProject;
21
+ } | {
22
+ type: 'multiple';
23
+ projects: DotreqProject[];
24
+ };
25
+ /**
26
+ * Check if a directory is a configured dotrequirements project
27
+ * A project is configured if:
28
+ * 1. It has a package.json that includes "dotreq" (in dependencies or devDependencies)
29
+ * 2. It has a .env.local with DOTREQUIREMENTS_PROJECT_ID and DOTREQUIREMENTS_PROJECT_SECRET
30
+ */
31
+ export declare function isConfiguredProject(dir: string): DotreqProject | null;
32
+ /**
33
+ * Find all configured dotrequirements projects within a directory tree
34
+ * Searches recursively, respecting ignore patterns
35
+ */
36
+ export declare function findConfiguredProjects(startDir: string): Promise<DotreqProject[]>;
37
+ /**
38
+ * Discover dotrequirements project(s) from the MCP working directory
39
+ * Implements the 4-case discovery logic:
40
+ * 1. No configured projects → error message
41
+ * 2. Working directory itself is configured → use it
42
+ * 3. Exactly one child is configured → use it
43
+ * 4. Multiple children configured → return all with projectId parameter support
44
+ */
45
+ export declare function discoverProjects(workingDir?: string): Promise<ProjectDiscoveryResult>;
46
+ /**
47
+ * Resolve a single project from discovery result
48
+ * If projectId is provided and multiple projects exist, returns the matching project
49
+ * Otherwise returns the single project or throws an error
50
+ */
51
+ export declare function resolveProject(result: ProjectDiscoveryResult, projectId?: string): DotreqProject;
52
+ /**
53
+ * Convenience function to discover and resolve a single project
54
+ * Combines discoverProjects() and resolveProject() for common use case
55
+ */
56
+ export declare function getProject(workingDir?: string, projectId?: string): Promise<DotreqProject>;
57
+ //# sourceMappingURL=project-discovery.d.ts.map
@@ -0,0 +1,146 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { glob } from 'glob';
4
+ /**
5
+ * Check if a directory is a configured dotrequirements project
6
+ * A project is configured if:
7
+ * 1. It has a package.json that includes "dotreq" (in dependencies or devDependencies)
8
+ * 2. It has a .env.local with DOTREQUIREMENTS_PROJECT_ID and DOTREQUIREMENTS_PROJECT_SECRET
9
+ */
10
+ export function isConfiguredProject(dir) {
11
+ const pkgPath = path.join(dir, 'package.json');
12
+ const envPath = path.join(dir, '.env.local');
13
+ // Check for package.json with dotreq
14
+ if (!fs.existsSync(pkgPath)) {
15
+ return null;
16
+ }
17
+ try {
18
+ const pkgContent = fs.readFileSync(pkgPath, 'utf-8');
19
+ // Look for "dotreq" anywhere in the file (flexible matching)
20
+ if (!pkgContent.includes('dotreq')) {
21
+ return null;
22
+ }
23
+ }
24
+ catch {
25
+ return null;
26
+ }
27
+ // Check for .env.local with required vars
28
+ if (!fs.existsSync(envPath)) {
29
+ return null;
30
+ }
31
+ let projectId;
32
+ let projectSecret;
33
+ try {
34
+ const envContent = fs.readFileSync(envPath, 'utf-8');
35
+ const lines = envContent.split('\n');
36
+ for (const line of lines) {
37
+ const trimmed = line.trim();
38
+ if (trimmed.startsWith('DOTREQUIREMENTS_PROJECT_ID=')) {
39
+ projectId = trimmed.split('=')[1]?.trim().replace(/['"]/g, '');
40
+ }
41
+ if (trimmed.startsWith('DOTREQUIREMENTS_PROJECT_SECRET=')) {
42
+ projectSecret = trimmed.split('=')[1]?.trim().replace(/['"]/g, '');
43
+ }
44
+ }
45
+ }
46
+ catch {
47
+ return null;
48
+ }
49
+ if (!projectId || !projectSecret) {
50
+ return null;
51
+ }
52
+ return {
53
+ path: dir,
54
+ projectId,
55
+ projectSecret,
56
+ };
57
+ }
58
+ /**
59
+ * Find all configured dotrequirements projects within a directory tree
60
+ * Searches recursively, respecting ignore patterns
61
+ */
62
+ export async function findConfiguredProjects(startDir) {
63
+ const projects = [];
64
+ // Find all package.json files, but allow .env.local despite being in .gitignore
65
+ const packageJsonFiles = await glob('**/package.json', {
66
+ cwd: startDir,
67
+ absolute: true,
68
+ ignore: ['**/node_modules/**', '**/dist/**', '**/.git/**', '**/build/**'],
69
+ });
70
+ for (const pkgPath of packageJsonFiles) {
71
+ const dir = path.dirname(pkgPath);
72
+ const project = isConfiguredProject(dir);
73
+ if (project) {
74
+ projects.push(project);
75
+ }
76
+ }
77
+ return projects;
78
+ }
79
+ /**
80
+ * Discover dotrequirements project(s) from the MCP working directory
81
+ * Implements the 4-case discovery logic:
82
+ * 1. No configured projects → error message
83
+ * 2. Working directory itself is configured → use it
84
+ * 3. Exactly one child is configured → use it
85
+ * 4. Multiple children configured → return all with projectId parameter support
86
+ */
87
+ export async function discoverProjects(workingDir = process.cwd()) {
88
+ // Case 2: Check if working directory itself is configured
89
+ const selfProject = isConfiguredProject(workingDir);
90
+ if (selfProject) {
91
+ return { type: 'single', project: selfProject };
92
+ }
93
+ // Find all configured projects in descendants
94
+ const childProjects = await findConfiguredProjects(workingDir);
95
+ // Case 1: No projects found
96
+ if (childProjects.length === 0) {
97
+ return {
98
+ type: 'none',
99
+ message: `No dotrequirements projects found in ${workingDir} or its children.\n\nA project must have:\n1. package.json with "dotreq" dependency\n2. .env.local with DOTREQUIREMENTS_PROJECT_ID and DOTREQUIREMENTS_PROJECT_SECRET`,
100
+ };
101
+ }
102
+ // Case 3: Exactly one child project
103
+ if (childProjects.length === 1) {
104
+ return { type: 'single', project: childProjects[0] };
105
+ }
106
+ // Case 4: Multiple child projects
107
+ return { type: 'multiple', projects: childProjects };
108
+ }
109
+ /**
110
+ * Resolve a single project from discovery result
111
+ * If projectId is provided and multiple projects exist, returns the matching project
112
+ * Otherwise returns the single project or throws an error
113
+ */
114
+ export function resolveProject(result, projectId) {
115
+ if (result.type === 'none') {
116
+ throw new Error(result.message);
117
+ }
118
+ if (result.type === 'single') {
119
+ return result.project;
120
+ }
121
+ // Multiple projects case
122
+ if (!projectId) {
123
+ const projectList = result.projects
124
+ .map((p) => {
125
+ const relativePath = path.relative(process.cwd(), p.path);
126
+ return ` - ${relativePath || '.'}: ${p.projectId}`;
127
+ })
128
+ .join('\n');
129
+ throw new Error(`Multiple dotrequirements projects found. Please specify projectId parameter:\n\n${projectList}`);
130
+ }
131
+ const project = result.projects.find((p) => p.projectId === projectId);
132
+ if (!project) {
133
+ const availableIds = result.projects.map((p) => p.projectId).join(', ');
134
+ throw new Error(`Project "${projectId}" not found. Available projects: ${availableIds}`);
135
+ }
136
+ return project;
137
+ }
138
+ /**
139
+ * Convenience function to discover and resolve a single project
140
+ * Combines discoverProjects() and resolveProject() for common use case
141
+ */
142
+ export async function getProject(workingDir, projectId) {
143
+ const result = await discoverProjects(workingDir);
144
+ return resolveProject(result, projectId);
145
+ }
146
+ //# sourceMappingURL=project-discovery.js.map
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Detect project name from various sources
3
+ * Priority: --name flag > package.json > directory name > user prompt
4
+ */
5
+ export declare function detectProjectName(options: {
6
+ name?: string;
7
+ }): Promise<string>;
8
+ //# sourceMappingURL=project-name.d.ts.map
@@ -0,0 +1,48 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import * as readline from 'readline';
4
+ /**
5
+ * Detect project name from various sources
6
+ * Priority: --name flag > package.json > directory name > user prompt
7
+ */
8
+ export async function detectProjectName(options) {
9
+ // 1. Check --name flag
10
+ if (options.name) {
11
+ return options.name;
12
+ }
13
+ // 2. Try package.json
14
+ try {
15
+ const pkgPath = path.join(process.cwd(), 'package.json');
16
+ const pkgContent = fs.readFileSync(pkgPath, 'utf-8');
17
+ const pkg = JSON.parse(pkgContent);
18
+ if (pkg.name) {
19
+ return pkg.name;
20
+ }
21
+ }
22
+ catch {
23
+ // Ignore errors - package.json might not exist
24
+ }
25
+ // 3. Use directory name
26
+ const dirName = path.basename(process.cwd());
27
+ if (dirName && dirName !== '/' && dirName !== '.') {
28
+ return dirName;
29
+ }
30
+ // 4. Fallback to user prompt
31
+ return await promptForProjectName();
32
+ }
33
+ /**
34
+ * Prompt user for project name interactively
35
+ */
36
+ async function promptForProjectName() {
37
+ const rl = readline.createInterface({
38
+ input: process.stdin,
39
+ output: process.stdout,
40
+ });
41
+ return new Promise((resolve) => {
42
+ rl.question('Project name: ', (answer) => {
43
+ rl.close();
44
+ resolve(answer.trim() || 'my-project');
45
+ });
46
+ });
47
+ }
48
+ //# sourceMappingURL=project-name.js.map
@@ -0,0 +1,25 @@
1
+ import { ConvexHttpClient } from 'convex/browser';
2
+ export interface SelectedProject {
3
+ projectId: string;
4
+ projectName: string;
5
+ projectSlug: string | null;
6
+ }
7
+ /**
8
+ * Prompt user to select a team from their available teams.
9
+ * Returns the team ID, or null if user cancels.
10
+ */
11
+ export declare function selectTeam(client: ConvexHttpClient): Promise<string | null>;
12
+ /**
13
+ * Prompt user to select a project from a team.
14
+ * Returns the selected project info, or null if user cancels or no projects exist.
15
+ */
16
+ export declare function selectProject(client: ConvexHttpClient, teamId: string): Promise<SelectedProject | null>;
17
+ /**
18
+ * Get or create a user-scoped secret for a project.
19
+ * Returns the secret and whether it was newly created.
20
+ */
21
+ export declare function getOrCreateProjectSecret(client: ConvexHttpClient, projectId: string): Promise<{
22
+ secret: string;
23
+ created: boolean;
24
+ }>;
25
+ //# sourceMappingURL=project-selector.d.ts.map