@compilr-dev/cli 0.7.5 → 0.8.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.
@@ -1,124 +1,11 @@
1
1
  /**
2
- * API Client for CLI Authentication
2
+ * Auth API client now a thin re-export of the SDK's shared implementation
3
+ * (`@compilr-dev/sdk` host layer). CLI and Desktop talk to the same compilr.dev
4
+ * Netlify Functions; the client lives in one place.
3
5
  *
4
- * All backend communication goes through Netlify Functions.
5
- * No direct Supabase SDK dependency the server handles Supabase calls.
6
- *
7
- * Note: This is for USER authentication only — users still bring their own LLM API keys.
8
- */
9
- export interface RefreshTokenResponse {
10
- success: boolean;
11
- access_token?: string;
12
- refresh_token?: string;
13
- expires_in?: number;
14
- user?: {
15
- id: string;
16
- email: string;
17
- };
18
- error?: string;
19
- }
20
- export interface ProfileData {
21
- user_id: string;
22
- email: string;
23
- account_type: 'free' | 'pro' | 'team' | 'enterprise';
24
- telemetry_enabled: boolean;
25
- default_provider: string;
26
- default_model: string;
27
- default_tier: string;
28
- theme: string;
29
- total_sessions: number;
30
- total_messages: number;
31
- first_session_at: string | null;
32
- last_session_at: string | null;
33
- created_at: string;
34
- }
35
- export interface HeartbeatData {
36
- session_id: string;
37
- cli_version: string;
38
- os: string;
39
- node_version: string;
40
- project_id?: number;
41
- total_messages?: number;
42
- total_tokens_in?: number;
43
- total_tokens_out?: number;
44
- commands_used?: Record<string, number>;
45
- agents_used?: string[];
46
- tools_used?: string[];
47
- llm_provider?: string;
48
- llm_model?: string;
49
- }
50
- export interface DeviceCodeResponse {
51
- device_code: string;
52
- user_code: string;
53
- verification_uri: string;
54
- verification_uri_complete: string;
55
- expires_in: number;
56
- interval: number;
57
- }
58
- export interface DeviceTokenResponse {
59
- access_token: string;
60
- refresh_token: string;
61
- token_type: string;
62
- expires_in: number;
63
- api_token?: string;
64
- user: {
65
- id: string;
66
- email: string;
67
- };
68
- }
69
- export interface DeviceTokenError {
70
- error: 'authorization_pending' | 'slow_down' | 'expired_token' | 'access_denied' | 'invalid_grant' | 'server_error';
71
- error_description: string;
72
- }
73
- /**
74
- * Refresh an access token using a refresh token.
75
- * Calls the server-side Netlify function which handles Supabase internally.
76
- */
77
- export declare function refreshToken(refreshTokenValue: string): Promise<RefreshTokenResponse>;
78
- /**
79
- * Send heartbeat to record session.
80
- */
81
- export declare function sendHeartbeat(accessToken: string, data: HeartbeatData): Promise<{
82
- success: boolean;
83
- telemetry_enabled?: boolean;
84
- error?: string;
85
- }>;
86
- /**
87
- * Get user profile from CLI API.
88
- */
89
- export declare function getProfile(accessToken: string): Promise<{
90
- success: boolean;
91
- profile?: ProfileData;
92
- error?: string;
93
- }>;
94
- /**
95
- * Update user profile via CLI API.
96
- */
97
- export declare function updateProfile(accessToken: string, updates: Partial<Pick<ProfileData, 'telemetry_enabled' | 'default_provider' | 'default_model' | 'default_tier' | 'theme'>>): Promise<{
98
- success: boolean;
99
- profile?: ProfileData;
100
- error?: string;
101
- }>;
102
- /**
103
- * Request a device code for CLI authentication.
104
- * Returns codes and URLs for the user to authorize.
105
- */
106
- export declare function requestDeviceCode(): Promise<{
107
- success: boolean;
108
- data?: DeviceCodeResponse;
109
- error?: string;
110
- }>;
111
- /**
112
- * Poll for device token after user authorizes.
113
- * Returns tokens on success, or error status if still pending.
114
- */
115
- export declare function pollDeviceToken(deviceCode: string): Promise<{
116
- success: boolean;
117
- data?: DeviceTokenResponse;
118
- error?: DeviceTokenError;
119
- networkError?: string;
120
- }>;
121
- /**
122
- * Get the authorization URL for the device flow.
6
+ * The SDK functions read `COMPILR_API_URL` / `COMPILR_WEB_URL` (same defaults as
7
+ * before) and accept an optional endpoints override the CLI doesn't pass, so
8
+ * behavior is unchanged.
123
9
  */
