@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.
- package/README.md +88 -0
- package/dist/commands/start.d.ts +0 -2
- package/dist/commands/start.js +15 -112
- package/dist/index.js +162 -28
- package/dist/lib/protocol.d.ts +12 -0
- package/dist/lib/ws-client.d.ts +0 -1
- package/dist/lib/ws-client.js +1 -19
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +38 -1
- package/dist/static/assets/{ar-SA-G6X2FPQ2-DyNcQEG6.js → ar-SA-G6X2FPQ2-B_SxQ-6m.js} +1 -1
- package/dist/static/assets/{arc-BbhT3kpR.js → arc-BHUXEPrZ.js} +1 -1
- package/dist/static/assets/{az-AZ-76LH7QW2-6FRN2HRu.js → az-AZ-76LH7QW2-BlvyWSwk.js} +1 -1
- package/dist/static/assets/{bg-BG-XCXSNQG7-BAatOQKg.js → bg-BG-XCXSNQG7-0r7Wudt8.js} +1 -1
- package/dist/static/assets/{blockDiagram-38ab4fdb-CWMvXzur.js → blockDiagram-38ab4fdb--F6nhgD6.js} +1 -1
- package/dist/static/assets/{bn-BD-2XOGV67Q-CS2Tej4d.js → bn-BD-2XOGV67Q-BZD_Pqhq.js} +1 -1
- package/dist/static/assets/{c4Diagram-3d4e48cf-C7AOUEVw.js → c4Diagram-3d4e48cf-UeDzIq2O.js} +1 -1
- package/dist/static/assets/{ca-ES-6MX7JW3Y-CrRON-ta.js → ca-ES-6MX7JW3Y-CZgdIKMA.js} +1 -1
- package/dist/static/assets/channel-DhjoGFlg.js +1 -0
- package/dist/static/assets/{classDiagram-70f12bd4-Dy_Zdc-c.js → classDiagram-70f12bd4-DeyMeiBu.js} +1 -1
- package/dist/static/assets/{classDiagram-v2-f2320105-CjSxB_UF.js → classDiagram-v2-f2320105-Bk8AeLrr.js} +1 -1
- package/dist/static/assets/clone-ByGN8WN_.js +1 -0
- package/dist/static/assets/{createText-2e5e7dd3-CREXrDg4.js → createText-2e5e7dd3-DnbsK3MN.js} +1 -1
- package/dist/static/assets/{cs-CZ-2BRQDIVT-BLn2zwD5.js → cs-CZ-2BRQDIVT-CLa_VygN.js} +1 -1
- package/dist/static/assets/{da-DK-5WZEPLOC-DTfqWS1m.js → da-DK-5WZEPLOC-BmW_nUSq.js} +1 -1
- package/dist/static/assets/{de-DE-XR44H4JA-CBm33ToK.js → de-DE-XR44H4JA--5rmTjHo.js} +1 -1
- package/dist/static/assets/{edges-e0da2a9e-XV6mdrlk.js → edges-e0da2a9e-CKQsVn7i.js} +1 -1
- package/dist/static/assets/{el-GR-BZB4AONW-CovIUFtq.js → el-GR-BZB4AONW-rKA9Mq4n.js} +1 -1
- package/dist/static/assets/{erDiagram-9861fffd-BHiuXMbj.js → erDiagram-9861fffd-ziHG4YvA.js} +1 -1
- package/dist/static/assets/{es-ES-U4NZUMDT-xhwf3Gvs.js → es-ES-U4NZUMDT-BFm9Ux3a.js} +1 -1
- package/dist/static/assets/{eu-ES-A7QVB2H4-CY-s9u74.js → eu-ES-A7QVB2H4-Cybcrton.js} +1 -1
- package/dist/static/assets/{fa-IR-HGAKTJCU-CqiYv3_M.js → fa-IR-HGAKTJCU-5ZjeXKnE.js} +1 -1
- package/dist/static/assets/{fi-FI-Z5N7JZ37-ByFmziTt.js → fi-FI-Z5N7JZ37-CGEkSJPa.js} +1 -1
- package/dist/static/assets/{flowDb-956e92f1--YaaQ1az.js → flowDb-956e92f1-D736XJvu.js} +1 -1
- package/dist/static/assets/{flowDiagram-66a62f08-CiStLJ_j.js → flowDiagram-66a62f08-CG--9i8T.js} +1 -1
- package/dist/static/assets/flowDiagram-v2-96b9c2cf-B7WIfjiW.js +1 -0
- package/dist/static/assets/{flowchart-elk-definition-4a651766-iZZsNsFB.js → flowchart-elk-definition-4a651766-DAgXbUsa.js} +1 -1
- package/dist/static/assets/{fr-FR-RHASNOE6-BDM0Orh-.js → fr-FR-RHASNOE6-BW-iHr_F.js} +1 -1
- package/dist/static/assets/{ganttDiagram-c361ad54--D8Gb02S.js → ganttDiagram-c361ad54-D0-J_oDc.js} +1 -1
- package/dist/static/assets/{gitGraphDiagram-72cf32ee-BP9V2H_i.js → gitGraphDiagram-72cf32ee-CBCpXDNp.js} +1 -1
- package/dist/static/assets/{gl-ES-HMX3MZ6V-YzWSA_U5.js → gl-ES-HMX3MZ6V-D3cMO8vu.js} +1 -1
- package/dist/static/assets/{graph-CDY5v4hg.js → graph-DUXmdtPb.js} +1 -1
- package/dist/static/assets/{he-IL-6SHJWFNN-DGhuVKeh.js → he-IL-6SHJWFNN-BS8Oszb0.js} +1 -1
- package/dist/static/assets/{hi-IN-IWLTKZ5I-Bf2OxFpo.js → hi-IN-IWLTKZ5I-BxOUYMil.js} +1 -1
- package/dist/static/assets/{hu-HU-A5ZG7DT2-Bgm6MfVV.js → hu-HU-A5ZG7DT2-CaAgi9C1.js} +1 -1
- package/dist/static/assets/{id-ID-SAP4L64H-Be4CWRpp.js → id-ID-SAP4L64H-D5LP6mv6.js} +1 -1
- package/dist/static/assets/{index-3862675e-DO7_p44e.js → index-3862675e-B8wyG3v7.js} +1 -1
- package/dist/static/assets/{index-M3ercR-c.js → index-DiE2gTLf.js} +4 -4
- package/dist/static/assets/{index-D9K5p6xj.js → index-m4FXdstx.js} +33 -33
- package/dist/static/assets/{infoDiagram-f8f76790-R_fZ7_IN.js → infoDiagram-f8f76790-MXSb4zzt.js} +1 -1
- package/dist/static/assets/{it-IT-JPQ66NNP-BNcyVwD2.js → it-IT-JPQ66NNP-LfvTvlu9.js} +1 -1
- package/dist/static/assets/{ja-JP-DBVTYXUO-CLiGkSXX.js → ja-JP-DBVTYXUO-BW2_HfWO.js} +1 -1
- package/dist/static/assets/{journeyDiagram-49397b02-DXa6Hio7.js → journeyDiagram-49397b02-DMZpnQVC.js} +1 -1
- package/dist/static/assets/{kaa-6HZHGXH3-CtCiUe72.js → kaa-6HZHGXH3-BCF2e4sX.js} +1 -1
- package/dist/static/assets/{kab-KAB-ZGHBKWFO-jaWR3c0c.js → kab-KAB-ZGHBKWFO-DvVMe_ub.js} +1 -1
- package/dist/static/assets/{kk-KZ-P5N5QNE5-Dz3mQQBP.js → kk-KZ-P5N5QNE5-C6gtRyOV.js} +1 -1
- package/dist/static/assets/{km-KH-HSX4SM5Z-CQlDP0cg.js → km-KH-HSX4SM5Z-7g9tQRub.js} +1 -1
- package/dist/static/assets/{ko-KR-MTYHY66A-SADoSfWj.js → ko-KR-MTYHY66A-33mKnohU.js} +1 -1
- package/dist/static/assets/{ku-TR-6OUDTVRD-B27fDZ8n.js → ku-TR-6OUDTVRD-C-UWXY18.js} +1 -1
- package/dist/static/assets/{layout-DFkOIywR.js → layout-PGsHwraD.js} +1 -1
- package/dist/static/assets/{line-BkGGz1Gi.js → line-BVSSTePC.js} +1 -1
- package/dist/static/assets/{linear-bj4xbXj8.js → linear-DpEZ-k52.js} +1 -1
- package/dist/static/assets/{lt-LT-XHIRWOB4-CoJ8AQlv.js → lt-LT-XHIRWOB4-D9VkULIO.js} +1 -1
- package/dist/static/assets/{lv-LV-5QDEKY6T-BNu6FZuj.js → lv-LV-5QDEKY6T-DnQtp9DT.js} +1 -1
- package/dist/static/assets/{mindmap-definition-fc14e90a-DmZ8jzp5.js → mindmap-definition-fc14e90a-CwBChg42.js} +1 -1
- package/dist/static/assets/{mr-IN-CRQNXWMA-BvurhLbm.js → mr-IN-CRQNXWMA-DyYnvMP4.js} +1 -1
- package/dist/static/assets/{my-MM-5M5IBNSE-mRTevT97.js → my-MM-5M5IBNSE-BhB1LFZa.js} +1 -1
- package/dist/static/assets/{nb-NO-T6EIAALU-BcOTaqeP.js → nb-NO-T6EIAALU-DJiwQVXx.js} +1 -1
- package/dist/static/assets/{nl-NL-IS3SIHDZ-Bd8Xm5YF.js → nl-NL-IS3SIHDZ-94bfdVTy.js} +1 -1
- package/dist/static/assets/{nn-NO-6E72VCQL-DEvQB3Jv.js → nn-NO-6E72VCQL-BUbOMr7e.js} +1 -1
- package/dist/static/assets/{oc-FR-POXYY2M6--hbNWw9E.js → oc-FR-POXYY2M6-zvcBjRJU.js} +1 -1
- package/dist/static/assets/{pa-IN-N4M65BXN-pIQoan9P.js → pa-IN-N4M65BXN-BS9gYB_p.js} +1 -1
- package/dist/static/assets/{pica-C10VOhpQ.js → pica-LFVYuJpF.js} +1 -1
- package/dist/static/assets/{pieDiagram-8a3498a8-CiEHu0OP.js → pieDiagram-8a3498a8-5zNe-ltL.js} +1 -1
- package/dist/static/assets/{pl-PL-T2D74RX3-DFiZqgo0.js → pl-PL-T2D74RX3-BhVdzySA.js} +1 -1
- package/dist/static/assets/{pt-BR-5N22H2LF-Cc0xlz4q.js → pt-BR-5N22H2LF-B4BClC5Q.js} +1 -1
- package/dist/static/assets/{pt-PT-UZXXM6DQ-B3zd49KM.js → pt-PT-UZXXM6DQ-AUuFdB4a.js} +1 -1
- package/dist/static/assets/{quadrantDiagram-120e2f19-DKTSsmZy.js → quadrantDiagram-120e2f19-DDfqr6q2.js} +1 -1
- package/dist/static/assets/{requirementDiagram-deff3bca-DQOiJiwY.js → requirementDiagram-deff3bca-BY6FvGFs.js} +1 -1
- package/dist/static/assets/{ro-RO-JPDTUUEW-C9N7IthB.js → ro-RO-JPDTUUEW-BJIvvYIf.js} +1 -1
- package/dist/static/assets/{ru-RU-B4JR7IUQ-CIZdU2_B.js → ru-RU-B4JR7IUQ-ChcJqEpE.js} +1 -1
- package/dist/static/assets/{sankeyDiagram-04a897e0-TilwwYL-.js → sankeyDiagram-04a897e0-D2TTpXpT.js} +1 -1
- package/dist/static/assets/{sequenceDiagram-704730f1-BiVJk4lg.js → sequenceDiagram-704730f1-CNVC98Nb.js} +1 -1
- package/dist/static/assets/{si-LK-N5RQ5JYF-CxFkliCr.js → si-LK-N5RQ5JYF-CKI-Rs0y.js} +1 -1
- package/dist/static/assets/{sk-SK-C5VTKIMK-JZtsQcSw.js → sk-SK-C5VTKIMK-CPcecWCV.js} +1 -1
- package/dist/static/assets/{sl-SI-NN7IZMDC-BUYqyq49.js → sl-SI-NN7IZMDC-1y9l4X50.js} +1 -1
- package/dist/static/assets/{stateDiagram-587899a1-B1sCio7H.js → stateDiagram-587899a1-_uOvLGxH.js} +1 -1
- package/dist/static/assets/{stateDiagram-v2-d93cdb3a-CoGzFssB.js → stateDiagram-v2-d93cdb3a-BDVGakN2.js} +1 -1
- package/dist/static/assets/{styles-6aaf32cf-C1s7Ul3J.js → styles-6aaf32cf-Ca0e03mT.js} +1 -1
- package/dist/static/assets/{styles-9a916d00-Bp5-VH98.js → styles-9a916d00-Dz3fuQUX.js} +1 -1
- package/dist/static/assets/{styles-c10674c1-DfO5CrB5.js → styles-c10674c1-BaVrI6jw.js} +1 -1
- package/dist/static/assets/{subset-shared.chunk-QCqXuXCH.js → subset-shared.chunk-2Q6uVsYe.js} +1 -1
- package/dist/static/assets/{subset-worker.chunk-BjKI4tmi.js → subset-worker.chunk-CtGBh8pV.js} +1 -1
- package/dist/static/assets/{sv-SE-XGPEYMSR-Cz1rfwXY.js → sv-SE-XGPEYMSR-9iK0OyDP.js} +1 -1
- package/dist/static/assets/{svgDrawCommon-08f97a94-mI-dkcv_.js → svgDrawCommon-08f97a94-ivBwKijF.js} +1 -1
- package/dist/static/assets/{ta-IN-2NMHFXQM-CtHc6ooj.js → ta-IN-2NMHFXQM-B0jLRmSS.js} +1 -1
- package/dist/static/assets/{th-TH-HPSO5L25-xIQ7sAKy.js → th-TH-HPSO5L25-CiIy1r86.js} +1 -1
- package/dist/static/assets/{timeline-definition-85554ec2-BylSkqlD.js → timeline-definition-85554ec2-BLQl-tu5.js} +1 -1
- package/dist/static/assets/{tr-TR-DEFEU3FU-CrIfozru.js → tr-TR-DEFEU3FU-qzN4ITYa.js} +1 -1
- package/dist/static/assets/{uk-UA-QMV73CPH-Dr07MVsF.js → uk-UA-QMV73CPH-DdDC0F2N.js} +1 -1
- package/dist/static/assets/{vi-VN-M7AON7JQ-ChRYxYRs.js → vi-VN-M7AON7JQ-DqBERZhB.js} +1 -1
- package/dist/static/assets/{xychartDiagram-e933f94c-ByBCDYvY.js → xychartDiagram-e933f94c-E3COBa2c.js} +1 -1
- package/dist/static/assets/{zh-CN-LNUGB5OW-B9Dg6rSU.js → zh-CN-LNUGB5OW-DLiLgAqB.js} +1 -1
- package/dist/static/assets/{zh-HK-E62DVLB3-17W7WnGJ.js → zh-HK-E62DVLB3-BteRs5sw.js} +1 -1
- package/dist/static/assets/{zh-TW-RAJ6MFWO-B1yJY7cJ.js → zh-TW-RAJ6MFWO-AjevN55s.js} +1 -1
- package/dist/static/index.html +1 -1
- package/package.json +9 -2
- package/dist/static/assets/channel-DfDbkfoc.js +0 -1
- package/dist/static/assets/clone-C-cGF6mM.js +0 -1
- 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
|
package/dist/commands/start.d.ts
CHANGED
package/dist/commands/start.js
CHANGED
|
@@ -1,76 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { resolve
|
|
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,
|
|
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
|
-
|
|
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('
|
|
133
|
-
|
|
134
|
-
|
|
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(
|
|
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 {
|
|
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.
|
|
10
|
+
.version('0.2.0');
|
|
11
11
|
program
|
|
12
12
|
.command('start')
|
|
13
|
-
.description('Start the canvas
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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}`);
|
package/dist/lib/protocol.d.ts
CHANGED
|
@@ -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';
|
package/dist/lib/ws-client.d.ts
CHANGED
package/dist/lib/ws-client.js
CHANGED
|
@@ -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
|
}
|
package/dist/server/index.d.ts
CHANGED