@bogyie/opencode-kiro-plugin 0.3.5 → 0.3.6
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 +30 -2
- package/dist/auth.d.ts +11 -2
- package/dist/auth.js +49 -12
- package/dist/config.d.ts +2 -0
- package/dist/config.js +15 -0
- package/dist/plugin.js +7 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -38,9 +38,31 @@ 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, OpenCode's startup model discovery may start the same Kiro CLI login flow automatically. The plugin runs `kiro-cli login
|
|
41
|
+
When no usable Kiro CLI session is available, OpenCode's startup model discovery may start the same Kiro CLI login flow automatically. The plugin runs `kiro-cli login`, waits for login to complete through `kiro-cli whoami`, and then runs the model discovery command one more time. If that login fails or times out, discovery fails for that startup attempt; it does not loop.
|
|
42
42
|
|
|
43
|
-
You can also open OpenCode's provider connector, choose Kiro, and select `Kiro CLI login`. The connector flow starts `kiro-cli login
|
|
43
|
+
You can also open OpenCode's provider connector, choose Kiro, and select `Kiro CLI login`. The connector flow starts `kiro-cli login`, waits until the login URL/device code is visible, returns it to the connector, and then waits until `kiro-cli whoami` succeeds. 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 credentials expire during use, direct fetch and `cli-chat` start the shared Kiro login flow, wait for it to complete, and retry the failed request once. `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
|
+
|
|
45
|
+
For AWS IAM Identity Center login, configure the CLI login arguments separately from the API region:
|
|
46
|
+
|
|
47
|
+
```jsonc
|
|
48
|
+
{
|
|
49
|
+
"plugin": [
|
|
50
|
+
[
|
|
51
|
+
"@bogyie/opencode-kiro-plugin",
|
|
52
|
+
{
|
|
53
|
+
"region": "ap-northeast-2",
|
|
54
|
+
"login": {
|
|
55
|
+
"license": "pro",
|
|
56
|
+
"identityProvider": "https://example.awsapps.com/start",
|
|
57
|
+
"region": "ap-northeast-2"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
This runs `kiro-cli login --license pro --identity-provider https://example.awsapps.com/start --region ap-northeast-2`. Do not enable `login.useDeviceFlow` for the normal browser callback flow; Kiro CLI needs to keep its localhost callback listener alive while the browser redirects back.
|
|
44
66
|
|
|
45
67
|
Use the `kiro_status` plugin tool to inspect provider id, backend, region, auth method, and discovered model count. Secrets are redacted in diagnostics.
|
|
46
68
|
|
|
@@ -56,6 +78,12 @@ Configure the backend through plugin options:
|
|
|
56
78
|
{
|
|
57
79
|
"backend": "auto",
|
|
58
80
|
"region": "us-east-1",
|
|
81
|
+
// Optional Kiro CLI login overrides:
|
|
82
|
+
// "login": {
|
|
83
|
+
// "license": "pro",
|
|
84
|
+
// "identityProvider": "https://example.awsapps.com/start",
|
|
85
|
+
// "region": "ap-northeast-2"
|
|
86
|
+
// },
|
|
59
87
|
"endpoint": "https://q.us-east-1.amazonaws.com",
|
|
60
88
|
"maxAttempts": 3,
|
|
61
89
|
"requestTimeoutMs": 120000,
|
package/dist/auth.d.ts
CHANGED
|
@@ -37,10 +37,18 @@ export interface KiroLoginSession {
|
|
|
37
37
|
waitForPrompt(timeoutMs?: number): Promise<boolean>;
|
|
38
38
|
waitForAuth(runner?: CommandRunner): Promise<boolean>;
|
|
39
39
|
}
|
|
40
|
+
export interface KiroCliLoginOptions {
|
|
41
|
+
readonly license?: "free" | "pro";
|
|
42
|
+
readonly identityProvider?: string;
|
|
43
|
+
readonly region?: string;
|
|
44
|
+
readonly useDeviceFlow?: boolean;
|
|
45
|
+
readonly extraArgs?: ReadonlyArray<string>;
|
|
46
|
+
}
|
|
40
47
|
export interface KiroLoginFlowOptions {
|
|
41
48
|
readonly spawner?: ProcessSpawner;
|
|
42
49
|
readonly runner?: CommandRunner;
|
|
43
50
|
readonly promptTimeoutMs?: number;
|
|
51
|
+
readonly login?: KiroCliLoginOptions;
|
|
44
52
|
}
|
|
45
53
|
export declare const KIRO_LOGIN_URL = "https://view.awsapps.com/start";
|
|
46
54
|
export declare const DEFAULT_KIRO_CLI_DB_PATH: string;
|
|
@@ -50,8 +58,9 @@ export declare function regionFromProfileArn(profileArn: string | undefined): st
|
|
|
50
58
|
export declare function readKiroCliSessionCredential(options?: KiroCliSessionCredentialOptions, runner?: CommandRunner): Promise<KiroCliSessionCredential | undefined>;
|
|
51
59
|
export declare function extractKiroLoginUrl(output: string): string;
|
|
52
60
|
export declare function extractKiroLoginCode(output: string): string | undefined;
|
|
53
|
-
export declare function
|
|
54
|
-
export declare function
|
|
61
|
+
export declare function kiroCliLoginArgs(options?: KiroCliLoginOptions): string[];
|
|
62
|
+
export declare function startKiroCliLogin(optionsOrSpawner?: KiroCliLoginOptions | ProcessSpawner, spawner?: ProcessSpawner): KiroLoginSession;
|
|
63
|
+
export declare function startKiroCliLoginOnce(optionsOrSpawner?: KiroCliLoginOptions | ProcessSpawner, spawner?: ProcessSpawner): KiroLoginSession;
|
|
55
64
|
export declare function runKiroLoginFlowOnce(options?: KiroLoginFlowOptions): Promise<boolean>;
|
|
56
65
|
export declare function redacted(value: string | undefined): string | undefined;
|
|
57
66
|
export declare function detectAuth(env?: NodeJS.ProcessEnv, runner?: CommandRunner): Promise<AuthDiagnostics>;
|
package/dist/auth.js
CHANGED
|
@@ -11,7 +11,9 @@ export const DEFAULT_KIRO_CLI_TOKEN_KEYS = ["kirocli:odic:token", "codewhisperer
|
|
|
11
11
|
const LOGIN_REUSE_WINDOW_MS = 2 * 60 * 1000;
|
|
12
12
|
let sharedLoginSession;
|
|
13
13
|
let sharedLoginStartedAt = 0;
|
|
14
|
+
let sharedLoginSessionKey = "";
|
|
14
15
|
let sharedLoginFlow;
|
|
16
|
+
let sharedLoginFlowKey = "";
|
|
15
17
|
export async function runCommand(command, args, options = {}) {
|
|
16
18
|
try {
|
|
17
19
|
const result = await execFileAsync(command, [...args], { timeout: options.timeoutMs ?? 5000 });
|
|
@@ -153,8 +155,33 @@ export function extractKiroLoginUrl(output) {
|
|
|
153
155
|
export function extractKiroLoginCode(output) {
|
|
154
156
|
return /\b[A-Z0-9]{4}-[A-Z0-9]{4}\b/.exec(output)?.[0] ?? /\b[A-Z0-9]{8,}\b/.exec(output)?.[0];
|
|
155
157
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
+
function defaultSpawner(command, args) {
|
|
159
|
+
return spawn(command, [...args], { stdio: ["ignore", "pipe", "pipe"] });
|
|
160
|
+
}
|
|
161
|
+
function loginKey(options = {}) {
|
|
162
|
+
return JSON.stringify(kiroCliLoginArgs(options));
|
|
163
|
+
}
|
|
164
|
+
function loginOptionsAndSpawner(optionsOrSpawner, spawner) {
|
|
165
|
+
if (typeof optionsOrSpawner === "function")
|
|
166
|
+
return { options: {}, spawner: optionsOrSpawner };
|
|
167
|
+
return { options: optionsOrSpawner ?? {}, spawner: spawner ?? defaultSpawner };
|
|
168
|
+
}
|
|
169
|
+
export function kiroCliLoginArgs(options = {}) {
|
|
170
|
+
const args = ["login"];
|
|
171
|
+
if (options.license)
|
|
172
|
+
args.push("--license", options.license);
|
|
173
|
+
if (options.identityProvider)
|
|
174
|
+
args.push("--identity-provider", options.identityProvider);
|
|
175
|
+
if (options.region)
|
|
176
|
+
args.push("--region", options.region);
|
|
177
|
+
if (options.useDeviceFlow)
|
|
178
|
+
args.push("--use-device-flow");
|
|
179
|
+
args.push(...(options.extraArgs ?? []));
|
|
180
|
+
return args;
|
|
181
|
+
}
|
|
182
|
+
export function startKiroCliLogin(optionsOrSpawner, spawner) {
|
|
183
|
+
const resolved = loginOptionsAndSpawner(optionsOrSpawner, spawner);
|
|
184
|
+
const child = resolved.spawner("kiro-cli", kiroCliLoginArgs(resolved.options));
|
|
158
185
|
let output = "";
|
|
159
186
|
let exited = false;
|
|
160
187
|
let exitCode = null;
|
|
@@ -206,11 +233,14 @@ export function startKiroCliLogin(spawner = (command, args) => spawn(command, [.
|
|
|
206
233
|
},
|
|
207
234
|
};
|
|
208
235
|
}
|
|
209
|
-
export function startKiroCliLoginOnce(
|
|
236
|
+
export function startKiroCliLoginOnce(optionsOrSpawner, spawner) {
|
|
237
|
+
const resolved = loginOptionsAndSpawner(optionsOrSpawner, spawner);
|
|
238
|
+
const key = loginKey(resolved.options);
|
|
210
239
|
const now = Date.now();
|
|
211
|
-
if (sharedLoginSession && now - sharedLoginStartedAt < LOGIN_REUSE_WINDOW_MS)
|
|
240
|
+
if (sharedLoginSession && sharedLoginSessionKey === key && now - sharedLoginStartedAt < LOGIN_REUSE_WINDOW_MS) {
|
|
212
241
|
return sharedLoginSession;
|
|
213
|
-
|
|
242
|
+
}
|
|
243
|
+
const session = startKiroCliLogin(resolved.options, resolved.spawner);
|
|
214
244
|
const wrapped = {
|
|
215
245
|
get url() {
|
|
216
246
|
return session.url;
|
|
@@ -232,22 +262,29 @@ export function startKiroCliLoginOnce(spawner = (command, args) => spawn(command
|
|
|
232
262
|
if (sharedLoginSession === wrapped) {
|
|
233
263
|
sharedLoginSession = undefined;
|
|
234
264
|
sharedLoginStartedAt = 0;
|
|
265
|
+
sharedLoginSessionKey = "";
|
|
235
266
|
}
|
|
236
267
|
}
|
|
237
268
|
},
|
|
238
269
|
};
|
|
239
270
|
sharedLoginSession = wrapped;
|
|
240
271
|
sharedLoginStartedAt = now;
|
|
272
|
+
sharedLoginSessionKey = key;
|
|
241
273
|
return wrapped;
|
|
242
274
|
}
|
|
243
275
|
export async function runKiroLoginFlowOnce(options = {}) {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
276
|
+
const key = loginKey(options.login);
|
|
277
|
+
if (!sharedLoginFlow || sharedLoginFlowKey !== key) {
|
|
278
|
+
sharedLoginFlowKey = key;
|
|
279
|
+
sharedLoginFlow = (async () => {
|
|
280
|
+
const session = startKiroCliLoginOnce(options.login, options.spawner);
|
|
281
|
+
await session.waitForPrompt(options.promptTimeoutMs);
|
|
282
|
+
return session.waitForAuth(options.runner);
|
|
283
|
+
})().finally(() => {
|
|
284
|
+
sharedLoginFlow = undefined;
|
|
285
|
+
sharedLoginFlowKey = "";
|
|
286
|
+
});
|
|
287
|
+
}
|
|
251
288
|
return sharedLoginFlow;
|
|
252
289
|
}
|
|
253
290
|
export function redacted(value) {
|
package/dist/config.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import type { KiroCliLoginOptions } from "./auth.js";
|
|
1
2
|
export type BackendMode = "auto" | "fetch" | "cli-chat" | "acp";
|
|
2
3
|
export type ModelDiscoveryMode = "auto" | "off";
|
|
3
4
|
export interface KiroPluginOptions {
|
|
4
5
|
readonly providerID: string;
|
|
5
6
|
readonly region: string;
|
|
7
|
+
readonly login: KiroCliLoginOptions;
|
|
6
8
|
readonly endpoint?: string;
|
|
7
9
|
readonly backend: BackendMode;
|
|
8
10
|
readonly modelDiscovery: ModelDiscoveryMode;
|
package/dist/config.js
CHANGED
|
@@ -43,6 +43,20 @@ function optionalString(value) {
|
|
|
43
43
|
const trimmed = value.trim();
|
|
44
44
|
return trimmed || undefined;
|
|
45
45
|
}
|
|
46
|
+
function loginOptions(value) {
|
|
47
|
+
const input = value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
48
|
+
const license = input.license === "free" || input.license === "pro" ? input.license : undefined;
|
|
49
|
+
const identityProvider = optionalString(input.identityProvider);
|
|
50
|
+
const region = optionalString(input.region);
|
|
51
|
+
const extraArgs = stringArray(input.extraArgs);
|
|
52
|
+
return {
|
|
53
|
+
...(license ? { license } : {}),
|
|
54
|
+
...(identityProvider ? { identityProvider } : {}),
|
|
55
|
+
...(region ? { region } : {}),
|
|
56
|
+
useDeviceFlow: input.useDeviceFlow === true,
|
|
57
|
+
...(extraArgs.length > 0 ? { extraArgs } : {}),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
46
60
|
export function loadOptions(raw = {}) {
|
|
47
61
|
const input = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
|
48
62
|
const backend = typeof input.backend === "string" && BACKENDS.has(input.backend) ? input.backend : "auto";
|
|
@@ -58,6 +72,7 @@ export function loadOptions(raw = {}) {
|
|
|
58
72
|
return {
|
|
59
73
|
providerID: typeof input.providerID === "string" && input.providerID ? input.providerID : DEFAULT_PROVIDER_ID,
|
|
60
74
|
region: typeof input.region === "string" && input.region ? input.region : DEFAULT_REGION,
|
|
75
|
+
login: loginOptions(input.login),
|
|
61
76
|
...(endpoint ? { endpoint } : {}),
|
|
62
77
|
backend,
|
|
63
78
|
modelDiscovery,
|
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, startKiroCliLoginOnce } from "./auth.js";
|
|
3
|
+
import { detectAuth, resolveApiKey, runKiroLoginFlowOnce, 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";
|
|
@@ -73,11 +73,14 @@ function localTransport(options, accessToken) {
|
|
|
73
73
|
return new KiroCliChatTransport({
|
|
74
74
|
trustAllTools: options.trustAllTools,
|
|
75
75
|
...(options.requestTimeoutMs ? { requestTimeoutMs: options.requestTimeoutMs } : {}),
|
|
76
|
+
login: () => runKiroLoginFlowOnce({ login: options.login }),
|
|
76
77
|
});
|
|
77
78
|
}
|
|
78
79
|
if (backend === "fetch") {
|
|
79
80
|
const apiKey = accessToken || process.env.KIRO_API_KEY;
|
|
80
|
-
return new KiroRestTransport(fetchTransportOptions(options, apiKey === "kiro-plugin-local-transport" ? undefined : apiKey)
|
|
81
|
+
return new KiroRestTransport(fetchTransportOptions(options, apiKey === "kiro-plugin-local-transport" ? undefined : apiKey), {
|
|
82
|
+
login: () => runKiroLoginFlowOnce({ login: options.login }),
|
|
83
|
+
});
|
|
81
84
|
}
|
|
82
85
|
return undefined;
|
|
83
86
|
}
|
|
@@ -119,7 +122,7 @@ export function createKiroPlugin() {
|
|
|
119
122
|
return;
|
|
120
123
|
}
|
|
121
124
|
discoveryAttempted = true;
|
|
122
|
-
discovery ??= refreshModelCacheFromCommand(modelCache, options.modelDiscoveryCommand[0], options.modelDiscoveryCommand.slice(1), { loginOnAuthFailure: true })
|
|
125
|
+
discovery ??= refreshModelCacheFromCommand(modelCache, options.modelDiscoveryCommand[0], options.modelDiscoveryCommand.slice(1), { loginOnAuthFailure: true, login: () => runKiroLoginFlowOnce({ login: options.login }) })
|
|
123
126
|
.catch(() => undefined)
|
|
124
127
|
.then(() => {
|
|
125
128
|
discovery = undefined;
|
|
@@ -175,7 +178,7 @@ export function createKiroPlugin() {
|
|
|
175
178
|
type: "oauth",
|
|
176
179
|
label: "Kiro CLI login",
|
|
177
180
|
authorize: async () => {
|
|
178
|
-
const session = startKiroCliLoginOnce();
|
|
181
|
+
const session = startKiroCliLoginOnce(options.login);
|
|
179
182
|
await session.waitForPrompt();
|
|
180
183
|
return {
|
|
181
184
|
url: session.url,
|