Compare commits
92
Commits
github-v1.0.5
...
v0.5.18
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e9f52934e9 | ||
|
|
732b67f8ce | ||
|
|
d47bb96784 | ||
|
|
6456350564 | ||
|
|
c5c0a2ca6e | ||
|
|
3706b2bca7 | ||
|
|
1f57b9a70f | ||
|
|
004f53f741 | ||
|
|
cf29ec0a59 | ||
|
|
b5e08acdf7 | ||
|
|
7ddeeeb4f8 | ||
|
|
0f1697b2ab | ||
|
|
6e626afdcb | ||
|
|
0fe94c1616 | ||
|
|
a42b004c72 | ||
|
|
35f57768fd | ||
|
|
9a90ce84fb | ||
|
|
133fe41cd5 | ||
|
|
74c1085103 | ||
|
|
497fc170fd | ||
|
|
3edab60560 | ||
|
|
3f2ac2b9b0 | ||
|
|
1577b44087 | ||
|
|
39f52f48f2 | ||
|
|
4fadbcfb90 | ||
|
|
08c5c401ba | ||
|
|
ba2e86c7ef | ||
|
|
6d056789c7 | ||
|
|
5d508cc9c2 | ||
|
|
d9233872b9 | ||
|
|
aa4dba1541 | ||
|
|
947a3e8aff | ||
|
|
9a3186317b | ||
|
|
b1e584ca1d | ||
|
|
bca523eb63 | ||
|
|
2ff4cd2c2b | ||
|
|
d686269377 | ||
|
|
491abd6b5b | ||
|
|
4518f96e3d | ||
|
|
a9dcbedf99 | ||
|
|
9231043eb4 | ||
|
|
04dcd87170 | ||
|
|
c31fd9ed79 | ||
|
|
2989d92794 | ||
|
|
256d074411 | ||
|
|
8b01676ec0 | ||
|
|
34c6c8494a | ||
|
|
522bed6b7d | ||
|
|
dda672284c | ||
|
|
6018364164 | ||
|
|
bc0d438cee | ||
|
|
abef91c223 | ||
|
|
1bbf6d38e5 | ||
|
|
c9c9db1e8d | ||
|
|
b11fe9fbc6 | ||
|
|
60f3d413de | ||
|
|
1df2d78b85 | ||
|
|
2286a872c1 | ||
|
|
8a83301e0d | ||
|
|
9bc40f00e3 | ||
|
|
c3c440948a | ||
|
|
aa10f8a7f6 | ||
|
|
a2db58f125 | ||
|
|
574be9febf | ||
|
|
5b05ede748 | ||
|
|
4032426185 | ||
|
|
8d8045ff95 | ||
|
|
b3c8bec019 | ||
|
|
25f43adaa0 | ||
|
|
4913ee6afd | ||
|
|
c59ded82b3 | ||
|
|
40bdbf92a3 | ||
|
|
ad76d7e57d | ||
|
|
863ae6fa7d | ||
|
|
8f230ad4b4 | ||
|
|
c0f90eb564 | ||
|
|
50fb337270 | ||
|
|
e08ec077b0 | ||
|
|
796245d146 | ||
|
|
303a1044a8 | ||
|
|
f19586cebd | ||
|
|
5d12cadba7 | ||
|
|
745988f9e3 | ||
|
|
61580e6dce | ||
|
|
2dea8f0f6b | ||
|
|
446ce488c0 | ||
|
|
21b000aed0 | ||
|
|
0cdd8be70a | ||
|
|
2f4db2777c | ||
|
|
667ff90dd6 | ||
|
|
cd3d91209a | ||
|
|
75ed131abf |
@@ -32,14 +32,12 @@ jobs:
|
|||||||
"webfetch": "deny"
|
"webfetch": "deny"
|
||||||
}
|
}
|
||||||
run: |
|
run: |
|
||||||
opencode run -m anthropic/claude-sonnet-4-20250514 "A new issue has been created: '${{ github.event.issue.title }}'
|
opencode run -m anthropic/claude-sonnet-4-20250514 "A new issue has been created:'
|
||||||
|
|
||||||
Issue number:
|
Issue number:
|
||||||
${{ github.event.issue.number }}
|
${{ github.event.issue.number }}
|
||||||
Issue body:
|
|
||||||
${{ github.event.issue.body }}
|
|
||||||
|
|
||||||
Please search through existing issues (excluding #${{ github.event.issue.number }}) in this repository to find any potential duplicates of this new issue.
|
Lookup this issue and search through existing issues (excluding #${{ github.event.issue.number }}) in this repository to find any potential duplicates of this new issue.
|
||||||
Consider:
|
Consider:
|
||||||
1. Similar titles or descriptions
|
1. Similar titles or descriptions
|
||||||
2. Same error messages or symptoms
|
2. Same error messages or symptoms
|
||||||
|
|||||||
+22
-5
@@ -1,6 +1,4 @@
|
|||||||
---
|
---
|
||||||
model: openai/gpt-5
|
|
||||||
reasoningEffort: medium
|
|
||||||
description: ALWAYS use this when writing docs
|
description: ALWAYS use this when writing docs
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -8,7 +6,26 @@ You are an expert technical documentation writer
|
|||||||
|
|
||||||
You are not verbose
|
You are not verbose
|
||||||
|
|
||||||
Every chunk of text should be followed by an example or something besides text
|
The title of the page should be a word or a 2-3 word phrase
|
||||||
to look at.
|
|
||||||
|
|
||||||
Chunks of text should not be more than 2 sentences long.
|
The description should be one short line, should not start with "The", should
|
||||||
|
avoid repeating the title of the page, should be 5-10 words long
|
||||||
|
|
||||||
|
Chunks of text should not be more than 2 sentences long
|
||||||
|
|
||||||
|
Each section is spearated by a divider of 3 dashes
|
||||||
|
|
||||||
|
The section titles are short with only the first letter of the word capitalized
|
||||||
|
|
||||||
|
The section titles are in the imperative mood
|
||||||
|
|
||||||
|
The section titles should not repeat the term used in the page title, for
|
||||||
|
example, if the page title is "Models", avoid using a section title like "Add
|
||||||
|
new models". This might be unavoidable in some cases, but try to avoid it.
|
||||||
|
|
||||||
|
Check out the /packages/web/src/content/docs/docs/index.mdx as an example.
|
||||||
|
|
||||||
|
For JS or TS code snippets remove trailing semicolons and any trailing commas
|
||||||
|
that might not be needed.
|
||||||
|
|
||||||
|
If you are making a commit prefix the commit message with `docs:`
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
commit and push
|
||||||
|
|
||||||
|
make sure it includes a prefix like
|
||||||
|
docs:
|
||||||
|
tui:
|
||||||
|
core:
|
||||||
|
ci:
|
||||||
|
ignore:
|
||||||
|
wip:
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
description: hello world
|
||||||
|
---
|
||||||
|
|
||||||
|
hey there $ARGUMENTS
|
||||||
|
|
||||||
|
!`ls`
|
||||||
|
check out @README.md
|
||||||
@@ -51,3 +51,9 @@
|
|||||||
| 2025-08-15 | 193,608 (+4,545) | 181,792 (+2,051) | 375,400 (+6,596) |
|
| 2025-08-15 | 193,608 (+4,545) | 181,792 (+2,051) | 375,400 (+6,596) |
|
||||||
| 2025-08-16 | 198,118 (+4,510) | 184,558 (+2,766) | 382,676 (+7,276) |
|
| 2025-08-16 | 198,118 (+4,510) | 184,558 (+2,766) | 382,676 (+7,276) |
|
||||||
| 2025-08-17 | 201,299 (+3,181) | 186,269 (+1,711) | 387,568 (+4,892) |
|
| 2025-08-17 | 201,299 (+3,181) | 186,269 (+1,711) | 387,568 (+4,892) |
|
||||||
|
| 2025-08-18 | 204,559 (+3,260) | 187,399 (+1,130) | 391,958 (+4,390) |
|
||||||
|
| 2025-08-19 | 209,814 (+5,255) | 189,668 (+2,269) | 399,482 (+7,524) |
|
||||||
|
| 2025-08-20 | 214,497 (+4,683) | 191,481 (+1,813) | 405,978 (+6,496) |
|
||||||
|
| 2025-08-21 | 220,465 (+5,968) | 194,784 (+3,303) | 415,249 (+9,271) |
|
||||||
|
| 2025-08-22 | 225,899 (+5,434) | 197,204 (+2,420) | 423,103 (+7,854) |
|
||||||
|
| 2025-08-23 | 229,005 (+3,106) | 199,238 (+2,034) | 428,243 (+5,140) |
|
||||||
|
|||||||
@@ -15,6 +15,8 @@
|
|||||||
"name": "@opencode/cloud-app",
|
"name": "@opencode/cloud-app",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ibm/plex": "6.4.1",
|
"@ibm/plex": "6.4.1",
|
||||||
|
"@openauthjs/openauth": "0.0.0-20250322224806",
|
||||||
|
"@opencode/cloud-core": "workspace:*",
|
||||||
"@solidjs/meta": "^0.29.4",
|
"@solidjs/meta": "^0.29.4",
|
||||||
"@solidjs/router": "^0.15.0",
|
"@solidjs/router": "^0.15.0",
|
||||||
"@solidjs/start": "^1.1.0",
|
"@solidjs/start": "^1.1.0",
|
||||||
@@ -24,7 +26,7 @@
|
|||||||
},
|
},
|
||||||
"cloud/core": {
|
"cloud/core": {
|
||||||
"name": "@opencode/cloud-core",
|
"name": "@opencode/cloud-core",
|
||||||
"version": "0.5.4",
|
"version": "0.5.15",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-sts": "3.782.0",
|
"@aws-sdk/client-sts": "3.782.0",
|
||||||
"drizzle-orm": "0.41.0",
|
"drizzle-orm": "0.41.0",
|
||||||
@@ -38,7 +40,7 @@
|
|||||||
},
|
},
|
||||||
"cloud/function": {
|
"cloud/function": {
|
||||||
"name": "@opencode/cloud-function",
|
"name": "@opencode/cloud-function",
|
||||||
"version": "0.5.4",
|
"version": "0.5.15",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/anthropic": "2.0.0",
|
"@ai-sdk/anthropic": "2.0.0",
|
||||||
"@ai-sdk/openai": "2.0.2",
|
"@ai-sdk/openai": "2.0.2",
|
||||||
@@ -58,7 +60,7 @@
|
|||||||
},
|
},
|
||||||
"cloud/web": {
|
"cloud/web": {
|
||||||
"name": "@opencode/cloud-web",
|
"name": "@opencode/cloud-web",
|
||||||
"version": "0.5.4",
|
"version": "0.5.15",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "0.13.9",
|
"@kobalte/core": "0.13.9",
|
||||||
"@openauthjs/solid": "0.0.0-20250322224806",
|
"@openauthjs/solid": "0.0.0-20250322224806",
|
||||||
@@ -77,7 +79,7 @@
|
|||||||
},
|
},
|
||||||
"packages/function": {
|
"packages/function": {
|
||||||
"name": "@opencode/function",
|
"name": "@opencode/function",
|
||||||
"version": "0.5.4",
|
"version": "0.5.15",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/auth-app": "8.0.1",
|
"@octokit/auth-app": "8.0.1",
|
||||||
"@octokit/rest": "22.0.0",
|
"@octokit/rest": "22.0.0",
|
||||||
@@ -92,18 +94,14 @@
|
|||||||
},
|
},
|
||||||
"packages/opencode": {
|
"packages/opencode": {
|
||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"version": "0.5.4",
|
"version": "0.5.15",
|
||||||
"bin": {
|
"bin": {
|
||||||
"opencode": "./bin/opencode",
|
"opencode": "./bin/opencode",
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "1.11.1",
|
|
||||||
"@actions/github": "6.0.1",
|
|
||||||
"@clack/prompts": "1.0.0-alpha.1",
|
"@clack/prompts": "1.0.0-alpha.1",
|
||||||
"@hono/zod-validator": "catalog:",
|
"@hono/zod-validator": "catalog:",
|
||||||
"@modelcontextprotocol/sdk": "1.15.1",
|
"@modelcontextprotocol/sdk": "1.15.1",
|
||||||
"@octokit/graphql": "9.0.1",
|
|
||||||
"@octokit/rest": "22.0.0",
|
|
||||||
"@openauthjs/openauth": "0.4.3",
|
"@openauthjs/openauth": "0.4.3",
|
||||||
"@opencode-ai/plugin": "workspace:*",
|
"@opencode-ai/plugin": "workspace:*",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
@@ -146,19 +144,22 @@
|
|||||||
},
|
},
|
||||||
"packages/plugin": {
|
"packages/plugin": {
|
||||||
"name": "@opencode-ai/plugin",
|
"name": "@opencode-ai/plugin",
|
||||||
"version": "0.5.4",
|
"version": "0.5.15",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@hey-api/openapi-ts": "0.80.1",
|
"@hey-api/openapi-ts": "0.81.0",
|
||||||
"@tsconfig/node22": "catalog:",
|
"@tsconfig/node22": "catalog:",
|
||||||
"typescript": "catalog:",
|
"typescript": "catalog:",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"packages/sdk/js": {
|
"packages/sdk/js": {
|
||||||
"name": "@opencode-ai/sdk",
|
"name": "@opencode-ai/sdk",
|
||||||
"version": "0.5.4",
|
"version": "0.5.15",
|
||||||
|
"dependencies": {
|
||||||
|
"@hey-api/openapi-ts": "0.80.1",
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@hey-api/openapi-ts": "0.80.1",
|
"@hey-api/openapi-ts": "0.80.1",
|
||||||
"@tsconfig/node22": "catalog:",
|
"@tsconfig/node22": "catalog:",
|
||||||
@@ -167,7 +168,7 @@
|
|||||||
},
|
},
|
||||||
"packages/web": {
|
"packages/web": {
|
||||||
"name": "@opencode/web",
|
"name": "@opencode/web",
|
||||||
"version": "0.5.4",
|
"version": "0.5.15",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/cloudflare": "12.6.3",
|
"@astrojs/cloudflare": "12.6.3",
|
||||||
"@astrojs/markdown-remark": "6.3.1",
|
"@astrojs/markdown-remark": "6.3.1",
|
||||||
@@ -189,7 +190,7 @@
|
|||||||
"sharp": "0.32.5",
|
"sharp": "0.32.5",
|
||||||
"shiki": "3.4.2",
|
"shiki": "3.4.2",
|
||||||
"solid-js": "1.9.7",
|
"solid-js": "1.9.7",
|
||||||
"toolbeam-docs-theme": "0.4.3",
|
"toolbeam-docs-theme": "0.4.6",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "catalog:",
|
"@types/node": "catalog:",
|
||||||
@@ -206,6 +207,9 @@
|
|||||||
"web-tree-sitter",
|
"web-tree-sitter",
|
||||||
"tree-sitter-bash",
|
"tree-sitter-bash",
|
||||||
],
|
],
|
||||||
|
"overrides": {
|
||||||
|
"zod": "3.25.76",
|
||||||
|
},
|
||||||
"catalog": {
|
"catalog": {
|
||||||
"@hono/zod-validator": "0.4.2",
|
"@hono/zod-validator": "0.4.2",
|
||||||
"@tsconfig/node22": "22.0.2",
|
"@tsconfig/node22": "22.0.2",
|
||||||
@@ -217,16 +221,6 @@
|
|||||||
"zod": "3.25.76",
|
"zod": "3.25.76",
|
||||||
},
|
},
|
||||||
"packages": {
|
"packages": {
|
||||||
"@actions/core": ["@actions/core@1.11.1", "", { "dependencies": { "@actions/exec": "^1.1.1", "@actions/http-client": "^2.0.1" } }, "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A=="],
|
|
||||||
|
|
||||||
"@actions/exec": ["@actions/exec@1.1.1", "", { "dependencies": { "@actions/io": "^1.0.1" } }, "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w=="],
|
|
||||||
|
|
||||||
"@actions/github": ["@actions/github@6.0.1", "", { "dependencies": { "@actions/http-client": "^2.2.0", "@octokit/core": "^5.0.1", "@octokit/plugin-paginate-rest": "^9.2.2", "@octokit/plugin-rest-endpoint-methods": "^10.4.0", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "undici": "^5.28.5" } }, "sha512-xbZVcaqD4XnQAe35qSQqskb3SqIAfRyLBrHMd/8TuL7hJSz2QtbDwnNM8zWx4zO5l2fnGtseNE3MbEvD7BxVMw=="],
|
|
||||||
|
|
||||||
"@actions/http-client": ["@actions/http-client@2.2.3", "", { "dependencies": { "tunnel": "^0.0.6", "undici": "^5.25.4" } }, "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA=="],
|
|
||||||
|
|
||||||
"@actions/io": ["@actions/io@1.1.3", "", {}, "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="],
|
|
||||||
|
|
||||||
"@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@2.2.10", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-icLGO7Q0NinnHIPgT+y1QjHVwH4HwV+brWbvM+FfCG2Afpa89PyKa3Ret91kGjZpBgM/xnj1B7K5eM+rRlsXQA=="],
|
"@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@2.2.10", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-icLGO7Q0NinnHIPgT+y1QjHVwH4HwV+brWbvM+FfCG2Afpa89PyKa3Ret91kGjZpBgM/xnj1B7K5eM+rRlsXQA=="],
|
||||||
|
|
||||||
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-uyyaO4KhxoIKZztREqLPh+6/K3ZJx/rp72JKoUEL9/kC+vfQTThUfPnY/bUryUpcnawx8IY/tSoYNOi/8PCv7w=="],
|
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-uyyaO4KhxoIKZztREqLPh+6/K3ZJx/rp72JKoUEL9/kC+vfQTThUfPnY/bUryUpcnawx8IY/tSoYNOi/8PCv7w=="],
|
||||||
@@ -485,7 +479,7 @@
|
|||||||
|
|
||||||
"@expressive-code/plugin-text-markers": ["@expressive-code/plugin-text-markers@0.41.3", "", { "dependencies": { "@expressive-code/core": "^0.41.3" } }, "sha512-SN8tkIzDpA0HLAscEYD2IVrfLiid6qEdE9QLlGVSxO1KEw7qYvjpbNBQjUjMr5/jvTJ7ys6zysU2vLPHE0sb2g=="],
|
"@expressive-code/plugin-text-markers": ["@expressive-code/plugin-text-markers@0.41.3", "", { "dependencies": { "@expressive-code/core": "^0.41.3" } }, "sha512-SN8tkIzDpA0HLAscEYD2IVrfLiid6qEdE9QLlGVSxO1KEw7qYvjpbNBQjUjMr5/jvTJ7ys6zysU2vLPHE0sb2g=="],
|
||||||
|
|
||||||
"@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="],
|
"@fastify/busboy": ["@fastify/busboy@3.1.1", "", {}, "sha512-5DGmA8FTdB2XbDeEwc/5ZXBl6UbBAyBOOLlPuBnZ/N1SwdH9Ii+cOX3tBROlDgcTXxjOYnLMVoKk9+FXAw0CJw=="],
|
||||||
|
|
||||||
"@floating-ui/core": ["@floating-ui/core@1.7.3", "", { "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w=="],
|
"@floating-ui/core": ["@floating-ui/core@1.7.3", "", { "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w=="],
|
||||||
|
|
||||||
@@ -501,7 +495,7 @@
|
|||||||
|
|
||||||
"@hey-api/json-schema-ref-parser": ["@hey-api/json-schema-ref-parser@1.0.6", "", { "dependencies": { "@jsdevtools/ono": "^7.1.3", "@types/json-schema": "^7.0.15", "js-yaml": "^4.1.0", "lodash": "^4.17.21" } }, "sha512-yktiFZoWPtEW8QKS65eqKwA5MTKp88CyiL8q72WynrBs/73SAaxlSWlA2zW/DZlywZ5hX1OYzrCC0wFdvO9c2w=="],
|
"@hey-api/json-schema-ref-parser": ["@hey-api/json-schema-ref-parser@1.0.6", "", { "dependencies": { "@jsdevtools/ono": "^7.1.3", "@types/json-schema": "^7.0.15", "js-yaml": "^4.1.0", "lodash": "^4.17.21" } }, "sha512-yktiFZoWPtEW8QKS65eqKwA5MTKp88CyiL8q72WynrBs/73SAaxlSWlA2zW/DZlywZ5hX1OYzrCC0wFdvO9c2w=="],
|
||||||
|
|
||||||
"@hey-api/openapi-ts": ["@hey-api/openapi-ts@0.80.1", "", { "dependencies": { "@hey-api/json-schema-ref-parser": "1.0.6", "ansi-colors": "4.1.3", "c12": "2.0.1", "color-support": "1.1.3", "commander": "13.0.0", "handlebars": "4.7.8", "open": "10.1.2", "semver": "7.7.2" }, "peerDependencies": { "typescript": "^5.5.3" }, "bin": { "openapi-ts": "bin/index.cjs" } }, "sha512-AC478kg36vmmrseLZNFonZ/cmXXmDzW5yWz4PVg1S8ebJsRtVRJ/QU+mtnXfzf9avN2P0pz/AO4WAe4jyFY2gA=="],
|
"@hey-api/openapi-ts": ["@hey-api/openapi-ts@0.81.0", "", { "dependencies": { "@hey-api/json-schema-ref-parser": "1.0.6", "ansi-colors": "4.1.3", "c12": "2.0.1", "color-support": "1.1.3", "commander": "13.0.0", "handlebars": "4.7.8", "js-yaml": "4.1.0", "open": "10.1.2", "semver": "7.7.2" }, "peerDependencies": { "typescript": "^5.5.3" }, "bin": { "openapi-ts": "bin/index.cjs" } }, "sha512-PoJukNBkUfHOoMDpN33bBETX49TUhy7Hu8Sa0jslOvFndvZ5VjQr4Nl/Dzjb9LG1Lp5HjybyTJMA6a1zYk/q6A=="],
|
||||||
|
|
||||||
"@hono/zod-validator": ["@hono/zod-validator@0.4.2", "", { "peerDependencies": { "hono": ">=3.9.0", "zod": "^3.19.1" } }, "sha512-1rrlBg+EpDPhzOV4hT9pxr5+xDVmKuz6YJl+la7VCwK6ass5ldyKm5fD+umJdV2zhHD6jROoCCv8NbTwyfhT0g=="],
|
"@hono/zod-validator": ["@hono/zod-validator@0.4.2", "", { "peerDependencies": { "hono": ">=3.9.0", "zod": "^3.19.1" } }, "sha512-1rrlBg+EpDPhzOV4hT9pxr5+xDVmKuz6YJl+la7VCwK6ass5ldyKm5fD+umJdV2zhHD6jROoCCv8NbTwyfhT0g=="],
|
||||||
|
|
||||||
@@ -1469,8 +1463,6 @@
|
|||||||
|
|
||||||
"depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
|
"depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
|
||||||
|
|
||||||
"deprecation": ["deprecation@2.3.1", "", {}, "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="],
|
|
||||||
|
|
||||||
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
|
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
|
||||||
|
|
||||||
"destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="],
|
"destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="],
|
||||||
@@ -2819,7 +2811,7 @@
|
|||||||
|
|
||||||
"toml": ["toml@3.0.0", "", {}, "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w=="],
|
"toml": ["toml@3.0.0", "", {}, "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w=="],
|
||||||
|
|
||||||
"toolbeam-docs-theme": ["toolbeam-docs-theme@0.4.3", "", { "peerDependencies": { "@astrojs/starlight": "^0.34.3", "astro": "^5.7.13" } }, "sha512-3um/NsSq4xFeKbKrNGPHIzfTixwnEVvroqA8Q+lecnYHHJ5TtiYTggHDqewOW+I67t0J1IVBwVKUPjxiQfIcog=="],
|
"toolbeam-docs-theme": ["toolbeam-docs-theme@0.4.6", "", { "peerDependencies": { "@astrojs/starlight": "^0.34.3", "astro": "^5.7.13" } }, "sha512-s4yKn3PYnmPCl6MHPkQ2MedSgVh7FMAP64DiXnAfCls/H/RR6iTe0/SFgPTprz+HXXaKvv2iPo3XuvmDRY6cxQ=="],
|
||||||
|
|
||||||
"tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
|
"tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
|
||||||
|
|
||||||
@@ -2843,8 +2835,6 @@
|
|||||||
|
|
||||||
"tuf-js": ["tuf-js@2.2.1", "", { "dependencies": { "@tufjs/models": "2.0.1", "debug": "^4.3.4", "make-fetch-happen": "^13.0.1" } }, "sha512-GwIJau9XaA8nLVbUXsN3IlFi7WmQ48gBUrl3FTkkL/XLu/POhBzfmX9hd33FNMX1qAsfl6ozO1iMmW9NC8YniA=="],
|
"tuf-js": ["tuf-js@2.2.1", "", { "dependencies": { "@tufjs/models": "2.0.1", "debug": "^4.3.4", "make-fetch-happen": "^13.0.1" } }, "sha512-GwIJau9XaA8nLVbUXsN3IlFi7WmQ48gBUrl3FTkkL/XLu/POhBzfmX9hd33FNMX1qAsfl6ozO1iMmW9NC8YniA=="],
|
||||||
|
|
||||||
"tunnel": ["tunnel@0.0.6", "", {}, "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="],
|
|
||||||
|
|
||||||
"tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="],
|
"tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="],
|
||||||
|
|
||||||
"turndown": ["turndown@7.2.0", "", { "dependencies": { "@mixmark-io/domino": "^2.2.0" } }, "sha512-eCZGBN4nNNqM9Owkv9HAtWRYfLA4h909E/WGAWWBpmB275ehNhZyk87/Tpvjbp0jjNl9XwCsbe6bm6CqFsgD+A=="],
|
"turndown": ["turndown@7.2.0", "", { "dependencies": { "@mixmark-io/domino": "^2.2.0" } }, "sha512-eCZGBN4nNNqM9Owkv9HAtWRYfLA4h909E/WGAWWBpmB275ehNhZyk87/Tpvjbp0jjNl9XwCsbe6bm6CqFsgD+A=="],
|
||||||
@@ -2869,7 +2859,7 @@
|
|||||||
|
|
||||||
"unctx": ["unctx@2.4.1", "", { "dependencies": { "acorn": "^8.14.0", "estree-walker": "^3.0.3", "magic-string": "^0.30.17", "unplugin": "^2.1.0" } }, "sha512-AbaYw0Nm4mK4qjhns67C+kgxR2YWiwlDBPzxrN8h8C6VtAdCgditAY5Dezu3IJy4XVqAnbrXt9oQJvsn3fyozg=="],
|
"unctx": ["unctx@2.4.1", "", { "dependencies": { "acorn": "^8.14.0", "estree-walker": "^3.0.3", "magic-string": "^0.30.17", "unplugin": "^2.1.0" } }, "sha512-AbaYw0Nm4mK4qjhns67C+kgxR2YWiwlDBPzxrN8h8C6VtAdCgditAY5Dezu3IJy4XVqAnbrXt9oQJvsn3fyozg=="],
|
||||||
|
|
||||||
"undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
|
"undici": ["undici@7.13.0", "", {}, "sha512-l+zSMssRqrzDcb3fjMkjjLGmuiiK2pMIcV++mJaAc9vhjSGpvM7h43QgP+OAMb1GImHmbPyG2tBXeuyG5iY4gA=="],
|
||||||
|
|
||||||
"undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
"undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||||
|
|
||||||
@@ -3063,16 +3053,6 @@
|
|||||||
|
|
||||||
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
|
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
|
||||||
|
|
||||||
"@actions/github/@octokit/core": ["@octokit/core@5.2.2", "", { "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "@octokit/types": "^13.0.0", "before-after-hook": "^2.2.0", "universal-user-agent": "^6.0.0" } }, "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg=="],
|
|
||||||
|
|
||||||
"@actions/github/@octokit/plugin-paginate-rest": ["@octokit/plugin-paginate-rest@9.2.2", "", { "dependencies": { "@octokit/types": "^12.6.0" }, "peerDependencies": { "@octokit/core": "5" } }, "sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ=="],
|
|
||||||
|
|
||||||
"@actions/github/@octokit/plugin-rest-endpoint-methods": ["@octokit/plugin-rest-endpoint-methods@10.4.1", "", { "dependencies": { "@octokit/types": "^12.6.0" }, "peerDependencies": { "@octokit/core": "5" } }, "sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg=="],
|
|
||||||
|
|
||||||
"@actions/github/@octokit/request": ["@octokit/request@8.4.1", "", { "dependencies": { "@octokit/endpoint": "^9.0.6", "@octokit/request-error": "^5.1.1", "@octokit/types": "^13.1.0", "universal-user-agent": "^6.0.0" } }, "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw=="],
|
|
||||||
|
|
||||||
"@actions/github/@octokit/request-error": ["@octokit/request-error@5.1.1", "", { "dependencies": { "@octokit/types": "^13.1.0", "deprecation": "^2.0.0", "once": "^1.4.0" } }, "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g=="],
|
|
||||||
|
|
||||||
"@ai-sdk/amazon-bedrock/@ai-sdk/provider": ["@ai-sdk/provider@1.1.3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg=="],
|
"@ai-sdk/amazon-bedrock/@ai-sdk/provider": ["@ai-sdk/provider@1.1.3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg=="],
|
||||||
|
|
||||||
"@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@2.2.8", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "nanoid": "^3.3.8", "secure-json-parse": "^2.7.0" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA=="],
|
"@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@2.2.8", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "nanoid": "^3.3.8", "secure-json-parse": "^2.7.0" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA=="],
|
||||||
@@ -3089,8 +3069,6 @@
|
|||||||
|
|
||||||
"@astrojs/mdx/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
|
"@astrojs/mdx/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
|
||||||
|
|
||||||
"@astrojs/sitemap/zod": ["zod@3.25.49", "", {}, "sha512-JMMPMy9ZBk3XFEdbM3iL1brx4NUSejd6xr3ELrrGEfGb355gjhiAWtG3K5o+AViV/3ZfkIrCzXsZn6SbLwTR8Q=="],
|
|
||||||
|
|
||||||
"@astrojs/solid-js/vite": ["vite@6.3.5", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ=="],
|
"@astrojs/solid-js/vite": ["vite@6.3.5", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ=="],
|
||||||
|
|
||||||
"@astrojs/solid-js/vite-plugin-solid": ["vite-plugin-solid@2.11.8", "", { "dependencies": { "@babel/core": "^7.23.3", "@types/babel__core": "^7.20.4", "babel-preset-solid": "^1.8.4", "merge-anything": "^5.1.7", "solid-refresh": "^0.6.3", "vitefu": "^1.0.4" }, "peerDependencies": { "@testing-library/jest-dom": "^5.16.6 || ^5.17.0 || ^6.*", "solid-js": "^1.7.2", "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@testing-library/jest-dom"] }, "sha512-hFrCxBfv3B1BmFqnJF4JOCYpjrmi/zwyeKjcomQ0khh8HFyQ8SbuBWQ7zGojfrz6HUOBFrJBNySDi/JgAHytWg=="],
|
"@astrojs/solid-js/vite-plugin-solid": ["vite-plugin-solid@2.11.8", "", { "dependencies": { "@babel/core": "^7.23.3", "@types/babel__core": "^7.20.4", "babel-preset-solid": "^1.8.4", "merge-anything": "^5.1.7", "solid-refresh": "^0.6.3", "vitefu": "^1.0.4" }, "peerDependencies": { "@testing-library/jest-dom": "^5.16.6 || ^5.17.0 || ^6.*", "solid-js": "^1.7.2", "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@testing-library/jest-dom"] }, "sha512-hFrCxBfv3B1BmFqnJF4JOCYpjrmi/zwyeKjcomQ0khh8HFyQ8SbuBWQ7zGojfrz6HUOBFrJBNySDi/JgAHytWg=="],
|
||||||
@@ -3187,8 +3165,6 @@
|
|||||||
|
|
||||||
"@mdx-js/mdx/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
|
"@mdx-js/mdx/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
|
||||||
|
|
||||||
"@modelcontextprotocol/sdk/zod": ["zod@3.25.49", "", {}, "sha512-JMMPMy9ZBk3XFEdbM3iL1brx4NUSejd6xr3ELrrGEfGb355gjhiAWtG3K5o+AViV/3ZfkIrCzXsZn6SbLwTR8Q=="],
|
|
||||||
|
|
||||||
"@netlify/dev-utils/find-up": ["find-up@7.0.0", "", { "dependencies": { "locate-path": "^7.2.0", "path-exists": "^5.0.0", "unicorn-magic": "^0.1.0" } }, "sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g=="],
|
"@netlify/dev-utils/find-up": ["find-up@7.0.0", "", { "dependencies": { "locate-path": "^7.2.0", "path-exists": "^5.0.0", "unicorn-magic": "^0.1.0" } }, "sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g=="],
|
||||||
|
|
||||||
"@netlify/dev-utils/uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="],
|
"@netlify/dev-utils/uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="],
|
||||||
@@ -3235,6 +3211,8 @@
|
|||||||
|
|
||||||
"@openauthjs/solid/@openauthjs/openauth": ["@openauthjs/openauth@0.4.2", "", { "dependencies": { "@standard-schema/spec": "1.0.0-beta.3", "aws4fetch": "1.0.20", "jose": "5.9.6" }, "peerDependencies": { "arctic": "^2.2.2", "hono": "^4.0.0" } }, "sha512-8+Bia559iffrZXfQ0LWXrVVVriochS88pDtB8indyQ1S+40MQgDBu8aBzKt+fgSrTmoQGCTT+wlOXgbjc9qIcw=="],
|
"@openauthjs/solid/@openauthjs/openauth": ["@openauthjs/openauth@0.4.2", "", { "dependencies": { "@standard-schema/spec": "1.0.0-beta.3", "aws4fetch": "1.0.20", "jose": "5.9.6" }, "peerDependencies": { "arctic": "^2.2.2", "hono": "^4.0.0" } }, "sha512-8+Bia559iffrZXfQ0LWXrVVVriochS88pDtB8indyQ1S+40MQgDBu8aBzKt+fgSrTmoQGCTT+wlOXgbjc9qIcw=="],
|
||||||
|
|
||||||
|
"@opencode-ai/sdk/@hey-api/openapi-ts": ["@hey-api/openapi-ts@0.80.1", "", { "dependencies": { "@hey-api/json-schema-ref-parser": "1.0.6", "ansi-colors": "4.1.3", "c12": "2.0.1", "color-support": "1.1.3", "commander": "13.0.0", "handlebars": "4.7.8", "open": "10.1.2", "semver": "7.7.2" }, "peerDependencies": { "typescript": "^5.5.3" }, "bin": { "openapi-ts": "bin/index.cjs" } }, "sha512-AC478kg36vmmrseLZNFonZ/cmXXmDzW5yWz4PVg1S8ebJsRtVRJ/QU+mtnXfzf9avN2P0pz/AO4WAe4jyFY2gA=="],
|
||||||
|
|
||||||
"@opencode/cloud-web/solid-js": ["solid-js@1.9.5", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "^1.1.0", "seroval-plugins": "^1.1.0" } }, "sha512-ogI3DaFcyn6UhYhrgcyRAMbu/buBJitYQASZz5WzfQVPP10RD2AbCoRZ517psnezrasyCbWzIxZ6kVqet768xw=="],
|
"@opencode/cloud-web/solid-js": ["solid-js@1.9.5", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "^1.1.0", "seroval-plugins": "^1.1.0" } }, "sha512-ogI3DaFcyn6UhYhrgcyRAMbu/buBJitYQASZz5WzfQVPP10RD2AbCoRZ517psnezrasyCbWzIxZ6kVqet768xw=="],
|
||||||
|
|
||||||
"@opentelemetry/instrumentation-grpc/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.27.0", "", {}, "sha512-sAay1RrB+ONOem0OZanAR1ZI/k7yDpnOQSQmTMuGImUQb2y8EbSaCJ94FQluM74xoU03vlb2d2U90hZluL6nQg=="],
|
"@opentelemetry/instrumentation-grpc/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.27.0", "", {}, "sha512-sAay1RrB+ONOem0OZanAR1ZI/k7yDpnOQSQmTMuGImUQb2y8EbSaCJ94FQluM74xoU03vlb2d2U90hZluL6nQg=="],
|
||||||
@@ -3347,8 +3325,6 @@
|
|||||||
|
|
||||||
"@whatwg-node/fetch/urlpattern-polyfill": ["urlpattern-polyfill@10.1.0", "", {}, "sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw=="],
|
"@whatwg-node/fetch/urlpattern-polyfill": ["urlpattern-polyfill@10.1.0", "", {}, "sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw=="],
|
||||||
|
|
||||||
"@whatwg-node/node-fetch/@fastify/busboy": ["@fastify/busboy@3.1.1", "", {}, "sha512-5DGmA8FTdB2XbDeEwc/5ZXBl6UbBAyBOOLlPuBnZ/N1SwdH9Ii+cOX3tBROlDgcTXxjOYnLMVoKk9+FXAw0CJw=="],
|
|
||||||
|
|
||||||
"ai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.1", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-/iP1sKc6UdJgGH98OCly7sWJKv+J9G47PnTjIj40IJMUQKwDrUMyf7zOOfRtPwSuNifYhSoJQ4s1WltI65gJ/g=="],
|
"ai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.1", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-/iP1sKc6UdJgGH98OCly7sWJKv+J9G47PnTjIj40IJMUQKwDrUMyf7zOOfRtPwSuNifYhSoJQ4s1WltI65gJ/g=="],
|
||||||
|
|
||||||
"ansi-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
"ansi-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||||
@@ -3367,8 +3343,6 @@
|
|||||||
|
|
||||||
"astro/vite": ["vite@6.3.5", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ=="],
|
"astro/vite": ["vite@6.3.5", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ=="],
|
||||||
|
|
||||||
"astro/zod": ["zod@3.25.49", "", {}, "sha512-JMMPMy9ZBk3XFEdbM3iL1brx4NUSejd6xr3ELrrGEfGb355gjhiAWtG3K5o+AViV/3ZfkIrCzXsZn6SbLwTR8Q=="],
|
|
||||||
|
|
||||||
"babel-plugin-jsx-dom-expressions/@babel/helper-module-imports": ["@babel/helper-module-imports@7.18.6", "", { "dependencies": { "@babel/types": "^7.18.6" } }, "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA=="],
|
"babel-plugin-jsx-dom-expressions/@babel/helper-module-imports": ["@babel/helper-module-imports@7.18.6", "", { "dependencies": { "@babel/types": "^7.18.6" } }, "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA=="],
|
||||||
|
|
||||||
"bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
|
"bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
|
||||||
@@ -3443,12 +3417,8 @@
|
|||||||
|
|
||||||
"miniflare/sharp": ["sharp@0.33.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="],
|
"miniflare/sharp": ["sharp@0.33.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="],
|
||||||
|
|
||||||
"miniflare/undici": ["undici@7.13.0", "", {}, "sha512-l+zSMssRqrzDcb3fjMkjjLGmuiiK2pMIcV++mJaAc9vhjSGpvM7h43QgP+OAMb1GImHmbPyG2tBXeuyG5iY4gA=="],
|
|
||||||
|
|
||||||
"miniflare/youch": ["youch@4.1.0-beta.10", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@poppinss/dumper": "^0.6.4", "@speed-highlight/core": "^1.2.7", "cookie": "^1.0.2", "youch-core": "^0.3.3" } }, "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ=="],
|
"miniflare/youch": ["youch@4.1.0-beta.10", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@poppinss/dumper": "^0.6.4", "@speed-highlight/core": "^1.2.7", "cookie": "^1.0.2", "youch-core": "^0.3.3" } }, "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ=="],
|
||||||
|
|
||||||
"miniflare/zod": ["zod@3.22.3", "", {}, "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug=="],
|
|
||||||
|
|
||||||
"minipass-flush/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
|
"minipass-flush/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
|
||||||
|
|
||||||
"minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
|
"minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
|
||||||
@@ -3493,8 +3463,6 @@
|
|||||||
|
|
||||||
"opencontrol/hono": ["hono@4.7.4", "", {}, "sha512-Pst8FuGqz3L7tFF+u9Pu70eI0xa5S3LPUmrNd5Jm8nTHze9FxLTK9Kaj5g/k4UcwuJSXTP65SyHOPLrffpcAJg=="],
|
"opencontrol/hono": ["hono@4.7.4", "", {}, "sha512-Pst8FuGqz3L7tFF+u9Pu70eI0xa5S3LPUmrNd5Jm8nTHze9FxLTK9Kaj5g/k4UcwuJSXTP65SyHOPLrffpcAJg=="],
|
||||||
|
|
||||||
"opencontrol/zod": ["zod@3.24.2", "", {}, "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="],
|
|
||||||
|
|
||||||
"opencontrol/zod-to-json-schema": ["zod-to-json-schema@3.24.3", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-HIAfWdYIt1sssHfYZFCXp4rU1w2r8hVVXYIlmoa0r0gABLs5di3RCqPU5DDROogVz1pAdYBaz7HK5n9pSUNs3A=="],
|
"opencontrol/zod-to-json-schema": ["zod-to-json-schema@3.24.3", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-HIAfWdYIt1sssHfYZFCXp4rU1w2r8hVVXYIlmoa0r0gABLs5di3RCqPU5DDROogVz1pAdYBaz7HK5n9pSUNs3A=="],
|
||||||
|
|
||||||
"openid-client/jose": ["jose@4.15.9", "", {}, "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA=="],
|
"openid-client/jose": ["jose@4.15.9", "", {}, "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA=="],
|
||||||
@@ -3615,28 +3583,6 @@
|
|||||||
|
|
||||||
"zip-stream/readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="],
|
"zip-stream/readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="],
|
||||||
|
|
||||||
"@actions/github/@octokit/core/@octokit/auth-token": ["@octokit/auth-token@4.0.0", "", {}, "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA=="],
|
|
||||||
|
|
||||||
"@actions/github/@octokit/core/@octokit/graphql": ["@octokit/graphql@7.1.1", "", { "dependencies": { "@octokit/request": "^8.4.1", "@octokit/types": "^13.0.0", "universal-user-agent": "^6.0.0" } }, "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g=="],
|
|
||||||
|
|
||||||
"@actions/github/@octokit/core/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="],
|
|
||||||
|
|
||||||
"@actions/github/@octokit/core/before-after-hook": ["before-after-hook@2.2.3", "", {}, "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="],
|
|
||||||
|
|
||||||
"@actions/github/@octokit/core/universal-user-agent": ["universal-user-agent@6.0.1", "", {}, "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ=="],
|
|
||||||
|
|
||||||
"@actions/github/@octokit/plugin-paginate-rest/@octokit/types": ["@octokit/types@12.6.0", "", { "dependencies": { "@octokit/openapi-types": "^20.0.0" } }, "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw=="],
|
|
||||||
|
|
||||||
"@actions/github/@octokit/plugin-rest-endpoint-methods/@octokit/types": ["@octokit/types@12.6.0", "", { "dependencies": { "@octokit/openapi-types": "^20.0.0" } }, "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw=="],
|
|
||||||
|
|
||||||
"@actions/github/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@9.0.6", "", { "dependencies": { "@octokit/types": "^13.1.0", "universal-user-agent": "^6.0.0" } }, "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw=="],
|
|
||||||
|
|
||||||
"@actions/github/@octokit/request/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="],
|
|
||||||
|
|
||||||
"@actions/github/@octokit/request/universal-user-agent": ["universal-user-agent@6.0.1", "", {}, "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ=="],
|
|
||||||
|
|
||||||
"@actions/github/@octokit/request-error/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="],
|
|
||||||
|
|
||||||
"@astrojs/mdx/@astrojs/markdown-remark/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.6.1", "", {}, "sha512-l5Pqf6uZu31aG+3Lv8nl/3s4DbUzdlxTWDof4pEpto6GUJNhhCbelVi9dEyurOVyqaelwmS9oSyOWOENSfgo9A=="],
|
"@astrojs/mdx/@astrojs/markdown-remark/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.6.1", "", {}, "sha512-l5Pqf6uZu31aG+3Lv8nl/3s4DbUzdlxTWDof4pEpto6GUJNhhCbelVi9dEyurOVyqaelwmS9oSyOWOENSfgo9A=="],
|
||||||
|
|
||||||
"@astrojs/mdx/@astrojs/markdown-remark/@astrojs/prism": ["@astrojs/prism@3.3.0", "", { "dependencies": { "prismjs": "^1.30.0" } }, "sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ=="],
|
"@astrojs/mdx/@astrojs/markdown-remark/@astrojs/prism": ["@astrojs/prism@3.3.0", "", { "dependencies": { "prismjs": "^1.30.0" } }, "sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ=="],
|
||||||
@@ -3935,8 +3881,6 @@
|
|||||||
|
|
||||||
"opencontrol/@modelcontextprotocol/sdk/pkce-challenge": ["pkce-challenge@4.1.0", "", {}, "sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ=="],
|
"opencontrol/@modelcontextprotocol/sdk/pkce-challenge": ["pkce-challenge@4.1.0", "", {}, "sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ=="],
|
||||||
|
|
||||||
"opencontrol/@modelcontextprotocol/sdk/zod": ["zod@3.25.49", "", {}, "sha512-JMMPMy9ZBk3XFEdbM3iL1brx4NUSejd6xr3ELrrGEfGb355gjhiAWtG3K5o+AViV/3ZfkIrCzXsZn6SbLwTR8Q=="],
|
|
||||||
|
|
||||||
"opencontrol/@modelcontextprotocol/sdk/zod-to-json-schema": ["zod-to-json-schema@3.24.5", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g=="],
|
"opencontrol/@modelcontextprotocol/sdk/zod-to-json-schema": ["zod-to-json-schema@3.24.5", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g=="],
|
||||||
|
|
||||||
"prebuild-install/tar-fs/tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="],
|
"prebuild-install/tar-fs/tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="],
|
||||||
@@ -4021,16 +3965,6 @@
|
|||||||
|
|
||||||
"zip-stream/readable-stream/events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="],
|
"zip-stream/readable-stream/events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="],
|
||||||
|
|
||||||
"@actions/github/@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="],
|
|
||||||
|
|
||||||
"@actions/github/@octokit/plugin-paginate-rest/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@20.0.0", "", {}, "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA=="],
|
|
||||||
|
|
||||||
"@actions/github/@octokit/plugin-rest-endpoint-methods/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@20.0.0", "", {}, "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA=="],
|
|
||||||
|
|
||||||
"@actions/github/@octokit/request-error/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="],
|
|
||||||
|
|
||||||
"@actions/github/@octokit/request/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="],
|
|
||||||
|
|
||||||
"@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
|
"@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
|
||||||
|
|
||||||
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
|
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
|
||||||
|
|||||||
@@ -5,15 +5,17 @@
|
|||||||
"dev": "vinxi dev --host 0.0.0.0",
|
"dev": "vinxi dev --host 0.0.0.0",
|
||||||
"build": "vinxi build",
|
"build": "vinxi build",
|
||||||
"start": "vinxi start",
|
"start": "vinxi start",
|
||||||
"version": "0.5.5"
|
"version": "0.5.18"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ibm/plex": "6.4.1",
|
"@ibm/plex": "6.4.1",
|
||||||
|
"@openauthjs/openauth": "0.0.0-20250322224806",
|
||||||
"@solidjs/meta": "^0.29.4",
|
"@solidjs/meta": "^0.29.4",
|
||||||
"@solidjs/router": "^0.15.0",
|
"@solidjs/router": "^0.15.0",
|
||||||
"@solidjs/start": "^1.1.0",
|
"@solidjs/start": "^1.1.0",
|
||||||
"solid-js": "^1.9.5",
|
"solid-js": "^1.9.5",
|
||||||
"vinxi": "^0.5.7"
|
"vinxi": "^0.5.7",
|
||||||
|
"@opencode/cloud-core": "workspace:*"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=22"
|
"node": ">=22"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { MetaProvider, Title } from "@solidjs/meta";
|
import { MetaProvider, Title } from "@solidjs/meta";
|
||||||
import { Router } from "@solidjs/router";
|
import { Router } from "@solidjs/router";
|
||||||
import { FileRoutes } from "@solidjs/start/router";
|
import { FileRoutes } from "@solidjs/start/router";
|
||||||
import { Suspense } from "solid-js";
|
import { ErrorBoundary, Suspense } from "solid-js";
|
||||||
import "@ibm/plex/css/ibm-plex.css";
|
import "@ibm/plex/css/ibm-plex.css";
|
||||||
import "./app.css";
|
import "./app.css";
|
||||||
|
|
||||||
@@ -11,7 +11,9 @@ export default function App() {
|
|||||||
root={props => (
|
root={props => (
|
||||||
<MetaProvider>
|
<MetaProvider>
|
||||||
<Title>SolidStart - Basic</Title>
|
<Title>SolidStart - Basic</Title>
|
||||||
<Suspense>{props.children}</Suspense>
|
<ErrorBoundary fallback={<div>Something went wrong</div>}>
|
||||||
|
<Suspense>{props.children}</Suspense>
|
||||||
|
</ErrorBoundary>
|
||||||
</MetaProvider>
|
</MetaProvider>
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
|
||||||
|
|
||||||
|
import { useSession } from "vinxi/http"
|
||||||
|
import { createClient } from "@openauthjs/openauth/client"
|
||||||
|
import { getRequestEvent } from "solid-js/web"
|
||||||
|
import { and, Database, eq, inArray } from "@opencode/cloud-core/drizzle/index.js"
|
||||||
|
import { WorkspaceTable } from "@opencode/cloud-core/schema/workspace.sql.js"
|
||||||
|
import { UserTable } from "@opencode/cloud-core/schema/user.sql.js"
|
||||||
|
import { query, redirect } from "@solidjs/router"
|
||||||
|
import { AccountTable } from "@opencode/cloud-core/schema/account.sql.js"
|
||||||
|
import { Actor } from "@opencode/cloud-core/actor.js"
|
||||||
|
|
||||||
|
export async function withActor<T>(fn: () => T) {
|
||||||
|
const actor = await getActor()
|
||||||
|
return Actor.provide(actor.type, actor.properties, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getActor = query(async (): Promise<Actor.Info> => {
|
||||||
|
"use server"
|
||||||
|
const evt = getRequestEvent()
|
||||||
|
const url = new URL(evt!.request.headers.get("referer") ?? evt!.request.url)
|
||||||
|
const auth = await useAuthSession()
|
||||||
|
const [workspaceHint] = url.pathname.split("/").filter((x) => x.length > 0)
|
||||||
|
if (!workspaceHint) {
|
||||||
|
if (auth.data.current) {
|
||||||
|
const current = auth.data.account[auth.data.current]
|
||||||
|
return {
|
||||||
|
type: "account",
|
||||||
|
properties: {
|
||||||
|
email: current.email,
|
||||||
|
accountID: current.id,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Object.keys(auth.data.account).length > 0) {
|
||||||
|
const current = Object.values(auth.data.account)[0]
|
||||||
|
await auth.update(val => ({
|
||||||
|
...val,
|
||||||
|
current: current.id,
|
||||||
|
}))
|
||||||
|
return {
|
||||||
|
type: "account",
|
||||||
|
properties: {
|
||||||
|
email: current.email,
|
||||||
|
accountID: current.id,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: "public",
|
||||||
|
properties: {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const accounts = Object.keys(auth.data.account)
|
||||||
|
const result = await Database.transaction(async (tx) => {
|
||||||
|
return await tx.select({
|
||||||
|
user: UserTable
|
||||||
|
})
|
||||||
|
.from(AccountTable)
|
||||||
|
.innerJoin(UserTable, and(eq(UserTable.email, AccountTable.email)))
|
||||||
|
.innerJoin(WorkspaceTable, eq(WorkspaceTable.id, UserTable.workspaceID))
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
inArray(AccountTable.id, accounts),
|
||||||
|
eq(WorkspaceTable.id, workspaceHint),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.limit(1)
|
||||||
|
.execute()
|
||||||
|
.then((x) => x[0])
|
||||||
|
})
|
||||||
|
if (result) {
|
||||||
|
return {
|
||||||
|
type: "user",
|
||||||
|
properties: {
|
||||||
|
userID: result.user.id,
|
||||||
|
workspaceID: result.user.workspaceID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw redirect("/auth/authorize")
|
||||||
|
}, "actor")
|
||||||
|
|
||||||
|
|
||||||
|
export const AuthClient = createClient({
|
||||||
|
clientID: "app",
|
||||||
|
issuer: import.meta.env.VITE_AUTH_URL,
|
||||||
|
})
|
||||||
|
|
||||||
|
export interface AuthSession {
|
||||||
|
account: Record<string, {
|
||||||
|
id: string
|
||||||
|
email: string
|
||||||
|
}>
|
||||||
|
current?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAuthSession() {
|
||||||
|
|
||||||
|
return useSession<AuthSession>({
|
||||||
|
password: "0".repeat(32),
|
||||||
|
name: "auth"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function AuthProvider() {
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { createAsync, query } from "@solidjs/router"
|
||||||
|
import { getActor, withActor } from "~/context/auth"
|
||||||
|
|
||||||
|
const getPosts = query(async () => {
|
||||||
|
"use server"
|
||||||
|
return withActor(() => {
|
||||||
|
return "ok"
|
||||||
|
})
|
||||||
|
}, "posts")
|
||||||
|
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
const actor = createAsync(async () => getActor())
|
||||||
|
return <div>{JSON.stringify(actor())}</div>
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import type { APIEvent } from "@solidjs/start/server"
|
||||||
|
import { AuthClient } from "~/context/auth"
|
||||||
|
|
||||||
|
export async function GET(input: APIEvent) {
|
||||||
|
const result = await AuthClient.authorize(new URL("./callback", input.request.url).toString(), "code")
|
||||||
|
return Response.redirect(result.url, 302)
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import type { APIEvent } from "@solidjs/start/server"
|
||||||
|
import { AuthClient, useAuthSession } from "~/context/auth"
|
||||||
|
|
||||||
|
export async function GET(input: APIEvent) {
|
||||||
|
const url = new URL(input.request.url)
|
||||||
|
const code = url.searchParams.get("code")
|
||||||
|
if (!code) throw new Error("No code found")
|
||||||
|
const result = await AuthClient.exchange(code, `${url.origin}${url.pathname}`)
|
||||||
|
if (result.err) {
|
||||||
|
throw new Error(result.err.message)
|
||||||
|
}
|
||||||
|
const decoded = AuthClient.decode(result.tokens.access, {} as any)
|
||||||
|
if (decoded.err) throw new Error(decoded.err.message)
|
||||||
|
const session = await useAuthSession()
|
||||||
|
const id = decoded.subject.properties.accountID
|
||||||
|
await session.update((value) => {
|
||||||
|
return {
|
||||||
|
...value,
|
||||||
|
account: {
|
||||||
|
[id]: {
|
||||||
|
id,
|
||||||
|
email: decoded.subject.properties.email,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
current: id,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
result,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,9 @@ import IMG_SPLASH from "../asset/screenshot-splash.webp"
|
|||||||
import IMG_VSCODE from "../asset/screenshot-vscode.webp"
|
import IMG_VSCODE from "../asset/screenshot-vscode.webp"
|
||||||
import IMG_GITHUB from "../asset/screenshot-github.webp"
|
import IMG_GITHUB from "../asset/screenshot-github.webp"
|
||||||
import { IconCopy, IconCheck } from "../component/icon"
|
import { IconCopy, IconCheck } from "../component/icon"
|
||||||
|
import { createAsync, query, redirect, RouteDefinition } from "@solidjs/router"
|
||||||
|
import { getActor, withActor } from "~/context/auth"
|
||||||
|
import { Account } from "@opencode/cloud-core/account.js"
|
||||||
|
|
||||||
function CopyStatus() {
|
function CopyStatus() {
|
||||||
return (
|
return (
|
||||||
@@ -16,7 +19,22 @@ function CopyStatus() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isLoggedIn = query(async () => {
|
||||||
|
"use server"
|
||||||
|
const actor = await getActor()
|
||||||
|
if (actor.type === "account") {
|
||||||
|
const workspaces = await withActor(() => Account.workspaces())
|
||||||
|
throw redirect("/" + workspaces[0].id)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}, "isLoggedIn")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
|
createAsync(() => isLoggedIn(), {
|
||||||
|
deferStream: true,
|
||||||
|
})
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const commands = document.querySelectorAll("[data-copy]")
|
const commands = document.querySelectorAll("[data-copy]")
|
||||||
for (const button of commands) {
|
for (const button of commands) {
|
||||||
@@ -51,7 +69,7 @@ export default function Home() {
|
|||||||
<a href="/docs">Get Started</a>
|
<a href="/docs">Get Started</a>
|
||||||
</div>
|
</div>
|
||||||
<div data-slot="right">
|
<div data-slot="right">
|
||||||
<button data-copy data-slot="command" data-command="curl -fsSL https://opencode.ai/install | bash">
|
<button data-copy data-slot="command">
|
||||||
<span>
|
<span>
|
||||||
<span>curl -fsSL </span>
|
<span>curl -fsSL </span>
|
||||||
<span data-slot="protocol">https://</span>
|
<span data-slot="protocol">https://</span>
|
||||||
|
|||||||
Vendored
+9
@@ -0,0 +1,9 @@
|
|||||||
|
/* This file is auto-generated by SST. Do not edit. */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
/* deno-fmt-ignore-file */
|
||||||
|
|
||||||
|
/// <reference path="../../sst-env.d.ts" />
|
||||||
|
|
||||||
|
import "sst"
|
||||||
|
export {}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"name": "@opencode/cloud-core",
|
"name": "@opencode/cloud-core",
|
||||||
"version": "0.5.5",
|
"version": "0.5.18",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ export namespace Actor {
|
|||||||
properties: {
|
properties: {
|
||||||
userID: string
|
userID: string
|
||||||
workspaceID: string
|
workspaceID: string
|
||||||
email: string
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Resource } from "sst"
|
|||||||
export * from "drizzle-orm"
|
export * from "drizzle-orm"
|
||||||
import postgres from "postgres"
|
import postgres from "postgres"
|
||||||
|
|
||||||
function createClient() {
|
const createClient = memo(() => {
|
||||||
const client = postgres({
|
const client = postgres({
|
||||||
idle_timeout: 30000,
|
idle_timeout: 30000,
|
||||||
connect_timeout: 30000,
|
connect_timeout: 30000,
|
||||||
@@ -19,12 +19,13 @@ function createClient() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
return drizzle(client, {})
|
return drizzle(client, {})
|
||||||
}
|
})
|
||||||
|
|
||||||
import { PgTransaction, type PgTransactionConfig } from "drizzle-orm/pg-core"
|
import { PgTransaction, type PgTransactionConfig } from "drizzle-orm/pg-core"
|
||||||
import type { ExtractTablesWithRelations } from "drizzle-orm"
|
import type { ExtractTablesWithRelations } from "drizzle-orm"
|
||||||
import type { PostgresJsQueryResultHKT } from "drizzle-orm/postgres-js"
|
import type { PostgresJsQueryResultHKT } from "drizzle-orm/postgres-js"
|
||||||
import { Context } from "../context"
|
import { Context } from "../context"
|
||||||
|
import { memo } from "../util/memo"
|
||||||
|
|
||||||
export namespace Database {
|
export namespace Database {
|
||||||
export type Transaction = PgTransaction<
|
export type Transaction = PgTransaction<
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
export function memo<T>(fn: () => T) {
|
||||||
|
let value: T | undefined
|
||||||
|
let loaded = false
|
||||||
|
|
||||||
|
return (): T => {
|
||||||
|
if (loaded) return value as T
|
||||||
|
loaded = true
|
||||||
|
value = fn()
|
||||||
|
return value as T
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode/cloud-function",
|
"name": "@opencode/cloud-function",
|
||||||
"version": "0.5.5",
|
"version": "0.5.18",
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ import { Resource } from "sst"
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
import { issuer } from "@openauthjs/openauth"
|
import { issuer } from "@openauthjs/openauth"
|
||||||
import { createSubjects } from "@openauthjs/openauth/subject"
|
import { createSubjects } from "@openauthjs/openauth/subject"
|
||||||
import { CodeProvider } from "@openauthjs/openauth/provider/code"
|
|
||||||
import { GithubProvider } from "@openauthjs/openauth/provider/github"
|
import { GithubProvider } from "@openauthjs/openauth/provider/github"
|
||||||
import { GoogleOidcProvider } from "@openauthjs/openauth/provider/google"
|
import { GoogleOidcProvider } from "@openauthjs/openauth/provider/google"
|
||||||
import { CloudflareStorage } from "@openauthjs/openauth/storage/cloudflare"
|
import { CloudflareStorage } from "@openauthjs/openauth/storage/cloudflare"
|
||||||
import { Account } from "@opencode/cloud-core/account.js"
|
import { Account } from "@opencode/cloud-core/account.js"
|
||||||
|
import { Workspace } from "@opencode/cloud-core/workspace.js"
|
||||||
|
import { Actor } from "@opencode/cloud-core/actor.js"
|
||||||
|
|
||||||
type Env = {
|
type Env = {
|
||||||
AuthStorage: KVNamespace
|
AuthStorage: KVNamespace
|
||||||
@@ -117,6 +118,12 @@ export default {
|
|||||||
email: email!,
|
email: email!,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
await Actor.provide("account", { accountID, email }, async () => {
|
||||||
|
const workspaces = await Account.workspaces()
|
||||||
|
if (workspaces.length === 0) {
|
||||||
|
await Workspace.create()
|
||||||
|
}
|
||||||
|
})
|
||||||
return ctx.subject("account", accountID, { accountID, email })
|
return ctx.subject("account", accountID, { accountID, email })
|
||||||
},
|
},
|
||||||
}).fetch(request, env, ctx)
|
}).fetch(request, env, ctx)
|
||||||
|
|||||||
Vendored
-4
@@ -14,10 +14,6 @@ declare module "sst" {
|
|||||||
"type": "sst.sst.Linkable"
|
"type": "sst.sst.Linkable"
|
||||||
"value": string
|
"value": string
|
||||||
}
|
}
|
||||||
"Console": {
|
|
||||||
"type": "sst.cloudflare.StaticSite"
|
|
||||||
"url": string
|
|
||||||
}
|
|
||||||
"DATABASE_PASSWORD": {
|
"DATABASE_PASSWORD": {
|
||||||
"type": "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
"value": string
|
"value": string
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode/cloud-web",
|
"name": "@opencode/cloud-web",
|
||||||
"version": "0.5.5",
|
"version": "0.5.18",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@
|
|||||||
"@actions/github": "6.0.1",
|
"@actions/github": "6.0.1",
|
||||||
"@octokit/graphql": "9.0.1",
|
"@octokit/graphql": "9.0.1",
|
||||||
"@octokit/rest": "22.0.0",
|
"@octokit/rest": "22.0.0",
|
||||||
"@opencode-ai/sdk": "^0.5.4",
|
"@opencode-ai/sdk": "0.5.4",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
|
|||||||
Vendored
+9
@@ -0,0 +1,9 @@
|
|||||||
|
/* This file is auto-generated by SST. Do not edit. */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
/* deno-fmt-ignore-file */
|
||||||
|
|
||||||
|
/// <reference path="../sst-env.d.ts" />
|
||||||
|
|
||||||
|
import "sst"
|
||||||
|
export {}
|
||||||
+2
-2
@@ -25,8 +25,8 @@ export const api = new sst.cloudflare.Worker("Api", {
|
|||||||
])
|
])
|
||||||
args.migrations = {
|
args.migrations = {
|
||||||
// Note: when releasing the next tag, make sure all stages use tag v2
|
// Note: when releasing the next tag, make sure all stages use tag v2
|
||||||
oldTag: $app.stage === "production" ? "" : "v1",
|
oldTag: $app.stage === "production" || $app.stage === "thdxr" ? "" : "v1",
|
||||||
newTag: $app.stage === "production" ? "" : "v1",
|
newTag: $app.stage === "production" || $app.stage === "thdxr" ? "" : "v1",
|
||||||
//newSqliteClasses: ["SyncServer"],
|
//newSqliteClasses: ["SyncServer"],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
+14
-1
@@ -10,7 +10,7 @@ const DATABASE_USERNAME = new sst.Secret("DATABASE_USERNAME")
|
|||||||
const DATABASE_PASSWORD = new sst.Secret("DATABASE_PASSWORD")
|
const DATABASE_PASSWORD = new sst.Secret("DATABASE_PASSWORD")
|
||||||
export const database = new sst.Linkable("Database", {
|
export const database = new sst.Linkable("Database", {
|
||||||
properties: {
|
properties: {
|
||||||
host: "aws-us-east-2-1.pg.psdb.cloud",
|
host: `aws-us-east-2-${$app.stage === "thdxr" ? "2" : "1"}.pg.psdb.cloud`,
|
||||||
database: "postgres",
|
database: "postgres",
|
||||||
username: DATABASE_USERNAME.value,
|
username: DATABASE_USERNAME.value,
|
||||||
password: DATABASE_PASSWORD.value,
|
password: DATABASE_PASSWORD.value,
|
||||||
@@ -106,6 +106,7 @@ export const gateway = new sst.cloudflare.Worker("GatewayApi", {
|
|||||||
// CONSOLE
|
// CONSOLE
|
||||||
////////////////
|
////////////////
|
||||||
|
|
||||||
|
/*
|
||||||
export const console = new sst.cloudflare.x.StaticSite("Console", {
|
export const console = new sst.cloudflare.x.StaticSite("Console", {
|
||||||
domain: `console.${domain}`,
|
domain: `console.${domain}`,
|
||||||
path: "cloud/web",
|
path: "cloud/web",
|
||||||
@@ -119,3 +120,15 @@ export const console = new sst.cloudflare.x.StaticSite("Console", {
|
|||||||
VITE_AUTH_URL: auth.url.apply((url) => url!),
|
VITE_AUTH_URL: auth.url.apply((url) => url!),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
*/
|
||||||
|
|
||||||
|
new sst.x.DevCommand("Solid", {
|
||||||
|
link: [database],
|
||||||
|
dev: {
|
||||||
|
directory: "cloud/app",
|
||||||
|
command: "bun dev",
|
||||||
|
},
|
||||||
|
environment: {
|
||||||
|
VITE_AUTH_URL: auth.url.apply((url) => url!),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"keep": {
|
||||||
|
"days": true,
|
||||||
|
"amount": 14
|
||||||
|
},
|
||||||
|
"auditLog": "/Users/adam/code/opencode/dev/logs/.496fc674ed58d31f8b883da41cc2adb4564aad58-audit.json",
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"date": 1755891797740,
|
||||||
|
"name": "/Users/adam/code/opencode/dev/logs/mcp-puppeteer-2025-08-22.log",
|
||||||
|
"hash": "dd9b1f2e98b661ba2f56b91dd9afbdb25e50adbdd52ed1b0eef1d2045235d17c"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"hashType": "sha256"
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-08-22 14:43:17.765"}
|
||||||
|
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-08-22 14:43:17.766"}
|
||||||
|
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-08-22 14:46:45.539"}
|
||||||
|
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-08-22 14:46:45.540"}
|
||||||
|
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-08-22 14:53:08.159"}
|
||||||
|
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-08-22 14:53:08.160"}
|
||||||
+1
-4
@@ -1,10 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://opencode.ai/config.json",
|
"$schema": "https://opencode.ai/config.json",
|
||||||
|
|
||||||
"mcp": {
|
"mcp": {
|
||||||
"context7": {
|
|
||||||
"type": "remote",
|
|
||||||
"url": "https://mcp.context7.com/sse"
|
|
||||||
},
|
|
||||||
"weather": {
|
"weather": {
|
||||||
"type": "local",
|
"type": "local",
|
||||||
"command": ["opencode", "x", "@h1deya/mcp-server-weather"]
|
"command": ["opencode", "x", "@h1deya/mcp-server-weather"]
|
||||||
|
|||||||
@@ -51,5 +51,8 @@
|
|||||||
"tree-sitter-bash",
|
"tree-sitter-bash",
|
||||||
"web-tree-sitter"
|
"web-tree-sitter"
|
||||||
],
|
],
|
||||||
|
"overrides": {
|
||||||
|
"zod": "3.25.76"
|
||||||
|
},
|
||||||
"patchedDependencies": {}
|
"patchedDependencies": {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode/function",
|
"name": "@opencode/function",
|
||||||
"version": "0.5.5",
|
"version": "0.5.18",
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
Vendored
-4
@@ -14,10 +14,6 @@ declare module "sst" {
|
|||||||
"type": "sst.sst.Linkable"
|
"type": "sst.sst.Linkable"
|
||||||
"value": string
|
"value": string
|
||||||
}
|
}
|
||||||
"Console": {
|
|
||||||
"type": "sst.cloudflare.StaticSite"
|
|
||||||
"url": string
|
|
||||||
}
|
|
||||||
"DATABASE_PASSWORD": {
|
"DATABASE_PASSWORD": {
|
||||||
"type": "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
"value": string
|
"value": string
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"version": "0.5.5",
|
"version": "0.5.18",
|
||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|||||||
@@ -97,7 +97,6 @@ if (!snapshot) {
|
|||||||
const macX64Sha = await $`sha256sum ./dist/opencode-darwin-x64.zip | cut -d' ' -f1`.text().then((x) => x.trim())
|
const macX64Sha = await $`sha256sum ./dist/opencode-darwin-x64.zip | cut -d' ' -f1`.text().then((x) => x.trim())
|
||||||
const macArm64Sha = await $`sha256sum ./dist/opencode-darwin-arm64.zip | cut -d' ' -f1`.text().then((x) => x.trim())
|
const macArm64Sha = await $`sha256sum ./dist/opencode-darwin-arm64.zip | cut -d' ' -f1`.text().then((x) => x.trim())
|
||||||
|
|
||||||
/* AUR package - commented out as AUR is down
|
|
||||||
const pkgbuild = [
|
const pkgbuild = [
|
||||||
"# Maintainer: dax",
|
"# Maintainer: dax",
|
||||||
"# Maintainer: adam",
|
"# Maintainer: adam",
|
||||||
@@ -136,7 +135,6 @@ if (!snapshot) {
|
|||||||
await $`cd ./dist/aur-${pkg} && git commit -m "Update to v${version}"`
|
await $`cd ./dist/aur-${pkg} && git commit -m "Update to v${version}"`
|
||||||
if (!dry) await $`cd ./dist/aur-${pkg} && git push`
|
if (!dry) await $`cd ./dist/aur-${pkg} && git push`
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
// Homebrew formula
|
// Homebrew formula
|
||||||
const homebrewFormula = [
|
const homebrewFormula = [
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Config } from "../src/config/config"
|
|||||||
import { zodToJsonSchema } from "zod-to-json-schema"
|
import { zodToJsonSchema } from "zod-to-json-schema"
|
||||||
|
|
||||||
const file = process.argv[2]
|
const file = process.argv[2]
|
||||||
|
console.log(file)
|
||||||
|
|
||||||
const result = zodToJsonSchema(Config.Info, {
|
const result = zodToJsonSchema(Config.Info, {
|
||||||
/**
|
/**
|
||||||
@@ -31,5 +32,13 @@ const result = zodToJsonSchema(Config.Info, {
|
|||||||
|
|
||||||
return jsonSchema
|
return jsonSchema
|
||||||
},
|
},
|
||||||
})
|
}) as Record<string, unknown> & {
|
||||||
|
allowComments?: boolean
|
||||||
|
allowTrailingCommas?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
// used for json lsps since config supports jsonc
|
||||||
|
result.allowComments = true
|
||||||
|
result.allowTrailingCommas = true
|
||||||
|
|
||||||
await Bun.write(file, JSON.stringify(result, null, 2))
|
await Bun.write(file, JSON.stringify(result, null, 2))
|
||||||
|
|||||||
@@ -48,6 +48,15 @@ export namespace Agent {
|
|||||||
}
|
}
|
||||||
const agentPermission = mergeAgentPermissions(defaultPermission, cfg.permission ?? {})
|
const agentPermission = mergeAgentPermissions(defaultPermission, cfg.permission ?? {})
|
||||||
|
|
||||||
|
const planPermission = mergeAgentPermissions(
|
||||||
|
{
|
||||||
|
edit: "ask",
|
||||||
|
bash: "ask",
|
||||||
|
webfetch: "allow",
|
||||||
|
},
|
||||||
|
cfg.permission ?? {},
|
||||||
|
)
|
||||||
|
|
||||||
const result: Record<string, Info> = {
|
const result: Record<string, Info> = {
|
||||||
general: {
|
general: {
|
||||||
name: "general",
|
name: "general",
|
||||||
@@ -74,11 +83,8 @@ export namespace Agent {
|
|||||||
plan: {
|
plan: {
|
||||||
name: "plan",
|
name: "plan",
|
||||||
options: {},
|
options: {},
|
||||||
permission: agentPermission,
|
permission: planPermission,
|
||||||
tools: {
|
tools: {
|
||||||
write: false,
|
|
||||||
edit: false,
|
|
||||||
patch: false,
|
|
||||||
...defaultTools,
|
...defaultTools,
|
||||||
},
|
},
|
||||||
mode: "primary",
|
mode: "primary",
|
||||||
@@ -100,7 +106,7 @@ export namespace Agent {
|
|||||||
tools: {},
|
tools: {},
|
||||||
builtIn: false,
|
builtIn: false,
|
||||||
}
|
}
|
||||||
const { model, prompt, tools, description, temperature, top_p, mode, permission, ...extra } = value
|
const { name, model, prompt, tools, description, temperature, top_p, mode, permission, ...extra } = value
|
||||||
item.options = {
|
item.options = {
|
||||||
...item.options,
|
...item.options,
|
||||||
...extra,
|
...extra,
|
||||||
@@ -120,6 +126,8 @@ export namespace Agent {
|
|||||||
if (temperature != undefined) item.temperature = temperature
|
if (temperature != undefined) item.temperature = temperature
|
||||||
if (top_p != undefined) item.topP = top_p
|
if (top_p != undefined) item.topP = top_p
|
||||||
if (mode) item.mode = mode
|
if (mode) item.mode = mode
|
||||||
|
// just here for consistency & to prevent it from being added as an option
|
||||||
|
if (name) item.name = name
|
||||||
|
|
||||||
if (permission ?? cfg.permission) {
|
if (permission ?? cfg.permission) {
|
||||||
item.permission = mergeAgentPermissions(cfg.permission ?? {}, permission ?? {})
|
item.permission = mergeAgentPermissions(cfg.permission ?? {}, permission ?? {})
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import { Snapshot } from "../snapshot"
|
|||||||
|
|
||||||
export async function bootstrap<T>(input: App.Input, cb: (app: App.Info) => Promise<T>) {
|
export async function bootstrap<T>(input: App.Input, cb: (app: App.Info) => Promise<T>) {
|
||||||
return App.provide(input, async (app) => {
|
return App.provide(input, async (app) => {
|
||||||
|
await Plugin.init()
|
||||||
Share.init()
|
Share.init()
|
||||||
Format.init()
|
Format.init()
|
||||||
Plugin.init()
|
|
||||||
ConfigHooks.init()
|
ConfigHooks.init()
|
||||||
LSP.init()
|
LSP.init()
|
||||||
Snapshot.init()
|
Snapshot.init()
|
||||||
|
|||||||
@@ -46,7 +46,10 @@ const AgentCreateCommand = cmd({
|
|||||||
const spinner = prompts.spinner()
|
const spinner = prompts.spinner()
|
||||||
|
|
||||||
spinner.start("Generating agent configuration...")
|
spinner.start("Generating agent configuration...")
|
||||||
const generated = await Agent.generate({ description: query })
|
const generated = await Agent.generate({ description: query }).catch((error) => {
|
||||||
|
spinner.stop(`LLM failed to generate agent: ${error.message}`, 1)
|
||||||
|
throw new UI.CancelledError()
|
||||||
|
})
|
||||||
spinner.stop(`Agent ${generated.identifier} generated`)
|
spinner.stop(`Agent ${generated.identifier} generated`)
|
||||||
|
|
||||||
const availableTools = [
|
const availableTools = [
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { Auth } from "../../auth"
|
import { Auth } from "../../auth"
|
||||||
import { cmd } from "./cmd"
|
import { cmd } from "./cmd"
|
||||||
import * as prompts from "@clack/prompts"
|
import * as prompts from "@clack/prompts"
|
||||||
import open from "open"
|
|
||||||
import { UI } from "../ui"
|
import { UI } from "../ui"
|
||||||
import { ModelsDev } from "../../provider/models"
|
import { ModelsDev } from "../../provider/models"
|
||||||
import { map, pipe, sortBy, values } from "remeda"
|
import { map, pipe, sortBy, values } from "remeda"
|
||||||
@@ -159,9 +158,6 @@ export const AuthLoginCommand = cmd({
|
|||||||
const authorize = await method.authorize()
|
const authorize = await method.authorize()
|
||||||
|
|
||||||
if (authorize.url) {
|
if (authorize.url) {
|
||||||
try {
|
|
||||||
await open(authorize.url)
|
|
||||||
} catch (e) {}
|
|
||||||
prompts.log.info("Go to: " + authorize.url)
|
prompts.log.info("Go to: " + authorize.url)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,12 +172,20 @@ export const AuthLoginCommand = cmd({
|
|||||||
spinner.stop("Failed to authorize", 1)
|
spinner.stop("Failed to authorize", 1)
|
||||||
}
|
}
|
||||||
if (result.type === "success") {
|
if (result.type === "success") {
|
||||||
await Auth.set(provider, {
|
if ("refresh" in result) {
|
||||||
type: "oauth",
|
await Auth.set(provider, {
|
||||||
refresh: result.refresh,
|
type: "oauth",
|
||||||
access: result.access,
|
refresh: result.refresh,
|
||||||
expires: result.expires,
|
access: result.access,
|
||||||
})
|
expires: result.expires,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if ("key" in result) {
|
||||||
|
await Auth.set(provider, {
|
||||||
|
type: "api",
|
||||||
|
key: result.key,
|
||||||
|
})
|
||||||
|
}
|
||||||
spinner.stop("Login successful")
|
spinner.stop("Login successful")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -197,12 +201,20 @@ export const AuthLoginCommand = cmd({
|
|||||||
prompts.log.error("Failed to authorize")
|
prompts.log.error("Failed to authorize")
|
||||||
}
|
}
|
||||||
if (result.type === "success") {
|
if (result.type === "success") {
|
||||||
await Auth.set(provider, {
|
if ("refresh" in result) {
|
||||||
type: "oauth",
|
await Auth.set(provider, {
|
||||||
refresh: result.refresh,
|
type: "oauth",
|
||||||
access: result.access,
|
refresh: result.refresh,
|
||||||
expires: result.expires,
|
access: result.access,
|
||||||
})
|
expires: result.expires,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if ("key" in result) {
|
||||||
|
await Auth.set(provider, {
|
||||||
|
type: "api",
|
||||||
|
key: result.key,
|
||||||
|
})
|
||||||
|
}
|
||||||
prompts.log.success("Login successful")
|
prompts.log.success("Login successful")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,11 +67,17 @@ export const RunCommand = cmd({
|
|||||||
await bootstrap({ cwd: process.cwd() }, async () => {
|
await bootstrap({ cwd: process.cwd() }, async () => {
|
||||||
const session = await (async () => {
|
const session = await (async () => {
|
||||||
if (args.continue) {
|
if (args.continue) {
|
||||||
const list = Session.list()
|
const it = Session.list()
|
||||||
const first = await list.next()
|
try {
|
||||||
await list.return()
|
for await (const s of it) {
|
||||||
if (first.done) return
|
if (s.parentID === undefined) {
|
||||||
return first.value
|
return s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
} finally {
|
||||||
|
await it.return()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.session) return Session.get(args.session)
|
if (args.session) return Session.get(args.session)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export const ServeCommand = cmd({
|
|||||||
alias: ["p"],
|
alias: ["p"],
|
||||||
type: "number",
|
type: "number",
|
||||||
describe: "port to listen on",
|
describe: "port to listen on",
|
||||||
default: 4096,
|
default: 0,
|
||||||
})
|
})
|
||||||
.option("hostname", {
|
.option("hostname", {
|
||||||
alias: ["h"],
|
alias: ["h"],
|
||||||
|
|||||||
@@ -82,11 +82,17 @@ export const TuiCommand = cmd({
|
|||||||
const result = await bootstrap({ cwd }, async (app) => {
|
const result = await bootstrap({ cwd }, async (app) => {
|
||||||
const sessionID = await (async () => {
|
const sessionID = await (async () => {
|
||||||
if (args.continue) {
|
if (args.continue) {
|
||||||
const list = Session.list()
|
const it = Session.list()
|
||||||
const first = await list.next()
|
try {
|
||||||
await list.return()
|
for await (const s of it) {
|
||||||
if (first.done) return
|
if (s.parentID === undefined) {
|
||||||
return first.value.id
|
return s.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
} finally {
|
||||||
|
await it.return()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (args.session) {
|
if (args.session) {
|
||||||
return args.session
|
return args.session
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export const UpgradeCommand = {
|
|||||||
spinner.start("Upgrading...")
|
spinner.start("Upgrading...")
|
||||||
const err = await Installation.upgrade(method, target).catch((err) => err)
|
const err = await Installation.upgrade(method, target).catch((err) => err)
|
||||||
if (err) {
|
if (err) {
|
||||||
spinner.stop("Upgrade failed")
|
spinner.stop("Upgrade failed", 1)
|
||||||
if (err instanceof Installation.UpgradeFailedError) prompts.log.error(err.data.stderr)
|
if (err instanceof Installation.UpgradeFailedError) prompts.log.error(err.data.stderr)
|
||||||
else if (err instanceof Error) prompts.log.error(err.message)
|
else if (err instanceof Error) prompts.log.error(err.message)
|
||||||
prompts.outro("Done")
|
prompts.outro("Done")
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import z from "zod"
|
||||||
|
import { App } from "../app/app"
|
||||||
|
import { Config } from "../config/config"
|
||||||
|
|
||||||
|
export namespace Command {
|
||||||
|
export const Info = z
|
||||||
|
.object({
|
||||||
|
name: z.string(),
|
||||||
|
description: z.string().optional(),
|
||||||
|
agent: z.string().optional(),
|
||||||
|
model: z.string().optional(),
|
||||||
|
template: z.string(),
|
||||||
|
})
|
||||||
|
.openapi({
|
||||||
|
ref: "Command",
|
||||||
|
})
|
||||||
|
export type Info = z.infer<typeof Info>
|
||||||
|
|
||||||
|
const state = App.state("command", async () => {
|
||||||
|
const cfg = await Config.get()
|
||||||
|
|
||||||
|
const result: Record<string, Info> = {}
|
||||||
|
|
||||||
|
for (const [name, command] of Object.entries(cfg.command ?? {})) {
|
||||||
|
result[name] = {
|
||||||
|
name,
|
||||||
|
agent: command.agent,
|
||||||
|
model: command.model,
|
||||||
|
description: command.description,
|
||||||
|
template: command.template,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
|
||||||
|
export async function get(name: string) {
|
||||||
|
return state().then((x) => x[name])
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function list() {
|
||||||
|
return state().then((x) => Object.values(x))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -64,7 +64,7 @@ export namespace Config {
|
|||||||
if (agentFolderPath.includes("/")) {
|
if (agentFolderPath.includes("/")) {
|
||||||
const relativePath = agentFolderPath.replace(".md", "")
|
const relativePath = agentFolderPath.replace(".md", "")
|
||||||
const pathParts = relativePath.split("/")
|
const pathParts = relativePath.split("/")
|
||||||
agentName = pathParts.slice(0, -1).join("/").toUpperCase() + "/" + pathParts[pathParts.length - 1].toUpperCase()
|
agentName = pathParts.slice(0, -1).join("/") + "/" + pathParts[pathParts.length - 1]
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
@@ -107,6 +107,32 @@ export namespace Config {
|
|||||||
}
|
}
|
||||||
throw new InvalidError({ path: item }, { cause: parsed.error })
|
throw new InvalidError({ path: item }, { cause: parsed.error })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load command markdown files
|
||||||
|
result.command = result.command || {}
|
||||||
|
const markdownCommands = [
|
||||||
|
...(await Filesystem.globUp("command/*.md", Global.Path.config, Global.Path.config)),
|
||||||
|
...(await Filesystem.globUp(".opencode/command/*.md", app.path.cwd, app.path.root)),
|
||||||
|
]
|
||||||
|
for (const item of markdownCommands) {
|
||||||
|
const content = await Bun.file(item).text()
|
||||||
|
const md = matter(content)
|
||||||
|
if (!md.data) continue
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
name: path.basename(item, ".md"),
|
||||||
|
...md.data,
|
||||||
|
template: md.content.trim(),
|
||||||
|
}
|
||||||
|
const parsed = Command.safeParse(config)
|
||||||
|
if (parsed.success) {
|
||||||
|
result.command = mergeDeep(result.command, {
|
||||||
|
[config.name]: parsed.data,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
throw new InvalidError({ path: item }, { cause: parsed.error })
|
||||||
|
}
|
||||||
// Migrate deprecated mode field to agent field
|
// Migrate deprecated mode field to agent field
|
||||||
for (const [name, mode] of Object.entries(result.mode)) {
|
for (const [name, mode] of Object.entries(result.mode)) {
|
||||||
result.agent = mergeDeep(result.agent ?? {}, {
|
result.agent = mergeDeep(result.agent ?? {}, {
|
||||||
@@ -192,6 +218,14 @@ export namespace Config {
|
|||||||
export const Permission = z.union([z.literal("ask"), z.literal("allow"), z.literal("deny")])
|
export const Permission = z.union([z.literal("ask"), z.literal("allow"), z.literal("deny")])
|
||||||
export type Permission = z.infer<typeof Permission>
|
export type Permission = z.infer<typeof Permission>
|
||||||
|
|
||||||
|
export const Command = z.object({
|
||||||
|
template: z.string(),
|
||||||
|
description: z.string().optional(),
|
||||||
|
agent: z.string().optional(),
|
||||||
|
model: z.string().optional(),
|
||||||
|
})
|
||||||
|
export type Command = z.infer<typeof Command>
|
||||||
|
|
||||||
export const Agent = z
|
export const Agent = z
|
||||||
.object({
|
.object({
|
||||||
model: z.string().optional(),
|
model: z.string().optional(),
|
||||||
@@ -229,6 +263,7 @@ export namespace Config {
|
|||||||
session_export: z.string().optional().default("<leader>x").describe("Export session to editor"),
|
session_export: z.string().optional().default("<leader>x").describe("Export session to editor"),
|
||||||
session_new: z.string().optional().default("<leader>n").describe("Create a new session"),
|
session_new: z.string().optional().default("<leader>n").describe("Create a new session"),
|
||||||
session_list: z.string().optional().default("<leader>l").describe("List all sessions"),
|
session_list: z.string().optional().default("<leader>l").describe("List all sessions"),
|
||||||
|
session_timeline: z.string().optional().default("<leader>g").describe("Show session timeline"),
|
||||||
session_share: z.string().optional().default("<leader>s").describe("Share current session"),
|
session_share: z.string().optional().default("<leader>s").describe("Share current session"),
|
||||||
session_unshare: z.string().optional().default("none").describe("Unshare current session"),
|
session_unshare: z.string().optional().default("none").describe("Unshare current session"),
|
||||||
session_interrupt: z.string().optional().default("esc").describe("Interrupt current session"),
|
session_interrupt: z.string().optional().default("esc").describe("Interrupt current session"),
|
||||||
@@ -304,6 +339,7 @@ export namespace Config {
|
|||||||
theme: z.string().optional().describe("Theme name to use for the interface"),
|
theme: z.string().optional().describe("Theme name to use for the interface"),
|
||||||
keybinds: Keybinds.optional().describe("Custom keybind configurations"),
|
keybinds: Keybinds.optional().describe("Custom keybind configurations"),
|
||||||
tui: TUI.optional().describe("TUI specific settings"),
|
tui: TUI.optional().describe("TUI specific settings"),
|
||||||
|
command: z.record(z.string(), Command).optional(),
|
||||||
plugin: z.string().array().optional(),
|
plugin: z.string().array().optional(),
|
||||||
snapshot: z.boolean().optional(),
|
snapshot: z.boolean().optional(),
|
||||||
share: z
|
share: z
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export namespace Flag {
|
|||||||
export const OPENCODE_DISABLE_AUTOUPDATE = truthy("OPENCODE_DISABLE_AUTOUPDATE")
|
export const OPENCODE_DISABLE_AUTOUPDATE = truthy("OPENCODE_DISABLE_AUTOUPDATE")
|
||||||
export const OPENCODE_PERMISSION = process.env["OPENCODE_PERMISSION"]
|
export const OPENCODE_PERMISSION = process.env["OPENCODE_PERMISSION"]
|
||||||
export const OPENCODE_DISABLE_DEFAULT_PLUGINS = truthy("OPENCODE_DISABLE_DEFAULT_PLUGINS")
|
export const OPENCODE_DISABLE_DEFAULT_PLUGINS = truthy("OPENCODE_DISABLE_DEFAULT_PLUGINS")
|
||||||
|
export const OPENCODE_DISABLE_LSP_DOWNLOAD = truthy("OPENCODE_DISABLE_LSP_DOWNLOAD")
|
||||||
|
|
||||||
function truthy(key: string) {
|
function truthy(key: string) {
|
||||||
const value = process.env[key]?.toLowerCase()
|
const value = process.env[key]?.toLowerCase()
|
||||||
|
|||||||
@@ -68,19 +68,29 @@ export namespace Format {
|
|||||||
|
|
||||||
for (const item of await getFormatter(ext)) {
|
for (const item of await getFormatter(ext)) {
|
||||||
log.info("running", { command: item.command })
|
log.info("running", { command: item.command })
|
||||||
const proc = Bun.spawn({
|
try {
|
||||||
cmd: item.command.map((x) => x.replace("$FILE", file)),
|
const proc = Bun.spawn({
|
||||||
cwd: App.info().path.cwd,
|
cmd: item.command.map((x) => x.replace("$FILE", file)),
|
||||||
env: { ...process.env, ...item.environment },
|
cwd: App.info().path.cwd,
|
||||||
stdout: "ignore",
|
env: { ...process.env, ...item.environment },
|
||||||
stderr: "ignore",
|
stdout: "ignore",
|
||||||
})
|
stderr: "ignore",
|
||||||
const exit = await proc.exited
|
})
|
||||||
if (exit !== 0)
|
const exit = await proc.exited
|
||||||
|
if (exit !== 0)
|
||||||
|
log.error("failed", {
|
||||||
|
command: item.command,
|
||||||
|
...item.environment,
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
log.error("failed", {
|
log.error("failed", {
|
||||||
|
error,
|
||||||
command: item.command,
|
command: item.command,
|
||||||
...item.environment,
|
...item.environment,
|
||||||
})
|
})
|
||||||
|
// re-raising
|
||||||
|
throw error
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,13 +28,18 @@ await Promise.all([
|
|||||||
fs.mkdir(Global.Path.bin, { recursive: true }),
|
fs.mkdir(Global.Path.bin, { recursive: true }),
|
||||||
])
|
])
|
||||||
|
|
||||||
const CACHE_VERSION = "8"
|
const CACHE_VERSION = "9"
|
||||||
|
|
||||||
const version = await Bun.file(path.join(Global.Path.cache, "version"))
|
const version = await Bun.file(path.join(Global.Path.cache, "version"))
|
||||||
.text()
|
.text()
|
||||||
.catch(() => "0")
|
.catch(() => "0")
|
||||||
|
|
||||||
if (version !== CACHE_VERSION) {
|
if (version !== CACHE_VERSION) {
|
||||||
await fs.rm(Global.Path.cache, { recursive: true, force: true })
|
try {
|
||||||
|
const contents = await fs.readdir(Global.Path.cache)
|
||||||
|
await Promise.all(
|
||||||
|
contents.map((item) => fs.rm(path.join(Global.Path.cache, item), { recursive: true, force: true })),
|
||||||
|
)
|
||||||
|
} catch (e) {}
|
||||||
await Bun.file(path.join(Global.Path.cache, "version")).write(CACHE_VERSION)
|
await Bun.file(path.join(Global.Path.cache, "version")).write(CACHE_VERSION)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,7 +126,11 @@ export namespace LSP {
|
|||||||
result.push(match)
|
result.push(match)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const handle = await server.spawn(App.info(), root)
|
const handle = await server.spawn(App.info(), root).catch((err) => {
|
||||||
|
s.broken.add(root + server.id)
|
||||||
|
log.error(`Failed to spawn LSP server ${server.id}`, { error: err })
|
||||||
|
return undefined
|
||||||
|
})
|
||||||
if (!handle) continue
|
if (!handle) continue
|
||||||
const client = await LSPClient.create({
|
const client = await LSPClient.create({
|
||||||
serverID: server.id,
|
serverID: server.id,
|
||||||
@@ -135,7 +139,7 @@ export namespace LSP {
|
|||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
s.broken.add(root + server.id)
|
s.broken.add(root + server.id)
|
||||||
handle.process.kill()
|
handle.process.kill()
|
||||||
log.error("", { error: err })
|
log.error(`Failed to initialize LSP client ${server.id}`, { error: err })
|
||||||
})
|
})
|
||||||
if (!client) continue
|
if (!client) continue
|
||||||
s.clients.push(client)
|
s.clients.push(client)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { BunProc } from "../bun"
|
|||||||
import { $ } from "bun"
|
import { $ } from "bun"
|
||||||
import fs from "fs/promises"
|
import fs from "fs/promises"
|
||||||
import { Filesystem } from "../util/filesystem"
|
import { Filesystem } from "../util/filesystem"
|
||||||
|
import { Flag } from "../flag/flag"
|
||||||
|
|
||||||
export namespace LSPServer {
|
export namespace LSPServer {
|
||||||
const log = Log.create({ service: "lsp.server" })
|
const log = Log.create({ service: "lsp.server" })
|
||||||
@@ -95,6 +96,7 @@ export namespace LSPServer {
|
|||||||
"vue-language-server.js",
|
"vue-language-server.js",
|
||||||
)
|
)
|
||||||
if (!(await Bun.file(js).exists())) {
|
if (!(await Bun.file(js).exists())) {
|
||||||
|
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||||
await Bun.spawn([BunProc.which(), "install", "@vue/language-server"], {
|
await Bun.spawn([BunProc.which(), "install", "@vue/language-server"], {
|
||||||
cwd: Global.Path.bin,
|
cwd: Global.Path.bin,
|
||||||
env: {
|
env: {
|
||||||
@@ -148,6 +150,7 @@ export namespace LSPServer {
|
|||||||
if (!eslint) return
|
if (!eslint) return
|
||||||
const serverPath = path.join(Global.Path.bin, "vscode-eslint", "server", "out", "eslintServer.js")
|
const serverPath = path.join(Global.Path.bin, "vscode-eslint", "server", "out", "eslintServer.js")
|
||||||
if (!(await Bun.file(serverPath).exists())) {
|
if (!(await Bun.file(serverPath).exists())) {
|
||||||
|
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||||
log.info("downloading and building VS Code ESLint server")
|
log.info("downloading and building VS Code ESLint server")
|
||||||
const response = await fetch("https://github.com/microsoft/vscode-eslint/archive/refs/heads/main.zip")
|
const response = await fetch("https://github.com/microsoft/vscode-eslint/archive/refs/heads/main.zip")
|
||||||
if (!response.ok) return
|
if (!response.ok) return
|
||||||
@@ -200,6 +203,8 @@ export namespace LSPServer {
|
|||||||
})
|
})
|
||||||
if (!bin) {
|
if (!bin) {
|
||||||
if (!Bun.which("go")) return
|
if (!Bun.which("go")) return
|
||||||
|
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||||
|
|
||||||
log.info("installing gopls")
|
log.info("installing gopls")
|
||||||
const proc = Bun.spawn({
|
const proc = Bun.spawn({
|
||||||
cmd: ["go", "install", "golang.org/x/tools/gopls@latest"],
|
cmd: ["go", "install", "golang.org/x/tools/gopls@latest"],
|
||||||
@@ -241,6 +246,7 @@ export namespace LSPServer {
|
|||||||
log.info("Ruby not found, please install Ruby first")
|
log.info("Ruby not found, please install Ruby first")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||||
log.info("installing ruby-lsp")
|
log.info("installing ruby-lsp")
|
||||||
const proc = Bun.spawn({
|
const proc = Bun.spawn({
|
||||||
cmd: ["gem", "install", "ruby-lsp", "--bindir", Global.Path.bin],
|
cmd: ["gem", "install", "ruby-lsp", "--bindir", Global.Path.bin],
|
||||||
@@ -276,6 +282,7 @@ export namespace LSPServer {
|
|||||||
if (!binary) {
|
if (!binary) {
|
||||||
const js = path.join(Global.Path.bin, "node_modules", "pyright", "dist", "pyright-langserver.js")
|
const js = path.join(Global.Path.bin, "node_modules", "pyright", "dist", "pyright-langserver.js")
|
||||||
if (!(await Bun.file(js).exists())) {
|
if (!(await Bun.file(js).exists())) {
|
||||||
|
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||||
await Bun.spawn([BunProc.which(), "install", "pyright"], {
|
await Bun.spawn([BunProc.which(), "install", "pyright"], {
|
||||||
cwd: Global.Path.bin,
|
cwd: Global.Path.bin,
|
||||||
env: {
|
env: {
|
||||||
@@ -323,6 +330,7 @@ export namespace LSPServer {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||||
log.info("downloading elixir-ls from GitHub releases")
|
log.info("downloading elixir-ls from GitHub releases")
|
||||||
|
|
||||||
const response = await fetch("https://github.com/elixir-lsp/elixir-ls/archive/refs/heads/master.zip")
|
const response = await fetch("https://github.com/elixir-lsp/elixir-ls/archive/refs/heads/master.zip")
|
||||||
@@ -372,6 +380,7 @@ export namespace LSPServer {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||||
log.info("downloading zls from GitHub releases")
|
log.info("downloading zls from GitHub releases")
|
||||||
|
|
||||||
const releaseResponse = await fetch("https://api.github.com/repos/zigtools/zls/releases/latest")
|
const releaseResponse = await fetch("https://api.github.com/repos/zigtools/zls/releases/latest")
|
||||||
@@ -475,6 +484,7 @@ export namespace LSPServer {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||||
log.info("installing csharp-ls via dotnet tool")
|
log.info("installing csharp-ls via dotnet tool")
|
||||||
const proc = Bun.spawn({
|
const proc = Bun.spawn({
|
||||||
cmd: ["dotnet", "tool", "install", "csharp-ls", "--tool-path", Global.Path.bin],
|
cmd: ["dotnet", "tool", "install", "csharp-ls", "--tool-path", Global.Path.bin],
|
||||||
@@ -527,6 +537,7 @@ export namespace LSPServer {
|
|||||||
PATH: process.env["PATH"] + ":" + Global.Path.bin,
|
PATH: process.env["PATH"] + ":" + Global.Path.bin,
|
||||||
})
|
})
|
||||||
if (!bin) {
|
if (!bin) {
|
||||||
|
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||||
log.info("downloading clangd from GitHub releases")
|
log.info("downloading clangd from GitHub releases")
|
||||||
|
|
||||||
const releaseResponse = await fetch("https://api.github.com/repos/clangd/clangd/releases/latest")
|
const releaseResponse = await fetch("https://api.github.com/repos/clangd/clangd/releases/latest")
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ export namespace Plugin {
|
|||||||
}
|
}
|
||||||
const plugins = [...(config.plugin ?? [])]
|
const plugins = [...(config.plugin ?? [])]
|
||||||
if (!Flag.OPENCODE_DISABLE_DEFAULT_PLUGINS) {
|
if (!Flag.OPENCODE_DISABLE_DEFAULT_PLUGINS) {
|
||||||
plugins.push("opencode-copilot-auth")
|
plugins.push("opencode-copilot-auth@0.0.2")
|
||||||
plugins.push("opencode-anthropic-auth")
|
plugins.push("opencode-anthropic-auth@0.0.2")
|
||||||
}
|
}
|
||||||
for (let plugin of plugins) {
|
for (let plugin of plugins) {
|
||||||
log.info("loading plugin", { path: plugin })
|
log.info("loading plugin", { path: plugin })
|
||||||
@@ -68,7 +68,12 @@ export namespace Plugin {
|
|||||||
return state().then((x) => x.hooks)
|
return state().then((x) => x.hooks)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function init() {
|
export async function init() {
|
||||||
|
const hooks = await state().then((x) => x.hooks)
|
||||||
|
const config = await Config.get()
|
||||||
|
for (const hook of hooks) {
|
||||||
|
await hook.config?.(config)
|
||||||
|
}
|
||||||
Bus.subscribeAll(async (input) => {
|
Bus.subscribeAll(async (input) => {
|
||||||
const hooks = await state().then((x) => x.hooks)
|
const hooks = await state().then((x) => x.hooks)
|
||||||
for (const hook of hooks) {
|
for (const hook of hooks) {
|
||||||
|
|||||||
@@ -36,6 +36,12 @@ export namespace Provider {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async opencode(input) {
|
||||||
|
return {
|
||||||
|
autoload: Object.keys(input.models).length > 0,
|
||||||
|
options: {},
|
||||||
|
}
|
||||||
|
},
|
||||||
openai: async () => {
|
openai: async () => {
|
||||||
return {
|
return {
|
||||||
autoload: false,
|
autoload: false,
|
||||||
@@ -276,6 +282,15 @@ export namespace Provider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const [providerID, provider] of Object.entries(providers)) {
|
for (const [providerID, provider] of Object.entries(providers)) {
|
||||||
|
// Filter out blacklisted models
|
||||||
|
const filteredModels = Object.fromEntries(
|
||||||
|
Object.entries(provider.info.models).filter(
|
||||||
|
([modelID]) =>
|
||||||
|
modelID !== "gpt-5-chat-latest" && !(providerID === "openrouter" && modelID === "openai/gpt-5-chat"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
provider.info.models = filteredModels
|
||||||
|
|
||||||
if (Object.keys(provider.info.models).length === 0) {
|
if (Object.keys(provider.info.models).length === 0) {
|
||||||
delete providers[providerID]
|
delete providers[providerID]
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import { Permission } from "../permission"
|
|||||||
import { lazy } from "../util/lazy"
|
import { lazy } from "../util/lazy"
|
||||||
import { Agent } from "../agent/agent"
|
import { Agent } from "../agent/agent"
|
||||||
import { Auth } from "../auth"
|
import { Auth } from "../auth"
|
||||||
|
import { Command } from "../command"
|
||||||
|
|
||||||
const ERRORS = {
|
const ERRORS = {
|
||||||
400: {
|
400: {
|
||||||
@@ -102,7 +103,7 @@ export namespace Server {
|
|||||||
200: {
|
200: {
|
||||||
description: "Event stream",
|
description: "Event stream",
|
||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"text/event-stream": {
|
||||||
schema: resolver(
|
schema: resolver(
|
||||||
Bus.payloads().openapi({
|
Bus.payloads().openapi({
|
||||||
ref: "Event",
|
ref: "Event",
|
||||||
@@ -611,7 +612,12 @@ export namespace Server {
|
|||||||
description: "Created message",
|
description: "Created message",
|
||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
schema: resolver(MessageV2.Assistant),
|
schema: resolver(
|
||||||
|
z.object({
|
||||||
|
info: MessageV2.Assistant,
|
||||||
|
parts: MessageV2.Part.array(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -631,6 +637,41 @@ export namespace Server {
|
|||||||
return c.json(msg)
|
return c.json(msg)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
.post(
|
||||||
|
"/session/:id/command",
|
||||||
|
describeRoute({
|
||||||
|
description: "Send a new command to a session",
|
||||||
|
operationId: "session.command",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Created message",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: resolver(
|
||||||
|
z.object({
|
||||||
|
info: MessageV2.Assistant,
|
||||||
|
parts: MessageV2.Part.array(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
zValidator(
|
||||||
|
"param",
|
||||||
|
z.object({
|
||||||
|
id: z.string().openapi({ description: "Session ID" }),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
zValidator("json", Session.CommandInput.omit({ sessionID: true })),
|
||||||
|
async (c) => {
|
||||||
|
const sessionID = c.req.valid("param").id
|
||||||
|
const body = c.req.valid("json")
|
||||||
|
const msg = await Session.command({ ...body, sessionID })
|
||||||
|
return c.json(msg)
|
||||||
|
},
|
||||||
|
)
|
||||||
.post(
|
.post(
|
||||||
"/session/:id/shell",
|
"/session/:id/shell",
|
||||||
describeRoute({
|
describeRoute({
|
||||||
@@ -653,7 +694,7 @@ export namespace Server {
|
|||||||
id: z.string().openapi({ description: "Session ID" }),
|
id: z.string().openapi({ description: "Session ID" }),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
zValidator("json", Session.CommandInput.omit({ sessionID: true })),
|
zValidator("json", Session.ShellInput.omit({ sessionID: true })),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
const sessionID = c.req.valid("param").id
|
const sessionID = c.req.valid("param").id
|
||||||
const body = c.req.valid("json")
|
const body = c.req.valid("json")
|
||||||
@@ -750,6 +791,27 @@ export namespace Server {
|
|||||||
return c.json(true)
|
return c.json(true)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
.get(
|
||||||
|
"/command",
|
||||||
|
describeRoute({
|
||||||
|
description: "List all commands",
|
||||||
|
operationId: "command.list",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "List of commands",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: resolver(Command.Info.array()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (c) => {
|
||||||
|
const commands = await Command.list()
|
||||||
|
return c.json(commands)
|
||||||
|
},
|
||||||
|
)
|
||||||
.get(
|
.get(
|
||||||
"/config/providers",
|
"/config/providers",
|
||||||
describeRoute({
|
describeRoute({
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
|
|
||||||
import PROMPT_INITIALIZE from "../session/prompt/initialize.txt"
|
import PROMPT_INITIALIZE from "../session/prompt/initialize.txt"
|
||||||
import PROMPT_PLAN from "../session/prompt/plan.txt"
|
import PROMPT_PLAN from "../session/prompt/plan.txt"
|
||||||
|
import BUILD_SWITCH from "../session/prompt/build-switch.txt"
|
||||||
|
|
||||||
import { App } from "../app/app"
|
import { App } from "../app/app"
|
||||||
import { Bus } from "../bus"
|
import { Bus } from "../bus"
|
||||||
@@ -46,6 +47,8 @@ import { Permission } from "../permission"
|
|||||||
import { Wildcard } from "../util/wildcard"
|
import { Wildcard } from "../util/wildcard"
|
||||||
import { ulid } from "ulid"
|
import { ulid } from "ulid"
|
||||||
import { defer } from "../util/defer"
|
import { defer } from "../util/defer"
|
||||||
|
import { Command } from "../command"
|
||||||
|
import { $ } from "bun"
|
||||||
|
|
||||||
export namespace Session {
|
export namespace Session {
|
||||||
const log = Log.create({ service: "session" })
|
const log = Log.create({ service: "session" })
|
||||||
@@ -726,6 +729,18 @@ export namespace Session {
|
|||||||
synthetic: true,
|
synthetic: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const lastAssistantMsg = msgs.filter((x) => x.info.role === "assistant").at(-1)?.info as MessageV2.Assistant
|
||||||
|
if (lastAssistantMsg?.mode === "plan" && agent.name === "build") {
|
||||||
|
msgs.at(-1)?.parts.push({
|
||||||
|
id: Identifier.ascending("part"),
|
||||||
|
messageID: userMsg.id,
|
||||||
|
sessionID: input.sessionID,
|
||||||
|
type: "text",
|
||||||
|
text: BUILD_SWITCH,
|
||||||
|
synthetic: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
let system = SystemPrompt.header(input.providerID)
|
let system = SystemPrompt.header(input.providerID)
|
||||||
system.push(
|
system.push(
|
||||||
...(() => {
|
...(() => {
|
||||||
@@ -945,6 +960,13 @@ export namespace Session {
|
|||||||
toolName: "invalid",
|
toolName: "invalid",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
headers:
|
||||||
|
input.providerID === "opencode"
|
||||||
|
? {
|
||||||
|
"x-opencode-session": input.sessionID,
|
||||||
|
"x-opencode-request": userMsg.id,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
maxRetries: 3,
|
maxRetries: 3,
|
||||||
activeTools: Object.keys(tools).filter((x) => x !== "invalid"),
|
activeTools: Object.keys(tools).filter((x) => x !== "invalid"),
|
||||||
maxOutputTokens: outputLimit,
|
maxOutputTokens: outputLimit,
|
||||||
@@ -1005,13 +1027,13 @@ export namespace Session {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CommandInput = z.object({
|
export const ShellInput = z.object({
|
||||||
sessionID: Identifier.schema("session"),
|
sessionID: Identifier.schema("session"),
|
||||||
agent: z.string(),
|
agent: z.string(),
|
||||||
command: z.string(),
|
command: z.string(),
|
||||||
})
|
})
|
||||||
export type CommandInput = z.infer<typeof CommandInput>
|
export type ShellInput = z.infer<typeof ShellInput>
|
||||||
export async function shell(input: CommandInput) {
|
export async function shell(input: ShellInput) {
|
||||||
using abort = lock(input.sessionID)
|
using abort = lock(input.sessionID)
|
||||||
const msg: MessageV2.Assistant = {
|
const msg: MessageV2.Assistant = {
|
||||||
id: Identifier.ascending("message"),
|
id: Identifier.ascending("message"),
|
||||||
@@ -1135,6 +1157,72 @@ export namespace Session {
|
|||||||
return { info: msg, parts: [part] }
|
return { info: msg, parts: [part] }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const CommandInput = z.object({
|
||||||
|
messageID: Identifier.schema("message").optional(),
|
||||||
|
sessionID: Identifier.schema("session"),
|
||||||
|
agent: z.string().optional(),
|
||||||
|
model: z.string().optional(),
|
||||||
|
arguments: z.string(),
|
||||||
|
command: z.string(),
|
||||||
|
})
|
||||||
|
export type CommandInput = z.infer<typeof CommandInput>
|
||||||
|
const bashRegex = /!`([^`]+)`/g
|
||||||
|
const fileRegex = /@([^\s]+)/g
|
||||||
|
|
||||||
|
export async function command(input: CommandInput) {
|
||||||
|
const command = await Command.get(input.command)
|
||||||
|
const agent = input.agent ?? command.agent ?? "build"
|
||||||
|
const model =
|
||||||
|
input.model ??
|
||||||
|
command.model ??
|
||||||
|
(await Agent.get(agent).then((x) => (x.model ? `${x.model.providerID}/${x.model.modelID}` : undefined))) ??
|
||||||
|
(await Provider.defaultModel().then((x) => `${x.providerID}/${x.modelID}`))
|
||||||
|
let template = command.template.replace("$ARGUMENTS", input.arguments)
|
||||||
|
|
||||||
|
const bash = Array.from(template.matchAll(bashRegex))
|
||||||
|
if (bash.length > 0) {
|
||||||
|
const results = await Promise.all(
|
||||||
|
bash.map(async ([, cmd]) => {
|
||||||
|
try {
|
||||||
|
return await $`${{ raw: cmd }}`.nothrow().text()
|
||||||
|
} catch (error) {
|
||||||
|
return `Error executing command: ${error instanceof Error ? error.message : String(error)}`
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
let index = 0
|
||||||
|
template = template.replace(bashRegex, () => results[index++])
|
||||||
|
}
|
||||||
|
|
||||||
|
const parts = [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: template,
|
||||||
|
},
|
||||||
|
] as ChatInput["parts"]
|
||||||
|
|
||||||
|
const matches = template.matchAll(fileRegex)
|
||||||
|
const app = App.info()
|
||||||
|
|
||||||
|
for (const match of matches) {
|
||||||
|
const file = path.join(app.path.cwd, match[1])
|
||||||
|
parts.push({
|
||||||
|
type: "file",
|
||||||
|
url: `file://${file}`,
|
||||||
|
filename: match[1],
|
||||||
|
mime: "text/plain",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return chat({
|
||||||
|
sessionID: input.sessionID,
|
||||||
|
messageID: input.messageID,
|
||||||
|
...Provider.parseModel(model!),
|
||||||
|
agent,
|
||||||
|
parts,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function createProcessor(assistantMsg: MessageV2.Assistant, model: ModelsDev.Model) {
|
function createProcessor(assistantMsg: MessageV2.Assistant, model: ModelsDev.Model) {
|
||||||
const toolcalls: Record<string, MessageV2.ToolPart> = {}
|
const toolcalls: Record<string, MessageV2.ToolPart> = {}
|
||||||
let snapshot: string | undefined
|
let snapshot: string | undefined
|
||||||
@@ -1592,7 +1680,7 @@ export namespace Session {
|
|||||||
const tokens = {
|
const tokens = {
|
||||||
input: usage.inputTokens ?? 0,
|
input: usage.inputTokens ?? 0,
|
||||||
output: usage.outputTokens ?? 0,
|
output: usage.outputTokens ?? 0,
|
||||||
reasoning: 0,
|
reasoning: usage?.reasoningTokens ?? 0,
|
||||||
cache: {
|
cache: {
|
||||||
write: (metadata?.["anthropic"]?.["cacheCreationInputTokens"] ??
|
write: (metadata?.["anthropic"]?.["cacheCreationInputTokens"] ??
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
Your operational mode has changed from plan to build. You are no longer in read-only mode. You are permitted to make file changes as necessary and utilize your arsenal of tools as needed.
|
||||||
@@ -86,7 +86,7 @@ export namespace ToolRegistry {
|
|||||||
result["webfetch"] = false
|
result["webfetch"] = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modelID.includes("qwen")) {
|
if (modelID.toLowerCase().includes("qwen")) {
|
||||||
result["todowrite"] = false
|
result["todowrite"] = false
|
||||||
result["todoread"] = false
|
result["todoread"] = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,7 +101,12 @@ export namespace Log {
|
|||||||
...extra,
|
...extra,
|
||||||
})
|
})
|
||||||
.filter(([_, value]) => value !== undefined && value !== null)
|
.filter(([_, value]) => value !== undefined && value !== null)
|
||||||
.map(([key, value]) => `${key}=${typeof value === "object" ? JSON.stringify(value) : value}`)
|
.map(([key, value]) => {
|
||||||
|
const prefix = `${key}=`
|
||||||
|
if (value instanceof Error) return prefix + value.message
|
||||||
|
if (typeof value === "object") return prefix + JSON.stringify(value)
|
||||||
|
return prefix + value
|
||||||
|
})
|
||||||
.join(" ")
|
.join(" ")
|
||||||
const next = new Date()
|
const next = new Date()
|
||||||
const diff = next.getTime() - last
|
const diff = next.getTime() - last
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"name": "@opencode-ai/plugin",
|
"name": "@opencode-ai/plugin",
|
||||||
"version": "0.5.5",
|
"version": "0.5.18",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
@@ -19,8 +19,8 @@
|
|||||||
"@opencode-ai/sdk": "workspace:*"
|
"@opencode-ai/sdk": "workspace:*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "catalog:",
|
"@hey-api/openapi-ts": "0.81.0",
|
||||||
"@hey-api/openapi-ts": "0.80.1",
|
"@tsconfig/node22": "catalog:",
|
||||||
"@tsconfig/node22": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import type {
|
|||||||
UserMessage,
|
UserMessage,
|
||||||
Part,
|
Part,
|
||||||
Auth,
|
Auth,
|
||||||
|
Config,
|
||||||
} from "@opencode-ai/sdk"
|
} from "@opencode-ai/sdk"
|
||||||
import type { BunShell } from "./shell"
|
import type { BunShell } from "./shell"
|
||||||
|
|
||||||
@@ -20,6 +21,7 @@ export type Plugin = (input: PluginInput) => Promise<Hooks>
|
|||||||
|
|
||||||
export interface Hooks {
|
export interface Hooks {
|
||||||
event?: (input: { event: Event }) => Promise<void>
|
event?: (input: { event: Event }) => Promise<void>
|
||||||
|
config?: (input: Config) => Promise<void>
|
||||||
auth?: {
|
auth?: {
|
||||||
provider: string
|
provider: string
|
||||||
loader?: (auth: () => Promise<Auth>, provider: Provider) => Promise<Record<string, any>>
|
loader?: (auth: () => Promise<Auth>, provider: Provider) => Promise<Record<string, any>>
|
||||||
@@ -32,12 +34,16 @@ export interface Hooks {
|
|||||||
| {
|
| {
|
||||||
method: "auto"
|
method: "auto"
|
||||||
callback(): Promise<
|
callback(): Promise<
|
||||||
| {
|
| ({
|
||||||
type: "success"
|
type: "success"
|
||||||
refresh: string
|
} & (
|
||||||
access: string
|
| {
|
||||||
expires: number
|
refresh: string
|
||||||
}
|
access: string
|
||||||
|
expires: number
|
||||||
|
}
|
||||||
|
| { key: string }
|
||||||
|
))
|
||||||
| {
|
| {
|
||||||
type: "failed"
|
type: "failed"
|
||||||
}
|
}
|
||||||
@@ -46,12 +52,16 @@ export interface Hooks {
|
|||||||
| {
|
| {
|
||||||
method: "code"
|
method: "code"
|
||||||
callback(code: string): Promise<
|
callback(code: string): Promise<
|
||||||
| {
|
| ({
|
||||||
type: "success"
|
type: "success"
|
||||||
refresh: string
|
} & (
|
||||||
access: string
|
| {
|
||||||
expires: number
|
refresh: string
|
||||||
}
|
access: string
|
||||||
|
expires: number
|
||||||
|
}
|
||||||
|
| { key: string }
|
||||||
|
))
|
||||||
| {
|
| {
|
||||||
type: "failed"
|
type: "failed"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,11 @@
|
|||||||
"module": "preserve",
|
"module": "preserve",
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
|
"lib": [
|
||||||
|
"es2022",
|
||||||
|
"dom",
|
||||||
|
"dom.iterable"
|
||||||
|
],
|
||||||
"customConditions": [
|
"customConditions": [
|
||||||
"development"
|
"development"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
configured_endpoints: 39
|
configured_endpoints: 41
|
||||||
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-e4b6496e5f2c68fa8b3ea1b88e40041eaf5ce2652001344df80bf130675d1766.yml
|
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-6d8e9dfd438cac63fc7d689ea29adfff81ff8880c2d8e1e10fc36f375a721594.yml
|
||||||
openapi_spec_hash: df474311dc9e4a89cd483bd8b8d971d8
|
openapi_spec_hash: 7ac6028dd5957c67a98d91e790863c80
|
||||||
config_hash: eab3723c4c2232a6ba1821151259d6da
|
config_hash: fb625e876313a9f8f31532348fa91f59
|
||||||
|
|||||||
+14
-1
@@ -70,6 +70,16 @@ Methods:
|
|||||||
|
|
||||||
- <code title="get /config">client.Config.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ConfigService.Get">Get</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Config">Config</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
- <code title="get /config">client.Config.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ConfigService.Get">Get</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Config">Config</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||||
|
|
||||||
|
# Command
|
||||||
|
|
||||||
|
Response Types:
|
||||||
|
|
||||||
|
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Command">Command</a>
|
||||||
|
|
||||||
|
Methods:
|
||||||
|
|
||||||
|
- <code title="get /command">client.Command.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#CommandService.List">List</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Command">Command</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||||
|
|
||||||
# Session
|
# Session
|
||||||
|
|
||||||
Params Types:
|
Params Types:
|
||||||
@@ -105,6 +115,8 @@ Response Types:
|
|||||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ToolStatePending">ToolStatePending</a>
|
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ToolStatePending">ToolStatePending</a>
|
||||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ToolStateRunning">ToolStateRunning</a>
|
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ToolStateRunning">ToolStateRunning</a>
|
||||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#UserMessage">UserMessage</a>
|
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#UserMessage">UserMessage</a>
|
||||||
|
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionChatResponse">SessionChatResponse</a>
|
||||||
|
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionCommandResponse">SessionCommandResponse</a>
|
||||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionMessageResponse">SessionMessageResponse</a>
|
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionMessageResponse">SessionMessageResponse</a>
|
||||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionMessagesResponse">SessionMessagesResponse</a>
|
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionMessagesResponse">SessionMessagesResponse</a>
|
||||||
|
|
||||||
@@ -115,8 +127,9 @@ Methods:
|
|||||||
- <code title="get /session">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.List">List</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
- <code title="get /session">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.List">List</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||||
- <code title="delete /session/{id}">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Delete">Delete</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
- <code title="delete /session/{id}">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Delete">Delete</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||||
- <code title="post /session/{id}/abort">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Abort">Abort</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
- <code title="post /session/{id}/abort">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Abort">Abort</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||||
- <code title="post /session/{id}/message">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Chat">Chat</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionChatParams">SessionChatParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AssistantMessage">AssistantMessage</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
- <code title="post /session/{id}/message">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Chat">Chat</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionChatParams">SessionChatParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionChatResponse">SessionChatResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||||
- <code title="get /session/{id}/children">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Children">Children</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
- <code title="get /session/{id}/children">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Children">Children</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||||
|
- <code title="post /session/{id}/command">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Command">Command</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionCommandParams">SessionCommandParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionCommandResponse">SessionCommandResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||||
- <code title="get /session/{id}">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Get">Get</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
- <code title="get /session/{id}">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Get">Get</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||||
- <code title="post /session/{id}/init">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Init">Init</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionInitParams">SessionInitParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
- <code title="post /session/{id}/init">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Init">Init</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionInitParams">SessionInitParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||||
- <code title="get /session/{id}/message/{messageID}">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Message">Message</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, messageID <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionMessageResponse">SessionMessageResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
- <code title="get /session/{id}/message/{messageID}">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Message">Message</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, messageID <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionMessageResponse">SessionMessageResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ type Client struct {
|
|||||||
Find *FindService
|
Find *FindService
|
||||||
File *FileService
|
File *FileService
|
||||||
Config *ConfigService
|
Config *ConfigService
|
||||||
|
Command *CommandService
|
||||||
Session *SessionService
|
Session *SessionService
|
||||||
Tui *TuiService
|
Tui *TuiService
|
||||||
}
|
}
|
||||||
@@ -49,6 +50,7 @@ func NewClient(opts ...option.RequestOption) (r *Client) {
|
|||||||
r.Find = NewFindService(opts...)
|
r.Find = NewFindService(opts...)
|
||||||
r.File = NewFileService(opts...)
|
r.File = NewFileService(opts...)
|
||||||
r.Config = NewConfigService(opts...)
|
r.Config = NewConfigService(opts...)
|
||||||
|
r.Command = NewCommandService(opts...)
|
||||||
r.Session = NewSessionService(opts...)
|
r.Session = NewSessionService(opts...)
|
||||||
r.Tui = NewTuiService(opts...)
|
r.Tui = NewTuiService(opts...)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
||||||
|
|
||||||
|
package opencode
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/sst/opencode-sdk-go/internal/apijson"
|
||||||
|
"github.com/sst/opencode-sdk-go/internal/requestconfig"
|
||||||
|
"github.com/sst/opencode-sdk-go/option"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CommandService contains methods and other services that help with interacting
|
||||||
|
// with the opencode API.
|
||||||
|
//
|
||||||
|
// Note, unlike clients, this service does not read variables from the environment
|
||||||
|
// automatically. You should not instantiate this service directly, and instead use
|
||||||
|
// the [NewCommandService] method instead.
|
||||||
|
type CommandService struct {
|
||||||
|
Options []option.RequestOption
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCommandService generates a new service that applies the given options to each
|
||||||
|
// request. These options are applied after the parent client's options (if there
|
||||||
|
// is one), and before any request-specific options.
|
||||||
|
func NewCommandService(opts ...option.RequestOption) (r *CommandService) {
|
||||||
|
r = &CommandService{}
|
||||||
|
r.Options = opts
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// List all commands
|
||||||
|
func (r *CommandService) List(ctx context.Context, opts ...option.RequestOption) (res *[]Command, err error) {
|
||||||
|
opts = append(r.Options[:], opts...)
|
||||||
|
path := "command"
|
||||||
|
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type Command struct {
|
||||||
|
Name string `json:"name,required"`
|
||||||
|
Template string `json:"template,required"`
|
||||||
|
Agent string `json:"agent"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Model string `json:"model"`
|
||||||
|
JSON commandJSON `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// commandJSON contains the JSON metadata for the struct [Command]
|
||||||
|
type commandJSON struct {
|
||||||
|
Name apijson.Field
|
||||||
|
Template apijson.Field
|
||||||
|
Agent apijson.Field
|
||||||
|
Description apijson.Field
|
||||||
|
Model apijson.Field
|
||||||
|
raw string
|
||||||
|
ExtraFields map[string]apijson.Field
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Command) UnmarshalJSON(data []byte) (err error) {
|
||||||
|
return apijson.UnmarshalRoot(data, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r commandJSON) RawJSON() string {
|
||||||
|
return r.raw
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
||||||
|
|
||||||
|
package opencode_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/sst/opencode-sdk-go"
|
||||||
|
"github.com/sst/opencode-sdk-go/internal/testutil"
|
||||||
|
"github.com/sst/opencode-sdk-go/option"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCommandList(t *testing.T) {
|
||||||
|
t.Skip("skipped: tests are disabled for the time being")
|
||||||
|
baseURL := "http://localhost:4010"
|
||||||
|
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
|
||||||
|
baseURL = envURL
|
||||||
|
}
|
||||||
|
if !testutil.CheckTestServer(t, baseURL) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
client := opencode.NewClient(
|
||||||
|
option.WithBaseURL(baseURL),
|
||||||
|
)
|
||||||
|
_, err := client.Command.List(context.TODO())
|
||||||
|
if err != nil {
|
||||||
|
var apierr *opencode.Error
|
||||||
|
if errors.As(err, &apierr) {
|
||||||
|
t.Log(string(apierr.DumpRequest(true)))
|
||||||
|
}
|
||||||
|
t.Fatalf("err should be nil: %s", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,7 +49,8 @@ type Config struct {
|
|||||||
// automatically
|
// automatically
|
||||||
Autoshare bool `json:"autoshare"`
|
Autoshare bool `json:"autoshare"`
|
||||||
// Automatically update to the latest version
|
// Automatically update to the latest version
|
||||||
Autoupdate bool `json:"autoupdate"`
|
Autoupdate bool `json:"autoupdate"`
|
||||||
|
Command map[string]ConfigCommand `json:"command"`
|
||||||
// Disable providers that are loaded automatically
|
// Disable providers that are loaded automatically
|
||||||
DisabledProviders []string `json:"disabled_providers"`
|
DisabledProviders []string `json:"disabled_providers"`
|
||||||
Experimental ConfigExperimental `json:"experimental"`
|
Experimental ConfigExperimental `json:"experimental"`
|
||||||
@@ -79,7 +80,8 @@ type Config struct {
|
|||||||
SmallModel string `json:"small_model"`
|
SmallModel string `json:"small_model"`
|
||||||
Snapshot bool `json:"snapshot"`
|
Snapshot bool `json:"snapshot"`
|
||||||
// Theme name to use for the interface
|
// Theme name to use for the interface
|
||||||
Theme string `json:"theme"`
|
Theme string `json:"theme"`
|
||||||
|
Tools map[string]bool `json:"tools"`
|
||||||
// TUI specific settings
|
// TUI specific settings
|
||||||
Tui ConfigTui `json:"tui"`
|
Tui ConfigTui `json:"tui"`
|
||||||
// Custom username to display in conversations instead of system username
|
// Custom username to display in conversations instead of system username
|
||||||
@@ -93,6 +95,7 @@ type configJSON struct {
|
|||||||
Agent apijson.Field
|
Agent apijson.Field
|
||||||
Autoshare apijson.Field
|
Autoshare apijson.Field
|
||||||
Autoupdate apijson.Field
|
Autoupdate apijson.Field
|
||||||
|
Command apijson.Field
|
||||||
DisabledProviders apijson.Field
|
DisabledProviders apijson.Field
|
||||||
Experimental apijson.Field
|
Experimental apijson.Field
|
||||||
Formatter apijson.Field
|
Formatter apijson.Field
|
||||||
@@ -110,6 +113,7 @@ type configJSON struct {
|
|||||||
SmallModel apijson.Field
|
SmallModel apijson.Field
|
||||||
Snapshot apijson.Field
|
Snapshot apijson.Field
|
||||||
Theme apijson.Field
|
Theme apijson.Field
|
||||||
|
Tools apijson.Field
|
||||||
Tui apijson.Field
|
Tui apijson.Field
|
||||||
Username apijson.Field
|
Username apijson.Field
|
||||||
raw string
|
raw string
|
||||||
@@ -662,6 +666,32 @@ func (r ConfigAgentPlanPermissionWebfetch) IsKnown() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ConfigCommand struct {
|
||||||
|
Template string `json:"template,required"`
|
||||||
|
Agent string `json:"agent"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Model string `json:"model"`
|
||||||
|
JSON configCommandJSON `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// configCommandJSON contains the JSON metadata for the struct [ConfigCommand]
|
||||||
|
type configCommandJSON struct {
|
||||||
|
Template apijson.Field
|
||||||
|
Agent apijson.Field
|
||||||
|
Description apijson.Field
|
||||||
|
Model apijson.Field
|
||||||
|
raw string
|
||||||
|
ExtraFields map[string]apijson.Field
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ConfigCommand) UnmarshalJSON(data []byte) (err error) {
|
||||||
|
return apijson.UnmarshalRoot(data, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r configCommandJSON) RawJSON() string {
|
||||||
|
return r.raw
|
||||||
|
}
|
||||||
|
|
||||||
type ConfigExperimental struct {
|
type ConfigExperimental struct {
|
||||||
Hook ConfigExperimentalHook `json:"hook"`
|
Hook ConfigExperimentalHook `json:"hook"`
|
||||||
JSON configExperimentalJSON `json:"-"`
|
JSON configExperimentalJSON `json:"-"`
|
||||||
@@ -1760,6 +1790,8 @@ type KeybindsConfig struct {
|
|||||||
SessionNew string `json:"session_new,required"`
|
SessionNew string `json:"session_new,required"`
|
||||||
// Share current session
|
// Share current session
|
||||||
SessionShare string `json:"session_share,required"`
|
SessionShare string `json:"session_share,required"`
|
||||||
|
// Show session timeline
|
||||||
|
SessionTimeline string `json:"session_timeline,required"`
|
||||||
// Unshare current session
|
// Unshare current session
|
||||||
SessionUnshare string `json:"session_unshare,required"`
|
SessionUnshare string `json:"session_unshare,required"`
|
||||||
// @deprecated use agent_cycle. Next agent
|
// @deprecated use agent_cycle. Next agent
|
||||||
@@ -1821,6 +1853,7 @@ type keybindsConfigJSON struct {
|
|||||||
SessionList apijson.Field
|
SessionList apijson.Field
|
||||||
SessionNew apijson.Field
|
SessionNew apijson.Field
|
||||||
SessionShare apijson.Field
|
SessionShare apijson.Field
|
||||||
|
SessionTimeline apijson.Field
|
||||||
SessionUnshare apijson.Field
|
SessionUnshare apijson.Field
|
||||||
SwitchAgent apijson.Field
|
SwitchAgent apijson.Field
|
||||||
SwitchAgentReverse apijson.Field
|
SwitchAgentReverse apijson.Field
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ func (r *EventService) ListStreaming(ctx context.Context, opts ...option.Request
|
|||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
opts = append(r.Options[:], opts...)
|
opts = append(r.Options[:], opts...)
|
||||||
|
opts = append([]option.RequestOption{option.WithHeader("Accept", "text/event-stream")}, opts...)
|
||||||
path := "event"
|
path := "event"
|
||||||
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &raw, opts...)
|
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &raw, opts...)
|
||||||
return ssestream.NewStream[EventListResponse](ssestream.NewDecoder(raw), err)
|
return ssestream.NewStream[EventListResponse](ssestream.NewDecoder(raw), err)
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ func (r *SessionService) Abort(ctx context.Context, id string, opts ...option.Re
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create and send a new message to a session
|
// Create and send a new message to a session
|
||||||
func (r *SessionService) Chat(ctx context.Context, id string, body SessionChatParams, opts ...option.RequestOption) (res *AssistantMessage, err error) {
|
func (r *SessionService) Chat(ctx context.Context, id string, body SessionChatParams, opts ...option.RequestOption) (res *SessionChatResponse, err error) {
|
||||||
opts = append(r.Options[:], opts...)
|
opts = append(r.Options[:], opts...)
|
||||||
if id == "" {
|
if id == "" {
|
||||||
err = errors.New("missing required id parameter")
|
err = errors.New("missing required id parameter")
|
||||||
@@ -114,6 +114,18 @@ func (r *SessionService) Children(ctx context.Context, id string, opts ...option
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send a new command to a session
|
||||||
|
func (r *SessionService) Command(ctx context.Context, id string, body SessionCommandParams, opts ...option.RequestOption) (res *SessionCommandResponse, err error) {
|
||||||
|
opts = append(r.Options[:], opts...)
|
||||||
|
if id == "" {
|
||||||
|
err = errors.New("missing required id parameter")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
path := fmt.Sprintf("session/%s/command", id)
|
||||||
|
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Get session
|
// Get session
|
||||||
func (r *SessionService) Get(ctx context.Context, id string, opts ...option.RequestOption) (res *Session, err error) {
|
func (r *SessionService) Get(ctx context.Context, id string, opts ...option.RequestOption) (res *Session, err error) {
|
||||||
opts = append(r.Options[:], opts...)
|
opts = append(r.Options[:], opts...)
|
||||||
@@ -2278,6 +2290,52 @@ func (r userMessageTimeJSON) RawJSON() string {
|
|||||||
return r.raw
|
return r.raw
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SessionChatResponse struct {
|
||||||
|
Info AssistantMessage `json:"info,required"`
|
||||||
|
Parts []Part `json:"parts,required"`
|
||||||
|
JSON sessionChatResponseJSON `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// sessionChatResponseJSON contains the JSON metadata for the struct
|
||||||
|
// [SessionChatResponse]
|
||||||
|
type sessionChatResponseJSON struct {
|
||||||
|
Info apijson.Field
|
||||||
|
Parts apijson.Field
|
||||||
|
raw string
|
||||||
|
ExtraFields map[string]apijson.Field
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SessionChatResponse) UnmarshalJSON(data []byte) (err error) {
|
||||||
|
return apijson.UnmarshalRoot(data, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r sessionChatResponseJSON) RawJSON() string {
|
||||||
|
return r.raw
|
||||||
|
}
|
||||||
|
|
||||||
|
type SessionCommandResponse struct {
|
||||||
|
Info AssistantMessage `json:"info,required"`
|
||||||
|
Parts []Part `json:"parts,required"`
|
||||||
|
JSON sessionCommandResponseJSON `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// sessionCommandResponseJSON contains the JSON metadata for the struct
|
||||||
|
// [SessionCommandResponse]
|
||||||
|
type sessionCommandResponseJSON struct {
|
||||||
|
Info apijson.Field
|
||||||
|
Parts apijson.Field
|
||||||
|
raw string
|
||||||
|
ExtraFields map[string]apijson.Field
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SessionCommandResponse) UnmarshalJSON(data []byte) (err error) {
|
||||||
|
return apijson.UnmarshalRoot(data, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r sessionCommandResponseJSON) RawJSON() string {
|
||||||
|
return r.raw
|
||||||
|
}
|
||||||
|
|
||||||
type SessionMessageResponse struct {
|
type SessionMessageResponse struct {
|
||||||
Info Message `json:"info,required"`
|
Info Message `json:"info,required"`
|
||||||
Parts []Part `json:"parts,required"`
|
Parts []Part `json:"parts,required"`
|
||||||
@@ -2396,6 +2454,18 @@ func (r SessionChatParamsPartsType) IsKnown() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SessionCommandParams struct {
|
||||||
|
Arguments param.Field[string] `json:"arguments,required"`
|
||||||
|
Command param.Field[string] `json:"command,required"`
|
||||||
|
Agent param.Field[string] `json:"agent"`
|
||||||
|
MessageID param.Field[string] `json:"messageID"`
|
||||||
|
Model param.Field[string] `json:"model"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r SessionCommandParams) MarshalJSON() (data []byte, err error) {
|
||||||
|
return apijson.MarshalRoot(r)
|
||||||
|
}
|
||||||
|
|
||||||
type SessionInitParams struct {
|
type SessionInitParams struct {
|
||||||
MessageID param.Field[string] `json:"messageID,required"`
|
MessageID param.Field[string] `json:"messageID,required"`
|
||||||
ModelID param.Field[string] `json:"modelID,required"`
|
ModelID param.Field[string] `json:"modelID,required"`
|
||||||
|
|||||||
@@ -199,6 +199,38 @@ func TestSessionChildren(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSessionCommandWithOptionalParams(t *testing.T) {
|
||||||
|
t.Skip("skipped: tests are disabled for the time being")
|
||||||
|
baseURL := "http://localhost:4010"
|
||||||
|
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
|
||||||
|
baseURL = envURL
|
||||||
|
}
|
||||||
|
if !testutil.CheckTestServer(t, baseURL) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
client := opencode.NewClient(
|
||||||
|
option.WithBaseURL(baseURL),
|
||||||
|
)
|
||||||
|
_, err := client.Session.Command(
|
||||||
|
context.TODO(),
|
||||||
|
"id",
|
||||||
|
opencode.SessionCommandParams{
|
||||||
|
Arguments: opencode.F("arguments"),
|
||||||
|
Command: opencode.F("command"),
|
||||||
|
Agent: opencode.F("agent"),
|
||||||
|
MessageID: opencode.F("msg"),
|
||||||
|
Model: opencode.F("model"),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
var apierr *opencode.Error
|
||||||
|
if errors.As(err, &apierr) {
|
||||||
|
t.Log(string(apierr.DumpRequest(true)))
|
||||||
|
}
|
||||||
|
t.Fatalf("err should be nil: %s", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestSessionGet(t *testing.T) {
|
func TestSessionGet(t *testing.T) {
|
||||||
t.Skip("skipped: tests are disabled for the time being")
|
t.Skip("skipped: tests are disabled for the time being")
|
||||||
baseURL := "http://localhost:4010"
|
baseURL := "http://localhost:4010"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"name": "@opencode-ai/sdk",
|
"name": "@opencode-ai/sdk",
|
||||||
"version": "0.5.5",
|
"version": "0.5.18",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
@@ -10,6 +10,14 @@
|
|||||||
".": {
|
".": {
|
||||||
"development": "./src/index.ts",
|
"development": "./src/index.ts",
|
||||||
"import": "./dist/index.js"
|
"import": "./dist/index.js"
|
||||||
|
},
|
||||||
|
"./client": {
|
||||||
|
"development": "./src/client.ts",
|
||||||
|
"import": "./dist/client.js"
|
||||||
|
},
|
||||||
|
"./server": {
|
||||||
|
"development": "./src/server.ts",
|
||||||
|
"import": "./dist/server.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
@@ -19,5 +27,8 @@
|
|||||||
"typescript": "catalog:",
|
"typescript": "catalog:",
|
||||||
"@hey-api/openapi-ts": "0.80.1",
|
"@hey-api/openapi-ts": "0.80.1",
|
||||||
"@tsconfig/node22": "catalog:"
|
"@tsconfig/node22": "catalog:"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@hey-api/openapi-ts": "0.80.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
export * from "./gen/types.gen.js"
|
||||||
|
export { type Config as OpencodeClientConfig, OpencodeClient }
|
||||||
|
|
||||||
|
import { createClient } from "./gen/client/client.js"
|
||||||
|
import { type Config } from "./gen/client/types.js"
|
||||||
|
import { OpencodeClient } from "./gen/sdk.gen.js"
|
||||||
|
|
||||||
|
export function createOpencodeClient(config?: Config) {
|
||||||
|
const client = createClient(config)
|
||||||
|
return new OpencodeClient({ client })
|
||||||
|
}
|
||||||
@@ -39,6 +39,8 @@ import type {
|
|||||||
SessionChatResponses,
|
SessionChatResponses,
|
||||||
SessionMessageData,
|
SessionMessageData,
|
||||||
SessionMessageResponses,
|
SessionMessageResponses,
|
||||||
|
SessionCommandData,
|
||||||
|
SessionCommandResponses,
|
||||||
SessionShellData,
|
SessionShellData,
|
||||||
SessionShellResponses,
|
SessionShellResponses,
|
||||||
SessionRevertData,
|
SessionRevertData,
|
||||||
@@ -47,6 +49,8 @@ import type {
|
|||||||
SessionUnrevertResponses,
|
SessionUnrevertResponses,
|
||||||
PostSessionByIdPermissionsByPermissionIdData,
|
PostSessionByIdPermissionsByPermissionIdData,
|
||||||
PostSessionByIdPermissionsByPermissionIdResponses,
|
PostSessionByIdPermissionsByPermissionIdResponses,
|
||||||
|
CommandListData,
|
||||||
|
CommandListResponses,
|
||||||
ConfigProvidersData,
|
ConfigProvidersData,
|
||||||
ConfigProvidersResponses,
|
ConfigProvidersResponses,
|
||||||
FindTextData,
|
FindTextData,
|
||||||
@@ -355,6 +359,20 @@ class Session extends _HeyApiClient {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a new command to a session
|
||||||
|
*/
|
||||||
|
public command<ThrowOnError extends boolean = false>(options: Options<SessionCommandData, ThrowOnError>) {
|
||||||
|
return (options.client ?? this._client).post<SessionCommandResponses, unknown, ThrowOnError>({
|
||||||
|
url: "/session/{id}/command",
|
||||||
|
...options,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
...options.headers,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run a shell command
|
* Run a shell command
|
||||||
*/
|
*/
|
||||||
@@ -394,6 +412,18 @@ class Session extends _HeyApiClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Command extends _HeyApiClient {
|
||||||
|
/**
|
||||||
|
* List all commands
|
||||||
|
*/
|
||||||
|
public list<ThrowOnError extends boolean = false>(options?: Options<CommandListData, ThrowOnError>) {
|
||||||
|
return (options?.client ?? this._client).get<CommandListResponses, unknown, ThrowOnError>({
|
||||||
|
url: "/command",
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class Find extends _HeyApiClient {
|
class Find extends _HeyApiClient {
|
||||||
/**
|
/**
|
||||||
* Find text in files
|
* Find text in files
|
||||||
@@ -592,6 +622,7 @@ export class OpencodeClient extends _HeyApiClient {
|
|||||||
app = new App({ client: this._client })
|
app = new App({ client: this._client })
|
||||||
config = new Config({ client: this._client })
|
config = new Config({ client: this._client })
|
||||||
session = new Session({ client: this._client })
|
session = new Session({ client: this._client })
|
||||||
|
command = new Command({ client: this._client })
|
||||||
find = new Find({ client: this._client })
|
find = new Find({ client: this._client })
|
||||||
file = new File({ client: this._client })
|
file = new File({ client: this._client })
|
||||||
tui = new Tui({ client: this._client })
|
tui = new Tui({ client: this._client })
|
||||||
|
|||||||
@@ -585,6 +585,14 @@ export type Config = {
|
|||||||
*/
|
*/
|
||||||
scroll_speed: number
|
scroll_speed: number
|
||||||
}
|
}
|
||||||
|
command?: {
|
||||||
|
[key: string]: {
|
||||||
|
template: string
|
||||||
|
description?: string
|
||||||
|
agent?: string
|
||||||
|
model?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
plugin?: Array<string>
|
plugin?: Array<string>
|
||||||
snapshot?: boolean
|
snapshot?: boolean
|
||||||
/**
|
/**
|
||||||
@@ -729,6 +737,9 @@ export type Config = {
|
|||||||
}
|
}
|
||||||
webfetch?: "ask" | "allow" | "deny"
|
webfetch?: "ask" | "allow" | "deny"
|
||||||
}
|
}
|
||||||
|
tools?: {
|
||||||
|
[key: string]: boolean
|
||||||
|
}
|
||||||
experimental?: {
|
experimental?: {
|
||||||
hook?: {
|
hook?: {
|
||||||
file_edited?: {
|
file_edited?: {
|
||||||
@@ -794,6 +805,10 @@ export type KeybindsConfig = {
|
|||||||
* List all sessions
|
* List all sessions
|
||||||
*/
|
*/
|
||||||
session_list: string
|
session_list: string
|
||||||
|
/**
|
||||||
|
* Show session timeline
|
||||||
|
*/
|
||||||
|
session_timeline: string
|
||||||
/**
|
/**
|
||||||
* Share current session
|
* Share current session
|
||||||
*/
|
*/
|
||||||
@@ -1103,6 +1118,14 @@ export type AgentPartInput = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Command = {
|
||||||
|
name: string
|
||||||
|
description?: string
|
||||||
|
agent?: string
|
||||||
|
model?: string
|
||||||
|
template: string
|
||||||
|
}
|
||||||
|
|
||||||
export type Symbol = {
|
export type Symbol = {
|
||||||
name: string
|
name: string
|
||||||
kind: number
|
kind: number
|
||||||
@@ -1520,7 +1543,10 @@ export type SessionChatResponses = {
|
|||||||
/**
|
/**
|
||||||
* Created message
|
* Created message
|
||||||
*/
|
*/
|
||||||
200: AssistantMessage
|
200: {
|
||||||
|
info: AssistantMessage
|
||||||
|
parts: Array<Part>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SessionChatResponse = SessionChatResponses[keyof SessionChatResponses]
|
export type SessionChatResponse = SessionChatResponses[keyof SessionChatResponses]
|
||||||
@@ -1553,6 +1579,36 @@ export type SessionMessageResponses = {
|
|||||||
|
|
||||||
export type SessionMessageResponse = SessionMessageResponses[keyof SessionMessageResponses]
|
export type SessionMessageResponse = SessionMessageResponses[keyof SessionMessageResponses]
|
||||||
|
|
||||||
|
export type SessionCommandData = {
|
||||||
|
body?: {
|
||||||
|
messageID?: string
|
||||||
|
agent?: string
|
||||||
|
model?: string
|
||||||
|
arguments: string
|
||||||
|
command: string
|
||||||
|
}
|
||||||
|
path: {
|
||||||
|
/**
|
||||||
|
* Session ID
|
||||||
|
*/
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
query?: never
|
||||||
|
url: "/session/{id}/command"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SessionCommandResponses = {
|
||||||
|
/**
|
||||||
|
* Created message
|
||||||
|
*/
|
||||||
|
200: {
|
||||||
|
info: AssistantMessage
|
||||||
|
parts: Array<Part>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SessionCommandResponse = SessionCommandResponses[keyof SessionCommandResponses]
|
||||||
|
|
||||||
export type SessionShellData = {
|
export type SessionShellData = {
|
||||||
body?: {
|
body?: {
|
||||||
agent: string
|
agent: string
|
||||||
@@ -1638,6 +1694,22 @@ export type PostSessionByIdPermissionsByPermissionIdResponses = {
|
|||||||
export type PostSessionByIdPermissionsByPermissionIdResponse =
|
export type PostSessionByIdPermissionsByPermissionIdResponse =
|
||||||
PostSessionByIdPermissionsByPermissionIdResponses[keyof PostSessionByIdPermissionsByPermissionIdResponses]
|
PostSessionByIdPermissionsByPermissionIdResponses[keyof PostSessionByIdPermissionsByPermissionIdResponses]
|
||||||
|
|
||||||
|
export type CommandListData = {
|
||||||
|
body?: never
|
||||||
|
path?: never
|
||||||
|
query?: never
|
||||||
|
url: "/command"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CommandListResponses = {
|
||||||
|
/**
|
||||||
|
* List of commands
|
||||||
|
*/
|
||||||
|
200: Array<Command>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CommandListResponse = CommandListResponses[keyof CommandListResponses]
|
||||||
|
|
||||||
export type ConfigProvidersData = {
|
export type ConfigProvidersData = {
|
||||||
body?: never
|
body?: never
|
||||||
path?: never
|
path?: never
|
||||||
|
|||||||
@@ -1,35 +1,2 @@
|
|||||||
import { createClient } from "./gen/client/client.js"
|
export * from "./client.js"
|
||||||
import { type Config } from "./gen/client/types.js"
|
export * from "./server.js"
|
||||||
import { OpencodeClient } from "./gen/sdk.gen.js"
|
|
||||||
export * from "./gen/types.gen.js"
|
|
||||||
import { spawn } from "child_process"
|
|
||||||
|
|
||||||
export function createOpencodeClient(config?: Config) {
|
|
||||||
const client = createClient(config)
|
|
||||||
return new OpencodeClient({ client })
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ServerConfig = {
|
|
||||||
host?: string
|
|
||||||
port?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createOpencodeServer(config?: ServerConfig) {
|
|
||||||
config = Object.assign(
|
|
||||||
{
|
|
||||||
host: "127.0.0.1",
|
|
||||||
port: 4096,
|
|
||||||
},
|
|
||||||
config ?? {},
|
|
||||||
)
|
|
||||||
|
|
||||||
const proc = spawn(`opencode`, [`serve`, `--host=${config.host}`, `--port=${config.port}`])
|
|
||||||
const url = `http://${config.host}:${config.port}`
|
|
||||||
|
|
||||||
return {
|
|
||||||
url,
|
|
||||||
close() {
|
|
||||||
proc.kill()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import { spawn } from "node:child_process"
|
||||||
|
|
||||||
|
export type ServerConfig = {
|
||||||
|
hostname?: string
|
||||||
|
port?: number
|
||||||
|
signal?: AbortSignal
|
||||||
|
timeout?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createOpencodeServer(config?: ServerConfig) {
|
||||||
|
config = Object.assign(
|
||||||
|
{
|
||||||
|
hostname: "127.0.0.1",
|
||||||
|
port: 4096,
|
||||||
|
timeout: 5000,
|
||||||
|
},
|
||||||
|
config ?? {},
|
||||||
|
)
|
||||||
|
|
||||||
|
const proc = spawn(`opencode`, [`serve`, `--hostname=${config.hostname}`, `--port=${config.port}`], {
|
||||||
|
signal: config.signal,
|
||||||
|
})
|
||||||
|
|
||||||
|
const url = await new Promise<string>((resolve, reject) => {
|
||||||
|
const id = setTimeout(() => {
|
||||||
|
reject(new Error(`Timeout waiting for server to start after ${config.timeout}ms`))
|
||||||
|
}, config.timeout)
|
||||||
|
let output = ""
|
||||||
|
proc.stdout?.on("data", (chunk) => {
|
||||||
|
output += chunk.toString()
|
||||||
|
const lines = output.split("\n")
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line.startsWith("opencode server listening")) {
|
||||||
|
const match = line.match(/on\s+(https?:\/\/[^\s]+)/)
|
||||||
|
if (!match) {
|
||||||
|
throw new Error(`Failed to parse server url from output: ${line}`)
|
||||||
|
}
|
||||||
|
clearTimeout(id)
|
||||||
|
resolve(match[1])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
proc.stderr?.on("data", (chunk) => {
|
||||||
|
output += chunk.toString()
|
||||||
|
})
|
||||||
|
proc.on("exit", (code) => {
|
||||||
|
clearTimeout(id)
|
||||||
|
let msg = `Server exited with code ${code}`
|
||||||
|
if (output.trim()) {
|
||||||
|
msg += `\nServer output: ${output}`
|
||||||
|
}
|
||||||
|
reject(new Error(msg))
|
||||||
|
})
|
||||||
|
proc.on("error", (error) => {
|
||||||
|
clearTimeout(id)
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
if (config.signal) {
|
||||||
|
config.signal.addEventListener("abort", () => {
|
||||||
|
clearTimeout(id)
|
||||||
|
reject(new Error("Aborted"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
url,
|
||||||
|
close() {
|
||||||
|
proc.kill()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -85,6 +85,12 @@ resources:
|
|||||||
methods:
|
methods:
|
||||||
get: get /config
|
get: get /config
|
||||||
|
|
||||||
|
command:
|
||||||
|
models:
|
||||||
|
command: Command
|
||||||
|
methods:
|
||||||
|
list: get /command
|
||||||
|
|
||||||
session:
|
session:
|
||||||
models:
|
models:
|
||||||
session: Session
|
session: Session
|
||||||
@@ -126,6 +132,7 @@ resources:
|
|||||||
message: get /session/{id}/message/{messageID}
|
message: get /session/{id}/message/{messageID}
|
||||||
messages: get /session/{id}/message
|
messages: get /session/{id}/message
|
||||||
chat: post /session/{id}/message
|
chat: post /session/{id}/message
|
||||||
|
command: post /session/{id}/command
|
||||||
shell: post /session/{id}/shell
|
shell: post /session/{id}/shell
|
||||||
update: patch /session/{id}
|
update: patch /session/{id}
|
||||||
revert: post /session/{id}/revert
|
revert: post /session/{id}/revert
|
||||||
|
|||||||
@@ -84,6 +84,10 @@ type SendPrompt = Prompt
|
|||||||
type SendShell = struct {
|
type SendShell = struct {
|
||||||
Command string
|
Command string
|
||||||
}
|
}
|
||||||
|
type SendCommand = struct {
|
||||||
|
Command string
|
||||||
|
Args string
|
||||||
|
}
|
||||||
type SetEditorContentMsg struct {
|
type SetEditorContentMsg struct {
|
||||||
Text string
|
Text string
|
||||||
}
|
}
|
||||||
@@ -183,6 +187,11 @@ func New(
|
|||||||
|
|
||||||
slog.Debug("Loaded config", "config", configInfo)
|
slog.Debug("Loaded config", "config", configInfo)
|
||||||
|
|
||||||
|
customCommands, err := httpClient.Command.List(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
app := &App{
|
app := &App{
|
||||||
Info: appInfo,
|
Info: appInfo,
|
||||||
Agents: agents,
|
Agents: agents,
|
||||||
@@ -194,7 +203,7 @@ func New(
|
|||||||
AgentIndex: agentIndex,
|
AgentIndex: agentIndex,
|
||||||
Session: &opencode.Session{},
|
Session: &opencode.Session{},
|
||||||
Messages: []Message{},
|
Messages: []Message{},
|
||||||
Commands: commands.LoadFromConfig(configInfo),
|
Commands: commands.LoadFromConfig(configInfo, *customCommands),
|
||||||
InitialModel: initialModel,
|
InitialModel: initialModel,
|
||||||
InitialPrompt: initialPrompt,
|
InitialPrompt: initialPrompt,
|
||||||
InitialAgent: initialAgent,
|
InitialAgent: initialAgent,
|
||||||
@@ -207,6 +216,9 @@ func New(
|
|||||||
|
|
||||||
func (a *App) Keybind(commandName commands.CommandName) string {
|
func (a *App) Keybind(commandName commands.CommandName) string {
|
||||||
command := a.Commands[commandName]
|
command := a.Commands[commandName]
|
||||||
|
if len(command.Keybindings) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
kb := command.Keybindings[0]
|
kb := command.Keybindings[0]
|
||||||
key := kb.Key
|
key := kb.Key
|
||||||
if kb.RequiresLeader {
|
if kb.RequiresLeader {
|
||||||
@@ -650,6 +662,25 @@ func (a *App) IsBusy() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) HasAnimatingWork() bool {
|
||||||
|
for _, msg := range a.Messages {
|
||||||
|
switch casted := msg.Info.(type) {
|
||||||
|
case opencode.AssistantMessage:
|
||||||
|
if casted.Time.Completed == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, p := range msg.Parts {
|
||||||
|
if tp, ok := p.(opencode.ToolPart); ok {
|
||||||
|
if tp.State.Status == opencode.ToolPartStateStatusPending {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (a *App) SaveState() tea.Cmd {
|
func (a *App) SaveState() tea.Cmd {
|
||||||
return func() tea.Msg {
|
return func() tea.Msg {
|
||||||
err := SaveState(a.StatePath, a.State)
|
err := SaveState(a.StatePath, a.State)
|
||||||
@@ -771,6 +802,38 @@ func (a *App) SendPrompt(ctx context.Context, prompt Prompt) (*App, tea.Cmd) {
|
|||||||
return a, tea.Batch(cmds...)
|
return a, tea.Batch(cmds...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) SendCommand(ctx context.Context, command string, args string) (*App, tea.Cmd) {
|
||||||
|
var cmds []tea.Cmd
|
||||||
|
if a.Session.ID == "" {
|
||||||
|
session, err := a.CreateSession(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return a, toast.NewErrorToast(err.Error())
|
||||||
|
}
|
||||||
|
a.Session = session
|
||||||
|
cmds = append(cmds, util.CmdHandler(SessionCreatedMsg{Session: session}))
|
||||||
|
}
|
||||||
|
|
||||||
|
cmds = append(cmds, func() tea.Msg {
|
||||||
|
_, err := a.Client.Session.Command(
|
||||||
|
context.Background(),
|
||||||
|
a.Session.ID,
|
||||||
|
opencode.SessionCommandParams{
|
||||||
|
Command: opencode.F(command),
|
||||||
|
Arguments: opencode.F(args),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to execute command", "error", err)
|
||||||
|
return toast.NewErrorToast("Failed to execute command")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
// The actual response will come through SSE
|
||||||
|
// For now, just return success
|
||||||
|
return a, tea.Batch(cmds...)
|
||||||
|
}
|
||||||
|
|
||||||
func (a *App) SendShell(ctx context.Context, command string) (*App, tea.Cmd) {
|
func (a *App) SendShell(ctx context.Context, command string) (*App, tea.Cmd) {
|
||||||
var cmds []tea.Cmd
|
var cmds []tea.Cmd
|
||||||
if a.Session.ID == "" {
|
if a.Session.ID == "" {
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ type Command struct {
|
|||||||
Description string
|
Description string
|
||||||
Keybindings []Keybinding
|
Keybindings []Keybinding
|
||||||
Trigger []string
|
Trigger []string
|
||||||
|
Custom bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Command) Keys() []string {
|
func (c Command) Keys() []string {
|
||||||
@@ -96,6 +97,7 @@ func (r CommandRegistry) Sorted() []Command {
|
|||||||
})
|
})
|
||||||
return commands
|
return commands
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r CommandRegistry) Matches(msg tea.KeyPressMsg, leader bool) []Command {
|
func (r CommandRegistry) Matches(msg tea.KeyPressMsg, leader bool) []Command {
|
||||||
var matched []Command
|
var matched []Command
|
||||||
for _, command := range r.Sorted() {
|
for _, command := range r.Sorted() {
|
||||||
@@ -107,7 +109,6 @@ func (r CommandRegistry) Matches(msg tea.KeyPressMsg, leader bool) []Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
||||||
SessionChildCycleCommand CommandName = "session_child_cycle"
|
SessionChildCycleCommand CommandName = "session_child_cycle"
|
||||||
SessionChildCycleReverseCommand CommandName = "session_child_cycle_reverse"
|
SessionChildCycleReverseCommand CommandName = "session_child_cycle_reverse"
|
||||||
ModelCycleRecentReverseCommand CommandName = "model_cycle_recent_reverse"
|
ModelCycleRecentReverseCommand CommandName = "model_cycle_recent_reverse"
|
||||||
@@ -119,40 +120,40 @@ const (
|
|||||||
EditorOpenCommand CommandName = "editor_open"
|
EditorOpenCommand CommandName = "editor_open"
|
||||||
SessionNewCommand CommandName = "session_new"
|
SessionNewCommand CommandName = "session_new"
|
||||||
SessionListCommand CommandName = "session_list"
|
SessionListCommand CommandName = "session_list"
|
||||||
SessionNavigationCommand CommandName = "session_navigation"
|
SessionTimelineCommand CommandName = "session_timeline"
|
||||||
SessionShareCommand CommandName = "session_share"
|
SessionShareCommand CommandName = "session_share"
|
||||||
SessionUnshareCommand CommandName = "session_unshare"
|
SessionUnshareCommand CommandName = "session_unshare"
|
||||||
SessionInterruptCommand CommandName = "session_interrupt"
|
SessionInterruptCommand CommandName = "session_interrupt"
|
||||||
SessionCompactCommand CommandName = "session_compact"
|
SessionCompactCommand CommandName = "session_compact"
|
||||||
SessionExportCommand CommandName = "session_export"
|
SessionExportCommand CommandName = "session_export"
|
||||||
ToolDetailsCommand CommandName = "tool_details"
|
ToolDetailsCommand CommandName = "tool_details"
|
||||||
ThinkingBlocksCommand CommandName = "thinking_blocks"
|
ThinkingBlocksCommand CommandName = "thinking_blocks"
|
||||||
ModelListCommand CommandName = "model_list"
|
ModelListCommand CommandName = "model_list"
|
||||||
AgentListCommand CommandName = "agent_list"
|
AgentListCommand CommandName = "agent_list"
|
||||||
ModelCycleRecentCommand CommandName = "model_cycle_recent"
|
ModelCycleRecentCommand CommandName = "model_cycle_recent"
|
||||||
ThemeListCommand CommandName = "theme_list"
|
ThemeListCommand CommandName = "theme_list"
|
||||||
FileListCommand CommandName = "file_list"
|
FileListCommand CommandName = "file_list"
|
||||||
FileCloseCommand CommandName = "file_close"
|
FileCloseCommand CommandName = "file_close"
|
||||||
FileSearchCommand CommandName = "file_search"
|
FileSearchCommand CommandName = "file_search"
|
||||||
FileDiffToggleCommand CommandName = "file_diff_toggle"
|
FileDiffToggleCommand CommandName = "file_diff_toggle"
|
||||||
ProjectInitCommand CommandName = "project_init"
|
ProjectInitCommand CommandName = "project_init"
|
||||||
InputClearCommand CommandName = "input_clear"
|
InputClearCommand CommandName = "input_clear"
|
||||||
InputPasteCommand CommandName = "input_paste"
|
InputPasteCommand CommandName = "input_paste"
|
||||||
InputSubmitCommand CommandName = "input_submit"
|
InputSubmitCommand CommandName = "input_submit"
|
||||||
InputNewlineCommand CommandName = "input_newline"
|
InputNewlineCommand CommandName = "input_newline"
|
||||||
MessagesPageUpCommand CommandName = "messages_page_up"
|
MessagesPageUpCommand CommandName = "messages_page_up"
|
||||||
MessagesPageDownCommand CommandName = "messages_page_down"
|
MessagesPageDownCommand CommandName = "messages_page_down"
|
||||||
MessagesHalfPageUpCommand CommandName = "messages_half_page_up"
|
MessagesHalfPageUpCommand CommandName = "messages_half_page_up"
|
||||||
MessagesHalfPageDownCommand CommandName = "messages_half_page_down"
|
MessagesHalfPageDownCommand CommandName = "messages_half_page_down"
|
||||||
MessagesPreviousCommand CommandName = "messages_previous"
|
MessagesPreviousCommand CommandName = "messages_previous"
|
||||||
MessagesNextCommand CommandName = "messages_next"
|
MessagesNextCommand CommandName = "messages_next"
|
||||||
MessagesFirstCommand CommandName = "messages_first"
|
MessagesFirstCommand CommandName = "messages_first"
|
||||||
MessagesLastCommand CommandName = "messages_last"
|
MessagesLastCommand CommandName = "messages_last"
|
||||||
MessagesLayoutToggleCommand CommandName = "messages_layout_toggle"
|
MessagesLayoutToggleCommand CommandName = "messages_layout_toggle"
|
||||||
MessagesCopyCommand CommandName = "messages_copy"
|
MessagesCopyCommand CommandName = "messages_copy"
|
||||||
MessagesUndoCommand CommandName = "messages_undo"
|
MessagesUndoCommand CommandName = "messages_undo"
|
||||||
MessagesRedoCommand CommandName = "messages_redo"
|
MessagesRedoCommand CommandName = "messages_redo"
|
||||||
AppExitCommand CommandName = "app_exit"
|
AppExitCommand CommandName = "app_exit"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (k Command) Matches(msg tea.KeyPressMsg, leader bool) bool {
|
func (k Command) Matches(msg tea.KeyPressMsg, leader bool) bool {
|
||||||
@@ -183,7 +184,7 @@ func parseBindings(bindings ...string) []Keybinding {
|
|||||||
return parsedBindings
|
return parsedBindings
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadFromConfig(config *opencode.Config) CommandRegistry {
|
func LoadFromConfig(config *opencode.Config, customCommands []opencode.Command) CommandRegistry {
|
||||||
defaults := []Command{
|
defaults := []Command{
|
||||||
{
|
{
|
||||||
Name: AppHelpCommand,
|
Name: AppHelpCommand,
|
||||||
@@ -216,10 +217,10 @@ func LoadFromConfig(config *opencode.Config) CommandRegistry {
|
|||||||
Trigger: []string{"sessions", "resume", "continue"},
|
Trigger: []string{"sessions", "resume", "continue"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: SessionNavigationCommand,
|
Name: SessionTimelineCommand,
|
||||||
Description: "jump to message",
|
Description: "show session timeline",
|
||||||
Keybindings: parseBindings("<leader>g"),
|
Keybindings: parseBindings("<leader>g"),
|
||||||
Trigger: []string{"jump", "goto", "navigate"},
|
Trigger: []string{"timeline", "history", "goto"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: SessionShareCommand,
|
Name: SessionShareCommand,
|
||||||
@@ -401,6 +402,16 @@ func LoadFromConfig(config *opencode.Config) CommandRegistry {
|
|||||||
}
|
}
|
||||||
registry[command.Name] = command
|
registry[command.Name] = command
|
||||||
}
|
}
|
||||||
|
for _, command := range customCommands {
|
||||||
|
registry[CommandName(command.Name)] = Command{
|
||||||
|
Name: CommandName(command.Name),
|
||||||
|
Description: command.Description,
|
||||||
|
Trigger: []string{command.Name},
|
||||||
|
Keybindings: []Keybinding{},
|
||||||
|
Custom: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
slog.Info("Loaded commands", "commands", registry)
|
slog.Info("Loaded commands", "commands", registry)
|
||||||
return registry
|
return registry
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -224,10 +224,17 @@ func (m *editorComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
case dialog.CompletionSelectedMsg:
|
case dialog.CompletionSelectedMsg:
|
||||||
switch msg.Item.ProviderID {
|
switch msg.Item.ProviderID {
|
||||||
case "commands":
|
case "commands":
|
||||||
commandName := strings.TrimPrefix(msg.Item.Value, "/")
|
command := msg.Item.RawData.(commands.Command)
|
||||||
|
if command.Custom {
|
||||||
|
m.SetValue("/" + command.PrimaryTrigger() + " ")
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
updated, cmd := m.Clear()
|
updated, cmd := m.Clear()
|
||||||
m = updated.(*editorComponent)
|
m = updated.(*editorComponent)
|
||||||
cmds = append(cmds, cmd)
|
cmds = append(cmds, cmd)
|
||||||
|
|
||||||
|
commandName := strings.TrimPrefix(msg.Item.Value, "/")
|
||||||
cmds = append(cmds, util.CmdHandler(commands.ExecuteCommandMsg(m.app.Commands[commands.CommandName(commandName)])))
|
cmds = append(cmds, util.CmdHandler(commands.ExecuteCommandMsg(m.app.Commands[commands.CommandName(commandName)])))
|
||||||
return m, tea.Batch(cmds...)
|
return m, tea.Batch(cmds...)
|
||||||
case "files":
|
case "files":
|
||||||
@@ -339,6 +346,7 @@ func (m *editorComponent) Content() string {
|
|||||||
t := theme.CurrentTheme()
|
t := theme.CurrentTheme()
|
||||||
base := styles.NewStyle().Foreground(t.Text()).Background(t.Background()).Render
|
base := styles.NewStyle().Foreground(t.Text()).Background(t.Background()).Render
|
||||||
muted := styles.NewStyle().Foreground(t.TextMuted()).Background(t.Background()).Render
|
muted := styles.NewStyle().Foreground(t.TextMuted()).Background(t.Background()).Render
|
||||||
|
|
||||||
promptStyle := styles.NewStyle().Foreground(t.Primary()).
|
promptStyle := styles.NewStyle().Foreground(t.Primary()).
|
||||||
Padding(0, 0, 0, 1).
|
Padding(0, 0, 0, 1).
|
||||||
Bold(true)
|
Bold(true)
|
||||||
@@ -480,6 +488,25 @@ func (m *editorComponent) Submit() (tea.Model, tea.Cmd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var cmds []tea.Cmd
|
var cmds []tea.Cmd
|
||||||
|
if strings.HasPrefix(value, "/") {
|
||||||
|
value = value[1:]
|
||||||
|
commandName := strings.Split(value, " ")[0]
|
||||||
|
command := m.app.Commands[commands.CommandName(commandName)]
|
||||||
|
if command.Custom {
|
||||||
|
args := strings.TrimPrefix(value, command.PrimaryTrigger()+" ")
|
||||||
|
cmds = append(
|
||||||
|
cmds,
|
||||||
|
util.CmdHandler(app.SendCommand{Command: string(command.Name), Args: args}),
|
||||||
|
)
|
||||||
|
|
||||||
|
updated, cmd := m.Clear()
|
||||||
|
m = updated.(*editorComponent)
|
||||||
|
cmds = append(cmds, cmd)
|
||||||
|
|
||||||
|
return m, tea.Batch(cmds...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
attachments := m.textarea.GetAttachments()
|
attachments := m.textarea.GetAttachments()
|
||||||
|
|
||||||
prompt := app.Prompt{Text: value, Attachments: attachments}
|
prompt := app.Prompt{Text: value, Attachments: attachments}
|
||||||
|
|||||||
@@ -213,6 +213,7 @@ func renderText(
|
|||||||
extra string,
|
extra string,
|
||||||
isThinking bool,
|
isThinking bool,
|
||||||
isQueued bool,
|
isQueued bool,
|
||||||
|
shimmer bool,
|
||||||
fileParts []opencode.FilePart,
|
fileParts []opencode.FilePart,
|
||||||
agentParts []opencode.AgentPart,
|
agentParts []opencode.AgentPart,
|
||||||
toolCalls ...opencode.ToolPart,
|
toolCalls ...opencode.ToolPart,
|
||||||
@@ -234,7 +235,18 @@ func renderText(
|
|||||||
}
|
}
|
||||||
content = util.ToMarkdown(text, width, backgroundColor)
|
content = util.ToMarkdown(text, width, backgroundColor)
|
||||||
if isThinking {
|
if isThinking {
|
||||||
content = styles.NewStyle().Background(backgroundColor).Foreground(t.TextMuted()).Render("Thinking") + "\n\n" + content
|
var label string
|
||||||
|
if shimmer {
|
||||||
|
label = util.Shimmer("Thinking...", backgroundColor, t.TextMuted(), t.Accent())
|
||||||
|
} else {
|
||||||
|
label = styles.NewStyle().Background(backgroundColor).Foreground(t.TextMuted()).Render("Thinking...")
|
||||||
|
}
|
||||||
|
label = styles.NewStyle().Background(backgroundColor).Width(width - 6).Render(label)
|
||||||
|
content = label + "\n\n" + content
|
||||||
|
} else if strings.TrimSpace(text) == "Generating..." {
|
||||||
|
label := util.Shimmer(text, backgroundColor, t.TextMuted(), t.Text())
|
||||||
|
label = styles.NewStyle().Background(backgroundColor).Width(width - 6).Render(label)
|
||||||
|
content = label
|
||||||
}
|
}
|
||||||
case opencode.UserMessage:
|
case opencode.UserMessage:
|
||||||
ts = time.UnixMilli(int64(casted.Time.Created))
|
ts = time.UnixMilli(int64(casted.Time.Created))
|
||||||
@@ -659,10 +671,22 @@ func renderToolDetails(
|
|||||||
body = strings.Join(steps, "\n")
|
body = strings.Join(steps, "\n")
|
||||||
|
|
||||||
body += "\n\n"
|
body += "\n\n"
|
||||||
body += baseStyle(app.Keybind(commands.SessionChildCycleCommand)) +
|
|
||||||
mutedStyle(", ") +
|
// Build navigation hint with proper spacing
|
||||||
baseStyle(app.Keybind(commands.SessionChildCycleReverseCommand)) +
|
cycleKeybind := app.Keybind(commands.SessionChildCycleCommand)
|
||||||
mutedStyle(" navigate child sessions")
|
cycleReverseKeybind := app.Keybind(commands.SessionChildCycleReverseCommand)
|
||||||
|
|
||||||
|
var navParts []string
|
||||||
|
if cycleKeybind != "" {
|
||||||
|
navParts = append(navParts, baseStyle(cycleKeybind))
|
||||||
|
}
|
||||||
|
if cycleReverseKeybind != "" {
|
||||||
|
navParts = append(navParts, baseStyle(cycleReverseKeybind))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(navParts) > 0 {
|
||||||
|
body += strings.Join(navParts, mutedStyle(", ")) + mutedStyle(" navigate child sessions")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
body = defaultStyle(body)
|
body = defaultStyle(body)
|
||||||
default:
|
default:
|
||||||
@@ -779,7 +803,9 @@ func renderToolTitle(
|
|||||||
) string {
|
) string {
|
||||||
if toolCall.State.Status == opencode.ToolPartStateStatusPending {
|
if toolCall.State.Status == opencode.ToolPartStateStatusPending {
|
||||||
title := renderToolAction(toolCall.Tool)
|
title := renderToolAction(toolCall.Tool)
|
||||||
return styles.NewStyle().Width(width - 6).Render(title)
|
t := theme.CurrentTheme()
|
||||||
|
shiny := util.Shimmer(title, t.BackgroundPanel(), t.TextMuted(), t.Accent())
|
||||||
|
return styles.NewStyle().Background(t.BackgroundPanel()).Width(width - 6).Render(shiny)
|
||||||
}
|
}
|
||||||
|
|
||||||
toolArgs := ""
|
toolArgs := ""
|
||||||
@@ -895,7 +921,9 @@ func renderArgs(args *map[string]any, titleKey string) string {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if key == "filePath" || key == "path" {
|
if key == "filePath" || key == "path" {
|
||||||
value = util.Relative(value.(string))
|
if strValue, ok := value.(string); ok {
|
||||||
|
value = util.Relative(strValue)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if key == titleKey {
|
if key == titleKey {
|
||||||
title = fmt.Sprintf("%s", value)
|
title = fmt.Sprintf("%s", value)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
tea "github.com/charmbracelet/bubbletea/v2"
|
tea "github.com/charmbracelet/bubbletea/v2"
|
||||||
"github.com/charmbracelet/lipgloss/v2"
|
"github.com/charmbracelet/lipgloss/v2"
|
||||||
@@ -59,6 +60,7 @@ type messagesComponent struct {
|
|||||||
lineCount int
|
lineCount int
|
||||||
selection *selection
|
selection *selection
|
||||||
messagePositions map[string]int // map message ID to line position
|
messagePositions map[string]int // map message ID to line position
|
||||||
|
animating bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type selection struct {
|
type selection struct {
|
||||||
@@ -99,6 +101,7 @@ func (s selection) coords(offset int) *selection {
|
|||||||
|
|
||||||
type ToggleToolDetailsMsg struct{}
|
type ToggleToolDetailsMsg struct{}
|
||||||
type ToggleThinkingBlocksMsg struct{}
|
type ToggleThinkingBlocksMsg struct{}
|
||||||
|
type shimmerTickMsg struct{}
|
||||||
|
|
||||||
func (m *messagesComponent) Init() tea.Cmd {
|
func (m *messagesComponent) Init() tea.Cmd {
|
||||||
return tea.Batch(m.viewport.Init())
|
return tea.Batch(m.viewport.Init())
|
||||||
@@ -107,6 +110,15 @@ func (m *messagesComponent) Init() tea.Cmd {
|
|||||||
func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
var cmds []tea.Cmd
|
var cmds []tea.Cmd
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
|
case shimmerTickMsg:
|
||||||
|
if !m.app.HasAnimatingWork() {
|
||||||
|
m.animating = false
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
return m, tea.Sequence(
|
||||||
|
m.renderView(),
|
||||||
|
tea.Tick(90*time.Millisecond, func(t time.Time) tea.Msg { return shimmerTickMsg{} }),
|
||||||
|
)
|
||||||
case tea.MouseClickMsg:
|
case tea.MouseClickMsg:
|
||||||
slog.Info("mouse", "x", msg.X, "y", msg.Y, "offset", m.viewport.YOffset)
|
slog.Info("mouse", "x", msg.X, "y", msg.Y, "offset", m.viewport.YOffset)
|
||||||
y := msg.Y + m.viewport.YOffset
|
y := msg.Y + m.viewport.YOffset
|
||||||
@@ -162,6 +174,10 @@ func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
m.viewport.GotoBottom()
|
m.viewport.GotoBottom()
|
||||||
m.tail = true
|
m.tail = true
|
||||||
return m, nil
|
return m, nil
|
||||||
|
case app.SendCommand:
|
||||||
|
m.viewport.GotoBottom()
|
||||||
|
m.tail = true
|
||||||
|
return m, nil
|
||||||
case dialog.ThemeSelectedMsg:
|
case dialog.ThemeSelectedMsg:
|
||||||
m.cache.Clear()
|
m.cache.Clear()
|
||||||
m.loading = true
|
m.loading = true
|
||||||
@@ -270,6 +286,12 @@ func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
if m.dirty {
|
if m.dirty {
|
||||||
cmds = append(cmds, m.renderView())
|
cmds = append(cmds, m.renderView())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start shimmer ticks if any assistant/tool is in-flight
|
||||||
|
if !m.animating && m.app.HasAnimatingWork() {
|
||||||
|
m.animating = true
|
||||||
|
cmds = append(cmds, tea.Tick(90*time.Millisecond, func(t time.Time) tea.Msg { return shimmerTickMsg{} }))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m.tail = m.viewport.AtBottom()
|
m.tail = m.viewport.AtBottom()
|
||||||
@@ -318,6 +340,25 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
|||||||
|
|
||||||
width := m.width // always use full width
|
width := m.width // always use full width
|
||||||
|
|
||||||
|
// Find the last streaming ReasoningPart to only shimmer that one
|
||||||
|
lastStreamingReasoningID := ""
|
||||||
|
if m.showThinkingBlocks {
|
||||||
|
for mi := len(m.app.Messages) - 1; mi >= 0 && lastStreamingReasoningID == ""; mi-- {
|
||||||
|
if _, ok := m.app.Messages[mi].Info.(opencode.AssistantMessage); !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parts := m.app.Messages[mi].Parts
|
||||||
|
for pi := len(parts) - 1; pi >= 0; pi-- {
|
||||||
|
if rp, ok := parts[pi].(opencode.ReasoningPart); ok {
|
||||||
|
if strings.TrimSpace(rp.Text) != "" && rp.Time.End == 0 {
|
||||||
|
lastStreamingReasoningID = rp.ID
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
reverted := false
|
reverted := false
|
||||||
revertedMessageCount := 0
|
revertedMessageCount := 0
|
||||||
revertedToolCount := 0
|
revertedToolCount := 0
|
||||||
@@ -419,6 +460,7 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
|||||||
files,
|
files,
|
||||||
false,
|
false,
|
||||||
isQueued,
|
isQueued,
|
||||||
|
false,
|
||||||
fileParts,
|
fileParts,
|
||||||
agentParts,
|
agentParts,
|
||||||
)
|
)
|
||||||
@@ -495,6 +537,7 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
|||||||
"",
|
"",
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
[]opencode.FilePart{},
|
[]opencode.FilePart{},
|
||||||
[]opencode.AgentPart{},
|
[]opencode.AgentPart{},
|
||||||
toolCallParts...,
|
toolCallParts...,
|
||||||
@@ -512,6 +555,7 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
|||||||
"",
|
"",
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
[]opencode.FilePart{},
|
[]opencode.FilePart{},
|
||||||
[]opencode.AgentPart{},
|
[]opencode.AgentPart{},
|
||||||
toolCallParts...,
|
toolCallParts...,
|
||||||
@@ -582,6 +626,7 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
|||||||
}
|
}
|
||||||
if part.Text != "" {
|
if part.Text != "" {
|
||||||
text := part.Text
|
text := part.Text
|
||||||
|
shimmer := part.Time.End == 0 && part.ID == lastStreamingReasoningID
|
||||||
content = renderText(
|
content = renderText(
|
||||||
m.app,
|
m.app,
|
||||||
message.Info,
|
message.Info,
|
||||||
@@ -592,6 +637,7 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
|||||||
"",
|
"",
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
|
shimmer,
|
||||||
[]opencode.FilePart{},
|
[]opencode.FilePart{},
|
||||||
[]opencode.AgentPart{},
|
[]opencode.AgentPart{},
|
||||||
)
|
)
|
||||||
@@ -626,6 +672,7 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
|||||||
"",
|
"",
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
[]opencode.FilePart{},
|
[]opencode.FilePart{},
|
||||||
[]opencode.AgentPart{},
|
[]opencode.AgentPart{},
|
||||||
)
|
)
|
||||||
@@ -728,16 +775,18 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
|||||||
} else {
|
} else {
|
||||||
for _, part := range response.Parts {
|
for _, part := range response.Parts {
|
||||||
if part.CallID == m.app.CurrentPermission.CallID {
|
if part.CallID == m.app.CurrentPermission.CallID {
|
||||||
content := renderToolDetails(
|
if toolPart, ok := part.AsUnion().(opencode.ToolPart); ok {
|
||||||
m.app,
|
content := renderToolDetails(
|
||||||
part.AsUnion().(opencode.ToolPart),
|
m.app,
|
||||||
m.app.CurrentPermission,
|
toolPart,
|
||||||
width,
|
m.app.CurrentPermission,
|
||||||
)
|
width,
|
||||||
if content != "" {
|
)
|
||||||
partCount++
|
if content != "" {
|
||||||
lineCount += lipgloss.Height(content) + 1
|
partCount++
|
||||||
blocks = append(blocks, content)
|
lineCount += lipgloss.Height(content) + 1
|
||||||
|
blocks = append(blocks, content)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+69
-31
@@ -18,8 +18,8 @@ import (
|
|||||||
"github.com/sst/opencode/internal/util"
|
"github.com/sst/opencode/internal/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NavigationDialog interface for the session navigation dialog
|
// TimelineDialog interface for the session timeline dialog
|
||||||
type NavigationDialog interface {
|
type TimelineDialog interface {
|
||||||
layout.Modal
|
layout.Modal
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,8 +34,8 @@ type RestoreToMessageMsg struct {
|
|||||||
Index int
|
Index int
|
||||||
}
|
}
|
||||||
|
|
||||||
// navigationItem represents a user message in the navigation list
|
// timelineItem represents a user message in the timeline list
|
||||||
type navigationItem struct {
|
type timelineItem struct {
|
||||||
messageID string
|
messageID string
|
||||||
content string
|
content string
|
||||||
timestamp time.Time
|
timestamp time.Time
|
||||||
@@ -43,25 +43,38 @@ type navigationItem struct {
|
|||||||
toolCount int // Number of tools used in this message
|
toolCount int // Number of tools used in this message
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n navigationItem) Render(
|
func (n timelineItem) Render(
|
||||||
selected bool,
|
selected bool,
|
||||||
width int,
|
width int,
|
||||||
isFirstInViewport bool,
|
isFirstInViewport bool,
|
||||||
baseStyle styles.Style,
|
baseStyle styles.Style,
|
||||||
|
isCurrent bool,
|
||||||
) string {
|
) string {
|
||||||
t := theme.CurrentTheme()
|
t := theme.CurrentTheme()
|
||||||
infoStyle := baseStyle.Background(t.BackgroundPanel()).Foreground(t.Info()).Render
|
infoStyle := baseStyle.Background(t.BackgroundPanel()).Foreground(t.Info()).Render
|
||||||
textStyle := baseStyle.Background(t.BackgroundPanel()).Foreground(t.Text()).Render
|
textStyle := baseStyle.Background(t.BackgroundPanel()).Foreground(t.Text()).Render
|
||||||
|
|
||||||
|
// Add dot after timestamp if this is the current message - only apply color when not selected
|
||||||
|
var dot string
|
||||||
|
var dotVisualLen int
|
||||||
|
if isCurrent {
|
||||||
|
if selected {
|
||||||
|
dot = "● "
|
||||||
|
} else {
|
||||||
|
dot = lipgloss.NewStyle().Foreground(t.Success()).Render("● ")
|
||||||
|
}
|
||||||
|
dotVisualLen = 2 // "● " is 2 characters wide
|
||||||
|
}
|
||||||
|
|
||||||
// Format timestamp - only apply color when not selected
|
// Format timestamp - only apply color when not selected
|
||||||
var timeStr string
|
var timeStr string
|
||||||
var timeVisualLen int
|
var timeVisualLen int
|
||||||
if selected {
|
if selected {
|
||||||
timeStr = n.timestamp.Format("15:04") + " "
|
timeStr = n.timestamp.Format("15:04") + " " + dot
|
||||||
timeVisualLen = lipgloss.Width(timeStr)
|
timeVisualLen = lipgloss.Width(n.timestamp.Format("15:04")+" ") + dotVisualLen
|
||||||
} else {
|
} else {
|
||||||
timeStr = infoStyle(n.timestamp.Format("15:04") + " ")
|
timeStr = infoStyle(n.timestamp.Format("15:04")+" ") + dot
|
||||||
timeVisualLen = lipgloss.Width(timeStr)
|
timeVisualLen = lipgloss.Width(n.timestamp.Format("15:04")+" ") + dotVisualLen
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tool count display (fixed width for alignment) - only apply color when not selected
|
// Tool count display (fixed width for alignment) - only apply color when not selected
|
||||||
@@ -78,7 +91,7 @@ func (n navigationItem) Render(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Calculate available space for content
|
// Calculate available space for content
|
||||||
// Reserve space for: timestamp + space + toolInfo + padding + some buffer
|
// Reserve space for: timestamp + dot + space + toolInfo + padding + some buffer
|
||||||
reservedSpace := timeVisualLen + 1 + toolInfoVisualLen + 4
|
reservedSpace := timeVisualLen + 1 + toolInfoVisualLen + 4
|
||||||
contentWidth := max(width-reservedSpace, 8)
|
contentWidth := max(width-reservedSpace, 8)
|
||||||
|
|
||||||
@@ -135,23 +148,23 @@ func (n navigationItem) Render(
|
|||||||
return itemStyle.Render(text)
|
return itemStyle.Render(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n navigationItem) Selectable() bool {
|
func (n timelineItem) Selectable() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
type navigationDialog struct {
|
type timelineDialog struct {
|
||||||
width int
|
width int
|
||||||
height int
|
height int
|
||||||
modal *modal.Modal
|
modal *modal.Modal
|
||||||
list list.List[navigationItem]
|
list list.List[timelineItem]
|
||||||
app *app.App
|
app *app.App
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *navigationDialog) Init() tea.Cmd {
|
func (n *timelineDialog) Init() tea.Cmd {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *navigationDialog) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
func (n *timelineDialog) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
case tea.WindowSizeMsg:
|
case tea.WindowSizeMsg:
|
||||||
n.width = msg.Width
|
n.width = msg.Width
|
||||||
@@ -163,7 +176,7 @@ func (n *navigationDialog) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
// Handle navigation and immediately scroll to selected message
|
// Handle navigation and immediately scroll to selected message
|
||||||
var cmd tea.Cmd
|
var cmd tea.Cmd
|
||||||
listModel, cmd := n.list.Update(msg)
|
listModel, cmd := n.list.Update(msg)
|
||||||
n.list = listModel.(list.List[navigationItem])
|
n.list = listModel.(list.List[timelineItem])
|
||||||
|
|
||||||
// Get the newly selected item and scroll to it immediately
|
// Get the newly selected item and scroll to it immediately
|
||||||
if item, idx := n.list.GetSelectedItem(); idx >= 0 {
|
if item, idx := n.list.GetSelectedItem(); idx >= 0 {
|
||||||
@@ -191,11 +204,11 @@ func (n *navigationDialog) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
|
|
||||||
var cmd tea.Cmd
|
var cmd tea.Cmd
|
||||||
listModel, cmd := n.list.Update(msg)
|
listModel, cmd := n.list.Update(msg)
|
||||||
n.list = listModel.(list.List[navigationItem])
|
n.list = listModel.(list.List[timelineItem])
|
||||||
return n, cmd
|
return n, cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *navigationDialog) Render(background string) string {
|
func (n *timelineDialog) Render(background string) string {
|
||||||
listView := n.list.View()
|
listView := n.list.View()
|
||||||
|
|
||||||
t := theme.CurrentTheme()
|
t := theme.CurrentTheme()
|
||||||
@@ -229,7 +242,7 @@ func (n *navigationDialog) Render(background string) string {
|
|||||||
return n.modal.Render(content, background)
|
return n.modal.Render(content, background)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *navigationDialog) Close() tea.Cmd {
|
func (n *timelineDialog) Close() tea.Cmd {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,9 +281,9 @@ func countToolsInResponse(messages []app.Message, userMessageIndex int) int {
|
|||||||
return count
|
return count
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewNavigationDialog creates a new session navigation dialog
|
// NewTimelineDialog creates a new session timeline dialog
|
||||||
func NewNavigationDialog(app *app.App) NavigationDialog {
|
func NewTimelineDialog(app *app.App) TimelineDialog { // renamed from NewNavigationDialog
|
||||||
var items []navigationItem
|
var items []timelineItem
|
||||||
|
|
||||||
// Filter to only user messages and extract relevant info
|
// Filter to only user messages and extract relevant info
|
||||||
for i, message := range app.Messages {
|
for i, message := range app.Messages {
|
||||||
@@ -278,7 +291,7 @@ func NewNavigationDialog(app *app.App) NavigationDialog {
|
|||||||
preview := extractMessagePreview(message.Parts)
|
preview := extractMessagePreview(message.Parts)
|
||||||
toolCount := countToolsInResponse(app.Messages, i)
|
toolCount := countToolsInResponse(app.Messages, i)
|
||||||
|
|
||||||
items = append(items, navigationItem{
|
items = append(items, timelineItem{
|
||||||
messageID: userMsg.ID,
|
messageID: userMsg.ID,
|
||||||
content: preview,
|
content: preview,
|
||||||
timestamp: time.UnixMilli(int64(userMsg.Time.Created)),
|
timestamp: time.UnixMilli(int64(userMsg.Time.Created)),
|
||||||
@@ -290,25 +303,50 @@ func NewNavigationDialog(app *app.App) NavigationDialog {
|
|||||||
|
|
||||||
listComponent := list.NewListComponent(
|
listComponent := list.NewListComponent(
|
||||||
list.WithItems(items),
|
list.WithItems(items),
|
||||||
list.WithMaxVisibleHeight[navigationItem](12),
|
list.WithMaxVisibleHeight[timelineItem](12),
|
||||||
list.WithFallbackMessage[navigationItem]("No user messages in this session"),
|
list.WithFallbackMessage[timelineItem]("No user messages in this session"),
|
||||||
list.WithAlphaNumericKeys[navigationItem](true),
|
list.WithAlphaNumericKeys[timelineItem](true),
|
||||||
list.WithRenderFunc(
|
list.WithRenderFunc(
|
||||||
func(item navigationItem, selected bool, width int, baseStyle styles.Style) string {
|
func(item timelineItem, selected bool, width int, baseStyle styles.Style) string {
|
||||||
return item.Render(selected, width, false, baseStyle)
|
// Determine if this item is the current message for the session
|
||||||
|
isCurrent := false
|
||||||
|
if app.Session.Revert.MessageID != "" {
|
||||||
|
// When reverted, Session.Revert.MessageID contains the NEXT user message ID
|
||||||
|
// So we need to find the previous user message to highlight the correct one
|
||||||
|
for i, navItem := range items {
|
||||||
|
if navItem.messageID == app.Session.Revert.MessageID && i > 0 {
|
||||||
|
// Found the next message, so the previous one is current
|
||||||
|
isCurrent = item.messageID == items[i-1].messageID
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if len(app.Messages) > 0 {
|
||||||
|
// If not reverted, highlight the last user message
|
||||||
|
lastUserMsgID := ""
|
||||||
|
for i := len(app.Messages) - 1; i >= 0; i-- {
|
||||||
|
if userMsg, ok := app.Messages[i].Info.(opencode.UserMessage); ok {
|
||||||
|
lastUserMsgID = userMsg.ID
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isCurrent = item.messageID == lastUserMsgID
|
||||||
|
}
|
||||||
|
// Only show the dot if undo/redo/restore is available
|
||||||
|
showDot := app.Session.Revert.MessageID != ""
|
||||||
|
return item.Render(selected, width, false, baseStyle, isCurrent && showDot)
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
list.WithSelectableFunc(func(item navigationItem) bool {
|
list.WithSelectableFunc(func(item timelineItem) bool {
|
||||||
return true
|
return true
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
listComponent.SetMaxWidth(layout.Current.Container.Width - 12)
|
listComponent.SetMaxWidth(layout.Current.Container.Width - 12)
|
||||||
|
|
||||||
return &navigationDialog{
|
return &timelineDialog{
|
||||||
list: listComponent,
|
list: listComponent,
|
||||||
app: app,
|
app: app,
|
||||||
modal: modal.New(
|
modal: modal.New(
|
||||||
modal.WithTitle("Jump to Message"),
|
modal.WithTitle("Session Timeline"),
|
||||||
modal.WithMaxWidth(layout.Current.Container.Width-8),
|
modal.WithMaxWidth(layout.Current.Container.Width-8),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
@@ -408,6 +408,24 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
a.app, cmd = a.app.SendPrompt(context.Background(), msg)
|
a.app, cmd = a.app.SendPrompt(context.Background(), msg)
|
||||||
cmds = append(cmds, cmd)
|
cmds = append(cmds, cmd)
|
||||||
}
|
}
|
||||||
|
case app.SendCommand:
|
||||||
|
// If we're in a child session, switch back to parent before sending prompt
|
||||||
|
if a.app.Session.ParentID != "" {
|
||||||
|
parentSession, err := a.app.Client.Session.Get(context.Background(), a.app.Session.ParentID)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to get parent session", "error", err)
|
||||||
|
return a, toast.NewErrorToast("Failed to get parent session")
|
||||||
|
}
|
||||||
|
a.app.Session = parentSession
|
||||||
|
a.app, cmd = a.app.SendCommand(context.Background(), msg.Command, msg.Args)
|
||||||
|
cmds = append(cmds, tea.Sequence(
|
||||||
|
util.CmdHandler(app.SessionSelectedMsg(parentSession)),
|
||||||
|
cmd,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
a.app, cmd = a.app.SendCommand(context.Background(), msg.Command, msg.Args)
|
||||||
|
cmds = append(cmds, cmd)
|
||||||
|
}
|
||||||
case app.SendShell:
|
case app.SendShell:
|
||||||
// If we're in a child session, switch back to parent before sending prompt
|
// If we're in a child session, switch back to parent before sending prompt
|
||||||
if a.app.Session.ParentID != "" {
|
if a.app.Session.ParentID != "" {
|
||||||
@@ -728,8 +746,8 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
case "/tui/open-sessions":
|
case "/tui/open-sessions":
|
||||||
sessionDialog := dialog.NewSessionDialog(a.app)
|
sessionDialog := dialog.NewSessionDialog(a.app)
|
||||||
a.modal = sessionDialog
|
a.modal = sessionDialog
|
||||||
case "/tui/open-navigation":
|
case "/tui/open-timeline":
|
||||||
navigationDialog := dialog.NewNavigationDialog(a.app)
|
navigationDialog := dialog.NewTimelineDialog(a.app)
|
||||||
a.modal = navigationDialog
|
a.modal = navigationDialog
|
||||||
case "/tui/open-themes":
|
case "/tui/open-themes":
|
||||||
themeDialog := dialog.NewThemeDialog()
|
themeDialog := dialog.NewThemeDialog()
|
||||||
@@ -1146,11 +1164,11 @@ func (a Model) executeCommand(command commands.Command) (tea.Model, tea.Cmd) {
|
|||||||
case commands.SessionListCommand:
|
case commands.SessionListCommand:
|
||||||
sessionDialog := dialog.NewSessionDialog(a.app)
|
sessionDialog := dialog.NewSessionDialog(a.app)
|
||||||
a.modal = sessionDialog
|
a.modal = sessionDialog
|
||||||
case commands.SessionNavigationCommand:
|
case commands.SessionTimelineCommand:
|
||||||
if a.app.Session.ID == "" {
|
if a.app.Session.ID == "" {
|
||||||
return a, toast.NewErrorToast("No active session")
|
return a, toast.NewErrorToast("No active session")
|
||||||
}
|
}
|
||||||
navigationDialog := dialog.NewNavigationDialog(a.app)
|
navigationDialog := dialog.NewTimelineDialog(a.app)
|
||||||
a.modal = navigationDialog
|
a.modal = navigationDialog
|
||||||
case commands.SessionShareCommand:
|
case commands.SessionShareCommand:
|
||||||
if a.app.Session.ID == "" {
|
if a.app.Session.ID == "" {
|
||||||
|
|||||||
@@ -2,12 +2,31 @@ package util
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
opencode "github.com/sst/opencode-sdk-go"
|
opencode "github.com/sst/opencode-sdk-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func sanitizeValue(val any) any {
|
||||||
|
if val == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err, ok := val.(error); ok {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
v := reflect.ValueOf(val)
|
||||||
|
if v.Kind() == reflect.Interface && !v.IsNil() {
|
||||||
|
return fmt.Sprintf("%T", val)
|
||||||
|
}
|
||||||
|
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
type APILogHandler struct {
|
type APILogHandler struct {
|
||||||
client *opencode.Client
|
client *opencode.Client
|
||||||
service string
|
service string
|
||||||
@@ -67,21 +86,13 @@ func (h *APILogHandler) Handle(ctx context.Context, r slog.Record) error {
|
|||||||
h.mu.Lock()
|
h.mu.Lock()
|
||||||
for _, attr := range h.attrs {
|
for _, attr := range h.attrs {
|
||||||
val := attr.Value.Any()
|
val := attr.Value.Any()
|
||||||
if err, ok := val.(error); ok {
|
extra[attr.Key] = sanitizeValue(val)
|
||||||
extra[attr.Key] = err.Error()
|
|
||||||
} else {
|
|
||||||
extra[attr.Key] = val
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
h.mu.Unlock()
|
h.mu.Unlock()
|
||||||
|
|
||||||
r.Attrs(func(attr slog.Attr) bool {
|
r.Attrs(func(attr slog.Attr) bool {
|
||||||
val := attr.Value.Any()
|
val := attr.Value.Any()
|
||||||
if err, ok := val.(error); ok {
|
extra[attr.Key] = sanitizeValue(val)
|
||||||
extra[attr.Key] = err.Error()
|
|
||||||
} else {
|
|
||||||
extra[attr.Key] = val
|
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,143 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/lipgloss/v2"
|
||||||
|
"github.com/charmbracelet/lipgloss/v2/compat"
|
||||||
|
"github.com/sst/opencode/internal/styles"
|
||||||
|
)
|
||||||
|
|
||||||
|
var shimmerStart = time.Now()
|
||||||
|
|
||||||
|
// Shimmer renders text with a moving foreground highlight.
|
||||||
|
// bg is the background color, dim is the base text color, bright is the highlight color.
|
||||||
|
func Shimmer(s string, bg compat.AdaptiveColor, _ compat.AdaptiveColor, _ compat.AdaptiveColor) string {
|
||||||
|
if s == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
runes := []rune(s)
|
||||||
|
n := len(runes)
|
||||||
|
if n == 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
pad := 10
|
||||||
|
period := float64(n + pad*2)
|
||||||
|
sweep := 2.5
|
||||||
|
elapsed := time.Since(shimmerStart).Seconds()
|
||||||
|
pos := (math.Mod(elapsed, sweep) / sweep) * period
|
||||||
|
|
||||||
|
half := 4.0
|
||||||
|
|
||||||
|
type seg struct {
|
||||||
|
useHex bool
|
||||||
|
hex string
|
||||||
|
bold bool
|
||||||
|
faint bool
|
||||||
|
text string
|
||||||
|
}
|
||||||
|
var segs []seg
|
||||||
|
|
||||||
|
useHex := hasTrueColor()
|
||||||
|
for i, r := range runes {
|
||||||
|
ip := float64(i + pad)
|
||||||
|
dist := math.Abs(ip - pos)
|
||||||
|
t := 0.0
|
||||||
|
if dist <= half {
|
||||||
|
x := math.Pi * (dist / half)
|
||||||
|
t = 0.5 * (1.0 + math.Cos(x))
|
||||||
|
}
|
||||||
|
// Cosine brightness: base + amp*t (quantized for grouping)
|
||||||
|
base := 0.55
|
||||||
|
amp := 0.45
|
||||||
|
brightness := base
|
||||||
|
if t > 0 {
|
||||||
|
brightness = base + amp*t
|
||||||
|
}
|
||||||
|
lvl := int(math.Round(brightness * 255.0))
|
||||||
|
if !useHex {
|
||||||
|
step := 24 // ~11 steps across range for non-truecolor
|
||||||
|
lvl = int(math.Round(float64(lvl)/float64(step))) * step
|
||||||
|
}
|
||||||
|
|
||||||
|
bold := lvl >= 208
|
||||||
|
faint := lvl <= 128
|
||||||
|
|
||||||
|
// truecolor if possible; else fallback to modifiers only
|
||||||
|
hex := ""
|
||||||
|
if useHex {
|
||||||
|
if lvl < 0 {
|
||||||
|
lvl = 0
|
||||||
|
}
|
||||||
|
if lvl > 255 {
|
||||||
|
lvl = 255
|
||||||
|
}
|
||||||
|
hex = rgbHex(lvl, lvl, lvl)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(segs) == 0 {
|
||||||
|
segs = append(segs, seg{useHex: useHex, hex: hex, bold: bold, faint: faint, text: string(r)})
|
||||||
|
} else {
|
||||||
|
last := &segs[len(segs)-1]
|
||||||
|
if last.useHex == useHex && last.hex == hex && last.bold == bold && last.faint == faint {
|
||||||
|
last.text += string(r)
|
||||||
|
} else {
|
||||||
|
segs = append(segs, seg{useHex: useHex, hex: hex, bold: bold, faint: faint, text: string(r)})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var b strings.Builder
|
||||||
|
for _, g := range segs {
|
||||||
|
st := styles.NewStyle().Background(bg)
|
||||||
|
if g.useHex && g.hex != "" {
|
||||||
|
c := compat.AdaptiveColor{Dark: lipgloss.Color(g.hex), Light: lipgloss.Color(g.hex)}
|
||||||
|
st = st.Foreground(c)
|
||||||
|
}
|
||||||
|
if g.bold {
|
||||||
|
st = st.Bold(true)
|
||||||
|
}
|
||||||
|
if g.faint {
|
||||||
|
st = st.Faint(true)
|
||||||
|
}
|
||||||
|
b.WriteString(st.Render(g.text))
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasTrueColor() bool {
|
||||||
|
c := strings.ToLower(os.Getenv("COLORTERM"))
|
||||||
|
return strings.Contains(c, "truecolor") || strings.Contains(c, "24bit")
|
||||||
|
}
|
||||||
|
|
||||||
|
func rgbHex(r, g, b int) string {
|
||||||
|
if r < 0 {
|
||||||
|
r = 0
|
||||||
|
}
|
||||||
|
if r > 255 {
|
||||||
|
r = 255
|
||||||
|
}
|
||||||
|
if g < 0 {
|
||||||
|
g = 0
|
||||||
|
}
|
||||||
|
if g > 255 {
|
||||||
|
g = 255
|
||||||
|
}
|
||||||
|
if b < 0 {
|
||||||
|
b = 0
|
||||||
|
}
|
||||||
|
if b > 255 {
|
||||||
|
b = 255
|
||||||
|
}
|
||||||
|
return "#" + hex2(r) + hex2(g) + hex2(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func hex2(v int) string {
|
||||||
|
const digits = "0123456789abcdef"
|
||||||
|
return string([]byte{digits[(v>>4)&0xF], digits[v&0xF]})
|
||||||
|
}
|
||||||
@@ -9,8 +9,6 @@ import { rehypeHeadingIds } from "@astrojs/markdown-remark"
|
|||||||
import rehypeAutolinkHeadings from "rehype-autolink-headings"
|
import rehypeAutolinkHeadings from "rehype-autolink-headings"
|
||||||
import { spawnSync } from "child_process"
|
import { spawnSync } from "child_process"
|
||||||
|
|
||||||
const github = "https://github.com/sst/opencode"
|
|
||||||
|
|
||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
site: config.url,
|
site: config.url,
|
||||||
@@ -49,7 +47,7 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
editLink: {
|
editLink: {
|
||||||
baseUrl: `${github}/edit/dev/packages/web/`,
|
baseUrl: `${config.github}/edit/dev/packages/web/`,
|
||||||
},
|
},
|
||||||
markdown: {
|
markdown: {
|
||||||
headingLinks: false,
|
headingLinks: false,
|
||||||
@@ -69,7 +67,7 @@ export default defineConfig({
|
|||||||
|
|
||||||
{
|
{
|
||||||
label: "Usage",
|
label: "Usage",
|
||||||
items: ["docs/cli", "docs/ide", "docs/share", "docs/github"],
|
items: ["docs/tui", "docs/cli", "docs/ide", "docs/share", "docs/github", "docs/gitlab"],
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -79,14 +77,19 @@ export default defineConfig({
|
|||||||
"docs/agents",
|
"docs/agents",
|
||||||
"docs/models",
|
"docs/models",
|
||||||
"docs/themes",
|
"docs/themes",
|
||||||
"docs/plugins",
|
|
||||||
"docs/keybinds",
|
"docs/keybinds",
|
||||||
|
"docs/commands",
|
||||||
"docs/formatters",
|
"docs/formatters",
|
||||||
"docs/permissions",
|
"docs/permissions",
|
||||||
"docs/lsp",
|
"docs/lsp",
|
||||||
"docs/mcp-servers",
|
"docs/mcp-servers",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
label: "Develop",
|
||||||
|
items: ["docs/sdk", "docs/server", "docs/plugins"],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
components: {
|
components: {
|
||||||
Hero: "./src/components/Hero.astro",
|
Hero: "./src/components/Hero.astro",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode/web",
|
"name": "@opencode/web",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.5.5",
|
"version": "0.5.18",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "astro dev",
|
"dev": "astro dev",
|
||||||
"dev:remote": "sst shell --stage=dev --target=Web astro dev",
|
"dev:remote": "sst shell --stage=dev --target=Web astro dev",
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
"sharp": "0.32.5",
|
"sharp": "0.32.5",
|
||||||
"shiki": "3.4.2",
|
"shiki": "3.4.2",
|
||||||
"solid-js": "1.9.7",
|
"solid-js": "1.9.7",
|
||||||
"toolbeam-docs-theme": "0.4.3"
|
"toolbeam-docs-theme": "0.4.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"opencode": "workspace:*",
|
"opencode": "workspace:*",
|
||||||
|
|||||||
@@ -179,8 +179,6 @@ Provide constructive feedback without making direct changes.
|
|||||||
|
|
||||||
The markdown file name becomes the agent name. For example, `review.md` creates a `review` agent.
|
The markdown file name becomes the agent name. For example, `review.md` creates a `review` agent.
|
||||||
|
|
||||||
Let's look at these configuration options in detail.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Options
|
## Options
|
||||||
@@ -535,7 +533,7 @@ For example, with OpenAI's reasoning models, you can control the reasoning effor
|
|||||||
"agent": {
|
"agent": {
|
||||||
"deep-thinker": {
|
"deep-thinker": {
|
||||||
"description": "Agent that uses high reasoning effort for complex problems",
|
"description": "Agent that uses high reasoning effort for complex problems",
|
||||||
"model": "openai/gpt-5-turbo",
|
"model": "openai/gpt-5",
|
||||||
"reasoningEffort": "high",
|
"reasoningEffort": "high",
|
||||||
"textVerbosity": "low"
|
"textVerbosity": "low"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,136 +1,23 @@
|
|||||||
---
|
---
|
||||||
title: CLI
|
title: CLI
|
||||||
description: The opencode CLI options and commands.
|
description: opencode CLI options and commands.
|
||||||
---
|
---
|
||||||
|
|
||||||
import { Tabs, TabItem } from "@astrojs/starlight/components"
|
import { Tabs, TabItem } from "@astrojs/starlight/components"
|
||||||
|
|
||||||
Running the opencode CLI starts it for the current directory.
|
The opencode CLI by default starts the [TUI](/docs/tui) when run without any arguments.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
opencode
|
opencode
|
||||||
```
|
```
|
||||||
|
|
||||||
Or you can start it for a specific working directory.
|
But it also accepts commands as documented on this page. This allows you to interact with opencode programmatically.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
opencode /path/to/project
|
opencode run "Explain how closures work in JavaScript"
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Slash commands
|
|
||||||
|
|
||||||
When using the opencode CLI, you can type `/` followed by a command name to quickly execute actions. For example:
|
|
||||||
|
|
||||||
:::tip
|
|
||||||
You can also use `@` to reference files in your messages.
|
|
||||||
:::
|
|
||||||
|
|
||||||
```bash frame="none"
|
|
||||||
/help
|
|
||||||
```
|
|
||||||
|
|
||||||
Here are all available slash commands:
|
|
||||||
|
|
||||||
- **`/help`**
|
|
||||||
|
|
||||||
Show the help dialog.
|
|
||||||
|
|
||||||
- **`/editor`**
|
|
||||||
|
|
||||||
Open external editor for composing messages. Uses the editor set in your `EDITOR` environment variable. [Learn more](#editor-setup).
|
|
||||||
|
|
||||||
- **`/export`**
|
|
||||||
|
|
||||||
Export current conversation to Markdown and open in your default editor. Uses the editor set in your `EDITOR` environment variable. [Learn more](#editor-setup).
|
|
||||||
|
|
||||||
- **`/new`**
|
|
||||||
|
|
||||||
Start a new session. _Alias_: `/clear`
|
|
||||||
|
|
||||||
- **`/sessions`**
|
|
||||||
|
|
||||||
List and switch between sessions. _Aliases_: `/resume`, `/continue`
|
|
||||||
|
|
||||||
- **`/share`**
|
|
||||||
|
|
||||||
Share current session. [Learn more](/docs/share).
|
|
||||||
|
|
||||||
- **`/unshare`**
|
|
||||||
|
|
||||||
Unshare current session. [Learn more](/docs/share#un-sharing).
|
|
||||||
|
|
||||||
- **`/compact`**
|
|
||||||
|
|
||||||
Compact the current session. _Alias_: `/summarize`
|
|
||||||
|
|
||||||
- **`/details`**
|
|
||||||
|
|
||||||
Toggle tool execution details.
|
|
||||||
|
|
||||||
- **`/models`**
|
|
||||||
|
|
||||||
List available models.
|
|
||||||
|
|
||||||
- **`/themes`**
|
|
||||||
|
|
||||||
List available themes.
|
|
||||||
|
|
||||||
- **`/init`**
|
|
||||||
|
|
||||||
Create or update `AGENTS.md` file. [Learn more](/docs/rules).
|
|
||||||
|
|
||||||
- **`/undo`**
|
|
||||||
|
|
||||||
Undo last message.
|
|
||||||
|
|
||||||
- **`/redo`**
|
|
||||||
|
|
||||||
Redo message.
|
|
||||||
|
|
||||||
- **`/exit`**
|
|
||||||
|
|
||||||
Exit opencode. _Aliases_: `/quit`, `/q`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Bash commands
|
|
||||||
|
|
||||||
Start a line with `!` to run a shell command. The output is added to the conversation as a tool result.
|
|
||||||
|
|
||||||
```bash frame="none"
|
|
||||||
!ls -la
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Editor setup
|
|
||||||
|
|
||||||
Both the `/editor` and `/export` commands use the editor specified in your `EDITOR` environment variable.
|
|
||||||
|
|
||||||
<Tabs>
|
|
||||||
<TabItem label="Linux/macOS">
|
|
||||||
```bash export EDITOR=nano # or vim, code, etc. ``` To make it permanent, add this to your shell profile;
|
|
||||||
`~/.bashrc`, `~/.zshrc`, etc.
|
|
||||||
</TabItem>
|
|
||||||
<TabItem label="Windows (CMD)">
|
|
||||||
```bash set EDITOR=notepad # or code, vim, etc. ``` To make it permanent, use **System Properties** > **Environment
|
|
||||||
Variables**.
|
|
||||||
</TabItem>
|
|
||||||
<TabItem label="Windows (PowerShell)">
|
|
||||||
```bash $env:EDITOR = "notepad" # or "code", "vim", etc. ``` To make it permanent, add this to your PowerShell
|
|
||||||
profile.
|
|
||||||
</TabItem>
|
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
Popular editor options include:
|
|
||||||
|
|
||||||
- `code` - Visual Studio Code
|
|
||||||
- `vim` - Vim editor
|
|
||||||
- `nano` - Nano editor
|
|
||||||
- `notepad` - Windows Notepad
|
|
||||||
- `subl` - Sublime Text
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -289,7 +176,7 @@ opencode run Explain the use of context in Go
|
|||||||
|
|
||||||
### serve
|
### serve
|
||||||
|
|
||||||
Start a headless opencode server for API access.
|
Start a headless opencode server for API access. Check out the [server docs](/docs/server) for the full HTTP interface.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
opencode serve
|
opencode serve
|
||||||
|
|||||||
@@ -0,0 +1,156 @@
|
|||||||
|
---
|
||||||
|
title: Commands
|
||||||
|
description: Create custom commands for repetitive tasks.
|
||||||
|
---
|
||||||
|
|
||||||
|
Define custom commands to automate repetitive coding tasks.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Create command files
|
||||||
|
|
||||||
|
Create markdown files in the `command/` directory to define custom commands.
|
||||||
|
|
||||||
|
Create `.opencode/command/test.md`:
|
||||||
|
|
||||||
|
```md title=".opencode/command/test.md"
|
||||||
|
---
|
||||||
|
description: Run tests with coverage
|
||||||
|
agent: build
|
||||||
|
model: anthropic/claude-3-5-sonnet-20241022
|
||||||
|
---
|
||||||
|
|
||||||
|
Run the full test suite with coverage report and show any failures.
|
||||||
|
Focus on the failing tests and suggest fixes.
|
||||||
|
```
|
||||||
|
|
||||||
|
The frontmatter defines command properties. The content becomes the template.
|
||||||
|
|
||||||
|
Use the command by typing `/` followed by the command name.
|
||||||
|
|
||||||
|
```bash frame="none"
|
||||||
|
"/test"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Use arguments
|
||||||
|
|
||||||
|
Pass arguments to commands using the `$ARGUMENTS` placeholder.
|
||||||
|
|
||||||
|
Create `.opencode/command/component.md`:
|
||||||
|
|
||||||
|
```md title=".opencode/command/component.md"
|
||||||
|
---
|
||||||
|
description: Create a new component
|
||||||
|
---
|
||||||
|
|
||||||
|
Create a new React component named $ARGUMENTS with TypeScript support.
|
||||||
|
Include proper typing and basic structure.
|
||||||
|
```
|
||||||
|
|
||||||
|
Run the command with arguments:
|
||||||
|
|
||||||
|
```bash frame="none"
|
||||||
|
"/component Button"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Inject shell output
|
||||||
|
|
||||||
|
Use `!command` to inject shell command output into your prompt.
|
||||||
|
|
||||||
|
Create `.opencode/command/analyze-coverage.md`:
|
||||||
|
|
||||||
|
```md title=".opencode/command/analyze-coverage.md"
|
||||||
|
---
|
||||||
|
description: Analyze test coverage
|
||||||
|
---
|
||||||
|
|
||||||
|
Here are the current test results:
|
||||||
|
`!npm test`
|
||||||
|
|
||||||
|
Based on these results, suggest improvements to increase coverage.
|
||||||
|
```
|
||||||
|
|
||||||
|
Create `.opencode/command/review-changes.md`:
|
||||||
|
|
||||||
|
```md title=".opencode/command/review-changes.md"
|
||||||
|
---
|
||||||
|
description: Review recent changes
|
||||||
|
---
|
||||||
|
|
||||||
|
Recent git commits:
|
||||||
|
`!git log --oneline -10`
|
||||||
|
|
||||||
|
Review these changes and suggest any improvements.
|
||||||
|
```
|
||||||
|
|
||||||
|
Commands run in your project's root directory and their output becomes part of the prompt.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Reference files
|
||||||
|
|
||||||
|
Include files in your command using `@` followed by the filename.
|
||||||
|
|
||||||
|
Create `.opencode/command/review-component.md`:
|
||||||
|
|
||||||
|
```md title=".opencode/command/review-component.md"
|
||||||
|
---
|
||||||
|
description: Review component
|
||||||
|
---
|
||||||
|
|
||||||
|
Review the component in @src/components/Button.tsx.
|
||||||
|
Check for performance issues and suggest improvements.
|
||||||
|
```
|
||||||
|
|
||||||
|
The file content gets included in the prompt automatically.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Command properties
|
||||||
|
|
||||||
|
Configure commands with these optional frontmatter properties:
|
||||||
|
|
||||||
|
- **description**: Brief explanation of what the command does
|
||||||
|
- **agent**: Agent to use (defaults to "build")
|
||||||
|
- **model**: Specific model to use for this command
|
||||||
|
|
||||||
|
Create `.opencode/command/code-review.md`:
|
||||||
|
|
||||||
|
```md title=".opencode/command/code-review.md"
|
||||||
|
---
|
||||||
|
description: Code review assistant
|
||||||
|
agent: build
|
||||||
|
model: anthropic/claude-3-5-sonnet-20241022
|
||||||
|
---
|
||||||
|
|
||||||
|
Review the code for best practices and suggest improvements.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Command directory
|
||||||
|
|
||||||
|
Store command files in these locations:
|
||||||
|
|
||||||
|
- `.opencode/command/` - Project-specific commands
|
||||||
|
- `command/` - Global commands in config directory
|
||||||
|
|
||||||
|
Project commands take precedence over global ones.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Built-in commands
|
||||||
|
|
||||||
|
opencode includes several built-in commands:
|
||||||
|
|
||||||
|
- `/init` - Initialize project and create AGENTS.md
|
||||||
|
- `/undo` - Revert the last changes
|
||||||
|
- `/redo` - Restore reverted changes
|
||||||
|
- `/share` - Share the current conversation
|
||||||
|
- `/help` - Show available commands and keybinds
|
||||||
|
|
||||||
|
Use `/help` to see all available commands in your setup.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: GitHub
|
title: GitHub
|
||||||
description: Use opencode in GitHub issues and pull-requests
|
description: Use opencode in GitHub issues and pull-requests.
|
||||||
---
|
---
|
||||||
|
|
||||||
opencode integrates with your GitHub workflow. Mention `/opencode` or `/oc` in your comment, and opencode will execute tasks within your GitHub Actions runner.
|
opencode integrates with your GitHub workflow. Mention `/opencode` or `/oc` in your comment, and opencode will execute tasks within your GitHub Actions runner.
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
---
|
||||||
|
title: GitLab
|
||||||
|
description: Use opencode in GitLab issues and merge requests.
|
||||||
|
---
|
||||||
|
|
||||||
|
opencode integrates with your GitLab workflow.
|
||||||
|
Mention `@opencode` in a comment, and opencode will execute tasks within your GitLab CI pipeline.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Triage issues**: Ask opencode to look into an issue and explain it to you.
|
||||||
|
- **Fix and implement**: Ask opencode to fix an issue or implement a feature.
|
||||||
|
It will work create a new branch and raised a merge request with the changes.
|
||||||
|
- **Secure**: opencode runs on your GitLab runners.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
opencode runs in your GitLab CI/CD pipeline, here's what you'll need to set it up:
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
Check out the [**GitLab docs**](https://docs.gitlab.com/user/duo_agent_platform/agent_assistant/) for up to date instructions.
|
||||||
|
:::
|
||||||
|
|
||||||
|
1. Configure your GitLab environment
|
||||||
|
2. Set up CI/CD
|
||||||
|
3. Get an AI model provider API key
|
||||||
|
4. Create a service account
|
||||||
|
5. Configure CI/CD variables
|
||||||
|
6. Create a flow config file, here's an example:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Flow configuration</summary>
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
image: node:22-slim
|
||||||
|
commands:
|
||||||
|
- echo "Installing opencode"
|
||||||
|
- npm install --global opencode-ai
|
||||||
|
- echo "Installing glab"
|
||||||
|
- export GITLAB_TOKEN=$GITLAB_TOKEN_OPENCODE
|
||||||
|
- apt-get update --quiet && apt-get install --yes curl wget gpg git && rm --recursive --force /var/lib/apt/lists/*
|
||||||
|
- curl --silent --show-error --location "https://raw.githubusercontent.com/upciti/wakemeops/main/assets/install_repository" | bash
|
||||||
|
- apt-get install --yes glab
|
||||||
|
- echo "Configuring glab"
|
||||||
|
- echo $GITLAB_HOST
|
||||||
|
- echo "Creating opencode auth configuration"
|
||||||
|
- mkdir --parents ~/.local/share/opencode
|
||||||
|
- |
|
||||||
|
cat > ~/.local/share/opencode/auth.json << EOF
|
||||||
|
{
|
||||||
|
"anthropic": {
|
||||||
|
"type": "api",
|
||||||
|
"key": "$ANTHROPIC_API_KEY"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
- echo "Configuring git"
|
||||||
|
- git config --global user.email "opencode@gitlab.com"
|
||||||
|
- git config --global user.name "Opencode"
|
||||||
|
- echo "Testing glab"
|
||||||
|
- glab issue list
|
||||||
|
- echo "Running Opencode"
|
||||||
|
- |
|
||||||
|
opencode run "
|
||||||
|
You are an AI assistant helping with GitLab operations.
|
||||||
|
|
||||||
|
Context: $AI_FLOW_CONTEXT
|
||||||
|
Task: $AI_FLOW_INPUT
|
||||||
|
Event: $AI_FLOW_EVENT
|
||||||
|
|
||||||
|
Please execute the requested task using the available GitLab tools.
|
||||||
|
Be thorough in your analysis and provide clear explanations.
|
||||||
|
|
||||||
|
<important>
|
||||||
|
Please use the glab CLI to access data from GitLab. The glab CLI has already been authenticated. You can run the corresponding commands.
|
||||||
|
|
||||||
|
If you are asked to summarise an MR or issue or asked to provide more information then please post back a note to the MR/Issue so that the user can see it.
|
||||||
|
You don't need to commit or push up changes, those will be done automatically based on the file changes you make.
|
||||||
|
</important>
|
||||||
|
"
|
||||||
|
- git checkout --branch $CI_WORKLOAD_REF origin/$CI_WORKLOAD_REF
|
||||||
|
- echo "Checking for git changes and pushing if any exist"
|
||||||
|
- |
|
||||||
|
if ! git diff --quiet || ! git diff --cached --quiet || [ --not --zero "$(git ls-files --others --exclude-standard)" ]; then
|
||||||
|
echo "Git changes detected, adding and pushing..."
|
||||||
|
git add .
|
||||||
|
if git diff --cached --quiet; then
|
||||||
|
echo "No staged changes to commit"
|
||||||
|
else
|
||||||
|
echo "Committing changes to branch: $CI_WORKLOAD_REF"
|
||||||
|
git commit --message "Codex changes"
|
||||||
|
echo "Pushing changes up to $CI_WORKLOAD_REF"
|
||||||
|
git push https://gitlab-ci-token:$GITLAB_TOKEN@$GITLAB_HOST/gl-demo-ultimate-dev-ai-epic-17570/test-java-project.git $CI_WORKLOAD_REF
|
||||||
|
echo "Changes successfully pushed"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "No git changes detected, skipping push"
|
||||||
|
fi
|
||||||
|
variables:
|
||||||
|
- ANTHROPIC_API_KEY
|
||||||
|
- GITLAB_TOKEN_OPENCODE
|
||||||
|
- GITLAB_HOST
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
You can refer to the [GitLab CLI agents docs](https://docs.gitlab.com/user/duo_agent_platform/agent_assistant/) for detailed instructions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
Here are some examples of how you can use opencode in GitLab.
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
You can configure to use a different trigger phrase than `@opencode`.
|
||||||
|
:::
|
||||||
|
|
||||||
|
- **Explain an issue**
|
||||||
|
|
||||||
|
Add this comment in a GitLab issue.
|
||||||
|
|
||||||
|
```
|
||||||
|
@opencode explain this issue
|
||||||
|
```
|
||||||
|
|
||||||
|
opencode will read the issue and reply with a clear explanation.
|
||||||
|
|
||||||
|
- **Fix an issue**
|
||||||
|
|
||||||
|
In a GitLab issue, say:
|
||||||
|
|
||||||
|
```
|
||||||
|
@opencode fix this
|
||||||
|
```
|
||||||
|
|
||||||
|
opencode will create a new branch, implement the changes, and open a merge request with the changes.
|
||||||
|
|
||||||
|
- **Review merge requests**
|
||||||
|
|
||||||
|
Leave the following comment on a GitLab merge request.
|
||||||
|
|
||||||
|
```
|
||||||
|
@opencode review this merge request
|
||||||
|
```
|
||||||
|
|
||||||
|
opencode will review the merge request and provide feedback.
|
||||||
@@ -42,24 +42,24 @@ You can also install it with the following:
|
|||||||
|
|
||||||
<Tabs>
|
<Tabs>
|
||||||
<TabItem label="npm">
|
<TabItem label="npm">
|
||||||
```bash
|
```bash
|
||||||
npm install -g opencode-ai
|
npm install -g opencode-ai
|
||||||
```
|
```
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem label="Bun">
|
<TabItem label="Bun">
|
||||||
```bash
|
```bash
|
||||||
bun install -g opencode-ai
|
bun install -g opencode-ai
|
||||||
```
|
```
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem label="pnpm">
|
<TabItem label="pnpm">
|
||||||
```bash
|
```bash
|
||||||
pnpm install -g opencode-ai
|
pnpm install -g opencode-ai
|
||||||
```
|
```
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem label="Yarn">
|
<TabItem label="Yarn">
|
||||||
```bash
|
```bash
|
||||||
yarn global add opencode-ai
|
yarn global add opencode-ai
|
||||||
```
|
```
|
||||||
</TabItem>
|
</TabItem>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
@@ -157,16 +157,16 @@ help.
|
|||||||
|
|
||||||
You can ask opencode to explain the codebase to you.
|
You can ask opencode to explain the codebase to you.
|
||||||
|
|
||||||
```txt frame="none"
|
:::tip
|
||||||
|
Use the `@` key to fuzzy search for files in the project.
|
||||||
|
:::
|
||||||
|
|
||||||
|
```txt frame="none" "@packages/functions/src/api/index.ts"
|
||||||
How is authentication handled in @packages/functions/src/api/index.ts
|
How is authentication handled in @packages/functions/src/api/index.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
This is helpful if there's a part of the codebase that you didn't work on.
|
This is helpful if there's a part of the codebase that you didn't work on.
|
||||||
|
|
||||||
:::tip
|
|
||||||
Use the `@` key to fuzzy search for files in the project.
|
|
||||||
:::
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Add features
|
### Add features
|
||||||
@@ -238,7 +238,7 @@ You can ask opencode to add new features to your project. Though we first recomm
|
|||||||
For more straightforward changes, you can ask opencode to directly build it
|
For more straightforward changes, you can ask opencode to directly build it
|
||||||
without having to review the plan first.
|
without having to review the plan first.
|
||||||
|
|
||||||
```txt frame="none"
|
```txt frame="none" "@packages/functions/src/settings.ts" "@packages/functions/src/notes.ts"
|
||||||
We need to add authentication to the /settings route. Take a look at how this is
|
We need to add authentication to the /settings route. Take a look at how this is
|
||||||
handled in the /notes route in @packages/functions/src/notes.ts and implement
|
handled in the /notes route in @packages/functions/src/notes.ts and implement
|
||||||
the same logic in @packages/functions/src/settings.ts
|
the same logic in @packages/functions/src/settings.ts
|
||||||
@@ -253,7 +253,7 @@ changes.
|
|||||||
|
|
||||||
Let's say you ask opencode to make some changes.
|
Let's say you ask opencode to make some changes.
|
||||||
|
|
||||||
```txt frame="none"
|
```txt frame="none" "@packages/functions/src/api/index.ts"
|
||||||
Can you refactor the function in @packages/functions/src/api/index.ts?
|
Can you refactor the function in @packages/functions/src/api/index.ts?
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -267,7 +267,7 @@ using the `/undo` command.
|
|||||||
opencode will now revert the changes you made and show your original message
|
opencode will now revert the changes you made and show your original message
|
||||||
again.
|
again.
|
||||||
|
|
||||||
```txt frame="none"
|
```txt frame="none" "@packages/functions/src/api/index.ts"
|
||||||
Can you refactor the function in @packages/functions/src/api/index.ts?
|
Can you refactor the function in @packages/functions/src/api/index.ts?
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -308,4 +308,4 @@ Here's an [example conversation](https://opencode.ai/s/4XP1fce5) with opencode.
|
|||||||
|
|
||||||
And that's it! You are now a pro at using opencode.
|
And that's it! You are now a pro at using opencode.
|
||||||
|
|
||||||
To make it your own, we recommend [picking a theme](/docs/themes), [customizing the keybinds](/docs/keybinds), [configuring code formatters](/docs/formatters), or playing around with the [opencode config](/docs/config).
|
To make it your own, we recommend [picking a theme](/docs/themes), [customizing the keybinds](/docs/keybinds), [configuring code formatters](/docs/formatters), [creating custom commands](/docs/commands), or playing around with the [opencode config](/docs/config).
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ opencode has a list of keybinds that you can customize through the opencode conf
|
|||||||
"session_export": "<leader>x",
|
"session_export": "<leader>x",
|
||||||
"session_new": "<leader>n",
|
"session_new": "<leader>n",
|
||||||
"session_list": "<leader>l",
|
"session_list": "<leader>l",
|
||||||
"session_navigation": "<leader>g",
|
|
||||||
"session_share": "<leader>s",
|
"session_share": "<leader>s",
|
||||||
"session_unshare": "none",
|
"session_unshare": "none",
|
||||||
"session_interrupt": "esc",
|
"session_interrupt": "esc",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ title: LSP Servers
|
|||||||
description: opencode integrates with your LSP servers.
|
description: opencode integrates with your LSP servers.
|
||||||
---
|
---
|
||||||
|
|
||||||
opencode integrates with your Language Server Protocol (LSP) to help the LLM interacts with your codebase. It uses diagnostics to provide feebdack to the LLM. And _go-to-definition_ and _find-references_ to help navigate your codebase.
|
opencode integrates with your Language Server Protocol (LSP) to help the LLM interacts with your codebase. It uses diagnostics to provide feedback to the LLM. And _go-to-definition_ and _find-references_ to help navigate your codebase.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -614,8 +614,6 @@ In this example:
|
|||||||
|
|
||||||
### OpenAI
|
### OpenAI
|
||||||
|
|
||||||
https://platform.openai.com/api-keys
|
|
||||||
|
|
||||||
1. Head over to the [OpenAI Platform console](https://platform.openai.com/api-keys), click **Create new secret key**, and copy the key.
|
1. Head over to the [OpenAI Platform console](https://platform.openai.com/api-keys), click **Create new secret key**, and copy the key.
|
||||||
|
|
||||||
2. Run `opencode auth login` and select OpenAI.
|
2. Run `opencode auth login` and select OpenAI.
|
||||||
|
|||||||
@@ -0,0 +1,314 @@
|
|||||||
|
---
|
||||||
|
title: SDK
|
||||||
|
description: JS SDK for the opencode server.
|
||||||
|
---
|
||||||
|
|
||||||
|
import config from "../../../../config.mjs"
|
||||||
|
export const typesUrl = `${config.github}/blob/dev/packages/sdk/js/src/gen/types.gen.ts`
|
||||||
|
|
||||||
|
The opencode [JS/TS SDK](https://www.npmjs.com/package/@opencode-ai/sdk) provides a type-safe client for interacting with the opencode server. You can use it to build custom integrations and control opencode programmatically.
|
||||||
|
|
||||||
|
[Learn more](/docs/server) about how the opencode server works.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
Install the SDK from npm:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install @opencode-ai/sdk
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Create client
|
||||||
|
|
||||||
|
Create a client instance to connect to your opencode server:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { createOpencodeClient } from "@opencode-ai/sdk"
|
||||||
|
|
||||||
|
const client = createOpencodeClient({
|
||||||
|
baseUrl: "http://localhost:4096",
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Options
|
||||||
|
|
||||||
|
| Option | Type | Description | Default |
|
||||||
|
| --------------- | ---------- | --------------------------------- | ----------------------- |
|
||||||
|
| `baseUrl` | `string` | URL of the opencode server | `http://localhost:4096` |
|
||||||
|
| `fetch` | `function` | Custom fetch implementation | `globalThis.fetch` |
|
||||||
|
| `parseAs` | `string` | Response parsing method | `auto` |
|
||||||
|
| `responseStyle` | `string` | Return style: `data` or `fields` | `fields` |
|
||||||
|
| `throwOnError` | `boolean` | Throw errors instead of returning | `false` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Start server
|
||||||
|
|
||||||
|
You can also programmatically start an opencode server:
|
||||||
|
|
||||||
|
````javascript
|
||||||
|
import { createOpencodeServer } from "@opencode-ai/sdk"
|
||||||
|
|
||||||
|
const server = await createOpencodeServer({
|
||||||
|
hostname: "127.0.0.1",
|
||||||
|
port: 4096,
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(`Server running at ${server.url}`)
|
||||||
|
|
||||||
|
server.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
#### Options
|
||||||
|
|
||||||
|
| Option | Type | Description | Default |
|
||||||
|
| ---------- | -------------- | ------------------------------ | ------------- |
|
||||||
|
| `hostname` | `string` | Server hostname | `127.0.0.1` |
|
||||||
|
| `port` | `number` | Server port | `4096` |
|
||||||
|
| `signal` | `AbortSignal` | Abort signal for cancellation | `undefined` |
|
||||||
|
| `timeout` | `number` | Timeout in ms for server start | `5000` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Types
|
||||||
|
|
||||||
|
The SDK includes TypeScript definitions for all API types. Import them directly:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import type { Session, Message, Part } from "@opencode-ai/sdk"
|
||||||
|
````
|
||||||
|
|
||||||
|
All types are generated from the server's OpenAPI specification and available in the <a href={typesUrl}>types file</a>.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Errors
|
||||||
|
|
||||||
|
The SDK throws typed errors that you can catch and handle:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
try {
|
||||||
|
const session = await client.session.get({ id: "invalid-id" })
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to get session:", error.message)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## APIs
|
||||||
|
|
||||||
|
The SDK exposes all server APIs through a type-safe client interface.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### App
|
||||||
|
|
||||||
|
| Method | Description | Response |
|
||||||
|
| ------------ | ------------------ | --------------------------------------- |
|
||||||
|
| `app.get()` | Get app info | <a href={typesUrl}><code>App</code></a> |
|
||||||
|
| `app.init()` | Initialize the app | `boolean` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const app = await client.app.get()
|
||||||
|
await client.app.init()
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Config
|
||||||
|
|
||||||
|
| Method | Description | Response |
|
||||||
|
| -------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------- |
|
||||||
|
| `config.get()` | Get config info | <a href={typesUrl}><code>Config</code></a> |
|
||||||
|
| `config.providers()` | List providers and default models | `{ providers: `<a href={typesUrl}><code>Provider[]</code></a>`, default: { [key: string]: string } }` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const config = await client.config.get()
|
||||||
|
const { providers, default: defaults } = await client.config.providers()
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Sessions
|
||||||
|
|
||||||
|
| Method | Description | Notes |
|
||||||
|
| ------------------------------------------------------------- | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| `session.list()` | List sessions | Returns <a href={typesUrl}><code>Session[]</code></a> |
|
||||||
|
| `session.get({ id })` | Get session | Returns <a href={typesUrl}><code>Session</code></a> |
|
||||||
|
| `session.children({ id })` | List child sessions | Returns <a href={typesUrl}><code>Session[]</code></a> |
|
||||||
|
| `session.create({ parentID?, title? })` | Create session | Returns <a href={typesUrl}><code>Session</code></a> |
|
||||||
|
| `session.delete({ id })` | Delete session | Returns `boolean` |
|
||||||
|
| `session.update({ id, title? })` | Update session properties | Returns <a href={typesUrl}><code>Session</code></a> |
|
||||||
|
| `session.init({ id, messageID, providerID, modelID })` | Analyze app and create `AGENTS.md` | Returns `boolean` |
|
||||||
|
| `session.abort({ id })` | Abort a running session | Returns `boolean` |
|
||||||
|
| `session.share({ id })` | Share session | Returns <a href={typesUrl}><code>Session</code></a> |
|
||||||
|
| `session.unshare({ id })` | Unshare session | Returns <a href={typesUrl}><code>Session</code></a> |
|
||||||
|
| `session.summarize({ id, providerID, modelID })` | Summarize session | Returns `boolean` |
|
||||||
|
| `session.messages({ id })` | List messages in a session | Returns `{ info: `<a href={typesUrl}><code>Message</code></a>`, parts: `<a href={typesUrl}><code>Part[]</code></a>`}[]` |
|
||||||
|
| `session.message({ id, messageID })` | Get message details | Returns `{ info: `<a href={typesUrl}><code>Message</code></a>`, parts: `<a href={typesUrl}><code>Part[]</code></a>`}` |
|
||||||
|
| `session.chat({ id, ...chatInput })` | Send chat message | Returns <a href={typesUrl}><code>Message</code></a> |
|
||||||
|
| `session.shell({ id, agent, command })` | Run a shell command | Returns <a href={typesUrl}><code>Message</code></a> |
|
||||||
|
| `session.revert({ id, messageID, partID? })` | Revert a message | Returns <a href={typesUrl}><code>Session</code></a> |
|
||||||
|
| `session.unrevert({ id })` | Restore reverted messages | Returns <a href={typesUrl}><code>Session</code></a> |
|
||||||
|
| `session.permissions.respond({ id, permissionID, response })` | Respond to a permission request | Returns `boolean` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Create and manage sessions
|
||||||
|
const session = await client.session.create({ title: "My session" })
|
||||||
|
const sessions = await client.session.list()
|
||||||
|
|
||||||
|
// Send messages
|
||||||
|
const message = await client.session.chat({
|
||||||
|
id: session.id,
|
||||||
|
providerID: "anthropic",
|
||||||
|
modelID: "claude-3-5-sonnet-20241022",
|
||||||
|
parts: [{ type: "text", text: "Hello!" }],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Files
|
||||||
|
|
||||||
|
| Method | Description | Response |
|
||||||
|
| ------------------------- | ---------------------------- | ------------------------------------------------------------------------------------------- |
|
||||||
|
| `find.text({ pattern })` | Search for text in files | Array of match objects with `path`, `lines`, `line_number`, `absolute_offset`, `submatches` |
|
||||||
|
| `find.files({ query })` | Find files by name | `string[]` (file paths) |
|
||||||
|
| `find.symbols({ query })` | Find workspace symbols | <a href={typesUrl}><code>Symbol[]</code></a> |
|
||||||
|
| `file.read({ path })` | Read a file | `{ type: "raw" \| "patch", content: string }` |
|
||||||
|
| `file.status()` | Get status for tracked files | <a href={typesUrl}><code>File[]</code></a> |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Search and read files
|
||||||
|
const textResults = await client.find.text({ pattern: "function.*opencode" })
|
||||||
|
const files = await client.find.files({ query: "*.ts" })
|
||||||
|
const content = await client.file.read({ path: "src/index.ts" })
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Logging
|
||||||
|
|
||||||
|
| Method | Description | Response |
|
||||||
|
| ------------------------------------------------ | --------------- | --------- |
|
||||||
|
| `log.write({ service, level, message, extra? })` | Write log entry | `boolean` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
await client.log.write({
|
||||||
|
service: "my-app",
|
||||||
|
level: "info",
|
||||||
|
message: "Operation completed",
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Agents
|
||||||
|
|
||||||
|
| Method | Description | Response |
|
||||||
|
| -------------- | ------------------------- | ------------------------------------------- |
|
||||||
|
| `agent.list()` | List all available agents | <a href={typesUrl}><code>Agent[]</code></a> |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const agents = await client.agent.list()
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### TUI
|
||||||
|
|
||||||
|
| Method | Description | Response |
|
||||||
|
| --------------------------------------------- | --------------------------------- | ---------------------- |
|
||||||
|
| `tui.appendPrompt({ text })` | Append text to the prompt | `boolean` |
|
||||||
|
| `tui.openHelp()` | Open the help dialog | `boolean` |
|
||||||
|
| `tui.openSessions()` | Open the session selector | `boolean` |
|
||||||
|
| `tui.openThemes()` | Open the theme selector | `boolean` |
|
||||||
|
| `tui.openModels()` | Open the model selector | `boolean` |
|
||||||
|
| `tui.submitPrompt()` | Submit the current prompt | `boolean` |
|
||||||
|
| `tui.clearPrompt()` | Clear the prompt | `boolean` |
|
||||||
|
| `tui.executeCommand({ command })` | Execute a command | `boolean` |
|
||||||
|
| `tui.showToast({ title?, message, variant })` | Show toast notification | `boolean` |
|
||||||
|
| `tui.control.next()` | Wait for the next control request | Control request object |
|
||||||
|
| `tui.control.response({ body })` | Respond to a control request | `boolean` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Control TUI interface
|
||||||
|
await client.tui.appendPrompt({ text: "Add this to prompt" })
|
||||||
|
await client.tui.showToast({
|
||||||
|
message: "Task completed",
|
||||||
|
variant: "success",
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Auth
|
||||||
|
|
||||||
|
| Method | Description | Response |
|
||||||
|
| ------------------------------- | ------------------------------ | --------- |
|
||||||
|
| `auth.set({ id, ...authData })` | Set authentication credentials | `boolean` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
await client.auth.set({
|
||||||
|
id: "anthropic",
|
||||||
|
type: "api",
|
||||||
|
key: "your-api-key",
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Events
|
||||||
|
|
||||||
|
| Method | Description | Response |
|
||||||
|
| ------------------- | ------------------------- | ------------------------- |
|
||||||
|
| `event.subscribe()` | Server-sent events stream | Server-sent events stream |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Listen to real-time events
|
||||||
|
const eventStream = await client.event.subscribe()
|
||||||
|
for await (const event of eventStream) {
|
||||||
|
console.log("Event:", event.type, event.properties)
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
---
|
||||||
|
title: Server
|
||||||
|
description: Interact with opencode server over HTTP.
|
||||||
|
---
|
||||||
|
|
||||||
|
import config from "../../../../config.mjs"
|
||||||
|
export const typesUrl = `${config.github}/blob/dev/packages/sdk/js/src/gen/types.gen.ts`
|
||||||
|
|
||||||
|
The `opencode serve` command runs a headless HTTP server that exposes an OpenAPI endpoint that an opencode client can use.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
opencode serve [--port <number>] [--hostname <string>]
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Options
|
||||||
|
|
||||||
|
| Flag | Short | Description | Default |
|
||||||
|
| ------------ | ----- | --------------------- | ----------- |
|
||||||
|
| `--port` | `-p` | Port to listen on | `4096` |
|
||||||
|
| `--hostname` | `-h` | Hostname to listen on | `127.0.0.1` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### How it works
|
||||||
|
|
||||||
|
When you run `opencode` it starts a TUI and a server. Where the TUI is the
|
||||||
|
client that talks to the server. The server exposes an OpenAPI 3.1 spec
|
||||||
|
endpoint. This endpoint is also used to generate an [SDK](/docs/sdk).
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
Use the opencode server to interact with opencode programmatically.
|
||||||
|
:::
|
||||||
|
|
||||||
|
This architecture lets opencode support multiple clients and allows you to interact with opencode programmatically.
|
||||||
|
|
||||||
|
You can run `opencode serve` to start a standalone server. If you have the
|
||||||
|
opencode TUI running, `opencode serve` will start a new server.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Connect to an existing server
|
||||||
|
|
||||||
|
When you start the TUI it randomly assigns a port and hostname. You can instead pass in the `--hostname` and `--port` [flags](/docs/cli). Then use this to connect to its server.
|
||||||
|
|
||||||
|
The [`/tui`](#tui) endpoint can be used to drive the TUI through the server. For example, you can prefill or run a prompt. This setup is used by the opencode [IDE](/docs/ide) plugins.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Spec
|
||||||
|
|
||||||
|
The server publishes an OpenAPI 3.1 spec that can be viewed at:
|
||||||
|
|
||||||
|
```
|
||||||
|
http://<hostname>:<port>/doc
|
||||||
|
```
|
||||||
|
|
||||||
|
For example, `http://localhost:4096/doc`. Use the spec to generate clients or inspect request and response types. Or view it in a Swagger explorer.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## APIs
|
||||||
|
|
||||||
|
The opencode server exposes the following APIs.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### App
|
||||||
|
|
||||||
|
| Method | Path | Description | Response |
|
||||||
|
| ------ | ----------- | ------------------ | --------------------------------------- |
|
||||||
|
| `GET` | `/app` | Get app info | <a href={typesUrl}><code>App</code></a> |
|
||||||
|
| `POST` | `/app/init` | Initialize the app | `boolean` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Config
|
||||||
|
|
||||||
|
| Method | Path | Description | Response |
|
||||||
|
| ------ | ------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------- |
|
||||||
|
| `GET` | `/config` | Get config info | <a href={typesUrl}><code>Config</code></a> |
|
||||||
|
| `GET` | `/config/providers` | List providers and default models | `{ providers: `<a href={typesUrl}>Provider[]</a>`, default: { [key: string]: string } }` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Sessions
|
||||||
|
|
||||||
|
| Method | Path | Description | Notes |
|
||||||
|
| -------- | ---------------------------------------- | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| `GET` | `/session` | List sessions | Returns <a href={typesUrl}><code>Session[]</code></a> |
|
||||||
|
| `GET` | `/session/:id` | Get session | Returns <a href={typesUrl}><code>Session</code></a> |
|
||||||
|
| `GET` | `/session/:id/children` | List child sessions | Returns <a href={typesUrl}><code>Session[]</code></a> |
|
||||||
|
| `POST` | `/session` | Create session | body: `{ parentID?, title? }`, returns <a href={typesUrl}><code>Session</code></a> |
|
||||||
|
| `DELETE` | `/session/:id` | Delete session | |
|
||||||
|
| `PATCH` | `/session/:id` | Update session properties | body: `{ title? }`, returns <a href={typesUrl}><code>Session</code></a> |
|
||||||
|
| `POST` | `/session/:id/init` | Analyze app and create `AGENTS.md` | body: `{ messageID, providerID, modelID }` |
|
||||||
|
| `POST` | `/session/:id/abort` | Abort a running session | |
|
||||||
|
| `POST` | `/session/:id/share` | Share session | Returns <a href={typesUrl}><code>Session</code></a> |
|
||||||
|
| `DELETE` | `/session/:id/share` | Unshare session | Returns <a href={typesUrl}><code>Session</code></a> |
|
||||||
|
| `POST` | `/session/:id/summarize` | Summarize session | |
|
||||||
|
| `GET` | `/session/:id/message` | List messages in a session | Returns `{ info: `<a href={typesUrl}>Message</a>`, parts: `<a href={typesUrl}>Part[]</a>`}[]` |
|
||||||
|
| `GET` | `/session/:id/message/:messageID` | Get message details | Returns `{ info: `<a href={typesUrl}>Message</a>`, parts: `<a href={typesUrl}>Part[]</a>`}` |
|
||||||
|
| `POST` | `/session/:id/message` | Send chat message | body matches [`ChatInput`](https://github.com/sst/opencode/blob/main/packages/opencode/src/session/index.ts#L358), returns <a href={typesUrl}><code>Message</code></a> |
|
||||||
|
| `POST` | `/session/:id/shell` | Run a shell command | body matches [`CommandInput`](https://github.com/sst/opencode/blob/main/packages/opencode/src/session/index.ts#L1007), returns <a href={typesUrl}><code>Message</code></a> |
|
||||||
|
| `POST` | `/session/:id/revert` | Revert a message | body: `{ messageID }` |
|
||||||
|
| `POST` | `/session/:id/unrevert` | Restore reverted messages | |
|
||||||
|
| `POST` | `/session/:id/permissions/:permissionID` | Respond to a permission request | body: `{ response }` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Files
|
||||||
|
|
||||||
|
| Method | Path | Description | Response |
|
||||||
|
| ------ | ------------------------ | ---------------------------- | ------------------------------------------------------------------------------------------- |
|
||||||
|
| `GET` | `/find?pattern=<pat>` | Search for text in files | Array of match objects with `path`, `lines`, `line_number`, `absolute_offset`, `submatches` |
|
||||||
|
| `GET` | `/find/file?query=<q>` | Find files by name | `string[]` (file paths) |
|
||||||
|
| `GET` | `/find/symbol?query=<q>` | Find workspace symbols | <a href={typesUrl}><code>Symbol[]</code></a> |
|
||||||
|
| `GET` | `/file?path=<path>` | Read a file | `{ type: "raw" \| "patch", content: string }` |
|
||||||
|
| `GET` | `/file/status` | Get status for tracked files | <a href={typesUrl}><code>File[]</code></a> |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Logging
|
||||||
|
|
||||||
|
| Method | Path | Description | Response |
|
||||||
|
| ------ | ------ | ------------------------------------------------------------ | --------- |
|
||||||
|
| `POST` | `/log` | Write log entry. Body: `{ service, level, message, extra? }` | `boolean` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Agents
|
||||||
|
|
||||||
|
| Method | Path | Description | Response |
|
||||||
|
| ------ | -------- | ------------------------- | ------------------------------------------- |
|
||||||
|
| `GET` | `/agent` | List all available agents | <a href={typesUrl}><code>Agent[]</code></a> |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### TUI
|
||||||
|
|
||||||
|
| Method | Path | Description | Response |
|
||||||
|
| ------ | ----------------------- | ------------------------------------------- | ---------------------- |
|
||||||
|
| `POST` | `/tui/append-prompt` | Append text to the prompt | `boolean` |
|
||||||
|
| `POST` | `/tui/open-help` | Open the help dialog | `boolean` |
|
||||||
|
| `POST` | `/tui/open-sessions` | Open the session selector | `boolean` |
|
||||||
|
| `POST` | `/tui/open-themes` | Open the theme selector | `boolean` |
|
||||||
|
| `POST` | `/tui/open-models` | Open the model selector | `boolean` |
|
||||||
|
| `POST` | `/tui/submit-prompt` | Submit the current prompt | `boolean` |
|
||||||
|
| `POST` | `/tui/clear-prompt` | Clear the prompt | `boolean` |
|
||||||
|
| `POST` | `/tui/execute-command` | Execute a command (`{ command }`) | `boolean` |
|
||||||
|
| `POST` | `/tui/show-toast` | Show toast (`{ title?, message, variant }`) | `boolean` |
|
||||||
|
| `GET` | `/tui/control/next` | Wait for the next control request | Control request object |
|
||||||
|
| `POST` | `/tui/control/response` | Respond to a control request (`{ body }`) | `boolean` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Auth
|
||||||
|
|
||||||
|
| Method | Path | Description | Response |
|
||||||
|
| ------ | ----------- | --------------------------------------------------------------- | --------- |
|
||||||
|
| `PUT` | `/auth/:id` | Set authentication credentials. Body must match provider schema | `boolean` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Events
|
||||||
|
|
||||||
|
| Method | Path | Description | Response |
|
||||||
|
| ------ | -------- | ----------------------------------------------------------------------------- | ------------------------- |
|
||||||
|
| `GET` | `/event` | Server-sent events stream. First event is `server.connected`, then bus events | Server-sent events stream |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Docs
|
||||||
|
|
||||||
|
| Method | Path | Description | Response |
|
||||||
|
| ------ | ------ | -------------------------------------- | ------------------------------------------ |
|
||||||
|
| `GET` | `/doc` | OpenAPI 3.1 specification | HTML page with OpenAPI spec |
|
||||||
@@ -0,0 +1,290 @@
|
|||||||
|
---
|
||||||
|
title: TUI
|
||||||
|
description: Using the opencode terminal user interface.
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Tabs, TabItem } from "@astrojs/starlight/components"
|
||||||
|
|
||||||
|
opencode provides an interactive terminal interface or TUI for working on your projects with an LLM.
|
||||||
|
|
||||||
|
Running opencode starts the TUI for the current directory.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
opencode
|
||||||
|
```
|
||||||
|
|
||||||
|
Or you can start it for a specific working directory.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
opencode /path/to/project
|
||||||
|
```
|
||||||
|
|
||||||
|
Once you're in the TUI, you can prompt it with a message.
|
||||||
|
|
||||||
|
```text
|
||||||
|
Give me a quick summary of the codebase.
|
||||||
|
```
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
You can also use `@` to reference files in your messages.
|
||||||
|
:::
|
||||||
|
|
||||||
|
```text "@packages/functions/src/api/index.ts"
|
||||||
|
How is auth handled in @packages/functions/src/api/index.ts?
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
When using the opencode TUI, you can type `/` followed by a command name to quickly execute actions. For example:
|
||||||
|
|
||||||
|
```bash frame="none"
|
||||||
|
/help
|
||||||
|
```
|
||||||
|
|
||||||
|
Most commands also have keybind using `ctrl+x` as the leader key, where `ctrl+x` is the default leader key. [Learn more](/docs/keybinds).
|
||||||
|
|
||||||
|
Here are all available slash commands:
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### compact
|
||||||
|
|
||||||
|
Compact the current session. _Alias_: `/summarize`
|
||||||
|
|
||||||
|
```bash frame="none"
|
||||||
|
/compact
|
||||||
|
```
|
||||||
|
|
||||||
|
**Keybind:** `ctrl+x c`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### details
|
||||||
|
|
||||||
|
Toggle tool execution details.
|
||||||
|
|
||||||
|
```bash frame="none"
|
||||||
|
/details
|
||||||
|
```
|
||||||
|
|
||||||
|
**Keybind:** `ctrl+x d`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### editor
|
||||||
|
|
||||||
|
Open external editor for composing messages. Uses the editor set in your `EDITOR` environment variable. [Learn more](#editor-setup).
|
||||||
|
|
||||||
|
```bash frame="none"
|
||||||
|
/editor
|
||||||
|
```
|
||||||
|
|
||||||
|
**Keybind:** `ctrl+x e`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### exit
|
||||||
|
|
||||||
|
Exit opencode. _Aliases_: `/quit`, `/q`
|
||||||
|
|
||||||
|
```bash frame="none"
|
||||||
|
/exit
|
||||||
|
```
|
||||||
|
|
||||||
|
**Keybind:** `ctrl+x q`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### export
|
||||||
|
|
||||||
|
Export current conversation to Markdown and open in your default editor. Uses the editor set in your `EDITOR` environment variable. [Learn more](#editor-setup).
|
||||||
|
|
||||||
|
```bash frame="none"
|
||||||
|
/export
|
||||||
|
```
|
||||||
|
|
||||||
|
**Keybind:** `ctrl+x x`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### help
|
||||||
|
|
||||||
|
Show the help dialog.
|
||||||
|
|
||||||
|
```bash frame="none"
|
||||||
|
/help
|
||||||
|
```
|
||||||
|
|
||||||
|
**Keybind:** `ctrl+x h`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### init
|
||||||
|
|
||||||
|
Create or update `AGENTS.md` file. [Learn more](/docs/rules).
|
||||||
|
|
||||||
|
```bash frame="none"
|
||||||
|
/init
|
||||||
|
```
|
||||||
|
|
||||||
|
**Keybind:** `ctrl+x i`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### models
|
||||||
|
|
||||||
|
List available models.
|
||||||
|
|
||||||
|
```bash frame="none"
|
||||||
|
/models
|
||||||
|
```
|
||||||
|
|
||||||
|
**Keybind:** `ctrl+x m`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### new
|
||||||
|
|
||||||
|
Start a new session. _Alias_: `/clear`
|
||||||
|
|
||||||
|
```bash frame="none"
|
||||||
|
/new
|
||||||
|
```
|
||||||
|
|
||||||
|
**Keybind:** `ctrl+x n`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### redo
|
||||||
|
|
||||||
|
Redo a previously undone message. Only available after using `/undo`.
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
Any file changes will also be restored.
|
||||||
|
:::
|
||||||
|
|
||||||
|
```bash frame="none"
|
||||||
|
/redo
|
||||||
|
```
|
||||||
|
|
||||||
|
**Keybind:** `ctrl+x r`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### sessions
|
||||||
|
|
||||||
|
List and switch between sessions. _Aliases_: `/resume`, `/continue`
|
||||||
|
|
||||||
|
```bash frame="none"
|
||||||
|
/sessions
|
||||||
|
```
|
||||||
|
|
||||||
|
**Keybind:** `ctrl+x l`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### share
|
||||||
|
|
||||||
|
Share current session. [Learn more](/docs/share).
|
||||||
|
|
||||||
|
```bash frame="none"
|
||||||
|
/share
|
||||||
|
```
|
||||||
|
|
||||||
|
**Keybind:** `ctrl+x s`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### themes
|
||||||
|
|
||||||
|
List available themes.
|
||||||
|
|
||||||
|
```bash frame="none"
|
||||||
|
/themes
|
||||||
|
```
|
||||||
|
|
||||||
|
**Keybind:** `ctrl+x t`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### undo
|
||||||
|
|
||||||
|
Undo last message in the conversation. Removes the most recent user message, all subsequent responses, and any file changes.
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
Any file changes made will also be reverted.
|
||||||
|
:::
|
||||||
|
|
||||||
|
```bash frame="none"
|
||||||
|
/undo
|
||||||
|
```
|
||||||
|
|
||||||
|
**Keybind:** `ctrl+x u`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### unshare
|
||||||
|
|
||||||
|
Unshare current session. [Learn more](/docs/share#un-sharing).
|
||||||
|
|
||||||
|
```bash frame="none"
|
||||||
|
/unshare
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bash commands
|
||||||
|
|
||||||
|
Start a message with `!` to run a shell command.
|
||||||
|
|
||||||
|
```bash frame="none"
|
||||||
|
!ls -la
|
||||||
|
```
|
||||||
|
|
||||||
|
The output of the command is added to the conversation as a tool result.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Editor setup
|
||||||
|
|
||||||
|
Both the `/editor` and `/export` commands use the editor specified in your `EDITOR` environment variable.
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<TabItem label="Linux/macOS">
|
||||||
|
```bash
|
||||||
|
export EDITOR=nano # or vim, code, etc.
|
||||||
|
```
|
||||||
|
|
||||||
|
To make it permanent, add this to your shell profile;
|
||||||
|
`~/.bashrc`, `~/.zshrc`, etc.
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem label="Windows (CMD)">
|
||||||
|
```bash
|
||||||
|
set EDITOR=notepad # or code, vim, etc.
|
||||||
|
```
|
||||||
|
|
||||||
|
To make it permanent, use **System Properties** > **Environment
|
||||||
|
Variables**.
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem label="Windows (PowerShell)">
|
||||||
|
```bash
|
||||||
|
$env:EDITOR = "notepad" # or "code", "vim", etc.
|
||||||
|
```
|
||||||
|
|
||||||
|
To make it permanent, add this to your PowerShell
|
||||||
|
profile.
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
Popular editor options include:
|
||||||
|
|
||||||
|
- `code` - Visual Studio Code
|
||||||
|
- `vim` - Vim editor
|
||||||
|
- `nano` - Nano editor
|
||||||
|
- `notepad` - Windows Notepad
|
||||||
|
- `subl` - Sublime Text
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user