124
- export declare function getAuthorizationUrl(userCode?: string): string;
10
+ export { refreshToken, sendHeartbeat, getProfile, updateProfile, requestDeviceCode, pollDeviceToken, getAuthorizationUrl, } from '@compilr-dev/sdk';
11
+ export type { RefreshTokenResponse, ProfileData, HeartbeatData, DeviceCodeResponse, DeviceTokenResponse, DeviceTokenError, } from '@compilr-dev/sdk';
@@ -1,261 +1,10 @@
1
1
  /**
2
- * API Client for CLI Authentication
2
+ * Auth API client now a thin re-export of the SDK's shared implementation
3
+ * (`@compilr-dev/sdk` host layer). CLI and Desktop talk to the same compilr.dev
4
+ * Netlify Functions; the client lives in one place.
3
5
  *
4
- * All backend communication goes through Netlify Functions.
5
- * No direct Supabase SDK dependency the server handles Supabase calls.
6
- *
7
- * Note: This is for USER authentication only — users still bring their own LLM API keys.
8
- */
9
- import * as os from 'os';
10
- // =============================================================================
11
- // Configuration
12
- // =============================================================================
13
- // API endpoint for CLI-specific operations
14
- // Use COMPILR_API_URL env var for testing against branch previews
15
- const API_BASE_URL = process.env.COMPILR_API_URL || 'https://compilr.dev/.netlify/functions';
16
- // Device flow authorization URL
17
- // Use COMPILR_WEB_URL env var for testing against branch previews
18
- const WEB_BASE_URL = process.env.COMPILR_WEB_URL || 'https://compilr.dev';
19
- const AUTHORIZATION_URL = `${WEB_BASE_URL}/cli/authorize`;
20
- // =============================================================================
21
- // Token Refresh (via Netlify Function)
22
- // =============================================================================
23
- /**
24
- * Refresh an access token using a refresh token.
25
- * Calls the server-side Netlify function which handles Supabase internally.
26
- */
27
- export async function refreshToken(refreshTokenValue) {
28
- try {
29
- const response = await fetch(`${API_BASE_URL}/cli-refresh-token`, {
30
- method: 'POST',
31
- headers: {
32
- 'Content-Type': 'application/json',
33
- },
34
- body: JSON.stringify({ refresh_token: refreshTokenValue }),
35
- });
36
- const contentType = response.headers.get('content-type') ?? '';
37
- if (!contentType.includes('application/json')) {
38
- return { success: false, error: `Server returned non-JSON response (${String(response.status)})` };
39
- }
40
- const result = await response.json();
41
- if (!response.ok) {
42
- return {
43
- success: false,
44
- error: result.error ?? `HTTP ${String(response.status)}`,
45
- };
46
- }
47
- return {
48
- success: true,
49
- access_token: result.access_token,
50
- refresh_token: result.refresh_token,
51
- expires_in: result.expires_in,
52
- user: result.user,
53
- };
54
- }
55
- catch (error) {
56
- return {
57
- success: false,
58
- error: error instanceof Error ? error.message : 'Network error',
59
- };
60
- }
61
- }
62
- // =============================================================================
63
- // CLI API Operations (against our Netlify Functions)
64
- // =============================================================================
65
- /**
66
- * Send heartbeat to record session.
67
- */
68
- export async function sendHeartbeat(accessToken, data) {
69
- try {
70
- const response = await fetch(`${API_BASE_URL}/cli-heartbeat`, {
71
- method: 'POST',
72
- headers: {
73
- 'Authorization': `Bearer ${accessToken}`,
74
- 'Content-Type': 'application/json',
75
- },
76
- body: JSON.stringify(data),
77
- });
78
- const contentType = response.headers.get('content-type') ?? '';
79
- if (!contentType.includes('application/json')) {
80
- return { success: false, error: `Server returned non-JSON response (${String(response.status)})` };
81
- }
82
- const result = await response.json();
83
- if (!response.ok) {
84
- return {
85
- success: false,
86
- error: result.error ?? `HTTP ${String(response.status)}`,
87
- };
88
- }
89
- return {
90
- success: result.success ?? true,
91
- telemetry_enabled: result.telemetry_enabled,
92
- };
93
- }
94
- catch (error) {
95
- return {
96
- success: false,
97
- error: error instanceof Error ? error.message : 'Network error',
98
- };
99
- }
100
- }
101
- /**
102
- * Get user profile from CLI API.
103
- */
104
- export async function getProfile(accessToken) {
105
- try {
106
- const response = await fetch(`${API_BASE_URL}/cli-profile`, {
107
- method: 'GET',
108
- headers: {
109
- 'Authorization': `Bearer ${accessToken}`,
110
- },
111
- });
112
- const contentType = response.headers.get('content-type') ?? '';
113
- if (!contentType.includes('application/json')) {
114
- return { success: false, error: `Server returned non-JSON response (${String(response.status)})` };
115
- }
116
- const result = await response.json();
117
- if (!response.ok) {
118
- return {
119
- success: false,
120
- error: result.error ?? `HTTP ${String(response.status)}`,
121
- };
122
- }
123
- return {
124
- success: true,
125
- profile: result,
126
- };
127
- }
128
- catch (error) {
129
- return {
130
- success: false,
131
- error: error instanceof Error ? error.message : 'Network error',
132
- };
133
- }
134
- }
135
- /**
136
- * Update user profile via CLI API.
137
- */
138
- export async function updateProfile(accessToken, updates) {
139
- try {
140
- const response = await fetch(`${API_BASE_URL}/cli-profile`, {
141
- method: 'PATCH',
142
- headers: {
143
- 'Authorization': `Bearer ${accessToken}`,
144
- 'Content-Type': 'application/json',
145
- },
146
- body: JSON.stringify(updates),
147
- });
148
- const contentType = response.headers.get('content-type') ?? '';
149
- if (!contentType.includes('application/json')) {
150
- return { success: false, error: `Server returned non-JSON response (${String(response.status)})` };
151
- }
152
- const result = await response.json();
153
- if (!response.ok) {
154
- return {
155
- success: false,
156
- error: result.error ?? `HTTP ${String(response.status)}`,
157
- };
158
- }
159
- return {
160
- success: result.success ?? true,
161
- profile: result.profile,
162
- };
163
- }
164
- catch (error) {
165
- return {
166
- success: false,
167
- error: error instanceof Error ? error.message : 'Network error',
168
- };
169
- }
170
- }
171
- // =============================================================================
172
- // Device Flow Authentication
173
- // =============================================================================
174
- /**
175
- * Request a device code for CLI authentication.
176
- * Returns codes and URLs for the user to authorize.
177
- */
178
- export async function requestDeviceCode() {
179
- try {
180
- const url = `${API_BASE_URL}/cli-device-code`;
181
- const response = await fetch(url, {
182
- method: 'POST',
183
- headers: {
184
- 'Content-Type': 'application/json',
185
- },
186
- });
187
- const contentType = response.headers.get('content-type') ?? '';
188
- if (!contentType.includes('application/json')) {
189
- return {
190
- success: false,
191
- error: `Non-JSON response (${String(response.status)}) from: ${url}`,
192
- };
193
- }
194
- if (!response.ok) {
195
- const result = await response.json();
196
- return {
197
- success: false,
198
- error: result.error ?? `HTTP ${String(response.status)}`,
199
- };
200
- }
201
- const data = await response.json();
202
- return { success: true, data };
203
- }
204
- catch (error) {
205
- return {
206
- success: false,
207
- error: error instanceof Error ? error.message : 'Network error',
208
- };
209
- }
210
- }
211
- /**
212
- * Poll for device token after user authorizes.
213
- * Returns tokens on success, or error status if still pending.
214
- */
215
- export async function pollDeviceToken(deviceCode) {
216
- try {
217
- const response = await fetch(`${API_BASE_URL}/cli-device-token`, {
218
- method: 'POST',
219
- headers: {
220
- 'Content-Type': 'application/json',
221
- },
222
- body: JSON.stringify({
223
- device_code: deviceCode,
224
- hostname: os.hostname(),
225
- }),
226
- });
227
- const contentType = response.headers.get('content-type') ?? '';
228
- if (!contentType.includes('application/json')) {
229
- return {
230
- success: false,
231
- networkError: `Server returned non-JSON response (${String(response.status)})`,
232
- };
233
- }
234
- const result = await response.json();
235
- if (!response.ok) {
236
- return {
237
- success: false,
238
- error: result,
239
- };
240
- }
241
- return {
242
- success: true,
243
- data: result,
244
- };
245
- }
246
- catch (error) {
247
- return {
248
- success: false,
249
- networkError: error instanceof Error ? error.message : 'Network error',
250
- };
251
- }
252
- }
253
- /**
254
- * Get the authorization URL for the device flow.
6
+ * The SDK functions read `COMPILR_API_URL` / `COMPILR_WEB_URL` (same defaults as
7
+ * before) and accept an optional endpoints override the CLI doesn't pass, so
8
+ * behavior is unchanged.
255
9
  */
