From 37adc65a1290e03997afd8230a112d464ec4ac3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Wed, 12 Aug 2026 03:58:13 +0000 Subject: [PATCH] delete the spent one-shot scripts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Eight scripts in scripts/ that nothing references and that mostly can no longer run. Kept in history; none of them is recoverable knowledge that isn't already in the code they migrated to. Three could not run at all against the current database: migrate-items-to-files.ts SELECT * FROM tasks — that table was dropped when items became files reset-user-data.ts deletes chat_sessions, chat_groups, projects; none exists. It has no transaction, so it would wipe user_settings, user_state, user_integrations and dock_configs and THEN throw. A half-wiped account is worse than no script. It also misses chat_session_events, which is where chat state actually lives now. add-email-dock-user2.ts one-time, hardcoded to user 2, seeds a dock containing /projects The rest are spent migrations whose destination is now the only implementation: migrate-auth-to-pg.ts JSON -> Postgres, 2026-02 migrate-pg-to-files.ts Postgres -> JSON, the other leg of the same abandoned round trip migrate-server-settings-to-pg.ts 2026-02 migrate-emails-to-sqlite.ts backfill into the email sidecar's store, 2026-07-31 seed-imap-uids.ts the sidecar writes imap_lastuid/imap_uidvalidity itself now (sidecar/email/gmail-api.ts:533-535) Kept, and why, since "unreferenced" was not the test: rebuild-soulseek-tree.ts is reusable by construction — it runs the same buildTree the sidecar's ingest runs, so it answers any future change in tree shape. reindex-music.ts is named in sidecar/music/index.ts:447. provision-user-dirs.ts shares USER_DIRS with data-path.ts. cleanup-desktop.sh and officer-set-display.sh are called by setup-desktop.sh. Co-Authored-By: Claude Opus 5 --- scripts/add-email-dock-user2.ts | 30 ----- scripts/migrate-auth-to-pg.ts | 137 ------------------- scripts/migrate-emails-to-sqlite.ts | 81 ------------ scripts/migrate-items-to-files.ts | 112 ---------------- scripts/migrate-pg-to-files.ts | 79 ----------- scripts/migrate-server-settings-to-pg.ts | 42 ------ scripts/reset-user-data.ts | 161 ----------------------- scripts/seed-imap-uids.ts | 88 ------------- 8 files changed, 730 deletions(-) delete mode 100644 scripts/add-email-dock-user2.ts delete mode 100644 scripts/migrate-auth-to-pg.ts delete mode 100644 scripts/migrate-emails-to-sqlite.ts delete mode 100644 scripts/migrate-items-to-files.ts delete mode 100644 scripts/migrate-pg-to-files.ts delete mode 100644 scripts/migrate-server-settings-to-pg.ts delete mode 100644 scripts/reset-user-data.ts delete mode 100644 scripts/seed-imap-uids.ts diff --git a/scripts/add-email-dock-user2.ts b/scripts/add-email-dock-user2.ts deleted file mode 100644 index 61b2fd47..00000000 --- a/scripts/add-email-dock-user2.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * One-time script: add /email to user 2's dock - * - * Usage: bun run scripts/add-email-dock-user2.ts - */ - -import { getDockPaths, setDockPaths } from 'officerdb'; - -const USER_ID = 2; -const DEFAULT_PATHS = ['/', '/files', '/automation', '/projects', '/dashboards', '/chat']; - -async function main() { - const existing = await getDockPaths(USER_ID); - const paths = existing ?? DEFAULT_PATHS; - - if (paths.includes('/email')) { - console.log(`[dock] User ${USER_ID} already has /email in dock`); - } else { - paths.push('/email'); - await setDockPaths(USER_ID, paths); - console.log(`[dock] Added /email to user ${USER_ID}'s dock: ${JSON.stringify(paths)}`); - } - - process.exit(0); -} - -main().catch((err) => { - console.error('[dock] Failed:', err); - process.exit(1); -}); diff --git a/scripts/migrate-auth-to-pg.ts b/scripts/migrate-auth-to-pg.ts deleted file mode 100644 index 5f1f469e..00000000 --- a/scripts/migrate-auth-to-pg.ts +++ /dev/null @@ -1,137 +0,0 @@ -/** - * Migration script: auth data from JSON files → PostgreSQL - * - * Migrates: - * - users.json → users table - * - passkeys.json → passkeys table (email → userId FK) - * - token-blacklist.json → token_blacklist table - * - * Usage: bun run scripts/migrate-auth-to-pg.ts - */ - -import { join } from 'node:path'; -import { db } from 'officerdb/db'; -import { users, passkeys, tokenBlacklist } from 'officerdb/schema'; - -const DATA_PATH = process.env.DATA_PATH ?? join(process.cwd(), 'data'); -const AUTH_DIR = join(DATA_PATH, 'auth'); - -type OldUser = { - id: number; - email: string; - password: string | null; - role: string; - status: string; - name: string | null; - username: string | null; - avatar: string | null; - passwordChangedAt: number | null; -}; - -type OldPasskey = { - id: number; - email: string; - origin: string | null; - credentialId: string | null; - publicKey: string | null; - counter: number; -}; - -type OldBlacklistEntry = { - jti: string; - expiresAt: number; -}; - -async function readJson(path: string, fallback: T): Promise { - try { - const file = Bun.file(path); - if (!(await file.exists())) return fallback; - return (await file.json()) as T; - } catch { - return fallback; - } -} - -async function migrate() { - console.log(`[migrate] Reading JSON files from ${AUTH_DIR}`); - - const oldUsers = await readJson(join(AUTH_DIR, 'users.json'), []); - const oldPasskeys = await readJson(join(AUTH_DIR, 'passkeys.json'), []); - const oldBlacklist = await readJson(join(AUTH_DIR, 'token-blacklist.json'), []); - - console.log(`[migrate] Found: ${oldUsers.length} users, ${oldPasskeys.length} passkeys, ${oldBlacklist.length} blacklisted tokens`); - - if (oldUsers.length === 0) { - console.log('[migrate] No users to migrate. Done.'); - process.exit(0); - } - - // Build email → userId map for passkey migration - const emailToUserId = new Map(); - - // Migrate users - console.log('[migrate] Migrating users...'); - for (const u of oldUsers) { - const [inserted] = await db - .insert(users) - .values({ - email: u.email, - password: u.password, - status: u.status as 'Unverified' | 'Active' | 'Prospect' | 'Invited' | 'Blocked' | 'Banned' | 'Deleted', - name: u.name, - username: u.username, - avatar: u.avatar, - passwordChangedAt: u.passwordChangedAt ? new Date(u.passwordChangedAt) : null, - }) - .returning(); - - emailToUserId.set(u.email, inserted!.id); - console.log(` [user] ${u.email} (old id=${u.id} → new id=${inserted!.id})`); - } - - // Migrate passkeys - if (oldPasskeys.length > 0) { - console.log('[migrate] Migrating passkeys...'); - for (const p of oldPasskeys) { - const userId = emailToUserId.get(p.email); - if (!userId) { - console.warn(` [passkey] Skipping passkey for unknown email: ${p.email}`); - continue; - } - - await db.insert(passkeys).values({ - userId, - origin: p.origin, - credentialId: p.credentialId, - publicKey: p.publicKey, - counter: p.counter, - }); - console.log(` [passkey] ${p.email} / ${p.origin}`); - } - } - - // Migrate token blacklist - if (oldBlacklist.length > 0) { - const now = Math.floor(Date.now() / 1000); - const active = oldBlacklist.filter((b) => b.expiresAt >= now); - console.log(`[migrate] Migrating ${active.length} active blacklisted tokens (${oldBlacklist.length - active.length} expired, skipped)...`); - - for (const b of active) { - await db - .insert(tokenBlacklist) - .values({ - jti: b.jti, - expiresAt: new Date(b.expiresAt * 1000), - }) - .onConflictDoNothing(); - } - } - - console.log('[migrate] Done!'); - process.exit(0); -} - -migrate().catch((err) => { - console.error('[migrate] Failed:', err); - process.exit(1); -}); diff --git a/scripts/migrate-emails-to-sqlite.ts b/scripts/migrate-emails-to-sqlite.ts deleted file mode 100644 index aa1a1bc2..00000000 --- a/scripts/migrate-emails-to-sqlite.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { readdirSync, readFileSync } from 'node:fs'; -import { join } from 'node:path'; -import { openEmailDb, upsertFromRawEml, setSyncMeta } from '../src/servers/sidecar/email/store'; - -const DATA_PATH = process.env.DATA_PATH ?? join(process.cwd(), 'data'); - -// Find all user directories that have Gmail emails -const targetEmail = process.argv[2]; - -if (targetEmail) { - migrate(targetEmail); -} else { - const entries = readdirSync(DATA_PATH, { withFileTypes: true }); - for (const entry of entries) { - if (!entry.isDirectory()) continue; - if (!entry.name.includes('@')) continue; - const emailDir = join(DATA_PATH, entry.name, 'Gmail', 'emails'); - try { - const files = readdirSync(emailDir).filter((f) => f.endsWith('.eml')); - if (files.length > 0) migrate(entry.name); - } catch { - // no Gmail dir for this user - } - } -} - -function migrate(userEmail: string): void { - console.log(`Migrating ${userEmail}...`); - const emailDir = join(DATA_PATH, userEmail, 'Gmail', 'emails'); - // Obsolete one-off migration (old .eml-file store → SQLite); kept only to compile. - const db = openEmailDb(userEmail, userEmail); - - let filenames: string[]; - try { - filenames = readdirSync(emailDir).filter((f) => f.endsWith('.eml')); - } catch { - console.log(' No .eml files found'); - db.close(); - return; - } - - const existingIds = new Set(); - const rows = db.query('SELECT id FROM emails').all() as Array<{ id: string }>; - for (const row of rows) existingIds.add(row.id); - - let added = 0; - let skipped = 0; - let errors = 0; - - db.exec('BEGIN'); - try { - for (const filename of filenames) { - const id = filename.replace(/\.eml$/, ''); - if (existingIds.has(id)) { - skipped++; - continue; - } - try { - const raw = readFileSync(join(emailDir, filename), 'utf-8'); - upsertFromRawEml({ db, id, raw, integration: 'gmail', emailAccount: userEmail, labels: ['INBOX'] }); - added++; - } catch { - errors++; - } - } - db.exec('COMMIT'); - } catch (err) { - db.exec('ROLLBACK'); - throw err; - } - - console.log(` ${filenames.length} .eml files — ${added} added, ${skipped} skipped, ${errors} errors`); - - // Store the latest email date so the next sync only fetches emails after it - const row = db.query('SELECT date FROM emails ORDER BY date DESC LIMIT 1').get() as { date: string } | null; - if (row?.date) { - setSyncMeta(db, 'last_sync_date', row.date); - console.log(` Stored last_sync_date: ${row.date}`); - } - db.close(); -} diff --git a/scripts/migrate-items-to-files.ts b/scripts/migrate-items-to-files.ts deleted file mode 100644 index 07d26dd1..00000000 --- a/scripts/migrate-items-to-files.ts +++ /dev/null @@ -1,112 +0,0 @@ -/** - * One-time migration: consolidate every agent item into the flat, file-based store - * ($OFFICER_ITEMS_DIR) and export the DB-backed `tasks` table to TASK.md files. - * - * Idempotent — safe to re-run. Run this BEFORE applying the drop-tables DB migration - * (it reads the `tasks` table, which still exists until that migration runs). - * - * Sources, in precedence order (later overwrites earlier on a dirName collision): - * - tasks: officer_db.tasks rows (native → global → user) - * - skills / tools / processes / extensions: $DATA_PATH/ then $DATA_PATH// - * - tools: marketplace registry tools not already present (archive safety) - * - * Usage: bun run scripts/migrate-items-to-files.ts - */ - -import { join, resolve } from 'node:path'; -import { readdir, cp } from 'node:fs/promises'; -import { existsSync } from 'node:fs'; -import { db } from 'officerdb/db'; -import { sql } from 'drizzle-orm'; -import { itemsDir, ensureItemDirs, DATA_PATH, OFFICER_ITEMS_DIR, type ItemType } from '../src/servers/data-path'; -import { importTask } from '../src/servers/api/tasks/task-files'; - -ensureItemDirs(); -console.log(`Target store: ${OFFICER_ITEMS_DIR}`); - -async function listSubdirs(dir: string): Promise { - try { - return (await readdir(dir, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name); - } catch { - return []; - } -} - -// ── 1. Tasks: Postgres → TASK.md files ── -// Order native → global → user so user/global overwrite native on a dirName collision. -const scopeRank = (s: string) => (s === 'user' ? 2 : s === 'global' ? 1 : 0); - -console.log('\n── Tasks (DB → files) ──'); -let taskRows: Record[] = []; -try { - taskRows = (await db.execute(sql.raw('SELECT * FROM tasks'))) as unknown as Record[]; -} catch (err) { - console.log(` could not read tasks table (already dropped?): ${err instanceof Error ? err.message : err}`); -} -taskRows.sort((a, b) => scopeRank(String(a.scope)) - scopeRank(String(b.scope))); - -for (const row of taskRows) { - const dirName = String(row.dir_name); - await importTask(dirName, { - name: String(row.name ?? dirName), - description: row.description == null ? null : String(row.description), - version: Number(row.version) || 1, - mode: String(row.mode ?? 'agentic'), - language: row.language == null ? null : String(row.language), - args: (row.args as string[] | null) ?? null, - tags: (row.tags as string[] | null) ?? null, - tools: (row.tools as string[] | null) ?? null, - skills: (row.skills as string[] | null) ?? null, - inputs: row.inputs ?? null, - outputs: row.outputs ?? null, - dependencies: row.dependencies ?? null, - config: row.config ?? null, - trigger: row.trigger ?? null, - body: row.body == null ? '' : String(row.body), - implementation: row.implementation == null ? null : String(row.implementation), - }); - console.log(` ${dirName} (${row.scope})`); -} -console.log(` ${taskRows.length} task file(s) written`); - -// ── 2. On-disk items → flat store ── -const DISK_TYPES: ItemType[] = ['skills', 'tools', 'processes', 'extensions']; - -async function copyItemsFrom(srcTypeDir: string, type: ItemType): Promise { - let n = 0; - for (const name of await listSubdirs(srcTypeDir)) { - await cp(join(srcTypeDir, name), join(itemsDir(type), name), { recursive: true, force: true }); - n++; - } - return n; -} - -console.log('\n── Disk items (DATA_PATH → flat store) ──'); -const emailDirs = (await listSubdirs(DATA_PATH)).filter((n) => n.includes('@')); - -for (const type of DISK_TYPES) { - let n = await copyItemsFrom(join(DATA_PATH, type), type); // global - for (const email of emailDirs) n += await copyItemsFrom(join(DATA_PATH, email, type), type); // user (overwrites) - console.log(` ${type}: ${n} item(s) copied`); -} - -// ── 3. Marketplace registry tools not already present (archive safety) ── -const MARKETPLACE_REGISTRY = process.env.MARKETPLACE_REGISTRY ?? resolve(import.meta.dir, '../../marketplace/registry'); -console.log(`\n── Marketplace registry (${MARKETPLACE_REGISTRY}) ──`); -if (existsSync(MARKETPLACE_REGISTRY)) { - let n = 0; - for (const name of await listSubdirs(join(MARKETPLACE_REGISTRY, 'tools'))) { - const target = join(itemsDir('tools'), name); - if (existsSync(target)) continue; // don't clobber a synced/user version - await cp(join(MARKETPLACE_REGISTRY, 'tools', name), target, { recursive: true }); - n++; - console.log(` tool ${name} (from registry)`); - } - console.log(` ${n} registry tool(s) added`); - console.log(' registry tasks come from the DB export above (native scope) — skipped here'); -} else { - console.log(' registry not found, skipping'); -} - -console.log('\nDone. Verify counts in the UI, then apply the drop-tables DB migration.'); -process.exit(0); diff --git a/scripts/migrate-pg-to-files.ts b/scripts/migrate-pg-to-files.ts deleted file mode 100644 index 683c76fb..00000000 --- a/scripts/migrate-pg-to-files.ts +++ /dev/null @@ -1,79 +0,0 @@ -/** - * One-time migration: PostgreSQL auth tables → JSON files - * - * Usage: - * POSTGRES_URL="postgres://..." bun run scripts/migrate-pg-to-files.ts - * - * Reads users and passkeys from Postgres, writes JSON files to {DATA_PATH}/auth/. - * Safe to run multiple times (overwrites files). - */ - -import { join } from 'node:path'; -import { mkdir } from 'node:fs/promises'; -import postgres from 'postgres'; - -const POSTGRES_URL = process.env.POSTGRES_URL; -if (!POSTGRES_URL) { - console.error('POSTGRES_URL env var is required'); - process.exit(1); -} - -const DATA_PATH = process.env.DATA_PATH ?? join(process.cwd(), 'data'); -const AUTH_DIR = join(DATA_PATH, 'auth'); - -const sql = postgres(POSTGRES_URL); - -try { - await mkdir(AUTH_DIR, { recursive: true }); - - const users = await sql`SELECT id, email, password, role, status, name, username, avatar, password_changed_at FROM users ORDER BY id`; - const passkeys = await sql`SELECT id, email, origin, credential_id, public_key, counter FROM passkeys ORDER BY id`; - - const mappedUsers = users.map((u) => ({ - id: Number(u.id), - email: u.email, - password: u.password ?? null, - role: u.role ?? 'Member', - status: u.status ?? 'Unverified', - name: u.name ?? null, - username: u.username ?? null, - avatar: u.avatar ?? null, - passwordChangedAt: u.password_changed_at ? Number(u.password_changed_at) : null, - })); - - const mappedPasskeys = passkeys.map((p) => ({ - id: Number(p.id), - email: p.email, - origin: p.origin ?? null, - credentialId: p.credential_id ?? null, - publicKey: p.public_key ?? null, - counter: Number(p.counter ?? 0), - })); - - const maxUserId = mappedUsers.reduce((max, u) => Math.max(max, u.id), 0); - const maxPasskeyId = mappedPasskeys.reduce((max, p) => Math.max(max, p.id), 0); - - const meta = { - nextUserId: maxUserId + 1, - nextPasskeyId: maxPasskeyId + 1, - }; - - const write = (file: string, data: unknown) => Bun.write(join(AUTH_DIR, file), JSON.stringify(data, null, 2)); - - await Promise.all([ - write('users.json', mappedUsers), - write('passkeys.json', mappedPasskeys), - write('passkey-challenges.json', []), - write('token-blacklist.json', []), - write('meta.json', meta), - ]); - - console.log(`Migrated ${mappedUsers.length} users, ${mappedPasskeys.length} passkeys`); - console.log(`Files written to ${AUTH_DIR}`); - console.log(`meta: nextUserId=${meta.nextUserId}, nextPasskeyId=${meta.nextPasskeyId}`); -} catch (err) { - console.error('Migration failed:', err); - process.exit(1); -} finally { - await sql.end(); -} diff --git a/scripts/migrate-server-settings-to-pg.ts b/scripts/migrate-server-settings-to-pg.ts deleted file mode 100644 index e7ef3f8f..00000000 --- a/scripts/migrate-server-settings-to-pg.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Migration script: server-settings.json → PostgreSQL server_config table - * - * Usage: bun run scripts/migrate-server-settings-to-pg.ts - */ - -import { join } from 'node:path'; -import { writeServerSettings } from 'officerdb'; - -const DATA_PATH = process.env.DATA_PATH ?? join(process.cwd(), 'data'); -const settingsPath = join(DATA_PATH, 'server-settings', 'server-settings.json'); - -async function migrate() { - console.log(`[migrate] Reading ${settingsPath}`); - - const file = Bun.file(settingsPath); - if (!(await file.exists())) { - console.log('[migrate] No server-settings.json found. Done.'); - process.exit(0); - } - - let settings: Record; - try { - settings = await file.json(); - } catch { - console.log('[migrate] Could not parse server-settings.json. Done.'); - process.exit(0); - } - - const keys = Object.keys(settings); - console.log(`[migrate] Found ${keys.length} keys: ${keys.join(', ')}`); - - await writeServerSettings(settings); - console.log('[migrate] Written to server_config table.'); - console.log('[migrate] Done!'); - process.exit(0); -} - -migrate().catch((err) => { - console.error('[migrate] Failed:', err); - process.exit(1); -}); diff --git a/scripts/reset-user-data.ts b/scripts/reset-user-data.ts deleted file mode 100644 index 15a90f85..00000000 --- a/scripts/reset-user-data.ts +++ /dev/null @@ -1,161 +0,0 @@ -/** - * Reset all user data while keeping auth credentials. - * - * Deletes: - * - DB: user_settings, user_state, user_integrations, dock_configs, - * chat_sessions (cascades chat_messages), chat_groups, - * dashboards, screens, projects, - * task_logs, queue_jobs, terminal_containers - * - Filesystem: entire $DATA_PATH// directory - * (home, settings, state, dashboards, chat_sessions, emails.db, - * Gmail, skills, tools, tasks, processes, extensions, logs, cache, etc.) - * - Queue job files: $DATA_PATH/queue/jobs/*.json owned by user - * - Terminal containers map: removes user entry from terminal-containers.json - * - * Preserves: - * - users table row (account, password, role, status) - * - passkeys table rows - * - passkey_challenges, token_blacklist - * - * Usage: bun run scripts/reset-user-data.ts - * bun run scripts/reset-user-data.ts --yes (skip confirmation) - */ - -import { join } from 'node:path'; -import { rm, readdir, unlink } from 'node:fs/promises'; -import { db } from 'officerdb/db'; -import { users } from 'officerdb/schema'; -import { eq, sql } from 'drizzle-orm'; - -const DATA_PATH = process.env.DATA_PATH ?? join(process.cwd(), 'data'); - -const email = process.argv[2]; -const skipConfirm = process.argv.includes('--yes'); - -if (!email) { - console.error('Usage: bun run scripts/reset-user-data.ts [--yes]'); - process.exit(1); -} - -// ── Resolve user ── - -const [user] = await db.select({ id: users.id, email: users.email }).from(users).where(eq(users.email, email)); - -if (!user) { - console.error(`User not found: ${email}`); - process.exit(1); -} - -console.log(`\nUser: ${user.email} (id: ${user.id})`); -console.log(`Data dir: ${join(DATA_PATH, email)}`); -console.log('\nThis will delete ALL user data (settings, chats, dashboards, emails, home dir, etc.)'); -console.log('Auth credentials (account, passkeys) will be preserved.\n'); - -if (!skipConfirm) { - process.stdout.write('Continue? [y/N] '); - const response = await new Promise((resolve) => { - process.stdin.once('data', (data) => resolve(data.toString().trim())); - }); - if (response.toLowerCase() !== 'y') { - console.log('Aborted.'); - process.exit(0); - } -} - -const userId = user.id; - -// ── Database cleanup ── -// All these tables have ON DELETE CASCADE from users, but we don't want to delete the user. -// Delete explicitly by user_id. - -console.log('\n── Database ──'); - -const tables = [ - 'user_settings', - 'user_state', - 'user_integrations', - 'dock_configs', - 'chat_sessions', // cascades chat_messages - 'chat_groups', - 'dashboards', - 'screens', - 'projects', - 'task_logs', - 'queue_jobs', - 'terminal_containers', -]; - -for (const table of tables) { - const result = await db.execute(sql.raw(`DELETE FROM ${table} WHERE user_id = ${userId}`)); - const count = result.length ?? 0; - console.log(` ${table}: ${count} rows deleted`); -} - -// Agent items (skills, tools, tasks, processes, extensions) are now flat files in -// $OFFICER_ITEMS_DIR, shared and not user-owned — intentionally left untouched by a user reset. - -// ── Queue job files ── - -console.log('\n── Queue job files ──'); -const queueDir = join(DATA_PATH, 'queue', 'jobs'); -try { - const entries = await readdir(queueDir); - let deleted = 0; - for (const entry of entries) { - if (!entry.endsWith('.json')) continue; - try { - const file = Bun.file(join(queueDir, entry)); - const job = await file.json(); - if (job.userId === email) { - await unlink(join(queueDir, entry)); - deleted++; - } - } catch { - // skip unreadable files - } - } - console.log(` ${deleted} job files deleted`); -} catch { - console.log(' queue dir not found, skipping'); -} - -// ── Terminal containers map ── - -console.log('\n── Terminal containers ──'); -const containerMapPath = join(DATA_PATH, 'terminal-containers.json'); -try { - const file = Bun.file(containerMapPath); - if (await file.exists()) { - const map = await file.json(); - let changed = false; - for (const key of Object.keys(map)) { - if (key === email || map[key]?.email === email) { - delete map[key]; - changed = true; - } - } - if (changed) { - await Bun.write(containerMapPath, JSON.stringify(map, null, 2)); - console.log(' removed from terminal-containers.json'); - } else { - console.log(' no entry found'); - } - } -} catch { - console.log(' terminal-containers.json not found, skipping'); -} - -// ── Filesystem ── - -console.log('\n── Filesystem ──'); -const userDir = join(DATA_PATH, email); -try { - await rm(userDir, { recursive: true, force: true }); - console.log(` removed ${userDir}`); -} catch (err: unknown) { - const msg = err instanceof Error ? err.message : String(err); - console.log(` failed to remove ${userDir}: ${msg}`); -} - -console.log('\nDone. User auth preserved, all data wiped.'); -process.exit(0); diff --git a/scripts/seed-imap-uids.ts b/scripts/seed-imap-uids.ts deleted file mode 100644 index a23a9c2c..00000000 --- a/scripts/seed-imap-uids.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { ImapFlow } from 'imapflow'; -import { getUserByEmail, getUserIntegration, getServerIntegration } from 'officerdb'; -import { openEmailDb, setSyncMeta } from '../src/servers/sidecar/email/store'; - -const userEmail = process.argv[2]; -if (!userEmail) { - console.error('Usage: bun run scripts/seed-imap-uids.ts '); - process.exit(1); -} - -// ── Load credentials ── - -const dbUser = await getUserByEmail(userEmail); -if (!dbUser) throw new Error('User not found'); - -const userGoogle = await getUserIntegration(dbUser.id, 'google'); -const config = userGoogle?.config as Record | undefined; -if (!config?.accessToken) throw new Error('No OAuth tokens found'); - -// Refresh token if needed -let accessToken = config.accessToken as string; -const expiresAt = config.expiresAt as number | undefined; -if (!expiresAt || expiresAt < Date.now() + 60_000) { - console.log('Refreshing expired token...'); - const serverGoogle = await getServerIntegration('google'); - const serverConfig = serverGoogle?.config as Record; - const res = await fetch('https://oauth2.googleapis.com/token', { - method: 'POST', - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - body: new URLSearchParams({ - client_id: serverConfig.clientId as string, - client_secret: serverConfig.clientSecret as string, - refresh_token: config.refreshToken as string, - grant_type: 'refresh_token', - }), - }); - if (!res.ok) throw new Error(`Token refresh failed: ${await res.text()}`); - const data = (await res.json()) as { access_token: string }; - accessToken = data.access_token; -} - -// ── Connect IMAP ── - -const client = new ImapFlow({ - host: 'imap.gmail.com', - port: 993, - secure: true, - auth: { user: config.email as string, accessToken }, - logger: false, -}); - -await client.connect(); -console.log('Connected to IMAP'); - -const GMAIL_PREFIX_RE = /^\[(?:Gmail|Google Mail)\]\//; -const SKIP_SUFFIXES = new Set(['All Mail', 'Trash', 'Spam', 'Bin']); - -const folders = await client.list(); -const db = openEmailDb(userEmail, config.email as string); - -let seeded = 0; - -for (const folder of folders) { - const suffix = folder.path.replace(GMAIL_PREFIX_RE, ''); - const isGmailFolder = suffix !== folder.path; - if (isGmailFolder && SKIP_SUFFIXES.has(suffix)) continue; - if (folder.specialUse && ['\\Trash', '\\Junk', '\\All'].includes(folder.specialUse)) continue; - - try { - const status = await client.status(folder.path, { uidNext: true, uidValidity: true }); - const lastUid = (status.uidNext ?? 1) - 1; - const uidValidity = String(status.uidValidity); - - setSyncMeta(db, `imap_lastuid:${folder.path}`, String(lastUid)); - setSyncMeta(db, `imap_uidvalidity:${folder.path}`, uidValidity); - - console.log(` ${folder.path}: lastUid=${lastUid}, uidValidity=${uidValidity}`); - seeded++; - } catch (err) { - console.log(` ${folder.path}: skipped (${err instanceof Error ? err.message : err})`); - } -} - -db.close(); -await client.logout(); - -console.log(`\nSeeded ${seeded} folders. Next sync will only fetch new messages.`); -process.exit(0);