opencode-kiro 0.1.2 → 0.1.4

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 CHANGED
@@ -157,9 +157,17 @@ image-capable models receive it as an attachment.
157
157
  ## Credits in the sidebar
158
158
 
159
159
  Kiro is subscription-metered: requests consume **credits**, and the dollar cost
160
- opencode normally displays is always $0.00. The plugin therefore registers a
161
- **full replacement** for the built-in sidebar context box, showing tokens, context
162
- percentage, and session credits.
160
+ opencode normally displays for Kiro turns is always $0.00. The plugin therefore
161
+ registers a **full replacement** for the built-in sidebar context box, showing
162
+ tokens, context percentage, and a spend line.
163
+
164
+ The spend line adapts to what the session actually used:
165
+
166
+ - **Dollars only** (no Kiro turns): the builtin's exact `$X.XX spent` line, so
167
+ non-Kiro sessions look identical to the built-in box.
168
+ - **Credits only** (Kiro turns, no dollar cost): a single `N credits` line.
169
+ - **Both** (the session used a dollar-based model AND Kiro in one session): two
170
+ stacked lines, `$X.XX spent` then `N credits`, so neither figure is hidden.
163
171
 
164
172
  Disable the builtin box in `tui.json` so only the replacement renders:
165
173
 
@@ -174,19 +182,21 @@ one plus the plugin's). The credits value and its unit come from provider metada
174
182
  emitted by the SDK (kiro-cli reports the unit); nothing is hardcoded client-side.
175
183
 
176
184
  Trade-off: the replacement box applies to **every** session and disabling the builtin
177
- is global: mixed-provider users lose the builtin `$X.XX spent` line for non-Kiro
178
- sessions too; if you need dollar cost there, leave the builtin enabled at the cost of
179
- the duplicate box.
185
+ is global. The replacement reproduces the builtin `$X.XX spent` line for non-Kiro
186
+ sessions (and stacks it above credits when a session used both), so mixed-provider
187
+ users keep dollar cost in the sidebar; if you prefer the original built-in box, leave
188
+ it enabled at the cost of the duplicate box.
180
189
 
181
190
  ## Known limitation (read this)
182
191
 
183
- **Credits display is TUI-sidebar-only.** Every other cost surface (the prompt footer,
184
- ACP clients, the web app, desktop, web share pages, and CLI cost output) shows $0.00
185
- for Kiro sessions. The models.dev catalog declares Kiro's per-token `cost` as 0 (it is a
186
- subscription-metered provider with no per-token pricing), so opencode core computes $0.00
187
- everywhere it renders dollar cost. That is expected, not a defect. A cross-surface credits
188
- display would require opencode core changes and is intentionally out of scope for this
189
- plugin.
192
+ **Credits render in the TUI only.** Two TUI surfaces show them: the sidebar context
193
+ box (above) and the input/prompt meta row chip (`session_prompt_right`), which sits
194
+ beside the host's `$` cost chip. Every other cost surface (ACP clients, the web app,
195
+ desktop, web share pages, and CLI cost output) shows $0.00 for Kiro sessions. The
196
+ models.dev catalog declares Kiro's per-token `cost` as 0 (it is a subscription-metered
197
+ provider with no per-token pricing), so opencode core computes $0.00 everywhere it
198
+ renders dollar cost. That is expected, not a defect. A cross-surface credits display
199
+ would require opencode core changes and is intentionally out of scope for this plugin.
190
200
 
191
201
  ## How it works
192
202
 
@@ -47,11 +47,24 @@ function formatCredits(value, unit) {
47
47
  const label = value === 1 || unit.endsWith("s") ? unit : `${unit}s`;
48
48
  return `${amount} ${label}`;
49
49
  }
50
+ var money = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" });
51
+ function spendLines(input) {
52
+ const { cost, credits } = input;
53
+ const dollars = money.format(Number.isFinite(cost) ? cost : 0);
54
+ if (credits.present && cost > 0) {
55
+ return [`${dollars} spent`, formatCredits(credits.total, credits.unit)];
56
+ }
57
+ if (credits.present) {
58
+ return [formatCredits(credits.total, credits.unit)];
59
+ }
60
+ return [`${dollars} spent`];
61
+ }
50
62
 