256
- export function getAuthorizationUrl(userCode) {
257
- if (userCode) {
258
- return `${AUTHORIZATION_URL}?code=${userCode}`;
259
- }
260
- return AUTHORIZATION_URL;
261
- }
10
+ export { refreshToken, sendHeartbeat, getProfile, updateProfile, requestDeviceCode, pollDeviceToken, getAuthorizationUrl, } from '@compilr-dev/sdk';
@@ -1,52 +1,10 @@
1
1
  /**
2
- * Auth Storage Module
2
+ * Auth storage — now a thin re-export of the SDK's shared implementation
3
+ * (`@compilr-dev/sdk` host layer). CLI and Desktop persist the same
4
+ * `~/.compilr-dev/auth.json`; the logic lives in one place.
3
5
  *
4
- * Manages persistent storage of authentication credentials.
5
- * File: ~/.compilr-dev/auth.json
6
- *
7
- * Security:
8
- * - File permissions set to 0600 (owner read/write only)
9
- * - Tokens are JWTs from Supabase Auth
10
- */
11
- export interface AuthUser {
12
- id: string;
13
- email: string;
14
- createdAt: string;
15
- }
16
- export interface AuthSession {
17
- accessToken: string;
18
- refreshToken: string;
19
- expiresAt: string;
20
- apiToken?: string;
21
- }
22
- export interface AuthData {
23
- version: number;
24
- user: AuthUser;
25
- session: AuthSession;
26
- }
27
- /**
28
- * Load auth data from disk.
29
- * Returns null if file doesn't exist or is invalid.
30
- */
31
- export declare function loadAuthData(): AuthData | null;
32
- /**
33
- * Save auth data to disk with secure permissions.
34
- */
35
- export declare function saveAuthData(data: AuthData): void;
36
- /**
37
- * Clear auth data (logout).
38
- */
39
- export declare function clearAuthData(): void;
40
- /**
41
- * Update just the session tokens (after refresh).
42
- */
43
- export declare function updateSession(session: AuthSession): boolean;
44
- /**
45
- * Check if auth data exists (quick check without loading).
46
- */
47
- export declare function hasAuthData(): boolean;
48
- /**
49
- * Check if stored auth file has insecure permissions.
50
- * Returns warning message if insecure, null if OK.
6
+ * The SDK functions default to `~/.compilr-dev` (== this module's historical
7
+ * CONFIG_DIR), so behavior is identical — no `dir` argument is passed.
51
8
  */
