pixifact-cli 0.4.0 → 0.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.
@@ -0,0 +1,490 @@
1
+ import { createHash } from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { parseSceneTemplate } from 'pixifact/compiler';
6
+
7
+ export const editorSessionProtocolVersion = 2;
8
+
9
+ export type EditorContextSyncState = 'synced' | 'saving' | 'conflict' | 'error';
10
+
11
+ export type EditorNodeContext =
12
+ | {
13
+ kind: 'pixi';
14
+ type: string;
15
+ id?: string;
16
+ props: Record<string, unknown>;
17
+ childCount: number;
18
+ }
19
+ | {
20
+ kind: 'sceneInstance';
21
+ type: string;
22
+ id?: string;
23
+ scene: string;
24
+ props: Record<string, unknown>;
25
+ events: Record<string, string>;
26
+ slots: Record<string, number>;
27
+ }
28
+ | {
29
+ kind: 'slotOutlet';
30
+ name: string;
31
+ };
32
+
33
+ export type EditorSelectionContext =
34
+ | { kind: 'scene' }
35
+ | {
36
+ kind: 'node';
37
+ locator: string;
38
+ node: EditorNodeContext;
39
+ };
40
+
41
+ export interface EditorBrowserContext {
42
+ scene: {
43
+ path: string;
44
+ revision: string;
45
+ syncState: EditorContextSyncState;
46
+ };
47
+ selection: EditorSelectionContext;
48
+ }
49
+
50
+ export interface EditorSessionResumeState {
51
+ scenePath: string;
52
+ selectedLocator?: string;
53
+ }
54
+
55
+ export interface EditorContext extends EditorBrowserContext {
56
+ protocolVersion: typeof editorSessionProtocolVersion;
57
+ projectRoot: string;
58
+ editor: {
59
+ connected: true;
60
+ updatedAt: string;
61
+ };
62
+ }
63
+
64
+ export interface EditorSessionDescriptor {
65
+ protocolVersion: typeof editorSessionProtocolVersion;
66
+ projectRoot: string;
67
+ pid: number;
68
+ origin: string;
69
+ token: string;
70
+ }
71
+
72
+ interface EditorBrowserSocket {
73
+ send(data: string): void;
74
+ }
75
+
76
+ interface EditorHostSessionOptions {
77
+ projectRoot: string;
78
+ token: string;
79
+ now?: () => Date;
80
+ }
81
+
82
+ interface EditorSessionLookupOptions {
83
+ projectRoot: string;
84
+ sessionsRoot?: string;
85
+ fetch?: typeof fetch;
86
+ }
87
+
88
+ interface ClaimEditorSessionOptions extends EditorSessionLookupOptions {
89
+ descriptor: EditorSessionDescriptor;
90
+ }
91
+
92
+ function jsonResponse(value: unknown, status = 200) {
93
+ return Response.json(value, { status });
94
+ }
95
+
96
+ function sceneVersion(source: string) {
97
+ return `sha256:${createHash('sha256').update(source).digest('hex')}`;
98
+ }
99
+
100
+ function authorizationMatches(request: Request, token: string) {
101
+ return request.headers.get('authorization') === `Bearer ${token}`;
102
+ }
103
+
104
+ function resolveScenePath(projectRoot: string, scenePath: string) {
105
+ if (!scenePath || path.isAbsolute(scenePath) || path.extname(scenePath).toLowerCase() !== '.scene') {
106
+ throw new Error('Editor context Scene path must be a project-relative .scene file.');
107
+ }
108
+ const absolutePath = fs.realpathSync(path.resolve(projectRoot, scenePath));
109
+ if (absolutePath !== projectRoot && !absolutePath.startsWith(`${projectRoot}${path.sep}`)) {
110
+ throw new Error('Editor context Scene path must stay inside the project root.');
111
+ }
112
+ return absolutePath;
113
+ }
114
+
115
+ function sourcePosition(message: string, source: string) {
116
+ const match = message.match(/\boffset (\d+)\b/);
117
+ if (!match) {
118
+ return {};
119
+ }
120
+ const offset = Number(match[1]);
121
+ const lines = source.slice(0, offset).split('\n');
122
+ return {
123
+ line: lines.length,
124
+ column: lines.at(-1)!.length + 1,
125
+ };
126
+ }
127
+
128
+ function isRecord(value: unknown): value is Record<string, unknown> {
129
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
130
+ }
131
+
132
+ function isEditorBrowserContext(value: unknown): value is EditorBrowserContext {
133
+ if (!isRecord(value) || !isRecord(value.scene) || !isRecord(value.selection)) {
134
+ return false;
135
+ }
136
+ const syncState = value.scene.syncState;
137
+ if (
138
+ typeof value.scene.path !== 'string'
139
+ || typeof value.scene.revision !== 'string'
140
+ || !['synced', 'saving', 'conflict', 'error'].includes(String(syncState))
141
+ ) {
142
+ return false;
143
+ }
144
+ if (value.selection.kind === 'scene') {
145
+ return true;
146
+ }
147
+ return value.selection.kind === 'node'
148
+ && typeof value.selection.locator === 'string'
149
+ && isRecord(value.selection.node);
150
+ }
151
+
152
+ export function createEditorHostSession(options: EditorHostSessionOptions) {
153
+ const projectRoot = fs.realpathSync(options.projectRoot);
154
+ const now = options.now ?? (() => new Date());
155
+ const browsers = new Set<EditorBrowserSocket>();
156
+ let activeBrowser: EditorBrowserSocket | undefined;
157
+ let context: EditorContext | undefined;
158
+ let resume: EditorSessionResumeState | undefined;
159
+
160
+ function sendState(
161
+ socket: EditorBrowserSocket,
162
+ type: 'editorSessionActive' | 'editorSessionStandby',
163
+ details: { error?: string; reason?: 'takenOver' } = {},
164
+ ) {
165
+ socket.send(JSON.stringify({
166
+ type,
167
+ protocolVersion: editorSessionProtocolVersion,
168
+ ...details,
169
+ ...(resume ? { resume } : {}),
170
+ }));
171
+ }
172
+
173
+ function notifyStandbyBrowsers() {
174
+ for (const socket of browsers) {
175
+ if (socket !== activeBrowser) sendState(socket, 'editorSessionStandby');
176
+ }
177
+ }
178
+
179
+ function open(socket: EditorBrowserSocket) {
180
+ browsers.add(socket);
181
+ if (activeBrowser) {
182
+ sendState(socket, 'editorSessionStandby');
183
+ return false;
184
+ }
185
+ activeBrowser = socket;
186
+ sendState(socket, 'editorSessionActive');
187
+ return true;
188
+ }
189
+
190
+ function close(socket: EditorBrowserSocket) {
191
+ browsers.delete(socket);
192
+ if (socket === activeBrowser) {
193
+ activeBrowser = undefined;
194
+ context = undefined;
195
+ }
196
+ }
197
+
198
+ function message(socket: EditorBrowserSocket, data: string | ArrayBuffer | Uint8Array) {
199
+ const parsed = JSON.parse(String(data)) as Record<string, unknown>;
200
+ if (parsed.protocolVersion !== editorSessionProtocolVersion) {
201
+ throw new Error('Editor session protocol version does not match. Restart Pixifact Editor.');
202
+ }
203
+ if (parsed.type === 'editorSessionTakeoverRequested') {
204
+ if (!browsers.has(socket) || socket === activeBrowser) return;
205
+ if (activeBrowser && context?.scene.syncState !== 'synced') {
206
+ sendState(socket, 'editorSessionStandby', {
207
+ error: '当前 Editor 尚未同步,暂时无法接管。',
208
+ });
209
+ return;
210
+ }
211
+ const previous = activeBrowser;
212
+ activeBrowser = socket;
213
+ context = undefined;
214
+ for (const browser of browsers) {
215
+ if (browser === socket) {
216
+ sendState(browser, 'editorSessionActive');
217
+ } else {
218
+ sendState(browser, 'editorSessionStandby', {
219
+ ...(browser === previous ? { reason: 'takenOver' as const } : {}),
220
+ });
221
+ }
222
+ }
223
+ return;
224
+ }
225
+ if (socket !== activeBrowser || parsed.type !== 'editorContextChanged') return;
226
+ if (!isEditorBrowserContext(parsed.context)) {
227
+ throw new Error('Editor context message is invalid.');
228
+ }
229
+ context = {
230
+ protocolVersion: editorSessionProtocolVersion,
231
+ projectRoot,
232
+ editor: {
233
+ connected: true,
234
+ updatedAt: now().toISOString(),
235
+ },
236
+ ...structuredClone(parsed.context),
237
+ };
238
+ resume = {
239
+ scenePath: context.scene.path,
240
+ ...(context.selection.kind === 'node'
241
+ ? { selectedLocator: context.selection.locator }
242
+ : {}),
243
+ };
244
+ notifyStandbyBrowsers();
245
+ }
246
+
247
+ function contextResponse() {
248
+ if (!activeBrowser) {
249
+ return jsonResponse({
250
+ ok: false,
251
+ error: 'No active Editor browser session.',
252
+ }, 409);
253
+ }
254
+ if (!context) {
255
+ return jsonResponse({
256
+ ok: false,
257
+ error: 'Editor context is not ready.',
258
+ }, 409);
259
+ }
260
+ if (context.scene.syncState !== 'synced') {
261
+ return jsonResponse({
262
+ ok: false,
263
+ error: 'Editor Scene is not synchronized.',
264
+ syncState: context.scene.syncState,
265
+ }, 409);
266
+ }
267
+
268
+ const source = fs.readFileSync(resolveScenePath(projectRoot, context.scene.path), 'utf8');
269
+ const revision = sceneVersion(source);
270
+ if (revision !== context.scene.revision) {
271
+ try {
272
+ parseSceneTemplate(source);
273
+ } catch (error) {
274
+ const message = error instanceof Error ? error.message : String(error);
275
+ return jsonResponse({
276
+ ok: false,
277
+ error: 'Scene parsing failed.',
278
+ scene: {
279
+ path: context.scene.path,
280
+ revision,
281
+ },
282
+ diagnostics: [{
283
+ path: '__scene__',
284
+ prop: 'source',
285
+ expected: 'valid Pixifact .scene source',
286
+ actual: message,
287
+ ...sourcePosition(message, source),
288
+ }],
289
+ }, 422);
290
+ }
291
+ return jsonResponse({
292
+ ok: false,
293
+ error: 'Editor context is updating.',
294
+ scene: {
295
+ path: context.scene.path,
296
+ revision,
297
+ },
298
+ }, 409);
299
+ }
300
+ return jsonResponse(context);
301
+ }
302
+
303
+ function fetchRequest(request: Request) {
304
+ const pathname = new URL(request.url).pathname;
305
+ if (pathname !== '/api/editor-session/health' && pathname !== '/api/editor-context') {
306
+ return undefined;
307
+ }
308
+ if (!authorizationMatches(request, options.token)) {
309
+ return jsonResponse({ ok: false, error: 'Editor session token is invalid.' }, 401);
310
+ }
311
+ if (pathname === '/api/editor-session/health') {
312
+ return jsonResponse({
313
+ protocolVersion: editorSessionProtocolVersion,
314
+ projectRoot,
315
+ });
316
+ }
317
+ return contextResponse();
318
+ }
319
+
320
+ function notifyProjectFileChanged(changedPath: string) {
321
+ activeBrowser?.send(JSON.stringify({ type: 'projectFileChanged', path: changedPath }));
322
+ }
323
+
324
+ return {
325
+ open,
326
+ close,
327
+ message,
328
+ fetch: fetchRequest,
329
+ notifyProjectFileChanged,
330
+ };
331
+ }
332
+
333
+ export function defaultEditorSessionsRoot() {
334
+ return path.join(os.tmpdir(), 'pixifact', 'editor-sessions');
335
+ }
336
+
337
+ export function editorSessionDescriptorPath(projectRoot: string, sessionsRoot = defaultEditorSessionsRoot()) {
338
+ const canonicalRoot = fs.realpathSync(projectRoot);
339
+ const projectHash = createHash('sha256').update(canonicalRoot).digest('hex');
340
+ return path.join(sessionsRoot, `${projectHash}.json`);
341
+ }
342
+
343
+ function validateDescriptor(value: unknown): EditorSessionDescriptor {
344
+ if (
345
+ !isRecord(value)
346
+ || typeof value.protocolVersion !== 'number'
347
+ || typeof value.projectRoot !== 'string'
348
+ || typeof value.pid !== 'number'
349
+ || typeof value.origin !== 'string'
350
+ || typeof value.token !== 'string'
351
+ ) {
352
+ throw new Error('Pixifact Editor session descriptor is invalid.');
353
+ }
354
+ if (value.protocolVersion !== editorSessionProtocolVersion) {
355
+ throw new Error('Editor session protocol version does not match. Restart Pixifact Editor.');
356
+ }
357
+ return value as unknown as EditorSessionDescriptor;
358
+ }
359
+
360
+ export function readEditorSessionDescriptor(projectRoot: string, sessionsRoot = defaultEditorSessionsRoot()) {
361
+ const descriptorPath = editorSessionDescriptorPath(projectRoot, sessionsRoot);
362
+ if (!fs.existsSync(descriptorPath)) {
363
+ return undefined;
364
+ }
365
+ return validateDescriptor(JSON.parse(fs.readFileSync(descriptorPath, 'utf8')));
366
+ }
367
+
368
+ export function writeEditorSessionDescriptor(
369
+ descriptor: EditorSessionDescriptor,
370
+ sessionsRoot = defaultEditorSessionsRoot(),
371
+ ) {
372
+ fs.mkdirSync(sessionsRoot, { recursive: true });
373
+ fs.writeFileSync(
374
+ editorSessionDescriptorPath(descriptor.projectRoot, sessionsRoot),
375
+ `${JSON.stringify(descriptor, null, 2)}\n`,
376
+ { encoding: 'utf8', flag: 'wx', mode: 0o600 },
377
+ );
378
+ }
379
+
380
+ export function removeEditorSessionDescriptor(
381
+ descriptor: EditorSessionDescriptor,
382
+ sessionsRoot = defaultEditorSessionsRoot(),
383
+ ) {
384
+ const descriptorPath = editorSessionDescriptorPath(descriptor.projectRoot, sessionsRoot);
385
+ if (!fs.existsSync(descriptorPath)) {
386
+ return;
387
+ }
388
+ const current = readEditorSessionDescriptor(descriptor.projectRoot, sessionsRoot);
389
+ if (current?.token === descriptor.token) {
390
+ fs.unlinkSync(descriptorPath);
391
+ }
392
+ }
393
+
394
+ export async function findActiveEditorSession(options: EditorSessionLookupOptions) {
395
+ const projectRoot = fs.realpathSync(options.projectRoot);
396
+ const descriptor = readEditorSessionDescriptor(projectRoot, options.sessionsRoot);
397
+ if (!descriptor) {
398
+ return undefined;
399
+ }
400
+ if (descriptor.projectRoot !== projectRoot) {
401
+ throw new Error('Pixifact Editor session belongs to a different project.');
402
+ }
403
+ const fetcher = options.fetch ?? fetch;
404
+ let response: Response;
405
+ try {
406
+ response = await fetcher(`${descriptor.origin}/api/editor-session/health`, {
407
+ headers: { authorization: `Bearer ${descriptor.token}` },
408
+ });
409
+ } catch {
410
+ return undefined;
411
+ }
412
+ if (!response.ok) {
413
+ return undefined;
414
+ }
415
+ const health = validateDescriptorHealth(await response.json());
416
+ if (health.projectRoot !== projectRoot) {
417
+ throw new Error('Pixifact Editor Host health check returned a different project.');
418
+ }
419
+ return descriptor;
420
+ }
421
+
422
+ function validateDescriptorHealth(value: unknown) {
423
+ if (!isRecord(value) || typeof value.protocolVersion !== 'number' || typeof value.projectRoot !== 'string') {
424
+ throw new Error('Pixifact Editor Host health response is invalid.');
425
+ }
426
+ if (value.protocolVersion !== editorSessionProtocolVersion) {
427
+ throw new Error('Editor session protocol version does not match. Restart Pixifact Editor.');
428
+ }
429
+ return value as { protocolVersion: number; projectRoot: string };
430
+ }
431
+
432
+ export async function claimEditorSession(options: ClaimEditorSessionOptions) {
433
+ for (let attempt = 0; attempt < 2; attempt += 1) {
434
+ const current = readEditorSessionDescriptor(options.projectRoot, options.sessionsRoot);
435
+ const active = await findActiveEditorSession(options);
436
+ if (active) {
437
+ return { descriptor: active, owned: false as const };
438
+ }
439
+ if (current) {
440
+ removeEditorSessionDescriptor(current, options.sessionsRoot);
441
+ }
442
+ try {
443
+ writeEditorSessionDescriptor(options.descriptor, options.sessionsRoot);
444
+ return { descriptor: options.descriptor, owned: true as const };
445
+ } catch (error) {
446
+ if (!isRecord(error) || error.code !== 'EEXIST') {
447
+ throw error;
448
+ }
449
+ }
450
+ }
451
+ throw new Error('Pixifact Editor session could not be registered.');
452
+ }
453
+
454
+ export async function queryEditorContext(options: EditorSessionLookupOptions) {
455
+ const projectRoot = fs.realpathSync(options.projectRoot);
456
+ const descriptor = readEditorSessionDescriptor(projectRoot, options.sessionsRoot);
457
+ if (!descriptor) {
458
+ return {
459
+ ok: false,
460
+ error: 'No Pixifact Editor Host is running for this project.',
461
+ };
462
+ }
463
+ const fetcher = options.fetch ?? fetch;
464
+ try {
465
+ const response = await fetcher(`${descriptor.origin}/api/editor-context`, {
466
+ headers: { authorization: `Bearer ${descriptor.token}` },
467
+ });
468
+ const result = await response.json() as Record<string, unknown>;
469
+ if (response.ok) {
470
+ if (result.protocolVersion !== editorSessionProtocolVersion) {
471
+ return {
472
+ ok: false,
473
+ error: 'Editor session protocol version does not match. Restart Pixifact Editor.',
474
+ };
475
+ }
476
+ if (result.projectRoot !== projectRoot) {
477
+ return {
478
+ ok: false,
479
+ error: 'Pixifact Editor context belongs to a different project.',
480
+ };
481
+ }
482
+ }
483
+ return result;
484
+ } catch {
485
+ return {
486
+ ok: false,
487
+ error: 'Pixifact Editor Host is not reachable for this project.',
488
+ };
489
+ }
490
+ }
@@ -3,7 +3,8 @@ import { createPixifactAutomation } from './automation';
3
3
  import { hintForCommandError } from 'pixifact';
