claudeup 3.7.2 → 3.9.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.
Files changed (52) hide show
  1. package/package.json +1 -1
  2. package/src/data/settings-catalog.js +612 -0
  3. package/src/data/settings-catalog.ts +689 -0
  4. package/src/data/skill-repos.js +86 -0
  5. package/src/data/skill-repos.ts +97 -0
  6. package/src/services/plugin-manager.js +2 -0
  7. package/src/services/plugin-manager.ts +3 -0
  8. package/src/services/profiles.js +161 -0
  9. package/src/services/profiles.ts +225 -0
  10. package/src/services/settings-manager.js +108 -0
  11. package/src/services/settings-manager.ts +140 -0
  12. package/src/services/skills-manager.js +239 -0
  13. package/src/services/skills-manager.ts +328 -0
  14. package/src/services/skillsmp-client.js +67 -0
  15. package/src/services/skillsmp-client.ts +89 -0
  16. package/src/types/index.ts +101 -1
  17. package/src/ui/App.js +23 -18
  18. package/src/ui/App.tsx +27 -23
  19. package/src/ui/components/TabBar.js +9 -8
  20. package/src/ui/components/TabBar.tsx +15 -19
  21. package/src/ui/components/layout/ScreenLayout.js +8 -14
  22. package/src/ui/components/layout/ScreenLayout.tsx +51 -58
  23. package/src/ui/components/modals/ModalContainer.js +43 -11
  24. package/src/ui/components/modals/ModalContainer.tsx +44 -12
  25. package/src/ui/components/modals/SelectModal.js +4 -18
  26. package/src/ui/components/modals/SelectModal.tsx +10 -21
  27. package/src/ui/screens/CliToolsScreen.js +2 -2
  28. package/src/ui/screens/CliToolsScreen.tsx +8 -8
  29. package/src/ui/screens/EnvVarsScreen.js +248 -116
  30. package/src/ui/screens/EnvVarsScreen.tsx +419 -184
  31. package/src/ui/screens/McpRegistryScreen.tsx +18 -6
  32. package/src/ui/screens/McpScreen.js +1 -1
  33. package/src/ui/screens/McpScreen.tsx +15 -5
  34. package/src/ui/screens/ModelSelectorScreen.js +3 -5
  35. package/src/ui/screens/ModelSelectorScreen.tsx +12 -16
  36. package/src/ui/screens/PluginsScreen.js +154 -66
  37. package/src/ui/screens/PluginsScreen.tsx +280 -97
  38. package/src/ui/screens/ProfilesScreen.js +255 -0
  39. package/src/ui/screens/ProfilesScreen.tsx +487 -0
  40. package/src/ui/screens/SkillsScreen.js +325 -0
  41. package/src/ui/screens/SkillsScreen.tsx +574 -0
  42. package/src/ui/screens/StatusLineScreen.js +2 -2
  43. package/src/ui/screens/StatusLineScreen.tsx +10 -12
  44. package/src/ui/screens/index.js +3 -2
  45. package/src/ui/screens/index.ts +3 -2
  46. package/src/ui/state/AppContext.js +2 -1
  47. package/src/ui/state/AppContext.tsx +2 -0
  48. package/src/ui/state/reducer.js +151 -19
  49. package/src/ui/state/reducer.ts +167 -19
  50. package/src/ui/state/types.ts +58 -14
  51. package/src/utils/clipboard.js +56 -0
  52. package/src/utils/clipboard.ts +58 -0
@@ -91,7 +91,7 @@ export function useModal() {
91
91
  });
92
92
  });
93
93
  },
