@gtkx/cli 1.4.0 → 1.6.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/README.md +1 -1
- package/dist/builder.d.ts +1 -0
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +2 -2
- package/dist/builder.js.map +1 -1
- package/dist/cli.js +4 -0
- package/dist/cli.js.map +1 -1
- package/dist/codegen/freshness.d.ts.map +1 -1
- package/dist/codegen/freshness.js +2 -1
- package/dist/codegen/freshness.js.map +1 -1
- package/dist/codegen/reference.d.ts +17 -0
- package/dist/codegen/reference.d.ts.map +1 -0
- package/dist/codegen/reference.js +37 -0
- package/dist/codegen/reference.js.map +1 -0
- package/dist/codegen/report.d.ts.map +1 -1
- package/dist/codegen/report.js +17 -3
- package/dist/codegen/report.js.map +1 -1
- package/dist/codegen/run-codegen.d.ts +4 -0
- package/dist/codegen/run-codegen.d.ts.map +1 -1
- package/dist/codegen/run-codegen.js +93 -27
- package/dist/codegen/run-codegen.js.map +1 -1
- package/dist/commands/codegen.js +1 -1
- package/dist/commands/codegen.js.map +1 -1
- package/dist/commands/docs.d.ts.map +1 -1
- package/dist/commands/docs.js +10 -16
- package/dist/commands/docs.js.map +1 -1
- package/dist/commands/mcp.d.ts +13 -0
- package/dist/commands/mcp.d.ts.map +1 -0
- package/dist/commands/mcp.js +68 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/deploy/freedesktop/localize.d.ts +6 -0
- package/dist/deploy/freedesktop/localize.d.ts.map +1 -0
- package/dist/deploy/freedesktop/localize.js +107 -0
- package/dist/deploy/freedesktop/localize.js.map +1 -0
- package/dist/deploy/install-hints.d.ts.map +1 -1
- package/dist/deploy/install-hints.js +5 -0
- package/dist/deploy/install-hints.js.map +1 -1
- package/dist/deploy/notices/gtkx.js +1 -1
- package/dist/deploy/notices/gtkx.js.map +1 -1
- package/dist/deploy/notices/libraries.d.ts.map +1 -1
- package/dist/deploy/notices/libraries.js +2 -1
- package/dist/deploy/notices/libraries.js.map +1 -1
- package/dist/deploy/payload/launcher.d.ts.map +1 -1
- package/dist/deploy/payload/launcher.js +2 -0
- package/dist/deploy/payload/launcher.js.map +1 -1
- package/dist/deploy/payload/stage.d.ts +1 -1
- package/dist/deploy/payload/stage.d.ts.map +1 -1
- package/dist/deploy/payload/stage.js +20 -11
- package/dist/deploy/payload/stage.js.map +1 -1
- package/dist/deploy/run-deploy.d.ts.map +1 -1
- package/dist/deploy/run-deploy.js +87 -15
- package/dist/deploy/run-deploy.js.map +1 -1
- package/dist/deploy/settings/libraries.d.ts.map +1 -1
- package/dist/deploy/settings/libraries.js +7 -9
- package/dist/deploy/settings/libraries.js.map +1 -1
- package/dist/deploy/targets/flatpak-source.d.ts.map +1 -1
- package/dist/deploy/targets/flatpak-source.js +21 -10
- package/dist/deploy/targets/flatpak-source.js.map +1 -1
- package/dist/deploy/tools.d.ts +6 -1
- package/dist/deploy/tools.d.ts.map +1 -1
- package/dist/deploy/tools.js +27 -4
- package/dist/deploy/tools.js.map +1 -1
- package/dist/dev/locale-dir.d.ts +3 -0
- package/dist/dev/locale-dir.d.ts.map +1 -0
- package/dist/dev/locale-dir.js +17 -0
- package/dist/dev/locale-dir.js.map +1 -0
- package/dist/dev/runner-main.d.ts.map +1 -1
- package/dist/dev/runner-main.js +4 -1
- package/dist/dev/runner-main.js.map +1 -1
- package/dist/dev/runner.d.ts.map +1 -1
- package/dist/dev/runner.js +6 -0
- package/dist/dev/runner.js.map +1 -1
- package/dist/dev/vite-dev-server.d.ts +1 -0
- package/dist/dev/vite-dev-server.d.ts.map +1 -1
- package/dist/dev/vite-dev-server.js.map +1 -1
- package/dist/i18n/binding-extractor.d.ts +30 -0
- package/dist/i18n/binding-extractor.d.ts.map +1 -0
- package/dist/i18n/binding-extractor.js +1324 -0
- package/dist/i18n/binding-extractor.js.map +1 -0
- package/dist/i18n/catalog-template.d.ts +3 -0
- package/dist/i18n/catalog-template.d.ts.map +1 -0
- package/dist/i18n/catalog-template.js +18 -0
- package/dist/i18n/catalog-template.js.map +1 -0
- package/dist/i18n/catalogs.d.ts +21 -0
- package/dist/i18n/catalogs.d.ts.map +1 -0
- package/dist/i18n/catalogs.js +139 -0
- package/dist/i18n/catalogs.js.map +1 -0
- package/dist/i18n/extractor-plugin.d.ts +8 -0
- package/dist/i18n/extractor-plugin.d.ts.map +1 -0
- package/dist/i18n/extractor-plugin.js +247 -0
- package/dist/i18n/extractor-plugin.js.map +1 -0
- package/dist/i18n/metadata-templates.d.ts +11 -0
- package/dist/i18n/metadata-templates.d.ts.map +1 -0
- package/dist/i18n/metadata-templates.js +11 -0
- package/dist/i18n/metadata-templates.js.map +1 -0
- package/dist/i18n/source-messages.d.ts +23 -0
- package/dist/i18n/source-messages.d.ts.map +1 -0
- package/dist/i18n/source-messages.js +403 -0
- package/dist/i18n/source-messages.js.map +1 -0
- package/dist/i18n/types.d.ts +10 -0
- package/dist/i18n/types.d.ts.map +1 -0
- package/dist/i18n/types.js +108 -0
- package/dist/i18n/types.js.map +1 -0
- package/dist/internal/agent-rules.d.ts +7 -0
- package/dist/internal/agent-rules.d.ts.map +1 -0
- package/dist/internal/agent-rules.js +106 -0
- package/dist/internal/agent-rules.js.map +1 -0
- package/dist/internal/docs-elements.d.ts +8 -0
- package/dist/internal/docs-elements.d.ts.map +1 -0
- package/dist/internal/docs-elements.js +12 -0
- package/dist/internal/docs-elements.js.map +1 -0
- package/dist/internal/source-imports.d.ts +5 -1
- package/dist/internal/source-imports.d.ts.map +1 -1
- package/dist/internal/source-imports.js +25 -47
- package/dist/internal/source-imports.js.map +1 -1
- package/dist/mcp/client-config.d.ts +22 -0
- package/dist/mcp/client-config.d.ts.map +1 -0
- package/dist/mcp/client-config.js +94 -0
- package/dist/mcp/client-config.js.map +1 -0
- package/dist/mcp/client.d.ts +0 -1
- package/dist/mcp/client.d.ts.map +1 -1
- package/dist/mcp/client.js +11 -39
- package/dist/mcp/client.js.map +1 -1
- package/dist/mcp/handlers.d.ts +2 -1
- package/dist/mcp/handlers.d.ts.map +1 -1
- package/dist/mcp/handlers.js.map +1 -1
- package/dist/settings/render.d.ts +4 -1
- package/dist/settings/render.d.ts.map +1 -1
- package/dist/settings/render.js +11 -2
- package/dist/settings/render.js.map +1 -1
- package/dist/settings/schema.d.ts.map +1 -1
- package/dist/settings/schema.js +3 -1
- package/dist/settings/schema.js.map +1 -1
- package/dist/vite-plugins/animated.d.ts +5 -0
- package/dist/vite-plugins/animated.d.ts.map +1 -0
- package/dist/vite-plugins/animated.js +325 -0
- package/dist/vite-plugins/animated.js.map +1 -0
- package/dist/vite-plugins/asset-imports.d.ts.map +1 -1
- package/dist/vite-plugins/asset-imports.js +4 -14
- package/dist/vite-plugins/asset-imports.js.map +1 -1
- package/dist/vite-plugins/i18n.d.ts +5 -0
- package/dist/vite-plugins/i18n.d.ts.map +1 -0
- package/dist/vite-plugins/i18n.js +88 -0
- package/dist/vite-plugins/i18n.js.map +1 -0
- package/dist/vite-plugins/index.d.ts +1 -1
- package/dist/vite-plugins/index.d.ts.map +1 -1
- package/dist/vite-plugins/index.js +5 -1
- package/dist/vite-plugins/index.js.map +1 -1
- package/dist/vite-plugins/resources.d.ts.map +1 -1
- package/dist/vite-plugins/resources.js +12 -57
- package/dist/vite-plugins/resources.js.map +1 -1
- package/dist/vite-plugins/self-contained.d.ts.map +1 -1
- package/dist/vite-plugins/self-contained.js +128 -135
- package/dist/vite-plugins/self-contained.js.map +1 -1
- package/dist/vite-plugins/settings-worker-env.js +2 -2
- package/dist/vite-plugins/settings-worker-env.js.map +1 -1
- package/dist/vite-plugins/settings.d.ts.map +1 -1
- package/dist/vite-plugins/settings.js +2 -1
- package/dist/vite-plugins/settings.js.map +1 -1
- package/package.json +11 -9
- package/src/builder.ts +3 -2
- package/src/cli.ts +5 -0
- package/src/codegen/freshness.ts +2 -1
- package/src/codegen/reference.ts +59 -0
- package/src/codegen/report.ts +18 -3
- package/src/codegen/run-codegen.ts +134 -31
- package/src/commands/codegen.ts +1 -1
- package/src/commands/docs.ts +10 -28
- package/src/commands/mcp.ts +81 -0
- package/src/deploy/freedesktop/localize.ts +144 -0
- package/src/deploy/install-hints.ts +5 -0
- package/src/deploy/notices/gtkx.ts +1 -1
- package/src/deploy/notices/libraries.ts +2 -1
- package/src/deploy/payload/launcher.ts +2 -0
- package/src/deploy/payload/stage.ts +35 -10
- package/src/deploy/run-deploy.ts +128 -13
- package/src/deploy/settings/libraries.ts +7 -10
- package/src/deploy/targets/flatpak-source.ts +33 -10
- package/src/deploy/tools.ts +36 -3
- package/src/dev/locale-dir.ts +22 -0
- package/src/dev/runner-main.ts +4 -1
- package/src/dev/runner.ts +8 -0
- package/src/dev/vite-dev-server.ts +1 -0
- package/src/i18n/binding-extractor.ts +1969 -0
- package/src/i18n/catalog-template.ts +24 -0
- package/src/i18n/catalogs.ts +216 -0
- package/src/i18n/extractor-plugin.ts +413 -0
- package/src/i18n/metadata-templates.ts +29 -0
- package/src/i18n/source-messages.ts +608 -0
- package/src/i18n/types.ts +154 -0
- package/src/internal/agent-rules.ts +135 -0
- package/src/internal/docs-elements.ts +21 -0
- package/src/internal/source-imports.ts +31 -60
- package/src/mcp/client-config.ts +130 -0
- package/src/mcp/client.ts +17 -44
- package/src/mcp/handlers.ts +7 -6
- package/src/settings/render.ts +16 -2
- package/src/settings/schema.ts +3 -1
- package/src/vite-plugins/animated.ts +486 -0
- package/src/vite-plugins/asset-imports.ts +5 -17
- package/src/vite-plugins/i18n.ts +146 -0
- package/src/vite-plugins/index.ts +5 -0
- package/src/vite-plugins/resources.ts +12 -74
- package/src/vite-plugins/self-contained.ts +150 -169
- package/src/vite-plugins/settings-worker-env.ts +2 -2
- package/src/vite-plugins/settings.ts +2 -1
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { REFERENCE_PATH } from "../codegen/reference.js";
|
|
4
|
+
const AGENTS_FILENAME = "AGENTS.md";
|
|
5
|
+
const CLAUDE_FILENAME = "CLAUDE.md";
|
|
6
|
+
const CLAUDE_CONTENTS = `@${AGENTS_FILENAME}\n`;
|
|
7
|
+
const BEGIN_MARKER = "<!-- BEGIN:gtkx-agent-rules -->";
|
|
8
|
+
const END_MARKER = "<!-- END:gtkx-agent-rules -->";
|
|
9
|
+
const BLOCK_PATTERN = /<!-- BEGIN:gtkx-agent-rules -->[\s\S]*?<!-- END:gtkx-agent-rules -->/;
|
|
10
|
+
const TOP_LEVEL_HEADING = /^# /m;
|
|
11
|
+
const rulesBody = (heading) => [
|
|
12
|
+
`${heading} GTKX`,
|
|
13
|
+
"",
|
|
14
|
+
"This is not the GTK you have seen before. Most GTK code in your training data is C, PyGObject, Vala or " +
|
|
15
|
+
"GJS, and almost none of it is valid here. Check the rules below against what you are about to write.",
|
|
16
|
+
"",
|
|
17
|
+
"- Children are JSX, never `.append()`, `pack_start()`, `set_child()` or `add()`.",
|
|
18
|
+
'- Signals are props: `onClicked`, not `widget.connect("clicked", ...)`.',
|
|
19
|
+
"- Props are camelCase: `marginTop`, not `margin-top` or `margin_top`.",
|
|
20
|
+
"- There is no `Gtk.Template`, no `.ui` XML, and no `GtkBuilder`. The JSX tree is the definition.",
|
|
21
|
+
"- Elements come from `@gtkx/jsx/<namespace>` and classes, enums and functions from " +
|
|
22
|
+
"`@gtkx/gi/<namespace>`. Both are generated for this project by `gtkx codegen`, not installed from npm, " +
|
|
23
|
+
"so they match the GIR libraries this project declares.",
|
|
24
|
+
"",
|
|
25
|
+
`Read \`${REFERENCE_PATH}/index.md\` before writing widget code. It is generated from this project's own ` +
|
|
26
|
+
"GIR libraries and is the authority on which props, signals and methods exist. Do not infer a prop from " +
|
|
27
|
+
"another toolkit, from a C function name, or from a similar element.",
|
|
28
|
+
"",
|
|
29
|
+
"| Command | What it does |",
|
|
30
|
+
"| --- | --- |",
|
|
31
|
+
"| `gtkx dev` | Run the app with fast refresh |",
|
|
32
|
+
"| `gtkx codegen` | Regenerate bindings and this reference |",
|
|
33
|
+
"| `tsc --noEmit` | Typecheck |",
|
|
34
|
+
"| `vitest run` | Run the tests |",
|
|
35
|
+
"",
|
|
36
|
+
"Never call UI work done without looking at the running app. With `gtkx dev` up, the gtkx MCP server " +
|
|
37
|
+
"exposes the live widget tree, queries, clicks and screenshots; use them to confirm the change landed.",
|
|
38
|
+
"",
|
|
39
|
+
"This block is written by `gtkx codegen`. Anything outside the markers is yours and is left alone, and " +
|
|
40
|
+
"committing the block with your work keeps the tree clean.",
|
|
41
|
+
];
|
|
42
|
+
const renderBlock = (heading) => [BEGIN_MARKER, "", ...rulesBody(heading), "", END_MARKER].join("\n");
|
|
43
|
+
const headingFor = (contents) => (TOP_LEVEL_HEADING.test(contents) ? "##" : "#");
|
|
44
|
+
const withTrailingNewline = (contents) => (contents.endsWith("\n") ? contents : `${contents}\n`);
|
|
45
|
+
const appendBlock = (contents, block) => {
|
|
46
|
+
if (contents.trim() === "") {
|
|
47
|
+
return `${block}\n`;
|
|
48
|
+
}
|
|
49
|
+
return `${withTrailingNewline(contents)}\n${block}\n`;
|
|
50
|
+
};
|
|
51
|
+
const replaceBlock = (contents, block) => {
|
|
52
|
+
const match = BLOCK_PATTERN.exec(contents);
|
|
53
|
+
if (match === null) {
|
|
54
|
+
return appendBlock(contents, block);
|
|
55
|
+
}
|
|
56
|
+
return contents.slice(0, match.index) + block + contents.slice(match.index + match[0].length);
|
|
57
|
+
};
|
|
58
|
+
const nextContents = (contents) => {
|
|
59
|
+
const heading = headingFor(contents.replace(BLOCK_PATTERN, ""));
|
|
60
|
+
return replaceBlock(contents, renderBlock(heading));
|
|
61
|
+
};
|
|
62
|
+
const errorCode = (error) => error instanceof Error ? error.code : undefined;
|
|
63
|
+
const readOrUndefined = (path) => {
|
|
64
|
+
try {
|
|
65
|
+
return readFileSync(path, "utf8");
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
if (errorCode(error) === "ENOENT") {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
const hasWritten = (path, contents) => {
|
|
75
|
+
if (readOrUndefined(path) === contents) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
writeFileSync(path, contents);
|
|
79
|
+
return true;
|
|
80
|
+
};
|
|
81
|
+
const hasWrittenClaudeImport = (root) => {
|
|
82
|
+
try {
|
|
83
|
+
writeFileSync(join(root, CLAUDE_FILENAME), CLAUDE_CONTENTS, { flag: "wx" });
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
if (errorCode(error) === "EEXIST") {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
const upsertAgentRules = (root) => {
|
|
94
|
+
const agentsPath = join(root, AGENTS_FILENAME);
|
|
95
|
+
const existing = readOrUndefined(agentsPath) ?? "";
|
|
96
|
+
const files = [];
|
|
97
|
+
if (hasWritten(agentsPath, nextContents(existing))) {
|
|
98
|
+
files.push(AGENTS_FILENAME);
|
|
99
|
+
}
|
|
100
|
+
if (hasWrittenClaudeImport(root)) {
|
|
101
|
+
files.push(CLAUDE_FILENAME);
|
|
102
|
+
}
|
|
103
|
+
return { isWritten: files.length > 0, files };
|
|
104
|
+
};
|
|
105
|
+
export { upsertAgentRules };
|
|
106
|
+
//# sourceMappingURL=agent-rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-rules.js","sourceRoot":"","sources":["../../src/internal/agent-rules.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACtD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAOzD,MAAM,eAAe,GAAG,WAAW,CAAC;AACpC,MAAM,eAAe,GAAG,WAAW,CAAC;AACpC,MAAM,eAAe,GAAG,IAAI,eAAe,IAAI,CAAC;AAChD,MAAM,YAAY,GAAG,iCAAiC,CAAC;AACvD,MAAM,UAAU,GAAG,+BAA+B,CAAC;AACnD,MAAM,aAAa,GAAG,sEAAsE,CAAC;AAC7F,MAAM,iBAAiB,GAAG,MAAM,CAAC;AAEjC,MAAM,SAAS,GAAG,CAAC,OAAe,EAAY,EAAE,CAAC;IAC7C,GAAG,OAAO,OAAO;IACjB,EAAE;IACF,yGAAyG;QACzG,sGAAsG;IACtG,EAAE;IACF,kFAAkF;IAClF,yEAAyE;IACzE,uEAAuE;IACvE,kGAAkG;IAClG,qFAAqF;QACrF,yGAAyG;QACzG,wDAAwD;IACxD,EAAE;IACF,UAAU,cAAc,kFAAkF;QAC1G,yGAAyG;QACzG,qEAAqE;IACrE,EAAE;IACF,4BAA4B;IAC5B,eAAe;IACf,gDAAgD;IAChD,6DAA6D;IAC7D,gCAAgC;IAChC,kCAAkC;IAClC,EAAE;IACF,sGAAsG;QACtG,uGAAuG;IACvG,EAAE;IACF,wGAAwG;QACxG,2DAA2D;CAC9D,CAAC;AAEF,MAAM,WAAW,GAAG,CAAC,OAAe,EAAU,EAAE,CAC5C,CAAC,YAAY,EAAE,EAAE,EAAE,GAAG,SAAS,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,UAAU,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEzE,MAAM,UAAU,GAAG,CAAC,QAAgB,EAAU,EAAE,CAAC,CAAC,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;AACjG,MAAM,mBAAmB,GAAG,CAAC,QAAgB,EAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,QAAQ,IAAI,CAAC,CAAC;AAEjH,MAAM,WAAW,GAAG,CAAC,QAAgB,EAAE,KAAa,EAAU,EAAE;IAC5D,IAAI,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACzB,OAAO,GAAG,KAAK,IAAI,CAAC;IACxB,CAAC;IAED,OAAO,GAAG,mBAAmB,CAAC,QAAQ,CAAC,KAAK,KAAK,IAAI,CAAC;AAC1D,CAAC,CAAC;AAEF,MAAM,YAAY,GAAG,CAAC,QAAgB,EAAE,KAAa,EAAU,EAAE;IAC7D,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAE3C,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACjB,OAAO,WAAW,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IACxC,CAAC;IAED,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,GAAG,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AAClG,CAAC,CAAC;AAEF,MAAM,YAAY,GAAG,CAAC,QAAgB,EAAU,EAAE;IAC9C,MAAM,OAAO,GAAG,UAAU,CAAC,QAAQ,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC,CAAC;IAEhE,OAAO,YAAY,CAAC,QAAQ,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC;AACxD,CAAC,CAAC;AAEF,MAAM,SAAS,GAAG,CAAC,KAAc,EAAsB,EAAE,CACrD,KAAK,YAAY,KAAK,CAAC,CAAC,CAAE,KAA+B,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;AAE/E,MAAM,eAAe,GAAG,CAAC,IAAY,EAAsB,EAAE;IACzD,IAAI,CAAC;QACD,OAAO,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACtC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,IAAI,SAAS,CAAC,KAAK,CAAC,KAAK,QAAQ,EAAE,CAAC;YAChC,OAAO,SAAS,CAAC;QACrB,CAAC;QAED,MAAM,KAAK,CAAC;IAChB,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,UAAU,GAAG,CAAC,IAAY,EAAE,QAAgB,EAAW,EAAE;IAC3D,IAAI,eAAe,CAAC,IAAI,CAAC,KAAK,QAAQ,EAAE,CAAC;QACrC,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,aAAa,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAE9B,OAAO,IAAI,CAAC;AAChB,CAAC,CAAC;AAEF,MAAM,sBAAsB,GAAG,CAAC,IAAY,EAAW,EAAE;IACrD,IAAI,CAAC;QACD,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,eAAe,CAAC,EAAE,eAAe,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAE5E,OAAO,IAAI,CAAC;IAChB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,IAAI,SAAS,CAAC,KAAK,CAAC,KAAK,QAAQ,EAAE,CAAC;YAChC,OAAO,KAAK,CAAC;QACjB,CAAC;QAED,MAAM,KAAK,CAAC;IAChB,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,gBAAgB,GAAG,CAAC,IAAY,EAAoB,EAAE;IACxD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,EAAE,eAAe,CAAC,CAAC;IAC/C,MAAM,QAAQ,GAAG,eAAe,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;IACnD,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,IAAI,UAAU,CAAC,UAAU,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;QACjD,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IAChC,CAAC;IAED,IAAI,sBAAsB,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/B,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IAChC,CAAC;IAED,OAAO,EAAE,SAAS,EAAE,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC;AAClD,CAAC,CAAC;AAEF,OAAO,EAAE,gBAAgB,EAAE,CAAC","sourcesContent":["import { readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { REFERENCE_PATH } from \"../codegen/reference.js\";\n\ntype AgentRulesResult = {\n isWritten: boolean;\n files: string[];\n};\n\nconst AGENTS_FILENAME = \"AGENTS.md\";\nconst CLAUDE_FILENAME = \"CLAUDE.md\";\nconst CLAUDE_CONTENTS = `@${AGENTS_FILENAME}\\n`;\nconst BEGIN_MARKER = \"<!-- BEGIN:gtkx-agent-rules -->\";\nconst END_MARKER = \"<!-- END:gtkx-agent-rules -->\";\nconst BLOCK_PATTERN = /<!-- BEGIN:gtkx-agent-rules -->[\\s\\S]*?<!-- END:gtkx-agent-rules -->/;\nconst TOP_LEVEL_HEADING = /^# /m;\n\nconst rulesBody = (heading: string): string[] => [\n `${heading} GTKX`,\n \"\",\n \"This is not the GTK you have seen before. Most GTK code in your training data is C, PyGObject, Vala or \" +\n \"GJS, and almost none of it is valid here. Check the rules below against what you are about to write.\",\n \"\",\n \"- Children are JSX, never `.append()`, `pack_start()`, `set_child()` or `add()`.\",\n '- Signals are props: `onClicked`, not `widget.connect(\"clicked\", ...)`.',\n \"- Props are camelCase: `marginTop`, not `margin-top` or `margin_top`.\",\n \"- There is no `Gtk.Template`, no `.ui` XML, and no `GtkBuilder`. The JSX tree is the definition.\",\n \"- Elements come from `@gtkx/jsx/<namespace>` and classes, enums and functions from \" +\n \"`@gtkx/gi/<namespace>`. Both are generated for this project by `gtkx codegen`, not installed from npm, \" +\n \"so they match the GIR libraries this project declares.\",\n \"\",\n `Read \\`${REFERENCE_PATH}/index.md\\` before writing widget code. It is generated from this project's own ` +\n \"GIR libraries and is the authority on which props, signals and methods exist. Do not infer a prop from \" +\n \"another toolkit, from a C function name, or from a similar element.\",\n \"\",\n \"| Command | What it does |\",\n \"| --- | --- |\",\n \"| `gtkx dev` | Run the app with fast refresh |\",\n \"| `gtkx codegen` | Regenerate bindings and this reference |\",\n \"| `tsc --noEmit` | Typecheck |\",\n \"| `vitest run` | Run the tests |\",\n \"\",\n \"Never call UI work done without looking at the running app. With `gtkx dev` up, the gtkx MCP server \" +\n \"exposes the live widget tree, queries, clicks and screenshots; use them to confirm the change landed.\",\n \"\",\n \"This block is written by `gtkx codegen`. Anything outside the markers is yours and is left alone, and \" +\n \"committing the block with your work keeps the tree clean.\",\n];\n\nconst renderBlock = (heading: string): string =>\n [BEGIN_MARKER, \"\", ...rulesBody(heading), \"\", END_MARKER].join(\"\\n\");\n\nconst headingFor = (contents: string): string => (TOP_LEVEL_HEADING.test(contents) ? \"##\" : \"#\");\nconst withTrailingNewline = (contents: string): string => (contents.endsWith(\"\\n\") ? contents : `${contents}\\n`);\n\nconst appendBlock = (contents: string, block: string): string => {\n if (contents.trim() === \"\") {\n return `${block}\\n`;\n }\n\n return `${withTrailingNewline(contents)}\\n${block}\\n`;\n};\n\nconst replaceBlock = (contents: string, block: string): string => {\n const match = BLOCK_PATTERN.exec(contents);\n\n if (match === null) {\n return appendBlock(contents, block);\n }\n\n return contents.slice(0, match.index) + block + contents.slice(match.index + match[0].length);\n};\n\nconst nextContents = (contents: string): string => {\n const heading = headingFor(contents.replace(BLOCK_PATTERN, \"\"));\n\n return replaceBlock(contents, renderBlock(heading));\n};\n\nconst errorCode = (error: unknown): string | undefined =>\n error instanceof Error ? (error as NodeJS.ErrnoException).code : undefined;\n\nconst readOrUndefined = (path: string): string | undefined => {\n try {\n return readFileSync(path, \"utf8\");\n } catch (error) {\n if (errorCode(error) === \"ENOENT\") {\n return undefined;\n }\n\n throw error;\n }\n};\n\nconst hasWritten = (path: string, contents: string): boolean => {\n if (readOrUndefined(path) === contents) {\n return false;\n }\n\n writeFileSync(path, contents);\n\n return true;\n};\n\nconst hasWrittenClaudeImport = (root: string): boolean => {\n try {\n writeFileSync(join(root, CLAUDE_FILENAME), CLAUDE_CONTENTS, { flag: \"wx\" });\n\n return true;\n } catch (error) {\n if (errorCode(error) === \"EEXIST\") {\n return false;\n }\n\n throw error;\n }\n};\n\nconst upsertAgentRules = (root: string): AgentRulesResult => {\n const agentsPath = join(root, AGENTS_FILENAME);\n const existing = readOrUndefined(agentsPath) ?? \"\";\n const files: string[] = [];\n\n if (hasWritten(agentsPath, nextContents(existing))) {\n files.push(AGENTS_FILENAME);\n }\n\n if (hasWrittenClaudeImport(root)) {\n files.push(CLAUDE_FILENAME);\n }\n\n return { isWritten: files.length > 0, files };\n};\n\nexport { upsertAgentRules };\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type ElementProps, type OmittedProps } from "@gtkx/codegen";
|
|
2
|
+
type DocsElements = {
|
|
3
|
+
props: ElementProps;
|
|
4
|
+
omittedProps: OmittedProps;
|
|
5
|
+
};
|
|
6
|
+
declare const resolveDocsElements: (cwd: string) => Promise<DocsElements>;
|
|
7
|
+
export { resolveDocsElements };
|
|
8
|
+
//# sourceMappingURL=docs-elements.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docs-elements.d.ts","sourceRoot":"","sources":["../../src/internal/docs-elements.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,YAAY,EAAqC,MAAM,eAAe,CAAC;AAGxG,KAAK,YAAY,GAAG;IAChB,KAAK,EAAE,YAAY,CAAC;IACpB,YAAY,EAAE,YAAY,CAAC;CAC9B,CAAC;AAEF,QAAA,MAAM,mBAAmB,GAAU,KAAK,MAAM,KAAG,OAAO,CAAC,YAAY,CAUpE,CAAC;AAEF,OAAO,EAAE,mBAAmB,EAAE,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { readBuiltinElements, resolveStore } from "@gtkx/codegen";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
const resolveDocsElements = async (cwd) => {
|
|
4
|
+
const { gi, reactSubexports } = resolveStore(cwd);
|
|
5
|
+
if (!existsSync(gi.storeDir)) {
|
|
6
|
+
return { props: {}, omittedProps: {} };
|
|
7
|
+
}
|
|
8
|
+
const { props, omittedProps } = await readBuiltinElements(reactSubexports, gi.storeDir);
|
|
9
|
+
return { props, omittedProps };
|
|
10
|
+
};
|
|
11
|
+
export { resolveDocsElements };
|
|
12
|
+
//# sourceMappingURL=docs-elements.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docs-elements.js","sourceRoot":"","sources":["../../src/internal/docs-elements.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwC,mBAAmB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AACxG,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAOrC,MAAM,mBAAmB,GAAG,KAAK,EAAE,GAAW,EAAyB,EAAE;IACrE,MAAM,EAAE,EAAE,EAAE,eAAe,EAAE,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;IAElD,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC3B,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC;IAC3C,CAAC;IAED,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,GAAG,MAAM,mBAAmB,CAAC,eAAe,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC;IAExF,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC;AACnC,CAAC,CAAC;AAEF,OAAO,EAAE,mBAAmB,EAAE,CAAC","sourcesContent":["import { type ElementProps, type OmittedProps, readBuiltinElements, resolveStore } from \"@gtkx/codegen\";\nimport { existsSync } from \"node:fs\";\n\ntype DocsElements = {\n props: ElementProps;\n omittedProps: OmittedProps;\n};\n\nconst resolveDocsElements = async (cwd: string): Promise<DocsElements> => {\n const { gi, reactSubexports } = resolveStore(cwd);\n\n if (!existsSync(gi.storeDir)) {\n return { props: {}, omittedProps: {} };\n }\n\n const { props, omittedProps } = await readBuiltinElements(reactSubexports, gi.storeDir);\n\n return { props, omittedProps };\n};\n\nexport { resolveDocsElements };\n"]}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
import type { ParserOptions } from "vite";
|
|
1
2
|
type SourceImport = {
|
|
2
3
|
importer: string;
|
|
3
4
|
source: string;
|
|
4
5
|
};
|
|
6
|
+
type SourceLanguage = NonNullable<ParserOptions["lang"]>;
|
|
7
|
+
declare const sourceLanguage: (path: string) => SourceLanguage | undefined;
|
|
8
|
+
declare const discoverSourceFiles: (dir: string) => string[];
|
|
5
9
|
declare const parseRuntimeImportsIn: (path: string) => SourceImport[] | null;
|
|
6
10
|
declare const discoverSourceImports: (dir: string) => SourceImport[];
|
|
7
|
-
export { discoverSourceImports, parseRuntimeImportsIn, type SourceImport };
|
|
11
|
+
export { discoverSourceFiles, discoverSourceImports, parseRuntimeImportsIn, type SourceImport, sourceLanguage };
|
|
8
12
|
//# sourceMappingURL=source-imports.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"source-imports.d.ts","sourceRoot":"","sources":["../../src/internal/source-imports.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"source-imports.d.ts","sourceRoot":"","sources":["../../src/internal/source-imports.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAuB,aAAa,EAAE,MAAM,MAAM,CAAC;AAM/D,KAAK,YAAY,GAAG;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,KAAK,cAAc,GAAG,WAAW,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC;AAsBzD,QAAA,MAAM,cAAc,GAAI,MAAM,MAAM,KAAG,cAAc,GAAG,SAMvD,CAAC;AAkCF,QAAA,MAAM,mBAAmB,GAAI,KAAK,MAAM,KAAG,MAAM,EAKhD,CAAC;AA+EF,QAAA,MAAM,qBAAqB,GAAI,MAAM,MAAM,KAAG,YAAY,EAAE,GAAG,IACP,CAAC;AAKzD,QAAA,MAAM,qBAAqB,GAAI,KAAK,MAAM,KAAG,YAAY,EAKxD,CAAC;AAEF,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,KAAK,YAAY,EAAE,cAAc,EAAE,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { sortStringsBy } from "@gtkx/utils";
|
|
2
2
|
import { readdirSync, readFileSync } from "node:fs";
|
|
3
3
|
import { extname, join } from "node:path";
|
|
4
|
-
import { parseSync } from "vite";
|
|
4
|
+
import { parseSync, Visitor } from "vite";
|
|
5
5
|
const SOURCE_LANGUAGES = new Map([
|
|
6
6
|
[".cjs", "jsx"],
|
|
7
7
|
[".cts", "ts"],
|
|
@@ -13,8 +13,6 @@ const SOURCE_LANGUAGES = new Map([
|
|
|
13
13
|
[".tsx", "tsx"],
|
|
14
14
|
]);
|
|
15
15
|
const EXCLUDED_DIRECTORIES = new Set([
|
|
16
|
-
".git",
|
|
17
|
-
".gtkx",
|
|
18
16
|
"build",
|
|
19
17
|
"coverage",
|
|
20
18
|
"dist",
|
|
@@ -27,10 +25,11 @@ const sourceLanguage = (path) => {
|
|
|
27
25
|
}
|
|
28
26
|
return SOURCE_LANGUAGES.get(extname(path).toLowerCase());
|
|
29
27
|
};
|
|
28
|
+
const isWalkableDirectory = (name) => !name.startsWith(".") && !EXCLUDED_DIRECTORIES.has(name);
|
|
30
29
|
const collectSourceEntry = (dir, entry, found) => {
|
|
31
30
|
const path = join(dir, entry.name);
|
|
32
31
|
if (entry.isDirectory()) {
|
|
33
|
-
if (
|
|
32
|
+
if (isWalkableDirectory(entry.name)) {
|
|
34
33
|
sourceFilesIn(path, found);
|
|
35
34
|
}
|
|
36
35
|
return;
|
|
@@ -51,6 +50,11 @@ const sourceFilesIn = (dir, found) => {
|
|
|
51
50
|
collectSourceEntry(dir, entry, found);
|
|
52
51
|
}
|
|
53
52
|
};
|
|
53
|
+
const discoverSourceFiles = (dir) => {
|
|
54
|
+
const files = [];
|
|
55
|
+
sourceFilesIn(dir, files);
|
|
56
|
+
return sortStringsBy(files, (path) => path);
|
|
57
|
+
};
|
|
54
58
|
const staticImportSources = (module) => [
|
|
55
59
|
...module.staticImports.map((statement) => statement.moduleRequest.value),
|
|
56
60
|
...module.staticExports.flatMap((statement) => statement.entries
|
|
@@ -67,49 +71,24 @@ const staticRuntimeImportSources = (module) => [
|
|
|
67
71
|
.filter((source) => source !== undefined)),
|
|
68
72
|
];
|
|
69
73
|
const templateImportSource = (node) => {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
const quasis = node.quasis;
|
|
74
|
-
const first = quasis[0];
|
|
75
|
-
if (!isRecord(first) || !isRecord(first.value)) {
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
|
-
return typeof first.value.cooked === "string" ? first.value.cooked : null;
|
|
74
|
+
const [quasi] = node.quasis;
|
|
75
|
+
return quasi === undefined || node.expressions.length > 0 ? null : quasi.value.cooked;
|
|
79
76
|
};
|
|
80
77
|
const literalImportSource = (node) => {
|
|
81
|
-
if (
|
|
82
|
-
return
|
|
83
|
-
}
|
|
84
|
-
if (node.type === "Literal") {
|
|
85
|
-
return typeof node.value === "string" ? node.value : null;
|
|
78
|
+
if (node.type === "TemplateLiteral") {
|
|
79
|
+
return templateImportSource(node);
|
|
86
80
|
}
|
|
87
|
-
return node.type === "
|
|
81
|
+
return node.type === "Literal" && typeof node.value === "string" ? node.value : null;
|
|
88
82
|
};
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
found.push(source);
|
|
99
|
-
}
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
collectDynamicImportArray(Object.values(node), found);
|
|
103
|
-
};
|
|
104
|
-
const collectDynamicImportSources = (node, found) => {
|
|
105
|
-
if (Array.isArray(node)) {
|
|
106
|
-
collectDynamicImportArray(node, found);
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
if (!isRecord(node)) {
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
collectDynamicImportRecord(node, found);
|
|
83
|
+
const collectDynamicImportSources = (program, found) => {
|
|
84
|
+
new Visitor({
|
|
85
|
+
ImportExpression: (node) => {
|
|
86
|
+
const source = literalImportSource(node.source);
|
|
87
|
+
if (source !== null) {
|
|
88
|
+
found.push(source);
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
}).visit(program);
|
|
113
92
|
};
|
|
114
93
|
const parseImportsInWith = (path, staticSources) => {
|
|
115
94
|
const lang = sourceLanguage(path);
|
|
@@ -135,10 +114,9 @@ const parseRuntimeImportsIn = (path) => parseImportsInWith(path, staticRuntimeIm
|
|
|
135
114
|
const importsIn = (path) => parseImportsIn(path) ?? [];
|
|
136
115
|
const importKey = (entry) => `${entry.importer}\0${entry.source}`;
|
|
137
116
|
const discoverSourceImports = (dir) => {
|
|
138
|
-
const files =
|
|
139
|
-
sourceFilesIn(dir, files);
|
|
117
|
+
const files = discoverSourceFiles(dir);
|
|
140
118
|
const imports = files.flatMap((path) => importsIn(path));
|
|
141
119
|
return sortStringsBy(imports, importKey);
|
|
142
120
|
};
|
|
143
|
-
export { discoverSourceImports, parseRuntimeImportsIn };
|
|
121
|
+
export { discoverSourceFiles, discoverSourceImports, parseRuntimeImportsIn, sourceLanguage };
|
|
144
122
|
//# sourceMappingURL=source-imports.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"source-imports.js","sourceRoot":"","sources":["../../src/internal/source-imports.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ParseResult, ParserOptions } from \"vite\";\nimport { isRecord, sortStringsBy } from \"@gtkx/utils\";\nimport { type Dirent, readdirSync, readFileSync } from \"node:fs\";\nimport { extname, join } from \"node:path\";\nimport { parseSync } from \"vite\";\n\ntype SourceImport = {\n importer: string;\n source: string;\n};\n\ntype SourceLanguage = NonNullable<ParserOptions[\"lang\"]>;\ntype ParsedModule = ParseResult[\"module\"];\n\nconst SOURCE_LANGUAGES: Map<string, SourceLanguage> = new Map([\n [\".cjs\", \"jsx\"],\n [\".cts\", \"ts\"],\n [\".js\", \"jsx\"],\n [\".jsx\", \"jsx\"],\n [\".mjs\", \"jsx\"],\n [\".mts\", \"ts\"],\n [\".ts\", \"ts\"],\n [\".tsx\", \"tsx\"],\n]);\n\nconst EXCLUDED_DIRECTORIES: Set<string> = new Set([\n \".git\",\n \".gtkx\",\n \"build\",\n \"coverage\",\n \"dist\",\n \"node_modules\",\n \"out-tsc\",\n]);\n\nconst sourceLanguage = (path: string): SourceLanguage | undefined => {\n if (path.endsWith(\".d.ts\") || path.endsWith(\".d.cts\") || path.endsWith(\".d.mts\")) {\n return undefined;\n }\n\n return SOURCE_LANGUAGES.get(extname(path).toLowerCase());\n};\n\nconst collectSourceEntry = (dir: string, entry: Dirent, found: string[]): void => {\n const path = join(dir, entry.name);\n\n if (entry.isDirectory()) {\n if (!EXCLUDED_DIRECTORIES.has(entry.name)) {\n sourceFilesIn(path, found);\n }\n\n return;\n }\n\n if (entry.isFile() && sourceLanguage(path) !== undefined) {\n found.push(path);\n }\n};\n\nconst sourceFilesIn = (dir: string, found: string[]): void => {\n let entries: Dirent[];\n\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n return;\n }\n\n for (const entry of entries) {\n collectSourceEntry(dir, entry, found);\n }\n};\n\nconst staticImportSources = (module: ParsedModule): string[] => [\n ...module.staticImports.map((statement) => statement.moduleRequest.value),\n ...module.staticExports.flatMap((statement) =>\n statement.entries\n .map((entry) => entry.moduleRequest?.value)\n .filter((source): source is string => source !== undefined),\n ),\n];\n\nconst staticRuntimeImportSources = (module: ParsedModule): string[] => [\n ...module.staticImports\n .filter((statement) => statement.entries.length === 0 || statement.entries.some((entry) => !entry.isType))\n .map((statement) => statement.moduleRequest.value),\n ...module.staticExports.flatMap((statement) =>\n statement.entries\n .filter((entry) => !entry.isType)\n .map((entry) => entry.moduleRequest?.value)\n .filter((source): source is string => source !== undefined),\n ),\n];\n\nconst templateImportSource = (node: Record<string, unknown>): string | null => {\n if (!Array.isArray(node.expressions) || node.expressions.length > 0 || !Array.isArray(node.quasis)) {\n return null;\n }\n\n const quasis: unknown[] = node.quasis;\n const first: unknown = quasis[0];\n\n if (!isRecord(first) || !isRecord(first.value)) {\n return null;\n }\n\n return typeof first.value.cooked === \"string\" ? first.value.cooked : null;\n};\n\nconst literalImportSource = (node: unknown): string | null => {\n if (!isRecord(node)) {\n return null;\n }\n\n if (node.type === \"Literal\") {\n return typeof node.value === \"string\" ? node.value : null;\n }\n\n return node.type === \"TemplateLiteral\" ? templateImportSource(node) : null;\n};\n\nconst collectDynamicImportArray = (nodes: unknown[], found: string[]): void => {\n for (const child of nodes) {\n collectDynamicImportSources(child, found);\n }\n};\n\nconst collectDynamicImportRecord = (node: Record<string, unknown>, found: string[]): void => {\n if (node.type === \"ImportExpression\") {\n const source = literalImportSource(node.source);\n\n if (source !== null) {\n found.push(source);\n }\n\n return;\n }\n\n collectDynamicImportArray(Object.values(node), found);\n};\n\nconst collectDynamicImportSources = (node: unknown, found: string[]): void => {\n if (Array.isArray(node)) {\n collectDynamicImportArray(node, found);\n\n return;\n }\n\n if (!isRecord(node)) {\n return;\n }\n\n collectDynamicImportRecord(node, found);\n};\n\nconst parseImportsInWith = (\n path: string,\n staticSources: (module: ParsedModule) => string[],\n): SourceImport[] | null => {\n const lang = sourceLanguage(path);\n\n if (lang === undefined) {\n return [];\n }\n\n let parsed: ParseResult;\n\n try {\n parsed = parseSync(path, readFileSync(path, \"utf8\"), { lang });\n } catch {\n return null;\n }\n\n if (parsed.errors.length > 0) {\n return null;\n }\n\n const sources = staticSources(parsed.module);\n collectDynamicImportSources(parsed.program, sources);\n\n return [...new Set(sources)].map((source) => ({ importer: path, source }));\n};\n\nconst parseImportsIn = (path: string): SourceImport[] | null => parseImportsInWith(path, staticImportSources);\n\nconst parseRuntimeImportsIn = (path: string): SourceImport[] | null =>\n parseImportsInWith(path, staticRuntimeImportSources);\n\nconst importsIn = (path: string): SourceImport[] => parseImportsIn(path) ?? [];\nconst importKey = (entry: SourceImport): string => `${entry.importer}\\0${entry.source}`;\n\nconst discoverSourceImports = (dir: string): SourceImport[] => {\n const files: string[] = [];\n sourceFilesIn(dir, files);\n const imports = files.flatMap((path) => importsIn(path));\n\n return sortStringsBy(imports, importKey);\n};\n\nexport { discoverSourceImports, parseRuntimeImportsIn, type SourceImport };\n"]}
|
|
1
|
+
{"version":3,"file":"source-imports.js","sourceRoot":"","sources":["../../src/internal/source-imports.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ESTree, ParseResult, ParserOptions } from \"vite\";\nimport { sortStringsBy } from \"@gtkx/utils\";\nimport { type Dirent, readdirSync, readFileSync } from \"node:fs\";\nimport { extname, join } from \"node:path\";\nimport { parseSync, Visitor } from \"vite\";\n\ntype SourceImport = {\n importer: string;\n source: string;\n};\n\ntype SourceLanguage = NonNullable<ParserOptions[\"lang\"]>;\ntype ParsedModule = ParseResult[\"module\"];\n\nconst SOURCE_LANGUAGES: Map<string, SourceLanguage> = new Map([\n [\".cjs\", \"jsx\"],\n [\".cts\", \"ts\"],\n [\".js\", \"jsx\"],\n [\".jsx\", \"jsx\"],\n [\".mjs\", \"jsx\"],\n [\".mts\", \"ts\"],\n [\".ts\", \"ts\"],\n [\".tsx\", \"tsx\"],\n]);\n\nconst EXCLUDED_DIRECTORIES: Set<string> = new Set([\n \"build\",\n \"coverage\",\n \"dist\",\n \"node_modules\",\n \"out-tsc\",\n]);\n\nconst sourceLanguage = (path: string): SourceLanguage | undefined => {\n if (path.endsWith(\".d.ts\") || path.endsWith(\".d.cts\") || path.endsWith(\".d.mts\")) {\n return undefined;\n }\n\n return SOURCE_LANGUAGES.get(extname(path).toLowerCase());\n};\n\nconst isWalkableDirectory = (name: string): boolean => !name.startsWith(\".\") && !EXCLUDED_DIRECTORIES.has(name);\n\nconst collectSourceEntry = (dir: string, entry: Dirent, found: string[]): void => {\n const path = join(dir, entry.name);\n\n if (entry.isDirectory()) {\n if (isWalkableDirectory(entry.name)) {\n sourceFilesIn(path, found);\n }\n\n return;\n }\n\n if (entry.isFile() && sourceLanguage(path) !== undefined) {\n found.push(path);\n }\n};\n\nconst sourceFilesIn = (dir: string, found: string[]): void => {\n let entries: Dirent[];\n\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n return;\n }\n\n for (const entry of entries) {\n collectSourceEntry(dir, entry, found);\n }\n};\n\nconst discoverSourceFiles = (dir: string): string[] => {\n const files: string[] = [];\n sourceFilesIn(dir, files);\n\n return sortStringsBy(files, (path) => path);\n};\n\nconst staticImportSources = (module: ParsedModule): string[] => [\n ...module.staticImports.map((statement) => statement.moduleRequest.value),\n ...module.staticExports.flatMap((statement) =>\n statement.entries\n .map((entry) => entry.moduleRequest?.value)\n .filter((source): source is string => source !== undefined),\n ),\n];\n\nconst staticRuntimeImportSources = (module: ParsedModule): string[] => [\n ...module.staticImports\n .filter((statement) => statement.entries.length === 0 || statement.entries.some((entry) => !entry.isType))\n .map((statement) => statement.moduleRequest.value),\n ...module.staticExports.flatMap((statement) =>\n statement.entries\n .filter((entry) => !entry.isType)\n .map((entry) => entry.moduleRequest?.value)\n .filter((source): source is string => source !== undefined),\n ),\n];\n\nconst templateImportSource = (node: ESTree.TemplateLiteral): string | null => {\n const [quasi] = node.quasis;\n\n return quasi === undefined || node.expressions.length > 0 ? null : quasi.value.cooked;\n};\n\nconst literalImportSource = (node: ESTree.Expression): string | null => {\n if (node.type === \"TemplateLiteral\") {\n return templateImportSource(node);\n }\n\n return node.type === \"Literal\" && typeof node.value === \"string\" ? node.value : null;\n};\n\nconst collectDynamicImportSources = (program: ESTree.Program, found: string[]): void => {\n new Visitor({\n ImportExpression: (node) => {\n const source = literalImportSource(node.source);\n\n if (source !== null) {\n found.push(source);\n }\n },\n }).visit(program);\n};\n\nconst parseImportsInWith = (\n path: string,\n staticSources: (module: ParsedModule) => string[],\n): SourceImport[] | null => {\n const lang = sourceLanguage(path);\n\n if (lang === undefined) {\n return [];\n }\n\n let parsed: ParseResult;\n\n try {\n parsed = parseSync(path, readFileSync(path, \"utf8\"), { lang });\n } catch {\n return null;\n }\n\n if (parsed.errors.length > 0) {\n return null;\n }\n\n const sources = staticSources(parsed.module);\n collectDynamicImportSources(parsed.program, sources);\n\n return [...new Set(sources)].map((source) => ({ importer: path, source }));\n};\n\nconst parseImportsIn = (path: string): SourceImport[] | null => parseImportsInWith(path, staticImportSources);\n\nconst parseRuntimeImportsIn = (path: string): SourceImport[] | null =>\n parseImportsInWith(path, staticRuntimeImportSources);\n\nconst importsIn = (path: string): SourceImport[] => parseImportsIn(path) ?? [];\nconst importKey = (entry: SourceImport): string => `${entry.importer}\\0${entry.source}`;\n\nconst discoverSourceImports = (dir: string): SourceImport[] => {\n const files = discoverSourceFiles(dir);\n const imports = files.flatMap((path) => importsIn(path));\n\n return sortStringsBy(imports, importKey);\n};\n\nexport { discoverSourceFiles, discoverSourceImports, parseRuntimeImportsIn, type SourceImport, sourceLanguage };\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
type ClientName = "claude" | "cursor" | "vscode" | "opencode" | "codex";
|
|
2
|
+
type ServerEntry = Record<string, unknown>;
|
|
3
|
+
type Client = {
|
|
4
|
+
name: ClientName;
|
|
5
|
+
file: string;
|
|
6
|
+
key: string;
|
|
7
|
+
entry: ServerEntry;
|
|
8
|
+
};
|
|
9
|
+
type ClientResult = {
|
|
10
|
+
kind: "written";
|
|
11
|
+
path: string;
|
|
12
|
+
isCreated: boolean;
|
|
13
|
+
} | {
|
|
14
|
+
kind: "manual";
|
|
15
|
+
path: string;
|
|
16
|
+
snippet: string;
|
|
17
|
+
};
|
|
18
|
+
declare const CLIENTS: Client[];
|
|
19
|
+
declare const isClientName: (value: string) => value is ClientName;
|
|
20
|
+
declare const writeClientConfig: (root: string, name: ClientName) => ClientResult;
|
|
21
|
+
export { CLIENTS, isClientName, writeClientConfig, type ClientName, type ClientResult };
|
|
22
|
+
//# sourceMappingURL=client-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-config.d.ts","sourceRoot":"","sources":["../../src/mcp/client-config.ts"],"names":[],"mappings":"AAGA,KAAK,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAC;AACxE,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE3C,KAAK,MAAM,GAAG;IACV,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,WAAW,CAAC;CACtB,CAAC;AAEF,KAAK,YAAY,GACX;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,GACvD;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAatD,QAAA,MAAM,OAAO,EAAE,MAAM,EA+BpB,CAAC;AAIF,QAAA,MAAM,YAAY,GAAI,OAAO,MAAM,KAAG,KAAK,IAAI,UAAqC,CAAC;AAiDrF,QAAA,MAAM,iBAAiB,GAAI,MAAM,MAAM,EAAE,MAAM,UAAU,KAAG,YAe3D,CAAC;AAEF,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,KAAK,UAAU,EAAE,KAAK,YAAY,EAAE,CAAC"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
const SERVER_NAME = "gtkx";
|
|
4
|
+
const COMMAND = "npx";
|
|
5
|
+
const ARGS = ["gtkx", "mcp"];
|
|
6
|
+
const CODEX_PATH = "~/.codex/config.toml";
|
|
7
|
+
const CODEX_SNIPPET = [
|
|
8
|
+
`[mcp_servers.${SERVER_NAME}]`,
|
|
9
|
+
`command = ${JSON.stringify(COMMAND)}`,
|
|
10
|
+
`args = ${JSON.stringify(ARGS)}`,
|
|
11
|
+
].join("\n");
|
|
12
|
+
const CLIENTS = [
|
|
13
|
+
{
|
|
14
|
+
name: "claude",
|
|
15
|
+
file: ".mcp.json",
|
|
16
|
+
key: "mcpServers",
|
|
17
|
+
entry: { command: COMMAND, args: ARGS },
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: "cursor",
|
|
21
|
+
file: join(".cursor", "mcp.json"),
|
|
22
|
+
key: "mcpServers",
|
|
23
|
+
entry: { command: COMMAND, args: ARGS },
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: "vscode",
|
|
27
|
+
file: join(".vscode", "mcp.json"),
|
|
28
|
+
key: "servers",
|
|
29
|
+
entry: { type: "stdio", command: COMMAND, args: ARGS },
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: "opencode",
|
|
33
|
+
file: "opencode.json",
|
|
34
|
+
key: "mcp",
|
|
35
|
+
entry: { type: "local", command: [COMMAND, ...ARGS], enabled: true },
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: "codex",
|
|
39
|
+
file: CODEX_PATH,
|
|
40
|
+
key: "mcp_servers",
|
|
41
|
+
entry: {},
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
const CLIENT_NAMES = new Set(CLIENTS.map((client) => client.name));
|
|
45
|
+
const isClientName = (value) => CLIENT_NAMES.has(value);
|
|
46
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
47
|
+
const readOrUndefined = (path) => {
|
|
48
|
+
try {
|
|
49
|
+
return readFileSync(path, "utf8");
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
if (error instanceof Error && error.code === "ENOENT") {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const readJson = (path) => {
|
|
59
|
+
const source = readOrUndefined(path);
|
|
60
|
+
if (source === undefined) {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
const parsed = JSON.parse(source);
|
|
64
|
+
if (!isRecord(parsed)) {
|
|
65
|
+
throw new Error(`Refusing to edit ${path}: its contents are not a JSON object.`);
|
|
66
|
+
}
|
|
67
|
+
return parsed;
|
|
68
|
+
};
|
|
69
|
+
const getSection = (document, key) => {
|
|
70
|
+
const section = document[key];
|
|
71
|
+
return isRecord(section) ? section : {};
|
|
72
|
+
};
|
|
73
|
+
const clientFor = (name) => {
|
|
74
|
+
const client = CLIENTS.find((candidate) => candidate.name === name);
|
|
75
|
+
if (client === undefined) {
|
|
76
|
+
throw new Error(`Unknown MCP client "${name}".`);
|
|
77
|
+
}
|
|
78
|
+
return client;
|
|
79
|
+
};
|
|
80
|
+
const writeClientConfig = (root, name) => {
|
|
81
|
+
const client = clientFor(name);
|
|
82
|
+
if (name === "codex") {
|
|
83
|
+
return { kind: "manual", path: CODEX_PATH, snippet: CODEX_SNIPPET };
|
|
84
|
+
}
|
|
85
|
+
const path = join(root, client.file);
|
|
86
|
+
const existing = readJson(path);
|
|
87
|
+
const document = existing ?? {};
|
|
88
|
+
const section = { ...getSection(document, client.key), [SERVER_NAME]: client.entry };
|
|
89
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
90
|
+
writeFileSync(path, `${JSON.stringify({ ...document, [client.key]: section }, null, 4)}\n`);
|
|
91
|
+
return { kind: "written", path, isCreated: existing === undefined };
|
|
92
|
+
};
|
|
93
|
+
export { CLIENTS, isClientName, writeClientConfig };
|
|
94
|
+
//# sourceMappingURL=client-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-config.js","sourceRoot":"","sources":["../../src/mcp/client-config.ts"],"names":[],"mappings":"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","sourcesContent":["import { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\n\ntype ClientName = \"claude\" | \"cursor\" | \"vscode\" | \"opencode\" | \"codex\";\ntype ServerEntry = Record<string, unknown>;\n\ntype Client = {\n name: ClientName;\n file: string;\n key: string;\n entry: ServerEntry;\n};\n\ntype ClientResult =\n | { kind: \"written\"; path: string; isCreated: boolean } |\n { kind: \"manual\"; path: string; snippet: string };\n\nconst SERVER_NAME = \"gtkx\";\nconst COMMAND = \"npx\";\nconst ARGS = [\"gtkx\", \"mcp\"];\nconst CODEX_PATH = \"~/.codex/config.toml\";\n\nconst CODEX_SNIPPET = [\n `[mcp_servers.${SERVER_NAME}]`,\n `command = ${JSON.stringify(COMMAND)}`,\n `args = ${JSON.stringify(ARGS)}`,\n].join(\"\\n\");\n\nconst CLIENTS: Client[] = [\n {\n name: \"claude\",\n file: \".mcp.json\",\n key: \"mcpServers\",\n entry: { command: COMMAND, args: ARGS },\n },\n {\n name: \"cursor\",\n file: join(\".cursor\", \"mcp.json\"),\n key: \"mcpServers\",\n entry: { command: COMMAND, args: ARGS },\n },\n {\n name: \"vscode\",\n file: join(\".vscode\", \"mcp.json\"),\n key: \"servers\",\n entry: { type: \"stdio\", command: COMMAND, args: ARGS },\n },\n {\n name: \"opencode\",\n file: \"opencode.json\",\n key: \"mcp\",\n entry: { type: \"local\", command: [COMMAND, ...ARGS], enabled: true },\n },\n {\n name: \"codex\",\n file: CODEX_PATH,\n key: \"mcp_servers\",\n entry: {},\n },\n];\n\nconst CLIENT_NAMES: Set<string> = new Set(CLIENTS.map((client) => client.name));\n\nconst isClientName = (value: string): value is ClientName => CLIENT_NAMES.has(value);\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst readOrUndefined = (path: string): string | undefined => {\n try {\n return readFileSync(path, \"utf8\");\n } catch (error) {\n if (error instanceof Error && (error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return undefined;\n }\n\n throw error;\n }\n};\n\nconst readJson = (path: string): Record<string, unknown> | undefined => {\n const source = readOrUndefined(path);\n\n if (source === undefined) {\n return undefined;\n }\n\n const parsed: unknown = JSON.parse(source);\n\n if (!isRecord(parsed)) {\n throw new Error(`Refusing to edit ${path}: its contents are not a JSON object.`);\n }\n\n return parsed;\n};\n\nconst getSection = (document: Record<string, unknown>, key: string): Record<string, unknown> => {\n const section = document[key];\n\n return isRecord(section) ? section : {};\n};\n\nconst clientFor = (name: ClientName): Client => {\n const client = CLIENTS.find((candidate) => candidate.name === name);\n\n if (client === undefined) {\n throw new Error(`Unknown MCP client \"${name}\".`);\n }\n\n return client;\n};\n\nconst writeClientConfig = (root: string, name: ClientName): ClientResult => {\n const client = clientFor(name);\n\n if (name === \"codex\") {\n return { kind: \"manual\", path: CODEX_PATH, snippet: CODEX_SNIPPET };\n }\n\n const path = join(root, client.file);\n const existing = readJson(path);\n const document = existing ?? {};\n const section = { ...getSection(document, client.key), [SERVER_NAME]: client.entry };\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, `${JSON.stringify({ ...document, [client.key]: section }, null, 4)}\\n`);\n\n return { kind: \"written\", path, isCreated: existing === undefined };\n};\n\nexport { CLIENTS, isClientName, writeClientConfig, type ClientName, type ClientResult };\n"]}
|
package/dist/mcp/client.d.ts
CHANGED
package/dist/mcp/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/mcp/client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/mcp/client.ts"],"names":[],"mappings":"AAcA,KAAK,gBAAgB,GAAG;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;CACzB,CAAC;AAsCF,cAAM,SAAS;IACX,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,UAAU,CAAmC;IACrD,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,oBAAoB,CAAyC;IACrE,OAAO,CAAC,QAAQ,CAAwB;gBAE5B,OAAO,EAAE,gBAAgB;IAKrC,OAAO,CAAC,WAAW;IAWnB,OAAO,CAAC,iBAAiB;YAUX,kBAAkB;IAWhC,OAAO,CAAC,cAAc;IAwBtB,OAAO,CAAC,iBAAiB;IAWzB,OAAO,CAAC,QAAQ;IAgBhB,OAAO,CAAC,aAAa;IAYf,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAiB9B,UAAU,IAAI,IAAI;CAsBrB;AAED,OAAO,EAAE,SAAS,EAAE,CAAC"}
|
package/dist/mcp/client.js
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
import * as Gio from "@gtkx/gi/gio";
|
|
2
2
|
import * as Gtk from "@gtkx/gi/gtk";
|
|
3
|
-
import { DEFAULT_SOCKET_PATH,
|
|
4
|
-
import { error, errorMessage, info, normalizeError
|
|
3
|
+
import { DEFAULT_SOCKET_PATH, isConnectionClosedError, ProtocolConnection, } from "@gtkx/mcp/internal";
|
|
4
|
+
import { error, errorMessage, info, normalizeError } from "@gtkx/utils";
|
|
5
5
|
import * as net from "node:net";
|
|
6
6
|
import { dispatch } from "./handlers.js";
|
|
7
7
|
import { WidgetRegistry } from "./widget-registry.js";
|
|
8
8
|
const RECONNECT_DELAY_MS = 2000;
|
|
9
9
|
const REGISTER_TIMEOUT_MS = 30_000;
|
|
10
10
|
const DISCONNECT_ERROR_CODES = new Set(["ENOENT", "ECONNREFUSED", "EPIPE", "ECONNRESET"]);
|
|
11
|
-
const toResponseError = (error) => error instanceof ProtocolError
|
|
12
|
-
? error.toErrorObject()
|
|
13
|
-
: { code: ErrorCode.INTERNAL_ERROR, message: errorMessage(error) };
|
|
14
11
|
const connectSettler = (callbacks) => {
|
|
15
12
|
let isSettled = false;
|
|
16
13
|
const settle = (notify) => {
|
|
@@ -54,7 +51,7 @@ class McpClient {
|
|
|
54
51
|
}
|
|
55
52
|
handleSocketError(socketError) {
|
|
56
53
|
const code = socketError.code;
|
|
57
|
-
if (code !== undefined && DISCONNECT_ERROR_CODES.has(code)) {
|
|
54
|
+
if (isConnectionClosedError(socketError) || (code !== undefined && DISCONNECT_ERROR_CODES.has(code))) {
|
|
58
55
|
this.scheduleReconnect();
|
|
59
56
|
}
|
|
60
57
|
else {
|
|
@@ -88,12 +85,7 @@ class McpClient {
|
|
|
88
85
|
settle.fail(socketError);
|
|
89
86
|
},
|
|
90
87
|
});
|
|
91
|
-
connection.
|
|
92
|
-
void this.handleRequest(request);
|
|
93
|
-
});
|
|
94
|
-
connection.on("invalid", ({ error: parseError }) => {
|
|
95
|
-
warn(`Received invalid JSON from MCP server: ${parseError.message}`);
|
|
96
|
-
});
|
|
88
|
+
connection.fallbackRequestHandler = (request) => this.handleRequest(request);
|
|
97
89
|
this.socket = socket;
|
|
98
90
|
this.connection = connection;
|
|
99
91
|
}
|
|
@@ -116,32 +108,13 @@ class McpClient {
|
|
|
116
108
|
projectRoot: process.cwd(),
|
|
117
109
|
}, REGISTER_TIMEOUT_MS);
|
|
118
110
|
}
|
|
119
|
-
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
try {
|
|
126
|
-
const defaultApp = Gio.Application.getDefault();
|
|
127
|
-
if (!(defaultApp instanceof Gtk.Application)) {
|
|
128
|
-
throw new TypeError("Application not initialized");
|
|
129
|
-
}
|
|
130
|
-
this.registry.refresh();
|
|
131
|
-
const result = await dispatch(method, params, { app: defaultApp, registry: this.registry });
|
|
132
|
-
connection.write({ id, result });
|
|
133
|
-
}
|
|
134
|
-
catch (dispatchError) {
|
|
135
|
-
connection.write({ id, error: toResponseError(dispatchError) });
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
async handleRequest(request) {
|
|
139
|
-
try {
|
|
140
|
-
await this.respondToRequest(request);
|
|
141
|
-
}
|
|
142
|
-
catch (requestError) {
|
|
143
|
-
error("Error handling request:", requestError);
|
|
111
|
+
handleRequest(request) {
|
|
112
|
+
const defaultApp = Gio.Application.getDefault();
|
|
113
|
+
if (!(defaultApp instanceof Gtk.Application)) {
|
|
114
|
+
throw new TypeError("Application not initialized");
|
|
144
115
|
}
|
|
116
|
+
this.registry.refresh();
|
|
117
|
+
return dispatch(request.method, request.params, { app: defaultApp, registry: this.registry });
|
|
145
118
|
}
|
|
146
119
|
async connect() {
|
|
147
120
|
return new Promise((resolve, reject) => {
|
|
@@ -168,9 +141,8 @@ class McpClient {
|
|
|
168
141
|
clearTimeout(this.reconnectTimer);
|
|
169
142
|
this.reconnectTimer = null;
|
|
170
143
|
}
|
|
171
|
-
this.connection?.rejectPending(new Error("Client disconnected"));
|
|
172
144
|
if (this.socket) {
|
|
173
|
-
this.connection?.
|
|
145
|
+
void Promise.allSettled([this.connection?.send("app.unregister")]);
|
|
174
146
|
this.socket.destroy();
|
|
175
147
|
this.socket = null;
|
|
176
148
|
}
|