lineadraw 0.7.0 → 0.9.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 CHANGED
@@ -1,177 +1,29 @@
1
1
  # lineadraw
2
2
 
3
- [Linea](https://lineadraw.com) for Node: the headless drawing SDK, the
4
- typings for blocks and commands, and the marketplace CLI — the one
5
- dependency a script, an agent, or a block/command repository needs.
3
+ [Linea](https://lineadraw.com) for Node — one package, four jobs:
6
4
 
7
- - **Typings** — `lineadraw.d.ts` makes `import { defineBlock } from
8
- "lineadraw"` and `import { … } from "lineadraw/helpers"` type-check in
9
- any editor/CI, with parameter types inferred into your `draw` function.
10
- - **SDK** — `import { openDrawing } from "lineadraw/sdk"`: open, edit,
11
- render and export `.linea` documents headlessly
12
- ([below](#headless-sdk-lineadrawsdk)).
13
- - **CLI** — `lineadraw manifest | check | init` turns any repository into
14
- a Linea **marketplace**: a git repo the editor can browse and install
15
- blocks/commands from.
16
-
17
- ## Quick start
18
-
19
- Scaffold a fresh marketplace repository:
20
-
21
- ```bash
22
- npm create lineadraw my-blocks
23
- ```
24
-
25
- Or add the structure to an existing project:
5
+ | Job | What you get | Entry point |
6
+ | --- | --- | --- |
7
+ | [**Drawing SDK**](#1-drawing-sdk) | Open, create, edit, render and export `.linea` / DXF / DWG / PDF / SVG / PNG headlessly — for scripts, agents and batch tools. Also drives drawings open in the desktop app. | `import { openDrawing } from "lineadraw/sdk"` |
8
+ | [**Extension development**](#2-extension-development) | Typings for parametric **blocks** and macro **commands**: `defineBlock` / `defineCommand` type-check in any editor, with parameter types inferred. | `import { defineBlock } from "lineadraw"` |
9
+ | [**Marketplace management**](#3-marketplace-management) | Turn a git repo into a Linea **marketplace** the editor can browse and install from: scaffold it, validate every module, generate the catalog, check it in CI. | `lineadraw init` · `manifest` · `check` |
10
+ | [**CLI for the desktop app**](#4-cli-for-the-running-desktop-app) | Talk to the running Linea desktop app from a terminal: list open drawings, open and close files, run any `linea_*` operation. | `lineadraw status` · `open` · `close` · `call` · `tools` |
26
11
 
27
12
  ```bash
28
- npm i -D lineadraw
29
- npx lineadraw init
30
- ```
31
-
32
- Either way you get `blocks/` and `commands/` folders, a `tsconfig.json`
33
- wired to the typings, a sample block, a CI workflow, and a generated
34
- catalog (`.lineadraw/marketplace.json`) — the repo is addable in the
35
- editor from the first push.
36
-
37
- ## Writing a block
38
-
39
- A block is one module with a single `defineBlock` default export:
40
- typed parameters in, geometry out.
41
-
42
- ```ts
43
- // blocks/surface.block.ts
44
- import { defineBlock } from "lineadraw";
45
-
46
- export default defineBlock({
47
- // The id is the block's identity everywhere: @<collection>/<slug>.
48
- // Re-publishing the same id updates installed copies in place.
49
- id: "@my-blocks/surface",
50
- name: "Surface",
51
- version: "1.0.0",
52
- params: [
53
- { name: "kind", type: "enum", default: "rock", options: ["rock", "ground"] },
54
- ],
55
- place: ["Start point", "End point"], // one point pick per label, in order
56
- draw: ({ params, inputs: [start, end] }) => {
57
- params.kind; // "rock" | "ground" — inferred from the params table
58
- return [{ type: "line", a: start, b: end }];
59
- },
60
- });
13
+ npm i lineadraw # the SDK in a script or tool
14
+ npm i -D lineadraw # typings + CLI in a block/command repository
15
+ npm create lineadraw my-blocks # a fresh marketplace repository
61
16
  ```
62
17
 
63
- Notable optional members: `place` may instead be an interactive
64
- `({ params, pickPoint, pickObject })` function run once at insertion;
65
- `dependencies` lists ids of blocks your `draw` emits instances of (installs
66
- pull them along); `paramVisibility({ params })` hides property rows;
67
- `previewInputs` curates the inputs used for thumbnails. Units are
68
- millimeters, Y-up, angles in radians CCW.
69
-
70
- Parameter types (the same rows serve a command's `params`):
18
+ Requires Node.js >= 20. Units everywhere are millimeters, Y-up, angles in
19
+ radians CCW.
71
20
 
72
- - `number` — `min`/`max`, `integer`, `step`, `prefix`/`suffix` ("1:",
73
- "mm"), `presets` (standard values in the field's dropdown).
74
- - `string` — `placeholder`, `multiline`, `presets`.
75
- - `boolean` (`display: "toggle"` — a toggle button showing the label,
76
- compact in a row), `enum` (`options`, each optionally with a `swatch:
77
- "#rrggbb"` color square; `display: "buttons"` — a segmented toggle group
78
- for short choices, falling back to a dropdown when it doesn't fit),
79
- `color` (`"#rrggbb"`, `presets` = a palette).
80
- - `numberList` (`number[]`, typed Tekla-style: `"0 2*7200"` →
81
- `[0, 7200, 7200]`) and `stringList` (`string[]`: `'A "B 2"'` →
82
- `["A", "B 2"]`), both with `presets` and `placeholder` (shown while
83
- empty).
84
- - Pickers over the document's resources, valued by id / name:
85
- `textStyle`, `dimStyle`, `font`, `lineType`, `hatchPattern`.
86
- - `label` — a section heading (no value); `row` — 2–3 compact params on one
87
- panel row (`items`, each label a short prefix like "W"); its items are
88
- ordinary flat params (`params.w`).
89
-
90
- Every param may carry a `hint` (the label's tooltip). An instance that
91
- overrides a param shows "Reset" beside it in Properties, back to your
92
- default. `draw` also receives
93
- `styles`: `styles.text(id)` / `styles.dim(id)` give a document style (an
94
- empty or unknown id → the first), for blocks that lay out with real text
95
- sizes.
96
-
97
- ## Writing a command
98
-
99
- A command is a macro with a single `defineCommand` default export. Its
100
- whole run is one undo step; Esc during a pick reverts cleanly. A
101
- `params` table (the same rows a block's `params` uses) is asked for in a
102
- dialog before the run and arrives typed as `ctx.params`; headless callers
103
- pass it by name.
104
-
105
- ```ts
106
- // commands/circle-here.cmd.ts
107
- import { defineCommand } from "lineadraw";
108
-
109
- export default defineCommand({
110
- id: "@my-blocks/circle-here",
111
- name: "Circle here",
112
- run: async ({ document, pickPoint, prompt, showToast }) => {
113
- const center = await pickPoint("Center point");
114
- const r = await prompt("Radius");
115
- document.add([{ type: "circle", center, radius: Number.parseFloat(r) }]);
116
- showToast("Done", "success");
117
- },
118
- });
119
- ```
120
-
121
- Imports are limited to `"lineadraw"` and `"lineadraw/helpers"` (geometry
122
- helpers) — the editor resolves both when executing your module.
123
-
124
- ## The CLI
125
-
126
- ```
127
- lineadraw manifest Validate all modules and write .lineadraw/marketplace.json
128
- lineadraw check Validate without writing; fail if the committed manifest is stale
129
- lineadraw init Add the marketplace structure to the current npm project
130
-
131
- lineadraw status App version, port, the open drawings: id, name, path, kind, dirty
132
- lineadraw open <file.linea> Open a file in a new window
133
- lineadraw close [--editor <x>] [--discard]
134
- lineadraw call <operation> ['<json>'] [--editor <x>] Run one linea_* tool
135
- lineadraw tools The operations the bridge offers
136
- ```
137
-
138
- The bridge commands talk to the running Linea desktop app on
139
- `127.0.0.1:7431` (the app's `bridge.json` supplies the port and the agent
140
- token; `--port`, `--token` and `LINEA_BRIDGE_TOKEN` override) — see
141
- docs/desktop-bridge.md in the linea repo.
142
-
143
- The manifest is deterministic (repo-relative paths, no git metadata), so
144
- `check` is a byte-for-byte diff — the included CI workflow runs it on every
145
- push and PR. `manifest`/`check` also **dry-run every block** through
146
- `lineadraw/sdk` and render thumbnails.
147
-
148
- Collection name and owner come from your package.json:
149
-
150
- ```json
151
- { "lineadraw": { "marketplaceName": "My blocks", "marketplaceOwner": "Me" } }
152
- ```
153
-
154
- ## Using a marketplace in the editor
155
-
156
- In [the editor](https://lineadraw.com): **Library → Marketplace tab → the
157
- gear button (Manage marketplaces…) → Add a marketplace** and enter
158
- `owner/repo` (or `owner/repo@branch`). For a private repository,
159
- create a fine-grained personal access token with read-only Contents access
160
- to just that repository and paste it in the add dialog — it is stored only
161
- on that device. Installing embeds the module (and its declared dependency
162
- closure) in the document, so drawings keep working offline and travel with
163
- the file.
164
-
165
- ## Headless SDK (`lineadraw/sdk`)
21
+ ## 1. Drawing SDK
166
22
 
167
23
  A script-friendly API over the Linea CAD kernel: open, create, query, edit,
168
24
  render, and import/export `.linea` documents headlessly in Node — no browser,
169
25
  no server. Built for agents, CLIs, and batch tooling.
170
26
 
171
- ```bash
172
- npm i lineadraw
173
- ```
174
-
175
27
  ```js
176
28
  import { newDrawing, openDrawing } from "lineadraw/sdk";
177
29
 
@@ -180,7 +32,7 @@ const walls = doc.addLayer({ name: "walls", color: "#e8e8e8", lineWidth: 0.5 });
180
32
  doc.add([
181
33
  { type: "polyline", layerId: walls, closed: true,
182
34
  points: [[0, 0], [6000, 0], [6000, 3000], [0, 3000]] },
183
- { type: "dimension", points: [[0, 0], [6000, 0]], offset: [0, -500],
35
+ { type: "dimension", points: [[0, 0], [6000, 0]], offset: -500,
184
36
  scale: 50 }, // annotations × drawing scale
185
37
  ]);
186
38
  doc.addLayout("Plan 1:50", { sheet: "A3", viewport: { scale: "1:50" } });
@@ -221,7 +73,11 @@ await doc.exportPdf("plan.pdf");
221
73
 
222
74
  ### API surface
223
75
 
224
- `newDrawing(name?)` / `drawingFromJson(text)` / `openDrawing(path)` → `Drawing`:
76
+ `newDrawing(name?, options?)` / `drawingFromJson(text)` / `openDrawing(path, options?)`
77
+ → `Drawing`; `options.marketplaces` lists the marketplaces to search and
78
+ install from (`{ kind: "github", spec: "owner/repo[@branch]", token? }` or
79
+ `{ kind: "folder", folder }`, priority order; the public collection is
80
+ appended when missing):
225
81
 
226
82
  - **Name**: `drawing.name` is the file name (set on `openDrawing`, updated
227
83
  by `save(path)`); `newDrawing(name)` names it until it is saved. The
@@ -238,11 +94,23 @@ await doc.exportPdf("plan.pdf");
238
94
  a throw rolls back), `undo()`, `redo()`, `applyOps(ops)`, `ready()`
239
95
  - **Styles** (by name or id): `listTextStyles()`, `addTextStyle({name, …})`,
240
96
  `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
97
+ while objects use it) — the same four for `DimStyle`
98
+ - **Line types / hatch patterns** (the document's own definitions, by
99
+ name, resolved ahead of the built-in catalogue): `listLineTypes()`,
100
+ `addLineTypes([{name, dashesMm}])`, `updateLineType(name, patch)`,
101
+ `removeLineType(name)`; `listHatchPatterns()`,
102
+ `addHatchPatterns([{name, lines}])`, `updateHatchPattern(name, patch)`,
103
+ `removeHatchPattern(name)`
243
104
  - **Modules**: `await addBlockDefinitions(sources)` / `removeBlockDefinition(id)`,
244
105
  `await addUserCommands(sources)` / `removeUserCommand(id)`,
245
106
  `await setBlockDefinitions(sources)`, `await setCommands(sources)`
107
+ - **Marketplaces**: `listMarketplaces()`, `await addMarketplaces(list)`
108
+ (verifies each catalog), `removeMarketplaces(names)`,
109
+ `await searchMarketplaces({ query?, kind?, marketplace? })` (entries with
110
+ install status), `await installMarketplaceEntries(ids, marketplace?)`
111
+ (declared block dependencies first, one undo step) — then insert a block
112
+ with `add([{ type: "block", definitionId }])`. Marketplaces are process
113
+ configuration, never written to the file.
246
114
  - **Scripts**: `await runScript(source, inputs?, params?)` runs a
247
115
  `defineCommand` module (the same one the editor palette runs) headlessly
248
116
  as ONE undo step — its declared `inputs` (points) are supplied up front,
@@ -253,8 +121,10 @@ await doc.exportPdf("plan.pdf");
253
121
  - **Layers**: `addLayer`, `updateLayer(nameOrId, patch)`,
254
122
  `removeLayer(nameOrId)` (must be empty and not current),
255
123
  `reorderLayers(order)`, `setCurrentLayer(nameOrId)`
256
- - **Assets**: `addAsset(dataUrl) → assetId` (for `image` objects;
257
- content-keyed), `removeAsset(id)` (refused while referenced)
124
+ - **Assets**: `addAsset(dataUrl) → assetId` (content-keyed; an embedded
125
+ reference names it), `listAssets()`, `removeAsset(id)` (refused while
126
+ referenced)
127
+ - **Settings**: `updateSettings(patch)` (document settings, e.g. units)
258
128
  - **Transform**: `move`, `rotate` (radians), `scale`, `mirror`, `copy`, `array`
259
129
  - **Layouts**: `addLayout(name, {sheet, scale, viewport, index})`,
260
130
  `updateLayout(nameOrId, {name, sheet, printPalette})`, `removeLayout`,
@@ -268,57 +138,85 @@ await doc.exportPdf("plan.pdf");
268
138
  `unitOverride`), `importDwg(path)`/`importDwgData(bytes)` (DWG R13–R2018),
269
139
  `importPdfData(arrayBuffer)`
270
140
 
271
- Full parameter docs are in the bundled `dist/index.d.ts` JSDoc.
141
+ Full parameter docs are in the bundled `dist/sdk.d.ts` JSDoc.
272
142
 
273
143
  ### Object types
274
144
 
275
145
  `line` (a, b) · `polyline` (points with optional bulge, closed) · `circle` ·
276
146
  `arc` (CCW start→end) · `ellipse` (center, majorAxis vector, ratio) ·
277
- `hatch` (loops — outer + holes, even-odd; fill solid/pattern/lines) ·
147
+ `point` (p) · `hatch` (loops — outer + holes, even-odd; fill solid/pattern/lines) ·
278
148
  `text` (position, content; **default alignment center/center**; optional
279
149
  leaderLines with the arrowhead at index 0) · `dimension` (linear/angular/
280
- radial/diameter; `offset` is a vector from points[0] to the dimension line;
281
- linear with >2 points = chain) · `block` (instances of script definitions) ·
282
- `viewport` (layouts only) · `image` (placement of an asset).
283
-
284
- ### Block scripts
285
-
286
- `document.blockDefinitions` is an array of ES-module **source strings**
287
- (`await doc.setBlockDefinitions([...sources])` — async so the script
288
- transpiler is loaded and block geometry is definitive when it resolves).
289
- A block is one `defineBlock` call:
150
+ radial/diameter; linear: `offset` is the signed distance of the dimension
151
+ line from points[0] (left of the measuring direction positive) and
152
+ `rotation` an optional locked measuring direction; angular: `offset` is the
153
+ signed arc radius; `labels[i]` place and word each segment's text —
154
+ `along`/`across` in mm, unset = automatic, `content` with `{value}` for the
155
+ measurement; linear with >2 points = chain) · `block` (instances of script definitions) ·
156
+ `table` (columns, rows, cells) · `comment` (a review bubble, never
157
+ printed) · `reference` (a linked or embedded file: `.linea`, DXF, DWG,
158
+ PDF, PNG/JPEG — a picture is a reference whose `source` is
159
+ `{ kind: "asset", assetId }`) · `viewport` (layouts only).
160
+
161
+ ### Block definitions in a document
162
+
163
+ `document.blockDefinitions` is an array of ES-module **source strings** —
164
+ the same `defineBlock` modules described under
165
+ [Extension development](#2-extension-development). Load them with
166
+ `await doc.setBlockDefinitions([...sources])` or `addBlockDefinitions`
167
+ (async, so the script transpiler is loaded and block geometry is
168
+ definitive when it resolves), then place instances with
169
+ `add([{ type: "block", definitionId }])`. An instance's first point input
170
+ is its pivot: `draw` sees inputs localized to it, and the instance's
171
+ `rotation`/`scale` apply about it.
172
+
173
+ ### Running editors (the desktop bridge)
174
+
175
+ `Drawing` works on a document in your own process. To work on a drawing
176
+ that is OPEN in the Linea desktop app (or a web tab paired with it), connect
177
+ to the app's bridge on `127.0.0.1:7431`:
290
178
 
291
179
  ```ts
292
- import { defineBlock } from "lineadraw";
293
- import { polar, add } from "lineadraw/helpers"; // vector-math helpers
180
+ import { connectEditors } from "lineadraw/sdk";
294
181
 
295
- export default defineBlock({
296
- id: "chair", // instances reference this
297
- name: "Chair",
298
- params: [{ name: "width", type: "number", default: 450 }],
299
- draw: ({ params }) => [
300
- // Plain object DTOs in block-local coordinates; params.width: number.
301
- { type: "line", a: { x: 0, y: 0 }, b: { x: params.width, y: 0 } },
302
- ],
182
+ const linea = await connectEditors(); // port + agent token from the app's bridge.json
183
+ const [first] = await linea.editors(); // { id, name, path, kind, readOnly, dirty }
184
+ const plan = linea.editor(first.id); // by document id, file path or unique name
185
+
186
+ // One linea_* operation = one undo step in that editor.
187
+ await plan.call("linea_add_objects", {
188
+ objects: [{ type: "circle", center: [0, 0], radius: 500 }],
303
189
  });
190
+
191
+ // A whole command module, run INSIDE the editor against the full Drawing
192
+ // API: one round trip, one undo step. `inputs` / `params` answer the
193
+ // module's declared ones.
194
+ const result = await plan.runScript(
195
+ `import { defineCommand } from "lineadraw";
196
+ export default defineCommand({
197
+ id: "count-lines",
198
+ name: "Count lines",
199
+ run: ({ document }) => document.listObjects({ type: "line" }).length,
200
+ });`,
201
+ );
202
+
203
+ const other = await linea.open("details.linea"); // a window of its own
204
+ await linea.editor(other.id).close({ discard: true });
304
205
  ```
305
206
 
306
- Optional members: `place` — an array of point labels (one pick per label)
307
- or an interactive `({ params, pickPoint, pickObject })` function run once
308
- at insertion (default is one insertion point) — and
309
- `paramVisibility({ params })` (hides property rows). `params` may be the
310
- table literal or a zero-arg function returning it. Imports are limited to
311
- `"lineadraw"` and `"lineadraw/helpers"`. The instance's first point input
312
- is its pivot: `draw` sees inputs localized to it, and instance
313
- `rotation`/`scale` apply about it. (Plain named exports — incl. the
314
- pre-rename `main`/`defineInput` names — remain evaluatable as the frozen
315
- wire form.)
207
+ The operations are the same `linea_*` catalog the MCP server and the CLI
208
+ offer (`await linea.tools()` lists them). Failures throw a `BridgeError`
209
+ whose `code` says why: `unreachable` (the app isn't running),
210
+ `ambiguous` (several editors are open and none was named — `details`
211
+ lists them), `noSuchEditor`, `readOnly`, `validation`… The connection
212
+ options (`port`, `token`, `bridgeFile`) override what bridge.json says;
213
+ `LINEA_BRIDGE_TOKEN` supplies a token too.
316
214
 
317
215
  ### Interchange notes
318
216
 
319
217
  - DXF/DWG export flattens semantics: block instances are exported as their
320
- evaluated geometry (no `INSERT` reuse) and `image` objects are skipped
321
- (DXF images reference external files). The export result reports both in
218
+ evaluated geometry (no `INSERT` reuse), and embedded references are
219
+ skipped (DXF can't embed a file). The export result reports both in
322
220
  `skipped`.
323
221
  - PDF import extracts vector strokes and text runs (dimensions arrive as
324
222
  exploded geometry). pdf.js loads lazily on first use, as do the PNG
@@ -330,10 +228,185 @@ wire form.)
330
228
  - Dimensions validate their point counts per kind (linear ≥ 2, angular ≥ 3,
331
229
  radial/diameter ≥ 2).
332
230
 
231
+ ## 2. Extension development
232
+
233
+ Blocks and commands are plain TypeScript (or JavaScript) modules. The
234
+ package's `lineadraw.d.ts` types the two module names a script may import —
235
+ `"lineadraw"` (`defineBlock`, `defineCommand`) and `"lineadraw/helpers"`
236
+ (geometry helpers) — so authoring type-checks in VS Code, any other
237
+ TypeScript-aware editor, and CI. The editor resolves both names when it
238
+ runs your module; nothing else can be imported. `lineadraw init` writes a
239
+ `tsconfig.json` wired to the typings.
240
+
241
+ A module works on its own too: drop a `.block.ts` / `.cmd.ts` file onto the
242
+ editor, or exercise it headlessly with the SDK
243
+ (`await doc.addBlockDefinitions([source])`, `await doc.runScript(source)`).
244
+
245
+ ### Writing a block
246
+
247
+ A block is one module with a single `defineBlock` default export:
248
+ typed parameters in, geometry out.
249
+
250
+ ```ts
251
+ // blocks/surface.block.ts
252
+ import { defineBlock } from "lineadraw";
253
+
254
+ export default defineBlock({
255
+ // The id is the block's identity everywhere: @<collection>/<slug>.
256
+ // Re-publishing the same id updates installed copies in place.
257
+ id: "@my-blocks/surface",
258
+ name: "Surface",
259
+ version: "1.0.0",
260
+ params: [
261
+ { name: "kind", type: "enum", default: "rock", options: ["rock", "ground"] },
262
+ ],
263
+ place: ["Start point", "End point"], // one point pick per label, in order
264
+ draw: ({ params, inputs: [start, end] }) => {
265
+ params.kind; // "rock" | "ground" — inferred from the params table
266
+ return [{ type: "line", a: start, b: end }];
267
+ },
268
+ });
269
+ ```
270
+
271
+ Notable optional members: `place` may instead be an interactive
272
+ `({ params, pickPoint, pickObject })` function run once at insertion;
273
+ `dependencies` lists ids of blocks your `draw` emits instances of (installs
274
+ pull them along); `paramVisibility({ params })` hides property rows;
275
+ `previewInputs` curates the inputs used for thumbnails. Units are
276
+ millimeters, Y-up, angles in radians CCW.
277
+
278
+ Parameter types (the same rows serve a command's `params`):
279
+
280
+ - `number` — `min`/`max`, `integer`, `step`, `prefix`/`suffix` ("1:",
281
+ "mm"), `presets` (standard values in the field's dropdown).
282
+ - `string` — `placeholder`, `multiline`, `presets`.
283
+ - `boolean` (`display: "toggle"` — a toggle button showing the label,
284
+ compact in a row), `enum` (`options`, each optionally with a `swatch:
285
+ "#rrggbb"` color square; `display: "buttons"` — a segmented toggle group
286
+ for short choices, falling back to a dropdown when it doesn't fit),
287
+ `color` (`"#rrggbb"`, `presets` = a palette).
288
+ - `numberList` (`number[]`, typed Tekla-style: `"0 2*7200"` →
289
+ `[0, 7200, 7200]`) and `stringList` (`string[]`: `'A "B 2"'` →
290
+ `["A", "B 2"]`), both with `presets` and `placeholder` (shown while
291
+ empty).
292
+ - Pickers over the document's resources, valued by id / name:
293
+ `textStyle`, `dimStyle`, `font`, `lineType`, `hatchPattern`.
294
+ - `label` — a section heading (no value); `row` — 2–3 compact params on one
295
+ panel row (`items`, each label a short prefix like "W"); its items are
296
+ ordinary flat params (`params.w`).
297
+
298
+ Every param may carry a `hint` (the label's tooltip). An instance that
299
+ overrides a param shows "Reset" beside it in Properties, back to your
300
+ default. `draw` also receives
301
+ `styles`: `styles.text(id)` / `styles.dim(id)` give a document style (an
302
+ empty or unknown id → the first), for blocks that lay out with real text
303
+ sizes.
304
+
305
+ ### Writing a command
306
+
307
+ A command is a macro with a single `defineCommand` default export. Its
308
+ whole run is one undo step; Esc during a pick reverts cleanly. A
309
+ `params` table (the same rows a block's `params` uses) is asked for in a
310
+ dialog before the run and arrives typed as `ctx.params`; headless callers
311
+ pass it by name.
312
+
313
+ ```ts
314
+ // commands/circle-here.cmd.ts
315
+ import { defineCommand } from "lineadraw";
316
+
317
+ export default defineCommand({
318
+ id: "@my-blocks/circle-here",
319
+ name: "Circle here",
320
+ inputs: ["Center point"],
321
+ params: [{ name: "radius", type: "number", default: 10, min: 0.1 }],
322
+ // `inputs` are picked in the editor (or passed by a headless caller),
323
+ // `params` asked for in a dialog; `editor` is absent when headless.
324
+ run: async ({ document, editor, inputs: [center], params }) => {
325
+ document.add([{ type: "circle", center, radius: params.radius }]);
326
+ editor?.toast("Done", "success");
327
+ },
328
+ });
329
+ ```
330
+
331
+ ## 3. Marketplace management
332
+
333
+ A marketplace is a git repository of blocks and commands plus a generated
334
+ catalog (`.lineadraw/marketplace.json`) that the editor reads.
335
+
336
+ Scaffold a fresh one:
337
+
338
+ ```bash
339
+ npm create lineadraw my-blocks
340
+ ```
341
+
342
+ Or add the structure to an existing npm project:
343
+
344
+ ```bash
345
+ npm i -D lineadraw
346
+ npx lineadraw init
347
+ ```
348
+
349
+ Either way you get `blocks/` and `commands/` folders, a `tsconfig.json`
350
+ wired to the typings, a sample block, a CI workflow, the catalog, and
351
+ `npm run manifest` / `npm run check` scripts — the repo is addable in the
352
+ editor from the first push.
353
+
354
+ ```
355
+ lineadraw manifest [--dir <path>] Validate all modules and write .lineadraw/marketplace.json
356
+ lineadraw check [--dir <path>] Validate without writing; fail if the committed manifest is stale
357
+ lineadraw init [--dir <path>] Add the marketplace structure to the current npm project
358
+ ```
359
+
360
+ `manifest` and `check` **dry-run every block** through `lineadraw/sdk` and
361
+ bake preview thumbnails into the catalog. The manifest is deterministic
362
+ (repo-relative paths, no git metadata), so `check` compares the parsed JSON
363
+ — formatting, a trailing newline or CRLF line endings never make it stale.
364
+ The included CI workflow runs it on every push and PR.
365
+
366
+ Collection name and owner come from your package.json:
367
+
368
+ ```json
369
+ { "lineadraw": { "marketplaceName": "My blocks", "marketplaceOwner": "Me" } }
370
+ ```
371
+
372
+ ### Using a marketplace in the editor
373
+
374
+ In [the editor](https://lineadraw.com): **Library → Marketplace tab → the
375
+ gear button (Manage marketplaces…) → Add a marketplace** and enter
376
+ `owner/repo` (or `owner/repo@branch`). For a private repository,
377
+ create a fine-grained personal access token with read-only Contents access
378
+ to just that repository and paste it in the add dialog — it is stored only
379
+ on that device. Installing embeds the module (and its declared dependency
380
+ closure) in the document, so drawings keep working offline and travel with
381
+ the file.
382
+
383
+ ## 4. CLI for the running desktop app
384
+
385
+ ```
386
+ lineadraw status App version, port, the open drawings: id, name, path, kind, dirty
387
+ lineadraw open <file.linea> Open a file in a new window
388
+ lineadraw close [--editor <x>] [--discard]
389
+ lineadraw call <operation> ['<json>'] [--editor <x>] Run one linea_* operation
390
+ lineadraw tools The operations the bridge offers
391
+ ```
392
+
393
+ These commands talk to the running Linea desktop app on `127.0.0.1:7431`
394
+ (the app's `bridge.json` supplies the port and the agent token; `--port`,
395
+ `--token` and `LINEA_BRIDGE_TOKEN` override). `--editor` names a drawing by
396
+ document id, file path or unique name, and may be left out while only one
397
+ is open. Each `call` is one undo step in that editor. From code, use the
398
+ SDK's [`connectEditors()`](#running-editors-the-desktop-bridge).
399
+
400
+ ```bash
401
+ npx lineadraw call linea_add_objects '{"objects":[{"type":"circle","center":[0,0],"radius":500}]}'
402
+ ```
403
+
333
404
  ## Ecosystem
334
405
 
335
- - [Linea editor](https://lineadraw.com) — the browser CAD app that runs
336
- these modules.
406
+ - [Linea](https://lineadraw.com) — the CAD editor, on the desktop and on
407
+ the web, that runs these modules.
408
+ - [`create-lineadraw`](https://www.npmjs.com/package/create-lineadraw) —
409
+ `npm create lineadraw`, the marketplace scaffolder.
337
410
  - [`lineadraw/lineadraw`](https://github.com/lineadraw/lineadraw) — the
338
411
  public collection: real blocks/commands to learn from, agent skill, and
339
412
  guides.