@bogyie/opencode-kiro-plugin 0.2.12 → 0.2.13

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
@@ -38,9 +38,9 @@ The plugin resolves credentials in this order:
38
38
  3. Local Kiro CLI session token from the Kiro CLI SQLite store
39
39
  4. `kiro-cli whoami` diagnostics for CLI session visibility
40
40
 
41
- When no usable Kiro CLI session is available, choose the `Kiro CLI login` auth method in OpenCode. The plugin starts `kiro-cli login --use-device-flow`, opens the browser through the official CLI login flow, and waits until `kiro-cli whoami` succeeds.
41
+ When no usable Kiro CLI session is available, open OpenCode's provider connector, choose Kiro, and select `Kiro CLI login`. The plugin starts `kiro-cli login --use-device-flow`, waits until the verification URL/device code is visible, returns them to the connector, and then waits until `kiro-cli whoami` succeeds. The same auth method also works from OpenCode's CLI provider connect flow.
42
42
 
43
- Direct fetch mode uses a usable API key/token when one is configured; otherwise it reads the active Kiro CLI session token and calls Kiro's REST/EventStream endpoint directly. `cli-chat` mode uses the official `kiro-cli chat --no-interactive` surface and depends on the local Kiro CLI login state. `acp` mode uses the official `kiro-cli acp` surface, but is still treated as an explicit backend while its real-world protocol behavior is validated across Kiro CLI versions.
43
+ The plugin does not start login during OpenCode startup or as a side effect of a model request. Direct fetch mode uses a usable API key/token when one is configured; otherwise it reads the active Kiro CLI session token and calls Kiro's REST/EventStream endpoint directly. If no credential is available, requests fail with `KIRO_AUTH_ERROR` and you can connect from OpenCode's provider connector or run `kiro-cli login` manually. `cli-chat` mode uses the official `kiro-cli chat --no-interactive` surface and depends on the local Kiro CLI login state. `acp` mode uses the official `kiro-cli acp` surface, but is still treated as an explicit backend while its real-world protocol behavior is validated across Kiro CLI versions.
44
44
 
45
45
  Use the `kiro_status` plugin tool to inspect provider id, backend, region, auth method, and discovered model count. Secrets are redacted in diagnostics.
46
46
 
package/dist/auth.d.ts CHANGED
@@ -32,7 +32,9 @@ export interface KiroCliSessionCredentialOptions {
32
32
  }
33
33
  export interface KiroLoginSession {
34
34
  readonly url: string;
35
+ readonly code: string | undefined;
35
36
  readonly instructions: string;
37
+ waitForPrompt(timeoutMs?: number): Promise<boolean>;
36
38
  waitForAuth(runner?: CommandRunner): Promise<boolean>;
37
39
  }
38
40
  export declare const KIRO_LOGIN_URL = "https://view.awsapps.com/start";
