clementine-agent 1.1.29 → 1.1.31

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/dist/cli/index.js CHANGED
@@ -155,7 +155,7 @@ function ensureDataHome() {
155
155
  }
156
156
  }
157
157
  // ── Commands ─────────────────────────────────────────────────────────
158
- function cmdLaunch(options) {
158
+ async function cmdLaunch(options) {
159
159
  if (options.uninstall) {
160
160
  if (process.platform === 'darwin') {
161
161
  const plistPath = getLaunchdPlistPath();
@@ -197,6 +197,22 @@ function cmdLaunch(options) {
197
197
  }
198
198
  return;
199
199
  }
200
+ // Ensure data home exists so the wizard's sentinel write lands somewhere,
201
+ // then run the one-time keychain ACL repair wizard if applicable. This
202
+ // happens before --install so the launchd-spawned daemon (no TTY) inherits
203
+ // already-repaired entries; before --foreground so the user sees the
204
+ // prompt; and before the daemon spawn for the same reason.
205
+ ensureDataHome();
206
+ if (!options.skipWizard) {
207
+ try {
208
+ const { runFirstRunKeychainWizardIfNeeded } = await import('../config/keychain-first-run-wizard.js');
209
+ await runFirstRunKeychainWizardIfNeeded(BASE_DIR);
210
+ }
211
+ catch (err) {
212
+ // Wizard failure must never block launch — log and continue.
213
+ console.error(` ${'\x1b[0;90m'}keychain wizard skipped: ${err.message}${'\x1b[0m'}`);
214
+ }
215
+ }
200
216
  if (options.install) {
201
217
  if (process.platform === 'darwin') {
202
218
  const plistPath = getLaunchdPlistPath();
@@ -414,7 +430,7 @@ async function cmdRestart(options) {
414
430
  }
415
431
  }
416
432
  catch { /* dashboard module may not be available */ }
417
- cmdLaunch({ foreground: options.foreground });
433
+ await cmdLaunch({ foreground: options.foreground });
418
434
  if (dashboardWasRunning) {
419
435
  try {
420
436
  const { spawn: spawnProc } = await import('node:child_process');
@@ -1317,6 +1333,7 @@ async function cmdConfigHardenPermissions(opts) {
1317
1333
  // ── Config keychain-fix-acl ─────────────────────────────────────────
1318
1334
  async function cmdConfigKeychainFixAcl(opts) {
1319
1335
  const { listClementineKeychainEntries, fixAllClementineEntries } = await import('../config/keychain-fix-acl.js');
1336
+ const { markKeychainWizardDone } = await import('../config/keychain-first-run-wizard.js');
1320
1337
  const DIM = '\x1b[0;90m';
1321
1338
  const BOLD = '\x1b[1m';
1322
1339
  const GREEN = '\x1b[0;32m';
@@ -1332,6 +1349,8 @@ async function cmdConfigKeychainFixAcl(opts) {
1332
1349
  if (entries.length === 0) {
1333
1350
  console.log(` ${GREEN}Nothing to fix.${RESET}`);
1334
1351
  console.log();
1352
+ // No entries means the launch wizard has nothing to do either.
1353
+ markKeychainWizardDone(BASE_DIR);
1335
1354
  return;
1336
1355
  }
1337
1356
  if (opts.list) {
@@ -1365,6 +1384,9 @@ async function cmdConfigKeychainFixAcl(opts) {
1365
1384
  console.log(` ${DIM}Failed entries can be fixed manually in Keychain Access.app:${RESET}`);
1366
1385
  console.log(` ${DIM} search "clementine-agent" → double-click → Access Control → Allow all applications.${RESET}`);
1367
1386
  }
1387
+ // Always mark the launch wizard satisfied — user has explicitly decided
1388
+ // to deal with this via the manual command.
1389
+ markKeychainWizardDone(BASE_DIR);
1368
1390
  console.log();
1369
1391
  }
1370
1392
  // ── Analytics ────────────────────────────────────────────────────────
@@ -3144,13 +3166,15 @@ async function cmdUpdate(options) {
3144
3166
  console.log(` ${GREEN}OK${RESET} Daemon stopped`);
3145
3167
  }
3146
3168
  }
3147
- // Helper: if update fails after stopping daemon, relaunch before exiting
3148
- function failAndRestart(backupDir) {
3169
+ // Helper: if update fails after stopping daemon, relaunch before exiting.
3170
+ // Runs cmdLaunch with skipWizard so it doesn't try to prompt mid-recovery —
3171
+ // the wizard, if needed, fires on the user's next intentional `clementine launch`.
3172
+ async function failAndRestart(backupDir) {
3149
3173
  if (wasRunning) {
3150
3174
  console.log();
3151
3175
  console.log(` Restarting daemon (was running before update)...`);
3152
3176
  try {
3153
- cmdLaunch({});
3177
+ await cmdLaunch({ skipWizard: true });
3154
3178
  console.log(` ${GREEN}OK${RESET} Daemon restarted`);
3155
3179
  }
3156
3180
  catch {
@@ -3242,7 +3266,7 @@ async function cmdUpdate(options) {
3242
3266
  }
3243
3267
  catch { /* best effort */ }
3244
3268
  }
3245
- failAndRestart(backupDir);
3269
+ await failAndRestart(backupDir);
3246
3270
  }
3247
3271
  // 6. npm install
3248
3272
  console.log(` ${S()} Installing dependencies...`);
@@ -3255,7 +3279,7 @@ async function cmdUpdate(options) {
3255
3279
  }
3256
3280
  catch (err) {
3257
3281
  console.error(` ${RED}FAIL${RESET} npm install failed: ${String(err).slice(0, 200)}`);
3258
- failAndRestart(backupDir);
3282
+ await failAndRestart(backupDir);
3259
3283
  }
3260
3284
  // 6b. Rebuild native modules (better-sqlite3) for current Node version
3261
3285
  try {
@@ -3347,7 +3371,7 @@ async function cmdUpdate(options) {
3347
3371
  }
3348
3372
  catch (retryErr) {
3349
3373
  console.error(` ${RED}FAIL${RESET} Build failed after update: ${String(retryErr).slice(0, 200)}`);
3350
- failAndRestart(backupDir);
3374
+ await failAndRestart(backupDir);
3351
3375
  }
3352
3376
  }
3353
3377
  // 7b. Verify build output is fresh
@@ -3363,7 +3387,7 @@ async function cmdUpdate(options) {
3363
3387
  }
3364
3388
  catch (err) {
3365
3389
  console.error(` ${RED}FAIL${RESET} Clean rebuild failed: ${String(err).slice(0, 200)}`);
3366
- failAndRestart(backupDir);
3390
+ await failAndRestart(backupDir);
3367
3391
  }
3368
3392
  }
3369
3393
  }
@@ -3615,7 +3639,10 @@ async function cmdUpdate(options) {
3615
3639
  // Ensure build output is fully flushed before spawning new process
3616
3640
  execSync('sync', { stdio: 'pipe' });
3617
3641
  console.log(` ${S()} Restarting daemon...`);
3618
- cmdLaunch({});
3642
+ // Let the keychain wizard fire here too — for users on launchd who only
3643
+ // ever run `clementine update` (the daemon respawns automatically), this
3644
+ // is their one chance to see the prompt and repair legacy ACLs.
3645
+ await cmdLaunch({});
3619
3646
  }
3620
3647
  // 13. Post-restart health check — verify daemon started and channels connected
3621
3648
  if (options.restart || wasRunning) {
@@ -0,0 +1,26 @@
1
+ /**
2
+ * One-time interactive wizard that repairs ACLs on legacy clementine-agent
3
+ * keychain entries during `clementine launch`.
4
+ *
5
+ * Why this exists: entries written before commit 88cfd99 used
6
+ * `add-generic-password -T ''` (no apps pre-approved), so every Clementine
7
+ * read would trigger a per-app approval dialog. Newer writes use
8
+ * `-T /usr/bin/security` and read silently. Existing users have legacy
9
+ * entries that need a one-time partition-list repair to stop the prompt
10
+ * cascade.
11
+ *
12
+ * The manual fix is `clementine config keychain-fix-acl`. This wizard runs
13
+ * the same fix automatically on the next `clementine launch` (where we
14
+ * know we have a TTY for the macOS login-keychain password prompt), then
15
+ * writes a sentinel so we never prompt again on this machine.
16
+ *
17
+ * Skipped when:
18
+ * - non-darwin platform (no keychain),
19
+ * - non-TTY stdin (launchd, systemd, CI — no way to prompt),
20
+ * - sentinel already exists (already offered + decided),
21
+ * - no clementine-agent entries exist (nothing to repair).
22
+ */
23
+ /** Write the sentinel so the wizard skips on subsequent launches. */
24
+ export declare function markKeychainWizardDone(baseDir: string): void;
25
+ export declare function runFirstRunKeychainWizardIfNeeded(baseDir: string): Promise<void>;
26
+ //# sourceMappingURL=keychain-first-run-wizard.d.ts.map
@@ -0,0 +1,115 @@
1
+ /**
2
+ * One-time interactive wizard that repairs ACLs on legacy clementine-agent
3
+ * keychain entries during `clementine launch`.
4
+ *
5
+ * Why this exists: entries written before commit 88cfd99 used
6
+ * `add-generic-password -T ''` (no apps pre-approved), so every Clementine
7
+ * read would trigger a per-app approval dialog. Newer writes use
8
+ * `-T /usr/bin/security` and read silently. Existing users have legacy
9
+ * entries that need a one-time partition-list repair to stop the prompt
10
+ * cascade.
11
+ *
12
+ * The manual fix is `clementine config keychain-fix-acl`. This wizard runs
13
+ * the same fix automatically on the next `clementine launch` (where we
14
+ * know we have a TTY for the macOS login-keychain password prompt), then
15
+ * writes a sentinel so we never prompt again on this machine.
16
+ *
17
+ * Skipped when:
18
+ * - non-darwin platform (no keychain),
19
+ * - non-TTY stdin (launchd, systemd, CI — no way to prompt),
20
+ * - sentinel already exists (already offered + decided),
21
+ * - no clementine-agent entries exist (nothing to repair).
22
+ */
23
+ import { existsSync, writeFileSync } from 'node:fs';
24
+ import path from 'node:path';
25
+ import readline from 'node:readline/promises';
26
+ import { stdin as input, stdout as output } from 'node:process';
27
+ const SENTINEL_FILE = '.keychain-acl-wizard-done';
28
+ function sentinelPath(baseDir) {
29
+ return path.join(baseDir, SENTINEL_FILE);
30
+ }
31
+ /** Write the sentinel so the wizard skips on subsequent launches. */
32
+ export function markKeychainWizardDone(baseDir) {
33
+ try {
34
+ writeFileSync(sentinelPath(baseDir), new Date().toISOString() + '\n');
35
+ }
36
+ catch {
37
+ // Best-effort. If we can't write the sentinel the user gets re-prompted
38
+ // next launch — annoying but not broken.
39
+ }
40
+ }
41
+ export async function runFirstRunKeychainWizardIfNeeded(baseDir) {
42
+ if (process.platform !== 'darwin')
43
+ return;
44
+ if (!input.isTTY)
45
+ return;
46
+ if (existsSync(sentinelPath(baseDir)))
47
+ return;
48
+ const { listClementineKeychainEntries, fixAllClementineEntries } = await import('./keychain-fix-acl.js');
49
+ const entries = listClementineKeychainEntries().filter((e) => e.isClementine);
50
+ if (entries.length === 0) {
51
+ // Nothing to fix — write sentinel so we don't re-scan every launch.
52
+ markKeychainWizardDone(baseDir);
53
+ return;
54
+ }
55
+ const DIM = '\x1b[0;90m';
56
+ const BOLD = '\x1b[1m';
57
+ const YELLOW = '\x1b[0;33m';
58
+ const GREEN = '\x1b[0;32m';
59
+ const RED = '\x1b[0;31m';
60
+ const RESET = '\x1b[0m';
61
+ console.log();
62
+ console.log(` ${BOLD}One-time keychain setup${RESET}`);
63
+ console.log(` ${DIM}${entries.length} keychain entr${entries.length === 1 ? 'y' : 'ies'} from a previous version need an${RESET}`);
64
+ console.log(` ${DIM}access-control update so Clementine can read them${RESET}`);
65
+ console.log(` ${DIM}silently — otherwise macOS will prompt on every read.${RESET}`);
66
+ console.log();
67
+ console.log(` ${DIM}macOS will ask once for your login-keychain password.${RESET}`);
68
+ console.log(` ${DIM}After that, no more prompts. We won't ask again.${RESET}`);
69
+ console.log();
70
+ const rl = readline.createInterface({ input, output });
71
+ let answer;
72
+ try {
73
+ answer = (await rl.question(` Repair now? ${DIM}[Y/n]${RESET} `)).trim().toLowerCase();
74
+ }
75
+ finally {
76
+ rl.close();
77
+ }
78
+ if (answer === 'n' || answer === 'no') {
79
+ console.log();
80
+ console.log(` ${YELLOW}Skipped.${RESET} ${DIM}Run later with: clementine config keychain-fix-acl${RESET}`);
81
+ console.log();
82
+ markKeychainWizardDone(baseDir);
83
+ return;
84
+ }
85
+ console.log();
86
+ console.log(` ${BOLD}Repairing ACLs...${RESET}`);
87
+ console.log();
88
+ const results = fixAllClementineEntries();
89
+ let okCount = 0;
90
+ let failCount = 0;
91
+ for (const r of results) {
92
+ if (r.status === 'fixed') {
93
+ console.log(` ${GREEN}✓${RESET} ${r.account}`);
94
+ okCount++;
95
+ }
96
+ else if (r.status === 'failed') {
97
+ console.log(` ${RED}✗${RESET} ${r.account} ${DIM}— ${r.error ?? 'unknown'}${RESET}`);
98
+ failCount++;
99
+ }
100
+ }
101
+ console.log();
102
+ if (failCount === 0) {
103
+ console.log(` ${GREEN}Done — ${okCount} entr${okCount === 1 ? 'y' : 'ies'} repaired.${RESET} ${DIM}Future reads silent.${RESET}`);
104
+ }
105
+ else {
106
+ console.log(` ${YELLOW}${okCount} fixed, ${failCount} failed.${RESET}`);
107
+ console.log(` ${DIM}Failed entries can be fixed manually in Keychain Access.app:${RESET}`);
108
+ console.log(` ${DIM} search "clementine-agent" → right-click → Get Info → Access Control.${RESET}`);
109
+ }
110
+ console.log();
111
+ // Always mark done — even on partial failure we don't want to re-prompt
112
+ // every launch. The user can re-run the manual command if they want.
113
+ markKeychainWizardDone(baseDir);
114
+ }
115
+ //# sourceMappingURL=keychain-first-run-wizard.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clementine-agent",
3
- "version": "1.1.29",
3
+ "version": "1.1.31",
4
4
  "description": "Clementine — Personal AI Assistant (TypeScript)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",