@gtkx/cli 0.14.0 → 0.15.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/dist/mcp-client.js +23 -1
- package/package.json +6 -6
- package/templates/claude/EXAMPLES.md.ejs +10 -2
- package/templates/claude/WIDGETS.md.ejs +167 -13
package/dist/mcp-client.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as net from "node:net";
|
|
2
2
|
import { getNativeId, getNativeObject } from "@gtkx/ffi";
|
|
3
|
+
import { Value } from "@gtkx/ffi/gobject";
|
|
3
4
|
import * as Gtk from "@gtkx/ffi/gtk";
|
|
4
5
|
import { DEFAULT_SOCKET_PATH, IpcRequestSchema, IpcResponseSchema, McpError, McpErrorCode, methodNotFoundError, widgetNotFoundError, } from "@gtkx/mcp";
|
|
5
6
|
import { getApplication } from "@gtkx/react";
|
|
@@ -354,7 +355,28 @@ class McpClient {
|
|
|
354
355
|
if (!widget) {
|
|
355
356
|
throw widgetNotFoundError(p.widgetId);
|
|
356
357
|
}
|
|
357
|
-
const signalArgs = (p.args ?? [])
|
|
358
|
+
const signalArgs = (p.args ?? []).map((arg) => {
|
|
359
|
+
switch (arg.type) {
|
|
360
|
+
case "boolean":
|
|
361
|
+
return Value.newFromBoolean(arg.value);
|
|
362
|
+
case "int":
|
|
363
|
+
return Value.newFromInt(arg.value);
|
|
364
|
+
case "uint":
|
|
365
|
+
return Value.newFromUint(arg.value);
|
|
366
|
+
case "int64":
|
|
367
|
+
return Value.newFromInt64(arg.value);
|
|
368
|
+
case "uint64":
|
|
369
|
+
return Value.newFromUint64(arg.value);
|
|
370
|
+
case "float":
|
|
371
|
+
return Value.newFromFloat(arg.value);
|
|
372
|
+
case "double":
|
|
373
|
+
return Value.newFromDouble(arg.value);
|
|
374
|
+
case "string":
|
|
375
|
+
return Value.newFromString(arg.value);
|
|
376
|
+
default:
|
|
377
|
+
throw new McpError(McpErrorCode.INVALID_REQUEST, `Unknown argument type: ${arg.type}`);
|
|
378
|
+
}
|
|
379
|
+
});
|
|
358
380
|
await testing.fireEvent(widget, p.signal, ...signalArgs);
|
|
359
381
|
return { success: true };
|
|
360
382
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gtkx/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "CLI for GTKX - create and develop GTK4 React applications",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"gtkx",
|
|
@@ -58,19 +58,19 @@
|
|
|
58
58
|
"ejs": "^3.1.10",
|
|
59
59
|
"react-refresh": "^0.18.0",
|
|
60
60
|
"vite": "^7.3.1",
|
|
61
|
-
"@gtkx/
|
|
62
|
-
"@gtkx/
|
|
63
|
-
"@gtkx/
|
|
61
|
+
"@gtkx/mcp": "0.15.0",
|
|
62
|
+
"@gtkx/react": "0.15.0",
|
|
63
|
+
"@gtkx/ffi": "0.15.0"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
66
|
"@types/ejs": "^3.1.5",
|
|
67
67
|
"@types/react-refresh": "^0.14.7",
|
|
68
68
|
"memfs": "^4.51.1",
|
|
69
|
-
"@gtkx/testing": "0.
|
|
69
|
+
"@gtkx/testing": "0.15.0"
|
|
70
70
|
},
|
|
71
71
|
"peerDependencies": {
|
|
72
72
|
"react": "^19",
|
|
73
|
-
"@gtkx/testing": "0.
|
|
73
|
+
"@gtkx/testing": "0.15.0"
|
|
74
74
|
},
|
|
75
75
|
"peerDependenciesMeta": {
|
|
76
76
|
"@gtkx/testing": {
|
|
@@ -339,7 +339,7 @@ const FileTable = () => {
|
|
|
339
339
|
|
|
340
340
|
return (
|
|
341
341
|
<GtkScrolledWindow vexpand cssClasses={["card"]}>
|
|
342
|
-
<GtkColumnView estimatedRowHeight={48} sortColumn={sortColumn} sortOrder={sortOrder}
|
|
342
|
+
<GtkColumnView estimatedRowHeight={48} sortColumn={sortColumn} sortOrder={sortOrder} onSortChanged={handleSort}>
|
|
343
343
|
<x.ColumnViewColumn<FileItem> title="Name" id="name" expand sortable renderCell={(f) => <GtkLabel label={f?.name ?? ""} />} />
|
|
344
344
|
<x.ColumnViewColumn<FileItem> title="Size" id="size" fixedWidth={100} sortable renderCell={(f) => <GtkLabel label={`${f?.size ?? 0} KB`} />} />
|
|
345
345
|
<x.ColumnViewColumn<FileItem> title="Modified" id="modified" fixedWidth={120} sortable renderCell={(f) => <GtkLabel label={f?.modified ?? ""} />} />
|
|
@@ -701,7 +701,15 @@ const ViewSwitcher = () => {
|
|
|
701
701
|
|
|
702
702
|
return (
|
|
703
703
|
<GtkBox orientation={Gtk.Orientation.VERTICAL} spacing={12}>
|
|
704
|
-
<AdwToggleGroup
|
|
704
|
+
<AdwToggleGroup
|
|
705
|
+
activeName={view}
|
|
706
|
+
onNotify={(group, prop) => {
|
|
707
|
+
if (prop === "active-name") {
|
|
708
|
+
setView(group.getActiveName() ?? "list");
|
|
709
|
+
}
|
|
710
|
+
}}
|
|
711
|
+
halign={Gtk.Align.CENTER}
|
|
712
|
+
>
|
|
705
713
|
<x.Toggle id="list" iconName="view-list-symbolic" tooltip="List view" />
|
|
706
714
|
<x.Toggle id="grid" iconName="view-grid-symbolic" tooltip="Grid view" />
|
|
707
715
|
</AdwToggleGroup>
|
|
@@ -169,7 +169,7 @@ Grid-based virtual scrolling.
|
|
|
169
169
|
Table with sortable columns.
|
|
170
170
|
|
|
171
171
|
```tsx
|
|
172
|
-
<GtkColumnView estimatedRowHeight={48} sortColumn="name" sortOrder={Gtk.SortType.ASCENDING}
|
|
172
|
+
<GtkColumnView estimatedRowHeight={48} sortColumn="name" sortOrder={Gtk.SortType.ASCENDING} onSortChanged={handleSort}>
|
|
173
173
|
<x.ColumnViewColumn<Item>
|
|
174
174
|
title="Name"
|
|
175
175
|
id="name"
|
|
@@ -500,7 +500,16 @@ Input in list row.
|
|
|
500
500
|
Segmented button group for mutually exclusive options.
|
|
501
501
|
|
|
502
502
|
```tsx
|
|
503
|
-
|
|
503
|
+
const [viewMode, setViewMode] = useState("list");
|
|
504
|
+
|
|
505
|
+
<AdwToggleGroup
|
|
506
|
+
activeName={viewMode}
|
|
507
|
+
onNotify={(group, prop) => {
|
|
508
|
+
if (prop === "active-name") {
|
|
509
|
+
setViewMode(group.getActiveName() ?? "list");
|
|
510
|
+
}
|
|
511
|
+
}}
|
|
512
|
+
>
|
|
504
513
|
<x.Toggle id="list" iconName="view-list-symbolic" tooltip="List view" />
|
|
505
514
|
<x.Toggle id="grid" iconName="view-grid-symbolic" tooltip="Grid view" />
|
|
506
515
|
<x.Toggle id="flow" label="Flow" />
|
|
@@ -578,33 +587,26 @@ All widgets support drag-and-drop through props. Use `onDragPrepare`, `onDragBeg
|
|
|
578
587
|
|
|
579
588
|
```tsx
|
|
580
589
|
import * as Gdk from "@gtkx/ffi/gdk";
|
|
581
|
-
import
|
|
582
|
-
import { typeFromName } from "@gtkx/ffi/gobject";
|
|
590
|
+
import { Type, Value } from "@gtkx/ffi/gobject";
|
|
583
591
|
import { GtkButton, GtkBox, GtkLabel } from "@gtkx/react";
|
|
584
592
|
import { useState } from "react";
|
|
585
593
|
|
|
586
594
|
const DraggableButton = ({ label }: { label: string }) => {
|
|
587
|
-
const stringType = typeFromName("gchararray");
|
|
588
|
-
const value = new GObject.Value();
|
|
589
|
-
value.init(stringType);
|
|
590
|
-
value.setString(label);
|
|
591
|
-
|
|
592
595
|
return (
|
|
593
596
|
<GtkButton
|
|
594
597
|
label={label}
|
|
595
|
-
onDragPrepare={() => Gdk.ContentProvider.newForValue(
|
|
598
|
+
onDragPrepare={() => Gdk.ContentProvider.newForValue(Value.newFromString(label))}
|
|
596
599
|
/>
|
|
597
600
|
);
|
|
598
601
|
};
|
|
599
602
|
|
|
600
603
|
const DropZone = () => {
|
|
601
604
|
const [dropped, setDropped] = useState<string | null>(null);
|
|
602
|
-
const stringType = typeFromName("gchararray");
|
|
603
605
|
|
|
604
606
|
return (
|
|
605
607
|
<GtkBox
|
|
606
|
-
dropTypes={[
|
|
607
|
-
onDrop={(value:
|
|
608
|
+
dropTypes={[Type.STRING]}
|
|
609
|
+
onDrop={(value: Value) => {
|
|
608
610
|
setDropped(value.getString());
|
|
609
611
|
return true;
|
|
610
612
|
}}
|
|
@@ -613,3 +615,155 @@ const DropZone = () => {
|
|
|
613
615
|
</GtkBox>
|
|
614
616
|
);
|
|
615
617
|
};
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
## GValue Factories
|
|
621
|
+
|
|
622
|
+
Create typed values for drag-and-drop and signal emission:
|
|
623
|
+
|
|
624
|
+
| Factory | Description |
|
|
625
|
+
| ------------------------------ | ----------------------------- |
|
|
626
|
+
| `Value.newFromString(str)` | String values |
|
|
627
|
+
| `Value.newFromDouble(num)` | 64-bit floating point |
|
|
628
|
+
| `Value.newFromInt(num)` | 32-bit signed integer |
|
|
629
|
+
| `Value.newFromBoolean(bool)` | Boolean values |
|
|
630
|
+
| `Value.newFromObject(obj)` | GObject instances |
|
|
631
|
+
| `Value.newFromBoxed(boxed)` | Boxed types (Gdk.RGBA, etc.) |
|
|
632
|
+
| `Value.newFromEnum(gtype, n)` | Enum values (requires GType) |
|
|
633
|
+
| `Value.newFromFlags(gtype, n)` | Flags values (requires GType) |
|
|
634
|
+
|
|
635
|
+
Type constants for `dropTypes`: `Type.STRING`, `Type.INT`, `Type.DOUBLE`, `Type.BOOLEAN`, `Type.OBJECT`.
|
|
636
|
+
|
|
637
|
+
## Custom Drawing
|
|
638
|
+
|
|
639
|
+
Render custom graphics with `GtkDrawingArea` using the `onDraw` callback:
|
|
640
|
+
|
|
641
|
+
```tsx
|
|
642
|
+
import { GtkDrawingArea } from "@gtkx/react";
|
|
643
|
+
import type { Context } from "@gtkx/ffi/cairo";
|
|
644
|
+
import * as Gtk from "@gtkx/ffi/gtk";
|
|
645
|
+
|
|
646
|
+
const Canvas = () => {
|
|
647
|
+
const handleDraw = (self: Gtk.DrawingArea, cr: Context, width: number, height: number) => {
|
|
648
|
+
cr.setSourceRgb(0.2, 0.4, 0.8);
|
|
649
|
+
cr.rectangle(10, 10, width - 20, height - 20);
|
|
650
|
+
cr.fill();
|
|
651
|
+
};
|
|
652
|
+
|
|
653
|
+
return <GtkDrawingArea contentWidth={400} contentHeight={300} onDraw={handleDraw} />;
|
|
654
|
+
};
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
Use `onGestureDragBegin`, `onGestureDragUpdate`, `onGestureDragEnd` for interactive drawing. Call `widget.queueDraw()` to trigger redraws.
|
|
658
|
+
|
|
659
|
+
## Adjustment
|
|
660
|
+
|
|
661
|
+
Configure adjustable widgets declaratively with `x.Adjustment`:
|
|
662
|
+
|
|
663
|
+
```tsx
|
|
664
|
+
import { x, GtkScale, GtkBox, GtkLabel } from "@gtkx/react";
|
|
665
|
+
import * as Gtk from "@gtkx/ffi/gtk";
|
|
666
|
+
import { useState } from "react";
|
|
667
|
+
|
|
668
|
+
const VolumeControl = () => {
|
|
669
|
+
const [volume, setVolume] = useState(50);
|
|
670
|
+
|
|
671
|
+
return (
|
|
672
|
+
<GtkBox orientation={Gtk.Orientation.VERTICAL} spacing={12}>
|
|
673
|
+
<GtkScale drawValue hexpand>
|
|
674
|
+
<x.Adjustment value={volume} lower={0} upper={100} stepIncrement={1} pageIncrement={10} onValueChanged={setVolume} />
|
|
675
|
+
</GtkScale>
|
|
676
|
+
<GtkLabel label={`Volume: ${Math.round(volume)}%`} />
|
|
677
|
+
</GtkBox>
|
|
678
|
+
);
|
|
679
|
+
};
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
Works with `GtkScale`, `GtkScrollbar`, `GtkScaleButton`, `GtkSpinButton`, `GtkListBox`.
|
|
683
|
+
|
|
684
|
+
**Props:** `value`, `lower`, `upper`, `stepIncrement`, `pageIncrement`, `pageSize`, `onValueChanged`
|
|
685
|
+
|
|
686
|
+
## TextBuffer
|
|
687
|
+
|
|
688
|
+
Configure `GtkTextView` buffers declaratively with `x.TextBuffer`:
|
|
689
|
+
|
|
690
|
+
```tsx
|
|
691
|
+
import { x, GtkTextView, GtkScrolledWindow } from "@gtkx/react";
|
|
692
|
+
import * as Gtk from "@gtkx/ffi/gtk";
|
|
693
|
+
import { useState } from "react";
|
|
694
|
+
|
|
695
|
+
const TextEditor = () => {
|
|
696
|
+
const [text, setText] = useState("Hello, World!");
|
|
697
|
+
|
|
698
|
+
return (
|
|
699
|
+
<GtkScrolledWindow minContentHeight={200}>
|
|
700
|
+
<GtkTextView wrapMode={Gtk.WrapMode.WORD_CHAR}>
|
|
701
|
+
<x.TextBuffer text={text} enableUndo onTextChanged={setText} />
|
|
702
|
+
</GtkTextView>
|
|
703
|
+
</GtkScrolledWindow>
|
|
704
|
+
);
|
|
705
|
+
};
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
**Props:** `text`, `enableUndo`, `onTextChanged`, `onCanUndoChanged`, `onCanRedoChanged`
|
|
709
|
+
|
|
710
|
+
## SourceBuffer
|
|
711
|
+
|
|
712
|
+
Configure `GtkSourceView` buffers with syntax highlighting using `x.SourceBuffer`:
|
|
713
|
+
|
|
714
|
+
```tsx
|
|
715
|
+
import { x, GtkSourceView, GtkScrolledWindow } from "@gtkx/react";
|
|
716
|
+
import { useState } from "react";
|
|
717
|
+
|
|
718
|
+
const CodeEditor = () => {
|
|
719
|
+
const [code, setCode] = useState('console.log("Hello!");');
|
|
720
|
+
|
|
721
|
+
return (
|
|
722
|
+
<GtkScrolledWindow minContentHeight={300}>
|
|
723
|
+
<GtkSourceView showLineNumbers highlightCurrentLine tabWidth={4}>
|
|
724
|
+
<x.SourceBuffer
|
|
725
|
+
text={code}
|
|
726
|
+
language="typescript"
|
|
727
|
+
styleScheme="Adwaita-dark"
|
|
728
|
+
highlightSyntax
|
|
729
|
+
highlightMatchingBrackets
|
|
730
|
+
enableUndo
|
|
731
|
+
onTextChanged={setCode}
|
|
732
|
+
/>
|
|
733
|
+
</GtkSourceView>
|
|
734
|
+
</GtkScrolledWindow>
|
|
735
|
+
);
|
|
736
|
+
};
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
**Props:** `text`, `language`, `styleScheme`, `highlightSyntax`, `highlightMatchingBrackets`, `enableUndo`, `onTextChanged`, `onCanUndoChanged`, `onCanRedoChanged`, `onCursorMoved`
|
|
740
|
+
|
|
741
|
+
## Keyboard Shortcuts
|
|
742
|
+
|
|
743
|
+
Attach shortcuts with `x.ShortcutController` and `x.Shortcut`:
|
|
744
|
+
|
|
745
|
+
```tsx
|
|
746
|
+
import { x, GtkBox, GtkLabel } from "@gtkx/react";
|
|
747
|
+
import * as Gtk from "@gtkx/ffi/gtk";
|
|
748
|
+
import { useState } from "react";
|
|
749
|
+
|
|
750
|
+
const App = () => {
|
|
751
|
+
const [count, setCount] = useState(0);
|
|
752
|
+
|
|
753
|
+
return (
|
|
754
|
+
<GtkBox orientation={Gtk.Orientation.VERTICAL} spacing={12} focusable>
|
|
755
|
+
<x.ShortcutController scope={Gtk.ShortcutScope.LOCAL}>
|
|
756
|
+
<x.Shortcut trigger="<Control>equal" onActivate={() => setCount((c) => c + 1)} />
|
|
757
|
+
<x.Shortcut trigger="<Control>minus" onActivate={() => setCount((c) => c - 1)} />
|
|
758
|
+
</x.ShortcutController>
|
|
759
|
+
<GtkLabel label={`Count: ${count}`} />
|
|
760
|
+
</GtkBox>
|
|
761
|
+
);
|
|
762
|
+
};
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
**Scopes:** `LOCAL` (widget focus), `MANAGED` (parent managed), `GLOBAL` (window-wide)
|
|
766
|
+
|
|
767
|
+
**Trigger syntax:** `<Control>s`, `<Control><Shift>s`, `<Alt>F4`, `<Primary>q`, `F5`
|
|
768
|
+
|
|
769
|
+
**Multiple triggers:** `trigger={["F5", "<Control>r"]}`
|