52
- export declare function checkAuthFilePermissions(): string | null;
9
+ export { loadAuthData, saveAuthData, clearAuthData, updateSession, hasAuthData, checkAuthFilePermissions, } from '@compilr-dev/sdk';
10
+ export type { AuthUser, AuthSession, AuthData } from '@compilr-dev/sdk';
@@ -1,118 +1,9 @@
1
1
  /**
2
- * Auth Storage Module
2
+ * Auth storage — now a thin re-export of the SDK's shared implementation
3
+ * (`@compilr-dev/sdk` host layer). CLI and Desktop persist the same
4
+ * `~/.compilr-dev/auth.json`; the logic lives in one place.
3
5
  *
4
- * Manages persistent storage of authentication credentials.
5
- * File: ~/.compilr-dev/auth.json
6
- *
7
- * Security:
8
- * - File permissions set to 0600 (owner read/write only)
9
- * - Tokens are JWTs from Supabase Auth
10
- */
11
- import * as fs from 'fs';
12
- import * as path from 'path';
13
- import * as os from 'os';
14
- // =============================================================================
15
- // Constants
16
- // =============================================================================
17
- const CONFIG_DIR = path.join(os.homedir(), '.compilr-dev');
18
- const AUTH_FILE = path.join(CONFIG_DIR, 'auth.json');
19
- // =============================================================================
20
- // Storage Operations
21
- // =============================================================================
22
- /**
23
- * Load auth data from disk.
24
- * Returns null if file doesn't exist or is invalid.
25
- */
26
- export function loadAuthData() {
27
- try {
28
- if (!fs.existsSync(AUTH_FILE)) {
29
- return null;
30
- }
31
- const data = fs.readFileSync(AUTH_FILE, 'utf-8');
32
- const parsed = JSON.parse(data);
33
- // Validate required fields
34
- if (typeof parsed.version !== 'number' ||
35
- typeof parsed.user !== 'object' ||
36
- !parsed.user ||
37
- typeof parsed.session !== 'object' ||
38
- !parsed.session) {
39
- return null;
40
- }
41
- return parsed;
42
- }
43
- catch {
44
- // File doesn't exist or is corrupted
45
- return null;
46
- }
47
- }
48
- /**
49
- * Save auth data to disk with secure permissions.
50
- */
51
- export function saveAuthData(data) {
52
- // Ensure config directory exists
53
- fs.mkdirSync(CONFIG_DIR, { recursive: true });
54
- // Write with restrictive permissions
55
- fs.writeFileSync(AUTH_FILE, JSON.stringify(data, null, 2), {
56
- encoding: 'utf-8',
57
- mode: 0o600, // Owner read/write only
58
- });
59
- // Verify permissions (fix if system ignored mode)
60
- try {
61
- fs.chmodSync(AUTH_FILE, 0o600);
62
- }
63
- catch {
64
- // Ignore chmod errors (e.g., on Windows)
65
- }
66
- }
67
- /**
68
- * Clear auth data (logout).
69
- */
70
- export function clearAuthData() {
71
- try {
72
- if (fs.existsSync(AUTH_FILE)) {
73
- fs.unlinkSync(AUTH_FILE);
74
- }
75
- }
76
- catch {
77
- // Ignore errors during cleanup
78
- }
79
- }
80
- /**
81
- * Update just the session tokens (after refresh).
82
- */
83
- export function updateSession(session) {
84
- const data = loadAuthData();
85
- if (!data) {
86
- return false;
87
- }
88
- data.session = session;
89
- saveAuthData(data);
90
- return true;
91
- }
92
- /**
93
- * Check if auth data exists (quick check without loading).
94
- */
95
- export function hasAuthData() {
96
- return fs.existsSync(AUTH_FILE);
97
- }
98
- /**
99
- * Check if stored auth file has insecure permissions.
100
- * Returns warning message if insecure, null if OK.
6
+ * The SDK functions default to `~/.compilr-dev` (== this module's historical
7
+ * CONFIG_DIR), so behavior is identical — no `dir` argument is passed.
101
8
  */
