-
Notifications
You must be signed in to change notification settings - Fork 0
Add describe tool #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,177 @@ | ||
| import { tool } from "@opencode-ai/plugin/tool"; | ||
|
|
||
| /** | ||
| * Raw DDEV describe output from `ddev describe -j` | ||
| */ | ||
| export type DdevDescribeRaw = { | ||
| shortroot?: string; | ||
| approot?: string; | ||
| status?: string; | ||
| name?: string; | ||
| type?: string; | ||
| php_version?: string; | ||
| webserver_type?: string; | ||
| database_type?: string; | ||
| database_version?: string; | ||
| router_status?: string; | ||
| docroot?: string; | ||
| mutagen_enabled?: boolean; | ||
| hostnames?: string[]; | ||
| httpurl?: string; | ||
| httpsurl?: string; | ||
| services?: { | ||
| web?: { | ||
| host_ports_mapping?: Array<{ | ||
| exposed_port: string; | ||
| host_port: string; | ||
| }>; | ||
| }; | ||
| db?: { | ||
| host_ports_mapping?: Array<{ | ||
| exposed_port: string; | ||
| host_port: string; | ||
| }>; | ||
| }; | ||
| }; | ||
| [key: string]: unknown; | ||
| }; | ||
|
|
||
| /** | ||
| * Simplified DDEV project information for LLM consumption | ||
| */ | ||
| export type DdevProjectInfo = { | ||
| name: string; | ||
| status: string; | ||
| type: string | null; | ||
| domain: string; | ||
| httpsUrl: string; | ||
| httpUrl: string; | ||
| webPort: string | null; | ||
| dbPort: string | null; | ||
| phpVersion: string | null; | ||
| webserverType: string | null; | ||
| dbType: string | null; | ||
| dbVersion: string | null; | ||
| }; | ||
|
|
||
| /** | ||
| * Default fields to expose in the describe tool | ||
| */ | ||
| const DEFAULT_FIELDS = [ | ||
| 'name', | ||
| 'status', | ||
| 'type', | ||
| 'domain', | ||
| 'httpsUrl', | ||
| 'httpUrl', | ||
| 'webPort', | ||
| 'dbPort', | ||
| ] as const; | ||
|
|
||
| /** | ||
| * Fetches and parses DDEV project data from `ddev describe -j` | ||
| * | ||
| * @param $ - Exec function for running shell commands | ||
| * @returns Parsed DDEV describe data or null if unavailable | ||
| */ | ||
| export async function getDdevDescribeData($: any): Promise<DdevDescribeRaw | null> { | ||
| try { | ||
| const result = await $`ddev describe -j`.quiet().nothrow(); | ||
|
|
||
| if (result.exitCode !== 0) { | ||
| return null; | ||
| } | ||
|
|
||
| const output = result.stdout.toString(); | ||
|
|
||
| try { | ||
| const data = JSON.parse(output); | ||
| return (data?.raw as DdevDescribeRaw) ?? null; | ||
| } catch { | ||
| return null; | ||
| } | ||
| } catch { | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Extracts meaningful project information from raw DDEV describe data | ||
| * | ||
| * @param raw - Raw DDEV describe data | ||
| * @returns Simplified project information | ||
| */ | ||
| export function extractProjectInfo(raw: DdevDescribeRaw): DdevProjectInfo { | ||
| const name = raw.name ?? 'unknown'; | ||
| const status = raw.status ?? 'unknown'; | ||
| const type = raw.type ?? null; | ||
| const httpsUrl = raw.httpsurl ?? ''; | ||
| const httpUrl = raw.httpurl ?? ''; | ||
| const domain = (raw.hostnames?.[0] ?? httpsUrl.replace(/^https?:\/\//, '')) || 'localhost'; | ||
|
|
||
| let webPort: string | null = null; | ||
| let dbPort: string | null = null; | ||
|
|
||
| if (raw.services) { | ||
| const webMapping = raw.services.web?.host_ports_mapping?.[0]; | ||
| if (webMapping) { | ||
| webPort = webMapping.host_port; | ||
| } | ||
|
|
||
| const dbMapping = raw.services.db?.host_ports_mapping?.[0]; | ||
| if (dbMapping) { | ||
| dbPort = dbMapping.host_port; | ||
| } | ||
| } | ||
|
|
||
| return { | ||
| name, | ||
| status, | ||
| type, | ||
| domain, | ||
| httpsUrl, | ||
| httpUrl, | ||
| webPort, | ||
| dbPort, | ||
| phpVersion: raw.php_version ?? null, | ||
| webserverType: raw.webserver_type ?? null, | ||
| dbType: raw.database_type ?? null, | ||
| dbVersion: raw.database_version ?? null, | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * Creates a DDEV describe tool for viewing project information | ||
| * | ||
| * Provides access to core DDEV project data like domain, ports, and status. | ||
| * Uses configurable field selection to expose relevant information without overwhelming context. | ||
| */ | ||
| export const createDdevDescribeTool = ($: any) => { | ||
| return tool({ | ||
| description: "Get DDEV project information including domain, ports, and status. Use this to understand the current DDEV environment configuration.", | ||
| args: { | ||
| fields: tool.schema.array(tool.schema.string()).optional().describe(`Fields to include in the response. Available options: ${[...DEFAULT_FIELDS, 'phpVersion', 'webserverType', 'dbType', 'dbVersion'].join(', ')}. Defaults to core fields: ${DEFAULT_FIELDS.join(', ')}.`), | ||
| }, | ||
| async execute(args) { | ||
| const raw = await getDdevDescribeData($); | ||
|
|
||
| if (!raw) { | ||
| throw new Error('Failed to get DDEV project data. Make sure DDEV is running.'); | ||
| } | ||
|
|
||
| const info = extractProjectInfo(raw); | ||
| const requestedFields = args.fields ?? [...DEFAULT_FIELDS]; | ||
|
|
||
| const result: Partial<DdevProjectInfo> = {}; | ||
|
|
||
| for (const field of requestedFields) { | ||
| if (field in info) { | ||
| const value = info[field as keyof DdevProjectInfo]; | ||
| result[field as keyof DdevProjectInfo] = value as any; | ||
| } | ||
|
Comment on lines
+167
to
+171
|
||
| } | ||
|
|
||
| return JSON.stringify(result, null, 2); | ||
| }, | ||
| }); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,5 +1,7 @@ | ||||||
| import type { Plugin } from "@opencode-ai/plugin"; | ||||||
| import { createDdevLogsTool } from "./logs"; | ||||||
| import { createDdevDescribeTool, getDdevDescribeData } from "./describe"; | ||||||
| import type { DdevDescribeRaw } from "./describe"; | ||||||
|
|
||||||
| /** | ||||||
| * DDEV Plugin for OpenCode | ||||||
|
|
@@ -9,19 +11,13 @@ import { createDdevLogsTool } from "./logs"; | |||||
| */ | ||||||
| export const DDEVPlugin: Plugin = async ({ project, client, $, directory, worktree }) => { | ||||||
| const CONTAINER_ROOT = '/var/www/html' as const; | ||||||
| const HOST_ONLY_COMMANDS = ['git', 'gh', 'docker', 'ddev'] as const; | ||||||
| const HOST_ONLY_COMMANDS = ['git', 'gh', 'docker', 'ddev', 'curl'] as const; | ||||||
| const CACHE_DURATION_MS = 120000; // 2 minutes | ||||||
|
|
||||||
| /** | ||||||
| * Raw DDEV project data from `ddev describe -j` | ||||||
| */ | ||||||
| type DdevRawData = { | ||||||
| shortroot?: string; | ||||||
| approot?: string; | ||||||
| status?: string; | ||||||
| name?: string; | ||||||
| [key: string]: unknown; | ||||||
| }; | ||||||
| type DdevRawData = DdevDescribeRaw; | ||||||
|
Comment on lines
17
to
+20
|
||||||
|
|
||||||
| /** | ||||||
| * Cached DDEV state with timestamp for cache invalidation | ||||||
|
|
@@ -202,21 +198,26 @@ export const DDEVPlugin: Plugin = async ({ project, client, $, directory, worktr | |||||
|
|
||||||
| /** | ||||||
| * Notifies LLM about DDEV environment on first command execution | ||||||
| * | ||||||
| * @link https://docs.ddev.com/en/stable/users/extend/customization-extendibility/#environment-variables-for-containers-and-services | ||||||
|
||||||
| * @link https://docs.ddev.com/en/stable/users/extend/customization-extendibility/#environment-variables-for-containers-and-services | |
| * @link https://docs.ddev.com/en/stable/users/extend/customization-extendibility/#environment-variables-for-containers-and-services |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message states "Make sure DDEV is running" but DDEV might be stopped, not installed, or the project might not be configured. Consider making the error message more accurate, such as "Failed to get DDEV project data. Ensure DDEV is installed, a project is configured, and DDEV is running."