@oalo-ai/cli 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +392 -2
  3. package/man/oalo.1 +1530 -0
  4. package/package.json +44 -4
  5. package/plugin/.claude-plugin/plugin.json +12 -0
  6. package/plugin/skills/oalo/SKILL.md +414 -0
  7. package/src/api.js +132 -0
  8. package/src/app-ref.js +11 -0
  9. package/src/args.js +67 -0
  10. package/src/bot-settings.json +468 -0
  11. package/src/channel.js +162 -0
  12. package/src/commands/apps.js +64 -0
  13. package/src/commands/bots.js +278 -0
  14. package/src/commands/call.js +179 -0
  15. package/src/commands/channels.js +110 -0
  16. package/src/commands/data.js +446 -0
  17. package/src/commands/db.js +58 -0
  18. package/src/commands/deploy.js +431 -0
  19. package/src/commands/dockerfile.js +81 -0
  20. package/src/commands/docs.js +70 -0
  21. package/src/commands/emit.js +124 -0
  22. package/src/commands/env.js +223 -0
  23. package/src/commands/graphs.js +570 -0
  24. package/src/commands/incidents.js +44 -0
  25. package/src/commands/link.js +150 -0
  26. package/src/commands/login.js +180 -0
  27. package/src/commands/logs.js +69 -0
  28. package/src/commands/mcp.js +125 -0
  29. package/src/commands/metrics.js +168 -0
  30. package/src/commands/products.js +169 -0
  31. package/src/commands/qa.js +98 -0
  32. package/src/commands/rehearse.js +100 -0
  33. package/src/commands/run.js +152 -0
  34. package/src/commands/secrets.js +68 -0
  35. package/src/commands/setup.js +96 -0
  36. package/src/commands/skill.js +142 -0
  37. package/src/commands/sources.js +377 -0
  38. package/src/commands/switches.js +156 -0
  39. package/src/commands/tail.js +84 -0
  40. package/src/commands/tokens.js +85 -0
  41. package/src/commands/typegen.js +505 -0
  42. package/src/commands/whoami.js +112 -0
  43. package/src/config.js +157 -0
  44. package/src/data.js +392 -0
  45. package/src/envfile.js +71 -0
  46. package/src/events.js +63 -0
  47. package/src/graphfiles.js +273 -0
  48. package/src/help.js +920 -0
  49. package/src/index.js +216 -0
  50. package/src/mcp/tools.js +773 -0
  51. package/src/server.js +73 -0
  52. package/src/workspace.js +44 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 OALO
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,393 @@
1
- # Temporary Holding Version
1
+ # @oalo-ai/cli
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Work on an OALO workspace from your terminal: edit chat flows, loops and code
4
+ tools as files and push them back safely, run test conversations with a bot,
5
+ generate typed bindings for your own app, and serve all of it to a coding agent
6
+ over MCP.
7
+
8
+ ## Install
9
+
10
+ Node 18 or newer:
11
+
12
+ ```sh
13
+ npm install -g @oalo-ai/cli
14
+ oalo --version
15
+ ```
16
+
17
+ `man oalo` documents every command and every bot setting. `oalo coord` (agent
18
+ coordination) also needs [Bun](https://bun.sh); everything else runs on Node.
19
+
20
+ ## Start
21
+
22
+ ```sh
23
+ oalo login <workspace> # <workspace>.oalo.ai: approve this machine in the browser
24
+ oalo link --server https://<workspace>.oalo.ai # this project; installs the skill for coding agents
25
+ oalo typegen # types for your models, workflows and helpers
26
+ oalo link --status
27
+ ```
28
+
29
+ ## Discovering what it does
30
+
31
+ ```sh
32
+ oalo --help # overview
33
+ oalo help typegen # one command: flags, notes, exit codes
34
+ oalo help --json # machine-readable, for an agent driving the CLI
35
+ ```
36
+
37
+ `help --json` describes every command, flag, exit code, token kind and
38
+ environment variable in one object. Human and JSON forms come from the same
39
+ registry, so they cannot disagree, and a test asserts the documented commands
40
+ are exactly the dispatchable ones.
41
+
42
+ Explicit help exits 0. Only an invalid invocation is non-zero (2 for an unknown
43
+ command, 1 for no command at all).
44
+
45
+ ## Two token kinds
46
+
47
+ They are not interchangeable, and `link` verifies each against the thing it is
48
+ actually for, so a mix-up fails immediately instead of surfacing later as a
49
+ confusing auth error.
50
+
51
+ | kind | from | plane | used by |
52
+ |---|---|---|---|
53
+ | **sync** | `oalo login` (read-only), or Account → CLI & agents | data (`/api/sync/v1`) | `link`, `typegen`, `tail` |
54
+ | **CLI** | `oalo login`, or Account → CLI & agents | the build surface | everything else, `mcp` included |
55
+
56
+ `oalo login` gets you both as one **connection**, and so does "Create
57
+ connection" on the dashboard's Account → CLI & agents page. A connection is
58
+ listed as one row there and revoked as one.
59
+
60
+ ## Connecting a machine
61
+
62
+ ```sh
63
+ oalo login <workspace>
64
+ ```
65
+
66
+ It prints a short code and opens the dashboard on Account → CLI & agents.
67
+ Check that the page shows the same code, then approve. The CLI saves both
68
+ tokens to `~/.oalo/credentials.json` (mode `0600`, never in a project), and
69
+ every command finds them by workspace URL. The code lasts 10 minutes and
70
+ nothing is created until you approve. `--no-browser` prints the URL without
71
+ opening it, for a remote shell. `oalo logout` revokes the connection on the
72
+ server and forgets the tokens.
73
+
74
+ On a machine with no browser (a server, a container), click **Create
75
+ connection** on that page instead. It shows one `oalo link` command with both
76
+ tokens filled in, plus the MCP config block. Those tokens are shown once.
77
+
78
+ The login's data token is **read-only**: the CLI never writes through the sync
79
+ API. Data tokens for an app's own servers, with write access or limited to some
80
+ tables, are separate. Make those under **Sync API** in the dashboard.
81
+
82
+ > **Server-side only.** A sync token carries no end-user identity, so it reads
83
+ > and writes *every* member's rows. Never ship one inside a mobile or web app:
84
+ > those authenticate their own users instead. See `tokens-and-credentials` in
85
+ > the knowledge base.
86
+
87
+ ## What `link` writes
88
+
89
+ | | |
90
+ |---|---|
91
+ | `.oalo/config.json` | project-local: workspace, app, env, last-seen schema version. **Safe to commit.** |
92
+ | `~/.oalo/credentials.json` | the token, mode `0600`. **Never** in the project tree. |
93
+
94
+ The token is written at user level by design, not by convention, so it cannot
95
+ be committed by accident. `link` also adds `.oalo/credentials*` to `.gitignore`
96
+ as a second line of defence. Set `OALO_HOME` to relocate the store (CI,
97
+ containers, tests).
98
+
99
+ `link` verifies the token against the workspace **before** saving anything. A
100
+ link that reports success and then fails on first use is the worst outcome: you
101
+ have no reason to suspect the credential, so you go looking for the bug in your
102
+ own code.
103
+
104
+ ## dev vs prod
105
+
106
+ App data lives in a `dev` and a `prod` schema, and `--env` decides which one
107
+ every later command touches. It defaults to `dev` and is recorded in the config
108
+ so it is explicit rather than ambient.
109
+
110
+ ## Linking also configures the MCP server
111
+
112
+ `@oalo-ai/cli` falls back to the same credential store when `OALO_URL` /
113
+ `OALO_CLI_TOKEN` are not set, so one `link` can serve both. (The two use
114
+ different token kinds: the CLI uses a **sync** token for data, the MCP server a
115
+ **CLI/MCP** token for the build surface.)
116
+
117
+ ## `oalo typegen`
118
+
119
+ ```sh
120
+ oalo typegen # writes oalo.generated.ts
121
+ oalo typegen --out src/oalo.ts
122
+ oalo typegen --check # non-zero if stale; use this in CI
123
+ ```
124
+
125
+ Generates TypeScript from the workspace's own manifest, so the bindings describe
126
+ *your* schema rather than a generic API. Commit the output.
127
+
128
+ ```ts
129
+ import type { RowOf, DocumentOf, ModelName } from "./oalo.generated";
130
+ import { MODELS } from "./oalo.generated";
131
+
132
+ const row: RowOf<"clients"> = await loadSomehow();
133
+ const id: number | undefined = row.document.user_id; // typed
134
+ const pk = MODELS.clients.primaryKey; // narrows to "id"
135
+ ```
136
+
137
+ Three things worth knowing about the output:
138
+
139
+ - **Document interfaces are open.** The manifest is a partial description, not a
140
+ closed schema: `clients` may declare only `user_id` while real rows also carry
141
+ `email`, `name` and `phone`. Declared fields are typed; the rest stay
142
+ reachable through an index signature. Closing them would make correct code
143
+ fail to compile.
144
+ - **Types are named `<Table>Doc`** (`ClientsDoc`, `CatalogProductsDoc`) rather
145
+ than singularised. Singularising is a bug farm (quizzes, measurements,
146
+ stages), and the suffix says what the type is: the document body, not the
147
+ whole row.
148
+ - **The env is stamped in the header.** `dev` and `prod` are separate schemas,
149
+ so bindings generated against one are not a promise about the other.
150
+
151
+ Field types mirror `mix sync.gen.model` exactly, so the TypeScript and Dart
152
+ generators cannot drift. Note `datetime` is an **epoch number**, not a string.
153
+
154
+ If your token is table-scoped, typegen describes only the tables it can read.
155
+ That is intended: the bindings then cover exactly what the caller can use.
156
+
157
+ ## `oalo tail`
158
+
159
+ Watch the workspace while you work.
160
+
161
+ ```sh
162
+ oalo tail # readable lines
163
+ oalo tail --filter loop # only events matching some text
164
+ oalo tail --json | jq . # one JSON object per line
165
+ oalo tail --all # include streaming chunk events
166
+ ```
167
+
168
+ ```
169
+ 19:09:01 jobs:update running=0
170
+ 19:09:04 activity:event phil saved "Onboarding"
171
+ 19:09:04 activity:event phil deleted flow
172
+ ```
173
+
174
+ This answers "I did a thing, did the thing I expected happen?" A flow that never
175
+ fired shows up as silence rather than as a mystery.
176
+
177
+ **What it shows:** loop and job state, builds and previews, team chat, and the
178
+ activity stream (who changed what). It intercepts *every* push rather than a
179
+ fixed list, so event types added later appear without a client update.
180
+
181
+ **What it does not show:** direct writes through the Sync REST API. Those do
182
+ not broadcast on this channel. You see their *consequences*: the loop a write
183
+ triggered, the job it queued.
184
+
185
+ Events go to stdout and the banner to stderr, so `oalo tail | grep ...` and
186
+ `oalo tail --json | jq` both behave. Needs a **CLI** token, not a sync token.
187
+
188
+ ## `oalo run` and `oalo call`
189
+
190
+ ```sh
191
+ oalo run loop Nurture --watch # fire a loop, stream its nodes
192
+ oalo run bot bot_5t0v… "hello" # send a bot a test message
193
+ oalo call jobs:snapshot | jq . # any build-surface event
194
+ oalo call loops:run_now '{"name":"Nurture"}'
195
+ oalo call --list # events you can call, by namespace
196
+ oalo call --list loops: # one namespace, with each event's params
197
+ oalo emit form.submitted --dry-run # which loops an event would fire
198
+ oalo whoami # who and where the CLI acts, tokens checked
199
+ ```
200
+
201
+ ```
202
+ started loop_hello (run 1750)
203
+ 19:37:54 loop_node_status running step_1
204
+ 19:38:04 loop_node_status done step_1
205
+ 19:38:04 loop_run_status success
206
+ ```
207
+
208
+ `run` stops as soon as the run reaches a terminal status, so `--timeout` is a
209
+ ceiling rather than a wait. A loop that parks on a durable wait reports
210
+ `waiting` and stops watching, because it may resume hours later.
211
+
212
+ **`run loop` fires the loop directly and does NOT exercise its trigger.**
213
+ Triggers come from the event bus (a real form submission, a real inbound
214
+ message). So this proves the loop *body* works. To test the trigger, use
215
+ `oalo emit`.
216
+
217
+ `call` is the escape hatch. The build surface is ~500 events, and wrapping each
218
+ as its own command would be a lot of surface for little gain, so first-class
219
+ commands cover the common paths and `call` reaches the rest. Params are a JSON
220
+ object; org and user identity always come from your token, never the params, so
221
+ a call can never exceed what you can do.
222
+
223
+ `call --list` asks the workspace which events you may call. The list comes from
224
+ the server's own handlers, so it can't go stale, and it leaves out what your
225
+ access would refuse. Give it text (`oalo call --list template`) to see the
226
+ matching events with the param shapes each accepts. An unknown event says so;
227
+ an event that exists but gets the wrong params says which params it takes; a
228
+ wrong kind of id (`"nope"` where a number goes) says that, instead of a database
229
+ error.
230
+
231
+ ## `oalo emit`
232
+
233
+ ```sh
234
+ oalo emit --list # events a trigger can listen for
235
+ oalo emit form.submitted --payload '{"form":"quote"}' --dry-run
236
+ oalo emit form.submitted --payload '{"form":"quote"}' --contact <contact id>
237
+ ```
238
+
239
+ Fires a domain event through the real event bus, as if a form had been
240
+ submitted or an order placed, so you can check that a loop's trigger and its
241
+ payload filter are wired. `--dry-run` lists the loops it would fire and changes
242
+ nothing; start there, because a real emit fires those loops for real, and they
243
+ may email or message real contacts.
244
+
245
+ The workspace keeps it safe to have around:
246
+
247
+ - Only admins, and members an admin gave **Developer** access (Team panel), can
248
+ emit.
249
+ - The type must be one a trigger can listen for. A typo gets "did you mean"
250
+ instead of an event nothing hears.
251
+ - `--contact` must be an existing contact. emit never creates one.
252
+ - Each event is stored tagged `cli:<you>` and posted to the team feed, so
253
+ nobody mistakes a test for a real customer.
254
+
255
+ ## `oalo whoami`
256
+
257
+ Shows the workspace, who the CLI acts as (role, Developer access), the
258
+ connection's name and last use, whether the data token works, and the
259
+ workspace's API version. Run it first when a command is refused: it says which
260
+ token is the problem. The CLI checks the workspace's version before commands
261
+ that need newer server code, and says which side to update instead of failing
262
+ halfway.
263
+
264
+ ## Editing flows, loops and tools: `pull`, `lint`, `push`, `status`
265
+
266
+ ```sh
267
+ oalo pull # into .oalo/: flows, loops, code tools, node catalogs
268
+ oalo lint # check edited files; nothing is saved
269
+ oalo push # lint, then save each edited file
270
+ oalo status # new / clean / modified / behind / conflict / gone
271
+ ```
272
+
273
+ `pull` writes `.oalo/flows/<bot>/<flow>.json`, `.oalo/loops/<loop>.json`
274
+ (app-pinned loops under `loops/apps/<app>/`), `.oalo/tools/<tool>.js` with its
275
+ `.json`, and the node contracts `.oalo/catalog/flow.json` and `loop.json`. Each
276
+ file keeps what you edit (`flow`, `loop`, `tool`, and a tool's `.js`) apart from
277
+ the CLI's bookkeeping (`id`, `version`, `base_hash`).
278
+
279
+ `push` is guarded three ways:
280
+
281
+ - **Concurrent edits.** It sends the version you pulled. If someone saved in
282
+ between, the push is refused as a conflict and their copy is written next to
283
+ yours (`*.server.json`, or `*.server.js` for a tool) to merge from. `--force`
284
+ overwrites instead.
285
+ - **Lint.** Saves are strict: a file with error-level findings is refused, and
286
+ each finding says what to fix. `--no-strict` saves anyway, but a graph that is
287
+ structurally broken (unknown node type, node without an id) is always refused.
288
+ - **New files.** A file without a version creates a flow, loop or tool, unless
289
+ one with that name already exists, in which case it is refused rather than
290
+ replaced.
291
+
292
+ `pull` never overwrites a file you have edited unless you pass `--force`.
293
+
294
+ ## Test conversations: `oalo qa`
295
+
296
+ ```sh
297
+ oalo qa bot_xxx "hola" "busco un sofá" --channel whatsapp
298
+ oalo qa bot_xxx --script quote.json --json
299
+ ```
300
+
301
+ Sends each message, waits until the bot settles, and prints its replies, where
302
+ any flow is (and what it waits for), the path of flow steps, and the cost.
303
+ `--channel` makes the bot behave as on that channel: WhatsApp, Instagram and SMS
304
+ run in text mode.
305
+
306
+ The conversation is a hidden preview with no phone number, so nothing reaches a
307
+ customer. Everything else is real: tools and flows run, contact details are
308
+ saved, handoffs happen.
309
+
310
+ ## Teaching your coding agent: `oalo skill`
311
+
312
+ `oalo skill` installs `.claude/skills/oalo/SKILL.md`, which Claude Code loads on
313
+ its own: the pull, lint, push, qa loop and the rules that keep a live workspace
314
+ safe. `--agents` writes the same rules into `AGENTS.md` for other agents.
315
+
316
+ ### As a Claude Code plugin
317
+
318
+ The `plugin/` folder is a Claude Code plugin: the same skill plus the MCP server
319
+ (`npx -y @oalo-ai/cli mcp`), and `.claude-plugin/marketplace.json` lists it. From
320
+ a clone, or once this package's repository is public:
321
+
322
+ ```sh
323
+ /plugin marketplace add <path or owner/repo>
324
+ /plugin install oalo@oalo
325
+ ```
326
+
327
+ ## `oalo mcp`
328
+
329
+ Serves this workspace over MCP so Claude Code, Cursor, or any MCP client can
330
+ build flows, bots, tools and knowledge in it.
331
+
332
+ ```json
333
+ {
334
+ "mcpServers": {
335
+ "oalo": {
336
+ "command": "npx",
337
+ "args": ["-y", "@oalo-ai/cli", "mcp"],
338
+ "env": {
339
+ "OALO_URL": "https://<workspace>.oalo.ai",
340
+ "OALO_CLI_TOKEN": "oalo_cli_..."
341
+ }
342
+ }
343
+ }
344
+ }
345
+ ```
346
+
347
+ The dashboard hands you this exact block when you create a connection. If you
348
+ have already run `oalo login` (or `oalo link --cli-token`), the `env` entries
349
+ are optional: the server falls back to the stored credential.
350
+
351
+ The build surface is a channel with ~442 events, so the server exposes a
352
+ **curated** subset as tools plus an `oalo_call` passthrough for the long tail.
353
+ Mapping all of them would flood an agent's context and wreck tool selection.
354
+
355
+ A CLI token **acts as you**: it inherits your permissions exactly and there is
356
+ no read-only mode. Treat it like a password. Running the server does not disturb
357
+ your dashboard session; both can be open at once.
358
+
359
+ ### Editing a flow safely
360
+
361
+ `oalo_save_flow` has two traps, both encoded in its schema:
362
+
363
+ - **`graph` replaces what is stored.** Not a patch. Read the flow first and send
364
+ every node and edge you want to keep.
365
+ - **The read side calls it `version`; the write side wants `base_version`.**
366
+ Echo it back so a concurrent edit is rejected rather than silently
367
+ overwritten. (The server now also accepts `version`, but being explicit is
368
+ clearer.)
369
+
370
+ ## Development
371
+
372
+ ```sh
373
+ npm run test:unit # offline: the flow/loop/tool file format
374
+ npm run smoke # link + typegen + help contract, against a live server
375
+ npm run smoke:mcp # the MCP server, over the real protocol
376
+ npm run smoke:mcp -- --write # also exercises save_flow against a throwaway flow
377
+ OALO_SMOKE_QA_BOT=bot_xxx npm run smoke:mcp # also a real test conversation (spends model money)
378
+ ```
379
+
380
+ `OALO_DEBUG=1` turns on transport logging (stderr).
381
+
382
+
383
+
384
+ ```sh
385
+ OALO_URL=http://localhost:4242 OALO_SYNC_TOKEN=... npm run smoke
386
+ ```
387
+
388
+ The suite runs entirely in a temp directory with `OALO_HOME` redirected, so it
389
+ never touches the repo or a real credential store, and exits non-zero on
390
+ failure. Set `OALO_TABLE_TOKEN` to a per-table token to also cover the regression where
391
+ a table-scoped token could read rows but not the manifest, and
392
+ `OALO_SMOKE_TSC` to a `tsc` binary to assert the generated bindings actually
393
+ compile under `--strict`.