@lotics/cli 0.315.13 → 0.315.14

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/dist/src/cli.js CHANGED
@@ -46872,7 +46872,7 @@ async function saveSignedLinks(links, dir) {
46872
46872
  }
46873
46873
 
46874
46874
  // src/version.ts
46875
- var VERSION = "0.315.13";
46875
+ var VERSION = "0.315.14";
46876
46876
  var APP_SDK_VERSION = "0.123.10";
46877
46877
 
46878
46878
  // src/timezone.ts
@@ -46931,7 +46931,7 @@ var app_bindings_default = "# App bindings \u2014 the queries, workflows and age
46931
46931
  var document_templates_default = '# Document Templates\n\nTurn workspace data into finished documents \u2014 invoices, contracts, reports, letters,\nlabels, emails \u2014 by filling a reusable template instead of writing a file from scratch.\nYou register a template once, then generate a filled file (or a rendered email) from it as\nmany times as you like, feeding it data from records, a workflow, or an agent run.\n\nThis is a **capability + usage** guide. For the exact input schema of any tool named here,\nrun `lotics tools <tool_name>` \u2014 that is always the source of truth for arguments.\n\n## The five template types\n\n| Type | What it fills | Output | Create with |\n|---|---|---|---|\n| `pdf-form` | fields overlaid on an **uploaded PDF** at fixed positions | PDF | `create_pdf_template` (mode `form`), on the CLI or in chat |\n| `html` (a PDF) | an **HTML + Handlebars** layout you write from scratch | PDF | `create_pdf_template` (mode `html`), on the CLI or in chat |\n| `excel` | an **uploaded `.xlsx`** with `{{marker}}` cells | `.xlsx` | `create_excel_template` |\n| `word` | an **uploaded `.docx`** with `{{marker}}`s | `.docx` | `create_word_template` |\n| `email` | an **inline HTML + Handlebars** body, rendered when the email is sent | email | `create_email_template`, on the CLI or in chat |\n\nTwo shapes underneath: **file-backed** (`pdf-form`, `excel`, `word`) clone an uploaded\noffice file and mark it up; **inline** (`html`, `email`) store the markup you author directly.\n\n## The lifecycle: create \u2192 generate \u2192 chain\n\n1. **Create** a template \u2014 register the file (or inline markup) and declare a `variables`\n map (the named slots the template fills). This returns a template id (`dtl_\u2026`).\n2. **Generate** a filled file \u2014 call `generate_document` with the\n template id, a `filename` (no extension), and a `data` map keyed by your variable names.\n It substitutes the markers and returns a **generated file** (with a `file_id`).\n3. **Chain** the result \u2014 generation only *produces* the file. Attaching it to a record,\n sending it, or saving it locally is a separate step: inside a workflow, pass the returned\n `file_id` to the next step; from the CLI, `lotics download <file_id>` fetches it.\n\nEmail is the exception: `generate_document` refuses an email template. An email template is\nrendered to a subject + body **at send time** from the template and the data the send step\nsupplies \u2014 so you author it with `create_email_template` (on the CLI or in chat) and a workflow/app send step (or the\nweb composer) does the rendering and delivery. External agents send their own email directly.\n\n## Creating each type\n\n### PDF from HTML\n\nWrite a full HTML document with inline CSS and `{{variable}}` placeholders, as `create_pdf_template`\nwith mode `html` on the CLI or in chat; it renders to PDF via a headless browser. Good for invoices, reports, letters, certificates \u2014 anything whose\nlayout you control. Declare `variables` (plain text, rich `html`, an auto-rendered `table`,\nor a `list`), and optionally `format` (page size) and `orientation`. See\n`lotics tools create_pdf_template` for the exact variable shapes.\n\n### PDF form\n\nFor an existing PDF you must fill in place (a government form, a printed contract), on the CLI or in chat:\n\n1. `lotics upload ./form.pdf` \u2192 a `file_id`.\n2. `create_pdf_template` with mode `form` and that `file_id`.\n3. `analyze_pdf_template` \u2014 extracts the labels and their positions off the page.\n4. `update_pdf_template` \u2014 define the positioned fields (`text` / `checkbox` / `image`, each\n with a page + x/y/size) in `variables`.\n\nForm-mode data is scalar-only (text, numbers, checkboxes) \u2014 one value per positioned field.\n\n### Excel\n\n1. Build a `.xlsx` in Excel (or with any spreadsheet library) and put `{{marker}}`s in the cells that\n should be filled. `lotics upload ./template.xlsx` \u2192 a `file_id`.\n2. `create_excel_template` with that `template_file_id` and a `variables` map. Markers are\n **validated at create time** \u2014 a structural error (mismatched loop, unknown helper, bad\n placement) blocks the save, and declared variables with no matching marker come back in\n `unmarked_variables` (skipped, not filled).\n\nOn the CLI, `validate_excel_template` checks an uploaded file\'s markers without creating a template. Pass\nraw numbers / ISO dates / booleans at generate time \u2014 the cell\'s number format handles display.\n\n### Word\n\n1. Build a `.docx` with `{{marker}}`s where values go. `lotics upload ./template.docx`.\n2. `create_word_template` with the `template_file_id`. `variables` is optional \u2014 every\n marker in the document is derived automatically (`{{name}}` \u2192 string,\n `{{FOR \u2026 IN name}}` \u2192 array, `{{IF name}}` \u2192 boolean). When you do declare, an unmarked\n declaration comes back in `unmarked_variables` and is skipped, and markers you didn\'t\n declare are still auto-added (`derived_variables`) \u2014 the stored contract always covers\n every marker in the document.\n\nAt generate time, `data` must provide a key for **every** marker \u2014 pass `""` for fields\nthat should render blank; a missing key fails with the full list of missing markers.\n\n### Email\n\nInline HTML + Handlebars, no uploaded file \u2014 `create_email_template`, on the CLI or in chat. Declare `variables`, and optionally a default\n`subject` (which itself supports `{{variable}}` expressions) and default to/cc/bcc. The body\nand subject render from the data at send time.\n\n## Markers, at a usage level\n\nEvery type supports three shapes of substitution. The exact syntax differs per engine \u2014 read\nthe create-tool description for the reference; here is the capability:\n\n- **Scalar** \u2014 a single value in one spot: `{{name}}`, `{{total}}`. Whole-cell scalars in\n Excel preserve their type via the cell\'s number format.\n- **Repeating rows / line items** \u2014 one template row rendered once per item in a list, for\n invoice lines, order rows, tables. Excel and the HTML/email engines use a Handlebars-style\n `{{#each items}}\u2026{{/each}}`; Word uses `{{FOR item IN items}}\u2026{{$item.field}}\u2026{{END-FOR item}}`. The HTML and email types\n also offer an auto-rendered `table` variable \u2014 declare its columns and pass an array, no\n hand-written loop. (Excel always uses the `{{#each}}` marker rows \u2014 it has no auto-rendered\n `table` type; its variable types are string/number/date/boolean/array.)\n- **Conditional sections** \u2014 a block shown only when a condition holds (a "paid" stamp, an\n optional notes block). Excel/HTML/email use `{{#if}}\u2026{{else}}\u2026{{/if}}`; Word uses `{{IF name}}\u2026{{END-IF name}}`.\n\nThe `html` and `email` types format a raw value with a helper, in the template\'s `locale` (`vi` or `en`, set at\ncreate or update; none prints in the organization\'s language); an empty value prints `""`:\n`{{money total "VND"}}` \u2192 `28.000.000 \u20AB` (`en`: `\u20AB28,000,000`), `{{number qty}}` \u2192 grouped, up to 2 decimals,\n`{{date issued_on}}` \u2192 `14/09/2026` (a moment on the workspace\'s timezone; a date-fns pattern as the second argument,\nliteral text quoted: `"\'Ng\xE0y\' dd"`), and `{{words total "USD"}}` \u2192 `M\u1ED9t ngh\xECn \u0111\xF4 la M\u1EF9 n\u0103m m\u01B0\u01A1i xu` (`en`: `One\nthousand US dollars and fifty cents`; no code reads VND); `{{date (add_days issued_on -1)}}` prints the day before, `add_days`\nshifting a date by whole days. A value the helper cannot read fails the render.\n\nRun `lotics tools create_excel_template`, `create_word_template`, `create_pdf_template`, or\n`create_email_template` for each engine\'s exact marker grammar and helper list \u2014 don\'t guess it.\n\n## Generating a filled file\n\n```bash\n# See what a template expects, then fill it\nlotics tools generate_document\nlotics run get_template \'{"template_id":"dtl_..."}\' # its declared variables\nlotics run generate_document \'{"document_template_id":"dtl_...","filename":"invoice-1042","data":{"company_name":"Acme","total":1042,"items":[...]}}\'\nlotics download <file_id> -o ./out/\n```\n\n- `data` is a map keyed by your variable names. Scalars for scalar/form fields; arrays for\n `table`/`list`/loop variables. `filename` excludes the extension (the type sets it).\n- `generate_document` renders the template\'s own type and returns a generated file object; take\n its `file_id` onward.\n- In a workflow, a **generate step** calls the same tool and hands the `file_id` to the next\n step (attach to a record, send as an attachment, etc.).\n\n## Discovering and managing templates\n\nFour unified tools work across all five types:\n\n| Tool | Does |\n|---|---|\n| `query_templates` | list templates (optionally filter by type: excel/word/pdf/email) |\n| `get_template` | one template\'s full definition, incl. the `variables` it expects |\n| `clone_template` | copy a template to iterate on, on the CLI or in chat |\n| `delete_template` | remove a template, on the CLI or in chat |\n\nCall `get_template` before generating when you don\'t already know a template\'s variable names.\n\n## Reaching the tools\n\n```bash\nlotics tools # all categories\nlotics tools create_word_template # one tool: full description + input schema\nlotics run <tool> \'<json-args>\' # execute (inline JSON, @file.json, or `-` for piped stdin)\n```\n\nThe template tools live in the categories **PDF Templates**, **Excel Templates**,\n**Word Templates**, **Email Templates**, and **Templates** (the unified query/get/clone/delete).\nUploading the source file for a file-backed template is `lotics upload <file>`; fetching a\ngenerated file is `lotics download <file_id>`.\n';
