@agent-canvas/cli 0.1.0 → 0.2.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 (109) hide show
  1. package/README.md +88 -0
  2. package/dist/commands/start.d.ts +0 -2
  3. package/dist/commands/start.js +15 -112
  4. package/dist/index.js +162 -28
  5. package/dist/lib/protocol.d.ts +12 -0
  6. package/dist/lib/ws-client.d.ts +0 -1
  7. package/dist/lib/ws-client.js +1 -19
  8. package/dist/server/index.d.ts +1 -0
  9. package/dist/server/index.js +38 -1
  10. package/dist/static/assets/{ar-SA-G6X2FPQ2-DyNcQEG6.js → ar-SA-G6X2FPQ2-B_SxQ-6m.js} +1 -1
  11. package/dist/static/assets/{arc-BbhT3kpR.js → arc-BHUXEPrZ.js} +1 -1
  12. package/dist/static/assets/{az-AZ-76LH7QW2-6FRN2HRu.js → az-AZ-76LH7QW2-BlvyWSwk.js} +1 -1
  13. package/dist/static/assets/{bg-BG-XCXSNQG7-BAatOQKg.js → bg-BG-XCXSNQG7-0r7Wudt8.js} +1 -1
  14. package/dist/static/assets/{blockDiagram-38ab4fdb-CWMvXzur.js → blockDiagram-38ab4fdb--F6nhgD6.js} +1 -1
  15. package/dist/static/assets/{bn-BD-2XOGV67Q-CS2Tej4d.js → bn-BD-2XOGV67Q-BZD_Pqhq.js} +1 -1
  16. package/dist/static/assets/{c4Diagram-3d4e48cf-C7AOUEVw.js → c4Diagram-3d4e48cf-UeDzIq2O.js} +1 -1
  17. package/dist/static/assets/{ca-ES-6MX7JW3Y-CrRON-ta.js → ca-ES-6MX7JW3Y-CZgdIKMA.js} +1 -1
  18. package/dist/static/assets/channel-DhjoGFlg.js +1 -0
  19. package/dist/static/assets/{classDiagram-70f12bd4-Dy_Zdc-c.js → classDiagram-70f12bd4-DeyMeiBu.js} +1 -1
  20. package/dist/static/assets/{classDiagram-v2-f2320105-CjSxB_UF.js → classDiagram-v2-f2320105-Bk8AeLrr.js} +1 -1
  21. package/dist/static/assets/clone-ByGN8WN_.js +1 -0
  22. package/dist/static/assets/{createText-2e5e7dd3-CREXrDg4.js → createText-2e5e7dd3-DnbsK3MN.js} +1 -1
  23. package/dist/static/assets/{cs-CZ-2BRQDIVT-BLn2zwD5.js → cs-CZ-2BRQDIVT-CLa_VygN.js} +1 -1
  24. package/dist/static/assets/{da-DK-5WZEPLOC-DTfqWS1m.js → da-DK-5WZEPLOC-BmW_nUSq.js} +1 -1
  25. package/dist/static/assets/{de-DE-XR44H4JA-CBm33ToK.js → de-DE-XR44H4JA--5rmTjHo.js} +1 -1
  26. package/dist/static/assets/{edges-e0da2a9e-XV6mdrlk.js → edges-e0da2a9e-CKQsVn7i.js} +1 -1
  27. package/dist/static/assets/{el-GR-BZB4AONW-CovIUFtq.js → el-GR-BZB4AONW-rKA9Mq4n.js} +1 -1
  28. package/dist/static/assets/{erDiagram-9861fffd-BHiuXMbj.js → erDiagram-9861fffd-ziHG4YvA.js} +1 -1
  29. package/dist/static/assets/{es-ES-U4NZUMDT-xhwf3Gvs.js → es-ES-U4NZUMDT-BFm9Ux3a.js} +1 -1
  30. package/dist/static/assets/{eu-ES-A7QVB2H4-CY-s9u74.js → eu-ES-A7QVB2H4-Cybcrton.js} +1 -1
  31. package/dist/static/assets/{fa-IR-HGAKTJCU-CqiYv3_M.js → fa-IR-HGAKTJCU-5ZjeXKnE.js} +1 -1
  32. package/dist/static/assets/{fi-FI-Z5N7JZ37-ByFmziTt.js → fi-FI-Z5N7JZ37-CGEkSJPa.js} +1 -1
  33. package/dist/static/assets/{flowDb-956e92f1--YaaQ1az.js → flowDb-956e92f1-D736XJvu.js} +1 -1
  34. package/dist/static/assets/{flowDiagram-66a62f08-CiStLJ_j.js → flowDiagram-66a62f08-CG--9i8T.js} +1 -1
  35. package/dist/static/assets/flowDiagram-v2-96b9c2cf-B7WIfjiW.js +1 -0
  36. package/dist/static/assets/{flowchart-elk-definition-4a651766-iZZsNsFB.js → flowchart-elk-definition-4a651766-DAgXbUsa.js} +1 -1
  37. package/dist/static/assets/{fr-FR-RHASNOE6-BDM0Orh-.js → fr-FR-RHASNOE6-BW-iHr_F.js} +1 -1
  38. package/dist/static/assets/{ganttDiagram-c361ad54--D8Gb02S.js → ganttDiagram-c361ad54-D0-J_oDc.js} +1 -1
  39. package/dist/static/assets/{gitGraphDiagram-72cf32ee-BP9V2H_i.js → gitGraphDiagram-72cf32ee-CBCpXDNp.js} +1 -1
  40. package/dist/static/assets/{gl-ES-HMX3MZ6V-YzWSA_U5.js → gl-ES-HMX3MZ6V-D3cMO8vu.js} +1 -1
  41. package/dist/static/assets/{graph-CDY5v4hg.js → graph-DUXmdtPb.js} +1 -1
  42. package/dist/static/assets/{he-IL-6SHJWFNN-DGhuVKeh.js → he-IL-6SHJWFNN-BS8Oszb0.js} +1 -1
  43. package/dist/static/assets/{hi-IN-IWLTKZ5I-Bf2OxFpo.js → hi-IN-IWLTKZ5I-BxOUYMil.js} +1 -1
  44. package/dist/static/assets/{hu-HU-A5ZG7DT2-Bgm6MfVV.js → hu-HU-A5ZG7DT2-CaAgi9C1.js} +1 -1
  45. package/dist/static/assets/{id-ID-SAP4L64H-Be4CWRpp.js → id-ID-SAP4L64H-D5LP6mv6.js} +1 -1
  46. package/dist/static/assets/{index-3862675e-DO7_p44e.js → index-3862675e-B8wyG3v7.js} +1 -1
  47. package/dist/static/assets/{index-M3ercR-c.js → index-DiE2gTLf.js} +4 -4
  48. package/dist/static/assets/{index-D9K5p6xj.js → index-m4FXdstx.js} +33 -33
  49. package/dist/static/assets/{infoDiagram-f8f76790-R_fZ7_IN.js → infoDiagram-f8f76790-MXSb4zzt.js} +1 -1
  50. package/dist/static/assets/{it-IT-JPQ66NNP-BNcyVwD2.js → it-IT-JPQ66NNP-LfvTvlu9.js} +1 -1
  51. package/dist/static/assets/{ja-JP-DBVTYXUO-CLiGkSXX.js → ja-JP-DBVTYXUO-BW2_HfWO.js} +1 -1
  52. package/dist/static/assets/{journeyDiagram-49397b02-DXa6Hio7.js → journeyDiagram-49397b02-DMZpnQVC.js} +1 -1
  53. package/dist/static/assets/{kaa-6HZHGXH3-CtCiUe72.js → kaa-6HZHGXH3-BCF2e4sX.js} +1 -1
  54. package/dist/static/assets/{kab-KAB-ZGHBKWFO-jaWR3c0c.js → kab-KAB-ZGHBKWFO-DvVMe_ub.js} +1 -1
  55. package/dist/static/assets/{kk-KZ-P5N5QNE5-Dz3mQQBP.js → kk-KZ-P5N5QNE5-C6gtRyOV.js} +1 -1
  56. package/dist/static/assets/{km-KH-HSX4SM5Z-CQlDP0cg.js → km-KH-HSX4SM5Z-7g9tQRub.js} +1 -1
  57. package/dist/static/assets/{ko-KR-MTYHY66A-SADoSfWj.js → ko-KR-MTYHY66A-33mKnohU.js} +1 -1
  58. package/dist/static/assets/{ku-TR-6OUDTVRD-B27fDZ8n.js → ku-TR-6OUDTVRD-C-UWXY18.js} +1 -1
  59. package/dist/static/assets/{layout-DFkOIywR.js → layout-PGsHwraD.js} +1 -1
  60. package/dist/static/assets/{line-BkGGz1Gi.js → line-BVSSTePC.js} +1 -1
  61. package/dist/static/assets/{linear-bj4xbXj8.js → linear-DpEZ-k52.js} +1 -1
  62. package/dist/static/assets/{lt-LT-XHIRWOB4-CoJ8AQlv.js → lt-LT-XHIRWOB4-D9VkULIO.js} +1 -1
  63. package/dist/static/assets/{lv-LV-5QDEKY6T-BNu6FZuj.js → lv-LV-5QDEKY6T-DnQtp9DT.js} +1 -1
  64. package/dist/static/assets/{mindmap-definition-fc14e90a-DmZ8jzp5.js → mindmap-definition-fc14e90a-CwBChg42.js} +1 -1
  65. package/dist/static/assets/{mr-IN-CRQNXWMA-BvurhLbm.js → mr-IN-CRQNXWMA-DyYnvMP4.js} +1 -1
  66. package/dist/static/assets/{my-MM-5M5IBNSE-mRTevT97.js → my-MM-5M5IBNSE-BhB1LFZa.js} +1 -1
  67. package/dist/static/assets/{nb-NO-T6EIAALU-BcOTaqeP.js → nb-NO-T6EIAALU-DJiwQVXx.js} +1 -1
  68. package/dist/static/assets/{nl-NL-IS3SIHDZ-Bd8Xm5YF.js → nl-NL-IS3SIHDZ-94bfdVTy.js} +1 -1
  69. package/dist/static/assets/{nn-NO-6E72VCQL-DEvQB3Jv.js → nn-NO-6E72VCQL-BUbOMr7e.js} +1 -1
  70. package/dist/static/assets/{oc-FR-POXYY2M6--hbNWw9E.js → oc-FR-POXYY2M6-zvcBjRJU.js} +1 -1
  71. package/dist/static/assets/{pa-IN-N4M65BXN-pIQoan9P.js → pa-IN-N4M65BXN-BS9gYB_p.js} +1 -1
  72. package/dist/static/assets/{pica-C10VOhpQ.js → pica-LFVYuJpF.js} +1 -1
  73. package/dist/static/assets/{pieDiagram-8a3498a8-CiEHu0OP.js → pieDiagram-8a3498a8-5zNe-ltL.js} +1 -1
  74. package/dist/static/assets/{pl-PL-T2D74RX3-DFiZqgo0.js → pl-PL-T2D74RX3-BhVdzySA.js} +1 -1
  75. package/dist/static/assets/{pt-BR-5N22H2LF-Cc0xlz4q.js → pt-BR-5N22H2LF-B4BClC5Q.js} +1 -1
  76. package/dist/static/assets/{pt-PT-UZXXM6DQ-B3zd49KM.js → pt-PT-UZXXM6DQ-AUuFdB4a.js} +1 -1
  77. package/dist/static/assets/{quadrantDiagram-120e2f19-DKTSsmZy.js → quadrantDiagram-120e2f19-DDfqr6q2.js} +1 -1
  78. package/dist/static/assets/{requirementDiagram-deff3bca-DQOiJiwY.js → requirementDiagram-deff3bca-BY6FvGFs.js} +1 -1
  79. package/dist/static/assets/{ro-RO-JPDTUUEW-C9N7IthB.js → ro-RO-JPDTUUEW-BJIvvYIf.js} +1 -1
  80. package/dist/static/assets/{ru-RU-B4JR7IUQ-CIZdU2_B.js → ru-RU-B4JR7IUQ-ChcJqEpE.js} +1 -1
  81. package/dist/static/assets/{sankeyDiagram-04a897e0-TilwwYL-.js → sankeyDiagram-04a897e0-D2TTpXpT.js} +1 -1
  82. package/dist/static/assets/{sequenceDiagram-704730f1-BiVJk4lg.js → sequenceDiagram-704730f1-CNVC98Nb.js} +1 -1
  83. package/dist/static/assets/{si-LK-N5RQ5JYF-CxFkliCr.js → si-LK-N5RQ5JYF-CKI-Rs0y.js} +1 -1
  84. package/dist/static/assets/{sk-SK-C5VTKIMK-JZtsQcSw.js → sk-SK-C5VTKIMK-CPcecWCV.js} +1 -1
  85. package/dist/static/assets/{sl-SI-NN7IZMDC-BUYqyq49.js → sl-SI-NN7IZMDC-1y9l4X50.js} +1 -1
  86. package/dist/static/assets/{stateDiagram-587899a1-B1sCio7H.js → stateDiagram-587899a1-_uOvLGxH.js} +1 -1
  87. package/dist/static/assets/{stateDiagram-v2-d93cdb3a-CoGzFssB.js → stateDiagram-v2-d93cdb3a-BDVGakN2.js} +1 -1
  88. package/dist/static/assets/{styles-6aaf32cf-C1s7Ul3J.js → styles-6aaf32cf-Ca0e03mT.js} +1 -1
  89. package/dist/static/assets/{styles-9a916d00-Bp5-VH98.js → styles-9a916d00-Dz3fuQUX.js} +1 -1
  90. package/dist/static/assets/{styles-c10674c1-DfO5CrB5.js → styles-c10674c1-BaVrI6jw.js} +1 -1
  91. package/dist/static/assets/{subset-shared.chunk-QCqXuXCH.js → subset-shared.chunk-2Q6uVsYe.js} +1 -1
  92. package/dist/static/assets/{subset-worker.chunk-BjKI4tmi.js → subset-worker.chunk-CtGBh8pV.js} +1 -1
  93. package/dist/static/assets/{sv-SE-XGPEYMSR-Cz1rfwXY.js → sv-SE-XGPEYMSR-9iK0OyDP.js} +1 -1
  94. package/dist/static/assets/{svgDrawCommon-08f97a94-mI-dkcv_.js → svgDrawCommon-08f97a94-ivBwKijF.js} +1 -1
  95. package/dist/static/assets/{ta-IN-2NMHFXQM-CtHc6ooj.js → ta-IN-2NMHFXQM-B0jLRmSS.js} +1 -1
  96. package/dist/static/assets/{th-TH-HPSO5L25-xIQ7sAKy.js → th-TH-HPSO5L25-CiIy1r86.js} +1 -1
  97. package/dist/static/assets/{timeline-definition-85554ec2-BylSkqlD.js → timeline-definition-85554ec2-BLQl-tu5.js} +1 -1
  98. package/dist/static/assets/{tr-TR-DEFEU3FU-CrIfozru.js → tr-TR-DEFEU3FU-qzN4ITYa.js} +1 -1
  99. package/dist/static/assets/{uk-UA-QMV73CPH-Dr07MVsF.js → uk-UA-QMV73CPH-DdDC0F2N.js} +1 -1
  100. package/dist/static/assets/{vi-VN-M7AON7JQ-ChRYxYRs.js → vi-VN-M7AON7JQ-DqBERZhB.js} +1 -1
  101. package/dist/static/assets/{xychartDiagram-e933f94c-ByBCDYvY.js → xychartDiagram-e933f94c-E3COBa2c.js} +1 -1
  102. package/dist/static/assets/{zh-CN-LNUGB5OW-B9Dg6rSU.js → zh-CN-LNUGB5OW-DLiLgAqB.js} +1 -1
  103. package/dist/static/assets/{zh-HK-E62DVLB3-17W7WnGJ.js → zh-HK-E62DVLB3-BteRs5sw.js} +1 -1
  104. package/dist/static/assets/{zh-TW-RAJ6MFWO-B1yJY7cJ.js → zh-TW-RAJ6MFWO-AjevN55s.js} +1 -1
  105. package/dist/static/index.html +1 -1
  106. package/package.json +9 -2
  107. package/dist/static/assets/channel-DfDbkfoc.js +0 -1
  108. package/dist/static/assets/clone-C-cGF6mM.js +0 -1
  109. package/dist/static/assets/flowDiagram-v2-96b9c2cf-D8VcptSb.js +0 -1