102
- export function checkAuthFilePermissions() {
103
- try {
104
- if (!fs.existsSync(AUTH_FILE)) {
105
- return null;
106
- }
107
- const stats = fs.statSync(AUTH_FILE);
108
- const mode = stats.mode & 0o777; // Get permission bits
109
- // Check if group or others have any access
110
- if (mode & 0o077) {
111
- return `Warning: Auth file has insecure permissions (${mode.toString(8)}). Consider running: chmod 600 ${AUTH_FILE}`;
112
- }
113
- return null;
114
- }
115
- catch {
116
- return null;
117
- }
118
- }
9
+ export { loadAuthData, saveAuthData, clearAuthData, updateSession, hasAuthData, checkAuthFilePermissions, } from '@compilr-dev/sdk';
@@ -25,7 +25,13 @@ export const themeCommand = {
25
25
  ],
26
26
  async execute(_args, ctx) {
27
27
  const themeOverlay = new ThemeOverlayV2();
28
- await ctx.ui.showOverlay(themeOverlay);
28
+ const result = await ctx.ui.showOverlay(themeOverlay);
29
+ // The overlay updates the theme registry, but the input prompt prefix (the
30
+ // accent-colored "compilr>") is cached and must be rebuilt to pick up the new
31
+ // accent color — otherwise it stays stale until the next restart.
32
+ if (result?.themeChanged) {
33
+ ctx.ui.refreshPrompt();
34
+ }
29
35
  return true;
30
36
  },