46932
46932
 
46933
46933
  // docs/knowledge_docs.md
46934
- var knowledge_docs_default = '# Knowledge Docs\n\nKnowledge docs are the AI\'s **rulebook layer** \u2014 the workspace-specific facts and rules an\nagent can\'t know on its own: your price list, your product codes, your SOPs, your shipping\ntariffs, your glossary of in-house terms. You write them once; the agent **searches and reads\nthem on demand** while it works, pulling in only the lines it needs.\n\nThey are deliberately **not** injected into the agent wholesale. Instead the agent retrieves\nfrom them line by line \u2014 grep, then read \u2014 so a 10,000-line tariff book costs nothing until a\nquestion actually touches it, and a 10MB one is no different.\n\nThis is a **capability + usage** guide. For the exact input schema of any tool named here, run\n`lotics tools <tool_name>`.\n\n## Rulebook, not methodology\n\nPut in a knowledge doc the things the model **cannot guess**: your specific numbers, codes,\nnames, exceptions, and policies. Do **not** put in it general skills the model already has\n("how to write a polite email", "how to summarize"). If the agent would get it right without\nthe doc, the doc is noise.\n\n## Creating a doc \u2014 `lotics knowledge create`\n\n```bash\nlotics knowledge create --name "Shipping tariffs" \\\n --description "HS-coded rates; searchable by lane and code" \\\n --tags "HS,nh\u1EADp kh\u1EA9u" \\ # file it as you make it \u2014 see Classifying, below\n --from ./tariffs.md # the body is a Markdown file; or --content \'<inline>\'\n```\n\nThis reads the body from your filesystem and creates the doc through `create_knowledge`\n(prints the new id). A raw `lotics run create_knowledge @doc.json` works too \u2014 read a large\n`content` from a file or stdin. `create_knowledge` takes five fields, three of them required:\n\n- `name` \u2014 what it is. Just the name: **grouping belongs in `tags`, not in a prefix baked into\n every title.**\n- `description` \u2014 what the doc is **and what to grep it for**: its vocabulary and the synonyms\n an ambiguous query would use. Surfaced in the tree before any body is read, and truncated at\n 200 characters. See *Write for grep* below.\n- `content` \u2014 Markdown.\n- `tags` *(optional)* \u2014 the labels someone would filter by, e.g. `["H\u1ED3 s\u01A1 NOXH", "Kho\u1EA3n 13"]`.\n Give **every** one that is true of the doc, not just the main one \u2014 a corpus is narrowed by\n intersecting tags, so a doc carrying one label can only ever be found down one path. On\n `update_knowledge` the array **replaces** what is stored, so resend the labels it should keep.\n- `files` *(optional)* \u2014 file ids of the source `.docx`/`.pdf`/`.xlsx` the doc was built from,\n stored alongside it so a reader can open the original. The body stays the searchable text.\n\nA new doc is created **owned by you, active in your own agent context, and private** \u2014 no one\nelse can see it yet. Changing a doc\'s default-active state is done through the web app / REST\nsurface, not this tool.\n\n## Access\n\nA knowledge doc reaches an agent only when the member can `use` the doc. New docs are private\nto the owner; share them with other members or groups using the IAM tool `share_resource` on the CLI or in chat\n(category **Admin**; `unshare_resource` to revoke). An organization admin who is a person is no\nexception: they read a doc only once it is shared with them, or once they share it with\nthemselves, which the audit log records. An API key acting as itself keeps the access set on the\nkey.\n\n## How an agent uses a doc \u2014 ls, grep, cat\n\nDocs are not injected wholesale. The agent works the corpus like a filesystem, and all three\nverbs respect access, so only docs the caller may use ever surface.\n\n1. **`list_knowledge`** \u2014 `ls`. The corpus as a flat list: id, name, tags, and a truncated\n description; `tag` narrows it. No bodies.\n2. **`grep_knowledge`** \u2014 `grep -rn`. Match across every readable doc (or one doc, or one\n tag), returning **doc, line number, and the matching line**. It runs inside Postgres,\n so only matching lines cross the wire.\n3. **`read_knowledge`** \u2014 `cat` / `sed -n \'X,Yp\'`. Read a doc whole or by line range, to see\n the context around a hit.\n\nMatching is **not ranked** \u2014 there is no index and no tokenizer, which is why a corpus in any\nscript works and why nothing goes stale. The consequence is that the *agent* does the\nnarrowing: a distinctive phrase is sharply selective, a whole question matches everything.\n`grep_knowledge` always reports `total_matches`, so "too broad" is visible and cheap to fix.\n\n### Literal by default, expression on request\n\n`pattern` is **literal text** \u2014 every character matches itself. Set `regex: true` to read it as\na POSIX regular expression instead.\n\n```\ngrep_knowledge({ pattern: "C/O (Form E)" })\ngrep_knowledge({ pattern: "\\\\yNK\\\\y", regex: true, case_sensitive: true })\n```\n\nThe default is the safe one rather than the conventional one, and the asymmetry is worth\nknowing. As an expression, `C/O (Form E)` searches for `C/O Form E` \u2014 not in the document \u2014 and\nreturns a confident zero; `[C\u0168]` becomes a one-character class and matches every `C` in the\ncorpus. Both look like ordinary answers. A literal search has no such failure: the worst case\nis an expression sent without the flag, which finds nothing and says so, naming the search that\nran and what to pass instead. That last part is **measured, not guessed** \u2014 an empty result tells\nyou whether reading the pattern as an expression would have matched, so an alternation sent without\nthe flag (`phrase A|phrase B`) comes back naming `regex: true` rather than advising you to shorten a\npattern that was never too broad.\n\n### Matching options\n\n- **Tone marks match exactly by default.** `diacritic_insensitive: true` folds them, so `ca phe`\n matches `c\xE0 ph\xEA` \u2014 reach for it when the pattern was typed without them, not by habit: folding\n strips the pattern to ASCII, where a short Vietnamese word matches inside unrelated words\n (`m\xE3` finds `manifest`). An empty result tells you when folding would have matched.\n- **Case folds by default**, independently of diacritics. `case_sensitive: true` matches case\n exactly \u2014 useful for an acronym (`NK` vs `nk`) that a folded search would blur.\n- **Whitespace is normalized on both sides.** A body converted from PDF, Word or Excel carries\n non-breaking spaces, soft hyphens, zero-width marks and padded runs that nobody types into a\n query; those fold to ordinary single spaces before matching, so a correct search does not return\n a silent zero on text that is present. A literal pattern is normalized the same way; an\n expression is left byte-exact, since collapsing its whitespace would rewrite it.\n- **What `regex: true` supports**: quantifiers, character classes, alternation, anchors,\n backreferences, non-greedy forms, `(?i)`, and both lookahead and lookbehind. A word boundary\n is **`\\y`**, not `\\b` \u2014 Postgres spells it differently, and `\\b` is rewritten for you rather\n than silently matching nothing. Named groups and `\\p{\u2026}` are likewise rewritten to their POSIX\n equivalents. A `\\p{\u2026}` with no POSIX equivalent, and a malformed expression, are both refused\n with the reason.\n\nWhere an expression cannot express the question at all \u2014 a value that must be computed, a\nlayout matching cannot address \u2014 stage the doc into a code run instead (`code_exec`, in chat or an app agent, with\n`knowledge_doc_ids` puts it at `inputs/knowledge/<id>.md` as a real file).\n\n### What a result is bounded by\n\nA result carries at most 40.000 characters. Matched lines are admitted first and context fills\nwhat remains, so an answer is never dropped to make room for a neighbouring line. Anything the\nbudget cut is REPORTED \u2014 `chars_elided_matches` (an answer was dropped) and\n`context_omitted_matches` (an answer was kept without the context you asked for) mean different\nthings and call for different fixes: narrow the pattern, or ask for fewer `context_lines`.\nIndividual lines longer than 600 characters are clipped and marked, with `read_knowledge` giving\nthe rest.\n\n`total_matches` is always the true total, even when fewer are shown.\n\n## Write for grep \u2014 the rules that decide whether an answer is findable\n\nRetrieval addresses **lines**. Every rule below follows from that one fact.\n\n- **One self-contained fact per line.** A grep hit returns *that line*. For dense or tabular\n data \u2014 a price row, a tariff code, a charge entry \u2014 put the whole record on one line.\n- **Repeat the searchable terms on every line; headings do not carry down.** A line reading\n `Rate: 15%` under a heading `Roasted coffee` will never match a search for `coffee`. Restate\n the identifying terms inline, even when it reads redundantly to a human. This is the single\n rule most often got wrong.\n- **Keep a line under ~600 characters.** Past that the line is truncated in the agent\'s view\n and marked as cut; the agent can still `read_knowledge` for the rest, but it costs a round\n trip. Put the identifying terms early and the long tail late.\n- **Put synonyms and translations on the line itself.** A bilingual row (`C\xE0 ph\xEA, \u0111\xE3 rang /\n Coffee, roasted`) matches queries in either language for free. The same trick carries\n colloquial terms next to official ones.\n- **For prose, keep a rule and its exception close.** A hit returns one line, so a rule on line\n 40 and its exception on line 90 can be retrieved apart. Use `context_lines` when reading, and\n keep related clauses adjacent when writing.\n\nMarkdown headings are ordinary text \u2014 useful for a human and for orienting a `read_knowledge`,\nbut they carry no retrieval weight of their own.\n\n**The description is the discovery hint.** It is what the agent sees in the tree before it has\nread a byte of the body, so it is the only clue for *what term to grep for*. Write it to name\nthe doc\'s vocabulary \u2014 the words that actually appear inside it. Keep it to a sentence: it is\ntruncated at 200 characters, and a keyword-stuffed description is cut, not rewarded.\n\n## Updating a doc \u2014 `lotics knowledge update`\n\n```bash\nlotics knowledge update kdc_... --from ./tariffs.md # replace the body (--name / --description too)\n```\n\nSend only the fields you\'re changing. `--from` / `--content` replaces the body; `--name` /\n`--description` / `--tags` change metadata. `update_knowledge` resolves concurrency **internally** \u2014 it\nre-reads the current content pointer and version-chains the new body \u2014 so there is no version\ntoken to pass from the CLI. (The chat agent may instead send an `edits` array \u2014 anchored\nreplace / insert / append \u2014 for a surgical change; see `lotics tools update_knowledge`.) Refine\nstructure as you learn what users actually ask: add the synonym that failed to match, split a\nline that was too coarse, restate a term the heading was carrying.\n\n## Classifying a corpus \u2014 tags\n\nA doc carries a SET of labels, not a folder. Order does not matter, a doc can wear several, and\nthe vocabulary is whatever the docs themselves use \u2014 a tag stops existing the moment nothing\ncarries it.\n\n```bash\nlotics knowledge update kdc_... --tags "HS,nh\u1EADp kh\u1EA9u" # ONE doc: state its complete set\nlotics knowledge tag kdc_a kdc_b --add HS --remove draft # MANY docs: a diff applied to each\n```\n\nThe split is deliberate. Looking at one doc you know what it should carry, so `--tags` replaces.\nAcross a set you do not \u2014 the docs carry different labels \u2014 so `tag` adds and removes against\neach doc\'s own set, and never states one classification for all of them. Removal ignores case;\nadding a label a doc already carries changes nothing.\n\nAn agent narrows by tag rather than reading the whole corpus:\n\n```bash\nlotics tools list_knowledge # takes an optional tag\nlotics tools grep_knowledge # same\n```\n\n## Taking a doc out of circulation \u2014 `hide`\n\n```bash\nlotics knowledge hide kdc_... # superseded, draft, source material\nlotics knowledge list --include-hidden # find it again\nlotics knowledge unhide kdc_... # put it back\n```\n\nA hidden doc is out of every **listing**: it is gone from the Library\'s list, from\n`list_knowledge`, and from the corpus `grep_knowledge` searches, so nothing finds it by browsing.\nIt is not deleted, not unshared, and still **reads when addressed by id** \u2014 `read_knowledge` with\nits id, a code run staging it, an app agent that declares it. That is the point: hiding is a\nstatement about discovery, so it cannot silently break an app that depends on a doc by name.\n\nReach for it instead of `rm` whenever the material still matters: last year\'s tariff schedule, a\nhandbook a newer one replaced, the raw source a curated doc was written from.\n\n## Reaching the tools\n\n```bash\nlotics knowledge list # catalog: id, name, tags, description\nlotics knowledge get kdc_... -o doc.md # read a body to a file (omit -o for stdout)\nlotics tools update_knowledge # full input schema for any knowledge tool\n```\n\nThe Knowledge category covers `list_knowledge`, `create_knowledge`, `update_knowledge`, and\n`delete_knowledge` \u2014 fronted by the `lotics knowledge list | create | get | update | tag | hide |\nunhide | rm` commands, and all but `delete_knowledge` reachable over MCP as well, so a corpus can be\nauthored from whichever surface you already work in. `list`, `tag`, `hide` and `unhide` go over REST rather than a tool, because\nthey are a person\'s view of the corpus: the tools answer what the ASSISTANT may browse, and a\nhidden doc is out of that set by definition. Sharing a doc to other members is `share_resource` / `unshare_resource` on the\nCLI or in chat (category **Admin**).\n';
46934
+ var knowledge_docs_default = '# Knowledge Docs\n\nKnowledge docs are the AI\'s **rulebook layer** \u2014 the workspace-specific facts and rules an\nagent can\'t know on its own: your price list, your product codes, your SOPs, your shipping\ntariffs, your glossary of in-house terms. You write them once; the agent **searches and reads\nthem on demand** while it works, pulling in only the lines it needs.\n\nThey are deliberately **not** injected into the agent wholesale. Instead the agent retrieves\nfrom them line by line \u2014 grep, then read \u2014 so a 10,000-line tariff book costs nothing until a\nquestion actually touches it, and a 10MB one is no different.\n\nThis is a **capability + usage** guide. For the exact input schema of any tool named here, run\n`lotics tools <tool_name>`.\n\n## Rulebook, not methodology\n\nPut in a knowledge doc the things the model **cannot guess**: your specific numbers, codes,\nnames, exceptions, and policies. Do **not** put in it general skills the model already has\n("how to write a polite email", "how to summarize"). If the agent would get it right without\nthe doc, the doc is noise.\n\n## Creating a doc \u2014 `lotics knowledge create`\n\n```bash\nlotics knowledge create --name "Shipping tariffs" \\\n --description "HS-coded rates; searchable by lane and code" \\\n --tags "HS,nh\u1EADp kh\u1EA9u" \\ # file it as you make it \u2014 see Classifying, below\n --from ./tariffs.md # the body is a Markdown file; or --content \'<inline>\'\n```\n\nThis reads the body from your filesystem and creates the doc through `create_knowledge`\n(prints the new id). A raw `lotics run create_knowledge @doc.json` works too \u2014 read a large\n`content` from a file or stdin. `create_knowledge` takes five fields, three of them required:\n\n- `name` \u2014 what it is. Just the name: **grouping belongs in `tags`, not in a prefix baked into\n every title.**\n- `description` \u2014 what the doc is **and what to grep it for**: its vocabulary and the synonyms\n an ambiguous query would use. Surfaced in the tree before any body is read, and truncated at\n 200 characters. See *Write for grep* below.\n- `content` \u2014 Markdown.\n- `tags` *(optional)* \u2014 the labels someone would filter by, e.g. `["H\u1ED3 s\u01A1 NOXH", "Kho\u1EA3n 13"]`.\n Give **every** one that is true of the doc, not just the main one \u2014 a corpus is narrowed by\n intersecting tags, so a doc carrying one label can only ever be found down one path. On\n `update_knowledge` the array **replaces** what is stored, so resend the labels it should keep.\n- `files` *(optional)* \u2014 file ids of the source `.docx`/`.pdf`/`.xlsx` the doc was built from,\n stored alongside it so a reader can open the original. The body stays the searchable text.\n\nA new doc is created **owned by you, active in your own agent context, and private** \u2014 no one\nelse can see it yet. Changing a doc\'s default-active state is done through the web app / REST\nsurface, not this tool.\n\n## Access\n\nA knowledge doc reaches an agent only when the member can `use` the doc. New docs are private\nto the owner; share them with other members or groups using the IAM tool `share_resource` on the CLI or in chat\n(category **Admin**; `unshare_resource` to revoke). An organization admin who is a person is no\nexception: they read a doc only once it is shared with them, or once they share it with\nthemselves, which the audit log records. An API key acting as itself keeps the access set on the\nkey.\n\n## How an agent uses a doc \u2014 ls, grep, cat\n\nDocs are not injected wholesale. The agent works the corpus like a filesystem, and all three\nverbs respect access, so only docs the caller may use ever surface.\n\n1. **`list_knowledge`** \u2014 `ls`. The corpus as a flat list: id, name, tags, and a truncated\n description; `tag` narrows it. No bodies.\n2. **`grep_knowledge`** \u2014 `grep -rn`. Match across every readable doc (or one doc, or one\n tag), returning **doc, line number, and the matching line**. It runs inside Postgres,\n so only matching lines cross the wire.\n3. **`read_knowledge`** \u2014 `cat` / `sed -n \'X,Yp\'`. Read a doc whole or by line range, to see\n the context around a hit.\n\nMatching is **not ranked** \u2014 there is no index and no tokenizer, which is why a corpus in any\nscript works and why nothing goes stale. The consequence is that the *agent* does the\nnarrowing: a distinctive phrase is sharply selective, a whole question matches everything.\n`grep_knowledge` always reports `total_matches`, so "too broad" is visible and cheap to fix.\n\n### Literal by default, expression on request\n\n`pattern` is **literal text** \u2014 every character matches itself. Set `regex: true` to read it as\na POSIX regular expression instead.\n\n```\ngrep_knowledge({ pattern: "C/O (Form E)" })\ngrep_knowledge({ pattern: "\\\\yNK\\\\y", regex: true, case_sensitive: true })\n```\n\nThe default is the safe one rather than the conventional one, and the asymmetry is worth\nknowing. As an expression, `C/O (Form E)` searches for `C/O Form E` \u2014 not in the document \u2014 and\nreturns a confident zero; `[C\u0168]` becomes a one-character class and matches every `C` in the\ncorpus. Both look like ordinary answers. A literal search has no such failure: the worst case\nis an expression sent without the flag, which finds nothing and says so, naming the search that\nran and what to pass instead. That last part is **measured, not guessed** \u2014 an empty result tells\nyou whether reading the pattern as an expression would have matched, so an alternation sent without\nthe flag (`phrase A|phrase B`) comes back naming `regex: true` rather than advising you to shorten a\npattern that was never too broad.\n\n### Matching options\n\n- **Tone marks match exactly by default.** `diacritic_insensitive: true` folds them, so `ca phe`\n matches `c\xE0 ph\xEA` \u2014 reach for it when the pattern was typed without them, not by habit: folding\n strips the pattern to ASCII, where a short Vietnamese word matches inside unrelated words\n (`m\xE3` finds `manifest`). An empty result tells you when folding would have matched.\n- **Case folds by default**, independently of diacritics. `case_sensitive: true` matches case\n exactly \u2014 useful for an acronym (`NK` vs `nk`) that a folded search would blur.\n- **Whitespace is normalized on both sides.** A body converted from PDF, Word or Excel carries\n non-breaking spaces, soft hyphens, zero-width marks and padded runs that nobody types into a\n query; those fold to ordinary single spaces before matching, so a correct search does not return\n a silent zero on text that is present. A literal pattern is normalized the same way; an\n expression is left byte-exact, since collapsing its whitespace would rewrite it.\n- **What `regex: true` supports**: quantifiers, character classes, alternation, anchors,\n backreferences, non-greedy forms, `(?i)`, and both lookahead and lookbehind. A word boundary\n is **`\\y`**, not `\\b` \u2014 Postgres spells it differently, and `\\b` is rewritten for you rather\n than silently matching nothing. Named groups and `\\p{\u2026}` are likewise rewritten to their POSIX\n equivalents. A `\\p{\u2026}` with no POSIX equivalent, and a malformed expression, are both refused\n with the reason.\n\nWhere an expression cannot express the question at all \u2014 a value that must be computed, a\nlayout matching cannot address \u2014 stage the doc into a code run instead (`code_exec`, in chat or an app agent, with\n`knowledge_doc_ids` puts it at `inputs/knowledge/<id>.md` as a real file).\n\n### What a result is bounded by\n\nA result carries at most 40.000 characters. Matched lines are admitted first and context fills\nwhat remains, so an answer is never dropped to make room for a neighbouring line. Anything the\nbudget cut is REPORTED \u2014 `chars_elided_matches` (an answer was dropped) and\n`context_omitted_matches` (an answer was kept without the context you asked for) mean different\nthings and call for different fixes: narrow the pattern, or ask for fewer `context_lines`.\nIndividual lines longer than 600 characters are clipped and marked, with `read_knowledge` giving\nthe rest.\n\n`total_matches` is always the true total, even when fewer are shown.\n\n## Write for grep \u2014 the rules that decide whether an answer is findable\n\nRetrieval addresses **lines**. Every rule below follows from that one fact.\n\n- **One self-contained fact per line.** A grep hit returns *that line*. For dense or tabular\n data \u2014 a price row, a tariff code, a charge entry \u2014 put the whole record on one line.\n- **Repeat the searchable terms on every line; headings do not carry down.** A line reading\n `Rate: 15%` under a heading `Roasted coffee` will never match a search for `coffee`. Restate\n the identifying terms inline, even when it reads redundantly to a human. This is the single\n rule most often got wrong.\n- **Keep a line under ~600 characters.** Past that the line is truncated in the agent\'s view\n and marked as cut; the agent can still `read_knowledge` for the rest, but it costs a round\n trip. Put the identifying terms early and the long tail late.\n- **Put synonyms and translations on the line itself.** A bilingual row (`C\xE0 ph\xEA, \u0111\xE3 rang /\n Coffee, roasted`) matches queries in either language for free. The same trick carries\n colloquial terms next to official ones.\n- **For prose, keep a rule and its exception close.** A hit returns one line, so a rule on line\n 40 and its exception on line 90 can be retrieved apart. Use `context_lines` when reading, and\n keep related clauses adjacent when writing.\n\nMarkdown headings are ordinary text \u2014 useful for a human and for orienting a `read_knowledge`,\nbut they carry no retrieval weight of their own.\n\n**The description is the discovery hint.** It is what the agent sees in the tree before it has\nread a byte of the body, so it is the only clue for *what term to grep for*. Write it to name\nthe doc\'s vocabulary \u2014 the words that actually appear inside it. Keep it to a sentence: it is\ntruncated at 200 characters, and a keyword-stuffed description is cut, not rewarded.\n\n## Updating a doc \u2014 `lotics knowledge update`\n\n```bash\nlotics knowledge update kdc_... --from ./tariffs.md # replace the body (--name / --description too)\n```\n\nSend only the fields you\'re changing. `--from` / `--content` replaces the body; `--name` /\n`--description` / `--tags` change metadata. `update_knowledge` resolves concurrency **internally** \u2014 it\nre-reads the current content pointer and version-chains the new body \u2014 so there is no version\ntoken to pass from the CLI. (The chat agent may instead send an `edits` array \u2014 anchored\nreplace / insert / append \u2014 for a surgical change; see `lotics tools update_knowledge`.) Refine\nstructure as you learn what users actually ask: add the synonym that failed to match, split a\nline that was too coarse, restate a term the heading was carrying.\n\n## Classifying a corpus \u2014 tags\n\nA doc carries a SET of labels, not a folder. Order does not matter, a doc can wear several, and\nthe vocabulary is whatever the docs themselves use \u2014 a tag stops existing the moment nothing\ncarries it.\n\n```bash\nlotics knowledge update kdc_... --tags "HS,nh\u1EADp kh\u1EA9u" # ONE doc: state its complete set\nlotics knowledge tag kdc_a kdc_b --add HS --remove draft # MANY docs: a diff applied to each\n```\n\nThe split is deliberate. Looking at one doc you know what it should carry, so `--tags` replaces.\nAcross a set you do not \u2014 the docs carry different labels \u2014 so `tag` adds and removes against\neach doc\'s own set, and never states one classification for all of them. Removal ignores case;\nadding a label a doc already carries changes nothing.\n\nAn agent narrows by tag rather than reading the whole corpus:\n\n```bash\nlotics tools list_knowledge # takes an optional tag\nlotics tools grep_knowledge # same\n```\n\n## Taking a doc out of circulation \u2014 `hide`\n\n```bash\nlotics knowledge hide kdc_... # superseded, draft, source material\nlotics knowledge list --include-hidden # find it again\nlotics knowledge unhide kdc_... # put it back\n```\n\nA hidden doc is out of every **listing**: it is gone from the Library\'s list, from\n`list_knowledge`, and from the corpus `grep_knowledge` searches, so nothing finds it by browsing.\nIt is not deleted, not unshared, and still **reads when addressed by id** \u2014 `read_knowledge` with\nits id, a code run staging it, an app agent that declares it. That is the point: hiding is a\nstatement about discovery, so it cannot silently break an app that depends on a doc by name.\n\nReach for it instead of `rm` whenever the material still matters: last year\'s tariff schedule, a\nhandbook a newer one replaced, the raw source a curated doc was written from.\n\n## Formatting \u2014 what a doc draws\n\nA doc is markdown with GFM tables. Beyond that it draws a few blocks, each in syntax you already write; the platform draws them in its own style, so every doc looks alike.\n\n| Write | Draws |\n|---|---|\n| `> [!NOTE]`, `[!TIP]`, `[!IMPORTANT]`, `[!WARNING]` or `[!CAUTION]` opening a blockquote | a callout in that tone |\n| `<Note>`, `<Info>`, `<Tip>`, `<Check>`, `<Warning>`, `<Danger>` | the same callouts |\n| a ```mermaid fence | a diagram: flowchart, sequence, timeline, gantt, `pie`, `xychart-beta`, `quadrantChart` (a 2x2) |\n| `<Steps>` holding `<Step title="\u2026">` | a numbered walk |\n| `<Tabs>` holding `<Tab title="\u2026">` | switchable panels |\n| `<Columns cols={2}>` (or `<CardGroup>`) holding `<Card title="\u2026" href="\u2026">` | side-by-side cards that stack on a phone |\n| a table whose header cells are all empty | a table with no header row (a key/value table) |\n| `<Accordion title="\u2026">`, `<AccordionGroup>`, or `<details>` with a one-line `<summary>\u2026</summary>` | a section that opens on press |\n\nEach tag stands on a line of its own, and a tag line inside a fenced code block is code. Attributes are text in double quotes or a number in braces; nothing in a tag runs. A write that adds a tag that does not close, nests wrongly, carries an attribute the tag does not take, or names a component this list lacks (`<Frame>`, `<CodeGroup>`) is refused with the lines to fix; lowercase HTML (`<br>`) stays text.\n\nA chart\'s numbers live in its mermaid source, which agents read and grep like any text: write them there once, not again in a table.\n\n## Reaching the tools\n\n```bash\nlotics knowledge list # catalog: id, name, tags, description\nlotics knowledge get kdc_... -o doc.md # read a body to a file (omit -o for stdout)\nlotics tools update_knowledge # full input schema for any knowledge tool\n```\n\nThe Knowledge category covers `list_knowledge`, `create_knowledge`, `update_knowledge`, and\n`delete_knowledge` \u2014 fronted by the `lotics knowledge list | create | get | update | tag | hide |\nunhide | rm` commands, and all but `delete_knowledge` reachable over MCP as well, so a corpus can be\nauthored from whichever surface you already work in. `list`, `tag`, `hide` and `unhide` go over REST rather than a tool, because\nthey are a person\'s view of the corpus: the tools answer what the ASSISTANT may browse, and a\nhidden doc is out of that set by definition. Sharing a doc to other members is `share_resource` / `unshare_resource` on the\nCLI or in chat (category **Admin**).\n';
46935
46935
 
