@lotics/cli 0.315.18 → 0.316.1
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/AGENTS.md +37 -24
- package/README.md +42 -37
- package/dist/src/cli.js +27346 -49103
- package/dist/src/client.d.ts +12 -88
- package/dist/src/client.js +10 -74
- package/dist/src/invocation.d.ts +1 -1
- package/docs/agent_surface.md +121 -114
- package/docs/app_architecture.md +39 -0
- package/docs/app_bindings.md +24 -62
- package/docs/building_an_app.md +97 -81
- package/docs/cli_reference.md +18 -19
- package/docs/data_model.md +129 -183
- package/docs/design.md +156 -170
- package/docs/document_templates.md +135 -99
- package/docs/examples.md +1599 -1238
- package/docs/field_values.md +14 -11
- package/docs/filters.md +47 -9
- package/docs/first_build.md +180 -0
- package/docs/getting_started.md +15 -0
- package/docs/knowledge_docs.md +73 -209
- package/docs/migration.md +22 -26
- package/docs/workflow_steps.md +22 -579
- package/docs/workflows.md +409 -209
- package/docs/working_in_a_workspace.md +60 -0
- package/package.json +1 -1
|
@@ -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.
|
|
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
|
-
|
|
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 | `
|
|
16
|
-
| `html` (a PDF) | an **HTML + Handlebars** layout you write from scratch | PDF | `
|
|
17
|
-
| `excel` | an **uploaded `.xlsx`** with `{{marker}}` cells | `.xlsx` | `
|
|
18
|
-
| `word` | an **uploaded `.docx`** with `{{marker}}`s | `.docx` | `
|
|
19
|
-
| `email` | an **inline HTML + Handlebars** body, rendered when the email is sent | email | `
|
|
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
|
|
33
|
-
`file_id`
|
|
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 `
|
|
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
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
###
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
|
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,103 @@ 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
|
|
110
|
-
|
|
111
|
-
`{{
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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.
|
|
107
|
+
|
|
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
|
+
A Word command (`{{ … }}`) reads the data and calls, by name, a Word helper (`picture`, `qr`, `code128`) or one of these text
|
|
111
|
+
and number functions: `trim`, `trimStart`, `trimEnd`, `lower`, `upper`, `capitalize`, `replace`, `replaceAll`, `removeChars`, `removeSpaces`,
|
|
112
|
+
`startsWith`, `endsWith`, `contains`, `substring`, `padStart`, `padEnd`, `length`, `join`, `split`, `repeat`, `formatNumber`,
|
|
113
|
+
`formatDecimal`, `numberToWords`. It holds no JavaScript method or regular-expression literal: `{{upper(name)}}`,
|
|
114
|
+
`{{formatDecimal(total, 0, "vi-VN")}}`, and `{{removeSpaces(removeChars(cccd || "", ".…"))}}` for the text without its dots,
|
|
115
|
+
ellipses and spaces. `removeChars(text, chars)` removes every character of `text` that occurs in `chars` (a set of characters,
|
|
116
|
+
never a pattern); `removeSpaces(text)` removes every whitespace character of `text`, anywhere in it, the set `trim` trims.
|
|
117
|
+
|
|
118
|
+
A field named like a function is read as the field, and calling the function then fails.
|
|
116
119
|
|
|
117
|
-
|
|
118
|
-
`create_email_template` for each engine's exact marker grammar and helper list — don't guess it.
|
|
120
|
+
`set_template`'s description holds each type's exact marker grammar and helper list.
|
|
119
121
|
|
|
120
122
|
## Generating a filled file
|
|
121
123
|
|
|
122
|
-
```
|
|
123
|
-
#
|
|
124
|
-
|
|
125
|
-
|
|
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/
|
|
124
|
+
```
|
|
125
|
+
get_template {"template_id": "dtl_…"} # its declared variables
|
|
126
|
+
generate_document {"document_template_id": "dtl_…", "filename": "invoice-1042",
|
|
127
|
+
"data": {"company_name": "Acme", "total": 1042, "items": [...]}}
|
|
128
128
|
```
|
|
129
129
|
|
|
130
130
|
- `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).
|
|
131
|
+
`table`/`list`/loop variables. `filename` excludes the extension (the type sets it). Pass a select, a
|
|
132
|
+
member or a link as `get_record`'s `display` words, a number and a date raw.
|
|
132
133
|
- `generate_document` renders the template's own type and returns a generated file object; take
|
|
133
134
|
its `file_id` onward.
|
|
135
|
+
- A PDF no template is wanted for takes `html`, a `filename` and its `data` in place of `document_template_id`: the markup is
|
|
136
|
+
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
137
|
- In a workflow, a **generate step** calls the same tool and hands the `file_id` to the next
|
|
135
138
|
step (attach to a record, send as an attachment, etc.).
|
|
136
139
|
|
|
137
|
-
##
|
|
140
|
+
## A template an app's Act makes
|
|
138
141
|
|
|
139
|
-
|
|
142
|
+
An Act with `template` (or `templates`) fills an `html`, `excel` or `word` template from its record and files the result
|
|
143
|
+
(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
|
|
144
|
+
alias, so name each variable after one: `{{customer}}`, `{{total}}`. A select, a member and a link arrive as their words
|
|
145
|
+
(a link as the linked row's title); a number and a date arrive raw, for `{{money total "VND"}}` and `{{date issued_on}}`.
|
|
146
|
+
The rows of each child the record draws arrive under the name of the record's own link to them, for a loop:
|
|
140
147
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
| `delete_template` | remove a template, on the CLI or in chat |
|
|
148
|
+
```html
|
|
149
|
+
<h1>Quote {{code}}</h1>
|
|
150
|
+
<p>{{customer}} — {{money total "VND"}}</p>
|
|
151
|
+
{{#each lines}}<p>{{description}} × {{quantity}}</p>{{/each}}
|
|
152
|
+
```
|
|
147
153
|
|
|
148
|
-
|
|
154
|
+
Here `code`, `customer` and `total` are fields of the record and `lines` is its link to the child table. The rows
|
|
155
|
+
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}}` …
|
|
156
|
+
`{{$line.description}}` … `{{END-FOR line}}`.
|
|
157
|
+
|
|
158
|
+
Every List the record draws and the template names (an html template's variables, a Word template's markers) hands its
|
|
159
|
+
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
|
|
160
|
+
`key` (the table's alias where it states none), and a List that sums a number hands the sums under `<name>_totals`, one
|
|
161
|
+
entry per number the List draws as a column or as the table's figure. A `where` bound may be `{ "record": "<field>" }`,
|
|
162
|
+
a date or number the record itself holds, so a record can print a window of its own days. A service plan row (customer,
|
|
163
|
+
`period_start`, `period_end`) draws the customer's visits within its period:
|
|
149
164
|
|
|
150
|
-
|
|
165
|
+
```
|
|
166
|
+
{ "type": "List", "key": "visits", "entity": "visit", "same": "customer", "label": "Visits", "columns": ["visited_on", "hours"],
|
|
167
|
+
"where": { "visited_on": { "gte": { "record": "period_start" }, "lte": { "record": "period_end" } } } }
|
|
168
|
+
```
|
|
151
169
|
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
lotics run <tool> '<json-args>' # execute (inline JSON, @file.json, or `-` for piped stdin)
|
|
170
|
+
```html
|
|
171
|
+
{{#each visits}}<p>{{visited_on}} — {{hours}} h</p>{{/each}}
|
|
172
|
+
<p>Total {{visits_totals.hours}} h</p>
|
|
156
173
|
```
|
|
157
174
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
175
|
+
A bound the record leaves blank, or holds as anything but one whole day (`2026-05`) or one figure, keeps no row:
|
|
176
|
+
`visits` is empty and `visits_totals.hours` is `0`. The sums add a number as the List's foot does: a cell holding a
|
|
177
|
+
figure adds it, any other cell (blank, an errored formula, a lookup's list) adds nothing. A Word template loops
|
|
178
|
+
`{{FOR v IN visits}}` and prints `{{visits_totals.hours}}` the same way. A template that names a List narrowed to the
|
|
179
|
+
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
|
|
180
|
+
bound narrows what is listed and is never an access bound; the table's row rules are.
|
|
181
|
+
|
|
182
|
+
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`).
|
|
183
|
+
`model/groups-and-templates` has the rest.
|
|
184
|
+
|
|
185
|
+
## Discovering and managing templates
|
|
186
|
+
|
|
187
|
+
These tools work across all five types:
|
|
188
|
+
|
|
189
|
+
| Tool | Does |
|
|
190
|
+
|---|---|
|
|
191
|
+
| `get_workspace` | list the workspace's templates (`kind: "template"`), each with its id, type and one line |
|
|
192
|
+
| `get_template` | one template's full definition, incl. the `variables` it expects and the `current_version` a change needs |
|
|
193
|
+
| `set_template` | create a template, or change one |
|
|
194
|
+
| `clone_template` | copy a template to iterate on, in chat |
|
|
195
|
+
| `delete_template` | remove a template |
|
|
196
|
+
|
|
197
|
+
Call `get_template` before generating when you don't already know a template's variable names.
|