summer-engine 2.2.0 → 2.5.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +20 -5
- package/.codex-plugin/plugin.json +15 -5
- package/.cursor-plugin/plugin.json +15 -5
- package/.factory-plugin/plugin.json +2 -2
- package/.opencode/INSTALL.md +1 -1
- package/CHANGELOG.md +27 -6
- package/CLAUDE.md +2 -2
- package/GEMINI.md +2 -3
- package/README.md +82 -58
- package/_persona/summer/SKILL.md +5 -6
- package/commands/gameskill.md +103 -0
- package/dist/bin/summer.js +12 -4
- package/dist/commands/create.js +2 -0
- package/dist/commands/doctor.js +71 -1
- package/dist/commands/install.d.ts +17 -0
- package/dist/commands/install.js +63 -4
- package/dist/commands/memory.d.ts +14 -0
- package/dist/commands/memory.js +196 -0
- package/dist/commands/setup.js +12 -2
- package/dist/commands/skills.js +205 -8
- package/dist/commands/status.js +3 -0
- package/dist/lib/agent-config.d.ts +2 -2
- package/dist/lib/agent-config.js +298 -5
- package/dist/lib/api-client.d.ts +13 -0
- package/dist/lib/api-client.js +63 -26
- package/dist/lib/async-op-lifecycle.d.ts +60 -0
- package/dist/lib/async-op-lifecycle.js +83 -0
- package/dist/lib/project-memory.d.ts +33 -0
- package/dist/lib/project-memory.js +220 -0
- package/dist/lib/skills-registry.d.ts +134 -61
- package/dist/lib/skills-registry.js +106 -4
- package/dist/lib/version-check.d.ts +106 -0
- package/dist/lib/version-check.js +338 -0
- package/dist/mcp/server.d.ts +2 -0
- package/dist/mcp/server.js +81 -0
- package/dist/mcp/tools/debug-tools.js +32 -2
- package/dist/mcp/tools/project-tools.js +18 -6
- package/dist/mcp/tools/with-engine.d.ts +14 -0
- package/dist/mcp/tools/with-engine.js +52 -7
- package/gemini-extension.json +2 -2
- package/hooks/pre-commit-doctor.sh +0 -0
- package/hooks/run-hook.cmd +0 -0
- package/hooks/session-start.sh +0 -0
- package/package.json +6 -4
- package/skills/2d-assets/character-portrait/SKILL.md +1 -1
- package/skills/2d-assets/concept-art/SKILL.md +1 -1
- package/skills/2d-assets/create-asset-sheet/SKILL.md +163 -0
- package/skills/2d-assets/instantiate-asset-pack/SKILL.md +245 -0
- package/skills/2d-assets/pixel-art/SKILL.md +1 -1
- package/skills/2d-assets/skybox-panorama/SKILL.md +1 -1
- package/skills/2d-assets/sprite-sheet/SKILL.md +1 -1
- package/skills/2d-assets/tileable-texture/SKILL.md +1 -1
- package/skills/2d-assets/ui-graphics/SKILL.md +1 -1
- package/skills/2d-assets/use-widget-asset/SKILL.md +217 -0
- package/skills/3d-assets/character-model/SKILL.md +1 -1
- package/skills/3d-assets/organic-model/SKILL.md +1 -1
- package/skills/3d-assets/prop-model/SKILL.md +1 -1
- package/skills/3d-assets/vehicle-model/SKILL.md +1 -1
- package/skills/README.md +37 -30
- package/skills/ai-and-npcs/design-npc/SKILL.md +6 -6
- package/skills/animation/animation-tree/SKILL.md +2 -2
- package/skills/animation/facial-and-lipsync/SKILL.md +1 -1
- package/skills/animation/generate-motion/SKILL.md +1 -1
- package/skills/animation/procedural-animation/SKILL.md +2 -2
- package/skills/audio/adaptive-music/SKILL.md +1 -1
- package/skills/audio/audio-direction/SKILL.md +5 -5
- package/skills/audio/sound-effect/SKILL.md +2 -2
- package/skills/audio/voice-line/SKILL.md +25 -3
- package/skills/character-controllers/fps-controller/SKILL.md +5 -5
- package/skills/debugging/debug/SKILL.md +39 -1
- package/skills/deployment/export-and-ship/SKILL.md +4 -4
- package/skills/gameplay-mechanics/auto-fire-targeting/SKILL.md +170 -0
- package/skills/gameplay-mechanics/design-mechanic/SKILL.md +8 -8
- package/skills/level-design/design-level/SKILL.md +5 -5
- package/skills/level-design/scene-to-level/SKILL.md +125 -0
- package/skills/multiplayer-and-networking/host-authoritative-state/SKILL.md +4 -4
- package/skills/multiplayer-and-networking/peer-to-peer-multiplayer/SKILL.md +4 -4
- package/skills/multiplayer-and-networking/setup-multiplayer/SKILL.md +7 -7
- package/skills/performance/tune-performance/SKILL.md +5 -5
- package/skills/rendering-and-lighting/3d-lighting/SKILL.md +1 -1
- package/skills/rendering-and-lighting/art-direction/SKILL.md +5 -5
- package/skills/scene-and-project/brainstorm-game/SKILL.md +4 -4
- package/skills/scene-and-project/play/SKILL.md +1 -1
- package/skills/scene-and-project/scene-composition/SKILL.md +30 -1
- package/skills/scripting-patterns/gdscript-patterns/SKILL.md +53 -0
- package/skills/ui-and-ux/ui-basics/SKILL.md +1 -1
- package/skills/video/animated-loop/SKILL.md +1 -1
- package/skills/video/cinematic-cutscene/SKILL.md +1 -1
- package/skills/video/trailer-shot/SKILL.md +1 -1
- package/skills/visual-effects/game-feel/SKILL.md +6 -6
- package/skills/workflow/brainstorming/SKILL.md +164 -0
- package/skills/workflow/brainstorming/scripts/frame-template.html +214 -0
- package/skills/workflow/brainstorming/scripts/helper.js +88 -0
- package/skills/workflow/brainstorming/scripts/server.cjs +354 -0
- package/skills/workflow/brainstorming/scripts/start-server.sh +148 -0
- package/skills/workflow/brainstorming/scripts/stop-server.sh +56 -0
- package/skills/workflow/brainstorming/spec-document-reviewer-prompt.md +49 -0
- package/skills/workflow/brainstorming/visual-companion.md +287 -0
- package/skills/workflow/debugging-game-feel/SKILL.md +174 -0
- package/skills/workflow/diagnosing-perf-regressions/SKILL.md +190 -0
- package/skills/workflow/dispatching-parallel-agents/SKILL.md +182 -0
- package/skills/workflow/gameskill/SKILL.md +111 -0
- package/skills/workflow/investigating-bugs/CREATION-LOG.md +119 -0
- package/skills/workflow/investigating-bugs/SKILL.md +296 -0
- package/skills/workflow/investigating-bugs/condition-based-waiting-example.ts +158 -0
- package/skills/workflow/investigating-bugs/condition-based-waiting.md +115 -0
- package/skills/workflow/investigating-bugs/defense-in-depth.md +122 -0
- package/skills/workflow/investigating-bugs/find-polluter.sh +63 -0
- package/skills/workflow/investigating-bugs/root-cause-tracing.md +169 -0
- package/skills/workflow/investigating-bugs/test-academic.md +14 -0
- package/skills/workflow/investigating-bugs/test-pressure-1.md +58 -0
- package/skills/workflow/investigating-bugs/test-pressure-2.md +68 -0
- package/skills/workflow/investigating-bugs/test-pressure-3.md +69 -0
- package/skills/workflow/playtesting-a-feature/SKILL.md +173 -0
- package/skills/{_meta → workflow}/skill-create/SKILL.md +32 -22
- package/skills/{_meta → workflow}/skill-improve/SKILL.md +9 -10
- package/skills/{_meta → workflow}/skill-test/SKILL.md +12 -12
- package/skills/{_meta → workflow}/using-summer/SKILL.md +5 -2
- package/skills/workflow/verification-before-completion/SKILL.md +139 -0
- package/skills/workflow/writing-plans/SKILL.md +152 -0
- package/skills/workflow/writing-plans/plan-document-reviewer-prompt.md +49 -0
- package/skills/workflow/writing-skills/SKILL.md +655 -0
- package/skills/workflow/writing-skills/anthropic-best-practices.md +1150 -0
- package/skills/workflow/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
- package/skills/workflow/writing-skills/graphviz-conventions.dot +172 -0
- package/skills/workflow/writing-skills/persuasion-principles.md +187 -0
- package/skills/workflow/writing-skills/render-graphs.js +168 -0
- package/skills/workflow/writing-skills/testing-skills-with-subagents.md +384 -0
- package/dist/commands/assets.d.ts +0 -2
- package/dist/commands/assets.js +0 -189
- package/dist/commands/debug.d.ts +0 -2
- package/dist/commands/debug.js +0 -127
- package/dist/commands/doctor.test.d.ts +0 -1
- package/dist/commands/doctor.test.js +0 -29
- package/dist/commands/project.d.ts +0 -2
- package/dist/commands/project.js +0 -170
- package/dist/commands/scene.d.ts +0 -2
- package/dist/commands/scene.js +0 -290
- package/dist/lib/agent-config.test.d.ts +0 -1
- package/dist/lib/agent-config.test.js +0 -80
- package/dist/lib/format.test.d.ts +0 -1
- package/dist/lib/format.test.js +0 -36
- package/dist/lib/plugin-manifests.test.d.ts +0 -1
- package/dist/lib/plugin-manifests.test.js +0 -134
- package/dist/lib/with-engine.d.ts +0 -14
- package/dist/lib/with-engine.js +0 -109
- package/skills/_shared/collaborative-protocol.md +0 -50
- package/skills/_shared/gd-style.md +0 -121
- package/skills/_shared/godot-version.md +0 -32
- package/skills/_shared/mcp-tools-reference.md +0 -133
- package/skills/_shared/summer-folder.md +0 -44
- package/skills/_shared/template-registry.md +0 -40
- package/skills/_tests/runner.md +0 -90
- package/skills/_tests/specs/3d-lighting.md +0 -19
- package/skills/_tests/specs/art-direction.md +0 -115
- package/skills/_tests/specs/asset-strategy.md +0 -19
- package/skills/_tests/specs/audio-direction.md +0 -123
- package/skills/_tests/specs/brainstorm-game.md +0 -99
- package/skills/_tests/specs/debug.md +0 -94
- package/skills/_tests/specs/design-level.md +0 -113
- package/skills/_tests/specs/design-mechanic.md +0 -112
- package/skills/_tests/specs/design-npc.md +0 -143
- package/skills/_tests/specs/export-and-ship.md +0 -114
- package/skills/_tests/specs/fps-controller.md +0 -113
- package/skills/_tests/specs/gdscript-patterns.md +0 -19
- package/skills/_tests/specs/host-authoritative-state.md +0 -104
- package/skills/_tests/specs/make-game.md +0 -19
- package/skills/_tests/specs/peer-to-peer-multiplayer.md +0 -99
- package/skills/_tests/specs/play.md +0 -88
- package/skills/_tests/specs/scene-composition.md +0 -19
- package/skills/_tests/specs/setup-multiplayer.md +0 -106
- package/skills/_tests/specs/tune-performance.md +0 -104
- package/skills/_tests/specs/ui-basics.md +0 -19
- package/skills/_tests/specs/vfx.md +0 -116
- package/skills/catalog.yaml +0 -991
package/dist/commands/scene.js
DELETED
|
@@ -1,290 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { readFile } from "fs/promises";
|
|
3
|
-
import { join } from "path";
|
|
4
|
-
import { withEngineClient } from "../lib/with-engine.js";
|
|
5
|
-
import { outputResult, outputError, parseValue, readJsonArg, isJsonMode, } from "../lib/format.js";
|
|
6
|
-
async function readMainSceneFromProject(projectPath) {
|
|
7
|
-
if (!projectPath)
|
|
8
|
-
return null;
|
|
9
|
-
try {
|
|
10
|
-
const text = await readFile(join(projectPath, "project.godot"), "utf-8");
|
|
11
|
-
const match = text.match(/run\/main_scene="([^"]+)"/);
|
|
12
|
-
return match?.[1] ?? null;
|
|
13
|
-
}
|
|
14
|
-
catch {
|
|
15
|
-
return null;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
export const sceneCommand = new Command("scene").description("Scene editing commands (requires running engine)");
|
|
19
|
-
// --- Tier 1 ---
|
|
20
|
-
sceneCommand
|
|
21
|
-
.command("tree")
|
|
22
|
-
.description("Get the full scene tree of the currently open scene")
|
|
23
|
-
.action(async (_opts, cmd) => {
|
|
24
|
-
const fmt = isJsonMode(cmd);
|
|
25
|
-
try {
|
|
26
|
-
const result = await withEngineClient((c) => c.getSceneState());
|
|
27
|
-
outputResult(result, fmt);
|
|
28
|
-
}
|
|
29
|
-
catch (err) {
|
|
30
|
-
outputError(err instanceof Error ? err.message : String(err), fmt);
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
sceneCommand
|
|
34
|
-
.command("open <path>")
|
|
35
|
-
.description("Open a scene file in the editor (e.g. res://main.tscn)")
|
|
36
|
-
.action(async (path, _opts, cmd) => {
|
|
37
|
-
const fmt = isJsonMode(cmd);
|
|
38
|
-
try {
|
|
39
|
-
const result = await withEngineClient((c) => c.executeOps([{ op: "OpenScene", path }]));
|
|
40
|
-
outputResult(result, fmt);
|
|
41
|
-
}
|
|
42
|
-
catch (err) {
|
|
43
|
-
outputError(err instanceof Error ? err.message : String(err), fmt);
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
sceneCommand
|
|
47
|
-
.command("add <parent> <type> <name>")
|
|
48
|
-
.description("Add a node to the scene tree (e.g. summer scene add ./World MeshInstance3D Floor)")
|
|
49
|
-
.action(async (parent, type, name, _opts, cmd) => {
|
|
50
|
-
const fmt = isJsonMode(cmd);
|
|
51
|
-
try {
|
|
52
|
-
const result = await withEngineClient((c) => c.executeOps([{ op: "AddNode", parent, type, name }]));
|
|
53
|
-
outputResult(result, fmt);
|
|
54
|
-
}
|
|
55
|
-
catch (err) {
|
|
56
|
-
outputError(err instanceof Error ? err.message : String(err), fmt);
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
sceneCommand
|
|
60
|
-
.command("prop <path> <key> <value>")
|
|
61
|
-
.description('Set a property on a node (e.g. summer scene prop ./Player position "Vector3(0,5,0)")')
|
|
62
|
-
.action(async (path, key, value, _opts, cmd) => {
|
|
63
|
-
const fmt = isJsonMode(cmd);
|
|
64
|
-
try {
|
|
65
|
-
const result = await withEngineClient((c) => c.executeOps([{ op: "SetProp", path, key, value: parseValue(value) }]));
|
|
66
|
-
outputResult(result, fmt);
|
|
67
|
-
}
|
|
68
|
-
catch (err) {
|
|
69
|
-
outputError(err instanceof Error ? err.message : String(err), fmt);
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
sceneCommand
|
|
73
|
-
.command("save [path]")
|
|
74
|
-
.description("Save the current scene (optionally save-as to a new path)")
|
|
75
|
-
.action(async (path, _opts, cmd) => {
|
|
76
|
-
const fmt = isJsonMode(cmd);
|
|
77
|
-
try {
|
|
78
|
-
const op = { op: "SaveScene" };
|
|
79
|
-
if (path)
|
|
80
|
-
op.path = path;
|
|
81
|
-
const result = await withEngineClient((c) => c.executeOps([op]));
|
|
82
|
-
outputResult(result, fmt);
|
|
83
|
-
}
|
|
84
|
-
catch (err) {
|
|
85
|
-
outputError(err instanceof Error ? err.message : String(err), fmt);
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
sceneCommand
|
|
89
|
-
.command("inspect <path>")
|
|
90
|
-
.description("Get all editable properties of a node (e.g. summer scene inspect Player)")
|
|
91
|
-
.action(async (path, _opts, cmd) => {
|
|
92
|
-
const fmt = isJsonMode(cmd);
|
|
93
|
-
try {
|
|
94
|
-
const result = await withEngineClient((c) => c.inspectNode(path));
|
|
95
|
-
outputResult(result, fmt);
|
|
96
|
-
}
|
|
97
|
-
catch (err) {
|
|
98
|
-
outputError(err instanceof Error ? err.message : String(err), fmt);
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
sceneCommand
|
|
102
|
-
.command("batch <json>")
|
|
103
|
-
.description('Execute multiple ops in one undo step. Pass JSON array or "-" for stdin')
|
|
104
|
-
.action(async (jsonArg, _opts, cmd) => {
|
|
105
|
-
const fmt = isJsonMode(cmd);
|
|
106
|
-
try {
|
|
107
|
-
const ops = (await readJsonArg(jsonArg));
|
|
108
|
-
const result = await withEngineClient((c) => c.executeOps(ops, { groupUndo: true }));
|
|
109
|
-
outputResult(result, fmt);
|
|
110
|
-
}
|
|
111
|
-
catch (err) {
|
|
112
|
-
outputError(err instanceof Error ? err.message : String(err), fmt);
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
// --- Tier 2 ---
|
|
116
|
-
sceneCommand
|
|
117
|
-
.command("remove <path>")
|
|
118
|
-
.description("Remove a node and all its children from the scene tree")
|
|
119
|
-
.action(async (path, _opts, cmd) => {
|
|
120
|
-
const fmt = isJsonMode(cmd);
|
|
121
|
-
try {
|
|
122
|
-
const result = await withEngineClient((c) => c.executeOps([{ op: "RemoveNode", path }]));
|
|
123
|
-
outputResult(result, fmt);
|
|
124
|
-
}
|
|
125
|
-
catch (err) {
|
|
126
|
-
outputError(err instanceof Error ? err.message : String(err), fmt);
|
|
127
|
-
}
|
|
128
|
-
});
|
|
129
|
-
sceneCommand
|
|
130
|
-
.command("rprop <nodePath> <resourceProperty> <subProperty> <value>")
|
|
131
|
-
.description("Set a nested resource property (e.g. summer scene rprop ./Player/Col shape size \"Vector3(1,2,1)\")")
|
|
132
|
-
.action(async (nodePath, resourceProperty, subProperty, value, _opts, cmd) => {
|
|
133
|
-
const fmt = isJsonMode(cmd);
|
|
134
|
-
try {
|
|
135
|
-
const result = await withEngineClient((c) => c.executeOps([
|
|
136
|
-
{
|
|
137
|
-
op: "SetResourceProperty",
|
|
138
|
-
nodePath,
|
|
139
|
-
resourceProperty,
|
|
140
|
-
subProperty,
|
|
141
|
-
value: parseValue(value),
|
|
142
|
-
},
|
|
143
|
-
]));
|
|
144
|
-
outputResult(result, fmt);
|
|
145
|
-
}
|
|
146
|
-
catch (err) {
|
|
147
|
-
outputError(err instanceof Error ? err.message : String(err), fmt);
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
sceneCommand
|
|
151
|
-
.command("instantiate <parent> <scene>")
|
|
152
|
-
.description("Add an existing scene/model as a child (e.g. summer scene instantiate ./World res://player.tscn)")
|
|
153
|
-
.option("--name <name>", "Override the instance name")
|
|
154
|
-
.action(async (parent, scene, opts, cmd) => {
|
|
155
|
-
const fmt = isJsonMode(cmd);
|
|
156
|
-
try {
|
|
157
|
-
const op = {
|
|
158
|
-
op: "InstantiateScene",
|
|
159
|
-
parent,
|
|
160
|
-
scene,
|
|
161
|
-
};
|
|
162
|
-
if (opts.name)
|
|
163
|
-
op.name = opts.name;
|
|
164
|
-
const result = await withEngineClient((c) => c.executeOps([op]));
|
|
165
|
-
outputResult(result, fmt);
|
|
166
|
-
}
|
|
167
|
-
catch (err) {
|
|
168
|
-
outputError(err instanceof Error ? err.message : String(err), fmt);
|
|
169
|
-
}
|
|
170
|
-
});
|
|
171
|
-
sceneCommand
|
|
172
|
-
.command("create <path>")
|
|
173
|
-
.description("Create a new empty scene file")
|
|
174
|
-
.option("--root-name <name>", "Root node name", "Main")
|
|
175
|
-
.option("--yes", "Skip safety confirmation")
|
|
176
|
-
.action(async (path, opts, cmd) => {
|
|
177
|
-
const fmt = isJsonMode(cmd);
|
|
178
|
-
if (!opts.yes) {
|
|
179
|
-
outputError("Scene creation uses a temporary mutation strategy. Pass --yes to confirm.", fmt);
|
|
180
|
-
}
|
|
181
|
-
try {
|
|
182
|
-
const result = await withEngineClient(async (client) => {
|
|
183
|
-
const health = (await client.health());
|
|
184
|
-
const currentScene = typeof health.scene === "string" && health.scene.length > 0
|
|
185
|
-
? health.scene
|
|
186
|
-
: null;
|
|
187
|
-
const projectPath = typeof health.project_path === "string"
|
|
188
|
-
? health.project_path
|
|
189
|
-
: undefined;
|
|
190
|
-
const mainScene = await readMainSceneFromProject(projectPath);
|
|
191
|
-
const templateScene = currentScene || mainScene;
|
|
192
|
-
if (!templateScene) {
|
|
193
|
-
throw new Error("No scene open and could not resolve main scene. Run `summer project context` first.");
|
|
194
|
-
}
|
|
195
|
-
await client.executeOps([{ op: "OpenScene", path: templateScene }]);
|
|
196
|
-
const tree = (await client.getSceneState());
|
|
197
|
-
const children = tree.data?.children ?? tree.children ?? [];
|
|
198
|
-
const removeOps = children
|
|
199
|
-
.map((c) => c.path)
|
|
200
|
-
.filter((p) => typeof p === "string" && p.length > 0)
|
|
201
|
-
.map((p) => ({ op: "RemoveNode", path: `./${p}` }));
|
|
202
|
-
if (removeOps.length > 0) {
|
|
203
|
-
await client.executeOps(removeOps, { groupUndo: true });
|
|
204
|
-
}
|
|
205
|
-
await client.executeOps([
|
|
206
|
-
{ op: "SetProp", path: ".", key: "name", value: opts.rootName },
|
|
207
|
-
]);
|
|
208
|
-
await client.executeOps([{ op: "SaveScene", path }]);
|
|
209
|
-
if (currentScene && currentScene !== path) {
|
|
210
|
-
await client.executeOps([{ op: "OpenScene", path: currentScene }]);
|
|
211
|
-
}
|
|
212
|
-
return {
|
|
213
|
-
ok: true,
|
|
214
|
-
created: path,
|
|
215
|
-
rootName: opts.rootName,
|
|
216
|
-
templateScene,
|
|
217
|
-
restoredScene: currentScene,
|
|
218
|
-
};
|
|
219
|
-
});
|
|
220
|
-
outputResult(result, fmt);
|
|
221
|
-
}
|
|
222
|
-
catch (err) {
|
|
223
|
-
outputError(err instanceof Error ? err.message : String(err), fmt);
|
|
224
|
-
}
|
|
225
|
-
});
|
|
226
|
-
sceneCommand
|
|
227
|
-
.command("replace <path>")
|
|
228
|
-
.description("Replace a node with a different type or scene")
|
|
229
|
-
.option("--type <type>", "New node type (e.g. RigidBody3D)")
|
|
230
|
-
.option("--scene <scene>", "Scene to replace with (e.g. res://enemies/boss.tscn)")
|
|
231
|
-
.action(async (path, opts, cmd) => {
|
|
232
|
-
const fmt = isJsonMode(cmd);
|
|
233
|
-
try {
|
|
234
|
-
const op = { op: "ReplaceNode", path };
|
|
235
|
-
if (opts.type)
|
|
236
|
-
op.type = opts.type;
|
|
237
|
-
if (opts.scene)
|
|
238
|
-
op.scene = opts.scene;
|
|
239
|
-
const result = await withEngineClient((c) => c.executeOps([op]));
|
|
240
|
-
outputResult(result, fmt);
|
|
241
|
-
}
|
|
242
|
-
catch (err) {
|
|
243
|
-
outputError(err instanceof Error ? err.message : String(err), fmt);
|
|
244
|
-
}
|
|
245
|
-
});
|
|
246
|
-
sceneCommand
|
|
247
|
-
.command("signal <emitter> <signal> <receiver> <method>")
|
|
248
|
-
.description("Connect a signal between two nodes (e.g. summer scene signal ./HitArea body_entered ./Player _on_hit)")
|
|
249
|
-
.action(async (emitter, signal, receiver, method, _opts, cmd) => {
|
|
250
|
-
const fmt = isJsonMode(cmd);
|
|
251
|
-
try {
|
|
252
|
-
const result = await withEngineClient((c) => c.executeOps([
|
|
253
|
-
{ op: "ConnectSignal", emitter, signal, receiver, method },
|
|
254
|
-
]));
|
|
255
|
-
outputResult(result, fmt);
|
|
256
|
-
}
|
|
257
|
-
catch (err) {
|
|
258
|
-
outputError(err instanceof Error ? err.message : String(err), fmt);
|
|
259
|
-
}
|
|
260
|
-
});
|
|
261
|
-
sceneCommand
|
|
262
|
-
.command("select <nodePath>")
|
|
263
|
-
.description("Select a node in the editor inspector")
|
|
264
|
-
.option("--scene <scenePath>", "Open this scene first, then select")
|
|
265
|
-
.action(async (nodePath, opts, cmd) => {
|
|
266
|
-
const fmt = isJsonMode(cmd);
|
|
267
|
-
try {
|
|
268
|
-
const op = { op: "SelectNode", nodePath };
|
|
269
|
-
if (opts.scene)
|
|
270
|
-
op.scenePath = opts.scene;
|
|
271
|
-
const result = await withEngineClient((c) => c.executeOps([op]));
|
|
272
|
-
outputResult(result, fmt);
|
|
273
|
-
}
|
|
274
|
-
catch (err) {
|
|
275
|
-
outputError(err instanceof Error ? err.message : String(err), fmt);
|
|
276
|
-
}
|
|
277
|
-
});
|
|
278
|
-
sceneCommand
|
|
279
|
-
.command("inspect-resource <path>")
|
|
280
|
-
.description("Get all properties of a resource (material, mesh, shape, etc.)")
|
|
281
|
-
.action(async (path, _opts, cmd) => {
|
|
282
|
-
const fmt = isJsonMode(cmd);
|
|
283
|
-
try {
|
|
284
|
-
const result = await withEngineClient((c) => c.inspectResource(path));
|
|
285
|
-
outputResult(result, fmt);
|
|
286
|
-
}
|
|
287
|
-
catch (err) {
|
|
288
|
-
outputError(err instanceof Error ? err.message : String(err), fmt);
|
|
289
|
-
}
|
|
290
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { mkdtempSync, readFileSync, writeFileSync } from "fs";
|
|
2
|
-
import { tmpdir } from "os";
|
|
3
|
-
import { join } from "path";
|
|
4
|
-
import { describe, expect, it } from "vitest";
|
|
5
|
-
import { configureAgentMcp } from "./agent-config.js";
|
|
6
|
-
function tmp() {
|
|
7
|
-
return mkdtempSync(join(tmpdir(), "summer-agent-config-"));
|
|
8
|
-
}
|
|
9
|
-
describe("configureAgentMcp", () => {
|
|
10
|
-
it("writes a fresh claude-code config", async () => {
|
|
11
|
-
const dir = tmp();
|
|
12
|
-
const path = join(dir, ".claude.json");
|
|
13
|
-
const result = await configureAgentMcp({
|
|
14
|
-
agent: "claude-code",
|
|
15
|
-
scope: "user",
|
|
16
|
-
env: { SUMMER_CLAUDE_CONFIG_FILE: path },
|
|
17
|
-
});
|
|
18
|
-
expect(result.wrote).toBe(true);
|
|
19
|
-
const written = JSON.parse(readFileSync(path, "utf-8"));
|
|
20
|
-
expect(written.mcpServers["summer-engine"].command).toBe("npx");
|
|
21
|
-
expect(written.mcpServers["summer-engine"].args).toEqual([
|
|
22
|
-
"summer-engine",
|
|
23
|
-
"mcp",
|
|
24
|
-
]);
|
|
25
|
-
});
|
|
26
|
-
it("preserves unrelated keys when merging claude-code config", async () => {
|
|
27
|
-
const dir = tmp();
|
|
28
|
-
const path = join(dir, ".claude.json");
|
|
29
|
-
writeFileSync(path, JSON.stringify({
|
|
30
|
-
theme: "dark",
|
|
31
|
-
mcpServers: {
|
|
32
|
-
other: { command: "node", args: ["other.js"] },
|
|
33
|
-
},
|
|
34
|
-
}, null, 2));
|
|
35
|
-
await configureAgentMcp({
|
|
36
|
-
agent: "claude-code",
|
|
37
|
-
scope: "user",
|
|
38
|
-
env: { SUMMER_CLAUDE_CONFIG_FILE: path },
|
|
39
|
-
});
|
|
40
|
-
const written = JSON.parse(readFileSync(path, "utf-8"));
|
|
41
|
-
expect(written.theme).toBe("dark");
|
|
42
|
-
expect(written.mcpServers.other.command).toBe("node");
|
|
43
|
-
expect(written.mcpServers["summer-engine"].command).toBe("npx");
|
|
44
|
-
});
|
|
45
|
-
it("upserts a codex TOML server table", async () => {
|
|
46
|
-
const dir = tmp();
|
|
47
|
-
const path = join(dir, "config.toml");
|
|
48
|
-
writeFileSync(path, ['[mcp_servers.other]', 'command = "node"', 'args = ["other.js"]', ''].join("\n"));
|
|
49
|
-
const first = await configureAgentMcp({
|
|
50
|
-
agent: "codex",
|
|
51
|
-
scope: "user",
|
|
52
|
-
env: { SUMMER_CODEX_CONFIG_FILE: path },
|
|
53
|
-
});
|
|
54
|
-
expect(first.wrote).toBe(true);
|
|
55
|
-
let content = readFileSync(path, "utf-8");
|
|
56
|
-
expect(content).toContain("[mcp_servers.other]");
|
|
57
|
-
expect(content).toContain("[mcp_servers.summer-engine]");
|
|
58
|
-
const second = await configureAgentMcp({
|
|
59
|
-
agent: "codex",
|
|
60
|
-
scope: "user",
|
|
61
|
-
env: { SUMMER_CODEX_CONFIG_FILE: path },
|
|
62
|
-
});
|
|
63
|
-
expect(second.wrote).toBe(false);
|
|
64
|
-
content = readFileSync(path, "utf-8");
|
|
65
|
-
const occurrences = content.match(/\[mcp_servers\.summer-engine\]/g) ?? [];
|
|
66
|
-
expect(occurrences.length).toBe(1);
|
|
67
|
-
});
|
|
68
|
-
it("dry-run does not write", async () => {
|
|
69
|
-
const dir = tmp();
|
|
70
|
-
const path = join(dir, ".claude.json");
|
|
71
|
-
const result = await configureAgentMcp({
|
|
72
|
-
agent: "claude-code",
|
|
73
|
-
scope: "user",
|
|
74
|
-
dryRun: true,
|
|
75
|
-
env: { SUMMER_CLAUDE_CONFIG_FILE: path },
|
|
76
|
-
});
|
|
77
|
-
expect(result.wrote).toBe(false);
|
|
78
|
-
expect(() => readFileSync(path, "utf-8")).toThrow();
|
|
79
|
-
});
|
|
80
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/lib/format.test.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { homedir } from "os";
|
|
2
|
-
import { describe, expect, it } from "vitest";
|
|
3
|
-
import { stripAnsi, tildeify } from "./format.js";
|
|
4
|
-
describe("tildeify", () => {
|
|
5
|
-
it("replaces home directory prefix with ~", () => {
|
|
6
|
-
const home = homedir();
|
|
7
|
-
expect(tildeify(home + "/.claude.json")).toBe("~/.claude.json");
|
|
8
|
-
expect(tildeify(home + "/projects/foo")).toBe("~/projects/foo");
|
|
9
|
-
});
|
|
10
|
-
it("returns ~ when path is the home directory exactly", () => {
|
|
11
|
-
expect(tildeify(homedir())).toBe("~");
|
|
12
|
-
});
|
|
13
|
-
it("does not touch non-home paths", () => {
|
|
14
|
-
expect(tildeify("/Applications/Summer.app")).toBe("/Applications/Summer.app");
|
|
15
|
-
expect(tildeify("/tmp/foo")).toBe("/tmp/foo");
|
|
16
|
-
});
|
|
17
|
-
it("does not partial-match (e.g. /Users/Mat vs /Users/MathiasWork)", () => {
|
|
18
|
-
const home = homedir();
|
|
19
|
-
const sibling = home.slice(0, -2) + "Other";
|
|
20
|
-
expect(tildeify(sibling + "/file")).toBe(sibling + "/file");
|
|
21
|
-
});
|
|
22
|
-
it("handles empty/null/undefined input", () => {
|
|
23
|
-
expect(tildeify(undefined)).toBe("");
|
|
24
|
-
expect(tildeify(null)).toBe("");
|
|
25
|
-
expect(tildeify("")).toBe("");
|
|
26
|
-
});
|
|
27
|
-
});
|
|
28
|
-
describe("stripAnsi", () => {
|
|
29
|
-
it("removes ANSI color codes", () => {
|
|
30
|
-
expect(stripAnsi("\x1b[31mhello\x1b[0m")).toBe("hello");
|
|
31
|
-
expect(stripAnsi("\x1b[1;32mbold green\x1b[0m text")).toBe("bold green text");
|
|
32
|
-
});
|
|
33
|
-
it("leaves clean strings untouched", () => {
|
|
34
|
-
expect(stripAnsi("hello")).toBe("hello");
|
|
35
|
-
});
|
|
36
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Validates that every agent plugin manifest references real, on-disk skills,
|
|
3
|
-
* and that every skill claimed HAVE in catalog.yaml actually exists.
|
|
4
|
-
*
|
|
5
|
-
* Catches broken-path bugs before they ship. Add a manifest, add a path here.
|
|
6
|
-
*/
|
|
7
|
-
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
8
|
-
import { dirname, join } from "node:path";
|
|
9
|
-
import { fileURLToPath } from "node:url";
|
|
10
|
-
import { describe, expect, it } from "vitest";
|
|
11
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
|
-
const repoRoot = join(__dirname, "..", "..");
|
|
13
|
-
const MANIFESTS = [
|
|
14
|
-
{
|
|
15
|
-
name: ".claude-plugin/plugin.json",
|
|
16
|
-
path: join(repoRoot, ".claude-plugin", "plugin.json"),
|
|
17
|
-
skillsField: [],
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
name: ".cursor-plugin/plugin.json",
|
|
21
|
-
path: join(repoRoot, ".cursor-plugin", "plugin.json"),
|
|
22
|
-
skillsField: [],
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
name: ".codex-plugin/plugin.json",
|
|
26
|
-
path: join(repoRoot, ".codex-plugin", "plugin.json"),
|
|
27
|
-
skillsField: [],
|
|
28
|
-
},
|
|
29
|
-
];
|
|
30
|
-
function loadSkillsField(manifestPath) {
|
|
31
|
-
const raw = readFileSync(manifestPath, "utf-8");
|
|
32
|
-
const json = JSON.parse(raw);
|
|
33
|
-
if (json.skills === undefined) {
|
|
34
|
-
throw new Error(`${manifestPath}: missing "skills" field`);
|
|
35
|
-
}
|
|
36
|
-
return json.skills;
|
|
37
|
-
}
|
|
38
|
-
function listSkillDirs(skillsRoot) {
|
|
39
|
-
const entries = readdirSync(skillsRoot);
|
|
40
|
-
const out = [];
|
|
41
|
-
for (const cat of entries) {
|
|
42
|
-
const catPath = join(skillsRoot, cat);
|
|
43
|
-
if (!statSync(catPath).isDirectory())
|
|
44
|
-
continue;
|
|
45
|
-
if (cat === "_shared" || cat === "_tests")
|
|
46
|
-
continue;
|
|
47
|
-
for (const skill of readdirSync(catPath)) {
|
|
48
|
-
const skillPath = join(catPath, skill);
|
|
49
|
-
if (!statSync(skillPath).isDirectory())
|
|
50
|
-
continue;
|
|
51
|
-
if (existsSync(join(skillPath, "SKILL.md"))) {
|
|
52
|
-
out.push(`./skills/${cat}/${skill}/`);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return out;
|
|
57
|
-
}
|
|
58
|
-
describe("agent plugin manifests", () => {
|
|
59
|
-
for (const m of MANIFESTS) {
|
|
60
|
-
describe(m.name, () => {
|
|
61
|
-
it("exists and parses as JSON", () => {
|
|
62
|
-
expect(existsSync(m.path), `${m.name} not found`).toBe(true);
|
|
63
|
-
m.skillsField = loadSkillsField(m.path);
|
|
64
|
-
});
|
|
65
|
-
it("every referenced skill path resolves to a SKILL.md on disk", () => {
|
|
66
|
-
const skills = m.skillsField;
|
|
67
|
-
if (typeof skills === "string") {
|
|
68
|
-
// Directory pointer mode (e.g. "./skills/")
|
|
69
|
-
const dir = join(repoRoot, skills);
|
|
70
|
-
expect(existsSync(dir), `${m.name}: skills dir ${skills} missing`).toBe(true);
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
const broken = [];
|
|
74
|
-
for (const rel of skills) {
|
|
75
|
-
const skillMd = join(repoRoot, rel, "SKILL.md");
|
|
76
|
-
if (!existsSync(skillMd))
|
|
77
|
-
broken.push(rel);
|
|
78
|
-
}
|
|
79
|
-
expect(broken, `${m.name} references missing skills:\n${broken.join("\n")}`).toEqual([]);
|
|
80
|
-
});
|
|
81
|
-
it("does not duplicate skill paths", () => {
|
|
82
|
-
const skills = m.skillsField;
|
|
83
|
-
if (typeof skills === "string")
|
|
84
|
-
return;
|
|
85
|
-
const seen = new Set();
|
|
86
|
-
const dupes = [];
|
|
87
|
-
for (const rel of skills) {
|
|
88
|
-
if (seen.has(rel))
|
|
89
|
-
dupes.push(rel);
|
|
90
|
-
seen.add(rel);
|
|
91
|
-
}
|
|
92
|
-
expect(dupes, `${m.name} duplicates: ${dupes.join(", ")}`).toEqual([]);
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
describe("skills directory <-> manifest coverage", () => {
|
|
97
|
-
it("every on-disk skill is listed in .claude-plugin/plugin.json", () => {
|
|
98
|
-
const onDisk = listSkillDirs(join(repoRoot, "skills"));
|
|
99
|
-
const claudeManifest = JSON.parse(readFileSync(join(repoRoot, ".claude-plugin", "plugin.json"), "utf-8"));
|
|
100
|
-
const listed = new Set(claudeManifest.skills);
|
|
101
|
-
const orphans = onDisk.filter((p) => !listed.has(p));
|
|
102
|
-
expect(orphans, `On-disk skills missing from .claude-plugin/plugin.json (add or move under skills/_shared|_tests):\n${orphans.join("\n")}`).toEqual([]);
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
describe("skill SKILL.md frontmatter", () => {
|
|
107
|
-
it("every shipped SKILL.md has name and Use-when-style description", () => {
|
|
108
|
-
const skillsRoot = join(repoRoot, "skills");
|
|
109
|
-
const skillDirs = listSkillDirs(skillsRoot);
|
|
110
|
-
const failures = [];
|
|
111
|
-
for (const rel of skillDirs) {
|
|
112
|
-
const file = join(repoRoot, rel, "SKILL.md");
|
|
113
|
-
const text = readFileSync(file, "utf-8");
|
|
114
|
-
const match = text.match(/^---\s*\n([\s\S]+?)\n---/);
|
|
115
|
-
if (!match) {
|
|
116
|
-
failures.push(`${rel}: no frontmatter`);
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
|
-
const fm = match[1];
|
|
120
|
-
const nameMatch = fm.match(/^name:\s*(.+)$/m);
|
|
121
|
-
const descMatch = fm.match(/^description:\s*(.+)$/m);
|
|
122
|
-
if (!nameMatch || !nameMatch[1].trim())
|
|
123
|
-
failures.push(`${rel}: missing name`);
|
|
124
|
-
if (!descMatch || !descMatch[1].trim())
|
|
125
|
-
failures.push(`${rel}: missing description`);
|
|
126
|
-
const desc = descMatch?.[1] ?? "";
|
|
127
|
-
// Auto-trigger discipline: description must contain "Use when"
|
|
128
|
-
if (desc && !/\bUse when\b/i.test(desc)) {
|
|
129
|
-
failures.push(`${rel}: description must contain "Use when ..." (got: "${desc.slice(0, 80)}...")`);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
expect(failures, `Skill frontmatter problems:\n${failures.join("\n")}`).toEqual([]);
|
|
133
|
-
});
|
|
134
|
-
});
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { EngineApiClient } from "./api-client.js";
|
|
2
|
-
export declare function extractOpError(result: unknown): string | null;
|
|
3
|
-
export declare function buildActionHint(message: string): string | null;
|
|
4
|
-
export declare function checkQuota(): Promise<{
|
|
5
|
-
allowed: boolean;
|
|
6
|
-
message?: string;
|
|
7
|
-
}>;
|
|
8
|
-
export declare function getClient(): Promise<EngineApiClient>;
|
|
9
|
-
export declare function resetClient(): void;
|
|
10
|
-
/**
|
|
11
|
-
* Connect to the engine, check quota, and run the given function.
|
|
12
|
-
* Throws on errors (quota, connection, op errors).
|
|
13
|
-
*/
|
|
14
|
-
export declare function withEngineClient<T>(fn: (client: EngineApiClient) => Promise<T>): Promise<T>;
|
package/dist/lib/with-engine.js
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import { EngineApiClient } from "./api-client.js";
|
|
2
|
-
import { getAuthToken } from "./auth.js";
|
|
3
|
-
const GATEWAY_URL = process.env.SUMMER_GATEWAY_URL || "https://www.summerengine.com";
|
|
4
|
-
export function extractOpError(result) {
|
|
5
|
-
if (!result || typeof result !== "object")
|
|
6
|
-
return null;
|
|
7
|
-
const op = result;
|
|
8
|
-
if (op.status === "error" && op.error)
|
|
9
|
-
return op.error;
|
|
10
|
-
const firstFailed = op.results?.find((r) => r.ok === false && r.error);
|
|
11
|
-
return firstFailed?.error ?? null;
|
|
12
|
-
}
|
|
13
|
-
export function buildActionHint(message) {
|
|
14
|
-
const normalized = message.toLowerCase();
|
|
15
|
-
if (normalized.includes("no scene open") || normalized.includes("no edited scene")) {
|
|
16
|
-
return "No scene is currently open. Run `summer project context` first, then `summer project open-main` (or `summer scene open` with a known .tscn path).";
|
|
17
|
-
}
|
|
18
|
-
if (normalized.includes("failed to open scene")) {
|
|
19
|
-
return "Scene path could not be opened. Run `summer project context` to get mainScene, then open that exact path. Avoid guessing scene filenames.";
|
|
20
|
-
}
|
|
21
|
-
if (normalized.includes("writefile cannot edit .tscn/.scn")) {
|
|
22
|
-
return "Cannot write .tscn/.scn directly. Use summer scene commands for scene-tree operations, and file-edit tools for .gd scripts.";
|
|
23
|
-
}
|
|
24
|
-
return null;
|
|
25
|
-
}
|
|
26
|
-
export async function checkQuota() {
|
|
27
|
-
const token = await getAuthToken();
|
|
28
|
-
if (!token) {
|
|
29
|
-
return {
|
|
30
|
-
allowed: false,
|
|
31
|
-
message: "Not signed in. Run: summer login\nOr open: https://www.summerengine.com/login\nEngine tools require a Summer Engine account (free to create).",
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
try {
|
|
35
|
-
const res = await fetch(`${GATEWAY_URL}/api/mcp/log-local-call`, {
|
|
36
|
-
method: "POST",
|
|
37
|
-
headers: {
|
|
38
|
-
Authorization: `Bearer ${token}`,
|
|
39
|
-
"Content-Type": "application/json",
|
|
40
|
-
},
|
|
41
|
-
body: JSON.stringify({}),
|
|
42
|
-
signal: AbortSignal.timeout(10000),
|
|
43
|
-
});
|
|
44
|
-
const data = (await res.json().catch(() => ({})));
|
|
45
|
-
if ([401, 402, 403, 429].includes(res.status)) {
|
|
46
|
-
return {
|
|
47
|
-
allowed: false,
|
|
48
|
-
message: data.message || "Quota check failed. Try again.",
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
// Soft-fail on service/gateway issues so local tools keep working.
|
|
52
|
-
if (!res.ok) {
|
|
53
|
-
return { allowed: true };
|
|
54
|
-
}
|
|
55
|
-
if (data.allowed === false) {
|
|
56
|
-
return {
|
|
57
|
-
allowed: false,
|
|
58
|
-
message: data.message || "Call limit reached. Upgrade at https://summerengine.com/pricing",
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
return { allowed: true };
|
|
62
|
-
}
|
|
63
|
-
catch {
|
|
64
|
-
// Network/transient errors should not brick local usage.
|
|
65
|
-
return { allowed: true };
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
let cachedClient = null;
|
|
69
|
-
export function getClient() {
|
|
70
|
-
if (cachedClient)
|
|
71
|
-
return Promise.resolve(cachedClient);
|
|
72
|
-
return EngineApiClient.connect().then((c) => {
|
|
73
|
-
cachedClient = c;
|
|
74
|
-
return c;
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
export function resetClient() {
|
|
78
|
-
cachedClient = null;
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Connect to the engine, check quota, and run the given function.
|
|
82
|
-
* Throws on errors (quota, connection, op errors).
|
|
83
|
-
*/
|
|
84
|
-
export async function withEngineClient(fn) {
|
|
85
|
-
const quota = await checkQuota();
|
|
86
|
-
if (!quota.allowed) {
|
|
87
|
-
throw new Error(quota.message || "Quota exceeded");
|
|
88
|
-
}
|
|
89
|
-
try {
|
|
90
|
-
const client = await getClient();
|
|
91
|
-
const result = await fn(client);
|
|
92
|
-
const opError = extractOpError(result);
|
|
93
|
-
if (opError) {
|
|
94
|
-
const hint = buildActionHint(opError);
|
|
95
|
-
throw new Error(hint ? `${opError}\n\nHint: ${hint}` : opError);
|
|
96
|
-
}
|
|
97
|
-
return result;
|
|
98
|
-
}
|
|
99
|
-
catch (err) {
|
|
100
|
-
// Reset client on connection errors so next call retries
|
|
101
|
-
if (err instanceof Error &&
|
|
102
|
-
(err.message.includes("not running") ||
|
|
103
|
-
err.message.includes("not responding") ||
|
|
104
|
-
err.message.includes("ECONNREFUSED"))) {
|
|
105
|
-
resetClient();
|
|
106
|
-
}
|
|
107
|
-
throw err;
|
|
108
|
-
}
|
|
109
|
-
}
|