clementine-agent 1.1.28 → 1.1.30

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,7 @@ 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
+ await cmdLaunch({ skipWizard: true });
3619
3643
  }
3620
3644
  // 13. Post-restart health check — verify daemon started and channels connected
3621
3645
  if (options.restart || wasRunning) {
@@ -3862,20 +3886,175 @@ const siCmd = program
3862
3886
  .description('Manage Clementine self-improvement');
3863
3887
  siCmd
3864
3888
  .command('status')
3865
- .description('Show self-improvement state and baseline metrics')
3866
- .action(async () => {
3889
+ .description('Show self-improvement health last cycle, infra errors, per-agent runs, recent activity')
3890
+ .option('--json', 'Emit machine-readable JSON')
3891
+ .action(async (opts) => {
3892
+ const BOLD = '\x1b[1m';
3893
+ const DIM = '\x1b[0;90m';
3894
+ const GREEN = '\x1b[0;32m';
3895
+ const YELLOW = '\x1b[1;33m';
3896
+ const RED = '\x1b[0;31m';
3897
+ const CYAN = '\x1b[0;36m';
3898
+ const RESET = '\x1b[0m';
3867
3899
  try {
3900
+ process.env.CLEMENTINE_HOME = BASE_DIR;
3868
3901
  const { SelfImproveLoop } = await import('../agent/self-improve.js');
3869
3902
  const { PersonalAssistant } = await import('../agent/assistant.js');
3870
3903
  const assistant = new PersonalAssistant();
3871
3904
  const loop = new SelfImproveLoop(assistant);
3872
3905
  const state = loop.loadState();
3873
- const m = state.baselineMetrics;
3874
- console.log(`Status: ${state.status}`);
3875
- console.log(`Last run: ${state.lastRunAt || 'never'}`);
3876
- console.log(`Total experiments: ${state.totalExperiments}`);
3877
- console.log(`Pending approvals: ${state.pendingApprovals}`);
3878
- console.log(`Baseline Feedback: ${(m.feedbackPositiveRatio * 100).toFixed(0)}% positive, Cron: ${(m.cronSuccessRate * 100).toFixed(0)}% success, Quality: ${m.avgResponseQuality.toFixed(2)}`);
3906
+ const log = loop.loadExperimentLog();
3907
+ const pending = loop.getPendingChanges();
3908
+ // Compute "last successful cycle" — most recent log entry that wasn't
3909
+ // a plateau record or pure infra failure. Different from lastRunAt
3910
+ // (which moves on every attempt, even crashed ones).
3911
+ const lastSuccessful = [...log].reverse().find(e => e.area !== 'soul' || e.hypothesis !== 'No new hypothesis — diversity constraint exhausted');
3912
+ const nowMs = Date.now();
3913
+ const formatAge = (iso) => {
3914
+ if (!iso)
3915
+ return 'never';
3916
+ const ms = nowMs - Date.parse(iso);
3917
+ const h = Math.floor(ms / 3_600_000);
3918
+ if (h < 1)
3919
+ return `${Math.floor(ms / 60_000)}m ago`;
3920
+ if (h < 48)
3921
+ return `${h}h ago`;
3922
+ return `${Math.floor(h / 24)}d ago`;
3923
+ };
3924
+ // Auto-applied count over the last 7 days = experiments with status 'approved'
3925
+ const since7d = nowMs - 7 * 86_400_000;
3926
+ const autoAppliedRecent = log.filter(e => e.approvalStatus === 'approved' && Date.parse(e.startedAt) >= since7d).length;
3927
+ // Per-agent SI runs from heartbeat state file.
3928
+ const hbStateFile = path.join(BASE_DIR, '.heartbeat_state.json');
3929
+ let perAgentRuns = {};
3930
+ try {
3931
+ if (existsSync(hbStateFile)) {
3932
+ const hb = JSON.parse(readFileSync(hbStateFile, 'utf-8'));
3933
+ perAgentRuns = (hb.lastAgentSiRuns ?? {});
3934
+ }
3935
+ }
3936
+ catch { /* non-fatal */ }
3937
+ if (opts.json) {
3938
+ console.log(JSON.stringify({
3939
+ state,
3940
+ lastSuccessfulAt: lastSuccessful?.startedAt ?? null,
3941
+ autoAppliedLast7d: autoAppliedRecent,
3942
+ pendingCount: pending.length,
3943
+ perAgentRuns,
3944
+ recent: log.slice(-5).reverse(),
3945
+ }, null, 2));
3946
+ return;
3947
+ }
3948
+ // ── Header ─────────────────────────────────────────────────────
3949
+ console.log();
3950
+ console.log(` ${BOLD}Self-improve loop${RESET}`);
3951
+ console.log(` ${DIM}Status: ${RESET}${state.status}`);
3952
+ console.log(` ${DIM}Last attempted: ${RESET}${state.lastRunAt || 'never'} ${DIM}(${formatAge(state.lastRunAt)})${RESET}`);
3953
+ // Stalled-loop warning: if we have a lastRunAt but no successful cycle
3954
+ // in 36+ hours, surface red. That's the visibility gap from pillar #4.
3955
+ const lastSuccAt = lastSuccessful?.startedAt;
3956
+ const hoursSinceSuccess = lastSuccAt ? (nowMs - Date.parse(lastSuccAt)) / 3_600_000 : null;
3957
+ if (lastSuccAt) {
3958
+ const stallTag = hoursSinceSuccess !== null && hoursSinceSuccess > 36 ? ` ${YELLOW}⚠ stalled${RESET}` : ` ${GREEN}✓${RESET}`;
3959
+ console.log(` ${DIM}Last successful: ${RESET}${lastSuccAt} ${DIM}(${formatAge(lastSuccAt)})${RESET}${stallTag}`);
3960
+ }
3961
+ else {
3962
+ console.log(` ${DIM}Last successful: ${RESET}never ${YELLOW}⚠ no cycles yet${RESET}`);
3963
+ }
3964
+ console.log(` ${DIM}Total experiments:${RESET} ${state.totalExperiments}`);
3965
+ console.log(` ${DIM}Auto-applied (7d):${RESET} ${autoAppliedRecent}`);
3966
+ console.log(` ${DIM}Pending review: ${RESET}${pending.length > 0 ? `${YELLOW}${pending.length}${RESET}` : '0'}`);
3967
+ if (state.infraError) {
3968
+ console.log();
3969
+ console.log(` ${RED}⚠ Infra error blocking the loop:${RESET}`);
3970
+ console.log(` Category: ${state.infraError.category}`);
3971
+ console.log(` Diagnostic: ${state.infraError.diagnostic.slice(0, 200)}`);
3972
+ }
3973
+ else {
3974
+ console.log(` ${DIM}Infra errors: ${RESET}${GREEN}none${RESET}`);
3975
+ }
3976
+ // ── Per-agent cycles ───────────────────────────────────────────
3977
+ const agentEntries = Object.entries(perAgentRuns);
3978
+ if (agentEntries.length > 0) {
3979
+ console.log();
3980
+ console.log(` ${BOLD}Per-agent cycles${RESET} ${DIM}(weekly cadence, 2 AM)${RESET}`);
3981
+ for (const [slug, iso] of agentEntries) {
3982
+ console.log(` ${CYAN}${slug.padEnd(28)}${RESET}${DIM}last run ${formatAge(iso)}${RESET}`);
3983
+ }
3984
+ }
3985
+ // ── Recent activity ────────────────────────────────────────────
3986
+ const recent = log.slice(-5).reverse();
3987
+ if (recent.length > 0) {
3988
+ console.log();
3989
+ console.log(` ${BOLD}Recent activity${RESET}`);
3990
+ for (const e of recent) {
3991
+ const score = (e.score * 10).toFixed(1);
3992
+ let icon = '❌';
3993
+ if (e.approvalStatus === 'approved')
3994
+ icon = '✅';
3995
+ else if (e.approvalStatus === 'pending')
3996
+ icon = '⏳';
3997
+ else if (e.approvalStatus === 'unsurfaced')
3998
+ icon = '⛔';
3999
+ const what = e.hypothesis.slice(0, 60);
4000
+ console.log(` ${icon} ${DIM}#${String(e.iteration).padEnd(3)}${RESET} ${e.area.padEnd(16)} ${score.padStart(4)}/10 "${what}"`);
4001
+ }
4002
+ }
4003
+ if (pending.length > 0) {
4004
+ console.log();
4005
+ console.log(` ${YELLOW}${pending.length} change(s) pending your review${RESET}`);
4006
+ console.log(` ${BOLD}clementine self-improve pending${RESET} ${DIM}— see what they propose${RESET}`);
4007
+ console.log(` ${BOLD}clementine self-improve apply <id>${RESET} ${DIM}— approve and apply one${RESET}`);
4008
+ }
4009
+ console.log();
4010
+ }
4011
+ catch (err) {
4012
+ console.error('Error:', err);
4013
+ process.exit(1);
4014
+ }
4015
+ });
4016
+ siCmd
4017
+ .command('pending')
4018
+ .description('List pending self-improve changes — what needs your review')
4019
+ .option('--json', 'Emit machine-readable JSON')
4020
+ .action(async (opts) => {
4021
+ const BOLD = '\x1b[1m';
4022
+ const DIM = '\x1b[0;90m';
4023
+ const YELLOW = '\x1b[1;33m';
4024
+ const CYAN = '\x1b[0;36m';
4025
+ const RESET = '\x1b[0m';
4026
+ try {
4027
+ process.env.CLEMENTINE_HOME = BASE_DIR;
4028
+ const { SelfImproveLoop } = await import('../agent/self-improve.js');
4029
+ const { PersonalAssistant } = await import('../agent/assistant.js');
4030
+ const assistant = new PersonalAssistant();
4031
+ const loop = new SelfImproveLoop(assistant);
4032
+ const pending = loop.getPendingChanges();
4033
+ if (opts.json) {
4034
+ console.log(JSON.stringify(pending.map(p => ({
4035
+ id: p.id, area: p.area, target: p.target,
4036
+ score: p.score, hypothesis: p.hypothesis, reason: p.reason,
4037
+ })), null, 2));
4038
+ return;
4039
+ }
4040
+ if (pending.length === 0) {
4041
+ console.log();
4042
+ console.log(` ${DIM}No changes pending review.${RESET}`);
4043
+ console.log();
4044
+ return;
4045
+ }
4046
+ console.log();
4047
+ console.log(` ${YELLOW}${pending.length} change${pending.length === 1 ? '' : 's'} pending${RESET}`);
4048
+ console.log();
4049
+ for (const p of pending) {
4050
+ const score = (p.score * 10).toFixed(1);
4051
+ console.log(` ${BOLD}#${p.id}${RESET} ${CYAN}${p.area}${RESET} ${DIM}→${RESET} ${p.target} ${DIM}(score ${score}/10)${RESET}`);
4052
+ console.log(` ${p.hypothesis}`);
4053
+ console.log(` ${DIM}${p.reason}${RESET}`);
4054
+ console.log();
4055
+ }
4056
+ console.log(` Apply: ${BOLD}clementine self-improve apply <id>${RESET}`);
4057
+ console.log();
3879
4058
  }
3880
4059
  catch (err) {
3881
4060
  console.error('Error:', err);
@@ -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.28",
3
+ "version": "1.1.30",
4
4
  "description": "Clementine — Personal AI Assistant (TypeScript)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",