Compare commits
1 Commits
feat-gener
...
docs/auto-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d76d72adc |
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"task-master-ai": patch
|
||||
---
|
||||
|
||||
Improve `analyze-complexity` cli docs and `--research` flag documentation
|
||||
@@ -11,6 +11,9 @@
|
||||
"access": "public",
|
||||
"baseBranch": "main",
|
||||
"ignore": [
|
||||
"docs"
|
||||
"docs",
|
||||
"@tm/cli",
|
||||
"@tm/core",
|
||||
"@tm/build-config"
|
||||
]
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"task-master-ai": minor
|
||||
---
|
||||
|
||||
Add Cursor IDE custom slash command support
|
||||
|
||||
Expose Task Master commands as Cursor slash commands by copying assets/claude/commands to .cursor/commands on profile add and cleaning up on remove.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"task-master-ai": patch
|
||||
---
|
||||
|
||||
Change parent task back to "pending" when all subtasks are in "pending" state
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"task-master-ai": minor
|
||||
---
|
||||
|
||||
Added api keys page on docs website: docs.task-master.dev/getting-started/api-keys
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
"task-master-ai": minor
|
||||
---
|
||||
|
||||
Move to AI SDK v5:
|
||||
|
||||
- Works better with claude-code and gemini-cli as ai providers
|
||||
- Improved openai model family compatibility
|
||||
- Migrate ollama provider to v2
|
||||
- Closes #1223, #1013, #1161, #1174
|
||||
@@ -1,30 +0,0 @@
|
||||
---
|
||||
"task-master-ai": minor
|
||||
---
|
||||
|
||||
Migrate AI services to use generateObject for structured data generation
|
||||
|
||||
This update migrates all AI service calls from generateText to generateObject, ensuring more reliable and structured responses across all commands.
|
||||
|
||||
### Key Changes:
|
||||
|
||||
- **Unified AI Service**: Replaced separate generateText implementations with a single generateObjectService that handles structured data generation
|
||||
- **JSON Mode Support**: Added proper JSON mode configuration for providers that support it (OpenAI, Anthropic, Google, Groq)
|
||||
- **Schema Validation**: Integrated Zod schemas for all AI-generated content with automatic validation
|
||||
- **Provider Compatibility**: Maintained compatibility with all existing providers while leveraging their native structured output capabilities
|
||||
- **Improved Reliability**: Structured output generation reduces parsing errors and ensures consistent data formats
|
||||
|
||||
### Technical Improvements:
|
||||
|
||||
- Centralized provider configuration in `ai-providers-unified.js`
|
||||
- Added `generateObject` support detection for each provider
|
||||
- Implemented proper error handling for schema validation failures
|
||||
- Maintained backward compatibility with existing prompt structures
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
- Fixed subtask ID numbering issue where AI was generating inconsistent IDs (101-105, 601-603) instead of sequential numbering (1, 2, 3...)
|
||||
- Enhanced prompt instructions to enforce proper ID generation patterns
|
||||
- Ensured subtasks display correctly as X.1, X.2, X.3 format
|
||||
|
||||
This migration improves the reliability and consistency of AI-generated content throughout the Task Master application.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"task-master-ai": patch
|
||||
---
|
||||
|
||||
Fix Claude Code settings validation for pathToClaudeCodeExecutable
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"task-master-ai": patch
|
||||
---
|
||||
|
||||
Fix sonar deep research model failing, should be called `sonar-deep-research`
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"task-master-ai": minor
|
||||
---
|
||||
|
||||
Upgrade grok-cli ai provider to ai sdk v5
|
||||
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@@ -6,6 +6,9 @@ on:
|
||||
- main
|
||||
- next
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- next
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
|
||||
5
.github/workflows/extension-ci.yml
vendored
5
.github/workflows/extension-ci.yml
vendored
@@ -41,7 +41,8 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- name: Install Monorepo Dependencies
|
||||
- name: Install Extension Dependencies
|
||||
working-directory: apps/extension
|
||||
run: npm ci
|
||||
timeout-minutes: 5
|
||||
|
||||
@@ -67,6 +68,7 @@ jobs:
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- name: Install if cache miss
|
||||
working-directory: apps/extension
|
||||
run: npm ci
|
||||
timeout-minutes: 3
|
||||
|
||||
@@ -98,6 +100,7 @@ jobs:
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- name: Install if cache miss
|
||||
working-directory: apps/extension
|
||||
run: npm ci
|
||||
timeout-minutes: 3
|
||||
|
||||
|
||||
3
.github/workflows/extension-release.yml
vendored
3
.github/workflows/extension-release.yml
vendored
@@ -31,7 +31,8 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- name: Install Monorepo Dependencies
|
||||
- name: Install Extension Dependencies
|
||||
working-directory: apps/extension
|
||||
run: npm ci
|
||||
timeout-minutes: 5
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"$schema": "https://unpkg.com/@manypkg/get-packages@1.1.3/schema.json",
|
||||
"defaultBranch": "main",
|
||||
"ignoredRules": ["ROOT_HAS_DEPENDENCIES", "INTERNAL_MISMATCH"],
|
||||
"ignoredPackages": ["@tm/core", "@tm/cli", "@tm/build-config"]
|
||||
}
|
||||
29
CHANGELOG.md
29
CHANGELOG.md
@@ -1,34 +1,5 @@
|
||||
# task-master-ai
|
||||
|
||||
## 0.27.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1248](https://github.com/eyaltoledano/claude-task-master/pull/1248) [`044a7bf`](https://github.com/eyaltoledano/claude-task-master/commit/044a7bfc98049298177bc655cf341d7a8b6a0011) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix set-status for subtasks:
|
||||
- Parent tasks are now set as `done` when subtasks are all `done`
|
||||
- Parent tasks are now set as `in-progress` when at least one subtask is `in-progress` or `done`
|
||||
|
||||
## 0.27.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1232](https://github.com/eyaltoledano/claude-task-master/pull/1232) [`f487736`](https://github.com/eyaltoledano/claude-task-master/commit/f487736670ef8c484059f676293777eabb249c9e) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix module not found for new 0.27.0 release
|
||||
|
||||
- [#1233](https://github.com/eyaltoledano/claude-task-master/pull/1233) [`c911608`](https://github.com/eyaltoledano/claude-task-master/commit/c911608f60454253f4e024b57ca84e5a5a53f65c) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix Zed MCP configuration by adding required "source" property
|
||||
- Add "source": "custom" property to task-master-ai server in Zed settings.json
|
||||
|
||||
## 0.27.1-rc.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1233](https://github.com/eyaltoledano/claude-task-master/pull/1233) [`1a18794`](https://github.com/eyaltoledano/claude-task-master/commit/1a1879483b86c118a4e46c02cbf4acebfcf6bcf9) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - One last testing final final
|
||||
|
||||
## 0.27.1-rc.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1232](https://github.com/eyaltoledano/claude-task-master/pull/1232) [`f487736`](https://github.com/eyaltoledano/claude-task-master/commit/f487736670ef8c484059f676293777eabb249c9e) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix module not found for new 0.27.0 release
|
||||
|
||||
## 0.27.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
16
README.md
16
README.md
@@ -60,19 +60,6 @@ The following documentation is also available in the `docs` directory:
|
||||
|
||||
> **Note:** After clicking the link, you'll still need to add your API keys to the configuration. The link installs the MCP server with placeholder keys that you'll need to replace with your actual API keys.
|
||||
|
||||
#### Claude Code Quick Install
|
||||
|
||||
For Claude Code users:
|
||||
|
||||
```bash
|
||||
claude mcp add taskmaster-ai -- npx -y task-master-ai
|
||||
```
|
||||
|
||||
Don't forget to add your API keys to the configuration:
|
||||
- in the root .env of your Project
|
||||
- in the "env" section of your mcp config for taskmaster-ai
|
||||
|
||||
|
||||
## Requirements
|
||||
|
||||
Taskmaster utilizes AI across several commands, and those require a separate API key. You can use a variety of models from different AI providers provided you add your API keys. For example, if you want to use Claude 3.7, you'll need an Anthropic API key.
|
||||
@@ -105,11 +92,10 @@ MCP (Model Control Protocol) lets you run Task Master directly from your editor.
|
||||
| | Project | `<project_folder>/.cursor/mcp.json` | `<project_folder>\.cursor\mcp.json` | `mcpServers` |
|
||||
| **Windsurf** | Global | `~/.codeium/windsurf/mcp_config.json` | `%USERPROFILE%\.codeium\windsurf\mcp_config.json` | `mcpServers` |
|
||||
| **VS Code** | Project | `<project_folder>/.vscode/mcp.json` | `<project_folder>\.vscode\mcp.json` | `servers` |
|
||||
| **Q CLI** | Global | `~/.aws/amazonq/mcp.json` | | `mcpServers` |
|
||||
|
||||
##### Manual Configuration
|
||||
|
||||
###### Cursor & Windsurf & Q Developer CLI (`mcpServers`)
|
||||
###### Cursor & Windsurf (`mcpServers`)
|
||||
|
||||
```json
|
||||
{
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
# @tm/cli
|
||||
|
||||
## null
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @tm/core@null
|
||||
|
||||
## 0.27.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"name": "@tm/cli",
|
||||
"version": "0.27.0",
|
||||
"description": "Task Master CLI - Command line interface for task management",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
@@ -23,19 +24,19 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@tm/core": "*",
|
||||
"boxen": "^8.0.1",
|
||||
"boxen": "^7.1.1",
|
||||
"chalk": "5.6.2",
|
||||
"cli-table3": "^0.6.5",
|
||||
"commander": "^12.1.0",
|
||||
"inquirer": "^12.5.0",
|
||||
"ora": "^8.2.0"
|
||||
"inquirer": "^9.2.10",
|
||||
"ora": "^8.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^1.9.4",
|
||||
"@types/inquirer": "^9.0.3",
|
||||
"@types/node": "^22.10.5",
|
||||
"tsx": "^4.20.4",
|
||||
"typescript": "^5.9.2",
|
||||
"typescript": "^5.7.3",
|
||||
"vitest": "^2.1.8"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -18,8 +18,7 @@ export * as ui from './utils/ui.js';
|
||||
export {
|
||||
checkForUpdate,
|
||||
performAutoUpdate,
|
||||
displayUpgradeNotification,
|
||||
compareVersions
|
||||
displayUpgradeNotification
|
||||
} from './utils/auto-update.js';
|
||||
|
||||
// Re-export commonly used types from tm-core
|
||||
|
||||
@@ -7,6 +7,7 @@ import https from 'https';
|
||||
import chalk from 'chalk';
|
||||
import ora from 'ora';
|
||||
import boxen from 'boxen';
|
||||
import packageJson from '../../../../package.json' with { type: 'json' };
|
||||
|
||||
export interface UpdateInfo {
|
||||
currentVersion: string;
|
||||
@@ -15,18 +16,15 @@ export interface UpdateInfo {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current version from build-time injected environment variable
|
||||
* Get current version from package.json
|
||||
*/
|
||||
function getCurrentVersion(): string {
|
||||
// Version is injected at build time via TM_PUBLIC_VERSION
|
||||
const version = process.env.TM_PUBLIC_VERSION;
|
||||
if (version && version !== 'unknown') {
|
||||
return version;
|
||||
try {
|
||||
return packageJson.version;
|
||||
} catch (error) {
|
||||
console.warn('Could not read package.json for version info');
|
||||
return '0.0.0';
|
||||
}
|
||||
|
||||
// Fallback for development or if injection failed
|
||||
console.warn('Could not read version from TM_PUBLIC_VERSION, using fallback');
|
||||
return '0.0.0';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -35,7 +33,7 @@ function getCurrentVersion(): string {
|
||||
* @param v2 - Second version
|
||||
* @returns -1 if v1 < v2, 0 if v1 = v2, 1 if v1 > v2
|
||||
*/
|
||||
export function compareVersions(v1: string, v2: string): number {
|
||||
function compareVersions(v1: string, v2: string): number {
|
||||
const toParts = (v: string) => {
|
||||
const [core, pre = ''] = v.split('-', 2);
|
||||
const nums = core.split('.').map((n) => Number.parseInt(n, 10) || 0);
|
||||
@@ -158,18 +156,10 @@ export function displayUpgradeNotification(
|
||||
export async function performAutoUpdate(
|
||||
latestVersion: string
|
||||
): Promise<boolean> {
|
||||
if (
|
||||
process.env.TASKMASTER_SKIP_AUTO_UPDATE === '1' ||
|
||||
process.env.CI ||
|
||||
process.env.NODE_ENV === 'test'
|
||||
) {
|
||||
const reason =
|
||||
process.env.TASKMASTER_SKIP_AUTO_UPDATE === '1'
|
||||
? 'TASKMASTER_SKIP_AUTO_UPDATE=1'
|
||||
: process.env.CI
|
||||
? 'CI environment'
|
||||
: 'NODE_ENV=test';
|
||||
console.log(chalk.dim(`Skipping auto-update (${reason})`));
|
||||
if (process.env.TASKMASTER_SKIP_AUTO_UPDATE === '1' || process.env.CI) {
|
||||
console.log(
|
||||
chalk.dim('Skipping auto-update (TASKMASTER_SKIP_AUTO_UPDATE/CI).')
|
||||
);
|
||||
return false;
|
||||
}
|
||||
const spinner = ora({
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# docs
|
||||
|
||||
## 0.0.4
|
||||
|
||||
## 0.0.3
|
||||
|
||||
## 0.0.2
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
# Task Master Documentation
|
||||
|
||||
Welcome to the Task Master documentation. This documentation site provides comprehensive guides for getting started with Task Master.
|
||||
Welcome to the Task Master documentation. Use the links below to navigate to the information you need:
|
||||
|
||||
## Getting Started
|
||||
|
||||
- [Quick Start Guide](/getting-started/quick-start) - Complete setup and first-time usage guide
|
||||
- [Requirements](/getting-started/quick-start/requirements) - What you need to get started
|
||||
- [Installation](/getting-started/quick-start/installation) - How to install Task Master
|
||||
- [Configuration Guide](archive/configuration.md) - Set up environment variables and customize Task Master
|
||||
- [Tutorial](archive/ctutorial.md) - Step-by-step guide to getting started with Task Master
|
||||
|
||||
## Core Capabilities
|
||||
## Reference
|
||||
|
||||
- [MCP Tools](/capabilities/mcp) - Model Control Protocol integration
|
||||
- [CLI Commands](/capabilities/cli-root-commands) - Command line interface reference
|
||||
- [Task Structure](/capabilities/task-structure) - Understanding tasks and subtasks
|
||||
- [Command Reference](archive/ccommand-reference.md) - Complete list of all available commands
|
||||
- [Task Structure](archive/ctask-structure.md) - Understanding the task format and features
|
||||
|
||||
## Best Practices
|
||||
## Examples & Licensing
|
||||
|
||||
- [Advanced Configuration](/best-practices/configuration-advanced) - Detailed configuration options
|
||||
- [Advanced Tasks](/best-practices/advanced-tasks) - Working with complex task structures
|
||||
- [Example Interactions](archive/cexamples.md) - Common Cursor AI interaction examples
|
||||
- [Licensing Information](archive/clicensing.md) - Detailed information about the license
|
||||
|
||||
## Need More Help?
|
||||
|
||||
If you can't find what you're looking for in these docs, please check the root README.md or visit our [GitHub repository](https://github.com/eyaltoledano/claude-task-master).
|
||||
If you can't find what you're looking for in these docs, please check the [main README](../README.md) or visit our [GitHub repository](https://github.com/eyaltoledano/claude-task-master).
|
||||
|
||||
@@ -66,6 +66,32 @@ Taskmaster uses two primary methods for configuration:
|
||||
- **Location:**
|
||||
- For CLI usage: Create a `.env` file in your project root.
|
||||
- For MCP/Cursor usage: Configure keys in the `env` section of your `.cursor/mcp.json` file.
|
||||
|
||||
## MCP Timeout Configuration
|
||||
|
||||
For MCP usage, it's important to configure appropriate timeouts for long-running AI operations:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"task-master-ai": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "task-master-ai"],
|
||||
"timeout": 300,
|
||||
"env": {
|
||||
"ANTHROPIC_API_KEY": "your-api-key-here"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Key points:**
|
||||
- **Default MCP timeout**: 60 seconds (often insufficient for AI operations)
|
||||
- **Recommended timeout**: 300 seconds (5 minutes)
|
||||
- **Operations that benefit**: `parse_prd`, `expand_task`, `analyze_project_complexity`, `research`
|
||||
- **Automatic setup**: Included when using `task-master rules add` for supported editors
|
||||
|
||||
- **Required API Keys (Depending on configured providers):**
|
||||
- `ANTHROPIC_API_KEY`: Your Anthropic API key.
|
||||
- `PERPLEXITY_API_KEY`: Your Perplexity API key.
|
||||
|
||||
@@ -156,7 +156,7 @@ sidebarTitle: "CLI Commands"
|
||||
# Use an alternative tasks file
|
||||
task-master analyze-complexity --file=custom-tasks.json
|
||||
|
||||
# Use your configured research model for research-backed complexity analysis
|
||||
# Use Perplexity AI for research-backed complexity analysis
|
||||
task-master analyze-complexity --research
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
@@ -7,6 +7,60 @@ sidebarTitle: "MCP Tools"
|
||||
|
||||
This document provides an overview of the MCP (Machine-to-Machine Communication Protocol) interface for the Task Master application. The MCP interface is defined in the `mcp-server/` directory and exposes the application's core functionalities as a set of tools that can be called remotely.
|
||||
|
||||
## MCP Timeout Configuration
|
||||
|
||||
Long-running AI operations in Task Master can exceed the default 60-second MCP timeout. Operations like `parse_prd`, `expand_task`, `analyze_project_complexity`, and `research` may take 2-5 minutes to complete.
|
||||
|
||||
### Adding Timeout Configuration
|
||||
|
||||
Add a `timeout` parameter to your MCP configuration to extend the timeout limit:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"task-master-ai": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "task-master-ai"],
|
||||
"timeout": 300,
|
||||
"env": {
|
||||
"ANTHROPIC_API_KEY": "your-anthropic-api-key"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Configuration Details:**
|
||||
- **`timeout: 300`** - Sets timeout to 300 seconds (5 minutes)
|
||||
- **Value range**: 1-3600 seconds (1 second to 1 hour)
|
||||
- **Recommended**: 300 seconds provides sufficient time for most AI operations
|
||||
- **Format**: Integer value in seconds (not milliseconds)
|
||||
|
||||
### Automatic Setup
|
||||
|
||||
When using supported AI coding assistants, the timeout configuration is automatically included when you add Task Master rules:
|
||||
|
||||
```bash
|
||||
# Automatically includes timeout configuration
|
||||
task-master rules add cursor
|
||||
task-master rules add roo
|
||||
task-master rules add windsurf
|
||||
task-master rules add vscode
|
||||
```
|
||||
|
||||
### Troubleshooting Timeouts
|
||||
|
||||
If you're still experiencing timeout errors:
|
||||
|
||||
1. **Verify configuration**: Check that `timeout: 300` is present in your MCP config
|
||||
2. **Restart editor**: Restart your editor after making configuration changes
|
||||
3. **Increase timeout**: For very complex operations, try `timeout: 600` (10 minutes)
|
||||
4. **Check API keys**: Ensure required API keys are properly configured
|
||||
|
||||
**Expected behavior:**
|
||||
- **Before fix**: Operations fail after 60 seconds with `MCP request timed out after 60000ms`
|
||||
- **After fix**: Operations complete successfully within the configured timeout limit
|
||||
|
||||
## Core Concepts
|
||||
|
||||
The MCP interface is built on top of the `fastmcp` library and registers a set of tools that correspond to the core functionalities of the Task Master application. These tools are defined in the `mcp-server/src/tools/` directory and are registered with the MCP server in `mcp-server/src/tools/index.js`.
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
"getting-started/quick-start/execute-quick"
|
||||
]
|
||||
},
|
||||
"getting-started/api-keys",
|
||||
"getting-started/faq",
|
||||
"getting-started/contribute"
|
||||
]
|
||||
|
||||
@@ -1,267 +0,0 @@
|
||||
# API Keys Configuration
|
||||
|
||||
Task Master supports multiple AI providers through environment variables. This page lists all available API keys and their configuration requirements.
|
||||
|
||||
## Required API Keys
|
||||
|
||||
> **Note**: At least one required API key must be configured for Task Master to function.
|
||||
>
|
||||
> "Required: Yes" below means "required to use that specific provider," not "required globally." You only need at least one provider configured.
|
||||
|
||||
### ANTHROPIC_API_KEY (Recommended)
|
||||
- **Provider**: Anthropic Claude models
|
||||
- **Format**: `sk-ant-api03-...`
|
||||
- **Required**: ✅ **Yes**
|
||||
- **Models**: Claude 3.5 Sonnet, Claude 3 Haiku, Claude 3 Opus
|
||||
- **Get Key**: [Anthropic Console](https://console.anthropic.com/)
|
||||
|
||||
```bash
|
||||
ANTHROPIC_API_KEY="sk-ant-api03-your-key-here"
|
||||
```
|
||||
|
||||
### PERPLEXITY_API_KEY (Highly Recommended for Research)
|
||||
- **Provider**: Perplexity AI (Research features)
|
||||
- **Format**: `pplx-...`
|
||||
- **Required**: ✅ **Yes**
|
||||
- **Purpose**: Enables research-backed task expansions and updates
|
||||
- **Models**: Perplexity Sonar models
|
||||
- **Get Key**: [Perplexity API](https://www.perplexity.ai/settings/api)
|
||||
|
||||
```bash
|
||||
PERPLEXITY_API_KEY="pplx-your-key-here"
|
||||
```
|
||||
|
||||
### OPENAI_API_KEY
|
||||
- **Provider**: OpenAI GPT models
|
||||
- **Format**: `sk-proj-...` or `sk-...`
|
||||
- **Required**: ✅ **Yes**
|
||||
- **Models**: GPT-4, GPT-4 Turbo, GPT-3.5 Turbo, O1 models
|
||||
- **Get Key**: [OpenAI Platform](https://platform.openai.com/api-keys)
|
||||
|
||||
```bash
|
||||
OPENAI_API_KEY="sk-proj-your-key-here"
|
||||
```
|
||||
|
||||
### GOOGLE_API_KEY
|
||||
- **Provider**: Google Gemini models
|
||||
- **Format**: Various formats
|
||||
- **Required**: ✅ **Yes**
|
||||
- **Models**: Gemini Pro, Gemini Flash, Gemini Ultra
|
||||
- **Get Key**: [Google AI Studio](https://aistudio.google.com/app/apikey)
|
||||
- **Alternative**: Use `GOOGLE_APPLICATION_CREDENTIALS` for service account (Google Vertex)
|
||||
|
||||
```bash
|
||||
GOOGLE_API_KEY="your-google-api-key-here"
|
||||
```
|
||||
|
||||
### GROQ_API_KEY
|
||||
- **Provider**: Groq (High-performance inference)
|
||||
- **Required**: ✅ **Yes**
|
||||
- **Models**: Llama models, Mixtral models (via Groq)
|
||||
- **Get Key**: [Groq Console](https://console.groq.com/keys)
|
||||
|
||||
```bash
|
||||
GROQ_API_KEY="your-groq-key-here"
|
||||
```
|
||||
|
||||
### OPENROUTER_API_KEY
|
||||
- **Provider**: OpenRouter (Multiple model access)
|
||||
- **Required**: ✅ **Yes**
|
||||
- **Models**: Access to various models through single API
|
||||
- **Get Key**: [OpenRouter](https://openrouter.ai/keys)
|
||||
|
||||
```bash
|
||||
OPENROUTER_API_KEY="your-openrouter-key-here"
|
||||
```
|
||||
|
||||
### AZURE_OPENAI_API_KEY
|
||||
- **Provider**: Azure OpenAI Service
|
||||
- **Required**: ✅ **Yes**
|
||||
- **Requirements**: Also requires `AZURE_OPENAI_ENDPOINT` configuration
|
||||
- **Models**: GPT models via Azure
|
||||
- **Get Key**: [Azure Portal](https://portal.azure.com/)
|
||||
|
||||
```bash
|
||||
AZURE_OPENAI_API_KEY="your-azure-key-here"
|
||||
```
|
||||
|
||||
### XAI_API_KEY
|
||||
- **Provider**: xAI (Grok) models
|
||||
- **Required**: ✅ **Yes**
|
||||
- **Models**: Grok models
|
||||
- **Get Key**: [xAI Console](https://console.x.ai/)
|
||||
|
||||
```bash
|
||||
XAI_API_KEY="your-xai-key-here"
|
||||
```
|
||||
|
||||
## Optional API Keys
|
||||
|
||||
> **Note**: These API keys are optional - providers will work without them or use alternative authentication methods.
|
||||
|
||||
### AWS_ACCESS_KEY_ID (Bedrock)
|
||||
- **Provider**: AWS Bedrock
|
||||
- **Required**: ❌ **No** (uses AWS credential chain)
|
||||
- **Models**: Claude models via AWS Bedrock
|
||||
- **Authentication**: Uses AWS credential chain (profiles, IAM roles, etc.)
|
||||
- **Get Key**: [AWS Console](https://console.aws.amazon.com/iam/)
|
||||
|
||||
```bash
|
||||
# Optional - AWS credential chain is preferred
|
||||
AWS_ACCESS_KEY_ID="your-aws-access-key"
|
||||
AWS_SECRET_ACCESS_KEY="your-aws-secret-key"
|
||||
```
|
||||
|
||||
### CLAUDE_CODE_API_KEY
|
||||
- **Provider**: Claude Code CLI
|
||||
- **Required**: ❌ **No** (uses OAuth tokens)
|
||||
- **Purpose**: Integration with local Claude Code CLI
|
||||
- **Authentication**: Uses OAuth tokens, no API key needed
|
||||
|
||||
```bash
|
||||
# Not typically needed
|
||||
CLAUDE_CODE_API_KEY="not-usually-required"
|
||||
```
|
||||
|
||||
### GEMINI_API_KEY
|
||||
- **Provider**: Gemini CLI
|
||||
- **Required**: ❌ **No** (uses OAuth authentication)
|
||||
- **Purpose**: Integration with Gemini CLI
|
||||
- **Authentication**: Primarily uses OAuth via CLI, API key is optional
|
||||
|
||||
```bash
|
||||
# Optional - OAuth via CLI is preferred
|
||||
GEMINI_API_KEY="your-gemini-key-here"
|
||||
```
|
||||
|
||||
### GROK_CLI_API_KEY
|
||||
- **Provider**: Grok CLI
|
||||
- **Required**: ❌ **No** (can use CLI config)
|
||||
- **Purpose**: Integration with Grok CLI
|
||||
- **Authentication**: Can use Grok CLI's own config file
|
||||
|
||||
```bash
|
||||
# Optional - CLI config is preferred
|
||||
GROK_CLI_API_KEY="your-grok-cli-key"
|
||||
```
|
||||
|
||||
### OLLAMA_API_KEY
|
||||
- **Provider**: Ollama (Local/Remote)
|
||||
- **Required**: ❌ **No** (local installation doesn't need key)
|
||||
- **Purpose**: For remote Ollama servers that require authentication
|
||||
- **Requirements**: Only needed for remote servers with authentication
|
||||
- **Note**: Not needed for local Ollama installations
|
||||
|
||||
```bash
|
||||
# Only needed for remote Ollama servers
|
||||
OLLAMA_API_KEY="your-ollama-api-key-here"
|
||||
```
|
||||
|
||||
### GITHUB_API_KEY
|
||||
- **Provider**: GitHub (Import/Export features)
|
||||
- **Format**: `ghp_...` or `github_pat_...`
|
||||
- **Required**: ❌ **No** (for GitHub features only)
|
||||
- **Purpose**: GitHub import/export features
|
||||
- **Get Key**: [GitHub Settings](https://github.com/settings/tokens)
|
||||
|
||||
```bash
|
||||
GITHUB_API_KEY="ghp-your-github-key-here"
|
||||
```
|
||||
|
||||
## Configuration Methods
|
||||
|
||||
### Method 1: Environment File (.env)
|
||||
Create a `.env` file in your project root:
|
||||
|
||||
```bash
|
||||
# Copy from .env.example
|
||||
cp .env.example .env
|
||||
|
||||
# Edit with your keys
|
||||
vim .env
|
||||
```
|
||||
|
||||
### Method 2: System Environment Variables
|
||||
```bash
|
||||
export ANTHROPIC_API_KEY="your-key-here"
|
||||
export PERPLEXITY_API_KEY="your-key-here"
|
||||
# ... other keys
|
||||
```
|
||||
|
||||
### Method 3: MCP Server Configuration
|
||||
For Claude Code integration, configure keys in `.mcp.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"task-master-ai": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "task-master-ai"],
|
||||
"env": {
|
||||
"ANTHROPIC_API_KEY": "your-key-here",
|
||||
"PERPLEXITY_API_KEY": "your-key-here",
|
||||
"OPENAI_API_KEY": "your-key-here"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Key Requirements
|
||||
|
||||
### Minimum Requirements
|
||||
- **At least one** AI provider key is required
|
||||
- **ANTHROPIC_API_KEY** is recommended as the primary provider
|
||||
- **PERPLEXITY_API_KEY** is highly recommended for research features
|
||||
|
||||
### Provider-Specific Requirements
|
||||
- **Azure OpenAI**: Requires both `AZURE_OPENAI_API_KEY` and `AZURE_OPENAI_ENDPOINT` configuration
|
||||
- **Google Vertex**: Requires `VERTEX_PROJECT_ID` and `VERTEX_LOCATION` environment variables
|
||||
- **AWS Bedrock**: Uses AWS credential chain (profiles, IAM roles, etc.) instead of API keys
|
||||
- **Ollama**: Only needs API key for remote servers with authentication
|
||||
- **CLI Providers**: Gemini CLI, Grok CLI, and Claude Code use OAuth/CLI config instead of API keys
|
||||
|
||||
## Model Configuration
|
||||
|
||||
After setting up API keys, configure which models to use:
|
||||
|
||||
```bash
|
||||
# Interactive model setup
|
||||
task-master models --setup
|
||||
|
||||
# Set specific models
|
||||
task-master models --set-main claude-3-5-sonnet-20241022
|
||||
task-master models --set-research perplexity-llama-3.1-sonar-large-128k-online
|
||||
task-master models --set-fallback gpt-4o-mini
|
||||
```
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
1. **Never commit API keys** to version control
|
||||
2. **Use .env files** and add them to `.gitignore`
|
||||
3. **Rotate keys regularly** especially if compromised
|
||||
4. **Use minimal permissions** for service accounts
|
||||
5. **Monitor usage** to detect unauthorized access
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Key Validation
|
||||
```bash
|
||||
# Check if keys are properly configured
|
||||
task-master models
|
||||
|
||||
# Test specific provider
|
||||
task-master add-task --prompt="test task" --model=claude-3-5-sonnet-20241022
|
||||
```
|
||||
|
||||
### Common Issues
|
||||
- **Invalid key format**: Check the expected format for each provider
|
||||
- **Insufficient permissions**: Ensure keys have necessary API access
|
||||
- **Rate limits**: Some providers have usage limits
|
||||
- **Regional restrictions**: Some models may not be available in all regions
|
||||
|
||||
### Getting Help
|
||||
If you encounter issues with API key configuration:
|
||||
- Check the [FAQ](/getting-started/faq) for common solutions
|
||||
- Join our [Discord community](https://discord.gg/fWJkU7rf) for support
|
||||
- Report issues on [GitHub](https://github.com/eyaltoledano/claude-task-master/issues)
|
||||
@@ -20,6 +20,7 @@ For MCP/Cursor usage: Configure keys in the env section of your .cursor/mcp.json
|
||||
"task-master-ai": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "task-master-ai"],
|
||||
"timeout": 300,
|
||||
"env": {
|
||||
"ANTHROPIC_API_KEY": "ANTHROPIC_API_KEY_HERE",
|
||||
"PERPLEXITY_API_KEY": "PERPLEXITY_API_KEY_HERE",
|
||||
@@ -37,6 +38,10 @@ For MCP/Cursor usage: Configure keys in the env section of your .cursor/mcp.json
|
||||
}
|
||||
```
|
||||
|
||||
<Note>
|
||||
The `timeout: 300` setting (5 minutes) is recommended for long-running AI operations like `parse-prd`, `expand-all`, and `analyze-complexity`. This prevents timeout errors during complex tasks.
|
||||
</Note>
|
||||
|
||||
### CLI Usage: `.env` File
|
||||
|
||||
Create a `.env` file in your project root and include the keys for the providers you plan to use:
|
||||
@@ -108,5 +113,5 @@ You don’t need to configure everything up front. Most settings can be left as
|
||||
</Accordion>
|
||||
|
||||
<Note>
|
||||
For advanced configuration options and detailed customization, see our [Advanced Configuration Guide](/best-practices/configuration-advanced) page.
|
||||
For advanced configuration options and detailed customization, see our [Advanced Configuration Guide](/docs/best-practices/configuration-advanced) page.
|
||||
</Note>
|
||||
@@ -56,4 +56,4 @@ If you ran into problems and had to debug errors you can create new rules as you
|
||||
|
||||
By now you have all you need to get started executing code faster and smarter with Task Master.
|
||||
|
||||
If you have any questions please check out [Frequently Asked Questions](/getting-started/faq)
|
||||
If you have any questions please check out [Frequently Asked Questions](/docs/getting-started/faq)
|
||||
|
||||
@@ -30,19 +30,6 @@ cursor://anysphere.cursor-deeplink/mcp/install?name=taskmaster-ai&config=eyJjb21
|
||||
```
|
||||
|
||||
> **Note:** After clicking the link, you'll still need to add your API keys to the configuration. The link installs the MCP server with placeholder keys that you'll need to replace with your actual API keys.
|
||||
|
||||
### Claude Code Quick Install
|
||||
|
||||
For Claude Code users:
|
||||
|
||||
```bash
|
||||
claude mcp add taskmaster-ai -- npx -y task-master-ai
|
||||
```
|
||||
|
||||
Don't forget to add your API keys to the configuration:
|
||||
- in the root .env of your Project
|
||||
- in the "env" section of your mcp config for taskmaster-ai
|
||||
|
||||
</Accordion>
|
||||
## Installation Options
|
||||
|
||||
|
||||
@@ -6,13 +6,13 @@ sidebarTitle: "Quick Start"
|
||||
This guide is for new users who want to start using Task Master with minimal setup time.
|
||||
|
||||
It covers:
|
||||
- [Requirements](/getting-started/quick-start/requirements): You will need Node.js and an AI model API Key.
|
||||
- [Installation](/getting-started/quick-start/installation): How to Install Task Master.
|
||||
- [Configuration](/getting-started/quick-start/configuration-quick): Setting up your API Key, MCP, and more.
|
||||
- [PRD](/getting-started/quick-start/prd-quick): Writing and parsing your first PRD.
|
||||
- [Task Setup](/getting-started/quick-start/tasks-quick): Preparing your tasks for execution.
|
||||
- [Executing Tasks](/getting-started/quick-start/execute-quick): Using Task Master to execute tasks.
|
||||
- [Rules & Context](/getting-started/quick-start/rules-quick): Learn how and why to build context in your project over time.
|
||||
- [Requirements](/docs/getting-started/quick-start/requirements): You will need Node.js and an AI model API Key.
|
||||
- [Installation](/docs/getting-started/quick-start/installation): How to Install Task Master.
|
||||
- [Configuration](/docs/getting-started/quick-start/configuration-quick): Setting up your API Key, MCP, and more.
|
||||
- [PRD](/docs/getting-started/quick-start/prd-quick): Writing and parsing your first PRD.
|
||||
- [Task Setup](/docs/getting-started/quick-start/tasks-quick): Preparing your tasks for execution.
|
||||
- [Executing Tasks](/docs/getting-started/quick-start/execute-quick): Using Task Master to execute tasks.
|
||||
- [Rules & Context](/docs/getting-started/quick-start/rules-quick): Learn how and why to build context in your project over time.
|
||||
|
||||
<Tip>
|
||||
By the end of this guide, you'll have everything you need to begin working productively with Task Master.
|
||||
|
||||
@@ -61,25 +61,9 @@ Task Master can provide a complexity report which can be helpful to read before
|
||||
Can you analyze the complexity of our tasks to help me understand which ones need to be broken down further?
|
||||
```
|
||||
|
||||
The agent will use the `analyze_project_complexity` MCP tool, or you can run it directly with the CLI command:
|
||||
```bash
|
||||
task-master analyze-complexity
|
||||
```
|
||||
|
||||
For more comprehensive analysis using your configured research model, you can use:
|
||||
```bash
|
||||
task-master analyze-complexity --research
|
||||
```
|
||||
|
||||
<Tip>
|
||||
The `--research` flag uses whatever research model you have configured in `.taskmaster/config.json` (configurable via `task-master models --setup`) for research-backed complexity analysis, providing more informed recommendations.
|
||||
</Tip>
|
||||
|
||||
You can view the report in a friendly table using:
|
||||
```
|
||||
Can you show me the complexity report in a more readable format?
|
||||
```
|
||||
|
||||
For more detailed CLI options, see the [Analyze Task Complexity](/capabilities/cli-root-commands#analyze-task-complexity) section.
|
||||
|
||||
<Check>Now you are ready to begin [executing tasks](/getting-started/quick-start/execute-quick)</Check>
|
||||
<Check>Now you are ready to begin [executing tasks](/docs/getting-started/quick-start/execute-quick)</Check>
|
||||
@@ -4,7 +4,7 @@ Welcome to v1 of the Task Master Docs. Expect weekly updates as we expand and re
|
||||
|
||||
We've organized the docs into three sections depending on your experience level and goals:
|
||||
|
||||
### Getting Started - Jump in to [Quick Start](/getting-started/quick-start)
|
||||
### Getting Started - Jump in to [Quick Start](/docs/getting-started/quick-start)
|
||||
Designed for first-time users. Get set up, create your first PRD, and run your first task.
|
||||
|
||||
### Best Practices
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "docs",
|
||||
"version": "0.0.4",
|
||||
"version": "0.0.3",
|
||||
"private": true,
|
||||
"description": "Task Master documentation powered by Mintlify",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,33 +1,5 @@
|
||||
# Change Log
|
||||
|
||||
## 0.25.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`044a7bf`](https://github.com/eyaltoledano/claude-task-master/commit/044a7bfc98049298177bc655cf341d7a8b6a0011)]:
|
||||
- task-master-ai@0.27.2
|
||||
|
||||
## 0.25.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`f487736`](https://github.com/eyaltoledano/claude-task-master/commit/f487736670ef8c484059f676293777eabb249c9e), [`c911608`](https://github.com/eyaltoledano/claude-task-master/commit/c911608f60454253f4e024b57ca84e5a5a53f65c), [`1a18794`](https://github.com/eyaltoledano/claude-task-master/commit/1a1879483b86c118a4e46c02cbf4acebfcf6bcf9)]:
|
||||
- task-master-ai@0.27.1
|
||||
|
||||
## 0.25.2-rc.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`1a18794`](https://github.com/eyaltoledano/claude-task-master/commit/1a1879483b86c118a4e46c02cbf4acebfcf6bcf9)]:
|
||||
- task-master-ai@0.27.1-rc.1
|
||||
|
||||
## 0.25.2-rc.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`f487736`](https://github.com/eyaltoledano/claude-task-master/commit/f487736670ef8c484059f676293777eabb249c9e)]:
|
||||
- task-master-ai@0.27.1-rc.0
|
||||
|
||||
## 0.25.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"private": true,
|
||||
"displayName": "TaskMaster",
|
||||
"description": "A visual Kanban board interface for TaskMaster projects in VS Code",
|
||||
"version": "0.25.3",
|
||||
"version": "0.25.0",
|
||||
"publisher": "Hamster",
|
||||
"icon": "assets/icon.png",
|
||||
"engines": {
|
||||
@@ -254,9 +254,8 @@
|
||||
"@radix-ui/react-separator": "^1.1.7",
|
||||
"@radix-ui/react-slot": "^1.2.3",
|
||||
"@tailwindcss/postcss": "^4.1.11",
|
||||
"@tanstack/react-query": "^5.83.0",
|
||||
"@types/mocha": "^10.0.10",
|
||||
"@types/node": "^22.10.5",
|
||||
"@types/node": "20.x",
|
||||
"@types/react": "19.1.8",
|
||||
"@types/react-dom": "19.1.6",
|
||||
"@types/vscode": "^1.101.0",
|
||||
@@ -272,12 +271,12 @@
|
||||
"lucide-react": "^0.525.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"postcss": "8.5.6",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwindcss": "4.1.11",
|
||||
"typescript": "^5.9.2",
|
||||
"@tm/core": "*"
|
||||
"typescript": "^5.8.3",
|
||||
"@tanstack/react-query": "^5.83.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"overrides": {
|
||||
"glob@<8": "^10.4.5",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "task-master-hamster",
|
||||
"displayName": "Taskmaster AI",
|
||||
"description": "A visual Kanban board interface for Taskmaster projects in VS Code",
|
||||
"version": "0.25.3",
|
||||
"version": "0.23.1",
|
||||
"publisher": "Hamster",
|
||||
"icon": "assets/icon.png",
|
||||
"engines": {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"outDir": "out",
|
||||
"lib": ["ES2022", "DOM"],
|
||||
"sourceMap": true,
|
||||
"rootDir": "src",
|
||||
"strict": true /* enable all strict type-checking options */,
|
||||
"moduleResolution": "Node",
|
||||
"esModuleInterop": true,
|
||||
@@ -20,10 +21,8 @@
|
||||
"@/*": ["./src/*"],
|
||||
"@/components/*": ["./src/components/*"],
|
||||
"@/lib/*": ["./src/lib/*"],
|
||||
"@tm/core": ["../../packages/tm-core/src/index.ts"],
|
||||
"@tm/core/*": ["../../packages/tm-core/src/*"]
|
||||
"@tm/core": ["../core/src"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", ".vscode-test", "out", "dist"]
|
||||
}
|
||||
|
||||
@@ -1,231 +0,0 @@
|
||||
# TODO: Move to apps/docs inside our documentation website
|
||||
|
||||
# Claude Code Integration Guide
|
||||
|
||||
This guide covers how to use Task Master with Claude Code AI SDK integration for enhanced AI-powered development workflows.
|
||||
|
||||
## Overview
|
||||
|
||||
Claude Code integration allows Task Master to leverage the Claude Code CLI for AI operations without requiring direct API keys. The integration uses OAuth tokens managed by the Claude Code CLI itself.
|
||||
|
||||
## Authentication Setup
|
||||
|
||||
The Claude Code provider uses token authentication managed by the Claude Code CLI.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
1. **Install Claude Code CLI** (if not already installed):
|
||||
|
||||
```bash
|
||||
# Installation method depends on your system
|
||||
# Follow Claude Code documentation for installation
|
||||
```
|
||||
|
||||
2. **Set up OAuth token** using Claude Code CLI:
|
||||
|
||||
```bash
|
||||
claude setup-token
|
||||
```
|
||||
|
||||
This command will:
|
||||
- Guide you through OAuth authentication
|
||||
- Store the token securely for CLI usage
|
||||
- Enable Task Master to use Claude Code without manual API key configuration
|
||||
|
||||
### Authentication Priority
|
||||
|
||||
Task Master will attempt authentication in this order:
|
||||
|
||||
1. **Environment Variable** (optional): `CLAUDE_CODE_OAUTH_TOKEN`
|
||||
- Useful for CI/CD environments or when you want to override the default token
|
||||
- Not required if you've set up the CLI token
|
||||
|
||||
2. **Claude Code CLI Token** (recommended): Token managed by `claude setup-token`
|
||||
- Automatically used when available
|
||||
- Most convenient for local development
|
||||
|
||||
3. **Fallback**: Error if neither is available
|
||||
|
||||
## Configuration
|
||||
|
||||
### Basic Configuration
|
||||
|
||||
Add Claude Code to your Task Master configuration:
|
||||
|
||||
```javascript
|
||||
// In your .taskmaster/config.json or via task-master models command
|
||||
{
|
||||
"models": {
|
||||
"main": "claude-code:sonnet", // Use Claude Code with Sonnet
|
||||
"research": "perplexity-llama-3.1-sonar-large-128k-online",
|
||||
"fallback": "claude-code:opus" // Use Claude Code with Opus as fallback
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Supported Models
|
||||
|
||||
- `claude-code:sonnet` - Claude 3.5 Sonnet via Claude Code CLI
|
||||
- `claude-code:opus` - Claude 3 Opus via Claude Code CLI
|
||||
|
||||
### Environment Variables (Optional)
|
||||
|
||||
While not required, you can optionally set:
|
||||
|
||||
```bash
|
||||
export CLAUDE_CODE_OAUTH_TOKEN="your_oauth_token_here"
|
||||
```
|
||||
|
||||
This is only needed in specific scenarios like:
|
||||
|
||||
- CI/CD pipelines
|
||||
- Docker containers
|
||||
- When you want to use a different token than the CLI default
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Task Operations
|
||||
|
||||
```bash
|
||||
# Use Claude Code for task operations
|
||||
task-master add-task --prompt="Implement user authentication system" --research
|
||||
task-master expand --id=1 --research
|
||||
task-master update-task --id=1.1 --prompt="Add JWT token validation"
|
||||
```
|
||||
|
||||
### Model Configuration Commands
|
||||
|
||||
```bash
|
||||
# Set Claude Code as main model
|
||||
task-master models --set-main claude-code:sonnet
|
||||
|
||||
# Use interactive setup
|
||||
task-master models --setup
|
||||
# Then select "claude-code" from the provider list
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### 1. "Claude Code CLI not available" Error
|
||||
|
||||
**Problem**: Task Master cannot connect to Claude Code CLI.
|
||||
|
||||
**Solutions**:
|
||||
|
||||
- Ensure Claude Code CLI is installed and in your PATH
|
||||
- Run `claude setup-token` to configure authentication
|
||||
- Verify Claude Code CLI works: `claude --help`
|
||||
|
||||
#### 2. Authentication Failures
|
||||
|
||||
**Problem**: Token authentication is failing.
|
||||
|
||||
**Solutions**:
|
||||
|
||||
- Re-run `claude setup-token` to refresh your OAuth token
|
||||
- Check if your token has expired
|
||||
- Verify Claude Code CLI can authenticate: try a simple `claude` command
|
||||
|
||||
#### 3. Model Not Available
|
||||
|
||||
**Problem**: Specified Claude Code model is not supported.
|
||||
|
||||
**Solutions**:
|
||||
|
||||
- Use supported models: `sonnet` or `opus`
|
||||
- Check model availability: `task-master models --list`
|
||||
- Verify your Claude Code CLI has access to the requested model
|
||||
|
||||
### Debug Steps
|
||||
|
||||
1. **Test Claude Code CLI directly**:
|
||||
|
||||
```bash
|
||||
claude --help
|
||||
# Should show help without errors
|
||||
```
|
||||
|
||||
2. **Test authentication**:
|
||||
|
||||
```bash
|
||||
claude setup-token --verify
|
||||
# Should confirm token is valid
|
||||
```
|
||||
|
||||
3. **Test Task Master integration**:
|
||||
|
||||
```bash
|
||||
task-master models --test claude-code:sonnet
|
||||
# Should successfully connect and test the model
|
||||
```
|
||||
|
||||
4. **Check logs**:
|
||||
- Task Master logs will show detailed error messages
|
||||
- Use `--verbose` flag for more detailed output
|
||||
|
||||
### Environment-Specific Configuration
|
||||
|
||||
#### Docker/Containers
|
||||
|
||||
When running in Docker, you'll need to:
|
||||
|
||||
1. Install Claude Code CLI in your container
|
||||
2. Set up authentication via environment variable:
|
||||
|
||||
```dockerfile
|
||||
ENV CLAUDE_CODE_OAUTH_TOKEN="your_token_here"
|
||||
```
|
||||
|
||||
#### CI/CD Pipelines
|
||||
|
||||
For automated environments:
|
||||
|
||||
1. Set up a service account token or use environment variables
|
||||
2. Ensure Claude Code CLI is available in the pipeline environment
|
||||
3. Configure authentication before running Task Master commands
|
||||
|
||||
## Integration with AI SDK
|
||||
|
||||
Task Master's Claude Code integration uses the official `ai-sdk-provider-claude-code` package, providing:
|
||||
|
||||
- **Streaming Support**: Real-time token streaming for interactive experiences
|
||||
- **Full AI SDK Compatibility**: Works with generateText, streamText, and other AI SDK functions
|
||||
- **Automatic Error Handling**: Graceful degradation when Claude Code is unavailable
|
||||
- **Type Safety**: Full TypeScript support with proper type definitions
|
||||
|
||||
### Example AI SDK Usage
|
||||
|
||||
```javascript
|
||||
import { generateText } from 'ai';
|
||||
import { ClaudeCodeProvider } from './src/ai-providers/claude-code.js';
|
||||
|
||||
const provider = new ClaudeCodeProvider();
|
||||
const client = provider.getClient();
|
||||
|
||||
const result = await generateText({
|
||||
model: client('sonnet'),
|
||||
messages: [
|
||||
{ role: 'user', content: 'Hello Claude!' }
|
||||
]
|
||||
});
|
||||
|
||||
console.log(result.text);
|
||||
```
|
||||
|
||||
## Security Notes
|
||||
|
||||
- OAuth tokens are managed securely by Claude Code CLI
|
||||
- No API keys need to be stored in your project files
|
||||
- Tokens are automatically refreshed by the Claude Code CLI
|
||||
- Environment variables should only be used in secure environments
|
||||
|
||||
## Getting Help
|
||||
|
||||
If you encounter issues:
|
||||
|
||||
1. Check the Claude Code CLI documentation
|
||||
2. Verify your authentication setup with `claude setup-token --verify`
|
||||
3. Review Task Master logs for detailed error messages
|
||||
4. Open an issue with both Task Master and Claude Code version information
|
||||
@@ -1,4 +1,4 @@
|
||||
# Available Models as of September 23, 2025
|
||||
# Available Models as of September 19, 2025
|
||||
|
||||
## Main Models
|
||||
|
||||
@@ -119,7 +119,7 @@
|
||||
| groq | deepseek-r1-distill-llama-70b | 0.52 | 0.75 | 0.99 |
|
||||
| perplexity | sonar-pro | — | 3 | 15 |
|
||||
| perplexity | sonar | — | 1 | 1 |
|
||||
| perplexity | sonar-deep-research | 0.211 | 2 | 8 |
|
||||
| perplexity | deep-research | 0.211 | 2 | 8 |
|
||||
| perplexity | sonar-reasoning-pro | 0.211 | 2 | 8 |
|
||||
| perplexity | sonar-reasoning | 0.211 | 1 | 5 |
|
||||
| bedrock | us.anthropic.claude-3-opus-20240229-v1:0 | 0.725 | 15 | 75 |
|
||||
|
||||
@@ -75,50 +75,13 @@ function generateExampleFromSchema(schema) {
|
||||
return result;
|
||||
|
||||
case 'ZodString':
|
||||
// Check for min/max length constraints
|
||||
if (def.checks) {
|
||||
const minCheck = def.checks.find((c) => c.kind === 'min');
|
||||
const maxCheck = def.checks.find((c) => c.kind === 'max');
|
||||
if (minCheck && maxCheck) {
|
||||
return (
|
||||
'<string between ' +
|
||||
minCheck.value +
|
||||
'-' +
|
||||
maxCheck.value +
|
||||
' characters>'
|
||||
);
|
||||
} else if (minCheck) {
|
||||
return '<string with at least ' + minCheck.value + ' characters>';
|
||||
} else if (maxCheck) {
|
||||
return '<string up to ' + maxCheck.value + ' characters>';
|
||||
}
|
||||
}
|
||||
return '<string>';
|
||||
return 'string';
|
||||
|
||||
case 'ZodNumber':
|
||||
// Check for int, positive, min/max constraints
|
||||
if (def.checks) {
|
||||
const intCheck = def.checks.find((c) => c.kind === 'int');
|
||||
const minCheck = def.checks.find((c) => c.kind === 'min');
|
||||
const maxCheck = def.checks.find((c) => c.kind === 'max');
|
||||
|
||||
if (intCheck && minCheck && minCheck.value > 0) {
|
||||
return '<positive integer>';
|
||||
} else if (intCheck) {
|
||||
return '<integer>';
|
||||
} else if (minCheck || maxCheck) {
|
||||
return (
|
||||
'<number' +
|
||||
(minCheck ? ' >= ' + minCheck.value : '') +
|
||||
(maxCheck ? ' <= ' + maxCheck.value : '') +
|
||||
'>'
|
||||
);
|
||||
}
|
||||
}
|
||||
return '<number>';
|
||||
return 0;
|
||||
|
||||
case 'ZodBoolean':
|
||||
return '<boolean>';
|
||||
return false;
|
||||
|
||||
case 'ZodArray':
|
||||
const elementExample = generateExampleFromSchema(def.type);
|
||||
|
||||
48
output.txt
Normal file
48
output.txt
Normal file
File diff suppressed because one or more lines are too long
7394
package-lock.json
generated
7394
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
62
package.json
62
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "task-master-ai",
|
||||
"version": "0.27.2",
|
||||
"version": "0.27.0",
|
||||
"description": "A task management system for ambitious AI-driven development that doesn't overwhelm and confuse Cursor.",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
@@ -17,7 +17,7 @@
|
||||
"turbo:build": "turbo build",
|
||||
"turbo:typecheck": "turbo typecheck",
|
||||
"build:build-config": "npm run build -w @tm/build-config",
|
||||
"test": "cross-env NODE_ENV=test node --experimental-vm-modules node_modules/.bin/jest",
|
||||
"test": "node --experimental-vm-modules node_modules/.bin/jest",
|
||||
"test:unit": "node --experimental-vm-modules node_modules/.bin/jest --testPathPattern=unit",
|
||||
"test:integration": "node --experimental-vm-modules node_modules/.bin/jest --testPathPattern=integration",
|
||||
"test:fails": "node --experimental-vm-modules node_modules/.bin/jest --onlyFailures",
|
||||
@@ -33,9 +33,7 @@
|
||||
"inspector": "npx @modelcontextprotocol/inspector node dist/mcp-server.js",
|
||||
"mcp-server": "node dist/mcp-server.js",
|
||||
"format-check": "biome format .",
|
||||
"format": "biome format . --write",
|
||||
"deps:check": "manypkg check || echo 'Note: Workspace package version warnings are expected for internal @tm/* packages'",
|
||||
"deps:fix": "manypkg fix"
|
||||
"format": "biome format . --write"
|
||||
},
|
||||
"keywords": [
|
||||
"claude",
|
||||
@@ -52,26 +50,22 @@
|
||||
"author": "Eyal Toledano",
|
||||
"license": "MIT WITH Commons-Clause",
|
||||
"dependencies": {
|
||||
"@ai-sdk/amazon-bedrock": "^3.0.23",
|
||||
"@ai-sdk/anthropic": "^2.0.18",
|
||||
"@ai-sdk/azure": "^2.0.34",
|
||||
"@ai-sdk/google": "^2.0.16",
|
||||
"@ai-sdk/google-vertex": "^3.0.29",
|
||||
"@ai-sdk/groq": "^2.0.21",
|
||||
"@ai-sdk/mistral": "^2.0.16",
|
||||
"@ai-sdk/openai": "^2.0.34",
|
||||
"@ai-sdk/perplexity": "^2.0.10",
|
||||
"@ai-sdk/provider": "^2.0.0",
|
||||
"@ai-sdk/provider-utils": "^3.0.10",
|
||||
"@ai-sdk/xai": "^2.0.22",
|
||||
"@aws-sdk/credential-providers": "^3.895.0",
|
||||
"@ai-sdk/amazon-bedrock": "^2.2.9",
|
||||
"@ai-sdk/anthropic": "^1.2.10",
|
||||
"@ai-sdk/azure": "^1.3.17",
|
||||
"@ai-sdk/google": "^1.2.13",
|
||||
"@ai-sdk/google-vertex": "^2.2.23",
|
||||
"@ai-sdk/groq": "^1.2.9",
|
||||
"@ai-sdk/mistral": "^1.2.7",
|
||||
"@ai-sdk/openai": "^1.3.20",
|
||||
"@ai-sdk/perplexity": "^1.1.7",
|
||||
"@ai-sdk/xai": "^1.2.15",
|
||||
"@anthropic-ai/sdk": "^0.39.0",
|
||||
"@aws-sdk/credential-providers": "^3.817.0",
|
||||
"@inquirer/search": "^3.0.15",
|
||||
"@openrouter/ai-sdk-provider": "^1.2.0",
|
||||
"@openrouter/ai-sdk-provider": "^0.4.5",
|
||||
"@streamparser/json": "^0.0.22",
|
||||
"@supabase/supabase-js": "^2.57.4",
|
||||
"ai": "^5.0.51",
|
||||
"ai-sdk-provider-claude-code": "^1.1.4",
|
||||
"ai-sdk-provider-gemini-cli": "^1.1.1",
|
||||
"ai": "^4.3.10",
|
||||
"ajv": "^8.17.1",
|
||||
"ajv-formats": "^3.0.1",
|
||||
"boxen": "^8.0.1",
|
||||
@@ -79,9 +73,9 @@
|
||||
"cli-highlight": "^2.1.11",
|
||||
"cli-progress": "^3.12.0",
|
||||
"cli-table3": "^0.6.5",
|
||||
"commander": "^12.1.0",
|
||||
"commander": "^11.1.0",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.6.1",
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "^4.21.2",
|
||||
"fastmcp": "^3.5.0",
|
||||
"figlet": "^1.8.0",
|
||||
@@ -96,14 +90,17 @@
|
||||
"lru-cache": "^10.2.0",
|
||||
"marked": "^15.0.12",
|
||||
"marked-terminal": "^7.3.0",
|
||||
"ollama-ai-provider-v2": "^1.3.1",
|
||||
"ollama-ai-provider": "^1.2.0",
|
||||
"openai": "^4.89.0",
|
||||
"ora": "^8.2.0",
|
||||
"uuid": "^11.1.0",
|
||||
"zod": "^4.1.11"
|
||||
"zod": "^3.23.8",
|
||||
"zod-to-json-schema": "^3.24.5"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@anthropic-ai/claude-code": "^1.0.88",
|
||||
"@biomejs/cli-linux-x64": "^1.9.4"
|
||||
"@biomejs/cli-linux-x64": "^1.9.4",
|
||||
"ai-sdk-provider-gemini-cli": "^0.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
@@ -123,16 +120,15 @@
|
||||
"whatwg-url": "^11.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tm/cli": "*",
|
||||
"@biomejs/biome": "^1.9.4",
|
||||
"@changesets/changelog-github": "^0.5.1",
|
||||
"@changesets/cli": "^2.28.1",
|
||||
"@manypkg/cli": "^0.25.1",
|
||||
"@tm/ai-sdk-provider-grok-cli": "*",
|
||||
"@tm/cli": "*",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/marked-terminal": "^6.1.1",
|
||||
"concurrently": "^9.2.1",
|
||||
"cross-env": "^10.0.0",
|
||||
"dotenv-mono": "^1.5.1",
|
||||
"execa": "^8.0.1",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-node": "^29.7.0",
|
||||
@@ -141,8 +137,8 @@
|
||||
"supertest": "^7.1.0",
|
||||
"ts-jest": "^29.4.2",
|
||||
"tsdown": "^0.15.2",
|
||||
"tsx": "^4.20.4",
|
||||
"turbo": "2.5.6",
|
||||
"tsx": "^4.16.2",
|
||||
"turbo": "^2.5.6",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,165 +0,0 @@
|
||||
# AI SDK Provider for Grok CLI
|
||||
|
||||
A provider for the [AI SDK](https://sdk.vercel.ai) that integrates with [Grok CLI](https://docs.x.ai/api) for accessing xAI's Grok language models.
|
||||
|
||||
## Features
|
||||
|
||||
- ✅ **AI SDK v5 Compatible** - Full support for the latest AI SDK interfaces
|
||||
- ✅ **Streaming & Non-streaming** - Both generation modes supported
|
||||
- ✅ **Error Handling** - Comprehensive error handling with retry logic
|
||||
- ✅ **Type Safety** - Full TypeScript support with proper type definitions
|
||||
- ✅ **JSON Mode** - Automatic JSON extraction from responses
|
||||
- ✅ **Abort Signals** - Proper cancellation support
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install @tm/ai-sdk-provider-grok-cli
|
||||
# or
|
||||
yarn add @tm/ai-sdk-provider-grok-cli
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. Install the Grok CLI:
|
||||
|
||||
```bash
|
||||
npm install -g grok-cli
|
||||
# or follow xAI's installation instructions
|
||||
```
|
||||
|
||||
2. Set up authentication:
|
||||
|
||||
```bash
|
||||
export GROK_CLI_API_KEY="your-api-key"
|
||||
# or configure via grok CLI: grok config set api-key your-key
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```typescript
|
||||
import { grokCli } from '@tm/ai-sdk-provider-grok-cli';
|
||||
import { generateText } from 'ai';
|
||||
|
||||
const result = await generateText({
|
||||
model: grokCli('grok-3-latest'),
|
||||
prompt: 'Write a haiku about TypeScript'
|
||||
});
|
||||
|
||||
console.log(result.text);
|
||||
```
|
||||
|
||||
### Streaming
|
||||
|
||||
```typescript
|
||||
import { grokCli } from '@tm/ai-sdk-provider-grok-cli';
|
||||
import { streamText } from 'ai';
|
||||
|
||||
const { textStream } = await streamText({
|
||||
model: grokCli('grok-4-latest'),
|
||||
prompt: 'Explain quantum computing'
|
||||
});
|
||||
|
||||
for await (const delta of textStream) {
|
||||
process.stdout.write(delta);
|
||||
}
|
||||
```
|
||||
|
||||
### JSON Mode
|
||||
|
||||
```typescript
|
||||
import { grokCli } from '@tm/ai-sdk-provider-grok-cli';
|
||||
import { generateObject } from 'ai';
|
||||
import { z } from 'zod';
|
||||
|
||||
const result = await generateObject({
|
||||
model: grokCli('grok-3-latest'),
|
||||
schema: z.object({
|
||||
name: z.string(),
|
||||
age: z.number(),
|
||||
hobbies: z.array(z.string())
|
||||
}),
|
||||
prompt: 'Generate a person profile'
|
||||
});
|
||||
|
||||
console.log(result.object);
|
||||
```
|
||||
|
||||
## Supported Models
|
||||
|
||||
- `grok-3-latest` - Grok 3 (latest version)
|
||||
- `grok-4-latest` - Grok 4 (latest version)
|
||||
- `grok-4` - Grok 4 (stable)
|
||||
- Custom model strings supported
|
||||
|
||||
## Configuration
|
||||
|
||||
### Provider Settings
|
||||
|
||||
```typescript
|
||||
import { createGrokCli } from '@tm/ai-sdk-provider-grok-cli';
|
||||
|
||||
const grok = createGrokCli({
|
||||
apiKey: 'your-api-key', // Optional if set via env/CLI
|
||||
timeout: 120000, // 2 minutes default
|
||||
workingDirectory: '/path/to/project', // Optional
|
||||
baseURL: 'https://api.x.ai' // Optional
|
||||
});
|
||||
```
|
||||
|
||||
### Model Settings
|
||||
|
||||
```typescript
|
||||
const model = grok('grok-4-latest', {
|
||||
timeout: 300000, // 5 minutes for grok-4
|
||||
// Other CLI-specific settings
|
||||
});
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
The provider includes comprehensive error handling:
|
||||
|
||||
```typescript
|
||||
import {
|
||||
isAuthenticationError,
|
||||
isTimeoutError,
|
||||
isInstallationError
|
||||
} from '@tm/ai-sdk-provider-grok-cli';
|
||||
|
||||
try {
|
||||
const result = await generateText({
|
||||
model: grokCli('grok-4-latest'),
|
||||
prompt: 'Hello!'
|
||||
});
|
||||
} catch (error) {
|
||||
if (isAuthenticationError(error)) {
|
||||
console.error('Authentication failed:', error.message);
|
||||
} else if (isTimeoutError(error)) {
|
||||
console.error('Request timed out:', error.message);
|
||||
} else if (isInstallationError(error)) {
|
||||
console.error('Grok CLI not installed or not found in PATH');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Start development mode (keep running during development)
|
||||
npm run dev
|
||||
|
||||
# Type check
|
||||
npm run typecheck
|
||||
|
||||
# Run tests (requires build first)
|
||||
NODE_ENV=production npm run build
|
||||
npm test
|
||||
```
|
||||
|
||||
**Important**: Always run `npm run dev` and keep it running during development. This ensures proper compilation and hot-reloading of TypeScript files.
|
||||
@@ -1,35 +0,0 @@
|
||||
{
|
||||
"name": "@tm/ai-sdk-provider-grok-cli",
|
||||
"private": true,
|
||||
"description": "AI SDK provider for Grok CLI integration",
|
||||
"type": "module",
|
||||
"types": "./src/index.ts",
|
||||
"main": "./dist/index.js",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"test:ui": "vitest --ui",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider": "^2.0.0",
|
||||
"@ai-sdk/provider-utils": "^3.0.10",
|
||||
"jsonc-parser": "^3.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.18.6",
|
||||
"typescript": "^5.9.2",
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"keywords": ["ai", "grok", "x.ai", "cli", "language-model", "provider"],
|
||||
"files": ["dist/**/*", "README.md"],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
@@ -1,188 +0,0 @@
|
||||
/**
|
||||
* Tests for error handling utilities
|
||||
*/
|
||||
|
||||
import { APICallError, LoadAPIKeyError } from '@ai-sdk/provider';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
createAPICallError,
|
||||
createAuthenticationError,
|
||||
createInstallationError,
|
||||
createTimeoutError,
|
||||
getErrorMetadata,
|
||||
isAuthenticationError,
|
||||
isInstallationError,
|
||||
isTimeoutError
|
||||
} from './errors.js';
|
||||
|
||||
describe('createAPICallError', () => {
|
||||
it('should create APICallError with metadata', () => {
|
||||
const error = createAPICallError({
|
||||
message: 'Test error',
|
||||
code: 'TEST_ERROR',
|
||||
exitCode: 1,
|
||||
stderr: 'Error output',
|
||||
stdout: 'Success output',
|
||||
promptExcerpt: 'Test prompt',
|
||||
isRetryable: true
|
||||
});
|
||||
|
||||
expect(error).toBeInstanceOf(APICallError);
|
||||
expect(error.message).toBe('Test error');
|
||||
expect(error.isRetryable).toBe(true);
|
||||
expect(error.url).toBe('grok-cli://command');
|
||||
expect(error.data).toEqual({
|
||||
code: 'TEST_ERROR',
|
||||
exitCode: 1,
|
||||
stderr: 'Error output',
|
||||
stdout: 'Success output',
|
||||
promptExcerpt: 'Test prompt'
|
||||
});
|
||||
});
|
||||
|
||||
it('should create APICallError with minimal parameters', () => {
|
||||
const error = createAPICallError({
|
||||
message: 'Simple error'
|
||||
});
|
||||
|
||||
expect(error).toBeInstanceOf(APICallError);
|
||||
expect(error.message).toBe('Simple error');
|
||||
expect(error.isRetryable).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createAuthenticationError', () => {
|
||||
it('should create LoadAPIKeyError with custom message', () => {
|
||||
const error = createAuthenticationError({
|
||||
message: 'Custom auth error'
|
||||
});
|
||||
|
||||
expect(error).toBeInstanceOf(LoadAPIKeyError);
|
||||
expect(error.message).toBe('Custom auth error');
|
||||
});
|
||||
|
||||
it('should create LoadAPIKeyError with default message', () => {
|
||||
const error = createAuthenticationError({});
|
||||
|
||||
expect(error).toBeInstanceOf(LoadAPIKeyError);
|
||||
expect(error.message).toContain('Authentication failed');
|
||||
});
|
||||
});
|
||||
|
||||
describe('createTimeoutError', () => {
|
||||
it('should create APICallError for timeout', () => {
|
||||
const error = createTimeoutError({
|
||||
message: 'Operation timed out',
|
||||
timeoutMs: 5000,
|
||||
promptExcerpt: 'Test prompt'
|
||||
});
|
||||
|
||||
expect(error).toBeInstanceOf(APICallError);
|
||||
expect(error.message).toBe('Operation timed out');
|
||||
expect(error.isRetryable).toBe(true);
|
||||
expect(error.data).toEqual({
|
||||
code: 'TIMEOUT',
|
||||
promptExcerpt: 'Test prompt',
|
||||
timeoutMs: 5000
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('createInstallationError', () => {
|
||||
it('should create APICallError for installation issues', () => {
|
||||
const error = createInstallationError({
|
||||
message: 'CLI not found'
|
||||
});
|
||||
|
||||
expect(error).toBeInstanceOf(APICallError);
|
||||
expect(error.message).toBe('CLI not found');
|
||||
expect(error.isRetryable).toBe(false);
|
||||
expect(error.url).toBe('grok-cli://installation');
|
||||
});
|
||||
|
||||
it('should create APICallError with default message', () => {
|
||||
const error = createInstallationError({});
|
||||
|
||||
expect(error).toBeInstanceOf(APICallError);
|
||||
expect(error.message).toContain('Grok CLI is not installed');
|
||||
});
|
||||
});
|
||||
|
||||
describe('isAuthenticationError', () => {
|
||||
it('should return true for LoadAPIKeyError', () => {
|
||||
const error = new LoadAPIKeyError({ message: 'Auth failed' });
|
||||
expect(isAuthenticationError(error)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true for APICallError with 401 exit code', () => {
|
||||
const error = new APICallError({
|
||||
message: 'Unauthorized',
|
||||
data: { exitCode: 401 }
|
||||
});
|
||||
expect(isAuthenticationError(error)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for other errors', () => {
|
||||
const error = new Error('Generic error');
|
||||
expect(isAuthenticationError(error)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isTimeoutError', () => {
|
||||
it('should return true for timeout APICallError', () => {
|
||||
const error = new APICallError({
|
||||
message: 'Timeout',
|
||||
data: { code: 'TIMEOUT' }
|
||||
});
|
||||
expect(isTimeoutError(error)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for other errors', () => {
|
||||
const error = new APICallError({ message: 'Other error' });
|
||||
expect(isTimeoutError(error)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isInstallationError', () => {
|
||||
it('should return true for installation APICallError', () => {
|
||||
const error = new APICallError({
|
||||
message: 'Not installed',
|
||||
url: 'grok-cli://installation'
|
||||
});
|
||||
expect(isInstallationError(error)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for other errors', () => {
|
||||
const error = new APICallError({ message: 'Other error' });
|
||||
expect(isInstallationError(error)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getErrorMetadata', () => {
|
||||
it('should return metadata from APICallError', () => {
|
||||
const metadata = {
|
||||
code: 'TEST_ERROR',
|
||||
exitCode: 1,
|
||||
stderr: 'Error output'
|
||||
};
|
||||
const error = new APICallError({
|
||||
message: 'Test error',
|
||||
data: metadata
|
||||
});
|
||||
|
||||
const result = getErrorMetadata(error);
|
||||
expect(result).toEqual(metadata);
|
||||
});
|
||||
|
||||
it('should return undefined for errors without metadata', () => {
|
||||
const error = new Error('Generic error');
|
||||
const result = getErrorMetadata(error);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined for APICallError without data', () => {
|
||||
const error = new APICallError({ message: 'Test error' });
|
||||
const result = getErrorMetadata(error);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -1,187 +0,0 @@
|
||||
/**
|
||||
* Error handling utilities for Grok CLI provider
|
||||
*/
|
||||
|
||||
import { APICallError, LoadAPIKeyError } from '@ai-sdk/provider';
|
||||
import type { GrokCliErrorMetadata } from './types.js';
|
||||
|
||||
/**
|
||||
* Parameters for creating API call errors
|
||||
*/
|
||||
interface CreateAPICallErrorParams {
|
||||
/** Error message */
|
||||
message: string;
|
||||
/** Error code */
|
||||
code?: string;
|
||||
/** Process exit code */
|
||||
exitCode?: number;
|
||||
/** Standard error output */
|
||||
stderr?: string;
|
||||
/** Standard output */
|
||||
stdout?: string;
|
||||
/** Excerpt of the prompt */
|
||||
promptExcerpt?: string;
|
||||
/** Whether the error is retryable */
|
||||
isRetryable?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameters for creating authentication errors
|
||||
*/
|
||||
interface CreateAuthenticationErrorParams {
|
||||
/** Error message */
|
||||
message?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameters for creating timeout errors
|
||||
*/
|
||||
interface CreateTimeoutErrorParams {
|
||||
/** Error message */
|
||||
message: string;
|
||||
/** Excerpt of the prompt */
|
||||
promptExcerpt?: string;
|
||||
/** Timeout in milliseconds */
|
||||
timeoutMs: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameters for creating installation errors
|
||||
*/
|
||||
interface CreateInstallationErrorParams {
|
||||
/** Error message */
|
||||
message?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an API call error with Grok CLI specific metadata
|
||||
*/
|
||||
export function createAPICallError({
|
||||
message,
|
||||
code,
|
||||
exitCode,
|
||||
stderr,
|
||||
stdout,
|
||||
promptExcerpt,
|
||||
isRetryable = false
|
||||
}: CreateAPICallErrorParams): APICallError {
|
||||
const metadata: GrokCliErrorMetadata = {
|
||||
code,
|
||||
exitCode,
|
||||
stderr,
|
||||
stdout,
|
||||
promptExcerpt
|
||||
};
|
||||
|
||||
return new APICallError({
|
||||
message,
|
||||
isRetryable,
|
||||
url: 'grok-cli://command',
|
||||
requestBodyValues: promptExcerpt ? { prompt: promptExcerpt } : undefined,
|
||||
data: metadata
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an authentication error
|
||||
*/
|
||||
export function createAuthenticationError({
|
||||
message
|
||||
}: CreateAuthenticationErrorParams): LoadAPIKeyError {
|
||||
return new LoadAPIKeyError({
|
||||
message:
|
||||
message ||
|
||||
'Authentication failed. Please ensure Grok CLI is properly configured with API key.'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a timeout error
|
||||
*/
|
||||
export function createTimeoutError({
|
||||
message,
|
||||
promptExcerpt,
|
||||
timeoutMs
|
||||
}: CreateTimeoutErrorParams): APICallError {
|
||||
const metadata: GrokCliErrorMetadata & { timeoutMs: number } = {
|
||||
code: 'TIMEOUT',
|
||||
promptExcerpt,
|
||||
timeoutMs
|
||||
};
|
||||
|
||||
return new APICallError({
|
||||
message,
|
||||
isRetryable: true,
|
||||
url: 'grok-cli://command',
|
||||
requestBodyValues: promptExcerpt ? { prompt: promptExcerpt } : undefined,
|
||||
data: metadata
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a CLI installation error
|
||||
*/
|
||||
export function createInstallationError({
|
||||
message
|
||||
}: CreateInstallationErrorParams): APICallError {
|
||||
return new APICallError({
|
||||
message:
|
||||
message ||
|
||||
'Grok CLI is not installed or not found in PATH. Please install with: npm install -g @vibe-kit/grok-cli',
|
||||
isRetryable: false,
|
||||
url: 'grok-cli://installation',
|
||||
requestBodyValues: undefined
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an error is an authentication error
|
||||
*/
|
||||
export function isAuthenticationError(
|
||||
error: unknown
|
||||
): error is LoadAPIKeyError {
|
||||
if (error instanceof LoadAPIKeyError) return true;
|
||||
if (error instanceof APICallError) {
|
||||
const metadata = error.data as GrokCliErrorMetadata | undefined;
|
||||
if (!metadata) return false;
|
||||
return (
|
||||
metadata.exitCode === 401 ||
|
||||
metadata.code === 'AUTHENTICATION_ERROR' ||
|
||||
metadata.code === 'UNAUTHORIZED'
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an error is a timeout error
|
||||
*/
|
||||
export function isTimeoutError(error: unknown): error is APICallError {
|
||||
if (
|
||||
error instanceof APICallError &&
|
||||
(error.data as GrokCliErrorMetadata)?.code === 'TIMEOUT'
|
||||
)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an error is an installation error
|
||||
*/
|
||||
export function isInstallationError(error: unknown): error is APICallError {
|
||||
if (error instanceof APICallError && error.url === 'grok-cli://installation')
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get error metadata from an error
|
||||
*/
|
||||
export function getErrorMetadata(
|
||||
error: unknown
|
||||
): GrokCliErrorMetadata | undefined {
|
||||
if (error instanceof APICallError && error.data) {
|
||||
return error.data as GrokCliErrorMetadata;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
/**
|
||||
* Tests for Grok CLI provider
|
||||
*/
|
||||
|
||||
import { NoSuchModelError } from '@ai-sdk/provider';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { GrokCliLanguageModel } from './grok-cli-language-model.js';
|
||||
import { createGrokCli, grokCli } from './grok-cli-provider.js';
|
||||
|
||||
// Mock the GrokCliLanguageModel
|
||||
vi.mock('./grok-cli-language-model.js', () => ({
|
||||
GrokCliLanguageModel: vi.fn().mockImplementation((options) => ({
|
||||
modelId: options.id,
|
||||
settings: options.settings,
|
||||
provider: 'grok-cli'
|
||||
}))
|
||||
}));
|
||||
|
||||
describe('createGrokCli', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should create a provider with default settings', () => {
|
||||
const provider = createGrokCli();
|
||||
expect(typeof provider).toBe('function');
|
||||
expect(typeof provider.languageModel).toBe('function');
|
||||
expect(typeof provider.chat).toBe('function');
|
||||
expect(typeof provider.textEmbeddingModel).toBe('function');
|
||||
expect(typeof provider.imageModel).toBe('function');
|
||||
});
|
||||
|
||||
it('should create a provider with custom default settings', () => {
|
||||
const defaultSettings = {
|
||||
timeout: 5000,
|
||||
workingDirectory: '/custom/path'
|
||||
};
|
||||
const provider = createGrokCli({ defaultSettings });
|
||||
|
||||
const model = provider('grok-2-mini');
|
||||
|
||||
expect(GrokCliLanguageModel).toHaveBeenCalledWith({
|
||||
id: 'grok-2-mini',
|
||||
settings: defaultSettings
|
||||
});
|
||||
});
|
||||
|
||||
it('should create language models with merged settings', () => {
|
||||
const defaultSettings = { timeout: 5000 };
|
||||
const provider = createGrokCli({ defaultSettings });
|
||||
|
||||
const modelSettings = { apiKey: 'test-key' };
|
||||
const model = provider('grok-2', modelSettings);
|
||||
|
||||
expect(GrokCliLanguageModel).toHaveBeenCalledWith({
|
||||
id: 'grok-2',
|
||||
settings: { timeout: 5000, apiKey: 'test-key' }
|
||||
});
|
||||
});
|
||||
|
||||
it('should create models via languageModel method', () => {
|
||||
const provider = createGrokCli();
|
||||
const model = provider.languageModel('grok-2-mini', { timeout: 1000 });
|
||||
|
||||
expect(GrokCliLanguageModel).toHaveBeenCalledWith({
|
||||
id: 'grok-2-mini',
|
||||
settings: { timeout: 1000 }
|
||||
});
|
||||
});
|
||||
|
||||
it('should create models via chat method (alias)', () => {
|
||||
const provider = createGrokCli();
|
||||
const model = provider.chat('grok-2');
|
||||
|
||||
expect(GrokCliLanguageModel).toHaveBeenCalledWith({
|
||||
id: 'grok-2',
|
||||
settings: {}
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw error when called with new keyword', () => {
|
||||
const provider = createGrokCli();
|
||||
expect(() => {
|
||||
// @ts-expect-error - intentionally testing invalid usage
|
||||
new provider('grok-2');
|
||||
}).toThrow(
|
||||
'The Grok CLI model function cannot be called with the new keyword.'
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw NoSuchModelError for textEmbeddingModel', () => {
|
||||
const provider = createGrokCli();
|
||||
expect(() => {
|
||||
provider.textEmbeddingModel('test-model');
|
||||
}).toThrow(NoSuchModelError);
|
||||
});
|
||||
|
||||
it('should throw NoSuchModelError for imageModel', () => {
|
||||
const provider = createGrokCli();
|
||||
expect(() => {
|
||||
provider.imageModel('test-model');
|
||||
}).toThrow(NoSuchModelError);
|
||||
});
|
||||
});
|
||||
|
||||
describe('default grokCli provider', () => {
|
||||
it('should be a pre-configured provider instance', () => {
|
||||
expect(typeof grokCli).toBe('function');
|
||||
expect(typeof grokCli.languageModel).toBe('function');
|
||||
expect(typeof grokCli.chat).toBe('function');
|
||||
});
|
||||
|
||||
it('should create models with default configuration', () => {
|
||||
const model = grokCli('grok-2-mini');
|
||||
|
||||
expect(GrokCliLanguageModel).toHaveBeenCalledWith({
|
||||
id: 'grok-2-mini',
|
||||
settings: {}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,108 +0,0 @@
|
||||
/**
|
||||
* Grok CLI provider implementation for AI SDK v5
|
||||
*/
|
||||
|
||||
import type { LanguageModelV2, ProviderV2 } from '@ai-sdk/provider';
|
||||
import { NoSuchModelError } from '@ai-sdk/provider';
|
||||
import { GrokCliLanguageModel } from './grok-cli-language-model.js';
|
||||
import type { GrokCliModelId, GrokCliSettings } from './types.js';
|
||||
|
||||
/**
|
||||
* Grok CLI provider interface that extends the AI SDK's ProviderV2
|
||||
*/
|
||||
export interface GrokCliProvider extends ProviderV2 {
|
||||
/**
|
||||
* Creates a language model instance for the specified model ID.
|
||||
* This is a shorthand for calling `languageModel()`.
|
||||
*/
|
||||
(modelId: GrokCliModelId, settings?: GrokCliSettings): LanguageModelV2;
|
||||
|
||||
/**
|
||||
* Creates a language model instance for text generation.
|
||||
*/
|
||||
languageModel(
|
||||
modelId: GrokCliModelId,
|
||||
settings?: GrokCliSettings
|
||||
): LanguageModelV2;
|
||||
|
||||
/**
|
||||
* Alias for `languageModel()` to maintain compatibility with AI SDK patterns.
|
||||
*/
|
||||
chat(modelId: GrokCliModelId, settings?: GrokCliSettings): LanguageModelV2;
|
||||
|
||||
textEmbeddingModel(modelId: string): never;
|
||||
imageModel(modelId: string): never;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration options for creating a Grok CLI provider instance
|
||||
*/
|
||||
export interface GrokCliProviderSettings {
|
||||
/**
|
||||
* Default settings to use for all models created by this provider.
|
||||
* Individual model settings will override these defaults.
|
||||
*/
|
||||
defaultSettings?: GrokCliSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Grok CLI provider instance with the specified configuration.
|
||||
* The provider can be used to create language models for interacting with Grok models.
|
||||
*/
|
||||
export function createGrokCli(
|
||||
options: GrokCliProviderSettings = {}
|
||||
): GrokCliProvider {
|
||||
const createModel = (
|
||||
modelId: GrokCliModelId,
|
||||
settings: GrokCliSettings = {}
|
||||
): LanguageModelV2 => {
|
||||
const mergedSettings = {
|
||||
...options.defaultSettings,
|
||||
...settings
|
||||
};
|
||||
|
||||
return new GrokCliLanguageModel({
|
||||
id: modelId,
|
||||
settings: mergedSettings
|
||||
});
|
||||
};
|
||||
|
||||
const provider = function (
|
||||
modelId: GrokCliModelId,
|
||||
settings?: GrokCliSettings
|
||||
) {
|
||||
if (new.target) {
|
||||
throw new Error(
|
||||
'The Grok CLI model function cannot be called with the new keyword.'
|
||||
);
|
||||
}
|
||||
|
||||
return createModel(modelId, settings);
|
||||
};
|
||||
|
||||
provider.languageModel = createModel;
|
||||
provider.chat = createModel; // Alias for languageModel
|
||||
|
||||
// Add textEmbeddingModel method that throws NoSuchModelError
|
||||
provider.textEmbeddingModel = (modelId: string) => {
|
||||
throw new NoSuchModelError({
|
||||
modelId,
|
||||
modelType: 'textEmbeddingModel'
|
||||
});
|
||||
};
|
||||
|
||||
provider.imageModel = (modelId: string) => {
|
||||
throw new NoSuchModelError({
|
||||
modelId,
|
||||
modelType: 'imageModel'
|
||||
});
|
||||
};
|
||||
|
||||
return provider as GrokCliProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default Grok CLI provider instance.
|
||||
* Pre-configured provider for quick usage without custom settings.
|
||||
*/
|
||||
export const grokCli = createGrokCli();
|
||||
@@ -1,64 +0,0 @@
|
||||
/**
|
||||
* Provider exports for creating and configuring Grok CLI instances.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates a new Grok CLI provider instance and the default provider instance.
|
||||
*/
|
||||
export { createGrokCli, grokCli } from './grok-cli-provider.js';
|
||||
|
||||
/**
|
||||
* Type definitions for the Grok CLI provider.
|
||||
*/
|
||||
export type {
|
||||
GrokCliProvider,
|
||||
GrokCliProviderSettings
|
||||
} from './grok-cli-provider.js';
|
||||
|
||||
/**
|
||||
* Language model implementation for Grok CLI.
|
||||
* This class implements the AI SDK's LanguageModelV2 interface.
|
||||
*/
|
||||
export { GrokCliLanguageModel } from './grok-cli-language-model.js';
|
||||
|
||||
/**
|
||||
* Type definitions for Grok CLI language models.
|
||||
*/
|
||||
export type {
|
||||
GrokCliModelId,
|
||||
GrokCliLanguageModelOptions,
|
||||
GrokCliSettings,
|
||||
GrokCliMessage,
|
||||
GrokCliResponse,
|
||||
GrokCliErrorMetadata
|
||||
} from './types.js';
|
||||
|
||||
/**
|
||||
* Error handling utilities for Grok CLI.
|
||||
* These functions help create and identify specific error types.
|
||||
*/
|
||||
export {
|
||||
isAuthenticationError,
|
||||
isTimeoutError,
|
||||
isInstallationError,
|
||||
getErrorMetadata,
|
||||
createAPICallError,
|
||||
createAuthenticationError,
|
||||
createTimeoutError,
|
||||
createInstallationError
|
||||
} from './errors.js';
|
||||
|
||||
/**
|
||||
* Message conversion utilities for Grok CLI communication.
|
||||
*/
|
||||
export {
|
||||
convertToGrokCliMessages,
|
||||
convertFromGrokCliResponse,
|
||||
createPromptFromMessages,
|
||||
escapeShellArg
|
||||
} from './message-converter.js';
|
||||
|
||||
/**
|
||||
* JSON extraction utilities for parsing Grok responses.
|
||||
*/
|
||||
export { extractJson } from './json-extractor.js';
|
||||
@@ -1,81 +0,0 @@
|
||||
/**
|
||||
* Tests for JSON extraction utilities
|
||||
*/
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { extractJson } from './json-extractor.js';
|
||||
|
||||
describe('extractJson', () => {
|
||||
it('should extract JSON from markdown code blocks', () => {
|
||||
const text = '```json\n{"name": "test", "value": 42}\n```';
|
||||
const result = extractJson(text);
|
||||
expect(JSON.parse(result)).toEqual({ name: 'test', value: 42 });
|
||||
});
|
||||
|
||||
it('should extract JSON from generic code blocks', () => {
|
||||
const text = '```\n{"name": "test", "value": 42}\n```';
|
||||
const result = extractJson(text);
|
||||
expect(JSON.parse(result)).toEqual({ name: 'test', value: 42 });
|
||||
});
|
||||
|
||||
it('should remove JavaScript variable declarations', () => {
|
||||
const text = 'const result = {"name": "test", "value": 42};';
|
||||
const result = extractJson(text);
|
||||
expect(JSON.parse(result)).toEqual({ name: 'test', value: 42 });
|
||||
});
|
||||
|
||||
it('should handle let variable declarations', () => {
|
||||
const text = 'let data = {"name": "test", "value": 42};';
|
||||
const result = extractJson(text);
|
||||
expect(JSON.parse(result)).toEqual({ name: 'test', value: 42 });
|
||||
});
|
||||
|
||||
it('should handle var variable declarations', () => {
|
||||
const text = 'var config = {"name": "test", "value": 42};';
|
||||
const result = extractJson(text);
|
||||
expect(JSON.parse(result)).toEqual({ name: 'test', value: 42 });
|
||||
});
|
||||
|
||||
it('should extract JSON arrays', () => {
|
||||
const text = '[{"name": "test1"}, {"name": "test2"}]';
|
||||
const result = extractJson(text);
|
||||
expect(JSON.parse(result)).toEqual([{ name: 'test1' }, { name: 'test2' }]);
|
||||
});
|
||||
|
||||
it('should convert JavaScript object literals to JSON', () => {
|
||||
const text = "{name: 'test', value: 42}";
|
||||
const result = extractJson(text);
|
||||
expect(JSON.parse(result)).toEqual({ name: 'test', value: 42 });
|
||||
});
|
||||
|
||||
it('should return valid JSON (canonical formatting)', () => {
|
||||
const text = '{"name": "test", "value": 42}';
|
||||
const result = extractJson(text);
|
||||
expect(JSON.parse(result)).toEqual({ name: 'test', value: 42 });
|
||||
});
|
||||
|
||||
it('should return original text when JSON parsing fails completely', () => {
|
||||
const text = 'This is not JSON at all';
|
||||
const result = extractJson(text);
|
||||
expect(result).toBe('This is not JSON at all');
|
||||
});
|
||||
|
||||
it('should handle complex nested objects', () => {
|
||||
const text =
|
||||
'```json\n{\n "user": {\n "name": "John",\n "age": 30\n },\n "items": [1, 2, 3]\n}\n```';
|
||||
const result = extractJson(text);
|
||||
expect(JSON.parse(result)).toEqual({
|
||||
user: {
|
||||
name: 'John',
|
||||
age: 30
|
||||
},
|
||||
items: [1, 2, 3]
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle mixed quotes in object literals', () => {
|
||||
const text = `{name: "test", value: 'mixed quotes'}`;
|
||||
const result = extractJson(text);
|
||||
expect(JSON.parse(result)).toEqual({ name: 'test', value: 'mixed quotes' });
|
||||
});
|
||||
});
|
||||
@@ -1,132 +0,0 @@
|
||||
/**
|
||||
* Extract JSON from AI's response using a tolerant parser.
|
||||
*
|
||||
* The function removes common wrappers such as markdown fences or variable
|
||||
* declarations and then attempts to parse the remaining text with
|
||||
* `jsonc-parser`. If valid JSON (or JSONC) can be parsed, it is returned as a
|
||||
* string via `JSON.stringify`. Otherwise the original text is returned.
|
||||
*
|
||||
* @param text - Raw text which may contain JSON
|
||||
* @returns A valid JSON string if extraction succeeds, otherwise the original text
|
||||
*/
|
||||
import { parse, type ParseError } from 'jsonc-parser';
|
||||
|
||||
export function extractJson(text: string): string {
|
||||
let content = text.trim();
|
||||
|
||||
// Strip ```json or ``` fences
|
||||
const fenceMatch = /```(?:json)?\s*([\s\S]*?)\s*```/i.exec(content);
|
||||
if (fenceMatch) {
|
||||
content = fenceMatch[1];
|
||||
}
|
||||
|
||||
// Strip variable declarations like `const foo =` or `let foo =`
|
||||
const varMatch = /^\s*(?:const|let|var)\s+\w+\s*=\s*([\s\S]*)/i.exec(content);
|
||||
if (varMatch) {
|
||||
content = varMatch[1];
|
||||
// Remove trailing semicolon if present
|
||||
if (content.trim().endsWith(';')) {
|
||||
content = content.trim().slice(0, -1);
|
||||
}
|
||||
}
|
||||
|
||||
// Find the first opening bracket
|
||||
const firstObj = content.indexOf('{');
|
||||
const firstArr = content.indexOf('[');
|
||||
if (firstObj === -1 && firstArr === -1) {
|
||||
return text;
|
||||
}
|
||||
const start =
|
||||
firstArr === -1
|
||||
? firstObj
|
||||
: firstObj === -1
|
||||
? firstArr
|
||||
: Math.min(firstObj, firstArr);
|
||||
content = content.slice(start);
|
||||
|
||||
// Try to parse the entire string with jsonc-parser
|
||||
const tryParse = (value: string): string | undefined => {
|
||||
const errors: ParseError[] = [];
|
||||
try {
|
||||
const result = parse(value, errors, { allowTrailingComma: true });
|
||||
if (errors.length === 0) {
|
||||
return JSON.stringify(result, null, 2);
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const parsed = tryParse(content);
|
||||
if (parsed !== undefined) {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
// If parsing the full string failed, use a more efficient approach
|
||||
// to find valid JSON boundaries
|
||||
const openChar = content[0];
|
||||
const closeChar = openChar === '{' ? '}' : ']';
|
||||
|
||||
// Find all potential closing positions by tracking nesting depth
|
||||
const closingPositions: number[] = [];
|
||||
let depth = 0;
|
||||
let inString = false;
|
||||
let escapeNext = false;
|
||||
|
||||
for (let i = 0; i < content.length; i++) {
|
||||
const char = content[i];
|
||||
|
||||
if (escapeNext) {
|
||||
escapeNext = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === '\\') {
|
||||
escapeNext = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === '"' && !inString) {
|
||||
inString = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === '"' && inString) {
|
||||
inString = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip content inside strings
|
||||
if (inString) continue;
|
||||
|
||||
if (char === openChar) {
|
||||
depth++;
|
||||
} else if (char === closeChar) {
|
||||
depth--;
|
||||
if (depth === 0) {
|
||||
closingPositions.push(i + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try parsing at each valid closing position, starting from the end
|
||||
for (let i = closingPositions.length - 1; i >= 0; i--) {
|
||||
const attempt = tryParse(content.slice(0, closingPositions[i]));
|
||||
if (attempt !== undefined) {
|
||||
return attempt;
|
||||
}
|
||||
}
|
||||
|
||||
// As a final fallback, try the original character-by-character approach
|
||||
// but only for the last 1000 characters to limit performance impact
|
||||
const searchStart = Math.max(0, content.length - 1000);
|
||||
for (let end = content.length - 1; end > searchStart; end--) {
|
||||
const attempt = tryParse(content.slice(0, end));
|
||||
if (attempt !== undefined) {
|
||||
return attempt;
|
||||
}
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
/**
|
||||
* Tests for message conversion utilities
|
||||
*/
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
convertFromGrokCliResponse,
|
||||
convertToGrokCliMessages,
|
||||
createPromptFromMessages,
|
||||
escapeShellArg
|
||||
} from './message-converter.js';
|
||||
|
||||
describe('convertToGrokCliMessages', () => {
|
||||
it('should convert string content messages', () => {
|
||||
const messages = [
|
||||
{ role: 'user', content: 'Hello, world!' },
|
||||
{ role: 'assistant', content: 'Hi there!' }
|
||||
];
|
||||
|
||||
const result = convertToGrokCliMessages(messages);
|
||||
|
||||
expect(result).toEqual([
|
||||
{ role: 'user', content: 'Hello, world!' },
|
||||
{ role: 'assistant', content: 'Hi there!' }
|
||||
]);
|
||||
});
|
||||
|
||||
it('should convert array content messages', () => {
|
||||
const messages = [
|
||||
{
|
||||
role: 'user',
|
||||
content: [
|
||||
{ type: 'text', text: 'Hello' },
|
||||
{ type: 'text', text: 'World' }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const result = convertToGrokCliMessages(messages);
|
||||
|
||||
expect(result).toEqual([{ role: 'user', content: 'Hello\nWorld' }]);
|
||||
});
|
||||
|
||||
it('should convert object content messages', () => {
|
||||
const messages = [
|
||||
{
|
||||
role: 'user',
|
||||
content: { text: 'Hello from object' }
|
||||
}
|
||||
];
|
||||
|
||||
const result = convertToGrokCliMessages(messages);
|
||||
|
||||
expect(result).toEqual([{ role: 'user', content: 'Hello from object' }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('convertFromGrokCliResponse', () => {
|
||||
it('should parse JSONL response format', () => {
|
||||
const responseText = `{"role": "assistant", "content": "Hello there!", "usage": {"prompt_tokens": 10, "completion_tokens": 5, "total_tokens": 15}}`;
|
||||
|
||||
const result = convertFromGrokCliResponse(responseText);
|
||||
|
||||
expect(result).toEqual({
|
||||
text: 'Hello there!',
|
||||
usage: {
|
||||
promptTokens: 10,
|
||||
completionTokens: 5,
|
||||
totalTokens: 15
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle multiple lines in JSONL format', () => {
|
||||
const responseText = `{"role": "user", "content": "Hello"}
|
||||
{"role": "assistant", "content": "Hi there!", "usage": {"prompt_tokens": 5, "completion_tokens": 3}}`;
|
||||
|
||||
const result = convertFromGrokCliResponse(responseText);
|
||||
|
||||
expect(result).toEqual({
|
||||
text: 'Hi there!',
|
||||
usage: {
|
||||
promptTokens: 5,
|
||||
completionTokens: 3,
|
||||
totalTokens: 0
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should fallback to raw text when parsing fails', () => {
|
||||
const responseText = 'Invalid JSON response';
|
||||
|
||||
const result = convertFromGrokCliResponse(responseText);
|
||||
|
||||
expect(result).toEqual({
|
||||
text: 'Invalid JSON response',
|
||||
usage: undefined
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('createPromptFromMessages', () => {
|
||||
it('should create formatted prompt from messages', () => {
|
||||
const messages = [
|
||||
{ role: 'system', content: 'You are a helpful assistant.' },
|
||||
{ role: 'user', content: 'What is 2+2?' },
|
||||
{ role: 'assistant', content: '2+2 equals 4.' }
|
||||
];
|
||||
|
||||
const result = createPromptFromMessages(messages);
|
||||
|
||||
expect(result).toBe(
|
||||
'System: You are a helpful assistant.\n\nUser: What is 2+2?\n\nAssistant: 2+2 equals 4.'
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle custom role names', () => {
|
||||
const messages = [{ role: 'custom', content: 'Custom message' }];
|
||||
|
||||
const result = createPromptFromMessages(messages);
|
||||
|
||||
expect(result).toBe('custom: Custom message');
|
||||
});
|
||||
|
||||
it('should trim whitespace from message content', () => {
|
||||
const messages = [
|
||||
{ role: 'user', content: ' Hello with spaces ' },
|
||||
{ role: 'assistant', content: '\n\nResponse with newlines\n\n' }
|
||||
];
|
||||
|
||||
const result = createPromptFromMessages(messages);
|
||||
|
||||
expect(result).toBe(
|
||||
'User: Hello with spaces\n\nAssistant: Response with newlines'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('escapeShellArg', () => {
|
||||
it('should escape single quotes', () => {
|
||||
const arg = "It's a test";
|
||||
const result = escapeShellArg(arg);
|
||||
expect(result).toBe("'It'\\''s a test'");
|
||||
});
|
||||
|
||||
it('should handle strings without special characters', () => {
|
||||
const arg = 'simple string';
|
||||
const result = escapeShellArg(arg);
|
||||
expect(result).toBe("'simple string'");
|
||||
});
|
||||
|
||||
it('should convert non-string values to strings', () => {
|
||||
const arg = 123;
|
||||
const result = escapeShellArg(arg);
|
||||
expect(result).toBe("'123'");
|
||||
});
|
||||
|
||||
it('should handle empty strings', () => {
|
||||
const arg = '';
|
||||
const result = escapeShellArg(arg);
|
||||
expect(result).toBe("''");
|
||||
});
|
||||
});
|
||||
@@ -1,81 +0,0 @@
|
||||
/**
|
||||
* Type definitions for Grok CLI provider
|
||||
*/
|
||||
|
||||
/**
|
||||
* Settings for configuring Grok CLI behavior
|
||||
*/
|
||||
export interface GrokCliSettings {
|
||||
/** API key for Grok CLI */
|
||||
apiKey?: string;
|
||||
/** Base URL for Grok API */
|
||||
baseURL?: string;
|
||||
/** Default model to use */
|
||||
model?: string;
|
||||
/** Timeout in milliseconds */
|
||||
timeout?: number;
|
||||
/** Working directory for CLI commands */
|
||||
workingDirectory?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model identifiers supported by Grok CLI
|
||||
*/
|
||||
export type GrokCliModelId = string;
|
||||
|
||||
/**
|
||||
* Error metadata for Grok CLI operations
|
||||
*/
|
||||
export interface GrokCliErrorMetadata {
|
||||
/** Error code */
|
||||
code?: string;
|
||||
/** Process exit code */
|
||||
exitCode?: number;
|
||||
/** Standard error output */
|
||||
stderr?: string;
|
||||
/** Standard output */
|
||||
stdout?: string;
|
||||
/** Excerpt of the prompt that caused the error */
|
||||
promptExcerpt?: string;
|
||||
/** Timeout value in milliseconds */
|
||||
timeoutMs?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Message format for Grok CLI communication
|
||||
*/
|
||||
export interface GrokCliMessage {
|
||||
/** Message role (user, assistant, system) */
|
||||
role: string;
|
||||
/** Message content */
|
||||
content: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Response format from Grok CLI
|
||||
*/
|
||||
export interface GrokCliResponse {
|
||||
/** Message role */
|
||||
role: string;
|
||||
/** Response content */
|
||||
content: string;
|
||||
/** Token usage information */
|
||||
usage?: {
|
||||
/** Input tokens used */
|
||||
prompt_tokens?: number;
|
||||
/** Output tokens used */
|
||||
completion_tokens?: number;
|
||||
/** Total tokens used */
|
||||
total_tokens?: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration options for Grok CLI language model
|
||||
*/
|
||||
export interface GrokCliLanguageModelOptions {
|
||||
/** Model identifier */
|
||||
id: GrokCliModelId;
|
||||
/** Model settings */
|
||||
settings?: GrokCliSettings;
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2022"],
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"baseUrl": ".",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictBindCallApply": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"noImplicitThis": true,
|
||||
"alwaysStrict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"moduleResolution": "bundler",
|
||||
"moduleDetection": "force",
|
||||
"types": ["node"],
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"allowImportingTsExtensions": false
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", "tests", "**/*.test.ts", "**/*.spec.ts"]
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
# @tm/build-config
|
||||
|
||||
## null
|
||||
|
||||
## 1.0.1
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"name": "@tm/build-config",
|
||||
"version": "1.0.1",
|
||||
"description": "Shared build configuration for Task Master monorepo",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
@@ -20,7 +21,8 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.9.2"
|
||||
"dotenv-mono": "^1.5.1",
|
||||
"typescript": "^5.7.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"tsup": "^8.5.0"
|
||||
|
||||
@@ -43,9 +43,9 @@ export const baseConfig: Partial<UserConfig> = {
|
||||
export function mergeConfig(
|
||||
base: Partial<UserConfig>,
|
||||
overrides: Partial<UserConfig>
|
||||
): UserConfig {
|
||||
): Partial<UserConfig> {
|
||||
return {
|
||||
...base,
|
||||
...overrides
|
||||
} as UserConfig;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## null
|
||||
|
||||
## 0.26.1
|
||||
|
||||
All notable changes to the @task-master/tm-core package will be documented in this file.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"name": "@tm/core",
|
||||
"version": "0.26.1",
|
||||
"private": true,
|
||||
"description": "Core library for Task Master - TypeScript task management system",
|
||||
"type": "module",
|
||||
@@ -31,13 +32,21 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@supabase/supabase-js": "^2.57.4",
|
||||
"zod": "^4.1.11"
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.10.5",
|
||||
"@vitest/coverage-v8": "^3.2.4",
|
||||
"typescript": "^5.9.2",
|
||||
"vitest": "^3.2.4"
|
||||
"@biomejs/biome": "^1.9.4",
|
||||
"@tm/build-config": "*",
|
||||
"@types/node": "^20.11.30",
|
||||
"@vitest/coverage-v8": "^2.0.5",
|
||||
"dotenv-mono": "^1.3.14",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsup": "^8.5.0",
|
||||
"typescript": "^5.4.3",
|
||||
"vitest": "^2.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"files": ["src", "README.md", "CHANGELOG.md"],
|
||||
"keywords": ["task-management", "typescript", "ai", "prd", "parser"],
|
||||
|
||||
@@ -3,17 +3,7 @@
|
||||
* This file defines the contract for all storage implementations
|
||||
*/
|
||||
|
||||
import type { Task, TaskMetadata, TaskStatus } from '../types/index.js';
|
||||
|
||||
/**
|
||||
* Result type for updateTaskStatus operations
|
||||
*/
|
||||
export interface UpdateStatusResult {
|
||||
success: boolean;
|
||||
oldStatus: TaskStatus;
|
||||
newStatus: TaskStatus;
|
||||
taskId: string;
|
||||
}
|
||||
import type { Task, TaskMetadata } from '../types/index.js';
|
||||
|
||||
/**
|
||||
* Interface for storage operations on tasks
|
||||
@@ -64,19 +54,6 @@ export interface IStorage {
|
||||
tag?: string
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* Update task or subtask status by ID
|
||||
* @param taskId - ID of the task or subtask (e.g., "1" or "1.2")
|
||||
* @param newStatus - New status to set
|
||||
* @param tag - Optional tag context for the task
|
||||
* @returns Promise that resolves to update result with old and new status
|
||||
*/
|
||||
updateTaskStatus(
|
||||
taskId: string,
|
||||
newStatus: TaskStatus,
|
||||
tag?: string
|
||||
): Promise<UpdateStatusResult>;
|
||||
|
||||
/**
|
||||
* Delete a task by ID
|
||||
* @param taskId - ID of the task to delete
|
||||
@@ -214,11 +191,6 @@ export abstract class BaseStorage implements IStorage {
|
||||
updates: Partial<Task>,
|
||||
tag?: string
|
||||
): Promise<void>;
|
||||
abstract updateTaskStatus(
|
||||
taskId: string,
|
||||
newStatus: TaskStatus,
|
||||
tag?: string
|
||||
): Promise<UpdateStatusResult>;
|
||||
abstract deleteTask(taskId: string, tag?: string): Promise<void>;
|
||||
abstract exists(tag?: string): Promise<boolean>;
|
||||
abstract loadMetadata(tag?: string): Promise<TaskMetadata | null>;
|
||||
|
||||
@@ -446,7 +446,7 @@ export class TaskService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update task status - delegates to storage layer which handles storage-specific logic
|
||||
* Update task status
|
||||
*/
|
||||
async updateTaskStatus(
|
||||
taskId: string | number,
|
||||
@@ -468,28 +468,49 @@ export class TaskService {
|
||||
|
||||
// Use provided tag or get active tag
|
||||
const activeTag = tag || this.getActiveTag();
|
||||
|
||||
const taskIdStr = String(taskId);
|
||||
|
||||
try {
|
||||
// Delegate to storage layer which handles the specific logic for tasks vs subtasks
|
||||
return await this.storage.updateTaskStatus(
|
||||
taskIdStr,
|
||||
newStatus,
|
||||
activeTag
|
||||
// TODO: For now, assume it's a regular task and just try to update directly
|
||||
// In the future, we can add subtask support if needed
|
||||
if (taskIdStr.includes('.')) {
|
||||
throw new TaskMasterError(
|
||||
'Subtask status updates not yet supported in API storage',
|
||||
ERROR_CODES.NOT_IMPLEMENTED
|
||||
);
|
||||
}
|
||||
|
||||
// Get the current task to get old status (simple, direct approach)
|
||||
let currentTask: Task | null;
|
||||
try {
|
||||
// Try to get the task directly
|
||||
currentTask = await this.storage.loadTask(taskIdStr, activeTag);
|
||||
} catch (error) {
|
||||
throw new TaskMasterError(
|
||||
`Failed to update task status for ${taskIdStr}`,
|
||||
ERROR_CODES.STORAGE_ERROR,
|
||||
{
|
||||
operation: 'updateTaskStatus',
|
||||
resource: 'task',
|
||||
taskId: taskIdStr,
|
||||
newStatus,
|
||||
tag: activeTag
|
||||
},
|
||||
`Failed to load task ${taskIdStr}`,
|
||||
ERROR_CODES.TASK_NOT_FOUND,
|
||||
{ taskId: taskIdStr },
|
||||
error as Error
|
||||
);
|
||||
}
|
||||
|
||||
if (!currentTask) {
|
||||
throw new TaskMasterError(
|
||||
`Task ${taskIdStr} not found`,
|
||||
ERROR_CODES.TASK_NOT_FOUND
|
||||
);
|
||||
}
|
||||
|
||||
const oldStatus = currentTask.status;
|
||||
|
||||
// Simple, direct update - just change the status
|
||||
await this.storage.updateTask(taskIdStr, { status: newStatus }, activeTag);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
oldStatus,
|
||||
newStatus,
|
||||
taskId: taskIdStr
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,15 +5,9 @@
|
||||
|
||||
import type {
|
||||
IStorage,
|
||||
StorageStats,
|
||||
UpdateStatusResult
|
||||
StorageStats
|
||||
} from '../interfaces/storage.interface.js';
|
||||
import type {
|
||||
Task,
|
||||
TaskMetadata,
|
||||
TaskTag,
|
||||
TaskStatus
|
||||
} from '../types/index.js';
|
||||
import type { Task, TaskMetadata, TaskTag } from '../types/index.js';
|
||||
import { ERROR_CODES, TaskMasterError } from '../errors/task-master-error.js';
|
||||
import { TaskRepository } from '../repositories/task-repository.interface.js';
|
||||
import { SupabaseTaskRepository } from '../repositories/supabase-task-repository.js';
|
||||
@@ -491,62 +485,6 @@ export class ApiStorage implements IStorage {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update task or subtask status by ID - for API storage
|
||||
*/
|
||||
async updateTaskStatus(
|
||||
taskId: string,
|
||||
newStatus: TaskStatus,
|
||||
tag?: string
|
||||
): Promise<UpdateStatusResult> {
|
||||
await this.ensureInitialized();
|
||||
|
||||
try {
|
||||
const existingTask = await this.retryOperation(() =>
|
||||
this.repository.getTask(this.projectId, taskId)
|
||||
);
|
||||
|
||||
if (!existingTask) {
|
||||
throw new Error(`Task ${taskId} not found`);
|
||||
}
|
||||
|
||||
const oldStatus = existingTask.status;
|
||||
if (oldStatus === newStatus) {
|
||||
return {
|
||||
success: true,
|
||||
oldStatus,
|
||||
newStatus,
|
||||
taskId
|
||||
};
|
||||
}
|
||||
|
||||
// Update the task/subtask status
|
||||
await this.retryOperation(() =>
|
||||
this.repository.updateTask(this.projectId, taskId, {
|
||||
status: newStatus,
|
||||
updatedAt: new Date().toISOString()
|
||||
})
|
||||
);
|
||||
|
||||
// Note: Parent status auto-adjustment is handled by the backend API service
|
||||
// which has its own business logic for managing task relationships
|
||||
|
||||
return {
|
||||
success: true,
|
||||
oldStatus,
|
||||
newStatus,
|
||||
taskId
|
||||
};
|
||||
} catch (error) {
|
||||
throw new TaskMasterError(
|
||||
'Failed to update task status via API',
|
||||
ERROR_CODES.STORAGE_ERROR,
|
||||
{ operation: 'updateTaskStatus', taskId, newStatus, tag },
|
||||
error as Error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all available tags
|
||||
*/
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
* @fileoverview Refactored file-based storage implementation for Task Master
|
||||
*/
|
||||
|
||||
import type { Task, TaskMetadata, TaskStatus } from '../../types/index.js';
|
||||
import type { Task, TaskMetadata } from '../../types/index.js';
|
||||
import type {
|
||||
IStorage,
|
||||
StorageStats,
|
||||
UpdateStatusResult
|
||||
StorageStats
|
||||
} from '../../interfaces/storage.interface.js';
|
||||
import { FormatHandler } from './format-handler.js';
|
||||
import { FileOperations } from './file-operations.js';
|
||||
@@ -282,159 +281,6 @@ export class FileStorage implements IStorage {
|
||||
await this.saveTasks(tasks, tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update task or subtask status by ID - handles file storage logic with parent/subtask relationships
|
||||
*/
|
||||
async updateTaskStatus(
|
||||
taskId: string,
|
||||
newStatus: TaskStatus,
|
||||
tag?: string
|
||||
): Promise<UpdateStatusResult> {
|
||||
const tasks = await this.loadTasks(tag);
|
||||
|
||||
// Check if this is a subtask (contains a dot)
|
||||
if (taskId.includes('.')) {
|
||||
return this.updateSubtaskStatusInFile(tasks, taskId, newStatus, tag);
|
||||
}
|
||||
|
||||
// Handle regular task update
|
||||
const taskIndex = tasks.findIndex((t) => String(t.id) === String(taskId));
|
||||
|
||||
if (taskIndex === -1) {
|
||||
throw new Error(`Task ${taskId} not found`);
|
||||
}
|
||||
|
||||
const oldStatus = tasks[taskIndex].status;
|
||||
if (oldStatus === newStatus) {
|
||||
return {
|
||||
success: true,
|
||||
oldStatus,
|
||||
newStatus,
|
||||
taskId: String(taskId)
|
||||
};
|
||||
}
|
||||
|
||||
tasks[taskIndex] = {
|
||||
...tasks[taskIndex],
|
||||
status: newStatus,
|
||||
updatedAt: new Date().toISOString()
|
||||
};
|
||||
|
||||
await this.saveTasks(tasks, tag);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
oldStatus,
|
||||
newStatus,
|
||||
taskId: String(taskId)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update subtask status within file storage - handles parent status auto-adjustment
|
||||
*/
|
||||
private async updateSubtaskStatusInFile(
|
||||
tasks: Task[],
|
||||
subtaskId: string,
|
||||
newStatus: TaskStatus,
|
||||
tag?: string
|
||||
): Promise<UpdateStatusResult> {
|
||||
// Parse the subtask ID to get parent ID and subtask ID
|
||||
const parts = subtaskId.split('.');
|
||||
if (parts.length !== 2) {
|
||||
throw new Error(
|
||||
`Invalid subtask ID format: ${subtaskId}. Expected format: parentId.subtaskId`
|
||||
);
|
||||
}
|
||||
|
||||
const [parentId, subIdRaw] = parts;
|
||||
const subId = subIdRaw.trim();
|
||||
if (!/^\d+$/.test(subId)) {
|
||||
throw new Error(
|
||||
`Invalid subtask ID: ${subId}. Subtask ID must be a positive integer.`
|
||||
);
|
||||
}
|
||||
const subtaskNumericId = Number(subId);
|
||||
|
||||
// Find the parent task
|
||||
const parentTaskIndex = tasks.findIndex(
|
||||
(t) => String(t.id) === String(parentId)
|
||||
);
|
||||
|
||||
if (parentTaskIndex === -1) {
|
||||
throw new Error(`Parent task ${parentId} not found`);
|
||||
}
|
||||
|
||||
const parentTask = tasks[parentTaskIndex];
|
||||
|
||||
// Find the subtask within the parent task
|
||||
const subtaskIndex = parentTask.subtasks.findIndex(
|
||||
(st) => st.id === subtaskNumericId || String(st.id) === subId
|
||||
);
|
||||
|
||||
if (subtaskIndex === -1) {
|
||||
throw new Error(
|
||||
`Subtask ${subtaskId} not found in parent task ${parentId}`
|
||||
);
|
||||
}
|
||||
|
||||
const oldStatus = parentTask.subtasks[subtaskIndex].status || 'pending';
|
||||
if (oldStatus === newStatus) {
|
||||
return {
|
||||
success: true,
|
||||
oldStatus,
|
||||
newStatus,
|
||||
taskId: subtaskId
|
||||
};
|
||||
}
|
||||
|
||||
const now = new Date().toISOString();
|
||||
|
||||
// Update the subtask status
|
||||
parentTask.subtasks[subtaskIndex] = {
|
||||
...parentTask.subtasks[subtaskIndex],
|
||||
status: newStatus,
|
||||
updatedAt: now
|
||||
};
|
||||
|
||||
// Auto-adjust parent status based on subtask statuses
|
||||
const subs = parentTask.subtasks;
|
||||
let parentNewStatus = parentTask.status;
|
||||
if (subs.length > 0) {
|
||||
const norm = (s: any) => s.status || 'pending';
|
||||
const isDoneLike = (s: any) => {
|
||||
const st = norm(s);
|
||||
return st === 'done' || st === 'completed';
|
||||
};
|
||||
const allDone = subs.every(isDoneLike);
|
||||
const anyInProgress = subs.some((s) => norm(s) === 'in-progress');
|
||||
const anyDone = subs.some(isDoneLike);
|
||||
const allPending = subs.every((s) => norm(s) === 'pending');
|
||||
|
||||
if (allDone) parentNewStatus = 'done';
|
||||
else if (anyInProgress || anyDone) parentNewStatus = 'in-progress';
|
||||
else if (allPending) parentNewStatus = 'pending';
|
||||
}
|
||||
|
||||
// Always bump updatedAt; update status only if changed
|
||||
tasks[parentTaskIndex] = {
|
||||
...parentTask,
|
||||
...(parentNewStatus !== parentTask.status
|
||||
? { status: parentNewStatus }
|
||||
: {}),
|
||||
updatedAt: now
|
||||
};
|
||||
|
||||
await this.saveTasks(tasks, tag);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
oldStatus,
|
||||
newStatus,
|
||||
taskId: subtaskId
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a task
|
||||
*/
|
||||
|
||||
@@ -82,7 +82,7 @@ export class StorageFactory {
|
||||
apiAccessToken: credentials.token,
|
||||
apiEndpoint:
|
||||
config.storage?.apiEndpoint ||
|
||||
process.env.TM_PUBLIC_BASE_DOMAIN ||
|
||||
process.env.HAMSTER_API_URL ||
|
||||
'https://tryhamster.com/api'
|
||||
};
|
||||
config.storage = nextStorage;
|
||||
@@ -112,7 +112,7 @@ export class StorageFactory {
|
||||
apiAccessToken: credentials.token,
|
||||
apiEndpoint:
|
||||
config.storage?.apiEndpoint ||
|
||||
process.env.TM_PUBLIC_BASE_DOMAIN ||
|
||||
process.env.HAMSTER_API_URL ||
|
||||
'https://tryhamster.com/api'
|
||||
};
|
||||
config.storage = nextStorage;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user