@cortexkit/aft-opencode 0.9.1 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/dist/bridge.d.ts.map +1 -1
  2. package/dist/bridge.js +18 -4
  3. package/dist/bridge.js.map +1 -1
  4. package/dist/config.d.ts +1 -0
  5. package/dist/config.d.ts.map +1 -1
  6. package/dist/config.js +2 -0
  7. package/dist/config.js.map +1 -1
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +182 -1
  10. package/dist/index.js.map +1 -1
  11. package/dist/notifications.d.ts +48 -0
  12. package/dist/notifications.d.ts.map +1 -0
  13. package/dist/notifications.js +307 -0
  14. package/dist/notifications.js.map +1 -0
  15. package/dist/onnx-runtime.d.ts +35 -0
  16. package/dist/onnx-runtime.d.ts.map +1 -0
  17. package/dist/onnx-runtime.js +203 -0
  18. package/dist/onnx-runtime.js.map +1 -0
  19. package/dist/pool.d.ts +2 -0
  20. package/dist/pool.d.ts.map +1 -1
  21. package/dist/pool.js +25 -6
  22. package/dist/pool.js.map +1 -1
  23. package/dist/resolver.d.ts.map +1 -1
  24. package/dist/resolver.js +15 -4
  25. package/dist/resolver.js.map +1 -1
  26. package/dist/shared/opencode-config-dir.d.ts +16 -0
  27. package/dist/shared/opencode-config-dir.d.ts.map +1 -0
  28. package/dist/shared/opencode-config-dir.js +26 -0
  29. package/dist/shared/opencode-config-dir.js.map +1 -0
  30. package/dist/shared/rpc-client.d.ts +16 -0
  31. package/dist/shared/rpc-client.d.ts.map +1 -0
  32. package/dist/shared/rpc-client.js +108 -0
  33. package/dist/shared/rpc-client.js.map +1 -0
  34. package/dist/shared/rpc-server.d.ts +17 -0
  35. package/dist/shared/rpc-server.d.ts.map +1 -0
  36. package/dist/shared/rpc-server.js +120 -0
  37. package/dist/shared/rpc-server.js.map +1 -0
  38. package/dist/shared/rpc-utils.d.ts +11 -0
  39. package/dist/shared/rpc-utils.d.ts.map +1 -0
  40. package/dist/shared/rpc-utils.js +20 -0
  41. package/dist/shared/rpc-utils.js.map +1 -0
  42. package/dist/shared/runtime.d.ts +10 -0
  43. package/dist/shared/runtime.d.ts.map +1 -0
  44. package/dist/shared/runtime.js +14 -0
  45. package/dist/shared/runtime.js.map +1 -0
  46. package/dist/shared/status.d.ts +37 -0
  47. package/dist/shared/status.d.ts.map +1 -0
  48. package/dist/shared/status.js +135 -0
  49. package/dist/shared/status.js.map +1 -0
  50. package/dist/shared/tui-config.d.ts +2 -0
  51. package/dist/shared/tui-config.d.ts.map +1 -0
  52. package/dist/shared/tui-config.js +134 -0
  53. package/dist/shared/tui-config.js.map +1 -0
  54. package/dist/tools/search.d.ts.map +1 -1
  55. package/dist/tools/search.js +23 -3
  56. package/dist/tools/search.js.map +1 -1
  57. package/dist/tools/semantic.d.ts +4 -0
  58. package/dist/tools/semantic.d.ts.map +1 -0
  59. package/dist/tools/semantic.js +35 -0
  60. package/dist/tools/semantic.js.map +1 -0
  61. package/package.json +26 -6
  62. package/src/shared/opencode-config-dir.ts +46 -0
  63. package/src/shared/rpc-client.ts +123 -0
  64. package/src/shared/rpc-server.ts +135 -0
  65. package/src/shared/rpc-utils.ts +21 -0
  66. package/src/shared/runtime.ts +26 -0
  67. package/src/shared/status.ts +206 -0
  68. package/src/shared/tui-config.ts +155 -0
  69. package/src/tui/index.tsx +209 -0
  70. package/src/tui/types/opencode-plugin-tui.d.ts +232 -0
