Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
deec765
[Symphony] Start contribution for #511
May 22, 2026
d5975c4
MAESTRO: Add CLI server discovery module
May 22, 2026
46603d6
MAESTRO: Auto-start CLI web server discovery
May 22, 2026
d71783c
MAESTRO: Clean up CLI server discovery on shutdown
May 22, 2026
9837da5
MAESTRO: Add CLI server discovery tests
May 22, 2026
aeeabbb
MAESTRO: Add open file tab websocket message
May 22, 2026
d8442b0
MAESTRO: Add refresh file tree websocket IPC
May 22, 2026
ec2b9b8
MAESTRO: Add refresh auto run docs WebSocket message
May 22, 2026
9233732
MAESTRO: Add remote preload listener declarations
May 22, 2026
933baf4
MAESTRO: Wire renderer CLI IPC handlers
May 22, 2026
071441d
MAESTRO: Focus desktop on select session requests
May 22, 2026
db400cb
MAESTRO: Add CLI WebSocket client service
May 22, 2026
d836184
MAESTRO: add CLI open-file command
May 22, 2026
447cf9d
MAESTRO: add refresh-files CLI command
May 22, 2026
15faf59
MAESTRO: add refresh-auto-run CLI command
May 22, 2026
4b546a0
MAESTRO: Add send tab focus flag
May 22, 2026
ad9e6d3
MAESTRO: Add CLI status command
May 22, 2026
32cee44
MAESTRO: Add CLI Maestro client tests
May 22, 2026
cded1ac
MAESTRO: Add configure auto-run IPC plumbing
May 22, 2026
4c16fa2
MAESTRO: Wire remote auto-run renderer handler
May 22, 2026
07f0093
MAESTRO: Add auto-run CLI command
May 22, 2026
0116530
MAESTRO: Document CLI IPC commands in system prompt
May 22, 2026
f844243
MAESTRO: Add auto-run CLI IPC tests
May 22, 2026
d5a098c
Fix CLI IPC review issues
May 22, 2026
cad6fd5
Merge rc into CLI IPC branch
Jun 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

82 changes: 82 additions & 0 deletions src/__tests__/cli/commands/auto-run.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,13 @@ vi.mock('../../../cli/services/maestro-client', () => ({
resolveTargetSessionId: vi.fn(),
}));

vi.mock('../../../cli/services/storage', () => ({
readSessions: vi.fn(),
}));

import { autoRun } from '../../../cli/commands/auto-run';
import { withMaestroClient, resolveTargetSessionId } from '../../../cli/services/maestro-client';
import { readSessions } from '../../../cli/services/storage';
import { existsSync } from 'fs';

