@lotics/cli 0.315.17 → 0.316.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.
@@ -5,18 +5,19 @@ labels, emails — by filling a reusable template instead of writing a file from
5
5
  You register a template once, then generate a filled file (or a rendered email) from it as
6
6
  many times as you like, feeding it data from records, a workflow, or an agent run.
7
7
 
8
- This is a **capability + usage** guide. For the exact input schema of any tool named here,
9
- run `lotics tools <tool_name>` — that is always the source of truth for arguments.
8
+ This is a **capability + usage** guide. A tool's exact input schema is in its own description.
10
9
 
11
10
  ## The five template types
12
11
 
13
- | Type | What it fills | Output | Create with |
12
+ `set_template` writes all five. Its `type` names which one a new template is.
13
+
14
+ | Type | What it fills | Output | `type` |
14
15
  |---|---|---|---|
15
- | `pdf-form` | fields overlaid on an **uploaded PDF** at fixed positions | PDF | `create_pdf_template` (mode `form`), on the CLI or in chat |
16
- | `html` (a PDF) | an **HTML + Handlebars** layout you write from scratch | PDF | `create_pdf_template` (mode `html`), on the CLI or in chat |
17
- | `excel` | an **uploaded `.xlsx`** with `{{marker}}` cells | `.xlsx` | `create_excel_template` |
18
- | `word` | an **uploaded `.docx`** with `{{marker}}`s | `.docx` | `create_word_template` |
19
- | `email` | an **inline HTML + Handlebars** body, rendered when the email is sent | email | `create_email_template`, on the CLI or in chat |
16
+ | `pdf-form` | fields overlaid on an **uploaded PDF** at fixed positions | PDF | `"pdf-form"` |
17
+ | `html` (a PDF) | an **HTML + Handlebars** layout you write from scratch | PDF | `"html"` |
18
+ | `excel` | an **uploaded `.xlsx`** with `{{marker}}` cells | `.xlsx` | `"excel"` |
19
+ | `word` | an **uploaded `.docx`** with `{{marker}}`s | `.docx` | `"word"` |
20
+ | `email` | an **inline HTML + Handlebars** body, rendered when the email is sent | email | `"email"` |
20
21
 
21
22
  Two shapes underneath: **file-backed** (`pdf-form`, `excel`, `word`) clone an uploaded
22
23
  office file and mark it up; **inline** (`html`, `email`) store the markup you author directly.
@@ -29,71 +30,63 @@ office file and mark it up; **inline** (`html`, `email`) store the markup you au
29
30
  template id, a `filename` (no extension), and a `data` map keyed by your variable names.
30
31
  It substitutes the markers and returns a **generated file** (with a `file_id`).
31
32
  3. **Chain** the result — generation only *produces* the file. Attaching it to a record,
32
- sending it, or saving it locally is a separate step: inside a workflow, pass the returned
33
- `file_id` to the next step; from the CLI, `lotics download <file_id>` fetches it.
33
+ sending it, or saving it is a separate step: `update_records` with `add_to` on a files field
34
+ files the `file_id` on a record (`remove_made`, a template id, takes out the paper that template
35
+ made earlier); over MCP and on the CLI, `download_file` fetches it; inside a workflow, pass the `file_id` to the next
36
+ step. A paper an app makes is its Act's `template`, which files it on the record itself
37
+ (below).
34
38
 
35
39
  Email is the exception: `generate_document` refuses an email template. An email template is
36
40
  rendered to a subject + body **at send time** from the template and the data the send step
