@akonwi/kit 0.1.0 → 0.2.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.
- package/README.md +1 -1
- package/dist/kit +0 -0
- package/dist/plugin.d.ts +107 -78
- package/dist/toasts.d.ts +1 -1
- package/docs/features/plugins.md +60 -2
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# kit
|
|
2
2
|
|
|
3
|
-
Kit is a TUI coding agent heavily inspired by [Pi](https://pi.dev) and built on top of [`pi-agent-core`](https://github.com/
|
|
3
|
+
Kit is a TUI coding agent heavily inspired by [Pi](https://pi.dev) and built on top of [`pi-agent-core`](https://github.com/earendil-works/pi-mono/tree/main/packages/agent) with [OpenTUI](https://opentui.com/).
|
|
4
4
|
|
|
5
5
|
## Requirements
|
|
6
6
|
|
package/dist/kit
CHANGED
|
Binary file
|
package/dist/plugin.d.ts
CHANGED
|
@@ -1,45 +1,76 @@
|
|
|
1
|
-
import type { AgentMessage } from "@
|
|
2
|
-
import type { Api, Model, Static, TSchema } from "@
|
|
3
|
-
import type { JSX } from "solid-js";
|
|
1
|
+
import type { AgentMessage } from "@earendil-works/pi-agent-core";
|
|
2
|
+
import type { Api, Model, Static, TSchema } from "@earendil-works/pi-ai";
|
|
4
3
|
import type { ToastInput } from "./toasts";
|
|
5
4
|
|
|
6
|
-
export type
|
|
7
|
-
export type PluginDispose = () => void;
|
|
5
|
+
export type Disposer = () => void;
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
interface UI {
|
|
8
|
+
toast: (toast: ToastInput) => void;
|
|
9
|
+
select(input: {
|
|
10
|
+
title: string;
|
|
11
|
+
message?: string;
|
|
12
|
+
options: string[];
|
|
13
|
+
filterable?: boolean;
|
|
14
|
+
placeholder?: string;
|
|
15
|
+
}): Promise<string | undefined>;
|
|
16
|
+
select<T>(input: {
|
|
17
|
+
title: string;
|
|
18
|
+
message?: string;
|
|
19
|
+
options: Array<{ label: string; value: T; description?: string }>;
|
|
20
|
+
filterable?: boolean;
|
|
21
|
+
placeholder?: string;
|
|
22
|
+
}): Promise<T | undefined>;
|
|
23
|
+
input(input: {
|
|
24
|
+
title: string;
|
|
25
|
+
message?: string;
|
|
26
|
+
placeholder?: string;
|
|
27
|
+
initialValue?: string;
|
|
28
|
+
}): Promise<string | undefined>;
|
|
29
|
+
confirm(input: {
|
|
30
|
+
title: string;
|
|
31
|
+
message?: string;
|
|
32
|
+
confirmLabel?: string;
|
|
33
|
+
cancelLabel?: string;
|
|
34
|
+
defaultValue?: boolean;
|
|
35
|
+
}): Promise<boolean>;
|
|
36
|
+
}
|
|
12
37
|
|
|
13
|
-
export type
|
|
14
|
-
done: (result: T) => void;
|
|
15
|
-
surfaceProps: PluginOverlaySurfaceProps;
|
|
16
|
-
};
|
|
38
|
+
export type ToolExecutionMode = "sequential" | "parallel";
|
|
17
39
|
|
|
18
|
-
export type
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
) => Promise<T>;
|
|
23
|
-
getTranscriptViewport: () => { width: number; height: number } | null;
|
|
40
|
+
export type ToolCall = {
|
|
41
|
+
id: string;
|
|
42
|
+
name: string;
|
|
43
|
+
input: Record<string, unknown>;
|
|
24
44
|
};
|
|
25
45
|
|
|
26
|
-
export type
|
|
46
|
+
export type ToolCallDecision =
|
|
47
|
+
| { action: "allow" }
|
|
48
|
+
| { action: "reject-and-continue"; message?: string }
|
|
49
|
+
| undefined
|
|
50
|
+
// biome-ignore lint/suspicious/noConfusingVoidType: tool call handlers may omit a return value to allow the call.
|
|
51
|
+
| void;
|
|
27
52
|
|
|
28
|
-
export type
|
|
53
|
+
export type ToolCallHandler = (
|
|
54
|
+
toolCall: ToolCall,
|
|
55
|
+
ctx: EventContext,
|
|
56
|
+
signal?: AbortSignal,
|
|
57
|
+
) => ToolCallDecision | Promise<ToolCallDecision>;
|
|
58
|
+
|
|
59
|
+
export type ToolResultContent =
|
|
29
60
|
| { type: "text"; text: string }
|
|
30
61
|
| { type: "image"; data: string; mimeType: string };
|
|
31
62
|
|
|
32
|
-
export type
|
|
33
|
-
content:
|
|
63
|
+
export type ToolResult<TDetails = unknown> = {
|
|
64
|
+
content: ToolResultContent[];
|
|
34
65
|
details: TDetails;
|
|
35
66
|
terminate?: boolean;
|
|
36
67
|
};
|
|
37
68
|
|
|
38
|
-
export type
|
|
39
|
-
partialResult:
|
|
69
|
+
export type ToolUpdateCallback<TDetails = unknown> = (
|
|
70
|
+
partialResult: ToolResult<TDetails>,
|
|
40
71
|
) => void;
|
|
41
72
|
|
|
42
|
-
export type
|
|
73
|
+
export type ToolDefinition<
|
|
43
74
|
TParameters extends TSchema = TSchema,
|
|
44
75
|
TDetails = unknown,
|
|
45
76
|
> = {
|
|
@@ -54,21 +85,21 @@ export type PluginToolDefinition<
|
|
|
54
85
|
toolCallId: string,
|
|
55
86
|
params: Static<TParameters>,
|
|
56
87
|
signal?: AbortSignal,
|
|
57
|
-
onUpdate?:
|
|
58
|
-
) => Promise<
|
|
59
|
-
executionMode?:
|
|
88
|
+
onUpdate?: ToolUpdateCallback<TDetails>,
|
|
89
|
+
) => Promise<ToolResult<TDetails>>;
|
|
90
|
+
executionMode?: ToolExecutionMode;
|
|
60
91
|
};
|
|
61
92
|
|
|
62
|
-
|
|
93
|
+
type Logger = {
|
|
63
94
|
log: (...args: unknown[]) => void;
|
|
64
95
|
};
|
|
65
96
|
|
|
66
|
-
export type
|
|
97
|
+
export type MessagePart = {
|
|
67
98
|
type: string;
|
|
68
99
|
[key: string]: unknown;
|
|
69
100
|
};
|
|
70
101
|
|
|
71
|
-
export type
|
|
102
|
+
export type Session = {
|
|
72
103
|
id: string;
|
|
73
104
|
cwd: string;
|
|
74
105
|
name?: string;
|
|
@@ -82,11 +113,11 @@ export type PluginSession = {
|
|
|
82
113
|
[key: string]: unknown;
|
|
83
114
|
};
|
|
84
115
|
|
|
85
|
-
|
|
86
|
-
get: () =>
|
|
116
|
+
type SessionAPI = {
|
|
117
|
+
get: () => Session;
|
|
87
118
|
getMessages: () => AgentMessage[];
|
|
88
119
|
setName: (name: string) => Promise<void>;
|
|
89
|
-
submitMessage: (input: string |
|
|
120
|
+
submitMessage: (input: string | MessagePart[]) => Promise<void>;
|
|
90
121
|
submitPromptCommandMessage: (
|
|
91
122
|
command: string,
|
|
92
123
|
args: string,
|
|
@@ -94,9 +125,7 @@ export type PluginSessionAPI = {
|
|
|
94
125
|
) => Promise<void>;
|
|
95
126
|
};
|
|
96
127
|
|
|
97
|
-
export type
|
|
98
|
-
|
|
99
|
-
export type PluginSettings = {
|
|
128
|
+
export type Settings = {
|
|
100
129
|
theme?: string;
|
|
101
130
|
bells?: boolean;
|
|
102
131
|
speech?:
|
|
@@ -110,7 +139,7 @@ export type PluginSettings = {
|
|
|
110
139
|
guidedQuestions?: boolean;
|
|
111
140
|
sessionNaming?: boolean;
|
|
112
141
|
diffs?: {
|
|
113
|
-
view?:
|
|
142
|
+
view?: "unified" | "split";
|
|
114
143
|
};
|
|
115
144
|
retry?: {
|
|
116
145
|
enabled?: boolean;
|
|
@@ -121,76 +150,76 @@ export type PluginSettings = {
|
|
|
121
150
|
[key: string]: unknown;
|
|
122
151
|
};
|
|
123
152
|
|
|
124
|
-
|
|
125
|
-
get: () =>
|
|
126
|
-
update: (patch: Partial<
|
|
153
|
+
type SettingsAPI = {
|
|
154
|
+
get: () => Settings;
|
|
155
|
+
update: (patch: Partial<Settings>) => Promise<void>;
|
|
127
156
|
};
|
|
128
157
|
|
|
129
|
-
|
|
158
|
+
type ModelAPI = {
|
|
130
159
|
getCurrent: () => Model<Api> | undefined;
|
|
131
160
|
};
|
|
132
161
|
|
|
133
|
-
|
|
162
|
+
type SystemAPI = {
|
|
134
163
|
readonly cwd: string;
|
|
135
164
|
open: (url: string | URL) => Promise<void>;
|
|
136
165
|
};
|
|
137
166
|
|
|
138
|
-
export type
|
|
139
|
-
logger:
|
|
140
|
-
ui:
|
|
141
|
-
session:
|
|
142
|
-
settings:
|
|
143
|
-
model:
|
|
144
|
-
system:
|
|
167
|
+
export type EventContext = {
|
|
168
|
+
logger: Logger;
|
|
169
|
+
ui: UI;
|
|
170
|
+
session: SessionAPI;
|
|
171
|
+
settings: SettingsAPI;
|
|
172
|
+
model: ModelAPI;
|
|
173
|
+
system: SystemAPI;
|
|
145
174
|
};
|
|
146
175
|
|
|
147
|
-
export type
|
|
176
|
+
export type CommandContext = EventContext & {
|
|
148
177
|
args: string;
|
|
149
178
|
};
|
|
150
179
|
|
|
151
|
-
export type
|
|
180
|
+
export type CommandOptions = {
|
|
152
181
|
title?: string;
|
|
153
182
|
description?: string;
|
|
154
183
|
argName?: string;
|
|
155
184
|
category?: string;
|
|
156
185
|
};
|
|
157
186
|
|
|
158
|
-
export type
|
|
187
|
+
export type RuntimeEvent<Type extends string = string> = {
|
|
159
188
|
type: Type;
|
|
160
189
|
} & Record<string, unknown>;
|
|
161
190
|
|
|
162
|
-
export type
|
|
163
|
-
event:
|
|
164
|
-
ctx:
|
|
191
|
+
export type EventHandler<Type extends string = string> = (
|
|
192
|
+
event: RuntimeEvent<Type>,
|
|
193
|
+
ctx: EventContext,
|
|
165
194
|
) => void | Promise<void>;
|
|
166
195
|
|
|
167
196
|
export interface PluginAPI {
|
|
168
|
-
logger:
|
|
169
|
-
ui:
|
|
170
|
-
session:
|
|
171
|
-
settings:
|
|
172
|
-
model:
|
|
173
|
-
system:
|
|
174
|
-
on(handler:
|
|
175
|
-
on<Type extends string>(
|
|
176
|
-
type: Type,
|
|
177
|
-
handler: PluginEventHandler<Type>,
|
|
178
|
-
): PluginSubscription;
|
|
197
|
+
logger: Logger;
|
|
198
|
+
ui: UI;
|
|
199
|
+
session: SessionAPI;
|
|
200
|
+
settings: SettingsAPI;
|
|
201
|
+
model: ModelAPI;
|
|
202
|
+
system: SystemAPI;
|
|
203
|
+
on(handler: EventHandler): Disposer;
|
|
204
|
+
on<Type extends string>(type: Type, handler: EventHandler<Type>): Disposer;
|
|
179
205
|
on<Prefix extends string>(
|
|
180
206
|
options: { prefix: Prefix },
|
|
181
|
-
handler:
|
|
182
|
-
):
|
|
207
|
+
handler: EventHandler<`${Prefix}${string}`>,
|
|
208
|
+
): Disposer;
|
|
183
209
|
registerCommand: (
|
|
184
210
|
id: string,
|
|
185
|
-
options:
|
|
186
|
-
handler: (ctx:
|
|
187
|
-
) =>
|
|
211
|
+
options: CommandOptions,
|
|
212
|
+
handler: (ctx: CommandContext) => void | Promise<void>,
|
|
213
|
+
) => Disposer;
|
|
188
214
|
registerTool: <TParameters extends TSchema, TDetails>(
|
|
189
|
-
tool:
|
|
190
|
-
) =>
|
|
191
|
-
|
|
192
|
-
|
|
215
|
+
tool: ToolDefinition<TParameters, TDetails>,
|
|
216
|
+
) => Disposer;
|
|
217
|
+
onToolCall: (handler: ToolCallHandler) => Disposer;
|
|
218
|
+
addSystemPrompt: (text: string) => Disposer;
|
|
219
|
+
addDebugSection: (key: string, lines: string[]) => Disposer;
|
|
193
220
|
}
|
|
194
221
|
|
|
195
|
-
|
|
196
|
-
|
|
222
|
+
export type Plugin = (
|
|
223
|
+
kit: PluginAPI,
|
|
224
|
+
// biome-ignore lint/suspicious/noConfusingVoidType: plugin definitions may omit a return value or return a disposer.
|
|
225
|
+
) => void | Disposer;
|
package/dist/toasts.d.ts
CHANGED
package/docs/features/plugins.md
CHANGED
|
@@ -37,7 +37,7 @@ export default function MyPlugin(kit: PluginAPI) {
|
|
|
37
37
|
async (ctx) => {
|
|
38
38
|
ctx.ui.toast({
|
|
39
39
|
title: "Hello plugin",
|
|
40
|
-
|
|
40
|
+
subtitle: "Loaded from a Kit plugin.",
|
|
41
41
|
variant: "info",
|
|
42
42
|
});
|
|
43
43
|
},
|
|
@@ -45,7 +45,7 @@ export default function MyPlugin(kit: PluginAPI) {
|
|
|
45
45
|
}
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
Plugin functions may return a
|
|
48
|
+
Plugin functions may return a `Disposer` for resources not registered through Kit:
|
|
49
49
|
|
|
50
50
|
```ts
|
|
51
51
|
import type { PluginAPI } from "@akonwi/kit/plugin";
|
|
@@ -61,6 +61,64 @@ export default function WatchPlugin(kit: PluginAPI) {
|
|
|
61
61
|
|
|
62
62
|
Registrations made through `kit` are cleaned up automatically on `/reload`.
|
|
63
63
|
|
|
64
|
+
Common exported SDK types include `PluginAPI`, `Plugin`, `Disposer`, `CommandContext`, `CommandOptions`, `RuntimeEvent`, `EventContext`, `ToolDefinition`, `ToolResult`, `ToolCall`, and `ToolCallDecision`.
|
|
65
|
+
|
|
66
|
+
## UI helpers
|
|
67
|
+
|
|
68
|
+
`kit.ui.toast({ title, subtitle, variant })` remains the lightweight notification API. For small interactive flows, Kit also provides app-owned UI primitives:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
const picked = await kit.ui.select({
|
|
72
|
+
title: "Choose target",
|
|
73
|
+
options: [
|
|
74
|
+
{ label: "Current file", value: "file", description: "Use the active context" },
|
|
75
|
+
{ label: "Whole project", value: "project" },
|
|
76
|
+
],
|
|
77
|
+
filterable: true,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const name = await kit.ui.input({
|
|
81
|
+
title: "Name this run",
|
|
82
|
+
placeholder: "experiment name",
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const ok = await kit.ui.confirm({
|
|
86
|
+
title: "Continue?",
|
|
87
|
+
message: "This will submit a follow-up message.",
|
|
88
|
+
confirmLabel: "Continue",
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
These helpers use Kit-owned dialogs and return `undefined` when selection/input is cancelled. `confirm` returns `false` for cancel/escape. The public plugin UI API is intentionally limited to `toast`, `select`, `input`, and `confirm` so Kit can keep ownership of rendering, focus, theme, and compatibility.
|
|
93
|
+
|
|
94
|
+
## Tool approval hooks
|
|
95
|
+
|
|
96
|
+
Plugins can register a callback that runs before a tool executes. Return `{ action: "allow" }` or no value to run the tool; return `{ action: "reject-and-continue", message }` to block it and let the agent continue.
|
|
97
|
+
|
|
98
|
+
If multiple plugins register tool-call handlers, Kit evaluates them in registration order. `allow` does not short-circuit; the first rejection blocks the call.
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
kit.onToolCall(async (toolCall, ctx) => {
|
|
102
|
+
if (toolCall.name !== "bash") return { action: "allow" };
|
|
103
|
+
const command = toolCall.input.command;
|
|
104
|
+
if (typeof command !== "string" || !command.includes("rm")) {
|
|
105
|
+
return { action: "allow" };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const approved = await ctx.ui.confirm({
|
|
109
|
+
title: "Approve bash?",
|
|
110
|
+
message: command,
|
|
111
|
+
confirmLabel: "Allow",
|
|
112
|
+
cancelLabel: "Block",
|
|
113
|
+
defaultValue: false,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
return approved
|
|
117
|
+
? { action: "allow" }
|
|
118
|
+
: { action: "reject-and-continue", message: "User denied bash." };
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
64
122
|
## Reloading
|
|
65
123
|
|
|
66
124
|
Use `/reload` after editing plugin files. Kit re-discovers plugin files and reloads them with cache busting so changed `.ts` contents are picked up.
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akonwi/kit",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"author": "Akonwi Ngoh <akonwi@gmail.com>",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "A TUI coding agent",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"repository": {
|
|
@@ -50,13 +50,13 @@
|
|
|
50
50
|
"typecheck": "tsc --noEmit"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@
|
|
54
|
-
"@
|
|
53
|
+
"@earendil-works/pi-agent-core": "^0.74.0",
|
|
54
|
+
"@earendil-works/pi-ai": "^0.74.0",
|
|
55
55
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
56
56
|
"@opentui/core": "0.2.2",
|
|
57
57
|
"@opentui/solid": "0.2.2",
|
|
58
58
|
"@pierre/diffs": "^1.1.16",
|
|
59
|
-
"glob": "^
|
|
59
|
+
"glob": "^13.0.6",
|
|
60
60
|
"ignore": "^7.0.5",
|
|
61
61
|
"solid-js": "1.9.12",
|
|
62
62
|
"web-tree-sitter": "0.25.10",
|