@sunerpy/opencode-kiro-auth 0.3.0 → 0.4.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
@@ -136,6 +136,13 @@ avoid a hot loop.
136
136
  choose "Remove a Kiro account (N stored)", then pick the account from the
137
137
  dropdown (or cancel).
138
138
 
139
+ > **Note:** removal is persistent. Once you remove an account it stays
140
+ > removed across restarts and Kiro CLI auto-sync — it won't come back on its
141
+ > own. To re-add it, just log in with that account again via
142
+ > `opencode auth login`. See
143
+ > [Removing accounts & the removal tombstone](docs/CONFIGURATION.md#removing-accounts--the-removal-tombstone)
144
+ > for how this works.
145
+
139
146
  Full config key reference: [docs/CONFIGURATION.md](docs/CONFIGURATION.md).
140
147
 
141
148
  ## Usage display
@@ -3,6 +3,7 @@ import { extractRegionFromArn, normalizeRegion } from '../../constants.js';
3
3
  import { authorizeKiroIDC, pollKiroIDCToken } from '../../kiro/oauth-idc.js';
4
4
  import { createDeterministicAccountId } from '../../plugin/accounts.js';
5
5
  import * as logger from '../../plugin/logger.js';
6
+ import { kiroDb } from '../../plugin/storage/sqlite.js';
6
7
  import { makePlaceholderEmail } from '../../plugin/sync/kiro-cli-parser.js';
7
8
  import { readActiveProfileArnFromKiroCli } from '../../plugin/sync/kiro-cli-profile.js';
8
9
  import { fetchUsageLimits } from '../../plugin/usage.js';
@@ -156,6 +157,8 @@ export class IdcAuthMethod {
156
157
  };
157
158
  await this.repository.save(acc);
158
159
  this.accountManager?.addAccount?.(acc);
160
+ // clear tombstone ONLY on deliberate user login; never in auto-sync (would revive removed accounts)
161
+ await kiroDb.clearRemovedAccount(id);
159
162
  return { type: 'success', key: token.accessToken };
160
163
  }
161
164
  catch (e) {
@@ -160,6 +160,11 @@ export class AccountManager {
160
160
  email: a.email,
161
161
  error: e instanceof Error ? e.message : String(e)
162
162
  }));
163
+ kiroDb.addRemovedAccount(a.id).catch((e) => logger.warn('DB write failed', {
164
+ method: 'removeAccount:tombstone',
165
+ email: a.email,
166
+ error: e instanceof Error ? e.message : String(e)
167
+ }));
163
168
  if (this.accounts.length === 0)
164
169
  this.cursor = 0;
165
170
  else if (this.cursor >= this.accounts.length)
@@ -5,6 +5,7 @@ export function runMigrations(db) {
5
5
  migrateStartUrlColumn(db);
6
6
  migrateOidcRegionColumn(db);
7
7
  migrateDropRefreshTokenUniqueIndex(db);
8
+ migrateRemovedAccountsTable(db);
8
9
  }
9
10
  function migrateToUniqueRefreshToken(db) {
10
11
  const hasIndex = db
@@ -153,3 +154,8 @@ function migrateDropRefreshTokenUniqueIndex(db) {
153
154
  }
154
155
  }
155
156
  }