31
37
  };
@@ -78,6 +84,10 @@ export const configCommand = {
78
84
  messageCount,
79
85
  });
80
86
  const result = await ctx.ui.showOverlay(configOverlay);
87
+ // The config overlay can change the theme via its sub-overlay without
88
+ // surfacing it in the result, so rebuild the cached prompt prefix
89
+ // unconditionally on close — refreshPrompt() reads colors live and is cheap.
90
+ ctx.ui.refreshPrompt();
81
91
  if (result?.settingsChanged) {
82
92
  // Sync TerminalUI config with persisted settings
83
93
  const updated = getSettings();
Binary file
@@ -6,7 +6,7 @@
6
6
  * - Uses encrypted file for offline backup
7
7
  * - Warms cache on startup
8
8
  */
9
- import { EntitlementCache } from '@compilr-dev/sdk';
9
+ import { EntitlementCache, fetchEntitlements, } from '@compilr-dev/sdk';
10
10
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
11
11
  import { join } from 'node:path';
12
12
  import { createCipheriv, createDecipheriv, randomBytes, createHash } from 'node:crypto';
@@ -60,23 +60,9 @@ const cliStore = {
60
60
  }
61
61
  },
62
62
  };
63
- // ─── Fetch Function ─────────────────────────────────────────────────────────
64
- async function fetchEntitlements(getToken) {
65
- const token = await getToken();
66
- const apiUrl = process.env['COMPILR_API_URL'] ?? 'https://compilr.dev/.netlify/functions';
67
- const resp = await fetch(`${apiUrl}/cli-entitlements`, {
68
- method: 'GET',
69
- headers: {
70
- 'Authorization': `Bearer ${token}`,
71
- 'Content-Type': 'application/json',
72
- },
73
- });
74
- if (!resp.ok) {
75
- throw new Error(`Entitlements fetch failed: ${String(resp.status)}`);
76
- }
77
- return await resp.json();
78
- }
79
63
  // ─── Singleton ──────────────────────────────────────────────────────────────
