opencode-kiro 0.1.0 → 0.1.2
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/dist/{chunk-JO7U4OSF.js → chunk-JHCHA2UV.js} +3 -2
- package/dist/{context-view-CGBZAYM5.js → context-view-3MFY3ASR.js} +7 -2
- package/dist/credits-chip-view-SKNVCWRO.js +20 -0
- package/dist/server.d.ts +13 -2
- package/dist/server.js +195 -72
- package/dist/tui.d.ts +7 -0
- package/dist/tui.js +6 -2
- package/package.json +2 -2
|
@@ -33,10 +33,11 @@ function sumSessionCredits(messages, partsByMessage) {
|
|
|
33
33
|
if (!hit) return acc;
|
|
34
34
|
return {
|
|
35
35
|
total: acc.total + hit.credits,
|
|
36
|
-
unit: hit.unit ?? acc.unit
|
|
36
|
+
unit: hit.unit ?? acc.unit,
|
|
37
|
+
present: true
|
|
37
38
|
};
|
|
38
39
|
},
|
|
39
|
-
{ total: 0, unit: void 0 }
|
|
40
|
+
{ total: 0, unit: void 0, present: false }
|
|
40
41
|
);
|
|
41
42
|
}
|
|
42
43
|
var creditsAmount = new Intl.NumberFormat("en-US", { maximumFractionDigits: 2 });
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
formatCredits,
|
|
3
3
|
sumSessionCredits
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-JHCHA2UV.js";
|
|
5
5
|
|
|
6
6
|
// src/tui/context-view.ts
|
|
7
7
|
import { createElement, effect, insert, insertNode, setProp } from "@opentui/solid";
|
|
8
8
|
import { createMemo } from "solid-js";
|
|
9
|
+
var money = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" });
|
|
9
10
|
function createContextView(api, sessionID) {
|
|
10
11
|
const theme = () => api.theme.current;
|
|
11
12
|
const messages = createMemo(() => api.state.session.messages(sessionID));
|
|
@@ -27,6 +28,7 @@ function createContextView(api, sessionID) {
|
|
|
27
28
|
};
|
|
28
29
|
});
|
|
29
30
|
const credits = createMemo(() => sumSessionCredits(messages(), (messageID) => api.state.part(messageID)));
|
|
31
|
+
const cost = createMemo(() => api.state.session.get(sessionID)?.cost ?? 0);
|
|
30
32
|
const root = createElement("box");
|
|
31
33
|
insertNode(root, headerLine(theme));
|
|
32
34
|
insertNode(
|
|
@@ -39,7 +41,10 @@ function createContextView(api, sessionID) {
|
|
|
39
41
|
);
|
|
40
42
|
insertNode(
|
|
41
43
|
root,
|
|
42
|
-
mutedLine(
|
|
44
|
+
mutedLine(
|
|
45
|
+
theme,
|
|
46
|
+
() => credits().present ? formatCredits(credits().total, credits().unit) : `${money.format(cost())} spent`
|
|
47
|
+
)
|
|
43
48
|
);
|
|
44
49
|
return root;
|
|
45
50
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatCredits,
|
|
3
|
+
sumSessionCredits
|
|
4
|
+
} from "./chunk-JHCHA2UV.js";
|
|
5
|
+
|
|
6
|
+
// src/tui/credits-chip-view.ts
|
|
7
|
+
import { createElement, effect, insert, setProp } from "@opentui/solid";
|
|
8
|
+
import { createMemo } from "solid-js";
|
|
9
|
+
function createCreditsChipView(api, sessionID) {
|
|
10
|
+
const messages = createMemo(() => api.state.session.messages(sessionID));
|
|
11
|
+
const credits = createMemo(() => sumSessionCredits(messages(), (messageID) => api.state.part(messageID)));
|
|
12
|
+
const chip = createElement("text");
|
|
13
|
+
effect(() => setProp(chip, "fg", api.theme.current.textMuted));
|
|
14
|
+
setProp(chip, "wrapMode", "none");
|
|
15
|
+
insert(chip, () => credits().present ? formatCredits(credits().total, credits().unit) : "");
|
|
16
|
+
return chip;
|
|
17
|
+
}
|
|
18
|
+
export {
|
|
19
|
+
createCreditsChipView
|
|
20
|
+
};
|
package/dist/server.d.ts
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
|
-
import { PluginInput, Hooks } from '@opencode-ai/plugin';
|
|
1
|
+
import { Plugin, PluginInput, Hooks } from '@opencode-ai/plugin';
|
|
2
2
|
|
|
3
|
+
declare function kiroTokenPath(): string;
|
|
4
|
+
declare function readToken(tokenPath: string | undefined): Promise<{
|
|
5
|
+
type: "success";
|
|
6
|
+
refresh: string;
|
|
7
|
+
access: string;
|
|
8
|
+
expires: number;
|
|
9
|
+
} | {
|
|
10
|
+
type: "failed";
|
|
11
|
+
}>;
|
|
12
|
+
declare function notifyIfTokenExpired(client: PluginInput["client"] | undefined, tokenPath: string): Promise<void>;
|
|
13
|
+
declare const KiroAuthPlugin: Plugin;
|
|
3
14
|
declare const _default: {
|
|
4
15
|
id: string;
|
|
5
16
|
server: (input: PluginInput) => Promise<Hooks>;
|
|
6
17
|
};
|
|
7
18
|
|
|
8
|
-
export { _default as default };
|
|
19
|
+
export { KiroAuthPlugin, _default as default, kiroTokenPath, notifyIfTokenExpired, readToken };
|
package/dist/server.js
CHANGED
|
@@ -1,91 +1,214 @@
|
|
|
1
1
|
// src/server.ts
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
2
|
+
import { homedir } from "os";
|
|
3
|
+
import { dirname, join } from "path";
|
|
4
|
+
var KIRO_PLUGIN_NAME = "opencode-kiro";
|
|
5
|
+
var SIDEBAR_PLUGIN_ID = "internal:sidebar-context";
|
|
6
|
+
var server = async (input) => {
|
|
7
|
+
await notifyIfTokenExpired(input.client, kiroTokenPath());
|
|
8
|
+
const tuiPath = tuiConfigPath();
|
|
9
|
+
const alreadyConfigured = isSidebarConfigured(await readTuiConfig(tuiPath));
|
|
10
|
+
const prompts = alreadyConfigured ? [] : [
|
|
11
|
+
{
|
|
12
|
+
type: "select",
|
|
13
|
+
key: "sidebar",
|
|
14
|
+
message: "Enable the Kiro credits sidebar?",
|
|
15
|
+
options: [
|
|
16
|
+
{ label: "Yes", value: "yes", hint: "writes tui.json; restart to apply" },
|
|
17
|
+
{ label: "No", value: "no" }
|
|
18
|
+
]
|
|
19
|
+
}
|
|
20
|
+
];
|
|
21
|
+
return {
|
|
22
|
+
auth: {
|
|
23
|
+
provider: "kiro",
|
|
24
|
+
// Returned options are forwarded into createKiroAcp({...}). Relays each
|
|
25
|
+
// catalog model's limit.context into contextWindows keyed by api.id;
|
|
26
|
+
// zero/missing limits are skipped (SDK falls back to 1M).
|
|
27
|
+
loader: async (_getAuth, provider) => ({
|
|
28
|
+
cwd: input.directory ?? input.worktree,
|
|
29
|
+
agent: "opencode",
|
|
30
|
+
trustAllTools: true,
|
|
31
|
+
mcpTimeout: 45,
|
|
32
|
+
contextWindows: Object.fromEntries(
|
|
33
|
+
Object.values(provider.models).filter((m) => (m.limit?.context ?? 0) > 0).map((m) => [m.api.id, m.limit.context])
|
|
34
|
+
)
|
|
35
|
+
}),
|
|
36
|
+
methods: [
|
|
37
|
+
{
|
|
38
|
+
type: "oauth",
|
|
39
|
+
label: "Kiro CLI Login",
|
|
40
|
+
prompts,
|
|
41
|
+
async authorize(inputs) {
|
|
42
|
+
const { verifyAuth } = await import("kiro-acp-ai-provider");
|
|
43
|
+
const status = verifyAuth();
|
|
44
|
+
if (!status.installed)
|
|
45
|
+
throw new Error(
|
|
46
|
+
"kiro-cli is not installed. Install it from https://kiro.dev/docs/cli/"
|
|
47
|
+
);
|
|
48
|
+
const enableSidebar = !alreadyConfigured && inputs?.sidebar === "yes";
|
|
49
|
+
const onSuccess = async () => {
|
|
50
|
+
if (enableSidebar) await enableSidebarConfig(tuiPath, input);
|
|
51
|
+
};
|
|
52
|
+
if (status.authenticated) {
|
|
53
|
+
return {
|
|
54
|
+
url: "",
|
|
55
|
+
instructions: "",
|
|
56
|
+
method: "auto",
|
|
57
|
+
async callback() {
|
|
58
|
+
const result = await readToken(status.tokenPath);
|
|
59
|
+
if (result.type === "success") await onSuccess();
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
const { execFile } = await import("child_process");
|
|
65
|
+
const child = execFile("kiro-cli", ["login"]);
|
|
29
66
|
return {
|
|
30
67
|
url: "",
|
|
31
|
-
instructions: "",
|
|
68
|
+
instructions: "Complete Kiro authentication in the browser window that just opened. Waiting for login...",
|
|
32
69
|
method: "auto",
|
|
33
70
|
async callback() {
|
|
34
|
-
|
|
71
|
+
const maxWait = 12e4;
|
|
72
|
+
const start = Date.now();
|
|
73
|
+
while (Date.now() - start < maxWait) {
|
|
74
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
75
|
+
const check = verifyAuth();
|
|
76
|
+
if (check.authenticated) {
|
|
77
|
+
child.kill();
|
|
78
|
+
const result = await readToken(check.tokenPath);
|
|
79
|
+
if (result.type === "success") await onSuccess();
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
child.kill();
|
|
84
|
+
throw new Error(
|
|
85
|
+
"Kiro authentication timed out. Run `kiro-cli auth login` manually."
|
|
86
|
+
);
|
|
35
87
|
}
|
|
36
88
|
};
|
|
37
89
|
}
|
|
38
|
-
const { execFile } = await import("child_process");
|
|
39
|
-
const child = execFile("kiro-cli", ["login"]);
|
|
40
|
-
return {
|
|
41
|
-
url: "",
|
|
42
|
-
instructions: "Complete Kiro authentication in the browser window that just opened. Waiting for login...",
|
|
43
|
-
method: "auto",
|
|
44
|
-
async callback() {
|
|
45
|
-
const maxWait = 12e4;
|
|
46
|
-
const start = Date.now();
|
|
47
|
-
while (Date.now() - start < maxWait) {
|
|
48
|
-
await new Promise((r) => setTimeout(r, 2e3));
|
|
49
|
-
const check = verifyAuth();
|
|
50
|
-
if (check.authenticated) {
|
|
51
|
-
child.kill();
|
|
52
|
-
return readToken(check.tokenPath);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
child.kill();
|
|
56
|
-
throw new Error(
|
|
57
|
-
"Kiro authentication timed out. Run `kiro-cli auth login` manually."
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
90
|
}
|
|
62
|
-
|
|
63
|
-
|
|
91
|
+
]
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
function kiroTokenPath() {
|
|
96
|
+
return join(homedir(), ".aws", "sso", "cache", "kiro-auth-token.json");
|
|
97
|
+
}
|
|
98
|
+
async function readKiroTokenFile(tokenPath) {
|
|
99
|
+
try {
|
|
100
|
+
const { readFile } = await import("fs/promises");
|
|
101
|
+
const parsed = JSON.parse(await readFile(tokenPath, "utf8"));
|
|
102
|
+
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : void 0;
|
|
103
|
+
} catch {
|
|
104
|
+
return void 0;
|
|
64
105
|
}
|
|
65
|
-
}
|
|
106
|
+
}
|
|
107
|
+
function tokenExpiresAtMs(token) {
|
|
108
|
+
const raw = token?.expiresAt;
|
|
109
|
+
if (typeof raw === "number") return raw;
|
|
110
|
+
if (typeof raw === "string") return Date.parse(raw);
|
|
111
|
+
return NaN;
|
|
112
|
+
}
|
|
66
113
|
async function readToken(tokenPath) {
|
|
67
|
-
if (tokenPath) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
refresh: "",
|
|
74
|
-
access: raw.accessToken || "authenticated",
|
|
75
|
-
expires: raw.expiresAt ? new Date(raw.expiresAt).getTime() : Date.now() + 36e5
|
|
76
|
-
};
|
|
77
|
-
} catch {
|
|
78
|
-
return { type: "failed" };
|
|
79
|
-
}
|
|
114
|
+
if (!tokenPath) return { type: "failed" };
|
|
115
|
+
const token = await readKiroTokenFile(tokenPath);
|
|
116
|
+
if (!token) return { type: "failed" };
|
|
117
|
+
const expiresAtMs = tokenExpiresAtMs(token);
|
|
118
|
+
if (!Number.isFinite(expiresAtMs) || expiresAtMs <= Date.now()) {
|
|
119
|
+
return { type: "failed" };
|
|
80
120
|
}
|
|
121
|
+
const access = typeof token.accessToken === "string" ? token.accessToken : "";
|
|
122
|
+
const refresh = typeof token.refreshToken === "string" ? token.refreshToken : "";
|
|
81
123
|
return {
|
|
82
124
|
type: "success",
|
|
83
|
-
refresh
|
|
84
|
-
|
|
85
|
-
|
|
125
|
+
refresh,
|
|
126
|
+
// REAL refresh token from the file, no longer the hardcoded ""
|
|
127
|
+
access: access || "authenticated",
|
|
128
|
+
expires: expiresAtMs
|
|
86
129
|
};
|
|
87
130
|
}
|
|
131
|
+
async function notifyIfTokenExpired(client, tokenPath) {
|
|
132
|
+
try {
|
|
133
|
+
const token = await readKiroTokenFile(tokenPath);
|
|
134
|
+
const expiresAtMs = tokenExpiresAtMs(token);
|
|
135
|
+
if (Number.isFinite(expiresAtMs) && expiresAtMs > Date.now()) return;
|
|
136
|
+
const message = "Kiro token expired or missing. Run 'kiro-cli login' to re-authenticate.";
|
|
137
|
+
console.warn(message);
|
|
138
|
+
void client?.tui?.showToast?.({ body: { message, variant: "warning" } })?.catch(() => {
|
|
139
|
+
});
|
|
140
|
+
} catch {
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function tuiConfigPath() {
|
|
144
|
+
const base = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
145
|
+
return join(base, "opencode", "tui.json");
|
|
146
|
+
}
|
|
147
|
+
async function readTuiConfig(path) {
|
|
148
|
+
try {
|
|
149
|
+
const { readFile } = await import("fs/promises");
|
|
150
|
+
const parsed = JSON.parse(await readFile(path, "utf8"));
|
|
151
|
+
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : void 0;
|
|
152
|
+
} catch {
|
|
153
|
+
return void 0;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function isSidebarConfigured(config) {
|
|
157
|
+
if (!config) return false;
|
|
158
|
+
const plugin = config.plugin;
|
|
159
|
+
const enabled = config.plugin_enabled;
|
|
160
|
+
const hasPlugin = Array.isArray(plugin) && plugin.includes(KIRO_PLUGIN_NAME);
|
|
161
|
+
const sidebarOff = typeof enabled === "object" && enabled !== null && enabled[SIDEBAR_PLUGIN_ID] === false;
|
|
162
|
+
return hasPlugin && sidebarOff;
|
|
163
|
+
}
|
|
164
|
+
async function enableSidebarConfig(path, input) {
|
|
165
|
+
try {
|
|
166
|
+
const { readFile, writeFile, mkdir } = await import("fs/promises");
|
|
167
|
+
let raw;
|
|
168
|
+
try {
|
|
169
|
+
raw = await readFile(path, "utf8");
|
|
170
|
+
} catch {
|
|
171
|
+
raw = void 0;
|
|
172
|
+
}
|
|
173
|
+
let config;
|
|
174
|
+
if (raw === void 0) {
|
|
175
|
+
config = {};
|
|
176
|
+
} else {
|
|
177
|
+
try {
|
|
178
|
+
const parsed = JSON.parse(raw);
|
|
179
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return;
|
|
180
|
+
config = parsed;
|
|
181
|
+
} catch {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (isSidebarConfigured(config)) return;
|
|
186
|
+
const plugin = Array.isArray(config.plugin) ? [...config.plugin] : [];
|
|
187
|
+
if (!plugin.includes(KIRO_PLUGIN_NAME)) plugin.push(KIRO_PLUGIN_NAME);
|
|
188
|
+
config.plugin = plugin;
|
|
189
|
+
const enabled = typeof config.plugin_enabled === "object" && config.plugin_enabled !== null && !Array.isArray(config.plugin_enabled) ? config.plugin_enabled : {};
|
|
190
|
+
enabled[SIDEBAR_PLUGIN_ID] = false;
|
|
191
|
+
config.plugin_enabled = enabled;
|
|
192
|
+
await mkdir(dirname(path), { recursive: true });
|
|
193
|
+
await writeFile(path, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
194
|
+
try {
|
|
195
|
+
await input.client.tui.showToast({
|
|
196
|
+
body: {
|
|
197
|
+
message: "Kiro credits sidebar enabled. Restart opencode to see it.",
|
|
198
|
+
variant: "success"
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
} catch {
|
|
202
|
+
}
|
|
203
|
+
} catch {
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
var KiroAuthPlugin = server;
|
|
88
207
|
var server_default = { id: "kiro", server };
|
|
89
208
|
export {
|
|
90
|
-
|
|
209
|
+
KiroAuthPlugin,
|
|
210
|
+
server_default as default,
|
|
211
|
+
kiroTokenPath,
|
|
212
|
+
notifyIfTokenExpired,
|
|
213
|
+
readToken
|
|
91
214
|
};
|
package/dist/tui.d.ts
CHANGED
|
@@ -16,6 +16,13 @@ interface PartCredits {
|
|
|
16
16
|
interface SessionCredits {
|
|
17
17
|
total: number;
|
|
18
18
|
unit?: string;
|
|
19
|
+
/**
|
|
20
|
+
* True once any assistant message carried kiro credit metadata. Lets the
|
|
21
|
+
* sidebar/footer pick the credits view over the builtin "$X spent" fallback,
|
|
22
|
+
* since a credits total of 0 (a real kiro turn) is indistinguishable from
|
|
23
|
+
* "no kiro metadata at all" by `total` alone.
|
|
24
|
+
*/
|
|
25
|
+
present: boolean;
|
|
19
26
|
}
|
|
20
27
|
/** Read `{ kiro: { credits, creditsUnit } }` from a part; only finite numeric credits count. */
|
|
21
28
|
declare function readPartCredits(part: CreditPart): PartCredits | undefined;
|
package/dist/tui.js
CHANGED
|
@@ -4,16 +4,20 @@ import {
|
|
|
4
4
|
messageCredits,
|
|
5
5
|
readPartCredits,
|
|
6
6
|
sumSessionCredits
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-JHCHA2UV.js";
|
|
8
8
|
|
|
9
9
|
// src/tui.ts
|
|
10
10
|
var tui = async (api) => {
|
|
11
|
-
const { createContextView } = await import("./context-view-
|
|
11
|
+
const { createContextView } = await import("./context-view-3MFY3ASR.js");
|
|
12
|
+
const { createCreditsChipView } = await import("./credits-chip-view-SKNVCWRO.js");
|
|
12
13
|
api.slots.register({
|
|
13
14
|
order: 100,
|
|
14
15
|
slots: {
|
|
15
16
|
sidebar_content(_ctx, props) {
|
|
16
17
|
return createContextView(api, props.session_id);
|
|
18
|
+
},
|
|
19
|
+
session_prompt_right(_ctx, props) {
|
|
20
|
+
return createCreditsChipView(api, props.session_id);
|
|
17
21
|
}
|
|
18
22
|
}
|
|
19
23
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-kiro",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "The ACP-compliant Kiro plugin for opencode: auth via the official kiro-cli login, 12 Kiro models through the Agent Client Protocol, and TUI credits display",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Nacho F. Lizaur (https://github.com/NachoFLizaur)",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"@opencode-ai/plugin": "*"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"kiro-acp-ai-provider": "^2.0.
|
|
45
|
+
"kiro-acp-ai-provider": "^2.0.1"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@opencode-ai/plugin": "1.16.2",
|