@wong2kim/wmux 1.0.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.
Files changed (122) hide show
  1. package/README.md +157 -0
  2. package/assets/icon.ico +0 -0
  3. package/assets/icon.svg +6 -0
  4. package/dist/cli/cli/client.js +102 -0
  5. package/dist/cli/cli/commands/browser.js +137 -0
  6. package/dist/cli/cli/commands/input.js +80 -0
  7. package/dist/cli/cli/commands/notify.js +28 -0
  8. package/dist/cli/cli/commands/pane.js +88 -0
  9. package/dist/cli/cli/commands/surface.js +98 -0
  10. package/dist/cli/cli/commands/system.js +98 -0
  11. package/dist/cli/cli/commands/workspace.js +117 -0
  12. package/dist/cli/cli/index.js +140 -0
  13. package/dist/cli/cli/utils.js +47 -0
  14. package/dist/cli/shared/constants.js +54 -0
  15. package/dist/cli/shared/rpc.js +33 -0
  16. package/dist/cli/shared/types.js +79 -0
  17. package/dist/mcp/mcp/index.js +60 -0
  18. package/dist/mcp/mcp/wmux-client.js +146 -0
  19. package/dist/mcp/shared/constants.js +54 -0
  20. package/dist/mcp/shared/rpc.js +33 -0
  21. package/dist/mcp/shared/types.js +79 -0
  22. package/forge.config.ts +61 -0
  23. package/index.html +12 -0
  24. package/package.json +84 -0
  25. package/postcss.config.js +6 -0
  26. package/src/cli/client.ts +76 -0
  27. package/src/cli/commands/browser.ts +128 -0
  28. package/src/cli/commands/input.ts +72 -0
  29. package/src/cli/commands/notify.ts +29 -0
  30. package/src/cli/commands/pane.ts +90 -0
  31. package/src/cli/commands/surface.ts +102 -0
  32. package/src/cli/commands/system.ts +95 -0
  33. package/src/cli/commands/workspace.ts +116 -0
  34. package/src/cli/index.ts +145 -0
  35. package/src/cli/utils.ts +44 -0
  36. package/src/main/index.ts +86 -0
  37. package/src/main/ipc/handlers/clipboard.handler.ts +20 -0
  38. package/src/main/ipc/handlers/metadata.handler.ts +56 -0
  39. package/src/main/ipc/handlers/pty.handler.ts +69 -0
  40. package/src/main/ipc/handlers/session.handler.ts +17 -0
  41. package/src/main/ipc/handlers/shell.handler.ts +11 -0
  42. package/src/main/ipc/registerHandlers.ts +31 -0
  43. package/src/main/mcp/McpRegistrar.ts +156 -0
  44. package/src/main/metadata/MetadataCollector.ts +58 -0
  45. package/src/main/notification/ToastManager.ts +32 -0
  46. package/src/main/pipe/PipeServer.ts +190 -0
  47. package/src/main/pipe/RpcRouter.ts +46 -0
  48. package/src/main/pipe/handlers/_bridge.ts +40 -0
  49. package/src/main/pipe/handlers/browser.rpc.ts +132 -0
  50. package/src/main/pipe/handlers/input.rpc.ts +120 -0
  51. package/src/main/pipe/handlers/meta.rpc.ts +59 -0
  52. package/src/main/pipe/handlers/notify.rpc.ts +53 -0
  53. package/src/main/pipe/handlers/pane.rpc.ts +39 -0
  54. package/src/main/pipe/handlers/surface.rpc.ts +43 -0
  55. package/src/main/pipe/handlers/system.rpc.ts +36 -0
  56. package/src/main/pipe/handlers/workspace.rpc.ts +52 -0
  57. package/src/main/pty/AgentDetector.ts +247 -0
  58. package/src/main/pty/OscParser.ts +81 -0
  59. package/src/main/pty/PTYBridge.ts +88 -0
  60. package/src/main/pty/PTYManager.ts +104 -0
  61. package/src/main/pty/ShellDetector.ts +63 -0
  62. package/src/main/session/SessionManager.ts +53 -0
  63. package/src/main/updater/AutoUpdater.ts +132 -0
  64. package/src/main/window/createWindow.ts +71 -0
  65. package/src/mcp/README.md +56 -0
  66. package/src/mcp/index.ts +153 -0
  67. package/src/mcp/wmux-client.ts +127 -0
  68. package/src/preload/index.ts +111 -0
  69. package/src/preload/preload.ts +108 -0
  70. package/src/renderer/App.tsx +5 -0
  71. package/src/renderer/components/Browser/BrowserPanel.tsx +219 -0
  72. package/src/renderer/components/Browser/BrowserToolbar.tsx +253 -0
  73. package/src/renderer/components/Company/ApprovalDialog.tsx +3 -0
  74. package/src/renderer/components/Company/CompanyView.tsx +7 -0
  75. package/src/renderer/components/Company/MessageFeedPanel.tsx +3 -0
  76. package/src/renderer/components/Layout/AppLayout.tsx +234 -0
  77. package/src/renderer/components/Notification/NotificationPanel.tsx +129 -0
  78. package/src/renderer/components/Palette/CommandPalette.tsx +409 -0
  79. package/src/renderer/components/Palette/PaletteItem.tsx +55 -0
  80. package/src/renderer/components/Pane/Pane.tsx +122 -0
  81. package/src/renderer/components/Pane/PaneContainer.tsx +41 -0
  82. package/src/renderer/components/Pane/SurfaceTabs.tsx +46 -0
  83. package/src/renderer/components/Settings/SettingsPanel.tsx +886 -0
  84. package/src/renderer/components/Sidebar/MiniSidebar.tsx +67 -0
  85. package/src/renderer/components/Sidebar/Sidebar.tsx +84 -0
  86. package/src/renderer/components/Sidebar/WorkspaceItem.tsx +241 -0
  87. package/src/renderer/components/StatusBar/StatusBar.tsx +93 -0
  88. package/src/renderer/components/Terminal/SearchBar.tsx +126 -0
  89. package/src/renderer/components/Terminal/Terminal.tsx +102 -0
  90. package/src/renderer/components/Terminal/ViCopyMode.tsx +104 -0
  91. package/src/renderer/hooks/useKeyboard.ts +310 -0
  92. package/src/renderer/hooks/useNotificationListener.ts +80 -0
  93. package/src/renderer/hooks/useNotificationSound.ts +75 -0
  94. package/src/renderer/hooks/useRpcBridge.ts +451 -0
  95. package/src/renderer/hooks/useT.ts +11 -0
  96. package/src/renderer/hooks/useTerminal.ts +349 -0
  97. package/src/renderer/hooks/useViCopyMode.ts +320 -0
  98. package/src/renderer/i18n/index.ts +69 -0
  99. package/src/renderer/i18n/locales/en.ts +157 -0
  100. package/src/renderer/i18n/locales/ja.ts +155 -0
  101. package/src/renderer/i18n/locales/ko.ts +155 -0
  102. package/src/renderer/i18n/locales/zh.ts +155 -0
  103. package/src/renderer/index.tsx +6 -0
  104. package/src/renderer/stores/index.ts +19 -0
  105. package/src/renderer/stores/slices/notificationSlice.ts +56 -0
  106. package/src/renderer/stores/slices/paneSlice.ts +141 -0
  107. package/src/renderer/stores/slices/surfaceSlice.ts +122 -0
  108. package/src/renderer/stores/slices/uiSlice.ts +247 -0
  109. package/src/renderer/stores/slices/workspaceSlice.ts +120 -0
  110. package/src/renderer/styles/globals.css +150 -0
  111. package/src/renderer/themes.ts +99 -0
  112. package/src/shared/constants.ts +53 -0
  113. package/src/shared/electron.d.ts +11 -0
  114. package/src/shared/rpc.ts +71 -0
  115. package/src/shared/types.ts +176 -0
  116. package/tailwind.config.js +11 -0
  117. package/tsconfig.cli.json +24 -0
  118. package/tsconfig.json +21 -0
  119. package/tsconfig.mcp.json +25 -0
  120. package/vite.main.config.ts +14 -0
  121. package/vite.preload.config.ts +9 -0
  122. package/vite.renderer.config.ts +6 -0
