lineadraw 0.6.0 → 0.7.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 +16 -13
- package/bin/lineadraw.mjs +276 -277
- package/dist/chunk-NCNZ7A6M.js +7957 -0
- package/dist/chunk-NCNZ7A6M.js.map +1 -0
- package/dist/exportDxf-2HC6UIBS.js +8 -0
- package/dist/exportDxf-2HC6UIBS.js.map +1 -0
- package/dist/{node-OWPSMNLK.js → node-2RH2EKXG.js} +1 -1
- package/dist/node-2RH2EKXG.js.map +1 -0
- package/dist/sdk.d.ts +792 -683
- package/dist/sdk.js +772 -8363
- package/dist/sdk.js.map +1 -1
- package/lineadraw.d.ts +181 -27
- package/package.json +2 -2
- package/templates/README.md +18 -18
- package/dist/node-OWPSMNLK.js.map +0 -1
package/README.md
CHANGED
|
@@ -128,8 +128,7 @@ lineadraw manifest Validate all modules and write .lineadraw/marketplace.json
|
|
|
128
128
|
lineadraw check Validate without writing; fail if the committed manifest is stale
|
|
129
129
|
lineadraw init Add the marketplace structure to the current npm project
|
|
130
130
|
|
|
131
|
-
lineadraw status
|
|
132
|
-
lineadraw editors The open drawings: id, name, path, kind, dirty
|
|
131
|
+
lineadraw status App version, port, the open drawings: id, name, path, kind, dirty
|
|
133
132
|
lineadraw open <file.linea> Open a file in a new window
|
|
134
133
|
lineadraw close [--editor <x>] [--discard]
|
|
135
134
|
lineadraw call <operation> ['<json>'] [--editor <x>] Run one linea_* tool
|
|
@@ -184,7 +183,7 @@ doc.add([
|
|
|
184
183
|
{ type: "dimension", points: [[0, 0], [6000, 0]], offset: [0, -500],
|
|
185
184
|
scale: 50 }, // annotations × drawing scale
|
|
186
185
|
]);
|
|
187
|
-
doc.
|
|
186
|
+
doc.addLayout("Plan 1:50", { sheet: "A3", viewport: { scale: "1:50" } });
|
|
188
187
|
await doc.exportPng("check.png", { width: 1600 }); // verify by LOOKING at it
|
|
189
188
|
await doc.save("plan.linea");
|
|
190
189
|
await doc.exportPdf("plan.pdf");
|
|
@@ -212,7 +211,7 @@ await doc.exportPdf("plan.pdf");
|
|
|
212
211
|
paper size (2.5 body / 3.5 / 5 titles). `scale` is NOT a `styleOverride`
|
|
213
212
|
key; `add`/`update` reject it there.
|
|
214
213
|
- **Reads are detached snapshots**: `doc.document`, `doc.objects`,
|
|
215
|
-
`getObject`, `
|
|
214
|
+
`getObject`, `listObjects()` return deep clones — mutating them does nothing.
|
|
216
215
|
- **New documents are layout-less**; `exportPdf` of a layout-less document
|
|
217
216
|
falls back to one auto-fitted A3 page.
|
|
218
217
|
- `transaction(fn)` groups edits into one undo step. `array(ids, count,
|
|
@@ -229,22 +228,26 @@ await doc.exportPdf("plan.pdf");
|
|
|
229
228
|
`.linea` JSON itself carries no name.
|
|
230
229
|
|
|
231
230
|
- **Read**: `summary()`, `objects`, `layers`, `layouts`, `getObject`,
|
|
232
|
-
`
|
|
231
|
+
`listObjects({ids, type, layer, color, lineType, content, bbox, inside, near, layout})`,
|
|
233
232
|
`measure(id)` (`bboxOf(ids)`, `length(id)`, `area(id)`), `listLayers()`,
|
|
234
|
-
`listLayouts()`, `
|
|
233
|
+
`listLayouts()`, `listTextStyles()`, `listDimStyles()`,
|
|
234
|
+
`listBlockDefinitions()`, `listUserCommands()`, `revision`
|
|
235
235
|
- **Edit**: `add(objects, {layout?})`, `update(ids, patch)` or
|
|
236
236
|
`update([{id, ...patch}])`, `remove(ids)` (→ count), `reorder(ids,
|
|
237
237
|
"front" | "back" | {above} | {below})`, `transaction(fn)` (sync or async;
|
|
238
|
-
a throw rolls back), `undo()`, `redo()`, `
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
238
|
+
a throw rolls back), `undo()`, `redo()`, `applyOps(ops)`, `ready()`
|
|
239
|
+
- **Styles** (by name or id): `listTextStyles()`, `addTextStyle({name, …})`,
|
|
240
|
+
`updateTextStyle(nameOrId, patch)`, `removeTextStyle(nameOrId)` (refused
|
|
241
|
+
while objects use it) — the same four for `DimStyle`; `setTextStyle` /
|
|
242
|
+
`setDimStyle` remain as deprecated aliases of update
|
|
243
|
+
- **Modules**: `await addBlockDefinitions(sources)` / `removeBlockDefinition(id)`,
|
|
244
|
+
`await addUserCommands(sources)` / `removeUserCommand(id)`,
|
|
242
245
|
`await setBlockDefinitions(sources)`, `await setCommands(sources)`
|
|
243
246
|
- **Scripts**: `await runScript(source, inputs?, params?)` runs a
|
|
244
247
|
`defineCommand` module (the same one the editor palette runs) headlessly
|
|
245
248
|
as ONE undo step — its declared `inputs` (points) are supplied up front,
|
|
246
249
|
its declared `params` by name (`{ count: 3 }`; omitted ones take their
|
|
247
|
-
defaults, unknown names and wrong types throw); `
|
|
250
|
+
defaults, unknown names and wrong types throw); `runUserCommand(id, inputs?,
|
|
248
251
|
params?)` runs one of the document's own commands. Returns the script's
|
|
249
252
|
return value.
|
|
250
253
|
- **Layers**: `addLayer`, `updateLayer(nameOrId, patch)`,
|
|
@@ -253,14 +256,14 @@ await doc.exportPdf("plan.pdf");
|
|
|
253
256
|
- **Assets**: `addAsset(dataUrl) → assetId` (for `image` objects;
|
|
254
257
|
content-keyed), `removeAsset(id)` (refused while referenced)
|
|
255
258
|
- **Transform**: `move`, `rotate` (radians), `scale`, `mirror`, `copy`, `array`
|
|
256
|
-
- **Layouts**: `
|
|
259
|
+
- **Layouts**: `addLayout(name, {sheet, scale, viewport, index})`,
|
|
257
260
|
`updateLayout(nameOrId, {name, sheet, printPalette})`, `removeLayout`,
|
|
258
261
|
`reorderLayouts`, `parseScale("1:50")`. `sheet` is `"A3"` (landscape when
|
|
259
262
|
creating, the current orientation when patching), `{ name: "A3",
|
|
260
263
|
orientation: "portrait" }`, or `{ width, height }` in mm.
|
|
261
264
|
- **Export**: `renderSvg`/`exportSvg`, `renderPng`/`exportPng`, `await toDxf()`/`exportDxf`,
|
|
262
265
|
`toDwg`/`exportDwg` (DWG R2000–2018, default AC1027), `renderPdf`/`exportPdf`,
|
|
263
|
-
`
|
|
266
|
+
`toLinea`/`save`
|
|
264
267
|
- **Import (merge)**: `importDxf(path)`/`importDxfText(text)` (with
|
|
265
268
|
`unitOverride`), `importDwg(path)`/`importDwgData(bytes)` (DWG R13–R2018),
|
|
266
269
|
`importPdfData(arrayBuffer)`
|
package/bin/lineadraw.mjs
CHANGED
|
@@ -1,278 +1,277 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// The `lineadraw` CLI — two halves:
|
|
3
|
-
// * marketplace tooling for Linea block/command repos
|
|
4
|
-
// (docs/marketplace-devx.md in the linea repo):
|
|
5
|
-
// lineadraw manifest [--dir <path>] generate .lineadraw/marketplace.json
|
|
6
|
-
// lineadraw check [--dir <path>] validate + fail if the manifest is stale
|
|
7
|
-
// lineadraw init [--dir <path>] add the marketplace structure here
|
|
8
|
-
// * the BRIDGE: what the running Linea desktop app serves on
|
|
9
|
-
// 127.0.0.1:7431 (docs/desktop-bridge.md) — `status`, `
|
|
10
|
-
// `
|
|
11
|
-
// and the agent token; `--port` / `--token` / LINEA_BRIDGE_TOKEN override.
|
|
12
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
13
|
-
import { homedir } from "node:os";
|
|
14
|
-
import { dirname, join, resolve } from "node:path";
|
|
15
|
-
import {
|
|
16
|
-
buildManifest,
|
|
17
|
-
manifestJson,
|
|
18
|
-
MANIFEST_PATH,
|
|
19
|
-
verifyWithSdk,
|
|
20
|
-
withThumbnails,
|
|
21
|
-
} from "../lib/manifest.mjs";
|
|
22
|
-
import { init, tsconfigLibWarning } from "../lib/init.mjs";
|
|
23
|
-
|
|
24
|
-
const USAGE = `Usage: lineadraw <command> [options]
|
|
25
|
-
|
|
26
|
-
Marketplace repos (block / command collections):
|
|
27
|
-
manifest [--dir <path>] Validate all modules and write ${MANIFEST_PATH}
|
|
28
|
-
check [--dir <path>] Validate without writing; fail if the committed manifest is stale
|
|
29
|
-
init [--dir <path>] Add the marketplace structure to the current npm project
|
|
30
|
-
|
|
31
|
-
manifest and check also dry-run every block through lineadraw/sdk and bake
|
|
32
|
-
preview thumbnails into the catalog.
|
|
33
|
-
|
|
34
|
-
Running editors (the Linea desktop app on 127.0.0.1:7431):
|
|
35
|
-
status
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const
|
|
213
|
-
const
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
const
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
const
|
|
257
|
-
for (const f of
|
|
258
|
-
for (const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
}
|
|
2
|
+
// The `lineadraw` CLI — two halves:
|
|
3
|
+
// * marketplace tooling for Linea block/command repos
|
|
4
|
+
// (docs/marketplace-devx.md in the linea repo):
|
|
5
|
+
// lineadraw manifest [--dir <path>] generate .lineadraw/marketplace.json
|
|
6
|
+
// lineadraw check [--dir <path>] validate + fail if the manifest is stale
|
|
7
|
+
// lineadraw init [--dir <path>] add the marketplace structure here
|
|
8
|
+
// * the BRIDGE: what the running Linea desktop app serves on
|
|
9
|
+
// 127.0.0.1:7431 (docs/desktop-bridge.md) — `status`, `open`, `close`,
|
|
10
|
+
// `call`, `tools`. Reads the desktop's bridge.json for the port
|
|
11
|
+
// and the agent token; `--port` / `--token` / LINEA_BRIDGE_TOKEN override.
|
|
12
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
13
|
+
import { homedir } from "node:os";
|
|
14
|
+
import { dirname, join, resolve } from "node:path";
|
|
15
|
+
import {
|
|
16
|
+
buildManifest,
|
|
17
|
+
manifestJson,
|
|
18
|
+
MANIFEST_PATH,
|
|
19
|
+
verifyWithSdk,
|
|
20
|
+
withThumbnails,
|
|
21
|
+
} from "../lib/manifest.mjs";
|
|
22
|
+
import { init, tsconfigLibWarning } from "../lib/init.mjs";
|
|
23
|
+
|
|
24
|
+
const USAGE = `Usage: lineadraw <command> [options]
|
|
25
|
+
|
|
26
|
+
Marketplace repos (block / command collections):
|
|
27
|
+
manifest [--dir <path>] Validate all modules and write ${MANIFEST_PATH}
|
|
28
|
+
check [--dir <path>] Validate without writing; fail if the committed manifest is stale
|
|
29
|
+
init [--dir <path>] Add the marketplace structure to the current npm project
|
|
30
|
+
|
|
31
|
+
manifest and check also dry-run every block through lineadraw/sdk and bake
|
|
32
|
+
preview thumbnails into the catalog.
|
|
33
|
+
|
|
34
|
+
Running editors (the Linea desktop app on 127.0.0.1:7431):
|
|
35
|
+
status app version, port, the open drawings (id, name, path, kind, dirty)
|
|
36
|
+
open <file.linea> open a file in a window of its own (returns its editor)
|
|
37
|
+
close [--editor <x>] [--discard] close an editor like its window's x
|
|
38
|
+
call <operation> ['<json>'] [--editor <x>]
|
|
39
|
+
run one linea_* operation (see tools)
|
|
40
|
+
tools the operations the bridge offers
|
|
41
|
+
|
|
42
|
+
The desktop's bridge.json (port, agent token) is read automatically;
|
|
43
|
+
LINEA_BRIDGE_TOKEN or --token override the token, --port the port.`;
|
|
44
|
+
|
|
45
|
+
const args = process.argv.slice(2);
|
|
46
|
+
const command = args[0];
|
|
47
|
+
const dirFlag = args.indexOf("--dir");
|
|
48
|
+
const dir = resolve(dirFlag === -1 ? "." : (args[dirFlag + 1] ?? "."));
|
|
49
|
+
|
|
50
|
+
/** `--key value` / `--flag` after the command; the rest are positional. */
|
|
51
|
+
const parseArgs = (argv) => {
|
|
52
|
+
const positional = [];
|
|
53
|
+
const flags = {};
|
|
54
|
+
for (let i = 0; i < argv.length; i++) {
|
|
55
|
+
const a = argv[i];
|
|
56
|
+
if (a.startsWith("--")) {
|
|
57
|
+
const key = a.slice(2);
|
|
58
|
+
const next = argv[i + 1];
|
|
59
|
+
if (next !== undefined && !next.startsWith("--")) {
|
|
60
|
+
flags[key] = next;
|
|
61
|
+
i++;
|
|
62
|
+
} else flags[key] = true;
|
|
63
|
+
} else positional.push(a);
|
|
64
|
+
}
|
|
65
|
+
return { positional, flags };
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// --- bridge --------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
const APP_ID = "com.lineadraw.desktop";
|
|
71
|
+
const DEFAULT_PORT = 7431;
|
|
72
|
+
|
|
73
|
+
const bridgeFilePath = () => {
|
|
74
|
+
const home = homedir();
|
|
75
|
+
if (process.platform === "win32")
|
|
76
|
+
return join(
|
|
77
|
+
process.env.APPDATA ?? join(home, "AppData", "Roaming"),
|
|
78
|
+
APP_ID,
|
|
79
|
+
"bridge.json",
|
|
80
|
+
);
|
|
81
|
+
if (process.platform === "darwin")
|
|
82
|
+
return join(home, "Library", "Application Support", APP_ID, "bridge.json");
|
|
83
|
+
return join(
|
|
84
|
+
process.env.XDG_DATA_HOME ?? join(home, ".local", "share"),
|
|
85
|
+
APP_ID,
|
|
86
|
+
"bridge.json",
|
|
87
|
+
);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const readBridgeFile = () => {
|
|
91
|
+
const path = bridgeFilePath();
|
|
92
|
+
if (!existsSync(path)) return {};
|
|
93
|
+
try {
|
|
94
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
95
|
+
} catch {
|
|
96
|
+
return {};
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const bridge = (flags) => {
|
|
101
|
+
const file = readBridgeFile();
|
|
102
|
+
const port =
|
|
103
|
+
typeof flags.port === "string"
|
|
104
|
+
? Number(flags.port)
|
|
105
|
+
: file.port || DEFAULT_PORT;
|
|
106
|
+
const token =
|
|
107
|
+
(typeof flags.token === "string" ? flags.token : undefined) ??
|
|
108
|
+
process.env.LINEA_BRIDGE_TOKEN ??
|
|
109
|
+
file.token;
|
|
110
|
+
const headers = { "content-type": "application/json" };
|
|
111
|
+
if (token) headers.authorization = `Bearer ${token}`;
|
|
112
|
+
return { base: `http://127.0.0.1:${port}`, headers };
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const request = async (b, method, path, body) => {
|
|
116
|
+
let response;
|
|
117
|
+
try {
|
|
118
|
+
response = await fetch(`${b.base}${path}`, {
|
|
119
|
+
method,
|
|
120
|
+
headers: b.headers,
|
|
121
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
122
|
+
});
|
|
123
|
+
} catch (e) {
|
|
124
|
+
const cause = e?.cause?.code ?? e?.cause?.message ?? e?.message ?? e;
|
|
125
|
+
throw new Error(
|
|
126
|
+
`Nothing answers on ${b.base} (${cause}). Is the Linea desktop app running?`,
|
|
127
|
+
{ cause: e },
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
const text = await response.text();
|
|
131
|
+
let parsed = text;
|
|
132
|
+
try {
|
|
133
|
+
parsed = JSON.parse(text);
|
|
134
|
+
} catch {
|
|
135
|
+
// not JSON: keep the text
|
|
136
|
+
}
|
|
137
|
+
if (!response.ok) {
|
|
138
|
+
const error = parsed?.error;
|
|
139
|
+
throw new Error(
|
|
140
|
+
error?.message ?? `HTTP ${response.status}: ${text.slice(0, 200)}`,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
return parsed;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const editorQuery = (flags) =>
|
|
147
|
+
typeof flags.editor === "string"
|
|
148
|
+
? `?editor=${encodeURIComponent(flags.editor)}`
|
|
149
|
+
: "";
|
|
150
|
+
|
|
151
|
+
const bridgeCommand = async (cmd, positional, flags) => {
|
|
152
|
+
const b = bridge(flags);
|
|
153
|
+
switch (cmd) {
|
|
154
|
+
case "status":
|
|
155
|
+
return request(b, "GET", "/status");
|
|
156
|
+
case "open": {
|
|
157
|
+
const file = positional[0];
|
|
158
|
+
if (!file) throw new Error("open needs a .linea file path");
|
|
159
|
+
return request(b, "POST", "/api/linea_open_editor", {
|
|
160
|
+
path: resolve(file),
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
case "close":
|
|
164
|
+
return request(
|
|
165
|
+
b,
|
|
166
|
+
"POST",
|
|
167
|
+
`/api/linea_close_document${editorQuery(flags)}`,
|
|
168
|
+
{ discard: flags.discard === true },
|
|
169
|
+
);
|
|
170
|
+
case "call": {
|
|
171
|
+
const [operation, json] = positional;
|
|
172
|
+
if (!operation)
|
|
173
|
+
throw new Error("call needs an operation name (see `lineadraw tools`)");
|
|
174
|
+
const payload = json === undefined ? {} : JSON.parse(json);
|
|
175
|
+
return request(
|
|
176
|
+
b,
|
|
177
|
+
"POST",
|
|
178
|
+
`/api/${encodeURIComponent(operation)}${editorQuery(flags)}`,
|
|
179
|
+
payload,
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
case "tools": {
|
|
183
|
+
const reply = await request(b, "POST", "/mcp", {
|
|
184
|
+
jsonrpc: "2.0",
|
|
185
|
+
id: 1,
|
|
186
|
+
method: "tools/list",
|
|
187
|
+
});
|
|
188
|
+
return (reply.result?.tools ?? []).map((t) => ({
|
|
189
|
+
name: t.name,
|
|
190
|
+
description: t.description,
|
|
191
|
+
}));
|
|
192
|
+
}
|
|
193
|
+
default:
|
|
194
|
+
throw new Error(`Unknown command: ${cmd}\n\n${USAGE}`);
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const BRIDGE_COMMANDS = ["status", "open", "close", "call", "tools"];
|
|
199
|
+
|
|
200
|
+
const fail = (message) => {
|
|
201
|
+
console.error(message);
|
|
202
|
+
process.exit(1);
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const reportProblems = (problems) => {
|
|
206
|
+
for (const p of problems)
|
|
207
|
+
console.error(`REJECTED ${p.file}: ${p.problems.join("; ")}`);
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
/** Validate everything (light + sdk dry-run); exits on failure. */
|
|
211
|
+
const validatedManifest = async () => {
|
|
212
|
+
const { manifest: base, sources, problems } = buildManifest(dir);
|
|
213
|
+
const sdkResult = await verifyWithSdk(base, sources);
|
|
214
|
+
const all = [...problems, ...sdkResult.problems];
|
|
215
|
+
if (all.length > 0) {
|
|
216
|
+
reportProblems(all);
|
|
217
|
+
fail(`${all.length} problem(s) — nothing written.`);
|
|
218
|
+
}
|
|
219
|
+
const manifest = withThumbnails(base, sdkResult.thumbnails);
|
|
220
|
+
if (!sdkResult.ran)
|
|
221
|
+
console.warn(
|
|
222
|
+
`Blocks were not dry-run: lineadraw/sdk failed to load (${sdkResult.detail}). Try reinstalling lineadraw.`,
|
|
223
|
+
);
|
|
224
|
+
return manifest;
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
switch (command) {
|
|
228
|
+
case "manifest": {
|
|
229
|
+
const manifest = await validatedManifest();
|
|
230
|
+
const outPath = join(dir, MANIFEST_PATH);
|
|
231
|
+
mkdirSync(dirname(outPath), { recursive: true });
|
|
232
|
+
writeFileSync(outPath, manifestJson(manifest));
|
|
233
|
+
console.log(`${MANIFEST_PATH}: ${manifest.entries.length} entries`);
|
|
234
|
+
for (const e of manifest.entries)
|
|
235
|
+
console.log(` ${e.kind} ${e.id} v${e.version}`);
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
case "check": {
|
|
239
|
+
const manifest = await validatedManifest();
|
|
240
|
+
const committedPath = join(dir, MANIFEST_PATH);
|
|
241
|
+
const committed = existsSync(committedPath)
|
|
242
|
+
? readFileSync(committedPath, "utf8")
|
|
243
|
+
: null;
|
|
244
|
+
if (committed !== manifestJson(manifest))
|
|
245
|
+
fail(
|
|
246
|
+
`${MANIFEST_PATH} is ${committed === null ? "missing" : "stale"} — run \`npx lineadraw manifest\` and commit the result.`,
|
|
247
|
+
);
|
|
248
|
+
const libWarning = tsconfigLibWarning(dir);
|
|
249
|
+
if (libWarning !== null) console.warn(`warning: ${libWarning}`);
|
|
250
|
+
console.log(`OK: ${manifest.entries.length} entries, manifest is current.`);
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
case "init": {
|
|
254
|
+
try {
|
|
255
|
+
const { created, skipped, warnings } = await init(dir);
|
|
256
|
+
for (const f of created) console.log(`created ${f}`);
|
|
257
|
+
for (const f of skipped) console.log(`kept ${f}`);
|
|
258
|
+
for (const w of warnings) console.warn(`warning ${w}`);
|
|
259
|
+
console.log(
|
|
260
|
+
"\nDone. Commit, push, then add the repo in the editor: Library → Marketplace.",
|
|
261
|
+
);
|
|
262
|
+
} catch (e) {
|
|
263
|
+
fail(e instanceof Error ? e.message : String(e));
|
|
264
|
+
}
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
default: {
|
|
268
|
+
if (!BRIDGE_COMMANDS.includes(command)) fail(USAGE);
|
|
269
|
+
const { positional, flags } = parseArgs(args.slice(1));
|
|
270
|
+
try {
|
|
271
|
+
const result = await bridgeCommand(command, positional, flags);
|
|
272
|
+
console.log(JSON.stringify(result, null, 2));
|
|
273
|
+
} catch (e) {
|
|
274
|
+
fail(e instanceof Error ? e.message : String(e));
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|