@sunerpy/opencode-kiro-auth 0.9.0 → 0.10.0

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
@@ -85,7 +85,7 @@ customize which models are exposed (including thinking variants), see
85
85
  - Note: the TUI `/connect` flow does **not** currently run plugin OAuth
86
86
  prompts (Start URL / region), so Identity Center logins may fall back to
87
87
  Builder ID unless you use `opencode auth login` (or preconfigure
88
- defaults in `~/.config/opencode/kiro.json`).
88
+ defaults in `~/.config/opencode/kiro-auth-plugin/kiro.json`).
89
89
  - For **IAM Identity Center**, you may also need a **profile ARN**
90
90
  (`profileArn`) — auto-detected from `kiro-cli profile` if available, or
91
91
  set `idc_profile_arn` manually.
@@ -97,7 +97,7 @@ customize which models are exposed (including thinking variants), see
97
97
  ## Configuration
98
98
 
99
99
  Plugin-wide behavior (auth sync, account selection, retry limits, effort
100
- mapping) lives in `~/.config/opencode/kiro.json`. See
100
+ mapping) lives in `~/.config/opencode/kiro-auth-plugin/kiro.json`. See
101
101
  [docs/CONFIGURATION.md](docs/CONFIGURATION.md) for the full example and every
102
102
  option.
103
103
 
@@ -131,13 +131,13 @@ requests across them for combined quota and automatic failover.
131
131
  account (option 1) is the supported multi-account path.
132
132
 
133
133
  **Rotation strategy** — set `account_selection_strategy` in
134
- `~/.config/opencode/kiro.json`:
134
+ `~/.config/opencode/kiro-auth-plugin/kiro.json`:
135
135
 
136
- | Strategy | Behavior | Default |
137
- | -------------- | -------------------------------------------------------------------- | ------- |
138
- | `lowest-usage` | Each request picks the healthy account with the lowest used quota | ✅ |
139
- | `round-robin` | Cycles through accounts in order | |
140
- | `sticky` | Always uses the first account; switches only when it becomes unhealthy | |
136
+ | Strategy | Behavior | Default |
137
+ | -------------- | ---------------------------------------------------------------------- | ------- |
138
+ | `lowest-usage` | Each request picks the healthy account with the lowest used quota | ✅ |
139
+ | `round-robin` | Cycles through accounts in order | |
140
+ | `sticky` | Always uses the first account; switches only when it becomes unhealthy | |
141
141
 
142
142
  **Automatic failover** requires no configuration: a rate-limited or 403
143
143
  account is marked unhealthy and the next healthy account takes over. If every
@@ -165,7 +165,7 @@ per account, e.g. `929/10000`) and surfaces it in three places:
165
165
 
166
166
  1. **Auth menu label** — running `opencode auth login` and selecting
167
167
  `kiro-auth` shows `[current: <email> <used>/<limit> (<pct>%)]` on the
168
- first login method; with multiple accounts they're joined with ` · `,
168
+ first login method; with multiple accounts they're joined with `·`,
169
169
  capped at 3 with a `+N more` suffix.
170
170
  2. **Startup toast** — once per plugin init, a toast shows
171
171
  `Kiro usage (<email>): <used>/<limit> (<pct>%)` a few seconds after
@@ -191,11 +191,11 @@ your `provider.kiro-auth.models` block.
191
191
  Thinking budgets map to Kiro's native `effort` field automatically:
192
192
 
193
193
  | OpenCode budget | Kiro effort |
194
- | ---------------- | ----------- |
195
- | `<= 10000` | `low` |
196
- | `<= 20000` | `medium` |
197
- | `<= 28000` | `high` |
198
- | `> 28000` | `max` |
194
+ | --------------- | ----------- |
195
+ | `<= 10000` | `low` |
196
+ | `<= 20000` | `medium` |
197
+ | `<= 28000` | `high` |
198
+ | `> 28000` | `max` |
199
199
 
200
200
  Details and the full JSON example: [docs/MODELS.md](docs/MODELS.md).
201
201
 
@@ -283,12 +283,21 @@ Releases are automated with
283
283
  **Linux/macOS:**
284
284
 
285
285
  - SQLite database: `~/.config/opencode/kiro.db`
286
- - Plugin config: `~/.config/opencode/kiro.json`
286
+ - Plugin config: `~/.config/opencode/kiro-auth-plugin/kiro.json`
287
+ - Logs: `~/.config/opencode/kiro-auth-plugin/logs/`
288
+ - Refresh and keep-alive locks: `~/.config/opencode/kiro-auth-plugin/`
287
289
 
