@lotics/cli 0.316.20 → 0.316.21
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 +4 -5
- package/README.md +3 -7
- package/dist/src/cli.js +61 -115
- package/dist/src/client.js +2 -2
- package/docs/agent_surface.md +5 -5
- package/docs/app_bindings.md +18 -17
- package/docs/building_an_app.md +5 -6
- package/docs/cli_reference.md +6 -7
- package/docs/data_model.md +2 -2
- package/docs/document_templates.md +9 -8
- package/docs/filters.md +18 -1
- package/docs/first_build.md +2 -2
- package/docs/knowledge_docs.md +3 -3
- package/docs/migration.md +4 -4
- package/docs/workflow_steps.md +185 -6
- package/docs/workflows.md +71 -62
- package/docs/working_in_a_workspace.md +38 -7
- package/package.json +1 -1
package/dist/src/client.js
CHANGED
|
@@ -525,9 +525,9 @@ var LoticsClient = class {
|
|
|
525
525
|
return { path: absolutePath, filename, stored_filename: originalFilename };
|
|
526
526
|
}
|
|
527
527
|
async downloadRecordFiles(recordId, fieldAlias, outputDir) {
|
|
528
|
-
const result = await this.execute("
|
|
528
|
+
const result = await this.execute("query_records", { record_ids: [recordId] }, { format: "text" });
|
|
529
529
|
if (result.error) throw new Error(result.error);
|
|
530
|
-
const record = result.result;
|
|
530
|
+
const record = result.result?.records?.[0];
|
|
531
531
|
const files = record?.data?.[fieldAlias];
|
|
532
532
|
if (!Array.isArray(files)) {
|
|
533
533
|
throw new Error(`Field ${fieldAlias} on ${recordId} is not a file field or has no value`);
|
package/docs/agent_surface.md
CHANGED
|
@@ -18,7 +18,7 @@ are by its own — `get_app` lists them in the one roster, and nothing of a log
|
|
|
18
18
|
|
|
19
19
|
## Finding what an app offers
|
|
20
20
|
|
|
21
|
-
`get_workspace` maps the workspace. Its `search` matches the keys and descriptions of the Query and Workflow nodes an app declares, and the app's line names the matching keys; it never matches the generated `act_`, `create_` or `update_` aliases. `get_app` then gives the contracts. An agent runs a declared node before it falls back to `
|
|
21
|
+
`get_workspace` maps the workspace. Its `search` matches the keys and descriptions of the Query and Workflow nodes an app declares, and the app's line names the matching keys; it never matches the generated `act_`, `create_` or `update_` aliases. `get_app` then gives the contracts. An agent runs a declared node before it falls back to `query_records`.
|
|
22
22
|
|
|
23
23
|
## What every app generates
|
|
24
24
|
|
|
@@ -182,7 +182,7 @@ run_app_workflow {"app_id":"app_…","alias":"act_hand_over","inputs":{"record_i
|
|
|
182
182
|
|---|---|
|
|
183
183
|
| `inputs` | The workflow's declared inputs. An update takes the record and only the fields that change |
|
|
184
184
|
| `dry_run` | `true`: the run is made and taken back, so nothing is kept, and the answer is what the run would return, with the calls it would make — or `status: "not_previewed"` where the workflow cannot be dry-run (§ A dry run) |
|
|
185
|
-
| `as_member` | With `dry_run`, for a workspace admin: the run is judged as that member would press it — their stage owners, row rules and app access — and writes nothing. Anyone else is refused; `dry_run` on a record tool takes it the same way, and so
|
|
185
|
+
| `as_member` | With `dry_run`, for a workspace admin: the run is judged as that member would press it — their stage owners, row rules and app access — and writes nothing. Anyone else is refused; `dry_run` on a record tool takes it the same way, and so does `query_records`, which answers what that member would read |
|
|
186
186
|
| `runs` | Several runs in one call, 50 at most, in order, each its own write, answered under `runs`. The first one refused, failed or not dry-run ends the call. An input `{"from_run": 0, "path": "data.record_id"}` is an earlier run's answer at that path. A run states its own `dry_run`, or takes the call's. Dry runs one after another share their rows, taken back together, so a run reads what the one before it made; a run reads a dry run's answer only from a run dry-run in that stretch |
|
|
187
187
|
|
|
188
188
|
```
|
|
@@ -259,7 +259,7 @@ and the runs before it stand. A refused read fails the same way, naming the para
|
|
|
259
259
|
## Declaring more
|
|
260
260
|
|
|
261
261
|
What the business asks an agent in the app again and again, and no screen draws, is stated in the app's tree as Query and Workflow nodes, beside its
|
|
262
|
-
Acts: every figure or list whose definition is the business's own (open orders, what a customer owes, low stock) and every recurring operation (close the month, send to the accounting service, chase overdue invoices), each with a `description` written for an agent. A question asked once stays on `
|
|
262
|
+
Acts: every figure or list whose definition is the business's own (open orders, what a customer owes, low stock) and every recurring operation (close the month, send to the accounting service, chase overdue invoices), each with a `description` written for an agent. A question asked once stays on `query_records` with `aggregate`. A Workflow gets an Act only where a person presses it; work only an agent does has no button. Both are checked and compiled when the tree is written (`set_app`; `reference/query-node`, `reference/workflow-node`).
|
|
263
263
|
|
|
264
264
|
**A query** — the rows of one table, or one figure of them, in the app's own aliases:
|
|
265
265
|
|
|
@@ -301,8 +301,8 @@ a Reading's `key` take no name the app generates (`reference/app.generated-name`
|
|
|
301
301
|
}
|
|
302
302
|
```
|
|
303
303
|
|
|
304
|
-
`
|
|
305
|
-
the tree (`set_app_queries`, on the CLI
|
|
304
|
+
`set_workflow` sets its code (`workflows`). A query set on an app outside
|
|
305
|
+
the tree (`set_app_queries`, on the CLI) is removed by the next write, whose answer notes it; declared as a Query
|
|
306
306
|
node, it is the tree's. A caller sees the description and
|
|
307
307
|
each input's `description` and nothing else, so each says what the value is and what leaving it out means;
|
|
308
308
|
the check notes one that says nothing.
|
package/docs/app_bindings.md
CHANGED
|
@@ -5,17 +5,16 @@ under an alias — a JS identifier (`/^[a-zA-Z_$][a-zA-Z0-9_$]*$/`):
|
|
|
5
5
|
|
|
6
6
|
| Binding | Declared with | The app calls it with | An agent calls it with |
|
|
7
7
|
|---|---|---|---|
|
|
8
|
-
| Query | `set_app_queries` (CLI
|
|
9
|
-
| Workflow | `
|
|
8
|
+
| Query | `set_app_queries` (CLI) | `useQuery("<alias>", params?)` | `run_app_query` |
|
|
9
|
+
| Workflow | `set_workflow` | `useWorkflow("<alias>")({ ...inputs })` | `run_app_workflow` |
|
|
10
10
|
|
|
11
11
|
Each runs under the app's authority (`{ type: "app", app_id }`), the app owner's reach — never the
|
|
12
12
|
calling member's. Declaring one needs the app's owner or an admin. A write is live: the app's next
|
|
13
13
|
call uses it. `get_app` lists an app's aliases with their params and inputs.
|
|
14
14
|
|
|
15
|
-
Each reader returns a fingerprint of what it read — `
|
|
15
|
+
Each reader returns a fingerprint of what it read — `get_workflow`'s `version`, and on the CLI
|
|
16
16
|
`get_app_query`'s `sha` — and a write built on that read passes it back:
|
|
17
|
-
`set_app_queries`' `expected_shas` (alias → `sha`), `
|
|
18
|
-
`remove_app_binding`'s `expected_sha`. The write is
|
|
17
|
+
`set_app_queries`' `expected_shas` (alias → `sha`), and `set_workflow`'s `expected_version`. The write is
|
|
19
18
|
refused, and writes nothing, when the binding changed since; omit it for an unconditional write.
|
|
20
19
|
|
|
21
20
|
## Queries
|
|
@@ -37,10 +36,10 @@ A save checks what a deploy checks: every table is in this workspace and within
|
|
|
37
36
|
every projected, filtered and sorted field resolves, every param token is declared, and a key a
|
|
38
37
|
node's kind does not take (a stray `sort`, `limit`, `filter`, `search`) is refused by name.
|
|
39
38
|
|
|
40
|
-
On the CLI
|
|
39
|
+
On the CLI, `set_app_queries` takes `{ <alias>: <declaration> }`, one alias or several, and merges each: send
|
|
41
40
|
only the fields you change, `null` clears `params`, `description` or `templates` (never `ast`), and a
|
|
42
41
|
new alias needs an `ast`. Any other field is refused by name. An alias it omits is kept.
|
|
43
|
-
`
|
|
42
|
+
An alias set to `null` is unbound, under the same `expected_shas`; `get_app_query` reads one back.
|
|
44
43
|
|
|
45
44
|
### The query tree
|
|
46
45
|
|
|
@@ -128,8 +127,9 @@ keeps the type tracking the live field.
|
|
|
128
127
|
|
|
129
128
|
## Workflows
|
|
130
129
|
|
|
131
|
-
`
|
|
132
|
-
|
|
130
|
+
`set_workflow` binds a workflow body to an alias: `{ workflow_id?, name?, description?, source }`, the `source`
|
|
131
|
+
opening with `on({ type: "app", app_id, alias, inputs?, outputs? })`. Without a `workflow_id` it binds the alias; with
|
|
132
|
+
the one `get_app` lists for it, it replaces the body. On an app written as a tree the alias is one the tree names — a Workflow node's
|
|
133
133
|
`key` or an Act's `workflow` — and the node states `inputs`, `outputs` and `description`
|
|
134
134
|
(`reference/workflow-node`, whose value shapes differ from the **Inputs** below): a call stating any of
|
|
135
135
|
them for it is refused, and outputs the node leaves out are derived from the body. An alias the tree
|
|
@@ -137,20 +137,21 @@ names nowhere is archived by the next `set_app`. A Workflow node runs only for s
|
|
|
137
137
|
app: on an app shared by a public link, a visitor's call of it is refused as an alias the app does
|
|
138
138
|
not have. This page's `inputs` and `outputs` are for a custom-code app.
|
|
139
139
|
|
|
140
|
-
- `source` is the
|
|
140
|
+
- `source` is the header, then the body; the app is the trigger. The body reads each declared
|
|
141
141
|
input as `trigger.app_workflow.inputs.<name>`; no record is in scope, so it reads each one by id
|
|
142
|
-
with `
|
|
142
|
+
with `query_records` and `record_ids`.
|
|
143
143
|
The body's steps are the `workflows` reference.
|
|
144
|
-
- `inputs` is the payload the call site passes (see **Inputs**). Omitted, an existing workflow keeps
|
|
144
|
+
- `inputs` in the header is the payload the call site passes (see **Inputs**). Omitted, an existing workflow keeps
|
|
145
145
|
its inputs; on a first bind it takes an untyped payload. `{}` clears them.
|
|
146
|
-
- `outputs` is what `return({ data })` hands back (see **Outputs**). Usually omit it: the type is
|
|
146
|
+
- `outputs` in the header is what `return({ data })` hands back (see **Outputs**). Usually omit it: the type is
|
|
147
147
|
derived from the body's `return({ data })`, and the result echoes the bound `outputs`. Declared,
|
|
148
148
|
the body does not save unless its return matches.
|
|
149
149
|
- A save checks the body — parse, type-check against the declared inputs, names, lint, structure —
|
|
150
|
-
and answers with `[source/code] location: message` lines. `
|
|
150
|
+
and answers with `[source/code] location: message` lines. `dry_run: true` makes every check
|
|
151
151
|
and writes nothing. A call naming a live alias replaces its body in place, keeping its run history.
|
|
152
152
|
- A call whose payload does not match `inputs` is refused before the body runs.
|
|
153
153
|
|
|
154
|
-
`
|
|
155
|
-
rehearses a draft before binding;
|
|
156
|
-
|
|
154
|
+
`get_workflow` reads the source back; `set_workflow` with `dry_run` and `record_id` or `preview_inputs`
|
|
155
|
+
rehearses a draft before binding; `set_workflow` with `delete: true` and the `workflow_id` unbinds it. An unbind is refused
|
|
156
|
+
while the deployed version of the app calls the alias or an agent of the app declares it (`even_if_invoked` overrides it for a
|
|
157
|
+
workflow that has run, only when the caller that ran it is gone).
|
package/docs/building_an_app.md
CHANGED
|
@@ -83,8 +83,7 @@ lotics app pull <app_id> # the app's tree, as ./<name>.app.json
|
|
|
83
83
|
A new workspace is built in one order: one `set_tables` call with the tables, then every `set_app`
|
|
84
84
|
call, then fixes. One call carries at most 50 tables, 200 first rows a table, and 2000 rows and 2000
|
|
85
85
|
documents in all; a workspace of more tables is built in further calls, each naming the tables already made by
|
|
86
|
-
their ids.
|
|
87
|
-
`set_tables`.
|
|
86
|
+
their ids.
|
|
88
87
|
|
|
89
88
|
A table is a document: its `fields` are keyed by alias, and a link field names the table it
|
|
90
89
|
points at by id, or by the `ref` of another table of the same call, and states `multi`. A
|
|
@@ -123,10 +122,10 @@ Readings over its rows, a record's Sections, an Act per status move, a picture w
|
|
|
123
122
|
|
|
124
123
|
**The tree changes after the app exists, and applying it again is how it lands.** Pull the tree, edit
|
|
125
124
|
the file, apply it; or send `nodes` for one part. An Act whose write the tree cannot say names its own `workflow`; that workflow's code is
|
|
126
|
-
the live one, and `lotics run
|
|
125
|
+
the live one, and `lotics run set_workflow` changes it. A job no press starts — an agent or a
|
|
127
126
|
service handing the app a file or a list of rows — is a Workflow node the app declares
|
|
128
127
|
(`lotics docs reference/workflow-node`): the node states what it takes and hands back, and `lotics run
|
|
129
|
-
|
|
128
|
+
set_workflow` sets its code with `inputs` and `outputs` left out of its `on({ type: "app", ... })` header.
|
|
130
129
|
|
|
131
130
|
**What a person reads and does in the app, an agent reads and does through it.** Writing the tree generates a
|
|
132
131
|
query for every List, record, child List and figure the screen reads, and a workflow for every add, edit,
|
|
@@ -147,7 +146,7 @@ lotics app pull <app_id> # the live version's source, on a machin
|
|
|
147
146
|
```
|
|
148
147
|
|
|
149
148
|
What the app reads and writes is bound on the app, never in the project: `lotics run
|
|
150
|
-
set_app_queries` binds named queries, `lotics run
|
|
149
|
+
set_app_queries` binds named queries, `lotics run set_workflow` a workflow body, and each mints a
|
|
151
150
|
version. `useQuery("<alias>")` and `useWorkflow("<alias>")` call them. A deploy uploads the build
|
|
152
151
|
and carries every binding forward unchanged.
|
|
153
152
|
|
|
@@ -165,7 +164,7 @@ that caller.
|
|
|
165
164
|
Static checks prove parse, types and names — they evaluate nothing. Rehearse a write first:
|
|
166
165
|
|
|
167
166
|
```
|
|
168
|
-
lotics run
|
|
167
|
+
lotics run set_workflow '{"source":"on({ type: \"app\", app_id: \"app_…\", alias: \"…\" });\n…","dry_run":true,"record_id":"rec_…"}'
|
|
169
168
|
```
|
|
170
169
|
|
|
171
170
|
It walks the draft through the same engine an Act's preview uses, under the app's authority: reads are real,
|
package/docs/cli_reference.md
CHANGED
|
@@ -28,31 +28,30 @@ Per-command syntax, flags, contracts, and gotchas for the public `lotics` CLI. S
|
|
|
28
28
|
| `lotics run <tool> '<json>'` | Execute a tool (text output via toModelOutput). Args may also come from a file (`lotics run <tool> @args.json`) or stdin behind the `-` sentinel (`cat args.json \| lotics run <tool> -`) — both bypass the OS `ARG_MAX` limit for large payloads (a knowledge-doc `content`, a bulk update). A leading `@` on the args is unambiguously a file path (JSON args start with `{`). **Stdin is asked for, never guessed.** `lotics run <tool>` with no payload runs the tool with no arguments and returns at once. `lotics report` takes the same sentinel. In PowerShell use `@file`: quotes inside an inline argument are consumed by the shell, and the CLI reports the JSON it received with its quotes gone — the error names both escapes. |
|
|
29
29
|
| `lotics run <tool> --json '<json>'` | Execute a tool (full JSON output) |
|
|
30
30
|
| `lotics run <tool>` — **file cells** | A file in a tool's result carries its `fil_…` id and metadata and **no `url`**, on every tool and in both output modes. That is not a broken file — this surface resolves no URL for a cell. Reach the bytes with `lotics file download <file_id>`, which takes the id straight from the cell; the text output says so whenever a result carries one. |
|
|
31
|
-
| — | **Every tool is invoked here, including the ones that RUN something** (`run_app_workflow`, `run_app_query`) and every one that builds: `set_tables` (a list of table documents), `set_app` (one app), `set_app_queries`, `
|
|
31
|
+
| — | **Every tool is invoked here, including the ones that RUN something** (`run_app_workflow`, `run_app_query`) and every one that builds: `set_tables` (a list of table documents), `set_app` (one app), `set_app_queries`, `set_workflow`. A command exists only for work that touches a local file: `app apply`, `app create --custom`, `app pull`, `app deploy`. |
|
|
32
32
|
| — | **An app's queries and workflows take more than their params and inputs** (`lotics docs agent_surface`). `run_app_query '{"app_id","alias","params"}'` also takes `filters` and `sort` over the query's output columns, `fields` (the columns to return), `count` with `count_by` or `count_where`, `aggregate`, `limit` (200 rows at most; 50 where none is stated) and `cursor`, and `runs` — up to 20 reads in one call, each `{"alias", …}`, a refused one answered with its `error`; one call's rows total at most 262,144 bytes. `export: {"key", …narrowing}` instead of the read arguments makes the file an app's Export makes (`lotics docs agent_surface`). `run_app_workflow '{"app_id","alias","inputs"}'` also takes `dry_run: true` — a rehearsal on the real inputs, nothing written or sent; the answer is what the run would return, with the reads it made under `dry_run.reads`, the calls it would make under `dry_run.writes` and the papers it rendered under `dry_run.papers` and the parts of the answer it could not say under `dry_run.unpredicted`; a workflow no dry run is offered of answers `status: "not_previewed"` — and `runs` — up to 50 runs in order, the first refused, failed or not previewed ending the call, an input `{"from_run": <index>, "path": "data.<key>"}` reading an earlier run's answer. |
|
|
33
33
|
| — | **The exit code reports the WORK, not just the call — for the tool that RUNS one.** `run_app_workflow` whose envelope carries a failed `status` (`error`/`failed`/`cancelled`, or `not_previewed` — a dry run asked for and not given) exit non-zero and print `<tool> → <status>: <message>` to stderr, so `lotics run … && next-step` cannot walk past a refused run. The rule is an allowlist of FAILURE — an unrecognized status exits 0, so a status added later never turns a working script red. A parked run (`awaiting_input`) is not a failure: it is waiting for an answer and the work is still live. Only a TOP-LEVEL `status` counts; one inside the data belongs to the data. Any OTHER tool's `status` is data, and exits 0. |
|
|
34
34
|
| `lotics run <tool> '<json>' -o <dir>` | Also save every **signed link** the answer carries — an object with a `url`, a `filename` and the `expires_at` its signature ends at, at any depth, such as `screenshot_app`'s images. Each is fetched into `<dir>` (created if missing) under its `filename`, taking a free spelling beside a file of that name already there, and a link's `snapshot` text is written beside it as `<name>.txt`. The written paths follow the answer on **stdout**, one per line (on stderr under `--json`, whose stdout stays the one object). A link is signed for an hour; one the store no longer serves exits non-zero. A workspace file states no `expires_at` and is left to `lotics file download`. |
|
|
35
|
-
| `lotics run
|
|
35
|
+
| `lotics run set_members '{"members": {"<email or member id>": {"role": "member", "name": "…"}}}'` | Invite, rename, change a role, remove (`null`) or restore members, or give a managed member a `password` (8 to 128 characters); `groups` keeps the member groups in the same call. Admin only: the whole input is checked before an email is sent. A member's `lotics tools` does not list it, nor `set_workspace`. View-as is the web app's preview alone; the CLI acts as the signed-in member. |
|
|
36
36
|
| `lotics run <tool> --print-created` | Report the records the call created, grouped by table, with a paste-ready `delete_records` per table and the mandatory caveat naming what cannot be auto-undone (external integrations, notifications, possible sub-workflows). Works for any tool that returns a `side_effects` block, not workflows alone. |
|
|
37
37
|
| `lotics run <tool> --cleanup` | Implies `--print-created`, then runs those deletes — harvested records **only**, never files / external calls / notifications. **Not a rollback**; a rollback is structurally impossible here. A partial cleanup exits non-zero so a script cannot read it as success. |
|
|
38
38
|
| `lotics file upload <file\|dir...>` (alias `lotics upload`) · `--stdin` · `--base64` · `--url <url>` | Upload files/directories. **The transport is chosen by size and is not a flag**: under 8 MiB the file is POSTed to `/v1/files`, several such files to a request up to half that route's 100 MiB cap; at or above it the CLI takes presigned part URLs and PUTs the bytes straight to object storage, so they never pass through the API. That threshold matches the AWS CLI's own `multipart_threshold`, and the number matters less than there being nothing to choose — one verb, any size, up to the 2 GiB a workspace may store. A large upload reads one part at a time, so memory stays flat regardless of file size, and a failure part-way abandons the parts already sent rather than leaving them billable and invisible. A directory expands to its immediate files; `--as <name>` renames a single upload. **Three alternative byte sources, for a caller that never had the bytes on disk** — an attachment decoded in memory, a generated document, a signed download link — each mutually exclusive with the others and with a path argument: `--stdin` takes raw bytes on stdin, `--base64` takes base64 on stdin (the shape attachments arrive in), `--url <url>` fetches the URL first. `--stdin`/`--base64` REQUIRE `--as`, because stdin carries no filename and the mime type is derived from it; `--url` falls back to `Content-Disposition` then the URL's last path segment. `--base64` decodes STRICTLY — `Buffer.from(s, "base64")` silently skips invalid characters and truncates on bad padding, so a corrupted pipe would otherwise store a short file that only fails when a human opens it. The `--url` fetch happens in the CLI, not the server: the URL comes from the operator running the command, so routing it through the backend would add an SSRF surface to buy what `curl` already does. |
|
|
39
|
-
| `lotics file download <file_id> [<path>]` · `-o <dir>` | (alias `lotics download`) Download a stored file: `GET /v1/files/{id}/signed_url` → fetch the presigned URL and write it where you asked. **The two spellings mean two different things, and neither is read by shape: the positional `<path>` is the FILE to write, `-o <dir>` is the DIRECTORY to save into.** That is `cp` and `curl -o`, so nothing here consults an extension. A named file is written as named, its parent created, overwriting what is there — the point of naming it is that the next command opens that exact path. A directory is created if missing and written into under the stored filename (the response's `Content-Disposition`), taking a free spelling beside a file of that name already there so a repeat download never clobbers the first; with no destination at all, that filename lands in cwd. Give the destination once — a positional and `-o` together is refused, as is a positional that names an existing directory or ends in a separator (`a directory goes in -o`). The first argument is a **file id**, so a path in that slot is refused rather than sent as an id. The written path goes to **stdout** (under `--json`, `{file_id, path, filename, stored_filename}`) and the narration to stderr, so a download pipes into whatever opens it. `lotics file download record <record_id> <field_alias> [-o <dir>]` (the field named by the alias `
|
|
39
|
+
| `lotics file download <file_id> [<path>]` · `-o <dir>` | (alias `lotics download`) Download a stored file: `GET /v1/files/{id}/signed_url` → fetch the presigned URL and write it where you asked. **The two spellings mean two different things, and neither is read by shape: the positional `<path>` is the FILE to write, `-o <dir>` is the DIRECTORY to save into.** That is `cp` and `curl -o`, so nothing here consults an extension. A named file is written as named, its parent created, overwriting what is there — the point of naming it is that the next command opens that exact path. A directory is created if missing and written into under the stored filename (the response's `Content-Disposition`), taking a free spelling beside a file of that name already there so a repeat download never clobbers the first; with no destination at all, that filename lands in cwd. Give the destination once — a positional and `-o` together is refused, as is a positional that names an existing directory or ends in a separator (`a directory goes in -o`). The first argument is a **file id**, so a path in that slot is refused rather than sent as an id. The written path goes to **stdout** (under `--json`, `{file_id, path, filename, stored_filename}`) and the narration to stderr, so a download pipes into whatever opens it. `lotics file download record <record_id> <field_alias> [-o <dir>]` (the field named by the alias `query_records` shows it under) spreads every file on a record's file field over a DIRECTORY — there is no single file for N files to be. |
|
|
40
40
|
| `lotics file list [--limit <n>] [--cursor <token>]` | The workspace's files, newest first — id, upload time, bytes, MIME type, filename on stdout, one per line (`--json` for the object). `GET /v1/files` with no `file_ids`. A file holding the content of a knowledge doc or template you cannot use is left out. **A page, not a dump**: the store only ever grows, so the last line prints the command for the next page and `next_cursor` is null on the last one. The cursor is opaque and keyset — pass it back as given — so an upload landing mid-sweep cannot make a walk skip or repeat a row. Every other file verb takes an id, so this is the only answer to "what is in here" short of reading Postgres. |
|
|
41
|
-
| `lotics file delete <file_id>` | Archive a stored file, over the `delete_file` tool. Deleting an original deletes every version of it; a file's current version cannot be deleted on its own, and a document template's file, or a version of it, only by a member who manages the template. **Refused while a record cell, a comment, a knowledge doc, a document template, a voice session or a pending change request still references the file or a version it takes along** — the refusal names the referents, so this is safe to try. The bytes are left in object storage; the row no longer serves them, which is what "deleted" means here. There is no `lotics delete`: the verb needs its noun. |
|
|
42
41
|
| `lotics knowledge list [--include-hidden \| --deleted]` | `GET /v1/knowledge_docs` — a table of id, name, tags, description (`--json` for the docs). **REST, not `get_workspace` kind `knowledge`**: the tool answers what the ASSISTANT may browse, and a hidden doc is out of that corpus by definition, so a tool-backed listing could never show one and the person who hid it would have no way back to it. Hidden docs are left out unless `--include-hidden` asks; those rows are marked `(hidden)`. **`--deleted` is the one tool-backed form**: it calls `get_workspace` with `kind: "knowledge"` and `include_archived: true`, page by page, and prints the deleted docs you can manage (id, name, deleted time, description); it cannot be combined with `--include-hidden`. |
|
|
43
42
|
| `lotics knowledge create --name <n> [--description <d>] [--tags <a,b>] (--from <file.md> \| --content <str>)` | Read the body client-side (a file XOR an inline string — exactly one required), then call `set_knowledge` with `{ name, description, content, tags? }` and no `knowledge_doc_id`, which makes a doc (description defaults to `""`). `--tags` files the doc as it is made, which is the only moment a corpus reliably gets labelled. Prints the new id to stdout. Large files ride the POST body fine. |
|
|
44
43
|
| `lotics knowledge get <id> [-o <file.md>]` | `GET /v1/knowledge_docs/{id}` (`getKnowledgeDoc`) → the doc with its **hydrated `content`** (the one whole-content read for a non-sandbox client; the tool `read_knowledge` reads a line range). `-o` writes the body via `writeFileAtomic`; else the body goes to stdout. `--json` prints the full doc instead. |
|
|
45
44
|
| `lotics knowledge update <id> [--from <file.md> \| --content <str>] [--name <n>] [--description <d>] [--tags <a,b>]` | Call `set_knowledge` with a `knowledge_doc_id` and **only** the provided fields (a body from --from/--content becomes `content`; the tool diffs + CASes the content change internally, so the CLI passes no `expected_content_file_id`). `--tags` REPLACES the doc's label set — the single-doc form, where the caller is looking at one doc and can state what it should carry. At least one field required; --from and --content are mutually exclusive. |
|
|
46
45
|
| `lotics knowledge tag <id...> [--add <a,b>] [--remove <c,d>]` | `PATCH /v1/knowledge_docs` with `{ knowledge_doc_ids, add_tags?, remove_tags? }` — one transaction over the whole set. A **DIFF applied to each doc's own labels**, never a replacement: the docs named on one command line carry different labels, so one array across them would strip whatever the others were filed under. Removal matches case-insensitively; adding a label a doc already carries writes nothing. Ids may be separate arguments or comma-separated. At least one of --add/--remove required. |
|
|
47
46
|
| `lotics knowledge hide <id...>` / `lotics knowledge unhide <id...>` | `PATCH /v1/knowledge_docs` with `{ knowledge_doc_ids, hidden }`. Hiding takes docs out of every **listing** — the Library's list, and the corpus `get_workspace` lists and its `search` searches — while leaving IAM untouched and keeping them readable **by id** (`read_knowledge` with an id, a code run staging one, an app agent's declared set). So it can never silently break an app that depends on a doc, and unhiding costs nothing. Refuses the no-argument form rather than reading it as "everything". |
|
|
48
|
-
| `lotics knowledge rm <id...>` | Delete docs via `
|
|
49
|
-
| `lotics knowledge restore <id...>` | Bring deleted docs back via `
|
|
47
|
+
| `lotics knowledge rm <id...>` | Delete docs via `set_knowledge` (`{ knowledge_doc_id, delete: true }`), one call per id: each must be a live doc you manage that no app or workflow names, and the command reports which succeeded and which failed. Sharing and history are kept. The REST execute path does not gate `needsApproval`, so this runs unattended. |
|
|
48
|
+
| `lotics knowledge restore <id...>` | Bring deleted docs back via `set_knowledge` (`{ knowledge_doc_id, restore: true }`), one call per id: each must be a deleted doc you manage, and the command reports which succeeded and which failed. Refuses the no-argument form. |
|
|
50
49
|
| `lotics app create <name> --custom [path]` | **A custom-code app**: creates the app (`POST /v1/apps`), scaffolds a Vite + React + TypeScript project into `[path]` (default `./<name>`, refused when not empty — before the app row exists) that depends on `@lotics/app-sdk` alone and draws with plain React, and installs it (`npm install --ignore-scripts`), then writes the declarations of the app's live bindings (`GET /v1/apps/{id}/types`) into `.lotics/`, which the project's `tsconfig.json` includes. `package.json#lotics` names the app and its workspace, which is how `app deploy` in that directory finds both. The app has no version until the first `lotics app deploy`. `--custom` is required: a JSON app is made by `lotics app apply` (the tool `set_app`). The SDK's reference is `node_modules/@lotics/app-sdk/AGENTS.md` inside the project. |
|
|
51
50
|
| `lotics app pull [app_id] [path]` | **An app's live definition, pulled.** The app is read with `get_app`: its author is shown a body, a JSON **tree** (with the addresses of its nodes) pulled as that tree and a custom-code app (the identity alone) as its source; an app whose stored tree cannot be read is refused with the reason `get_app` gives, and so is an app that shows the caller no body (the caller does not author it, or it has no version). **A JSON app**: the tree is written as JSON to `[path]` (a directory receives `./<name>.app.json`; default `./<name>.app.json`), and the app's id and current version beside it in `<file>.lotics.json` (`{ "app_id": "…", "version_id": "…", "digest": "…" }`, the digest being the file as the CLI last left it; a record without all three is refused by `app apply`, naming `lotics app pull`) — a custom-code project records both in `package.json#lotics`, a single file has no such place — so the next `lotics app apply <file>` edits that app, and is refused if the app has since moved past that version. An existing file is overwritten only when its `.lotics.json` names this same app and the file is unchanged since it was last pulled or applied; a file with edits, or whose record names the app but no digest, is refused, naming `--force`, which overwrites it. `--force` is a flag of `app pull` alone. `--json` prints `{workspace_id, app_id, path, outcome}`. **A custom-code app**: its live source, as a project ready to deploy on it. The target is `[path]`, else this directory when it is the app's project (or no app is named), else `./<name>`. **The app's own project is brought up to date in place** — but only when it holds no edit since the version `package.json#lotics.current_version_id` names: its source (packed as a deploy packs it) is compared with that version's archive, ignoring `.lotics/` and `package.json#lotics`, and any difference refuses the pull, naming the changed files; a pull never merges, so local work is never lost. Already at the live version is a no-op that says so. **An empty or new directory receives the source whole**; any other directory is refused. Downloads come from `GET /v1/apps/{id}/versions/{version_id}/source`. `package.json#lotics` is then set to exactly the app, its workspace and the pulled version, dropping every other key an older CLI wrote there, so the next `lotics app deploy` builds on the live version; then `.lotics/` is written (`GET /v1/apps/{id}/types`) and dependencies installed (`npm ci` with a lockfile, else `npm install`, both `--ignore-scripts`). |
|
|
52
51
|
| `lotics app apply <file> [--app <app_id>] [--check] [--json]` | **A JSON app, one at a time, through the tool `set_app`.** The file is an app's tree (`lotics docs reference`: the App node with the nodes under it, its tables and templates named by alias). **No app known** (no `--app`, no `<file>.lotics.json` beside it): the file is sent as `{ "body": … }` to make a new app, named by the body's `name`, and the id made is printed, then recorded with its version in `<file>.lotics.json`. **An app known**: its current tree is read (`get_app`) and the **RFC 7396 merge patch** that turns it into the file is sent as `body` with the `app_id` and the version the patch was computed against as `base_version_id` — a key the file drops is `null` in the patch, a list is replaced whole, what the file leaves alone is left alone, and a tree the app already has sends nothing and says so. **A file pulled at a version the app has since left is refused** before anything is sent, naming both versions and `lotics app pull <app_id> <path>`: pull the current tree into another path, carry the edits over, apply that file. `--app` naming an app other than the sibling file's is refused too. A write that races the apply is refused by the server, which names the app's current version. A file that sets an object key to `null` is refused naming the key, since a merge patch reads `null` as a removal. `--app` names the app, else the sibling file does; an app whose `get_app` shows no body (it is not yours) or whose stored tree cannot be read is refused, and a custom-code app takes its identity (name, description, icon, colour) only, and no `<file>.lotics.json` records it, since an identity write makes no version — its source deploys with `app deploy`. **The file is never rewritten**: a node the tree leaves out draws as the rows call for it and is stored nowhere (`lotics docs design`). It prints what `set_app` answered and reports its `outcome`: its notes and findings, every finding at once, and the saves, adds and Deletes the write grants and removes. **`--check` sends `dry_run`** (a flag of `app apply` alone, as is `--app`) — what would refuse the tree, and what it would grant and remove — and writes nothing, the file and its sibling included; an `outcome` of `refused` exits 1. A tree the app already has, or one `set_app` answers `unchanged`, records the version the app stays at in the sibling. **To go back to an earlier version, apply the earlier tree again.** Resolves and ANNOUNCES its workspace first. `--json` prints `{workspace_id, app_id, outcome, write}`, `outcome` being `set_app`'s: `created`, `updated`, `unchanged` or `refused` (with `--check`, what it would be). |
|
|
53
52
|
| `lotics app deploy [-m <message>]` | **Build this directory and upload it as a new version of the live app.** Rewrites `.lotics/` with the declarations of the app's live bindings (`GET /v1/apps/{id}/types`, replacing each file there), then runs the project's `npm run typecheck` (warned about when absent; a type error in the app's own files stops the deploy, one inside an installed package is reported and does not) and `npm run build`, tars the source (without `node_modules`, `dist`, `.git`, `*.tsbuildinfo`) and `dist/`, and posts both to `POST /v1/apps/{id}/versions` on the version `package.json#lotics.current_version_id` names, then stamps the new one there. The version carries the app's queries, workflows, agents and capabilities forward unchanged — those are written through their tools. A project with no `build` script, or whose `package.json#lotics` still declares `queries`, `workflows`, `agents` or `capabilities`, is refused before anything is built; the refusal names the tool that sets each. **A 409 because another version went live since this directory's last deploy** (a deploy from elsewhere, a rollback) prints the server's sentence and the version that is live, and names `lotics app pull`: run in this directory, it brings an unedited project up to date, and lists the files a project with edits changed, to carry over into a fresh pull. `-m` (or a bare positional) is the version's message, optional. The workspace comes from `package.json#lotics.workspace_id` unless `--workspace` / `LOTICS_WORKSPACE` names another. |
|
|
54
53
|
| `lotics docs` \| `lotics docs <area>[/<section>]` \| `lotics docs [<area>[/<section>]] --grep <text>` | **This CLI's own references, carried inside the binary** — the model reference, this index, and every doc under `docs/` — so the doc a reader opens describes the binary answering, listed by the job a reader comes to do. Capped at ONE PAGE: a doc that does not fit prints its opening and the addresses of what it holds (`lotics docs <area>/<section>`, each section's size beside it, or a table's row names), and every address prints within a page. **A reference the binary's copy lacks, or a part of one the server serves, is read from the server's `docs` tool** with this machine's credential, after the copy's refusal — a page the server added since this binary was built, which a server refusal can cite. A name matching more than one reference, or a part missing from a guide to this CLI, is answered by the copy alone. **`--grep` searches the references the server serves** (not this CLI's own guides, which it refuses), with this machine's credential, narrowed to the area or section named: literal text unless `--regex`; a literal search that finds nothing while the pattern read as an expression matches (`a|b`) returns those lines under a note saying so, and `--literal` keeps the literal zero; with `--case-sensitive`, `--diacritic-insensitive`, `--context-lines <n>` and `--limit <n>` — the dialect the knowledge lines of `get_workspace` with `search` are read in — each hit under the address that opens its page; any of those options without `--grep` is refused. A custom-code app's SDK reference ships inside `@lotics/app-sdk` in the app's `node_modules`. |
|
|
55
54
|
| `lotics upgrade` | Update this CLI in place. Runs the same installer a person would, chosen by how THIS copy arrived: an npm install upgrades through npm, a script install re-runs the script — the runtime knows which (the executable is compiled, the npm bin runs under node), so nobody has to. It downloads nothing itself; resolving a version, verifying the checksum and replacing a running executable already exist in the installers, and a second copy of that inside the binary would be a second thing to get right. Replacing the binary while it runs is safe — a rename leaves the running image mapped on unix, and on Windows the installer moves the old aside precisely because the file is in use. Already current is a no-op that says so. Needs no auth. |
|
|
56
|
-
| `lotics docs reference` \| `lotics docs reference/<section>[/…]` | **The reference for tables and apps, from inside the binary** — at this CLI's version; `lotics docs` lists it under "Build an app", after `design`. Its first page is what the documents compose with, the working order (jobs → tables and fields → `records` → one app per job → one `set_tables` call with every table, then every `set_app` call) and the section addresses; every page of it is whole. Every key of a table document and an app body, every field `type` with the config each one needs, the option / view / inline-template shapes, `records` (how a row of a table is recognised), `write_rules`, the apps (each register, record, act and check), the row format (relative dates `@today` / `@month-start` with whole-day offsets; links as `"<table-ref>:<row-ref>"`), the rules, the caps of one `set_tables` call, and a worked example;
|
|
55
|
+
| `lotics docs reference` \| `lotics docs reference/<section>[/…]` | **The reference for tables and apps, from inside the binary** — at this CLI's version; `lotics docs` lists it under "Build an app", after `design`. Its first page is what the documents compose with, the working order (jobs → tables and fields → `records` → one app per job → one `set_tables` call with every table, then every `set_app` call) and the section addresses; every page of it is whole. Every key of a table document and an app body, every field `type` with the config each one needs, the option / view / inline-template shapes, `records` (how a row of a table is recognised), `write_rules`, the apps (each register, record, act and check), the row format (relative dates `@today` / `@month-start` with whole-day offsets; links as `"<table-ref>:<row-ref>"`), the rules, the caps of one `set_tables` call, and a worked example; **Offline, no account.** |
|
|
57
56
|
| `lotics report '<json>'` \| `lotics report @report.json` | File a report with the Lotics team about what got in your way. **Covers the classes telemetry structurally cannot see**: a capability that does not exist (no command ran, so nothing was recorded), a command that exited 0 having done the wrong thing, an error whose message did not name the remedy, and anything that made authoring slower than it should be. **A frame, not a paragraph** — `{goal, actual, expected?, tried?, wanted?}`, `goal` and `actual` required, unknown keys dropped rather than refused. **No severity or category.** Ingest is inline JSON, `@file`, or `-` for stdin. A bare sentence is refused with the frame printed beside it, so the fix is one step; a bare invocation prints the frame BEFORE asking for a credential, since someone whose key will not resolve is exactly who has something to report. **Not spooled**: unlike telemetry it posts inline, prints whether it landed, and exits non-zero if it did not, echoing the report back so a failed send never loses it. Runs regardless of `LOTICS_TELEMETRY` — invoking it IS the consent that passive collection needs an opt-in for — but with telemetry off there are no recorded commands to attach, and it says so rather than implying context it does not have. Requires auth. Never paste records, file contents, or credentials. **Prints the id of each frame filed** — a filing nobody can cite cannot be answered about. The ids come from the server, so an instance that only logs the frames prints the count alone; the CLI never mints one of its own, which would hand back a token that resolves to nothing. |
|
|
58
57
|
|
package/docs/data_model.md
CHANGED
|
@@ -175,11 +175,11 @@ A table is named by its alias wherever a call names one (`table_id`, a link's `t
|
|
|
175
175
|
the reader first, or in the same call.
|
|
176
176
|
- **`force` consents to losing values:** deleting a field rows hold, removing an option rows hold, and a change of type
|
|
177
177
|
that does not convert every value.
|
|
178
|
-
- A money formula is proven by its numbers. `
|
|
178
|
+
- A money formula is proven by its numbers. `query_records` with `aggregate` the affected totals before the change and after, and
|
|
179
179
|
compare. A new field in a formula is tested for the value you want, never for differing from an empty cell, which reads
|
|
180
180
|
`""` and differs from every option.
|
|
181
181
|
- A check's expressions are written in the language of `workflows/table-checks`.
|
|
182
|
-
- `views`
|
|
182
|
+
- Views are written with `set_table_views`, not `set_tables`: `views` is keyed by a view's id (patch; `null` deletes) or a new key (create), and `order` lists view ids or new keys. `get_table` lists each view's id and name and returns the definitions with `views: true`.
|
|
183
183
|
- A refused edit writes nothing and lists everything wrong; `dry_run` is for reading the grants it would make or deciding `force`.
|
|
184
184
|
|
|
185
185
|
A worked call: add the option `cancelled` to `stage` (the whole list restated), add a check, and consent to losing
|
|
@@ -68,14 +68,17 @@ this host, give the person the file and a `person` drop link.
|
|
|
68
68
|
you control. `variables` are plain text, rich `html`, an auto-rendered `table` or a `list`; `format` (page size) and
|
|
69
69
|
`orientation` are optional. A change may send `patch_ops`, exact search-and-replace edits of the stored markup, in place
|
|
70
70
|
of the whole `template`.
|
|
71
|
-
- **`pdf-form`** — an existing PDF filled in place. `set_template` with the uploaded `template_file_id
|
|
72
|
-
`
|
|
73
|
-
|
|
71
|
+
- **`pdf-form`** — an existing PDF filled in place. `set_template` with the uploaded `template_file_id`
|
|
72
|
+
answers `page_count` and `pages` (each page's `page_index`, unrotated MediaBox `width` and `height`, `origin_x`, `origin_y`
|
|
73
|
+
and its `labels`, the text with its position and font size; `get_template` returns the same); `set_template` again with
|
|
74
|
+
`expected_version` and the positioned `variables` (`text` / `checkbox` / `image`, each with a page and x/y/size). Data is one scalar per field. A field whose `page` is past `page_count`, or whose box lies outside that page's MediaBox,
|
|
75
|
+
is refused. The answer holds at most 50 pages and 400 labels a page; `pages_omitted` and each page's `labels_omitted`
|
|
76
|
+
count the rest, and `pages_unavailable` says why a template's pages could not be read.
|
|
74
77
|
A positioned text field prints in Arimo, which covers Vietnamese; a PDF's own field keeps its font while its value is
|
|
75
78
|
ASCII and switches to Arimo past it.
|
|
76
79
|
- **`excel`** — a `.xlsx` with `{{marker}}`s in the cells to fill, and a `variables` map. Markers are validated at create
|
|
77
80
|
time: a structural error blocks the save, and a declared variable with no marker comes back in `unmarked_variables`
|
|
78
|
-
(skipped).
|
|
81
|
+
(skipped). `set_template` with `dry_run: true` runs every check and stores nothing; a refusal lists every error with its code and cell, and `warnings[]` (`severity`, `code`, `message`, `sheet_name?`, `cell?`) comes back with the answer. Pass
|
|
79
82
|
raw numbers, ISO dates and booleans at generate time; the cell's number format shows them.
|
|
80
83
|
- **`word`** — a `.docx` with `{{marker}}`s. `variables` is optional: every marker is derived (`{{name}}` string,
|
|
81
84
|
`{{FOR … IN name}}` array, `{{IF name}}` boolean), and a marker you did not declare is still added
|
|
@@ -133,7 +136,7 @@ generate_document {"document_template_id": "dtl_…", "filename": "invoice-104
|
|
|
133
136
|
|
|
134
137
|
- `data` is a map keyed by your variable names. Scalars for scalar/form fields; arrays for
|
|
135
138
|
`table`/`list`/loop variables. `filename` excludes the extension (the type sets it). Pass a select, a
|
|
136
|
-
member or a link as `
|
|
139
|
+
member or a link as `query_records`' `display` words, a number and a date raw.
|
|
137
140
|
- `generate_document` renders the template's own type and returns a generated file object; take
|
|
138
141
|
its `file_id` onward.
|
|
139
142
|
- A PDF no template is wanted for takes `html`, a `filename` and its `data` in place of `document_template_id`: the markup is
|
|
@@ -194,8 +197,6 @@ These tools work across all five types:
|
|
|
194
197
|
|---|---|
|
|
195
198
|
| `get_workspace` | list the workspace's templates (`kind: "template"`), each with its id, type and one line |
|
|
196
199
|
| `get_template` | one template's full definition, incl. the `variables` it expects and the `current_version` a change needs |
|
|
197
|
-
| `set_template` | create a template,
|
|
198
|
-
| `clone_template` | copy a template to iterate on, in chat |
|
|
199
|
-
| `delete_template` | remove a template |
|
|
200
|
+
| `set_template` | create a template, change one, delete it (`delete: true` with `template_id`) or bring it back (`restore: true`); `dry_run: true` checks the call and stores nothing; a copy is `get_template` then `set_template` |
|
|
200
201
|
|
|
201
202
|
Call `get_template` before generating when you don't already know a template's variable names.
|
package/docs/filters.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Filters — choosing records by their fields
|
|
2
2
|
|
|
3
|
-
One grammar for every tool that takes a filter: `query_records
|
|
3
|
+
One grammar for every tool that takes a filter: `query_records` (rows and `aggregate`), `update_records`
|
|
4
4
|
and `delete_records` by filter, a view's filter, a rollup's filter, and the filters a caller passes
|
|
5
5
|
to an app's query.
|
|
6
6
|
|
|
@@ -151,3 +151,20 @@ before it (up to 3), and `condition` names a field of the last table reached:
|
|
|
151
151
|
## Sort
|
|
152
152
|
|
|
153
153
|
A view's sort is a list, applied in order: `[{ "field_key": "due", "order": "asc" | "desc" }]`.
|
|
154
|
+
|
|
155
|
+
## Counting, aggregating and reading by id
|
|
156
|
+
|
|
157
|
+
`query_records` takes `filters` in one table and answers rows, or, with `aggregate`, statistics over them: `count`, or an
|
|
158
|
+
operation by the field's type, optionally broken down by `group_by`. Records are read one table at a time. A question over
|
|
159
|
+
several tables is one number from `tables`, a list of entries that each state their own `table_id` and filter, since each
|
|
160
|
+
table spells its fields its own way; it combines with no top-level `table_id`, `view_id`, `search` or `filters`.
|
|
161
|
+
|
|
162
|
+
- **Values held in another table.** A filter value (a customer's name, a code) is not looked up for you. Find where it
|
|
163
|
+
lives with `get_workspace` and `search`, which names the tables and docs that hold the words, then state it in that
|
|
164
|
+
table's own filter. When `is_any_of` on a text field names values, the answer lists the ones no row of the table
|
|
165
|
+
matched, so a total that misses part of what you asked for says so; pass `tables` to count them across the tables
|
|
166
|
+
that hold them.
|
|
167
|
+
- **By id.** `record_ids` reads those records whole, deleted ones included, with their `display`, `links`, `archived_at`,
|
|
168
|
+
`created_at` and `updated_at`; `table_id` may be left out. It takes at most 50 ids on every origin (an agent, a workflow
|
|
169
|
+
step, an app) and combines with `field_keys` and `comments` only. `update_records` takes the same 50 ids, and with
|
|
170
|
+
`restore: true` brings deleted records back.
|
package/docs/first_build.md
CHANGED
|
@@ -153,7 +153,7 @@ set_access {"resources": [{"resource_type": "app", "resource_id": "app_…"}], "
|
|
|
153
153
|
|
|
154
154
|
## 4. What the owner will ask an agent: Query and Workflow nodes
|
|
155
155
|
|
|
156
|
-
Read the brief again for what the business asks its assistant again and again: a figure or list whose definition is its own (orders waiting for pickup), an operation (chase overdue balances). Declare each as a Query or Workflow node of the app, with a `description` for an agent that has not seen the screen (`agent_surface` § Declaring more); a connected service (MISA, a carrier) is reached by a Workflow node's step (`workflows`); a question asked once stays on `
|
|
156
|
+
Read the brief again for what the business asks its assistant again and again: a figure or list whose definition is its own (orders waiting for pickup), an operation (chase overdue balances). Declare each as a Query or Workflow node of the app, with a `description` for an agent that has not seen the screen (`agent_surface` § Declaring more); a connected service (MISA, a carrier) is reached by a Workflow node's step (`workflows`); a question asked once stays on `query_records` with `aggregate`.
|
|
157
157
|
|
|
158
158
|
## 5. The owner's rows: `create_records`
|
|
159
159
|
|
|
@@ -177,4 +177,4 @@ run_app_workflow {"app_id": "app_…", "alias": "act_start_baking", "inputs": {"
|
|
|
177
177
|
screenshot_app {"app_id": "app_…"}
|
|
178
178
|
```
|
|
179
179
|
|
|
180
|
-
Fix a failure in the table or app that causes it. Leave standing instructions with `set_workspace` (`instructions`, one entry per rule): who the business is, the words its people use, what any agent working here must keep to. As an admin, invite the people the brief names with `
|
|
180
|
+
Fix a failure in the table or app that causes it. Leave standing instructions with `set_workspace` (`instructions`, one entry per rule): who the business is, the words its people use, what any agent working here must keep to. As an admin, invite the people the brief names with `set_members` (`members` keyed by email, role `member` or `admin`); where the tool is missing, say whom to invite. Then show the owner what you built, job by job, each paper they sent and what makes it, with the choices you made for them to change.
|
package/docs/knowledge_docs.md
CHANGED
|
@@ -99,10 +99,10 @@ whatever the docs themselves use. `tags` on `set_knowledge` states one doc's com
|
|
|
99
99
|
|
|
100
100
|
## Deleting and restoring
|
|
101
101
|
|
|
102
|
-
`
|
|
102
|
+
`set_knowledge` with a `knowledge_doc_id` and `delete: true` deletes a doc: it leaves every listing, search and read. **Its sharing and version history are
|
|
103
103
|
kept**, so a deleted doc is recoverable. `get_workspace` kind `knowledge` with `include_archived: true` lists the deleted
|
|
104
|
-
docs you manage, and `
|
|
105
|
-
|
|
104
|
+
docs you manage, and `set_knowledge` with `restore: true` brings one back with the access it had. Each call takes one doc,
|
|
105
|
+
which must be one you manage. A doc with no owner is restored only by an admin, who
|
|
106
106
|
becomes its owner. A doc that an app or workflow still names cannot be deleted.
|
|
107
107
|
|
|
108
108
|
## Formatting — what a doc draws
|
package/docs/migration.md
CHANGED
|
@@ -20,15 +20,15 @@ roles are the workspace's own.
|
|
|
20
20
|
| `lotics scaffold diff` | `lotics app apply <file> --check` — what would refuse the tree, writing nothing |
|
|
21
21
|
| `lotics scaffold export` | `lotics app pull <app_id> [path]` — the app's tree as JSON, one app at a time |
|
|
22
22
|
| `lotics app codegen`, `app check`, `app dev`, `app preview` | nothing local: read an app with `lotics run get_app`, and change it through its tools |
|
|
23
|
-
| `lotics app workflow set` / `app query set` | `lotics run
|
|
23
|
+
| `lotics app workflow set` / `app query set` | `lotics run set_workflow` / `set_app_queries` — each mints a version |
|
|
24
24
|
| `lotics app versions` + a redeploy of an old tree | apply the earlier tree again (`lotics app apply <file>`), or deploy the earlier source; table changes and data writes stay |
|
|
25
25
|
| `lotics app rename`, `app subdomain`, `package.json#lotics.capabilities` | `lotics run set_app` with the app's id (`public_subdomain`, `capabilities`; its name is the tree's `name`) |
|
|
26
|
-
| `lotics app api publish` / `unpublish` | `lotics run
|
|
26
|
+
| `lotics app api publish` / `unpublish` | `lotics run set_app_api '{"app_id":"…","published":true}'` / `false` |
|
|
27
27
|
| `lotics field rename` | `lotics run set_tables` with a merge patch of the table's document |
|
|
28
28
|
| `lotics app kit` | nothing: a custom-code app depends on `@lotics/app-sdk` (below) |
|
|
29
29
|
| `@lotics/xlsx` / `@lotics/docx` to build a template's file | any `.xlsx` / `.docx` library, then `lotics upload` (`lotics docs document_templates`) |
|
|
30
30
|
| `lotics file preview` | `lotics file download <fil_id>`, then open it |
|
|
31
|
-
| `lotics library list` / `library show <slug>`, `lotics preset list` / `preset show <slug>` |
|
|
31
|
+
| `lotics library list` / `library show <slug>`, `lotics preset list` / `preset show <slug>` | nothing: state the tables (`lotics docs reference`) and pass them to `set_tables` |
|
|
32
32
|
| `lotics library init <apg_id>`, `lotics app upgrade`, `lotics library fixtures` | nothing copies a package any more: state the tables (`lotics docs reference`) and pass them to `set_tables` |
|
|
33
33
|
|
|
34
34
|
`lotics tools` lists every tool, and `lotics tools <name>` prints its input.
|
|
@@ -39,7 +39,7 @@ with `plan`, `queries` or `workflows`) is no longer read by anything; keep the p
|
|
|
39
39
|
rest.
|
|
40
40
|
|
|
41
41
|
**Authored workflow bodies** an act names (`workflow` on an act) are the live workflow's own body:
|
|
42
|
-
`lotics run
|
|
42
|
+
`lotics run set_workflow` changes it.
|
|
43
43
|
|
|
44
44
|
**A table written before `party`** draws its people and organisations as things — a row with no
|
|
45
45
|
picture shows no initials: state `party` (`person` or `organization`) in that table's `records`.
|