@pebbly/aseprite-ai-artist 0.1.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/CHANGELOG.md +170 -0
- package/LICENSE +21 -0
- package/README.md +169 -0
- package/agents/animation-director.md +65 -0
- package/agents/palette-smith.md +57 -0
- package/agents/pixel-critic.md +69 -0
- package/agents/rig-builder.md +46 -0
- package/bin/aseprite-ai-artist +6 -0
- package/bin/aseprite-ai-artist.mjs +44 -0
- package/dist/bridge/bridge.d.ts +52 -0
- package/dist/bridge/bridge.js +230 -0
- package/dist/bridge/bridge.js.map +1 -0
- package/dist/bridge/client.d.ts +54 -0
- package/dist/bridge/client.js +214 -0
- package/dist/bridge/client.js.map +1 -0
- package/dist/cli.d.ts +10 -0
- package/dist/cli.js +272 -0
- package/dist/cli.js.map +1 -0
- package/dist/extension.d.ts +21 -0
- package/dist/extension.js +65 -0
- package/dist/extension.js.map +1 -0
- package/dist/install.d.ts +35 -0
- package/dist/install.js +185 -0
- package/dist/install.js.map +1 -0
- package/dist/lib/color.d.ts +66 -0
- package/dist/lib/color.js +170 -0
- package/dist/lib/color.js.map +1 -0
- package/dist/lib/protocol.d.ts +78 -0
- package/dist/lib/protocol.js +72 -0
- package/dist/lib/protocol.js.map +1 -0
- package/dist/lib/render.d.ts +62 -0
- package/dist/lib/render.js +134 -0
- package/dist/lib/render.js.map +1 -0
- package/dist/lib/skills.d.ts +43 -0
- package/dist/lib/skills.js +112 -0
- package/dist/lib/skills.js.map +1 -0
- package/dist/lib/version.d.ts +4 -0
- package/dist/lib/version.js +25 -0
- package/dist/lib/version.js.map +1 -0
- package/dist/server.d.ts +15 -0
- package/dist/server.js +144 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/assets.d.ts +3 -0
- package/dist/tools/assets.js +181 -0
- package/dist/tools/assets.js.map +1 -0
- package/dist/tools/craft.d.ts +12 -0
- package/dist/tools/craft.js +143 -0
- package/dist/tools/craft.js.map +1 -0
- package/dist/tools/draw.d.ts +3 -0
- package/dist/tools/draw.js +256 -0
- package/dist/tools/draw.js.map +1 -0
- package/dist/tools/escape.d.ts +12 -0
- package/dist/tools/escape.js +49 -0
- package/dist/tools/escape.js.map +1 -0
- package/dist/tools/kit.d.ts +29 -0
- package/dist/tools/kit.js +85 -0
- package/dist/tools/kit.js.map +1 -0
- package/dist/tools/look.d.ts +7 -0
- package/dist/tools/look.js +215 -0
- package/dist/tools/look.js.map +1 -0
- package/dist/tools/palette.d.ts +3 -0
- package/dist/tools/palette.js +232 -0
- package/dist/tools/palette.js.map +1 -0
- package/dist/tools/session.d.ts +3 -0
- package/dist/tools/session.js +231 -0
- package/dist/tools/session.js.map +1 -0
- package/dist/tools/structure.d.ts +3 -0
- package/dist/tools/structure.js +218 -0
- package/dist/tools/structure.js.map +1 -0
- package/examples/mcp-configs/claude-code.json +9 -0
- package/examples/mcp-configs/codex.toml +13 -0
- package/examples/mcp-configs/cursor.json +9 -0
- package/examples/mcp-configs/gemini-cli.json +9 -0
- package/examples/mcp-configs/vscode.json +10 -0
- package/extension/ai-artist.lua +3291 -0
- package/extension/package.json +14 -0
- package/hooks/hooks.json +25 -0
- package/hooks/look-nudge.mjs +46 -0
- package/hooks/session-status.mjs +47 -0
- package/knowledge/palettes.json +60 -0
- package/package.json +69 -0
- package/rules/00-core-principles.md +57 -0
- package/rules/01-palette-and-color.md +51 -0
- package/rules/02-shading-and-light.md +56 -0
- package/rules/03-silhouette-and-form.md +60 -0
- package/rules/04-outlines-and-edges.md +46 -0
- package/rules/05-animation.md +62 -0
- package/rules/06-layers-and-rigging.md +52 -0
- package/rules/07-review-checklist.md +51 -0
- package/rules/README.md +16 -0
- package/skills/README.md +29 -0
- package/skills/pixel-animate/SKILL.md +91 -0
- package/skills/pixel-brief/SKILL.md +61 -0
- package/skills/pixel-draw/SKILL.md +93 -0
- package/skills/pixel-export/SKILL.md +80 -0
- package/skills/pixel-fix/SKILL.md +91 -0
- package/skills/pixel-new/SKILL.md +70 -0
- package/skills/pixel-palette/SKILL.md +80 -0
- package/skills/pixel-review/SKILL.md +81 -0
- package/skills/pixel-rig/SKILL.md +76 -0
- package/skills/pixel-shade/SKILL.md +77 -0
- package/skills/pixel-tileset/SKILL.md +104 -0
- package/templates/AGENTS.section.md +33 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standalone WebSocket bridge.
|
|
3
|
+
*
|
|
4
|
+
* Owns two ports and nothing else — it is a dumb relay with no knowledge of
|
|
5
|
+
* Aseprite semantics:
|
|
6
|
+
* plugin port : the Aseprite Lua extension dials in (at most one, last wins)
|
|
7
|
+
* control port : MCP server processes dial in (N of them)
|
|
8
|
+
*
|
|
9
|
+
* It runs as its own process, detached from any MCP server, because agent hosts
|
|
10
|
+
* own the MCP server lifecycle and restart it freely. A bridge living inside the
|
|
11
|
+
* MCP process drops the Aseprite connection on every restart and duplicate
|
|
12
|
+
* processes fight over the listen port. See docs/adr/0002-standalone-bridge.md.
|
|
13
|
+
*
|
|
14
|
+
* Singleton by port ownership: whoever binds the control port first is the
|
|
15
|
+
* bridge; a loser of the bind race exits 0 without complaint.
|
|
16
|
+
*/
|
|
17
|
+
export interface BridgeOptions {
|
|
18
|
+
pluginPort?: number;
|
|
19
|
+
controlPort?: number;
|
|
20
|
+
version: string;
|
|
21
|
+
log?: (msg: string) => void;
|
|
22
|
+
}
|
|
23
|
+
export declare class Bridge {
|
|
24
|
+
private readonly pluginPort;
|
|
25
|
+
private readonly controlPort;
|
|
26
|
+
private readonly version;
|
|
27
|
+
private readonly log;
|
|
28
|
+
private pluginServer;
|
|
29
|
+
private controlServer;
|
|
30
|
+
private plugin;
|
|
31
|
+
private hello;
|
|
32
|
+
private since;
|
|
33
|
+
private clients;
|
|
34
|
+
private nextClientId;
|
|
35
|
+
constructor(opts: BridgeOptions);
|
|
36
|
+
/**
|
|
37
|
+
* Bind both ports. Resolves false when the control port is already taken,
|
|
38
|
+
* which means another bridge already owns this machine's Aseprite link.
|
|
39
|
+
*/
|
|
40
|
+
start(): Promise<boolean>;
|
|
41
|
+
stop(): Promise<void>;
|
|
42
|
+
private onPluginConnect;
|
|
43
|
+
private onPluginMessage;
|
|
44
|
+
private onControlConnect;
|
|
45
|
+
private onControlMessage;
|
|
46
|
+
private stateFrame;
|
|
47
|
+
private broadcastState;
|
|
48
|
+
}
|
|
49
|
+
export declare class BridgeBindError extends Error {
|
|
50
|
+
readonly port: number;
|
|
51
|
+
constructor(port: number, cause: NodeJS.ErrnoException);
|
|
52
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standalone WebSocket bridge.
|
|
3
|
+
*
|
|
4
|
+
* Owns two ports and nothing else — it is a dumb relay with no knowledge of
|
|
5
|
+
* Aseprite semantics:
|
|
6
|
+
* plugin port : the Aseprite Lua extension dials in (at most one, last wins)
|
|
7
|
+
* control port : MCP server processes dial in (N of them)
|
|
8
|
+
*
|
|
9
|
+
* It runs as its own process, detached from any MCP server, because agent hosts
|
|
10
|
+
* own the MCP server lifecycle and restart it freely. A bridge living inside the
|
|
11
|
+
* MCP process drops the Aseprite connection on every restart and duplicate
|
|
12
|
+
* processes fight over the listen port. See docs/adr/0002-standalone-bridge.md.
|
|
13
|
+
*
|
|
14
|
+
* Singleton by port ownership: whoever binds the control port first is the
|
|
15
|
+
* bridge; a loser of the bind race exits 0 without complaint.
|
|
16
|
+
*/
|
|
17
|
+
import { WebSocketServer, WebSocket } from "ws";
|
|
18
|
+
import { DEFAULT_CONTROL_PORT, DEFAULT_PLUGIN_PORT, isHello, makeNamespacedId, splitNamespacedId, } from "../lib/protocol.js";
|
|
19
|
+
/**
|
|
20
|
+
* Frames larger than this are refused. A 64x64 pixel read is a few hundred KB;
|
|
21
|
+
* ws defaults to 100 MiB, which on an unauthenticated localhost socket is a
|
|
22
|
+
* one-line memory-exhaustion vector.
|
|
23
|
+
*/
|
|
24
|
+
const MAX_FRAME_BYTES = 16 * 1024 * 1024;
|
|
25
|
+
/** Control clients are one per agent window; a hundred means something is wrong. */
|
|
26
|
+
const MAX_CONTROL_CLIENTS = 64;
|
|
27
|
+
export class Bridge {
|
|
28
|
+
pluginPort;
|
|
29
|
+
controlPort;
|
|
30
|
+
version;
|
|
31
|
+
log;
|
|
32
|
+
pluginServer = null;
|
|
33
|
+
controlServer = null;
|
|
34
|
+
plugin = null;
|
|
35
|
+
hello = null;
|
|
36
|
+
since = Date.now();
|
|
37
|
+
clients = new Map();
|
|
38
|
+
nextClientId = 1;
|
|
39
|
+
constructor(opts) {
|
|
40
|
+
this.pluginPort = opts.pluginPort ?? DEFAULT_PLUGIN_PORT;
|
|
41
|
+
this.controlPort = opts.controlPort ?? DEFAULT_CONTROL_PORT;
|
|
42
|
+
this.version = opts.version;
|
|
43
|
+
this.log = opts.log ?? (() => { });
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Bind both ports. Resolves false when the control port is already taken,
|
|
47
|
+
* which means another bridge already owns this machine's Aseprite link.
|
|
48
|
+
*/
|
|
49
|
+
async start() {
|
|
50
|
+
const control = await listen(this.controlPort);
|
|
51
|
+
if (!control)
|
|
52
|
+
return false;
|
|
53
|
+
this.controlServer = control;
|
|
54
|
+
const plugin = await listen(this.pluginPort);
|
|
55
|
+
if (!plugin) {
|
|
56
|
+
// Ports must move together; a split ownership would strand the extension.
|
|
57
|
+
control.close();
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
this.pluginServer = plugin;
|
|
61
|
+
this.controlServer.on("connection", (socket) => this.onControlConnect(socket));
|
|
62
|
+
this.pluginServer.on("connection", (socket) => this.onPluginConnect(socket));
|
|
63
|
+
this.log(`bridge up — plugin :${this.pluginPort}, control :${this.controlPort}, v${this.version}`);
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
async stop() {
|
|
67
|
+
for (const c of this.clients.values())
|
|
68
|
+
c.socket.close();
|
|
69
|
+
this.plugin?.close();
|
|
70
|
+
this.pluginServer?.close();
|
|
71
|
+
this.controlServer?.close();
|
|
72
|
+
}
|
|
73
|
+
// ── plugin side ────────────────────────────────────────────────────────────
|
|
74
|
+
onPluginConnect(socket) {
|
|
75
|
+
// Last connection wins: an Aseprite restart must not be locked out by the
|
|
76
|
+
// corpse of a half-open previous socket.
|
|
77
|
+
if (this.plugin && this.plugin.readyState === WebSocket.OPEN) {
|
|
78
|
+
this.log("plugin reconnected — dropping previous socket");
|
|
79
|
+
this.plugin.close();
|
|
80
|
+
}
|
|
81
|
+
this.plugin = socket;
|
|
82
|
+
this.since = Date.now();
|
|
83
|
+
socket.on("message", (raw) => this.onPluginMessage(raw.toString()));
|
|
84
|
+
socket.on("close", () => {
|
|
85
|
+
if (this.plugin === socket) {
|
|
86
|
+
this.plugin = null;
|
|
87
|
+
this.hello = null;
|
|
88
|
+
this.since = Date.now();
|
|
89
|
+
this.broadcastState();
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
socket.on("error", () => socket.close());
|
|
93
|
+
this.broadcastState();
|
|
94
|
+
}
|
|
95
|
+
onPluginMessage(raw) {
|
|
96
|
+
let frame;
|
|
97
|
+
try {
|
|
98
|
+
frame = JSON.parse(raw);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
this.log(`dropped unparseable plugin frame (${raw.length} bytes)`);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (isHello(frame)) {
|
|
105
|
+
this.hello = frame;
|
|
106
|
+
this.since = Date.now();
|
|
107
|
+
this.broadcastState();
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const id = frame.id;
|
|
111
|
+
if (typeof id !== "string")
|
|
112
|
+
return;
|
|
113
|
+
const split = splitNamespacedId(id);
|
|
114
|
+
if (!split)
|
|
115
|
+
return;
|
|
116
|
+
const client = this.clients.get(split.clientId);
|
|
117
|
+
if (!client || client.socket.readyState !== WebSocket.OPEN)
|
|
118
|
+
return;
|
|
119
|
+
send(client.socket, { ...frame, id: split.originalId });
|
|
120
|
+
}
|
|
121
|
+
// ── control side ───────────────────────────────────────────────────────────
|
|
122
|
+
onControlConnect(socket) {
|
|
123
|
+
if (this.clients.size >= MAX_CONTROL_CLIENTS) {
|
|
124
|
+
send(socket, {
|
|
125
|
+
id: "connect",
|
|
126
|
+
ok: false,
|
|
127
|
+
error: {
|
|
128
|
+
code: "too_large",
|
|
129
|
+
message: `Refusing a ${MAX_CONTROL_CLIENTS + 1}th control client; something is opening connections in a loop.`,
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
socket.close();
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const id = this.nextClientId++;
|
|
136
|
+
this.clients.set(id, { id, socket });
|
|
137
|
+
socket.on("message", (raw) => this.onControlMessage(id, raw.toString()));
|
|
138
|
+
socket.on("close", () => this.clients.delete(id));
|
|
139
|
+
socket.on("error", () => socket.close());
|
|
140
|
+
// A client must know liveness before it sends anything.
|
|
141
|
+
send(socket, this.stateFrame());
|
|
142
|
+
}
|
|
143
|
+
onControlMessage(clientId, raw) {
|
|
144
|
+
let frame;
|
|
145
|
+
try {
|
|
146
|
+
frame = JSON.parse(raw);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (typeof frame.id !== "string")
|
|
152
|
+
return;
|
|
153
|
+
const client = this.clients.get(clientId);
|
|
154
|
+
if (!client)
|
|
155
|
+
return;
|
|
156
|
+
if (!this.plugin || this.plugin.readyState !== WebSocket.OPEN) {
|
|
157
|
+
// Answer on the plugin's behalf rather than letting the call hang. A
|
|
158
|
+
// timeout reads as "slow" to an agent; this reads as "stop and tell the user".
|
|
159
|
+
send(client.socket, {
|
|
160
|
+
id: frame.id,
|
|
161
|
+
ok: false,
|
|
162
|
+
error: {
|
|
163
|
+
code: "not_connected",
|
|
164
|
+
message: "Aseprite is not connected to the bridge. The extension is not running or Aseprite is closed.",
|
|
165
|
+
details: { doNotFallBackToDisk: true },
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
send(this.plugin, { ...frame, id: makeNamespacedId(clientId, frame.id) });
|
|
171
|
+
}
|
|
172
|
+
stateFrame() {
|
|
173
|
+
return {
|
|
174
|
+
type: "bridge_state",
|
|
175
|
+
pluginConnected: this.plugin?.readyState === WebSocket.OPEN,
|
|
176
|
+
hello: this.hello,
|
|
177
|
+
bridgeVersion: this.version,
|
|
178
|
+
since: this.since,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
broadcastState() {
|
|
182
|
+
const frame = this.stateFrame();
|
|
183
|
+
for (const c of this.clients.values()) {
|
|
184
|
+
if (c.socket.readyState === WebSocket.OPEN)
|
|
185
|
+
send(c.socket, frame);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function send(socket, payload) {
|
|
190
|
+
try {
|
|
191
|
+
socket.send(JSON.stringify(payload));
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
/* a dead socket is handled by its own close handler */
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
export class BridgeBindError extends Error {
|
|
198
|
+
port;
|
|
199
|
+
constructor(port, cause) {
|
|
200
|
+
super(`Could not bind 127.0.0.1:${port}: ${cause.code ?? cause.message}`);
|
|
201
|
+
this.name = "BridgeBindError";
|
|
202
|
+
this.port = port;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Resolves the server on success, or null when the port is already taken —
|
|
207
|
+
* which is the normal "a bridge already runs" outcome. Any OTHER bind failure
|
|
208
|
+
* rejects: reporting an EACCES as "another bridge owns this port" sends the
|
|
209
|
+
* user looking for a process that does not exist.
|
|
210
|
+
*/
|
|
211
|
+
function listen(port) {
|
|
212
|
+
return new Promise((resolve, reject) => {
|
|
213
|
+
const server = new WebSocketServer({ host: "127.0.0.1", port, maxPayload: MAX_FRAME_BYTES });
|
|
214
|
+
const onError = (err) => {
|
|
215
|
+
server.removeListener("listening", onListening);
|
|
216
|
+
if (err.code === "EADDRINUSE")
|
|
217
|
+
resolve(null);
|
|
218
|
+
else
|
|
219
|
+
reject(new BridgeBindError(port, err));
|
|
220
|
+
};
|
|
221
|
+
const onListening = () => {
|
|
222
|
+
server.removeListener("error", onError);
|
|
223
|
+
server.on("error", () => { });
|
|
224
|
+
resolve(server);
|
|
225
|
+
};
|
|
226
|
+
server.once("error", onError);
|
|
227
|
+
server.once("listening", onListening);
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
//# sourceMappingURL=bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge.js","sourceRoot":"","sources":["../../src/bridge/bridge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Control client: the MCP server's half of the link to Aseprite.
|
|
3
|
+
*
|
|
4
|
+
* Responsibilities beyond "send JSON":
|
|
5
|
+
* - spawn the bridge if nothing owns the control port yet (zero-setup goal);
|
|
6
|
+
* - reconnect with backoff, because the bridge may be restarted under us;
|
|
7
|
+
* - track `pluginConnected` from bridge_state so `preflight` can answer
|
|
8
|
+
* truthfully without a round-trip to Aseprite.
|
|
9
|
+
*/
|
|
10
|
+
import { HelloFrame } from "../lib/protocol.js";
|
|
11
|
+
export interface LiveClientOptions {
|
|
12
|
+
controlPort?: number;
|
|
13
|
+
pluginPort?: number;
|
|
14
|
+
/** Disable auto-spawning the bridge (useful in tests and CI). */
|
|
15
|
+
autoSpawnBridge?: boolean;
|
|
16
|
+
timeoutMs?: number;
|
|
17
|
+
log?: (msg: string) => void;
|
|
18
|
+
}
|
|
19
|
+
export declare class LiveClient {
|
|
20
|
+
private readonly controlPort;
|
|
21
|
+
private readonly pluginPort;
|
|
22
|
+
private readonly autoSpawn;
|
|
23
|
+
private readonly timeoutMs;
|
|
24
|
+
private readonly log;
|
|
25
|
+
private socket;
|
|
26
|
+
private pending;
|
|
27
|
+
private seq;
|
|
28
|
+
private reconnectDelay;
|
|
29
|
+
private reconnectTimer;
|
|
30
|
+
private closed;
|
|
31
|
+
private lastSpawnAt;
|
|
32
|
+
private state;
|
|
33
|
+
constructor(opts?: LiveClientOptions);
|
|
34
|
+
get bridgeConnected(): boolean;
|
|
35
|
+
get pluginConnected(): boolean;
|
|
36
|
+
get hello(): HelloFrame | null;
|
|
37
|
+
get features(): string[];
|
|
38
|
+
hasFeature(name: string): boolean;
|
|
39
|
+
/** Wait until the bridge link is up (not necessarily Aseprite). */
|
|
40
|
+
waitForBridge(ms?: number): Promise<boolean>;
|
|
41
|
+
/** Wait until Aseprite itself is attached. */
|
|
42
|
+
waitForPlugin(ms?: number): Promise<boolean>;
|
|
43
|
+
call<T = unknown>(cmd: string, args?: Record<string, unknown>): Promise<T>;
|
|
44
|
+
close(): void;
|
|
45
|
+
private rejectPending;
|
|
46
|
+
private connect;
|
|
47
|
+
private scheduleReconnect;
|
|
48
|
+
private onMessage;
|
|
49
|
+
/**
|
|
50
|
+
* Start the bridge as a detached child so it survives this MCP process.
|
|
51
|
+
* Losing the bind race is normal and silent — it means a bridge already runs.
|
|
52
|
+
*/
|
|
53
|
+
private spawnBridge;
|
|
54
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Control client: the MCP server's half of the link to Aseprite.
|
|
3
|
+
*
|
|
4
|
+
* Responsibilities beyond "send JSON":
|
|
5
|
+
* - spawn the bridge if nothing owns the control port yet (zero-setup goal);
|
|
6
|
+
* - reconnect with backoff, because the bridge may be restarted under us;
|
|
7
|
+
* - track `pluginConnected` from bridge_state so `preflight` can answer
|
|
8
|
+
* truthfully without a round-trip to Aseprite.
|
|
9
|
+
*/
|
|
10
|
+
import { spawn } from "node:child_process";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import { WebSocket } from "ws";
|
|
14
|
+
import { DEFAULT_CONTROL_PORT, LiveError, isBridgeState, isResult, notConnected, } from "../lib/protocol.js";
|
|
15
|
+
const DEFAULT_TIMEOUT_MS = 20_000;
|
|
16
|
+
const RECONNECT_BASE_MS = 250;
|
|
17
|
+
const RECONNECT_MAX_MS = 5_000;
|
|
18
|
+
/** Floor between bridge spawn attempts, so a failing spawn cannot become a fork bomb. */
|
|
19
|
+
const SPAWN_COOLDOWN_MS = 10_000;
|
|
20
|
+
export class LiveClient {
|
|
21
|
+
controlPort;
|
|
22
|
+
pluginPort;
|
|
23
|
+
autoSpawn;
|
|
24
|
+
timeoutMs;
|
|
25
|
+
log;
|
|
26
|
+
socket = null;
|
|
27
|
+
pending = new Map();
|
|
28
|
+
seq = 0;
|
|
29
|
+
reconnectDelay = RECONNECT_BASE_MS;
|
|
30
|
+
reconnectTimer = null;
|
|
31
|
+
closed = false;
|
|
32
|
+
lastSpawnAt = 0;
|
|
33
|
+
state = null;
|
|
34
|
+
constructor(opts = {}) {
|
|
35
|
+
this.controlPort = opts.controlPort ?? DEFAULT_CONTROL_PORT;
|
|
36
|
+
this.pluginPort = opts.pluginPort ?? this.controlPort - 1;
|
|
37
|
+
this.autoSpawn = opts.autoSpawnBridge ?? true;
|
|
38
|
+
this.timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
39
|
+
this.log = opts.log ?? (() => { });
|
|
40
|
+
this.connect();
|
|
41
|
+
}
|
|
42
|
+
get bridgeConnected() {
|
|
43
|
+
return this.socket?.readyState === WebSocket.OPEN;
|
|
44
|
+
}
|
|
45
|
+
get pluginConnected() {
|
|
46
|
+
return this.bridgeConnected && this.state?.pluginConnected === true;
|
|
47
|
+
}
|
|
48
|
+
get hello() {
|
|
49
|
+
return this.state?.hello ?? null;
|
|
50
|
+
}
|
|
51
|
+
get features() {
|
|
52
|
+
return this.state?.hello?.features ?? [];
|
|
53
|
+
}
|
|
54
|
+
hasFeature(name) {
|
|
55
|
+
return this.features.includes(name);
|
|
56
|
+
}
|
|
57
|
+
/** Wait until the bridge link is up (not necessarily Aseprite). */
|
|
58
|
+
async waitForBridge(ms = 3_000) {
|
|
59
|
+
const deadline = Date.now() + ms;
|
|
60
|
+
while (Date.now() < deadline) {
|
|
61
|
+
if (this.bridgeConnected && this.state)
|
|
62
|
+
return true;
|
|
63
|
+
await sleep(50);
|
|
64
|
+
}
|
|
65
|
+
return this.bridgeConnected;
|
|
66
|
+
}
|
|
67
|
+
/** Wait until Aseprite itself is attached. */
|
|
68
|
+
async waitForPlugin(ms = 3_000) {
|
|
69
|
+
const deadline = Date.now() + ms;
|
|
70
|
+
while (Date.now() < deadline) {
|
|
71
|
+
if (this.pluginConnected)
|
|
72
|
+
return true;
|
|
73
|
+
await sleep(50);
|
|
74
|
+
}
|
|
75
|
+
return this.pluginConnected;
|
|
76
|
+
}
|
|
77
|
+
async call(cmd, args = {}) {
|
|
78
|
+
if (!this.bridgeConnected) {
|
|
79
|
+
await this.waitForBridge(2_000);
|
|
80
|
+
if (!this.bridgeConnected)
|
|
81
|
+
throw notConnected("Bridge process is not reachable.");
|
|
82
|
+
}
|
|
83
|
+
if (!this.pluginConnected)
|
|
84
|
+
throw notConnected();
|
|
85
|
+
const id = `r${++this.seq}`;
|
|
86
|
+
const frame = { id, cmd, args };
|
|
87
|
+
return new Promise((resolve, reject) => {
|
|
88
|
+
const timer = setTimeout(() => {
|
|
89
|
+
this.pending.delete(id);
|
|
90
|
+
reject(new LiveError("timeout", `Aseprite did not answer '${cmd}' within ${this.timeoutMs}ms.`, {
|
|
91
|
+
cmd,
|
|
92
|
+
doNotFallBackToDisk: true,
|
|
93
|
+
}));
|
|
94
|
+
}, this.timeoutMs);
|
|
95
|
+
this.pending.set(id, {
|
|
96
|
+
resolve: resolve,
|
|
97
|
+
reject,
|
|
98
|
+
timer,
|
|
99
|
+
});
|
|
100
|
+
this.socket.send(JSON.stringify(frame));
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
close() {
|
|
104
|
+
this.closed = true;
|
|
105
|
+
if (this.reconnectTimer)
|
|
106
|
+
clearTimeout(this.reconnectTimer);
|
|
107
|
+
this.rejectPending(new LiveError("not_connected", "Client closed."));
|
|
108
|
+
this.socket?.close();
|
|
109
|
+
}
|
|
110
|
+
rejectPending(err) {
|
|
111
|
+
for (const p of this.pending.values()) {
|
|
112
|
+
clearTimeout(p.timer);
|
|
113
|
+
p.reject(err);
|
|
114
|
+
}
|
|
115
|
+
this.pending.clear();
|
|
116
|
+
}
|
|
117
|
+
// ── internals ──────────────────────────────────────────────────────────────
|
|
118
|
+
connect() {
|
|
119
|
+
if (this.closed)
|
|
120
|
+
return;
|
|
121
|
+
const socket = new WebSocket(`ws://127.0.0.1:${this.controlPort}`);
|
|
122
|
+
this.socket = socket;
|
|
123
|
+
socket.on("open", () => {
|
|
124
|
+
this.reconnectDelay = RECONNECT_BASE_MS;
|
|
125
|
+
this.log(`control link up on :${this.controlPort}`);
|
|
126
|
+
});
|
|
127
|
+
socket.on("message", (raw) => this.onMessage(raw.toString()));
|
|
128
|
+
socket.on("error", () => {
|
|
129
|
+
// Suppressed: 'close' always follows and owns the retry path.
|
|
130
|
+
});
|
|
131
|
+
socket.on("close", () => {
|
|
132
|
+
this.state = null;
|
|
133
|
+
// Fail in-flight calls now. Letting each wait out its own 20s timeout
|
|
134
|
+
// turns a known disconnect into what an agent reads as "Aseprite is slow"
|
|
135
|
+
// — and slow is the reading that makes it try something else.
|
|
136
|
+
this.rejectPending(notConnected("The bridge connection dropped while this call was in flight."));
|
|
137
|
+
if (this.closed)
|
|
138
|
+
return;
|
|
139
|
+
if (this.autoSpawn) {
|
|
140
|
+
// Retry the spawn on a later reconnect too: a bridge that died after we
|
|
141
|
+
// started it would otherwise leave this client looping forever with no
|
|
142
|
+
// recovery short of the user restarting things by hand.
|
|
143
|
+
const now = Date.now();
|
|
144
|
+
if (now - this.lastSpawnAt > SPAWN_COOLDOWN_MS) {
|
|
145
|
+
this.lastSpawnAt = now;
|
|
146
|
+
this.spawnBridge();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
this.scheduleReconnect();
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
scheduleReconnect() {
|
|
153
|
+
if (this.reconnectTimer)
|
|
154
|
+
return;
|
|
155
|
+
const delay = this.reconnectDelay;
|
|
156
|
+
this.reconnectDelay = Math.min(this.reconnectDelay * 2, RECONNECT_MAX_MS);
|
|
157
|
+
this.reconnectTimer = setTimeout(() => {
|
|
158
|
+
this.reconnectTimer = null;
|
|
159
|
+
this.connect();
|
|
160
|
+
}, delay);
|
|
161
|
+
// A pending retry must never be the reason a process stays alive: the
|
|
162
|
+
// stdio transport owns the server's lifetime, and in tests an un-unref'd
|
|
163
|
+
// timer turns a failed assertion into a hang.
|
|
164
|
+
this.reconnectTimer.unref?.();
|
|
165
|
+
}
|
|
166
|
+
onMessage(raw) {
|
|
167
|
+
let frame;
|
|
168
|
+
try {
|
|
169
|
+
frame = JSON.parse(raw);
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (isBridgeState(frame)) {
|
|
175
|
+
this.state = frame;
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (!isResult(frame))
|
|
179
|
+
return;
|
|
180
|
+
const result = frame;
|
|
181
|
+
const pending = this.pending.get(result.id);
|
|
182
|
+
if (!pending)
|
|
183
|
+
return;
|
|
184
|
+
this.pending.delete(result.id);
|
|
185
|
+
clearTimeout(pending.timer);
|
|
186
|
+
if (result.ok) {
|
|
187
|
+
pending.resolve(result.data ?? {});
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
const err = result.error;
|
|
191
|
+
pending.reject(new LiveError(err?.code ?? "aseprite_error", err?.message ?? "Aseprite reported an unspecified failure.", err?.details ?? {}));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Start the bridge as a detached child so it survives this MCP process.
|
|
196
|
+
* Losing the bind race is normal and silent — it means a bridge already runs.
|
|
197
|
+
*/
|
|
198
|
+
spawnBridge() {
|
|
199
|
+
try {
|
|
200
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
201
|
+
const cli = path.resolve(here, "..", "cli.js");
|
|
202
|
+
const child = spawn(process.execPath, [cli, "bridge", "--plugin-port", String(this.pluginPort), "--control-port", String(this.controlPort)], { detached: true, stdio: "ignore" });
|
|
203
|
+
child.unref();
|
|
204
|
+
this.log("spawned bridge process");
|
|
205
|
+
}
|
|
206
|
+
catch (err) {
|
|
207
|
+
this.log(`bridge spawn failed: ${err.message}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function sleep(ms) {
|
|
212
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
213
|
+
}
|
|
214
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/bridge/client.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* One binary, several jobs:
|
|
4
|
+
* serve (default) speak MCP over stdio
|
|
5
|
+
* bridge run the singleton WebSocket relay
|
|
6
|
+
* install <client…> wire this server into an agent's config
|
|
7
|
+
* install-extension copy the Lua extension into Aseprite
|
|
8
|
+
* doctor tell the user exactly what is broken
|
|
9
|
+
*/
|
|
10
|
+
export {};
|