@bogyie/opencode-kiro-plugin 0.3.16 → 0.3.18

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,26 @@ 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
- OpenCode startup does not start Kiro login. It does run best-effort model discovery with `kiro-cli chat --list-models --format json` so the model picker can track the current Kiro CLI list. If startup discovery succeeds, the result is stored in a local cache and used immediately. If startup discovery fails, the plugin keeps the last stored model list. If no stored list exists yet, it injects an `auto` placeholder model so Kiro still appears in OpenCode's provider connector.
41
+ During OpenCode startup, the plugin checks Kiro auth in the blocking `config` hook. If no `KIRO_API_KEY` or active `kiro-cli` session exists, it starts the configured `kiro-cli login --use-device-flow` flow once and waits for login to finish before model discovery and provider registration continue. It then runs best-effort model discovery with `kiro-cli chat --list-models --format json` so the model picker can track the current Kiro CLI list. If startup discovery succeeds, the result is stored in a local cache and used immediately. If startup discovery fails, the plugin keeps the last stored model list. If no stored list exists yet, it injects an `auto` placeholder model so Kiro still appears in OpenCode's provider connector.
42
42
 
43
- You can open OpenCode's provider connector, choose Kiro, and select `Kiro device login`. The connector runs `kiro-cli login --use-device-flow`, waits for the local Kiro CLI session to become authenticated, then stores a local transport marker in OpenCode auth. If `login.identityProvider`, `login.region`, `login.license`, or `login.extraArgs` are configured, those options are passed to `kiro-cli login` along with `--use-device-flow`. After login succeeds, the plugin also tries to refresh the runtime model cache. If no OpenCode connector marker or API key is configured, direct fetch still reads the active Kiro CLI session token and calls Kiro's REST/EventStream endpoint directly. If an API/model call fails with an auth error, the selected transport starts the same configured Kiro CLI login flow and retries the 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.
43
+ You can open OpenCode's provider connector, choose Kiro, and select `Kiro device login`. The connector runs `kiro-cli login --use-device-flow`, waits for the local Kiro CLI session to become authenticated, then stores a local transport marker in OpenCode auth. Configure `login.method` as `builder-id`, `google`, `github`, or `organization` to preselect the Kiro CLI login method. The plugin maps `builder-id`, `google`, and `github` to `--license free`; it maps `organization` to `--license pro`. If `login.identityProvider`, `login.region`, `login.license`, or `login.extraArgs` are configured, those options are also passed to `kiro-cli login` along with `--use-device-flow`. After login succeeds, the plugin also tries to refresh the runtime model cache. If no OpenCode connector marker or API key is configured, direct fetch still reads the active Kiro CLI session token and calls Kiro's REST/EventStream endpoint directly. If an API/model call fails with an auth error, the selected transport starts the same configured Kiro CLI login flow and retries the 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 personal Kiro login with GitHub:
46
+
47
+ ```jsonc
48
+ {
49
+ "plugin": [
50
+ [
51
+ "@bogyie/opencode-kiro-plugin",
52
+ {
53
+ "login": {
54
+ "method": "github"
55
+ }
56
+ }
57
+ ]
58
+ ]
59
+ }
60
+ ```
44
61
 
45
62
  For AWS IAM Identity Center login, configure the default device-flow Start URL separately from the API region:
46
63
 
@@ -52,7 +69,7 @@ For AWS IAM Identity Center login, configure the default device-flow Start URL s
52
69
  {
53
70
  "region": "ap-northeast-2",
54
71
  "login": {
55
- "license": "pro",
72
+ "method": "organization",
56
73
  "identityProvider": "https://example.awsapps.com/start",
57
74
  "region": "ap-northeast-2"
58
75
  }
@@ -62,7 +79,7 @@ For AWS IAM Identity Center login, configure the default device-flow Start URL s
62
79
  }
