Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c47fde2ca4 | ||
|
|
f42e1c6375 | ||
|
|
f68374ad22 | ||
|
|
5e86c9b791 | ||
|
|
94658c31c5 | ||
|
|
9fd672a1cb | ||
|
|
10523c4372 | ||
|
|
d1cd7d0344 | ||
|
|
06ac1be226 | ||
|
|
05489bc843 | ||
|
|
3f02eecf22 | ||
|
|
f5ca78ed7b | ||
|
|
894cbaa51e | ||
|
|
8b70b89fde | ||
|
|
f9dbc586dc | ||
|
|
ffeef63ca1 | ||
|
|
4da58294d9 | ||
|
|
fa2e88f49b | ||
|
|
28e765ef0a | ||
|
|
bfbcb5f200 | ||
|
|
89492b3002 | ||
|
|
2663415d47 | ||
|
|
51be67cc14 | ||
|
|
92a1943771 | ||
|
|
1e15fc273a | ||
|
|
104a895a71 | ||
|
|
f98e730405 | ||
|
|
b12bef05d3 | ||
|
|
2f1d001cc5 | ||
|
|
65d0b3ed6d | ||
|
|
22a34d7958 | ||
|
|
cb4401ec92 | ||
|
|
febf467b03 | ||
|
|
d55a2fd56c | ||
|
|
40f577e5e7 | ||
|
|
9e49870118 | ||
|
|
fe38e3ab02 | ||
|
|
0170577743 | ||
|
|
7de6ea5922 | ||
|
|
2fe7d13e69 | ||
|
|
1bc3c98ae7 | ||
|
|
55787f2caa | ||
|
|
7df61a74a0 | ||
|
|
4f23110880 | ||
|
|
041353f4ff | ||
|
|
c72f8b17c6 | ||
|
|
eb304f4115 | ||
|
|
5565f14ef5 | ||
|
|
10a4455c6f | ||
|
|
5ded6d6ad7 | ||
|
|
849a38c30c | ||
|
|
68050ab802 | ||
|
|
91d01fd4cc | ||
|
|
9beb0f8512 | ||
|
|
d4cb47eadc | ||
|
|
261ff416a9 | ||
|
|
d0a70cb217 | ||
|
|
20fc56d020 | ||
|
|
a57ae3ec93 | ||
|
|
30f9fa12d9 | ||
|
|
d473d4ffc8 | ||
|
|
af50596529 | ||
|
|
3823d8d50e | ||
|
|
7a926b32ce | ||
|
|
a5ede68241 | ||
|
|
60dc38050d | ||
|
|
31d0caee38 | ||
|
|
2a7ab45605 | ||
|
|
019054dd1e | ||
|
|
a018a15f32 | ||
|
|
e630d680dd | ||
|
|
9e392f25a6 | ||
|
|
2cc4e6ad7c | ||
|
|
70d8d1ab1e | ||
|
|
342aa27e03 | ||
|
|
e1aed0cd01 |
@@ -5,6 +5,8 @@ runs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Setup Bun
|
- name: Setup Bun
|
||||||
uses: oven-sh/setup-bun@v2
|
uses: oven-sh/setup-bun@v2
|
||||||
|
with:
|
||||||
|
bun-version-file: package.json
|
||||||
|
|
||||||
- name: Cache ~/.bun
|
- name: Cache ~/.bun
|
||||||
id: cache-bun
|
id: cache-bun
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ jobs:
|
|||||||
const description = issue.body || '';
|
const description = issue.body || '';
|
||||||
|
|
||||||
// Check for version patterns like v1.0.x or 1.0.x
|
// Check for version patterns like v1.0.x or 1.0.x
|
||||||
const versionPattern = /\b[v]?1\.0\.[x\d]\b/i;
|
const versionPattern = /[v]?1\.0\./i;
|
||||||
|
|
||||||
if (versionPattern.test(title) || versionPattern.test(description)) {
|
if (versionPattern.test(title) || versionPattern.test(description)) {
|
||||||
await github.rest.issues.addLabels({
|
await github.rest.issues.addLabels({
|
||||||
@@ -30,11 +30,4 @@ jobs:
|
|||||||
issue_number: issue.number,
|
issue_number: issue.number,
|
||||||
labels: ['opentui']
|
labels: ['opentui']
|
||||||
});
|
});
|
||||||
|
|
||||||
await github.rest.issues.addAssignees({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: issue.number,
|
|
||||||
assignees: ['thdxr']
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,4 +26,4 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||||
with:
|
with:
|
||||||
model: opencode/kimi-k2
|
model: opencode/glm-4.6
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ node_modules
|
|||||||
.env
|
.env
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
|
*~
|
||||||
openapi.json
|
openapi.json
|
||||||
playground
|
playground
|
||||||
tmp
|
tmp
|
||||||
|
|||||||
@@ -19,5 +19,5 @@ For anything in the packages/app use the ignore: prefix.
|
|||||||
prefer to explain WHY something was done from an end user perspective instead of
|
prefer to explain WHY something was done from an end user perspective instead of
|
||||||
WHAT was done.
|
WHAT was done.
|
||||||
|
|
||||||
do not do generic messages like "improvied agent experience" be very specific
|
do not do generic messages like "improved agent experience" be very specific
|
||||||
about what user facing changes were made
|
about what user facing changes were made
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
## Tool Calling
|
## Tool Calling
|
||||||
|
|
||||||
- ALWAYS USE PARALLEL TOOLS WHEN APPLICABLE. Here is an example illustrating how to execute 3 parallel file reads in this chat environnement:
|
- ALWAYS USE PARALLEL TOOLS WHEN APPLICABLE. Here is an example illustrating how to execute 3 parallel file reads in this chat environment:
|
||||||
|
|
||||||
json
|
json
|
||||||
{
|
{
|
||||||
|
|||||||
+3
-3
@@ -26,7 +26,7 @@ Want to take on an issue? Leave a comment and a maintainer may assign it to you
|
|||||||
|
|
||||||
## Developing OpenCode
|
## Developing OpenCode
|
||||||
|
|
||||||
- Requirements: Bun 1.3+, Go 1.24.x.
|
- Requirements: Bun 1.3+
|
||||||
- Install dependencies and start the dev server from the repo root:
|
- Install dependencies and start the dev server from the repo root:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -36,11 +36,11 @@ Want to take on an issue? Leave a comment and a maintainer may assign it to you
|
|||||||
|
|
||||||
- Core pieces:
|
- Core pieces:
|
||||||
- `packages/opencode`: OpenCode core business logic & server.
|
- `packages/opencode`: OpenCode core business logic & server.
|
||||||
- `packages/tui`: The TUI code, written in Go (will be removed soon in favor of [opentui](https://github.com/sst/opentui))
|
- `packages/opencode/src/cli/cmd/tui/`: The TUI code, written in SolidJS with [opentui](https://github.com/sst/opentui)
|
||||||
- `packages/plugin`: Source for `@opencode-ai/plugin`
|
- `packages/plugin`: Source for `@opencode-ai/plugin`
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> After touching `packages/opencode/src/server/server.ts`, the OpenCode team must regenerate the Stainless SDK before any client updates merge.
|
> After touching `packages/opencode/src/server/server.ts`, run "./packages/sdk/js/script/build.ts" to regenerate the JS sdk.
|
||||||
|
|
||||||
## Pull Request Expectations
|
## Pull Request Expectations
|
||||||
|
|
||||||
|
|||||||
@@ -126,3 +126,5 @@
|
|||||||
| 2025-10-30 | 613,746 (+7,487) | 542,064 (+0) | 1,155,810 (+7,487) |
|
| 2025-10-30 | 613,746 (+7,487) | 542,064 (+0) | 1,155,810 (+7,487) |
|
||||||
| 2025-10-30 | 617,846 (+4,100) | 555,026 (+12,962) | 1,172,872 (+17,062) |
|
| 2025-10-30 | 617,846 (+4,100) | 555,026 (+12,962) | 1,172,872 (+17,062) |
|
||||||
| 2025-10-31 | 626,612 (+8,766) | 564,579 (+9,553) | 1,191,191 (+18,319) |
|
| 2025-10-31 | 626,612 (+8,766) | 564,579 (+9,553) | 1,191,191 (+18,319) |
|
||||||
|
| 2025-11-01 | 636,100 (+9,488) | 581,806 (+17,227) | 1,217,906 (+26,715) |
|
||||||
|
| 2025-11-02 | 644,067 (+7,967) | 590,004 (+8,198) | 1,234,071 (+16,165) |
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/core": {
|
"packages/console/core": {
|
||||||
"name": "@opencode-ai/console-core",
|
"name": "@opencode-ai/console-core",
|
||||||
"version": "1.0.2",
|
"version": "1.0.14",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-sts": "3.782.0",
|
"@aws-sdk/client-sts": "3.782.0",
|
||||||
"@jsx-email/render": "1.1.1",
|
"@jsx-email/render": "1.1.1",
|
||||||
@@ -66,7 +66,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/function": {
|
"packages/console/function": {
|
||||||
"name": "@opencode-ai/console-function",
|
"name": "@opencode-ai/console-function",
|
||||||
"version": "1.0.2",
|
"version": "1.0.14",
|
||||||
"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",
|
||||||
@@ -90,7 +90,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/mail": {
|
"packages/console/mail": {
|
||||||
"name": "@opencode-ai/console-mail",
|
"name": "@opencode-ai/console-mail",
|
||||||
"version": "1.0.2",
|
"version": "1.0.14",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jsx-email/all": "2.2.3",
|
"@jsx-email/all": "2.2.3",
|
||||||
"@jsx-email/cli": "1.4.3",
|
"@jsx-email/cli": "1.4.3",
|
||||||
@@ -111,7 +111,7 @@
|
|||||||
},
|
},
|
||||||
"packages/desktop": {
|
"packages/desktop": {
|
||||||
"name": "@opencode-ai/desktop",
|
"name": "@opencode-ai/desktop",
|
||||||
"version": "1.0.2",
|
"version": "1.0.14",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
@@ -150,7 +150,7 @@
|
|||||||
},
|
},
|
||||||
"packages/function": {
|
"packages/function": {
|
||||||
"name": "@opencode-ai/function",
|
"name": "@opencode-ai/function",
|
||||||
"version": "1.0.2",
|
"version": "1.0.14",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/auth-app": "8.0.1",
|
"@octokit/auth-app": "8.0.1",
|
||||||
"@octokit/rest": "22.0.0",
|
"@octokit/rest": "22.0.0",
|
||||||
@@ -166,7 +166,7 @@
|
|||||||
},
|
},
|
||||||
"packages/opencode": {
|
"packages/opencode": {
|
||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"version": "1.0.2",
|
"version": "1.0.14",
|
||||||
"bin": {
|
"bin": {
|
||||||
"opencode": "./bin/opencode",
|
"opencode": "./bin/opencode",
|
||||||
},
|
},
|
||||||
@@ -184,8 +184,8 @@
|
|||||||
"@opencode-ai/plugin": "workspace:*",
|
"@opencode-ai/plugin": "workspace:*",
|
||||||
"@opencode-ai/script": "workspace:*",
|
"@opencode-ai/script": "workspace:*",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"@opentui/core": "0.0.0-20251031-fc297165",
|
"@opentui/core": "0.1.32",
|
||||||
"@opentui/solid": "0.0.0-20251031-fc297165",
|
"@opentui/solid": "0.1.32",
|
||||||
"@parcel/watcher": "2.5.1",
|
"@parcel/watcher": "2.5.1",
|
||||||
"@pierre/precision-diffs": "catalog:",
|
"@pierre/precision-diffs": "catalog:",
|
||||||
"@solid-primitives/event-bus": "1.1.2",
|
"@solid-primitives/event-bus": "1.1.2",
|
||||||
@@ -199,7 +199,7 @@
|
|||||||
"fuzzysort": "3.1.0",
|
"fuzzysort": "3.1.0",
|
||||||
"gray-matter": "4.0.3",
|
"gray-matter": "4.0.3",
|
||||||
"hono": "catalog:",
|
"hono": "catalog:",
|
||||||
"hono-openapi": "1.0.7",
|
"hono-openapi": "1.1.1",
|
||||||
"ignore": "7.0.5",
|
"ignore": "7.0.5",
|
||||||
"jsonc-parser": "3.3.1",
|
"jsonc-parser": "3.3.1",
|
||||||
"minimatch": "10.0.3",
|
"minimatch": "10.0.3",
|
||||||
@@ -243,7 +243,7 @@
|
|||||||
},
|
},
|
||||||
"packages/plugin": {
|
"packages/plugin": {
|
||||||
"name": "@opencode-ai/plugin",
|
"name": "@opencode-ai/plugin",
|
||||||
"version": "1.0.2",
|
"version": "1.0.14",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"zod": "catalog:",
|
"zod": "catalog:",
|
||||||
@@ -263,7 +263,7 @@
|
|||||||
},
|
},
|
||||||
"packages/sdk/js": {
|
"packages/sdk/js": {
|
||||||
"name": "@opencode-ai/sdk",
|
"name": "@opencode-ai/sdk",
|
||||||
"version": "1.0.2",
|
"version": "1.0.14",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@hey-api/openapi-ts": "0.81.0",
|
"@hey-api/openapi-ts": "0.81.0",
|
||||||
"@tsconfig/node22": "catalog:",
|
"@tsconfig/node22": "catalog:",
|
||||||
@@ -274,7 +274,7 @@
|
|||||||
},
|
},
|
||||||
"packages/slack": {
|
"packages/slack": {
|
||||||
"name": "@opencode-ai/slack",
|
"name": "@opencode-ai/slack",
|
||||||
"version": "1.0.2",
|
"version": "1.0.14",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"@slack/bolt": "^3.17.1",
|
"@slack/bolt": "^3.17.1",
|
||||||
@@ -287,7 +287,7 @@
|
|||||||
},
|
},
|
||||||
"packages/ui": {
|
"packages/ui": {
|
||||||
"name": "@opencode-ai/ui",
|
"name": "@opencode-ai/ui",
|
||||||
"version": "1.0.2",
|
"version": "1.0.14",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
@@ -317,7 +317,7 @@
|
|||||||
},
|
},
|
||||||
"packages/web": {
|
"packages/web": {
|
||||||
"name": "@opencode-ai/web",
|
"name": "@opencode-ai/web",
|
||||||
"version": "1.0.2",
|
"version": "1.0.14",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/cloudflare": "12.6.3",
|
"@astrojs/cloudflare": "12.6.3",
|
||||||
"@astrojs/markdown-remark": "6.3.1",
|
"@astrojs/markdown-remark": "6.3.1",
|
||||||
@@ -961,21 +961,21 @@
|
|||||||
|
|
||||||
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
|
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
|
||||||
|
|
||||||
"@opentui/core": ["@opentui/core@0.0.0-20251031-fc297165", "", { "dependencies": { "bun-ffi-structs": "^0.1.0", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.0.0-20251031-fc297165", "@opentui/core-darwin-x64": "0.0.0-20251031-fc297165", "@opentui/core-linux-arm64": "0.0.0-20251031-fc297165", "@opentui/core-linux-x64": "0.0.0-20251031-fc297165", "@opentui/core-win32-arm64": "0.0.0-20251031-fc297165", "@opentui/core-win32-x64": "0.0.0-20251031-fc297165", "bun-webgpu": "0.1.3", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-xtUF/uJF04d1wl4f7vsRNsDN8P9uK9Mcx1SAcm79wAN90VPNB4j2G0s7qlt8SD4zB0iWPjXICqJidjRzrQ3QVg=="],
|
"@opentui/core": ["@opentui/core@0.1.32", "", { "dependencies": { "bun-ffi-structs": "^0.1.0", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.32", "@opentui/core-darwin-x64": "0.1.32", "@opentui/core-linux-arm64": "0.1.32", "@opentui/core-linux-x64": "0.1.32", "@opentui/core-win32-arm64": "0.1.32", "@opentui/core-win32-x64": "0.1.32", "bun-webgpu": "0.1.3", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-6Ms1Gybyvl3Rt4k8OdA2c/5YlhobICcXjF5mn4k7tWujFnrBTm441G8k02pdIUffy7fD7dsouq12gfAVmSBmvA=="],
|
||||||
|
|
||||||
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.0.0-20251031-fc297165", "", { "os": "darwin", "cpu": "arm64" }, "sha512-SD5AiofTfOT+JBx7tcBcd6BdD9sc+RPkHbhIJeqkw5V/GJ4OjyUW3m2kyR9iTs1nLMbKD5o9gyVXpLig4KmFiQ=="],
|
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.32", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5yvhJEsXnZGS/q2jIfz97eA4wHOJyF/zTvJL6ykvqjlXwW+bOQ8S7WcpBShR2gf+49Exak3cO+XB16yMWmCyEw=="],
|
||||||
|
|
||||||
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.0.0-20251031-fc297165", "", { "os": "darwin", "cpu": "x64" }, "sha512-uhzxSvmfeK7vv8uNdhl8Mn2yMnjOVqdjZTOIV2aI8H9SCp8cmnzuLA8FXFO+BW6kgxsg6LbVdp4d4jDCgwtKLQ=="],
|
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.32", "", { "os": "darwin", "cpu": "x64" }, "sha512-DKEA3kYvFuj5C4i1N1ck+VEqYH1iCc1O958iGrc5r++jGcP0osKKEA5qSWbSlEy+iflr7Oydr350Aqyyt7J/pA=="],
|
||||||
|
|
||||||
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.0.0-20251031-fc297165", "", { "os": "linux", "cpu": "arm64" }, "sha512-qGjjk/QTrAyqwzPC+6NhqiQZ31k3GxufbtccF8Yqan0GLuA6GrKcU72IcPwVA5t/6VIXaLkJZyFfub7CoO1D/g=="],
|
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.32", "", { "os": "linux", "cpu": "arm64" }, "sha512-02rgp+Rq21hg3MhQIo8guvHNGuerJgdGSWqPsUs7HwsHLL1yD8ndMxdZxOvyHNsEjzrzklQqPishHZ97QbAVYQ=="],
|
||||||
|
|
||||||
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.0.0-20251031-fc297165", "", { "os": "linux", "cpu": "x64" }, "sha512-gre61Sxc9yX8lrqGNXz5fyE7xJHfkgDi8smGPE2OVP8HmXh0Rn1tXMzFywweEs9MELP3kdQ0VhimYJWkp8FyWg=="],
|
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.32", "", { "os": "linux", "cpu": "x64" }, "sha512-+ek+EYyJKC9xxVeqD14XlBYaaN7Xm45PGv7pviuqZMwJn+G6v6JxwfUNASOr/KT0N1BZdcDGp9EGToYGXwjsQg=="],
|
||||||
|
|
||||||
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.0.0-20251031-fc297165", "", { "os": "win32", "cpu": "arm64" }, "sha512-44jsq/Ea+jIjZDXyt0w23/DkvwniQFPRB1tocGp6VrOHyHKa0IPHAQ+iuM0felbnmdMUFYyTyh1iOfAcuZyaaA=="],
|
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.32", "", { "os": "win32", "cpu": "arm64" }, "sha512-k9vt+jBrrWAKOWmOC02G4S8V+1iftsq6a+8+Lt9Vc6GdXNTWIKCooB5FPAoLaQeb9TXKjK1WJFgibEXo3Q9XXQ=="],
|
||||||
|
|
||||||
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.0.0-20251031-fc297165", "", { "os": "win32", "cpu": "x64" }, "sha512-L20tCPrLFMCuX4lC2JTcixiCGFNM5RTHQwKLRcxcsSdKBr6a/7ztOG2a/2RNWkrrlbwTrUREVXH4Ivk3EOuStw=="],
|
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.32", "", { "os": "win32", "cpu": "x64" }, "sha512-aLUYKZVMyyyN2A0d5ETbt4ktTb6GCp4PXYHijhOXy7QPvg779H8fy7dpGUHZYEJdwAdGrIvi/y6SKn9FmuXisA=="],
|
||||||
|
|
||||||
"@opentui/solid": ["@opentui/solid@0.0.0-20251031-fc297165", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.0.0-20251031-fc297165", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-9u7ULKztDG1SvvU/wNCTFL7JYNPkG+pevcEU3JA7M2uUTIWrvKf/rD13lxtfVe7/yfxcY69SMRlaJGWpfxud5w=="],
|
"@opentui/solid": ["@opentui/solid@0.1.32", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.32", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-JYQ7DpC1oF1jCO4I27mxYGz8M5AmBzsm7xbcG3VANA9cpCY/Tp3YXxanw2Gx1G/xSTY6QMBPhaTEuiFdIQ6FRw=="],
|
||||||
|
|
||||||
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
|
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
|
||||||
|
|
||||||
@@ -2221,7 +2221,7 @@
|
|||||||
|
|
||||||
"hono": ["hono@4.7.10", "", {}, "sha512-QkACju9MiN59CKSY5JsGZCYmPZkA6sIW6OFCUp7qDjZu6S6KHtJHhAc9Uy9mV9F8PJ1/HQ3ybZF2yjCa/73fvQ=="],
|
"hono": ["hono@4.7.10", "", {}, "sha512-QkACju9MiN59CKSY5JsGZCYmPZkA6sIW6OFCUp7qDjZu6S6KHtJHhAc9Uy9mV9F8PJ1/HQ3ybZF2yjCa/73fvQ=="],
|
||||||
|
|
||||||
"hono-openapi": ["hono-openapi@1.0.7", "", { "peerDependencies": { "@hono/standard-validator": "^0.1.2", "@standard-community/standard-json": "^0.3.1", "@standard-community/standard-openapi": "^0.2.4", "@types/json-schema": "^7.0.15", "hono": "^4.8.3", "openapi-types": "^12.1.3" }, "optionalPeers": ["@hono/standard-validator", "hono"] }, "sha512-rMn+nn4/HMisyi549L3zT7WCmVvmpiKsyt790GcGfqvJf9mJfhq6txw09l0IhSBxpJpA0pXVKxFijcsnGfshUA=="],
|
"hono-openapi": ["hono-openapi@1.1.1", "", { "peerDependencies": { "@hono/standard-validator": "^0.1.2", "@standard-community/standard-json": "^0.3.5", "@standard-community/standard-openapi": "^0.2.8", "@types/json-schema": "^7.0.15", "hono": "^4.8.3", "openapi-types": "^12.1.3" }, "optionalPeers": ["@hono/standard-validator", "hono"] }, "sha512-AC3HNhZYPHhnZdSy2Je7GDoTTNxPos6rKRQKVDBbSilY3cWJPqsxRnN6zA4pU7tfxmQEMTqkiLXbw6sAaemB8Q=="],
|
||||||
|
|
||||||
"hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="],
|
"hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="],
|
||||||
|
|
||||||
|
|||||||
+45
-15
@@ -168,7 +168,9 @@ try {
|
|||||||
const summary = await summarize(response)
|
const summary = await summarize(response)
|
||||||
await pushToLocalBranch(summary)
|
await pushToLocalBranch(summary)
|
||||||
}
|
}
|
||||||
const hasShared = prData.comments.nodes.some((c) => c.body.includes(`${useShareUrl()}/s/${shareId}`))
|
const hasShared = prData.comments.nodes.some((c) =>
|
||||||
|
c.body.includes(`${useShareUrl()}/s/${shareId}`),
|
||||||
|
)
|
||||||
await updateComment(`${response}${footer({ image: !hasShared })}`)
|
await updateComment(`${response}${footer({ image: !hasShared })}`)
|
||||||
}
|
}
|
||||||
// Fork PR
|
// Fork PR
|
||||||
@@ -180,7 +182,9 @@ try {
|
|||||||
const summary = await summarize(response)
|
const summary = await summarize(response)
|
||||||
await pushToForkBranch(summary, prData)
|
await pushToForkBranch(summary, prData)
|
||||||
}
|
}
|
||||||
const hasShared = prData.comments.nodes.some((c) => c.body.includes(`${useShareUrl()}/s/${shareId}`))
|
const hasShared = prData.comments.nodes.some((c) =>
|
||||||
|
c.body.includes(`${useShareUrl()}/s/${shareId}`),
|
||||||
|
)
|
||||||
await updateComment(`${response}${footer({ image: !hasShared })}`)
|
await updateComment(`${response}${footer({ image: !hasShared })}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -361,7 +365,9 @@ async function getAccessToken() {
|
|||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const responseJson = (await response.json()) as { error?: string }
|
const responseJson = (await response.json()) as { error?: string }
|
||||||
throw new Error(`App token exchange failed: ${response.status} ${response.statusText} - ${responseJson.error}`)
|
throw new Error(
|
||||||
|
`App token exchange failed: ${response.status} ${response.statusText} - ${responseJson.error}`,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const responseJson = (await response.json()) as { token: string }
|
const responseJson = (await response.json()) as { token: string }
|
||||||
@@ -402,8 +408,12 @@ async function getUserPrompt() {
|
|||||||
// ie. <img alt="Image" src="https://github.com/user-attachments/assets/xxxx" />
|
// ie. <img alt="Image" src="https://github.com/user-attachments/assets/xxxx" />
|
||||||
// ie. [api.json](https://github.com/user-attachments/files/21433810/api.json)
|
// ie. [api.json](https://github.com/user-attachments/files/21433810/api.json)
|
||||||
// ie. 
|
// ie. 
|
||||||
const mdMatches = prompt.matchAll(/!?\[.*?\]\((https:\/\/github\.com\/user-attachments\/[^)]+)\)/gi)
|
const mdMatches = prompt.matchAll(
|
||||||
const tagMatches = prompt.matchAll(/<img .*?src="(https:\/\/github\.com\/user-attachments\/[^"]+)" \/>/gi)
|
/!?\[.*?\]\((https:\/\/github\.com\/user-attachments\/[^)]+)\)/gi,
|
||||||
|
)
|
||||||
|
const tagMatches = prompt.matchAll(
|
||||||
|
/<img .*?src="(https:\/\/github\.com\/user-attachments\/[^"]+)" \/>/gi,
|
||||||
|
)
|
||||||
const matches = [...mdMatches, ...tagMatches].sort((a, b) => a.index - b.index)
|
const matches = [...mdMatches, ...tagMatches].sort((a, b) => a.index - b.index)
|
||||||
console.log("Images", JSON.stringify(matches, null, 2))
|
console.log("Images", JSON.stringify(matches, null, 2))
|
||||||
|
|
||||||
@@ -430,7 +440,8 @@ async function getUserPrompt() {
|
|||||||
|
|
||||||
// Replace img tag with file path, ie. @image.png
|
// Replace img tag with file path, ie. @image.png
|
||||||
const replacement = `@${filename}`
|
const replacement = `@${filename}`
|
||||||
prompt = prompt.slice(0, start + offset) + replacement + prompt.slice(start + offset + tag.length)
|
prompt =
|
||||||
|
prompt.slice(0, start + offset) + replacement + prompt.slice(start + offset + tag.length)
|
||||||
offset += replacement.length - tag.length
|
offset += replacement.length - tag.length
|
||||||
|
|
||||||
const contentType = res.headers.get("content-type")
|
const contentType = res.headers.get("content-type")
|
||||||
@@ -498,7 +509,12 @@ async function subscribeSessionEvents() {
|
|||||||
? JSON.stringify(part.state.input)
|
? JSON.stringify(part.state.input)
|
||||||
: "Unknown"
|
: "Unknown"
|
||||||
console.log()
|
console.log()
|
||||||
console.log(color + `|`, "\x1b[0m\x1b[2m" + ` ${tool.padEnd(7, " ")}`, "", "\x1b[0m" + title)
|
console.log(
|
||||||
|
color + `|`,
|
||||||
|
"\x1b[0m\x1b[2m" + ` ${tool.padEnd(7, " ")}`,
|
||||||
|
"",
|
||||||
|
"\x1b[0m" + title,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (part.type === "text") {
|
if (part.type === "text") {
|
||||||
@@ -710,7 +726,8 @@ async function assertPermissions() {
|
|||||||
throw new Error(`Failed to check permissions for user ${actor}: ${error}`)
|
throw new Error(`Failed to check permissions for user ${actor}: ${error}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!["admin", "write"].includes(permission)) throw new Error(`User ${actor} does not have write permissions`)
|
if (!["admin", "write"].includes(permission))
|
||||||
|
throw new Error(`User ${actor} does not have write permissions`)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateComment(body: string) {
|
async function updateComment(body: string) {
|
||||||
@@ -730,12 +747,13 @@ async function updateComment(body: string) {
|
|||||||
async function createPR(base: string, branch: string, title: string, body: string) {
|
async function createPR(base: string, branch: string, title: string, body: string) {
|
||||||
console.log("Creating pull request...")
|
console.log("Creating pull request...")
|
||||||
const { repo } = useContext()
|
const { repo } = useContext()
|
||||||
|
const truncatedTitle = title.length > 256 ? title.slice(0, 253) + "..." : title
|
||||||
const pr = await octoRest.rest.pulls.create({
|
const pr = await octoRest.rest.pulls.create({
|
||||||
owner: repo.owner,
|
owner: repo.owner,
|
||||||
repo: repo.repo,
|
repo: repo.repo,
|
||||||
head: branch,
|
head: branch,
|
||||||
base,
|
base,
|
||||||
title,
|
title: truncatedTitle,
|
||||||
body,
|
body,
|
||||||
})
|
})
|
||||||
return pr.data.number
|
return pr.data.number
|
||||||
@@ -753,7 +771,9 @@ function footer(opts?: { image?: boolean }) {
|
|||||||
|
|
||||||
return `<a href="${useShareUrl()}/s/${shareId}"><img width="200" alt="${titleAlt}" src="https://social-cards.sst.dev/opencode-share/${title64}.png?model=${providerID}/${modelID}&version=${session.version}&id=${shareId}" /></a>\n`
|
return `<a href="${useShareUrl()}/s/${shareId}"><img width="200" alt="${titleAlt}" src="https://social-cards.sst.dev/opencode-share/${title64}.png?model=${providerID}/${modelID}&version=${session.version}&id=${shareId}" /></a>\n`
|
||||||
})()
|
})()
|
||||||
const shareUrl = shareId ? `[opencode session](${useShareUrl()}/s/${shareId}) | ` : ""
|
const shareUrl = shareId
|
||||||
|
? `[opencode session](${useShareUrl()}/s/${shareId}) | `
|
||||||
|
: ""
|
||||||
return `\n\n${image}${shareUrl}[github run](${useEnvRunUrl()})`
|
return `\n\n${image}${shareUrl}[github run](${useEnvRunUrl()})`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -936,9 +956,13 @@ function buildPromptDataForPR(pr: GitHubPullRequest) {
|
|||||||
})
|
})
|
||||||
.map((c) => `- ${c.author.login} at ${c.createdAt}: ${c.body}`)
|
.map((c) => `- ${c.author.login} at ${c.createdAt}: ${c.body}`)
|
||||||
|
|
||||||
const files = (pr.files.nodes || []).map((f) => `- ${f.path} (${f.changeType}) +${f.additions}/-${f.deletions}`)
|
const files = (pr.files.nodes || []).map(
|
||||||
|
(f) => `- ${f.path} (${f.changeType}) +${f.additions}/-${f.deletions}`,
|
||||||
|
)
|
||||||
const reviewData = (pr.reviews.nodes || []).map((r) => {
|
const reviewData = (pr.reviews.nodes || []).map((r) => {
|
||||||
const comments = (r.comments.nodes || []).map((c) => ` - ${c.path}:${c.line ?? "?"}: ${c.body}`)
|
const comments = (r.comments.nodes || []).map(
|
||||||
|
(c) => ` - ${c.path}:${c.line ?? "?"}: ${c.body}`,
|
||||||
|
)
|
||||||
return [
|
return [
|
||||||
`- ${r.author.login} at ${r.submittedAt}:`,
|
`- ${r.author.login} at ${r.submittedAt}:`,
|
||||||
` - Review body: ${r.body}`,
|
` - Review body: ${r.body}`,
|
||||||
@@ -960,9 +984,15 @@ function buildPromptDataForPR(pr: GitHubPullRequest) {
|
|||||||
`Deletions: ${pr.deletions}`,
|
`Deletions: ${pr.deletions}`,
|
||||||
`Total Commits: ${pr.commits.totalCount}`,
|
`Total Commits: ${pr.commits.totalCount}`,
|
||||||
`Changed Files: ${pr.files.nodes.length} files`,
|
`Changed Files: ${pr.files.nodes.length} files`,
|
||||||
...(comments.length > 0 ? ["<pull_request_comments>", ...comments, "</pull_request_comments>"] : []),
|
...(comments.length > 0
|
||||||
...(files.length > 0 ? ["<pull_request_changed_files>", ...files, "</pull_request_changed_files>"] : []),
|
? ["<pull_request_comments>", ...comments, "</pull_request_comments>"]
|
||||||
...(reviewData.length > 0 ? ["<pull_request_reviews>", ...reviewData, "</pull_request_reviews>"] : []),
|
: []),
|
||||||
|
...(files.length > 0
|
||||||
|
? ["<pull_request_changed_files>", ...files, "</pull_request_changed_files>"]
|
||||||
|
: []),
|
||||||
|
...(reviewData.length > 0
|
||||||
|
? ["<pull_request_reviews>", ...reviewData, "</pull_request_reviews>"]
|
||||||
|
: []),
|
||||||
"</pull_request>",
|
"</pull_request>",
|
||||||
].join("\n")
|
].join("\n")
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
"description": "AI-powered development tool",
|
"description": "AI-powered development tool",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"packageManager": "bun@1.3.0",
|
"packageManager": "bun@1.3.1",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts",
|
"dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts",
|
||||||
"typecheck": "bun turbo typecheck",
|
"typecheck": "bun turbo typecheck",
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
"dev:remote": "VITE_AUTH_URL=https://auth.dev.opencode.ai bun sst shell --stage=dev bun dev",
|
"dev:remote": "VITE_AUTH_URL=https://auth.dev.opencode.ai bun sst shell --stage=dev bun dev",
|
||||||
"build": "vinxi build && ../../opencode/script/schema.ts ./.output/public/config.json",
|
"build": "vinxi build && ../../opencode/script/schema.ts ./.output/public/config.json",
|
||||||
"start": "vinxi start",
|
"start": "vinxi start",
|
||||||
"version": "1.0.2"
|
"version": "1.0.14"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ibm/plex": "6.4.1",
|
"@ibm/plex": "6.4.1",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"name": "@opencode-ai/console-core",
|
"name": "@opencode-ai/console-core",
|
||||||
"version": "1.0.2",
|
"version": "1.0.14",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-function",
|
"name": "@opencode-ai/console-function",
|
||||||
"version": "1.0.2",
|
"version": "1.0.14",
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-mail",
|
"name": "@opencode-ai/console-mail",
|
||||||
"version": "1.0.2",
|
"version": "1.0.14",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jsx-email/all": "2.2.3",
|
"@jsx-email/all": "2.2.3",
|
||||||
"@jsx-email/cli": "1.4.3",
|
"@jsx-email/cli": "1.4.3",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/desktop",
|
"name": "@opencode-ai/desktop",
|
||||||
"version": "1.0.2",
|
"version": "1.0.14",
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,34 +1,64 @@
|
|||||||
import { For, Match, Switch, createEffect, createMemo, createSignal, onCleanup } from "solid-js"
|
import { For, JSXElement, Match, Show, Switch, createEffect, createMemo, createSignal, onCleanup } from "solid-js"
|
||||||
import { Part } from "@opencode-ai/ui"
|
import { Markdown, Part } from "@opencode-ai/ui"
|
||||||
import { useSync } from "@/context/sync"
|
import { useSync } from "@/context/sync"
|
||||||
import type { AssistantMessage as AssistantMessageType } from "@opencode-ai/sdk"
|
import type { AssistantMessage as AssistantMessageType, Part as PartType, ToolPart } from "@opencode-ai/sdk"
|
||||||
|
import { Spinner } from "./spinner"
|
||||||
|
|
||||||
export function MessageProgress(props: { assistantMessages: () => AssistantMessageType[] }) {
|
export function MessageProgress(props: { assistantMessages: () => AssistantMessageType[]; done?: boolean }) {
|
||||||
const sync = useSync()
|
const sync = useSync()
|
||||||
const items = createMemo(() => props.assistantMessages().flatMap((m) => sync.data.part[m.id]))
|
const parts = createMemo(() => props.assistantMessages().flatMap((m) => sync.data.part[m.id]))
|
||||||
|
const done = createMemo(() => props.done ?? false)
|
||||||
|
const currentTask = createMemo(
|
||||||
|
() =>
|
||||||
|
parts().findLast(
|
||||||
|
(p) =>
|
||||||
|
p &&
|
||||||
|
p.type === "tool" &&
|
||||||
|
p.tool === "task" &&
|
||||||
|
p.state &&
|
||||||
|
"metadata" in p.state &&
|
||||||
|
p.state.metadata &&
|
||||||
|
p.state.metadata.sessionId &&
|
||||||
|
p.state.status === "running",
|
||||||
|
) as ToolPart,
|
||||||
|
)
|
||||||
|
|
||||||
const finishedItems = createMemo(() => [
|
const resolvedParts = createMemo(() => {
|
||||||
"",
|
let resolved = parts()
|
||||||
"",
|
const task = currentTask()
|
||||||
"Loading...",
|
if (task && task.state && "metadata" in task.state && task.state.metadata?.sessionId) {
|
||||||
...items().filter(
|
const messages = sync.data.message[task.state.metadata.sessionId as string]?.filter((m) => m.role === "assistant")
|
||||||
(p) =>
|
resolved = messages?.flatMap((m) => sync.data.part[m.id]) ?? parts()
|
||||||
p?.type === "text" ||
|
}
|
||||||
(p?.type === "reasoning" && p.time?.end) ||
|
return resolved
|
||||||
(p?.type === "tool" && p.state.status === "completed"),
|
})
|
||||||
),
|
const currentText = createMemo(
|
||||||
"",
|
() =>
|
||||||
|
resolvedParts().findLast((p) => p?.type === "text")?.text ||
|
||||||
|
resolvedParts().findLast((p) => p?.type === "reasoning")?.text,
|
||||||
|
)
|
||||||
|
const eligibleItems = createMemo(() => {
|
||||||
|
return resolvedParts().filter((p) => p?.type === "tool" && p.state.status === "completed")
|
||||||
|
})
|
||||||
|
const finishedItems = createMemo<(JSXElement | PartType)[]>(() => [
|
||||||
|
<div class="h-8 w-full" />,
|
||||||
|
<div class="h-8 w-full" />,
|
||||||
|
<div class="flex items-center gap-x-5 pl-3 text-text-base">
|
||||||
|
<Spinner /> <span class="text-12-medium">Thinking...</span>
|
||||||
|
</div>,
|
||||||
|
...eligibleItems(),
|
||||||
|
...(done() ? [<div class="h-8 w-full" />, <div class="h-8 w-full" />, <div class="h-8 w-full" />] : []),
|
||||||
])
|
])
|
||||||
|
|
||||||
const MINIMUM_DELAY = 400
|
const delay = createMemo(() => (done() ? 220 : 400))
|
||||||
const [visibleCount, setVisibleCount] = createSignal(1)
|
const [visibleCount, setVisibleCount] = createSignal(eligibleItems().length)
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const total = finishedItems().length
|
const total = finishedItems().length
|
||||||
if (total > visibleCount()) {
|
if (total > visibleCount()) {
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
setVisibleCount((prev) => prev + 1)
|
setVisibleCount((prev) => prev + 1)
|
||||||
}, MINIMUM_DELAY)
|
}, delay())
|
||||||
onCleanup(() => clearTimeout(timer))
|
onCleanup(() => clearTimeout(timer))
|
||||||
} else if (total < visibleCount()) {
|
} else if (total < visibleCount()) {
|
||||||
setVisibleCount(total)
|
setVisibleCount(total)
|
||||||
@@ -42,41 +72,57 @@ export function MessageProgress(props: { assistantMessages: () => AssistantMessa
|
|||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div class="flex flex-col gap-3">
|
||||||
class="h-30 overflow-hidden pointer-events-none
|
|
||||||
mask-alpha mask-t-from-33% mask-t-from-background-base mask-t-to-transparent
|
|
||||||
mask-b-from-90% mask-b-from-background-base mask-b-to-transparent"
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
class="w-full flex flex-col items-start self-stretch gap-2 py-8
|
class="h-30 overflow-hidden pointer-events-none pb-1
|
||||||
transform transition-transform duration-500 ease-[cubic-bezier(0.22,1,0.36,1)]"
|
mask-alpha mask-t-from-33% mask-t-from-background-base mask-t-to-transparent
|
||||||
style={{ transform: `translateY(${translateY()})` }}
|
mask-b-from-95% mask-b-from-background-base mask-b-to-transparent"
|
||||||
>
|
>
|
||||||
<For each={finishedItems()}>
|
<div
|
||||||
{(part) => {
|
class="w-full flex flex-col items-start self-stretch gap-2 py-8
|
||||||
if (typeof part === "string") return <div class="h-8 flex items-center w-full">{part}</div>
|
transform transition-transform duration-500 ease-[cubic-bezier(0.22,1,0.36,1)]"
|
||||||
const message = createMemo(() => sync.data.message[part.sessionID].find((m) => m.id === part.messageID))
|
style={{ transform: `translateY(${translateY()})` }}
|
||||||
return (
|
>
|
||||||
<div class="h-8 flex items-center w-full">
|
<For each={finishedItems()}>
|
||||||
<Switch>
|
{(part) => {
|
||||||
<Match when={part.type === "text" && part}>
|
if (part && typeof part === "object" && "type" in part) {
|
||||||
{(p) => (
|
const message = createMemo(() => sync.data.message[part.sessionID].find((m) => m.id === part.messageID))
|
||||||
<div
|
return (
|
||||||
textContent={p().text}
|
<div class="h-8 flex items-center w-full">
|
||||||
class="text-12-regular text-text-base whitespace-nowrap truncate w-full"
|
<Switch>
|
||||||
/>
|
<Match when={part.type === "text" && part}>
|
||||||
)}
|
{(p) => (
|
||||||
</Match>
|
<div
|
||||||
<Match when={part.type === "reasoning" && part}>
|
textContent={p().text}
|
||||||
{(p) => <Part message={message()!} part={p()} />}
|
class="text-12-regular text-text-base whitespace-nowrap truncate w-full"
|
||||||
</Match>
|
/>
|
||||||
<Match when={part.type === "tool" && part}>{(p) => <Part message={message()!} part={p()} />}</Match>
|
)}
|
||||||
</Switch>
|
</Match>
|
||||||
</div>
|
<Match when={part.type === "reasoning" && part}>
|
||||||
)
|
{(p) => <Part message={message()!} part={p()} />}
|
||||||
}}
|
</Match>
|
||||||
</For>
|
<Match when={part.type === "tool" && part}>
|
||||||
|
{(p) => <Part message={message()!} part={p()} />}
|
||||||
|
</Match>
|
||||||
|
</Switch>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return <div class="h-8 flex items-center w-full">{part}</div>
|
||||||
|
}}
|
||||||
|
</For>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Show when={currentText()}>
|
||||||
|
{(text) => (
|
||||||
|
<div
|
||||||
|
class="max-h-36 flex flex-col justify-end overflow-hidden py-3
|
||||||
|
mask-alpha mask-t-from-80% mask-t-from-background-base mask-t-to-transparent"
|
||||||
|
>
|
||||||
|
<Markdown text={text()} class="w-full shrink-0 overflow-visible" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -334,7 +334,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
classList={{
|
classList={{
|
||||||
"bg-surface-raised-stronger-non-alpha border border-border-strong-base": true,
|
"bg-surface-raised-stronger-non-alpha border border-border-strong-base": true,
|
||||||
"rounded-2xl overflow-clip focus-within:shadow-xs-border-selected": true,
|
"rounded-2xl overflow-clip focus-within:border-transparent focus-within:shadow-xs-border-select": true,
|
||||||
[props.class ?? ""]: !!props.class,
|
[props.class ?? ""]: !!props.class,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -162,10 +162,32 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||||||
|
|
||||||
const recent = createMemo(() => store.recent.map(find).filter(Boolean))
|
const recent = createMemo(() => store.recent.map(find).filter(Boolean))
|
||||||
|
|
||||||
|
const cycle = (direction: 1 | -1) => {
|
||||||
|
const recentList = recent()
|
||||||
|
const current = currentModel()
|
||||||
|
if (!current) return
|
||||||
|
|
||||||
|
const index = recentList.findIndex((x) => x?.provider.id === current.provider.id && x?.id === current.id)
|
||||||
|
if (index === -1) return
|
||||||
|
|
||||||
|
let next = index + direction
|
||||||
|
if (next < 0) next = recentList.length - 1
|
||||||
|
if (next >= recentList.length) next = 0
|
||||||
|
|
||||||
|
const val = recentList[next]
|
||||||
|
if (!val) return
|
||||||
|
|
||||||
|
model.set({
|
||||||
|
providerID: val.provider.id,
|
||||||
|
modelID: val.id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
current: currentModel,
|
current: currentModel,
|
||||||
recent,
|
recent,
|
||||||
list,
|
list,
|
||||||
|
cycle,
|
||||||
set(model: ModelKey | undefined, options?: { recent?: boolean }) {
|
set(model: ModelKey | undefined, options?: { recent?: boolean }) {
|
||||||
batch(() => {
|
batch(() => {
|
||||||
setStore("model", agent.current().name, model ?? fallbackModel())
|
setStore("model", agent.current().name, model ?? fallbackModel())
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import {
|
|||||||
Accordion,
|
Accordion,
|
||||||
Diff,
|
Diff,
|
||||||
Collapsible,
|
Collapsible,
|
||||||
Part,
|
|
||||||
DiffChanges,
|
DiffChanges,
|
||||||
ProgressCircle,
|
ProgressCircle,
|
||||||
Message,
|
Message,
|
||||||
@@ -548,7 +547,79 @@ export default function Page() {
|
|||||||
<For each={local.session.userMessages()}>
|
<For each={local.session.userMessages()}>
|
||||||
{(message) => {
|
{(message) => {
|
||||||
const diffs = createMemo(() => message.summary?.diffs ?? [])
|
const diffs = createMemo(() => message.summary?.diffs ?? [])
|
||||||
const working = createMemo(() => !message.summary?.title)
|
const working = createMemo(() => !message.summary?.body)
|
||||||
|
const assistantMessages = createMemo(() => {
|
||||||
|
return sync.data.message[activeSession().id]?.filter(
|
||||||
|
(m) => m.role === "assistant" && m.parentID == message.id,
|
||||||
|
) as AssistantMessageType[]
|
||||||
|
})
|
||||||
|
const parts = createMemo(() =>
|
||||||
|
assistantMessages().flatMap((m) => sync.data.part[m.id]),
|
||||||
|
)
|
||||||
|
const lastPart = createMemo(() => parts().slice(-1)?.at(0))
|
||||||
|
const rawStatus = createMemo(() => {
|
||||||
|
const defaultStatus = "Working..."
|
||||||
|
const last = lastPart()
|
||||||
|
if (!last) return defaultStatus
|
||||||
|
|
||||||
|
if (last.type === "tool") {
|
||||||
|
switch (last.tool) {
|
||||||
|
case "task":
|
||||||
|
return "Delegating work..."
|
||||||
|
case "todowrite":
|
||||||
|
case "todoread":
|
||||||
|
return "Planning next steps..."
|
||||||
|
case "read":
|
||||||
|
return "Gathering context..."
|
||||||
|
case "list":
|
||||||
|
case "grep":
|
||||||
|
case "glob":
|
||||||
|
return "Searching the codebase..."
|
||||||
|
case "webfetch":
|
||||||
|
return "Searching the web..."
|
||||||
|
case "edit":
|
||||||
|
case "write":
|
||||||
|
return "Making edits..."
|
||||||
|
case "bash":
|
||||||
|
return "Running commands..."
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else if (last.type === "reasoning") {
|
||||||
|
return "Thinking..."
|
||||||
|
} else if (last.type === "text") {
|
||||||
|
return "Gathering thoughts..."
|
||||||
|
}
|
||||||
|
return defaultStatus
|
||||||
|
})
|
||||||
|
|
||||||
|
const [status, setStatus] = createSignal(rawStatus())
|
||||||
|
let lastStatusChange = Date.now()
|
||||||
|
let statusTimeout: number | undefined
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
const newStatus = rawStatus()
|
||||||
|
if (newStatus === status()) return
|
||||||
|
|
||||||
|
const timeSinceLastChange = Date.now() - lastStatusChange
|
||||||
|
|
||||||
|
if (timeSinceLastChange >= 1000) {
|
||||||
|
setStatus(newStatus)
|
||||||
|
lastStatusChange = Date.now()
|
||||||
|
if (statusTimeout) {
|
||||||
|
clearTimeout(statusTimeout)
|
||||||
|
statusTimeout = undefined
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (statusTimeout) clearTimeout(statusTimeout)
|
||||||
|
statusTimeout = setTimeout(() => {
|
||||||
|
setStatus(rawStatus())
|
||||||
|
lastStatusChange = Date.now()
|
||||||
|
statusTimeout = undefined
|
||||||
|
}, 1000 - timeSinceLastChange) as unknown as number
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li class="group/li flex items-center self-stretch">
|
<li class="group/li flex items-center self-stretch">
|
||||||
<button
|
<button
|
||||||
@@ -570,7 +641,10 @@ export default function Page() {
|
|||||||
"text-text-weak data-[active=true]:text-text-strong group-hover/li:text-text-base": true,
|
"text-text-weak data-[active=true]:text-text-strong group-hover/li:text-text-base": true,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{message.summary?.title ?? local.session.getMessageText(message)}
|
<Switch>
|
||||||
|
<Match when={working()}>{status()}</Match>
|
||||||
|
<Match when={true}>{message.summary?.title}</Match>
|
||||||
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
@@ -583,7 +657,8 @@ export default function Page() {
|
|||||||
<For each={local.session.userMessages()}>
|
<For each={local.session.userMessages()}>
|
||||||
{(message) => {
|
{(message) => {
|
||||||
const isActive = createMemo(() => local.session.activeMessage()?.id === message.id)
|
const isActive = createMemo(() => local.session.activeMessage()?.id === message.id)
|
||||||
const [initialized, setInitialized] = createSignal(!!message.summary?.title)
|
const [titled, setTitled] = createSignal(!!message.summary?.title)
|
||||||
|
const [completed, setCompleted] = createSignal(!!message.summary?.body)
|
||||||
const [expanded, setExpanded] = createSignal(false)
|
const [expanded, setExpanded] = createSignal(false)
|
||||||
const parts = createMemo(() => sync.data.part[message.id])
|
const parts = createMemo(() => sync.data.part[message.id])
|
||||||
const title = createMemo(() => message.summary?.title)
|
const title = createMemo(() => message.summary?.title)
|
||||||
@@ -597,11 +672,18 @@ export default function Page() {
|
|||||||
const hasToolPart = createMemo(() =>
|
const hasToolPart = createMemo(() =>
|
||||||
assistantMessages()
|
assistantMessages()
|
||||||
?.flatMap((m) => sync.data.part[m.id])
|
?.flatMap((m) => sync.data.part[m.id])
|
||||||
.some((p) => p.type === "tool"),
|
.some((p) => p?.type === "tool"),
|
||||||
)
|
)
|
||||||
const working = createMemo(() => !summary())
|
const working = createMemo(() => !summary())
|
||||||
|
|
||||||
|
// allowing time for the animations to finish
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
setTimeout(() => setInitialized(!!title()), 10_000)
|
title()
|
||||||
|
setTimeout(() => setTitled(!!title()), 10_000)
|
||||||
|
})
|
||||||
|
createEffect(() => {
|
||||||
|
summary()
|
||||||
|
setTimeout(() => setCompleted(!!summary()), 1200)
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -612,9 +694,18 @@ export default function Page() {
|
|||||||
>
|
>
|
||||||
{/* Title */}
|
{/* Title */}
|
||||||
<div class="py-2 flex flex-col items-start gap-2 self-stretch sticky top-0 bg-background-stronger z-10">
|
<div class="py-2 flex flex-col items-start gap-2 self-stretch sticky top-0 bg-background-stronger z-10">
|
||||||
<div class="text-14-medium text-text-strong overflow-hidden text-ellipsis min-w-0">
|
<div class="w-full text-14-medium text-text-strong">
|
||||||
<Show when={initialized()} fallback={<Typewriter as="h1" text={title()} />}>
|
<Show
|
||||||
<h1>{title()}</h1>
|
when={titled()}
|
||||||
|
fallback={
|
||||||
|
<Typewriter
|
||||||
|
as="h1"
|
||||||
|
text={title()}
|
||||||
|
class="overflow-hidden text-ellipsis min-w-0 text-nowrap"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<h1 class="overflow-hidden text-ellipsis min-w-0 text-nowrap">{title()}</h1>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -622,7 +713,7 @@ export default function Page() {
|
|||||||
<Message message={message} parts={parts()} />
|
<Message message={message} parts={parts()} />
|
||||||
</div>
|
</div>
|
||||||
{/* Summary */}
|
{/* Summary */}
|
||||||
<Show when={!working()}>
|
<Show when={completed()}>
|
||||||
<div class="w-full flex flex-col gap-6 items-start self-stretch">
|
<div class="w-full flex flex-col gap-6 items-start self-stretch">
|
||||||
<div class="flex flex-col items-start gap-1 self-stretch">
|
<div class="flex flex-col items-start gap-1 self-stretch">
|
||||||
<h2 class="text-12-medium text-text-weak">
|
<h2 class="text-12-medium text-text-weak">
|
||||||
@@ -631,7 +722,14 @@ export default function Page() {
|
|||||||
<Match when={true}>Response</Match>
|
<Match when={true}>Response</Match>
|
||||||
</Switch>
|
</Switch>
|
||||||
</h2>
|
</h2>
|
||||||
<Show when={summary()}>{(summary) => <Markdown text={summary()} />}</Show>
|
<Show when={summary()}>
|
||||||
|
{(summary) => (
|
||||||
|
<Markdown
|
||||||
|
classList={{ "[&>*]:fade-up-text": !diffs().length }}
|
||||||
|
text={summary()}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
<Accordion class="w-full" multiple>
|
<Accordion class="w-full" multiple>
|
||||||
<For each={diffs()}>
|
<For each={diffs()}>
|
||||||
@@ -684,17 +782,17 @@ export default function Page() {
|
|||||||
{/* Response */}
|
{/* Response */}
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<Switch>
|
<Switch>
|
||||||
<Match when={working()}>
|
<Match when={!completed()}>
|
||||||
<MessageProgress assistantMessages={assistantMessages} />
|
<MessageProgress assistantMessages={assistantMessages} done={!working()} />
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={!working() && hasToolPart()}>
|
<Match when={completed() && hasToolPart()}>
|
||||||
<Collapsible variant="ghost" open={expanded()} onOpenChange={setExpanded}>
|
<Collapsible variant="ghost" open={expanded()} onOpenChange={setExpanded}>
|
||||||
<Collapsible.Trigger class="text-text-weak hover:text-text-strong">
|
<Collapsible.Trigger class="text-text-weak hover:text-text-strong">
|
||||||
<div class="flex items-center gap-1 self-stretch">
|
<div class="flex items-center gap-1 self-stretch">
|
||||||
<div class="text-12-medium">
|
<div class="text-12-medium">
|
||||||
<Switch>
|
<Switch>
|
||||||
<Match when={expanded()}>Hide steps</Match>
|
<Match when={expanded()}>Hide details</Match>
|
||||||
<Match when={!expanded()}>Show steps</Match>
|
<Match when={!expanded()}>Show details</Match>
|
||||||
</Switch>
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
<Collapsible.Arrow />
|
<Collapsible.Arrow />
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/function",
|
"name": "@opencode-ai/function",
|
||||||
"version": "1.0.2",
|
"version": "1.0.14",
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"version": "1.0.2",
|
"version": "1.0.14",
|
||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
@@ -54,8 +54,8 @@
|
|||||||
"@opencode-ai/plugin": "workspace:*",
|
"@opencode-ai/plugin": "workspace:*",
|
||||||
"@opencode-ai/script": "workspace:*",
|
"@opencode-ai/script": "workspace:*",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"@opentui/core": "0.0.0-20251031-fc297165",
|
"@opentui/core": "0.1.32",
|
||||||
"@opentui/solid": "0.0.0-20251031-fc297165",
|
"@opentui/solid": "0.1.32",
|
||||||
"@parcel/watcher": "2.5.1",
|
"@parcel/watcher": "2.5.1",
|
||||||
"@solid-primitives/event-bus": "1.1.2",
|
"@solid-primitives/event-bus": "1.1.2",
|
||||||
"@pierre/precision-diffs": "catalog:",
|
"@pierre/precision-diffs": "catalog:",
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
"fuzzysort": "3.1.0",
|
"fuzzysort": "3.1.0",
|
||||||
"gray-matter": "4.0.3",
|
"gray-matter": "4.0.3",
|
||||||
"hono": "catalog:",
|
"hono": "catalog:",
|
||||||
"hono-openapi": "1.0.7",
|
"hono-openapi": "1.1.1",
|
||||||
"ignore": "7.0.5",
|
"ignore": "7.0.5",
|
||||||
"jsonc-parser": "3.3.1",
|
"jsonc-parser": "3.3.1",
|
||||||
"minimatch": "10.0.3",
|
"minimatch": "10.0.3",
|
||||||
|
|||||||
@@ -125,10 +125,8 @@ if (!Script.preview) {
|
|||||||
"build() {",
|
"build() {",
|
||||||
` cd "opencode-\${pkgver}"`,
|
` cd "opencode-\${pkgver}"`,
|
||||||
` bun install`,
|
` bun install`,
|
||||||
" cd packages/tui",
|
" cd ./packages/opencode",
|
||||||
` CGO_ENABLED=0 go build -ldflags="-s -w -X main.Version=\${pkgver}" -o tui cmd/opencode/main.go`,
|
` OPENCODE_CHANNEL=latest OPENCODE_VERSION=${pkgver} bun run ./script/build.ts --single`,
|
||||||
" cd ../opencode",
|
|
||||||
` bun build --define OPENCODE_TUI_PATH="'$(realpath ../tui/tui)'" --define OPENCODE_VERSION="'\${pkgver}'" --compile --target=bun-linux-x64 --outfile=opencode ./src/index.ts`,
|
|
||||||
"}",
|
"}",
|
||||||
"",
|
"",
|
||||||
"package() {",
|
"package() {",
|
||||||
|
|||||||
@@ -28,8 +28,6 @@ import { Storage } from "@/storage/storage"
|
|||||||
import { Command } from "@/command"
|
import { Command } from "@/command"
|
||||||
import { Agent as Agents } from "@/agent/agent"
|
import { Agent as Agents } from "@/agent/agent"
|
||||||
import { Permission } from "@/permission"
|
import { Permission } from "@/permission"
|
||||||
import { Session } from "@/session"
|
|
||||||
import { Identifier } from "@/id/id"
|
|
||||||
import { SessionCompaction } from "@/session/compaction"
|
import { SessionCompaction } from "@/session/compaction"
|
||||||
import type { Config } from "@/config/config"
|
import type { Config } from "@/config/config"
|
||||||
import { MCP } from "@/mcp"
|
import { MCP } from "@/mcp"
|
||||||
@@ -89,7 +87,11 @@ export namespace ACP {
|
|||||||
})
|
})
|
||||||
if (!res) return
|
if (!res) return
|
||||||
if (res.outcome.outcome !== "selected") {
|
if (res.outcome.outcome !== "selected") {
|
||||||
Permission.respond({ sessionID: permission.sessionID, permissionID: permission.id, response: "reject" })
|
Permission.respond({
|
||||||
|
sessionID: permission.sessionID,
|
||||||
|
permissionID: permission.id,
|
||||||
|
response: "reject",
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
Permission.respond({
|
Permission.respond({
|
||||||
@@ -111,9 +113,11 @@ export namespace ACP {
|
|||||||
const acpSession = this.sessionManager.get(part.sessionID)
|
const acpSession = this.sessionManager.get(part.sessionID)
|
||||||
if (!acpSession) return
|
if (!acpSession) return
|
||||||
|
|
||||||
const message = await Storage.read<MessageV2.Info>(["message", part.sessionID, part.messageID]).catch(
|
const message = await Storage.read<MessageV2.Info>([
|
||||||
() => undefined,
|
"message",
|
||||||
)
|
part.sessionID,
|
||||||
|
part.messageID,
|
||||||
|
]).catch(() => undefined)
|
||||||
if (!message || message.role !== "assistant") return
|
if (!message || message.role !== "assistant") return
|
||||||
|
|
||||||
if (part.type === "tool") {
|
if (part.type === "tool") {
|
||||||
@@ -192,7 +196,9 @@ export namespace ACP {
|
|||||||
sessionUpdate: "plan",
|
sessionUpdate: "plan",
|
||||||
entries: parsedTodos.data.map((todo) => {
|
entries: parsedTodos.data.map((todo) => {
|
||||||
const status: PlanEntry["status"] =
|
const status: PlanEntry["status"] =
|
||||||
todo.status === "cancelled" ? "completed" : (todo.status as PlanEntry["status"])
|
todo.status === "cancelled"
|
||||||
|
? "completed"
|
||||||
|
: (todo.status as PlanEntry["status"])
|
||||||
return {
|
return {
|
||||||
priority: "medium",
|
priority: "medium",
|
||||||
status,
|
status,
|
||||||
@@ -375,11 +381,6 @@ export namespace ACP {
|
|||||||
description: command.description ?? "",
|
description: command.description ?? "",
|
||||||
}))
|
}))
|
||||||
const names = new Set(availableCommands.map((c) => c.name))
|
const names = new Set(availableCommands.map((c) => c.name))
|
||||||
if (!names.has("init"))
|
|
||||||
availableCommands.push({
|
|
||||||
name: "init",
|
|
||||||
description: "create/update a AGENTS.md",
|
|
||||||
})
|
|
||||||
if (!names.has("compact"))
|
if (!names.has("compact"))
|
||||||
availableCommands.push({
|
availableCommands.push({
|
||||||
name: "compact",
|
name: "compact",
|
||||||
@@ -404,7 +405,8 @@ export namespace ACP {
|
|||||||
description: agent.description,
|
description: agent.description,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const currentModeId = availableModes.find((m) => m.name === "build")?.id ?? availableModes[0].id
|
const currentModeId =
|
||||||
|
availableModes.find((m) => m.name === "build")?.id ?? availableModes[0].id
|
||||||
|
|
||||||
const mcpServers: Record<string, Config.Mcp> = {}
|
const mcpServers: Record<string, Config.Mcp> = {}
|
||||||
for (const server of params.mcpServers) {
|
for (const server of params.mcpServers) {
|
||||||
@@ -585,14 +587,6 @@ export namespace ACP {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (cmd.name) {
|
switch (cmd.name) {
|
||||||
case "init":
|
|
||||||
await Session.initialize({
|
|
||||||
sessionID,
|
|
||||||
messageID: Identifier.ascending("message"),
|
|
||||||
providerID: model.providerID,
|
|
||||||
modelID: model.modelID,
|
|
||||||
})
|
|
||||||
break
|
|
||||||
case "compact":
|
case "compact":
|
||||||
await SessionCompaction.run({
|
await SessionCompaction.run({
|
||||||
sessionID,
|
sessionID,
|
||||||
@@ -665,7 +659,9 @@ export namespace ACP {
|
|||||||
|
|
||||||
function parseUri(
|
function parseUri(
|
||||||
uri: string,
|
uri: string,
|
||||||
): { type: "file"; url: string; filename: string; mime: string } | { type: "text"; text: string } {
|
):
|
||||||
|
| { type: "file"; url: string; filename: string; mime: string }
|
||||||
|
| { type: "text"; text: string } {
|
||||||
try {
|
try {
|
||||||
if (uri.startsWith("file://")) {
|
if (uri.startsWith("file://")) {
|
||||||
const path = uri.slice(7)
|
const path = uri.slice(7)
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ export const ServeCommand = cmd({
|
|||||||
default: 0,
|
default: 0,
|
||||||
})
|
})
|
||||||
.option("hostname", {
|
.option("hostname", {
|
||||||
alias: ["h"],
|
|
||||||
type: "string",
|
type: "string",
|
||||||
describe: "hostname to listen on",
|
describe: "hostname to listen on",
|
||||||
default: "127.0.0.1",
|
default: "127.0.0.1",
|
||||||
|
|||||||
@@ -65,7 +65,6 @@ export const TuiCommand = cmd({
|
|||||||
default: 0,
|
default: 0,
|
||||||
})
|
})
|
||||||
.option("hostname", {
|
.option("hostname", {
|
||||||
alias: ["h"],
|
|
||||||
type: "string",
|
type: "string",
|
||||||
describe: "hostname to listen on",
|
describe: "hostname to listen on",
|
||||||
default: "127.0.0.1",
|
default: "127.0.0.1",
|
||||||
|
|||||||
@@ -23,21 +23,81 @@ import { Session } from "@tui/routes/session"
|
|||||||
import { PromptHistoryProvider } from "./component/prompt/history"
|
import { PromptHistoryProvider } from "./component/prompt/history"
|
||||||
import { DialogAlert } from "./ui/dialog-alert"
|
import { DialogAlert } from "./ui/dialog-alert"
|
||||||
import { ToastProvider, useToast } from "./ui/toast"
|
import { ToastProvider, useToast } from "./ui/toast"
|
||||||
import { ExitProvider } from "./context/exit"
|
import { ExitProvider, useExit } from "./context/exit"
|
||||||
import type { SessionRoute } from "./context/route"
|
import type { SessionRoute } from "./context/route"
|
||||||
import { Session as SessionApi } from "@/session"
|
import { Session as SessionApi } from "@/session"
|
||||||
import { TuiEvent } from "./event"
|
import { TuiEvent } from "./event"
|
||||||
import { KVProvider, useKV } from "./context/kv"
|
import { KVProvider, useKV } from "./context/kv"
|
||||||
|
|
||||||
|
async function getTerminalBackgroundColor(): Promise<"dark" | "light"> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
let timeout: NodeJS.Timeout
|
||||||
|
|
||||||
|
const cleanup = () => {
|
||||||
|
process.stdin.setRawMode(false)
|
||||||
|
process.stdin.removeListener("data", handler)
|
||||||
|
clearTimeout(timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handler = (data: Buffer) => {
|
||||||
|
const str = data.toString()
|
||||||
|
const match = str.match(/\x1b]11;([^\x07\x1b]+)/)
|
||||||
|
if (match) {
|
||||||
|
cleanup()
|
||||||
|
const color = match[1]
|
||||||
|
// Parse RGB values from color string
|
||||||
|
// Formats: rgb:RR/GG/BB or #RRGGBB or rgb(R,G,B)
|
||||||
|
let r = 0,
|
||||||
|
g = 0,
|
||||||
|
b = 0
|
||||||
|
|
||||||
|
if (color.startsWith("rgb:")) {
|
||||||
|
const parts = color.substring(4).split("/")
|
||||||
|
r = parseInt(parts[0], 16) >> 8 // Convert 16-bit to 8-bit
|
||||||
|
g = parseInt(parts[1], 16) >> 8 // Convert 16-bit to 8-bit
|
||||||
|
b = parseInt(parts[2], 16) >> 8 // Convert 16-bit to 8-bit
|
||||||
|
} else if (color.startsWith("#")) {
|
||||||
|
r = parseInt(color.substring(1, 3), 16)
|
||||||
|
g = parseInt(color.substring(3, 5), 16)
|
||||||
|
b = parseInt(color.substring(5, 7), 16)
|
||||||
|
} else if (color.startsWith("rgb(")) {
|
||||||
|
const parts = color.substring(4, color.length - 1).split(",")
|
||||||
|
r = parseInt(parts[0])
|
||||||
|
g = parseInt(parts[1])
|
||||||
|
b = parseInt(parts[2])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate luminance using relative luminance formula
|
||||||
|
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255
|
||||||
|
|
||||||
|
// Determine if dark or light based on luminance threshold
|
||||||
|
resolve(luminance > 0.5 ? "light" : "dark")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
process.stdin.setRawMode(true)
|
||||||
|
process.stdin.on("data", handler)
|
||||||
|
process.stdout.write("\x1b]11;?\x07")
|
||||||
|
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
cleanup()
|
||||||
|
resolve("dark")
|
||||||
|
}, 1000)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export function tui(input: {
|
export function tui(input: {
|
||||||
url: string
|
url: string
|
||||||
sessionID?: string
|
sessionID?: string
|
||||||
model?: string
|
model?: string
|
||||||
agent?: string
|
agent?: string
|
||||||
|
prompt?: string
|
||||||
onExit?: () => Promise<void>
|
onExit?: () => Promise<void>
|
||||||
}) {
|
}) {
|
||||||
// promise to prevent immediate exit
|
// promise to prevent immediate exit
|
||||||
return new Promise<void>((resolve) => {
|
return new Promise<void>(async (resolve) => {
|
||||||
|
const mode = await getTerminalBackgroundColor()
|
||||||
|
|
||||||
const routeData: Route | undefined = input.sessionID
|
const routeData: Route | undefined = input.sessionID
|
||||||
? {
|
? {
|
||||||
type: "session",
|
type: "session",
|
||||||
@@ -53,15 +113,23 @@ export function tui(input: {
|
|||||||
render(
|
render(
|
||||||
() => {
|
() => {
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary fallback={<text>Something went wrong</text>}>
|
<ErrorBoundary
|
||||||
|
fallback={(error, reset) => (
|
||||||
|
<ErrorComponent error={error} reset={reset} onExit={onExit} />
|
||||||
|
)}
|
||||||
|
>
|
||||||
<ExitProvider onExit={onExit}>
|
<ExitProvider onExit={onExit}>
|
||||||
<KVProvider>
|
<KVProvider>
|
||||||
<ToastProvider>
|
<ToastProvider>
|
||||||
<RouteProvider data={routeData}>
|
<RouteProvider data={routeData}>
|
||||||
<SDKProvider url={input.url}>
|
<SDKProvider url={input.url}>
|
||||||
<SyncProvider>
|
<SyncProvider>
|
||||||
<ThemeProvider>
|
<ThemeProvider mode={mode}>
|
||||||
<LocalProvider initialModel={input.model} initialAgent={input.agent}>
|
<LocalProvider
|
||||||
|
initialModel={input.model}
|
||||||
|
initialAgent={input.agent}
|
||||||
|
initialPrompt={input.prompt}
|
||||||
|
>
|
||||||
<KeybindProvider>
|
<KeybindProvider>
|
||||||
<DialogProvider>
|
<DialogProvider>
|
||||||
<CommandProvider>
|
<CommandProvider>
|
||||||
@@ -86,6 +154,7 @@ export function tui(input: {
|
|||||||
targetFps: 60,
|
targetFps: 60,
|
||||||
gatherStats: false,
|
gatherStats: false,
|
||||||
exitOnCtrlC: false,
|
exitOnCtrlC: false,
|
||||||
|
useKittyKeyboard: true,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -104,7 +173,8 @@ function App() {
|
|||||||
const sync = useSync()
|
const sync = useSync()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const [sessionExists, setSessionExists] = createSignal(false)
|
const [sessionExists, setSessionExists] = createSignal(false)
|
||||||
const { theme } = useTheme()
|
const { theme, mode, setMode } = useTheme()
|
||||||
|
const exit = useExit()
|
||||||
|
|
||||||
useKeyboard(async (evt) => {
|
useKeyboard(async (evt) => {
|
||||||
if (evt.meta && evt.name === "t") {
|
if (evt.meta && evt.name === "t") {
|
||||||
@@ -168,6 +238,24 @@ function App() {
|
|||||||
dialog.replace(() => <DialogModel />)
|
dialog.replace(() => <DialogModel />)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "Model cycle",
|
||||||
|
value: "model.cycle_recent",
|
||||||
|
keybind: "model_cycle_recent",
|
||||||
|
category: "Agent",
|
||||||
|
onSelect: () => {
|
||||||
|
local.model.cycle(1)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Model cycle reverse",
|
||||||
|
value: "model.cycle_recent_reverse",
|
||||||
|
keybind: "model_cycle_recent_reverse",
|
||||||
|
category: "Agent",
|
||||||
|
onSelect: () => {
|
||||||
|
local.model.cycle(-1)
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "Switch agent",
|
title: "Switch agent",
|
||||||
value: "agent.list",
|
value: "agent.list",
|
||||||
@@ -214,6 +302,14 @@ function App() {
|
|||||||
},
|
},
|
||||||
category: "System",
|
category: "System",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: `Switch to ${mode() === "dark" ? "light" : "dark"} mode`,
|
||||||
|
value: "theme.switch_mode",
|
||||||
|
onSelect: () => {
|
||||||
|
setMode(mode() === "dark" ? "light" : "dark")
|
||||||
|
},
|
||||||
|
category: "System",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "Help",
|
title: "Help",
|
||||||
value: "help.show",
|
value: "help.show",
|
||||||
@@ -222,11 +318,17 @@ function App() {
|
|||||||
},
|
},
|
||||||
category: "System",
|
category: "System",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "Exit the app",
|
||||||
|
value: "app.exit",
|
||||||
|
onSelect: exit,
|
||||||
|
category: "System",
|
||||||
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const providerID = local.model.current().providerID
|
const providerID = local.model.current().providerID
|
||||||
if (providerID === "openrouter" && !kv.data.openrouter_warning) {
|
if (providerID === "openrouter" && !kv.get("openrouter_warning", false)) {
|
||||||
untrack(() => {
|
untrack(() => {
|
||||||
DialogAlert.show(
|
DialogAlert.show(
|
||||||
dialog,
|
dialog,
|
||||||
@@ -274,8 +376,9 @@ function App() {
|
|||||||
/* @ts-expect-error */
|
/* @ts-expect-error */
|
||||||
renderer.writeOut(finalOsc52)
|
renderer.writeOut(finalOsc52)
|
||||||
await Clipboard.copy(text)
|
await Clipboard.copy(text)
|
||||||
|
.then(() => toast.show({ message: "Copied to clipboard", variant: "info" }))
|
||||||
|
.catch(toast.error)
|
||||||
renderer.clearSelection()
|
renderer.clearSelection()
|
||||||
toast.show({ message: "Copied to clipboard", variant: "info" })
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -304,7 +407,9 @@ function App() {
|
|||||||
paddingRight={1}
|
paddingRight={1}
|
||||||
>
|
>
|
||||||
<text fg={theme.textMuted}>open</text>
|
<text fg={theme.textMuted}>open</text>
|
||||||
<text attributes={TextAttributes.BOLD}>code </text>
|
<text fg={theme.text} attributes={TextAttributes.BOLD}>
|
||||||
|
code{" "}
|
||||||
|
</text>
|
||||||
<text fg={theme.textMuted}>v{Installation.VERSION}</text>
|
<text fg={theme.textMuted}>v{Installation.VERSION}</text>
|
||||||
</box>
|
</box>
|
||||||
<box paddingLeft={1} paddingRight={1}>
|
<box paddingLeft={1} paddingRight={1}>
|
||||||
@@ -319,7 +424,7 @@ function App() {
|
|||||||
<text
|
<text
|
||||||
bg={local.agent.color(local.agent.current().name)}
|
bg={local.agent.color(local.agent.current().name)}
|
||||||
fg={theme.background}
|
fg={theme.background}
|
||||||
wrapMode="none"
|
wrapMode={undefined}
|
||||||
>
|
>
|
||||||
<span style={{ bold: true }}> {local.agent.current().name.toUpperCase()}</span>
|
<span style={{ bold: true }}> {local.agent.current().name.toUpperCase()}</span>
|
||||||
<span> AGENT </span>
|
<span> AGENT </span>
|
||||||
@@ -329,3 +434,57 @@ function App() {
|
|||||||
</box>
|
</box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ErrorComponent(props: { error: Error; reset: () => void; onExit: () => Promise<void> }) {
|
||||||
|
const term = useTerminalDimensions()
|
||||||
|
useKeyboard((evt) => {
|
||||||
|
if (evt.ctrl && evt.name === "c") {
|
||||||
|
props.onExit()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const [copied, setCopied] = createSignal(false)
|
||||||
|
|
||||||
|
const issueURL = new URL("https://github.com/sst/opencode/issues/new?template=bug-report.yml")
|
||||||
|
|
||||||
|
if (props.error.message) {
|
||||||
|
issueURL.searchParams.set("title", `opentui: fatal: ${props.error.message}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.error.stack) {
|
||||||
|
issueURL.searchParams.set(
|
||||||
|
"description",
|
||||||
|
"```\n" + props.error.stack.substring(0, 6000 - issueURL.toString().length) + "...\n```",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const copyIssueURL = () => {
|
||||||
|
Clipboard.copy(issueURL.toString()).then(() => {
|
||||||
|
setCopied(true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<box flexDirection="column" gap={1}>
|
||||||
|
<box flexDirection="row" gap={1} alignItems="center">
|
||||||
|
<text attributes={TextAttributes.BOLD}>Please report an issue.</text>
|
||||||
|
<box onMouseUp={copyIssueURL} backgroundColor="#565f89" padding={1}>
|
||||||
|
<text attributes={TextAttributes.BOLD}>Copy issue URL (exception info pre-filled)</text>
|
||||||
|
</box>
|
||||||
|
{copied() && <text>Successfully copied</text>}
|
||||||
|
</box>
|
||||||
|
<box flexDirection="row" gap={2} alignItems="center">
|
||||||
|
<text>A fatal error occurred!</text>
|
||||||
|
<box onMouseUp={props.reset} backgroundColor="#565f89" padding={1}>
|
||||||
|
<text>Reset TUI</text>
|
||||||
|
</box>
|
||||||
|
<box onMouseUp={props.onExit} backgroundColor="#565f89" padding={1}>
|
||||||
|
<text>Exit</text>
|
||||||
|
</box>
|
||||||
|
</box>
|
||||||
|
<scrollbox height={Math.floor(term().height * 0.7)}>
|
||||||
|
<text>{props.error.stack}</text>
|
||||||
|
</scrollbox>
|
||||||
|
<text>{props.error.message}</text>
|
||||||
|
</box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,7 +28,9 @@ function init() {
|
|||||||
return registrations().flatMap((x) => x())
|
return registrations().flatMap((x) => x())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let keybinds = true
|
||||||
useKeyboard((evt) => {
|
useKeyboard((evt) => {
|
||||||
|
if (!keybinds) return
|
||||||
for (const option of options()) {
|
for (const option of options()) {
|
||||||
if (option.keybind && keybind.match(option.keybind, evt)) {
|
if (option.keybind && keybind.match(option.keybind, evt)) {
|
||||||
evt.preventDefault()
|
evt.preventDefault()
|
||||||
@@ -39,14 +41,20 @@ function init() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const result = {
|
const result = {
|
||||||
trigger(name: string) {
|
trigger(name: string, source?: "prompt") {
|
||||||
for (const option of options()) {
|
for (const option of options()) {
|
||||||
if (option.value === name) {
|
if (option.value === name) {
|
||||||
option.onSelect?.(dialog)
|
option.onSelect?.(dialog, source)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
keybinds(enabled: boolean) {
|
||||||
|
keybinds = enabled
|
||||||
|
},
|
||||||
|
show() {
|
||||||
|
dialog.replace(() => <DialogCommand options={options()} />)
|
||||||
|
},
|
||||||
register(cb: () => CommandOption[]) {
|
register(cb: () => CommandOption[]) {
|
||||||
const results = createMemo(cb)
|
const results = createMemo(cb)
|
||||||
setRegistrations((arr) => [results, ...arr])
|
setRegistrations((arr) => [results, ...arr])
|
||||||
@@ -75,7 +83,7 @@ export function CommandProvider(props: ParentProps) {
|
|||||||
const keybind = useKeybind()
|
const keybind = useKeybind()
|
||||||
|
|
||||||
useKeyboard((evt) => {
|
useKeyboard((evt) => {
|
||||||
if (keybind.match("command_list", evt)) {
|
if (keybind.match("command_list", evt) && dialog.stack.length === 0) {
|
||||||
evt.preventDefault()
|
evt.preventDefault()
|
||||||
dialog.replace(() => <DialogCommand options={value.options} />)
|
dialog.replace(() => <DialogCommand options={value.options} />)
|
||||||
return
|
return
|
||||||
@@ -90,7 +98,10 @@ function DialogCommand(props: { options: CommandOption[] }) {
|
|||||||
return (
|
return (
|
||||||
<DialogSelect
|
<DialogSelect
|
||||||
title="Commands"
|
title="Commands"
|
||||||
options={props.options.map((x) => ({ ...x, footer: x.keybind ? keybind.print(x.keybind) : undefined }))}
|
options={props.options.map((x) => ({
|
||||||
|
...x,
|
||||||
|
footer: x.keybind ? keybind.print(x.keybind) : undefined,
|
||||||
|
}))}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { Locale } from "@/util/locale"
|
|||||||
import { Keybind } from "@/util/keybind"
|
import { Keybind } from "@/util/keybind"
|
||||||
import { useTheme } from "../context/theme"
|
import { useTheme } from "../context/theme"
|
||||||
import { useSDK } from "../context/sdk"
|
import { useSDK } from "../context/sdk"
|
||||||
|
import { DialogSessionRename } from "./dialog-session-rename"
|
||||||
|
|
||||||
export function DialogSessionList() {
|
export function DialogSessionList() {
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
@@ -59,7 +60,7 @@ export function DialogSessionList() {
|
|||||||
}}
|
}}
|
||||||
keybind={[
|
keybind={[
|
||||||
{
|
{
|
||||||
keybind: Keybind.parse("delete")[0],
|
keybind: Keybind.parse("ctrl+d")[0],
|
||||||
title: "delete",
|
title: "delete",
|
||||||
onTrigger: async (option) => {
|
onTrigger: async (option) => {
|
||||||
if (toDelete() === option.value) {
|
if (toDelete() === option.value) {
|
||||||
@@ -74,6 +75,13 @@ export function DialogSessionList() {
|
|||||||
setToDelete(option.value)
|
setToDelete(option.value)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
keybind: Keybind.parse("ctrl+r")[0],
|
||||||
|
title: "rename",
|
||||||
|
onTrigger: async (option) => {
|
||||||
|
dialog.replace(() => <DialogSessionRename session={option.value} />)
|
||||||
|
},
|
||||||
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { DialogPrompt } from "@tui/ui/dialog-prompt"
|
||||||
|
import { useDialog } from "@tui/ui/dialog"
|
||||||
|
import { useSync } from "@tui/context/sync"
|
||||||
|
import { createMemo } from "solid-js"
|
||||||
|
import { useSDK } from "../context/sdk"
|
||||||
|
|
||||||
|
interface DialogSessionRenameProps {
|
||||||
|
session: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DialogSessionRename(props: DialogSessionRenameProps) {
|
||||||
|
const dialog = useDialog()
|
||||||
|
const sync = useSync()
|
||||||
|
const sdk = useSDK()
|
||||||
|
const session = createMemo(() => sync.session.get(props.session))
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DialogPrompt
|
||||||
|
title="Rename Session"
|
||||||
|
value={session()?.title}
|
||||||
|
onConfirm={(value) => {
|
||||||
|
sdk.client.session.update({
|
||||||
|
path: {
|
||||||
|
id: props.session,
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
title: value,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
dialog.clear()
|
||||||
|
}}
|
||||||
|
onCancel={() => dialog.clear()}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { TextAttributes } from "@opentui/core"
|
import { TextAttributes } from "@opentui/core"
|
||||||
import { useTheme } from "../context/theme"
|
import { useTheme } from "../context/theme"
|
||||||
import { useSync } from "@tui/context/sync"
|
import { useSync } from "@tui/context/sync"
|
||||||
import { For, Match, Switch, Show } from "solid-js"
|
import { For, Match, Switch, Show, createMemo } from "solid-js"
|
||||||
|
|
||||||
export type DialogStatusProps = {}
|
export type DialogStatusProps = {}
|
||||||
|
|
||||||
@@ -9,15 +9,19 @@ export function DialogStatus() {
|
|||||||
const sync = useSync()
|
const sync = useSync()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
|
||||||
|
const enabledFormatters = createMemo(() => sync.data.formatter.filter((f) => f.enabled))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<box paddingLeft={2} paddingRight={2} gap={1} paddingBottom={1}>
|
<box paddingLeft={2} paddingRight={2} gap={1} paddingBottom={1}>
|
||||||
<box flexDirection="row" justifyContent="space-between">
|
<box flexDirection="row" justifyContent="space-between">
|
||||||
<text attributes={TextAttributes.BOLD}>Status</text>
|
<text fg={theme.text} attributes={TextAttributes.BOLD}>
|
||||||
|
Status
|
||||||
|
</text>
|
||||||
<text fg={theme.textMuted}>esc</text>
|
<text fg={theme.textMuted}>esc</text>
|
||||||
</box>
|
</box>
|
||||||
<Show when={Object.keys(sync.data.mcp).length > 0}>
|
<Show when={Object.keys(sync.data.mcp).length > 0} fallback={<text>No MCP Servers</text>}>
|
||||||
<box>
|
<box>
|
||||||
<text>{Object.keys(sync.data.mcp).length} MCP Servers</text>
|
<text fg={theme.text}>{Object.keys(sync.data.mcp).length} MCP Servers</text>
|
||||||
<For each={Object.entries(sync.data.mcp)}>
|
<For each={Object.entries(sync.data.mcp)}>
|
||||||
{([key, item]) => (
|
{([key, item]) => (
|
||||||
<box flexDirection="row" gap={1}>
|
<box flexDirection="row" gap={1}>
|
||||||
@@ -33,7 +37,7 @@ export function DialogStatus() {
|
|||||||
>
|
>
|
||||||
•
|
•
|
||||||
</text>
|
</text>
|
||||||
<text wrapMode="word">
|
<text fg={theme.text} wrapMode="word">
|
||||||
<b>{key}</b>{" "}
|
<b>{key}</b>{" "}
|
||||||
<span style={{ fg: theme.textMuted }}>
|
<span style={{ fg: theme.textMuted }}>
|
||||||
<Switch>
|
<Switch>
|
||||||
@@ -50,7 +54,7 @@ export function DialogStatus() {
|
|||||||
</Show>
|
</Show>
|
||||||
{sync.data.lsp.length > 0 && (
|
{sync.data.lsp.length > 0 && (
|
||||||
<box>
|
<box>
|
||||||
<text>{sync.data.lsp.length} LSP Servers</text>
|
<text fg={theme.text}>{sync.data.lsp.length} LSP Servers</text>
|
||||||
<For each={sync.data.lsp}>
|
<For each={sync.data.lsp}>
|
||||||
{(item) => (
|
{(item) => (
|
||||||
<box flexDirection="row" gap={1}>
|
<box flexDirection="row" gap={1}>
|
||||||
@@ -65,7 +69,7 @@ export function DialogStatus() {
|
|||||||
>
|
>
|
||||||
•
|
•
|
||||||
</text>
|
</text>
|
||||||
<text wrapMode="word">
|
<text fg={theme.text} wrapMode="word">
|
||||||
<b>{item.id}</b> <span style={{ fg: theme.textMuted }}>{item.root}</span>
|
<b>{item.id}</b> <span style={{ fg: theme.textMuted }}>{item.root}</span>
|
||||||
</text>
|
</text>
|
||||||
</box>
|
</box>
|
||||||
@@ -73,6 +77,31 @@ export function DialogStatus() {
|
|||||||
</For>
|
</For>
|
||||||
</box>
|
</box>
|
||||||
)}
|
)}
|
||||||
|
<Show
|
||||||
|
when={enabledFormatters().length > 0}
|
||||||
|
fallback={<text fg={theme.text}>No Formatters</text>}
|
||||||
|
>
|
||||||
|
<box>
|
||||||
|
<text fg={theme.text}>{enabledFormatters().length} Formatters</text>
|
||||||
|
<For each={enabledFormatters()}>
|
||||||
|
{(item) => (
|
||||||
|
<box flexDirection="row" gap={1}>
|
||||||
|
<text
|
||||||
|
flexShrink={0}
|
||||||
|
style={{
|
||||||
|
fg: theme.success,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
•
|
||||||
|
</text>
|
||||||
|
<text wrapMode="word" fg={theme.text}>
|
||||||
|
<b>{item.name}</b>
|
||||||
|
</text>
|
||||||
|
</box>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</box>
|
||||||
|
</Show>
|
||||||
</box>
|
</box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,15 +12,16 @@ export function DialogThemeList() {
|
|||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
let confirmed = false
|
let confirmed = false
|
||||||
let ref: DialogSelectRef<keyof typeof THEMES>
|
let ref: DialogSelectRef<keyof typeof THEMES>
|
||||||
const initial = theme.selectedTheme
|
const initial = theme.selected
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
// highlight the first theme in the list when we open it for UX
|
// highlight the first theme in the list when we open it for UX
|
||||||
theme.setSelectedTheme(Object.keys(THEMES)[0] as keyof typeof THEMES)
|
theme.set(Object.keys(THEMES)[0] as keyof typeof THEMES)
|
||||||
})
|
})
|
||||||
|
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
// if we close the dialog without confirming, reset back to the initial theme
|
// if we close the dialog without confirming, reset back to the initial theme
|
||||||
if (!confirmed) theme.setSelectedTheme(initial)
|
if (!confirmed) theme.set(initial)
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -28,10 +29,10 @@ export function DialogThemeList() {
|
|||||||
title="Themes"
|
title="Themes"
|
||||||
options={options}
|
options={options}
|
||||||
onMove={(opt) => {
|
onMove={(opt) => {
|
||||||
theme.setSelectedTheme(opt.value)
|
theme.set(opt.value)
|
||||||
}}
|
}}
|
||||||
onSelect={(opt) => {
|
onSelect={(opt) => {
|
||||||
theme.setSelectedTheme(opt.value)
|
theme.set(opt.value)
|
||||||
confirmed = true
|
confirmed = true
|
||||||
dialog.clear()
|
dialog.clear()
|
||||||
}}
|
}}
|
||||||
@@ -40,12 +41,12 @@ export function DialogThemeList() {
|
|||||||
}}
|
}}
|
||||||
onFilter={(query) => {
|
onFilter={(query) => {
|
||||||
if (query.length === 0) {
|
if (query.length === 0) {
|
||||||
theme.setSelectedTheme(initial)
|
theme.set(initial)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const first = ref.filtered[0]
|
const first = ref.filtered[0]
|
||||||
if (first) theme.setSelectedTheme(first.value)
|
if (first) theme.set(first.value)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export type AutocompleteRef = {
|
|||||||
|
|
||||||
export type AutocompleteOption = {
|
export type AutocompleteOption = {
|
||||||
display: string
|
display: string
|
||||||
|
aliases?: string[]
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
description?: string
|
description?: string
|
||||||
onSelect?: () => void
|
onSelect?: () => void
|
||||||
@@ -207,6 +208,7 @@ export function Autocomplete(props: {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
display: "/compact",
|
display: "/compact",
|
||||||
|
aliases: ["/summarize"],
|
||||||
description: "compact the session",
|
description: "compact the session",
|
||||||
onSelect: () => command.trigger("session.compact"),
|
onSelect: () => command.trigger("session.compact"),
|
||||||
},
|
},
|
||||||
@@ -222,11 +224,22 @@ export function Autocomplete(props: {
|
|||||||
description: "unshare a session",
|
description: "unshare a session",
|
||||||
onSelect: () => command.trigger("session.unshare"),
|
onSelect: () => command.trigger("session.unshare"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
display: "/rename",
|
||||||
|
description: "rename session",
|
||||||
|
onSelect: () => command.trigger("session.rename"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
display: "/timeline",
|
||||||
|
description: "jump to message",
|
||||||
|
onSelect: () => command.trigger("session.timeline"),
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
results.push(
|
results.push(
|
||||||
{
|
{
|
||||||
display: "/new",
|
display: "/new",
|
||||||
|
aliases: ["/clear"],
|
||||||
description: "create a new session",
|
description: "create a new session",
|
||||||
onSelect: () => command.trigger("session.new"),
|
onSelect: () => command.trigger("session.new"),
|
||||||
},
|
},
|
||||||
@@ -242,6 +255,7 @@ export function Autocomplete(props: {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
display: "/session",
|
display: "/session",
|
||||||
|
aliases: ["/resume", "/continue"],
|
||||||
description: "list sessions",
|
description: "list sessions",
|
||||||
onSelect: () => command.trigger("session.list"),
|
onSelect: () => command.trigger("session.list"),
|
||||||
},
|
},
|
||||||
@@ -250,11 +264,32 @@ export function Autocomplete(props: {
|
|||||||
description: "show status",
|
description: "show status",
|
||||||
onSelect: () => command.trigger("opencode.status"),
|
onSelect: () => command.trigger("opencode.status"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
display: "/theme",
|
||||||
|
description: "toggle theme",
|
||||||
|
onSelect: () => command.trigger("theme.switch"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
display: "/editor",
|
||||||
|
description: "open editor",
|
||||||
|
onSelect: () => command.trigger("prompt.editor", "prompt"),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
display: "/help",
|
display: "/help",
|
||||||
description: "show help",
|
description: "show help",
|
||||||
onSelect: () => command.trigger("help.show"),
|
onSelect: () => command.trigger("help.show"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
display: "/commands",
|
||||||
|
description: "show all commands",
|
||||||
|
onSelect: () => command.show(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
display: "/exit",
|
||||||
|
aliases: ["/quit", "/q"],
|
||||||
|
description: "exit the app",
|
||||||
|
onSelect: () => command.trigger("app.exit"),
|
||||||
|
},
|
||||||
)
|
)
|
||||||
const max = firstBy(results, [(x) => x.display.length, "desc"])?.display.length
|
const max = firstBy(results, [(x) => x.display.length, "desc"])?.display.length
|
||||||
if (!max) return results
|
if (!max) return results
|
||||||
@@ -273,7 +308,7 @@ export function Autocomplete(props: {
|
|||||||
const currentFilter = filter()
|
const currentFilter = filter()
|
||||||
if (!currentFilter) return mixed.slice(0, 10)
|
if (!currentFilter) return mixed.slice(0, 10)
|
||||||
const result = fuzzysort.go(currentFilter, mixed, {
|
const result = fuzzysort.go(currentFilter, mixed, {
|
||||||
keys: ["display", "description"],
|
keys: ["display", "description", (obj) => obj.aliases?.join(" ") ?? ""],
|
||||||
limit: 10,
|
limit: 10,
|
||||||
})
|
})
|
||||||
return result.map((arr) => arr.obj)
|
return result.map((arr) => arr.obj)
|
||||||
@@ -301,6 +336,7 @@ export function Autocomplete(props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function show(mode: "@" | "/") {
|
function show(mode: "@" | "/") {
|
||||||
|
command.keybinds(false)
|
||||||
setStore({
|
setStore({
|
||||||
visible: mode,
|
visible: mode,
|
||||||
index: props.input().visualCursor.offset,
|
index: props.input().visualCursor.offset,
|
||||||
@@ -318,6 +354,7 @@ export function Autocomplete(props: {
|
|||||||
const cursor = props.input().logicalCursor
|
const cursor = props.input().logicalCursor
|
||||||
props.input().deleteRange(0, 0, cursor.row, cursor.col)
|
props.input().deleteRange(0, 0, cursor.row, cursor.col)
|
||||||
}
|
}
|
||||||
|
command.keybinds(true)
|
||||||
setStore("visible", false)
|
setStore("visible", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ import {
|
|||||||
dim,
|
dim,
|
||||||
fg,
|
fg,
|
||||||
} from "@opentui/core"
|
} from "@opentui/core"
|
||||||
import { createEffect, createMemo, Match, Switch, type JSX, onMount } from "solid-js"
|
import { createEffect, createMemo, Match, Switch, type JSX, onMount, batch } from "solid-js"
|
||||||
import { useLocal } from "@tui/context/local"
|
import { useLocal } from "@tui/context/local"
|
||||||
import { SyntaxTheme, useTheme } from "@tui/context/theme"
|
import { useTheme } from "@tui/context/theme"
|
||||||
import { SplitBorder } from "@tui/component/border"
|
import { SplitBorder } from "@tui/component/border"
|
||||||
import { useSDK } from "@tui/context/sdk"
|
import { useSDK } from "@tui/context/sdk"
|
||||||
import { useRoute } from "@tui/context/route"
|
import { useRoute } from "@tui/context/route"
|
||||||
@@ -60,7 +60,7 @@ export function Prompt(props: PromptProps) {
|
|||||||
const history = usePromptHistory()
|
const history = usePromptHistory()
|
||||||
const command = useCommandDialog()
|
const command = useCommandDialog()
|
||||||
const renderer = useRenderer()
|
const renderer = useRenderer()
|
||||||
const { theme } = useTheme()
|
const { theme, syntax } = useTheme()
|
||||||
|
|
||||||
const textareaKeybindings = createMemo(() => {
|
const textareaKeybindings = createMemo(() => {
|
||||||
const newlineBindings = keybind.all.input_newline || []
|
const newlineBindings = keybind.all.input_newline || []
|
||||||
@@ -86,9 +86,9 @@ export function Prompt(props: PromptProps) {
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
const fileStyleId = SyntaxTheme.getStyleId("extmark.file")!
|
const fileStyleId = syntax().getStyleId("extmark.file")!
|
||||||
const agentStyleId = SyntaxTheme.getStyleId("extmark.agent")!
|
const agentStyleId = syntax().getStyleId("extmark.agent")!
|
||||||
const pasteStyleId = SyntaxTheme.getStyleId("extmark.paste")!
|
const pasteStyleId = syntax().getStyleId("extmark.paste")!
|
||||||
let promptPartTypeId: number
|
let promptPartTypeId: number
|
||||||
|
|
||||||
command.register(() => {
|
command.register(() => {
|
||||||
@@ -98,14 +98,9 @@ export function Prompt(props: PromptProps) {
|
|||||||
category: "Session",
|
category: "Session",
|
||||||
keybind: "editor_open",
|
keybind: "editor_open",
|
||||||
value: "prompt.editor",
|
value: "prompt.editor",
|
||||||
onSelect: async (dialog) => {
|
onSelect: async (dialog, trigger) => {
|
||||||
dialog.clear()
|
dialog.clear()
|
||||||
const value = input.plainText
|
const value = trigger === "prompt" ? "" : input.plainText
|
||||||
input.clear()
|
|
||||||
setStore("prompt", {
|
|
||||||
input: "",
|
|
||||||
parts: [],
|
|
||||||
})
|
|
||||||
const content = await Editor.open({ value, renderer })
|
const content = await Editor.open({ value, renderer })
|
||||||
if (content) {
|
if (content) {
|
||||||
input.setText(content, { history: false })
|
input.setText(content, { history: false })
|
||||||
@@ -139,6 +134,7 @@ export function Prompt(props: PromptProps) {
|
|||||||
keybind: "input_submit",
|
keybind: "input_submit",
|
||||||
category: "Prompt",
|
category: "Prompt",
|
||||||
onSelect: (dialog) => {
|
onSelect: (dialog) => {
|
||||||
|
if (!input.focused) return
|
||||||
submit()
|
submit()
|
||||||
dialog.clear()
|
dialog.clear()
|
||||||
},
|
},
|
||||||
@@ -194,6 +190,16 @@ export function Prompt(props: PromptProps) {
|
|||||||
input.focus()
|
input.focus()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
local.setInitialPrompt.listen((initialPrompt) => {
|
||||||
|
batch(() => {
|
||||||
|
setStore("prompt", {
|
||||||
|
input: initialPrompt,
|
||||||
|
parts: [],
|
||||||
|
})
|
||||||
|
input.insertText(initialPrompt)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
promptPartTypeId = input.extmarks.registerType("prompt-part")
|
promptPartTypeId = input.extmarks.registerType("prompt-part")
|
||||||
})
|
})
|
||||||
@@ -530,6 +536,17 @@ export function Prompt(props: PromptProps) {
|
|||||||
setStore("extmarkToPartIndex", new Map())
|
setStore("extmarkToPartIndex", new Map())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (keybind.match("input_forward_delete", e) && store.prompt.input !== "") {
|
||||||
|
const cursorOffset = input.cursorOffset
|
||||||
|
if (cursorOffset < input.plainText.length) {
|
||||||
|
const text = input.plainText
|
||||||
|
const newText = text.slice(0, cursorOffset) + text.slice(cursorOffset + 1)
|
||||||
|
input.setText(newText)
|
||||||
|
input.cursorOffset = cursorOffset
|
||||||
|
}
|
||||||
|
e.preventDefault()
|
||||||
|
return
|
||||||
|
}
|
||||||
if (keybind.match("app_exit", e)) {
|
if (keybind.match("app_exit", e)) {
|
||||||
await exit()
|
await exit()
|
||||||
return
|
return
|
||||||
@@ -552,10 +569,11 @@ export function Prompt(props: PromptProps) {
|
|||||||
if (store.mode === "normal") autocomplete.onKeyDown(e)
|
if (store.mode === "normal") autocomplete.onKeyDown(e)
|
||||||
if (!autocomplete.visible) {
|
if (!autocomplete.visible) {
|
||||||
if (
|
if (
|
||||||
(e.name === "up" && input.cursorOffset === 0) ||
|
(keybind.match("history_previous", e) && input.cursorOffset === 0) ||
|
||||||
(e.name === "down" && input.cursorOffset === input.plainText.length)
|
(keybind.match("history_next", e) &&
|
||||||
|
input.cursorOffset === input.plainText.length)
|
||||||
) {
|
) {
|
||||||
const direction = e.name === "up" ? -1 : 1
|
const direction = keybind.match("history_previous", e) ? -1 : 1
|
||||||
const item = history.move(direction, input.plainText)
|
const item = history.move(direction, input.plainText)
|
||||||
|
|
||||||
if (item) {
|
if (item) {
|
||||||
@@ -599,14 +617,16 @@ export function Prompt(props: PromptProps) {
|
|||||||
|
|
||||||
// trim ' from the beginning and end of the pasted content. just
|
// trim ' from the beginning and end of the pasted content. just
|
||||||
// ' and nothing else
|
// ' and nothing else
|
||||||
const filepath = pastedContent.replace(/^'+|'+$/g, "")
|
const filepath = pastedContent.replace(/^'+|'+$/g, "").replace(/\\ /g, " ")
|
||||||
|
console.log(pastedContent, filepath)
|
||||||
try {
|
try {
|
||||||
const file = Bun.file(filepath)
|
const file = Bun.file(filepath)
|
||||||
if (file.type.startsWith("image/")) {
|
if (file.type.startsWith("image/")) {
|
||||||
|
event.preventDefault()
|
||||||
const content = await file
|
const content = await file
|
||||||
.arrayBuffer()
|
.arrayBuffer()
|
||||||
.then((buffer) => Buffer.from(buffer).toString("base64"))
|
.then((buffer) => Buffer.from(buffer).toString("base64"))
|
||||||
.catch(() => {})
|
.catch(console.error)
|
||||||
if (content) {
|
if (content) {
|
||||||
await pasteImage({
|
await pasteImage({
|
||||||
filename: file.name,
|
filename: file.name,
|
||||||
@@ -663,7 +683,7 @@ export function Prompt(props: PromptProps) {
|
|||||||
onMouseDown={(r: MouseEvent) => r.target?.focus()}
|
onMouseDown={(r: MouseEvent) => r.target?.focus()}
|
||||||
focusedBackgroundColor={theme.backgroundElement}
|
focusedBackgroundColor={theme.backgroundElement}
|
||||||
cursorColor={theme.primary}
|
cursorColor={theme.primary}
|
||||||
syntaxStyle={SyntaxTheme}
|
syntaxStyle={syntax()}
|
||||||
/>
|
/>
|
||||||
</box>
|
</box>
|
||||||
<box
|
<box
|
||||||
@@ -674,7 +694,7 @@ export function Prompt(props: PromptProps) {
|
|||||||
></box>
|
></box>
|
||||||
</box>
|
</box>
|
||||||
<box flexDirection="row" justifyContent="space-between">
|
<box flexDirection="row" justifyContent="space-between">
|
||||||
<text flexShrink={0} wrapMode="none">
|
<text flexShrink={0} wrapMode="none" fg={theme.text}>
|
||||||
<span style={{ fg: theme.textMuted }}>{local.model.parsed().provider}</span>{" "}
|
<span style={{ fg: theme.textMuted }}>{local.model.parsed().provider}</span>{" "}
|
||||||
<span style={{ bold: true }}>{local.model.parsed().model}</span>
|
<span style={{ bold: true }}>{local.model.parsed().model}</span>
|
||||||
</text>
|
</text>
|
||||||
@@ -684,14 +704,14 @@ export function Prompt(props: PromptProps) {
|
|||||||
</Match>
|
</Match>
|
||||||
<Match when={status() === "working"}>
|
<Match when={status() === "working"}>
|
||||||
<box flexDirection="row" gap={1}>
|
<box flexDirection="row" gap={1}>
|
||||||
<text>
|
<text fg={theme.text}>
|
||||||
esc <span style={{ fg: theme.textMuted }}>interrupt</span>
|
esc <span style={{ fg: theme.textMuted }}>interrupt</span>
|
||||||
</text>
|
</text>
|
||||||
</box>
|
</box>
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={props.hint}>{props.hint!}</Match>
|
<Match when={props.hint}>{props.hint!}</Match>
|
||||||
<Match when={true}>
|
<Match when={true}>
|
||||||
<text>
|
<text fg={theme.text}>
|
||||||
ctrl+p <span style={{ fg: theme.textMuted }}>commands</span>
|
ctrl+p <span style={{ fg: theme.textMuted }}>commands</span>
|
||||||
</text>
|
</text>
|
||||||
</Match>
|
</Match>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Global } from "@/global"
|
import { Global } from "@/global"
|
||||||
import { createSignal } from "solid-js"
|
import { createSignal, type Setter } from "solid-js"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { createSimpleContext } from "./helper"
|
import { createSimpleContext } from "./helper"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
@@ -8,10 +8,7 @@ export const { use: useKV, provider: KVProvider } = createSimpleContext({
|
|||||||
name: "KV",
|
name: "KV",
|
||||||
init: () => {
|
init: () => {
|
||||||
const [ready, setReady] = createSignal(false)
|
const [ready, setReady] = createSignal(false)
|
||||||
const [kvStore, setKvStore] = createStore({
|
const [kvStore, setKvStore] = createStore<Record<string, any>>()
|
||||||
openrouter_warning: false,
|
|
||||||
theme: "opencode",
|
|
||||||
})
|
|
||||||
const file = Bun.file(path.join(Global.Path.state, "kv.json"))
|
const file = Bun.file(path.join(Global.Path.state, "kv.json"))
|
||||||
|
|
||||||
file
|
file
|
||||||
@@ -24,22 +21,29 @@ export const { use: useKV, provider: KVProvider } = createSimpleContext({
|
|||||||
setReady(true)
|
setReady(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
const result = {
|
||||||
get data() {
|
|
||||||
return kvStore
|
|
||||||
},
|
|
||||||
get ready() {
|
get ready() {
|
||||||
return ready()
|
return ready()
|
||||||
},
|
},
|
||||||
|
signal<T>(name: string, defaultValue: T) {
|
||||||
|
if (!kvStore[name]) setKvStore(name, defaultValue)
|
||||||
|
return [
|
||||||
|
function () {
|
||||||
|
return result.get(name)
|
||||||
|
},
|
||||||
|
function setter(next: Setter<T>) {
|
||||||
|
result.set(name, next)
|
||||||
|
},
|
||||||
|
] as const
|
||||||
|
},
|
||||||
|
get(key: string, defaultValue?: any) {
|
||||||
|
return kvStore[key] ?? defaultValue
|
||||||
|
},
|
||||||
set(key: string, value: any) {
|
set(key: string, value: any) {
|
||||||
setKvStore(key as any, value)
|
setKvStore(key, value)
|
||||||
Bun.write(
|
Bun.write(file, JSON.stringify(kvStore, null, 2))
|
||||||
file,
|
|
||||||
JSON.stringify({
|
|
||||||
[key]: value,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
return result
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -8,10 +8,11 @@ import { Global } from "@/global"
|
|||||||
import { iife } from "@/util/iife"
|
import { iife } from "@/util/iife"
|
||||||
import { createSimpleContext } from "./helper"
|
import { createSimpleContext } from "./helper"
|
||||||
import { useToast } from "../ui/toast"
|
import { useToast } from "../ui/toast"
|
||||||
|
import { createEventBus } from "@solid-primitives/event-bus"
|
||||||
|
|
||||||
export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
||||||
name: "Local",
|
name: "Local",
|
||||||
init: (props: { initialModel?: string; initialAgent?: string }) => {
|
init: (props: { initialModel?: string; initialAgent?: string; initialPrompt?: string }) => {
|
||||||
const sync = useSync()
|
const sync = useSync()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
@@ -147,15 +148,6 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||||||
setModelStore("ready", true)
|
setModelStore("ready", true)
|
||||||
})
|
})
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
Bun.write(
|
|
||||||
file,
|
|
||||||
JSON.stringify({
|
|
||||||
recent: modelStore.recent,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const fallbackModel = createMemo(() => {
|
const fallbackModel = createMemo(() => {
|
||||||
if (sync.data.config.model) {
|
if (sync.data.config.model) {
|
||||||
const [providerID, modelID] = sync.data.config.model.split("/")
|
const [providerID, modelID] = sync.data.config.model.split("/")
|
||||||
@@ -206,6 +198,21 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||||||
model: model.name ?? value.modelID,
|
model: model.name ?? value.modelID,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
cycle(direction: 1 | -1) {
|
||||||
|
const current = currentModel()
|
||||||
|
if (!current) return
|
||||||
|
const recent = modelStore.recent
|
||||||
|
const index = recent.findIndex(
|
||||||
|
(x) => x.providerID === current.providerID && x.modelID === current.modelID,
|
||||||
|
)
|
||||||
|
if (index === -1) return
|
||||||
|
let next = index + direction
|
||||||
|
if (next < 0) next = recent.length - 1
|
||||||
|
if (next >= recent.length) next = 0
|
||||||
|
const val = recent[next]
|
||||||
|
if (!val) return
|
||||||
|
setModelStore("model", agent.current().name, { ...val })
|
||||||
|
},
|
||||||
set(model: { providerID: string; modelID: string }, options?: { recent?: boolean }) {
|
set(model: { providerID: string; modelID: string }, options?: { recent?: boolean }) {
|
||||||
batch(() => {
|
batch(() => {
|
||||||
if (!isModelValid(model)) {
|
if (!isModelValid(model)) {
|
||||||
@@ -216,21 +223,36 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setModelStore("model", agent.current().name, model)
|
setModelStore("model", agent.current().name, model)
|
||||||
if (options?.recent) {
|
if (options?.recent) {
|
||||||
const uniq = uniqueBy([model, ...modelStore.recent], (x) => x.providerID + x.modelID)
|
const uniq = uniqueBy([model, ...modelStore.recent], (x) => x.providerID + x.modelID)
|
||||||
if (uniq.length > 5) uniq.pop()
|
if (uniq.length > 5) uniq.pop()
|
||||||
setModelStore("recent", uniq)
|
setModelStore("recent", uniq)
|
||||||
|
Bun.write(
|
||||||
|
file,
|
||||||
|
JSON.stringify({
|
||||||
|
recent: modelStore.recent,
|
||||||
|
}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const setInitialPrompt = createEventBus<string>()
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if (props.initialPrompt)
|
||||||
|
setInitialPrompt.emit(props.initialPrompt)
|
||||||
|
})
|
||||||
|
|
||||||
const result = {
|
const result = {
|
||||||
model,
|
model,
|
||||||
agent,
|
agent,
|
||||||
|
get setInitialPrompt() {
|
||||||
|
return setInitialPrompt
|
||||||
|
},
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import type {
|
|||||||
Permission,
|
Permission,
|
||||||
LspStatus,
|
LspStatus,
|
||||||
McpStatus,
|
McpStatus,
|
||||||
|
FormatterStatus,
|
||||||
} from "@opencode-ai/sdk"
|
} from "@opencode-ai/sdk"
|
||||||
import { createStore, produce, reconcile } from "solid-js/store"
|
import { createStore, produce, reconcile } from "solid-js/store"
|
||||||
import { useSDK } from "@tui/context/sdk"
|
import { useSDK } from "@tui/context/sdk"
|
||||||
@@ -42,6 +43,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
|||||||
mcp: {
|
mcp: {
|
||||||
[key: string]: McpStatus
|
[key: string]: McpStatus
|
||||||
}
|
}
|
||||||
|
formatter: FormatterStatus[]
|
||||||
}>({
|
}>({
|
||||||
config: {},
|
config: {},
|
||||||
ready: false,
|
ready: false,
|
||||||
@@ -55,6 +57,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
|||||||
part: {},
|
part: {},
|
||||||
lsp: [],
|
lsp: [],
|
||||||
mcp: {},
|
mcp: {},
|
||||||
|
formatter: [],
|
||||||
})
|
})
|
||||||
|
|
||||||
const sdk = useSDK()
|
const sdk = useSDK()
|
||||||
@@ -220,6 +223,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
|||||||
sdk.client.command.list().then((x) => setStore("command", x.data ?? [])),
|
sdk.client.command.list().then((x) => setStore("command", x.data ?? [])),
|
||||||
sdk.client.lsp.status().then((x) => setStore("lsp", x.data!)),
|
sdk.client.lsp.status().then((x) => setStore("lsp", x.data!)),
|
||||||
sdk.client.mcp.status().then((x) => setStore("mcp", x.data!)),
|
sdk.client.mcp.status().then((x) => setStore("mcp", x.data!)),
|
||||||
|
sdk.client.formatter.status().then((x) => setStore("formatter", x.data!)),
|
||||||
])
|
])
|
||||||
|
|
||||||
const result = {
|
const result = {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
|||||||
import { Prompt, type PromptRef } from "@tui/component/prompt"
|
import { Prompt } from "@tui/component/prompt"
|
||||||
import { createEffect, createMemo, Match, Show, Switch, type ParentProps } from "solid-js"
|
import { createMemo, Match, Show, Switch, type ParentProps } from "solid-js"
|
||||||
import { useTheme } from "@tui/context/theme"
|
import { useTheme } from "@tui/context/theme"
|
||||||
import { useKeybind } from "../context/keybind"
|
import { useKeybind } from "../context/keybind"
|
||||||
import type { KeybindsConfig } from "@opencode-ai/sdk"
|
import type { KeybindsConfig } from "@opencode-ai/sdk"
|
||||||
@@ -7,27 +7,18 @@ import { Logo } from "../component/logo"
|
|||||||
import { Locale } from "@/util/locale"
|
import { Locale } from "@/util/locale"
|
||||||
import { useSync } from "../context/sync"
|
import { useSync } from "../context/sync"
|
||||||
import { Toast } from "../ui/toast"
|
import { Toast } from "../ui/toast"
|
||||||
import { useDialog } from "../ui/dialog"
|
|
||||||
|
|
||||||
export function Home() {
|
export function Home() {
|
||||||
const sync = useSync()
|
const sync = useSync()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const dialog = useDialog()
|
|
||||||
const mcpError = createMemo(() => {
|
const mcpError = createMemo(() => {
|
||||||
return Object.values(sync.data.mcp).some((x) => x.status === "failed")
|
return Object.values(sync.data.mcp).some((x) => x.status === "failed")
|
||||||
})
|
})
|
||||||
let promptRef: PromptRef | undefined = undefined
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
dialog.allClosedEvent.listen(() => {
|
|
||||||
promptRef?.focus()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const Hint = (
|
const Hint = (
|
||||||
<Show when={Object.keys(sync.data.mcp).length > 0}>
|
<Show when={Object.keys(sync.data.mcp).length > 0}>
|
||||||
<box flexShrink={0} flexDirection="row" gap={1}>
|
<box flexShrink={0} flexDirection="row" gap={1}>
|
||||||
<text>
|
<text fg={theme.text}>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Match when={mcpError()}>
|
<Match when={mcpError()}>
|
||||||
<span style={{ fg: theme.error }}>•</span> mcp errors{" "}
|
<span style={{ fg: theme.error }}>•</span> mcp errors{" "}
|
||||||
@@ -64,7 +55,7 @@ export function Home() {
|
|||||||
<HelpRow keybind="agent_cycle">Switch agent</HelpRow>
|
<HelpRow keybind="agent_cycle">Switch agent</HelpRow>
|
||||||
</box>
|
</box>
|
||||||
<box width="100%" maxWidth={75} zIndex={1000} paddingTop={1}>
|
<box width="100%" maxWidth={75} zIndex={1000} paddingTop={1}>
|
||||||
<Prompt hint={Hint} ref={(r) => (promptRef = r)} />
|
<Prompt hint={Hint} />
|
||||||
</box>
|
</box>
|
||||||
<Toast />
|
<Toast />
|
||||||
</box>
|
</box>
|
||||||
@@ -76,7 +67,7 @@ function HelpRow(props: ParentProps<{ keybind: keyof KeybindsConfig }>) {
|
|||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
return (
|
return (
|
||||||
<box flexDirection="row" justifyContent="space-between" width="100%">
|
<box flexDirection="row" justifyContent="space-between" width="100%">
|
||||||
<text>{props.children}</text>
|
<text fg={theme.text}>{props.children}</text>
|
||||||
<text fg={theme.primary}>{keybind.print(props.keybind)}</text>
|
<text fg={theme.primary}>{keybind.print(props.keybind)}</text>
|
||||||
</box>
|
</box>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export function Header() {
|
|||||||
borderColor={theme.backgroundElement}
|
borderColor={theme.backgroundElement}
|
||||||
flexShrink={0}
|
flexShrink={0}
|
||||||
>
|
>
|
||||||
<text>
|
<text fg={theme.text}>
|
||||||
<span style={{ bold: true, fg: theme.accent }}>#</span>{" "}
|
<span style={{ bold: true, fg: theme.accent }}>#</span>{" "}
|
||||||
<span style={{ bold: true }}>{session().title}</span>
|
<span style={{ bold: true }}>{session().title}</span>
|
||||||
</text>
|
</text>
|
||||||
@@ -64,7 +64,7 @@ export function Header() {
|
|||||||
</text>
|
</text>
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={true}>
|
<Match when={true}>
|
||||||
<text wrapMode="word">
|
<text fg={theme.text} wrapMode="word">
|
||||||
/share <span style={{ fg: theme.textMuted }}>to create a shareable link</span>
|
/share <span style={{ fg: theme.textMuted }}>to create a shareable link</span>
|
||||||
</text>
|
</text>
|
||||||
</Match>
|
</Match>
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ import {
|
|||||||
} from "solid-js"
|
} from "solid-js"
|
||||||
import { Dynamic } from "solid-js/web"
|
import { Dynamic } from "solid-js/web"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { useRouteData } from "@tui/context/route"
|
import { useRoute, useRouteData } from "@tui/context/route"
|
||||||
import { useSync } from "@tui/context/sync"
|
import { useSync } from "@tui/context/sync"
|
||||||
import { SplitBorder } from "@tui/component/border"
|
import { SplitBorder } from "@tui/component/border"
|
||||||
import { SyntaxTheme, useTheme } from "@tui/context/theme"
|
import { useTheme } from "@tui/context/theme"
|
||||||
import { BoxRenderable, ScrollBoxRenderable, addDefaultParsers } from "@opentui/core"
|
import { BoxRenderable, ScrollBoxRenderable, addDefaultParsers } from "@opentui/core"
|
||||||
import { Prompt, type PromptRef } from "@tui/component/prompt"
|
import { Prompt, type PromptRef } from "@tui/component/prompt"
|
||||||
import type {
|
import type {
|
||||||
@@ -62,7 +62,10 @@ import { DialogTimeline } from "./dialog-timeline"
|
|||||||
import { Sidebar } from "./sidebar"
|
import { Sidebar } from "./sidebar"
|
||||||
import { LANGUAGE_EXTENSIONS } from "@/lsp/language"
|
import { LANGUAGE_EXTENSIONS } from "@/lsp/language"
|
||||||
import parsers from "../../../../../../parsers-config.ts"
|
import parsers from "../../../../../../parsers-config.ts"
|
||||||
import { Toast } from "../../ui/toast"
|
import { Clipboard } from "../../util/clipboard"
|
||||||
|
import { Toast, useToast } from "../../ui/toast"
|
||||||
|
import { DialogSessionRename } from "../../component/dialog-session-rename"
|
||||||
|
import { useKV } from "../../context/kv.tsx"
|
||||||
|
|
||||||
addDefaultParsers(parsers.parsers)
|
addDefaultParsers(parsers.parsers)
|
||||||
|
|
||||||
@@ -79,7 +82,9 @@ function use() {
|
|||||||
|
|
||||||
export function Session() {
|
export function Session() {
|
||||||
const route = useRouteData("session")
|
const route = useRouteData("session")
|
||||||
|
const { navigate } = useRoute()
|
||||||
const sync = useSync()
|
const sync = useSync()
|
||||||
|
const kv = useKV()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const session = createMemo(() => sync.session.get(route.sessionID)!)
|
const session = createMemo(() => sync.session.get(route.sessionID)!)
|
||||||
const messages = createMemo(() => sync.data.message[route.sessionID] ?? [])
|
const messages = createMemo(() => sync.data.message[route.sessionID] ?? [])
|
||||||
@@ -90,7 +95,7 @@ export function Session() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const dimensions = useTerminalDimensions()
|
const dimensions = useTerminalDimensions()
|
||||||
const [sidebar, setSidebar] = createSignal<"show" | "hide" | "auto">("auto")
|
const [sidebar, setSidebar] = createSignal<"show" | "hide" | "auto">(kv.get("sidebar", "auto"))
|
||||||
const [conceal, setConceal] = createSignal(true)
|
const [conceal, setConceal] = createSignal(true)
|
||||||
|
|
||||||
const wide = createMemo(() => dimensions().width > 120)
|
const wide = createMemo(() => dimensions().width > 120)
|
||||||
@@ -99,18 +104,14 @@ export function Session() {
|
|||||||
|
|
||||||
createEffect(() => sync.session.sync(route.sessionID))
|
createEffect(() => sync.session.sync(route.sessionID))
|
||||||
|
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
const sdk = useSDK()
|
const sdk = useSDK()
|
||||||
|
|
||||||
let scroll: ScrollBoxRenderable
|
let scroll: ScrollBoxRenderable
|
||||||
let prompt: PromptRef
|
let prompt: PromptRef
|
||||||
const keybind = useKeybind()
|
const keybind = useKeybind()
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
dialog.allClosedEvent.listen(() => {
|
|
||||||
prompt.focus()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
useKeyboard((evt) => {
|
useKeyboard((evt) => {
|
||||||
if (dialog.stack.length > 0) return
|
if (dialog.stack.length > 0) return
|
||||||
|
|
||||||
@@ -150,6 +151,23 @@ export function Session() {
|
|||||||
|
|
||||||
const local = useLocal()
|
const local = useLocal()
|
||||||
|
|
||||||
|
function moveChild(direction: number) {
|
||||||
|
const parentID = session()?.parentID ?? session()?.id
|
||||||
|
let children = sync.data.session
|
||||||
|
.filter((x) => x.parentID === parentID || x.id === parentID)
|
||||||
|
.toSorted((b, a) => a.id.localeCompare(b.id))
|
||||||
|
if (children.length === 1) return
|
||||||
|
let next = children.findIndex((x) => x.id === session()?.id) + direction
|
||||||
|
if (next >= children.length) next = 0
|
||||||
|
if (next < 0) next = children.length - 1
|
||||||
|
if (children[next]) {
|
||||||
|
navigate({
|
||||||
|
type: "session",
|
||||||
|
sessionID: children[next].id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const command = useCommandDialog()
|
const command = useCommandDialog()
|
||||||
command.register(() => [
|
command.register(() => [
|
||||||
{
|
{
|
||||||
@@ -195,12 +213,20 @@ export function Session() {
|
|||||||
keybind: "session_share",
|
keybind: "session_share",
|
||||||
disabled: !!session()?.share?.url,
|
disabled: !!session()?.share?.url,
|
||||||
category: "Session",
|
category: "Session",
|
||||||
onSelect: (dialog) => {
|
onSelect: async (dialog) => {
|
||||||
sdk.client.session.share({
|
await sdk.client.session
|
||||||
path: {
|
.share({
|
||||||
id: route.sessionID,
|
path: {
|
||||||
},
|
id: route.sessionID,
|
||||||
})
|
},
|
||||||
|
})
|
||||||
|
.then((res) =>
|
||||||
|
Clipboard.copy(res.data!.share!.url).catch(() =>
|
||||||
|
toast.show({ message: "Failed to copy URL to clipboard", variant: "error" }),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.then(() => toast.show({ message: "Share URL copied to clipboard!", variant: "success" }))
|
||||||
|
.catch(() => toast.show({ message: "Failed to share session", variant: "error" }))
|
||||||
dialog.clear()
|
dialog.clear()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -240,7 +266,9 @@ export function Session() {
|
|||||||
prompt.set(
|
prompt.set(
|
||||||
parts.reduce(
|
parts.reduce(
|
||||||
(agg, part) => {
|
(agg, part) => {
|
||||||
if (part.type === "text") agg.input += part.text
|
if (part.type === "text") {
|
||||||
|
if (!part.synthetic) agg.input += part.text
|
||||||
|
}
|
||||||
if (part.type === "file") agg.parts.push(part)
|
if (part.type === "file") agg.parts.push(part)
|
||||||
return agg
|
return agg
|
||||||
},
|
},
|
||||||
@@ -291,6 +319,8 @@ export function Session() {
|
|||||||
if (prev === "show") return "hide"
|
if (prev === "show") return "hide"
|
||||||
return "show"
|
return "show"
|
||||||
})
|
})
|
||||||
|
if (sidebar() === "show") kv.set("sidebar", "auto")
|
||||||
|
if (sidebar() === "hide") kv.set("sidebar", "hide")
|
||||||
dialog.clear()
|
dialog.clear()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -370,6 +400,37 @@ export function Session() {
|
|||||||
dialog.clear()
|
dialog.clear()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "Rename session",
|
||||||
|
value: "session.rename",
|
||||||
|
keybind: "session_rename",
|
||||||
|
category: "Session",
|
||||||
|
onSelect: (dialog) => {
|
||||||
|
dialog.replace(() => <DialogSessionRename session={route.sessionID} />)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Next child session",
|
||||||
|
value: "session.child.next",
|
||||||
|
keybind: "session_child_cycle",
|
||||||
|
category: "Session",
|
||||||
|
disabled: true,
|
||||||
|
onSelect: (dialog) => {
|
||||||
|
moveChild(1)
|
||||||
|
dialog.clear()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Previous child session",
|
||||||
|
value: "session.child.previous",
|
||||||
|
keybind: "session_child_cycle_reverse",
|
||||||
|
category: "Session",
|
||||||
|
disabled: true,
|
||||||
|
onSelect: (dialog) => {
|
||||||
|
moveChild(-1)
|
||||||
|
dialog.clear()
|
||||||
|
},
|
||||||
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
const revert = createMemo(() => {
|
const revert = createMemo(() => {
|
||||||
@@ -431,6 +492,34 @@ export function Session() {
|
|||||||
>
|
>
|
||||||
<box flexGrow={1} gap={1}>
|
<box flexGrow={1} gap={1}>
|
||||||
<Show when={session()}>
|
<Show when={session()}>
|
||||||
|
<Show when={session().parentID}>
|
||||||
|
<box
|
||||||
|
backgroundColor={theme.backgroundPanel}
|
||||||
|
justifyContent="space-between"
|
||||||
|
flexDirection="row"
|
||||||
|
paddingTop={1}
|
||||||
|
paddingBottom={1}
|
||||||
|
flexShrink={0}
|
||||||
|
paddingLeft={2}
|
||||||
|
paddingRight={2}
|
||||||
|
>
|
||||||
|
<text fg={theme.text}>
|
||||||
|
Previous{" "}
|
||||||
|
<span style={{ fg: theme.textMuted }}>
|
||||||
|
{keybind.print("session_child_cycle_reverse")}
|
||||||
|
</span>
|
||||||
|
</text>
|
||||||
|
<text fg={theme.text}>
|
||||||
|
<b>Viewing subagent session</b>
|
||||||
|
</text>
|
||||||
|
<text fg={theme.text}>
|
||||||
|
<span style={{ fg: theme.textMuted }}>
|
||||||
|
{keybind.print("session_child_cycle")}
|
||||||
|
</span>{" "}
|
||||||
|
Next
|
||||||
|
</text>
|
||||||
|
</box>
|
||||||
|
</Show>
|
||||||
<Show when={!sidebarVisible()}>
|
<Show when={!sidebarVisible()}>
|
||||||
<Header />
|
<Header />
|
||||||
</Show>
|
</Show>
|
||||||
@@ -614,7 +703,7 @@ function UserMessage(props: {
|
|||||||
borderColor={color()}
|
borderColor={color()}
|
||||||
flexShrink={0}
|
flexShrink={0}
|
||||||
>
|
>
|
||||||
<text>{text()?.text}</text>
|
<text fg={theme.text}>{text()?.text}</text>
|
||||||
<Show when={files().length}>
|
<Show when={files().length}>
|
||||||
<box flexDirection="row" paddingBottom={1} paddingTop={1} gap={1} flexWrap="wrap">
|
<box flexDirection="row" paddingBottom={1} paddingTop={1} gap={1} flexWrap="wrap">
|
||||||
<For each={files()}>
|
<For each={files()}>
|
||||||
@@ -625,7 +714,7 @@ function UserMessage(props: {
|
|||||||
return theme.secondary
|
return theme.secondary
|
||||||
})
|
})
|
||||||
return (
|
return (
|
||||||
<text>
|
<text fg={theme.text}>
|
||||||
<span style={{ bg: bg(), fg: theme.background }}>
|
<span style={{ bg: bg(), fg: theme.background }}>
|
||||||
{" "}
|
{" "}
|
||||||
{MIME_BADGE[file.mime] ?? file.mime}{" "}
|
{MIME_BADGE[file.mime] ?? file.mime}{" "}
|
||||||
@@ -640,7 +729,7 @@ function UserMessage(props: {
|
|||||||
</For>
|
</For>
|
||||||
</box>
|
</box>
|
||||||
</Show>
|
</Show>
|
||||||
<text>
|
<text fg={theme.text}>
|
||||||
{sync.data.config.username ?? "You"}{" "}
|
{sync.data.config.username ?? "You"}{" "}
|
||||||
<Show
|
<Show
|
||||||
when={queued()}
|
when={queued()}
|
||||||
@@ -755,7 +844,7 @@ function ReasoningPart(props: { part: ReasoningPart; message: AssistantMessage }
|
|||||||
paddingLeft={2}
|
paddingLeft={2}
|
||||||
backgroundColor={theme.backgroundPanel}
|
backgroundColor={theme.backgroundPanel}
|
||||||
>
|
>
|
||||||
<text>{props.part.text.trim()}</text>
|
<text fg={theme.text}>{props.part.text.trim()}</text>
|
||||||
</box>
|
</box>
|
||||||
</box>
|
</box>
|
||||||
</Show>
|
</Show>
|
||||||
@@ -764,13 +853,14 @@ function ReasoningPart(props: { part: ReasoningPart; message: AssistantMessage }
|
|||||||
|
|
||||||
function TextPart(props: { part: TextPart; message: AssistantMessage }) {
|
function TextPart(props: { part: TextPart; message: AssistantMessage }) {
|
||||||
const ctx = use()
|
const ctx = use()
|
||||||
|
const { syntax } = useTheme()
|
||||||
return (
|
return (
|
||||||
<Show when={props.part.text.trim()}>
|
<Show when={props.part.text.trim()}>
|
||||||
<box id={"text-" + props.part.id} paddingLeft={3} marginTop={1} flexShrink={0}>
|
<box id={"text-" + props.part.id} paddingLeft={3} marginTop={1} flexShrink={0}>
|
||||||
<code
|
<code
|
||||||
filetype="markdown"
|
filetype="markdown"
|
||||||
drawUnstyledText={false}
|
drawUnstyledText={false}
|
||||||
syntaxStyle={SyntaxTheme}
|
syntaxStyle={syntax()}
|
||||||
content={props.part.text.trim()}
|
content={props.part.text.trim()}
|
||||||
conceal={ctx.conceal()}
|
conceal={ctx.conceal()}
|
||||||
/>
|
/>
|
||||||
@@ -893,16 +983,14 @@ function GenericTool(props: ToolProps<any>) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ToolRegistration<T extends Tool.Info = any> = {
|
||||||
|
name: string
|
||||||
|
container: "inline" | "block"
|
||||||
|
render?: Component<ToolProps<T>>
|
||||||
|
}
|
||||||
const ToolRegistry = (() => {
|
const ToolRegistry = (() => {
|
||||||
const state: Record<
|
const state: Record<string, ToolRegistration> = {}
|
||||||
string,
|
function register<T extends Tool.Info>(input: ToolRegistration<T>) {
|
||||||
{ name: string; container: "inline" | "block"; render?: Component<ToolProps<any>> }
|
|
||||||
> = {}
|
|
||||||
function register<T extends Tool.Info>(input: {
|
|
||||||
name: string
|
|
||||||
container: "inline" | "block"
|
|
||||||
render?: Component<ToolProps<T>>
|
|
||||||
}) {
|
|
||||||
state[input.name] = input
|
state[input.name] = input
|
||||||
return input
|
return input
|
||||||
}
|
}
|
||||||
@@ -970,7 +1058,7 @@ ToolRegistry.register<typeof WriteTool>({
|
|||||||
name: "write",
|
name: "write",
|
||||||
container: "block",
|
container: "block",
|
||||||
render(props) {
|
render(props) {
|
||||||
const { theme } = useTheme()
|
const { theme, syntax } = useTheme()
|
||||||
const lines = createMemo(() => {
|
const lines = createMemo(() => {
|
||||||
return props.input.content?.split("\n") ?? []
|
return props.input.content?.split("\n") ?? []
|
||||||
})
|
})
|
||||||
@@ -1001,7 +1089,7 @@ ToolRegistry.register<typeof WriteTool>({
|
|||||||
<box paddingLeft={1} flexGrow={1}>
|
<box paddingLeft={1} flexGrow={1}>
|
||||||
<code
|
<code
|
||||||
filetype={filetype(props.input.filePath!)}
|
filetype={filetype(props.input.filePath!)}
|
||||||
syntaxStyle={SyntaxTheme}
|
syntaxStyle={syntax()}
|
||||||
content={code()}
|
content={code()}
|
||||||
/>
|
/>
|
||||||
</box>
|
</box>
|
||||||
@@ -1066,6 +1154,8 @@ ToolRegistry.register<typeof TaskTool>({
|
|||||||
container: "block",
|
container: "block",
|
||||||
render(props) {
|
render(props) {
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
const keybind = useKeybind()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ToolTitle icon="%" fallback="Delegating..." when={props.input.description}>
|
<ToolTitle icon="%" fallback="Delegating..." when={props.input.description}>
|
||||||
@@ -1082,6 +1172,10 @@ ToolRegistry.register<typeof TaskTool>({
|
|||||||
</For>
|
</For>
|
||||||
</box>
|
</box>
|
||||||
</Show>
|
</Show>
|
||||||
|
<text fg={theme.text}>
|
||||||
|
{keybind.print("session_child_cycle")}, {keybind.print("session_child_cycle_reverse")}
|
||||||
|
<span style={{ fg: theme.textMuted }}> to navigate between subagent sessions</span>
|
||||||
|
</text>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -1104,6 +1198,7 @@ ToolRegistry.register<typeof EditTool>({
|
|||||||
container: "block",
|
container: "block",
|
||||||
render(props) {
|
render(props) {
|
||||||
const ctx = use()
|
const ctx = use()
|
||||||
|
const { theme, syntax } = useTheme()
|
||||||
|
|
||||||
const style = createMemo(() => (ctx.width > 120 ? "split" : "stacked"))
|
const style = createMemo(() => (ctx.width > 120 ? "split" : "stacked"))
|
||||||
|
|
||||||
@@ -1183,21 +1278,21 @@ ToolRegistry.register<typeof EditTool>({
|
|||||||
</ToolTitle>
|
</ToolTitle>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Match when={props.permission["diff"]}>
|
<Match when={props.permission["diff"]}>
|
||||||
<text>{props.permission["diff"]?.trim()}</text>
|
<text fg={theme.text}>{props.permission["diff"]?.trim()}</text>
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={diff() && style() === "split"}>
|
<Match when={diff() && style() === "split"}>
|
||||||
<box paddingLeft={1} flexDirection="row" gap={2}>
|
<box paddingLeft={1} flexDirection="row" gap={2}>
|
||||||
<box flexGrow={1} flexBasis={0}>
|
<box flexGrow={1} flexBasis={0}>
|
||||||
<code filetype={ft()} syntaxStyle={SyntaxTheme} content={diff()!.oldContent} />
|
<code filetype={ft()} syntaxStyle={syntax()} content={diff()!.oldContent} />
|
||||||
</box>
|
</box>
|
||||||
<box flexGrow={1} flexBasis={0}>
|
<box flexGrow={1} flexBasis={0}>
|
||||||
<code filetype={ft()} syntaxStyle={SyntaxTheme} content={diff()!.newContent} />
|
<code filetype={ft()} syntaxStyle={syntax()} content={diff()!.newContent} />
|
||||||
</box>
|
</box>
|
||||||
</box>
|
</box>
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={code()}>
|
<Match when={code()}>
|
||||||
<box paddingLeft={1}>
|
<box paddingLeft={1}>
|
||||||
<code filetype={ft()} syntaxStyle={SyntaxTheme} content={code()} />
|
<code filetype={ft()} syntaxStyle={syntax()} content={code()} />
|
||||||
</box>
|
</box>
|
||||||
</Match>
|
</Match>
|
||||||
</Switch>
|
</Switch>
|
||||||
@@ -1210,6 +1305,7 @@ ToolRegistry.register<typeof PatchTool>({
|
|||||||
name: "patch",
|
name: "patch",
|
||||||
container: "block",
|
container: "block",
|
||||||
render(props) {
|
render(props) {
|
||||||
|
const { theme } = useTheme()
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ToolTitle icon="%" fallback="Preparing patch..." when={true}>
|
<ToolTitle icon="%" fallback="Preparing patch..." when={true}>
|
||||||
@@ -1217,7 +1313,7 @@ ToolRegistry.register<typeof PatchTool>({
|
|||||||
</ToolTitle>
|
</ToolTitle>
|
||||||
<Show when={props.output}>
|
<Show when={props.output}>
|
||||||
<box>
|
<box>
|
||||||
<text>{props.output?.trim()}</text>
|
<text fg={theme.text}>{props.output?.trim()}</text>
|
||||||
</box>
|
</box>
|
||||||
</Show>
|
</Show>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export function Sidebar(props: { sessionID: string }) {
|
|||||||
<Show when={session()}>
|
<Show when={session()}>
|
||||||
<box flexShrink={0} gap={1} width={40}>
|
<box flexShrink={0} gap={1} width={40}>
|
||||||
<box>
|
<box>
|
||||||
<text>
|
<text fg={theme.text}>
|
||||||
<b>{session().title}</b>
|
<b>{session().title}</b>
|
||||||
</text>
|
</text>
|
||||||
<Show when={session().share?.url}>
|
<Show when={session().share?.url}>
|
||||||
@@ -50,7 +50,7 @@ export function Sidebar(props: { sessionID: string }) {
|
|||||||
</Show>
|
</Show>
|
||||||
</box>
|
</box>
|
||||||
<box>
|
<box>
|
||||||
<text>
|
<text fg={theme.text}>
|
||||||
<b>Context</b>
|
<b>Context</b>
|
||||||
</text>
|
</text>
|
||||||
<text fg={theme.textMuted}>{context()?.tokens ?? 0} tokens</text>
|
<text fg={theme.textMuted}>{context()?.tokens ?? 0} tokens</text>
|
||||||
@@ -59,7 +59,7 @@ export function Sidebar(props: { sessionID: string }) {
|
|||||||
</box>
|
</box>
|
||||||
<Show when={Object.keys(sync.data.mcp).length > 0}>
|
<Show when={Object.keys(sync.data.mcp).length > 0}>
|
||||||
<box>
|
<box>
|
||||||
<text>
|
<text fg={theme.text}>
|
||||||
<b>MCP</b>
|
<b>MCP</b>
|
||||||
</text>
|
</text>
|
||||||
<For each={Object.entries(sync.data.mcp)}>
|
<For each={Object.entries(sync.data.mcp)}>
|
||||||
@@ -77,7 +77,7 @@ export function Sidebar(props: { sessionID: string }) {
|
|||||||
>
|
>
|
||||||
•
|
•
|
||||||
</text>
|
</text>
|
||||||
<text wrapMode="word">
|
<text fg={theme.text} wrapMode="word">
|
||||||
{key}{" "}
|
{key}{" "}
|
||||||
<span style={{ fg: theme.textMuted }}>
|
<span style={{ fg: theme.textMuted }}>
|
||||||
<Switch>
|
<Switch>
|
||||||
@@ -96,7 +96,7 @@ export function Sidebar(props: { sessionID: string }) {
|
|||||||
</Show>
|
</Show>
|
||||||
<Show when={sync.data.lsp.length > 0}>
|
<Show when={sync.data.lsp.length > 0}>
|
||||||
<box>
|
<box>
|
||||||
<text>
|
<text fg={theme.text}>
|
||||||
<b>LSP</b>
|
<b>LSP</b>
|
||||||
</text>
|
</text>
|
||||||
<For each={sync.data.lsp}>
|
<For each={sync.data.lsp}>
|
||||||
@@ -123,7 +123,7 @@ export function Sidebar(props: { sessionID: string }) {
|
|||||||
</Show>
|
</Show>
|
||||||
<Show when={session().summary?.diffs}>
|
<Show when={session().summary?.diffs}>
|
||||||
<box>
|
<box>
|
||||||
<text>
|
<text fg={theme.text}>
|
||||||
<b>Modified Files</b>
|
<b>Modified Files</b>
|
||||||
</text>
|
</text>
|
||||||
<For each={session().summary?.diffs || []}>
|
<For each={session().summary?.diffs || []}>
|
||||||
@@ -155,7 +155,7 @@ export function Sidebar(props: { sessionID: string }) {
|
|||||||
</Show>
|
</Show>
|
||||||
<Show when={todo().length > 0}>
|
<Show when={todo().length > 0}>
|
||||||
<box>
|
<box>
|
||||||
<text>
|
<text fg={theme.text}>
|
||||||
<b>Todo</b>
|
<b>Todo</b>
|
||||||
</text>
|
</text>
|
||||||
<For each={todo()}>
|
<For each={todo()}>
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ export const TuiSpawnCommand = cmd({
|
|||||||
default: 0,
|
default: 0,
|
||||||
})
|
})
|
||||||
.option("hostname", {
|
.option("hostname", {
|
||||||
alias: ["h"],
|
|
||||||
type: "string",
|
type: "string",
|
||||||
describe: "hostname to listen on",
|
describe: "hostname to listen on",
|
||||||
default: "127.0.0.1",
|
default: "127.0.0.1",
|
||||||
|
|||||||
@@ -32,6 +32,11 @@ export const TuiThreadCommand = cmd({
|
|||||||
describe: "session id to continue",
|
describe: "session id to continue",
|
||||||
type: "string",
|
type: "string",
|
||||||
})
|
})
|
||||||
|
.option("prompt", {
|
||||||
|
alias: ["p"],
|
||||||
|
type: "string",
|
||||||
|
describe: "prompt to use",
|
||||||
|
})
|
||||||
.option("agent", {
|
.option("agent", {
|
||||||
type: "string",
|
type: "string",
|
||||||
describe: "agent to use",
|
describe: "agent to use",
|
||||||
@@ -42,7 +47,6 @@ export const TuiThreadCommand = cmd({
|
|||||||
default: 0,
|
default: 0,
|
||||||
})
|
})
|
||||||
.option("hostname", {
|
.option("hostname", {
|
||||||
alias: ["h"],
|
|
||||||
type: "string",
|
type: "string",
|
||||||
describe: "hostname to listen on",
|
describe: "hostname to listen on",
|
||||||
default: "127.0.0.1",
|
default: "127.0.0.1",
|
||||||
@@ -96,6 +100,7 @@ export const TuiThreadCommand = cmd({
|
|||||||
sessionID,
|
sessionID,
|
||||||
model: args.model,
|
model: args.model,
|
||||||
agent: args.agent,
|
agent: args.agent,
|
||||||
|
prompt: args.prompt,
|
||||||
onExit: async () => {
|
onExit: async () => {
|
||||||
await client.call("shutdown", undefined)
|
await client.call("shutdown", undefined)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import { TextareaRenderable, TextAttributes } from "@opentui/core"
|
||||||
|
import { useTheme } from "../context/theme"
|
||||||
|
import { useDialog, type DialogContext } from "./dialog"
|
||||||
|
import { onMount } from "solid-js"
|
||||||
|
|
||||||
|
export type DialogPromptProps = {
|
||||||
|
title: string
|
||||||
|
value?: string
|
||||||
|
onConfirm?: (value: string) => void
|
||||||
|
onCancel?: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DialogPrompt(props: DialogPromptProps) {
|
||||||
|
const dialog = useDialog()
|
||||||
|
const { theme } = useTheme()
|
||||||
|
let textarea: TextareaRenderable
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
dialog.setSize("large")
|
||||||
|
setTimeout(() => {
|
||||||
|
textarea.focus()
|
||||||
|
}, 1)
|
||||||
|
textarea.gotoLineEnd()
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<box paddingLeft={2} paddingRight={2} gap={1}>
|
||||||
|
<box flexDirection="row" justifyContent="space-between">
|
||||||
|
<text attributes={TextAttributes.BOLD}>{props.title}</text>
|
||||||
|
<text fg={theme.textMuted}>esc</text>
|
||||||
|
</box>
|
||||||
|
<box>
|
||||||
|
<textarea
|
||||||
|
onSubmit={() => {
|
||||||
|
props.onConfirm?.(textarea.plainText)
|
||||||
|
dialog.clear()
|
||||||
|
}}
|
||||||
|
keyBindings={[{ name: "return", action: "submit" }]}
|
||||||
|
ref={(val: TextareaRenderable) => (textarea = val)}
|
||||||
|
initialValue={props.value}
|
||||||
|
placeholder="Enter text"
|
||||||
|
/>
|
||||||
|
</box>
|
||||||
|
<box paddingBottom={1}>
|
||||||
|
<text fg={theme.textMuted}>Press enter to confirm, esc to cancel</text>
|
||||||
|
</box>
|
||||||
|
</box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
DialogPrompt.show = (dialog: DialogContext, title: string, value?: string) => {
|
||||||
|
return new Promise<string | null>((resolve) => {
|
||||||
|
dialog.replace(
|
||||||
|
() => (
|
||||||
|
<DialogPrompt
|
||||||
|
title={title}
|
||||||
|
value={value}
|
||||||
|
onConfirm={(value) => resolve(value)}
|
||||||
|
onCancel={() => resolve(null)}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
() => resolve(null),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -35,7 +35,7 @@ export interface DialogSelectOption<T = any> {
|
|||||||
category?: string
|
category?: string
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
bg?: RGBA
|
bg?: RGBA
|
||||||
onSelect?: (ctx: DialogContext) => void
|
onSelect?: (ctx: DialogContext, trigger?: "prompt") => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DialogSelectRef<T> = {
|
export type DialogSelectRef<T> = {
|
||||||
@@ -59,7 +59,8 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
|||||||
props.options,
|
props.options,
|
||||||
filter((x) => x.disabled !== true),
|
filter((x) => x.disabled !== true),
|
||||||
take(props.limit ?? Infinity),
|
take(props.limit ?? Infinity),
|
||||||
(x) => (!needle ? x : fuzzysort.go(needle, x, { keys: ["title", "category"] }).map((x) => x.obj)),
|
(x) =>
|
||||||
|
!needle ? x : fuzzysort.go(needle, x, { keys: ["title", "category"] }).map((x) => x.obj),
|
||||||
)
|
)
|
||||||
return result
|
return result
|
||||||
})
|
})
|
||||||
@@ -122,20 +123,26 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
|||||||
|
|
||||||
const keybind = useKeybind()
|
const keybind = useKeybind()
|
||||||
useKeyboard((evt) => {
|
useKeyboard((evt) => {
|
||||||
if (evt.name === "up") move(-1)
|
if (evt.name === "up" || (evt.ctrl && evt.name === "p")) move(-1)
|
||||||
if (evt.name === "down") move(1)
|
if (evt.name === "down" || (evt.ctrl && evt.name === "n")) move(1)
|
||||||
if (evt.name === "pageup") move(-10)
|
if (evt.name === "pageup") move(-10)
|
||||||
if (evt.name === "pagedown") move(10)
|
if (evt.name === "pagedown") move(10)
|
||||||
if (evt.name === "return") {
|
if (evt.name === "return") {
|
||||||
const option = selected()
|
const option = selected()
|
||||||
if (option.onSelect) option.onSelect(dialog)
|
if (option) {
|
||||||
props.onSelect?.(option)
|
// evt.preventDefault()
|
||||||
|
if (option.onSelect) option.onSelect(dialog)
|
||||||
|
props.onSelect?.(option)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const item of props.keybind ?? []) {
|
for (const item of props.keybind ?? []) {
|
||||||
if (Keybind.match(item.keybind, keybind.parse(evt))) {
|
if (Keybind.match(item.keybind, keybind.parse(evt))) {
|
||||||
const s = selected()
|
const s = selected()
|
||||||
if (s) item.onTrigger(s)
|
if (s) {
|
||||||
|
evt.preventDefault()
|
||||||
|
item.onTrigger(s)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -155,7 +162,9 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
|||||||
<box gap={1}>
|
<box gap={1}>
|
||||||
<box paddingLeft={3} paddingRight={2}>
|
<box paddingLeft={3} paddingRight={2}>
|
||||||
<box flexDirection="row" justifyContent="space-between">
|
<box flexDirection="row" justifyContent="space-between">
|
||||||
<text attributes={TextAttributes.BOLD}>{props.title}</text>
|
<text fg={theme.text} attributes={TextAttributes.BOLD}>
|
||||||
|
{props.title}
|
||||||
|
</text>
|
||||||
<text fg={theme.textMuted}>esc</text>
|
<text fg={theme.textMuted}>esc</text>
|
||||||
</box>
|
</box>
|
||||||
<box paddingTop={1} paddingBottom={1}>
|
<box paddingTop={1} paddingBottom={1}>
|
||||||
@@ -166,12 +175,13 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
|||||||
props.onFilter?.(e)
|
props.onFilter?.(e)
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
|
onKeyDown={(e) => {}}
|
||||||
focusedBackgroundColor={theme.backgroundPanel}
|
focusedBackgroundColor={theme.backgroundPanel}
|
||||||
cursorColor={theme.primary}
|
cursorColor={theme.primary}
|
||||||
focusedTextColor={theme.textMuted}
|
focusedTextColor={theme.textMuted}
|
||||||
ref={(r) => {
|
ref={(r) => {
|
||||||
input = r
|
input = r
|
||||||
input.focus()
|
setTimeout(() => input.focus(), 1)
|
||||||
}}
|
}}
|
||||||
placeholder="Enter search term"
|
placeholder="Enter search term"
|
||||||
/>
|
/>
|
||||||
@@ -206,11 +216,15 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
|||||||
props.onSelect?.(option)
|
props.onSelect?.(option)
|
||||||
}}
|
}}
|
||||||
onMouseOver={() => {
|
onMouseOver={() => {
|
||||||
const index = filtered().findIndex((x) => isDeepEqual(x.value, option.value))
|
const index = filtered().findIndex((x) =>
|
||||||
|
isDeepEqual(x.value, option.value),
|
||||||
|
)
|
||||||
if (index === -1) return
|
if (index === -1) return
|
||||||
moveTo(index)
|
moveTo(index)
|
||||||
}}
|
}}
|
||||||
backgroundColor={active() ? (option.bg ?? theme.primary) : RGBA.fromInts(0, 0, 0, 0)}
|
backgroundColor={
|
||||||
|
active() ? (option.bg ?? theme.primary) : RGBA.fromInts(0, 0, 0, 0)
|
||||||
|
}
|
||||||
paddingLeft={1}
|
paddingLeft={1}
|
||||||
paddingRight={1}
|
paddingRight={1}
|
||||||
gap={1}
|
gap={1}
|
||||||
@@ -218,7 +232,9 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
|||||||
<Option
|
<Option
|
||||||
title={option.title}
|
title={option.title}
|
||||||
footer={option.footer}
|
footer={option.footer}
|
||||||
description={option.description !== category ? option.description : undefined}
|
description={
|
||||||
|
option.description !== category ? option.description : undefined
|
||||||
|
}
|
||||||
active={active()}
|
active={active()}
|
||||||
current={isDeepEqual(option.value, props.current)}
|
current={isDeepEqual(option.value, props.current)}
|
||||||
/>
|
/>
|
||||||
@@ -230,11 +246,13 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
|||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
</scrollbox>
|
</scrollbox>
|
||||||
<box paddingRight={2} paddingLeft={3} flexDirection="row" paddingBottom={1}>
|
<box paddingRight={2} paddingLeft={3} flexDirection="row" paddingBottom={1} gap={1}>
|
||||||
<For each={props.keybind ?? []}>
|
<For each={props.keybind ?? []}>
|
||||||
{(item) => (
|
{(item) => (
|
||||||
<text>
|
<text>
|
||||||
<span style={{ fg: theme.text, attributes: TextAttributes.BOLD }}>{Keybind.toString(item.keybind)}</span>
|
<span style={{ fg: theme.text, attributes: TextAttributes.BOLD }}>
|
||||||
|
{Keybind.toString(item.keybind)}
|
||||||
|
</span>
|
||||||
<span style={{ fg: theme.textMuted }}> {item.title}</span>
|
<span style={{ fg: theme.textMuted }}> {item.title}</span>
|
||||||
</text>
|
</text>
|
||||||
)}
|
)}
|
||||||
@@ -263,7 +281,10 @@ function Option(props: {
|
|||||||
wrapMode="none"
|
wrapMode="none"
|
||||||
>
|
>
|
||||||
{Locale.truncate(props.title, 62)}
|
{Locale.truncate(props.title, 62)}
|
||||||
<span style={{ fg: props.active ? theme.background : theme.textMuted }}> {props.description}</span>
|
<span style={{ fg: props.active ? theme.background : theme.textMuted }}>
|
||||||
|
{" "}
|
||||||
|
{props.description}
|
||||||
|
</span>
|
||||||
</text>
|
</text>
|
||||||
<Show when={props.footer}>
|
<Show when={props.footer}>
|
||||||
<box flexShrink={0}>
|
<box flexShrink={0}>
|
||||||
|
|||||||
@@ -1,23 +1,9 @@
|
|||||||
import { useKeyboard, useRenderer, useTerminalDimensions } from "@opentui/solid"
|
import { useKeyboard, useRenderer, useTerminalDimensions } from "@opentui/solid"
|
||||||
import { batch, createContext, createEffect, Show, useContext, type JSX, type ParentProps } from "solid-js"
|
import { batch, createContext, Show, useContext, type JSX, type ParentProps } from "solid-js"
|
||||||
import { useTheme } from "@tui/context/theme"
|
import { useTheme } from "@tui/context/theme"
|
||||||
import { Renderable, RGBA } from "@opentui/core"
|
import { Renderable, RGBA } from "@opentui/core"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { createEventBus } from "@solid-primitives/event-bus"
|
|
||||||
|
|
||||||
const Border = {
|
|
||||||
topLeft: "┃",
|
|
||||||
topRight: "┃",
|
|
||||||
bottomLeft: "┃",
|
|
||||||
bottomRight: "┃",
|
|
||||||
horizontal: "",
|
|
||||||
vertical: "┃",
|
|
||||||
topT: "+",
|
|
||||||
bottomT: "+",
|
|
||||||
leftT: "+",
|
|
||||||
rightT: "+",
|
|
||||||
cross: "+",
|
|
||||||
}
|
|
||||||
export function Dialog(
|
export function Dialog(
|
||||||
props: ParentProps<{
|
props: ParentProps<{
|
||||||
size?: "medium" | "large"
|
size?: "medium" | "large"
|
||||||
@@ -45,11 +31,9 @@ export function Dialog(
|
|||||||
onMouseUp={async (e) => {
|
onMouseUp={async (e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
}}
|
}}
|
||||||
customBorderChars={Border}
|
|
||||||
width={props.size === "large" ? 80 : 60}
|
width={props.size === "large" ? 80 : 60}
|
||||||
maxWidth={dimensions().width - 2}
|
maxWidth={dimensions().width - 2}
|
||||||
backgroundColor={theme.backgroundPanel}
|
backgroundColor={theme.backgroundPanel}
|
||||||
borderColor={theme.border}
|
|
||||||
paddingTop={1}
|
paddingTop={1}
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
@@ -66,7 +50,6 @@ function init() {
|
|||||||
}[],
|
}[],
|
||||||
size: "medium" as "medium" | "large",
|
size: "medium" as "medium" | "large",
|
||||||
})
|
})
|
||||||
const allClosedEvent = createEventBus<void>()
|
|
||||||
|
|
||||||
useKeyboard((evt) => {
|
useKeyboard((evt) => {
|
||||||
if (evt.name === "escape" && store.stack.length > 0) {
|
if (evt.name === "escape" && store.stack.length > 0) {
|
||||||
@@ -97,12 +80,6 @@ function init() {
|
|||||||
}, 1)
|
}, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
if (store.stack.length === 0) {
|
|
||||||
allClosedEvent.emit()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
clear() {
|
clear() {
|
||||||
for (const item of store.stack) {
|
for (const item of store.stack) {
|
||||||
@@ -115,7 +92,9 @@ function init() {
|
|||||||
refocus()
|
refocus()
|
||||||
},
|
},
|
||||||
replace(input: any, onClose?: () => void) {
|
replace(input: any, onClose?: () => void) {
|
||||||
if (store.stack.length === 0) focus = renderer.currentFocusedRenderable
|
if (store.stack.length === 0) {
|
||||||
|
focus = renderer.currentFocusedRenderable
|
||||||
|
}
|
||||||
for (const item of store.stack) {
|
for (const item of store.stack) {
|
||||||
if (item.onClose) item.onClose()
|
if (item.onClose) item.onClose()
|
||||||
}
|
}
|
||||||
@@ -136,9 +115,6 @@ function init() {
|
|||||||
setSize(size: "medium" | "large") {
|
setSize(size: "medium" | "large") {
|
||||||
setStore("size", size)
|
setStore("size", size)
|
||||||
},
|
},
|
||||||
get allClosedEvent() {
|
|
||||||
return allClosedEvent
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ function init() {
|
|||||||
|
|
||||||
let timeoutHandle: NodeJS.Timeout | null = null
|
let timeoutHandle: NodeJS.Timeout | null = null
|
||||||
|
|
||||||
return {
|
const toast = {
|
||||||
show(options: ToastOptions) {
|
show(options: ToastOptions) {
|
||||||
const parsedOptions = TuiEvent.ToastShow.properties.parse(options)
|
const parsedOptions = TuiEvent.ToastShow.properties.parse(options)
|
||||||
const { duration, ...currentToast } = parsedOptions
|
const { duration, ...currentToast } = parsedOptions
|
||||||
@@ -59,10 +59,22 @@ function init() {
|
|||||||
setStore("currentToast", null)
|
setStore("currentToast", null)
|
||||||
}, duration).unref()
|
}, duration).unref()
|
||||||
},
|
},
|
||||||
|
error: (err: any) => {
|
||||||
|
if (err instanceof Error)
|
||||||
|
return toast.show({
|
||||||
|
variant: "error",
|
||||||
|
message: err.message,
|
||||||
|
})
|
||||||
|
toast.show({
|
||||||
|
variant: "error",
|
||||||
|
message: "An unknown error has occurred",
|
||||||
|
})
|
||||||
|
},
|
||||||
get currentToast(): ToastOptions | null {
|
get currentToast(): ToastOptions | null {
|
||||||
return store.currentToast
|
return store.currentToast
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
return toast
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ToastContext = ReturnType<typeof init>
|
export type ToastContext = ReturnType<typeof init>
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export namespace Clipboard {
|
|||||||
const getCopyMethod = lazy(() => {
|
const getCopyMethod = lazy(() => {
|
||||||
const os = platform()
|
const os = platform()
|
||||||
|
|
||||||
if (os === "darwin") {
|
if (os === "darwin" && Bun.which("oascript")) {
|
||||||
console.log("clipboard: using osascript")
|
console.log("clipboard: using osascript")
|
||||||
return async (text: string) => {
|
return async (text: string) => {
|
||||||
const escaped = text.replace(/\\/g, "\\\\").replace(/"/g, '\\"')
|
const escaped = text.replace(/\\/g, "\\\\").replace(/"/g, '\\"')
|
||||||
@@ -70,13 +70,13 @@ export namespace Clipboard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (os === "linux") {
|
if (os === "linux") {
|
||||||
if (process.env["WAYLAND_DISPLAY"]) {
|
if (process.env["WAYLAND_DISPLAY"] && Bun.which("wl-copy")) {
|
||||||
console.log("clipboard: using wl-copy")
|
console.log("clipboard: using wl-copy")
|
||||||
return async (text: string) => {
|
return async (text: string) => {
|
||||||
const proc = Bun.spawn(["wl-copy"], { stdin: "pipe", stdout: "ignore", stderr: "ignore" })
|
const proc = Bun.spawn(["wl-copy"], { stdin: "pipe", stdout: "ignore", stderr: "ignore" })
|
||||||
proc.stdin.write(text)
|
proc.stdin.write(text)
|
||||||
proc.stdin.end()
|
proc.stdin.end()
|
||||||
await proc.exited
|
await proc.exited.catch(() => {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (Bun.which("xclip")) {
|
if (Bun.which("xclip")) {
|
||||||
@@ -89,7 +89,7 @@ export namespace Clipboard {
|
|||||||
})
|
})
|
||||||
proc.stdin.write(text)
|
proc.stdin.write(text)
|
||||||
proc.stdin.end()
|
proc.stdin.end()
|
||||||
await proc.exited
|
await proc.exited.catch(() => {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (Bun.which("xsel")) {
|
if (Bun.which("xsel")) {
|
||||||
@@ -102,7 +102,7 @@ export namespace Clipboard {
|
|||||||
})
|
})
|
||||||
proc.stdin.write(text)
|
proc.stdin.write(text)
|
||||||
proc.stdin.end()
|
proc.stdin.end()
|
||||||
await proc.exited
|
await proc.exited.catch(() => {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export namespace Editor {
|
|||||||
})
|
})
|
||||||
await proc.exited
|
await proc.exited
|
||||||
const content = await Bun.file(filepath).text()
|
const content = await Bun.file(filepath).text()
|
||||||
|
opts.renderer.currentRenderBuffer.clear()
|
||||||
opts.renderer.resume()
|
opts.renderer.resume()
|
||||||
opts.renderer.requestRender()
|
opts.renderer.requestRender()
|
||||||
return content || undefined
|
return content || undefined
|
||||||
|
|||||||
@@ -1,8 +1,27 @@
|
|||||||
import z from "zod"
|
import z from "zod"
|
||||||
import { Config } from "../config/config"
|
import { Config } from "../config/config"
|
||||||
import { Instance } from "../project/instance"
|
import { Instance } from "../project/instance"
|
||||||
|
import PROMPT_INITIALIZE from "./template/initialize.txt"
|
||||||
|
import { Bus } from "../bus"
|
||||||
|
import { Identifier } from "../id/id"
|
||||||
|
|
||||||
export namespace Command {
|
export namespace Command {
|
||||||
|
export const Default = {
|
||||||
|
INIT: "init",
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export const Event = {
|
||||||
|
Executed: Bus.event(
|
||||||
|
"command.executed",
|
||||||
|
z.object({
|
||||||
|
name: z.string(),
|
||||||
|
sessionID: Identifier.schema("session"),
|
||||||
|
arguments: z.string(),
|
||||||
|
messageID: Identifier.schema("message"),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
export const Info = z
|
export const Info = z
|
||||||
.object({
|
.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
@@ -33,6 +52,14 @@ export namespace Command {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (result[Default.INIT] === undefined) {
|
||||||
|
result[Default.INIT] = {
|
||||||
|
name: Default.INIT,
|
||||||
|
description: "create/update AGENTS.md",
|
||||||
|
template: PROMPT_INITIALIZE.replace("${path}", Instance.worktree),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
+2
@@ -6,3 +6,5 @@ The file you create will be given to agentic coding agents (such as yourself) th
|
|||||||
If there are Cursor rules (in .cursor/rules/ or .cursorrules) or Copilot rules (in .github/copilot-instructions.md), make sure to include them.
|
If there are Cursor rules (in .cursor/rules/ or .cursorrules) or Copilot rules (in .github/copilot-instructions.md), make sure to include them.
|
||||||
|
|
||||||
If there's already an AGENTS.md, improve it if it's located in ${path}
|
If there's already an AGENTS.md, improve it if it's located in ${path}
|
||||||
|
|
||||||
|
$ARGUMENTS
|
||||||
@@ -385,7 +385,11 @@ export namespace Config {
|
|||||||
.optional()
|
.optional()
|
||||||
.default("ctrl+x")
|
.default("ctrl+x")
|
||||||
.describe("Leader key for keybind combinations"),
|
.describe("Leader key for keybind combinations"),
|
||||||
app_exit: z.string().optional().default("ctrl+c,<leader>q").describe("Exit the application"),
|
app_exit: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.default("ctrl+c,ctrl+d,<leader>q")
|
||||||
|
.describe("Exit the application"),
|
||||||
editor_open: z.string().optional().default("<leader>e").describe("Open external editor"),
|
editor_open: z.string().optional().default("<leader>e").describe("Open external editor"),
|
||||||
theme_list: z.string().optional().default("<leader>t").describe("List available themes"),
|
theme_list: z.string().optional().default("<leader>t").describe("List available themes"),
|
||||||
sidebar_toggle: z.string().optional().default("<leader>b").describe("Toggle sidebar"),
|
sidebar_toggle: z.string().optional().default("<leader>b").describe("Toggle sidebar"),
|
||||||
@@ -449,18 +453,37 @@ export namespace Config {
|
|||||||
.default("<leader>h")
|
.default("<leader>h")
|
||||||
.describe("Toggle code block concealment in messages"),
|
.describe("Toggle code block concealment in messages"),
|
||||||
model_list: z.string().optional().default("<leader>m").describe("List available models"),
|
model_list: z.string().optional().default("<leader>m").describe("List available models"),
|
||||||
|
model_cycle_recent: z.string().optional().default("f2").describe("Next recently used model"),
|
||||||
|
model_cycle_recent_reverse: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.default("shift+f2")
|
||||||
|
.describe("Previous recently used model"),
|
||||||
command_list: z.string().optional().default("ctrl+p").describe("List available commands"),
|
command_list: z.string().optional().default("ctrl+p").describe("List available commands"),
|
||||||
agent_list: z.string().optional().default("<leader>a").describe("List agents"),
|
agent_list: z.string().optional().default("<leader>a").describe("List agents"),
|
||||||
agent_cycle: z.string().optional().default("tab").describe("Next agent"),
|
agent_cycle: z.string().optional().default("tab").describe("Next agent"),
|
||||||
agent_cycle_reverse: z.string().optional().default("shift+tab").describe("Previous agent"),
|
agent_cycle_reverse: z.string().optional().default("shift+tab").describe("Previous agent"),
|
||||||
input_clear: z.string().optional().default("ctrl+c").describe("Clear input field"),
|
input_clear: z.string().optional().default("ctrl+c").describe("Clear input field"),
|
||||||
|
input_forward_delete: z.string().optional().default("ctrl+d").describe("Forward delete"),
|
||||||
input_paste: z.string().optional().default("ctrl+v").describe("Paste from clipboard"),
|
input_paste: z.string().optional().default("ctrl+v").describe("Paste from clipboard"),
|
||||||
input_submit: z.string().optional().default("enter").describe("Submit input"),
|
input_submit: z.string().optional().default("return").describe("Submit input"),
|
||||||
input_newline: z
|
input_newline: z
|
||||||
.string()
|
.string()
|
||||||
.optional()
|
.optional()
|
||||||
.default("shift+enter,ctrl+j")
|
.default("shift+return,ctrl+j")
|
||||||
.describe("Insert newline in input"),
|
.describe("Insert newline in input"),
|
||||||
|
history_previous: z.string().optional().default("up").describe("Previous history item"),
|
||||||
|
history_next: z.string().optional().default("down").describe("Previous history item"),
|
||||||
|
session_child_cycle: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.default("ctrl+right")
|
||||||
|
.describe("Next child session"),
|
||||||
|
session_child_cycle_reverse: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.default("ctrl+left")
|
||||||
|
.describe("Previous child session"),
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
.meta({
|
.meta({
|
||||||
|
|||||||
@@ -367,6 +367,7 @@ export namespace Ripgrep {
|
|||||||
args.push(`--max-count=${input.limit}`)
|
args.push(`--max-count=${input.limit}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
args.push("--")
|
||||||
args.push(input.pattern)
|
args.push(input.pattern)
|
||||||
|
|
||||||
const command = args.join(" ")
|
const command = args.join(" ")
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Bus } from "../bus"
|
|||||||
import { File } from "../file"
|
import { File } from "../file"
|
||||||
import { Log } from "../util/log"
|
import { Log } from "../util/log"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
|
import z from "zod"
|
||||||
|
|
||||||
import * as Formatter from "./formatter"
|
import * as Formatter from "./formatter"
|
||||||
import { Config } from "../config/config"
|
import { Config } from "../config/config"
|
||||||
@@ -11,6 +12,17 @@ import { Instance } from "../project/instance"
|
|||||||
export namespace Format {
|
export namespace Format {
|
||||||
const log = Log.create({ service: "format" })
|
const log = Log.create({ service: "format" })
|
||||||
|
|
||||||
|
export const Status = z
|
||||||
|
.object({
|
||||||
|
name: z.string(),
|
||||||
|
extensions: z.string().array(),
|
||||||
|
enabled: z.boolean(),
|
||||||
|
})
|
||||||
|
.meta({
|
||||||
|
ref: "FormatterStatus",
|
||||||
|
})
|
||||||
|
export type Status = z.infer<typeof Status>
|
||||||
|
|
||||||
const state = Instance.state(async () => {
|
const state = Instance.state(async () => {
|
||||||
const enabled: Record<string, boolean> = {}
|
const enabled: Record<string, boolean> = {}
|
||||||
const cfg = await Config.get()
|
const cfg = await Config.get()
|
||||||
@@ -62,6 +74,20 @@ export namespace Format {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function status() {
|
||||||
|
const s = await state()
|
||||||
|
const result: Status[] = []
|
||||||
|
for (const formatter of Object.values(s.formatters)) {
|
||||||
|
const enabled = await isEnabled(formatter)
|
||||||
|
result.push({
|
||||||
|
name: formatter.name,
|
||||||
|
extensions: formatter.extensions,
|
||||||
|
enabled,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
export function init() {
|
export function init() {
|
||||||
log.info("init")
|
log.info("init")
|
||||||
Bus.subscribe(File.Event.Edited, async (payload) => {
|
Bus.subscribe(File.Event.Edited, async (payload) => {
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ process.on("uncaughtException", (e) => {
|
|||||||
const cli = yargs(hideBin(process.argv))
|
const cli = yargs(hideBin(process.argv))
|
||||||
.scriptName("opencode")
|
.scriptName("opencode")
|
||||||
.help("help", "show help")
|
.help("help", "show help")
|
||||||
|
.alias("help", "h")
|
||||||
.version("version", "show version number", Installation.VERSION)
|
.version("version", "show version number", Installation.VERSION)
|
||||||
.alias("version", "v")
|
.alias("version", "v")
|
||||||
.option("print-logs", {
|
.option("print-logs", {
|
||||||
@@ -140,5 +141,5 @@ try {
|
|||||||
// Most notably, some docker-container-based MCP servers don't handle such signals unless
|
// Most notably, some docker-container-based MCP servers don't handle such signals unless
|
||||||
// run using `docker run --init`.
|
// run using `docker run --init`.
|
||||||
// Explicitly exit to avoid any hanging subprocesses.
|
// Explicitly exit to avoid any hanging subprocesses.
|
||||||
process.exit();
|
process.exit()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ import { LSP } from "../lsp"
|
|||||||
import { FileWatcher } from "../file/watcher"
|
import { FileWatcher } from "../file/watcher"
|
||||||
import { File } from "../file"
|
import { File } from "../file"
|
||||||
import { Flag } from "../flag/flag"
|
import { Flag } from "../flag/flag"
|
||||||
|
import { Project } from "./project"
|
||||||
|
import { Bus } from "../bus"
|
||||||
|
import { Command } from "../command"
|
||||||
|
import { Instance } from "./instance"
|
||||||
|
|
||||||
export async function InstanceBootstrap() {
|
export async function InstanceBootstrap() {
|
||||||
if (Flag.OPENCODE_EXPERIMENTAL_NO_BOOTSTRAP) return
|
if (Flag.OPENCODE_EXPERIMENTAL_NO_BOOTSTRAP) return
|
||||||
@@ -14,4 +18,10 @@ export async function InstanceBootstrap() {
|
|||||||
await LSP.init()
|
await LSP.init()
|
||||||
FileWatcher.init()
|
FileWatcher.init()
|
||||||
File.init()
|
File.init()
|
||||||
|
|
||||||
|
Bus.subscribe(Command.Event.Executed, async (payload) => {
|
||||||
|
if (payload.properties.name === Command.Default.INIT) {
|
||||||
|
await Project.setInitialized(Instance.project.id)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,11 @@ const context = Context.create<Context>("instance")
|
|||||||
const cache = new Map<string, Context>()
|
const cache = new Map<string, Context>()
|
||||||
|
|
||||||
export const Instance = {
|
export const Instance = {
|
||||||
async provide<R>(input: { directory: string; init?: () => Promise<any>; fn: () => R }): Promise<R> {
|
async provide<R>(input: {
|
||||||
|
directory: string
|
||||||
|
init?: () => Promise<any>
|
||||||
|
fn: () => R
|
||||||
|
}): Promise<R> {
|
||||||
let existing = cache.get(input.directory)
|
let existing = cache.get(input.directory)
|
||||||
if (!existing) {
|
if (!existing) {
|
||||||
const project = await Project.fromDirectory(input.directory)
|
const project = await Project.fromDirectory(input.directory)
|
||||||
@@ -24,8 +28,8 @@ export const Instance = {
|
|||||||
}
|
}
|
||||||
return context.provide(existing, async () => {
|
return context.provide(existing, async () => {
|
||||||
if (!cache.has(input.directory)) {
|
if (!cache.has(input.directory)) {
|
||||||
await input.init?.()
|
|
||||||
cache.set(input.directory, existing)
|
cache.set(input.directory, existing)
|
||||||
|
await input.init?.()
|
||||||
}
|
}
|
||||||
return input.fn()
|
return input.fn()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import { Ripgrep } from "../file/ripgrep"
|
|||||||
import { Config } from "../config/config"
|
import { Config } from "../config/config"
|
||||||
import { File } from "../file"
|
import { File } from "../file"
|
||||||
import { LSP } from "../lsp"
|
import { LSP } from "../lsp"
|
||||||
|
import { Format } from "../format"
|
||||||
import { MessageV2 } from "../session/message-v2"
|
import { MessageV2 } from "../session/message-v2"
|
||||||
import { TuiRoute } from "./tui"
|
import { TuiRoute } from "./tui"
|
||||||
import { Permission } from "../permission"
|
import { Permission } from "../permission"
|
||||||
@@ -1336,6 +1337,26 @@ export namespace Server {
|
|||||||
return c.json(await LSP.status())
|
return c.json(await LSP.status())
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
.get(
|
||||||
|
"/formatter",
|
||||||
|
describeRoute({
|
||||||
|
description: "Get formatter status",
|
||||||
|
operationId: "formatter.status",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Formatter status",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: resolver(Format.Status.array()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (c) => {
|
||||||
|
return c.json(await Format.status())
|
||||||
|
},
|
||||||
|
)
|
||||||
.post(
|
.post(
|
||||||
"/tui/append-prompt",
|
"/tui/append-prompt",
|
||||||
describeRoute({
|
describeRoute({
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ import { Decimal } from "decimal.js"
|
|||||||
import z from "zod"
|
import z from "zod"
|
||||||
import { type LanguageModelUsage, type ProviderMetadata } from "ai"
|
import { type LanguageModelUsage, type ProviderMetadata } from "ai"
|
||||||
|
|
||||||
import PROMPT_INITIALIZE from "../session/prompt/initialize.txt"
|
|
||||||
|
|
||||||
import { Bus } from "../bus"
|
import { Bus } from "../bus"
|
||||||
import { Config } from "../config/config"
|
import { Config } from "../config/config"
|
||||||
import { Flag } from "../flag/flag"
|
import { Flag } from "../flag/flag"
|
||||||
@@ -14,11 +12,11 @@ import { Share } from "../share/share"
|
|||||||
import { Storage } from "../storage/storage"
|
import { Storage } from "../storage/storage"
|
||||||
import { Log } from "../util/log"
|
import { Log } from "../util/log"
|
||||||
import { MessageV2 } from "./message-v2"
|
import { MessageV2 } from "./message-v2"
|
||||||
import { Project } from "../project/project"
|
|
||||||
import { Instance } from "../project/instance"
|
import { Instance } from "../project/instance"
|
||||||
import { SessionPrompt } from "./prompt"
|
import { SessionPrompt } from "./prompt"
|
||||||
import { fn } from "@/util/fn"
|
import { fn } from "@/util/fn"
|
||||||
import { Snapshot } from "@/snapshot"
|
import { Snapshot } from "@/snapshot"
|
||||||
|
import { Command } from "../command"
|
||||||
|
|
||||||
export namespace Session {
|
export namespace Session {
|
||||||
const log = Log.create({ service: "session" })
|
const log = Log.create({ service: "session" })
|
||||||
@@ -164,7 +162,12 @@ export namespace Session {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
export async function createNext(input: { id?: string; title?: string; parentID?: string; directory: string }) {
|
export async function createNext(input: {
|
||||||
|
id?: string
|
||||||
|
title?: string
|
||||||
|
parentID?: string
|
||||||
|
directory: string
|
||||||
|
}) {
|
||||||
const result: Info = {
|
const result: Info = {
|
||||||
id: Identifier.descending("session", input.id),
|
id: Identifier.descending("session", input.id),
|
||||||
version: Installation.VERSION,
|
version: Installation.VERSION,
|
||||||
@@ -402,7 +405,9 @@ export namespace Session {
|
|||||||
.add(new Decimal(tokens.input).mul(input.model.cost?.input ?? 0).div(1_000_000))
|
.add(new Decimal(tokens.input).mul(input.model.cost?.input ?? 0).div(1_000_000))
|
||||||
.add(new Decimal(tokens.output).mul(input.model.cost?.output ?? 0).div(1_000_000))
|
.add(new Decimal(tokens.output).mul(input.model.cost?.output ?? 0).div(1_000_000))
|
||||||
.add(new Decimal(tokens.cache.read).mul(input.model.cost?.cache_read ?? 0).div(1_000_000))
|
.add(new Decimal(tokens.cache.read).mul(input.model.cost?.cache_read ?? 0).div(1_000_000))
|
||||||
.add(new Decimal(tokens.cache.write).mul(input.model.cost?.cache_write ?? 0).div(1_000_000))
|
.add(
|
||||||
|
new Decimal(tokens.cache.write).mul(input.model.cost?.cache_write ?? 0).div(1_000_000),
|
||||||
|
)
|
||||||
.toNumber(),
|
.toNumber(),
|
||||||
tokens,
|
tokens,
|
||||||
}
|
}
|
||||||
@@ -423,22 +428,13 @@ export namespace Session {
|
|||||||
messageID: Identifier.schema("message"),
|
messageID: Identifier.schema("message"),
|
||||||
}),
|
}),
|
||||||
async (input) => {
|
async (input) => {
|
||||||
await SessionPrompt.prompt({
|
await SessionPrompt.command({
|
||||||
sessionID: input.sessionID,
|
sessionID: input.sessionID,
|
||||||
messageID: input.messageID,
|
messageID: input.messageID,
|
||||||
model: {
|
model: input.providerID + "/" + input.modelID,
|
||||||
providerID: input.providerID,
|
command: Command.Default.INIT,
|
||||||
modelID: input.modelID,
|
arguments: "",
|
||||||
},
|
|
||||||
parts: [
|
|
||||||
{
|
|
||||||
id: Identifier.ascending("part"),
|
|
||||||
type: "text",
|
|
||||||
text: PROMPT_INITIALIZE.replace("${path}", Instance.worktree),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
await Project.setInitialized(Instance.project.id)
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1593,6 +1593,7 @@ export namespace SessionPrompt {
|
|||||||
let index = 0
|
let index = 0
|
||||||
template = template.replace(bashRegex, () => results[index++])
|
template = template.replace(bashRegex, () => results[index++])
|
||||||
}
|
}
|
||||||
|
template = template.trim()
|
||||||
|
|
||||||
const parts = [
|
const parts = [
|
||||||
{
|
{
|
||||||
@@ -1657,6 +1658,8 @@ export namespace SessionPrompt {
|
|||||||
})()
|
})()
|
||||||
|
|
||||||
const agent = await Agent.get(agentName)
|
const agent = await Agent.get(agentName)
|
||||||
|
let result: MessageV2.WithParts
|
||||||
|
|
||||||
if ((agent.mode === "subagent" && command.subtask !== false) || command.subtask === true) {
|
if ((agent.mode === "subagent" && command.subtask !== false) || command.subtask === true) {
|
||||||
using abort = lock(input.sessionID)
|
using abort = lock(input.sessionID)
|
||||||
|
|
||||||
@@ -1732,7 +1735,7 @@ export namespace SessionPrompt {
|
|||||||
}
|
}
|
||||||
await Session.updatePart(toolPart)
|
await Session.updatePart(toolPart)
|
||||||
|
|
||||||
const result = await TaskTool.init().then((t) =>
|
const taskResult = await TaskTool.init().then((t) =>
|
||||||
t.execute(args, {
|
t.execute(args, {
|
||||||
sessionID: input.sessionID,
|
sessionID: input.sessionID,
|
||||||
abort: abort.signal,
|
abort: abort.signal,
|
||||||
@@ -1760,22 +1763,31 @@ export namespace SessionPrompt {
|
|||||||
},
|
},
|
||||||
input: toolPart.state.input,
|
input: toolPart.state.input,
|
||||||
title: "",
|
title: "",
|
||||||
metadata: result.metadata,
|
metadata: taskResult.metadata,
|
||||||
output: result.output,
|
output: taskResult.output,
|
||||||
}
|
}
|
||||||
await Session.updatePart(toolPart)
|
await Session.updatePart(toolPart)
|
||||||
}
|
}
|
||||||
|
|
||||||
return { info: assistantMsg, parts: [toolPart] }
|
result = { info: assistantMsg, parts: [toolPart] }
|
||||||
|
} else {
|
||||||
|
result = await prompt({
|
||||||
|
sessionID: input.sessionID,
|
||||||
|
messageID: input.messageID,
|
||||||
|
model,
|
||||||
|
agent: agentName,
|
||||||
|
parts,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return prompt({
|
Bus.publish(Command.Event.Executed, {
|
||||||
|
name: input.command,
|
||||||
sessionID: input.sessionID,
|
sessionID: input.sessionID,
|
||||||
messageID: input.messageID,
|
arguments: input.arguments,
|
||||||
model,
|
messageID: result.info.id,
|
||||||
agent: agentName,
|
|
||||||
parts,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ensureTitle(input: {
|
async function ensureTitle(input: {
|
||||||
|
|||||||
@@ -6,10 +6,11 @@ import { generateText, type ModelMessage } from "ai"
|
|||||||
import { MessageV2 } from "./message-v2"
|
import { MessageV2 } from "./message-v2"
|
||||||
import { Identifier } from "@/id/id"
|
import { Identifier } from "@/id/id"
|
||||||
import { Snapshot } from "@/snapshot"
|
import { Snapshot } from "@/snapshot"
|
||||||
|
|
||||||
import { ProviderTransform } from "@/provider/transform"
|
import { ProviderTransform } from "@/provider/transform"
|
||||||
import { SystemPrompt } from "./system"
|
import { SystemPrompt } from "./system"
|
||||||
import { Log } from "@/util/log"
|
import { Log } from "@/util/log"
|
||||||
|
import path from "path"
|
||||||
|
import { Instance } from "@/project/instance"
|
||||||
|
|
||||||
export namespace SessionSummary {
|
export namespace SessionSummary {
|
||||||
const log = Log.create({ service: "session.summary" })
|
const log = Log.create({ service: "session.summary" })
|
||||||
@@ -33,10 +34,13 @@ export namespace SessionSummary {
|
|||||||
input.messages
|
input.messages
|
||||||
.flatMap((x) => x.parts)
|
.flatMap((x) => x.parts)
|
||||||
.filter((x) => x.type === "patch")
|
.filter((x) => x.type === "patch")
|
||||||
.flatMap((x) => x.files),
|
.flatMap((x) => x.files)
|
||||||
|
.map((x) => path.relative(Instance.worktree, x)),
|
||||||
)
|
)
|
||||||
const diffs = await computeDiff({ messages: input.messages }).then((x) =>
|
const diffs = await computeDiff({ messages: input.messages }).then((x) =>
|
||||||
x.filter((x) => files.has(x.file)),
|
x.filter((x) => {
|
||||||
|
return files.has(x.file)
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
await Session.update(input.sessionID, (draft) => {
|
await Session.update(input.sessionID, (draft) => {
|
||||||
draft.summary = {
|
draft.summary = {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export const GrepTool = Tool.define("grep", {
|
|||||||
const searchPath = params.path || Instance.directory
|
const searchPath = params.path || Instance.directory
|
||||||
|
|
||||||
const rgPath = await Ripgrep.filepath()
|
const rgPath = await Ripgrep.filepath()
|
||||||
const args = ["-nH", "--field-match-separator=|", params.pattern]
|
const args = ["-nH", "--field-match-separator=|", "--regexp", params.pattern]
|
||||||
if (params.include) {
|
if (params.include) {
|
||||||
args.push("--glob", params.include)
|
args.push("--glob", params.include)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ Usage notes:
|
|||||||
Example usage (NOTE: The agents below are fictional examples for illustration only - use the actual agents listed above):
|
Example usage (NOTE: The agents below are fictional examples for illustration only - use the actual agents listed above):
|
||||||
|
|
||||||
<example_agent_descriptions>
|
<example_agent_descriptions>
|
||||||
"code-reviewer": use this agent after you are done writing a signficant piece of code
|
"code-reviewer": use this agent after you are done writing a significant piece of code
|
||||||
"greeting-responder": use this agent when to respond to user greetings with a friendly joke
|
"greeting-responder": use this agent when to respond to user greetings with a friendly joke
|
||||||
</example_agent_description>
|
</example_agent_description>
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ function isPrime(n) {
|
|||||||
}
|
}
|
||||||
</code>
|
</code>
|
||||||
<commentary>
|
<commentary>
|
||||||
Since a signficant piece of code was written and the task was completed, now use the code-reviewer agent to review the code
|
Since a significant piece of code was written and the task was completed, now use the code-reviewer agent to review the code
|
||||||
</commentary>
|
</commentary>
|
||||||
assistant: Now let me use the code-reviewer agent to review the code
|
assistant: Now let me use the code-reviewer agent to review the code
|
||||||
assistant: Uses the Task tool to launch the code-reviewer agent
|
assistant: Uses the Task tool to launch the code-reviewer agent
|
||||||
|
|||||||
@@ -28,8 +28,14 @@ describe("Keybind.toString", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test("should convert shift modifier to string", () => {
|
test("should convert shift modifier to string", () => {
|
||||||
const info: Keybind.Info = { ctrl: false, meta: false, shift: true, leader: false, name: "enter" }
|
const info: Keybind.Info = {
|
||||||
expect(Keybind.toString(info)).toBe("shift+enter")
|
ctrl: false,
|
||||||
|
meta: false,
|
||||||
|
shift: true,
|
||||||
|
leader: false,
|
||||||
|
name: "return",
|
||||||
|
}
|
||||||
|
expect(Keybind.toString(info)).toBe("shift+return")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("should convert function key to string", () => {
|
test("should convert function key to string", () => {
|
||||||
@@ -38,7 +44,13 @@ describe("Keybind.toString", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test("should convert special key to string", () => {
|
test("should convert special key to string", () => {
|
||||||
const info: Keybind.Info = { ctrl: false, meta: false, shift: false, leader: false, name: "pgup" }
|
const info: Keybind.Info = {
|
||||||
|
ctrl: false,
|
||||||
|
meta: false,
|
||||||
|
shift: false,
|
||||||
|
leader: false,
|
||||||
|
name: "pgup",
|
||||||
|
}
|
||||||
expect(Keybind.toString(info)).toBe("pgup")
|
expect(Keybind.toString(info)).toBe("pgup")
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -220,15 +232,15 @@ describe("Keybind.parse", () => {
|
|||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
test("should parse shift+enter combination", () => {
|
test("should parse shift+return combination", () => {
|
||||||
const result = Keybind.parse("shift+enter")
|
const result = Keybind.parse("shift+return")
|
||||||
expect(result).toEqual([
|
expect(result).toEqual([
|
||||||
{
|
{
|
||||||
ctrl: false,
|
ctrl: false,
|
||||||
meta: false,
|
meta: false,
|
||||||
shift: true,
|
shift: true,
|
||||||
leader: false,
|
leader: false,
|
||||||
name: "enter",
|
name: "return",
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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": "1.0.2",
|
"version": "1.0.14",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"typecheck": "tsgo --noEmit",
|
"typecheck": "tsgo --noEmit",
|
||||||
|
|||||||
@@ -1,7 +1,18 @@
|
|||||||
import { $ } from "bun"
|
import { $ } from "bun"
|
||||||
|
import path from "path"
|
||||||
|
|
||||||
if (process.versions.bun !== "1.3.0") {
|
const rootPkgPath = path.resolve(import.meta.dir, "../../../package.json")
|
||||||
throw new Error("This script requires bun@1.3.0")
|
const rootPkg = await Bun.file(rootPkgPath).json()
|
||||||
|
const expectedBunVersion = rootPkg.packageManager?.split("@")[1]
|
||||||
|
|
||||||
|
if (!expectedBunVersion) {
|
||||||
|
throw new Error("packageManager field not found in root package.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.versions.bun !== expectedBunVersion) {
|
||||||
|
throw new Error(
|
||||||
|
`This script requires bun@${expectedBunVersion}, but you are using bun@${process.versions.bun}`,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const CHANNEL =
|
const CHANNEL =
|
||||||
@@ -9,6 +20,7 @@ const CHANNEL =
|
|||||||
(await $`git branch --show-current`.text().then((x) => x.trim()))
|
(await $`git branch --show-current`.text().then((x) => x.trim()))
|
||||||
const IS_PREVIEW = CHANNEL !== "latest"
|
const IS_PREVIEW = CHANNEL !== "latest"
|
||||||
const VERSION = await (async () => {
|
const VERSION = await (async () => {
|
||||||
|
if (process.env["OPENCODE_VERSION"]) return process.env["OPENCODE_VERSION"]
|
||||||
if (IS_PREVIEW)
|
if (IS_PREVIEW)
|
||||||
return `0.0.0-${CHANNEL}-${new Date().toISOString().slice(0, 16).replace(/[-:T]/g, "")}`
|
return `0.0.0-${CHANNEL}-${new Date().toISOString().slice(0, 16).replace(/[-:T]/g, "")}`
|
||||||
const version = await fetch("https://registry.npmjs.org/opencode-ai/latest")
|
const version = await fetch("https://registry.npmjs.org/opencode-ai/latest")
|
||||||
|
|||||||
@@ -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": "1.0.2",
|
"version": "1.0.14",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"typecheck": "tsgo --noEmit",
|
"typecheck": "tsgo --noEmit",
|
||||||
|
|||||||
@@ -107,6 +107,8 @@ import type {
|
|||||||
McpStatusResponses,
|
McpStatusResponses,
|
||||||
LspStatusData,
|
LspStatusData,
|
||||||
LspStatusResponses,
|
LspStatusResponses,
|
||||||
|
FormatterStatusData,
|
||||||
|
FormatterStatusResponses,
|
||||||
TuiAppendPromptData,
|
TuiAppendPromptData,
|
||||||
TuiAppendPromptResponses,
|
TuiAppendPromptResponses,
|
||||||
TuiAppendPromptErrors,
|
TuiAppendPromptErrors,
|
||||||
@@ -773,6 +775,20 @@ class Lsp extends _HeyApiClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Formatter extends _HeyApiClient {
|
||||||
|
/**
|
||||||
|
* Get formatter status
|
||||||
|
*/
|
||||||
|
public status<ThrowOnError extends boolean = false>(
|
||||||
|
options?: Options<FormatterStatusData, ThrowOnError>,
|
||||||
|
) {
|
||||||
|
return (options?.client ?? this._client).get<FormatterStatusResponses, unknown, ThrowOnError>({
|
||||||
|
url: "/formatter",
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class Control extends _HeyApiClient {
|
class Control extends _HeyApiClient {
|
||||||
/**
|
/**
|
||||||
* Get the next TUI request from the queue
|
* Get the next TUI request from the queue
|
||||||
@@ -1023,6 +1039,7 @@ export class OpencodeClient extends _HeyApiClient {
|
|||||||
app = new App({ client: this._client })
|
app = new App({ client: this._client })
|
||||||
mcp = new Mcp({ client: this._client })
|
mcp = new Mcp({ client: this._client })
|
||||||
lsp = new Lsp({ client: this._client })
|
lsp = new Lsp({ client: this._client })
|
||||||
|
formatter = new Formatter({ client: this._client })
|
||||||
tui = new Tui({ client: this._client })
|
tui = new Tui({ client: this._client })
|
||||||
auth = new Auth({ client: this._client })
|
auth = new Auth({ client: this._client })
|
||||||
event = new Event({ client: this._client })
|
event = new Event({ client: this._client })
|
||||||
|
|||||||
@@ -114,6 +114,14 @@ export type KeybindsConfig = {
|
|||||||
* List available models
|
* List available models
|
||||||
*/
|
*/
|
||||||
model_list?: string
|
model_list?: string
|
||||||
|
/**
|
||||||
|
* Next recently used model
|
||||||
|
*/
|
||||||
|
model_cycle_recent?: string
|
||||||
|
/**
|
||||||
|
* Previous recently used model
|
||||||
|
*/
|
||||||
|
model_cycle_recent_reverse?: string
|
||||||
/**
|
/**
|
||||||
* List available commands
|
* List available commands
|
||||||
*/
|
*/
|
||||||
@@ -134,6 +142,10 @@ export type KeybindsConfig = {
|
|||||||
* Clear input field
|
* Clear input field
|
||||||
*/
|
*/
|
||||||
input_clear?: string
|
input_clear?: string
|
||||||
|
/**
|
||||||
|
* Forward delete
|
||||||
|
*/
|
||||||
|
input_forward_delete?: string
|
||||||
/**
|
/**
|
||||||
* Paste from clipboard
|
* Paste from clipboard
|
||||||
*/
|
*/
|
||||||
@@ -146,6 +158,22 @@ export type KeybindsConfig = {
|
|||||||
* Insert newline in input
|
* Insert newline in input
|
||||||
*/
|
*/
|
||||||
input_newline?: string
|
input_newline?: string
|
||||||
|
/**
|
||||||
|
* Previous history item
|
||||||
|
*/
|
||||||
|
history_previous?: string
|
||||||
|
/**
|
||||||
|
* Previous history item
|
||||||
|
*/
|
||||||
|
history_next?: string
|
||||||
|
/**
|
||||||
|
* Next child session
|
||||||
|
*/
|
||||||
|
session_child_cycle?: string
|
||||||
|
/**
|
||||||
|
* Previous child session
|
||||||
|
*/
|
||||||
|
session_child_cycle_reverse?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AgentConfig = {
|
export type AgentConfig = {
|
||||||
@@ -1050,6 +1078,12 @@ export type LspStatus = {
|
|||||||
status: "connected" | "error"
|
status: "connected" | "error"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type FormatterStatus = {
|
||||||
|
name: string
|
||||||
|
extensions: Array<string>
|
||||||
|
enabled: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export type EventTuiPromptAppend = {
|
export type EventTuiPromptAppend = {
|
||||||
type: "tui.prompt.append"
|
type: "tui.prompt.append"
|
||||||
properties: {
|
properties: {
|
||||||
@@ -1228,6 +1262,16 @@ export type EventTodoUpdated = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type EventCommandExecuted = {
|
||||||
|
type: "command.executed"
|
||||||
|
properties: {
|
||||||
|
name: string
|
||||||
|
sessionID: string
|
||||||
|
arguments: string
|
||||||
|
messageID: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export type EventSessionIdle = {
|
export type EventSessionIdle = {
|
||||||
type: "session.idle"
|
type: "session.idle"
|
||||||
properties: {
|
properties: {
|
||||||
@@ -1290,6 +1334,7 @@ export type Event =
|
|||||||
| EventFileEdited
|
| EventFileEdited
|
||||||
| EventFileWatcherUpdated
|
| EventFileWatcherUpdated
|
||||||
| EventTodoUpdated
|
| EventTodoUpdated
|
||||||
|
| EventCommandExecuted
|
||||||
| EventSessionIdle
|
| EventSessionIdle
|
||||||
| EventSessionCreated
|
| EventSessionCreated
|
||||||
| EventSessionUpdated
|
| EventSessionUpdated
|
||||||
@@ -2491,6 +2536,24 @@ export type LspStatusResponses = {
|
|||||||
|
|
||||||
export type LspStatusResponse = LspStatusResponses[keyof LspStatusResponses]
|
export type LspStatusResponse = LspStatusResponses[keyof LspStatusResponses]
|
||||||
|
|
||||||
|
export type FormatterStatusData = {
|
||||||
|
body?: never
|
||||||
|
path?: never
|
||||||
|
query?: {
|
||||||
|
directory?: string
|
||||||
|
}
|
||||||
|
url: "/formatter"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FormatterStatusResponses = {
|
||||||
|
/**
|
||||||
|
* Formatter status
|
||||||
|
*/
|
||||||
|
200: Array<FormatterStatus>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FormatterStatusResponse = FormatterStatusResponses[keyof FormatterStatusResponses]
|
||||||
|
|
||||||
export type TuiAppendPromptData = {
|
export type TuiAppendPromptData = {
|
||||||
body?: {
|
body?: {
|
||||||
text: string
|
text: string
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/slack",
|
"name": "@opencode-ai/slack",
|
||||||
"version": "1.0.2",
|
"version": "1.0.14",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "bun run src/index.ts",
|
"dev": "bun run src/index.ts",
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
opencode-test
|
|
||||||
cmd/opencode/opencode
|
|
||||||
opencode
|
|
||||||
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
version: 2
|
|
||||||
project_name: opencode
|
|
||||||
before:
|
|
||||||
hooks:
|
|
||||||
builds:
|
|
||||||
- env:
|
|
||||||
- CGO_ENABLED=0
|
|
||||||
goos:
|
|
||||||
- linux
|
|
||||||
- darwin
|
|
||||||
goarch:
|
|
||||||
- amd64
|
|
||||||
- arm64
|
|
||||||
ldflags:
|
|
||||||
- -s -w -X github.com/sst/opencode/internal/version.Version={{.Version}}
|
|
||||||
main: ./main.go
|
|
||||||
|
|
||||||
archives:
|
|
||||||
- format: tar.gz
|
|
||||||
name_template: >-
|
|
||||||
opencode-
|
|
||||||
{{- if eq .Os "darwin" }}mac-
|
|
||||||
{{- else if eq .Os "windows" }}windows-
|
|
||||||
{{- else if eq .Os "linux" }}linux-{{end}}
|
|
||||||
{{- if eq .Arch "amd64" }}x86_64
|
|
||||||
{{- else if eq .Arch "#86" }}i386
|
|
||||||
{{- else }}{{ .Arch }}{{ end }}
|
|
||||||
{{- if .Arm }}v{{ .Arm }}{{ end }}
|
|
||||||
format_overrides:
|
|
||||||
- goos: windows
|
|
||||||
format: zip
|
|
||||||
checksum:
|
|
||||||
name_template: "checksums.txt"
|
|
||||||
snapshot:
|
|
||||||
name_template: "0.0.0-{{ .Timestamp }}"
|
|
||||||
aurs:
|
|
||||||
- name: opencode
|
|
||||||
homepage: "https://github.com/sst/opencode"
|
|
||||||
description: "terminal based agent that can build anything"
|
|
||||||
maintainers:
|
|
||||||
- "dax"
|
|
||||||
- "adam"
|
|
||||||
license: "MIT"
|
|
||||||
private_key: "{{ .Env.AUR_KEY }}"
|
|
||||||
git_url: "ssh://aur@aur.archlinux.org/opencode-bin.git"
|
|
||||||
provides:
|
|
||||||
- opencode
|
|
||||||
conflicts:
|
|
||||||
- opencode
|
|
||||||
package: |-
|
|
||||||
install -Dm755 ./opencode "${pkgdir}/usr/bin/opencode"
|
|
||||||
brews:
|
|
||||||
- repository:
|
|
||||||
owner: sst
|
|
||||||
name: homebrew-tap
|
|
||||||
nfpms:
|
|
||||||
- maintainer: kujtimiihoxha
|
|
||||||
description: terminal based agent that can build anything
|
|
||||||
formats:
|
|
||||||
- deb
|
|
||||||
- rpm
|
|
||||||
file_name_template: >-
|
|
||||||
{{ .ProjectName }}-
|
|
||||||
{{- if eq .Os "darwin" }}mac
|
|
||||||
{{- else }}{{ .Os }}{{ end }}-{{ .Arch }}
|
|
||||||
|
|
||||||
changelog:
|
|
||||||
sort: asc
|
|
||||||
filters:
|
|
||||||
exclude:
|
|
||||||
- "^docs:"
|
|
||||||
- "^doc:"
|
|
||||||
- "^test:"
|
|
||||||
- "^ci:"
|
|
||||||
- "^ignore:"
|
|
||||||
- "^example:"
|
|
||||||
- "^wip:"
|
|
||||||
@@ -1,175 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
"log/slog"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
tea "github.com/charmbracelet/bubbletea/v2"
|
|
||||||
flag "github.com/spf13/pflag"
|
|
||||||
"github.com/sst/opencode-sdk-go"
|
|
||||||
"github.com/sst/opencode-sdk-go/option"
|
|
||||||
"github.com/sst/opencode-sdk-go/packages/ssestream"
|
|
||||||
"github.com/sst/opencode/internal/api"
|
|
||||||
"github.com/sst/opencode/internal/app"
|
|
||||||
"github.com/sst/opencode/internal/clipboard"
|
|
||||||
"github.com/sst/opencode/internal/decoders"
|
|
||||||
"github.com/sst/opencode/internal/tui"
|
|
||||||
"github.com/sst/opencode/internal/util"
|
|
||||||
"golang.org/x/sync/errgroup"
|
|
||||||
)
|
|
||||||
|
|
||||||
var Version = "dev"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
version := Version
|
|
||||||
if version != "dev" && !strings.HasPrefix(Version, "v") {
|
|
||||||
version = "v" + Version
|
|
||||||
}
|
|
||||||
|
|
||||||
var model *string = flag.String("model", "", "model to begin with")
|
|
||||||
var prompt *string = flag.String("prompt", "", "prompt to begin with")
|
|
||||||
var agent *string = flag.String("agent", "", "agent to begin with")
|
|
||||||
var sessionID *string = flag.String("session", "", "session ID")
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
url := os.Getenv("OPENCODE_SERVER")
|
|
||||||
|
|
||||||
stat, err := os.Stdin.Stat()
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("Failed to stat stdin", "error", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if there's data piped to stdin
|
|
||||||
if (stat.Mode() & os.ModeCharDevice) == 0 {
|
|
||||||
stdin, err := io.ReadAll(os.Stdin)
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("Failed to read stdin", "error", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
stdinContent := strings.TrimSpace(string(stdin))
|
|
||||||
if stdinContent != "" {
|
|
||||||
if prompt == nil || *prompt == "" {
|
|
||||||
prompt = &stdinContent
|
|
||||||
} else {
|
|
||||||
combined := *prompt + "\n" + stdinContent
|
|
||||||
prompt = &combined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register custom SSE decoder to handle large events (>32MB)
|
|
||||||
// This is a workaround for the bufio.Scanner token size limit in the auto-generated SDK
|
|
||||||
// See: packages/tui/internal/decoders/decoder.go
|
|
||||||
ssestream.RegisterDecoder("text/event-stream", decoders.NewUnboundedDecoder)
|
|
||||||
|
|
||||||
httpClient := opencode.NewClient(
|
|
||||||
option.WithBaseURL(url),
|
|
||||||
)
|
|
||||||
|
|
||||||
var agents []opencode.Agent
|
|
||||||
var path *opencode.Path
|
|
||||||
var project *opencode.Project
|
|
||||||
|
|
||||||
batch := errgroup.Group{}
|
|
||||||
|
|
||||||
batch.Go(func() error {
|
|
||||||
result, err := httpClient.Project.Current(context.Background(), opencode.ProjectCurrentParams{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
project = result
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
batch.Go(func() error {
|
|
||||||
result, err := httpClient.Agent.List(context.Background(), opencode.AgentListParams{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
agents = *result
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
batch.Go(func() error {
|
|
||||||
result, err := httpClient.Path.Get(context.Background(), opencode.PathGetParams{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
path = result
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
err = batch.Wait()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
apiHandler := util.NewAPILogHandler(ctx, httpClient, "tui", slog.LevelDebug)
|
|
||||||
logger := slog.New(apiHandler)
|
|
||||||
slog.SetDefault(logger)
|
|
||||||
|
|
||||||
slog.Debug("TUI launched")
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
err = clipboard.Init()
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("Failed to initialize clipboard", "error", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Create main context for the application
|
|
||||||
app_, err := app.New(ctx, version, project, path, agents, httpClient, model, prompt, agent, sessionID)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tuiModel := tui.NewModel(app_).(*tui.Model)
|
|
||||||
program := tea.NewProgram(
|
|
||||||
tuiModel,
|
|
||||||
tea.WithAltScreen(),
|
|
||||||
tea.WithMouseCellMotion(),
|
|
||||||
)
|
|
||||||
|
|
||||||
// Set up signal handling for graceful shutdown
|
|
||||||
sigChan := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
stream := httpClient.Event.ListStreaming(ctx, opencode.EventListParams{})
|
|
||||||
for stream.Next() {
|
|
||||||
evt := stream.Current().AsUnion()
|
|
||||||
program.Send(evt)
|
|
||||||
}
|
|
||||||
if err := stream.Err(); err != nil {
|
|
||||||
slog.Error("Error streaming events", "error", err)
|
|
||||||
program.Send(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go api.Start(ctx, program, httpClient)
|
|
||||||
|
|
||||||
// Handle signals in a separate goroutine
|
|
||||||
go func() {
|
|
||||||
sig := <-sigChan
|
|
||||||
slog.Info("Received signal, shutting down gracefully", "signal", sig)
|
|
||||||
tuiModel.Cleanup()
|
|
||||||
program.Quit()
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Run the TUI
|
|
||||||
result, err := program.Run()
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("TUI error", "error", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tuiModel.Cleanup()
|
|
||||||
slog.Info("TUI exited", "result", result)
|
|
||||||
}
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
module github.com/sst/opencode
|
|
||||||
|
|
||||||
go 1.24.0
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/BurntSushi/toml v1.5.0
|
|
||||||
github.com/alecthomas/chroma/v2 v2.18.0
|
|
||||||
github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1
|
|
||||||
github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4
|
|
||||||
github.com/charmbracelet/glamour v0.10.0
|
|
||||||
github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.3
|
|
||||||
github.com/charmbracelet/x/ansi v0.9.3
|
|
||||||
github.com/fsnotify/fsnotify v1.8.0
|
|
||||||
github.com/google/uuid v1.6.0
|
|
||||||
github.com/lithammer/fuzzysearch v1.1.8
|
|
||||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6
|
|
||||||
github.com/muesli/reflow v0.3.0
|
|
||||||
github.com/muesli/termenv v0.16.0
|
|
||||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3
|
|
||||||
github.com/sst/opencode-sdk-go v0.1.0-alpha.8
|
|
||||||
golang.org/x/image v0.28.0
|
|
||||||
rsc.io/qr v0.2.0
|
|
||||||
)
|
|
||||||
|
|
||||||
replace (
|
|
||||||
github.com/charmbracelet/x/input => ./input
|
|
||||||
github.com/sst/opencode-sdk-go => ../sdk/go
|
|
||||||
)
|
|
||||||
|
|
||||||
require golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
|
|
||||||
|
|
||||||
require (
|
|
||||||
dario.cat/mergo v1.0.2 // indirect
|
|
||||||
github.com/atombender/go-jsonschema v0.20.0 // indirect
|
|
||||||
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 // indirect
|
|
||||||
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf // indirect
|
|
||||||
github.com/charmbracelet/x/input v0.3.7 // indirect
|
|
||||||
github.com/charmbracelet/x/windows v0.2.1 // indirect
|
|
||||||
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect
|
|
||||||
github.com/getkin/kin-openapi v0.127.0 // indirect
|
|
||||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
|
||||||
github.com/go-openapi/swag v0.23.0 // indirect
|
|
||||||
github.com/goccy/go-yaml v1.17.1 // indirect
|
|
||||||
github.com/invopop/yaml v0.3.1 // indirect
|
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
|
||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
|
||||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
|
||||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
|
||||||
github.com/oapi-codegen/oapi-codegen/v2 v2.4.1 // indirect
|
|
||||||
github.com/perimeterx/marshmallow v1.1.5 // indirect
|
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
|
||||||
github.com/sanity-io/litter v1.5.8 // indirect
|
|
||||||
github.com/sosodev/duration v1.3.1 // indirect
|
|
||||||
github.com/speakeasy-api/openapi-overlay v0.9.0 // indirect
|
|
||||||
github.com/spf13/cobra v1.9.1 // indirect
|
|
||||||
github.com/tidwall/gjson v1.14.4 // indirect
|
|
||||||
github.com/tidwall/match v1.1.1 // indirect
|
|
||||||
github.com/tidwall/pretty v1.2.1 // indirect
|
|
||||||
github.com/tidwall/sjson v1.2.5 // indirect
|
|
||||||
github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect
|
|
||||||
golang.org/x/mod v0.25.0 // indirect
|
|
||||||
golang.org/x/tools v0.34.0 // indirect
|
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/atotto/clipboard v0.1.4 // indirect
|
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
|
||||||
github.com/aymerick/douceur v0.2.0 // indirect
|
|
||||||
github.com/charmbracelet/colorprofile v0.3.1 // indirect
|
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.14-0.20250505150409-97991a1f17d1 // indirect
|
|
||||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
|
||||||
github.com/dlclark/regexp2 v1.11.5 // indirect
|
|
||||||
github.com/google/go-cmp v0.7.0 // indirect
|
|
||||||
github.com/gorilla/css v1.0.1 // indirect
|
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0
|
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
|
||||||
github.com/mattn/go-runewidth v0.0.16
|
|
||||||
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
|
|
||||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
|
||||||
github.com/rivo/uniseg v0.4.7
|
|
||||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
|
||||||
github.com/spf13/pflag v1.0.6
|
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
|
||||||
github.com/yuin/goldmark v1.7.8 // indirect
|
|
||||||
github.com/yuin/goldmark-emoji v1.0.5 // indirect
|
|
||||||
golang.org/x/net v0.41.0 // indirect
|
|
||||||
golang.org/x/sync v0.15.0
|
|
||||||
golang.org/x/sys v0.33.0 // indirect
|
|
||||||
golang.org/x/term v0.32.0 // indirect
|
|
||||||
golang.org/x/text v0.26.0
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
)
|
|
||||||
|
|
||||||
tool (
|
|
||||||
github.com/atombender/go-jsonschema
|
|
||||||
github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen
|
|
||||||
)
|
|
||||||
@@ -1,313 +0,0 @@
|
|||||||
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
|
||||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
|
||||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
|
||||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
|
||||||
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
|
|
||||||
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
|
|
||||||
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
|
|
||||||
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
|
||||||
github.com/alecthomas/chroma/v2 v2.18.0 h1:6h53Q4hW83SuF+jcsp7CVhLsMozzvQvO8HBbKQW+gn4=
|
|
||||||
github.com/alecthomas/chroma/v2 v2.18.0/go.mod h1:RVX6AvYm4VfYe/zsk7mjHueLDZor3aWCNE14TFlepBk=
|
|
||||||
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
|
||||||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
|
||||||
github.com/atombender/go-jsonschema v0.20.0 h1:AHg0LeI0HcjQ686ALwUNqVJjNRcSXpIR6U+wC2J0aFY=
|
|
||||||
github.com/atombender/go-jsonschema v0.20.0/go.mod h1:ZmbuR11v2+cMM0PdP6ySxtyZEGFBmhgF4xa4J6Hdls8=
|
|
||||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
|
||||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
|
||||||
github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
|
|
||||||
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
|
|
||||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
|
||||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
|
||||||
github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1 h1:swACzss0FjnyPz1enfX56GKkLiuKg5FlyVmOLIlU2kE=
|
|
||||||
github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1/go.mod h1:6HamsBKWqEC/FVHuQMHgQL+knPyvHH55HwJDHl/adMw=
|
|
||||||
github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4 h1:UgUuKKvBwgqm2ZEL+sKv/OLeavrUb4gfHgdxe6oIOno=
|
|
||||||
github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4/go.mod h1:0wWFRpsgF7vHsCukVZ5LAhZkiR4j875H6KEM2/tFQmA=
|
|
||||||
github.com/charmbracelet/colorprofile v0.3.1 h1:k8dTHMd7fgw4bnFd7jXTLZrSU/CQrKnL3m+AxCzDz40=
|
|
||||||
github.com/charmbracelet/colorprofile v0.3.1/go.mod h1:/GkGusxNs8VB/RSOh3fu0TJmQ4ICMMPApIIVn0KszZ0=
|
|
||||||
github.com/charmbracelet/glamour v0.10.0 h1:MtZvfwsYCx8jEPFJm3rIBFIMZUfUJ765oX8V6kXldcY=
|
|
||||||
github.com/charmbracelet/glamour v0.10.0/go.mod h1:f+uf+I/ChNmqo087elLnVdCiVgjSKWuXa/l6NU2ndYk=
|
|
||||||
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 h1:ZR7e0ro+SZZiIZD7msJyA+NjkCNNavuiPBLgerbOziE=
|
|
||||||
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA=
|
|
||||||
github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.3 h1:W6DpZX6zSkZr0iFq6JVh1vItLoxfYtNlaxOJtWp8Kis=
|
|
||||||
github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.3/go.mod h1:65HTtKURcv/ict9ZQhr6zT84JqIjMcJbyrZYHHKNfKA=
|
|
||||||
github.com/charmbracelet/x/ansi v0.9.3 h1:BXt5DHS/MKF+LjuK4huWrC6NCvHtexww7dMayh6GXd0=
|
|
||||||
github.com/charmbracelet/x/ansi v0.9.3/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
|
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.14-0.20250505150409-97991a1f17d1 h1:MTSs/nsZNfZPbYk/r9hluK2BtwoqvEYruAujNVwgDv0=
|
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.14-0.20250505150409-97991a1f17d1/go.mod h1:xBlh2Yi3DL3zy/2n15kITpg0YZardf/aa/hgUaIM6Rk=
|
|
||||||
github.com/charmbracelet/x/exp/golden v0.0.0-20250207160936-21c02780d27a h1:FsHEJ52OC4VuTzU8t+n5frMjLvpYWEznSr/u8tnkCYw=
|
|
||||||
github.com/charmbracelet/x/exp/golden v0.0.0-20250207160936-21c02780d27a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
|
|
||||||
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf h1:rLG0Yb6MQSDKdB52aGX55JT1oi0P0Kuaj7wi1bLUpnI=
|
|
||||||
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf/go.mod h1:B3UgsnsBZS/eX42BlaNiJkD1pPOUa+oF1IYC6Yd2CEU=
|
|
||||||
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
|
||||||
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
|
||||||
github.com/charmbracelet/x/windows v0.2.1 h1:3x7vnbpQrjpuq/4L+I4gNsG5htYoCiA5oe9hLjAij5I=
|
|
||||||
github.com/charmbracelet/x/windows v0.2.1/go.mod h1:ptZp16h40gDYqs5TSawSVW+yiLB13j4kSMA0lSCHL0M=
|
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
|
||||||
github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
|
|
||||||
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
|
||||||
github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58=
|
|
||||||
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 h1:PRxIJD8XjimM5aTknUK9w6DHLDox2r2M3DI4i2pnd3w=
|
|
||||||
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936/go.mod h1:ttYvX5qlB+mlV1okblJqcSMtR4c52UKxDiX9GRBS8+Q=
|
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
|
||||||
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
|
||||||
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
|
||||||
github.com/getkin/kin-openapi v0.127.0 h1:Mghqi3Dhryf3F8vR370nN67pAERW+3a95vomb3MAREY=
|
|
||||||
github.com/getkin/kin-openapi v0.127.0/go.mod h1:OZrfXzUfGrNbsKj+xmFBx6E5c6yH3At/tAKSc2UszXM=
|
|
||||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
|
||||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
|
||||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
|
||||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
|
||||||
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
|
||||||
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
|
||||||
github.com/goccy/go-yaml v1.17.1 h1:LI34wktB2xEE3ONG/2Ar54+/HJVBriAGJ55PHls4YuY=
|
|
||||||
github.com/goccy/go-yaml v1.17.1/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
|
||||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
|
||||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
|
||||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
|
||||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
|
||||||
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
|
|
||||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
|
||||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
|
||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
|
||||||
github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso=
|
|
||||||
github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA=
|
|
||||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
|
||||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
|
||||||
github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
|
|
||||||
github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=
|
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
|
||||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
|
||||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
|
||||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
|
||||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
|
||||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
|
||||||
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
|
|
||||||
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
|
|
||||||
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
|
|
||||||
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
|
|
||||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
|
||||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
|
||||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
|
||||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
|
||||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
|
||||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
|
||||||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
|
||||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
|
||||||
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
|
||||||
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
|
||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
|
||||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
|
||||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
|
||||||
github.com/oapi-codegen/oapi-codegen/v2 v2.4.1 h1:ykgG34472DWey7TSjd8vIfNykXgjOgYJZoQbKfEeY/Q=
|
|
||||||
github.com/oapi-codegen/oapi-codegen/v2 v2.4.1/go.mod h1:N5+lY1tiTDV3V1BeHtOxeWXHoPVeApvsvjJqegfoaz8=
|
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
|
||||||
github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
|
||||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
|
||||||
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
|
|
||||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
|
||||||
github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
|
|
||||||
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
|
||||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
|
||||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
|
||||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
|
||||||
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
|
|
||||||
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
|
|
||||||
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
|
|
||||||
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
|
|
||||||
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
|
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
|
||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
|
||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
|
||||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
|
||||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
|
||||||
github.com/sanity-io/litter v1.5.8 h1:uM/2lKrWdGbRXDrIq08Lh9XtVYoeGtcQxk9rtQ7+rYg=
|
|
||||||
github.com/sanity-io/litter v1.5.8/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=
|
|
||||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
|
||||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
|
||||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
|
||||||
github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4=
|
|
||||||
github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg=
|
|
||||||
github.com/speakeasy-api/openapi-overlay v0.9.0 h1:Wrz6NO02cNlLzx1fB093lBlYxSI54VRhy1aSutx0PQg=
|
|
||||||
github.com/speakeasy-api/openapi-overlay v0.9.0/go.mod h1:f5FloQrHA7MsxYg9djzMD5h6dxrHjVVByWKh7an8TRc=
|
|
||||||
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
|
||||||
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
|
||||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
|
||||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
|
||||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
|
||||||
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
|
|
||||||
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
|
||||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
|
||||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
|
||||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
|
||||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
|
||||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
|
||||||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
|
||||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
|
||||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
|
||||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
|
||||||
github.com/vmware-labs/yaml-jsonpath v0.3.2 h1:/5QKeCBGdsInyDCyVNLbXyilb61MXGi9NP674f9Hobk=
|
|
||||||
github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN1BWz5V+51Ewf8k+rQ=
|
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
|
||||||
github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
|
||||||
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
|
|
||||||
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
|
||||||
github.com/yuin/goldmark-emoji v1.0.5 h1:EMVWyCGPlXJfUXBXpuMu+ii3TIaxbVBnEX9uaDC4cIk=
|
|
||||||
github.com/yuin/goldmark-emoji v1.0.5/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
|
||||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
|
|
||||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
|
|
||||||
golang.org/x/image v0.28.0 h1:gdem5JW1OLS4FbkWgLO+7ZeFzYtL3xClb97GaUzYMFE=
|
|
||||||
golang.org/x/image v0.28.0/go.mod h1:GUJYXtnGKEUgggyzh+Vxt+AviiCcyiwpsl8iQ8MvwGY=
|
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
|
||||||
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
|
||||||
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
|
||||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
|
||||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
|
||||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
|
||||||
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
|
||||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
|
||||||
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
|
|
||||||
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
|
||||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
|
||||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
|
||||||
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
|
||||||
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
|
||||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
|
||||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
|
||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY=
|
|
||||||
rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs=
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
//go:build !windows
|
|
||||||
// +build !windows
|
|
||||||
|
|
||||||
package input
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/muesli/cancelreader"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newCancelreader(r io.Reader, _ int) (cancelreader.CancelReader, error) {
|
|
||||||
return cancelreader.NewReader(r) //nolint:wrapcheck
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user