4
4
  import { CompileSceneError, compileScenes } from 'pixifact/compiler-node';
5
5
  import type { SceneValidationDiagnostic } from 'pixifact/compiler';
6
- import { createLiveBridgeServer } from './liveBridgeServer';
6
+ import { startEditorServer } from './editorServer';
7
+ import { queryEditorContext } from './editorSession';
7
8
  import {
8
9
  buildWechatTarget,
9
10
  devWechatTarget,
@@ -13,14 +14,12 @@ import {
13
14
  } from './wechatTarget';
14
15
 
15
16
  type Automation = ReturnType<typeof createPixifactAutomation>;
16
- type LiveBridge = Pick<ReturnType<typeof createLiveBridgeServer>, 'connected' | 'callAction' | 'stop'> & {
17
- waitForConnection?: (timeoutMs?: number) => Promise<void>;
18
- };
19
17
 
20
18
  interface CliOptions {
21
19
  automation?: Automation;
22
- liveBridge?: LiveBridge;
23
20
  onWechatDevEvent?: (event: WechatDevEvent) => void;
21
+ readEditorContext?: typeof queryEditorContext;
22
+ startEditor?: typeof startEditorServer;
24
23
  }
25
24
 
26
25
  interface CliResult {
@@ -96,6 +95,15 @@ function isFailedResult(value: unknown): value is CliJsonResult {
96
95
  function compileScenesFailure(error: unknown): CliJsonResult {
97
96
  const message = error instanceof Error ? error.message : String(error);
98
97
  if (error instanceof CompileSceneError) {
98
+ if (error.diagnostics) {
99
+ return {
100
+ ok: false,
101
+ scene: error.scene,
102
+ error: 'Scene compile failed.',
103
+ diagnostics: error.diagnostics,
104
+ hint: 'Fix the listed diagnostics, then run compile-scenes again.',
105
+ };
106
+ }
99
107
  const sourceDiagnostic = sourceDiagnosticFromMessage(error.source ?? '', message);
100
108
  if (sourceDiagnostic) {
101
109
  return {
@@ -218,10 +226,25 @@ async function executeFileCommand(
218
226
  flags: Record<string, string | true>,
219
227
  automation: Automation,
220
228
  onWechatDevEvent?: (event: WechatDevEvent) => void,
229
+ startEditor: typeof startEditorServer = startEditorServer,
230
+ readEditorContext: typeof queryEditorContext = queryEditorContext,
221
231
  ) {
222
232
  const [area, action] = positionals;
223
233
  const projectRoot = projectRootFlag(flags);
224
234
 
235
+ if (area === 'editor' && action === undefined) {
236
+ const session = await startEditor({ projectRoot });
237
+ return {
238
+ ok: true,
239
+ projectRoot,
240
+ url: session.url,
241
+ };
242
+ }
243
+
244
+ if (area === 'editor' && action === 'context') {
245
+ return readEditorContext({ projectRoot });
246
+ }
247
+
225
248
  if ((area === 'build' || area === 'dev' || area === 'validate') && action === undefined) {
226
249
  const target = requireFlag(flags, 'target');
227
250
  if (target !== 'wechat') {
@@ -350,32 +373,7 @@ async function executeFileCommand(
350
373
  throw new Error(`Unknown Pixifact CLI command "${positionals.join(' ')}".`);
351
374
  }
352
375
 
353
- async function executeLiveCommand(positionals: string[], flags: Record<string, string | true>, bridge: LiveBridge) {
354
- if (!bridge.connected && bridge.waitForConnection) {
355
- await bridge.waitForConnection();
356
- }
357
- if (!bridge.connected) {
358
- throw new Error('No live Pixifact editor is connected.');
359
- }
360
-
361
- const [area, action] = positionals;
362
- if (area === 'summary' && action === undefined) {
363
- return bridge.callAction('summary', {});
364
- }
365
- if (area === 'scene' && action === 'get') {
366
- return bridge.callAction('scene.get', {});
367
- }
368
- if (area === 'node' && action === 'inspect') {
369
- return bridge.callAction('node.inspect', {
370
- node: requireFlag(flags, 'node'),
371
- });
372
- }
373
-
374
- throw new Error(`Unknown Pixifact live command "${positionals.join(' ')}".`);
375
- }
376
-
377
376
  export async function executePixifactCli(argv: string[], options: CliOptions = {}): Promise<CliResult> {
378
- let ownedBridge: ReturnType<typeof createLiveBridgeServer> | undefined;
379
377
  try {
380
378
  const parsed = parseArgs(argv);
381
379
  if (parsed.flags.help === true) {
@@ -395,14 +393,11 @@ export async function executePixifactCli(argv: string[], options: CliOptions = {
395
393
  'scene validate --all',
396
394
  ],
397
395
  auxiliaryCommands: [
396
+ 'editor',
397
+ 'editor context',
398
398
  'scene create --scene <scene-path> --name <SceneName>',
399
399
  'node inspect --scene <scene-path> --node <locator>',
400
400
  ],
401
- liveContextCommands: [
402
- 'live summary',
403
- 'live scene get',
404
- 'live node inspect --node <locator>',
405
- ],
406
401
  defaults: {
407
402
  projectRoot: 'current working directory',
408
403
  },
@@ -412,19 +407,14 @@ export async function executePixifactCli(argv: string[], options: CliOptions = {
412
407
  }
413
408
 
414
409
  const automation = options.automation ?? createPixifactAutomation();
415
- const isLive = parsed.positionals[0] === 'live';
416
- const result = isLive
417
- ? await executeLiveCommand(
418
- parsed.positionals.slice(1),
419
- parsed.flags,
420
- options.liveBridge ?? (ownedBridge = createLiveBridgeServer()),
421
- )
422
- : await executeFileCommand(
423
- parsed.positionals,
424
- parsed.flags,
425
- automation,
426
- options.onWechatDevEvent,
427
- );
410
+ const result = await executeFileCommand(
411
+ parsed.positionals,
412
+ parsed.flags,
413
+ automation,
414
+ options.onWechatDevEvent,
415
+ options.startEditor,
416
+ options.readEditorContext,
417
+ );
428
418
  if (isFailedResult(result)) {
429
419
  return {
430
420
  exitCode: 1,
@@ -447,8 +437,6 @@ export async function executePixifactCli(argv: string[], options: CliOptions = {
447
437
  hint: hintForCommandError(error instanceof Error ? error.message : String(error)),
448
438
  }),
449
439
  };
450
- } finally {
451
- ownedBridge?.stop();
452
440
  }
453
441
  }
454
442
 
package/src/liveBridge.ts DELETED
@@ -1,28 +0,0 @@
1
- export const pixifactAgentBridgePort = 8791;
2
- export const pixifactAgentBridgePath = '/pixifact-agent';
3
- export const pixifactAgentBridgeUrl = `ws://127.0.0.1:${pixifactAgentBridgePort}${pixifactAgentBridgePath}`;
4
-
5
- export type LiveBridgeRole = 'editor';
6
-
7
- export interface LiveBridgeHelloMessage {
8
- type: 'hello';
9
- role: LiveBridgeRole;
10
- }
11
-
12
- export interface LiveBridgeRequestMessage {
13
- type: 'request';
14
- id: string;
15
- action: string;
16
- arguments: unknown;
17
- }
18
-
19
- export interface LiveBridgeResponseMessage {
20
- type: 'response';
21
- id: string;
22
- ok: boolean;
23
- result?: unknown;
24
- error?: string;
25
- }
26
-
27
- export type LiveBridgeClientMessage = LiveBridgeHelloMessage | LiveBridgeResponseMessage;
28
- export type LiveBridgeServerMessage = LiveBridgeRequestMessage;