46936
46936
  // docs/migration.md
46937
46937
  var migration_default = "# @lotics/cli \u2014 migration notes\n\nWhat to DO when a release changes the shape of something the CLI once wrote to disk. Every other\ncontract is `docs/cli_reference.md`; this file is the one a reader opens once, because something\nalready on disk no longer matches what the CLI does.\n\n## Local app projects are gone\n\nAn app built from a model no longer lives in a directory. The live app is the only edit surface:\neach change mints a new version of it, and rolling back to an earlier version is the undo. What\nthe app is \u2014 its screens (`app.json`), its queries, workflows, agents and capabilities, and the\nmodel body it was compiled from \u2014 is held by that version, and the model's tables, fields, options,\ntemplates and roles are the workspace's own.\n\n| What you ran | What replaces it |\n|---|---|\n| `lotics scaffold check` + `scaffold apply` | `lotics model apply <model.json>` \u2014 checks the file, applies the tables and rows, then mints a version of every app it declares |\n| `lotics workspace build <model.json>` | `lotics model apply <model.json>` |\n| `lotics app create --from <model.json>#<app>` | `lotics model apply <model.json> --app <app>` |\n| `lotics app regenerate` | change the model file, then `lotics model apply` |\n| `lotics scaffold diff` | `lotics model check <model.json>` \u2014 what the apply would change, writing nothing |\n| `lotics scaffold export` | `lotics model pull [-o <model.json>]` \u2014 the workspace's model, rebuilt from what owns each part |\n| `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 |\n| `lotics app workflow set` / `app query set` / `app agent set` | `lotics run set_app_workflow` / `set_app_queries` / `set_app_agent` \u2014 each mints a version |\n| `lotics app versions` + a redeploy of an old tree | `lotics run query_app_versions`, then `lotics run rollback_app` \u2014 restores the app's earlier version; table changes and data writes stay |\n| `lotics app rename`, `app subdomain`, `package.json#lotics.capabilities` | `lotics run update_app` (`name`, `public_subdomain`, `capabilities`) |\n| `lotics app api publish` / `unpublish` | `lotics run publish_app_api` / `unpublish_app_api` |\n| `lotics field rename` | `lotics run update_table`, then the same label in the model file |\n| `lotics app kit` | nothing: a custom-code app depends on `@lotics/app-sdk` (below) |\n| `@lotics/xlsx` / `@lotics/docx` to build a template's file | any `.xlsx` / `.docx` library, then `lotics upload` (`lotics docs document_templates`) |\n| `lotics file preview` | `lotics file download <fil_id>`, then open it |\n| `model.json#apply` (`[{package, bind}]`) | state the tables under `entities` \u2014 a model carrying `apply` is refused |\n| `lotics library list` / `library show <slug>`, `lotics preset list` / `preset show <slug>` | the example models at `https://lotics.ai/presets/index.json`, each a complete `model.json` to adapt |\n| `model.json#from` (with `variants`, `rename`) | state every table under `entities` \u2014 a model carrying `from` is refused |\n| `lotics library init <apg_id>`, `lotics setup <apg_id>`, `lotics app upgrade`, `lotics library fixtures` | nothing copies a package any more: write a `model.json` (`lotics docs model`), then `lotics setup model.json` or `lotics model apply model.json` |\n\n`lotics tools` lists every tool, and `lotics tools <name>` prints its input.\n\n**An existing JSON app moves on the first `lotics model apply`** of the model it came from: that\napply binds each app alias to the app it already became and mints its next version from the file.\nA directory an earlier CLI wrote (`app.json`, `src/workflows/`, `.lotics/`, `package.json#lotics`\nwith `plan`, `queries` or `workflows`) is no longer read by anything; keep the model file, delete the\nrest.\n\n**Authored workflow bodies** an act names (`workflow` on an act) are the live workflow's own body:\nthe apply keeps it, and `lotics run set_app_workflow` changes it.\n\n**A model written before `party`** draws its people and organisations as things \u2014 a row with no\npicture shows no initials: state `party` (`person` or `organization`) on those entities' `records`.\n\n## Custom-code apps use `@lotics/app-sdk`\n\nA hand-written app depends on `@lotics/app-sdk` alone \u2014 the hooks (`useQuery`, `useWorkflow`, \u2026),\n`mount`, and `AppRouter` from `@lotics/app-sdk/router` \u2014 and draws with its own React.\n`@lotics/app-runtime` and `@lotics/ui` are no longer published: the runtime is what the platform\ndraws a model's app with, and it ships with the platform.\n\nAn app that listed `@lotics/app-runtime`:\n\n1. **The dependency.** `@lotics/app-sdk` replaces `@lotics/app-runtime` in `package.json`.\n2. **The import specifiers.** `@lotics/app-runtime/sdk` becomes `@lotics/app-sdk`, and\n `@lotics/app-runtime/router` becomes `@lotics/app-sdk/router` \u2014 in `src/` and in\n `vite.config.ts`.\n3. **The kit.** An app that imports `@lotics/ui` keeps the version it already installed; no newer one\n is published. Its screens can stay on it, or move to plain React (or any library) one screen at a\n time.\n4. **`package.json#lotics`** needs only `app_id`, `workspace_id` and `current_version_id`. The live\n app owns its `queries`, `workflows`, `agents` and `capabilities`, and `lotics app deploy` refuses\n a directory that still declares any of them; delete them, and the unread `synced` block.\n5. `lotics app deploy` builds the directory and uploads it as a new version.\n\nA deployed app keeps running on the bundle it shipped until it is deployed again.\n";