@@ -0,0 +1,128 @@
1
+ import { sendRequest } from '../client';
2
+ import { printResult, printError } from '../utils';
3
+ import type { RpcResponse } from '../../shared/rpc';
4
+
5
+ const BROWSER_HELP = `
6
+ wmux browser — Scriptable Browser API
7
+
8
+ USAGE
9
+ wmux browser <subcommand> [args]
10
+
11
+ SUBCOMMANDS
12
+ snapshot Return the full page HTML (document.documentElement.outerHTML)
13
+ click <selector> Click the first element matching the CSS selector
14
+ fill <selector> <text> Set the value of an input matching the CSS selector
15
+ eval <code> Execute arbitrary JavaScript in the page context
16
+ navigate <url> Navigate the active browser surface to a URL
17
+
18
+ EXAMPLES
19
+ wmux browser snapshot
20
+ wmux browser click "#submit-btn"
21
+ wmux browser fill "input[name=email]" "user@example.com"
22
+ wmux browser eval "document.title"
23
+ wmux browser navigate "https://example.com"
24
+ `.trimStart();
25
+
26
+ export async function handleBrowser(
27
+ args: string[],
28
+ jsonMode: boolean,
29
+ ): Promise<void> {
30
+ const sub = args[0];
31
+ const rest = args.slice(1);
32
+
33
+ if (!sub || sub === '--help' || sub === '-h') {
34
+ process.stdout.write(BROWSER_HELP);
35
+ process.exit(0);
36
+ }
37
+
38
+ let response: RpcResponse;
39
+
40
+ switch (sub) {
41
+ // ── browser snapshot ─────────────────────────────────────────────────────
42
+ case 'snapshot': {
43
+ response = await sendRequest('browser.snapshot', {});
44
+ if (jsonMode) {
45
+ printResult(response);
46
+ } else {
47
+ if (!response.ok) { printError(response); return; }
48
+ const r = response.result as { html?: string } | null;
49
+ process.stdout.write(r?.html ?? '');
50
+ }
51
+ break;
52
+ }
53
+
54
+ // ── browser click <selector> ─────────────────────────────────────────────
55
+ case 'click': {
56
+ const selector = rest[0];
57
+ if (!selector) {
58
+ console.error('Error: browser click requires <selector>');
59
+ process.exit(1);
60
+ }
61
+ response = await sendRequest('browser.click', { selector });
62
+ if (jsonMode) {
63
+ printResult(response);
64
+ } else {
65
+ if (!response.ok) { printError(response); return; }
66
+ console.log(`Clicked: ${selector}`);
67
+ }
68
+ break;
69
+ }
70
+
71
+ // ── browser fill <selector> <text> ───────────────────────────────────────
72
+ case 'fill': {
73
+ const selector = rest[0];
74
+ const text = rest.slice(1).join(' ');
75
+ if (!selector) {
76
+ console.error('Error: browser fill requires <selector> <text>');
77
+ process.exit(1);
78
+ }
79
+ response = await sendRequest('browser.fill', { selector, text });
80
+ if (jsonMode) {
81
+ printResult(response);
82
+ } else {
83
+ if (!response.ok) { printError(response); return; }
84
+ console.log(`Filled "${selector}" with "${text}"`);
85
+ }
86
+ break;
87
+ }
88
+
89
+ // ── browser eval <code> ──────────────────────────────────────────────────
90
+ case 'eval': {
91
+ const code = rest.join(' ');
92
+ if (!code) {
93
+ console.error('Error: browser eval requires <code>');
94
+ process.exit(1);
95
+ }
96
+ response = await sendRequest('browser.eval', { code });
97
+ if (jsonMode) {
98
+ printResult(response);
99
+ } else {
100
+ if (!response.ok) { printError(response); return; }
101
+ const r = response.result as { result?: unknown } | null;
102
+ console.log(JSON.stringify(r?.result, null, 2));
103
+ }
104
+ break;
105
+ }
106
+
107
+ // ── browser navigate <url> ───────────────────────────────────────────────
108
+ case 'navigate': {
109
+ const url = rest[0];
110
+ if (!url) {
111
+ console.error('Error: browser navigate requires <url>');
112
+ process.exit(1);
113
+ }
114
+ response = await sendRequest('browser.navigate', { url });
115
+ if (jsonMode) {
116
+ printResult(response);
117
+ } else {
118
+ if (!response.ok) { printError(response); return; }
119
+ console.log(`Navigated to: ${url}`);
120
+ }
121
+ break;
122
+ }
123
+
124
+ default:
125
+ console.error(`Unknown browser subcommand: "${sub}". Run 'wmux browser --help' for usage.`);
126
+ process.exit(1);
127
+ }
128
+ }
@@ -0,0 +1,72 @@
1
+ import { sendRequest } from '../client';
2
+ import { printResult, printError } from '../utils';
3
+ import type { RpcResponse } from '../../shared/rpc';
4
+
5
+ export async function handleInput(
6
+ cmd: string,
7
+ args: string[],
8
+ jsonMode: boolean
9
+ ): Promise<void> {
10
+ let response: RpcResponse;
11
+
12
+ switch (cmd) {
13
+ case 'send': {
14
+ const text = args.join(' ');
15
+ if (!text) {
16
+ console.error('Error: send requires <text>');
17
+ process.exit(1);
18
+ }
19
+ response = await sendRequest('input.send', { text });
20
+ if (jsonMode) {
21
+ printResult(response);
22
+ } else {
23
+ if (!response.ok) { printError(response); return; }
24
+ console.log('Text sent.');
25
+ }
26
+ break;
27
+ }
28
+
29
+ case 'send-key': {
30
+ const key = args[0];
31
+ if (!key) {
32
+ console.error('Error: send-key requires <keystroke>');
33
+ process.exit(1);
34
+ }
35
+ response = await sendRequest('input.sendKey', { key });
36
+ if (jsonMode) {
37
+ printResult(response);
38
+ } else {
39
+ if (!response.ok) { printError(response); return; }
40
+ console.log(`Key sent: ${key}`);
41
+ }
42
+ break;
43
+ }
44
+
45
+ case 'read-screen': {
46
+ response = await sendRequest('input.readScreen', {});
47
+ if (jsonMode) {
48
+ printResult(response);
49
+ } else {
50
+ if (!response.ok) { printError(response); return; }
51
+ // server returns { text: string } object or plain string
52
+ const result = response.result as { text?: string } | string;
53
+ let screen: string;
54
+ if (typeof result === 'object' && result !== null && 'text' in result) {
55
+ screen = result.text ?? '';
56
+ } else if (typeof result === 'string') {
57
+ screen = result;
58
+ } else {
59
+ console.log(JSON.stringify(result, null, 2));
60
+ break;
61
+ }
62
+ process.stdout.write(screen);
63
+ if (!screen.endsWith('\n')) process.stdout.write('\n');
64
+ }
65
+ break;
66
+ }
67
+
68
+ default:
69
+ console.error(`Unknown input command: ${cmd}`);
70
+ process.exit(1);
71
+ }
72
+ }
@@ -0,0 +1,29 @@
1
+ import { sendRequest } from '../client';
2
+ import { printResult, printError, parseFlag } from '../utils';
3
+ import type { RpcResponse } from '../../shared/rpc';
4
+
5
+ export async function handleNotify(
6
+ args: string[],
7
+ jsonMode: boolean
8
+ ): Promise<void> {
9
+ const title = parseFlag(args, '--title');
10
+ const body = parseFlag(args, '--body');
11
+
12
+ if (!title) {
13
+ console.error('Error: notify requires --title <text>');
14
+ process.exit(1);
15
+ }
16
+ if (!body) {
17
+ console.error('Error: notify requires --body <text>');
18
+ process.exit(1);
19
+ }
20
+
21
+ const response: RpcResponse = await sendRequest('notify', { title, body });
22
+
23
+ if (jsonMode) {
24
+ printResult(response);
25
+ } else {
26
+ if (!response.ok) { printError(response); return; }
27
+ console.log(`Notification sent: "${title}"`);
28
+ }
29
+ }
@@ -0,0 +1,90 @@
1
+ import { sendRequest } from '../client';
2
+ import { printResult, printError, parseFlag } from '../utils';
3
+ import type { RpcResponse } from '../../shared/rpc';
4
+
5
+ interface PaneInfo {
6
+ id: string;
7
+ type: 'leaf' | 'branch';
8
+ direction?: string;
9
+ activeSurfaceId?: string;
10
+ }
11
+
12
+ function formatPaneList(result: unknown): void {
13
+ const list = result as PaneInfo[];
14
+ if (!Array.isArray(list) || list.length === 0) {
15
+ console.log('No panes found.');
16
+ return;
17
+ }
18
+ const maxId = Math.max(...list.map((p) => p.id.length));
19
+ console.log('ID'.padEnd(maxId + 2) + 'TYPE'.padEnd(8) + 'DETAILS');
20
+ console.log('-'.repeat(maxId + 30));
21
+ for (const p of list) {
22
+ let details = '';
23
+ if (p.type === 'leaf' && p.activeSurfaceId) {
24
+ details = `active surface: ${p.activeSurfaceId}`;
25
+ } else if (p.type === 'branch' && p.direction) {
26
+ details = `direction: ${p.direction}`;
27
+ }
28
+ console.log(p.id.padEnd(maxId + 2) + p.type.padEnd(8) + details);
29
+ }
30
+ }
31
+
32
+ export async function handlePane(
33
+ cmd: string,
34
+ args: string[],
35
+ jsonMode: boolean
36
+ ): Promise<void> {
37
+ let response: RpcResponse;
38
+
39
+ switch (cmd) {
40
+ case 'list-panes': {
41
+ response = await sendRequest('pane.list', {});
42
+ if (jsonMode) {
43
+ printResult(response);
44
+ } else {
45
+ if (!response.ok) { printError(response); return; }
46
+ formatPaneList(response.result);
47
+ }
48
+ break;
49
+ }
50
+
51
+ case 'focus-pane': {
52
+ const id = args[0];
53
+ if (!id) {
54
+ console.error('Error: focus-pane requires <id>');
55
+ process.exit(1);
56
+ }
57
+ response = await sendRequest('pane.focus', { id });
58
+ if (jsonMode) {
59
+ printResult(response);
60
+ } else {
61
+ if (!response.ok) { printError(response); return; }
62
+ console.log(`Focused pane: ${id}`);
63
+ }
64
+ break;
65
+ }
66
+
67
+ case 'split': {
68
+ const direction = parseFlag(args, '--direction') ?? 'right';
69
+ if (direction !== 'right' && direction !== 'down') {
70
+ console.error('Error: --direction must be "right" or "down"');
71
+ process.exit(1);
72
+ }
73
+ // right → horizontal, down → vertical (server expects horizontal/vertical)
74
+ const dirMap: Record<string, string> = { right: 'horizontal', down: 'vertical' };
75
+ const mapped = dirMap[direction] || direction;
76
+ response = await sendRequest('pane.split', { direction: mapped });
77
+ if (jsonMode) {
78
+ printResult(response);
79
+ } else {
80
+ if (!response.ok) { printError(response); return; }
81
+ console.log(`Split pane ${direction}.`);
82
+ }
83
+ break;
84
+ }
85
+
86
+ default:
87
+ console.error(`Unknown pane command: ${cmd}`);
88
+ process.exit(1);
89
+ }
90
+ }
@@ -0,0 +1,102 @@
1
+ import { sendRequest } from '../client';
2
+ import { printResult, printError } from '../utils';
3
+ import type { RpcResponse } from '../../shared/rpc';
4
+
5
+ interface SurfaceInfo {
6
+ id: string;
7
+ title: string;
8
+ shell: string;
9
+ cwd: string;
10
+ }
11
+
12
+ function formatSurfaceList(result: unknown): void {
13
+ const list = result as SurfaceInfo[];
14
+ if (!Array.isArray(list) || list.length === 0) {
15
+ console.log('No surfaces found.');
16
+ return;
17
+ }
18
+ const maxId = Math.max(...list.map((s) => s.id.length));
19
+ const maxTitle = Math.max(...list.map((s) => (s.title ?? '').length), 5);
20
+ console.log(
21
+ 'ID'.padEnd(maxId + 2) +
22
+ 'TITLE'.padEnd(maxTitle + 2) +
23
+ 'SHELL'
24
+ );
25
+ console.log('-'.repeat(maxId + maxTitle + 20));
26
+ for (const s of list) {
27
+ console.log(
28
+ s.id.padEnd(maxId + 2) +
29
+ (s.title ?? '').padEnd(maxTitle + 2) +
30
+ (s.shell ?? '')
31
+ );
32
+ }
33
+ }
34
+
35
+ export async function handleSurface(
36
+ cmd: string,
37
+ args: string[],
38
+ jsonMode: boolean
39
+ ): Promise<void> {
40
+ let response: RpcResponse;
41
+
42
+ switch (cmd) {
43
+ case 'list-surfaces': {
44
+ response = await sendRequest('surface.list', {});
45
+ if (jsonMode) {
46
+ printResult(response);
47
+ } else {
48
+ if (!response.ok) { printError(response); return; }
49
+ formatSurfaceList(response.result);
50
+ }
51
+ break;
52
+ }
53
+
54
+ case 'new-surface': {
55
+ response = await sendRequest('surface.new', {});
56
+ if (jsonMode) {
57
+ printResult(response);
58
+ } else {
59
+ if (!response.ok) { printError(response); return; }
60
+ const s = response.result as SurfaceInfo;
61
+ console.log(`Created surface: ${s?.id ?? '(unknown)'}`);
62
+ }
63
+ break;
64
+ }
65
+
66
+ case 'focus-surface': {
67
+ const id = args[0];
68
+ if (!id) {
69
+ console.error('Error: focus-surface requires <id>');
70
+ process.exit(1);
71
+ }
72
+ response = await sendRequest('surface.focus', { id });
73
+ if (jsonMode) {
74
+ printResult(response);
75
+ } else {
76
+ if (!response.ok) { printError(response); return; }
77
+ console.log(`Focused surface: ${id}`);
78
+ }
79
+ break;
80
+ }
81
+
82
+ case 'close-surface': {
83
+ const id = args[0];
84
+ if (!id) {
85
+ console.error('Error: close-surface requires <id>');
86
+ process.exit(1);
87
+ }
88
+ response = await sendRequest('surface.close', { id });
89
+ if (jsonMode) {
90
+ printResult(response);
91
+ } else {
92
+ if (!response.ok) { printError(response); return; }
93
+ console.log(`Closed surface: ${id}`);
94
+ }
95
+ break;
96
+ }
97
+
98
+ default:
99
+ console.error(`Unknown surface command: ${cmd}`);
100
+ process.exit(1);
101
+ }
102
+ }
@@ -0,0 +1,95 @@
1
+ import { sendRequest } from '../client';
2
+ import { printResult, printError } from '../utils';
3
+ import type { RpcResponse } from '../../shared/rpc';
4
+
5
+ interface IdentifyResult {
6
+ app: string;
7
+ version: string;
8
+ platform: string;
9
+ }
10
+
11
+ export async function handleSystem(
12
+ cmd: string,
13
+ args: string[],
14
+ jsonMode: boolean
15
+ ): Promise<void> {
16
+ let response: RpcResponse;
17
+
18
+ switch (cmd) {
19
+ case 'identify': {
20
+ response = await sendRequest('system.identify', {});
21
+ if (jsonMode) {
22
+ printResult(response);
23
+ } else {
24
+ if (!response.ok) { printError(response); return; }
25
+ const info = response.result as IdentifyResult;
26
+ console.log(`app: ${info?.app ?? 'wmux'}`);
27
+ console.log(`version: ${info?.version ?? '1.0.0'}`);
28
+ console.log(`platform: ${info?.platform ?? process.platform}`);
29
+ }
30
+ break;
31
+ }
32
+
33
+ case 'capabilities': {
34
+ response = await sendRequest('system.capabilities', {});
35
+ if (jsonMode) {
36
+ printResult(response);
37
+ } else {
38
+ if (!response.ok) { printError(response); return; }
39
+ // server may return { methods: string[] } or string[] directly
40
+ const result = response.result as { methods?: string[] } | string[];
41
+ const methods = Array.isArray(result) ? result : (result?.methods || []);
42
+ if (methods.length > 0) {
43
+ console.log('Supported RPC methods:');
44
+ for (const m of methods) {
45
+ console.log(` ${m}`);
46
+ }
47
+ } else {
48
+ console.log(JSON.stringify(response.result, null, 2));
49
+ }
50
+ }
51
+ break;
52
+ }
53
+
54
+ case 'set-status': {
55
+ const text = args[0];
56
+ if (text === undefined) {
57
+ console.error('Error: set-status requires <text>');
58
+ process.exit(1);
59
+ }
60
+ response = await sendRequest('meta.setStatus', { text });
61
+ if (jsonMode) {
62
+ printResult(response);
63
+ } else {
64
+ if (!response.ok) { printError(response); return; }
65
+ console.log(`Status set: "${text}"`);
66
+ }
67
+ break;
68
+ }
69
+
70
+ case 'set-progress': {
71
+ const raw = args[0];
72
+ if (raw === undefined) {
73
+ console.error('Error: set-progress requires <0-100>');
74
+ process.exit(1);
75
+ }
76
+ const value = Number(raw);
77
+ if (isNaN(value) || value < 0 || value > 100) {
78
+ console.error('Error: progress value must be a number between 0 and 100');
79
+ process.exit(1);
80
+ }
81
+ response = await sendRequest('meta.setProgress', { value });
82
+ if (jsonMode) {
83
+ printResult(response);
84
+ } else {
85
+ if (!response.ok) { printError(response); return; }
86
+ console.log(`Progress set: ${value}%`);
87
+ }
88
+ break;
89
+ }
90
+
91
+ default:
92
+ console.error(`Unknown system command: ${cmd}`);
93
+ process.exit(1);
94
+ }
95
+ }
@@ -0,0 +1,116 @@
1
+ import { sendRequest } from '../client';
2
+ import { printResult, printError, parseFlag } from '../utils';
3
+ import type { RpcResponse } from '../../shared/rpc';
4
+
5
+ interface WorkspaceInfo {
6
+ id: string;
7
+ name: string;
8
+ isActive?: boolean;
9
+ }
10
+
11
+ function formatWorkspaceList(result: unknown): void {
12
+ const list = result as WorkspaceInfo[];
13
+ if (!Array.isArray(list) || list.length === 0) {
14
+ console.log('No workspaces found.');
15
+ return;
16
+ }
17
+ const maxId = Math.max(...list.map((w) => w.id.length));
18
+ console.log(
19
+ 'ID'.padEnd(maxId + 2) + 'NAME' + ' ' + 'STATUS'
20
+ );
21
+ console.log('-'.repeat(maxId + 30));
22
+ for (const ws of list) {
23
+ const active = ws.isActive ? ' (active)' : '';
24
+ console.log(ws.id.padEnd(maxId + 2) + ws.name + active);
25
+ }
26
+ }
27
+
28
+ function formatWorkspaceCurrent(result: unknown): void {
29
+ const ws = result as WorkspaceInfo | null;
30
+ if (!ws) {
31
+ console.log('No active workspace.');
32
+ return;
33
+ }
34
+ console.log(`Current workspace: ${ws.name} (${ws.id})`);
35
+ }
36
+
37
+ export async function handleWorkspace(
38
+ cmd: string,
39
+ args: string[],
40
+ jsonMode: boolean
41
+ ): Promise<void> {
42
+ let response: RpcResponse;
43
+
44
+ switch (cmd) {
45
+ case 'list-workspaces': {
46
+ response = await sendRequest('workspace.list', {});
47
+ if (jsonMode) {
48
+ printResult(response);
49
+ } else {
50
+ if (!response.ok) { printError(response); return; }
51
+ formatWorkspaceList(response.result);
52
+ }
53
+ break;
54
+ }
55
+
56
+ case 'new-workspace': {
57
+ const name = parseFlag(args, '--name') ?? `workspace-${Date.now()}`;
58
+ response = await sendRequest('workspace.new', { name });
59
+ if (jsonMode) {
60
+ printResult(response);
61
+ } else {
62
+ if (!response.ok) { printError(response); return; }
63
+ const ws = response.result as WorkspaceInfo;
64
+ console.log(`Created workspace: ${ws?.name ?? name} (${ws?.id ?? ''})`);
65
+ }
66
+ break;
67
+ }
68
+
69
+ case 'focus-workspace': {
70
+ const id = args[0];
71
+ if (!id) {
72
+ console.error('Error: focus-workspace requires <id>');
73
+ process.exit(1);
74
+ }
75
+ response = await sendRequest('workspace.focus', { id });
76
+ if (jsonMode) {
77
+ printResult(response);
78
+ } else {
79
+ if (!response.ok) { printError(response); return; }
80
+ console.log(`Focused workspace: ${id}`);
81
+ }
82
+ break;
83
+ }
84
+
85
+ case 'close-workspace': {
86
+ const id = args[0];
87
+ if (!id) {
88
+ console.error('Error: close-workspace requires <id>');
89
+ process.exit(1);
90
+ }
91
+ response = await sendRequest('workspace.close', { id });
92
+ if (jsonMode) {
93
+ printResult(response);
94
+ } else {
95
+ if (!response.ok) { printError(response); return; }
96
+ console.log(`Closed workspace: ${id}`);
97
+ }
98
+ break;
99
+ }
100
+
101
+ case 'current-workspace': {
102
+ response = await sendRequest('workspace.current', {});
103
+ if (jsonMode) {
104
+ printResult(response);
105
+ } else {
106
+ if (!response.ok) { printError(response); return; }
107
+ formatWorkspaceCurrent(response.result);
108
+ }
109
+ break;
110
+ }
111
+
112
+ default:
113
+ console.error(`Unknown workspace command: ${cmd}`);
114
+ process.exit(1);
115
+ }
116
+ }