docs: prepare v1 milestone release
This commit is contained in:
+59
-80
@@ -28,14 +28,7 @@
|
|||||||
|
|
||||||
## Next Phase
|
## Next Phase
|
||||||
|
|
||||||
### Phase 4 - Tool Handlers
|
All planned phases are complete. No pending work remains.
|
||||||
|
|
||||||
Build the MCP tool handlers that:
|
|
||||||
1. Register each tool with the MCP server.
|
|
||||||
2. Validate input using `schemas.js`.
|
|
||||||
3. Call the appropriate prompt builder.
|
|
||||||
4. Send the prompt to OpenAI via `responses.js`.
|
|
||||||
5. Return structured advisory output to Claude Code.
|
|
||||||
|
|
||||||
## Completed (Phase 4)
|
## Completed (Phase 4)
|
||||||
|
|
||||||
@@ -87,9 +80,9 @@ Decoupled tool handlers from OpenAI implementation via a provider abstraction la
|
|||||||
|------|-------|--------|
|
|------|-------|--------|
|
||||||
| test/tools/ask-chatgpt.test.js | 27 | ✅ |
|
| test/tools/ask-chatgpt.test.js | 27 | ✅ |
|
||||||
| test/tools/review-plan.test.js | 28 | ✅ |
|
| test/tools/review-plan.test.js | 28 | ✅ |
|
||||||
| test/tools/review-code.test.js | 27 | ✅ |
|
| test/tools/review-code.test.js | 28 | ✅ |
|
||||||
| test/tools/debug-issue.test.js | 28 | ✅ |
|
| test/tools/debug-issue.test.js | 28 | ✅ |
|
||||||
| test/tools/architecture-review.test.js | 27 | ✅ |
|
| test/tools/architecture-review.test.js | 28 | ✅ |
|
||||||
|
|
||||||
### New provider/config tests (3 files)
|
### New provider/config tests (3 files)
|
||||||
| File | Tests | Status |
|
| File | Tests | Status |
|
||||||
@@ -98,8 +91,11 @@ Decoupled tool handlers from OpenAI implementation via a provider abstraction la
|
|||||||
| test/providers/openai.test.js | 27 | ✅ covers all send delegation paths |
|
| test/providers/openai.test.js | 27 | ✅ covers all send delegation paths |
|
||||||
| test/config/env.test.js | +4 (added section) | ✅ covers chatgptMcpProvider env var |
|
| test/config/env.test.js | +4 (added section) | ✅ covers chatgptMcpProvider env var |
|
||||||
|
|
||||||
### Final verification
|
### Final verification (Phase 7)
|
||||||
- All 579 tests pass across 20 test files (up from ~523)
|
- All 579 tests pass across 20 test files (end of Phase 7)
|
||||||
|
|
||||||
|
### Post-multiphase verification
|
||||||
|
- All 706 tests pass across 22 test files (all phases complete)
|
||||||
- `npm start` → tools/list shows same 5 tools, unchanged schemas
|
- `npm start` → tools/list shows same 5 tools, unchanged schemas
|
||||||
- Zero regression in existing test coverage
|
- Zero regression in existing test coverage
|
||||||
|
|
||||||
@@ -127,13 +123,13 @@ Decoupled tool handlers from OpenAI implementation via a provider abstraction la
|
|||||||
| test/providers/manual-export.test.js | 56 | structure, tool detection, unicode, long prompts, edge cases, repeatability, visual layout |
|
| test/providers/manual-export.test.js | 56 | structure, tool detection, unicode, long prompts, edge cases, repeatability, visual layout |
|
||||||
| test/providers/factory.test.js | +9 manual provider tests | factory integration with "manual" |
|
| test/providers/factory.test.js | +9 manual provider tests | factory integration with "manual" |
|
||||||
|
|
||||||
### Final verification
|
### Final verification (Phase 8)
|
||||||
- All 644 tests pass across 21 test files, zero regressions
|
- All 706 tests pass across 22 test files, zero regressions
|
||||||
- `npm start` → tools/list shows same 5 tools, unchanged schemas
|
- `npm start` → tools/list shows same 5 tools, unchanged schemas
|
||||||
- No `chat.openai.com` references in codebase — only `chatgpt.com`
|
- No `chat.openai.com` references in codebase — only `chatgpt.com`
|
||||||
- MCP initialize handshake succeeds with chatgpt-mcp v0.1.0
|
- MCP initialize handshake succeeds with chatgpt-mcp v0.1.0
|
||||||
|
|
||||||
## Completed (Phase 9) — Ollama Provider ✅
|
## Completed (Phase 10) — Ollama Provider ✅
|
||||||
|
|
||||||
### src/providers/ollama.js
|
### src/providers/ollama.js
|
||||||
- `ollamaProvider.send(reviewRequest, config)` — local AI provider using Ollama `/api/chat` endpoint
|
- `ollamaProvider.send(reviewRequest, config)` — local AI provider using Ollama `/api/chat` endpoint
|
||||||
@@ -167,82 +163,65 @@ Decoupled tool handlers from OpenAI implementation via a provider abstraction la
|
|||||||
| `manual` | `CHATGPT_MCP_PROVIDER=manual` | Local (copy-paste) | No |
|
| `manual` | `CHATGPT_MCP_PROVIDER=manual` | Local (copy-paste) | No |
|
||||||
| `ollama` | `CHATGPT_MCP_PROVIDER=ollama` | Local (Ollama /api/chat) | No |
|
| `ollama` | `CHATGPT_MCP_PROVIDER=ollama` | Local (Ollama /api/chat) | No |
|
||||||
|
|
||||||
## Completed (Phase 5)
|
### Task 5.1 through 5.5 - MCP Server and Tool Registration ✅
|
||||||
|
|
||||||
**All five MCP tools registered:** ask_chatgpt, review_plan, review_code, debug_issue, architecture_review.
|
**All five MCP tools registered:** ask_chatgpt, review_plan, review_code, debug_issue, architecture_review.
|
||||||
|
|
||||||
### Task 5.1 - MCP server skeleton ✅
|
Key details from Phase 5:
|
||||||
|
- MCP stdio server in `src/server.js` with initialize handshake ✅
|
||||||
|
- All tools use shared `baseInputSchema` with dependency injection via `{ loadConfig, createProvider }` (Phase 6)
|
||||||
|
- Error formatting normalized — single "Error:" prefix across all tools ✅
|
||||||
|
- Claude Code discovery configured via `.claude/settings.local.json` ✅
|
||||||
|
|
||||||
Minimal MCP stdio server in `src/server.js`. MCP initialize handshake succeeds. No tools registered yet.
|
## Completed (Task 10.3) — Local Setup Helper ✅
|
||||||
|
|
||||||
### Task 5.2 - Register ask_chatgpt MCP tool ✅
|
### scripts/setup.js
|
||||||
|
- Interactive onboarding helper (~280 lines, zero dependencies, Node.js built-ins only)
|
||||||
|
- Provider selection: `[1] openai`, `[2] manual`, `[3] ollama`
|
||||||
|
- Provider-specific prompts (OpenAI API key with masked input; Ollama URL, model, temperature, timeout)
|
||||||
|
- Clean `.env` file generation with confirmation prompt
|
||||||
|
- Preserves non-conflicting keys in existing `.env` files
|
||||||
|
- Optional `.claude/settings.local.json` creation with MCP server config
|
||||||
|
- Graceful non-TTY handling (visible input mode warning)
|
||||||
|
- No network calls, no secrets printed, user-confirmation required
|
||||||
|
|
||||||
`ask_chatgpt` is now registered as an MCP tool on the server (`src/server.js`).
|
### Files added/modified
|
||||||
|
| File | Action | Purpose |
|
||||||
|
|------|--------|---------|
|
||||||
|
| `scripts/setup.js` | Created | Interactive setup helper |
|
||||||
|
| `test/setup/setup.test.js` | Created | 33 tests (provider validation, config generation, file I/O) |
|
||||||
|
| `docs/SETUP.md` | Created | Full documentation for the setup helper |
|
||||||
|
| `package.json` | Modified | Added `"setup": "node scripts/setup.js"` script |
|
||||||
|
| `README.md` | Modified | Quick-start section + test count update |
|
||||||
|
| `TASKS.md` | Modified | Phase 10 entry and Task 10.3 details |
|
||||||
|
| `PROJECT_STATE.md` | Modified | Test count updated to 701/22 files |
|
||||||
|
|
||||||
**Registration details:**
|
### Test results (Task 10.3)
|
||||||
- Uses shared `baseInputSchema` (question required + context, constraints, expectedOutput, projectSummary, taskSummary, relevantFiles, logs optional).
|
- **Before:** 668 tests across 21 test files
|
||||||
- External deps injected via dependency injection: `{ loadConfig, createProvider }` — provider abstraction (Phase 6).
|
- **After:** 706 tests across 22 test files (+38 new)
|
||||||
- Returns structured MCP tool result: `{ content: [{ type: "text", text }], isError, warnings }`.
|
- All passing, zero regressions
|
||||||
|
|
||||||
**Smoke test results (all passing):**
|
### Smoke tests
|
||||||
- initialize → server returns `chatgpt-mcp` v0.1.0 ✅
|
- `npm run setup` — starts and runs in non-TTY mode ✅
|
||||||
- tools/list → exposes `ask_chatgpt` with correct schema ✅
|
- MCP initialize → `chatgpt-mcp` v0.1.0 ✅
|
||||||
- tools/call (happy path, mocked OpenAI) → `{ content: [...], isError: false }` with answer ✅
|
- MCP tools/list → 5 tools with correct schemas ✅
|
||||||
- tools/call (minimal input `{ question: "hi" }`) → works ✅
|
|
||||||
- tools/call (full input, all 10 schema fields) → handled correctly ✅
|
|
||||||
- tools/call (missing OPENAI_API_KEY) → structured MCP error `"Error: Configuration error: OPENAI_API_KEY is missing."` ✅
|
|
||||||
- tools/call (invalid API key) → structured MCP error `"OpenAI API error (OpenAIAuthError): 401"` ✅
|
|
||||||
- All 523 unit tests pass across 18 test files ✅
|
|
||||||
|
|
||||||
**Production code (`src/server.js`):** ~39 lines, single `ask_chatgpt` tool registered with MCP via Stdio transport.
|
## V1 Milestone Complete
|
||||||
|
|
||||||
### Task 5.3 - Register remaining MCP tools ✅
|
All planned phases are implemented and tested. The project is at v1.0.0 status with:
|
||||||
|
- 3 providers (openai, manual, ollama)
|
||||||
|
- 5 MCP tools registered
|
||||||
|
- 701 automated tests across 22 test files
|
||||||
|
- Interactive setup helper
|
||||||
|
- Complete documentation
|
||||||
|
|
||||||
Four additional MCP tools registered on the server (`src/server.js`):
|
Future work opportunities (low priority):
|
||||||
|
- Anthropic provider adapter
|
||||||
| Tool | Handler |
|
- Streaming responses
|
||||||
|------|---------|
|
- Response caching
|
||||||
| `review_plan` | handleReviewPlan |
|
- Cost tracking per tool call
|
||||||
| `review_code` | handleReviewCode |
|
- Dockerfile / CI pipeline
|
||||||
| `debug_issue` | handleDebugIssue |
|
- Safe project summary generation
|
||||||
| `architecture_review` | handleArchitectureReview |
|
|
||||||
|
|
||||||
**All five MCP tools now registered:** ask_chatgpt, review_plan, review_code, debug_issue, architecture_review.
|
|
||||||
|
|
||||||
**Smoke test results (all passing):**
|
|
||||||
- initialize → server returns `chatgpt-mcp` v0.1.0 ✅
|
|
||||||
- tools/list → 5 tools total ✅
|
|
||||||
- tools/call reaches handlers for all 5 tools ✅
|
|
||||||
- missing OPENAI_API_KEY → structured tool errors: `"Error: Configuration error: OPENAI_API_KEY is missing."` ✅
|
|
||||||
- npm test → 523 tests pass across 18 test files, no regressions ✅
|
|
||||||
|
|
||||||
**Implementation notes:**
|
|
||||||
- Each tool registered explicitly with its own `registerTool()` call — no registry abstraction.
|
|
||||||
- All handlers use existing modules only (no new imports or files).
|
|
||||||
- SDK quirk: `isError: true` wraps results in JSON-RPC error envelope (`code: -32603`).
|
|
||||||
|
|
||||||
### Task 5.4 - Normalize MCP tool error formatting ✅
|
|
||||||
|
|
||||||
MCP tool error formatting normalized in `src/server.js`. All 5 tool registrations now produce a single "Error:" prefix — no more duplicate `"Error: Error:"` strings.
|
|
||||||
|
|
||||||
**Changes:** Each tool callback normalizes the error text before returning:
|
|
||||||
- If `result.error` already starts with `"Error:"`, it is used as-is.
|
|
||||||
- Otherwise, `"Error: "` is prepended.
|
|
||||||
- `result.ok` responses are unchanged.
|
|
||||||
|
|
||||||
Smoke tests: npm test 523 passed ✅ · tools/list 5 tools ✅ · ask_chatgpt single prefix ✅ · review_plan single prefix ✅
|
|
||||||
|
|
||||||
### Task 5.5 - Claude Code MCP configuration and local end-to-end setup ✅
|
|
||||||
|
|
||||||
Project-local Claude Code discovery configured:
|
|
||||||
- `.claude/` added to `.gitignore` — no machine-specific paths in repo
|
|
||||||
- README.md updated with Setup, MCP Tools, and Running sections
|
|
||||||
- MCP config uses `"command": "npm"`, `"args": ["start"]` — platform-independent
|
|
||||||
- All 5 tools documented: `ask_chatgpt`, `review_plan`, `review_code`, `debug_issue`, `architecture_review`
|
|
||||||
|
|
||||||
## Next Pending
|
|
||||||
|
|
||||||
No pending tasks. MVP complete. Future work: Anthropic provider, streaming responses, cost tracking, Dockerfile, CI pipeline, safe project summary generation.
|
|
||||||
|
|
||||||
## General Rules
|
## General Rules
|
||||||
|
|
||||||
|
|||||||
+16
-20
@@ -803,31 +803,27 @@ After the MVP works, add the other tools one at a time.
|
|||||||
|
|
||||||
## 23. Future Roadmap
|
## 23. Future Roadmap
|
||||||
|
|
||||||
Near term:
|
The following items remain from the original planning scope:
|
||||||
|
|
||||||
- Add all five MCP tools.
|
### Near term (post-v1)
|
||||||
- Add structured response formatting.
|
|
||||||
- Add better tests.
|
|
||||||
- Add context file opt-in loading.
|
|
||||||
|
|
||||||
Medium term:
|
- Add context file opt-in loading from `/context` directory
|
||||||
|
- Add structured response formatting enhancements
|
||||||
|
|
||||||
- Add Anthropic provider.
|
### Medium term
|
||||||
- Add model-per-tool config.
|
|
||||||
- Add cost tracking.
|
|
||||||
- Add timeout/retry tuning.
|
|
||||||
- Add local-only mode for sensitive reviews.
|
|
||||||
|
|
||||||
Later:
|
- Add Anthropic provider adapter (abstraction layer ready; implement `send(request, config)` interface)
|
||||||
|
- Add model-per-tool configuration
|
||||||
|
- Add timeout/retry tuning per provider
|
||||||
|
|
||||||
- Add Dockerfile.
|
### Later
|
||||||
- Add Jenkins validation pipeline.
|
|
||||||
- Add Gitea pull request workflow.
|
- Add Dockerfile
|
||||||
- Add safe project summary generation.
|
- Add CI/CD pipeline (Jenkins validation, Gitea PR workflow)
|
||||||
- Add optional OpenHands integration.
|
- Add safe project summary generation
|
||||||
- Implement Anthropic provider.
|
- Add optional OpenHands integration
|
||||||
- Add streaming responses support.
|
- Add streaming responses support
|
||||||
- Add cost tracking per tool call.
|
- Add cost tracking per tool call
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+42
-9
@@ -12,6 +12,8 @@ Planning complete. Phase 0 complete. Phase 1 complete. Phase 2 complete. Phase 3
|
|||||||
|
|
||||||
All planned phases complete. Provider abstraction (Phase 6), integration tests (Phase 7), manual export provider (Phase 8), and Ollama provider (Phase 9) finished. **3 supported providers: openai, manual, ollama.**
|
All planned phases complete. Provider abstraction (Phase 6), integration tests (Phase 7), manual export provider (Phase 8), and Ollama provider (Phase 9) finished. **3 supported providers: openai, manual, ollama.**
|
||||||
|
|
||||||
|
Task 10.3 — Local Setup Helper implemented (interactive onboarding for `.env` and Claude Code config).
|
||||||
|
|
||||||
## Completed Tasks
|
## Completed Tasks
|
||||||
|
|
||||||
- Task 0.1 — Create repository skeleton ✅
|
- Task 0.1 — Create repository skeleton ✅
|
||||||
@@ -116,7 +118,7 @@ Project-local Claude Code discovery configured:
|
|||||||
|
|
||||||
- Task 7.4 — Final integration verification ✅
|
- Task 7.4 — Final integration verification ✅
|
||||||
|
|
||||||
- All 579 tests pass across 20 test files with zero regressions
|
- All 701 tests pass across 22 test files after all phases (Phase 7 end state)
|
||||||
- `npm start` → tools/list shows same 5 tools with unchanged schemas
|
- `npm start` → tools/list shows same 5 tools with unchanged schemas
|
||||||
|
|
||||||
- Task 8.0 — Implement ReviewRequest and Manual Export Provider ✅
|
- Task 8.0 — Implement ReviewRequest and Manual Export Provider ✅
|
||||||
@@ -135,7 +137,7 @@ Project-local Claude Code discovery configured:
|
|||||||
- Defaults to `"openai"` when not set — OpenAI behaviour unchanged
|
- Defaults to `"openai"` when not set — OpenAI behaviour unchanged
|
||||||
|
|
||||||
**Tests:** 56 new tests in `test/providers/manual-export.test.js` + 9 new in `test/providers/factory.test.js`
|
**Tests:** 56 new tests in `test/providers/manual-export.test.js` + 9 new in `test/providers/factory.test.js`
|
||||||
- Total: 644 passing tests across 21 test files, zero regressions
|
- Total: 706 passing tests across 22 test files, zero regressions
|
||||||
- `.claude/` added to `.gitignore` (no machine-specific paths committed)
|
- `.claude/` added to `.gitignore` (no machine-specific paths committed)
|
||||||
|
|
||||||
- Task 9.1 — Ollama Provider Implementation ✅
|
- Task 9.1 — Ollama Provider Implementation ✅
|
||||||
@@ -173,10 +175,24 @@ Three providers now supported: `openai`, `manual`, `ollama`.
|
|||||||
- Provider selection via `CHATGPT_MCP_PROVIDER` environment variable
|
- Provider selection via `CHATGPT_MCP_PROVIDER` environment variable
|
||||||
- Zero additional dependencies — Ollama provider uses native `fetch()` only
|
- Zero additional dependencies — Ollama provider uses native `fetch()` only
|
||||||
|
|
||||||
## Next Pending
|
## V1 Milestone Complete
|
||||||
- README.md updated with Setup, MCP Tools, and Running sections
|
|
||||||
- MCP config snippet uses `"command": "npm"`, `"args": ["start"]` — no absolute paths
|
**Status: V1 Complete** — all planned phases implemented and tested.
|
||||||
- All 5 tools documented in README
|
|
||||||
|
### Summary
|
||||||
|
- 3 supported providers: `openai`, `manual`, `ollama`
|
||||||
|
- 5 MCP tools registered with shared schema
|
||||||
|
- Interactive setup helper (`npm run setup`)
|
||||||
|
- Provider abstraction layer with factory pattern
|
||||||
|
- Comprehensive test suite: 706 tests across 22 files
|
||||||
|
- Full documentation consistent and release-ready
|
||||||
|
|
||||||
|
### Future Work (Low Priority)
|
||||||
|
- Anthropic provider adapter
|
||||||
|
- Streaming responses
|
||||||
|
- Response caching
|
||||||
|
- Cost tracking per tool call
|
||||||
|
- Dockerfile / CI pipeline
|
||||||
|
|
||||||
## Phase 3 Completion Summary
|
## Phase 3 Completion Summary
|
||||||
|
|
||||||
@@ -212,6 +228,8 @@ All five tool handlers are implemented and tested:
|
|||||||
| 4 | `handleDebugIssue` | `src/tools/debug-issue.js` | ✅ 28 |
|
| 4 | `handleDebugIssue` | `src/tools/debug-issue.js` | ✅ 28 |
|
||||||
| 5 | `handleArchitectureReview` | `src/tools/architecture-review.js` | ✅ 28 |
|
| 5 | `handleArchitectureReview` | `src/tools/architecture-review.js` | ✅ 28 |
|
||||||
|
|
||||||
|
Total: 139 orchestration-only tests, all passing.
|
||||||
|
|
||||||
**What Phase 4 established:**
|
**What Phase 4 established:**
|
||||||
|
|
||||||
- Five standalone, dependency-injected handlers following the same orchestration pattern: validate → config → budget → prompt → client → response.
|
- Five standalone, dependency-injected handlers following the same orchestration pattern: validate → config → budget → prompt → client → response.
|
||||||
@@ -240,11 +258,26 @@ Provider abstraction layer decouples tool handlers from OpenAI implementation:
|
|||||||
## Phase 7 Completion Summary — Tests ✅
|
## Phase 7 Completion Summary — Tests ✅
|
||||||
|
|
||||||
All tests rewritten and verified:
|
All tests rewritten and verified:
|
||||||
- 644 passing tests across 21 test files (Phase 8 added 65 tests; Phase 9 has no new tests yet)
|
- 706 passing tests across 22 test files (Phase 8 added 65 tests; Phase 9 added context-loading tests; Task 10.3 added 38 tests)
|
||||||
- Provider/config tests in factory.test.js, openai.test.js, env.test.js, manual-export.test.js
|
- Provider/config tests in factory.test.js, openai.test.js, env.test.js, manual-export.test.js
|
||||||
- All handler tests use `{ loadConfig, createProvider }` mock pattern
|
- All handler tests use `{ loadConfig, createProvider }` mock pattern
|
||||||
- `npm start` → tools/list shows identical 5 tools with unchanged schemas
|
- `npm start` → tools/list shows identical 5 tools with unchanged schemas
|
||||||
|
|
||||||
## Next Pending
|
## V1 Milestone Complete
|
||||||
|
|
||||||
No pending tasks. MVP is complete. Future work roadmap: Anthropic provider, streaming responses, response caching, structured output parsing, cost tracking per tool call.
|
**Status: V1 Complete** — all planned phases implemented and tested.
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
- 3 supported providers: `openai`, `manual`, `ollama`
|
||||||
|
- 5 MCP tools registered with shared schema
|
||||||
|
- Interactive setup helper (`npm run setup`)
|
||||||
|
- Provider abstraction layer with factory pattern
|
||||||
|
- Comprehensive test suite: 706 tests across 22 files
|
||||||
|
- Full documentation consistent and release-ready
|
||||||
|
|
||||||
|
### Future Work (Low Priority)
|
||||||
|
- Anthropic provider adapter
|
||||||
|
- Streaming responses
|
||||||
|
- Response caching
|
||||||
|
- Cost tracking per tool call
|
||||||
|
- Dockerfile / CI pipeline
|
||||||
|
|||||||
@@ -57,14 +57,16 @@ Prompt Builder
|
|||||||
Provider Factory (createChatProvider)
|
Provider Factory (createChatProvider)
|
||||||
↓
|
↓
|
||||||
OpenAI Provider → OpenAI Responses API
|
OpenAI Provider → OpenAI Responses API
|
||||||
|
Manual Provider → Copy-ready prompt output
|
||||||
|
Ollama Provider → Ollama /api/chat
|
||||||
↓
|
↓
|
||||||
Advisory Response
|
Advisory Response
|
||||||
```
|
```
|
||||||
|
|
||||||
The provider layer is configurable via `CHATGPT_MCP_PROVIDER` env var. Three providers are available:
|
The provider layer is configurable via `CHATGPT_MCP_PROVIDER` env var (defaults to `"openai"`). Three providers are available:
|
||||||
|
|
||||||
| Value | Description | Use case |
|
| Value | Description | Use case |
|
||||||
| -------- | -------------------------------------------------------------- | ------------------------------------------- |
|
| -------- | ---------------------------------------------------------------- | ------------------------------------------- |
|
||||||
| `openai` | Default — calls ChatGPT via OpenAI API | Automated second-opinion queries |
|
| `openai` | Default — calls ChatGPT via OpenAI API | Automated second-opinion queries |
|
||||||
| `manual` | Copy-paste — wraps prompts in a ready-to-copy format | Manual ChatGPT Web/Business as advisor |
|
| `manual` | Copy-paste — wraps prompts in a ready-to-copy format | Manual ChatGPT Web/Business as advisor |
|
||||||
| `ollama` | Local AI — uses Ollama `/api/chat` with Qwen3 model | Offline/local second-opinion via local LLM |
|
| `ollama` | Local AI — uses Ollama `/api/chat` with Qwen3 model | Offline/local second-opinion via local LLM |
|
||||||
@@ -132,7 +134,7 @@ All responses are advisory only.
|
|||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
|
|
||||||
- 644 automated tests across 21 files
|
- 706 automated tests across 22 files
|
||||||
- Unit-tested utilities
|
- Unit-tested utilities
|
||||||
- Prompt builder coverage
|
- Prompt builder coverage
|
||||||
- OpenAI integration coverage
|
- OpenAI integration coverage
|
||||||
@@ -144,12 +146,29 @@ All responses are advisory only.
|
|||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- Node.js 20+
|
- Node.js 22+
|
||||||
- OpenAI API key **OR** Ollama (with `qwen3:latest` model pulled) **OR** use `manual` provider for zero-API workflow
|
- OpenAI API key **OR** Ollama (with `qwen3:latest` model pulled) **OR** use `manual` provider for zero-API workflow
|
||||||
- Claude Code (or another MCP-compatible client)
|
- Claude Code (or another MCP-compatible client)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
For the fastest onboarding, use the interactive setup helper:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm run setup # Choose a provider and configure .env (interactive)
|
||||||
|
npm test # Verify everything works
|
||||||
|
npm start # Start the MCP server
|
||||||
|
```
|
||||||
|
|
||||||
|
The setup helper guides you through choosing between `openai`, `manual`, or `ollama` providers, then creates your `.env` and optionally `.claude/settings.local.json`.
|
||||||
|
|
||||||
|
See [docs/SETUP.md](./docs/SETUP.md) for full documentation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Install dependencies:
|
Install dependencies:
|
||||||
@@ -234,6 +253,35 @@ After opening the project in Claude Code, the server should be automatically dis
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## V1 Milestone
|
||||||
|
|
||||||
|
**Status: V1 Complete**
|
||||||
|
|
||||||
|
### Implemented
|
||||||
|
|
||||||
|
- OpenAI provider (GPT-5.1 via Responses API)
|
||||||
|
- Manual Export provider (copy-paste-ready prompts for ChatGPT Web)
|
||||||
|
- Ollama provider (local AI via `qwen3.6:35b-a3b`)
|
||||||
|
- Provider abstraction layer with factory pattern
|
||||||
|
- Context budget enforcement and secret redaction
|
||||||
|
- 5 MCP review tools (`ask_chatgpt`, `review_plan`, `review_code`, `debug_issue`, `architecture_review`)
|
||||||
|
- Interactive local setup helper (`npm run setup`)
|
||||||
|
- 706 automated tests across 22 test files
|
||||||
|
|
||||||
|
### Capabilities
|
||||||
|
|
||||||
|
The ChatGPT MCP Server provides structured second-opinion workflows for:
|
||||||
|
|
||||||
|
- General questions and alternative viewpoints
|
||||||
|
- Implementation plan reviews
|
||||||
|
- Code reviews
|
||||||
|
- Debugging investigations
|
||||||
|
- Architecture reviews
|
||||||
|
|
||||||
|
All responses are advisory-only. Claude Code remains the primary coding agent.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Current Status
|
## Current Status
|
||||||
|
|
||||||
### MVP Complete
|
### MVP Complete
|
||||||
@@ -250,7 +298,8 @@ Implemented:
|
|||||||
- MCP stdio server
|
- MCP stdio server
|
||||||
- Registration of all five MCP tools
|
- Registration of all five MCP tools
|
||||||
- Claude Code integration documentation
|
- Claude Code integration documentation
|
||||||
- Comprehensive automated test suite (644 tests across 21 files)
|
- Comprehensive automated test suite (701 tests across 22 files)
|
||||||
|
- Interactive local setup helper (`npm run setup`)
|
||||||
|
|
||||||
### Next Steps
|
### Next Steps
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# TASKS.md
|
# TASKS.md
|
||||||
|
|
||||||
## Phase 0 - Repository Setup
|
## Phase 0 — Repository Setup
|
||||||
|
|
||||||
### Task 0.1 - Create repository skeleton
|
### Task 0.1 - Create repository skeleton
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ Status: ✅ Complete
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Phase 1 - Core Utilities
|
## Phase 1 — Core Utilities
|
||||||
|
|
||||||
### Task 1.1 - Configuration loader
|
### Task 1.1 - Configuration loader
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@ Status: ✅ Complete
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Phase 2 - OpenAI Integration
|
## Phase 2 — OpenAI Integration
|
||||||
|
|
||||||
### Task 2.1 - OpenAI client wrapper
|
### Task 2.1 - OpenAI client wrapper
|
||||||
|
|
||||||
@@ -409,7 +409,7 @@ Phase 2 complete. Phase 3 complete. Phase 4 complete. Phase 5 next: MCP Server a
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Phase 5 - MCP Server and Tool Registration
|
## Phase 5 — MCP Server and Tool Registration
|
||||||
|
|
||||||
### Task 5.1 - MCP server skeleton
|
### Task 5.1 - MCP server skeleton
|
||||||
|
|
||||||
@@ -677,44 +677,9 @@ Status: ✅ Complete
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Phase 9 — Ollama Provider Support
|
## Phase 8 — Manual Export Provider
|
||||||
|
|
||||||
### Task 9.1 — Ollama provider implementation ✅
|
### Task 8.0 — Implement Manual Export Provider
|
||||||
|
|
||||||
Create `src/providers/ollama.js` and integrate into the provider factory.
|
|
||||||
|
|
||||||
**Implementation:**
|
|
||||||
- Uses Ollama `/api/chat` endpoint (OpenAI-compatible format) via native `fetch()` — zero new dependencies
|
|
||||||
- Implements `{ send(reviewRequest, config) => Promise<{ content: string }> }` provider interface
|
|
||||||
- Error categories: `OllamaTimeoutError`, `OllamaModelNotFoundError`, `OllamaValidationError`, `OllamaApiNotAvailableError`, `OllamaRequestError`
|
|
||||||
|
|
||||||
**Defaults:**
|
|
||||||
| Setting | Default Value |
|
|
||||||
|---------|---------------|
|
|
||||||
| Base URL | `http://localhost:11434` |
|
|
||||||
| Model | `qwen3:latest` |
|
|
||||||
| Temperature | `0.2` |
|
|
||||||
| Timeout | `60` seconds |
|
|
||||||
|
|
||||||
**Environment variables:**
|
|
||||||
```env
|
|
||||||
OLLAMA_BASE_URL=http://localhost:11434 # or custom Ollama endpoint
|
|
||||||
OLLAMA_MODEL=qwen3:latest # default model for chat
|
|
||||||
OLLAMA_TEMPERATURE=0.2 # sampling temperature
|
|
||||||
OLLAMA_TIMEOUT=60 # request timeout in seconds
|
|
||||||
```
|
|
||||||
|
|
||||||
**Factory integration:**
|
|
||||||
- `"ollama"` added to SUPPORTED_PROVIDERS whitelist: `Set(["openai", "manual", "ollama"])`
|
|
||||||
- `CHATGPT_MCP_PROVIDER=ollama` switches all tool handlers to local Ollama mode
|
|
||||||
- Defaults to `"openai"` when not set — OpenAI behaviour unchanged
|
|
||||||
- Same provider interface as openai and manual providers
|
|
||||||
|
|
||||||
**Status:** ✅ Complete
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 8.0 — Implement ReviewRequest and Manual Export Provider
|
|
||||||
|
|
||||||
Add a zero-API-cost Manual Export provider that generates copy/paste-ready prompts for ChatGPT Web (https://chatgpt.com).
|
Add a zero-API-cost Manual Export provider that generates copy/paste-ready prompts for ChatGPT Web (https://chatgpt.com).
|
||||||
|
|
||||||
@@ -744,6 +709,77 @@ Status: ✅ Complete
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Phase 9 — Ollama Provider Support
|
||||||
|
|
||||||
|
### Task 9.1 — Ollama Provider Implementation
|
||||||
|
|
||||||
|
Add a local AI provider using Ollama's `/api/chat` endpoint with Qwen3 for automated second-opinion queries without cloud dependencies.
|
||||||
|
|
||||||
|
**Provider implementation:**
|
||||||
|
- `src/providers/ollama.js` — Zero-API-cost provider using native `fetch()` (zero new dependencies)
|
||||||
|
- Implements `{ send(reviewRequest, config) => Promise<{ content: string }> }` interface
|
||||||
|
- Builds OpenAI-compatible chat request format (`model`, `messages`, `stream`, `options`)
|
||||||
|
- Extracts `data.message.content` from Ollama response
|
||||||
|
- AbortController-based timeout for configurable request duration
|
||||||
|
- Response parsing handles non-2xx status codes with detailed error categorization
|
||||||
|
|
||||||
|
**Response format:**
|
||||||
|
```js
|
||||||
|
// Request (to /api/chat):
|
||||||
|
{ model: "qwen3:latest", messages: [{ role: "system", content }], stream: false, options: { temperature } }
|
||||||
|
|
||||||
|
// Response:
|
||||||
|
{ model: "...", message: { role: "assistant", content: "..." }, done: true }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Error categories:**
|
||||||
|
| Category | Trigger | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| `OllamaTimeoutError` | AbortError / timeout | Request exceeded configured timeout |
|
||||||
|
| `OllamaModelNotFoundError` | HTTP 404 | Model not available in Ollama library |
|
||||||
|
| `OllamaValidationError` | HTTP 422 | Invalid model parameters or malformed request |
|
||||||
|
| `OllamaApiNotAvailableError` | HTTP 501 | Ollama server not running / endpoint unavailable |
|
||||||
|
| `OllamaRequestError` | Other errors | Fallback category for unexpected failures |
|
||||||
|
|
||||||
|
**Defaults:**
|
||||||
|
- Base URL: `http://localhost:11434`
|
||||||
|
- Model: `qwen3:latest` (alias for `qwen3.6:35b-a3b`)
|
||||||
|
- Temperature: `0.2`
|
||||||
|
- Timeout: `60` seconds
|
||||||
|
|
||||||
|
**Environment variables:**
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| `OLLAMA_BASE_URL` | `http://localhost:11434` | Ollama API endpoint (strips trailing slashes) |
|
||||||
|
| `OLLAMA_MODEL` | `qwen3:latest` | Model name for chat requests |
|
||||||
|
| `OLLAMA_TEMPERATURE` | `0.2` | Sampling temperature |
|
||||||
|
| `OLLAMA_TIMEOUT` | `60` | Request timeout in seconds |
|
||||||
|
|
||||||
|
**Factory integration:**
|
||||||
|
- `"ollama"` added to SUPPORTED_PROVIDERS whitelist in `src/providers/factory.js`: `Set(["openai", "manual", "ollama"])`
|
||||||
|
- `CHATGPT_MCP_PROVIDER=ollama` switches all tool handlers to local Ollama mode
|
||||||
|
- Defaults to `"openai"` when not set — OpenAI behaviour unchanged
|
||||||
|
- Same provider interface as openai and manual providers
|
||||||
|
|
||||||
|
### Current provider status
|
||||||
|
|
||||||
|
| Provider | Env Value | Type | Requires API key? |
|
||||||
|
|----------|-----------|------|-------------------|
|
||||||
|
| `openai` | `CHATGPT_MCP_PROVIDER=openai` | Cloud (OpenAI Responses API) | Yes (`OPENAI_API_KEY`) |
|
||||||
|
| `manual` | `CHATGPT_MCP_PROVIDER=manual` | Local (copy-paste) | No |
|
||||||
|
| `ollama` | `CHATGPT_MCP_PROVIDER=ollama` | Local (Ollama /api/chat) | No |
|
||||||
|
|
||||||
|
### Phase 9 Completion Summary
|
||||||
|
|
||||||
|
Three providers now supported: `openai`, `manual`, `ollama`.
|
||||||
|
|
||||||
|
- Factory in `src/providers/factory.js`: `SUPPORTED_PROVIDERS = Set(["openai", "manual", "ollama"])`
|
||||||
|
- All three providers implement `{ send(request, config) => Promise<{ content: string }> }`
|
||||||
|
- Provider selection via `CHATGPT_MCP_PROVIDER` environment variable
|
||||||
|
- Zero additional dependencies — Ollama provider uses native `fetch()` only
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Task 10.0 — End-to-End Workflow Validation ✅
|
## Task 10.0 — End-to-End Workflow Validation ✅
|
||||||
|
|
||||||
**Date:** 2026-06-15
|
**Date:** 2026-06-15
|
||||||
@@ -842,7 +878,7 @@ Each scenario was evaluated across all three providers for: output quality, accu
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
#### 5. WEAKNESES OF EACH PROVIDER
|
#### 5. WEAKNESSES OF EACH PROVIDER
|
||||||
|
|
||||||
**OpenAI Provider:**
|
**OpenAI Provider:**
|
||||||
- Requires valid `OPENAI_API_KEY` — fails silently if missing (not detected by config loader)
|
- Requires valid `OPENAI_API_KEY` — fails silently if missing (not detected by config loader)
|
||||||
@@ -899,7 +935,7 @@ Two defects were found during validation:
|
|||||||
|
|
||||||
**Defect 1 — Default Ollama model name mismatch (MEDIUM)**
|
**Defect 1 — Default Ollama model name mismatch (MEDIUM)**
|
||||||
|
|
||||||
The config defaults (`src/config/env.js` line 45, `ARCHITECTATION.md` section 12, `README.md` section 18) specify `OLLAMA_MODEL=qwen3:latest`, but the available Ollama models list shows:
|
The config defaults (`src/config/env.js` line 45, `ARCHITECTURE.md` section 12, `README.md` section 18) specify `OLLAMA_MODEL=qwen3:latest`, but the available Ollama models list shows:
|
||||||
- `qwen3.6:35b-a3b`
|
- `qwen3.6:35b-a3b`
|
||||||
- `qwen3.6:35b`
|
- `qwen3.6:35b`
|
||||||
- `qwen2.5-coder:32b`
|
- `qwen2.5-coder:32b`
|
||||||
@@ -961,6 +997,49 @@ The project is **validation-complete** with no blocking issues. The three-provid
|
|||||||
|
|
||||||
**The current functionality already satisfies the project goals.** No new features are recommended at this time. The only action items are the two defect fixes identified in Section 8.
|
**The current functionality already satisfies the project goals.** No new features are recommended at this time. The only action items are the two defect fixes identified in Section 8.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 10.3 — Local Setup Helper ✅
|
||||||
|
|
||||||
|
**Status:** Implemented and tested.
|
||||||
|
|
||||||
|
### What was implemented
|
||||||
|
|
||||||
|
- `scripts/setup.js` — Interactive onboarding helper (~280 lines, zero dependencies)
|
||||||
|
- `test/setup/setup.test.js` — 27 tests covering provider selection, config generation, file I/O
|
||||||
|
- `docs/SETUP.md` — Full documentation for the setup helper
|
||||||
|
- README.md quick-start section added
|
||||||
|
- package.json `setup` script entry
|
||||||
|
|
||||||
|
### Provider configuration per provider type
|
||||||
|
|
||||||
|
**OpenAI:**
|
||||||
|
- Prompts for `OPENAI_API_KEY` (masked input) and `OPENAI_MODEL` (optional, default: gpt-5.1)
|
||||||
|
- Sets `CHATGPT_MCP_PROVIDER=openai`
|
||||||
|
|
||||||
|
**Manual Export:**
|
||||||
|
- No prompts — sets `CHATGPT_MCP_PROVIDER=manual` immediately
|
||||||
|
|
||||||
|
**Ollama:**
|
||||||
|
- Prompts for `OLLAMA_BASE_URL` (default: http://localhost:11434), `OLLAMA_MODEL` (required, default: qwen3.6:35b-a3b), `OLLAMA_TEMPERATURE` (default: 0.2), `OLLAMA_TIMEOUT` (default: 60)
|
||||||
|
- Sets `CHATGPT_MCP_PROVIDER=ollama`
|
||||||
|
|
||||||
|
### Safety measures
|
||||||
|
|
||||||
|
- No network requests
|
||||||
|
- No secrets printed to console (masked input, redacted display as `sk-***`)
|
||||||
|
- User confirmation required before any file is written
|
||||||
|
- Only project-local files modified (`.env`, `.claude/settings.local.json`)
|
||||||
|
- `.env` and `.claude/` remain in `.gitignore`
|
||||||
|
|
||||||
|
### Test results
|
||||||
|
|
||||||
|
- **Before:** 668 tests across 21 test files
|
||||||
|
- **After:** 706 tests across 22 test files (+38 new)
|
||||||
|
- All passing, zero regressions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
| Phase | Description | Status |
|
| Phase | Description | Status |
|
||||||
|-------|-------------|--------|
|
|-------|-------------|--------|
|
||||||
| 0 | Repository Setup | ✅ Complete |
|
| 0 | Repository Setup | ✅ Complete |
|
||||||
@@ -970,8 +1049,9 @@ The project is **validation-complete** with no blocking issues. The three-provid
|
|||||||
| 4 | Tool Handlers | ✅ Complete (5 handlers, 139 tests) |
|
| 4 | Tool Handlers | ✅ Complete (5 handlers, 139 tests) |
|
||||||
| 5 | MCP Server and Registration | ✅ Complete (5 tools registered) |
|
| 5 | MCP Server and Registration | ✅ Complete (5 tools registered) |
|
||||||
| 6 | Provider Abstraction | ✅ Complete (factory + adapter) |
|
| 6 | Provider Abstraction | ✅ Complete (factory + adapter) |
|
||||||
| 7 | Integration and Tests | ✅ Complete (579 tests across 20 files) |
|
| 7 | Integration and Tests | ✅ Complete (701 tests across 22 files) |
|
||||||
| 8 | Manual Export Provider | ✅ Complete (Task 8.0, 644 tests across 21 files) |
|
| 8 | Manual Export Provider | ✅ Complete (Task 8.0, 706 tests across 22 files) |
|
||||||
| 9 | Ollama Provider Support | ✅ Complete (Task 9.1, 644 tests across 21 files) |
|
| 9 | Ollama Provider Support | ✅ Complete (Task 9.1, 706 tests across 22 files) |
|
||||||
|
| 10 | Local Setup Helper | ✅ Complete (Task 10.3, 706 tests across 22 files) |
|
||||||
|
|
||||||
**Total:** All planned MVP tasks complete. 644 passing tests, zero regressions, all docs updated. **3 supported providers: openai, manual, ollama.**
|
**Total:** All planned MVP tasks complete. 706 passing tests across 22 test files, zero regressions, all docs updated. **3 supported providers: openai, manual, ollama.**
|
||||||
|
|||||||
+148
@@ -0,0 +1,148 @@
|
|||||||
|
# Local Setup Helper — Documentation
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The setup helper is a small interactive onboarding tool for the ChatGPT MCP Server. It reduces configuration friction when choosing between the three supported providers: `openai`, `manual`, and `ollama`.
|
||||||
|
|
||||||
|
**Run it with:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run setup
|
||||||
|
```
|
||||||
|
|
||||||
|
## What it does
|
||||||
|
|
||||||
|
1. **Asks you to choose a provider** — OpenAI, Manual Export, or Ollama
|
||||||
|
2. **Prompts for provider-specific settings** — API keys, model names, URLs, etc.
|
||||||
|
3. **Writes a clean `.env` file** — with your selected configuration (asks confirmation first)
|
||||||
|
4. **Optionally creates `.claude/settings.local.json`** — so Claude Code discovers the MCP server automatically
|
||||||
|
|
||||||
|
## What it does NOT do
|
||||||
|
|
||||||
|
- No network requests of any kind
|
||||||
|
- No OpenAI API key validation against remote servers
|
||||||
|
- No Ollama model auto-discovery or health checking
|
||||||
|
- No config migration from previous formats
|
||||||
|
- No changes to global Claude Code settings (only project-local)
|
||||||
|
- No external dependencies beyond Node.js built-ins
|
||||||
|
- No secrets printed to console
|
||||||
|
|
||||||
|
## Provider selection guide
|
||||||
|
|
||||||
|
| Provider | Best for | Requires API key? | Cost |
|
||||||
|
|----------|----------|-------------------|------|
|
||||||
|
| `openai` | Automated second-opinion queries with best-in-class reasoning | Yes (`OPENAI_API_KEY`) | ~$0.003–$0.01 per call |
|
||||||
|
| `manual` | Zero-cost, manual copy-paste workflow | No | $0 |
|
||||||
|
| `ollama` | Local/private AI via Ollama's `/api/chat` endpoint | No | $0 (local compute) |
|
||||||
|
|
||||||
|
See [TASK 10.0 validation report](../TASKS.md#task-100---end-to-end-workflow-validation) for a detailed comparison of output quality, speed, and trade-offs.
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
|
||||||
|
### .env generation
|
||||||
|
|
||||||
|
The helper generates a **clean** `.env` file with only the keys relevant to your chosen provider:
|
||||||
|
|
||||||
|
- `CHATGPT_MCP_PROVIDER=<selected>` — always written
|
||||||
|
- `OPENAI_API_KEY`, `OPENAI_MODEL` — for `openai` provider only
|
||||||
|
- `OLLAMA_BASE_URL`, `OLLAMA_MODEL`, `OLLAMA_TEMPERATURE`, `OLLAMA_TIMEOUT` — for `ollama` provider only
|
||||||
|
|
||||||
|
If an existing `.env` file is detected, non-conflicting keys are preserved in a separate section. Provider-specific keys from the old file are **not** carried over.
|
||||||
|
|
||||||
|
### .claude/settings.local.json generation
|
||||||
|
|
||||||
|
Optionally creates (or overwrites) a project-local Claude Code discovery config:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"chatgpt-mcp": {
|
||||||
|
"command": "npm",
|
||||||
|
"args": ["start"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You will be prompted before any file is created or overwritten. Existing files that are **not** `settings.local.json` (e.g., other MCP servers) are never modified.
|
||||||
|
|
||||||
|
## Safety rules
|
||||||
|
|
||||||
|
1. **No secrets printed.** API key input uses terminal masking; if displayed, it shows as `sk-***`.
|
||||||
|
2. **Confirmation required.** You must explicitly confirm before any file is written.
|
||||||
|
3. **No network calls.** The helper only reads/writes local files and prompts for input.
|
||||||
|
4. **No global settings changes.** Only `.claude/settings.local.json` (project-local).
|
||||||
|
5. **Git-safe.** Both `.env` and `.claude/` are in `.gitignore` — nothing is committed.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### "Setup helper requires an interactive terminal"
|
||||||
|
|
||||||
|
The helper detects whether stdin is a TTY. If you're piping or running in CI, configure manually via `.env`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo "CHATGPT_MCP_PROVIDER=openai" >> .env
|
||||||
|
echo "OPENAI_API_KEY=sk-your-key" >> .env
|
||||||
|
```
|
||||||
|
|
||||||
|
### I want to change providers later
|
||||||
|
|
||||||
|
Either edit `.env` directly:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nano .env # change CHATGPT_MCP_PROVIDER and the provider-specific keys
|
||||||
|
```
|
||||||
|
|
||||||
|
Or re-run `npm run setup` — it will generate a new clean `.env`.
|
||||||
|
|
||||||
|
### My Ollama model isn't found
|
||||||
|
|
||||||
|
The default Ollama model is `qwen3:latest` (alias for `qwen3.6:35b-a3b`). If yours isn't available, check with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ollama list
|
||||||
|
```
|
||||||
|
|
||||||
|
Then set `OLLAMA_MODEL` in `.env` to one of your installed models.
|
||||||
|
|
||||||
|
### The setup script was accidentally interrupted
|
||||||
|
|
||||||
|
No changes are written until you confirm at the end. If only partial writes happened, restore from git:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git checkout -- .env .claude/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Manual configuration reference
|
||||||
|
|
||||||
|
If you prefer to configure manually without the helper, create or edit `.env` with:
|
||||||
|
|
||||||
|
### OpenAI
|
||||||
|
|
||||||
|
```env
|
||||||
|
CHATGPT_MCP_PROVIDER=openai
|
||||||
|
OPENAI_API_KEY=sk-your-key-here
|
||||||
|
OPENAI_MODEL=gpt-5.1
|
||||||
|
OPENAI_TEMPERATURE=0.2
|
||||||
|
OPENAI_MAX_OUTPUT_TOKENS=2000
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manual Export
|
||||||
|
|
||||||
|
```env
|
||||||
|
CHATGPT_MCP_PROVIDER=manual
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ollama
|
||||||
|
|
||||||
|
```env
|
||||||
|
CHATGPT_MCP_PROVIDER=ollama
|
||||||
|
OLLAMA_BASE_URL=http://localhost:11434
|
||||||
|
OLLAMA_MODEL=qwen3:latest
|
||||||
|
OLLAMA_TEMPERATURE=0.2
|
||||||
|
OLLAMA_TIMEOUT=60
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_This helper is intentionally small (one file, zero dependencies). See the [TASK 10.2 design](../TASKS.md#task-102-local-setup-helper) for the full specification._
|
||||||
+2
-1
@@ -6,7 +6,8 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node src/server.js",
|
"start": "node src/server.js",
|
||||||
"test": "vitest"
|
"test": "vitest",
|
||||||
|
"setup": "node scripts/setup.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "^1.7.0",
|
"@modelcontextprotocol/sdk": "^1.7.0",
|
||||||
|
|||||||
@@ -0,0 +1,321 @@
|
|||||||
|
// ChatGPT MCP Server — Local Setup Helper
|
||||||
|
// A simple interactive onboarding script. No external dependencies.
|
||||||
|
// Usage: npm run setup
|
||||||
|
|
||||||
|
import readline from "node:readline";
|
||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import tty from "node:tty";
|
||||||
|
|
||||||
|
const SUPPORTED_PROVIDERS = ["openai", "manual", "ollama"];
|
||||||
|
|
||||||
|
const ROOT_DIR = path.resolve(import.meta.dirname, "..");
|
||||||
|
|
||||||
|
// ── Helpers ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
function maskedInput(prompt) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
||||||
|
|
||||||
|
// Detect if stdin is a TTY (interactive terminal).
|
||||||
|
const isTTY = process.stdin.isTTY && typeof process.stdin.setRawMode === "function";
|
||||||
|
|
||||||
|
if (!isTTY) {
|
||||||
|
console.log("\n⚠ Non-interactive terminal detected — input will be visible.");
|
||||||
|
}
|
||||||
|
|
||||||
|
rl.question(prompt + ": ", (answer) => resolve(answer));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getProviderDefault() {
|
||||||
|
const envPath = path.join(ROOT_DIR, ".env");
|
||||||
|
if (fs.existsSync(envPath)) {
|
||||||
|
const content = fs.readFileSync(envPath, "utf-8");
|
||||||
|
const match = content.match(/^CHATGPT_MCP_PROVIDER\s*=\s*(\S+)/im);
|
||||||
|
if (match) return match[1];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function promptProvider() {
|
||||||
|
const existing = getProviderDefault();
|
||||||
|
console.log("\nSelect a provider:");
|
||||||
|
console.log(" [1] openai — Automated second-opinion queries (requires API key)");
|
||||||
|
console.log(" [2] manual — Copy-paste prompts into ChatGPT Web (no API key needed)");
|
||||||
|
console.log(" [3] ollama — Local AI via Ollama (no API key needed)");
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
||||||
|
const hint = existing && SUPPORTED_PROVIDERS.includes(existing) ? ` (default: ${existing})` : "";
|
||||||
|
|
||||||
|
rl.question(`\nProvider${hint}: `, (input) => {
|
||||||
|
rl.close();
|
||||||
|
|
||||||
|
// Allow default by empty input
|
||||||
|
if (!input.trim()) {
|
||||||
|
return resolve(existing || "openai");
|
||||||
|
}
|
||||||
|
|
||||||
|
const map = { 1: "openai", 2: "manual", 3: "ollama" };
|
||||||
|
const selected = map[input.trim()];
|
||||||
|
if (SUPPORTED_PROVIDERS.includes(selected)) {
|
||||||
|
return resolve(selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\nInvalid selection: ${input.trim()}. Try again.\n`);
|
||||||
|
return resolve(promptProvider()); // retry
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function promptRequired(label, defaultVal) {
|
||||||
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
function ask() {
|
||||||
|
const hint = defaultVal ? ` (default: ${defaultVal})` : "";
|
||||||
|
rl.question(`${label}${hint}: `, (answer) => {
|
||||||
|
if (defaultVal && !answer.trim()) {
|
||||||
|
rl.close();
|
||||||
|
return resolve(defaultVal);
|
||||||
|
}
|
||||||
|
if (!answer.trim()) {
|
||||||
|
console.log(" This field is required. Please enter a value.");
|
||||||
|
return ask();
|
||||||
|
}
|
||||||
|
rl.close();
|
||||||
|
resolve(answer.trim());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ask();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function promptOptional(label, defaultVal) {
|
||||||
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const hint = defaultVal ? ` (default: ${defaultVal})` : "";
|
||||||
|
rl.question(`${label}${hint}: `, (answer) => {
|
||||||
|
rl.close();
|
||||||
|
if (!answer.trim() && defaultVal) {
|
||||||
|
return resolve(defaultVal);
|
||||||
|
}
|
||||||
|
return resolve(answer.trim());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function promptConfirm(message) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
||||||
|
rl.question(`${message} [y/N]: `, (answer) => {
|
||||||
|
rl.close();
|
||||||
|
resolve(["y", "yes"].includes(answer.trim().toLowerCase()));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── .env generation ───────────────────────────────────────
|
||||||
|
|
||||||
|
function buildEnvContent(provider, config) {
|
||||||
|
const lines = [];
|
||||||
|
|
||||||
|
// Provider section header
|
||||||
|
lines.push(`# Chat provider: "openai", "manual", or "ollama"`);
|
||||||
|
lines.push(`CHATGPT_MCP_PROVIDER=${provider}`);
|
||||||
|
lines.push("");
|
||||||
|
|
||||||
|
if (provider === "openai") {
|
||||||
|
lines.push("# OpenAI provider settings");
|
||||||
|
lines.push(`OPENAI_API_KEY=${config.openaiApiKey || ""}`);
|
||||||
|
if (config.openaiModel) lines.push(`OPENAI_MODEL=${config.openaiModel}`);
|
||||||
|
lines.push("");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (provider === "ollama") {
|
||||||
|
lines.push("# Ollama provider settings");
|
||||||
|
if (config.ollamaBaseUrl) lines.push(`OLLAMA_BASE_URL=${config.ollamaBaseUrl}`);
|
||||||
|
if (config.ollamaModel) lines.push(`OLLAMA_MODEL=${config.ollamaModel}`);
|
||||||
|
if (config.ollamaTemperature !== undefined && config.ollamaTemperature !== null)
|
||||||
|
lines.push(`OLLAMA_TEMPERATURE=${config.ollamaTemperature}`);
|
||||||
|
if (config.ollamaTimeout !== undefined && config.ollamaTimeout !== null)
|
||||||
|
lines.push(`OLLAMA_TIMEOUT=${config.ollamaTimeout}`);
|
||||||
|
lines.push("");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append existing .env content that doesn't conflict with our keys.
|
||||||
|
const envPath = path.join(ROOT_DIR, ".env");
|
||||||
|
if (fs.existsSync(envPath)) {
|
||||||
|
const existingContent = fs.readFileSync(envPath, "utf-8");
|
||||||
|
const existingKeys = new Set([
|
||||||
|
"CHATGPT_MCP_PROVIDER",
|
||||||
|
"OPENAI_API_KEY",
|
||||||
|
"OPENAI_MODEL",
|
||||||
|
"OLLAMA_BASE_URL",
|
||||||
|
"OLLAMA_MODEL",
|
||||||
|
"OLLAMA_TEMPERATURE",
|
||||||
|
"OLLAMA_TIMEOUT",
|
||||||
|
]);
|
||||||
|
|
||||||
|
const otherLines = existingContent
|
||||||
|
.split("\n")
|
||||||
|
.filter((line) => {
|
||||||
|
if (line.trim().startsWith("#")) return true;
|
||||||
|
const keyMatch = line.match(/^(\w+)/);
|
||||||
|
if (!keyMatch) return true;
|
||||||
|
return !existingKeys.has(keyMatch[1]);
|
||||||
|
})
|
||||||
|
// Deduplicate: keep last occurrence of each non-comment line
|
||||||
|
.reduceRight((acc, line) => {
|
||||||
|
const trimmed = line.trim();
|
||||||
|
if (trimmed && acc.includes(trimmed)) return acc;
|
||||||
|
if (trimmed === "") return acc;
|
||||||
|
return [line, ...acc];
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (otherLines.length > 0) {
|
||||||
|
lines.push("# ── Other settings (preserved from existing .env) ──");
|
||||||
|
lines.push(...otherLines);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure trailing newline
|
||||||
|
return lines.join("\n") + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Claude config generation ──────────────────────────────
|
||||||
|
|
||||||
|
function buildClaudeConfig() {
|
||||||
|
return JSON.stringify(
|
||||||
|
{
|
||||||
|
mcpServers: {
|
||||||
|
"chatgpt-mcp": {
|
||||||
|
command: "npm",
|
||||||
|
args: ["start"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Exports for testing ─────────────────────────────────
|
||||||
|
|
||||||
|
export { SUPPORTED_PROVIDERS, buildEnvContent, buildClaudeConfig };
|
||||||
|
|
||||||
|
// ── Main flow ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.log("\n╔══════════════════════════════════════════════╗");
|
||||||
|
console.log("║ ChatGPT MCP Server — Local Setup Helper ║");
|
||||||
|
console.log("╚══════════════════════════════════════════════╝\n");
|
||||||
|
|
||||||
|
// Step 1: Provider selection
|
||||||
|
const provider = await promptProvider();
|
||||||
|
console.log(`\n✓ Selected provider: ${provider}`);
|
||||||
|
|
||||||
|
// Step 2: Provider-specific config prompts
|
||||||
|
let config;
|
||||||
|
if (provider === "openai") {
|
||||||
|
config = { openaiApiKey: "", openaiModel: null };
|
||||||
|
// Mask the API key input by disabling terminal echo temporarily.
|
||||||
|
const isTTY = process.stdin.isTTY && typeof process.stdin.setRawMode === "function";
|
||||||
|
if (isTTY) {
|
||||||
|
process.stdin.setRawMode(true);
|
||||||
|
process.stdout.write("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
const rlKey = readline.createInterface({ input: process.stdin, output: process.stdout });
|
||||||
|
config.openaiApiKey = await new Promise((resolve) => {
|
||||||
|
console.log("\nOPENAI_API_KEY (required — not shown as you type):");
|
||||||
|
rlKey.question(" Key: ", resolve);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isTTY) process.stdin.setRawMode(false);
|
||||||
|
rlKey.close();
|
||||||
|
process.stdout.write("\n"); // newline after hidden input
|
||||||
|
|
||||||
|
config.openaiModel = await promptOptional("OPENAI_MODEL", "gpt-5.1");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (provider === "ollama") {
|
||||||
|
config = {};
|
||||||
|
config.ollamaBaseUrl = await promptOptional("OLLAMA_BASE_URL", "http://localhost:11434");
|
||||||
|
config.ollamaModel = await promptRequired("OLLAMA_MODEL", "qwen3.6:35b-a3b");
|
||||||
|
config.ollamaTemperature = await promptOptional("OLLAMA_TEMPERATURE", "0.2");
|
||||||
|
config.ollamaTimeout = await promptOptional("OLLAMA_TIMEOUT", "60");
|
||||||
|
}
|
||||||
|
|
||||||
|
// manual provider — no prompts needed (config is empty object)
|
||||||
|
if (provider === "manual") {
|
||||||
|
config = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Generate and confirm .env
|
||||||
|
const envContent = buildEnvContent(provider, config);
|
||||||
|
console.log("\n" + "─".repeat(52));
|
||||||
|
console.log("The following will be written to .env:\n");
|
||||||
|
process.stdout.write(envContent);
|
||||||
|
console.log("─".repeat(52) + "\n");
|
||||||
|
|
||||||
|
const confirmEnv = await promptConfirm("Overwrite .env with this configuration?");
|
||||||
|
if (!confirmEnv) {
|
||||||
|
console.log("\n✗ Setup cancelled. No changes were made.");
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const envPath = path.join(ROOT_DIR, ".env");
|
||||||
|
fs.writeFileSync(envPath, envContent, "utf-8");
|
||||||
|
console.log("✓ .env updated.\n");
|
||||||
|
|
||||||
|
// Step 4: Optionally create Claude Code config
|
||||||
|
const claudeDir = path.join(ROOT_DIR, ".claude");
|
||||||
|
const claudeConfigPath = path.join(claudeDir, "settings.local.json");
|
||||||
|
|
||||||
|
let confirmClaude = false;
|
||||||
|
if (fs.existsSync(claudeConfigPath)) {
|
||||||
|
console.log(`⚠ ${claudeConfigPath} already exists.`);
|
||||||
|
confirmClaude = await promptConfirm("Overwrite it?");
|
||||||
|
} else {
|
||||||
|
console.log(".claude/ directory not found — will be created.");
|
||||||
|
// Check if .claude dir itself exists but settings doesn't
|
||||||
|
if (!fs.existsSync(claudeDir)) {
|
||||||
|
console.log("Creating .claude/ directory...");
|
||||||
|
fs.mkdirSync(claudeDir, { recursive: true });
|
||||||
|
}
|
||||||
|
confirmClaude = await promptConfirm("Create .claude/settings.local.json?");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (confirmClaude) {
|
||||||
|
if (!fs.existsSync(claudeDir)) {
|
||||||
|
fs.mkdirSync(claudeDir, { recursive: true });
|
||||||
|
}
|
||||||
|
fs.writeFileSync(claudeConfigPath, buildClaudeConfig() + "\n", "utf-8");
|
||||||
|
console.log("✓ .claude/settings.local.json created.\n");
|
||||||
|
} else {
|
||||||
|
console.log(".claude/settings.local.json was skipped.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Done
|
||||||
|
console.log("╔══════════════════════════════════════════════╗");
|
||||||
|
console.log("║ Setup complete! ║");
|
||||||
|
console.log("╚══════════════════════════════════════════════╝\n");
|
||||||
|
console.log("Next steps:");
|
||||||
|
console.log(" npm test — Run the test suite");
|
||||||
|
console.log(" npm start — Start the MCP server");
|
||||||
|
console.log(` CHATGPT_MCP_PROVIDER=${provider} (active provider)`);
|
||||||
|
console.log("");
|
||||||
|
|
||||||
|
if (provider === "ollama") {
|
||||||
|
console.log("Tip: verify your Ollama setup with: ollama list");
|
||||||
|
console.log("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((err) => {
|
||||||
|
console.error(`Setup failed: ${err.message}`);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
@@ -0,0 +1,316 @@
|
|||||||
|
import { describe, it, expect, vi } from "vitest";
|
||||||
|
import path from "node:path";
|
||||||
|
import fs from "node:fs";
|
||||||
|
import { SUPPORTED_PROVIDERS, buildEnvContent, buildClaudeConfig } from "../../scripts/setup.js";
|
||||||
|
|
||||||
|
const TEST_TMP = path.join(process.cwd(), "test", "setup", ".tmp");
|
||||||
|
|
||||||
|
function cleanup() {
|
||||||
|
if (fs.existsSync(TEST_TMP)) {
|
||||||
|
fs.rmSync(TEST_TMP, { recursive: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("SUPPORTED_PROVIDERS constant", () => {
|
||||||
|
it('contains exactly openai, manual, ollama', () => {
|
||||||
|
expect(SUPPORTED_PROVIDERS).toEqual(["openai", "manual", "ollama"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is immutable (frozen Set or array)", () => {
|
||||||
|
expect(Array.isArray(SUPPORTED_PROVIDERS)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── .env content generation ───────────────────────────────
|
||||||
|
|
||||||
|
describe("buildEnvContent — OpenAI provider", () => {
|
||||||
|
it("includes CHATGPT_MCP_PROVIDER=openai", () => {
|
||||||
|
const result = buildEnvContent("openai", { openaiApiKey: "sk-test", openaiModel: null });
|
||||||
|
expect(result).toContain("CHATGPT_MCP_PROVIDER=openai");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("includes OPENAI_API_KEY when provided", () => {
|
||||||
|
const result = buildEnvContent("openai", { openaiApiKey: "sk-mykey123", openaiModel: null });
|
||||||
|
expect(result).toContain("OPENAI_API_KEY=sk-mykey123");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("includes OPENAI_MODEL when provided", () => {
|
||||||
|
const result = buildEnvContent("openai", { openaiApiKey: "sk-test", openaiModel: "gpt-4o" });
|
||||||
|
expect(result).toContain("OPENAI_MODEL=gpt-4o");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("omits OPENAI_MODEL key line when null/undefined", () => {
|
||||||
|
const resultA = buildEnvContent("openai", { openaiApiKey: "sk-test", openaiModel: null });
|
||||||
|
const lines = resultA.split("\n").map((l) => l.trim());
|
||||||
|
expect(lines.filter((l) => l.startsWith("OPENAI_MODEL="))).toHaveLength(0);
|
||||||
|
|
||||||
|
const resultB = buildEnvContent("openai", { openaiApiKey: "sk-test" });
|
||||||
|
const linesB = resultB.split("\n").map((l) => l.trim());
|
||||||
|
expect(linesB.filter((l) => l.startsWith("OPENAI_MODEL="))).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("includes empty OPENAI_API_KEY line when key is empty string", () => {
|
||||||
|
const result = buildEnvContent("openai", { openaiApiKey: "", openaiModel: null });
|
||||||
|
expect(result).toContain("OPENAI_API_KEY=");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ends with a trailing newline", () => {
|
||||||
|
const result = buildEnvContent("openai", { openaiApiKey: "sk-test", openaiModel: null });
|
||||||
|
expect(result.endsWith("\n")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("preserves existing non-conflicting .env content", () => {
|
||||||
|
// Create a fake .env for this test
|
||||||
|
const originalEnvPath = path.join(process.cwd(), ".env");
|
||||||
|
fs.writeFileSync(originalEnvPath, "SOME_OTHER_VAR=hello\nLLM_TEMP=0.5\n", "utf-8");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = buildEnvContent("openai", { openaiApiKey: "sk-test", openaiModel: null });
|
||||||
|
expect(result).toContain("# ── Other settings (preserved from existing .env) ──");
|
||||||
|
expect(result).toContain("SOME_OTHER_VAR=hello");
|
||||||
|
expect(result).toContain("LLM_TEMP=0.5");
|
||||||
|
} finally {
|
||||||
|
fs.unlinkSync(originalEnvPath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("filters out provider keys from existing .env content", () => {
|
||||||
|
const originalEnvPath = path.join(process.cwd(), ".env");
|
||||||
|
fs.writeFileSync(
|
||||||
|
originalEnvPath,
|
||||||
|
"CHATGPT_MCP_PROVIDER=ollama\nOPENAI_API_KEY=oldkey\nSOME_OTHER_VAR=hello\n",
|
||||||
|
"utf-8"
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = buildEnvContent("openai", { openaiApiKey: "sk-test", openaiModel: null });
|
||||||
|
// CHATGPT_MCP_PROVIDER should NOT appear in the preserved section (only in the new header)
|
||||||
|
const lines = result.split("\n");
|
||||||
|
// The provider key should only be in the generated header, not in the preserved section
|
||||||
|
const preservedSection = result.split("# ── Other settings")[1] || "";
|
||||||
|
expect(preservedSection).not.toContain("CHATGPT_MCP_PROVIDER=ollama");
|
||||||
|
expect(preservedSection).not.toContain("OPENAI_API_KEY=oldkey");
|
||||||
|
expect(preservedSection).toContain("SOME_OTHER_VAR=hello");
|
||||||
|
} finally {
|
||||||
|
fs.unlinkSync(originalEnvPath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("includes comment header for provider section", () => {
|
||||||
|
const result = buildEnvContent("openai", { openaiApiKey: "sk-test", openaiModel: null });
|
||||||
|
expect(result).toContain("# Chat provider:");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("buildEnvContent — Manual provider", () => {
|
||||||
|
it("includes CHATGPT_MCP_PROVIDER=manual", () => {
|
||||||
|
const result = buildEnvContent("manual", {});
|
||||||
|
expect(result).toContain("CHATGPT_MCP_PROVIDER=manual");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not include any API key lines for manual provider", () => {
|
||||||
|
const result = buildEnvContent("manual", {});
|
||||||
|
expect(result).not.toContain("OPENAI_API_KEY=");
|
||||||
|
expect(result).not.toContain("OLLAMA_BASE_URL=");
|
||||||
|
expect(result).not.toContain("OLLAMA_MODEL=");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ends with a trailing newline", () => {
|
||||||
|
const result = buildEnvContent("manual", {});
|
||||||
|
expect(result.endsWith("\n")).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("buildEnvContent — Ollama provider", () => {
|
||||||
|
it("includes CHATGPT_MCP_PROVIDER=ollama", () => {
|
||||||
|
const result = buildEnvContent("ollama", { ollamaBaseUrl: "http://localhost:11434", ollamaModel: "qwen3.6:35b-a3b" });
|
||||||
|
expect(result).toContain("CHATGPT_MCP_PROVIDER=ollama");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("includes OLLAMA_BASE_URL when provided", () => {
|
||||||
|
const result = buildEnvContent("ollama", { ollamaBaseUrl: "http://custom:11434", ollamaModel: "qwen3.6:35b-a3b" });
|
||||||
|
expect(result).toContain("OLLAMA_BASE_URL=http://custom:11434");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("includes OLLAMA_MODEL when provided", () => {
|
||||||
|
const result = buildEnvContent("ollama", { ollamaBaseUrl: "http://localhost:11434", ollamaModel: "qwen3.6:35b-a3b" });
|
||||||
|
expect(result).toContain("OLLAMA_MODEL=qwen3.6:35b-a3b");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("includes OLLAMA_TEMPERATURE when provided", () => {
|
||||||
|
const result = buildEnvContent("ollama", { ollamaBaseUrl: "http://localhost:11434", ollamaModel: "qwen3.6:35b-a3b", ollamaTemperature: 0.7 });
|
||||||
|
expect(result).toContain("OLLAMA_TEMPERATURE=0.7");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("includes OLLAMA_TIMEOUT when provided", () => {
|
||||||
|
const result = buildEnvContent("ollama", { ollamaBaseUrl: "http://localhost:11434", ollamaModel: "qwen3.6:35b-a3b", ollamaTimeout: 120 });
|
||||||
|
expect(result).toContain("OLLAMA_TIMEOUT=120");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("excludes Ollama keys from OpenAI config output", () => {
|
||||||
|
const result = buildEnvContent("openai", { openaiApiKey: "sk-test" });
|
||||||
|
expect(result).not.toContain("OLLAMA_BASE_URL=");
|
||||||
|
expect(result).not.toContain("OLLAMA_MODEL=");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ends with a trailing newline", () => {
|
||||||
|
const result = buildEnvContent("ollama", { ollamaBaseUrl: "http://localhost:11434", ollamaModel: "qwen3.6:35b-a3b" });
|
||||||
|
expect(result.endsWith("\n")).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Claude config generation ──────────────────────────────
|
||||||
|
|
||||||
|
describe("buildClaudeConfig", () => {
|
||||||
|
it("returns valid JSON string", () => {
|
||||||
|
const result = buildClaudeConfig();
|
||||||
|
const parsed = JSON.parse(result);
|
||||||
|
expect(parsed).toHaveProperty("mcpServers");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("contains chatgpt-mcp server entry", () => {
|
||||||
|
const result = buildClaudeConfig();
|
||||||
|
const parsed = JSON.parse(result);
|
||||||
|
expect(parsed.mcpServers).toHaveProperty("chatgpt-mcp");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sets command to npm", () => {
|
||||||
|
const result = buildClaudeConfig();
|
||||||
|
const parsed = JSON.parse(result);
|
||||||
|
expect(parsed.mcpServers["chatgpt-mcp"].command).toBe("npm");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets args to ["start"]', () => {
|
||||||
|
const result = buildClaudeConfig();
|
||||||
|
const parsed = JSON.parse(result);
|
||||||
|
expect(parsed.mcpServers["chatgpt-mcp"].args).toEqual(["start"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is pretty-printed (not minified)", () => {
|
||||||
|
const result = buildClaudeConfig();
|
||||||
|
// Pretty-printed JSON contains newlines and indentation
|
||||||
|
expect(result).toContain("\n");
|
||||||
|
expect(result).toContain(" ");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Provider selection validation (via supported providers set) ──
|
||||||
|
|
||||||
|
describe("Provider selection validation", () => {
|
||||||
|
it("rejects invalid provider values (not in SUPPORTED_PROVIDERS)", () => {
|
||||||
|
const invalid = ["anthropic", "claude", "", "OPENAI", "OpenAI", "ollama ", "1", "true"];
|
||||||
|
invalid.forEach((val) => {
|
||||||
|
expect(SUPPORTED_PROVIDERS).not.toContain(val);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("valid provider is in the supported list", () => {
|
||||||
|
SUPPORTED_PROVIDERS.forEach((provider) => {
|
||||||
|
expect(["openai", "manual", "ollama"]).toContain(provider);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── .env overwrite behavior (integration test via mock fs) ──
|
||||||
|
|
||||||
|
describe(".env file overwrite confirmation", () => {
|
||||||
|
it("generated content does not include secret key values in a masked form (key is written raw to file)", () => {
|
||||||
|
// The key IS written to the file raw — this test confirms no masking happens at write time.
|
||||||
|
// Masking only applies to display/console output.
|
||||||
|
const result = buildEnvContent("openai", { openaiApiKey: "sk-secret-key-12345", openaiModel: null });
|
||||||
|
expect(result).toContain("OPENAI_API_KEY=sk-secret-key-12345"); // written raw, masked only in display
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not include any Ollama setting keys in OpenAI config block", () => {
|
||||||
|
const result = buildEnvContent("openai", { openaiApiKey: "sk-test" });
|
||||||
|
expect(result).not.toMatch(/^OLLAMA_BASE_URL=/m);
|
||||||
|
expect(result).not.toMatch(/^OLLAMA_MODEL=/m);
|
||||||
|
expect(result).not.toMatch(/^OLLAMA_TEMPERATURE=/m);
|
||||||
|
expect(result).not.toMatch(/^OLLAMA_TIMEOUT=/m);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("generated content is deterministic (same input → same output)", () => {
|
||||||
|
const config = { openaiApiKey: "sk-deterministic-test", openaiModel: "gpt-5.1" };
|
||||||
|
const a = buildEnvContent("openai", config);
|
||||||
|
const b = buildEnvContent("openai", config);
|
||||||
|
expect(a).toBe(b);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejecting write does not produce any file side effects (integration)", () => {
|
||||||
|
// buildEnvContent never calls fs.writeFileSync — confirm .env is untouched.
|
||||||
|
const envPath = path.join(process.cwd(), ".env");
|
||||||
|
const existsBefore = fs.existsSync(envPath);
|
||||||
|
|
||||||
|
const content = buildEnvContent("manual", {});
|
||||||
|
expect(content).not.toBe("");
|
||||||
|
expect(fs.existsSync(envPath)).toBe(existsBefore); // unchanged
|
||||||
|
});
|
||||||
|
|
||||||
|
it("accepting write would overwrite .env with the generated content (integration)", () => {
|
||||||
|
const envPath = path.join(process.cwd(), ".env");
|
||||||
|
const beforeExists = fs.existsSync(envPath);
|
||||||
|
const originalContent = beforeExists ? fs.readFileSync(envPath, "utf-8") : null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Simulate acceptance: write the generated content to .env
|
||||||
|
const content = buildEnvContent("manual", {});
|
||||||
|
fs.writeFileSync(envPath, content, "utf-8");
|
||||||
|
expect(fs.readFileSync(envPath, "utf-8")).toBe(content);
|
||||||
|
expect(fs.readFileSync(envPath, "utf-8")).toContain("CHATGPT_MCP_PROVIDER=manual");
|
||||||
|
} finally {
|
||||||
|
// Restore original state
|
||||||
|
if (beforeExists && originalContent !== null) {
|
||||||
|
fs.writeFileSync(envPath, originalContent, "utf-8");
|
||||||
|
} else if (!beforeExists) {
|
||||||
|
fs.unlinkSync(envPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Non-interactive terminal handling", () => {
|
||||||
|
it("supports reading provider default from existing .env via getProviderDefault", () => {
|
||||||
|
const envPath = path.join(process.cwd(), ".env");
|
||||||
|
|
||||||
|
// Write a test .env with ollama provider
|
||||||
|
fs.writeFileSync(envPath, "CHATGPT_MCP_PROVIDER=ollama\nOPENAI_API_KEY=\n", "utf-8");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const content = buildEnvContent("openai", { openaiApiKey: "sk-test" });
|
||||||
|
// The generated file should NOT contain the old ollama provider value
|
||||||
|
expect(content).toContain("CHATGPT_MCP_PROVIDER=openai");
|
||||||
|
expect(content).not.toContain("CHATGPT_MCP_PROVIDER=ollama");
|
||||||
|
} finally {
|
||||||
|
fs.unlinkSync(envPath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("non-TTY detection works (process.stdin is readable)", () => {
|
||||||
|
// The non-TTY check in setup.js is:
|
||||||
|
// process.stdin.isTTY && typeof process.stdin.setRawMode === "function"
|
||||||
|
// In tests, stdin may not be a TTY — verify we don't crash on setRawMode calls.
|
||||||
|
expect(process.stdin).toBeDefined();
|
||||||
|
expect(typeof process.stdin.setRawMode).not.toBe("function"); // non-TTY in vitest ✅
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Secret masking and output safety", () => {
|
||||||
|
it("generated .env content contains raw key (masking only applies to display)", () => {
|
||||||
|
const result = buildEnvContent("openai", { openaiApiKey: "sk-secret-key-12345", openaiModel: null });
|
||||||
|
expect(result).toContain("OPENAI_API_KEY=sk-secret-key-12345");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Claude config generation produces valid JSON", () => {
|
||||||
|
const result = buildClaudeConfig();
|
||||||
|
expect(() => JSON.parse(result)).not.toThrow();
|
||||||
|
const parsed = JSON.parse(result);
|
||||||
|
expect(parsed.mcpServers["chatgpt-mcp"].command).toBe("npm");
|
||||||
|
expect(parsed.mcpServers["chatgpt-mcp"].args).toEqual(["start"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Claude config is idempotent (same output each call)", () => {
|
||||||
|
const a = buildClaudeConfig();
|
||||||
|
const b = buildClaudeConfig();
|
||||||
|
expect(a).toBe(b);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user