37
- supplies — so you author it with `create_email_template` (on the CLI or in chat) and a workflow/app send step (or the
41
+ supplies — so you author it with `set_template` and a workflow/app send step (or the
38
42
  web composer) does the rendering and delivery. External agents send their own email directly.
39
43
 
40
- ## Creating each type
41
-
42
- ### PDF from HTML
43
-
44
- Write a full HTML document with inline CSS and `{{variable}}` placeholders, as `create_pdf_template`
45
- with mode `html` on the CLI or in chat; it renders to PDF via a headless browser. Good for invoices, reports, letters, certificates — anything whose
46
- layout you control. Declare `variables` (plain text, rich `html`, an auto-rendered `table`,
47
- or a `list`), and optionally `format` (page size) and `orientation`. See
48
- `lotics tools create_pdf_template` for the exact variable shapes.
49
-
50
- ### PDF form
51
-
52
- For an existing PDF you must fill in place (a government form, a printed contract), on the CLI or in chat:
53
-
54
- 1. `lotics upload ./form.pdf` → a `file_id`.
55
- 2. `create_pdf_template` with mode `form` and that `file_id`.
56
- 3. `analyze_pdf_template` — extracts the labels and their positions off the page.
57
- 4. `update_pdf_template` — define the positioned fields (`text` / `checkbox` / `image`, each
58
- with a page + x/y/size) in `variables`.
59
-
60
- Form-mode data is scalar-only (text, numbers, checkboxes) — one value per positioned field.
61
-
62
- ### Excel
63
-
64
- 1. Build a `.xlsx` in Excel (or with any spreadsheet library) and put `{{marker}}`s in the cells that
65
- should be filled. `lotics upload ./template.xlsx` → a `file_id`.
66
- 2. `create_excel_template` with that `template_file_id` and a `variables` map. Markers are
67
- **validated at create time** — a structural error (mismatched loop, unknown helper, bad
68
- placement) blocks the save, and declared variables with no matching marker come back in
69
- `unmarked_variables` (skipped, not filled).
70
-
71
- On the CLI, `validate_excel_template` checks an uploaded file's markers without creating a template. Pass
72
- raw numbers / ISO dates / booleans at generate time — the cell's number format handles display.
73
-
74
- ### Word
75
-
76
- 1. Build a `.docx` with `{{marker}}`s where values go. `lotics upload ./template.docx`.
77
- 2. `create_word_template` with the `template_file_id`. `variables` is optional — every
78
- marker in the document is derived automatically (`{{name}}` → string,
79
- `{{FOR … IN name}}` → array, `{{IF name}}` → boolean). When you do declare, an unmarked
80
- declaration comes back in `unmarked_variables` and is skipped, and markers you didn't
81
- declare are still auto-added (`derived_variables`) — the stored contract always covers
82
- every marker in the document.
83
-
84
- At generate time, `data` must provide a key for **every** marker — pass `""` for fields
85
- that should render blank; a missing key fails with the full list of missing markers.
86
-
87
- ### Email
88
-
89
- Inline HTML + Handlebars, no uploaded file — `create_email_template`, on the CLI or in chat. Declare `variables`, and optionally a default
90
- `subject` (which itself supports `{{variable}}` expressions) and default to/cc/bcc. The body
91
- and subject render from the data at send time.
44
+ ## Creating and changing a template
45
+
46
+ A paper prints the owner's own terms only: the clauses, wording and fields of the sample they sent, never a term the owner did not state.
47
+
48
+ `set_template` creates a template when it is given no `template_id`, and changes the named one
49
+ when it is given one. Creating takes `type` and `name`, and the fields of its type; changing takes
50
+ `expected_version` — the `current_version` that `get_template` returned, so a concurrent edit is
51
+ refused rather than overwritten — and only the fields to change. A field the type does not take is
52
+ refused rather than dropped. `description` (one line on what the template produces) is on every type.
53
+
54
+ ### Getting a file in
55
+
56
+ A file-backed template starts from a file in the workspace: over MCP and on the CLI, `upload_file` takes a public URL,
57
+ or bytes you hold, and returns a `file_id`. When the person has the file and your host shows you its content but not its
58
+ bytes, `upload_file` with `person: true` returns a `drop_url` for them to open and a `ticket_id` per file; once they say
59
+ they dropped it, `upload_file` with that `ticket_id` returns the `file_id`.
60
+
61
+ A new Word or Excel paper is a file you build yourself, in your own filesystem or sandbox, with its markers typed where
62
+ the values go, then, over MCP or on the CLI, send it with `upload_file` `files` and register with `set_template`. Where your sandbox cannot reach
63
+ this host, give the person the file and a `person` drop link.
64
+
65
+ ### By type
66
+
67
+ - **`html`** — a full HTML document with inline CSS and `{{variable}}` placeholders, rendered to PDF; anything whose layout
68
+ you control. `variables` are plain text, rich `html`, an auto-rendered `table` or a `list`; `format` (page size) and
69
+ `orientation` are optional. A change may send `patch_ops`, exact search-and-replace edits of the stored markup, in place
70
+ of the whole `template`.
71
+ - **`pdf-form`** — an existing PDF filled in place. `set_template` with the uploaded `template_file_id`;
72
+ `analyze_pdf_template` reads the labels and positions off the page; `set_template` again with `expected_version` and the
73
+ positioned `variables` (`text` / `checkbox` / `image`, each with a page and x/y/size). Data is one scalar per field.
74
+ - **`excel`** — a `.xlsx` with `{{marker}}`s in the cells to fill, and a `variables` map. Markers are validated at create
75
+ time: a structural error blocks the save, and a declared variable with no marker comes back in `unmarked_variables`
76
+ (skipped). Over MCP and on the CLI, `validate_excel_template` checks a file's markers without creating a template. Pass
77
+ raw numbers, ISO dates and booleans at generate time; the cell's number format shows them.
78
+ - **`word`** — a `.docx` with `{{marker}}`s. `variables` is optional: every marker is derived (`{{name}}` string,
79
+ `{{FOR … IN name}}` array, `{{IF name}}` boolean), and a marker you did not declare is still added
80
+ (`derived_variables`). At generate time `data` needs a key for **every** marker, `""` for a blank one. A marker may run
81
+ across formatting runs and is still read whole. In a table, the row holding `{{FOR line IN lines}}` and the row holding
82
+ `{{END-FOR line}}` enclose the row that repeats.
83
+ - **`email`** — inline HTML + Handlebars, with `template` and `variables`; optionally a default `subject` (it takes
84
+ `{{variable}}` too) and `default_to`, `default_cc`, `default_bcc`. Body and subject render from the data at send time.
92
85
 
93
86
  ## Markers, at a usage level
94
87
 
95
88
  Every type supports three shapes of substitution. The exact syntax differs per engine — read
96
- the create-tool description for the reference; here is the capability:
89
+ the `set_template` description for the reference; here is the capability:
97
90
 
98
91
  - **Scalar** — a single value in one spot: `{{name}}`, `{{total}}`. Whole-cell scalars in
99
92
  Excel preserve their type via the cell's number format.
@@ -102,60 +95,93 @@ the create-tool description for the reference; here is the capability:
102
95
  `{{#each items}}…{{/each}}`; Word uses `{{FOR item IN items}}…{{$item.field}}…{{END-FOR item}}`. The HTML and email types
103
96
  also offer an auto-rendered `table` variable — declare its columns and pass an array, no
104
97
  hand-written loop. (Excel always uses the `{{#each}}` marker rows — it has no auto-rendered
105
- `table` type; its variable types are string/number/date/boolean/array.)
98
+ `table` type; its variable types are string/number/date/boolean/array.) Inside a loop the row's number counted from 1
99
+ is `{{@ordinal}}` (Excel, HTML, email) or `{{$ordinal}}` (Word), the innermost loop's: an STT column is a cell holding
100
+ `{{@ordinal}}` beside the row's other markers. `{{@index}}` counts from 0.
106
101
  - **Conditional sections** — a block shown only when a condition holds (a "paid" stamp, an
107
102
  optional notes block). Excel/HTML/email use `{{#if}}…{{else}}…{{/if}}`; Word uses `{{IF name}}…{{END-IF name}}`.