@@ -0,0 +1,209 @@
1
+ /** @jsxImportSource @opentui/solid */
2
+ // @ts-nocheck
3
+
4
+ import type { TuiPlugin, TuiPluginApi } from "@opencode-ai/plugin/tui";
5
+ import { AftRpcClient } from "../shared/rpc-client";
6
+ import { coerceAftStatus, formatStatusDialogMessage } from "../shared/status";
7
+
8
+ const STATUS_COMMAND = "aft-status";
9
+
10
+ // RPC clients keyed by directory — one per project
11
+ const rpcClients = new Map<string, AftRpcClient>();
12
+
13
+ function getRpcClient(directory: string): AftRpcClient {
14
+ let client = rpcClients.get(directory);
15
+ if (client) return client;
16
+
17
+ const home = process.env.HOME || process.env.USERPROFILE || "";
18
+ const dataHome = process.env.XDG_DATA_HOME || `${home}/.local/share`;
19
+ const storageDir = `${dataHome}/opencode/storage/plugin/aft`;
20
+
21
+ client = new AftRpcClient(storageDir, directory);
22
+ rpcClients.set(directory, client);
23
+ return client;
24
+ }
25
+
26
+ function getSessionId(api: TuiPluginApi): string | null {
27
+ try {
28
+ const route = api.route.current;
29
+ if (route?.name === "session" && route.params?.sessionID) {
30
+ return route.params.sessionID;
31
+ }
32
+ } catch {
33
+ // ignore
34
+ }
35
+ return null;
36
+ }
37
+
38
+ async function showStatusDialog(api: TuiPluginApi): Promise<void> {
39
+ const sessionID = getSessionId(api);
40
+ if (!sessionID) {
41
+ api.ui.toast({ message: "No active session", variant: "warning", duration: 5000 });
42
+ return;
43
+ }
44
+
45
+ const directory = api.state.path.directory ?? "";
46
+ if (!directory) {
47
+ api.ui.toast({ message: "No project directory", variant: "warning", duration: 5000 });
48
+ return;
49
+ }
50
+
51
+ const client = getRpcClient(directory);
52
+
53
+ // Fetch status immediately and show the dialog
54
+ let currentMessage = "Connecting to AFT...";
55
+ try {
56
+ const response = await client.call("status", { sessionID });
57
+ if ((response as Record<string, unknown>).success !== false) {
58
+ const status = coerceAftStatus(response as Record<string, unknown>);
59
+ currentMessage = formatStatusDialogMessage(status);
60
+ }
61
+ } catch {
62
+ currentMessage = "AFT is starting up. Status will refresh automatically...";
63
+ }
64
+
65
+ // Track whether dialog is still open for polling cleanup
66
+ let dialogOpen = true;
67
+ let pollTimer: ReturnType<typeof setInterval> | null = null;
68
+
69
+ // Show dialog with initial data
70
+ api.ui.dialog.setSize("large");
71
+ api.ui.dialog.replace(
72
+ () => {
73
+ // Start polling after dialog renders
74
+ if (!pollTimer) {
75
+ pollTimer = setInterval(async () => {
76
+ if (!dialogOpen) {
77
+ if (pollTimer) clearInterval(pollTimer);
78
+ return;
79
+ }
80
+ try {
81
+ const response = await client.call("status", { sessionID });
82
+ if ((response as Record<string, unknown>).success !== false) {
83
+ const status = coerceAftStatus(response as Record<string, unknown>);
84
+ const newMessage = formatStatusDialogMessage(status);
85
+ if (newMessage !== currentMessage) {
86
+ currentMessage = newMessage;
87
+ // Re-render dialog with updated status
88
+ api.ui.dialog.replace(
89
+ () => (
90
+ <api.ui.DialogAlert
91
+ title="AFT Status"
92
+ message={currentMessage}
93
+ onConfirm={() => {
94
+ dialogOpen = false;
95
+ if (pollTimer) clearInterval(pollTimer);
96
+ api.ui.dialog.setSize("medium");
97
+ }}
98
+ />
99
+ ),
100
+ () => {
101
+ dialogOpen = false;
102
+ if (pollTimer) clearInterval(pollTimer);
103
+ api.ui.dialog.setSize("medium");
104
+ },
105
+ );
106
+ }
107
+ }
108
+ } catch {
109
+ // Polling failure is non-fatal — just skip this tick
110
+ }
111
+ }, 1500);
112
+ }
113
+
114
+ return (
115
+ <api.ui.DialogAlert
116
+ title="AFT Status"
117
+ message={currentMessage}
118
+ onConfirm={() => {
119
+ dialogOpen = false;
120
+ if (pollTimer) clearInterval(pollTimer);
121
+ api.ui.dialog.setSize("medium");
122
+ }}
123
+ />
124
+ );
125
+ },
126
+ () => {
127
+ dialogOpen = false;
128
+ if (pollTimer) clearInterval(pollTimer);
129
+ api.ui.dialog.setSize("medium");
130
+ },
131
+ );
132
+ }
133
+
134
+ async function showStartupNotifications(api: TuiPluginApi): Promise<void> {
135
+ const directory = api.state.path.directory ?? "";
136
+ if (!directory) return;
137
+
138
+ const client = getRpcClient(directory);
139
+
140
+ // Check for feature announcements
141
+ try {
142
+ const announcement = (await client.call("get-announcement", {})) as {
143
+ show?: boolean;
144
+ version?: string;
145
+ features?: string[];
146
+ };
147
+
148
+ if (announcement.show && announcement.version && announcement.features?.length) {
149
+ const featureText = announcement.features.map((f: string) => ` • ${f}`).join("\n");
150
+
151
+ api.ui.dialog.replace(
152
+ () => (
153
+ <api.ui.DialogAlert
154
+ title={`AFT v${announcement.version}`}
155
+ message={`What's new:\n\n${featureText}`}
156
+ onConfirm={() => {
157
+ // Mark as announced so it doesn't show again
158
+ void client.call("mark-announced", {});
159
+ }}
160
+ />
161
+ ),
162
+ () => {
163
+ void client.call("mark-announced", {});
164
+ },
165
+ );
166
+ return; // Show one dialog at a time
167
+ }
168
+ } catch {
169
+ // RPC server not ready yet — skip announcements
170
+ }
171
+
172
+ // Check for warnings
173
+ try {
174
+ const result = (await client.call("get-warnings", {})) as { warnings?: string[] };
175
+ if (result.warnings?.length) {
176
+ const warningText = result.warnings.join("\n\n");
177
+ api.ui.dialog.replace(
178
+ () => <api.ui.DialogAlert title="AFT Warning" message={warningText} onConfirm={() => {}} />,
179
+ () => {},
180
+ );
181
+ }
182
+ } catch {
183
+ // RPC server not ready — skip warnings
184
+ }
185
+ }
186
+
187
+ const tui: TuiPlugin = async (api) => {
188
+ api.command.register(() => [
189
+ {
190
+ title: "AFT: Status",
191
+ value: "aft.status",
192
+ category: "AFT",
193
+ slash: { name: STATUS_COMMAND },
194
+ onSelect() {
195
+ void showStatusDialog(api);
196
+ },
197
+ },
198
+ ]);
199
+
200
+ // Show startup notifications — RPC server is already running by the time TUI loads
201
+ void showStartupNotifications(api);
202
+ };
203
+
204
+ const id = "aft-opencode";
205
+
206
+ export default {
207
+ id,
208
+ tui,
209
+ };
@@ -0,0 +1,232 @@
1
+ // Type declarations for @opencode-ai/plugin/tui
2
+ // These types are not yet exported by the installed @opencode-ai/plugin package
3
+
4
+ declare module "@opencode-ai/plugin/tui" {
5
+ import type {
6
+ createOpencodeClient as createOpencodeClientV2,
7
+ Event as TuiEvent,
8
+ Message,
9
+ Part,
10
+ Provider,
11
+ Config as SdkConfig,
12
+ } from "@opencode-ai/sdk/v2";
13
+
14
+ import type { CliRenderer, RGBA } from "@opentui/core";
15
+ import type { JSX, SolidPlugin } from "@opentui/solid";
16
+
17
+ type PluginOptions = Record<string, unknown>;
18
+
19
+ export type { CliRenderer };
20
+
21
+ export type TuiThemeCurrent = {
22
+ readonly primary: RGBA;
23
+ readonly secondary: RGBA;
24
+ readonly accent: RGBA;
25
+ readonly error: RGBA;
26
+ readonly warning: RGBA;
27
+ readonly success: RGBA;
28
+ readonly info: RGBA;
29
+ readonly text: RGBA;
30
+ readonly textMuted: RGBA;
31
+ readonly background: RGBA;
32
+ readonly backgroundPanel: RGBA;
33
+ readonly backgroundElement: RGBA;
34
+ readonly backgroundMenu: RGBA;
35
+ readonly border: RGBA;
36
+ readonly borderActive: RGBA;
37
+ readonly borderSubtle: RGBA;
38
+ [key: string]: unknown;
39
+ };
40
+
41
+ export type TuiTheme = {
42
+ readonly current: TuiThemeCurrent;
43
+ has: (name: string) => boolean;
44
+ set: (name: string) => boolean;
45
+ mode: () => "dark" | "light";
46
+ readonly ready: boolean;
47
+ };
48
+
49
+ export type TuiSlotMap = {
50
+ app: Record<string, never>;
51
+ home_logo: Record<string, never>;
52
+ home_bottom: Record<string, never>;
53
+ sidebar_title: {
54
+ session_id: string;
55
+ title: string;
56
+ share_url?: string;
57
+ };
58
+ sidebar_content: {
59
+ session_id: string;
60
+ };
61
+ sidebar_footer: {
62
+ session_id: string;
63
+ };
64
+ };
65
+
66
+ export type TuiSlotContext = {
67
+ theme: TuiTheme;
68
+ };
69
+
70
+ export type TuiSlotPlugin = Omit<SolidPlugin<TuiSlotMap, TuiSlotContext>, "id"> & {
71
+ id?: never;
72
+ };
73
+
74
+ export type TuiToast = {
75
+ variant?: "info" | "success" | "warning" | "error";
76
+ title?: string;
77
+ message: string;
78
+ duration?: number;
79
+ };
80
+
81
+ export type TuiDialogStack = {
82
+ replace: (render: () => JSX.Element, onClose?: () => void) => void;
83
+ clear: () => void;
84
+ setSize: (size: "medium" | "large" | "xlarge") => void;
85
+ readonly size: "medium" | "large" | "xlarge";
86
+ readonly depth: number;
87
+ readonly open: boolean;
88
+ };
89
+
90
+ export type TuiDialogAlertProps = {
91
+ title: string;
92
+ message: string;
93
+ onConfirm?: () => void;
94
+ };
95
+
96
+ export type TuiDialogConfirmProps = {
97
+ title: string;
98
+ message: string;
99
+ onConfirm?: () => void;
100
+ onCancel?: () => void;
101
+ };
102
+
103
+ export type TuiDialogPromptProps = {
104
+ title: string;
105
+ description?: () => JSX.Element;
106
+ placeholder?: string;
107
+ value?: string;
108
+ busy?: boolean;
109
+ busyText?: string;
110
+ onConfirm?: (value: string) => void;
111
+ onCancel?: () => void;
112
+ };
113
+
114
+ export type TuiDialogSelectOption<Value = unknown> = {
115
+ title: string;
116
+ value: Value;
117
+ description?: string;
118
+ footer?: JSX.Element | string;
119
+ category?: string;
120
+ disabled?: boolean;
121
+ onSelect?: () => void;
122
+ };
123
+
124
+ export type TuiDialogSelectProps<Value = unknown> = {
125
+ title: string;
126
+ placeholder?: string;
127
+ options: TuiDialogSelectOption<Value>[];
128
+ flat?: boolean;
129
+ onMove?: (option: TuiDialogSelectOption<Value>) => void;
130
+ onFilter?: (query: string) => void;
131
+ onSelect?: (option: TuiDialogSelectOption<Value>) => void;
132
+ skipFilter?: boolean;
133
+ current?: Value;
134
+ };
135
+
136
+ export type TuiState = {
137
+ readonly ready: boolean;
138
+ readonly config: SdkConfig;
139
+ readonly provider: ReadonlyArray<Provider>;
140
+ readonly path: {
141
+ state: string;
142
+ config: string;
143
+ worktree: string;
144
+ directory: string;
145
+ };
146
+ session: {
147
+ count: () => number;
148
+ messages: (sessionID: string) => ReadonlyArray<Message>;
149
+ };
150
+ part: (messageID: string) => ReadonlyArray<Part>;
151
+ };
152
+
153
+ export type TuiEventBus = {
154
+ on: <Type extends TuiEvent["type"]>(
155
+ type: Type,
156
+ handler: (event: Extract<TuiEvent, { type: Type }>) => void,
157
+ ) => () => void;
158
+ };
159
+
160
+ export type TuiLifecycle = {
161
+ readonly signal: AbortSignal;
162
+ onDispose: (fn: () => void | Promise<void>) => () => void;
163
+ };
164
+
165
+ export type TuiPluginApi = {
166
+ app: { readonly version: string };
167
+ command: {
168
+ register: (
169
+ cb: () => Array<{
170
+ title: string;
171
+ value: string;
172
+ description?: string;
173
+ category?: string;
174
+ keybind?: string;
175
+ suggested?: boolean;
176
+ hidden?: boolean;
177
+ enabled?: boolean;
178
+ slash?: {
179
+ name: string;
180
+ aliases?: string[];
181
+ };
182
+ onSelect?: () => void;
183
+ }>,
184
+ ) => () => void;
185
+ trigger: (value: string) => void;
186
+ };
187
+ route: {
188
+ register: (
189
+ routes: Array<{
190
+ name: string;
191
+ render: (input: { params?: Record<string, unknown> }) => JSX.Element;
192
+ }>,
193
+ ) => () => void;
194
+ navigate: (name: string, params?: Record<string, unknown>) => void;
195
+ readonly current:
196
+ | { name: "home" }
197
+ | { name: "session"; params: { sessionID: string; initialPrompt?: unknown } }
198
+ | { name: string; params?: Record<string, unknown> };
199
+ };
200
+ ui: {
201
+ DialogAlert: (props: TuiDialogAlertProps) => JSX.Element;
202
+ DialogConfirm: (props: TuiDialogConfirmProps) => JSX.Element;
203
+ DialogPrompt: (props: TuiDialogPromptProps) => JSX.Element;
204
+ DialogSelect: <Value = unknown>(props: TuiDialogSelectProps<Value>) => JSX.Element;
205
+ toast: (input: TuiToast) => void;
206
+ dialog: TuiDialogStack;
207
+ };
208
+ state: TuiState;
209
+ theme: TuiTheme;
210
+ client: ReturnType<typeof createOpencodeClientV2>;
211
+ event: TuiEventBus;
212
+ renderer: CliRenderer;
213
+ slots: {
214
+ register: (plugin: TuiSlotPlugin) => string;
215
+ };
216
+ lifecycle: TuiLifecycle;
217
+ };
218
+
219
+ export type TuiPluginMeta = {
220
+ state: "first" | "updated" | "same";
221
+ id: string;
222
+ source: "file" | "npm" | "internal";
223
+ spec: string;
224
+ target: string;
225
+ };
226
+
227
+ export type TuiPlugin = (
228
+ api: TuiPluginApi,
229
+ options: PluginOptions | undefined,
230
+ meta: TuiPluginMeta,
231
+ ) => Promise<void>;
232
+ }