@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.
- package/README.md +478 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +82 -0
- package/dist/commands/init.d.ts +6 -0
- package/dist/commands/init.js +355 -0
- package/dist/commands/link.d.ts +15 -0
- package/dist/commands/link.js +156 -0
- package/dist/commands/login.d.ts +12 -0
- package/dist/commands/login.js +117 -0
- package/dist/commands/logout.d.ts +5 -0
- package/dist/commands/logout.js +17 -0
- package/dist/commands/mcp-setup.d.ts +5 -0
- package/dist/commands/mcp-setup.js +367 -0
- package/dist/commands/mcp.d.ts +6 -0
- package/dist/commands/mcp.js +10 -0
- package/dist/commands/pull.d.ts +7 -0
- package/dist/commands/pull.js +171 -0
- package/dist/commands/push.d.ts +11 -0
- package/dist/commands/push.js +310 -0
- package/dist/commands/test.d.ts +6 -0
- package/dist/commands/test.js +78 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.js +16 -0
- package/dist/convex.d.ts +56 -0
- package/dist/convex.js +58 -0
- package/dist/harness/cache.d.ts +135 -0
- package/dist/harness/cache.js +342 -0
- package/dist/harness/convexReporting.d.ts +15 -0
- package/dist/harness/convexReporting.js +136 -0
- package/dist/harness/coverageCache.d.ts +30 -0
- package/dist/harness/coverageCache.js +70 -0
- package/dist/harness/finalize.d.ts +48 -0
- package/dist/harness/finalize.js +299 -0
- package/dist/harness/index.d.ts +70 -0
- package/dist/harness/index.js +103 -0
- package/dist/harness/localReporting.d.ts +6 -0
- package/dist/harness/localReporting.js +49 -0
- package/dist/harness/prepare.d.ts +41 -0
- package/dist/harness/prepare.js +83 -0
- package/dist/harness/requirementsLoader.d.ts +45 -0
- package/dist/harness/requirementsLoader.js +201 -0
- package/dist/harness/tracking.d.ts +49 -0
- package/dist/harness/tracking.js +179 -0
- package/dist/harness/types.d.ts +12 -0
- package/dist/harness/types.js +6 -0
- package/dist/mcp/convexClient.d.ts +43 -0
- package/dist/mcp/convexClient.js +101 -0
- package/dist/mcp/grep.d.ts +24 -0
- package/dist/mcp/grep.js +261 -0
- package/dist/mcp/index.d.ts +3 -0
- package/dist/mcp/index.js +1758 -0
- package/dist/mcp/requirements.d.ts +47 -0
- package/dist/mcp/requirements.js +141 -0
- package/dist/mcp/testCodeExtractor.d.ts +22 -0
- package/dist/mcp/testCodeExtractor.js +152 -0
- package/dist/mcp/types.d.ts +27 -0
- package/dist/mcp/types.js +2 -0
- package/dist/schema/browser.d.ts +12 -0
- package/dist/schema/browser.js +24 -0
- package/dist/schema/builder.d.ts +25 -0
- package/dist/schema/builder.js +125 -0
- package/dist/schema/conversions.d.ts +69 -0
- package/dist/schema/conversions.js +201 -0
- package/dist/schema/index.d.ts +14 -0
- package/dist/schema/index.js +24 -0
- package/dist/schema/parser-core.d.ts +61 -0
- package/dist/schema/parser-core.js +247 -0
- package/dist/schema/parser.d.ts +44 -0
- package/dist/schema/parser.js +295 -0
- package/dist/schema/resolver.d.ts +66 -0
- package/dist/schema/resolver.js +185 -0
- package/dist/schema/schemas.d.ts +312 -0
- package/dist/schema/schemas.js +258 -0
- package/dist/schema/test-schema.d.ts +5 -0
- package/dist/schema/test-schema.js +81 -0
- package/dist/templates/antigravity-gemini.md +3 -0
- package/dist/templates/antigravity-overview-rule.md +3 -0
- package/dist/templates/antigravity-test-rule.md +3 -0
- package/dist/templates/behavioral-core.md +25 -0
- package/dist/templates/claude-code-overview-skill.md +6 -0
- package/dist/templates/claude-code-skill.md +6 -0
- package/dist/templates/claude-code-test-skill.md +6 -0
- package/dist/templates/codex-agents.md +3 -0
- package/dist/templates/codex-overview-agents.md +3 -0
- package/dist/templates/codex-test-agents.md +3 -0
- package/dist/templates/cursor-overview-rule.mdc +5 -0
- package/dist/templates/cursor-rule.mdc +5 -0
- package/dist/templates/cursor-test-rule.mdc +5 -0
- package/dist/templates/example-requirements.d.ts +8 -0
- package/dist/templates/example-requirements.js +88 -0
- package/dist/templates/example-requirements.ts +88 -0
- package/dist/templates/overview-core.md +27 -0
- package/dist/templates/requirements-readme.d.ts +5 -0
- package/dist/templates/requirements-readme.js +31 -0
- package/dist/templates/requirements-readme.ts +30 -0
- package/dist/templates/test-writing-core.md +72 -0
- package/dist/utils/brand.d.ts +5 -0
- package/dist/utils/brand.js +8 -0
- package/dist/utils/browser-launch.d.ts +19 -0
- package/dist/utils/browser-launch.js +36 -0
- package/dist/utils/detect-existing-project.d.ts +5 -0
- package/dist/utils/detect-existing-project.js +34 -0
- package/dist/utils/env.d.ts +19 -0
- package/dist/utils/env.js +56 -0
- package/dist/utils/gitignore.d.ts +7 -0
- package/dist/utils/gitignore.js +29 -0
- package/dist/utils/local-project.d.ts +31 -0
- package/dist/utils/local-project.js +33 -0
- package/dist/utils/oauth-callback-server.d.ts +28 -0
- package/dist/utils/oauth-callback-server.js +156 -0
- package/dist/utils/oauth-flow.d.ts +22 -0
- package/dist/utils/oauth-flow.js +120 -0
- package/dist/utils/project-discovery.d.ts +57 -0
- package/dist/utils/project-discovery.js +146 -0
- package/dist/utils/project-name.d.ts +8 -0
- package/dist/utils/project-name.js +48 -0
- package/dist/utils/project-selector.d.ts +25 -0
- package/dist/utils/project-selector.js +69 -0
- package/dist/utils/prompts.d.ts +33 -0
- package/dist/utils/prompts.js +60 -0
- package/dist/utils/templates.d.ts +29 -0
- package/dist/utils/templates.js +67 -0
- package/dist/utils/token-refresh.d.ts +24 -0
- package/dist/utils/token-refresh.js +69 -0
- package/dist/utils/token-storage.d.ts +31 -0
- package/dist/utils/token-storage.js +57 -0
- 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, '&')
|
|
151
|
+
.replace(/</g, '<')
|
|
152
|
+
.replace(/>/g, '>')
|
|
153
|
+
.replace(/"/g, '"')
|
|
154
|
+
.replace(/'/g, ''');
|
|
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,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
|