157
+ function migrateRemovedAccountsTable(db) {
158
+ // Tombstone table: records account ids the user explicitly removed via the auth menu,
159
+ // so kiro-cli auto-sync does not silently re-import them. Cleared only on deliberate login.
160
+ db.exec('CREATE TABLE IF NOT EXISTS removed_accounts (id TEXT PRIMARY KEY, removed_at INTEGER NOT NULL)');
161
+ }
@@ -10,6 +10,10 @@ export declare class KiroDatabase {
10
10
  upsertAccount(acc: ManagedAccount): Promise<void>;
11
11
  batchUpsertAccounts(accounts: ManagedAccount[]): Promise<void>;
12
12
  deleteAccount(id: string): Promise<void>;
13
+ addRemovedAccount(id: string): Promise<void>;
14
+ isAccountRemoved(id: string): Promise<boolean>;
15
+ clearRemovedAccount(id: string): Promise<void>;
16
+ listRemovedAccounts(): Promise<string[]>;
13
17
  markAccountsUnhealthy(ids: string[], reason: string): Promise<void>;
14
18
  private rowToAccount;
15
19
  close(): void;
@@ -103,6 +103,24 @@ export class KiroDatabase {
103
103
  this.db.prepare('DELETE FROM accounts WHERE id = ?').run(id);
104
104
  });
105
105
  }
106
+ async addRemovedAccount(id) {
107
+ await withDatabaseLock(this.path, async () => {
108
+ this.db
109
+ .prepare('INSERT OR REPLACE INTO removed_accounts (id, removed_at) VALUES (?, ?)')
110
+ .run(id, Date.now());
111
+ });
112
+ }
113
+ async isAccountRemoved(id) {
114
+ return !!this.db.prepare('SELECT id FROM removed_accounts WHERE id = ?').get(id);
115
+ }
116
+ async clearRemovedAccount(id) {
117
+ await withDatabaseLock(this.path, async () => {
118
+ this.db.prepare('DELETE FROM removed_accounts WHERE id = ?').run(id);
119
+ });
120
+ }
121
+ async listRemovedAccounts() {
122
+ return this.db.prepare('SELECT id FROM removed_accounts').all().map((r) => r.id);
123
+ }
106
124
  async markAccountsUnhealthy(ids, reason) {
107
125
  if (ids.length === 0)
108
126
  return;
@@ -33,6 +33,7 @@ export async function syncFromKiroCli() {
33
33
  const deviceReg = safeJsonParse(deviceRegRow?.value);
34
34
  const regCreds = deviceReg ? findClientCredsRecursive(deviceReg) : {};
35
35
  const syncedAccounts = [];
36
+ let skippedRemoved = 0;
36
37
  for (const row of rows) {
37
38
  if (row.key.includes(':token')) {
38
39
  const data = safeJsonParse(row.value);
@@ -122,7 +123,10 @@ export async function syncFromKiroCli() {
122
123
  const placeholderId = createDeterministicAccountId(placeholderEmail, authMethod, clientId, profileArn);
123
124
  if (placeholderId !== id) {
124
125
  const placeholderRow = all.find((a) => a.id === placeholderId);
125
- if (placeholderRow) {
126
+ if (placeholderRow && (await kiroDb.isAccountRemoved(placeholderId))) {
127
+ skippedRemoved++;
128
+ }
129
+ else if (placeholderRow) {
126
130
  await kiroDb.upsertAccount({
127
131
  id: placeholderId,
128
132
  email: placeholderRow.email,
@@ -147,6 +151,10 @@ export async function syncFromKiroCli() {
147
151
  }
148
152
  }
149
153
  }
154
+ if (await kiroDb.isAccountRemoved(id)) {
155
+ skippedRemoved++;
156
+ continue;
157
+ }
150
158
  await kiroDb.upsertAccount({
151
159
  id,
152
160
  email: resolvedEmail,
@@ -181,6 +189,7 @@ export async function syncFromKiroCli() {
181
189
  await kiroDb.markAccountsUnhealthy(staleIds, STALE_CLI_ACCOUNT_REASON);
182
190
  logger.warn('Kiro CLI sync: deactivated stale cached accounts', { count: staleIds.length });
183
191
  }
192
+ logger.log('Kiro CLI sync: done', { synced: syncedAccounts.length, skippedRemoved });
184
193
  cliDb.close();
185
194
  }
186
195
  catch (e) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sunerpy/opencode-kiro-auth",
3
- "version": "0.3.0",
3
+ "version": "0.4.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",