108
103
 
109
- The `html` and `email` types format a raw value with a helper, in the template's `locale` (`vi` or `en`, set at
110
- create or update; none prints in the organization's language); an empty value prints `""`:
111
- `{{money total "VND"}}` → `28.000.000 ₫` (`en`: `₫28,000,000`), `{{number qty}}` → grouped, up to 2 decimals,
112
- `{{date issued_on}}` → `14/09/2026` (a moment on the workspace's timezone; a date-fns pattern as the second argument,
113
- literal text quoted: `"'Ngày' dd"`), and `{{words total "USD"}}` → `Một nghìn đô la Mỹ năm mươi xu` (`en`: `One
114
- thousand US dollars and fifty cents`; no code reads VND); `{{date (add_days issued_on -1)}}` prints the day before, `add_days`
115
- shifting a date by whole days. A value the helper cannot read fails the render.
104
+ The `html` and `email` types print a raw value through a helper in the template's `locale` (`vi` or `en`; none prints in the organization's language): `{{money total "VND"}}`, `{{number qty}}`, `{{date issued_on}}`, `{{words total "USD"}}` (`model/groups-and-templates` lists what each prints). `{{date (add_days issued_on -1)}}` prints the day before, `add_days` shifting a date by whole days. A value a helper cannot read fails the render.
105
+
106
+ A field can also print as a **QR code** or a **Code 128 barcode** (a picture a phone's camera or a scanner reads back): `{{qr link}}` or `{{code128 position 6}}` in html (never email: mail clients do not show the picture, and a save refuses it), `{{position | qr}}` or `{{position | code128:6}}` as a whole Excel cell (its row grows to the picture), `{{IMAGE qr(position)}}` or `{{IMAGE code128(position, 6)}}` in Word. The optional number is the picture's width in centimetres. Without it the width follows the value's length — each module prints 0.5 mm in a QR code and 0.33 mm in a barcode, so a longer value prints wider; a width that would shrink a module below what a scanner reads (0.3 mm, 0.19 mm) is refused, naming the least width that scans. An empty field draws nothing.
116
107
 
117
- Run `lotics tools create_excel_template`, `create_word_template`, `create_pdf_template`, or
118
- `create_email_template` for each engine's exact marker grammar and helper list — don't guess it.
108
+ A record's **photos** (a files field, an act's paper hands each as a file cell `{id, name, mime, url}`) print too, and only for a file whoever makes the paper may read — the same rule the file proxy applies; any other file is never drawn. html: `{{#each photos}}<img src="{{url}}">{{/each}}` (only the data's file cells are inlined; a url typed into a text field stays a link). Word: `{{IMAGE picture(cover, 8)}}`, and `{{FOR p IN photos}}{{IMAGE picture($p, 8)}}{{END-FOR p}}` for several. Excel: `{{cover | picture:6}}` as the whole cell, or `{{this | picture}}` inside `{{#each photos}}` (each row grows to its picture). The number is the width in centimetres (8, or 6 in Excel, by default). `picture` takes one image file: a list (loop over it), a file that is no image or one that cannot be read is refused by name. Photos are embedded at most 1600 pixels across, about 200 dpi at 20 cm.
109
+
110
+ `set_template`'s description holds each type's exact marker grammar and helper list.
119
111
 
120
112
  ## Generating a filled file
121
113
 
122
- ```bash
123
- # See what a template expects, then fill it
124
- lotics tools generate_document
125
- lotics run get_template '{"template_id":"dtl_..."}' # its declared variables
126
- lotics run generate_document '{"document_template_id":"dtl_...","filename":"invoice-1042","data":{"company_name":"Acme","total":1042,"items":[...]}}'
127
- lotics download <file_id> -o ./out/
114
+ ```
115
+ get_template {"template_id": "dtl_…"} # its declared variables
116
+ generate_document {"document_template_id": "dtl_…", "filename": "invoice-1042",
117
+ "data": {"company_name": "Acme", "total": 1042, "items": [...]}}
128
118
  ```
129
119
 
130
120
  - `data` is a map keyed by your variable names. Scalars for scalar/form fields; arrays for
131
- `table`/`list`/loop variables. `filename` excludes the extension (the type sets it).
121
+ `table`/`list`/loop variables. `filename` excludes the extension (the type sets it). Pass a select, a
122
+ member or a link as `get_record`'s `display` words, a number and a date raw.
132
123
  - `generate_document` renders the template's own type and returns a generated file object; take
133
124
  its `file_id` onward.
125
+ - A PDF no template is wanted for takes `html`, a `filename` and its `data` in place of `document_template_id`: the markup is
126
+ rendered as an `html` template is (the same markup rules, markers and helpers) and the PDF is stored as a workspace file. Name one of the two.
134
127
  - In a workflow, a **generate step** calls the same tool and hands the `file_id` to the next
135
128
  step (attach to a record, send as an attachment, etc.).
136
129
 
137
- ## Discovering and managing templates
130
+ ## A template an app's Act makes
138
131
 
139
- Four unified tools work across all five types:
132
+ An Act with `template` (or `templates`) fills an `html`, `excel` or `word` template from its record and files the result
133
+ (a PDF, an `.xlsx` or a `.docx`) in the record's files field `into`; `run_app_workflow` on `act_<key>` (or the `workflow` the Act names) presses it. The Act hands the template the record's fields by their
134
+ alias, so name each variable after one: `{{customer}}`, `{{total}}`. A select, a member and a link arrive as their words
135
+ (a link as the linked row's title); a number and a date arrive raw, for `{{money total "VND"}}` and `{{date issued_on}}`.
136
+ The rows of each child the record draws arrive under the name of the record's own link to them, for a loop:
140
137
 
141
- | Tool | Does |
142
- |---|---|
143
- | `query_templates` | list templates (optionally filter by type: excel/word/pdf/email) |
144
- | `get_template` | one template's full definition, incl. the `variables` it expects |
145
- | `clone_template` | copy a template to iterate on, on the CLI or in chat |
146
- | `delete_template` | remove a template, on the CLI or in chat |
138
+ ```html
139
+ <h1>Quote {{code}}</h1>
140
+ <p>{{customer}} — {{money total "VND"}}</p>
141
+ {{#each lines}}<p>{{description}} × {{quantity}}</p>{{/each}}
142
+ ```
147
143
 
148
- Call `get_template` before generating when you don't already know a template's variable names.
144
+ Here `code`, `customer` and `total` are fields of the record and `lines` is its link to the child table. The rows
145
+ also arrive under the child table's `ref` (`quote_line`), so `{{#each quote_line}}` fills as well; a Word template loops the same rows with `{{FOR line IN lines}}` …
146
+ `{{$line.description}}` … `{{END-FOR line}}`.
149
147
 
150
- ## Reaching the tools
148
+ Every List the record draws and the template names (an html template's variables, a Word template's markers) hands its
149
+ rows, each List's `where` applied; a List the template does not name is not read. A List that only reads hands them under its
150
+ `key` (the table's alias where it states none), and a List that sums a number hands the sums under `<name>_totals`, one
151
+ entry per number the List draws as a column or as the table's figure. A `where` bound may be `{ "record": "<field>" }`,
152
+ a date or number the record itself holds, so a record can print a window of its own days. A service plan row (customer,
153
+ `period_start`, `period_end`) draws the customer's visits within its period:
151
154
 
152
- ```bash
153
- lotics tools # all categories
154
- lotics tools create_word_template # one tool: full description + input schema
155
- lotics run <tool> '<json-args>' # execute (inline JSON, @file.json, or `-` for piped stdin)
156
155
  ```
156
+ { "type": "List", "key": "visits", "entity": "visit", "same": "customer", "label": "Visits", "columns": ["visited_on", "hours"],
157
+ "where": { "visited_on": { "gte": { "record": "period_start" }, "lte": { "record": "period_end" } } } }
158
+ ```
159
+
160
+ ```html
161
+ {{#each visits}}<p>{{visited_on}} — {{hours}} h</p>{{/each}}
162
+ <p>Total {{visits_totals.hours}} h</p>
163
+ ```
164
+
165
+ A bound the record leaves blank, or holds as anything but one whole day (`2026-05`) or one figure, keeps no row:
166
+ `visits` is empty and `visits_totals.hours` is `0`. The sums add a number as the List's foot does: a cell holding a
167
+ figure adds it, any other cell (blank, an errored formula, a lookup's list) adds nothing. A Word template loops
168
+ `{{FOR v IN visits}}` and prints `{{visits_totals.hours}}` the same way. A template that names a List narrowed to the
169
+ reader (`"me"`) is refused, and so is a name a rows List and a `list` List both reach: give the `list` a `key`. A record
170
+ bound narrows what is listed and is never an access bound; the table's row rules are.
157
171
 
158
- The template tools live in the categories **PDF Templates**, **Excel Templates**,
159
- **Word Templates**, **Email Templates**, and **Templates** (the unified query/get/clone/delete).
160
- Uploading the source file for a file-backed template is `lotics upload <file>`; fetching a
161
- generated file is `lotics download <file_id>`.
172
+ A paper over many rows (a client's month, a statement) is that: a record of the period whose List is windowed by its dates, made by the Act's `template`; or a List `Export` with `template` and `per` (`model/export-node`).
173
+ `model/groups-and-templates` has the rest.
174
+
175
+ ## Discovering and managing templates
176
+
177
+ These tools work across all five types:
178
+
179
+ | Tool | Does |
180
+ |---|---|
181
+ | `get_workspace` | list the workspace's templates (`kind: "template"`), each with its id, type and one line |
182
+ | `get_template` | one template's full definition, incl. the `variables` it expects and the `current_version` a change needs |
183
+ | `set_template` | create a template, or change one |
184
+ | `clone_template` | copy a template to iterate on, in chat |
185
+ | `delete_template` | remove a template |
186
+
187
+ Call `get_template` before generating when you don't already know a template's variable names.