@@ -214,6 +214,25 @@ statement about discovery, so it cannot silently break an app that depends on a
214
214
  Reach for it instead of `rm` whenever the material still matters: last year's tariff schedule, a
215
215
  handbook a newer one replaced, the raw source a curated doc was written from.
216
216
 
217
+ ## Formatting — what a doc draws
218
+
219
+ A doc is markdown with GFM tables. Beyond that it draws a few blocks, each in syntax you already write; the platform draws them in its own style, so every doc looks alike.
220
+
221
+ | Write | Draws |
222
+ |---|---|
223
+ | `> [!NOTE]`, `[!TIP]`, `[!IMPORTANT]`, `[!WARNING]` or `[!CAUTION]` opening a blockquote | a callout in that tone |
224
+ | `<Note>`, `<Info>`, `<Tip>`, `<Check>`, `<Warning>`, `<Danger>` | the same callouts |
225
+ | a ```mermaid fence | a diagram: flowchart, sequence, timeline, gantt, `pie`, `xychart-beta`, `quadrantChart` (a 2x2) |
226
+ | `<Steps>` holding `<Step title="…">` | a numbered walk |
227
+ | `<Tabs>` holding `<Tab title="…">` | switchable panels |
228
+ | `<Columns cols={2}>` (or `<CardGroup>`) holding `<Card title="…" href="…">` | side-by-side cards that stack on a phone |
229
+ | a table whose header cells are all empty | a table with no header row (a key/value table) |
230
+ | `<Accordion title="…">`, `<AccordionGroup>`, or `<details>` with a one-line `<summary>…</summary>` | a section that opens on press |
231
+
232
+ Each tag stands on a line of its own, and a tag line inside a fenced code block is code. Attributes are text in double quotes or a number in braces; nothing in a tag runs. A write that adds a tag that does not close, nests wrongly, carries an attribute the tag does not take, or names a component this list lacks (`<Frame>`, `<CodeGroup>`) is refused with the lines to fix; lowercase HTML (`<br>`) stays text.
233
+
234
+ A chart's numbers live in its mermaid source, which agents read and grep like any text: write them there once, not again in a table.
235
+
217
236
  ## Reaching the tools
218
237
 
219
238
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/cli",
3
- "version": "0.315.13",
3
+ "version": "0.315.14",
4
4
  "description": "Lotics SDK and CLI for AI agents",
5
5
  "type": "module",
6
6
  "bin": {