51
63
  export {
52
64
  readPartCredits,
53
65
  messageCredits,
54
66
  creditsForMessage,
55
67
  sumSessionCredits,
56
- formatCredits
68
+ formatCredits,
69
+ spendLines
57
70
  };
@@ -1,12 +1,11 @@
1
1
  import {
2
- formatCredits,
2
+ spendLines,
3
3
  sumSessionCredits
4
- } from "./chunk-JHCHA2UV.js";
4
+ } from "./chunk-MQVMKLNA.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" });
10
9
  function createContextView(api, sessionID) {
11
10
  const theme = () => api.theme.current;
12
11
  const messages = createMemo(() => api.state.session.messages(sessionID));
@@ -39,12 +38,10 @@ function createContextView(api, sessionID) {
39
38
  root,
40
39
  mutedLine(theme, () => `${usage().percent ?? 0}% used`)
41
40
  );
42
- insertNode(
41
+ const costLines = createMemo(() => spendLines({ cost: cost(), credits: credits() }));
42
+ insert(
43
43
  root,
44
- mutedLine(
45
- theme,
46
- () => credits().present ? formatCredits(credits().total, credits().unit) : `${money.format(cost())} spent`
47
- )
44
+ () => costLines().map((line) => mutedLine(theme, () => line))
48
45
  );
49
46
  return root;
50
47
  }
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  formatCredits,
3
3
  sumSessionCredits
4
- } from "./chunk-JHCHA2UV.js";
4
+ } from "./chunk-MQVMKLNA.js";
5
5
 
6
6
  // src/tui/credits-chip-view.ts
7
7
  import { createElement, effect, insert, setProp } from "@opentui/solid";
package/dist/server.d.ts CHANGED
@@ -9,7 +9,7 @@ declare function readToken(tokenPath: string | undefined): Promise<{
9
9
  } | {
10
10
  type: "failed";
11
11
  }>;
12
- declare function notifyIfTokenExpired(client: PluginInput["client"] | undefined, tokenPath: string): Promise<void>;
12
+ declare function notifyIfTokenExpired(client: PluginInput["client"] | undefined): Promise<void>;
13
13
  declare const KiroAuthPlugin: Plugin;
14
14
  declare const _default: {
15
15
  id: string;
package/dist/server.js CHANGED
@@ -4,7 +4,7 @@ import { dirname, join } from "path";
4
4
  var KIRO_PLUGIN_NAME = "opencode-kiro";
5
5
  var SIDEBAR_PLUGIN_ID = "internal:sidebar-context";
6
6
  var server = async (input) => {
7
- await notifyIfTokenExpired(input.client, kiroTokenPath());
7
+ await notifyIfTokenExpired(input.client);
8
8
  const tuiPath = tuiConfigPath();
9
9
  const alreadyConfigured = isSidebarConfigured(await readTuiConfig(tuiPath));
10
10
  const prompts = alreadyConfigured ? [] : [
@@ -62,7 +62,9 @@ var server = async (input) => {
62
62
  };
63
63
  }
64
64
  const { execFile } = await import("child_process");
65
- const child = execFile("kiro-cli", ["login"]);
65
+ const child = execFile("kiro-cli", ["login"], {
66
+ shell: process.platform === "win32"
67
+ });
66
68
  return {
67
69
  url: "",
68
70
  instructions: "Complete Kiro authentication in the browser window that just opened. Waiting for login...",
@@ -104,36 +106,28 @@ async function readKiroTokenFile(tokenPath) {
104
106
  return void 0;
105
107
  }
106
108
  }
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
- }
113
109
  async function readToken(tokenPath) {
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" };
120
- }
121
- const access = typeof token.accessToken === "string" ? token.accessToken : "";
122
- const refresh = typeof token.refreshToken === "string" ? token.refreshToken : "";
110
+ const { verifyAuth } = await import("kiro-acp-ai-provider");
111
+ if (!verifyAuth().authenticated) return { type: "failed" };
112
+ const token = tokenPath ? await readKiroTokenFile(tokenPath) : void 0;
113
+ const access = typeof token?.accessToken === "string" ? token.accessToken : "";
114
+ const refresh = typeof token?.refreshToken === "string" ? token.refreshToken : "";
123
115
  return {
124
116
  type: "success",
125
117
  refresh,
126
- // REAL refresh token from the file, no longer the hardcoded ""
118
+ // real refresh when present, else ""
127
119
  access: access || "authenticated",
128
- expires: expiresAtMs
120
+ // cosmetic; opencode-core only needs presence
121
+ // FUTURE expiry, refreshed every startup (server() re-runs each session) so
122
+ // opencode-core does not flag a logged-in user as expired. NOT the file value.
123
+ expires: Date.now() + 8 * 60 * 60 * 1e3
129
124
  };
130
125
  }