94
- select: (title, message, options) => {
94
+ select: (title, message, options, defaultIndex) => {
95
95
  return new Promise((resolve) => {
96
96
  dispatch({
97
97
  type: "SHOW_MODAL",
@@ -100,6 +100,7 @@ export function useModal() {
100
100
  title,
101
101
  message,
102
102
  options,
103
+ defaultIndex,
103
104
  onSelect: (value) => {
104
105
  dispatch({ type: "HIDE_MODAL" });
105
106
  resolve(value);
@@ -153,6 +153,7 @@ export function useModal() {
153
153
  title: string,
154
154
  message: string,
155
155
  options: { label: string; value: string; description?: string }[],
156
+ defaultIndex?: number,
156
157
  ): Promise<string | null> => {
157
158
  return new Promise((resolve) => {
158
159
  dispatch({
@@ -162,6 +163,7 @@ export function useModal() {
162
163
  title,
163
164
  message,
164
165
  options,
166
+ defaultIndex,
165
167
  onSelect: (value: string) => {
166
168
  dispatch({ type: "HIDE_MODAL" });
167
169
  resolve(value);
@@ -34,9 +34,9 @@ export const initialState = {
34
34
  presets: { status: "idle" },
35
35
  currentPreset: null,
36
36
  },
37
- envVars: {
37
+ settings: {
38
38
  selectedIndex: 0,
39
- variables: { status: "idle" },
39
+ values: { status: "idle" },
40
40
  },
41
41
  cliTools: {
42
42
  selectedIndex: 0,
@@ -47,6 +47,17 @@ export const initialState = {
47
47
  searchQuery: "",
48
48
  taskSize: "large",
49
49
  },
50
+ profiles: {
51
+ selectedIndex: 0,
52
+ profiles: { status: "idle" },
53
+ },
54
+ skills: {
55
+ scope: "user",
56
+ selectedIndex: 0,
57
+ searchQuery: "",
58
+ skills: { status: "idle" },
59
+ updateStatus: null,
60
+ },
50
61
  };
51
62
  export function appReducer(state, action) {
52
63
  switch (action.type) {
@@ -54,7 +65,12 @@ export function appReducer(state, action) {
54
65
  // Navigation
55
66
  // =========================================================================
56
67
  case "NAVIGATE":
57
- return { ...state, currentRoute: action.route };
68
+ return {
69
+ ...state,
70
+ currentRoute: action.route,
71
+ // Clear search state when navigating away
72
+ isSearching: false,
73
+ };
58
74
  // =========================================================================
59
75
  // Plugins Screen
60
76
  // =========================================================================
@@ -268,38 +284,38 @@ export function appReducer(state, action) {
268
284
  },
269
285
  };
270
286
  // =========================================================================
271
- // Env Vars Screen
287
+ // Settings Screen
272
288
  // =========================================================================
273
- case "ENVVARS_SELECT":
289
+ case "SETTINGS_SELECT":
274
290
  return {
275
291
  ...state,
276
- envVars: {
277
- ...state.envVars,
292
+ settings: {
293
+ ...state.settings,
278
294
  selectedIndex: action.index,
279
295
  },
280
296
  };
281
- case "ENVVARS_DATA_LOADING":
297
+ case "SETTINGS_DATA_LOADING":
282
298
  return {
283
299
  ...state,
284
- envVars: {
285
- ...state.envVars,
286
- variables: { status: "loading" },
300
+ settings: {
301
+ ...state.settings,
302
+ values: { status: "loading" },
287
303
  },
288
304
  };
289
- case "ENVVARS_DATA_SUCCESS":
305
+ case "SETTINGS_DATA_SUCCESS":
290
306
  return {
291
307
  ...state,
292
- envVars: {
293
- ...state.envVars,
294
- variables: { status: "success", data: action.variables },
308
+ settings: {
309
+ ...state.settings,
310
+ values: { status: "success", data: action.values },
295
311
  },
296
312
  };
297
- case "ENVVARS_DATA_ERROR":
313
+ case "SETTINGS_DATA_ERROR":
298
314
  return {
299
315
  ...state,
300
- envVars: {
301
- ...state.envVars,
302
- variables: { status: "error", error: action.error },
316
+ settings: {
317
+ ...state.settings,
318
+ values: { status: "error", error: action.error },
303
319
  },
304
320
  };
305
321
  // =========================================================================
@@ -380,6 +396,122 @@ export function appReducer(state, action) {
380
396
  },
381
397
  };
382
398
  // =========================================================================
399
+ // Profiles Screen
400
+ // =========================================================================
401
+ case "PROFILES_SELECT":
402
+ return {
403
+ ...state,
404
+ profiles: {
405
+ ...state.profiles,
406
+ selectedIndex: action.index,
407
+ },
408
+ };
409
+ case "PROFILES_DATA_LOADING":
410
+ return {
411
+ ...state,
412
+ profiles: {
413
+ ...state.profiles,
414
+ profiles: { status: "loading" },
415
+ },
416
+ };
417
+ case "PROFILES_DATA_SUCCESS":
418
+ return {
419
+ ...state,
420
+ profiles: {
421
+ ...state.profiles,
422
+ profiles: { status: "success", data: action.profiles },
423
+ },
424
+ };
425
+ case "PROFILES_DATA_ERROR":
426
+ return {
427
+ ...state,
428
+ profiles: {
429
+ ...state.profiles,
430
+ profiles: { status: "error", error: action.error },
431
+ },
432
+ };
433
+ // =========================================================================
434
+ // Skills Screen
435
+ // =========================================================================
436
+ case "SKILLS_SET_SCOPE":
437
+ return {
438
+ ...state,
439
+ skills: {
440
+ ...state.skills,
441
+ scope: action.scope,
442
+ selectedIndex: 0,
443
+ skills: { status: "loading" },
444
+ },
445
+ };
446
+ case "SKILLS_TOGGLE_SCOPE":
447
+ return appReducer(state, {
448
+ type: "SKILLS_SET_SCOPE",
449
+ scope: state.skills.scope === "user" ? "project" : "user",
450
+ });
451
+ case "SKILLS_SELECT":
452
+ return {
453
+ ...state,
454
+ skills: {
455
+ ...state.skills,
456
+ selectedIndex: action.index,
457
+ },
458
+ };
459
+ case "SKILLS_SET_SEARCH":
460
+ return {
461
+ ...state,
462
+ skills: {
463
+ ...state.skills,
464
+ searchQuery: action.query,
465
+ selectedIndex: 0,
466
+ },
467
+ };
468
+ case "SKILLS_DATA_LOADING":
469
+ return {
470
+ ...state,
471
+ skills: {
472
+ ...state.skills,
473
+ skills: { status: "loading" },
474
+ },
475
+ };
476
+ case "SKILLS_DATA_SUCCESS":
477
+ return {
478
+ ...state,
479
+ skills: {
480
+ ...state.skills,
481
+ skills: { status: "success", data: action.skills },
482
+ },
483
+ };
484
+ case "SKILLS_DATA_ERROR":
485
+ return {
486
+ ...state,
487
+ skills: {
488
+ ...state.skills,
489
+ skills: { status: "error", error: action.error },
490
+ },
491
+ };
492
+ case "SKILLS_UPDATE_STATUS":
493
+ return {
494
+ ...state,
495
+ skills: {
496
+ ...state.skills,
497
+ updateStatus: action.updates,
498
+ },
499
+ };
500
+ case "SKILLS_UPDATE_ITEM": {
501
+ if (state.skills.skills.status !== "success")
502
+ return state;
503
+ return {
504
+ ...state,
505
+ skills: {
506
+ ...state.skills,
507
+ skills: {
508
+ status: "success",
509
+ data: state.skills.skills.data.map((s) => s.name === action.name ? { ...s, ...action.updates } : s),
510
+ },
511
+ },
512
+ };
513
+ }
514
+ // =========================================================================
383
515
  // Modals
384
516
  // =========================================================================
385
517
  case "SHOW_MODAL":
@@ -44,9 +44,9 @@ export const initialState: AppState = {
44
44
  currentPreset: null,
45
45
  },
46
46
 
47
- envVars: {
47
+ settings: {
48
48
  selectedIndex: 0,
49
- variables: { status: "idle" },
49
+ values: { status: "idle" },
50
50
  },
51
51
 
52
52
  cliTools: {
@@ -59,6 +59,19 @@ export const initialState: AppState = {
59
59
  searchQuery: "",
60
60
  taskSize: "large",
61
61
  },
62
+
63
+ profiles: {
64
+ selectedIndex: 0,
65
+ profiles: { status: "idle" },
66
+ },
67
+
68
+ skills: {
69
+ scope: "user",
70
+ selectedIndex: 0,
71
+ searchQuery: "",
72
+ skills: { status: "idle" },
73
+ updateStatus: null,
74
+ },
62
75
  };
63
76
 
64
77
  export function appReducer(state: AppState, action: AppAction): AppState {
@@ -67,7 +80,12 @@ export function appReducer(state: AppState, action: AppAction): AppState {
67
80
  // Navigation
68
81
  // =========================================================================
69
82
  case "NAVIGATE":
70
- return { ...state, currentRoute: action.route };
83
+ return {
84
+ ...state,
85
+ currentRoute: action.route,
86
+ // Clear search state when navigating away
87
+ isSearching: false,
88
+ };
71
89
 
72
90
  // =========================================================================
73
91
  // Plugins Screen
@@ -304,41 +322,41 @@ export function appReducer(state: AppState, action: AppAction): AppState {
304
322
  };
305
323
 
306
324
  // =========================================================================
307
- // Env Vars Screen
325
+ // Settings Screen
308
326
  // =========================================================================
309
- case "ENVVARS_SELECT":
327
+ case "SETTINGS_SELECT":
310
328
  return {
311
329
  ...state,
312
- envVars: {
313
- ...state.envVars,
330
+ settings: {
331
+ ...state.settings,
314
332
  selectedIndex: action.index,
315
333
  },
316
334
  };
317
335
 
318
- case "ENVVARS_DATA_LOADING":
336
+ case "SETTINGS_DATA_LOADING":
319
337
  return {
320
338
  ...state,
321
- envVars: {
322
- ...state.envVars,
323
- variables: { status: "loading" },
339
+ settings: {
340
+ ...state.settings,
341
+ values: { status: "loading" },
324
342
  },
325
343
  };
326
344
 
327
- case "ENVVARS_DATA_SUCCESS":
345
+ case "SETTINGS_DATA_SUCCESS":
328
346
  return {
329
347
  ...state,
330
- envVars: {
331
- ...state.envVars,
332
- variables: { status: "success", data: action.variables },
348
+ settings: {
349
+ ...state.settings,
350
+ values: { status: "success", data: action.values },
333
351
  },
334
352
  };
335
353
 
336
- case "ENVVARS_DATA_ERROR":
354
+ case "SETTINGS_DATA_ERROR":
337
355
  return {
338
356
  ...state,
339
- envVars: {
340
- ...state.envVars,
341
- variables: { status: "error", error: action.error },
357
+ settings: {
358
+ ...state.settings,
359
+ values: { status: "error", error: action.error },
342
360
  },
343
361
  };
344
362
 
@@ -428,6 +446,136 @@ export function appReducer(state: AppState, action: AppAction): AppState {
428
446
  },
429
447
  };
430
448
 
449
+ // =========================================================================
450
+ // Profiles Screen
451
+ // =========================================================================
452
+ case "PROFILES_SELECT":
453
+ return {
454
+ ...state,
455
+ profiles: {
456
+ ...state.profiles,
457
+ selectedIndex: action.index,
458
+ },
459
+ };
460
+
461
+ case "PROFILES_DATA_LOADING":
462
+ return {
463
+ ...state,
464
+ profiles: {
465
+ ...state.profiles,
466
+ profiles: { status: "loading" },
467
+ },
468
+ };
469
+
470
+ case "PROFILES_DATA_SUCCESS":
471
+ return {
472
+ ...state,
473
+ profiles: {
474
+ ...state.profiles,
475
+ profiles: { status: "success", data: action.profiles },
476
+ },
477
+ };
478
+
479
+ case "PROFILES_DATA_ERROR":
480
+ return {
481
+ ...state,
482
+ profiles: {
483
+ ...state.profiles,
484
+ profiles: { status: "error", error: action.error },
485
+ },
486
+ };
487
+
488
+ // =========================================================================
489
+ // Skills Screen
490
+ // =========================================================================
491
+ case "SKILLS_SET_SCOPE":
492
+ return {
493
+ ...state,
494
+ skills: {
495
+ ...state.skills,
496
+ scope: action.scope,
497
+ selectedIndex: 0,
498
+ skills: { status: "loading" },
499
+ },
500
+ };
501
+
502
+ case "SKILLS_TOGGLE_SCOPE":
503
+ return appReducer(state, {
504
+ type: "SKILLS_SET_SCOPE",
505
+ scope: state.skills.scope === "user" ? "project" : "user",
506
+ });
507
+
508
+ case "SKILLS_SELECT":
509
+ return {
510
+ ...state,
511
+ skills: {
512
+ ...state.skills,
513
+ selectedIndex: action.index,
514
+ },
515
+ };
516
+
517
+ case "SKILLS_SET_SEARCH":
518
+ return {
519
+ ...state,
520
+ skills: {
521
+ ...state.skills,
522
+ searchQuery: action.query,
523
+ selectedIndex: 0,
524
+ },
525
+ };
526
+
527
+ case "SKILLS_DATA_LOADING":
528
+ return {
529
+ ...state,
530
+ skills: {
531
+ ...state.skills,
532
+ skills: { status: "loading" },
533
+ },
534
+ };
535
+
536
+ case "SKILLS_DATA_SUCCESS":
537
+ return {
538
+ ...state,
539
+ skills: {
540
+ ...state.skills,
541
+ skills: { status: "success", data: action.skills },
542
+ },
543
+ };
544
+
545
+ case "SKILLS_DATA_ERROR":
546
+ return {
547
+ ...state,
548
+ skills: {
549
+ ...state.skills,
550
+ skills: { status: "error", error: action.error },
551
+ },
552
+ };
553
+
554
+ case "SKILLS_UPDATE_STATUS":
555
+ return {
556
+ ...state,
557
+ skills: {
558
+ ...state.skills,
559
+ updateStatus: action.updates,
560
+ },
561
+ };
562
+
563
+ case "SKILLS_UPDATE_ITEM": {
564
+ if (state.skills.skills.status !== "success") return state;
565
+ return {
566
+ ...state,
567
+ skills: {
568
+ ...state.skills,
569
+ skills: {
570
+ status: "success",
571
+ data: state.skills.skills.data.map((s) =>
572
+ s.name === action.name ? { ...s, ...action.updates } : s,
573
+ ),
574
+ },
575
+ },
576
+ };
577
+ }
578
+
431
579
  // =========================================================================
432
580
  // Modals
433
581
  // =========================================================================
@@ -2,6 +2,8 @@ import type {
2
2
  Marketplace,
3
3
  McpServer,
4
4
  StatusLineConfig,
5
+ ProfileEntry,
6
+ SkillInfo,
5
7
  } from "../../types/index.js";
6
8
  import type { PluginInfo } from "../../services/plugin-manager.js";
7
9
 
@@ -13,19 +15,21 @@ export type Screen =
13
15
  | "plugins"
14
16
  | "mcp"
15
17
  | "mcp-registry"
16
- | "statusline"
17
- | "env-vars"
18
+ | "settings"
18
19
  | "cli-tools"
19
- | "model-selector";
20
+ | "model-selector"
21
+ | "profiles"
22
+ | "skills";
20
23
 
21
24
  export type Route =
22
25
  | { screen: "plugins" }
23
26
  | { screen: "mcp" }
24
27
  | { screen: "mcp-registry"; query?: string }
25
- | { screen: "statusline" }
26
- | { screen: "env-vars" }
28
+ | { screen: "settings" }
27
29
  | { screen: "cli-tools" }
28
- | { screen: "model-selector" };
30
+ | { screen: "model-selector" }
31
+ | { screen: "profiles" }
32
+ | { screen: "skills" };
29
33
 
30
34
  // ============================================================================
31
35
  // Async Data Types
@@ -68,6 +72,7 @@ export type ModalState =
68
72
  title: string;
69
73
  message: string;
70
74
  options: SelectOption[];
75
+ defaultIndex?: number;
71
76
  onSelect: (value: string) => void;
72
77
  onCancel: () => void;
73
78
  }
@@ -125,9 +130,14 @@ export interface StatusLineScreenState {
125
130
  currentPreset: string | null;
126
131
  }
127
132
 
128
- export interface EnvVarsScreenState {
133
+ export interface ScopedSettingValues {
134
+ user: string | undefined;
135
+ project: string | undefined;
136
+ }
137
+
138
+ export interface SettingsScreenState {
129
139
  selectedIndex: number;
130
- variables: AsyncData<Record<string, string>>;
140
+ values: AsyncData<Map<string, ScopedSettingValues>>;
131
141
  }
132
142
 
133
143
  export interface CliTool {
@@ -156,6 +166,21 @@ export interface ModelSelectorScreenState {
156
166
  taskSize: "large" | "small";
157
167
  }
158
168
 
169
+ export interface ProfilesScreenState {
170
+ selectedIndex: number;
171
+ profiles: AsyncData<ProfileEntry[]>;
172
+ }
173
+
174
+ export interface SkillsScreenState {
175
+ scope: "user" | "project";
176
+ selectedIndex: number;
177
+ searchQuery: string;
178
+ /** Available skills from Tree API, cross-referenced with lock files */
179
+ skills: AsyncData<SkillInfo[]>;
180
+ /** Null until check completes; then Map<skillName, hasUpdate> */
181
+ updateStatus: Map<string, boolean> | null;
182
+ }
183
+
159
184
  // ============================================================================
160
185
  // App State
161
186
  // ============================================================================
@@ -181,9 +206,11 @@ export interface AppState {
181
206
  mcp: McpScreenState;
182
207
  mcpRegistry: McpRegistryScreenState;
183
208
  statusline: StatusLineScreenState;
184
- envVars: EnvVarsScreenState;
209
+ settings: SettingsScreenState;
185
210
  cliTools: CliToolsScreenState;
186
211
  modelSelector: ModelSelectorScreenState;
212
+ profiles: ProfilesScreenState;
213
+ skills: SkillsScreenState;
187
214
  }
188
215
 
189
216
  // ============================================================================
@@ -239,11 +266,11 @@ export type AppAction =
239
266
  }
240
267
  | { type: "STATUSLINE_DATA_ERROR"; error: Error }
241
268
 
242
- // Env vars screen
243
- | { type: "ENVVARS_SELECT"; index: number }
244
- | { type: "ENVVARS_DATA_LOADING" }
245
- | { type: "ENVVARS_DATA_SUCCESS"; variables: Record<string, string> }
246
- | { type: "ENVVARS_DATA_ERROR"; error: Error }
269
+ // Settings screen
270
+ | { type: "SETTINGS_SELECT"; index: number }
271
+ | { type: "SETTINGS_DATA_LOADING" }
272
+ | { type: "SETTINGS_DATA_SUCCESS"; values: Map<string, ScopedSettingValues> }
273
+ | { type: "SETTINGS_DATA_ERROR"; error: Error }
247
274
 
248
275
  // CLI tools screen
249
276
  | { type: "CLITOOLS_SELECT"; index: number }
@@ -269,5 +296,22 @@ export type AppAction =
269
296
  // Search state (blocks global key handlers)
270
297
  | { type: "SET_SEARCHING"; isSearching: boolean }
271
298
 
299
+ // Profiles screen
300
+ | { type: "PROFILES_SELECT"; index: number }
301
+ | { type: "PROFILES_DATA_LOADING" }
302
+ | { type: "PROFILES_DATA_SUCCESS"; profiles: ProfileEntry[] }
303
+ | { type: "PROFILES_DATA_ERROR"; error: Error }
304
+
305
+ // Skills screen
306
+ | { type: "SKILLS_SET_SCOPE"; scope: "user" | "project" }
307
+ | { type: "SKILLS_TOGGLE_SCOPE" }
308
+ | { type: "SKILLS_SELECT"; index: number }
309
+ | { type: "SKILLS_SET_SEARCH"; query: string }
310
+ | { type: "SKILLS_DATA_LOADING" }
311
+ | { type: "SKILLS_DATA_SUCCESS"; skills: SkillInfo[] }
312
+ | { type: "SKILLS_DATA_ERROR"; error: Error }
313
+ | { type: "SKILLS_UPDATE_STATUS"; updates: Map<string, boolean> }
314
+ | { type: "SKILLS_UPDATE_ITEM"; name: string; updates: Partial<SkillInfo> }
315
+
272
316
  // Data refresh - triggers screens to refetch
273
317
  | { 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
+ }