package/README.md ADDED
@@ -0,0 +1,88 @@
1
+ # @agent-canvas/cli
2
+
3
+ A CLI tool that provides an Excalidraw canvas interface for AI agents.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g @agent-canvas/cli
9
+ ```
10
+
11
+ ## Features
12
+
13
+ - **Browser mode** (default): Opens Excalidraw in your browser
14
+ - **Electron mode** (optional): Desktop app with `--app` flag
15
+ - Full drawing capabilities: shapes, text, lines, arrows, polygons
16
+ - Element manipulation: move, rotate, group, delete
17
+ - File I/O: load and save .excalidraw files
18
+ - PNG export with scale, dark mode, and embed scene options
19
+
20
+ ## Usage
21
+
22
+ ### Start Canvas
23
+
24
+ ```bash
25
+ # Browser mode (default)
26
+ agent-canvas start
27
+ agent-canvas start -f diagram.excalidraw
28
+
29
+ # Electron mode (requires @agent-canvas/electron-app)
30
+ agent-canvas start --app
31
+ ```
32
+
33
+ ### Drawing
34
+
35
+ ```bash
36
+ # Add shapes
37
+ agent-canvas add-shape -t rectangle -x 100 -y 100 -w 200 -h 100 --background-color "#FFA07A" -l "My Box"
38
+ agent-canvas add-shape -t ellipse -x 300 -y 100 -w 100 -h 100
39
+ agent-canvas add-shape -t diamond -x 500 -y 100 -w 100 -h 100
40
+
41
+ # Add text
42
+ agent-canvas add-text -t "Hello World" -x 100 -y 300 --font-size 24
43
+
44
+ # Add lines and arrows
45
+ agent-canvas add-line -x 100 -y 400 --end-x 300 --end-y 400
46
+ agent-canvas add-arrow -x 100 -y 500 --end-x 300 --end-y 500
47
+
48
+ # Add polygon
49
+ agent-canvas add-polygon -p '[{"x":0,"y":0},{"x":100,"y":0},{"x":50,"y":100}]'
50
+ ```
51
+
52
+ ### Element Manipulation
53
+
54
+ ```bash
55
+ # Delete elements (supports batch)
56
+ agent-canvas delete-elements -i <id1>,<id2>,<id3>
57
+
58
+ # Rotate elements (degrees, positive = clockwise)
59
+ agent-canvas rotate-elements -i <id1>,<id2> -a 45
60
+
61
+ # Move elements
62
+ agent-canvas move-elements -i <id1>,<id2> --delta-x 50 --delta-y 100
63
+
64
+ # Group/ungroup
65
+ agent-canvas group-elements -i <id1>,<id2>,<id3>
66
+ agent-canvas ungroup-element -i <element-id>
67
+ ```
68
+
69
+ ### Read & Export
70
+
71
+ ```bash
72
+ # Read scene (TOON format - token efficient)
73
+ agent-canvas read
74
+
75
+ # Read scene (JSON format)
76
+ agent-canvas read --json
77
+
78
+ # Save to file
79
+ agent-canvas save diagram.excalidraw
80
+
81
+ # Export to PNG
82
+ agent-canvas export -o output.png
83
+ agent-canvas export -o output.png --scale 2 --dark --no-background
84
+ ```
85
+
86
+ ## License
87
+
88
+ MIT
@@ -1,3 +1 @@
1
- export declare function startBrowser(filePath?: string): Promise<void>;
2
- export declare function startApp(filePath?: string): Promise<void>;
3
1
  export declare function start(filePath?: string): Promise<void>;
@@ -1,76 +1,10 @@
1
- import { spawn, exec } from 'child_process';
2
- import { resolve, dirname } from 'path';
3
- import { fileURLToPath } from 'url';
1
+ import { exec } from 'child_process';
2
+ import { resolve } from 'path';
4
3
  import { readFileSync, existsSync } from 'fs';
5
4
  import { promisify } from 'util';
6
- import { connectToCanvas, isCanvasRunning, generateId } from '../lib/ws-client';
7
- import { startServer, isBrowserServerRunning } from '../server';
5
+ import { connectToCanvas, generateId } from '../lib/ws-client.js';
6
+ import { startServer, isBrowserServerRunning, isBrowserConnected } from '../server/index.js';
8
7
  const execAsync = promisify(exec);
9
- const __dirname = dirname(fileURLToPath(import.meta.url));
10
- function getDevElectronAppPath() {
11
- // Check if running in monorepo dev mode
12
- const devPath = resolve(__dirname, '../../../electron-app');
13
- if (existsSync(resolve(devPath, 'package.json'))) {
14
- return devPath;
15
- }
16
- return null;
17
- }
18
- async function findElectronAppCommand() {
19
- // Check if agent-canvas-app command is available
20
- try {
21
- const cmd = process.platform === 'win32' ? 'where' : 'which';
22
- await execAsync(`${cmd} agent-canvas-app`);
23
- return 'agent-canvas-app';
24
- }
25
- catch {
26
- return null;
27
- }
28
- }
29
- async function launchElectronApp() {
30
- // First, try to find installed electron-app command
31
- const appCommand = await findElectronAppCommand();
32
- if (appCommand) {
33
- // Use installed electron-app
34
- const child = spawn(appCommand, [], {
35
- detached: true,
36
- stdio: 'ignore',
37
- });
38
- child.unref();
39
- }
40
- else {
41
- // Check if running in dev mode (monorepo)
42
- const devPath = getDevElectronAppPath();
43
- if (devPath) {
44
- const child = spawn('bun', ['run', 'dev'], {
45
- cwd: devPath,
46
- detached: true,
47
- stdio: 'ignore',
48
- });
49
- child.unref();
50
- }
51
- else {
52
- // electron-app not found
53
- console.error('Electron app not found.');
54
- console.error('');
55
- console.error('To use --app mode, install the electron app:');
56
- console.error(' npm install -g @agent-canvas/electron-app');
57
- console.error('');
58
- console.error('Or use browser mode (default):');
59
- console.error(' agent-canvas start');
60
- process.exit(1);
61
- }
62
- }
63
- // Wait for app to start
64
- const maxRetries = 30;
65
- const retryInterval = 500;
66
- for (let i = 0; i < maxRetries; i++) {
67
- await new Promise((r) => setTimeout(r, retryInterval));
68
- if (await isCanvasRunning()) {
69
- return;
70
- }
71
- }
72
- throw new Error('Failed to start electron app');
73
- }
74
8
  async function openBrowser(url) {
75
9
  const platform = process.platform;
76
10
  try {
@@ -118,8 +52,7 @@ async function loadFile(filePath) {
118
52
  process.exit(1);
119
53
  }
120
54
  }
121
- // Browser mode (default)
122
- export async function startBrowser(filePath) {
55
+ export async function start(filePath) {
123
56
  if (filePath) {
124
57
  const absolutePath = resolve(filePath);
125
58
  if (!existsSync(absolutePath)) {
@@ -128,16 +61,19 @@ export async function startBrowser(filePath) {
128
61
  }
129
62
  }
130
63
  const running = await isBrowserServerRunning();
131
- if (running) {
132
- console.log('Canvas server already running');
133
- console.log('Opening browser...');
134
- await openBrowser('http://localhost:7891');
64
+ if (!running) {
65
+ console.log('Starting canvas server...');
66
+ await startServer();
67
+ }
68
+ // Give existing browser tabs a moment to reconnect (browser reconnects every 1s)
69
+ await new Promise((r) => setTimeout(r, 1500));
70
+ const browserConnected = await isBrowserConnected();
71
+ if (browserConnected) {
72
+ console.log('Canvas already running at http://localhost:7891');
135
73
  }
136
74
  else {
137
- console.log('Starting canvas server...');
138
- const { httpUrl } = await startServer();
139
75
  console.log('Opening browser...');
140
- await openBrowser(httpUrl);
76
+ await openBrowser('http://localhost:7891');
141
77
  }
142
78
  // Wait for browser to connect, then load file if specified
143
79
  if (filePath) {
@@ -162,36 +98,3 @@ export async function startBrowser(filePath) {
162
98
  console.log('\nCanvas is ready. Press Ctrl+C to stop.');
163
99
  await new Promise(() => { }); // Block forever
164
100
  }
165
- // Electron app mode (--app)
166
- export async function startApp(filePath) {
167
- if (filePath) {
168
- const absolutePath = resolve(filePath);
169
- if (!existsSync(absolutePath)) {
170
- console.error(`File not found: ${absolutePath}`);
171
- process.exit(1);
172
- }
173
- }
174
- console.log('Checking if canvas app is running...');
175
- const running = await isCanvasRunning();
176
- if (!running) {
177
- console.log('Starting canvas app...');
178
- await launchElectronApp();
179
- }
180
- console.log('Connecting to canvas app...');
181
- try {
182
- const client = await connectToCanvas();
183
- console.log('Connected to canvas app');
184
- if (filePath) {
185
- await loadFile(filePath);
186
- }
187
- client.close();
188
- }
189
- catch (err) {
190
- console.error('Failed to connect:', err instanceof Error ? err.message : err);
191
- process.exit(1);
192
- }
193
- }
194
- // Legacy export for backward compatibility
195
- export async function start(filePath) {
196
- return startBrowser(filePath);
197
- }
package/dist/index.js CHANGED
@@ -1,25 +1,19 @@
1
1
  import { Command } from 'commander';
2
2
  import { writeFileSync } from 'node:fs';
3
3
  import { encode as toToon } from '@toon-format/toon';
4
- import { startBrowser, startApp } from './commands/start';
5
- import { connectToCanvas, generateId } from './lib/ws-client';
4
+ import { start } from './commands/start.js';
5
+ import { connectToCanvas, generateId } from './lib/ws-client.js';
6
6
  const program = new Command();
7
7
  program
8
8
  .name('agent-canvas')
9
9
  .description('CLI for Agent Canvas - Excalidraw interface for AI agents')
10
- .version('0.1.0');
10
+ .version('0.2.0');
11
11
  program
12
12
  .command('start')
13
- .description('Start the canvas (browser mode by default, use --app for Electron)')
13
+ .description('Start the canvas server and open in browser')
14
14
  .option('-f, --file <path>', 'Load an .excalidraw file on start')
15
- .option('--app', 'Use Electron app instead of browser')
16
15
  .action(async (options) => {
17
- if (options.app) {
18
- await startApp(options.file);
19
- }
20
- else {
21
- await startBrowser(options.file);
22
- }
16
+ await start(options.file);
23
17
  });
24
18
  // ============================================================================
25
19
  // Add Shape
@@ -39,6 +33,7 @@ program
39
33
  .option('--fill-style <style>', 'Fill style: hachure, cross-hatch, solid, or zigzag')
40
34
  .option('-l, --label <text>', 'Text label inside the shape')
41
35
  .option('--label-font-size <number>', 'Label font size', parseFloat)
36
+ .option('-n, --note <text>', 'Note for this element (stored in customData)')
42
37
  .action(async (options) => {
43
38
  const client = await connectToCanvas();
44
39
  const params = {
@@ -52,6 +47,7 @@ program
52
47
  strokeWidth: options.strokeWidth,
53
48
  strokeStyle: options.strokeStyle,
54
49
  fillStyle: options.fillStyle,
50
+ customData: options.note ? { note: options.note } : undefined,
55
51
  };
56
52
  if (options.label) {
57
53
  params.label = { text: options.label, fontSize: options.labelFontSize };
@@ -78,6 +74,7 @@ program
78
74
  .option('--font-size <number>', 'Font size', parseFloat)
79
75
  .option('--text-align <align>', 'Text alignment: left, center, or right')
80
76
  .option('--stroke-color <color>', 'Text color (hex)')
77
+ .option('-n, --note <text>', 'Note for this element (stored in customData)')
81
78
  .action(async (options) => {
82
79
  const client = await connectToCanvas();
83
80
  const result = await client.send({
@@ -90,6 +87,7 @@ program
90
87
  fontSize: options.fontSize,
91
88
  textAlign: options.textAlign,
92
89
  strokeColor: options.strokeColor,
90
+ customData: options.note ? { note: options.note } : undefined,
93
91
  },
94
92
  });
95
93
  if (result.success) {
@@ -114,6 +112,7 @@ program
114
112
  .option('--stroke-color <color>', 'Line color (hex)')
115
113
  .option('--stroke-width <number>', 'Line width in pixels', parseFloat)
116
114
  .option('--stroke-style <style>', 'Line style: solid, dashed, or dotted')
115
+ .option('-n, --note <text>', 'Note for this element (stored in customData)')
117
116
  .action(async (options) => {
118
117
  const client = await connectToCanvas();
119
118
  const result = await client.send({
@@ -127,6 +126,7 @@ program
127
126
  strokeColor: options.strokeColor,
128
127
  strokeWidth: options.strokeWidth,
129
128
  strokeStyle: options.strokeStyle,
129
+ customData: options.note ? { note: options.note } : undefined,
130
130
  },
131
131
  });
132
132
  if (result.success) {
@@ -153,6 +153,7 @@ program
153
153
  .option('--stroke-style <style>', 'Arrow style: solid, dashed, or dotted')
154
154
  .option('--start-arrowhead <type>', 'Start arrowhead: arrow, bar, dot, triangle, diamond, none')
155
155
  .option('--end-arrowhead <type>', 'End arrowhead: arrow, bar, dot, triangle, diamond, none')
156
+ .option('-n, --note <text>', 'Note for this element (stored in customData)')
156
157
  .action(async (options) => {
157
158
  const client = await connectToCanvas();
158
159
  const result = await client.send({
@@ -168,6 +169,7 @@ program
168
169
  strokeStyle: options.strokeStyle,
169
170
  startArrowhead: options.startArrowhead,
170
171
  endArrowhead: options.endArrowhead,
172
+ customData: options.note ? { note: options.note } : undefined,
171
173
  },
172
174
  });
173
175
  if (result.success) {
@@ -191,6 +193,7 @@ program
191
193
  .option('--stroke-width <number>', 'Stroke width in pixels', parseFloat)
192
194
  .option('--stroke-style <style>', 'Stroke style: solid, dashed, or dotted')
193
195
  .option('--fill-style <style>', 'Fill style: hachure, cross-hatch, solid, or zigzag')
196
+ .option('-n, --note <text>', 'Note for this element (stored in customData)')
194
197
  .action(async (options) => {
195
198
  let points;
196
199
  try {
@@ -211,6 +214,7 @@ program
211
214
  strokeWidth: options.strokeWidth,
212
215
  strokeStyle: options.strokeStyle,
213
216
  fillStyle: options.fillStyle,
217
+ customData: options.note ? { note: options.note } : undefined,
214
218
  },
215
219
  });
216
220
  if (result.success) {
@@ -350,31 +354,161 @@ program
350
354
  program
351
355
  .command('read')
352
356
  .description('Read all elements from the canvas (TOON format by default)')
353
- .option('--json', 'Output as JSON')
357
+ .option('--json', 'Output raw Excalidraw scene JSON')
358
+ .option('--with-style', 'Include style info (stroke, bg) in TOON output')
354
359
  .action(async (options) => {
355
360
  const client = await connectToCanvas();
361
+ if (options.json) {
362
+ // Return raw Excalidraw scene data
363
+ const result = await client.send({
364
+ type: 'saveScene',
365
+ id: generateId(),
366
+ });
367
+ if (result.success && result.data) {
368
+ console.log(JSON.stringify(result.data, null, 2));
369
+ }
370
+ else {
371
+ console.error(`Failed: ${result.error}`);
372
+ process.exit(1);
373
+ }
374
+ client.close();
375
+ return;
376
+ }
356
377
  const result = await client.send({
357
378
  type: 'readScene',
358
379
  id: generateId(),
359
380
  });
360
381
  if (result.success && result.elements) {
361
- if (options.json) {
362
- console.log(JSON.stringify(result.elements, null, 2));
363
- }
364
- else {
365
- // Simplify elements for TOON output
366
- const elements = result.elements.map(el => ({
367
- id: el.id,
368
- type: el.type,
369
- x: Math.round(el.x),
370
- y: Math.round(el.y),
371
- ...(el.width !== undefined && { w: Math.round(el.width) }),
372
- ...(el.height !== undefined && { h: Math.round(el.height) }),
373
- ...(el.text && { text: el.text }),
374
- ...(el.groupIds?.length && { groups: el.groupIds }),
375
- }));
376
- console.log(toToon({ elements }));
382
+ const withStyle = options.withStyle;
383
+ // Separate elements into shapes, lines, labels (bound text), texts (standalone text), and groups
384
+ const shapes = [];
385
+ const lines = [];
386
+ const labels = [];
387
+ const texts = [];
388
+ const groupsMap = new Map(); // groupId -> elementIds
389
+ for (const el of result.elements) {
390
+ const angle = el.angle ? Math.round(el.angle * 180 / Math.PI) : 0; // Convert radians to degrees
391
+ // Collect group memberships
392
+ if (el.groupIds?.length) {
393
+ for (const groupId of el.groupIds) {
394
+ if (!groupsMap.has(groupId)) {
395
+ groupsMap.set(groupId, []);
396
+ }
397
+ groupsMap.get(groupId).push(el.id);
398
+ }
399
+ }
400
+ if (el.type === 'text') {
401
+ if (el.containerId) {
402
+ // Bound text (label)
403
+ labels.push({
404
+ id: el.id,
405
+ containerId: el.containerId,
406
+ content: el.text ?? '',
407
+ x: Math.round(el.x),
408
+ y: Math.round(el.y),
409
+ w: el.width !== undefined ? Math.round(el.width) : null,
410
+ h: el.height !== undefined ? Math.round(el.height) : null,
411
+ });
412
+ }
413
+ else {
414
+ // Standalone text
415
+ const text = {
416
+ id: el.id,
417
+ content: el.text ?? '',
418
+ x: Math.round(el.x),
419
+ y: Math.round(el.y),
420
+ w: el.width !== undefined ? Math.round(el.width) : null,
421
+ h: el.height !== undefined ? Math.round(el.height) : null,
422
+ angle,
423
+ };
424
+ if (withStyle) {
425
+ text.stroke = el.strokeColor ?? null;
426
+ }
427
+ texts.push(text);
428
+ }
429
+ }
430
+ else if (el.type === 'line' || el.type === 'arrow') {
431
+ const pts = el.points ?? [];
432
+ // Check if it's a closed polygon (first and last point within 8px threshold)
433
+ const isPolygon = el.type === 'line' && pts.length >= 3 && (() => {
434
+ const first = pts[0];
435
+ const last = pts[pts.length - 1];
436
+ const distance = Math.sqrt((last[0] - first[0]) ** 2 + (last[1] - first[1]) ** 2);
437
+ return distance <= 8;
438
+ })();
439
+ if (isPolygon) {
440
+ // Polygon - treat as shape
441
+ // Calculate bounding box from points
442
+ const xs = pts.map(p => p[0]);
443
+ const ys = pts.map(p => p[1]);
444
+ const minX = Math.min(...xs);
445
+ const maxX = Math.max(...xs);
446
+ const minY = Math.min(...ys);
447
+ const maxY = Math.max(...ys);
448
+ const shape = {
449
+ id: el.id,
450
+ type: 'polygon',
451
+ x: Math.round(el.x + minX),
452
+ y: Math.round(el.y + minY),
453
+ w: Math.round(maxX - minX),
454
+ h: Math.round(maxY - minY),
455
+ angle,
456
+ labelId: null,
457
+ note: el.customData?.note ?? null,
458
+ };
459
+ if (withStyle) {
460
+ shape.stroke = el.strokeColor ?? null;
461
+ shape.bg = el.backgroundColor ?? null;
462
+ }
463
+ shapes.push(shape);
464
+ }
465
+ else {
466
+ // Line/Arrow element
467
+ const lastPt = pts.length > 0 ? pts[pts.length - 1] : [0, 0];
468
+ const line = {
469
+ id: el.id,
470
+ type: el.type,
471
+ x: Math.round(el.x),
472
+ y: Math.round(el.y),
473
+ endX: Math.round(el.x + lastPt[0]),
474
+ endY: Math.round(el.y + lastPt[1]),
475
+ points: pts.length,
476
+ angle,
477
+ note: el.customData?.note ?? null,
478
+ };
479
+ if (withStyle) {
480
+ line.stroke = el.strokeColor ?? null;
481
+ }
482
+ lines.push(line);
483
+ }
484
+ }
485
+ else {
486
+ // Shape element (rectangle, ellipse, diamond, etc.)
487
+ const boundText = el.boundElements?.find(b => b.type === 'text');
488
+ const shape = {
489
+ id: el.id,
490
+ type: el.type,
491
+ x: Math.round(el.x),
492
+ y: Math.round(el.y),
493
+ w: el.width !== undefined ? Math.round(el.width) : null,
494
+ h: el.height !== undefined ? Math.round(el.height) : null,
495
+ angle,
496
+ labelId: boundText?.id ?? null,
497
+ note: el.customData?.note ?? null,
498
+ };
499
+ if (withStyle) {
500
+ shape.stroke = el.strokeColor ?? null;
501
+ shape.bg = el.backgroundColor ?? null;
502
+ }
503
+ shapes.push(shape);
504
+ }
377
505
  }
506
+ // Build groups array
507
+ const groups = Array.from(groupsMap.entries()).map(([id, elementIds]) => ({
508
+ id,
509
+ elementIds: elementIds.join(','),
510
+ }));
511
+ console.log(toToon({ shapes, lines, labels, texts, groups }));
378
512
  }
379
513
  else {
380
514
  console.error(`Failed: ${result.error}`);
@@ -17,6 +17,7 @@ export interface AddShapeParams {
17
17
  verticalAlign?: 'top' | 'middle' | 'bottom';
18
18
  strokeColor?: string;
19
19
  };
20
+ customData?: Record<string, unknown>;
20
21
  }
21
22
  export interface AddShapeResponse {
22
23
  type: 'addShapeResult';
@@ -32,6 +33,7 @@ export interface AddTextParams {
32
33
  fontSize?: number;
33
34
  textAlign?: 'left' | 'center' | 'right';
34
35
  strokeColor?: string;
36
+ customData?: Record<string, unknown>;
35
37
  }
36
38
  export interface AddTextResponse {
37
39
  type: 'addTextResult';
@@ -48,6 +50,7 @@ export interface AddLineParams {
48
50
  strokeColor?: string;
49
51
  strokeWidth?: number;
50
52
  strokeStyle?: 'solid' | 'dashed' | 'dotted';
53
+ customData?: Record<string, unknown>;
51
54
  }
52
55
  export interface AddLineResponse {
53
56
  type: 'addLineResult';
@@ -66,6 +69,7 @@ export interface AddArrowParams {
66
69
  strokeStyle?: 'solid' | 'dashed' | 'dotted';
67
70
  startArrowhead?: 'arrow' | 'bar' | 'dot' | 'triangle' | 'diamond' | 'none';
68
71
  endArrowhead?: 'arrow' | 'bar' | 'dot' | 'triangle' | 'diamond' | 'none';
72
+ customData?: Record<string, unknown>;
69
73
  }
70
74
  export interface AddArrowResponse {
71
75
  type: 'addArrowResult';
@@ -84,6 +88,7 @@ export interface AddPolygonParams {
84
88
  strokeWidth?: number;
85
89
  strokeStyle?: 'solid' | 'dashed' | 'dotted';
86
90
  fillStyle?: 'hachure' | 'cross-hatch' | 'solid' | 'zigzag';
91
+ customData?: Record<string, unknown>;
87
92
  }
88
93
  export interface AddPolygonResponse {
89
94
  type: 'addPolygonResult';
@@ -144,6 +149,10 @@ export interface MoveElementsResponse {
144
149
  movedCount?: number;
145
150
  error?: string;
146
151
  }
152
+ export interface BoundElement {
153
+ id: string;
154
+ type: 'arrow' | 'text';
155
+ }
147
156
  export interface SceneElement {
148
157
  id: string;
149
158
  type: string;
@@ -157,9 +166,12 @@ export interface SceneElement {
157
166
  groupIds?: string[];
158
167
  text?: string;
159
168
  fontSize?: number;
169
+ containerId?: string | null;
170
+ boundElements?: BoundElement[] | null;
160
171
  points?: number[][];
161
172
  startArrowhead?: string | null;
162
173
  endArrowhead?: string | null;
174
+ customData?: Record<string, unknown>;
163
175
  }
164
176
  export interface ReadSceneResponse {
165
177
  type: 'readSceneResult';
@@ -9,5 +9,4 @@ export interface WsClient {
9
9
  }) => Promise<T>;
10
10
  }
11
11
  export declare function connectToCanvas(timeout?: number): Promise<WsClient>;
12
- export declare function isCanvasRunning(): Promise<boolean>;
13
12
  export declare function generateId(): string;
@@ -1,5 +1,5 @@
1
1
  import WebSocket from 'ws';
2
- import { WS_PORT } from './protocol';
2
+ import { WS_PORT } from './protocol.js';
3
3
  const WS_URL = `ws://localhost:${WS_PORT}`;
4
4
  // Pending requests waiting for response
5
5
  const pendingRequests = new Map();
@@ -65,24 +65,6 @@ export function connectToCanvas(timeout = 5000) {
65
65
  });
66
66
  });
67
67
  }
68
- export function isCanvasRunning() {
69
- return new Promise((resolve) => {
70
- const ws = new WebSocket(WS_URL);
71
- const timer = setTimeout(() => {
72
- ws.close();
73
- resolve(false);
74
- }, 1000);
75
- ws.on('open', () => {
76
- clearTimeout(timer);
77
- ws.close();
78
- resolve(true);
79
- });
80
- ws.on('error', () => {
81
- clearTimeout(timer);
82
- resolve(false);
83
- });
84
- });
85
- }
86
68
  export function generateId() {
87
69
  return Math.random().toString(36).substring(2, 15);
88
70
  }
@@ -4,3 +4,4 @@ export declare function startServer(): Promise<{
4
4
  close: () => void;
5
5
  }>;
6
6
  export declare function isBrowserServerRunning(): Promise<boolean>;
7
+ export declare function isBrowserConnected(): Promise<boolean>;