131
- async function notifyIfTokenExpired(client, tokenPath) {
126
+ async function notifyIfTokenExpired(client) {
132
127
  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.";
128
+ const { verifyAuth } = await import("kiro-acp-ai-provider");
129
+ if (verifyAuth().authenticated) return;
130
+ const message = "Kiro is not logged in. Run 'kiro-cli login' to authenticate.";
137
131
  console.warn(message);
138
132
  void client?.tui?.showToast?.({ body: { message, variant: "warning" } })?.catch(() => {
139
133
  });
package/dist/tui.d.ts CHANGED
@@ -34,10 +34,24 @@ declare function creditsForMessage(parts: ReadonlyArray<CreditPart>): number | u
34
34
  declare function sumSessionCredits(messages: ReadonlyArray<CreditMessage>, partsByMessage: (messageID: string) => ReadonlyArray<CreditPart>): SessionCredits;
35
35
  /** Render credits with the unit, e.g. "12.5 credits", "1 credit". Unit is naively pluralized unless it ends in "s"; with no unit, only the number renders. */
36
36
  declare function formatCredits(value: number, unit?: string): string;
37
+ /**
38
+ * The sidebar's muted cost lines, returned as an ARRAY (one entry per rendered
39
+ * muted row). Three display states:
40
+ * - BOTH (credits present AND a non-zero dollar cost): TWO stacked lines
41
+ * ["$X.XX spent", "N credits"]
42
+ * - credits only (credits present, dollar cost 0): ["N credits"] (Kiro-only)
43
+ * - dollars only (no credits): ["$X.XX spent"] (also ["$0.00 spent"] when empty)
44
+ * The matrix keys off `credits.present` (a real 0-credit Kiro turn is present)
45
+ * and `cost > 0`, never off `credits.total` alone.
46
+ */
47
+ declare function spendLines(input: {
48
+ cost: number;
49
+ credits: SessionCredits;
50
+ }): string[];
37
51
 
38
52
  declare const _default: {
39
53
  id: string;
40
54
  tui: TuiPlugin;
41
55
  };
42
56
 
43
- export { type CreditMessage, type CreditPart, type PartCredits, type SessionCredits, creditsForMessage, _default as default, formatCredits, messageCredits, readPartCredits, sumSessionCredits };
57
+ export { type CreditMessage, type CreditPart, type PartCredits, type SessionCredits, creditsForMessage, _default as default, formatCredits, messageCredits, readPartCredits, spendLines, sumSessionCredits };
package/dist/tui.js CHANGED
@@ -3,13 +3,14 @@ import {
3
3
  formatCredits,
4
4
  messageCredits,
5
5
  readPartCredits,
6
+ spendLines,
6
7
  sumSessionCredits
7
- } from "./chunk-JHCHA2UV.js";
8
+ } from "./chunk-MQVMKLNA.js";
8
9
 
9
10
  // src/tui.ts
10
11
  var tui = async (api) => {
11
- const { createContextView } = await import("./context-view-3MFY3ASR.js");
12
- const { createCreditsChipView } = await import("./credits-chip-view-SKNVCWRO.js");
12
+ const { createContextView } = await import("./context-view-2KKFHTQV.js");
13
+ const { createCreditsChipView } = await import("./credits-chip-view-RL5JAEZ5.js");
13
14
  api.slots.register({
14
15
  order: 100,
15
16
  slots: {
@@ -29,5 +30,6 @@ export {
29
30
  formatCredits,
30
31
  messageCredits,
31
32
  readPartCredits,
33
+ spendLines,
32
34
  sumSessionCredits
33
35
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-kiro",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
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.1"
45
+ "kiro-acp-ai-provider": "^2.0.2"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@opencode-ai/plugin": "1.16.2",