diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx
index 4b177e292cf..f3c10f821dc 100644
--- a/packages/opencode/src/cli/cmd/tui/app.tsx
+++ b/packages/opencode/src/cli/cmd/tui/app.tsx
@@ -13,6 +13,7 @@ import { LocalProvider, useLocal } from "@tui/context/local"
import { DialogModel, useConnected } from "@tui/component/dialog-model"
import { DialogMcp } from "@tui/component/dialog-mcp"
import { DialogStatus } from "@tui/component/dialog-status"
+import { DialogInstructions } from "@tui/component/dialog-instructions"
import { DialogThemeList } from "@tui/component/dialog-theme-list"
import { DialogHelp } from "./ui/dialog-help"
import { CommandProvider, useCommandDialog } from "@tui/component/dialog-command"
@@ -447,6 +448,14 @@ function App() {
},
category: "System",
},
+ {
+ title: "View instructions",
+ value: "opencode.instructions",
+ onSelect: () => {
+ dialog.replace(() => )
+ },
+ category: "System",
+ },
{
title: "Switch theme",
value: "theme.switch",
diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-instructions.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-instructions.tsx
new file mode 100644
index 00000000000..28bbeb37534
--- /dev/null
+++ b/packages/opencode/src/cli/cmd/tui/component/dialog-instructions.tsx
@@ -0,0 +1,66 @@
+import { TextAttributes } from "@opentui/core"
+import { useTheme } from "../context/theme"
+import { useSDK } from "@tui/context/sdk"
+import { For, Show, createResource } from "solid-js"
+
+export function DialogInstructions() {
+ const sdk = useSDK()
+ const { theme } = useTheme()
+
+ const [instructions] = createResource(async () => {
+ const result = await sdk.client.instructions.list()
+ return result.data
+ })
+
+ return (
+
+
+
+ Instructions
+
+ esc
+
+ Loading...}>
+ 0}
+ fallback={No instruction files loaded}
+ >
+
+
+ Files
+
+ {(file) => (
+
+
+ •
+
+
+ {file}
+
+
+ )}
+
+
+
+
+
+ URLs
+
+ {(url) => (
+
+
+ •
+
+
+ {url}
+
+
+ )}
+
+
+
+
+
+
+ )
+}
diff --git a/packages/opencode/src/server/routes/instructions.ts b/packages/opencode/src/server/routes/instructions.ts
new file mode 100644
index 00000000000..ea927fce17c
--- /dev/null
+++ b/packages/opencode/src/server/routes/instructions.ts
@@ -0,0 +1,38 @@
+import { Hono } from "hono"
+import { describeRoute } from "hono-openapi"
+import { resolver } from "hono-openapi"
+import { SystemPrompt } from "../../session/system"
+import z from "zod"
+import { lazy } from "../../util/lazy"
+
+export const InstructionsRoutes = lazy(() =>
+ new Hono().get(
+ "/",
+ describeRoute({
+ summary: "List instructions",
+ description: "Get a list of all instruction files loaded for the current session.",
+ operationId: "instructions.list",
+ responses: {
+ 200: {
+ description: "List of instruction sources",
+ content: {
+ "application/json": {
+ schema: resolver(
+ z
+ .object({
+ files: z.array(z.string()),
+ urls: z.array(z.string()),
+ })
+ .meta({ ref: "Instructions" }),
+ ),
+ },
+ },
+ },
+ },
+ }),
+ async (c) => {
+ const result = await SystemPrompt.paths()
+ return c.json(result)
+ },
+ ),
+)
diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts
index fa646f21ea8..dead50de386 100644
--- a/packages/opencode/src/server/server.ts
+++ b/packages/opencode/src/server/server.ts
@@ -29,6 +29,7 @@ import { FileRoutes } from "./routes/file"
import { ConfigRoutes } from "./routes/config"
import { ExperimentalRoutes } from "./routes/experimental"
import { ProviderRoutes } from "./routes/provider"
+import { InstructionsRoutes } from "./routes/instructions"
import { lazy } from "../util/lazy"
import { InstanceBootstrap } from "../project/bootstrap"
import { Storage } from "../storage/storage"
@@ -152,6 +153,7 @@ export namespace Server {
)
.use(validator("query", z.object({ directory: z.string().optional() })))
.route("/project", ProjectRoutes())
+ .route("/instructions", InstructionsRoutes())
.route("/pty", PtyRoutes())
.route("/config", ConfigRoutes())
.route("/experimental", ExperimentalRoutes())
diff --git a/packages/opencode/src/session/system.ts b/packages/opencode/src/session/system.ts
index 8d619357a4f..c721363b284 100644
--- a/packages/opencode/src/session/system.ts
+++ b/packages/opencode/src/session/system.ts
@@ -92,16 +92,17 @@ export namespace SystemPrompt {
GLOBAL_RULE_FILES.push(path.join(Flag.OPENCODE_CONFIG_DIR, "AGENTS.md"))
}
- export async function custom() {
+ export async function paths() {
const config = await Config.get()
- const paths = new Set()
+ const files = new Set()
+ const urls: string[] = []
// Only scan local rule files when project discovery is enabled
if (!Flag.OPENCODE_DISABLE_PROJECT_CONFIG) {
for (const localRuleFile of LOCAL_RULE_FILES) {
const matches = await Filesystem.findUp(localRuleFile, Instance.directory, Instance.worktree)
if (matches.length > 0) {
- matches.forEach((path) => paths.add(path))
+ matches.forEach((p) => files.add(p))
break
}
}
@@ -109,12 +110,11 @@ export namespace SystemPrompt {
for (const globalRuleFile of GLOBAL_RULE_FILES) {
if (await Bun.file(globalRuleFile).exists()) {
- paths.add(globalRuleFile)
+ files.add(globalRuleFile)
break
}
}
- const urls: string[] = []
if (config.instructions) {
for (let instruction of config.instructions) {
if (instruction.startsWith("https://") || instruction.startsWith("http://")) {
@@ -136,11 +136,17 @@ export namespace SystemPrompt {
} else {
matches = await resolveRelativeInstruction(instruction)
}
- matches.forEach((path) => paths.add(path))
+ matches.forEach((p) => files.add(p))
}
}
- const foundFiles = Array.from(paths).map((p) =>
+ return { files: Array.from(files), urls }
+ }
+
+ export async function custom() {
+ const { files, urls } = await paths()
+
+ const foundFiles = files.map((p) =>
Bun.file(p)
.text()
.catch(() => "")
diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts
index 67e7ac80cb9..47499de8e9b 100644
--- a/packages/sdk/js/src/v2/gen/sdk.gen.ts
+++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts
@@ -36,6 +36,7 @@ import type {
GlobalEventResponses,
GlobalHealthResponses,
InstanceDisposeResponses,
+ InstructionsListResponses,
LspStatusResponses,
McpAddErrors,
McpAddResponses,
@@ -341,6 +342,27 @@ export class Project extends HeyApiClient {
}
}
+export class Instructions extends HeyApiClient {
+ /**
+ * List instructions
+ *
+ * Get a list of all instruction files loaded for the current session.
+ */
+ public list(
+ parameters?: {
+ directory?: string
+ },
+ options?: Options,
+ ) {
+ const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }])
+ return (options?.client ?? this.client).get({
+ url: "/instructions",
+ ...options,
+ ...params,
+ })
+ }
+}
+
export class Pty extends HeyApiClient {
/**
* List PTY sessions
@@ -3131,6 +3153,11 @@ export class OpencodeClient extends HeyApiClient {
return (this._project ??= new Project({ client: this.client }))
}
+ private _instructions?: Instructions
+ get instructions(): Instructions {
+ return (this._instructions ??= new Instructions({ client: this.client }))
+ }
+
private _pty?: Pty
get pty(): Pty {
return (this._pty ??= new Pty({ client: this.client }))
diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts
index 38a52b325ad..22aa1843fcf 100644
--- a/packages/sdk/js/src/v2/gen/types.gen.ts
+++ b/packages/sdk/js/src/v2/gen/types.gen.ts
@@ -945,6 +945,11 @@ export type NotFoundError = {
}
}
+export type Instructions = {
+ files: Array
+ urls: Array
+}
+
/**
* Custom keybind configurations
*/
@@ -2297,6 +2302,24 @@ export type ProjectUpdateResponses = {
export type ProjectUpdateResponse = ProjectUpdateResponses[keyof ProjectUpdateResponses]
+export type InstructionsListData = {
+ body?: never
+ path?: never
+ query?: {
+ directory?: string
+ }
+ url: "/instructions"
+}
+
+export type InstructionsListResponses = {
+ /**
+ * List of instruction sources
+ */
+ 200: Instructions
+}
+
+export type InstructionsListResponse = InstructionsListResponses[keyof InstructionsListResponses]
+
export type PtyListData = {
body?: never
path?: never