@popoverai/dotrequirements 0.23.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.
- package/README.md +167 -20
- package/dist/cli.js +121 -60
- package/dist/codebase-to-spec/budget.d.ts +53 -0
- package/dist/codebase-to-spec/budget.js +80 -0
- package/dist/codebase-to-spec/cache.d.ts +49 -0
- package/dist/codebase-to-spec/cache.js +54 -0
- package/dist/codebase-to-spec/claude.d.ts +69 -0
- package/dist/codebase-to-spec/claude.js +126 -0
- package/dist/codebase-to-spec/compose.d.ts +49 -0
- package/dist/codebase-to-spec/compose.js +124 -0
- package/dist/codebase-to-spec/edit-loop.d.ts +54 -0
- package/dist/codebase-to-spec/edit-loop.js +195 -0
- package/dist/codebase-to-spec/editor.d.ts +54 -0
- package/dist/codebase-to-spec/editor.js +74 -0
- package/dist/codebase-to-spec/exit-codes.d.ts +40 -0
- package/dist/codebase-to-spec/exit-codes.js +58 -0
- package/dist/codebase-to-spec/fan-out.d.ts +63 -0
- package/dist/codebase-to-spec/fan-out.js +215 -0
- package/dist/codebase-to-spec/interactive.d.ts +30 -0
- package/dist/codebase-to-spec/interactive.js +48 -0
- package/dist/codebase-to-spec/outline-review-loop.d.ts +51 -0
- package/dist/codebase-to-spec/outline-review-loop.js +187 -0
- package/dist/codebase-to-spec/pack.d.ts +51 -0
- package/dist/codebase-to-spec/pack.js +127 -0
- package/dist/codebase-to-spec/planner.d.ts +41 -0
- package/dist/codebase-to-spec/planner.js +76 -0
- package/dist/codebase-to-spec/present.d.ts +94 -0
- package/dist/codebase-to-spec/present.js +288 -0
- package/dist/codebase-to-spec/progress.d.ts +33 -0
- package/dist/codebase-to-spec/progress.js +28 -0
- package/dist/codebase-to-spec/prompts/editor.d.ts +13 -0
- package/dist/codebase-to-spec/prompts/editor.js +57 -0
- package/dist/codebase-to-spec/prompts/outline-reviewer.d.ts +12 -0
- package/dist/codebase-to-spec/prompts/outline-reviewer.js +87 -0
- package/dist/codebase-to-spec/prompts/planner-apply.d.ts +12 -0
- package/dist/codebase-to-spec/prompts/planner-apply.js +32 -0
- package/dist/codebase-to-spec/prompts/planner-initial.d.ts +11 -0
- package/dist/codebase-to-spec/prompts/planner-initial.js +125 -0
- package/dist/codebase-to-spec/prompts/planner-revise.d.ts +14 -0
- package/dist/codebase-to-spec/prompts/planner-revise.js +60 -0
- package/dist/codebase-to-spec/prompts/spec-reviewer.d.ts +16 -0
- package/dist/codebase-to-spec/prompts/spec-reviewer.js +96 -0
- package/dist/codebase-to-spec/prompts/specifier.d.ts +12 -0
- package/dist/codebase-to-spec/prompts/specifier.js +100 -0
- package/dist/codebase-to-spec/prompts/style-check.d.ts +12 -0
- package/dist/codebase-to-spec/prompts/style-check.js +78 -0
- package/dist/codebase-to-spec/schemas.d.ts +257 -0
- package/dist/codebase-to-spec/schemas.js +183 -0
- package/dist/codebase-to-spec/skill-install.d.ts +57 -0
- package/dist/codebase-to-spec/skill-install.js +79 -0
- package/dist/codebase-to-spec/slice.d.ts +49 -0
- package/dist/codebase-to-spec/slice.js +111 -0
- package/dist/codebase-to-spec/specifier.d.ts +60 -0
- package/dist/codebase-to-spec/specifier.js +79 -0
- package/dist/codebase-to-spec/style-check.d.ts +29 -0
- package/dist/codebase-to-spec/style-check.js +33 -0
- package/dist/codebase-to-spec/summary.d.ts +51 -0
- package/dist/codebase-to-spec/summary.js +183 -0
- package/dist/codebase-to-spec/validate.d.ts +46 -0
- package/dist/codebase-to-spec/validate.js +130 -0
- package/dist/commands/acceptance-test.d.ts +6 -0
- package/dist/commands/acceptance-test.js +212 -0
- package/dist/commands/ai-setup.d.ts +5 -0
- package/dist/commands/ai-setup.js +441 -0
- package/dist/commands/browsertest.js +34 -27
- package/dist/commands/codebase-to-spec/compose.d.ts +14 -0
- package/dist/commands/codebase-to-spec/compose.js +57 -0
- package/dist/commands/codebase-to-spec/edit-loop.d.ts +16 -0
- package/dist/commands/codebase-to-spec/edit-loop.js +83 -0
- package/dist/commands/codebase-to-spec/fan-out.d.ts +19 -0
- package/dist/commands/codebase-to-spec/fan-out.js +77 -0
- package/dist/commands/codebase-to-spec/index.d.ts +9 -0
- package/dist/commands/codebase-to-spec/index.js +135 -0
- package/dist/commands/codebase-to-spec/pack.d.ts +22 -0
- package/dist/commands/codebase-to-spec/pack.js +76 -0
- package/dist/commands/codebase-to-spec/plan-loop.d.ts +26 -0
- package/dist/commands/codebase-to-spec/plan-loop.js +105 -0
- package/dist/commands/codebase-to-spec/present.d.ts +21 -0
- package/dist/commands/codebase-to-spec/present.js +92 -0
- package/dist/commands/codebase-to-spec/run.d.ts +20 -0
- package/dist/commands/codebase-to-spec/run.js +85 -0
- package/dist/commands/codebase-to-spec/skill-install.d.ts +20 -0
- package/dist/commands/codebase-to-spec/skill-install.js +51 -0
- package/dist/commands/codebase-to-spec/specify-area.d.ts +18 -0
- package/dist/commands/codebase-to-spec/specify-area.js +82 -0
- package/dist/commands/codebase-to-spec/style-check.d.ts +15 -0
- package/dist/commands/codebase-to-spec/style-check.js +42 -0
- package/dist/commands/codebase-to-spec/validate.d.ts +18 -0
- package/dist/commands/codebase-to-spec/validate.js +38 -0
- package/dist/commands/create-requirement-document.d.ts +2 -0
- package/dist/commands/create-requirement-document.js +41 -0
- package/dist/commands/finalize.js +7 -7
- package/dist/commands/get.d.ts +2 -0
- package/dist/commands/get.js +55 -0
- package/dist/commands/init.js +132 -117
- package/dist/commands/link.js +27 -27
- package/dist/commands/list.d.ts +6 -0
- package/dist/commands/list.js +43 -0
- package/dist/commands/mcp-setup.js +159 -149
- package/dist/commands/mcp.js +1 -1
- package/dist/commands/prepare.js +4 -4
- package/dist/commands/pull.js +116 -121
- package/dist/commands/push.js +106 -112
- package/dist/commands/report.d.ts +6 -2
- package/dist/commands/report.js +177 -122
- package/dist/commands/requirements-for.d.ts +2 -0
- package/dist/commands/requirements-for.js +29 -0
- package/dist/commands/review-test.d.ts +2 -0
- package/dist/commands/review-test.js +75 -0
- package/dist/commands/search.d.ts +6 -0
- package/dist/commands/search.js +39 -0
- package/dist/commands/style-check.d.ts +7 -0
- package/dist/commands/style-check.js +75 -0
- package/dist/commands/test.js +53 -59
- package/dist/commands/tests-for.d.ts +2 -0
- package/dist/commands/tests-for.js +80 -0
- package/dist/commands/validate.d.ts +6 -0
- package/dist/commands/validate.js +72 -0
- package/dist/config.js +1 -1
- package/dist/convex.d.ts +34 -22
- package/dist/convex.js +38 -22
- package/dist/harness/cache.d.ts +1 -5
- package/dist/harness/cache.js +49 -59
- package/dist/harness/convexReporting.d.ts +1 -1
- package/dist/harness/convexReporting.js +9 -7
- package/dist/harness/coverageCache.js +3 -3
- package/dist/harness/finalize.js +59 -46
- package/dist/harness/index.d.ts +6 -7
- package/dist/harness/index.js +9 -10
- package/dist/harness/prepare.js +6 -5
- package/dist/harness/requirementsLoader.d.ts +2 -2
- package/dist/harness/requirementsLoader.js +13 -35
- package/dist/harness/tracking.js +18 -18
- package/dist/harness/types.d.ts +1 -1
- package/dist/mcp/convexClient.d.ts +0 -39
- package/dist/mcp/convexClient.js +2 -107
- package/dist/mcp/grep.d.ts +1 -1
- package/dist/mcp/grep.js +87 -42
- package/dist/mcp/handlers/authoring.d.ts +1 -1
- package/dist/mcp/handlers/authoring.js +30 -234
- package/dist/mcp/handlers/coverage.d.ts +1 -1
- package/dist/mcp/handlers/coverage.js +13 -15
- package/dist/mcp/handlers/debug.d.ts +2 -3
- package/dist/mcp/handlers/debug.js +10 -10
- package/dist/mcp/handlers/get.d.ts +1 -1
- package/dist/mcp/handlers/get.js +11 -10
- package/dist/mcp/handlers/index.d.ts +20 -20
- package/dist/mcp/handlers/index.js +10 -10
- package/dist/mcp/handlers/list.d.ts +4 -33
- package/dist/mcp/handlers/list.js +16 -38
- package/dist/mcp/handlers/push.d.ts +1 -1
- package/dist/mcp/handlers/push.js +28 -18
- package/dist/mcp/handlers/report.d.ts +16 -0
- package/dist/mcp/handlers/report.js +134 -0
- package/dist/mcp/handlers/review.d.ts +1 -1
- package/dist/mcp/handlers/review.js +40 -59
- package/dist/mcp/handlers/search.d.ts +1 -1
- package/dist/mcp/handlers/search.js +7 -9
- package/dist/mcp/handlers/test-mapping.d.ts +1 -1
- package/dist/mcp/handlers/test-mapping.js +14 -14
- package/dist/mcp/handlers/types.d.ts +3 -3
- package/dist/mcp/handlers/types.js +2 -2
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.js +147 -167
- package/dist/mcp/requirements.d.ts +2 -2
- package/dist/mcp/requirements.js +30 -30
- package/dist/mcp/testCodeExtractor.js +24 -26
- package/dist/mcp/types.d.ts +1 -1
- package/dist/push/core.d.ts +2 -2
- package/dist/push/core.js +20 -20
- package/dist/push/index.d.ts +1 -1
- package/dist/push/index.js +2 -2
- package/dist/requirements/cloud-ai.d.ts +57 -0
- package/dist/requirements/cloud-ai.js +104 -0
- package/dist/requirements/cloud-coverage.d.ts +41 -0
- package/dist/requirements/cloud-coverage.js +60 -0
- package/dist/requirements/coverage.d.ts +45 -0
- package/dist/requirements/coverage.js +114 -0
- package/dist/requirements/grep.d.ts +33 -0
- package/dist/requirements/grep.js +306 -0
- package/dist/requirements/index.d.ts +73 -0
- package/dist/requirements/index.js +174 -0
- package/dist/requirements/style-guide.d.ts +67 -0
- package/dist/requirements/style-guide.js +299 -0
- package/dist/requirements/testCodeExtractor.d.ts +22 -0
- package/dist/requirements/testCodeExtractor.js +150 -0
- package/dist/schema/browser.d.ts +8 -8
- package/dist/schema/browser.js +13 -15
- package/dist/schema/builder.d.ts +1 -1
- package/dist/schema/builder.js +13 -44
- package/dist/schema/conversions.d.ts +2 -2
- package/dist/schema/conversions.js +11 -11
- package/dist/schema/index.d.ts +9 -9
- package/dist/schema/index.js +15 -15
- package/dist/schema/parser-core.d.ts +1 -1
- package/dist/schema/parser-core.js +23 -22
- package/dist/schema/parser.d.ts +3 -3
- package/dist/schema/parser.js +27 -31
- package/dist/schema/resolver.d.ts +1 -1
- package/dist/schema/resolver.js +9 -9
- package/dist/schema/scenario.d.ts +1 -1
- package/dist/schema/scenario.js +1 -1
- package/dist/schema/schemas.d.ts +3 -3
- package/dist/schema/schemas.js +41 -28
- package/dist/schema/test-schema.js +27 -27
- package/dist/templates/context-file-section.md +3 -2
- package/dist/templates/example-requirements.js +1 -1
- package/dist/templates/example-requirements.ts +3 -1
- package/dist/templates/requirements-readme.js +1 -1
- package/dist/templates/requirements-readme.ts +1 -1
- package/dist/templates/skills/codebase-to-spec/SKILL.md +118 -0
- package/dist/utils/brand.js +3 -3
- package/dist/utils/browser-launch.js +4 -4
- package/dist/utils/context-file.d.ts +1 -1
- package/dist/utils/context-file.js +26 -26
- package/dist/utils/env.js +7 -7
- package/dist/utils/gitignore.js +7 -7
- package/dist/utils/oauth-callback-server.d.ts +1 -1
- package/dist/utils/oauth-callback-server.js +27 -25
- package/dist/utils/oauth-flow.js +32 -29
- package/dist/utils/project-discovery.d.ts +3 -3
- package/dist/utils/project-discovery.js +18 -17
- package/dist/utils/project-name.js +8 -8
- package/dist/utils/project-selector.d.ts +1 -1
- package/dist/utils/project-selector.js +24 -21
- package/dist/utils/project-settings.d.ts +1 -1
- package/dist/utils/project-settings.js +24 -22
- package/dist/utils/templates.js +6 -6
- package/package.json +2 -1
package/dist/utils/env.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import * as fs from
|
|
2
|
-
import * as path from
|
|
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,
|
|
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,
|
|
16
|
-
const lines = content.split(
|
|
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
|
|
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
|
}
|
package/dist/utils/gitignore.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import * as fs from
|
|
2
|
-
import * as path from
|
|
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,
|
|
10
|
-
const entryToAdd =
|
|
11
|
-
const comment =
|
|
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,
|
|
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(
|
|
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
|
|
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
|
|
8
|
-
import { URL } from
|
|
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 !==
|
|
20
|
+
if (req.method !== "GET" || !req.url) {
|
|
21
21
|
res.writeHead(404);
|
|
22
|
-
res.end(
|
|
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 !==
|
|
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(
|
|
31
|
-
const state = callbackUrl.searchParams.get(
|
|
32
|
-
const error = callbackUrl.searchParams.get(
|
|
33
|
-
const errorDescription = callbackUrl.searchParams.get(
|
|
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, {
|
|
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, {
|
|
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(
|
|
84
|
+
reject(new Error("Invalid callback: missing code or state"));
|
|
85
85
|
return;
|
|
86
86
|
}
|
|
87
87
|
// Success response
|
|
88
|
-
res.writeHead(200, {
|
|
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, {
|
|
113
|
-
res.end(
|
|
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(
|
|
122
|
+
reject(new Error("OAuth callback timeout: no response received within 2 minutes"));
|
|
123
123
|
}, timeoutMs);
|
|
124
124
|
// Handle server errors
|
|
125
|
-
server.on(
|
|
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,
|
|
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 ===
|
|
141
|
-
throw new Error(
|
|
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,
|
|
151
|
-
.replace(/</g,
|
|
152
|
-
.replace(/>/g,
|
|
153
|
-
.replace(/"/g,
|
|
154
|
-
.replace(/'/g,
|
|
152
|
+
.replace(/&/g, "&")
|
|
153
|
+
.replace(/</g, "<")
|
|
154
|
+
.replace(/>/g, ">")
|
|
155
|
+
.replace(/"/g, """)
|
|
156
|
+
.replace(/'/g, "'");
|
|
155
157
|
}
|
|
156
158
|
//# sourceMappingURL=oauth-callback-server.js.map
|
package/dist/utils/oauth-flow.js
CHANGED
|
@@ -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
|
|
8
|
-
import { ConvexHttpClient } from
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
const WORKOS_CLIENT_ID =
|
|
14
|
-
const WORKOS_AUTHORIZE_URL =
|
|
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(
|
|
28
|
+
console.log("Starting authentication flow...");
|
|
29
29
|
// Start local callback server on port 3010 (WorkOS requires pre-configured redirect URI)
|
|
30
|
-
const redirectUri =
|
|
31
|
-
const callbackPromise = startCallbackServer({
|
|
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(
|
|
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(
|
|
49
|
+
throw new Error("Invalid state parameter: possible CSRF attack");
|
|
47
50
|
}
|
|
48
|
-
console.log(
|
|
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(
|
|
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(
|
|
72
|
-
url.searchParams.set(
|
|
73
|
-
url.searchParams.set(
|
|
74
|
-
url.searchParams.set(
|
|
75
|
-
url.searchParams.set(
|
|
76
|
-
url.searchParams.set(
|
|
77
|
-
url.searchParams.set(
|
|
78
|
-
url.searchParams.set(
|
|
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(
|
|
92
|
-
const hash = createHash(
|
|
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(
|
|
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:
|
|
16
|
+
type: "none";
|
|
17
17
|
message: string;
|
|
18
18
|
} | {
|
|
19
|
-
type:
|
|
19
|
+
type: "single";
|
|
20
20
|
project: DotreqProject;
|
|
21
21
|
} | {
|
|
22
|
-
type:
|
|
22
|
+
type: "multiple";
|
|
23
23
|
projects: DotreqProject[];
|
|
24
24
|
};
|
|
25
25
|
/**
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import * as fs from
|
|
2
|
-
import * as path from
|
|
3
|
-
import { glob } from
|
|
4
|
-
import { readProjectSettings } from
|
|
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,
|
|
10
|
+
const requirementsDir = path.join(dir, ".requirements");
|
|
11
11
|
// Check for .requirements/ directory
|
|
12
|
-
if (!fs.existsSync(requirementsDir) ||
|
|
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(
|
|
42
|
+
const requirementsDirs = await glob("**/.requirements", {
|
|
42
43
|
cwd: startDir,
|
|
43
44
|
absolute: true,
|
|
44
|
-
ignore: [
|
|
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:
|
|
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:
|
|
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:
|
|
81
|
+
return { type: "single", project: childProjects[0] };
|
|
81
82
|
}
|
|
82
83
|
// Case 4: Multiple child projects
|
|
83
|
-
return { type:
|
|
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 ===
|
|
92
|
+
if (result.type === "none") {
|
|
92
93
|
throw new Error(result.message);
|
|
93
94
|
}
|
|
94
|
-
if (result.type ===
|
|
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 ||
|
|
103
|
+
return ` - ${relativePath || "."}: ${p.projectId}`;
|
|
103
104
|
})
|
|
104
|
-
.join(
|
|
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
|
|
2
|
-
import * as path from
|
|
3
|
-
import * as readline from
|
|
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(),
|
|
16
|
-
const pkgContent = fs.readFileSync(pkgPath,
|
|
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 !==
|
|
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(
|
|
42
|
+
rl.question("Project name: ", (answer) => {
|
|
43
43
|
rl.close();
|
|
44
|
-
resolve(answer.trim() ||
|
|
44
|
+
resolve(answer.trim() || "my-project");
|
|
45
45
|
});
|
|
46
46
|
});
|
|
47
47
|
}
|
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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(
|
|
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(
|
|
14
|
-
console.log(
|
|
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
|
-
?
|
|
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(
|
|
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 ??
|
|
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(
|
|
42
|
-
const projectsWithSecrets = await client.query(api.projectSecrets.queries.listOwnSecretsForTeam,
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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 ===
|
|
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(
|
|
93
|
-
{ title:
|
|
94
|
-
{ title:
|
|
95
|
-
{ title:
|
|
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) {
|
|
@@ -20,7 +20,7 @@ export interface ProjectSettings {
|
|
|
20
20
|
/**
|
|
21
21
|
* Project status indicating whether it's connected to cloud
|
|
22
22
|
*/
|
|
23
|
-
export type ProjectStatus =
|
|
23
|
+
export type ProjectStatus = "cloud-connected" | "local-only";
|
|
24
24
|
/**
|
|
25
25
|
* Result of finding a project
|
|
26
26
|
*/
|