describe('auto-run command', () => {
Expand All @@ -38,6 +43,24 @@ describe('auto-run command', () => {
consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
processExitSpy = vi.spyOn(process, 'exit').mockImplementation(() => undefined as never);
vi.mocked(readSessions).mockReturnValue([
{
id: 'agent-123',
name: 'Agent',
toolType: 'claude-code',
cwd: '/project',
projectRoot: '/project',
autoRunFolderPath: '/path/to',
},
{
id: 'full-agent-uuid-123',
name: 'Full Agent',
toolType: 'claude-code',
cwd: '/project',
projectRoot: '/project',
autoRunFolderPath: '/path/to',
},
]);
});

it('should configure auto-run with valid document paths', async () => {
Expand All @@ -62,6 +85,65 @@ describe('auto-run command', () => {
expect(processExitSpy).not.toHaveBeenCalled();
});

it('should send Auto Run folder-relative document filenames', async () => {
vi.mocked(existsSync).mockReturnValue(true);
vi.mocked(resolveTargetSessionId).mockReturnValue('agent-123');

let sentMessage: Record<string, unknown> | undefined;
vi.mocked(withMaestroClient).mockImplementation(async (action) => {
const mockClient = {
sendCommand: vi.fn().mockImplementation((msg) => {
sentMessage = msg;
return Promise.resolve({
type: 'configure_auto_run_result',
success: true,
});
}),
};
return action(mockClient as never);
});

await autoRun(['/path/to/doc.md', '/path/to/nested/step.md'], { agent: 'agent-123' });

const sentDocs = sentMessage!.documents as Array<{ filename: string }>;
expect(sentDocs.map((doc) => doc.filename)).toEqual(['doc.md', 'nested/step.md']);
});

it('should reject documents outside the selected Auto Run folder', async () => {
vi.mocked(existsSync).mockReturnValue(true);
vi.mocked(resolveTargetSessionId).mockReturnValue('agent-123');

await autoRun(['/other/doc.md'], { agent: 'agent-123' });

expect(consoleErrorSpy).toHaveBeenCalledWith(
expect.stringContaining("File must be inside the selected agent's Auto Run folder")
);
expect(processExitSpy).toHaveBeenCalledWith(1);
expect(withMaestroClient).not.toHaveBeenCalled();
});

it('should reject when the selected agent has no Auto Run folder configured', async () => {
vi.mocked(existsSync).mockReturnValue(true);
vi.mocked(resolveTargetSessionId).mockReturnValue('agent-123');
vi.mocked(readSessions).mockReturnValue([
{
id: 'agent-123',
name: 'Agent',
toolType: 'claude-code',
cwd: '/project',
projectRoot: '/project',
},
]);

await autoRun(['/path/to/doc.md'], { agent: 'agent-123' });

expect(consoleErrorSpy).toHaveBeenCalledWith(
expect.stringContaining('Selected agent has no Auto Run folder configured')
);
expect(processExitSpy).toHaveBeenCalledWith(1);
expect(withMaestroClient).not.toHaveBeenCalled();
});

it('should error with no documents', async () => {
await autoRun([], {});

Expand Down
104 changes: 104 additions & 0 deletions src/__tests__/cli/commands/refresh-auto-run.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/**
* @file refresh-auto-run.test.ts
* @description Tests for the refresh-auto-run CLI command
*/

import { describe, it, expect, vi, beforeEach, type MockInstance } from 'vitest';

vi.mock('../../../cli/services/maestro-client', () => ({
resolveTargetSessionId: vi.fn(),
withMaestroClient: vi.fn(),
}));

vi.mock('../../../cli/output/formatter', () => ({
formatError: vi.fn((message: string) => `Error: ${message}`),
}));

import { refreshAutoRun } from '../../../cli/commands/refresh-auto-run';
import { resolveTargetSessionId, withMaestroClient } from '../../../cli/services/maestro-client';

describe('refresh-auto-run command', () => {
let consoleSpy: MockInstance;
let consoleErrorSpy: MockInstance;
let processExitSpy: MockInstance;

beforeEach(() => {
vi.clearAllMocks();
consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
processExitSpy = vi.spyOn(process, 'exit').mockImplementation(() => undefined as never);
vi.mocked(resolveTargetSessionId).mockReturnValue('target-session');
vi.mocked(withMaestroClient).mockImplementation(async (action) => {
const client = {
sendCommand: vi.fn().mockResolvedValue({
type: 'refresh_auto_run_docs_result',
success: true,
}),
};
return action(client as never);
});
});

it('refreshes Auto Run documents with an explicit agent', async () => {
await refreshAutoRun({ agent: 'target-agent' });

expect(resolveTargetSessionId).toHaveBeenCalledWith('target-agent');
expect(withMaestroClient).toHaveBeenCalledTimes(1);
const action = vi.mocked(withMaestroClient).mock.calls[0][0];
const sendCommand = vi.fn().mockResolvedValue({
type: 'refresh_auto_run_docs_result',
success: true,
});
await action({ sendCommand } as never);
expect(sendCommand).toHaveBeenCalledWith(
{ type: 'refresh_auto_run_docs', sessionId: 'target-session' },
'refresh_auto_run_docs_result'
);
expect(consoleSpy).toHaveBeenCalledWith('Auto Run documents refreshed');
expect(processExitSpy).not.toHaveBeenCalled();
});

it('refreshes Auto Run documents with the resolved default agent', async () => {
vi.mocked(resolveTargetSessionId).mockReturnValue('resolved-session');

await refreshAutoRun({});

const action = vi.mocked(withMaestroClient).mock.calls[0][0];
const sendCommand = vi.fn().mockResolvedValue({
type: 'refresh_auto_run_docs_result',
success: true,
});
await action({ sendCommand } as never);
expect(sendCommand).toHaveBeenCalledWith(
{ type: 'refresh_auto_run_docs', sessionId: 'resolved-session' },
'refresh_auto_run_docs_result'
);
});

it('exits with an error when Maestro is not reachable', async () => {
vi.mocked(withMaestroClient).mockRejectedValue(new Error('Maestro desktop app is not running'));

await refreshAutoRun({ agent: 'target-agent' });

expect(consoleErrorSpy).toHaveBeenCalledWith('Error: Maestro desktop app is not running');
expect(processExitSpy).toHaveBeenCalledWith(1);
});

it('exits with an error when Maestro rejects the refresh', async () => {
vi.mocked(withMaestroClient).mockImplementation(async (action) => {
const client = {
sendCommand: vi.fn().mockResolvedValue({
type: 'refresh_auto_run_docs_result',
success: false,
error: 'Session not found',
}),
};
return action(client as never);
});

await refreshAutoRun({ agent: 'target-agent' });

expect(consoleErrorSpy).toHaveBeenCalledWith('Error: Session not found');
expect(processExitSpy).toHaveBeenCalledWith(1);
});
});
104 changes: 104 additions & 0 deletions src/__tests__/cli/commands/refresh-files.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/**
* @file refresh-files.test.ts
* @description Tests for the refresh-files CLI command
*/

import { describe, it, expect, vi, beforeEach, type MockInstance } from 'vitest';

vi.mock('../../../cli/services/maestro-client', () => ({
resolveTargetSessionId: vi.fn(),
withMaestroClient: vi.fn(),
}));

vi.mock('../../../cli/output/formatter', () => ({
formatError: vi.fn((message: string) => `Error: ${message}`),
}));

import { refreshFiles } from '../../../cli/commands/refresh-files';
import { resolveTargetSessionId, withMaestroClient } from '../../../cli/services/maestro-client';

describe('refresh-files command', () => {
let consoleSpy: MockInstance;
let consoleErrorSpy: MockInstance;
let processExitSpy: MockInstance;

beforeEach(() => {
vi.clearAllMocks();
consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
processExitSpy = vi.spyOn(process, 'exit').mockImplementation(() => undefined as never);
vi.mocked(resolveTargetSessionId).mockReturnValue('target-session');
vi.mocked(withMaestroClient).mockImplementation(async (action) => {
const client = {
sendCommand: vi.fn().mockResolvedValue({
type: 'refresh_file_tree_result',
success: true,
}),
};
return action(client as never);
});
});

it('refreshes the file tree with an explicit agent', async () => {
await refreshFiles({ agent: 'target-agent' });

expect(resolveTargetSessionId).toHaveBeenCalledWith('target-agent');
expect(withMaestroClient).toHaveBeenCalledTimes(1);
const action = vi.mocked(withMaestroClient).mock.calls[0][0];
const sendCommand = vi.fn().mockResolvedValue({
type: 'refresh_file_tree_result',
success: true,
});
await action({ sendCommand } as never);
expect(sendCommand).toHaveBeenCalledWith(
{ type: 'refresh_file_tree', sessionId: 'target-session' },
'refresh_file_tree_result'
);
expect(consoleSpy).toHaveBeenCalledWith('File tree refreshed');
expect(processExitSpy).not.toHaveBeenCalled();
});

it('refreshes the file tree with the resolved default agent', async () => {
vi.mocked(resolveTargetSessionId).mockReturnValue('resolved-session');

await refreshFiles({});

const action = vi.mocked(withMaestroClient).mock.calls[0][0];
const sendCommand = vi.fn().mockResolvedValue({
type: 'refresh_file_tree_result',
success: true,
});
await action({ sendCommand } as never);
expect(sendCommand).toHaveBeenCalledWith(
{ type: 'refresh_file_tree', sessionId: 'resolved-session' },
'refresh_file_tree_result'
);
});

it('exits with an error when Maestro is not reachable', async () => {
vi.mocked(withMaestroClient).mockRejectedValue(new Error('Maestro desktop app is not running'));

await refreshFiles({ agent: 'target-agent' });

expect(consoleErrorSpy).toHaveBeenCalledWith('Error: Maestro desktop app is not running');
expect(processExitSpy).toHaveBeenCalledWith(1);
});

it('exits with an error when Maestro rejects the refresh', async () => {
vi.mocked(withMaestroClient).mockImplementation(async (action) => {
const client = {
sendCommand: vi.fn().mockResolvedValue({
type: 'refresh_file_tree_result',
success: false,
error: 'Session not found',
}),
};
return action(client as never);
});

await refreshFiles({ agent: 'target-agent' });

expect(consoleErrorSpy).toHaveBeenCalledWith('Error: Session not found');
expect(processExitSpy).toHaveBeenCalledWith(1);
});
});
Loading