@bogyie/opencode-kiro-plugin 0.2.11 → 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 +2 -2
- package/dist/auth.d.ts +4 -0
- package/dist/auth.js +58 -8
- package/dist/cli-transport.d.ts +0 -1
- package/dist/cli-transport.js +1 -23
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/kiro-rest-transport.js +1 -1
- package/dist/plugin.js +15 -3
- package/package.json +1 -1
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
|
|
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,7 +44,9 @@ 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;
|
|
49
|
+
export declare function startKiroCliLoginOnce(spawner?: ProcessSpawner): KiroLoginSession;
|
|
46
50
|
export declare function redacted(value: string | undefined): string | undefined;
|
|
47
51
|
export declare function detectAuth(env?: NodeJS.ProcessEnv, runner?: CommandRunner): Promise<AuthDiagnostics>;
|
|
48
52
|
export declare function resolveApiKey(auth: () => Promise<{
|
package/dist/auth.js
CHANGED
|
@@ -8,6 +8,9 @@ const execFileAsync = promisify(execFile);
|
|
|
8
8
|
export const KIRO_LOGIN_URL = "https://view.awsapps.com/start";
|
|
9
9
|
export const DEFAULT_KIRO_CLI_DB_PATH = join(homedir(), "Library", "Application Support", "kiro-cli", "data.sqlite3");
|
|
10
10
|
export const DEFAULT_KIRO_CLI_TOKEN_KEYS = ["kirocli:odic:token", "codewhisperer:odic:token"];
|
|
11
|
+
const LOGIN_REUSE_WINDOW_MS = 2 * 60 * 1000;
|
|
12
|
+
let sharedLoginSession;
|
|
13
|
+
let sharedLoginStartedAt = 0;
|
|
11
14
|
export async function runCommand(command, args, options = {}) {
|
|
12
15
|
try {
|
|
13
16
|
const result = await execFileAsync(command, [...args], { timeout: options.timeoutMs ?? 5000 });
|
|
@@ -98,6 +101,9 @@ function firstUrl(text) {
|
|
|
98
101
|
export function extractKiroLoginUrl(output) {
|
|
99
102
|
return firstUrl(output) ?? KIRO_LOGIN_URL;
|
|
100
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
|
+
}
|
|
101
107
|
export function startKiroCliLogin(spawner = (command, args) => spawn(command, [...args], { stdio: ["ignore", "pipe", "pipe"] })) {
|
|
102
108
|
const child = spawner("kiro-cli", ["login", "--use-device-flow"]);
|
|
103
109
|
let output = "";
|
|
@@ -117,15 +123,25 @@ export function startKiroCliLogin(spawner = (command, args) => spawn(command, [.
|
|
|
117
123
|
get url() {
|
|
118
124
|
return extractKiroLoginUrl(output);
|
|
119
125
|
},
|
|
126
|
+
get code() {
|
|
127
|
+
return extractKiroLoginCode(output);
|
|
128
|
+
},
|
|
120
129
|
get instructions() {
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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));
|
|
129
145
|
},
|
|
130
146
|
async waitForAuth(runner = runCommand) {
|
|
131
147
|
const deadline = Date.now() + 10 * 60 * 1000;
|
|
@@ -141,6 +157,40 @@ export function startKiroCliLogin(spawner = (command, args) => spawn(command, [.
|
|
|
141
157
|
},
|
|
142
158
|
};
|
|
143
159
|
}
|
|
160
|
+
export function startKiroCliLoginOnce(spawner = (command, args) => spawn(command, [...args], { stdio: ["ignore", "pipe", "pipe"] })) {
|
|
161
|
+
const now = Date.now();
|
|
162
|
+
if (sharedLoginSession && now - sharedLoginStartedAt < LOGIN_REUSE_WINDOW_MS)
|
|
163
|
+
return sharedLoginSession;
|
|
164
|
+
const session = startKiroCliLogin(spawner);
|
|
165
|
+
const wrapped = {
|
|
166
|
+
get url() {
|
|
167
|
+
return session.url;
|
|
168
|
+
},
|
|
169
|
+
get code() {
|
|
170
|
+
return session.code;
|
|
171
|
+
},
|
|
172
|
+
get instructions() {
|
|
173
|
+
return session.instructions;
|
|
174
|
+
},
|
|
175
|
+
waitForPrompt(timeoutMs) {
|
|
176
|
+
return session.waitForPrompt(timeoutMs);
|
|
177
|
+
},
|
|
178
|
+
async waitForAuth(runner) {
|
|
179
|
+
try {
|
|
180
|
+
return await session.waitForAuth(runner);
|
|
181
|
+
}
|
|
182
|
+
finally {
|
|
183
|
+
if (sharedLoginSession === wrapped) {
|
|
184
|
+
sharedLoginSession = undefined;
|
|
185
|
+
sharedLoginStartedAt = 0;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
sharedLoginSession = wrapped;
|
|
191
|
+
sharedLoginStartedAt = now;
|
|
192
|
+
return wrapped;
|
|
193
|
+
}
|
|
144
194
|
export function redacted(value) {
|
|
145
195
|
if (!value)
|
|
146
196
|
return undefined;
|
package/dist/cli-transport.d.ts
CHANGED
|
@@ -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
|
}
|
package/dist/cli-transport.js
CHANGED
|
@@ -1,14 +1,6 @@
|
|
|
1
|
-
import { runCommand
|
|
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
|
-
startKiroCliLogin();
|
|
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 {
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export type { AcpConnection, AcpNotificationHandler, AcpStdioClientOptions, Json
|
|
|
3
3
|
export { acpPermissionResponse, KiroAcpTransport } from "./acp-transport.js";
|
|
4
4
|
export type { AcpSessionClient, KiroAcpTransportOptions } from "./acp-transport.js";
|
|
5
5
|
export { createKiroPlugin, effectiveBackend, KiroPlugin } from "./plugin.js";
|
|
6
|
-
export { detectAuth, extractKiroLoginUrl, KIRO_LOGIN_URL, readKiroCliSessionCredential, redacted, regionFromProfileArn, resolveApiKey, startKiroCliLogin, } from "./auth.js";
|
|
6
|
+
export { detectAuth, extractKiroLoginUrl, KIRO_LOGIN_URL, readKiroCliSessionCredential, redacted, regionFromProfileArn, resolveApiKey, startKiroCliLogin, startKiroCliLoginOnce, } from "./auth.js";
|
|
7
7
|
export { cliChatArgs, KiroCliChatTransport, promptForCli } from "./cli-transport.js";
|
|
8
8
|
export { createKiroFetch } from "./fetch-adapter.js";
|
|
9
9
|
export { loadOptions } from "./config.js";
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { KiroPlugin } from "./plugin.js";
|
|
|
2
2
|
export { AcpJsonRpcClient, createAcpStdioClient, decodeJsonRpc, encodeJsonRpc } from "./acp-client.js";
|
|
3
3
|
export { acpPermissionResponse, KiroAcpTransport } from "./acp-transport.js";
|
|
4
4
|
export { createKiroPlugin, effectiveBackend, KiroPlugin } from "./plugin.js";
|
|
5
|
-
export { detectAuth, extractKiroLoginUrl, KIRO_LOGIN_URL, readKiroCliSessionCredential, redacted, regionFromProfileArn, resolveApiKey, startKiroCliLogin, } from "./auth.js";
|
|
5
|
+
export { detectAuth, extractKiroLoginUrl, KIRO_LOGIN_URL, readKiroCliSessionCredential, redacted, regionFromProfileArn, resolveApiKey, startKiroCliLogin, startKiroCliLoginOnce, } from "./auth.js";
|
|
6
6
|
export { cliChatArgs, KiroCliChatTransport, promptForCli } from "./cli-transport.js";
|
|
7
7
|
export { createKiroFetch } from "./fetch-adapter.js";
|
|
8
8
|
export { loadOptions } from "./config.js";
|
|
@@ -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.
|
|
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { tool } from "@opencode-ai/plugin";
|
|
2
2
|
import { KiroAcpTransport } from "./acp-transport.js";
|
|
3
|
-
import { detectAuth, resolveApiKey,
|
|
3
|
+
import { detectAuth, resolveApiKey, startKiroCliLoginOnce } from "./auth.js";
|
|
4
4
|
import { KiroCliChatTransport } from "./cli-transport.js";
|
|
5
5
|
import { loadOptions } from "./config.js";
|
|
6
6
|
import { createKiroFetch } from "./fetch-adapter.js";
|
|
@@ -170,14 +170,26 @@ export function createKiroPlugin() {
|
|
|
170
170
|
type: "oauth",
|
|
171
171
|
label: "Kiro CLI login",
|
|
172
172
|
authorize: async () => {
|
|
173
|
-
const session =
|
|
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
|
-
|
|
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
|
},
|