@@ -42,6 +44,7 @@ export declare function runCommand(command: string, args: ReadonlyArray<string>,
42
44
  export declare function regionFromProfileArn(profileArn: string | undefined): string | undefined;
43
45
  export declare function readKiroCliSessionCredential(options?: KiroCliSessionCredentialOptions, runner?: CommandRunner): Promise<KiroCliSessionCredential | undefined>;
44
46
  export declare function extractKiroLoginUrl(output: string): string;
47
+ export declare function extractKiroLoginCode(output: string): string | undefined;
45
48
  export declare function startKiroCliLogin(spawner?: ProcessSpawner): KiroLoginSession;
46
49
  export declare function startKiroCliLoginOnce(spawner?: ProcessSpawner): KiroLoginSession;
47
50
  export declare function redacted(value: string | undefined): string | undefined;
package/dist/auth.js CHANGED
@@ -101,6 +101,9 @@ function firstUrl(text) {
101
101
  export function extractKiroLoginUrl(output) {
102
102
  return firstUrl(output) ?? KIRO_LOGIN_URL;
103
103
  }
104
+ export function extractKiroLoginCode(output) {
105
+ return /\b[A-Z0-9]{4}-[A-Z0-9]{4}\b/.exec(output)?.[0] ?? /\b[A-Z0-9]{8,}\b/.exec(output)?.[0];
106
+ }
104
107
  export function startKiroCliLogin(spawner = (command, args) => spawn(command, [...args], { stdio: ["ignore", "pipe", "pipe"] })) {
105
108
  const child = spawner("kiro-cli", ["login", "--use-device-flow"]);
106
109
  let output = "";
@@ -120,15 +123,25 @@ export function startKiroCliLogin(spawner = (command, args) => spawn(command, [.
120
123
  get url() {
121
124
  return extractKiroLoginUrl(output);
122
125
  },
126
+ get code() {
127
+ return extractKiroLoginCode(output);
128
+ },
123
129
  get instructions() {
124
- const url = extractKiroLoginUrl(output);
125
- const code = /\b[A-Z0-9]{4}-[A-Z0-9]{4}\b/.exec(output)?.[0] ?? /\b[A-Z0-9]{8,}\b/.exec(output)?.[0];
126
- return [
127
- "Complete Kiro CLI login in the browser.",
128
- `URL: ${url}`,
129
- ...(code ? [`Code: ${code}`] : []),
130
- "The plugin will continue after `kiro-cli whoami` succeeds.",
131
- ].join("\n");
130
+ const code = extractKiroLoginCode(output);
131
+ return code ? `Enter code: ${code}` : "Complete Kiro CLI login in your browser. This window will close automatically.";
132
+ },
133
+ async waitForPrompt(timeoutMs = 15_000) {
134
+ const deadline = Date.now() + timeoutMs;
135
+ while (Date.now() < deadline) {
136
+ if (extractKiroLoginCode(output))
137
+ return true;
138
+ if (firstUrl(output))
139
+ return true;
140
+ if (exited && exitCode !== 0)
141
+ return false;
142
+ await delay(100);
143
+ }
144
+ return Boolean(extractKiroLoginCode(output) || firstUrl(output));
132
145
  },
133
146
  async waitForAuth(runner = runCommand) {
134
147
  const deadline = Date.now() + 10 * 60 * 1000;
@@ -153,9 +166,15 @@ export function startKiroCliLoginOnce(spawner = (command, args) => spawn(command
153
166
  get url() {
154
167
  return session.url;
155
168
  },
169
+ get code() {
170
+ return session.code;
171
+ },
156
172
  get instructions() {
157
173
  return session.instructions;
158
174
  },
175
+ waitForPrompt(timeoutMs) {
176
+ return session.waitForPrompt(timeoutMs);
177
+ },
159
178
  async waitForAuth(runner) {
160
179
  try {
161
180
  return await session.waitForAuth(runner);
@@ -6,7 +6,6 @@ import { type ChildProcess } from "node:child_process";
6
6
  export interface CliChatTransportOptions {
7
7
  readonly runner?: CommandRunner;
8
8
  readonly spawner?: CliChatSpawner;
9
- readonly loginStarter?: () => void;
10
9
  readonly trustAllTools?: boolean;
11
10
  readonly requestTimeoutMs?: number;
12
11
  }
@@ -1,14 +1,6 @@
1
- import { runCommand, startKiroCliLoginOnce as startSharedKiroCliLoginOnce } from "./auth.js";
1
+ import { runCommand } from "./auth.js";
2
2
  import { KiroPluginError } from "./errors.js";
3
3
  import { spawn } from "node:child_process";
4
- let lastLoginStartAt = 0;
5
- function startKiroCliLoginOnce() {
6
- const now = Date.now();
7
- if (now - lastLoginStartAt < 30_000)
8
- return;
9
- lastLoginStartAt = now;
10
- startSharedKiroCliLoginOnce();
11
- }
12
4
  export function promptForCli(request) {
13
5
  const parts = [
14
6
  request.system ? `System:\n${request.system}` : "",
@@ -92,32 +84,20 @@ class AsyncQueue {
92
84
  export class KiroCliChatTransport {
93
85
  #runner;
94
86
  #spawner;
95
- #loginStarter;
96
87
  #trustAllTools;
97
88
  #requestTimeoutMs;
98
89
  constructor(options = {}) {
99
90
  this.#runner = options.runner ?? runCommand;
100
91
  this.#spawner = options.spawner ?? ((command, args) => spawn(command, [...args], { stdio: ["ignore", "pipe", "pipe"] }));
101
- this.#loginStarter = options.loginStarter ?? startKiroCliLoginOnce;
102
92
  this.#trustAllTools = options.trustAllTools === true;
103
93
  this.#requestTimeoutMs = options.requestTimeoutMs ?? 120_000;
104
94
  }
105
- #startLoginAfterAuthFailure() {
106
- try {
107
- this.#loginStarter();
108
- }
109
- catch {
110
- // Preserve the original auth error. Login launch failures are diagnostic-only here.
111
- }
112
- }
113
95
  async generate(request) {
114
96
  const result = await this.#runner("kiro-cli", cliChatArgs(request, { trustAllTools: this.#trustAllTools }), {
115
97
  timeoutMs: this.#requestTimeoutMs,
116
98
  });
117
99
  if (!result.ok) {
118
100
  const authError = result.stderr.toLowerCase().includes("not logged in");
119
- if (authError)
120
- this.#startLoginAfterAuthFailure();
121
101
  throw new KiroPluginError(result.stderr.trim() || "kiro-cli chat failed", authError ? "KIRO_AUTH_ERROR" : "KIRO_CLI_FAILED", authError ? 401 : 502);
122
102
  }
123
103
  const text = sanitizeCliChatOutput(result.stdout);
@@ -169,8 +149,6 @@ export class KiroCliChatTransport {
169
149
  return;
170
150
  }
171
151
  const authError = stderr.toLowerCase().includes("not logged in");
172
- if (authError)
173
- this.#startLoginAfterAuthFailure();
174
152
  queue.fail(new KiroPluginError(stderr.trim() || `kiro-cli chat exited${code === null ? "" : ` with code ${code}`}${signal ? ` and signal ${signal}` : ""}`, authError ? "KIRO_AUTH_ERROR" : "KIRO_CLI_FAILED", authError ? 401 : 502));
175
153
  });
176
154
  try {
@@ -322,7 +322,7 @@ export class KiroRestTransport {
322
322
  const session = this.#options.accessToken ? undefined : await this.#credentialProvider();
323
323
  const accessToken = this.#options.accessToken ?? session?.accessToken;
324
324
  if (!accessToken) {
325
- throw new KiroPluginError("No Kiro API token or Kiro CLI session token found. Run Kiro login and try again.", "KIRO_AUTH_ERROR", 401);
325
+ throw new KiroPluginError("No Kiro API token or Kiro CLI session token found. Connect Kiro from OpenCode's provider connector or run `kiro-cli login`.", "KIRO_AUTH_ERROR", 401);
326
326
  }
327
327
  const profileArn = this.#options.profileArn ?? session?.profileArn;
328
328
  return {
package/dist/plugin.js CHANGED
@@ -171,13 +171,25 @@ export function createKiroPlugin() {
171
171
  label: "Kiro CLI login",
172
172
  authorize: async () => {
173
173
  const session = startKiroCliLoginOnce();
174
+ await session.waitForPrompt();
174
175
  return {
175
176
  url: session.url,
176
177
  instructions: session.instructions,
177
178
  method: "auto",
178
179
  callback: async () => {
179
180
  const authenticated = await session.waitForAuth();
180
- return authenticated ? { type: "success", key: "kiro-plugin-local-transport" } : { type: "failed" };
181
+ if (!authenticated)
182
+ return { type: "failed" };
183
+ const diagnostics = await detectAuth().catch(() => undefined);
184
+ return {
185
+ type: "success",
186
+ key: "kiro-plugin-local-transport",
187
+ metadata: {
188
+ source: "kiro-cli",
189
+ ...(diagnostics?.account ? { account: diagnostics.account } : {}),
190
+ ...(diagnostics?.region ? { region: diagnostics.region } : {}),
191
+ },
192
+ };
181
193
  },
182
194
  };
183
195
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bogyie/opencode-kiro-plugin",
3
- "version": "0.2.12",
3
+ "version": "0.2.13",
4
4
  "description": "OpenCode plugin for using Kiro as a provider adapter",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",