style: standardize formatting in opencode tooling files
Reformat JSON configs and TypeScript scripts to use consistent tab indentation, semicolons, and double quotes.
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"source": "/tmp/skill-selector-curated-1953505229",
|
"source": "/tmp/skill-selector-curated-1953505229",
|
||||||
"sourceType": "local",
|
"sourceType": "local",
|
||||||
"localPath": "/tmp/skill-selector-curated-1953505229/zod-validation-expert",
|
"localPath": "/tmp/skill-selector-curated-1953505229/zod-validation-expert",
|
||||||
"installedAt": "2026-04-07T15:11:20.921Z"
|
"installedAt": "2026-04-07T15:11:20.921Z"
|
||||||
}
|
}
|
||||||
@@ -1,363 +1,341 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://opencode.ai/schemas/agent-metadata.json",
|
"$schema": "https://opencode.ai/schemas/agent-metadata.json",
|
||||||
"schema_version": "1.0.0",
|
"schema_version": "1.0.0",
|
||||||
"description": "Centralized metadata for OpenAgents Control agents. This file stores metadata that is not part of the OpenCode agent schema but is needed for registry management, installation, and documentation.",
|
"description": "Centralized metadata for OpenAgents Control agents. This file stores metadata that is not part of the OpenCode agent schema but is needed for registry management, installation, and documentation.",
|
||||||
"agents": {
|
"agents": {
|
||||||
"openagent": {
|
"openagent": {
|
||||||
"id": "openagent",
|
"id": "openagent",
|
||||||
"name": "OpenAgent",
|
"name": "OpenAgent",
|
||||||
"category": "core",
|
"category": "core",
|
||||||
"type": "agent",
|
"type": "agent",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"author": "opencode",
|
"author": "opencode",
|
||||||
"tags": ["universal", "coordination", "primary"],
|
"tags": ["universal", "coordination", "primary"],
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"subagent:task-manager",
|
"subagent:task-manager",
|
||||||
"subagent:batch-executor",
|
"subagent:batch-executor",
|
||||||
"subagent:documentation",
|
"subagent:documentation",
|
||||||
"subagent:contextscout",
|
"subagent:contextscout",
|
||||||
"subagent:externalscout",
|
"subagent:externalscout",
|
||||||
"context:standards-code",
|
"context:standards-code",
|
||||||
"context:standards-docs",
|
"context:standards-docs",
|
||||||
"context:standards-tests",
|
"context:standards-tests",
|
||||||
"context:review-ref",
|
"context:review-ref",
|
||||||
"context:delegation-ref",
|
"context:delegation-ref",
|
||||||
"context:external-libraries-workflow"
|
"context:external-libraries-workflow"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"opencoder": {
|
"opencoder": {
|
||||||
"id": "opencoder",
|
"id": "opencoder",
|
||||||
"name": "OpenCoder",
|
"name": "OpenCoder",
|
||||||
"category": "core",
|
"category": "core",
|
||||||
"type": "agent",
|
"type": "agent",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"author": "opencode",
|
"author": "opencode",
|
||||||
"tags": ["development", "coding", "implementation"],
|
"tags": ["development", "coding", "implementation"],
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"subagent:documentation",
|
"subagent:documentation",
|
||||||
"subagent:task-manager",
|
"subagent:task-manager",
|
||||||
"subagent:batch-executor",
|
"subagent:batch-executor",
|
||||||
"subagent:coder-agent",
|
"subagent:coder-agent",
|
||||||
"subagent:tester",
|
"subagent:tester",
|
||||||
"subagent:reviewer",
|
"subagent:reviewer",
|
||||||
"subagent:build-agent",
|
"subagent:build-agent",
|
||||||
"subagent:contextscout",
|
"subagent:contextscout",
|
||||||
"subagent:externalscout",
|
"subagent:externalscout",
|
||||||
"context:standards-code",
|
"context:standards-code",
|
||||||
"context:task-delegation-basics",
|
"context:task-delegation-basics",
|
||||||
"context:component-planning",
|
"context:component-planning",
|
||||||
"context:external-libraries-workflow"
|
"context:external-libraries-workflow"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"repo-manager": {
|
"repo-manager": {
|
||||||
"id": "repo-manager",
|
"id": "repo-manager",
|
||||||
"name": "Repo Manager",
|
"name": "Repo Manager",
|
||||||
"category": "meta",
|
"category": "meta",
|
||||||
"type": "agent",
|
"type": "agent",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"author": "opencode",
|
"author": "opencode",
|
||||||
"tags": ["repository", "management", "orchestration"],
|
"tags": ["repository", "management", "orchestration"],
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"subagent:task-manager",
|
"subagent:task-manager",
|
||||||
"subagent:contextscout",
|
"subagent:contextscout",
|
||||||
"subagent:documentation",
|
"subagent:documentation",
|
||||||
"subagent:coder-agent",
|
"subagent:coder-agent",
|
||||||
"subagent:tester",
|
"subagent:tester",
|
||||||
"subagent:reviewer",
|
"subagent:reviewer",
|
||||||
"subagent:build-agent"
|
"subagent:build-agent"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"system-builder": {
|
"system-builder": {
|
||||||
"id": "system-builder",
|
"id": "system-builder",
|
||||||
"name": "System Builder",
|
"name": "System Builder",
|
||||||
"category": "meta",
|
"category": "meta",
|
||||||
"type": "agent",
|
"type": "agent",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"author": "opencode",
|
"author": "opencode",
|
||||||
"tags": ["system-generation", "architecture", "scaffolding"],
|
"tags": ["system-generation", "architecture", "scaffolding"],
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"subagent:agent-generator",
|
"subagent:agent-generator",
|
||||||
"subagent:command-creator",
|
"subagent:command-creator",
|
||||||
"subagent:domain-analyzer",
|
"subagent:domain-analyzer",
|
||||||
"subagent:context-organizer",
|
"subagent:context-organizer",
|
||||||
"subagent:workflow-designer"
|
"subagent:workflow-designer"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"copywriter": {
|
"copywriter": {
|
||||||
"id": "copywriter",
|
"id": "copywriter",
|
||||||
"name": "Copywriter",
|
"name": "Copywriter",
|
||||||
"category": "content",
|
"category": "content",
|
||||||
"type": "agent",
|
"type": "agent",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"author": "opencode",
|
"author": "opencode",
|
||||||
"tags": ["content", "marketing", "writing"],
|
"tags": ["content", "marketing", "writing"],
|
||||||
"dependencies": [
|
"dependencies": ["context:standards-docs"]
|
||||||
"context:standards-docs"
|
},
|
||||||
]
|
"technical-writer": {
|
||||||
},
|
"id": "technical-writer",
|
||||||
"technical-writer": {
|
"name": "Technical Writer",
|
||||||
"id": "technical-writer",
|
"category": "content",
|
||||||
"name": "Technical Writer",
|
"type": "agent",
|
||||||
"category": "content",
|
"version": "1.0.0",
|
||||||
"type": "agent",
|
"author": "opencode",
|
||||||
"version": "1.0.0",
|
"tags": ["documentation", "technical", "writing"],
|
||||||
"author": "opencode",
|
"dependencies": ["context:standards-docs"]
|
||||||
"tags": ["documentation", "technical", "writing"],
|
},
|
||||||
"dependencies": [
|
"data-analyst": {
|
||||||
"context:standards-docs"
|
"id": "data-analyst",
|
||||||
]
|
"name": "Data Analyst",
|
||||||
},
|
"category": "data",
|
||||||
"data-analyst": {
|
"type": "agent",
|
||||||
"id": "data-analyst",
|
"version": "1.0.0",
|
||||||
"name": "Data Analyst",
|
"author": "opencode",
|
||||||
"category": "data",
|
"tags": ["data", "analysis", "visualization"],
|
||||||
"type": "agent",
|
"dependencies": []
|
||||||
"version": "1.0.0",
|
},
|
||||||
"author": "opencode",
|
"eval-runner": {
|
||||||
"tags": ["data", "analysis", "visualization"],
|
"id": "eval-runner",
|
||||||
"dependencies": []
|
"name": "Eval Runner",
|
||||||
},
|
"category": "testing",
|
||||||
"eval-runner": {
|
"type": "agent",
|
||||||
"id": "eval-runner",
|
"version": "1.0.0",
|
||||||
"name": "Eval Runner",
|
"author": "opencode",
|
||||||
"category": "testing",
|
"tags": ["testing", "evaluation", "quality"],
|
||||||
"type": "agent",
|
"dependencies": ["context:standards-tests"]
|
||||||
"version": "1.0.0",
|
},
|
||||||
"author": "opencode",
|
"task-manager": {
|
||||||
"tags": ["testing", "evaluation", "quality"],
|
"id": "task-manager",
|
||||||
"dependencies": [
|
"name": "TaskManager",
|
||||||
"context:standards-tests"
|
"category": "subagents/core",
|
||||||
]
|
"type": "subagent",
|
||||||
},
|
"version": "2.0.0",
|
||||||
"task-manager": {
|
"author": "opencode",
|
||||||
"id": "task-manager",
|
"tags": ["task-breakdown", "planning", "coordination"],
|
||||||
"name": "TaskManager",
|
"dependencies": ["context:task-delegation-basics"]
|
||||||
"category": "subagents/core",
|
},
|
||||||
"type": "subagent",
|
"batch-executor": {
|
||||||
"version": "2.0.0",
|
"id": "batch-executor",
|
||||||
"author": "opencode",
|
"name": "BatchExecutor",
|
||||||
"tags": ["task-breakdown", "planning", "coordination"],
|
"category": "subagents/core",
|
||||||
"dependencies": [
|
"type": "subagent",
|
||||||
"context:task-delegation-basics"
|
"version": "1.0.0",
|
||||||
]
|
"author": "opencode",
|
||||||
},
|
"tags": ["parallel-execution", "batch-management", "coordination"],
|
||||||
"batch-executor": {
|
"dependencies": ["subagent:coder-agent", "subagent:task-manager"]
|
||||||
"id": "batch-executor",
|
},
|
||||||
"name": "BatchExecutor",
|
"documentation": {
|
||||||
"category": "subagents/core",
|
"id": "documentation",
|
||||||
"type": "subagent",
|
"name": "DocWriter",
|
||||||
"version": "1.0.0",
|
"category": "subagents/core",
|
||||||
"author": "opencode",
|
"type": "subagent",
|
||||||
"tags": ["parallel-execution", "batch-management", "coordination"],
|
"version": "1.0.0",
|
||||||
"dependencies": [
|
"author": "opencode",
|
||||||
"subagent:coder-agent",
|
"tags": ["documentation", "writing"],
|
||||||
"subagent:task-manager"
|
"dependencies": ["context:standards-docs"]
|
||||||
]
|
},
|
||||||
},
|
"contextscout": {
|
||||||
"documentation": {
|
"id": "contextscout",
|
||||||
"id": "documentation",
|
"name": "ContextScout",
|
||||||
"name": "DocWriter",
|
"category": "subagents/core",
|
||||||
"category": "subagents/core",
|
"type": "subagent",
|
||||||
"type": "subagent",
|
"version": "1.0.0",
|
||||||
"version": "1.0.0",
|
"author": "opencode",
|
||||||
"author": "opencode",
|
"tags": ["context", "discovery", "search"],
|
||||||
"tags": ["documentation", "writing"],
|
"dependencies": []
|
||||||
"dependencies": [
|
},
|
||||||
"context:standards-docs"
|
"externalscout": {
|
||||||
]
|
"id": "externalscout",
|
||||||
},
|
"name": "ExternalScout",
|
||||||
"contextscout": {
|
"category": "subagents/core",
|
||||||
"id": "contextscout",
|
"type": "subagent",
|
||||||
"name": "ContextScout",
|
"version": "1.0.0",
|
||||||
"category": "subagents/core",
|
"author": "opencode",
|
||||||
"type": "subagent",
|
"tags": ["external", "documentation", "search"],
|
||||||
"version": "1.0.0",
|
"dependencies": []
|
||||||
"author": "opencode",
|
},
|
||||||
"tags": ["context", "discovery", "search"],
|
"context-manager": {
|
||||||
"dependencies": []
|
"id": "context-manager",
|
||||||
},
|
"name": "ContextManager",
|
||||||
"externalscout": {
|
"category": "subagents/core",
|
||||||
"id": "externalscout",
|
"type": "subagent",
|
||||||
"name": "ExternalScout",
|
"version": "1.0.0",
|
||||||
"category": "subagents/core",
|
"author": "opencode",
|
||||||
"type": "subagent",
|
"tags": ["context", "management", "organization"],
|
||||||
"version": "1.0.0",
|
"dependencies": []
|
||||||
"author": "opencode",
|
},
|
||||||
"tags": ["external", "documentation", "search"],
|
"context-retriever": {
|
||||||
"dependencies": []
|
"id": "context-retriever",
|
||||||
},
|
"name": "Context Retriever",
|
||||||
"context-manager": {
|
"category": "subagents/core",
|
||||||
"id": "context-manager",
|
"type": "subagent",
|
||||||
"name": "ContextManager",
|
"version": "1.0.0",
|
||||||
"category": "subagents/core",
|
"author": "opencode",
|
||||||
"type": "subagent",
|
"tags": ["context", "retrieval", "search"],
|
||||||
"version": "1.0.0",
|
"dependencies": []
|
||||||
"author": "opencode",
|
},
|
||||||
"tags": ["context", "management", "organization"],
|
"coder-agent": {
|
||||||
"dependencies": []
|
"id": "coder-agent",
|
||||||
},
|
"name": "CoderAgent",
|
||||||
"context-retriever": {
|
"category": "subagents/code",
|
||||||
"id": "context-retriever",
|
"type": "subagent",
|
||||||
"name": "Context Retriever",
|
"version": "1.0.0",
|
||||||
"category": "subagents/core",
|
"author": "opencode",
|
||||||
"type": "subagent",
|
"tags": ["coding", "implementation"],
|
||||||
"version": "1.0.0",
|
"dependencies": ["context:standards-code"]
|
||||||
"author": "opencode",
|
},
|
||||||
"tags": ["context", "retrieval", "search"],
|
"tester": {
|
||||||
"dependencies": []
|
"id": "tester",
|
||||||
},
|
"name": "TestEngineer",
|
||||||
"coder-agent": {
|
"category": "subagents/code",
|
||||||
"id": "coder-agent",
|
"type": "subagent",
|
||||||
"name": "CoderAgent",
|
"version": "1.0.0",
|
||||||
"category": "subagents/code",
|
"author": "opencode",
|
||||||
"type": "subagent",
|
"tags": ["testing", "tdd", "quality"],
|
||||||
"version": "1.0.0",
|
"dependencies": ["context:standards-tests"]
|
||||||
"author": "opencode",
|
},
|
||||||
"tags": ["coding", "implementation"],
|
"reviewer": {
|
||||||
"dependencies": [
|
"id": "reviewer",
|
||||||
"context:standards-code"
|
"name": "CodeReviewer",
|
||||||
]
|
"category": "subagents/code",
|
||||||
},
|
"type": "subagent",
|
||||||
"tester": {
|
"version": "1.0.0",
|
||||||
"id": "tester",
|
"author": "opencode",
|
||||||
"name": "TestEngineer",
|
"tags": ["review", "security", "quality"],
|
||||||
"category": "subagents/code",
|
"dependencies": ["context:standards-code", "context:review-ref"]
|
||||||
"type": "subagent",
|
},
|
||||||
"version": "1.0.0",
|
"build-agent": {
|
||||||
"author": "opencode",
|
"id": "build-agent",
|
||||||
"tags": ["testing", "tdd", "quality"],
|
"name": "BuildAgent",
|
||||||
"dependencies": [
|
"category": "subagents/code",
|
||||||
"context:standards-tests"
|
"type": "subagent",
|
||||||
]
|
"version": "1.0.0",
|
||||||
},
|
"author": "opencode",
|
||||||
"reviewer": {
|
"tags": ["build", "validation", "type-checking"],
|
||||||
"id": "reviewer",
|
"dependencies": []
|
||||||
"name": "CodeReviewer",
|
},
|
||||||
"category": "subagents/code",
|
"frontend-specialist": {
|
||||||
"type": "subagent",
|
"id": "frontend-specialist",
|
||||||
"version": "1.0.0",
|
"name": "OpenFrontendSpecialist",
|
||||||
"author": "opencode",
|
"category": "subagents/development",
|
||||||
"tags": ["review", "security", "quality"],
|
"type": "subagent",
|
||||||
"dependencies": [
|
"version": "1.0.0",
|
||||||
"context:standards-code",
|
"author": "opencode",
|
||||||
"context:review-ref"
|
"tags": ["frontend", "ui", "design"],
|
||||||
]
|
"dependencies": ["context:standards-code"]
|
||||||
},
|
},
|
||||||
"build-agent": {
|
"devops-specialist": {
|
||||||
"id": "build-agent",
|
"id": "devops-specialist",
|
||||||
"name": "BuildAgent",
|
"name": "OpenDevopsSpecialist",
|
||||||
"category": "subagents/code",
|
"category": "subagents/development",
|
||||||
"type": "subagent",
|
"type": "subagent",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"author": "opencode",
|
"author": "opencode",
|
||||||
"tags": ["build", "validation", "type-checking"],
|
"tags": ["devops", "ci-cd", "infrastructure"],
|
||||||
"dependencies": []
|
"dependencies": []
|
||||||
},
|
},
|
||||||
"frontend-specialist": {
|
"agent-generator": {
|
||||||
"id": "frontend-specialist",
|
"id": "agent-generator",
|
||||||
"name": "OpenFrontendSpecialist",
|
"name": "AgentGenerator",
|
||||||
"category": "subagents/development",
|
"category": "subagents/system-builder",
|
||||||
"type": "subagent",
|
"type": "subagent",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"author": "opencode",
|
"author": "opencode",
|
||||||
"tags": ["frontend", "ui", "design"],
|
"tags": ["generation", "agents", "scaffolding"],
|
||||||
"dependencies": [
|
"dependencies": []
|
||||||
"context:standards-code"
|
},
|
||||||
]
|
"command-creator": {
|
||||||
},
|
"id": "command-creator",
|
||||||
"devops-specialist": {
|
"name": "CommandCreator",
|
||||||
"id": "devops-specialist",
|
"category": "subagents/system-builder",
|
||||||
"name": "OpenDevopsSpecialist",
|
"type": "subagent",
|
||||||
"category": "subagents/development",
|
"version": "1.0.0",
|
||||||
"type": "subagent",
|
"author": "opencode",
|
||||||
"version": "1.0.0",
|
"tags": ["commands", "generation", "scaffolding"],
|
||||||
"author": "opencode",
|
"dependencies": []
|
||||||
"tags": ["devops", "ci-cd", "infrastructure"],
|
},
|
||||||
"dependencies": []
|
"domain-analyzer": {
|
||||||
},
|
"id": "domain-analyzer",
|
||||||
"agent-generator": {
|
"name": "DomainAnalyzer",
|
||||||
"id": "agent-generator",
|
"category": "subagents/system-builder",
|
||||||
"name": "AgentGenerator",
|
"type": "subagent",
|
||||||
"category": "subagents/system-builder",
|
"version": "1.0.0",
|
||||||
"type": "subagent",
|
"author": "opencode",
|
||||||
"version": "1.0.0",
|
"tags": ["analysis", "domain", "architecture"],
|
||||||
"author": "opencode",
|
"dependencies": []
|
||||||
"tags": ["generation", "agents", "scaffolding"],
|
},
|
||||||
"dependencies": []
|
"context-organizer": {
|
||||||
},
|
"id": "context-organizer",
|
||||||
"command-creator": {
|
"name": "ContextOrganizer",
|
||||||
"id": "command-creator",
|
"category": "subagents/system-builder",
|
||||||
"name": "CommandCreator",
|
"type": "subagent",
|
||||||
"category": "subagents/system-builder",
|
"version": "1.0.0",
|
||||||
"type": "subagent",
|
"author": "opencode",
|
||||||
"version": "1.0.0",
|
"tags": ["context", "organization", "structure"],
|
||||||
"author": "opencode",
|
"dependencies": []
|
||||||
"tags": ["commands", "generation", "scaffolding"],
|
},
|
||||||
"dependencies": []
|
"workflow-designer": {
|
||||||
},
|
"id": "workflow-designer",
|
||||||
"domain-analyzer": {
|
"name": "WorkflowDesigner",
|
||||||
"id": "domain-analyzer",
|
"category": "subagents/system-builder",
|
||||||
"name": "DomainAnalyzer",
|
"type": "subagent",
|
||||||
"category": "subagents/system-builder",
|
"version": "1.0.0",
|
||||||
"type": "subagent",
|
"author": "opencode",
|
||||||
"version": "1.0.0",
|
"tags": ["workflow", "design", "architecture"],
|
||||||
"author": "opencode",
|
"dependencies": []
|
||||||
"tags": ["analysis", "domain", "architecture"],
|
},
|
||||||
"dependencies": []
|
"image-specialist": {
|
||||||
},
|
"id": "image-specialist",
|
||||||
"context-organizer": {
|
"name": "Image Specialist",
|
||||||
"id": "context-organizer",
|
"category": "subagents/utils",
|
||||||
"name": "ContextOrganizer",
|
"type": "subagent",
|
||||||
"category": "subagents/system-builder",
|
"version": "1.0.0",
|
||||||
"type": "subagent",
|
"author": "opencode",
|
||||||
"version": "1.0.0",
|
"tags": ["images", "editing", "generation"],
|
||||||
"author": "opencode",
|
"dependencies": []
|
||||||
"tags": ["context", "organization", "structure"],
|
},
|
||||||
"dependencies": []
|
"simple-responder": {
|
||||||
},
|
"id": "simple-responder",
|
||||||
"workflow-designer": {
|
"name": "Simple Responder",
|
||||||
"id": "workflow-designer",
|
"category": "subagents/test",
|
||||||
"name": "WorkflowDesigner",
|
"type": "subagent",
|
||||||
"category": "subagents/system-builder",
|
"version": "1.0.0",
|
||||||
"type": "subagent",
|
"author": "opencode",
|
||||||
"version": "1.0.0",
|
"tags": ["testing", "evaluation"],
|
||||||
"author": "opencode",
|
"dependencies": []
|
||||||
"tags": ["workflow", "design", "architecture"],
|
}
|
||||||
"dependencies": []
|
},
|
||||||
},
|
"defaults": {
|
||||||
"image-specialist": {
|
"agent": {
|
||||||
"id": "image-specialist",
|
"version": "1.0.0",
|
||||||
"name": "Image Specialist",
|
"author": "opencode",
|
||||||
"category": "subagents/utils",
|
"type": "agent",
|
||||||
"type": "subagent",
|
"tags": []
|
||||||
"version": "1.0.0",
|
},
|
||||||
"author": "opencode",
|
"subagent": {
|
||||||
"tags": ["images", "editing", "generation"],
|
"version": "1.0.0",
|
||||||
"dependencies": []
|
"author": "opencode",
|
||||||
},
|
"type": "subagent",
|
||||||
"simple-responder": {
|
"tags": []
|
||||||
"id": "simple-responder",
|
}
|
||||||
"name": "Simple Responder",
|
}
|
||||||
"category": "subagents/test",
|
|
||||||
"type": "subagent",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"author": "opencode",
|
|
||||||
"tags": ["testing", "evaluation"],
|
|
||||||
"dependencies": []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"defaults": {
|
|
||||||
"agent": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"author": "opencode",
|
|
||||||
"type": "agent",
|
|
||||||
"tags": []
|
|
||||||
},
|
|
||||||
"subagent": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"author": "opencode",
|
|
||||||
"type": "subagent",
|
|
||||||
"tags": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"description": "Additional context file paths - agents load this via @ reference for dynamic pathing",
|
"description": "Additional context file paths - agents load this via @ reference for dynamic pathing",
|
||||||
"paths": {
|
"paths": {
|
||||||
"local": ".opencode/context",
|
"local": ".opencode/context",
|
||||||
"global": "~/.config/opencode/context"
|
"global": "~/.config/opencode/context"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,517 +19,576 @@
|
|||||||
* .tmp/tasks/completed/{feature-slug}/
|
* .tmp/tasks/completed/{feature-slug}/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const fs = require('fs');
|
const fs = require("fs");
|
||||||
const path = require('path');
|
const path = require("path");
|
||||||
|
|
||||||
// Find project root (look for .git or package.json)
|
// Find project root (look for .git or package.json)
|
||||||
function findProjectRoot(): string {
|
function findProjectRoot(): string {
|
||||||
let dir = process.cwd();
|
let dir = process.cwd();
|
||||||
while (dir !== path.dirname(dir)) {
|
while (dir !== path.dirname(dir)) {
|
||||||
if (fs.existsSync(path.join(dir, '.git')) || fs.existsSync(path.join(dir, 'package.json'))) {
|
if (
|
||||||
return dir;
|
fs.existsSync(path.join(dir, ".git")) ||
|
||||||
}
|
fs.existsSync(path.join(dir, "package.json"))
|
||||||
dir = path.dirname(dir);
|
) {
|
||||||
}
|
return dir;
|
||||||
return process.cwd();
|
}
|
||||||
|
dir = path.dirname(dir);
|
||||||
|
}
|
||||||
|
return process.cwd();
|
||||||
}
|
}
|
||||||
|
|
||||||
const PROJECT_ROOT = findProjectRoot();
|
const PROJECT_ROOT = findProjectRoot();
|
||||||
const TASKS_DIR = path.join(PROJECT_ROOT, '.tmp', 'tasks');
|
const TASKS_DIR = path.join(PROJECT_ROOT, ".tmp", "tasks");
|
||||||
const COMPLETED_DIR = path.join(TASKS_DIR, 'completed');
|
const COMPLETED_DIR = path.join(TASKS_DIR, "completed");
|
||||||
|
|
||||||
interface Task {
|
interface Task {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
status: 'active' | 'completed' | 'blocked' | 'archived';
|
status: "active" | "completed" | "blocked" | "archived";
|
||||||
objective: string;
|
objective: string;
|
||||||
context_files: string[];
|
context_files: string[];
|
||||||
reference_files?: string[];
|
reference_files?: string[];
|
||||||
exit_criteria: string[];
|
exit_criteria: string[];
|
||||||
subtask_count: number;
|
subtask_count: number;
|
||||||
completed_count: number;
|
completed_count: number;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
completed_at: string | null;
|
completed_at: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Subtask {
|
interface Subtask {
|
||||||
id: string;
|
id: string;
|
||||||
seq: string;
|
seq: string;
|
||||||
title: string;
|
title: string;
|
||||||
status: 'pending' | 'in_progress' | 'completed' | 'blocked';
|
status: "pending" | "in_progress" | "completed" | "blocked";
|
||||||
depends_on: string[];
|
depends_on: string[];
|
||||||
parallel: boolean;
|
parallel: boolean;
|
||||||
context_files: string[];
|
context_files: string[];
|
||||||
reference_files?: string[];
|
reference_files?: string[];
|
||||||
acceptance_criteria: string[];
|
acceptance_criteria: string[];
|
||||||
deliverables: string[];
|
deliverables: string[];
|
||||||
agent_id: string | null;
|
agent_id: string | null;
|
||||||
suggested_agent?: string;
|
suggested_agent?: string;
|
||||||
started_at: string | null;
|
started_at: string | null;
|
||||||
completed_at: string | null;
|
completed_at: string | null;
|
||||||
completion_summary: string | null;
|
completion_summary: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
function getFeatureDirs(): string[] {
|
function getFeatureDirs(): string[] {
|
||||||
if (!fs.existsSync(TASKS_DIR)) return [];
|
if (!fs.existsSync(TASKS_DIR)) return [];
|
||||||
return fs.readdirSync(TASKS_DIR).filter((f: string) => {
|
return fs.readdirSync(TASKS_DIR).filter((f: string) => {
|
||||||
const fullPath = path.join(TASKS_DIR, f);
|
const fullPath = path.join(TASKS_DIR, f);
|
||||||
return fs.statSync(fullPath).isDirectory() && f !== 'completed';
|
return fs.statSync(fullPath).isDirectory() && f !== "completed";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadTask(feature: string): Task | null {
|
function loadTask(feature: string): Task | null {
|
||||||
const taskPath = path.join(TASKS_DIR, feature, 'task.json');
|
const taskPath = path.join(TASKS_DIR, feature, "task.json");
|
||||||
if (!fs.existsSync(taskPath)) return null;
|
if (!fs.existsSync(taskPath)) return null;
|
||||||
return JSON.parse(fs.readFileSync(taskPath, 'utf-8'));
|
return JSON.parse(fs.readFileSync(taskPath, "utf-8"));
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadSubtasks(feature: string): Subtask[] {
|
function loadSubtasks(feature: string): Subtask[] {
|
||||||
const featureDir = path.join(TASKS_DIR, feature);
|
const featureDir = path.join(TASKS_DIR, feature);
|
||||||
if (!fs.existsSync(featureDir)) return [];
|
if (!fs.existsSync(featureDir)) return [];
|
||||||
|
|
||||||
const files = fs.readdirSync(featureDir)
|
const files = fs
|
||||||
.filter((f: string) => f.match(/^subtask_\d{2}\.json$/))
|
.readdirSync(featureDir)
|
||||||
.sort();
|
.filter((f: string) => f.match(/^subtask_\d{2}\.json$/))
|
||||||
|
.sort();
|
||||||
|
|
||||||
return files.map((f: string) => JSON.parse(fs.readFileSync(path.join(featureDir, f), 'utf-8')));
|
return files.map((f: string) =>
|
||||||
|
JSON.parse(fs.readFileSync(path.join(featureDir, f), "utf-8")),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveSubtask(feature: string, subtask: Subtask): void {
|
function saveSubtask(feature: string, subtask: Subtask): void {
|
||||||
const subtaskPath = path.join(TASKS_DIR, feature, `subtask_${subtask.seq}.json`);
|
const subtaskPath = path.join(
|
||||||
fs.writeFileSync(subtaskPath, JSON.stringify(subtask, null, 2));
|
TASKS_DIR,
|
||||||
|
feature,
|
||||||
|
`subtask_${subtask.seq}.json`,
|
||||||
|
);
|
||||||
|
fs.writeFileSync(subtaskPath, JSON.stringify(subtask, null, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveTask(feature: string, task: Task): void {
|
function saveTask(feature: string, task: Task): void {
|
||||||
const taskPath = path.join(TASKS_DIR, feature, 'task.json');
|
const taskPath = path.join(TASKS_DIR, feature, "task.json");
|
||||||
fs.writeFileSync(taskPath, JSON.stringify(task, null, 2));
|
fs.writeFileSync(taskPath, JSON.stringify(task, null, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commands
|
// Commands
|
||||||
function cmdStatus(feature?: string): void {
|
function cmdStatus(feature?: string): void {
|
||||||
const features = feature ? [feature] : getFeatureDirs();
|
const features = feature ? [feature] : getFeatureDirs();
|
||||||
|
|
||||||
if (features.length === 0) {
|
if (features.length === 0) {
|
||||||
console.log('No active features found.');
|
console.log("No active features found.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const f of features) {
|
for (const f of features) {
|
||||||
const task = loadTask(f);
|
const task = loadTask(f);
|
||||||
const subtasks = loadSubtasks(f);
|
const subtasks = loadSubtasks(f);
|
||||||
|
|
||||||
if (!task) {
|
if (!task) {
|
||||||
console.log(`\n[${f}] - No task.json found`);
|
console.log(`\n[${f}] - No task.json found`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const counts = {
|
const counts = {
|
||||||
pending: subtasks.filter(s => s.status === 'pending').length,
|
pending: subtasks.filter((s) => s.status === "pending").length,
|
||||||
in_progress: subtasks.filter(s => s.status === 'in_progress').length,
|
in_progress: subtasks.filter((s) => s.status === "in_progress").length,
|
||||||
completed: subtasks.filter(s => s.status === 'completed').length,
|
completed: subtasks.filter((s) => s.status === "completed").length,
|
||||||
blocked: subtasks.filter(s => s.status === 'blocked').length,
|
blocked: subtasks.filter((s) => s.status === "blocked").length,
|
||||||
};
|
};
|
||||||
|
|
||||||
const progress = subtasks.length > 0
|
const progress =
|
||||||
? Math.round((counts.completed / subtasks.length) * 100)
|
subtasks.length > 0
|
||||||
: 0;
|
? Math.round((counts.completed / subtasks.length) * 100)
|
||||||
|
: 0;
|
||||||
|
|
||||||
console.log(`\n[${f}] ${task.name}`);
|
console.log(`\n[${f}] ${task.name}`);
|
||||||
console.log(` Status: ${task.status} | Progress: ${progress}% (${counts.completed}/${subtasks.length})`);
|
console.log(
|
||||||
console.log(` Pending: ${counts.pending} | In Progress: ${counts.in_progress} | Completed: ${counts.completed} | Blocked: ${counts.blocked}`);
|
` Status: ${task.status} | Progress: ${progress}% (${counts.completed}/${subtasks.length})`,
|
||||||
}
|
);
|
||||||
|
console.log(
|
||||||
|
` Pending: ${counts.pending} | In Progress: ${counts.in_progress} | Completed: ${counts.completed} | Blocked: ${counts.blocked}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function cmdNext(feature?: string): void {
|
function cmdNext(feature?: string): void {
|
||||||
const features = feature ? [feature] : getFeatureDirs();
|
const features = feature ? [feature] : getFeatureDirs();
|
||||||
|
|
||||||
console.log('\n=== Ready Tasks (deps satisfied) ===\n');
|
console.log("\n=== Ready Tasks (deps satisfied) ===\n");
|
||||||
|
|
||||||
for (const f of features) {
|
for (const f of features) {
|
||||||
const subtasks = loadSubtasks(f);
|
const subtasks = loadSubtasks(f);
|
||||||
const completedSeqs = new Set(subtasks.filter(s => s.status === 'completed').map(s => s.seq));
|
const completedSeqs = new Set(
|
||||||
|
subtasks.filter((s) => s.status === "completed").map((s) => s.seq),
|
||||||
|
);
|
||||||
|
|
||||||
const ready = subtasks.filter(s => {
|
const ready = subtasks.filter((s) => {
|
||||||
if (s.status !== 'pending') return false;
|
if (s.status !== "pending") return false;
|
||||||
return s.depends_on.every(dep => completedSeqs.has(dep));
|
return s.depends_on.every((dep) => completedSeqs.has(dep));
|
||||||
});
|
});
|
||||||
|
|
||||||
if (ready.length > 0) {
|
if (ready.length > 0) {
|
||||||
console.log(`[${f}]`);
|
console.log(`[${f}]`);
|
||||||
for (const s of ready) {
|
for (const s of ready) {
|
||||||
const parallel = s.parallel ? '[parallel]' : '[sequential]';
|
const parallel = s.parallel ? "[parallel]" : "[sequential]";
|
||||||
console.log(` ${s.seq} - ${s.title} ${parallel}`);
|
console.log(` ${s.seq} - ${s.title} ${parallel}`);
|
||||||
}
|
}
|
||||||
console.log();
|
console.log();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function cmdParallel(feature?: string): void {
|
function cmdParallel(feature?: string): void {
|
||||||
const features = feature ? [feature] : getFeatureDirs();
|
const features = feature ? [feature] : getFeatureDirs();
|
||||||
|
|
||||||
console.log('\n=== Parallelizable Tasks Ready Now ===\n');
|
console.log("\n=== Parallelizable Tasks Ready Now ===\n");
|
||||||
|
|
||||||
for (const f of features) {
|
for (const f of features) {
|
||||||
const subtasks = loadSubtasks(f);
|
const subtasks = loadSubtasks(f);
|
||||||
const completedSeqs = new Set(subtasks.filter(s => s.status === 'completed').map(s => s.seq));
|
const completedSeqs = new Set(
|
||||||
|
subtasks.filter((s) => s.status === "completed").map((s) => s.seq),
|
||||||
|
);
|
||||||
|
|
||||||
const parallel = subtasks.filter(s => {
|
const parallel = subtasks.filter((s) => {
|
||||||
if (s.status !== 'pending') return false;
|
if (s.status !== "pending") return false;
|
||||||
if (!s.parallel) return false;
|
if (!s.parallel) return false;
|
||||||
return s.depends_on.every(dep => completedSeqs.has(dep));
|
return s.depends_on.every((dep) => completedSeqs.has(dep));
|
||||||
});
|
});
|
||||||
|
|
||||||
if (parallel.length > 0) {
|
if (parallel.length > 0) {
|
||||||
console.log(`[${f}] - ${parallel.length} parallel tasks:`);
|
console.log(`[${f}] - ${parallel.length} parallel tasks:`);
|
||||||
for (const s of parallel) {
|
for (const s of parallel) {
|
||||||
console.log(` ${s.seq} - ${s.title}`);
|
console.log(` ${s.seq} - ${s.title}`);
|
||||||
}
|
}
|
||||||
console.log();
|
console.log();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function cmdDeps(feature: string, seq: string): void {
|
function cmdDeps(feature: string, seq: string): void {
|
||||||
const subtasks = loadSubtasks(feature);
|
const subtasks = loadSubtasks(feature);
|
||||||
const target = subtasks.find(s => s.seq === seq);
|
const target = subtasks.find((s) => s.seq === seq);
|
||||||
|
|
||||||
if (!target) {
|
if (!target) {
|
||||||
console.log(`Task ${seq} not found in ${feature}`);
|
console.log(`Task ${seq} not found in ${feature}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`\n=== Dependency Tree: ${feature}/${seq} ===\n`);
|
console.log(`\n=== Dependency Tree: ${feature}/${seq} ===\n`);
|
||||||
console.log(`${seq} - ${target.title} [${target.status}]`);
|
console.log(`${seq} - ${target.title} [${target.status}]`);
|
||||||
|
|
||||||
if (target.depends_on.length === 0) {
|
if (target.depends_on.length === 0) {
|
||||||
console.log(' └── (no dependencies)');
|
console.log(" └── (no dependencies)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const printDeps = (seqs: string[], indent: string = ' '): void => {
|
const printDeps = (seqs: string[], indent: string = " "): void => {
|
||||||
for (let i = 0; i < seqs.length; i++) {
|
for (let i = 0; i < seqs.length; i++) {
|
||||||
const depSeq = seqs[i];
|
const depSeq = seqs[i];
|
||||||
const dep = subtasks.find(s => s.seq === depSeq);
|
const dep = subtasks.find((s) => s.seq === depSeq);
|
||||||
const isLast = i === seqs.length - 1;
|
const isLast = i === seqs.length - 1;
|
||||||
const branch = isLast ? '└──' : '├──';
|
const branch = isLast ? "└──" : "├──";
|
||||||
|
|
||||||
if (dep) {
|
if (dep) {
|
||||||
const statusIcon = dep.status === 'completed' ? '✓' : dep.status === 'in_progress' ? '~' : '○';
|
const statusIcon =
|
||||||
console.log(`${indent}${branch} ${statusIcon} ${depSeq} - ${dep.title} [${dep.status}]`);
|
dep.status === "completed"
|
||||||
if (dep.depends_on.length > 0) {
|
? "✓"
|
||||||
const newIndent = indent + (isLast ? ' ' : '│ ');
|
: dep.status === "in_progress"
|
||||||
printDeps(dep.depends_on, newIndent);
|
? "~"
|
||||||
}
|
: "○";
|
||||||
} else {
|
console.log(
|
||||||
console.log(`${indent}${branch} ? ${depSeq} - NOT FOUND`);
|
`${indent}${branch} ${statusIcon} ${depSeq} - ${dep.title} [${dep.status}]`,
|
||||||
}
|
);
|
||||||
}
|
if (dep.depends_on.length > 0) {
|
||||||
};
|
const newIndent = indent + (isLast ? " " : "│ ");
|
||||||
|
printDeps(dep.depends_on, newIndent);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(`${indent}${branch} ? ${depSeq} - NOT FOUND`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
printDeps(target.depends_on);
|
printDeps(target.depends_on);
|
||||||
}
|
}
|
||||||
|
|
||||||
function cmdBlocked(feature?: string): void {
|
function cmdBlocked(feature?: string): void {
|
||||||
const features = feature ? [feature] : getFeatureDirs();
|
const features = feature ? [feature] : getFeatureDirs();
|
||||||
|
|
||||||
console.log('\n=== Blocked Tasks ===\n');
|
console.log("\n=== Blocked Tasks ===\n");
|
||||||
|
|
||||||
for (const f of features) {
|
for (const f of features) {
|
||||||
const subtasks = loadSubtasks(f);
|
const subtasks = loadSubtasks(f);
|
||||||
const completedSeqs = new Set(subtasks.filter(s => s.status === 'completed').map(s => s.seq));
|
const completedSeqs = new Set(
|
||||||
|
subtasks.filter((s) => s.status === "completed").map((s) => s.seq),
|
||||||
|
);
|
||||||
|
|
||||||
const blocked = subtasks.filter(s => {
|
const blocked = subtasks.filter((s) => {
|
||||||
if (s.status === 'blocked') return true;
|
if (s.status === "blocked") return true;
|
||||||
if (s.status !== 'pending') return false;
|
if (s.status !== "pending") return false;
|
||||||
return !s.depends_on.every(dep => completedSeqs.has(dep));
|
return !s.depends_on.every((dep) => completedSeqs.has(dep));
|
||||||
});
|
});
|
||||||
|
|
||||||
if (blocked.length > 0) {
|
if (blocked.length > 0) {
|
||||||
console.log(`[${f}]`);
|
console.log(`[${f}]`);
|
||||||
for (const s of blocked) {
|
for (const s of blocked) {
|
||||||
const waitingFor = s.depends_on.filter(dep => !completedSeqs.has(dep));
|
const waitingFor = s.depends_on.filter(
|
||||||
const reason = s.status === 'blocked'
|
(dep) => !completedSeqs.has(dep),
|
||||||
? 'explicitly blocked'
|
);
|
||||||
: `waiting: ${waitingFor.join(', ')}`;
|
const reason =
|
||||||
console.log(` ${s.seq} - ${s.title} (${reason})`);
|
s.status === "blocked"
|
||||||
}
|
? "explicitly blocked"
|
||||||
console.log();
|
: `waiting: ${waitingFor.join(", ")}`;
|
||||||
}
|
console.log(` ${s.seq} - ${s.title} (${reason})`);
|
||||||
}
|
}
|
||||||
|
console.log();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function cmdComplete(feature: string, seq: string, summary: string): void {
|
function cmdComplete(feature: string, seq: string, summary: string): void {
|
||||||
if (summary.length > 200) {
|
if (summary.length > 200) {
|
||||||
console.log('Error: Summary must be max 200 characters');
|
console.log("Error: Summary must be max 200 characters");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const subtasks = loadSubtasks(feature);
|
const subtasks = loadSubtasks(feature);
|
||||||
const subtask = subtasks.find(s => s.seq === seq);
|
const subtask = subtasks.find((s) => s.seq === seq);
|
||||||
|
|
||||||
if (!subtask) {
|
if (!subtask) {
|
||||||
console.log(`Task ${seq} not found in ${feature}`);
|
console.log(`Task ${seq} not found in ${feature}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
subtask.status = 'completed';
|
subtask.status = "completed";
|
||||||
subtask.completed_at = new Date().toISOString();
|
subtask.completed_at = new Date().toISOString();
|
||||||
subtask.completion_summary = summary;
|
subtask.completion_summary = summary;
|
||||||
|
|
||||||
saveSubtask(feature, subtask);
|
saveSubtask(feature, subtask);
|
||||||
|
|
||||||
// Update task.json counts
|
// Update task.json counts
|
||||||
const task = loadTask(feature);
|
const task = loadTask(feature);
|
||||||
if (task) {
|
if (task) {
|
||||||
const newSubtasks = loadSubtasks(feature);
|
const newSubtasks = loadSubtasks(feature);
|
||||||
task.completed_count = newSubtasks.filter(s => s.status === 'completed').length;
|
task.completed_count = newSubtasks.filter(
|
||||||
saveTask(feature, task);
|
(s) => s.status === "completed",
|
||||||
}
|
).length;
|
||||||
|
saveTask(feature, task);
|
||||||
|
}
|
||||||
|
|
||||||
console.log(`\n✓ Marked ${feature}/${seq} as completed`);
|
console.log(`\n✓ Marked ${feature}/${seq} as completed`);
|
||||||
console.log(` Summary: ${summary}`);
|
console.log(` Summary: ${summary}`);
|
||||||
|
|
||||||
if (task) {
|
if (task) {
|
||||||
console.log(` Progress: ${task.completed_count}/${task.subtask_count}`);
|
console.log(` Progress: ${task.completed_count}/${task.subtask_count}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function cmdValidate(feature?: string): void {
|
function cmdValidate(feature?: string): void {
|
||||||
const features = feature ? [feature] : getFeatureDirs();
|
const features = feature ? [feature] : getFeatureDirs();
|
||||||
let hasErrors = false;
|
let hasErrors = false;
|
||||||
|
|
||||||
const validTaskStatuses = new Set(['active', 'completed', 'blocked', 'archived']);
|
const validTaskStatuses = new Set([
|
||||||
const validSubtaskStatuses = new Set(['pending', 'in_progress', 'completed', 'blocked']);
|
"active",
|
||||||
|
"completed",
|
||||||
|
"blocked",
|
||||||
|
"archived",
|
||||||
|
]);
|
||||||
|
const validSubtaskStatuses = new Set([
|
||||||
|
"pending",
|
||||||
|
"in_progress",
|
||||||
|
"completed",
|
||||||
|
"blocked",
|
||||||
|
]);
|
||||||
|
|
||||||
const requiredTaskFields = [
|
const requiredTaskFields = [
|
||||||
'id',
|
"id",
|
||||||
'name',
|
"name",
|
||||||
'status',
|
"status",
|
||||||
'objective',
|
"objective",
|
||||||
'context_files',
|
"context_files",
|
||||||
'exit_criteria',
|
"exit_criteria",
|
||||||
'subtask_count',
|
"subtask_count",
|
||||||
'completed_count',
|
"completed_count",
|
||||||
'created_at',
|
"created_at",
|
||||||
'completed_at',
|
"completed_at",
|
||||||
];
|
];
|
||||||
|
|
||||||
const requiredSubtaskFields = [
|
const requiredSubtaskFields = [
|
||||||
'id',
|
"id",
|
||||||
'seq',
|
"seq",
|
||||||
'title',
|
"title",
|
||||||
'status',
|
"status",
|
||||||
'depends_on',
|
"depends_on",
|
||||||
'parallel',
|
"parallel",
|
||||||
'context_files',
|
"context_files",
|
||||||
'acceptance_criteria',
|
"acceptance_criteria",
|
||||||
'deliverables',
|
"deliverables",
|
||||||
'agent_id',
|
"agent_id",
|
||||||
'started_at',
|
"started_at",
|
||||||
'completed_at',
|
"completed_at",
|
||||||
'completion_summary',
|
"completion_summary",
|
||||||
];
|
];
|
||||||
|
|
||||||
const hasField = (obj: any, field: string): boolean => Object.prototype.hasOwnProperty.call(obj, field);
|
const hasField = (obj: any, field: string): boolean =>
|
||||||
const isStringArray = (value: any): boolean => Array.isArray(value) && value.every(v => typeof v === 'string');
|
Object.prototype.hasOwnProperty.call(obj, field);
|
||||||
|
const isStringArray = (value: any): boolean =>
|
||||||
|
Array.isArray(value) && value.every((v) => typeof v === "string");
|
||||||
|
|
||||||
console.log('\n=== Validation Results ===\n');
|
console.log("\n=== Validation Results ===\n");
|
||||||
|
|
||||||
for (const f of features) {
|
for (const f of features) {
|
||||||
const errors: string[] = [];
|
const errors: string[] = [];
|
||||||
|
|
||||||
// Check task.json exists
|
// Check task.json exists
|
||||||
const task = loadTask(f);
|
const task = loadTask(f);
|
||||||
if (!task) {
|
if (!task) {
|
||||||
errors.push('Missing task.json');
|
errors.push("Missing task.json");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load and validate subtasks
|
// Load and validate subtasks
|
||||||
const subtasks = loadSubtasks(f);
|
const subtasks = loadSubtasks(f);
|
||||||
const seqCounts = new Map<string, number>();
|
const seqCounts = new Map<string, number>();
|
||||||
for (const s of subtasks) {
|
for (const s of subtasks) {
|
||||||
const seq = typeof s.seq === 'string' ? s.seq : '';
|
const seq = typeof s.seq === "string" ? s.seq : "";
|
||||||
seqCounts.set(seq, (seqCounts.get(seq) || 0) + 1);
|
seqCounts.set(seq, (seqCounts.get(seq) || 0) + 1);
|
||||||
}
|
}
|
||||||
const seqs = new Set(subtasks.map(s => s.seq));
|
const seqs = new Set(subtasks.map((s) => s.seq));
|
||||||
|
|
||||||
if (task) {
|
if (task) {
|
||||||
// Required fields in task.json
|
// Required fields in task.json
|
||||||
for (const field of requiredTaskFields) {
|
for (const field of requiredTaskFields) {
|
||||||
if (!hasField(task, field)) {
|
if (!hasField(task, field)) {
|
||||||
errors.push(`task.json: missing required field '${field}'`);
|
errors.push(`task.json: missing required field '${field}'`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Task ID should match feature slug
|
// Task ID should match feature slug
|
||||||
if (task.id !== f) {
|
if (task.id !== f) {
|
||||||
errors.push(`task.json id ('${task.id}') should match feature slug ('${f}')`);
|
errors.push(
|
||||||
}
|
`task.json id ('${task.id}') should match feature slug ('${f}')`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Task status should be valid
|
// Task status should be valid
|
||||||
if (!validTaskStatuses.has(task.status)) {
|
if (!validTaskStatuses.has(task.status)) {
|
||||||
errors.push(`task.json: invalid status '${task.status}'`);
|
errors.push(`task.json: invalid status '${task.status}'`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Basic type checks for key task fields
|
// Basic type checks for key task fields
|
||||||
if (!isStringArray(task.context_files)) {
|
if (!isStringArray(task.context_files)) {
|
||||||
errors.push('task.json: context_files must be string[]');
|
errors.push("task.json: context_files must be string[]");
|
||||||
}
|
}
|
||||||
if (hasField(task, 'reference_files') && task.reference_files !== undefined && !isStringArray(task.reference_files)) {
|
if (
|
||||||
errors.push('task.json: reference_files must be string[] when present');
|
hasField(task, "reference_files") &&
|
||||||
}
|
task.reference_files !== undefined &&
|
||||||
if (!isStringArray(task.exit_criteria)) {
|
!isStringArray(task.reference_files)
|
||||||
errors.push('task.json: exit_criteria must be string[]');
|
) {
|
||||||
}
|
errors.push("task.json: reference_files must be string[] when present");
|
||||||
if (typeof task.subtask_count !== 'number') {
|
}
|
||||||
errors.push('task.json: subtask_count must be number');
|
if (!isStringArray(task.exit_criteria)) {
|
||||||
}
|
errors.push("task.json: exit_criteria must be string[]");
|
||||||
if (typeof task.completed_count !== 'number') {
|
}
|
||||||
errors.push('task.json: completed_count must be number');
|
if (typeof task.subtask_count !== "number") {
|
||||||
}
|
errors.push("task.json: subtask_count must be number");
|
||||||
}
|
}
|
||||||
|
if (typeof task.completed_count !== "number") {
|
||||||
|
errors.push("task.json: completed_count must be number");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const s of subtasks) {
|
for (const s of subtasks) {
|
||||||
// Required fields in subtask files
|
// Required fields in subtask files
|
||||||
for (const field of requiredSubtaskFields) {
|
for (const field of requiredSubtaskFields) {
|
||||||
if (!hasField(s, field)) {
|
if (!hasField(s, field)) {
|
||||||
errors.push(`${s.seq || '??'}: missing required field '${field}'`);
|
errors.push(`${s.seq || "??"}: missing required field '${field}'`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sequence format and uniqueness
|
// Sequence format and uniqueness
|
||||||
if (!/^\d{2}$/.test(s.seq)) {
|
if (!/^\d{2}$/.test(s.seq)) {
|
||||||
errors.push(`${s.seq}: sequence must be 2 digits (e.g., 01, 02)`);
|
errors.push(`${s.seq}: sequence must be 2 digits (e.g., 01, 02)`);
|
||||||
}
|
}
|
||||||
if ((seqCounts.get(s.seq) || 0) > 1) {
|
if ((seqCounts.get(s.seq) || 0) > 1) {
|
||||||
errors.push(`${s.seq}: duplicate sequence number`);
|
errors.push(`${s.seq}: duplicate sequence number`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check ID format
|
// Check ID format
|
||||||
if (!s.id.startsWith(f)) {
|
if (!s.id.startsWith(f)) {
|
||||||
errors.push(`${s.seq}: ID should start with feature name`);
|
errors.push(`${s.seq}: ID should start with feature name`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Status should be valid
|
// Status should be valid
|
||||||
if (!validSubtaskStatuses.has(s.status)) {
|
if (!validSubtaskStatuses.has(s.status)) {
|
||||||
errors.push(`${s.seq}: invalid status '${s.status}'`);
|
errors.push(`${s.seq}: invalid status '${s.status}'`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type checks
|
// Type checks
|
||||||
if (!isStringArray(s.depends_on)) {
|
if (!isStringArray(s.depends_on)) {
|
||||||
errors.push(`${s.seq}: depends_on must be string[]`);
|
errors.push(`${s.seq}: depends_on must be string[]`);
|
||||||
}
|
}
|
||||||
if (typeof s.parallel !== 'boolean') {
|
if (typeof s.parallel !== "boolean") {
|
||||||
errors.push(`${s.seq}: parallel must be boolean`);
|
errors.push(`${s.seq}: parallel must be boolean`);
|
||||||
}
|
}
|
||||||
if (!isStringArray(s.context_files)) {
|
if (!isStringArray(s.context_files)) {
|
||||||
errors.push(`${s.seq}: context_files must be string[]`);
|
errors.push(`${s.seq}: context_files must be string[]`);
|
||||||
}
|
}
|
||||||
if (hasField(s, 'reference_files') && s.reference_files !== undefined && !isStringArray(s.reference_files)) {
|
if (
|
||||||
errors.push(`${s.seq}: reference_files must be string[] when present`);
|
hasField(s, "reference_files") &&
|
||||||
}
|
s.reference_files !== undefined &&
|
||||||
if (!isStringArray(s.acceptance_criteria)) {
|
!isStringArray(s.reference_files)
|
||||||
errors.push(`${s.seq}: acceptance_criteria must be string[]`);
|
) {
|
||||||
} else if (s.acceptance_criteria.length === 0) {
|
errors.push(`${s.seq}: reference_files must be string[] when present`);
|
||||||
errors.push(`${s.seq}: No acceptance criteria defined`);
|
}
|
||||||
}
|
if (!isStringArray(s.acceptance_criteria)) {
|
||||||
if (!isStringArray(s.deliverables)) {
|
errors.push(`${s.seq}: acceptance_criteria must be string[]`);
|
||||||
errors.push(`${s.seq}: deliverables must be string[]`);
|
} else if (s.acceptance_criteria.length === 0) {
|
||||||
} else if (s.deliverables.length === 0) {
|
errors.push(`${s.seq}: No acceptance criteria defined`);
|
||||||
errors.push(`${s.seq}: No deliverables defined`);
|
}
|
||||||
}
|
if (!isStringArray(s.deliverables)) {
|
||||||
|
errors.push(`${s.seq}: deliverables must be string[]`);
|
||||||
|
} else if (s.deliverables.length === 0) {
|
||||||
|
errors.push(`${s.seq}: No deliverables defined`);
|
||||||
|
}
|
||||||
|
|
||||||
// Self dependency is invalid
|
// Self dependency is invalid
|
||||||
if (Array.isArray(s.depends_on) && s.depends_on.includes(s.seq)) {
|
if (Array.isArray(s.depends_on) && s.depends_on.includes(s.seq)) {
|
||||||
errors.push(`${s.seq}: task cannot depend on itself`);
|
errors.push(`${s.seq}: task cannot depend on itself`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for missing dependencies
|
// Check for missing dependencies
|
||||||
for (const dep of (Array.isArray(s.depends_on) ? s.depends_on : [])) {
|
for (const dep of Array.isArray(s.depends_on) ? s.depends_on : []) {
|
||||||
if (!seqs.has(dep)) {
|
if (!seqs.has(dep)) {
|
||||||
errors.push(`${s.seq}: depends on non-existent task ${dep}`);
|
errors.push(`${s.seq}: depends on non-existent task ${dep}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for circular dependencies
|
// Check for circular dependencies
|
||||||
const visited = new Set<string>();
|
const visited = new Set<string>();
|
||||||
const checkCircular = (seq: string, path: string[]): boolean => {
|
const checkCircular = (seq: string, path: string[]): boolean => {
|
||||||
if (path.includes(seq)) {
|
if (path.includes(seq)) {
|
||||||
errors.push(`${s.seq}: circular dependency detected: ${[...path, seq].join(' -> ')}`);
|
errors.push(
|
||||||
return true;
|
`${s.seq}: circular dependency detected: ${[...path, seq].join(" -> ")}`,
|
||||||
}
|
);
|
||||||
if (visited.has(seq)) return false;
|
return true;
|
||||||
visited.add(seq);
|
}
|
||||||
|
if (visited.has(seq)) return false;
|
||||||
|
visited.add(seq);
|
||||||
|
|
||||||
const task = subtasks.find(t => t.seq === seq);
|
const task = subtasks.find((t) => t.seq === seq);
|
||||||
if (task) {
|
if (task) {
|
||||||
for (const dep of task.depends_on) {
|
for (const dep of task.depends_on) {
|
||||||
if (checkCircular(dep, [...path, seq])) return true;
|
if (checkCircular(dep, [...path, seq])) return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
checkCircular(s.seq, []);
|
checkCircular(s.seq, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check counts match
|
// Check counts match
|
||||||
if (task && task.subtask_count !== subtasks.length) {
|
if (task && task.subtask_count !== subtasks.length) {
|
||||||
errors.push(`task.json subtask_count (${task.subtask_count}) doesn't match actual count (${subtasks.length})`);
|
errors.push(
|
||||||
}
|
`task.json subtask_count (${task.subtask_count}) doesn't match actual count (${subtasks.length})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Print results
|
// Print results
|
||||||
console.log(`[${f}]`);
|
console.log(`[${f}]`);
|
||||||
if (errors.length === 0) {
|
if (errors.length === 0) {
|
||||||
console.log(' ✓ All checks passed');
|
console.log(" ✓ All checks passed");
|
||||||
} else {
|
} else {
|
||||||
for (const e of errors) {
|
for (const e of errors) {
|
||||||
console.log(` ✗ ERROR: ${e}`);
|
console.log(` ✗ ERROR: ${e}`);
|
||||||
hasErrors = true;
|
hasErrors = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log();
|
console.log();
|
||||||
}
|
}
|
||||||
|
|
||||||
process.exit(hasErrors ? 1 : 0);
|
process.exit(hasErrors ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main
|
// Main
|
||||||
const [,, command, ...args] = process.argv;
|
const [, , command, ...args] = process.argv;
|
||||||
|
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case 'status':
|
case "status":
|
||||||
cmdStatus(args[0]);
|
cmdStatus(args[0]);
|
||||||
break;
|
break;
|
||||||
case 'next':
|
case "next":
|
||||||
cmdNext(args[0]);
|
cmdNext(args[0]);
|
||||||
break;
|
break;
|
||||||
case 'parallel':
|
case "parallel":
|
||||||
cmdParallel(args[0]);
|
cmdParallel(args[0]);
|
||||||
break;
|
break;
|
||||||
case 'deps':
|
case "deps":
|
||||||
if (args.length < 2) {
|
if (args.length < 2) {
|
||||||
console.log('Usage: deps <feature> <seq>');
|
console.log("Usage: deps <feature> <seq>");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
cmdDeps(args[0], args[1]);
|
cmdDeps(args[0], args[1]);
|
||||||
break;
|
break;
|
||||||
case 'blocked':
|
case "blocked":
|
||||||
cmdBlocked(args[0]);
|
cmdBlocked(args[0]);
|
||||||
break;
|
break;
|
||||||
case 'complete':
|
case "complete":
|
||||||
if (args.length < 3) {
|
if (args.length < 3) {
|
||||||
console.log('Usage: complete <feature> <seq> "summary"');
|
console.log('Usage: complete <feature> <seq> "summary"');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
cmdComplete(args[0], args[1], args.slice(2).join(' '));
|
cmdComplete(args[0], args[1], args.slice(2).join(" "));
|
||||||
break;
|
break;
|
||||||
case 'validate':
|
case "validate":
|
||||||
cmdValidate(args[0]);
|
cmdValidate(args[0]);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.log(`
|
console.log(`
|
||||||
Task Management CLI
|
Task Management CLI
|
||||||
|
|
||||||
Usage: bunx --bun ts-node task-cli.ts <command> [feature] [args...]
|
Usage: bunx --bun ts-node task-cli.ts <command> [feature] [args...]
|
||||||
|
|||||||
194
.opencode/tool/env/index.ts
vendored
194
.opencode/tool/env/index.ts
vendored
@@ -1,28 +1,28 @@
|
|||||||
import { readFile } from "fs/promises"
|
import { readFile } from "fs/promises";
|
||||||
import { resolve } from "path"
|
import { resolve } from "path";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration for environment variable loading
|
* Configuration for environment variable loading
|
||||||
*/
|
*/
|
||||||
export interface EnvLoaderConfig {
|
export interface EnvLoaderConfig {
|
||||||
/** Custom paths to search for .env files (relative to current working directory) */
|
/** Custom paths to search for .env files (relative to current working directory) */
|
||||||
searchPaths?: string[]
|
searchPaths?: string[];
|
||||||
/** Whether to log when environment variables are loaded */
|
/** Whether to log when environment variables are loaded */
|
||||||
verbose?: boolean
|
verbose?: boolean;
|
||||||
/** Whether to override existing environment variables */
|
/** Whether to override existing environment variables */
|
||||||
override?: boolean
|
override?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default search paths for .env files
|
* Default search paths for .env files
|
||||||
*/
|
*/
|
||||||
const DEFAULT_ENV_PATHS = [
|
const DEFAULT_ENV_PATHS = [
|
||||||
'./.env',
|
"./.env",
|
||||||
'../.env',
|
"../.env",
|
||||||
'../../.env',
|
"../../.env",
|
||||||
'../plugin/.env',
|
"../plugin/.env",
|
||||||
'../../../.env'
|
"../../../.env",
|
||||||
]
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load environment variables from .env files
|
* Load environment variables from .env files
|
||||||
@@ -31,54 +31,56 @@ const DEFAULT_ENV_PATHS = [
|
|||||||
* @param config Configuration options
|
* @param config Configuration options
|
||||||
* @returns Object containing loaded environment variables
|
* @returns Object containing loaded environment variables
|
||||||
*/
|
*/
|
||||||
export async function loadEnvVariables(config: EnvLoaderConfig = {}): Promise<Record<string, string>> {
|
export async function loadEnvVariables(
|
||||||
const {
|
config: EnvLoaderConfig = {},
|
||||||
searchPaths = DEFAULT_ENV_PATHS,
|
): Promise<Record<string, string>> {
|
||||||
verbose = false,
|
const {
|
||||||
override = false
|
searchPaths = DEFAULT_ENV_PATHS,
|
||||||
} = config
|
verbose = false,
|
||||||
|
override = false,
|
||||||
|
} = config;
|
||||||
|
|
||||||
const loadedVars: Record<string, string> = {}
|
const loadedVars: Record<string, string> = {};
|
||||||
|
|
||||||
for (const envPath of searchPaths) {
|
for (const envPath of searchPaths) {
|
||||||
try {
|
try {
|
||||||
const fullPath = resolve(envPath)
|
const fullPath = resolve(envPath);
|
||||||
const content = await readFile(fullPath, 'utf8')
|
const content = await readFile(fullPath, "utf8");
|
||||||
|
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
console.log(`Checking .env file: ${envPath}`)
|
console.log(`Checking .env file: ${envPath}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse .env file content
|
// Parse .env file content
|
||||||
const lines = content.split('\n')
|
const lines = content.split("\n");
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
const trimmed = line.trim()
|
const trimmed = line.trim();
|
||||||
if (trimmed && !trimmed.startsWith('#') && trimmed.includes('=')) {
|
if (trimmed && !trimmed.startsWith("#") && trimmed.includes("=")) {
|
||||||
const [key, ...valueParts] = trimmed.split('=')
|
const [key, ...valueParts] = trimmed.split("=");
|
||||||
const value = valueParts.join('=').trim()
|
const value = valueParts.join("=").trim();
|
||||||
|
|
||||||
// Remove quotes if present
|
// Remove quotes if present
|
||||||
const cleanValue = value.replace(/^["']|["']$/g, '')
|
const cleanValue = value.replace(/^["']|["']$/g, "");
|
||||||
|
|
||||||
if (key && cleanValue && (override || !process.env[key])) {
|
if (key && cleanValue && (override || !process.env[key])) {
|
||||||
process.env[key] = cleanValue
|
process.env[key] = cleanValue;
|
||||||
loadedVars[key] = cleanValue
|
loadedVars[key] = cleanValue;
|
||||||
|
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
console.log(`Loaded ${key} from ${envPath}`)
|
console.log(`Loaded ${key} from ${envPath}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// File doesn't exist or can't be read, continue to next
|
// File doesn't exist or can't be read, continue to next
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
console.log(`Could not read ${envPath}: ${error.message}`)
|
console.log(`Could not read ${envPath}: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return loadedVars
|
return loadedVars;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -88,17 +90,20 @@ export async function loadEnvVariables(config: EnvLoaderConfig = {}): Promise<Re
|
|||||||
* @param config Configuration options
|
* @param config Configuration options
|
||||||
* @returns The environment variable value or null if not found
|
* @returns The environment variable value or null if not found
|
||||||
*/
|
*/
|
||||||
export async function getEnvVariable(varName: string, config: EnvLoaderConfig = {}): Promise<string | null> {
|
export async function getEnvVariable(
|
||||||
// First check if it's already in the environment
|
varName: string,
|
||||||
let value = process.env[varName]
|
config: EnvLoaderConfig = {},
|
||||||
|
): Promise<string | null> {
|
||||||
|
// First check if it's already in the environment
|
||||||
|
let value = process.env[varName];
|
||||||
|
|
||||||
if (!value) {
|
if (!value) {
|
||||||
// Try to load from .env files
|
// Try to load from .env files
|
||||||
const loadedVars = await loadEnvVariables(config)
|
const loadedVars = await loadEnvVariables(config);
|
||||||
value = loadedVars[varName] || process.env[varName]
|
value = loadedVars[varName] || process.env[varName];
|
||||||
}
|
}
|
||||||
|
|
||||||
return value || null
|
return value || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -110,23 +115,30 @@ export async function getEnvVariable(varName: string, config: EnvLoaderConfig =
|
|||||||
* @returns The environment variable value
|
* @returns The environment variable value
|
||||||
* @throws Error if the variable is not found
|
* @throws Error if the variable is not found
|
||||||
*/
|
*/
|
||||||
export async function getRequiredEnvVariable(varName: string, config: EnvLoaderConfig = {}): Promise<string> {
|
export async function getRequiredEnvVariable(
|
||||||
const value = await getEnvVariable(varName, config)
|
varName: string,
|
||||||
|
config: EnvLoaderConfig = {},
|
||||||
|
): Promise<string> {
|
||||||
|
const value = await getEnvVariable(varName, config);
|
||||||
|
|
||||||
if (!value) {
|
if (!value) {
|
||||||
const searchPaths = config.searchPaths || DEFAULT_ENV_PATHS
|
const searchPaths = config.searchPaths || DEFAULT_ENV_PATHS;
|
||||||
throw new Error(`${varName} not found. Please set it in your environment or .env file.
|
throw new Error(`${varName} not found. Please set it in your environment or .env file.
|
||||||
|
|
||||||
To fix this:
|
To fix this:
|
||||||
1. Add to .env file: ${varName}=your_value_here
|
1. Add to .env file: ${varName}=your_value_here
|
||||||
2. Or export it: export ${varName}=your_value_here
|
2. Or export it: export ${varName}=your_value_here
|
||||||
|
|
||||||
Current working directory: ${process.cwd()}
|
Current working directory: ${process.cwd()}
|
||||||
Searched paths: ${searchPaths.join(', ')}
|
Searched paths: ${searchPaths.join(", ")}
|
||||||
Environment variables available: ${Object.keys(process.env).filter(k => k.includes(varName.split('_')[0])).join(', ') || 'none matching'}`)
|
Environment variables available: ${
|
||||||
}
|
Object.keys(process.env)
|
||||||
|
.filter((k) => k.includes(varName.split("_")[0]))
|
||||||
|
.join(", ") || "none matching"
|
||||||
|
}`);
|
||||||
|
}
|
||||||
|
|
||||||
return value
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -137,22 +149,27 @@ Environment variables available: ${Object.keys(process.env).filter(k => k.includ
|
|||||||
* @returns Object with variable names as keys and values as values
|
* @returns Object with variable names as keys and values as values
|
||||||
* @throws Error if any variable is not found
|
* @throws Error if any variable is not found
|
||||||
*/
|
*/
|
||||||
export async function getRequiredEnvVariables(varNames: string[], config: EnvLoaderConfig = {}): Promise<Record<string, string>> {
|
export async function getRequiredEnvVariables(
|
||||||
const result: Record<string, string> = {}
|
varNames: string[],
|
||||||
|
config: EnvLoaderConfig = {},
|
||||||
|
): Promise<Record<string, string>> {
|
||||||
|
const result: Record<string, string> = {};
|
||||||
|
|
||||||
// Load all .env files first
|
// Load all .env files first
|
||||||
await loadEnvVariables(config)
|
await loadEnvVariables(config);
|
||||||
|
|
||||||
// Check each required variable
|
// Check each required variable
|
||||||
for (const varName of varNames) {
|
for (const varName of varNames) {
|
||||||
const value = process.env[varName]
|
const value = process.env[varName];
|
||||||
if (!value) {
|
if (!value) {
|
||||||
throw new Error(`Required environment variable ${varName} not found. Please set it in your environment or .env file.`)
|
throw new Error(
|
||||||
}
|
`Required environment variable ${varName} not found. Please set it in your environment or .env file.`,
|
||||||
result[varName] = value
|
);
|
||||||
}
|
}
|
||||||
|
result[varName] = value;
|
||||||
|
}
|
||||||
|
|
||||||
return result
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -163,6 +180,9 @@ export async function getRequiredEnvVariables(varNames: string[], config: EnvLoa
|
|||||||
* @returns The API key value
|
* @returns The API key value
|
||||||
* @throws Error if the API key is not found
|
* @throws Error if the API key is not found
|
||||||
*/
|
*/
|
||||||
export async function getApiKey(apiKeyName: string, config: EnvLoaderConfig = {}): Promise<string> {
|
export async function getApiKey(
|
||||||
return getRequiredEnvVariable(apiKeyName, config)
|
apiKeyName: string,
|
||||||
|
config: EnvLoaderConfig = {},
|
||||||
|
): Promise<string> {
|
||||||
|
return getRequiredEnvVariable(apiKeyName, config);
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user