63
80
  ```
64
81
 
65
- For IAM Identity Center, configure `login.identityProvider` and `login.region` in plugin options so they are passed to `kiro-cli login --use-device-flow`.
82
+ For IAM Identity Center, configure `login.method: "organization"`, `login.identityProvider`, and `login.region` in plugin options so they are passed to `kiro-cli login --license pro --use-device-flow`.
66
83
 
67
84
  Use the `kiro_status` plugin tool to inspect provider id, backend, region, auth method, and discovered model count. Use `kiro_refresh_models` when you explicitly want to run the configured model discovery command and update the in-memory and stored model cache. Secrets are redacted in diagnostics.
68
85
 
@@ -80,7 +97,7 @@ Configure the backend through plugin options:
80
97
  "region": "us-east-1",
81
98
  // Optional Kiro device login defaults:
82
99
  // "login": {
83
- // "license": "pro",
100
+ // "method": "organization",
84
101
  // "identityProvider": "https://example.awsapps.com/start",
85
102
  // "region": "ap-northeast-2"
86
103
  // },
@@ -155,7 +172,7 @@ This keeps new Kiro model ids usable before the package is updated without adver
155
172
 
156
173
  The plugin injects `provider.kiro` with the `auto` placeholder needed for OpenCode's provider connector. Define `provider.kiro.models` yourself only when you need explicit model-picker metadata such as a display name or context limit. Use plugin `modelAliases` for aliases that should resolve one requested model id to another.
157
174
 
158
- `modelDiscoveryCommand` defaults to `["kiro-cli", "chat", "--list-models", "--format", "json"]`. Set `modelDiscovery` to `"off"` to disable startup discovery and `kiro_refresh_models`; in that mode the plugin uses any stored cache and then the `auto` fallback. Discovery is best-effort and does not start login during OpenCode startup. `/v1/models` also uses only this discovery/cache path: it never invokes the chat transport or login fallback, and returns the cached list or `auto` if discovery fails. OpenCode title-generation requests also suppress login fallback so startup helper traffic cannot open a Kiro login window. Authenticate manually through the connector or let a real chat completion request handle auth failure. A successful connector login also triggers a forced model refresh. Discovery stdout can be a JSON array, `{ "models": [...] }`, `{ "data": [...] }`, Kiro CLI list-models JSON, Kiro CLI plain list output, or one model id per line.
175
+ `modelDiscoveryCommand` defaults to `["kiro-cli", "chat", "--list-models", "--format", "json"]`. Set `modelDiscovery` to `"off"` to disable startup discovery and `kiro_refresh_models`; in that mode the plugin uses any stored cache and then the `auto` fallback. During OpenCode startup, the plugin checks Kiro auth in the blocking `config` hook. If no `KIRO_API_KEY` or active `kiro-cli` session exists, it starts the configured `kiro-cli login --use-device-flow` flow once and waits for login to finish before model discovery and provider registration continue. `/v1/models` still uses only the discovery/cache path: it never invokes the chat transport or login fallback, and returns the cached list or `auto` if discovery fails. OpenCode title-generation requests also suppress login fallback so startup helper traffic cannot open a second Kiro login window. A successful connector login also triggers a forced model refresh. Discovery stdout can be a JSON array, `{ "models": [...] }`, `{ "data": [...] }`, Kiro CLI list-models JSON, Kiro CLI plain list output, or one model id per line.
159
176
 
160
177
  ## Troubleshooting
161
178
 
package/dist/auth.d.ts CHANGED
@@ -38,7 +38,9 @@ export interface KiroLoginSession {
38
38
  waitForPrompt(timeoutMs?: number): Promise<boolean>;
39
39
  waitForAuth(runner?: CommandRunner): Promise<boolean>;
40
40
  }
41
+ export type KiroCliLoginMethod = "builder-id" | "google" | "github" | "organization";
41
42
  export interface KiroCliLoginOptions {
43
+ readonly method?: KiroCliLoginMethod;
42
44
  readonly license?: "free" | "pro";
43
45
  readonly identityProvider?: string;
44
46
  readonly region?: string;
package/dist/auth.js CHANGED
@@ -412,7 +412,7 @@ export function extractKiroLoginCode(output) {
412
412
  return /\b[A-Z0-9]{4}-[A-Z0-9]{4}\b/.exec(output)?.[0] ?? /\b[A-Z0-9]{8,}\b/.exec(output)?.[0];
413
413
  }
414
414
  function defaultSpawner(command, args) {
415
- return spawn(command, [...args], { stdio: ["ignore", "pipe", "pipe"] });
415
+ return spawn(command, [...args], { stdio: ["pipe", "pipe", "pipe"] });
416
416
  }
417
417
  function loginKey(options = {}) {
418
418
  return JSON.stringify(kiroCliLoginArgs(options));
@@ -424,8 +424,9 @@ function loginOptionsAndSpawner(optionsOrSpawner, spawner) {
424
424
  }
425
425
  export function kiroCliLoginArgs(options = {}) {
426
426
  const args = ["login"];
427
- if (options.license)
428
- args.push("--license", options.license);
427
+ const license = options.license ?? (options.method === "organization" ? "pro" : options.method ? "free" : undefined);
428
+ if (license)
429
+ args.push("--license", license);
429
430
  if (options.identityProvider)
430
431
  args.push("--identity-provider", options.identityProvider);
431
432
  if (options.region)
@@ -435,17 +436,45 @@ export function kiroCliLoginArgs(options = {}) {
435
436
  args.push(...(options.extraArgs ?? []));
436
437
  return args;
437
438
  }
439
+ function loginMethodSelection(method) {
440
+ switch (method) {
441
+ case "builder-id":
442
+ return "\r";
443
+ case "google":
444
+ return "\x1B[B\r";
445
+ case "github":
446
+ return "\x1B[B\x1B[B\r";
447
+ case "organization":
448
+ return "\x1B[B\x1B[B\x1B[B\r";
449
+ }
450
+ }
451
+ function loginMethodPromptSeen(output) {
452
+ return (output.includes("Select login method") ||
453
+ output.includes("Use with Builder ID") ||
454
+ output.includes("Use with Google") ||
455
+ output.includes("Use with GitHub") ||
456
+ output.includes("Use with Your Organization"));
457
+ }
438
458
  export function startKiroCliLogin(optionsOrSpawner, spawner) {
439
459
  const resolved = loginOptionsAndSpawner(optionsOrSpawner, spawner);
440
460
  const child = resolved.spawner("kiro-cli", kiroCliLoginArgs(resolved.options));
441
461
  let output = "";
442
462
  let exited = false;
443
463
  let exitCode = null;
464
+ let selectedLoginMethod = false;
465
+ const maybeSelectLoginMethod = () => {
466
+ if (!resolved.options.method || selectedLoginMethod || !loginMethodPromptSeen(output))
467
+ return;
468
+ child.stdin?.write(loginMethodSelection(resolved.options.method));
469
+ selectedLoginMethod = true;
470
+ };
444
471
  child.stdout?.on("data", (chunk) => {
445
472
  output += chunk.toString("utf8");
473
+ maybeSelectLoginMethod();
446
474
  });
447
475
  child.stderr?.on("data", (chunk) => {
448
476
  output += chunk.toString("utf8");
477
+ maybeSelectLoginMethod();
449
478
  });
450
479
  child.on("exit", (code) => {
451
480
  exited = true;
package/dist/config.js CHANGED
@@ -5,6 +5,7 @@ export const DEFAULT_MODEL_CACHE_TTL_SECONDS = 6 * 60 * 60;
5
5
  export const DEFAULT_MAX_ATTEMPTS = 3;
6
6
  const BACKENDS = new Set(["auto", "fetch", "cli-chat", "acp"]);
7
7
  const DISCOVERY_MODES = new Set(["auto", "off"]);
8
+ const LOGIN_METHODS = new Set(["builder-id", "google", "github", "organization"]);
8
9
  function stringRecord(value) {
9
10
  if (!value || typeof value !== "object" || Array.isArray(value))
10
11
  return {};
@@ -45,11 +46,15 @@ function optionalString(value) {
45
46
  }
46
47
  function loginOptions(value) {
47
48
  const input = value && typeof value === "object" && !Array.isArray(value) ? value : {};
49
+ const method = typeof input.method === "string" && LOGIN_METHODS.has(input.method)
50
+ ? input.method
51
+ : undefined;
48
52
  const license = input.license === "free" || input.license === "pro" ? input.license : undefined;
49
53
  const identityProvider = optionalString(input.identityProvider);
50
54
  const region = optionalString(input.region);
51
55
  const extraArgs = stringArray(input.extraArgs);
52
56
  return {
57
+ ...(method ? { method } : {}),
53
58
  ...(license ? { license } : {}),
54
59
  ...(identityProvider ? { identityProvider } : {}),
55
60
  ...(region ? { region } : {}),
package/dist/plugin.js CHANGED
@@ -148,6 +148,8 @@ export function createKiroPlugin() {
148
148
  }
149
149
  let discovery;
150
150
  let lastModelDiscoveryAt = 0;
151
+ let startupLogin;
152
+ let startupLoginAttempted = false;
151
153
  const refreshModels = async (force = false) => {
152
154
  const discoveryIsStale = lastModelDiscoveryAt === 0 || Date.now() - lastModelDiscoveryAt > options.modelCacheTtlSeconds * 1000;
153
155
  if (options.modelDiscovery === "off" || options.modelDiscoveryCommand.length === 0 || (!force && !discoveryIsStale)) {
@@ -170,6 +172,29 @@ export function createKiroPlugin() {
170
172
  }
171
173
  return discovery;
172
174
  };
175
+ const ensureStartupAuthenticated = async () => {
176
+ const current = await detectAuth().catch(() => undefined);
177
+ if (current?.authenticated)
178
+ return true;
179
+ if (startupLogin)
180
+ return startupLogin;
181
+ if (startupLoginAttempted)
182
+ return false;
183
+ startupLoginAttempted = true;
184
+ startupLogin = (async () => {
185
+ const session = startKiroCliLoginOnce({
186
+ ...options.login,
187
+ useDeviceFlow: true,
188
+ });
189
+ const prompted = await session.waitForPrompt(options.requestTimeoutMs);
190
+ if (!prompted)
191
+ return false;
192
+ return session.waitForAuth();
193
+ })().finally(() => {
194
+ startupLogin = undefined;
195
+ });
196
+ return startupLogin;
197
+ };
173
198
  const resolver = new ModelResolver({
174
199
  cache: modelCache,
175
200
  aliases: options.modelAliases,
@@ -227,6 +252,7 @@ export function createKiroPlugin() {
227
252
  localServer = undefined;
228
253
  },
229
254
  config: async (config) => {
255
+ await ensureStartupAuthenticated();
230
256
  await refreshModels();
231
257
  config.provider ??= {};
232
258
  config.provider[options.providerID] ??= {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bogyie/opencode-kiro-plugin",
3
- "version": "0.3.16",
3
+ "version": "0.3.18",
4
4
  "description": "OpenCode plugin for using Kiro as a provider adapter",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -60,6 +60,7 @@
60
60
  "type": "plugin",
61
61
  "hooks": [
62
62
  "auth",
63
+ "chat.headers",
63
64
  "config",
64
65
  "provider",
65
66
  "tool"