claudeup 3.7.2 → 3.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.
- package/package.json +1 -1
- package/src/data/settings-catalog.js +612 -0
- package/src/data/settings-catalog.ts +689 -0
- package/src/services/plugin-manager.js +2 -0
- package/src/services/plugin-manager.ts +3 -0
- package/src/services/profiles.js +161 -0
- package/src/services/profiles.ts +225 -0
- package/src/services/settings-manager.js +108 -0
- package/src/services/settings-manager.ts +140 -0
- package/src/types/index.ts +34 -0
- package/src/ui/App.js +17 -18
- package/src/ui/App.tsx +21 -23
- package/src/ui/components/TabBar.js +8 -8
- package/src/ui/components/TabBar.tsx +14 -19
- package/src/ui/components/layout/ScreenLayout.js +8 -14
- package/src/ui/components/layout/ScreenLayout.tsx +51 -58
- package/src/ui/components/modals/ModalContainer.js +43 -11
- package/src/ui/components/modals/ModalContainer.tsx +44 -12
- package/src/ui/components/modals/SelectModal.js +4 -18
- package/src/ui/components/modals/SelectModal.tsx +10 -21
- package/src/ui/screens/CliToolsScreen.js +2 -2
- package/src/ui/screens/CliToolsScreen.tsx +8 -8
- package/src/ui/screens/EnvVarsScreen.js +248 -116
- package/src/ui/screens/EnvVarsScreen.tsx +419 -184
- package/src/ui/screens/McpRegistryScreen.tsx +18 -6
- package/src/ui/screens/McpScreen.js +1 -1
- package/src/ui/screens/McpScreen.tsx +15 -5
- package/src/ui/screens/ModelSelectorScreen.js +3 -5
- package/src/ui/screens/ModelSelectorScreen.tsx +12 -16
- package/src/ui/screens/PluginsScreen.js +154 -66
- package/src/ui/screens/PluginsScreen.tsx +280 -97
- package/src/ui/screens/ProfilesScreen.js +255 -0
- package/src/ui/screens/ProfilesScreen.tsx +487 -0
- package/src/ui/screens/StatusLineScreen.js +2 -2
- package/src/ui/screens/StatusLineScreen.tsx +10 -12
- package/src/ui/screens/index.js +2 -2
- package/src/ui/screens/index.ts +2 -2
- package/src/ui/state/AppContext.js +2 -1
- package/src/ui/state/AppContext.tsx +2 -0
- package/src/ui/state/reducer.js +63 -19
- package/src/ui/state/reducer.ts +68 -19
- package/src/ui/state/types.ts +33 -14
- package/src/utils/clipboard.js +56 -0
- package/src/utils/clipboard.ts +58 -0
package/src/ui/state/reducer.js
CHANGED
|
@@ -34,9 +34,9 @@ export const initialState = {
|
|
|
34
34
|
presets: { status: "idle" },
|
|
35
35
|
currentPreset: null,
|
|
36
36
|
},
|
|
37
|
-
|
|
37
|
+
settings: {
|
|
38
38
|
selectedIndex: 0,
|
|
39
|
-
|
|
39
|
+
values: { status: "idle" },
|
|
40
40
|
},
|
|
41
41
|
cliTools: {
|
|
42
42
|
selectedIndex: 0,
|
|
@@ -47,6 +47,10 @@ export const initialState = {
|
|
|
47
47
|
searchQuery: "",
|
|
48
48
|
taskSize: "large",
|
|
49
49
|
},
|
|
50
|
+
profiles: {
|
|
51
|
+
selectedIndex: 0,
|
|
52
|
+
profiles: { status: "idle" },
|
|
53
|
+
},
|
|
50
54
|
};
|
|
51
55
|
export function appReducer(state, action) {
|
|
52
56
|
switch (action.type) {
|
|
@@ -54,7 +58,12 @@ export function appReducer(state, action) {
|
|
|
54
58
|
// Navigation
|
|
55
59
|
// =========================================================================
|
|
56
60
|
case "NAVIGATE":
|
|
57
|
-
return {
|
|
61
|
+
return {
|
|
62
|
+
...state,
|
|
63
|
+
currentRoute: action.route,
|
|
64
|
+
// Clear search state when navigating away
|
|
65
|
+
isSearching: false,
|
|
66
|
+
};
|
|
58
67
|
// =========================================================================
|
|
59
68
|
// Plugins Screen
|
|
60
69
|
// =========================================================================
|
|
@@ -268,38 +277,38 @@ export function appReducer(state, action) {
|
|
|
268
277
|
},
|
|
269
278
|
};
|
|
270
279
|
// =========================================================================
|
|
271
|
-
//
|
|
280
|
+
// Settings Screen
|
|
272
281
|
// =========================================================================
|
|
273
|
-
case "
|
|
282
|
+
case "SETTINGS_SELECT":
|
|
274
283
|
return {
|
|
275
284
|
...state,
|
|
276
|
-
|
|
277
|
-
...state.
|
|
285
|
+
settings: {
|
|
286
|
+
...state.settings,
|
|
278
287
|
selectedIndex: action.index,
|
|
279
288
|
},
|
|
280
289
|
};
|
|
281
|
-
case "
|
|
290
|
+
case "SETTINGS_DATA_LOADING":
|
|
282
291
|
return {
|
|
283
292
|
...state,
|
|
284
|
-
|
|
285
|
-
...state.
|
|
286
|
-
|
|
293
|
+
settings: {
|
|
294
|
+
...state.settings,
|
|
295
|
+
values: { status: "loading" },
|
|
287
296
|
},
|
|
288
297
|
};
|
|
289
|
-
case "
|
|
298
|
+
case "SETTINGS_DATA_SUCCESS":
|
|
290
299
|
return {
|
|
291
300
|
...state,
|
|
292
|
-
|
|
293
|
-
...state.
|
|
294
|
-
|
|
301
|
+
settings: {
|
|
302
|
+
...state.settings,
|
|
303
|
+
values: { status: "success", data: action.values },
|
|
295
304
|
},
|
|
296
305
|
};
|
|
297
|
-
case "
|
|
306
|
+
case "SETTINGS_DATA_ERROR":
|
|
298
307
|
return {
|
|
299
308
|
...state,
|
|
300
|
-
|
|
301
|
-
...state.
|
|
302
|
-
|
|
309
|
+
settings: {
|
|
310
|
+
...state.settings,
|
|
311
|
+
values: { status: "error", error: action.error },
|
|
303
312
|
},
|
|
304
313
|
};
|
|
305
314
|
// =========================================================================
|
|
@@ -380,6 +389,41 @@ export function appReducer(state, action) {
|
|
|
380
389
|
},
|
|
381
390
|
};
|
|
382
391
|
// =========================================================================
|
|
392
|
+
// Profiles Screen
|
|
393
|
+
// =========================================================================
|
|
394
|
+
case "PROFILES_SELECT":
|
|
395
|
+
return {
|
|
396
|
+
...state,
|
|
397
|
+
profiles: {
|
|
398
|
+
...state.profiles,
|
|
399
|
+
selectedIndex: action.index,
|
|
400
|
+
},
|
|
401
|
+
};
|
|
402
|
+
case "PROFILES_DATA_LOADING":
|
|
403
|
+
return {
|
|
404
|
+
...state,
|
|
405
|
+
profiles: {
|
|
406
|
+
...state.profiles,
|
|
407
|
+
profiles: { status: "loading" },
|
|
408
|
+
},
|
|
409
|
+
};
|
|
410
|
+
case "PROFILES_DATA_SUCCESS":
|
|
411
|
+
return {
|
|
412
|
+
...state,
|
|
413
|
+
profiles: {
|
|
414
|
+
...state.profiles,
|
|
415
|
+
profiles: { status: "success", data: action.profiles },
|
|
416
|
+
},
|
|
417
|
+
};
|
|
418
|
+
case "PROFILES_DATA_ERROR":
|
|
419
|
+
return {
|
|
420
|
+
...state,
|
|
421
|
+
profiles: {
|
|
422
|
+
...state.profiles,
|
|
423
|
+
profiles: { status: "error", error: action.error },
|
|
424
|
+
},
|
|
425
|
+
};
|
|
426
|
+
// =========================================================================
|
|
383
427
|
// Modals
|
|
384
428
|
// =========================================================================
|
|
385
429
|
case "SHOW_MODAL":
|
package/src/ui/state/reducer.ts
CHANGED
|
@@ -44,9 +44,9 @@ export const initialState: AppState = {
|
|
|
44
44
|
currentPreset: null,
|
|
45
45
|
},
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
settings: {
|
|
48
48
|
selectedIndex: 0,
|
|
49
|
-
|
|
49
|
+
values: { status: "idle" },
|
|
50
50
|
},
|
|
51
51
|
|
|
52
52
|
cliTools: {
|
|
@@ -59,6 +59,11 @@ export const initialState: AppState = {
|
|
|
59
59
|
searchQuery: "",
|
|
60
60
|
taskSize: "large",
|
|
61
61
|
},
|
|
62
|
+
|
|
63
|
+
profiles: {
|
|
64
|
+
selectedIndex: 0,
|
|
65
|
+
profiles: { status: "idle" },
|
|
66
|
+
},
|
|
62
67
|
};
|
|
63
68
|
|
|
64
69
|
export function appReducer(state: AppState, action: AppAction): AppState {
|
|
@@ -67,7 +72,12 @@ export function appReducer(state: AppState, action: AppAction): AppState {
|
|
|
67
72
|
// Navigation
|
|
68
73
|
// =========================================================================
|
|
69
74
|
case "NAVIGATE":
|
|
70
|
-
return {
|
|
75
|
+
return {
|
|
76
|
+
...state,
|
|
77
|
+
currentRoute: action.route,
|
|
78
|
+
// Clear search state when navigating away
|
|
79
|
+
isSearching: false,
|
|
80
|
+
};
|
|
71
81
|
|
|
72
82
|
// =========================================================================
|
|
73
83
|
// Plugins Screen
|
|
@@ -304,41 +314,41 @@ export function appReducer(state: AppState, action: AppAction): AppState {
|
|
|
304
314
|
};
|
|
305
315
|
|
|
306
316
|
// =========================================================================
|
|
307
|
-
//
|
|
317
|
+
// Settings Screen
|
|
308
318
|
// =========================================================================
|
|
309
|
-
case "
|
|
319
|
+
case "SETTINGS_SELECT":
|
|
310
320
|
return {
|
|
311
321
|
...state,
|
|
312
|
-
|
|
313
|
-
...state.
|
|
322
|
+
settings: {
|
|
323
|
+
...state.settings,
|
|
314
324
|
selectedIndex: action.index,
|
|
315
325
|
},
|
|
316
326
|
};
|
|
317
327
|
|
|
318
|
-
case "
|
|
328
|
+
case "SETTINGS_DATA_LOADING":
|
|
319
329
|
return {
|
|
320
330
|
...state,
|
|
321
|
-
|
|
322
|
-
...state.
|
|
323
|
-
|
|
331
|
+
settings: {
|
|
332
|
+
...state.settings,
|
|
333
|
+
values: { status: "loading" },
|
|
324
334
|
},
|
|
325
335
|
};
|
|
326
336
|
|
|
327
|
-
case "
|
|
337
|
+
case "SETTINGS_DATA_SUCCESS":
|
|
328
338
|
return {
|
|
329
339
|
...state,
|
|
330
|
-
|
|
331
|
-
...state.
|
|
332
|
-
|
|
340
|
+
settings: {
|
|
341
|
+
...state.settings,
|
|
342
|
+
values: { status: "success", data: action.values },
|
|
333
343
|
},
|
|
334
344
|
};
|
|
335
345
|
|
|
336
|
-
case "
|
|
346
|
+
case "SETTINGS_DATA_ERROR":
|
|
337
347
|
return {
|
|
338
348
|
...state,
|
|
339
|
-
|
|
340
|
-
...state.
|
|
341
|
-
|
|
349
|
+
settings: {
|
|
350
|
+
...state.settings,
|
|
351
|
+
values: { status: "error", error: action.error },
|
|
342
352
|
},
|
|
343
353
|
};
|
|
344
354
|
|
|
@@ -428,6 +438,45 @@ export function appReducer(state: AppState, action: AppAction): AppState {
|
|
|
428
438
|
},
|
|
429
439
|
};
|
|
430
440
|
|
|
441
|
+
// =========================================================================
|
|
442
|
+
// Profiles Screen
|
|
443
|
+
// =========================================================================
|
|
444
|
+
case "PROFILES_SELECT":
|
|
445
|
+
return {
|
|
446
|
+
...state,
|
|
447
|
+
profiles: {
|
|
448
|
+
...state.profiles,
|
|
449
|
+
selectedIndex: action.index,
|
|
450
|
+
},
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
case "PROFILES_DATA_LOADING":
|
|
454
|
+
return {
|
|
455
|
+
...state,
|
|
456
|
+
profiles: {
|
|
457
|
+
...state.profiles,
|
|
458
|
+
profiles: { status: "loading" },
|
|
459
|
+
},
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
case "PROFILES_DATA_SUCCESS":
|
|
463
|
+
return {
|
|
464
|
+
...state,
|
|
465
|
+
profiles: {
|
|
466
|
+
...state.profiles,
|
|
467
|
+
profiles: { status: "success", data: action.profiles },
|
|
468
|
+
},
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
case "PROFILES_DATA_ERROR":
|
|
472
|
+
return {
|
|
473
|
+
...state,
|
|
474
|
+
profiles: {
|
|
475
|
+
...state.profiles,
|
|
476
|
+
profiles: { status: "error", error: action.error },
|
|
477
|
+
},
|
|
478
|
+
};
|
|
479
|
+
|
|
431
480
|
// =========================================================================
|
|
432
481
|
// Modals
|
|
433
482
|
// =========================================================================
|
package/src/ui/state/types.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type {
|
|
|
2
2
|
Marketplace,
|
|
3
3
|
McpServer,
|
|
4
4
|
StatusLineConfig,
|
|
5
|
+
ProfileEntry,
|
|
5
6
|
} from "../../types/index.js";
|
|
6
7
|
import type { PluginInfo } from "../../services/plugin-manager.js";
|
|
7
8
|
|
|
@@ -13,19 +14,19 @@ export type Screen =
|
|
|
13
14
|
| "plugins"
|
|
14
15
|
| "mcp"
|
|
15
16
|
| "mcp-registry"
|
|
16
|
-
| "
|
|
17
|
-
| "env-vars"
|
|
17
|
+
| "settings"
|
|
18
18
|
| "cli-tools"
|
|
19
|
-
| "model-selector"
|
|
19
|
+
| "model-selector"
|
|
20
|
+
| "profiles";
|
|
20
21
|
|
|
21
22
|
export type Route =
|
|
22
23
|
| { screen: "plugins" }
|
|
23
24
|
| { screen: "mcp" }
|
|
24
25
|
| { screen: "mcp-registry"; query?: string }
|
|
25
|
-
| { screen: "
|
|
26
|
-
| { screen: "env-vars" }
|
|
26
|
+
| { screen: "settings" }
|
|
27
27
|
| { screen: "cli-tools" }
|
|
28
|
-
| { screen: "model-selector" }
|
|
28
|
+
| { screen: "model-selector" }
|
|
29
|
+
| { screen: "profiles" };
|
|
29
30
|
|
|
30
31
|
// ============================================================================
|
|
31
32
|
// Async Data Types
|
|
@@ -68,6 +69,7 @@ export type ModalState =
|
|
|
68
69
|
title: string;
|
|
69
70
|
message: string;
|
|
70
71
|
options: SelectOption[];
|
|
72
|
+
defaultIndex?: number;
|
|
71
73
|
onSelect: (value: string) => void;
|
|
72
74
|
onCancel: () => void;
|
|
73
75
|
}
|
|
@@ -125,9 +127,14 @@ export interface StatusLineScreenState {
|
|
|
125
127
|
currentPreset: string | null;
|
|
126
128
|
}
|
|
127
129
|
|
|
128
|
-
export interface
|
|
130
|
+
export interface ScopedSettingValues {
|
|
131
|
+
user: string | undefined;
|
|
132
|
+
project: string | undefined;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface SettingsScreenState {
|
|
129
136
|
selectedIndex: number;
|
|
130
|
-
|
|
137
|
+
values: AsyncData<Map<string, ScopedSettingValues>>;
|
|
131
138
|
}
|
|
132
139
|
|
|
133
140
|
export interface CliTool {
|
|
@@ -156,6 +163,11 @@ export interface ModelSelectorScreenState {
|
|
|
156
163
|
taskSize: "large" | "small";
|
|
157
164
|
}
|
|
158
165
|
|
|
166
|
+
export interface ProfilesScreenState {
|
|
167
|
+
selectedIndex: number;
|
|
168
|
+
profiles: AsyncData<ProfileEntry[]>;
|
|
169
|
+
}
|
|
170
|
+
|
|
159
171
|
// ============================================================================
|
|
160
172
|
// App State
|
|
161
173
|
// ============================================================================
|
|
@@ -181,9 +193,10 @@ export interface AppState {
|
|
|
181
193
|
mcp: McpScreenState;
|
|
182
194
|
mcpRegistry: McpRegistryScreenState;
|
|
183
195
|
statusline: StatusLineScreenState;
|
|
184
|
-
|
|
196
|
+
settings: SettingsScreenState;
|
|
185
197
|
cliTools: CliToolsScreenState;
|
|
186
198
|
modelSelector: ModelSelectorScreenState;
|
|
199
|
+
profiles: ProfilesScreenState;
|
|
187
200
|
}
|
|
188
201
|
|
|
189
202
|
// ============================================================================
|
|
@@ -239,11 +252,11 @@ export type AppAction =
|
|
|
239
252
|
}
|
|
240
253
|
| { type: "STATUSLINE_DATA_ERROR"; error: Error }
|
|
241
254
|
|
|
242
|
-
//
|
|
243
|
-
| { type: "
|
|
244
|
-
| { type: "
|
|
245
|
-
| { type: "
|
|
246
|
-
| { type: "
|
|
255
|
+
// Settings screen
|
|
256
|
+
| { type: "SETTINGS_SELECT"; index: number }
|
|
257
|
+
| { type: "SETTINGS_DATA_LOADING" }
|
|
258
|
+
| { type: "SETTINGS_DATA_SUCCESS"; values: Map<string, ScopedSettingValues> }
|
|
259
|
+
| { type: "SETTINGS_DATA_ERROR"; error: Error }
|
|
247
260
|
|
|
248
261
|
// CLI tools screen
|
|
249
262
|
| { type: "CLITOOLS_SELECT"; index: number }
|
|
@@ -269,5 +282,11 @@ export type AppAction =
|
|
|
269
282
|
// Search state (blocks global key handlers)
|
|
270
283
|
| { type: "SET_SEARCHING"; isSearching: boolean }
|
|
271
284
|
|
|
285
|
+
// Profiles screen
|
|
286
|
+
| { type: "PROFILES_SELECT"; index: number }
|
|
287
|
+
| { type: "PROFILES_DATA_LOADING" }
|
|
288
|
+
| { type: "PROFILES_DATA_SUCCESS"; profiles: ProfileEntry[] }
|
|
289
|
+
| { type: "PROFILES_DATA_ERROR"; error: Error }
|
|
290
|
+
|
|
272
291
|
// Data refresh - triggers screens to refetch
|
|
273
292
|
| { type: "DATA_REFRESH_COMPLETE" };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
/**
|
|
3
|
+
* Write text to the system clipboard.
|
|
4
|
+
* macOS: uses pbcopy
|
|
5
|
+
* Linux: uses xclip
|
|
6
|
+
* Throws ClipboardUnavailableError if no clipboard tool is available.
|
|
7
|
+
*/
|
|
8
|
+
export async function writeClipboard(text) {
|
|
9
|
+
if (process.platform === "darwin") {
|
|
10
|
+
execSync("pbcopy", { input: text });
|
|
11
|
+
}
|
|
12
|
+
else if (process.platform === "linux") {
|
|
13
|
+
execSync("xclip -selection clipboard", { input: text });
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
throw new ClipboardUnavailableError(`Clipboard not supported on platform: ${process.platform}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Read text from the system clipboard.
|
|
21
|
+
* macOS: uses pbpaste
|
|
22
|
+
* Linux: uses xclip
|
|
23
|
+
* Throws ClipboardUnavailableError if no clipboard tool is available.
|
|
24
|
+
*/
|
|
25
|
+
export async function readClipboard() {
|
|
26
|
+
if (process.platform === "darwin") {
|
|
27
|
+
return execSync("pbpaste").toString();
|
|
28
|
+
}
|
|
29
|
+
else if (process.platform === "linux") {
|
|
30
|
+
return execSync("xclip -selection clipboard -o").toString();
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
throw new ClipboardUnavailableError(`Clipboard not supported on platform: ${process.platform}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export class ClipboardUnavailableError extends Error {
|
|
37
|
+
constructor(message) {
|
|
38
|
+
super(message);
|
|
39
|
+
this.name = "ClipboardUnavailableError";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** Check if clipboard operations are available on this platform/system */
|
|
43
|
+
export function isClipboardAvailable() {
|
|
44
|
+
if (process.platform === "darwin")
|
|
45
|
+
return true;
|
|
46
|
+
if (process.platform === "linux") {
|
|
47
|
+
try {
|
|
48
|
+
execSync("which xclip", { stdio: "ignore" });
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Write text to the system clipboard.
|
|
5
|
+
* macOS: uses pbcopy
|
|
6
|
+
* Linux: uses xclip
|
|
7
|
+
* Throws ClipboardUnavailableError if no clipboard tool is available.
|
|
8
|
+
*/
|
|
9
|
+
export async function writeClipboard(text: string): Promise<void> {
|
|
10
|
+
if (process.platform === "darwin") {
|
|
11
|
+
execSync("pbcopy", { input: text });
|
|
12
|
+
} else if (process.platform === "linux") {
|
|
13
|
+
execSync("xclip -selection clipboard", { input: text });
|
|
14
|
+
} else {
|
|
15
|
+
throw new ClipboardUnavailableError(
|
|
16
|
+
`Clipboard not supported on platform: ${process.platform}`,
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Read text from the system clipboard.
|
|
23
|
+
* macOS: uses pbpaste
|
|
24
|
+
* Linux: uses xclip
|
|
25
|
+
* Throws ClipboardUnavailableError if no clipboard tool is available.
|
|
26
|
+
*/
|
|
27
|
+
export async function readClipboard(): Promise<string> {
|
|
28
|
+
if (process.platform === "darwin") {
|
|
29
|
+
return execSync("pbpaste").toString();
|
|
30
|
+
} else if (process.platform === "linux") {
|
|
31
|
+
return execSync("xclip -selection clipboard -o").toString();
|
|
32
|
+
} else {
|
|
33
|
+
throw new ClipboardUnavailableError(
|
|
34
|
+
`Clipboard not supported on platform: ${process.platform}`,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export class ClipboardUnavailableError extends Error {
|
|
40
|
+
constructor(message: string) {
|
|
41
|
+
super(message);
|
|
42
|
+
this.name = "ClipboardUnavailableError";
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Check if clipboard operations are available on this platform/system */
|
|
47
|
+
export function isClipboardAvailable(): boolean {
|
|
48
|
+
if (process.platform === "darwin") return true;
|
|
49
|
+
if (process.platform === "linux") {
|
|
50
|
+
try {
|
|
51
|
+
execSync("which xclip", { stdio: "ignore" });
|
|
52
|
+
return true;
|
|
53
|
+
} catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|