gsd-pi 2.38.0-dev.eeb3520 → 2.39.0-dev.64cd3ed
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 +15 -11
- package/dist/app-paths.js +1 -1
- package/dist/cli.js +9 -0
- package/dist/extension-discovery.d.ts +5 -3
- package/dist/extension-discovery.js +14 -9
- package/dist/extension-registry.js +2 -2
- package/dist/remote-questions-config.js +2 -2
- package/dist/resource-loader.js +34 -1
- package/dist/resources/extensions/async-jobs/index.js +10 -0
- package/dist/resources/extensions/browser-tools/index.js +3 -1
- package/dist/resources/extensions/browser-tools/package.json +3 -1
- package/dist/resources/extensions/browser-tools/tools/verify.js +97 -0
- package/dist/resources/extensions/cmux/index.js +55 -1
- package/dist/resources/extensions/context7/package.json +1 -1
- package/dist/resources/extensions/env-utils.js +29 -0
- package/dist/resources/extensions/get-secrets-from-user.js +5 -24
- package/dist/resources/extensions/github-sync/cli.js +284 -0
- package/dist/resources/extensions/github-sync/index.js +73 -0
- package/dist/resources/extensions/github-sync/mapping.js +67 -0
- package/dist/resources/extensions/github-sync/sync.js +424 -0
- package/dist/resources/extensions/github-sync/templates.js +118 -0
- package/dist/resources/extensions/github-sync/types.js +7 -0
- package/dist/resources/extensions/google-search/package.json +3 -1
- package/dist/resources/extensions/gsd/auto/session.js +6 -23
- package/dist/resources/extensions/gsd/auto-dispatch.js +8 -9
- package/dist/resources/extensions/gsd/auto-loop.js +650 -588
- package/dist/resources/extensions/gsd/auto-post-unit.js +99 -70
- package/dist/resources/extensions/gsd/auto-prompts.js +202 -48
- package/dist/resources/extensions/gsd/auto-start.js +13 -2
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +13 -5
- package/dist/resources/extensions/gsd/auto-worktree.js +3 -3
- package/dist/resources/extensions/gsd/auto.js +143 -96
- package/dist/resources/extensions/gsd/captures.js +9 -1
- package/dist/resources/extensions/gsd/commands-extensions.js +3 -2
- package/dist/resources/extensions/gsd/commands-handlers.js +16 -3
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
- package/dist/resources/extensions/gsd/commands.js +24 -3
- package/dist/resources/extensions/gsd/context-budget.js +2 -10
- package/dist/resources/extensions/gsd/detection.js +1 -2
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +0 -2
- package/dist/resources/extensions/gsd/doctor-checks.js +82 -0
- package/dist/resources/extensions/gsd/doctor-environment.js +78 -0
- package/dist/resources/extensions/gsd/doctor-format.js +15 -0
- package/dist/resources/extensions/gsd/doctor-providers.js +30 -11
- package/dist/resources/extensions/gsd/doctor.js +204 -12
- package/dist/resources/extensions/gsd/exit-command.js +2 -1
- package/dist/resources/extensions/gsd/export.js +1 -1
- package/dist/resources/extensions/gsd/files.js +48 -9
- package/dist/resources/extensions/gsd/forensics.js +1 -1
- package/dist/resources/extensions/gsd/git-service.js +30 -12
- package/dist/resources/extensions/gsd/gitignore.js +16 -3
- package/dist/resources/extensions/gsd/guided-flow.js +149 -38
- package/dist/resources/extensions/gsd/health-widget-core.js +32 -70
- package/dist/resources/extensions/gsd/health-widget.js +3 -86
- package/dist/resources/extensions/gsd/index.js +24 -20
- package/dist/resources/extensions/gsd/migrate/parsers.js +1 -1
- package/dist/resources/extensions/gsd/migrate-external.js +18 -1
- package/dist/resources/extensions/gsd/native-git-bridge.js +37 -0
- package/dist/resources/extensions/gsd/package.json +1 -1
- package/dist/resources/extensions/gsd/paths.js +3 -0
- package/dist/resources/extensions/gsd/preferences-models.js +0 -12
- package/dist/resources/extensions/gsd/preferences-types.js +1 -1
- package/dist/resources/extensions/gsd/preferences-validation.js +59 -11
- package/dist/resources/extensions/gsd/preferences.js +22 -11
- package/dist/resources/extensions/gsd/prompt-loader.js +6 -2
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/discuss.md +11 -14
- package/dist/resources/extensions/gsd/prompts/execute-task.md +5 -3
- package/dist/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
- package/dist/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/queue.md +4 -8
- package/dist/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/dist/resources/extensions/gsd/prompts/research-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/run-uat.md +28 -11
- package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -2
- package/dist/resources/extensions/gsd/repo-identity.js +21 -4
- package/dist/resources/extensions/gsd/resource-version.js +2 -1
- package/dist/resources/extensions/gsd/roadmap-mutations.js +24 -0
- package/dist/resources/extensions/gsd/state.js +42 -23
- package/dist/resources/extensions/gsd/templates/runtime.md +21 -0
- package/dist/resources/extensions/gsd/templates/task-plan.md +3 -0
- package/dist/resources/extensions/gsd/visualizer-data.js +1 -1
- package/dist/resources/extensions/gsd/worktree.js +35 -16
- package/dist/resources/extensions/mcp-client/index.js +14 -1
- package/dist/resources/extensions/remote-questions/status.js +4 -1
- package/dist/resources/extensions/remote-questions/store.js +4 -1
- package/dist/resources/extensions/search-the-web/provider.js +2 -1
- package/dist/resources/extensions/shared/frontmatter.js +1 -1
- package/dist/resources/extensions/subagent/index.js +12 -3
- package/dist/resources/extensions/subagent/isolation.js +2 -1
- package/dist/resources/extensions/ttsr/rule-loader.js +2 -1
- package/dist/resources/extensions/universal-config/package.json +1 -1
- package/dist/welcome-screen.d.ts +12 -0
- package/dist/welcome-screen.js +53 -0
- package/package.json +1 -1
- package/packages/pi-ai/dist/utils/oauth/anthropic.js +2 -2
- package/packages/pi-ai/dist/utils/oauth/anthropic.js.map +1 -1
- package/packages/pi-ai/src/utils/oauth/anthropic.ts +2 -2
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +205 -7
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.js +8 -4
- package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/skills.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/skills.js +6 -1
- package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +1 -1
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +223 -7
- package/packages/pi-coding-agent/src/core/package-manager.ts +8 -4
- package/packages/pi-coding-agent/src/core/skills.ts +9 -1
- package/packages/pi-coding-agent/src/index.ts +1 -0
- package/pkg/package.json +1 -1
- package/src/resources/extensions/async-jobs/index.ts +11 -0
- package/src/resources/extensions/browser-tools/index.ts +3 -0
- package/src/resources/extensions/browser-tools/tools/verify.ts +117 -0
- package/src/resources/extensions/cmux/index.ts +57 -1
- package/src/resources/extensions/env-utils.ts +31 -0
- package/src/resources/extensions/get-secrets-from-user.ts +5 -24
- package/src/resources/extensions/github-sync/cli.ts +364 -0
- package/src/resources/extensions/github-sync/index.ts +93 -0
- package/src/resources/extensions/github-sync/mapping.ts +81 -0
- package/src/resources/extensions/github-sync/sync.ts +556 -0
- package/src/resources/extensions/github-sync/templates.ts +183 -0
- package/src/resources/extensions/github-sync/tests/cli.test.ts +20 -0
- package/src/resources/extensions/github-sync/tests/commit-linking.test.ts +39 -0
- package/src/resources/extensions/github-sync/tests/mapping.test.ts +104 -0
- package/src/resources/extensions/github-sync/tests/templates.test.ts +110 -0
- package/src/resources/extensions/github-sync/types.ts +47 -0
- package/src/resources/extensions/gsd/auto/session.ts +7 -25
- package/src/resources/extensions/gsd/auto-dispatch.ts +7 -9
- package/src/resources/extensions/gsd/auto-loop.ts +553 -546
- package/src/resources/extensions/gsd/auto-post-unit.ts +80 -44
- package/src/resources/extensions/gsd/auto-prompts.ts +247 -50
- package/src/resources/extensions/gsd/auto-start.ts +18 -2
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +15 -4
- package/src/resources/extensions/gsd/auto-worktree.ts +3 -3
- package/src/resources/extensions/gsd/auto.ts +139 -101
- package/src/resources/extensions/gsd/captures.ts +10 -1
- package/src/resources/extensions/gsd/commands-extensions.ts +4 -2
- package/src/resources/extensions/gsd/commands-handlers.ts +17 -2
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
- package/src/resources/extensions/gsd/commands.ts +26 -4
- package/src/resources/extensions/gsd/context-budget.ts +2 -12
- package/src/resources/extensions/gsd/detection.ts +2 -2
- package/src/resources/extensions/gsd/docs/preferences-reference.md +0 -2
- package/src/resources/extensions/gsd/doctor-checks.ts +75 -0
- package/src/resources/extensions/gsd/doctor-environment.ts +82 -1
- package/src/resources/extensions/gsd/doctor-format.ts +20 -0
- package/src/resources/extensions/gsd/doctor-providers.ts +30 -9
- package/src/resources/extensions/gsd/doctor-types.ts +16 -1
- package/src/resources/extensions/gsd/doctor.ts +199 -14
- package/src/resources/extensions/gsd/exit-command.ts +2 -2
- package/src/resources/extensions/gsd/export.ts +1 -1
- package/src/resources/extensions/gsd/files.ts +51 -11
- package/src/resources/extensions/gsd/forensics.ts +1 -1
- package/src/resources/extensions/gsd/git-service.ts +44 -10
- package/src/resources/extensions/gsd/gitignore.ts +17 -3
- package/src/resources/extensions/gsd/guided-flow.ts +177 -44
- package/src/resources/extensions/gsd/health-widget-core.ts +28 -80
- package/src/resources/extensions/gsd/health-widget.ts +3 -89
- package/src/resources/extensions/gsd/index.ts +24 -17
- package/src/resources/extensions/gsd/migrate/parsers.ts +1 -1
- package/src/resources/extensions/gsd/migrate-external.ts +18 -1
- package/src/resources/extensions/gsd/native-git-bridge.ts +37 -0
- package/src/resources/extensions/gsd/paths.ts +4 -0
- package/src/resources/extensions/gsd/preferences-models.ts +0 -12
- package/src/resources/extensions/gsd/preferences-types.ts +4 -4
- package/src/resources/extensions/gsd/preferences-validation.ts +51 -11
- package/src/resources/extensions/gsd/preferences.ts +25 -11
- package/src/resources/extensions/gsd/prompt-loader.ts +7 -2
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/discuss.md +11 -14
- package/src/resources/extensions/gsd/prompts/execute-task.md +5 -3
- package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
- package/src/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/queue.md +4 -8
- package/src/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/src/resources/extensions/gsd/prompts/research-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/run-uat.md +28 -11
- package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -2
- package/src/resources/extensions/gsd/repo-identity.ts +23 -4
- package/src/resources/extensions/gsd/resource-version.ts +3 -1
- package/src/resources/extensions/gsd/roadmap-mutations.ts +29 -0
- package/src/resources/extensions/gsd/state.ts +39 -21
- package/src/resources/extensions/gsd/templates/runtime.md +21 -0
- package/src/resources/extensions/gsd/templates/task-plan.md +3 -0
- package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +21 -18
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +122 -68
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/cmux.test.ts +93 -0
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +43 -0
- package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +266 -0
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +86 -3
- package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/health-widget.test.ts +16 -54
- package/src/resources/extensions/gsd/tests/parsers.test.ts +131 -14
- package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -7
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +21 -1
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +16 -4
- package/src/resources/extensions/gsd/tests/skill-activation.test.ts +140 -0
- package/src/resources/extensions/gsd/tests/worktree.test.ts +47 -0
- package/src/resources/extensions/gsd/types.ts +18 -1
- package/src/resources/extensions/gsd/verification-evidence.ts +16 -0
- package/src/resources/extensions/gsd/visualizer-data.ts +1 -1
- package/src/resources/extensions/gsd/worktree.ts +35 -15
- package/src/resources/extensions/mcp-client/index.ts +17 -1
- package/src/resources/extensions/remote-questions/status.ts +5 -1
- package/src/resources/extensions/remote-questions/store.ts +5 -1
- package/src/resources/extensions/search-the-web/provider.ts +2 -1
- package/src/resources/extensions/shared/frontmatter.ts +1 -1
- package/src/resources/extensions/subagent/index.ts +12 -3
- package/src/resources/extensions/subagent/isolation.ts +3 -1
- package/src/resources/extensions/ttsr/rule-loader.ts +3 -1
- package/dist/resources/extensions/gsd/prompt-compressor.js +0 -393
- package/dist/resources/extensions/gsd/semantic-chunker.js +0 -254
- package/dist/resources/extensions/gsd/summary-distiller.js +0 -212
- package/src/resources/extensions/gsd/prompt-compressor.ts +0 -508
- package/src/resources/extensions/gsd/semantic-chunker.ts +0 -336
- package/src/resources/extensions/gsd/summary-distiller.ts +0 -258
- package/src/resources/extensions/gsd/tests/context-compression.test.ts +0 -193
- package/src/resources/extensions/gsd/tests/prompt-compressor.test.ts +0 -529
- package/src/resources/extensions/gsd/tests/semantic-chunker.test.ts +0 -426
- package/src/resources/extensions/gsd/tests/summary-distiller.test.ts +0 -323
- package/src/resources/extensions/gsd/tests/token-optimization-benchmark.test.ts +0 -1272
- package/src/resources/extensions/gsd/tests/token-optimization-prefs.test.ts +0 -164
package/README.md
CHANGED
|
@@ -24,21 +24,25 @@ One command. Walk away. Come back to a built project with clean git history.
|
|
|
24
24
|
|
|
25
25
|
---
|
|
26
26
|
|
|
27
|
-
## What's New in v2.
|
|
27
|
+
## What's New in v2.38
|
|
28
28
|
|
|
29
|
-
- **
|
|
30
|
-
- **
|
|
31
|
-
- **
|
|
29
|
+
- **Reactive task execution (ADR-004)** — graph-derived parallel task dispatch within slices. When enabled, GSD derives a dependency graph from IO annotations in task plans and dispatches multiple non-conflicting tasks in parallel via subagents. Backward compatible — disabled by default. Enable with `reactive_execution: true` in preferences.
|
|
30
|
+
- **Anthropic Vertex AI provider** — run Claude models (Opus 4.6, Sonnet 4.6, Haiku 4.5) through Google Vertex AI. Set `ANTHROPIC_VERTEX_PROJECT_ID` to activate.
|
|
31
|
+
- **CI optimization** — GitHub Actions minutes reduced ~60-70% (~10k → ~3-4k/month)
|
|
32
|
+
- **Reactive batch verification** — dependency-based carry-forward for verification results across parallel task batches
|
|
33
|
+
- **Backtick file path enforcement** — task plan IO sections now require backtick-wrapped paths for reliable parsing
|
|
34
|
+
|
|
35
|
+
See the full [Changelog](./CHANGELOG.md) for details.
|
|
36
|
+
|
|
37
|
+
### Previous highlights (v2.34–v2.37)
|
|
38
|
+
|
|
39
|
+
- **cmux integration** — sidebar status, progress bars, and notifications for cmux terminal multiplexer users
|
|
40
|
+
- **Redesigned dashboard** — two-column layout with 4 widget modes (full → small → min → off)
|
|
32
41
|
- **AGENTS.md support** — deprecated `agent-instructions.md` in favor of standard `AGENTS.md` / `CLAUDE.md`
|
|
33
42
|
- **AI-powered triage** — automated issue and PR triage via Claude Haiku
|
|
34
|
-
- **Auto-generated OpenRouter registry** — model registry built from OpenRouter API
|
|
35
|
-
- **Extension manifest system** — user-managed enable/disable for bundled extensions
|
|
36
|
-
- **Pipeline simplification (ADR-003)** — merged research into planning, mechanical completion
|
|
37
|
-
- **Workflow templates** — right-sized workflows for every task type
|
|
38
|
-
- **Health widget** — always-on environment health checks with progress scoring
|
|
43
|
+
- **Auto-generated OpenRouter registry** — model registry built from OpenRouter API
|
|
39
44
|
- **`/gsd changelog`** — LLM-summarized release notes for any version
|
|
40
|
-
|
|
41
|
-
See the full [Changelog](./CHANGELOG.md) for details.
|
|
45
|
+
- **Search budget enforcement** — session-level cap prevents unbounded web search
|
|
42
46
|
|
|
43
47
|
---
|
|
44
48
|
|
package/dist/app-paths.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { homedir } from 'os';
|
|
2
2
|
import { join } from 'path';
|
|
3
|
-
export const appRoot = join(homedir(), '.gsd');
|
|
3
|
+
export const appRoot = process.env.GSD_HOME || join(homedir(), '.gsd');
|
|
4
4
|
export const agentDir = join(appRoot, 'agent');
|
|
5
5
|
export const sessionsDir = join(appRoot, 'sessions');
|
|
6
6
|
export const authFilePath = join(agentDir, 'auth.json');
|
package/dist/cli.js
CHANGED
|
@@ -505,6 +505,15 @@ if (enabledModelPatterns && enabledModelPatterns.length > 0) {
|
|
|
505
505
|
session.setScopedModels(scopedModels);
|
|
506
506
|
}
|
|
507
507
|
}
|
|
508
|
+
// Welcome screen — shown on every fresh interactive session before TUI takes over
|
|
509
|
+
{
|
|
510
|
+
const { printWelcomeScreen } = await import('./welcome-screen.js');
|
|
511
|
+
printWelcomeScreen({
|
|
512
|
+
version: process.env.GSD_VERSION || '0.0.0',
|
|
513
|
+
modelName: settingsManager.getDefaultModel() || undefined,
|
|
514
|
+
provider: settingsManager.getDefaultProvider() || undefined,
|
|
515
|
+
});
|
|
516
|
+
}
|
|
508
517
|
const interactiveMode = new InteractiveMode(session);
|
|
509
518
|
markStartup('InteractiveMode');
|
|
510
519
|
printStartupTimings();
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Resolves the entry-point file(s) for a single extension directory.
|
|
3
3
|
*
|
|
4
|
-
* 1. If the directory contains a package.json with a `pi
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* 1. If the directory contains a package.json with a `pi` manifest object,
|
|
5
|
+
* the manifest is authoritative:
|
|
6
|
+
* - `pi.extensions` array → resolve each entry relative to the directory.
|
|
7
|
+
* - `pi: {}` (no extensions) → return empty (library opt-out, e.g. cmux).
|
|
8
|
+
* 2. Only when no `pi` manifest exists does it fall back to `index.ts` → `index.js`.
|
|
7
9
|
*/
|
|
8
10
|
export declare function resolveExtensionEntries(dir: string): string[];
|
|
9
11
|
/**
|
|
@@ -6,24 +6,29 @@ function isExtensionFile(name) {
|
|
|
6
6
|
/**
|
|
7
7
|
* Resolves the entry-point file(s) for a single extension directory.
|
|
8
8
|
*
|
|
9
|
-
* 1. If the directory contains a package.json with a `pi
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* 1. If the directory contains a package.json with a `pi` manifest object,
|
|
10
|
+
* the manifest is authoritative:
|
|
11
|
+
* - `pi.extensions` array → resolve each entry relative to the directory.
|
|
12
|
+
* - `pi: {}` (no extensions) → return empty (library opt-out, e.g. cmux).
|
|
13
|
+
* 2. Only when no `pi` manifest exists does it fall back to `index.ts` → `index.js`.
|
|
12
14
|
*/
|
|
13
15
|
export function resolveExtensionEntries(dir) {
|
|
14
16
|
const packageJsonPath = join(dir, 'package.json');
|
|
15
17
|
if (existsSync(packageJsonPath)) {
|
|
16
18
|
try {
|
|
17
19
|
const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
if (pkg?.pi && typeof pkg.pi === 'object') {
|
|
21
|
+
// When a pi manifest exists, it is authoritative — don't fall through
|
|
22
|
+
// to index.ts/index.js auto-detection. This allows library directories
|
|
23
|
+
// (like cmux) to opt out by declaring "pi": {} with no extensions.
|
|
24
|
+
const declared = pkg.pi.extensions;
|
|
25
|
+
if (!Array.isArray(declared) || declared.length === 0) {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
return declared
|
|
21
29
|
.filter((entry) => typeof entry === 'string')
|
|
22
30
|
.map((entry) => resolve(dir, entry))
|
|
23
31
|
.filter((entry) => existsSync(entry));
|
|
24
|
-
if (resolved.length > 0) {
|
|
25
|
-
return resolved;
|
|
26
|
-
}
|
|
27
32
|
}
|
|
28
33
|
}
|
|
29
34
|
catch {
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* The only way an extension stops loading is an explicit `gsd extensions disable <id>`.
|
|
7
7
|
*/
|
|
8
8
|
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync } from "node:fs";
|
|
9
|
-
import {
|
|
9
|
+
import { appRoot } from "./app-paths.js";
|
|
10
10
|
import { dirname, join } from "node:path";
|
|
11
11
|
// ─── Validation ─────────────────────────────────────────────────────────────
|
|
12
12
|
function isRegistry(data) {
|
|
@@ -26,7 +26,7 @@ function isManifest(data) {
|
|
|
26
26
|
}
|
|
27
27
|
// ─── Registry Path ──────────────────────────────────────────────────────────
|
|
28
28
|
export function getRegistryPath() {
|
|
29
|
-
return join(
|
|
29
|
+
return join(appRoot, "extensions", "registry.json");
|
|
30
30
|
}
|
|
31
31
|
// ─── Registry I/O ───────────────────────────────────────────────────────────
|
|
32
32
|
function defaultRegistry() {
|
|
@@ -9,12 +9,12 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
11
11
|
import { dirname, join } from "node:path";
|
|
12
|
-
import {
|
|
12
|
+
import { appRoot } from "./app-paths.js";
|
|
13
13
|
// Inlined from preferences.ts to avoid crossing the compiled/uncompiled
|
|
14
14
|
// boundary — this file is compiled by tsc, but preferences.ts is loaded
|
|
15
15
|
// via jiti at runtime. Importing it as .js fails because no .js exists
|
|
16
16
|
// in dist/. See #592, #1110.
|
|
17
|
-
const GLOBAL_PREFERENCES_PATH = join(
|
|
17
|
+
const GLOBAL_PREFERENCES_PATH = join(appRoot, "preferences.md");
|
|
18
18
|
export function saveRemoteQuestionsConfig(channel, channelId) {
|
|
19
19
|
const prefsPath = GLOBAL_PREFERENCES_PATH;
|
|
20
20
|
const block = [
|
package/dist/resource-loader.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { DefaultResourceLoader } from '@gsd/pi-coding-agent';
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
|
-
import { chmodSync, copyFileSync, cpSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { chmodSync, copyFileSync, cpSync, existsSync, lstatSync, mkdirSync, readFileSync, readlinkSync, readdirSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
5
5
|
import { dirname, join, relative, resolve } from 'node:path';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
7
|
import { compareSemver } from './update-check.js';
|
|
@@ -217,6 +217,35 @@ function copyDirRecursive(src, dest) {
|
|
|
217
217
|
}
|
|
218
218
|
}
|
|
219
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* Creates (or updates) a symlink at agentDir/node_modules pointing to GSD's
|
|
222
|
+
* own node_modules directory.
|
|
223
|
+
*
|
|
224
|
+
* Native ESM `import()` ignores NODE_PATH — it resolves packages by walking
|
|
225
|
+
* up the directory tree from the importing file. Extension files synced to
|
|
226
|
+
* ~/.gsd/agent/extensions/ have no ancestor node_modules, so imports of
|
|
227
|
+
* @gsd/* packages fail. The symlink makes Node's standard resolution find
|
|
228
|
+
* them without requiring every call site to use jiti.
|
|
229
|
+
*/
|
|
230
|
+
function ensureNodeModulesSymlink(agentDir) {
|
|
231
|
+
const agentNodeModules = join(agentDir, 'node_modules');
|
|
232
|
+
const gsdNodeModules = join(packageRoot, 'node_modules');
|
|
233
|
+
try {
|
|
234
|
+
const existing = readlinkSync(agentNodeModules);
|
|
235
|
+
if (existing === gsdNodeModules)
|
|
236
|
+
return; // already correct
|
|
237
|
+
unlinkSync(agentNodeModules);
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
// readlinkSync throws if path doesn't exist or isn't a symlink — both are fine
|
|
241
|
+
}
|
|
242
|
+
try {
|
|
243
|
+
symlinkSync(gsdNodeModules, agentNodeModules, 'junction');
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
// Non-fatal — worst case, extensions fall back to NODE_PATH via jiti
|
|
247
|
+
}
|
|
248
|
+
}
|
|
220
249
|
/**
|
|
221
250
|
* Syncs all bundled resources to agentDir (~/.gsd/agent/) on every launch.
|
|
222
251
|
*
|
|
@@ -262,6 +291,10 @@ export function initResources(agentDir) {
|
|
|
262
291
|
// Ensure all newly copied files are owner-writable so the next run can
|
|
263
292
|
// overwrite them (covers extensions, agents, and skills in one walk).
|
|
264
293
|
makeTreeWritable(agentDir);
|
|
294
|
+
// Ensure ~/.gsd/agent/node_modules symlinks to GSD's node_modules so that
|
|
295
|
+
// native ESM import() calls from synced extension files can resolve @gsd/*
|
|
296
|
+
// packages via ancestor directory lookup. NODE_PATH only applies to CJS/jiti.
|
|
297
|
+
ensureNodeModulesSymlink(agentDir);
|
|
265
298
|
writeManagedResourceManifest(agentDir);
|
|
266
299
|
ensureRegistryEntries(join(agentDir, 'extensions'));
|
|
267
300
|
}
|
|
@@ -64,6 +64,16 @@ export default function AsyncJobs(pi) {
|
|
|
64
64
|
},
|
|
65
65
|
});
|
|
66
66
|
});
|
|
67
|
+
pi.on("session_before_switch", async () => {
|
|
68
|
+
if (manager) {
|
|
69
|
+
// Cancel all running background jobs — their results are no longer
|
|
70
|
+
// relevant to the new session and would produce wasteful follow-up
|
|
71
|
+
// notifications that trigger empty LLM turns (#1642).
|
|
72
|
+
for (const job of manager.getRunningJobs()) {
|
|
73
|
+
manager.cancel(job.id);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
});
|
|
67
77
|
pi.on("session_shutdown", async () => {
|
|
68
78
|
if (manager) {
|
|
69
79
|
manager.shutdown();
|
|
@@ -4,7 +4,7 @@ let registrationPromise = null;
|
|
|
4
4
|
async function registerBrowserTools(pi) {
|
|
5
5
|
if (!registrationPromise) {
|
|
6
6
|
registrationPromise = (async () => {
|
|
7
|
-
const [lifecycle, capture, settle, refs, utils, navigation, screenshot, interaction, inspection, session, assertions, refTools, wait, pages, forms, intent, pdf, statePersistence, networkMock, device, extract, visualDiff, zoom, codegen, actionCache, injectionDetection,] = await Promise.all([
|
|
7
|
+
const [lifecycle, capture, settle, refs, utils, navigation, screenshot, interaction, inspection, session, assertions, refTools, wait, pages, forms, intent, pdf, statePersistence, networkMock, device, extract, visualDiff, zoom, codegen, actionCache, injectionDetection, verify,] = await Promise.all([
|
|
8
8
|
importExtensionModule(import.meta.url, "./lifecycle.js"),
|
|
9
9
|
importExtensionModule(import.meta.url, "./capture.js"),
|
|
10
10
|
importExtensionModule(import.meta.url, "./settle.js"),
|
|
@@ -31,6 +31,7 @@ async function registerBrowserTools(pi) {
|
|
|
31
31
|
importExtensionModule(import.meta.url, "./tools/codegen.js"),
|
|
32
32
|
importExtensionModule(import.meta.url, "./tools/action-cache.js"),
|
|
33
33
|
importExtensionModule(import.meta.url, "./tools/injection-detect.js"),
|
|
34
|
+
importExtensionModule(import.meta.url, "./tools/verify.js"),
|
|
34
35
|
]);
|
|
35
36
|
const deps = {
|
|
36
37
|
ensureBrowser: lifecycle.ensureBrowser,
|
|
@@ -99,6 +100,7 @@ async function registerBrowserTools(pi) {
|
|
|
99
100
|
codegen.registerCodegenTools(pi, deps);
|
|
100
101
|
actionCache.registerActionCacheTools(pi, deps);
|
|
101
102
|
injectionDetection.registerInjectionDetectionTools(pi, deps);
|
|
103
|
+
verify.registerVerifyTools(pi, deps);
|
|
102
104
|
})().catch((error) => {
|
|
103
105
|
registrationPromise = null;
|
|
104
106
|
throw error;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
export function registerVerifyTools(pi, deps) {
|
|
3
|
+
pi.registerTool({
|
|
4
|
+
name: "browser_verify",
|
|
5
|
+
label: "Browser Verify",
|
|
6
|
+
description: "Run a structured browser verification flow: navigate to a URL, run checks (element visibility, text content), capture screenshots as evidence, and return structured pass/fail results.",
|
|
7
|
+
promptGuidelines: [
|
|
8
|
+
"Use browser_verify for UAT verification flows that need structured evidence.",
|
|
9
|
+
"Each check produces a pass/fail result with captured evidence.",
|
|
10
|
+
"Prefer this over manual navigation + assertion sequences for verification tasks.",
|
|
11
|
+
],
|
|
12
|
+
parameters: Type.Object({
|
|
13
|
+
url: Type.String({ description: "URL to navigate to" }),
|
|
14
|
+
checks: Type.Array(Type.Object({
|
|
15
|
+
description: Type.String({ description: "What this check verifies" }),
|
|
16
|
+
selector: Type.Optional(Type.String({ description: "CSS selector to check" })),
|
|
17
|
+
expectedText: Type.Optional(Type.String({ description: "Expected text content" })),
|
|
18
|
+
expectedVisible: Type.Optional(Type.Boolean({ description: "Whether element should be visible" })),
|
|
19
|
+
screenshot: Type.Optional(Type.Boolean({ description: "Capture screenshot as evidence" })),
|
|
20
|
+
}), { description: "Verification checks to run" }),
|
|
21
|
+
timeout: Type.Optional(Type.Number({ description: "Navigation timeout in ms", default: 10000 })),
|
|
22
|
+
}),
|
|
23
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
24
|
+
const startTime = Date.now();
|
|
25
|
+
const { page } = await deps.ensureBrowser();
|
|
26
|
+
const timeout = params.timeout ?? 10000;
|
|
27
|
+
try {
|
|
28
|
+
await page.goto(params.url, { waitUntil: "domcontentloaded", timeout });
|
|
29
|
+
}
|
|
30
|
+
catch (navErr) {
|
|
31
|
+
const msg = navErr instanceof Error ? navErr.message : String(navErr);
|
|
32
|
+
return {
|
|
33
|
+
content: [{ type: "text", text: `Navigation failed: ${msg}` }],
|
|
34
|
+
details: {
|
|
35
|
+
url: params.url,
|
|
36
|
+
passed: false,
|
|
37
|
+
checks: params.checks.map((c) => ({ description: c.description, passed: false, error: msg })),
|
|
38
|
+
duration: Date.now() - startTime,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
const results = [];
|
|
43
|
+
for (const check of params.checks) {
|
|
44
|
+
try {
|
|
45
|
+
let passed = true;
|
|
46
|
+
let actual;
|
|
47
|
+
let evidence;
|
|
48
|
+
if (check.selector) {
|
|
49
|
+
const element = await page.$(check.selector);
|
|
50
|
+
if (check.expectedVisible !== undefined) {
|
|
51
|
+
const isVisible = element ? await element.isVisible() : false;
|
|
52
|
+
passed = isVisible === check.expectedVisible;
|
|
53
|
+
actual = `visible=${isVisible}`;
|
|
54
|
+
}
|
|
55
|
+
if (check.expectedText !== undefined && element) {
|
|
56
|
+
const text = await element.textContent();
|
|
57
|
+
passed = passed && (text?.includes(check.expectedText) ?? false);
|
|
58
|
+
actual = `text="${text?.slice(0, 200)}"`;
|
|
59
|
+
}
|
|
60
|
+
if (!element && (check.expectedVisible === true || check.expectedText)) {
|
|
61
|
+
passed = false;
|
|
62
|
+
actual = "element not found";
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (check.screenshot) {
|
|
66
|
+
try {
|
|
67
|
+
const buf = await page.screenshot({ type: "png" });
|
|
68
|
+
evidence = `screenshot captured (${buf.length} bytes)`;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
evidence = "screenshot failed";
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
results.push({ description: check.description, passed, actual, evidence });
|
|
75
|
+
}
|
|
76
|
+
catch (checkErr) {
|
|
77
|
+
results.push({
|
|
78
|
+
description: check.description,
|
|
79
|
+
passed: false,
|
|
80
|
+
error: checkErr instanceof Error ? checkErr.message : String(checkErr),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const allPassed = results.every((r) => r.passed);
|
|
85
|
+
const summary = results.map((r) => `${r.passed ? "PASS" : "FAIL"}: ${r.description}${r.actual ? ` (${r.actual})` : ""}${r.error ? ` — ${r.error}` : ""}`).join("\n");
|
|
86
|
+
return {
|
|
87
|
+
content: [{ type: "text", text: `Verification ${allPassed ? "PASSED" : "FAILED"} (${results.filter(r => r.passed).length}/${results.length})\n\n${summary}` }],
|
|
88
|
+
details: {
|
|
89
|
+
url: params.url,
|
|
90
|
+
passed: allPassed,
|
|
91
|
+
checks: results,
|
|
92
|
+
duration: Date.now() - startTime,
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
}
|
|
@@ -237,11 +237,14 @@ export class CmuxClient {
|
|
|
237
237
|
return extractSurfaceIds(parsed);
|
|
238
238
|
}
|
|
239
239
|
async createSplit(direction) {
|
|
240
|
+
return this.createSplitFrom(this.config.surfaceId, direction);
|
|
241
|
+
}
|
|
242
|
+
async createSplitFrom(sourceSurfaceId, direction) {
|
|
240
243
|
if (!this.config.splits)
|
|
241
244
|
return null;
|
|
242
245
|
const before = new Set(await this.listSurfaceIds());
|
|
243
246
|
const args = ["new-split", direction];
|
|
244
|
-
const scopedArgs = this.appendSurface(this.appendWorkspace(args),
|
|
247
|
+
const scopedArgs = this.appendSurface(this.appendWorkspace(args), sourceSurfaceId);
|
|
245
248
|
await this.runAsync(scopedArgs);
|
|
246
249
|
const after = await this.listSurfaceIds();
|
|
247
250
|
for (const id of after) {
|
|
@@ -250,6 +253,57 @@ export class CmuxClient {
|
|
|
250
253
|
}
|
|
251
254
|
return null;
|
|
252
255
|
}
|
|
256
|
+
/**
|
|
257
|
+
* Create a grid of surfaces for parallel agent execution.
|
|
258
|
+
*
|
|
259
|
+
* Layout strategy (gsd stays in the original surface):
|
|
260
|
+
* 1 agent: [gsd | A]
|
|
261
|
+
* 2 agents: [gsd | A]
|
|
262
|
+
* [ | B]
|
|
263
|
+
* 3 agents: [gsd | A]
|
|
264
|
+
* [ C | B]
|
|
265
|
+
* 4 agents: [gsd | A]
|
|
266
|
+
* [ C | B] (D splits from B downward)
|
|
267
|
+
* [ | D]
|
|
268
|
+
*
|
|
269
|
+
* Returns surface IDs in order, or empty array on failure.
|
|
270
|
+
*/
|
|
271
|
+
async createGridLayout(count) {
|
|
272
|
+
if (!this.config.splits || count <= 0)
|
|
273
|
+
return [];
|
|
274
|
+
const surfaces = [];
|
|
275
|
+
// First split: create right column from the gsd surface
|
|
276
|
+
const rightCol = await this.createSplitFrom(this.config.surfaceId, "right");
|
|
277
|
+
if (!rightCol)
|
|
278
|
+
return [];
|
|
279
|
+
surfaces.push(rightCol);
|
|
280
|
+
if (count === 1)
|
|
281
|
+
return surfaces;
|
|
282
|
+
// Second split: split right column down → bottom-right
|
|
283
|
+
const bottomRight = await this.createSplitFrom(rightCol, "down");
|
|
284
|
+
if (!bottomRight)
|
|
285
|
+
return surfaces;
|
|
286
|
+
surfaces.push(bottomRight);
|
|
287
|
+
if (count === 2)
|
|
288
|
+
return surfaces;
|
|
289
|
+
// Third split: split gsd surface down → bottom-left
|
|
290
|
+
const bottomLeft = await this.createSplitFrom(this.config.surfaceId, "down");
|
|
291
|
+
if (!bottomLeft)
|
|
292
|
+
return surfaces;
|
|
293
|
+
surfaces.push(bottomLeft);
|
|
294
|
+
if (count === 3)
|
|
295
|
+
return surfaces;
|
|
296
|
+
// Fourth+: split subsequent surfaces down from the last created
|
|
297
|
+
let lastSurface = bottomRight;
|
|
298
|
+
for (let i = 3; i < count; i++) {
|
|
299
|
+
const next = await this.createSplitFrom(lastSurface, "down");
|
|
300
|
+
if (!next)
|
|
301
|
+
break;
|
|
302
|
+
surfaces.push(next);
|
|
303
|
+
lastSurface = next;
|
|
304
|
+
}
|
|
305
|
+
return surfaces;
|
|
306
|
+
}
|
|
253
307
|
async sendSurface(surfaceId, text) {
|
|
254
308
|
const payload = text.endsWith("\n") ? text : `${text}\n`;
|
|
255
309
|
const stdout = await this.runAsync(["send-surface", "--surface", surfaceId, payload]);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// GSD Extension — Environment variable utilities
|
|
2
|
+
// Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
|
|
3
|
+
//
|
|
4
|
+
// Pure utility for checking existing env keys in .env files and process.env.
|
|
5
|
+
// Extracted from get-secrets-from-user.ts to avoid pulling in @gsd/pi-tui
|
|
6
|
+
// when only env-checking is needed (e.g. from files.ts during report generation).
|
|
7
|
+
import { readFile } from "node:fs/promises";
|
|
8
|
+
/**
|
|
9
|
+
* Check which keys already exist in a .env file or process.env.
|
|
10
|
+
* Returns the subset of `keys` that are already set.
|
|
11
|
+
*/
|
|
12
|
+
export async function checkExistingEnvKeys(keys, envFilePath) {
|
|
13
|
+
let fileContent = "";
|
|
14
|
+
try {
|
|
15
|
+
fileContent = await readFile(envFilePath, "utf8");
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
// ENOENT or other read error — proceed with empty content
|
|
19
|
+
}
|
|
20
|
+
const existing = [];
|
|
21
|
+
for (const key of keys) {
|
|
22
|
+
const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
23
|
+
const regex = new RegExp(`^${escaped}\\s*=`, "m");
|
|
24
|
+
if (regex.test(fileContent) || key in process.env) {
|
|
25
|
+
existing.push(key);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return existing;
|
|
29
|
+
}
|
|
@@ -46,30 +46,11 @@ async function writeEnvKey(filePath, key, value) {
|
|
|
46
46
|
await writeFile(filePath, content, "utf8");
|
|
47
47
|
}
|
|
48
48
|
// ─── Exported utilities ───────────────────────────────────────────────────────
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
*/
|
|
55
|
-
export async function checkExistingEnvKeys(keys, envFilePath) {
|
|
56
|
-
let fileContent = "";
|
|
57
|
-
try {
|
|
58
|
-
fileContent = await readFile(envFilePath, "utf8");
|
|
59
|
-
}
|
|
60
|
-
catch {
|
|
61
|
-
// ENOENT or other read error — proceed with empty content
|
|
62
|
-
}
|
|
63
|
-
const existing = [];
|
|
64
|
-
for (const key of keys) {
|
|
65
|
-
const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
66
|
-
const regex = new RegExp(`^${escaped}\\s*=`, "m");
|
|
67
|
-
if (regex.test(fileContent) || key in process.env) {
|
|
68
|
-
existing.push(key);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return existing;
|
|
72
|
-
}
|
|
49
|
+
// Re-export from env-utils.ts so existing consumers still work.
|
|
50
|
+
// The implementation lives in env-utils.ts to avoid pulling @gsd/pi-tui
|
|
51
|
+
// into modules that only need env-checking (e.g. files.ts during reports).
|
|
52
|
+
import { checkExistingEnvKeys } from "./env-utils.js";
|
|
53
|
+
export { checkExistingEnvKeys };
|
|
73
54
|
/**
|
|
74
55
|
* Detect the write destination based on project files in basePath.
|
|
75
56
|
* Priority: vercel.json → convex/ dir → fallback "dotenv".
|