64
+ // Fetch lives in the SDK (shared with Desktop). `getToken` returns Promise<string>
65
+ // here, which satisfies the SDK's Promise<string | null> contract.
80
66
  import { DailyCounter } from '@compilr-dev/sdk';
81
67
  let cacheInstance = null;
82
68
  /** Daily message counter — tracks agent LLM calls per day */
@@ -8,7 +8,7 @@ import { log } from '../foundation/logger.js';
8
8
  import * as fs from 'fs';
9
9
  import * as path from 'path';
10
10
  import * as os from 'os';
11
- import { DEFAULT_PERMISSION_RULES, findMatchingRule as sdkFindMatchingRule, } from '@compilr-dev/sdk';
11
+ import { DEFAULT_PERMISSION_RULES, findMatchingRule as sdkFindMatchingRule, migrateRawSettings, } from '@compilr-dev/sdk';
12
12
  // =============================================================================
13
13
  // Constants
14
14
  // =============================================================================
@@ -103,32 +103,14 @@ export function getSettings() {
103
103
  if (settingsCache) {
104
104
  return settingsCache;
105
105
  }
106
+ // Value migrations live in the SDK (migrateRawSettings) so CLI and Desktop
107
+ // normalize the shared settings.json identically. It's pure, idempotent, and
108
+ // preserves unknown keys (incl. Desktop-only fields). We run it on the raw
109
+ // file, then merge over CLI defaults.
106
110
  const stored = loadFromDisk();
107
- let needsSave = false;
108
- // Migration: convert verbose boolean to verbosity string (before merge with defaults)
109
- if (!('verbosity' in stored) && stored.verbose === true) {
110
- stored.verbosity = 'verbose';
111
- needsSave = true;
112
- }
113
- settingsCache = { ...DEFAULT_SETTINGS, ...stored };
114
- if (needsSave) {
115
- saveToDisk(settingsCache);
116
- }
117
- // Migration: convert deprecated 'cwd' to 'off'
118
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
119
- if (settingsCache.projectStartup === 'cwd') {
120
- settingsCache.projectStartup = 'off';
121
- saveToDisk(settingsCache);
122
- }
123
- // Migration: convert old permission mode values
124
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
125
- const oldPermMode = settingsCache.permissionMode;
126
- if (oldPermMode === 'default' || oldPermMode === 'accept-edits') {
127
- settingsCache.permissionMode = 'normal';
128
- saveToDisk(settingsCache);
129
- }
130
- else if (oldPermMode === 'dont-ask') {
131
- settingsCache.permissionMode = 'auto-accept';
111
+ const { settings: migrated, changed } = migrateRawSettings(stored);
112
+ settingsCache = { ...DEFAULT_SETTINGS, ...migrated };
113
+ if (changed) {
132
114
  saveToDisk(settingsCache);
133
115
  }
134
116
  return settingsCache;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@compilr-dev/cli",
3
- "version": "0.7.5",
3
+ "version": "0.8.0",
4
4
  "description": "AI-powered coding assistant CLI using @compilr-dev/agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -60,7 +60,7 @@
60
60
  "@compilr-dev/editor-core": "^0.0.2",
61
61
  "@compilr-dev/factory": "^0.1.30",
62
62
  "@compilr-dev/logger": "^0.1.0",
63
- "@compilr-dev/sdk": "^0.10.39",
63
+ "@compilr-dev/sdk": "^0.11.0",
64
64
  "@compilr-dev/ui-core": "^0.0.1",
65
65
  "@modelcontextprotocol/sdk": "^1.23.0",
66
66
  "ansi-escapes": "^7.3.0",