@popoverai/dotrequirements 0.22.0 → 0.24.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 (230) hide show
  1. package/README.md +173 -23
  2. package/dist/cli.js +121 -61
  3. package/dist/codebase-to-spec/budget.d.ts +53 -0
  4. package/dist/codebase-to-spec/budget.js +80 -0
  5. package/dist/codebase-to-spec/cache.d.ts +49 -0
  6. package/dist/codebase-to-spec/cache.js +54 -0
  7. package/dist/codebase-to-spec/claude.d.ts +69 -0
  8. package/dist/codebase-to-spec/claude.js +126 -0
  9. package/dist/codebase-to-spec/compose.d.ts +49 -0
  10. package/dist/codebase-to-spec/compose.js +124 -0
  11. package/dist/codebase-to-spec/edit-loop.d.ts +54 -0
  12. package/dist/codebase-to-spec/edit-loop.js +195 -0
  13. package/dist/codebase-to-spec/editor.d.ts +54 -0
  14. package/dist/codebase-to-spec/editor.js +74 -0
  15. package/dist/codebase-to-spec/exit-codes.d.ts +40 -0
  16. package/dist/codebase-to-spec/exit-codes.js +58 -0
  17. package/dist/codebase-to-spec/fan-out.d.ts +63 -0
  18. package/dist/codebase-to-spec/fan-out.js +215 -0
  19. package/dist/codebase-to-spec/interactive.d.ts +30 -0
  20. package/dist/codebase-to-spec/interactive.js +48 -0
  21. package/dist/codebase-to-spec/outline-review-loop.d.ts +51 -0
  22. package/dist/codebase-to-spec/outline-review-loop.js +187 -0
  23. package/dist/codebase-to-spec/pack.d.ts +51 -0
  24. package/dist/codebase-to-spec/pack.js +127 -0
  25. package/dist/codebase-to-spec/planner.d.ts +41 -0
  26. package/dist/codebase-to-spec/planner.js +76 -0
  27. package/dist/codebase-to-spec/present.d.ts +94 -0
  28. package/dist/codebase-to-spec/present.js +288 -0
  29. package/dist/codebase-to-spec/progress.d.ts +33 -0
  30. package/dist/codebase-to-spec/progress.js +28 -0
  31. package/dist/codebase-to-spec/prompts/editor.d.ts +13 -0
  32. package/dist/codebase-to-spec/prompts/editor.js +57 -0
  33. package/dist/codebase-to-spec/prompts/outline-reviewer.d.ts +12 -0
  34. package/dist/codebase-to-spec/prompts/outline-reviewer.js +87 -0
  35. package/dist/codebase-to-spec/prompts/planner-apply.d.ts +12 -0
  36. package/dist/codebase-to-spec/prompts/planner-apply.js +32 -0
  37. package/dist/codebase-to-spec/prompts/planner-initial.d.ts +11 -0
  38. package/dist/codebase-to-spec/prompts/planner-initial.js +125 -0
  39. package/dist/codebase-to-spec/prompts/planner-revise.d.ts +14 -0
  40. package/dist/codebase-to-spec/prompts/planner-revise.js +60 -0
  41. package/dist/codebase-to-spec/prompts/spec-reviewer.d.ts +16 -0
  42. package/dist/codebase-to-spec/prompts/spec-reviewer.js +96 -0
  43. package/dist/codebase-to-spec/prompts/specifier.d.ts +12 -0
  44. package/dist/codebase-to-spec/prompts/specifier.js +100 -0
  45. package/dist/codebase-to-spec/prompts/style-check.d.ts +12 -0
  46. package/dist/codebase-to-spec/prompts/style-check.js +78 -0
  47. package/dist/codebase-to-spec/schemas.d.ts +257 -0
  48. package/dist/codebase-to-spec/schemas.js +183 -0
  49. package/dist/codebase-to-spec/skill-install.d.ts +57 -0
  50. package/dist/codebase-to-spec/skill-install.js +79 -0
  51. package/dist/codebase-to-spec/slice.d.ts +49 -0
  52. package/dist/codebase-to-spec/slice.js +111 -0
  53. package/dist/codebase-to-spec/specifier.d.ts +60 -0
  54. package/dist/codebase-to-spec/specifier.js +79 -0
  55. package/dist/codebase-to-spec/style-check.d.ts +29 -0
  56. package/dist/codebase-to-spec/style-check.js +33 -0
  57. package/dist/codebase-to-spec/summary.d.ts +51 -0
  58. package/dist/codebase-to-spec/summary.js +183 -0
  59. package/dist/codebase-to-spec/validate.d.ts +46 -0
  60. package/dist/codebase-to-spec/validate.js +130 -0
  61. package/dist/commands/acceptance-test.d.ts +6 -0
  62. package/dist/commands/acceptance-test.js +212 -0
  63. package/dist/commands/ai-setup.d.ts +5 -0
  64. package/dist/commands/ai-setup.js +441 -0
  65. package/dist/commands/browsertest.d.ts +0 -1
  66. package/dist/commands/browsertest.js +51 -26
  67. package/dist/commands/codebase-to-spec/compose.d.ts +14 -0
  68. package/dist/commands/codebase-to-spec/compose.js +57 -0
  69. package/dist/commands/codebase-to-spec/edit-loop.d.ts +16 -0
  70. package/dist/commands/codebase-to-spec/edit-loop.js +83 -0
  71. package/dist/commands/codebase-to-spec/fan-out.d.ts +19 -0
  72. package/dist/commands/codebase-to-spec/fan-out.js +77 -0
  73. package/dist/commands/codebase-to-spec/index.d.ts +9 -0
  74. package/dist/commands/codebase-to-spec/index.js +135 -0
  75. package/dist/commands/codebase-to-spec/pack.d.ts +22 -0
  76. package/dist/commands/codebase-to-spec/pack.js +76 -0
  77. package/dist/commands/codebase-to-spec/plan-loop.d.ts +26 -0
  78. package/dist/commands/codebase-to-spec/plan-loop.js +105 -0
  79. package/dist/commands/codebase-to-spec/present.d.ts +21 -0
  80. package/dist/commands/codebase-to-spec/present.js +92 -0
  81. package/dist/commands/codebase-to-spec/run.d.ts +20 -0
  82. package/dist/commands/codebase-to-spec/run.js +85 -0
  83. package/dist/commands/codebase-to-spec/skill-install.d.ts +20 -0
  84. package/dist/commands/codebase-to-spec/skill-install.js +51 -0
  85. package/dist/commands/codebase-to-spec/specify-area.d.ts +18 -0
  86. package/dist/commands/codebase-to-spec/specify-area.js +82 -0
  87. package/dist/commands/codebase-to-spec/style-check.d.ts +15 -0
  88. package/dist/commands/codebase-to-spec/style-check.js +42 -0
  89. package/dist/commands/codebase-to-spec/validate.d.ts +18 -0
  90. package/dist/commands/codebase-to-spec/validate.js +38 -0
  91. package/dist/commands/create-requirement-document.d.ts +2 -0
  92. package/dist/commands/create-requirement-document.js +41 -0
  93. package/dist/commands/finalize.js +7 -7
  94. package/dist/commands/get.d.ts +2 -0
  95. package/dist/commands/get.js +55 -0
  96. package/dist/commands/init.js +132 -117
  97. package/dist/commands/link.js +27 -27
  98. package/dist/commands/list.d.ts +6 -0
  99. package/dist/commands/list.js +43 -0
  100. package/dist/commands/mcp-setup.js +159 -149
  101. package/dist/commands/mcp.js +1 -1
  102. package/dist/commands/prepare.js +4 -4
  103. package/dist/commands/pull.js +116 -121
  104. package/dist/commands/push.js +106 -112
  105. package/dist/commands/report.d.ts +6 -2
  106. package/dist/commands/report.js +177 -122
  107. package/dist/commands/requirements-for.d.ts +2 -0
  108. package/dist/commands/requirements-for.js +29 -0
  109. package/dist/commands/review-test.d.ts +2 -0
  110. package/dist/commands/review-test.js +75 -0
  111. package/dist/commands/search.d.ts +6 -0
  112. package/dist/commands/search.js +39 -0
  113. package/dist/commands/style-check.d.ts +7 -0
  114. package/dist/commands/style-check.js +75 -0
  115. package/dist/commands/test.js +53 -59
  116. package/dist/commands/tests-for.d.ts +2 -0
  117. package/dist/commands/tests-for.js +80 -0
  118. package/dist/commands/validate.d.ts +6 -0
  119. package/dist/commands/validate.js +72 -0
  120. package/dist/config.js +1 -1
  121. package/dist/convex.d.ts +34 -22
  122. package/dist/convex.js +38 -22
  123. package/dist/harness/cache.d.ts +1 -5
  124. package/dist/harness/cache.js +49 -59
  125. package/dist/harness/convexReporting.d.ts +1 -1
  126. package/dist/harness/convexReporting.js +9 -7
  127. package/dist/harness/coverageCache.js +3 -3
  128. package/dist/harness/finalize.js +59 -46
  129. package/dist/harness/index.d.ts +6 -7
  130. package/dist/harness/index.js +9 -10
  131. package/dist/harness/prepare.js +6 -5
  132. package/dist/harness/requirementsLoader.d.ts +2 -2
  133. package/dist/harness/requirementsLoader.js +13 -35
  134. package/dist/harness/tracking.js +18 -18
  135. package/dist/harness/types.d.ts +1 -1
  136. package/dist/mcp/convexClient.d.ts +0 -39
  137. package/dist/mcp/convexClient.js +2 -107
  138. package/dist/mcp/grep.d.ts +1 -1
  139. package/dist/mcp/grep.js +87 -42
  140. package/dist/mcp/handlers/authoring.d.ts +1 -1
  141. package/dist/mcp/handlers/authoring.js +30 -234
  142. package/dist/mcp/handlers/coverage.d.ts +1 -1
  143. package/dist/mcp/handlers/coverage.js +13 -15
  144. package/dist/mcp/handlers/debug.d.ts +2 -3
  145. package/dist/mcp/handlers/debug.js +10 -10
  146. package/dist/mcp/handlers/get.d.ts +1 -1
  147. package/dist/mcp/handlers/get.js +11 -10
  148. package/dist/mcp/handlers/index.d.ts +20 -20
  149. package/dist/mcp/handlers/index.js +10 -10
  150. package/dist/mcp/handlers/list.d.ts +4 -33
  151. package/dist/mcp/handlers/list.js +16 -38
  152. package/dist/mcp/handlers/push.d.ts +1 -1
  153. package/dist/mcp/handlers/push.js +28 -18
  154. package/dist/mcp/handlers/report.d.ts +16 -0
  155. package/dist/mcp/handlers/report.js +134 -0
  156. package/dist/mcp/handlers/review.d.ts +1 -1
  157. package/dist/mcp/handlers/review.js +40 -59
  158. package/dist/mcp/handlers/search.d.ts +1 -1
  159. package/dist/mcp/handlers/search.js +7 -9
  160. package/dist/mcp/handlers/test-mapping.d.ts +1 -1
  161. package/dist/mcp/handlers/test-mapping.js +14 -14
  162. package/dist/mcp/handlers/types.d.ts +3 -3
  163. package/dist/mcp/handlers/types.js +2 -2
  164. package/dist/mcp/index.d.ts +1 -1
  165. package/dist/mcp/index.js +156 -178
  166. package/dist/mcp/requirements.d.ts +2 -2
  167. package/dist/mcp/requirements.js +30 -30
  168. package/dist/mcp/testCodeExtractor.js +24 -26
  169. package/dist/mcp/types.d.ts +1 -1
  170. package/dist/push/core.d.ts +2 -2
  171. package/dist/push/core.js +20 -20
  172. package/dist/push/index.d.ts +1 -1
  173. package/dist/push/index.js +2 -2
  174. package/dist/requirements/cloud-ai.d.ts +57 -0
  175. package/dist/requirements/cloud-ai.js +104 -0
  176. package/dist/requirements/cloud-coverage.d.ts +41 -0
  177. package/dist/requirements/cloud-coverage.js +60 -0
  178. package/dist/requirements/coverage.d.ts +45 -0
  179. package/dist/requirements/coverage.js +114 -0
  180. package/dist/requirements/grep.d.ts +33 -0
  181. package/dist/requirements/grep.js +306 -0
  182. package/dist/requirements/index.d.ts +73 -0
  183. package/dist/requirements/index.js +174 -0
  184. package/dist/requirements/style-guide.d.ts +67 -0
  185. package/dist/requirements/style-guide.js +299 -0
  186. package/dist/requirements/testCodeExtractor.d.ts +22 -0
  187. package/dist/requirements/testCodeExtractor.js +150 -0
  188. package/dist/schema/browser.d.ts +8 -6
  189. package/dist/schema/browser.js +14 -14
  190. package/dist/schema/builder.d.ts +1 -1
  191. package/dist/schema/builder.js +13 -44
  192. package/dist/schema/conversions.d.ts +2 -2
  193. package/dist/schema/conversions.js +11 -11
  194. package/dist/schema/index.d.ts +9 -7
  195. package/dist/schema/index.js +15 -13
  196. package/dist/schema/parser-core.d.ts +1 -1
  197. package/dist/schema/parser-core.js +23 -22
  198. package/dist/schema/parser.d.ts +3 -3
  199. package/dist/schema/parser.js +27 -31
  200. package/dist/schema/resolver.d.ts +1 -1
  201. package/dist/schema/resolver.js +9 -9
  202. package/dist/schema/scenario.d.ts +91 -0
  203. package/dist/schema/scenario.js +82 -0
  204. package/dist/schema/schemas.d.ts +3 -3
  205. package/dist/schema/schemas.js +41 -28
  206. package/dist/schema/test-schema.js +27 -27
  207. package/dist/templates/context-file-section.md +3 -2
  208. package/dist/templates/example-requirements.js +1 -1
  209. package/dist/templates/example-requirements.ts +3 -1
  210. package/dist/templates/requirements-readme.js +1 -1
  211. package/dist/templates/requirements-readme.ts +1 -1
  212. package/dist/templates/skills/codebase-to-spec/SKILL.md +118 -0
  213. package/dist/utils/brand.js +3 -3
  214. package/dist/utils/browser-launch.js +4 -4
  215. package/dist/utils/context-file.d.ts +1 -1
  216. package/dist/utils/context-file.js +26 -26
  217. package/dist/utils/env.js +7 -7
  218. package/dist/utils/gitignore.js +7 -7
  219. package/dist/utils/oauth-callback-server.d.ts +1 -1
  220. package/dist/utils/oauth-callback-server.js +27 -25
  221. package/dist/utils/oauth-flow.js +32 -29
  222. package/dist/utils/project-discovery.d.ts +3 -3
  223. package/dist/utils/project-discovery.js +18 -17
  224. package/dist/utils/project-name.js +8 -8
  225. package/dist/utils/project-selector.d.ts +1 -1
  226. package/dist/utils/project-selector.js +24 -21
  227. package/dist/utils/project-settings.d.ts +5 -4
  228. package/dist/utils/project-settings.js +28 -20
  229. package/dist/utils/templates.js +6 -6
  230. package/package.json +2 -1