288
290
  **Windows:**
289
291
 
290
292
  - SQLite database: `%APPDATA%\opencode\kiro.db`
291
- - Plugin config: `%APPDATA%\opencode\kiro.json`
293
+ - Plugin config: `%APPDATA%\opencode\kiro-auth-plugin\kiro.json`
294
+ - Logs: `%APPDATA%\opencode\kiro-auth-plugin\logs\`
295
+ - Refresh and keep-alive locks: `%APPDATA%\opencode\kiro-auth-plugin\`
296
+
297
+ Existing config, logs, and stale flat lock files are migrated automatically on
298
+ first startup; no action is needed. The SQLite database deliberately remains at
299
+ the flat `opencode/kiro.db` path because moving a live database during an upgrade
300
+ is unsafe.
292
301
 
293
302
  ## Acknowledgements
294
303
 
@@ -27,7 +27,7 @@ export class AccountSelector {
27
27
  if (!acc) {
28
28
  this.circuitBreakerTrips++;
29
29
  if (this.accountManager.allSelectableBlockedByOverage()) {
30
- throw new Error('All accounts have exceeded their free quota and entered paid overage. Set "stop_on_overage": false in ~/.config/opencode/kiro.json to continue with paid overage, or wait for the quota to reset.');
30
+ throw new Error('All accounts have exceeded their free quota and entered paid overage. Set "stop_on_overage": false in ~/.config/opencode/kiro-auth-plugin/kiro.json to continue with paid overage, or wait for the quota to reset.');
31
31
  }
32
32
  const wait = this.accountManager.getMinWaitTime();
33
33
  if (wait > 0 && wait < 30000) {
@@ -106,7 +106,7 @@ export class IdcAuthMethod {
106
106
  error: e instanceof Error ? e.message : String(e)
107
107
  });
108
108
  if (startUrl && !profileArn) {
109
- throw new Error(`Missing profile ARN for IAM Identity Center. Set "idc_profile_arn" in ~/.config/opencode/kiro.json, or run "kiro-cli profile" once so it can be auto-detected. Original error: ${e instanceof Error ? e.message : String(e)}`);
109
+ throw new Error(`Missing profile ARN for IAM Identity Center. Set "idc_profile_arn" in ~/.config/opencode/kiro-auth-plugin/kiro.json, or run "kiro-cli profile" once so it can be auto-detected. Original error: ${e instanceof Error ? e.message : String(e)}`);
110
110
  }
111
111
  const errMsg = e instanceof Error ? e.message : String(e);
112
112
  if (errMsg.includes('FEATURE_NOT_SUPPORTED')) {
@@ -176,7 +176,7 @@ export class IdcAuthMethod {
176
176
  catch (e) {
177
177
  const err = e instanceof Error ? e : new Error(String(e));
178
178
  logger.error('IDC auth callback failed', err);
179
- throw new Error(`IDC authorization failed: ${err.message}. Check ~/.config/opencode/kiro-logs/plugin.log for details. If this is an Identity Center account, ensure you have selected an AWS Q Developer/CodeWhisperer profile (try: kiro-cli profile).`);
179
+ throw new Error(`IDC authorization failed: ${err.message}. Check ~/.config/opencode/kiro-auth-plugin/logs/plugin.log for details. If this is an Identity Center account, ensure you have selected an AWS Q Developer/CodeWhisperer profile (try: kiro-cli profile).`);
180
180
  }
181
181
  }
182
182
  };
@@ -1,5 +1,5 @@
1
1
  import { type KiroConfig } from './schema.js';
2
- export declare function getUserConfigPath(): string;
2
+ export { getUserConfigPath } from '../paths.js';
3
3
  export declare function getProjectConfigPath(directory: string): string;
4
4
  export declare function loadConfig(directory: string): KiroConfig;
5
5
  export declare function configExists(path: string): boolean;
@@ -1,19 +1,9 @@
1
1
  import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
2
- import { homedir } from 'node:os';
3
2
  import { dirname, join } from 'node:path';
4
3
  import * as logger from '../logger.js';
4
+ import { getLegacyUserConfigPath, getLogsDir, getUserConfigPath } from '../paths.js';
5
5
  import { AccountSelectionStrategySchema, DEFAULT_CONFIG, KiroConfigSchema, RegionSchema } from './schema.js';
6
- function getConfigDir() {
7
- const platform = process.platform;
8
- if (platform === 'win32') {
9
- return join(process.env.APPDATA || join(homedir(), 'AppData', 'Roaming'), 'opencode');
10
- }
11
- const xdgConfig = process.env.XDG_CONFIG_HOME || join(homedir(), '.config');
12
- return join(xdgConfig, 'opencode');
13
- }
14
- export function getUserConfigPath() {
15
- return join(getConfigDir(), 'kiro.json');
16
- }
6
+ export { getUserConfigPath } from '../paths.js';
17
7
  function ensureUserConfigTemplate() {
18
8
  const path = getUserConfigPath();
19
9
  if (!existsSync(path)) {
@@ -155,11 +145,16 @@ function applyEnvOverrides(config) {
155
145
  };
156
146
  }
157
147
  export function loadConfig(directory) {
158
- ensureUserConfigTemplate();
159
- backfillUserConfig(getUserConfigPath());
160
- let config = { ...DEFAULT_CONFIG };
161
148
  const userConfigPath = getUserConfigPath();
162
- const userConfig = loadConfigFile(userConfigPath);
149
+ const legacyUserConfigPath = getLegacyUserConfigPath();
150
+ const configSourcePath = !existsSync(userConfigPath) && existsSync(legacyUserConfigPath)
151
+ ? legacyUserConfigPath
152
+ : userConfigPath;
153
+ if (configSourcePath === userConfigPath)
154
+ ensureUserConfigTemplate();
155
+ backfillUserConfig(configSourcePath);
156
+ let config = { ...DEFAULT_CONFIG };
157
+ const userConfig = loadConfigFile(configSourcePath);
163
158
  if (userConfig) {
164
159
  config = mergeConfigs(config, userConfig);
165
160
  }
@@ -175,5 +170,5 @@ export function configExists(path) {
175
170
  return existsSync(path);
176
171
  }
177
172
  export function getDefaultLogsDir() {
178
- return join(getConfigDir(), 'kiro-logs');
173
+ return getLogsDir();
179
174
  }
@@ -1,22 +1,15 @@
1
1
  import { Buffer } from 'node:buffer';
2
2
  import { appendFileSync, mkdirSync, writeFileSync } from 'node:fs';
3
- import { homedir } from 'node:os';
4
3
  import { join } from 'node:path';
4
+ import { getLogsDir } from './paths.js';
5
5
  const binaryToBase64Replacer = (_key, value) => {
6
6
  if (value instanceof Uint8Array)
7
7
  return Buffer.from(value).toString('base64');
8
8
  return value;
9
9
  };
10
- const getLogDir = () => {
11
- const platform = process.platform;
12
- const base = platform === 'win32'
13
- ? join(process.env.APPDATA || join(homedir(), 'AppData', 'Roaming'), 'opencode')
14
- : join(process.env.XDG_CONFIG_HOME || join(homedir(), '.config'), 'opencode');
15
- return join(base, 'kiro-logs');
16
- };
17
10
  const writeToFile = (level, message, ...args) => {
18
11
  try {
19
- const dir = getLogDir();
12
+ const dir = getLogsDir();
20
13
  mkdirSync(dir, { recursive: true });
21
14
  const path = join(dir, 'plugin.log');
22
15
  const timestamp = new Date().toISOString();
@@ -42,7 +35,7 @@ const writeToFile = (level, message, ...args) => {
42
35
  };
43
36
  const writeApiLog = (type, data, timestamp, isError = false) => {
44
37
  try {
45
- const dir = getLogDir();
38
+ const dir = getLogsDir();
46
39
  mkdirSync(dir, { recursive: true });
47
40
  const prefix = isError ? 'error_' : '';
48
41
  const filename = `${prefix}${timestamp}_${type}.json`;
@@ -0,0 +1,9 @@
1
+ export declare function getOpencodeConfigDir(): string;
2
+ export declare function getPluginDir(): string;
3
+ export declare function getUserConfigPath(): string;
4
+ export declare function getLegacyUserConfigPath(): string;
5
+ export declare function getLogsDir(): string;
6
+ export declare function getLegacyLogsDir(): string;
7
+ export declare function getRefreshLockPath(accountId: string): string;
8
+ export declare function getKeepAliveLockPath(): string;
9
+ export declare function getMigrationLockPath(): string;
@@ -0,0 +1,33 @@
1
+ import { homedir } from 'node:os';
2
+ import { join } from 'node:path';
3
+ export function getOpencodeConfigDir() {
4
+ const p = process.platform;
5
+ if (p === 'win32')
6
+ return join(process.env.APPDATA || join(homedir(), 'AppData', 'Roaming'), 'opencode');
7
+ return join(process.env.XDG_CONFIG_HOME || join(homedir(), '.config'), 'opencode');
8
+ }
9
+ export function getPluginDir() {
10
+ return join(getOpencodeConfigDir(), 'kiro-auth-plugin');
11
+ }
12
+ export function getUserConfigPath() {
13
+ return join(getPluginDir(), 'kiro.json');
14
+ }
15
+ export function getLegacyUserConfigPath() {
16
+ return join(getOpencodeConfigDir(), 'kiro.json');
17
+ }
18
+ export function getLogsDir() {
19
+ return join(getPluginDir(), 'logs');
20
+ }
21
+ export function getLegacyLogsDir() {
22
+ return join(getOpencodeConfigDir(), 'kiro-logs');
23
+ }
24
+ export function getRefreshLockPath(accountId) {
25
+ const safeAccountId = accountId.replace(/[^A-Za-z0-9_-]/g, '');
26
+ return join(getPluginDir(), `.kiro-refresh-${safeAccountId}.lock`);
27
+ }
28
+ export function getKeepAliveLockPath() {
29
+ return join(getPluginDir(), '.kiro-keepalive.lock');
30
+ }
31
+ export function getMigrationLockPath() {
32
+ return join(getOpencodeConfigDir(), '.kiro-auth-migration.lock');
33
+ }
@@ -1,7 +1,6 @@
1
1
  import type { ManagedAccount } from '../types.js';
2
+ export { getKeepAliveLockPath, getRefreshLockPath } from '../paths.js';
2
3
  type LockRelease = () => Promise<void>;
3
- export declare function getRefreshLockPath(accountId: string): string;
4
- export declare function getKeepAliveLockPath(): string;
5
4
  export declare function withDatabaseLock<T>(dbPath: string, fn: () => Promise<T>): Promise<T>;
6
5
  export declare function withRefreshLock<T>(accountId: string, fn: () => Promise<T>): Promise<T>;
7
6
  export declare function tryAcquireKeepAliveLock(): Promise<LockRelease | null>;
@@ -9,4 +8,3 @@ export declare function withKeepAliveLock<T>(fn: () => Promise<T>): Promise<T |
9
8
  export declare function createDeterministicId(email: string, authMethod: string, clientId?: string, profileArn?: string): string;
10
9
  export declare function mergeAccounts(existing: ManagedAccount[], incoming: ManagedAccount[]): ManagedAccount[];
11
10
  export declare function deduplicateAccounts(accounts: ManagedAccount[]): ManagedAccount[];
12
- export {};
@@ -1,9 +1,10 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { existsSync, promises as fs } from 'node:fs';
3
- import { homedir } from 'node:os';
4
- import { dirname, join } from 'node:path';
3
+ import { dirname } from 'node:path';
5
4
  import lockfile from 'proper-lockfile';
6
5
  import { isPermanentError } from '../health.js';
6
+ import { getKeepAliveLockPath, getRefreshLockPath } from '../paths.js';
7
+ export { getKeepAliveLockPath, getRefreshLockPath } from '../paths.js';
7
8
  const LOCK_OPTIONS = {
8
9
  stale: 10000,
9
10
  retries: {
@@ -29,23 +30,6 @@ const KEEP_ALIVE_LOCK_OPTIONS = {
29
30
  retries: 0,
30
31
  realpath: false
31
32
  };
32
- function getBaseDir() {
33
- // Keep this local instead of importing DB_PATH from sqlite.ts: sqlite.ts imports
34
- // this module and also constructs `kiroDb` at module load, so importing it here
35
- // would create a cycle and trigger database construction just to compute a lock path.
36
- const p = process.platform;
37
- if (p === 'win32') {
38
- return join(process.env.APPDATA || join(homedir(), 'AppData', 'Roaming'), 'opencode');
39
- }
40
- return join(process.env.XDG_CONFIG_HOME || join(homedir(), '.config'), 'opencode');
41
- }
42
- export function getRefreshLockPath(accountId) {
43
- const safeAccountId = accountId.replace(/[^A-Za-z0-9_-]/g, '');
44
- return join(getBaseDir(), `.kiro-refresh-${safeAccountId}.lock`);
45
- }
46
- export function getKeepAliveLockPath() {
47
- return join(getBaseDir(), '.kiro-keepalive.lock');
48
- }
49
33
  export async function withDatabaseLock(dbPath, fn) {
50
34
  if (!existsSync(dbPath)) {
51
35
  const dir = dbPath.substring(0, dbPath.lastIndexOf('/'));
@@ -0,0 +1 @@
1
+ export declare function migrateSafeFilesIfNeeded(): void;
@@ -0,0 +1,191 @@
1
+ import { closeSync, copyFileSync, existsSync, mkdirSync, openSync, readdirSync, renameSync, rmdirSync, unlinkSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import lockfile from 'proper-lockfile';
4
+ import * as logger from '../logger.js';
5
+ import { getLegacyLogsDir, getLegacyUserConfigPath, getLogsDir, getMigrationLockPath, getOpencodeConfigDir, getPluginDir, getUserConfigPath } from '../paths.js';
6
+ const MIGRATION_LOCK_OPTIONS = {
7
+ stale: 15000,
8
+ retries: 0,
9
+ realpath: false
10
+ };
11
+ const MIGRATION_LOCK_ATTEMPTS = 4;
12
+ function isCrossDeviceError(error) {
13
+ return error instanceof Error && 'code' in error && error.code === 'EXDEV';
14
+ }
15
+ function removeTemporaryFile(path) {
16
+ if (!existsSync(path))
17
+ return;
18
+ try {
19
+ unlinkSync(path);
20
+ }
21
+ catch (error) {
22
+ logger.warn('Storage layout migration: temporary file cleanup failed', {
23
+ path,
24
+ error: error instanceof Error ? error.message : String(error)
25
+ });
26
+ }
27
+ }
28
+ function moveFile(source, destination) {
29
+ try {
30
+ renameSync(source, destination);
31
+ return;
32
+ }
33
+ catch (error) {
34
+ if (!(error instanceof Error) || !isCrossDeviceError(error))
35
+ throw error;
36
+ }
37
+ const temporaryDestination = `${destination}.tmp-${process.pid}`;
38
+ try {
39
+ copyFileSync(source, temporaryDestination);
40
+ renameSync(temporaryDestination, destination);
41
+ unlinkSync(source);
42
+ }
43
+ catch (error) {
44
+ removeTemporaryFile(temporaryDestination);
45
+ throw error;
46
+ }
47
+ }
48
+ function migrateUserConfig() {
49
+ const legacyPath = getLegacyUserConfigPath();
50
+ const destinationPath = getUserConfigPath();
51
+ if (!existsSync(legacyPath) || existsSync(destinationPath))
52
+ return false;
53
+ moveFile(legacyPath, destinationPath);
54
+ return true;
55
+ }
56
+ function migrateLogs() {
57
+ const legacyDir = getLegacyLogsDir();
58
+ if (!existsSync(legacyDir))
59
+ return 0;
60
+ const destinationDir = getLogsDir();
61
+ mkdirSync(destinationDir, { recursive: true });
62
+ let moved = 0;
63
+ for (const entry of readdirSync(legacyDir)) {
64
+ const source = join(legacyDir, entry);
65
+ const destination = join(destinationDir, entry);
66
+ if (existsSync(destination))
67
+ continue;
68
+ try {
69
+ moveFile(source, destination);
70
+ moved++;
71
+ }
72
+ catch (error) {
73
+ logger.warn('Storage layout migration: log file move failed', {
74
+ source,
75
+ destination,
76
+ error: error instanceof Error ? error.message : String(error)
77
+ });
78
+ }
79
+ }
80
+ if (readdirSync(legacyDir).length === 0) {
81
+ try {
82
+ rmdirSync(legacyDir);
83
+ }
84
+ catch (error) {
85
+ logger.warn('Storage layout migration: legacy log directory cleanup failed', {
86
+ path: legacyDir,
87
+ error: error instanceof Error ? error.message : String(error)
88
+ });
89
+ }
90
+ }
91
+ return moved;
92
+ }
93
+ function removeStaleFlatLocks() {
94
+ const configDir = getOpencodeConfigDir();
95
+ let removed = 0;
96
+ for (const entry of readdirSync(configDir)) {
97
+ const isKeepAliveLock = entry === '.kiro-keepalive.lock';
98
+ const isRefreshLock = /^\.kiro-refresh-.*\.lock$/.test(entry);
99
+ if (!isKeepAliveLock && !isRefreshLock)
100
+ continue;
101
+ const path = join(configDir, entry);
102
+ try {
103
+ unlinkSync(path);
104
+ removed++;
105
+ }
106
+ catch (error) {
107
+ logger.warn('Storage layout migration: stale lock cleanup failed', {
108
+ path,
109
+ error: error instanceof Error ? error.message : String(error)
110
+ });
111
+ }
112
+ }
113
+ return removed;
114
+ }
115
+ function acquireMigrationLock(path) {
116
+ let lastError;
117
+ for (let attempt = 0; attempt < MIGRATION_LOCK_ATTEMPTS; attempt++) {
118
+ try {
119
+ return lockfile.lockSync(path, MIGRATION_LOCK_OPTIONS);
120
+ }
121
+ catch (error) {
122
+ lastError = error instanceof Error ? error : new Error(String(error));
123
+ }
124
+ }
125
+ logger.warn('Storage layout migration: lock acquisition skipped', lastError);
126
+ return null;
127
+ }
128
+ function runMigration() {
129
+ try {
130
+ mkdirSync(getPluginDir(), { recursive: true });
131
+ }
132
+ catch (error) {
133
+ logger.warn('Storage layout migration: plugin directory creation failed', error instanceof Error ? error.message : String(error));
134
+ return;
135
+ }
136
+ const migrationLockPath = getMigrationLockPath();
137
+ try {
138
+ closeSync(openSync(migrationLockPath, 'a'));
139
+ }
140
+ catch (error) {
141
+ logger.warn('Storage layout migration: lock file creation failed', error instanceof Error ? error.message : String(error));
142
+ return;
143
+ }
144
+ const release = acquireMigrationLock(migrationLockPath);
145
+ if (!release)
146
+ return;
147
+ let configMoved = false;
148
+ let logsMoved = 0;
149
+ let locksRemoved = 0;
150
+ try {
151
+ try {
152
+ configMoved = migrateUserConfig();
153
+ }
154
+ catch (error) {
155
+ logger.warn('Storage layout migration: config move failed', error instanceof Error ? error.message : String(error));
156
+ }
157
+ try {
158
+ logsMoved = migrateLogs();
159
+ }
160
+ catch (error) {
161
+ logger.warn('Storage layout migration: logs migration failed', error instanceof Error ? error.message : String(error));
162
+ }
163
+ try {
164
+ locksRemoved = removeStaleFlatLocks();
165
+ }
166
+ catch (error) {
167
+ logger.warn('Storage layout migration: stale lock scan failed', error instanceof Error ? error.message : String(error));
168
+ }
169
+ }
170
+ finally {
171
+ try {
172
+ release();
173
+ }
174
+ catch (error) {
175
+ logger.warn('Storage layout migration: lock release failed', error instanceof Error ? error.message : String(error));
176
+ }
177
+ }
178
+ logger.log('Storage layout migration complete', {
179
+ configMoved,
180
+ logsMoved,
181
+ locksRemoved
182
+ });
183
+ }
184
+ export function migrateSafeFilesIfNeeded() {
185
+ try {
186
+ runMigration();
187
+ }
188
+ catch (error) {
189
+ logger.warn('Storage layout migration failed non-fatally', error instanceof Error ? error.message : String(error));
190
+ }
191
+ }
package/dist/plugin.js CHANGED
@@ -7,6 +7,7 @@ import { AccountRepository } from './infrastructure/database/account-repository.
7
7
  import { AccountManager } from './plugin/accounts.js';
8
8
  import { bootstrapAuthIfNeeded } from './plugin/auth-bootstrap.js';
9
9
  import { loadConfig } from './plugin/config/index.js';
10
+ import { migrateSafeFilesIfNeeded } from './plugin/storage/migrate-layout.js';
10
11
  const KIRO_PROVIDER_ID = 'kiro-auth';
11
12
  let activeKeepAliveController = null;
12
13
  let keepAliveTeardownRegistered = false;
@@ -34,6 +35,12 @@ export function __getActiveKeepAliveControllerForTest() {
34
35
  return activeKeepAliveController;
35
36
  }
36
37
  export const createKiroPlugin = (id) => async ({ client, directory }) => {
38
+ try {
39
+ migrateSafeFilesIfNeeded();
40
+ }
41
+ catch (error) {
42
+ console.warn('Kiro storage layout migration failed non-fatally:', error instanceof Error ? error.message : String(error));
43
+ }
37
44
  const config = loadConfig(directory);
38
45
  const showToast = (message, variant) => {
39
46
  client.tui.showToast({ body: { message, variant } }).catch(() => { });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sunerpy/opencode-kiro-auth",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "OpenCode plugin for AWS Kiro (CodeWhisperer) providing access to Claude models",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",