diff --git a/packages/cli/src/commands/commands.ts b/packages/cli/src/commands/commands.ts index 8a6572dc256..682953ae97a 100644 --- a/packages/cli/src/commands/commands.ts +++ b/packages/cli/src/commands/commands.ts @@ -488,6 +488,12 @@ const Root = Spec.make(typeof OPENCODE_CLI_NAME === "string" ? OPENCODE_CLI_NAME }), ], }), + Spec.make("reload", { + description: "Reload configuration", + params: { + ...ServerParams, + }, + }), Spec.make("pair", { description: "Show server pairing information", params: { diff --git a/packages/cli/src/commands/handlers/reload.ts b/packages/cli/src/commands/handlers/reload.ts new file mode 100644 index 00000000000..5d4ca38445b --- /dev/null +++ b/packages/cli/src/commands/handlers/reload.ts @@ -0,0 +1,23 @@ +import { EOL } from "node:os" +import { Effect, Option } from "effect" +import { OpenCode } from "@opencode/client" +import { Service } from "@opencode/client/effect/service" +import { Commands } from "../commands" +import { Runtime } from "../../framework/runtime" +import { ServerConnection } from "../../services/server-connection" + +export default Runtime.handler( + Commands.commands.reload, + Effect.fn("cli.reload")(function* (input: Runtime.Input) { + const server = yield* ServerConnection.resolve({ + server: Option.getOrUndefined(input.server), + standalone: input.standalone, + }) + const client = OpenCode.make({ baseUrl: server.endpoint.url, headers: Service.headers(server.endpoint) }) + yield* Effect.tryPromise({ + try: (signal) => client.location.reload({ signal }), + catch: (cause) => cause, + }) + process.stdout.write("Configuration reloaded" + EOL) + }), +) diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 9ff4fac6943..5082e432b54 100755 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -55,6 +55,7 @@ const Handlers = Runtime.handlers(Commands, { mini: () => import("./commands/handlers/mini"), run: () => import("./commands/handlers/run"), pair: () => import("./commands/handlers/pair"), + reload: () => import("./commands/handlers/reload"), session: { list: () => import("./commands/handlers/session/list"), delete: () => import("./commands/handlers/session/delete"), diff --git a/packages/protocol/openapi.json b/packages/protocol/openapi.json index ecce0d54184..a08d054c560 100644 --- a/packages/protocol/openapi.json +++ b/packages/protocol/openapi.json @@ -163,7 +163,7 @@ } }, "description": "Shut down and rebuild every loaded location. Pending permissions and forms are cancelled; running sessions continue with fresh services at the next step boundary. Emits location.shutdown for client recovery and responds once all replacement builds settle.", - "summary": "Reload locations" + "summary": "Reload configuration" } }, "/api/agent": { @@ -8440,6 +8440,109 @@ "summary": "Find files" } }, + "/api/experimental/fs/write": { + "post": { + "tags": ["filesystem"], + "operationId": "experimental.fs.write", + "parameters": [ + { + "name": "location", + "in": "query", + "schema": { + "anyOf": [ + { + "type": "object", + "properties": { + "directory": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + { + "type": "null" + } + ] + }, + "required": false, + "style": "deepObject", + "explode": true + }, + { + "name": "path", + "in": "query", + "schema": { + "type": "string", + "description": "An absolute path or a path relative to the requested location. Missing parent directories are created." + }, + "required": true + } + ], + "security": [], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "location": { + "$ref": "#/components/schemas/Location.PublicRef" + }, + "data": { + "$ref": "#/components/schemas/FileSystem.Write" + } + }, + "required": ["location", "data"], + "additionalProperties": false + } + } + } + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestErrorEncoded" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorEncoded" + } + } + } + } + }, + "description": "Write the raw request body to an absolute path or a path relative to the requested location, creating parent directories, and return the resolved absolute path. Unlike read, the target is not confined to the location. Experimental: may change without compatibility guarantees.", + "summary": "Write file", + "requestBody": { + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + }, + "required": true + } + } + }, "/api/command": { "get": { "tags": ["command"], @@ -13537,6 +13640,16 @@ "required": ["path", "type"], "additionalProperties": false }, + "FileSystem.Write": { + "type": "object", + "properties": { + "path": { + "type": "string" + } + }, + "required": ["path"], + "additionalProperties": false + }, "ForbiddenErrorEncoded": { "type": "object", "properties": { @@ -13572,6 +13685,9 @@ "required": { "type": "boolean" }, + "hidden": { + "type": "boolean" + }, "when": { "type": "array", "items": { @@ -13761,6 +13877,9 @@ "required": { "type": "boolean" }, + "hidden": { + "type": "boolean" + }, "when": { "type": "array", "items": { @@ -13826,6 +13945,9 @@ "required": { "type": "boolean" }, + "hidden": { + "type": "boolean" + }, "when": { "type": "array", "items": { @@ -13878,6 +14000,9 @@ "required": { "type": "boolean" }, + "hidden": { + "type": "boolean" + }, "when": { "type": "array", "items": { @@ -14006,6 +14131,9 @@ "required": { "type": "boolean" }, + "hidden": { + "type": "boolean" + }, "when": { "type": "array", "items": { diff --git a/services/www/openapi.json b/services/www/openapi.json index ecce0d54184..a08d054c560 100644 --- a/services/www/openapi.json +++ b/services/www/openapi.json @@ -163,7 +163,7 @@ } }, "description": "Shut down and rebuild every loaded location. Pending permissions and forms are cancelled; running sessions continue with fresh services at the next step boundary. Emits location.shutdown for client recovery and responds once all replacement builds settle.", - "summary": "Reload locations" + "summary": "Reload configuration" } }, "/api/agent": { @@ -8440,6 +8440,109 @@ "summary": "Find files" } }, + "/api/experimental/fs/write": { + "post": { + "tags": ["filesystem"], + "operationId": "experimental.fs.write", + "parameters": [ + { + "name": "location", + "in": "query", + "schema": { + "anyOf": [ + { + "type": "object", + "properties": { + "directory": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + { + "type": "null" + } + ] + }, + "required": false, + "style": "deepObject", + "explode": true + }, + { + "name": "path", + "in": "query", + "schema": { + "type": "string", + "description": "An absolute path or a path relative to the requested location. Missing parent directories are created." + }, + "required": true + } + ], + "security": [], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "location": { + "$ref": "#/components/schemas/Location.PublicRef" + }, + "data": { + "$ref": "#/components/schemas/FileSystem.Write" + } + }, + "required": ["location", "data"], + "additionalProperties": false + } + } + } + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestErrorEncoded" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorEncoded" + } + } + } + } + }, + "description": "Write the raw request body to an absolute path or a path relative to the requested location, creating parent directories, and return the resolved absolute path. Unlike read, the target is not confined to the location. Experimental: may change without compatibility guarantees.", + "summary": "Write file", + "requestBody": { + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + }, + "required": true + } + } + }, "/api/command": { "get": { "tags": ["command"], @@ -13537,6 +13640,16 @@ "required": ["path", "type"], "additionalProperties": false }, + "FileSystem.Write": { + "type": "object", + "properties": { + "path": { + "type": "string" + } + }, + "required": ["path"], + "additionalProperties": false + }, "ForbiddenErrorEncoded": { "type": "object", "properties": { @@ -13572,6 +13685,9 @@ "required": { "type": "boolean" }, + "hidden": { + "type": "boolean" + }, "when": { "type": "array", "items": { @@ -13761,6 +13877,9 @@ "required": { "type": "boolean" }, + "hidden": { + "type": "boolean" + }, "when": { "type": "array", "items": { @@ -13826,6 +13945,9 @@ "required": { "type": "boolean" }, + "hidden": { + "type": "boolean" + }, "when": { "type": "array", "items": { @@ -13878,6 +14000,9 @@ "required": { "type": "boolean" }, + "hidden": { + "type": "boolean" + }, "when": { "type": "array", "items": { @@ -14006,6 +14131,9 @@ "required": { "type": "boolean" }, + "hidden": { + "type": "boolean" + }, "when": { "type": "array", "items": { diff --git a/services/www/public/openapi.json b/services/www/public/openapi.json index ecce0d54184..a08d054c560 100644 --- a/services/www/public/openapi.json +++ b/services/www/public/openapi.json @@ -163,7 +163,7 @@ } }, "description": "Shut down and rebuild every loaded location. Pending permissions and forms are cancelled; running sessions continue with fresh services at the next step boundary. Emits location.shutdown for client recovery and responds once all replacement builds settle.", - "summary": "Reload locations" + "summary": "Reload configuration" } }, "/api/agent": { @@ -8440,6 +8440,109 @@ "summary": "Find files" } }, + "/api/experimental/fs/write": { + "post": { + "tags": ["filesystem"], + "operationId": "experimental.fs.write", + "parameters": [ + { + "name": "location", + "in": "query", + "schema": { + "anyOf": [ + { + "type": "object", + "properties": { + "directory": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + { + "type": "null" + } + ] + }, + "required": false, + "style": "deepObject", + "explode": true + }, + { + "name": "path", + "in": "query", + "schema": { + "type": "string", + "description": "An absolute path or a path relative to the requested location. Missing parent directories are created." + }, + "required": true + } + ], + "security": [], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "location": { + "$ref": "#/components/schemas/Location.PublicRef" + }, + "data": { + "$ref": "#/components/schemas/FileSystem.Write" + } + }, + "required": ["location", "data"], + "additionalProperties": false + } + } + } + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestErrorEncoded" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorEncoded" + } + } + } + } + }, + "description": "Write the raw request body to an absolute path or a path relative to the requested location, creating parent directories, and return the resolved absolute path. Unlike read, the target is not confined to the location. Experimental: may change without compatibility guarantees.", + "summary": "Write file", + "requestBody": { + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + }, + "required": true + } + } + }, "/api/command": { "get": { "tags": ["command"], @@ -13537,6 +13640,16 @@ "required": ["path", "type"], "additionalProperties": false }, + "FileSystem.Write": { + "type": "object", + "properties": { + "path": { + "type": "string" + } + }, + "required": ["path"], + "additionalProperties": false + }, "ForbiddenErrorEncoded": { "type": "object", "properties": { @@ -13572,6 +13685,9 @@ "required": { "type": "boolean" }, + "hidden": { + "type": "boolean" + }, "when": { "type": "array", "items": { @@ -13761,6 +13877,9 @@ "required": { "type": "boolean" }, + "hidden": { + "type": "boolean" + }, "when": { "type": "array", "items": { @@ -13826,6 +13945,9 @@ "required": { "type": "boolean" }, + "hidden": { + "type": "boolean" + }, "when": { "type": "array", "items": { @@ -13878,6 +14000,9 @@ "required": { "type": "boolean" }, + "hidden": { + "type": "boolean" + }, "when": { "type": "array", "items": { @@ -14006,6 +14131,9 @@ "required": { "type": "boolean" }, + "hidden": { + "type": "boolean" + }, "when": { "type": "array", "items": { diff --git a/services/www/src/docs/content/cli/commands.mdx b/services/www/src/docs/content/cli/commands.mdx index c3a56cdc7b8..3b1d088ed4b 100644 --- a/services/www/src/docs/content/cli/commands.mdx +++ b/services/www/src/docs/content/cli/commands.mdx @@ -442,6 +442,14 @@ View all subcommands and flags. $ opencode service --help ``` +## reload + +`opencode reload` reloads configuration without restarting the server. Running sessions continue with fresh services at the next step boundary. + +```bash +$ opencode reload +``` + ## api `opencode api` makes a request to the running server.