feat: terminal startup command and container tools

- Add startup_command field to ToolType model and API
- Execute startup command before interactive shell in terminal sessions
- Add tmux and ranger to OpenCode container spec
- Update Tool Workshop UI with startup_command input for terminal types
- Add backend tests for startup_command CRUD operations
- Sync specs: tool-terminal, tool-types-definition, opencode-web-server
- New spec: tool-terminal-startup-command

Quality gates: Frontend typecheck/lint passed. Backend tests blocked by environment (Python/Docker not available).

OpenSpec: terminal-startup-and-container-tools
This commit is contained in:
OpenCode Agent
2026-05-24 22:21:55 +00:00
parent a4c429d53a
commit 01a0ef46c9
22 changed files with 1060 additions and 78 deletions
+3
View File
@@ -20,6 +20,7 @@ export interface ToolType {
dockerfile_template: string | null;
build_context: Record<string, string> | null;
readiness_probe: ReadinessProbe | null;
startup_command: string | null;
required_variables: string[];
created_by_id: string | null;
created_at: string;
@@ -39,6 +40,7 @@ export interface CreateToolTypeRequest {
dockerfile_template?: string;
build_context?: Record<string, string>;
readiness_probe?: ReadinessProbe;
startup_command?: string;
required_variables: string[];
}
@@ -54,6 +56,7 @@ export interface UpdateToolTypeRequest {
dockerfile_template?: string;
build_context?: Record<string, string>;
readiness_probe?: ReadinessProbe;
startup_command?: string;
required_variables?: string[];
}
+23
View File
@@ -58,6 +58,7 @@ export const ToolWorkshopPage = () => {
readiness_timeout: "30",
readiness_interval: "2",
required_variables: "",
startup_command: "",
});
const [toolTypeError, setToolTypeError] = useState<string | null>(null);
const [toolTypeDirty, setToolTypeDirty] = useState(false);
@@ -131,6 +132,7 @@ export const ToolWorkshopPage = () => {
readiness_timeout: "30",
readiness_interval: "2",
required_variables: "",
startup_command: "",
});
setToolTypeError(null);
setToolTypeDirty(false);
@@ -152,6 +154,7 @@ export const ToolWorkshopPage = () => {
readiness_timeout: toolType.readiness_probe?.timeout?.toString() || "30",
readiness_interval: toolType.readiness_probe?.interval?.toString() || "2",
required_variables: toolType.required_variables?.join(", ") || "",
startup_command: toolType.startup_command || "",
});
setToolTypeError(null);
setToolTypeDirty(false);
@@ -250,6 +253,7 @@ export const ToolWorkshopPage = () => {
dockerfile_template: toolTypeForm.definition_type === "dockerfile" ? template : undefined,
readiness_probe: readinessProbe,
required_variables: variables,
startup_command: toolTypeForm.startup_command.trim() || undefined,
};
const newTool = await createToolType(input);
setIsCreating(false);
@@ -268,6 +272,7 @@ export const ToolWorkshopPage = () => {
dockerfile_template: toolTypeForm.definition_type === "dockerfile" ? template : undefined,
readiness_probe: readinessProbe,
required_variables: variables,
startup_command: toolTypeForm.startup_command.trim() || undefined,
};
await updateToolType(selectedToolType.id, input);
setToolTypeDirty(false);
@@ -772,6 +777,24 @@ export const ToolWorkshopPage = () => {
</div>
</div>
{toolTypeForm.interface_type === "terminal" && (
<div className="form-group">
<label htmlFor="tool-type-startup-command">Startup Command</label>
<input
id="tool-type-startup-command"
type="text"
value={toolTypeForm.startup_command}
onChange={(e) => {
setToolTypeForm({ ...toolTypeForm, startup_command: e.target.value });
setToolTypeDirty(true);
}}
placeholder="e.g., cd /workspace && ls"
className="form-input"
/>
<small className="form-help">Command to run before the interactive shell for each new terminal session.</small>
</div>
)}
{toolTypeForm.requires_port && (
<div className="form-group">
<label htmlFor="tool-type-default-port">Default Port *</label>