package/dist/utils/env.js CHANGED
@@ -1,5 +1,5 @@
1
- import * as fs from 'fs';
2
- import * as path from 'path';
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
3
  /**
4
4
  * Load environment variables from .env.local file
5
5
  * Merges with existing process.env (process.env takes precedence by default)
@@ -7,16 +7,16 @@ import * as path from 'path';
7
7
  * @param force - If true, overwrite existing process.env values (used when reloading after init)
8
8
  */
9
9
  export function loadEnvFile(cwd = process.cwd(), force = false) {
10
- const envPath = path.join(cwd, '.env.local');
10
+ const envPath = path.join(cwd, ".env.local");
11
11
  if (!fs.existsSync(envPath)) {
12
12
  return;
13
13
  }
14
14
  try {
15
- const content = fs.readFileSync(envPath, 'utf-8');
16
- const lines = content.split('\n');
15
+ const content = fs.readFileSync(envPath, "utf-8");
16
+ const lines = content.split("\n");
17
17
  for (const line of lines) {
18
18
  // Skip empty lines and comments
19
- if (!line.trim() || line.trim().startsWith('#')) {
19
+ if (!line.trim() || line.trim().startsWith("#")) {
20
20
  continue;
21
21
  }
22
22
  // Parse KEY=VALUE format
@@ -31,7 +31,7 @@ export function loadEnvFile(cwd = process.cwd(), force = false) {
31
31
  }
32
32
  }
33
33
  }
34
- catch (error) {
34
+ catch {
35
35
  // Silently fail if we can't read the file
36
36
  // The commands will error appropriately if env vars are missing
37
37
  }
@@ -1,14 +1,14 @@
1
- import * as fs from 'fs';
2
- import * as path from 'path';
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
3
  /**
4
4
  * Ensure project-settings.json is in .gitignore
5
5
  * Creates .gitignore if it doesn't exist
6
6
  * Appends the entry if not already present
7
7
  */
8
8
  export function ensureGitignore(cwd = process.cwd()) {
9
- const gitignorePath = path.join(cwd, '.gitignore');
10
- const entryToAdd = '.requirements/project-settings.json';
11
- const comment = '# dotrequirements credentials';
9
+ const gitignorePath = path.join(cwd, ".gitignore");
10
+ const entryToAdd = ".requirements/project-settings.json";
11
+ const comment = "# dotrequirements credentials";
12
12
  // Check if .gitignore exists
13
13
  if (!fs.existsSync(gitignorePath)) {
14
14
  // Create new .gitignore
@@ -16,13 +16,13 @@ export function ensureGitignore(cwd = process.cwd()) {
16
16
  return;
17
17
  }
18
18
  // Read existing .gitignore
19
- const content = fs.readFileSync(gitignorePath, 'utf-8');
19
+ const content = fs.readFileSync(gitignorePath, "utf-8");
20
20
  // Check if entry is already present
21
21
  if (content.includes(entryToAdd)) {
22
22
  return; // Already present, nothing to do
23
23
  }
24
24
  // Append to .gitignore
25
- const separator = content.endsWith('\n') ? '' : '\n';
25
+ const separator = content.endsWith("\n") ? "" : "\n";
26
26
  const newContent = `${content}${separator}\n${comment}\n${entryToAdd}\n`;
27
27
  fs.writeFileSync(gitignorePath, newContent);
28
28
  }
@@ -4,7 +4,7 @@
4
4
  * Creates a temporary server on localhost to receive the OAuth redirect.
5
5
  * Implements AUTH-5: Local callback server for OAuth redirect
6
6
  */
7
- import * as http from 'http';
7
+ import * as http from "node:http";
8
8
  export interface CallbackResult {
9
9
  code: string;
10
10
  state: string;
@@ -4,8 +4,8 @@
4
4
  * Creates a temporary server on localhost to receive the OAuth redirect.
5
5
  * Implements AUTH-5: Local callback server for OAuth redirect
6
6
  */
7
- import * as http from 'http';
8
- import { URL } from 'url';
7
+ import * as http from "node:http";
8
+ import { URL } from "node:url";
9
9
  /**
10
10
  * Start a local HTTP server and wait for OAuth callback
11
11
  *
@@ -17,24 +17,24 @@ export async function startCallbackServer(options = {}) {
17
17
  let timeout;
18
18
  const server = http.createServer((req, res) => {
19
19
  // Only handle GET requests to the callback path
20
- if (req.method !== 'GET' || !req.url) {
20
+ if (req.method !== "GET" || !req.url) {
21
21
  res.writeHead(404);
22
- res.end('Not Found');
22
+ res.end("Not Found");
23
23
  return;
24
24
  }
25
25
  try {
26
26
  // Parse the callback URL
27
27
  const address = server.address();
28
- const port = address && typeof address !== 'string' ? address.port : 3000;
28
+ const port = address && typeof address !== "string" ? address.port : 3000;
29
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');
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
34
  // Handle OAuth errors
35
35
  if (error) {
36
36
  const message = errorDescription || error;
37
- res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
37
+ res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
38
38
  res.end(`
39
39
  <!DOCTYPE html>
40
40
  <html>
@@ -60,7 +60,7 @@ export async function startCallbackServer(options = {}) {
60
60
  }
61
61
  // Validate required parameters
62
62
  if (!code || !state) {
63
- res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
63
+ res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
64
64
  res.end(`
65
65
  <!DOCTYPE html>
66
66
  <html>
@@ -81,11 +81,11 @@ export async function startCallbackServer(options = {}) {
81
81
  `);
82
82
  clearTimeout(timeout);
83
83
  server.close();
84
- reject(new Error('Invalid callback: missing code or state'));
84
+ reject(new Error("Invalid callback: missing code or state"));
85
85
  return;
86
86
  }
87
87
  // Success response
88
- res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
88
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
89
89
  res.end(`
90
90
  <!DOCTYPE html>
91
91
  <html>
@@ -109,8 +109,8 @@ export async function startCallbackServer(options = {}) {
109
109
  resolve({ code, state });
110
110
  }
111
111
  catch (err) {
112
- res.writeHead(500, { 'Content-Type': 'text/plain' });
113
- res.end('Internal Server Error');
112
+ res.writeHead(500, { "Content-Type": "text/plain" });
113
+ res.end("Internal Server Error");
114
114
  clearTimeout(timeout);
115
115
  server.close();
116
116
  reject(err);
@@ -119,15 +119,15 @@ export async function startCallbackServer(options = {}) {
119
119
  // Set timeout
120
120
  timeout = setTimeout(() => {
121
121
  server.close();
122
- reject(new Error('OAuth callback timeout: no response received within 2 minutes'));
122
+ reject(new Error("OAuth callback timeout: no response received within 2 minutes"));
123
123
  }, timeoutMs);
124
124
  // Handle server errors
125
- server.on('error', (err) => {
125
+ server.on("error", (err) => {
126
126
  clearTimeout(timeout);
127
127
  reject(err);
128
128
  });
129
129
  // Start listening on specified port (0 = random available port)
130
- server.listen(port, 'localhost');
130
+ server.listen(port, "localhost");
131
131
  });
132
132
  }
133
133
  /**
@@ -137,9 +137,11 @@ export async function startCallbackServer(options = {}) {
137
137
  */
138
138
  export function getCallbackUrl(server) {
139
139
  const address = server.address();
140
- if (!address || typeof address === 'string') {
141
- throw new Error('Server is not listening on a network address');
140
+ if (!address || typeof address === "string") {
141
+ throw new Error("Server is not listening on a network address");
142
142
  }
143
+ // Node's `server.address()` returns `string | AddressInfo | null`; we're
144
+ // inside a server.listen() callback so we know it's an AddressInfo here.
143
145
  return `http://localhost:${address.port}/callback`;
144
146
  }
145
147
  /**
@@ -147,10 +149,10 @@ export function getCallbackUrl(server) {
147
149
  */
148
150
  function escapeHtml(str) {
149
151
  return str
150
- .replace(/&/g, '&amp;')
151
- .replace(/</g, '&lt;')
152
- .replace(/>/g, '&gt;')
153
- .replace(/"/g, '&quot;')
154
- .replace(/'/g, '&#039;');
152
+ .replace(/&/g, "&amp;")
153
+ .replace(/</g, "&lt;")
154
+ .replace(/>/g, "&gt;")
155
+ .replace(/"/g, "&quot;")
156
+ .replace(/'/g, "&#039;");
155
157
  }
156
158
  //# sourceMappingURL=oauth-callback-server.js.map
@@ -4,14 +4,14 @@
4
4
  * Coordinates browser launch, callback server, and token exchange.
5
5
  * Implements AUTH-24: OAuth flow orchestration
6
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 { getConvexUrl } from '../config.js';
12
- import { api } from '../convex.js';
13
- const WORKOS_CLIENT_ID = 'client_01KCM6WPYX201P7DRV5DHF4TR7';
14
- const WORKOS_AUTHORIZE_URL = 'https://api.workos.com/user_management/authorize';
7
+ import { randomBytes } from "node:crypto";
8
+ import { ConvexHttpClient } from "convex/browser";
9
+ import { getConvexUrl } from "../config.js";
10
+ import { api } from "../convex.js";
11
+ import { openBrowserWithFallback } from "./browser-launch.js";
12
+ import { startCallbackServer } from "./oauth-callback-server.js";
13
+ const WORKOS_CLIENT_ID = "client_01KCM6WPYX201P7DRV5DHF4TR7";
14
+ const WORKOS_AUTHORIZE_URL = "https://api.workos.com/user_management/authorize";
15
15
  /**
16
16
  * Execute the full OAuth flow:
17
17
  * 1. Start local callback server
@@ -25,10 +25,13 @@ export async function executeOAuthFlow() {
25
25
  const codeVerifier = generateCodeVerifier();
26
26
  const codeChallenge = await generateCodeChallenge(codeVerifier);
27
27
  const state = generateState();
28
- console.log('Starting authentication flow...');
28
+ console.log("Starting authentication flow...");
29
29
  // Start local callback server on port 3010 (WorkOS requires pre-configured redirect URI)
30
- const redirectUri = 'http://localhost:3010/callback';
31
- const callbackPromise = startCallbackServer({ port: 3010, timeoutMs: 120000 });
30
+ const redirectUri = "http://localhost:3010/callback";
31
+ const callbackPromise = startCallbackServer({
32
+ port: 3010,
33
+ timeoutMs: 120000,
34
+ });
32
35
  // Build authorization URL
33
36
  const authUrl = buildAuthorizationUrl({
34
37
  clientId: WORKOS_CLIENT_ID,
@@ -38,14 +41,14 @@ export async function executeOAuthFlow() {
38
41
  });
39
42
  // Open browser for user authentication
40
43
  await openBrowserWithFallback(authUrl);
41
- console.log('Waiting for authentication in browser...');
44
+ console.log("Waiting for authentication in browser...");
42
45
  // Wait for OAuth callback
43
46
  const { code, state: returnedState } = await callbackPromise;
44
47
  // Verify state to prevent CSRF attacks
45
48
  if (state !== returnedState) {
46
- throw new Error('Invalid state parameter: possible CSRF attack');
49
+ throw new Error("Invalid state parameter: possible CSRF attack");
47
50
  }
48
- console.log('Authentication successful, exchanging code for tokens...');
51
+ console.log("Authentication successful, exchanging code for tokens...");
49
52
  // Exchange authorization code for tokens via Convex action (server-side WorkOS call)
50
53
  const client = new ConvexHttpClient(getConvexUrl());
51
54
  const result = await client.action(api.lib.authActions.exchangeAuthorizationCode, {
@@ -56,7 +59,7 @@ export async function executeOAuthFlow() {
56
59
  throw new Error(`Token exchange failed: ${result.error}`);
57
60
  }
58
61
  const { accessToken, refreshToken, expiresAt } = result;
59
- console.log('✓ Authentication complete!');
62
+ console.log("✓ Authentication complete!");
60
63
  return {
61
64
  accessToken,
62
65
  refreshToken,
@@ -68,14 +71,14 @@ export async function executeOAuthFlow() {
68
71
  */
69
72
  function buildAuthorizationUrl(params) {
70
73
  const url = new URL(WORKOS_AUTHORIZE_URL);
71
- url.searchParams.set('client_id', params.clientId);
72
- url.searchParams.set('redirect_uri', params.redirectUri);
73
- url.searchParams.set('response_type', 'code');
74
- url.searchParams.set('state', params.state);
75
- url.searchParams.set('code_challenge', params.codeChallenge);
76
- url.searchParams.set('code_challenge_method', 'S256');
77
- url.searchParams.set('scope', 'openid profile email offline_access');
78
- url.searchParams.set('provider', 'authkit');
74
+ url.searchParams.set("client_id", params.clientId);
75
+ url.searchParams.set("redirect_uri", params.redirectUri);
76
+ url.searchParams.set("response_type", "code");
77
+ url.searchParams.set("state", params.state);
78
+ url.searchParams.set("code_challenge", params.codeChallenge);
79
+ url.searchParams.set("code_challenge_method", "S256");
80
+ url.searchParams.set("scope", "openid profile email offline_access");
81
+ url.searchParams.set("provider", "authkit");
79
82
  return url.toString();
80
83
  }
81
84
  /**
@@ -88,8 +91,8 @@ function generateCodeVerifier() {
88
91
  * Generate code challenge from verifier using SHA-256
89
92
  */
90
93
  async function generateCodeChallenge(verifier) {
91
- const { createHash } = await import('crypto');
92
- const hash = createHash('sha256').update(verifier).digest();
94
+ const { createHash } = await import("node:crypto");
95
+ const hash = createHash("sha256").update(verifier).digest();
93
96
  return base64UrlEncode(hash);
94
97
  }
95
98
  /**
@@ -103,9 +106,9 @@ function generateState() {
103
106
  */
104
107
  function base64UrlEncode(buffer) {
105
108
  return buffer
106
- .toString('base64')
107
- .replace(/\+/g, '-')
108
- .replace(/\//g, '_')
109
- .replace(/=/g, '');
109
+ .toString("base64")
110
+ .replace(/\+/g, "-")
111
+ .replace(/\//g, "_")
112
+ .replace(/=/g, "");
110
113
  }
111
114
  //# sourceMappingURL=oauth-flow.js.map
@@ -13,13 +13,13 @@ export interface DotreqProject {
13
13
  * Result of project discovery - either a single project or multiple projects
14
14
  */
15
15
  export type ProjectDiscoveryResult = {
16
- type: 'none';
16
+ type: "none";
17
17
  message: string;
18
18
  } | {
19
- type: 'single';
19
+ type: "single";
20
20
  project: DotreqProject;
21
21
  } | {
22
- type: 'multiple';
22
+ type: "multiple";
23
23
  projects: DotreqProject[];
24
24
  };
25
25
  /**
@@ -1,15 +1,16 @@
1
- import * as fs from 'fs';
2
- import * as path from 'path';
3
- import { glob } from 'glob';
4
- import { readProjectSettings } from './project-settings.js';
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { glob } from "glob";
4
+ import { readProjectSettings, } from "./project-settings.js";
5
5
  /**
6
6
  * Check if a directory is a configured dotrequirements project
7
7
  * A project is configured if it has .requirements/project-settings.json with credentials
8
8
  */
9
9
  export function isConfiguredProject(dir) {
10
- const requirementsDir = path.join(dir, '.requirements');
10
+ const requirementsDir = path.join(dir, ".requirements");
11
11
  // Check for .requirements/ directory
12
- if (!fs.existsSync(requirementsDir) || !fs.statSync(requirementsDir).isDirectory()) {
12
+ if (!fs.existsSync(requirementsDir) ||
13
+ !fs.statSync(requirementsDir).isDirectory()) {
13
14
  return null;
14
15
  }
15
16
  // Try to read credentials from project-settings.json
@@ -38,10 +39,10 @@ export function isConfiguredProject(dir) {
38
39
  export async function findConfiguredProjects(startDir) {
39
40
  const projects = [];
40
41
  // Find all .requirements directories
41
- const requirementsDirs = await glob('**/.requirements', {
42
+ const requirementsDirs = await glob("**/.requirements", {
42
43
  cwd: startDir,
43
44
  absolute: true,
44
- ignore: ['**/node_modules/**', '**/dist/**', '**/.git/**', '**/build/**'],
45
+ ignore: ["**/node_modules/**", "**/dist/**", "**/.git/**", "**/build/**"],
45
46
  });
46
47
  for (const reqDir of requirementsDirs) {
47
48
  const dir = path.dirname(reqDir);
@@ -64,23 +65,23 @@ export async function discoverProjects(workingDir = process.cwd()) {
64
65
  // Case 2: Check if working directory itself is configured
65
66
  const selfProject = isConfiguredProject(workingDir);
66
67
  if (selfProject) {
67
- return { type: 'single', project: selfProject };
68
+ return { type: "single", project: selfProject };
68
69
  }
69
70
  // Find all configured projects in descendants
70
71
  const childProjects = await findConfiguredProjects(workingDir);
71
72
  // Case 1: No projects found
72
73
  if (childProjects.length === 0) {
73
74
  return {
74
- type: 'none',
75
+ type: "none",
75
76
  message: `No dotrequirements projects found in ${workingDir} or its children.\n\nA project must have:\n1. A .requirements/ directory\n2. Cloud credentials in .requirements/project-settings.json\n\nRun "dotrequirements init" to create a project or "dotrequirements link" to connect to cloud.`,
76
77
  };
77
78
  }
78
79
  // Case 3: Exactly one child project
79
80
  if (childProjects.length === 1) {
80
- return { type: 'single', project: childProjects[0] };
81
+ return { type: "single", project: childProjects[0] };
81
82
  }
82
83
  // Case 4: Multiple child projects
83
- return { type: 'multiple', projects: childProjects };
84
+ return { type: "multiple", projects: childProjects };
84
85
  }
85
86
  /**
86
87
  * Resolve a single project from discovery result
@@ -88,10 +89,10 @@ export async function discoverProjects(workingDir = process.cwd()) {
88
89
  * Otherwise returns the single project or throws an error
89
90
  */
90
91
  export function resolveProject(result, projectId) {
91
- if (result.type === 'none') {
92
+ if (result.type === "none") {
92
93
  throw new Error(result.message);
93
94
  }
94
- if (result.type === 'single') {
95
+ if (result.type === "single") {
95
96
  return result.project;
96
97
  }
97
98
  // Multiple projects case
@@ -99,14 +100,14 @@ export function resolveProject(result, projectId) {
99
100
  const projectList = result.projects
100
101
  .map((p) => {
101
102
  const relativePath = path.relative(process.cwd(), p.path);
102
- return ` - ${relativePath || '.'}: ${p.projectId}`;
103
+ return ` - ${relativePath || "."}: ${p.projectId}`;
103
104
  })
104
- .join('\n');
105
+ .join("\n");
105
106
  throw new Error(`Multiple dotrequirements projects found. Please specify projectId parameter:\n\n${projectList}`);
106
107
  }
107
108
  const project = result.projects.find((p) => p.projectId === projectId);
108
109
  if (!project) {
109
- const availableIds = result.projects.map((p) => p.projectId).join(', ');
110
+ const availableIds = result.projects.map((p) => p.projectId).join(", ");
110
111
  throw new Error(`Project "${projectId}" not found. Available projects: ${availableIds}`);
111
112
  }
112
113
  return project;
@@ -1,6 +1,6 @@
1
- import * as fs from 'fs';
2
- import * as path from 'path';
3
- import * as readline from 'readline';
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import * as readline from "node:readline";
4
4
  /**
5
5
  * Detect project name from various sources
6
6
  * Priority: --name flag > package.json > directory name > user prompt
@@ -12,8 +12,8 @@ export async function detectProjectName(options) {
12
12
  }
13
13
  // 2. Try package.json
14
14
  try {
15
- const pkgPath = path.join(process.cwd(), 'package.json');
16
- const pkgContent = fs.readFileSync(pkgPath, 'utf-8');
15
+ const pkgPath = path.join(process.cwd(), "package.json");
16
+ const pkgContent = fs.readFileSync(pkgPath, "utf-8");
17
17
  const pkg = JSON.parse(pkgContent);
18
18
  if (pkg.name) {
19
19
  return pkg.name;
@@ -24,7 +24,7 @@ export async function detectProjectName(options) {
24
24
  }
25
25
  // 3. Use directory name
26
26
  const dirName = path.basename(process.cwd());
27
- if (dirName && dirName !== '/' && dirName !== '.') {
27
+ if (dirName && dirName !== "/" && dirName !== ".") {
28
28
  return dirName;
29
29
  }
30
30
  // 4. Fallback to user prompt
@@ -39,9 +39,9 @@ async function promptForProjectName() {
39
39
  output: process.stdout,
40
40
  });
41
41
  return new Promise((resolve) => {
42
- rl.question('Project name: ', (answer) => {
42
+ rl.question("Project name: ", (answer) => {
43
43
  rl.close();
44
- resolve(answer.trim() || 'my-project');
44
+ resolve(answer.trim() || "my-project");
45
45
  });
46
46
  });
47
47
  }
@@ -1,4 +1,4 @@
1
- import { ConvexHttpClient } from 'convex/browser';
1
+ import type { ConvexHttpClient } from "convex/browser";
2
2
  export interface SelectedProject {
3
3
  projectId: string;
4
4
  projectName: string;
@@ -1,36 +1,36 @@
1
- import { promptChoice } from './prompts.js';
2
- import { api } from '../convex.js';
1
+ import { api } from "../convex.js";
2
+ import { promptChoice } from "./prompts.js";
3
3
  /**
4
4
  * Prompt user to select a team from their available teams.
5
5
  * Returns the team ID and tier, or null if user cancels.
6
6
  */
7
7
  export async function selectTeam(client) {
8
- console.log('Fetching your teams...\n');
8
+ console.log("Fetching your teams...\n");
9
9
  // AUTH-24.0: Ensure user has a default team if they don't have any
10
10
  await client.mutation(api.teams.mutations.ensureDefaultTeam, {});
11
- const teams = await client.query(api.teams.queries.listForUserWithUsage);
11
+ const teams = (await client.query(api.teams.queries.listForUserWithUsage));
12
12
  if (teams.length === 0) {
13
- console.log('You are not a member of any teams.');
14
- console.log('To create a team, visit: https://app.dotrequirements.io/teams/new\n');
13
+ console.log("You are not a member of any teams.");
14
+ console.log("To create a team, visit: https://app.dotrequirements.io/teams/new\n");
15
15
  return null;
16
16
  }
17
17
  const teamChoices = teams.map((team) => {
18
18
  const limitDisplay = team.projectLimit === -1
19
- ? 'unlimited'
19
+ ? "unlimited"
20
20
  : `${team.projectCount}/${team.projectLimit}`;
21
21
  return {
22
22
  title: `${team.name} (${team.tierName} - ${limitDisplay} projects)`,
23
23
  value: team.teamId,
24
24
  };
25
25
  });
26
- const selectedTeamId = await promptChoice('Select a team:', teamChoices);
26
+ const selectedTeamId = await promptChoice("Select a team:", teamChoices);
27
27
  if (!selectedTeamId) {
28
28
  return null;
29
29
  }
30
30
  const selectedTeam = teams.find((t) => t.teamId === selectedTeamId);
31
31
  return {
32
32
  teamId: selectedTeamId,
33
- tier: selectedTeam?.tier ?? 'free',
33
+ tier: selectedTeam?.tier ?? "free",
34
34
  };
35
35
  }
36
36
  /**
@@ -38,10 +38,12 @@ export async function selectTeam(client) {
38
38
  * Returns the selected project info, or null if user cancels or no projects exist.
39
39
  */
40
40
  export async function selectProject(client, teamId) {
41
- console.log('\nFetching projects...\n');
42
- const projectsWithSecrets = await client.query(api.projectSecrets.queries.listOwnSecretsForTeam, { target: { type: 'team', id: teamId } });
41
+ console.log("\nFetching projects...\n");
42
+ const projectsWithSecrets = (await client.query(api.projectSecrets.queries.listOwnSecretsForTeam,
43
+ // biome-ignore lint/suspicious/noExplicitAny: CLI workspace doesn't import convex's branded Id<"teams"> type; the runtime value is a plain string the convex server validates.
44
+ { target: { type: "team", id: teamId } }));
43
45
  if (projectsWithSecrets.length === 0) {
44
- console.log('No projects found in this team.');
46
+ console.log("No projects found in this team.");
45
47
  console.log('Use "dotrequirements init" to create a new project.\n');
46
48
  return null;
47
49
  }
@@ -51,7 +53,7 @@ export async function selectProject(client, teamId) {
51
53
  : project.projectName,
52
54
  value: project.projectId,
53
55
  }));
54
- const selectedProjectId = await promptChoice('Select a project:', projectChoices);
56
+ const selectedProjectId = await promptChoice("Select a project:", projectChoices);
55
57
  if (!selectedProjectId) {
56
58
  return null;
57
59
  }
@@ -73,10 +75,11 @@ export async function selectProject(client, teamId) {
73
75
  * Free tier always gets 30 days regardless of this parameter.
74
76
  */
75
77
  export async function getOrCreateProjectSecret(client, projectId, expiryDays) {
76
- const result = await client.mutation(api.projectSecrets.mutations.ensureOwnSecret, {
77
- target: { type: 'project', id: projectId },
78
+ const result = (await client.mutation(api.projectSecrets.mutations.ensureOwnSecret, {
79
+ // biome-ignore lint/suspicious/noExplicitAny: CLI workspace doesn't import convex's branded Id<"projects"> type; the runtime value is a plain string the convex server validates.
80
+ target: { type: "project", id: projectId },
78
81
  expiryDays,
79
- });
82
+ }));
80
83
  return result;
81
84
  }
82
85
  /**
@@ -85,14 +88,14 @@ export async function getOrCreateProjectSecret(client, projectId, expiryDays) {
85
88
  */
86
89
  export async function promptExpiryDays(tierSlug) {
87
90
  // Free tier users don't get a choice - always 30 days
88
- if (tierSlug === 'free') {
91
+ if (tierSlug === "free") {
89
92
  return undefined;
90
93
  }
91
94
  // AUTHZ-3.1, AUTHZ-3.2: Paid tier users can choose expiry
92
- const choice = await promptChoice('How long should this secret be valid?', [
93
- { title: '365 days (Recommended)', value: '365' },
94
- { title: '90 days', value: '90' },
95
- { title: '30 days', value: '30' },
95
+ const choice = await promptChoice("How long should this secret be valid?", [
96
+ { title: "365 days (Recommended)", value: "365" },
97
+ { title: "90 days", value: "90" },
98
+ { title: "30 days", value: "30" },
96
99
  ]);
97
100
  // If user cancels, use default (365 for paid tier)
98
101
  if (!choice) {
@@ -1,7 +1,8 @@
1
- /**
2
- * Browser test configuration
3
- */
1
+ /** Browser test configuration. */
4
2
  export interface BrowserTestSettings {
3
+ modelName?: string;
4
+ modelApiKey?: string;
5
+ /** @deprecated Backward-compat fallback; use modelName + modelApiKey instead. */
5
6
  geminiApiKey?: string;
6
7
  vercelBypassSecret?: string;
7
8
  browserbaseApiKey?: string;
@@ -19,7 +20,7 @@ export interface ProjectSettings {
19
20
  /**
20
21
  * Project status indicating whether it's connected to cloud
21
22
  */
22
- export type ProjectStatus = 'cloud-connected' | 'local-only';
23
+ export type ProjectStatus = "cloud-connected" | "local-only";
23
24
  /**
24
25
  * Result of finding a project
25
26
  */