@junoflow/cli 0.3.0 → 0.9.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.
- package/README.md +693 -52
- package/app-client.js +306 -0
- package/bin/juno.js +7 -0
- package/eslint.app.config.js +34 -0
- package/eslint.flow.config.js +35 -0
- package/package.json +12 -4
- package/src/api.js +541 -30
- package/src/app-dev/build.js +134 -0
- package/src/app-dev/harness.js +108 -0
- package/src/app-dev/panel.html +336 -0
- package/src/app-dev/panel.js +290 -0
- package/src/app-dev/server.js +347 -0
- package/src/app-dev/stream.js +224 -0
- package/src/app-dev/watch.js +47 -0
- package/src/app-project.js +133 -0
- package/src/authoring-skills.js +43 -0
- package/src/commands/agent-skill-install.js +64 -0
- package/src/commands/app-check.js +75 -0
- package/src/commands/app-deploy.js +102 -0
- package/src/commands/app-dev.js +141 -0
- package/src/commands/app-gen-types.js +37 -0
- package/src/commands/app-init.js +46 -0
- package/src/commands/app-pull.js +106 -0
- package/src/commands/ask.js +60 -0
- package/src/commands/check.js +71 -27
- package/src/commands/config.js +26 -0
- package/src/commands/credential-set.js +68 -0
- package/src/commands/deploy.js +95 -51
- package/src/commands/flow-check.js +75 -0
- package/src/commands/flow-deploy.js +110 -0
- package/src/commands/flow-fixture.js +67 -0
- package/src/commands/flow-gen-types.js +37 -0
- package/src/commands/flow-init.js +59 -0
- package/src/commands/flow-pull.js +115 -0
- package/src/commands/{types.js → gen-types.js} +12 -5
- package/src/commands/init.js +28 -16
- package/src/commands/pull.js +111 -63
- package/src/commands/session-approve.js +73 -0
- package/src/commands/skill.js +73 -0
- package/src/commands/tool.js +249 -0
- package/src/commands/verify-types.js +4 -2
- package/src/config.js +34 -0
- package/src/diagnostics.js +14 -0
- package/src/errors.js +40 -0
- package/src/exit.js +22 -0
- package/src/flow-project.js +78 -0
- package/src/help.js +199 -0
- package/src/index.js +567 -75
- package/src/installed-skill.js +55 -0
- package/src/project.js +76 -8
- package/src/prompt.js +196 -0
- package/src/stamp.js +10 -9
- package/src/suggest.js +67 -0
- package/src/target.js +22 -7
- package/src/verbs.js +34 -0
- package/src/watch.js +87 -0
- package/src/working-copy.js +262 -0
- package/tsconfig.page.json +15 -0
- package/verbs.json +276 -0
package/README.md
CHANGED
|
@@ -1,52 +1,691 @@
|
|
|
1
1
|
# @junoflow/cli
|
|
2
2
|
|
|
3
|
-
Develop [Juno](https://github.com/boivie/juno)
|
|
4
|
-
|
|
3
|
+
Develop [Juno](https://github.com/boivie/juno) flows and integrations, and
|
|
4
|
+
operate a Juno instance, from the command line.
|
|
5
5
|
|
|
6
6
|
```bash
|
|
7
|
-
npm install --save-dev @junoflow/cli
|
|
8
|
-
|
|
7
|
+
npm install --save-dev @junoflow/cli # in a flow or integration working copy
|
|
8
|
+
npm install --global @junoflow/cli # to operate an instance from anywhere
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
The package installs one command, `juno`, with two sets of commands:
|
|
12
|
+
|
|
13
|
+
- **Operating an instance**: `juno <group> <verb> [id]` puts a named command in
|
|
14
|
+
front of every tool Juno's in-app assistant uses, so a coding agent (Claude
|
|
15
|
+
Code and similar), a shell script or a person can find things and act on them.
|
|
16
|
+
`juno ask` talks to the assistant itself.
|
|
17
|
+
- **Working copies**: `juno flow init`, `juno integration init` and the commands
|
|
18
|
+
beside them write a flow or an integration to disk as files, check it, and
|
|
19
|
+
deploy it. See [Working copies](#working-copies).
|
|
20
|
+
|
|
21
|
+
Juno has **no authentication on any endpoint**. An instance is a trusted-LAN
|
|
22
|
+
appliance; do not expose one to the internet.
|
|
23
|
+
|
|
24
|
+
## Operating an instance
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
juno config set url juno.local:8080 # once, outside a project
|
|
28
|
+
juno discover porch --kind state
|
|
29
|
+
juno state get porch-dark --jq .value
|
|
30
|
+
juno related endpoint:ha:light.hallway --degree 2
|
|
31
|
+
juno state preview-layers porch-dark --input - <<'EOF'
|
|
32
|
+
{"ops":[{"op":"insert", …}]}
|
|
33
|
+
EOF
|
|
34
|
+
juno ha call-service --input '{"domain":"light","service":"turn_off"}'
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The grammar is `juno <group> <verb> [id]`. The group is the kind of thing a
|
|
38
|
+
command acts on, so a `discover` hit's `{kind, id}` leads straight to
|
|
39
|
+
`juno <kind> <verb> <id>` and `juno related <kind>:<id>` (or `<kind> <id>`). A
|
|
40
|
+
verb may also be spelled with `_` (`juno state get_history`).
|
|
41
|
+
|
|
42
|
+
Every tool command sends the tool's own JSON input, and prints the tool's JSON
|
|
43
|
+
answer. The CLI holds no tool semantics: the instance validates the input,
|
|
44
|
+
writes the descriptions and classifies each command's effect.
|
|
45
|
+
|
|
46
|
+
### Help
|
|
47
|
+
|
|
48
|
+
| Command | Shows |
|
|
49
|
+
| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
|
50
|
+
| `juno --help` | the groups and top-level commands. Offline. |
|
|
51
|
+
| `juno <group> --help` | the group's commands, each with its effect and the first sentence of its description. Offline, it lists the commands alone. |
|
|
52
|
+
| `juno <group> <verb> --help` | the tool's full description, its effect, its input schema, and what the positional and flags fill in. Needs the instance. |
|
|
53
|
+
|
|
54
|
+
`juno <group>` on its own prints the group's help and exits 2. An unknown
|
|
55
|
+
command suggests the closest one: `juno stat get` answers
|
|
56
|
+
`did you mean "juno state get"?`.
|
|
57
|
+
|
|
58
|
+
### Effects
|
|
59
|
+
|
|
60
|
+
Every command has one effect:
|
|
61
|
+
|
|
62
|
+
- **read** changes nothing.
|
|
63
|
+
- **change** writes stored configuration that keeps a version timeline, so it
|
|
64
|
+
can be reverted.
|
|
65
|
+
- **act** does something now (calls a service, presses a button, sets a value,
|
|
66
|
+
starts a run, executes code), with nothing to roll back to.
|
|
67
|
+
|
|
68
|
+
`config set` and `agent-skill install` are **local**: they write a file on your
|
|
69
|
+
machine and change nothing on the instance. So is `app dev`, which serves an app
|
|
70
|
+
working copy on localhost and never connects to an instance.
|
|
71
|
+
|
|
72
|
+
No command of a lower effect is a string prefix of a command of a higher one.
|
|
73
|
+
Claude Code matches a Bash permission rule by prefix, so an allowlist can list
|
|
74
|
+
the read commands, and optionally the change commands, and every act still
|
|
75
|
+
prompts.
|
|
76
|
+
|
|
77
|
+
### Commands
|
|
78
|
+
|
|
79
|
+
The positional fills the named input field. A dash means the command takes no
|
|
80
|
+
positional; its input is a whole document (`device write`) or has no id
|
|
81
|
+
(`run list`).
|
|
82
|
+
|
|
83
|
+
**Top level**
|
|
84
|
+
|
|
85
|
+
| Command | Tool | Positional | Effect |
|
|
86
|
+
| --------------------------------------- | ------------- | ----------------------------------- | ------ |
|
|
87
|
+
| `juno discover <query> [--kind k]…` | `discover` | `query`; `--kind` → `kinds` | read |
|
|
88
|
+
| `juno related <kind>:<id> [--degree n]` | `get_related` | `kind`, `id`; `--degree` → `degree` | read |
|
|
89
|
+
| `juno skill [<name>] [--json]` | none | skill name | read |
|
|
90
|
+
| `juno ask ["<prompt>"]` | none | the prompt | act |
|
|
91
|
+
| `juno agent-skill install` | none | none | local |
|
|
92
|
+
|
|
93
|
+
`related` splits its positional on the first `:` only, so
|
|
94
|
+
`endpoint:ha:light.hallway` is kind `endpoint`, id `ha:light.hallway`.
|
|
95
|
+
|
|
96
|
+
**`state`**
|
|
97
|
+
|
|
98
|
+
| Command | Tool | Positional | Effect |
|
|
99
|
+
| --------------------------- | ------------------------------------- | ---------- | ------ |
|
|
100
|
+
| `juno state get` | `get_state` | `id` | read |
|
|
101
|
+
| `juno state get-history` | `get_state_history` | `id` | read |
|
|
102
|
+
| `juno state get-layers` | `get_state_layers` | `id` | read |
|
|
103
|
+
| `juno state evaluate-level` | `evaluate_level` | — | read |
|
|
104
|
+
| `juno state preview-layers` | `edit_state_layers`, `dry_run: true` | `id` | read |
|
|
105
|
+
| `juno state create` | `create_state` | `id` | change |
|
|
106
|
+
| `juno state edit-layers` | `edit_state_layers`, `dry_run: false` | `id` | change |
|
|
107
|
+
| `juno state set-value` | `set_state_value` | `id` | act |
|
|
108
|
+
| `juno state claim` | `claim_state` | `id` | act |
|
|
109
|
+
| `juno state release` | `release_state` | `id` | act |
|
|
110
|
+
|
|
111
|
+
`preview-layers` and `edit-layers` call the same tool. The preview always sends
|
|
112
|
+
`dry_run: true`, so a permission rule can allow the preview and not the edit; an
|
|
113
|
+
`--input` that says `"dry_run": false` is refused.
|
|
114
|
+
|
|
115
|
+
**`flow`**
|
|
116
|
+
|
|
117
|
+
| Command | Tool | Positional | Effect |
|
|
118
|
+
| ------------------------ | ------------------ | ---------- | ------ |
|
|
119
|
+
| `juno flow get` | `get_flow` | `id` | read |
|
|
120
|
+
| `juno flow set-enabled` | `set_flow_enabled` | `flow_id` | change |
|
|
121
|
+
| `juno flow trigger` | `trigger_flow` | `flow_id` | act |
|
|
122
|
+
| `juno flow fire-trigger` | `fire_trigger` | `flow_id` | act |
|
|
123
|
+
|
|
124
|
+
The group's working-copy commands (`flow init`, `gen-types`, `check`, `deploy`,
|
|
125
|
+
`pull`) are in [Flows as files](#flows-as-files).
|
|
126
|
+
|
|
127
|
+
**`run`**
|
|
128
|
+
|
|
129
|
+
| Command | Tool | Positional | Effect |
|
|
130
|
+
| --------------- | ----------- | ---------- | ------ |
|
|
131
|
+
| `juno run list` | `list_runs` | — | read |
|
|
132
|
+
| `juno run get` | `get_run` | `run_id` | read |
|
|
133
|
+
|
|
134
|
+
**`integration`**
|
|
135
|
+
|
|
136
|
+
| Command | Tool | Positional | Effect |
|
|
137
|
+
| ------------------------------- | -------------------------- | ------------- | ------ |
|
|
138
|
+
| `juno integration get` | `get_integration` | `id` | read |
|
|
139
|
+
| `juno integration configure` | `configure_integration` | `id` | change |
|
|
140
|
+
| `juno integration install` | `install_integration` | — | act |
|
|
141
|
+
| `juno integration run-schedule` | `run_integration_schedule` | `integration` | act |
|
|
142
|
+
|
|
143
|
+
The group's working-copy commands (`integration init`, `gen-types`,
|
|
144
|
+
`verify-types`, `check`, `deploy`, `pull`) are in
|
|
145
|
+
[Developing integrations](#developing-integrations).
|
|
146
|
+
|
|
147
|
+
**`device`**
|
|
148
|
+
|
|
149
|
+
| Command | Tool | Positional | Effect |
|
|
150
|
+
| --------------------- | ---------------- | ---------- | ------ |
|
|
151
|
+
| `juno device list` | `list_devices` | — | read |
|
|
152
|
+
| `juno device get` | `get_device` | `id` | read |
|
|
153
|
+
| `juno device preview` | `preview_device` | — | read |
|
|
154
|
+
| `juno device write` | `write_device` | — | change |
|
|
155
|
+
| `juno device suspend` | `suspend_device` | `id` | act |
|
|
156
|
+
| `juno device release` | `release_device` | `id` | act |
|
|
157
|
+
|
|
158
|
+
**`control`**
|
|
159
|
+
|
|
160
|
+
| Command | Tool | Positional | Effect |
|
|
161
|
+
| ---------------------- | ----------------- | ---------- | ------ |
|
|
162
|
+
| `juno control write` | `write_control` | — | change |
|
|
163
|
+
| `juno control use` | `use_control` | `id` | act |
|
|
164
|
+
| `juno control release` | `release_control` | `id` | act |
|
|
165
|
+
|
|
166
|
+
**`finding`**
|
|
167
|
+
|
|
168
|
+
| Command | Tool | Positional | Effect |
|
|
169
|
+
| ------------------ | ------------- | ---------- | ------ |
|
|
170
|
+
| `juno finding get` | `get_finding` | `slug` | read |
|
|
171
|
+
|
|
172
|
+
**`webhook`**
|
|
173
|
+
|
|
174
|
+
| Command | Tool | Positional | Effect |
|
|
175
|
+
| ----------------------------- | ------------------------ | ---------- | ------ |
|
|
176
|
+
| `juno webhook list` | `list_webhooks` | — | read |
|
|
177
|
+
| `juno webhook get-deliveries` | `get_webhook_deliveries` | `id` | read |
|
|
178
|
+
| `juno webhook write` | `write_webhook` | `id` | change |
|
|
179
|
+
| `juno webhook fire` | `fire_webhook` | `id` | act |
|
|
180
|
+
|
|
181
|
+
**`credential`**
|
|
182
|
+
|
|
183
|
+
| Command | Tool | Positional | Effect |
|
|
184
|
+
| -------------------------- | ------------------ | ------------- | ------ |
|
|
185
|
+
| `juno credential list` | `list_credentials` | — | read |
|
|
186
|
+
| `juno credential set <id>` | none | credential id | act |
|
|
187
|
+
|
|
188
|
+
**`ha`** (Home Assistant)
|
|
189
|
+
|
|
190
|
+
| Command | Tool | Positional | Effect |
|
|
191
|
+
| ---------------------------- | -------------------- | ----------- | ------ |
|
|
192
|
+
| `juno ha get-entity` | `get_entity` | `entity_id` | read |
|
|
193
|
+
| `juno ha get-entity-history` | `get_entity_history` | `entity_id` | read |
|
|
194
|
+
| `juno ha list-services` | `list_services` | — | read |
|
|
195
|
+
| `juno ha call-service` | `call_service` | — | act |
|
|
196
|
+
|
|
197
|
+
**`script`**
|
|
198
|
+
|
|
199
|
+
| Command | Tool | Positional | Effect |
|
|
200
|
+
| ------------------------------------- | ------------ | ----------------------------------------------------- | ------ |
|
|
201
|
+
| `juno script run <file> [--cred id]…` | `run_script` | `<file>` → `files`, `entry`; `--cred` → `credentials` | act |
|
|
202
|
+
|
|
203
|
+
`script run` reads the file relative to the working directory, puts its content
|
|
204
|
+
under `files` by its basename and makes it the `entry`.
|
|
205
|
+
|
|
206
|
+
**`session`** and **`config`**
|
|
207
|
+
|
|
208
|
+
| Command | Tool | Positional | Effect |
|
|
209
|
+
| --------------------------- | ---- | ----------- | ------ |
|
|
210
|
+
| `juno session approve <id>` | none | session id | act |
|
|
211
|
+
| `juno config set url <url>` | none | `url <url>` | local |
|
|
212
|
+
|
|
213
|
+
There is no command that calls a tool by name. One permission rule for such a
|
|
214
|
+
command would allow every act, so each tool has its own command instead, and a
|
|
215
|
+
tool added to the server needs a CLI release to reach.
|
|
216
|
+
|
|
217
|
+
### Input
|
|
218
|
+
|
|
219
|
+
The id, when the command has one, is the single positional argument. Everything
|
|
220
|
+
else is the tool's input JSON, given with `--input`:
|
|
221
|
+
|
|
222
|
+
| Form | Reads |
|
|
223
|
+
| ------------------ | ----------------------------------------- |
|
|
224
|
+
| `--input '<json>'` | the JSON itself |
|
|
225
|
+
| `--input -` | standard input |
|
|
226
|
+
| `--input @<path>` | a file, relative to the working directory |
|
|
227
|
+
|
|
228
|
+
The input must be a JSON object. Without `--input` it is `{}`. The CLI never
|
|
229
|
+
reads standard input unless `--input -` asks for it, because an open, empty
|
|
230
|
+
stdin would hang an agent, and it never prompts off a terminal.
|
|
231
|
+
|
|
232
|
+
The input is built in this order: the `--input` object, then the positional into
|
|
233
|
+
its field, then the command's flags, then the fixed input (`preview-layers`'
|
|
234
|
+
`dry_run`). A positional or flag that disagrees with a value already in
|
|
235
|
+
`--input` is a usage error naming both. The CLI checks nothing else: the
|
|
236
|
+
instance validates the input against the tool's schema and answers with the
|
|
237
|
+
failing field (`/id`, `/ops/0/when`, `/` for the input as a whole).
|
|
238
|
+
|
|
239
|
+
### Output
|
|
240
|
+
|
|
241
|
+
Standard output carries the tool's answer and nothing else:
|
|
242
|
+
|
|
243
|
+
- off a terminal, the instance's JSON byte for byte;
|
|
244
|
+
- on a terminal, the same JSON indented by two spaces. Never coloured.
|
|
245
|
+
|
|
246
|
+
The choice follows whether stdout is a terminal, so a script and an agent see
|
|
247
|
+
the same bytes. A tool that fails in its own terms answers `{"error": "…"}`;
|
|
248
|
+
that is printed on stdout like any answer, and the exit code is 1. Warnings,
|
|
249
|
+
progress and notes go to standard error.
|
|
250
|
+
|
|
251
|
+
`--jq <expr>` runs the answer through [`jq`](https://jqlang.org), which must be
|
|
252
|
+
on `PATH` (`jq -c` off a terminal, plain `jq` on one). A filter given this way
|
|
253
|
+
stays inside the command's own permission rule, where a pipe to `jq` would need
|
|
254
|
+
its own. A missing `jq` or an expression `jq` rejects exits 2.
|
|
255
|
+
|
|
256
|
+
### Exit codes
|
|
257
|
+
|
|
258
|
+
One table for the whole CLI, the working-copy commands included:
|
|
259
|
+
|
|
260
|
+
| Code | When |
|
|
261
|
+
| ---- | ---------------------------------------------------------------------------------------------------- |
|
|
262
|
+
| 0 | Success |
|
|
263
|
+
| 1 | A tool answer with a top-level string `error`, a failed check or deploy, or any other failure |
|
|
264
|
+
| 2 | Usage: an unknown command or flag, bad `--input`, input the instance rejects, a bad `--jq` |
|
|
265
|
+
| 3 | The instance is unreachable |
|
|
266
|
+
| 4 | A working-copy `deploy` or `check`, any kind: the instance changed since the working copy was pulled |
|
|
267
|
+
| 5 | `juno ask` / `session approve`: the turn is still running |
|
|
268
|
+
| 6 | `juno ask` / `session approve`: the assistant is waiting for an answer or an approval |
|
|
269
|
+
|
|
270
|
+
An instance that does not serve a tool the CLI names is older than the CLI; that
|
|
271
|
+
is also exit 2, and the message says so.
|
|
272
|
+
|
|
273
|
+
### `juno ask` and `juno session approve`
|
|
274
|
+
|
|
275
|
+
`juno ask` sends a prompt to Juno's in-app assistant through the
|
|
276
|
+
[Agent Prompt API](https://boivie.github.io/juno/reference/agent-api/) and
|
|
277
|
+
prints the turn's final event. It is for scripts, chat bridges and a person at a
|
|
278
|
+
terminal. Its effect is **act**, because the assistant can do anything its tools
|
|
279
|
+
allow.
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
juno ask "Why is the porch lamp on?"
|
|
283
|
+
juno ask --session <id> "And the hallway?" # continue a conversation
|
|
284
|
+
juno ask --session <id> # keep waiting on a running turn
|
|
285
|
+
juno session approve <id> # let a pending tool call through
|
|
286
|
+
juno ask --session <id> "not while I'm away" # deny it, with a reason
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
| Option | Meaning |
|
|
290
|
+
| ------------------ | -------------------------------------------------------------------------------- |
|
|
291
|
+
| `--session <id>` | resume that session. With no prompt, observe it: wait for its turn, send nothing |
|
|
292
|
+
| `--wait <seconds>` | off a terminal, how long one call waits before answering pending (default 60) |
|
|
293
|
+
|
|
294
|
+
Off a terminal each call is one request that waits up to `--wait` seconds, and
|
|
295
|
+
the final event is printed unchanged. On a terminal the answer streams: progress
|
|
296
|
+
and the session id go to standard error, and the final event is printed
|
|
297
|
+
indented. The exit code is the outcome:
|
|
298
|
+
|
|
299
|
+
| Event | Exit |
|
|
300
|
+
| ----------------------------------------------- | ---- |
|
|
301
|
+
| `message`, or `done` with `completed` | 0 |
|
|
302
|
+
| `pending` (still running) | 5 |
|
|
303
|
+
| `awaiting_input` (question or approval) | 6 |
|
|
304
|
+
| `done` with `failed` or `abandoned`, or `error` | 1 |
|
|
305
|
+
|
|
306
|
+
So "keep waiting" is `juno ask --session <id>` until the exit code is not 5. A
|
|
307
|
+
new conversation's session id is minted by the CLI, so a retry after a dropped
|
|
308
|
+
connection resumes the same session; a failed connection is retried twice before
|
|
309
|
+
exit 3. When the session is busy with another turn, `juno ask` waits for that
|
|
310
|
+
turn to end and sends again (up to three times), unless the turn ends waiting
|
|
311
|
+
for input, since the message would then be taken as the answer.
|
|
312
|
+
|
|
313
|
+
`juno ask approve` is refused: approving is `juno session approve <id>`, a
|
|
314
|
+
separate command so that a permission rule allowing `juno ask` does not also let
|
|
315
|
+
an agent approve the assistant's `call_service` on your behalf.
|
|
316
|
+
`session approve` first checks that the session is waiting for an approval and
|
|
317
|
+
exits 1 without sending anything otherwise. Any other message sent to a session
|
|
318
|
+
waiting for an approval denies it, and the assistant sees the message as the
|
|
319
|
+
reason.
|
|
320
|
+
|
|
321
|
+
### `juno credential set`
|
|
322
|
+
|
|
323
|
+
```bash
|
|
324
|
+
juno credential set pushover_token # update an existing one
|
|
325
|
+
juno credential set pushover_token --name Pushover --description "App token"
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
It reads the secret from the terminal with echo off and stores it. An existing
|
|
329
|
+
credential keeps its name and description unless `--name` or `--description`
|
|
330
|
+
replaces them; a new one needs both. It never prints the value.
|
|
331
|
+
|
|
332
|
+
It **refuses to run when standard input is not a terminal** (exit 2), so an
|
|
333
|
+
agent cannot pipe a value into it: a secret an agent types lands in its
|
|
334
|
+
transcript. A person runs it, directly or as `! juno credential set <id>` inside
|
|
335
|
+
Claude Code. For the same reason there is no tool that writes a credential.
|
|
336
|
+
|
|
337
|
+
### `juno skill`
|
|
338
|
+
|
|
339
|
+
`juno skill` lists the skills the assistant can load, one
|
|
340
|
+
`<name> — <description>` line each. `juno skill <name>` prints one skill's body.
|
|
341
|
+
`--json` prints the instance's JSON instead. The skills are the in-app
|
|
342
|
+
assistant's, rendered for a reader outside Juno: where the assistant reads a
|
|
343
|
+
tool name (`edit_state_layers`), you read the command in front of it
|
|
344
|
+
(`juno state edit-layers`), or what to do in a working copy instead
|
|
345
|
+
(`npm run deploy` for `submit_code`).
|
|
346
|
+
|
|
347
|
+
### `juno agent-skill install`
|
|
348
|
+
|
|
349
|
+
```bash
|
|
350
|
+
juno agent-skill install # .claude/skills/juno/SKILL.md
|
|
351
|
+
juno agent-skill install --dir ~/.claude/skills/juno # for every project
|
|
352
|
+
juno agent-skill install --allow-change # + every change command
|
|
353
|
+
juno agent-skill install --working-copy # + the npm scripts (run it in a working copy)
|
|
354
|
+
juno agent-skill install --agents-md >> AGENTS.md # for a harness that reads AGENTS.md
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
Writes the skill that teaches a coding agent to use Juno — `SKILL.md` in the
|
|
358
|
+
Agent Skills format, rendered by the instance (`GET /api/agent-skill`) — and
|
|
359
|
+
prints a Claude Code permission block on stdout:
|
|
360
|
+
|
|
361
|
+
```json
|
|
362
|
+
{"permissions":{"allow":["Bash(juno discover:*)","Bash(juno related:*)","Bash(juno state get:*)",…]}}
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
The block is generated from the command table and each command's effect. By
|
|
366
|
+
default it allows every `read` command. `--allow-change` adds every `change`
|
|
367
|
+
command (layer stacks, devices, controls, `flow deploy`, …), for when you want
|
|
368
|
+
the agent to reconfigure freely and use version history to review or undo.
|
|
369
|
+
`--working-copy` adds the working copy's npm scripts of the same tiers
|
|
370
|
+
(`npm test`, `npm run check`, and with `--allow-change` `npm run deploy`); those
|
|
371
|
+
trust whatever that `package.json` says the script runs, so put them in the
|
|
372
|
+
working copy's `.claude/settings.json`, never in user-wide settings. **An `act`
|
|
373
|
+
command is never in the block**: it always prompts. Add one by hand if you want
|
|
374
|
+
to, for example, let the agent press a test button. A command added in a later
|
|
375
|
+
version prompts until you regenerate the block.
|
|
376
|
+
|
|
377
|
+
The skill is short and stable; what changes per instance comes from `juno skill`
|
|
378
|
+
and `--help` at run time. It records the version it was rendered at, and
|
|
379
|
+
`juno skill`, `juno flow init` and `juno integration init` say on stderr when
|
|
380
|
+
the instance serves a newer one. `--agents-md` prints the same text as an
|
|
381
|
+
`AGENTS.md` section and writes nothing; `--json` prints the instance's answer.
|
|
382
|
+
|
|
383
|
+
## Choosing an instance
|
|
384
|
+
|
|
385
|
+
In order:
|
|
386
|
+
|
|
387
|
+
1. `--url <url>`, before or after the command words;
|
|
388
|
+
2. `$JUNO_URL`;
|
|
389
|
+
3. `"juno": { "target": … }` in `package.json` in the working directory;
|
|
390
|
+
4. the user config file, `$XDG_CONFIG_HOME/juno/config.json`
|
|
391
|
+
(`~/.config/juno/config.json` when `XDG_CONFIG_HOME` is unset).
|
|
392
|
+
|
|
393
|
+
A project downloaded from a Juno already has the third, so a fresh clone deploys
|
|
394
|
+
with no setup. `juno config set url <url>` writes the fourth, which is what
|
|
395
|
+
makes `juno` useful outside a project. A bare host such as `juno.local:8080`
|
|
396
|
+
means `http://juno.local:8080`.
|
|
397
|
+
|
|
398
|
+
Every request carries an `X-Juno-Client` header: `cli`, or `cli/claude-code`
|
|
399
|
+
when `CLAUDECODE=1` is set, as Claude Code sets it in the shells it starts. Juno
|
|
400
|
+
records a write made through a tool as `operator:<client>`. The header names who
|
|
401
|
+
made a change; it is not a security control.
|
|
402
|
+
|
|
403
|
+
## Working copies
|
|
404
|
+
|
|
405
|
+
A working copy is a directory on your machine holding one flow or one
|
|
406
|
+
integration as files. Juno keeps every version, so the instance is the source of
|
|
407
|
+
truth and a working copy is where you edit the next version. Its `package.json`
|
|
408
|
+
names the instance (`juno.target`) and the flow or install it belongs to, so a
|
|
409
|
+
fresh clone deploys with no setup.
|
|
410
|
+
|
|
411
|
+
Both kinds have the same loop and the same npm scripts, and so does an app
|
|
412
|
+
(`juno app init`), whose `check` stores a draft to preview at its `~draft/` URL:
|
|
413
|
+
|
|
414
|
+
```bash
|
|
415
|
+
npm install
|
|
416
|
+
npm test # local tsc, eslint and vitest
|
|
417
|
+
npm run check # ... then verify against the instance, which writes nothing
|
|
418
|
+
npm run deploy # verify, commit and activate the next version
|
|
419
|
+
npm run pull # bring the working copy up to the active version
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
| Script | Flow working copy | Integration working copy | App working copy |
|
|
423
|
+
| -------------- | ------------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------- |
|
|
424
|
+
| `typecheck` | `tsc --noEmit` | `tsc --noEmit` | `tsc --noEmit && tsc --noEmit -p page` |
|
|
425
|
+
| `lint` | `eslint .` | `eslint .` | `eslint .` |
|
|
426
|
+
| `verify-types` | (absent) | `juno integration verify-types` | (absent) |
|
|
427
|
+
| `test` | `npm run typecheck && npm run lint && vitest run` | `npm run verify-types && npm run typecheck && npm run lint && vitest run` | `npm run typecheck && npm run lint && vitest run` |
|
|
428
|
+
| `types` | `juno flow gen-types` | `juno integration gen-types` | `juno app gen-types` |
|
|
429
|
+
| `check` | `juno flow check` | `juno integration check` | `juno app check` |
|
|
430
|
+
| `deploy` | `juno flow deploy` | `juno integration deploy` | `juno app deploy` |
|
|
431
|
+
| `watch` | `juno flow deploy --watch` | `juno integration deploy --watch` | `juno app deploy --watch` |
|
|
432
|
+
| `pull` | `juno flow pull` | `juno integration pull` | `juno app pull` |
|
|
433
|
+
| `dev` | (absent) | (absent) | `juno app dev` |
|
|
434
|
+
|
|
435
|
+
`dev` exists only in an app working copy, because flows and integrations have no
|
|
436
|
+
page to serve. `npm run dev` serves the app on `http://localhost:8090/`
|
|
437
|
+
(`--port <n>` picks another) with no instance, no credentials and no network:
|
|
438
|
+
the page is built from `page/` on each request, the handlers run in Node through
|
|
439
|
+
the testkit's `appHarness`, and live values come from a scenario file
|
|
440
|
+
(`--scenario test/fixtures/<name>.json`). A panel beside the page sets values,
|
|
441
|
+
presses controls and switches scenarios, and saving a file rebuilds and reloads.
|
|
442
|
+
It uses the `esbuild` and `@junoflow/testkit` the working copy installs.
|
|
443
|
+
|
|
444
|
+
`init` writes these lines as the instance renders them. After that they are
|
|
445
|
+
yours: `pull` never changes `scripts`. To see the current lines, run `init` into
|
|
446
|
+
a fresh directory and compare.
|
|
447
|
+
|
|
448
|
+
The working-copy commands sit in the `flow` and `integration` groups beside the
|
|
449
|
+
commands that act on the instance, and `juno flow --help` and
|
|
450
|
+
`juno integration --help` list them under their own heading. Every one except
|
|
451
|
+
`init` acts on the working copy in the current directory and refuses an
|
|
452
|
+
argument: `juno flow deploy porch-light` exits 2 and tells you to run
|
|
453
|
+
`juno flow init porch-light` and deploy from inside it.
|
|
454
|
+
|
|
455
|
+
| Effect | Commands |
|
|
456
|
+
| -------- | -------------------------------------------------------------------- |
|
|
457
|
+
| `read` | `init`, `gen-types`, `verify-types`, `check`, `pull`, `flow fixture` |
|
|
458
|
+
| `change` | `deploy`: it adds a version to the flow's or install's history |
|
|
459
|
+
| `local` | `app dev`: a server on localhost that touches no instance |
|
|
460
|
+
|
|
461
|
+
`init`, `pull` and `flow fixture` write local files, but change nothing in Juno,
|
|
462
|
+
so their effect is `read`.
|
|
463
|
+
|
|
464
|
+
After writing a working copy, `init` prints the skills for writing that kind,
|
|
465
|
+
one `<name> — <description>` line each, the same index Juno's in-app flow or
|
|
466
|
+
integration author is given; load one with `juno skill <name>`.
|
|
467
|
+
`juno flow --help` and `juno integration --help` list them too.
|
|
468
|
+
|
|
469
|
+
### What Juno stores, and what `pull` writes
|
|
470
|
+
|
|
471
|
+
A working copy holds three kinds of file:
|
|
472
|
+
|
|
473
|
+
| Kind | Files | Stored by Juno |
|
|
474
|
+
| --------- | ------------------------------------------------------------------------------------------------- | -------------------------------------- |
|
|
475
|
+
| artifact | `manifest.json`, `src/**`, `test/**` | yes, with every version, byte for byte |
|
|
476
|
+
| generated | `types/**` | no; generated from the manifest |
|
|
477
|
+
| project | everything else: `tsconfig.json`, `eslint.config.js`, `README.md`, `.gitignore`, `package.json` … | no |
|
|
478
|
+
|
|
479
|
+
`check` and `deploy` send the artifact files. Your tests travel with the
|
|
480
|
+
version, so a copy pulled elsewhere gets them back. Juno stores `test/**` and
|
|
481
|
+
nothing more: it never type-checks, lints or runs a test. `npm test` runs them
|
|
482
|
+
in your checkout.
|
|
483
|
+
|
|
484
|
+
`pull` makes the artifact files the active version's, exactly as Juno stores
|
|
485
|
+
them, and regenerates `types/`. It deletes a local file only when Juno deleted
|
|
486
|
+
it: the version recorded in `juno.base_version` had the file and the pulled
|
|
487
|
+
version does not. A file neither version has is one you have not deployed yet,
|
|
488
|
+
and `pull` leaves it alone. A working copy with no `base_version` has nothing to
|
|
489
|
+
compare against, so `pull` deletes nothing. It prints what it wrote and, on a
|
|
490
|
+
second line, what it removed:
|
|
491
|
+
|
|
492
|
+
```text
|
|
493
|
+
pulled porch-light v7 from http://juno.local:8080 (manifest.json, package.json, src/flow.ts)
|
|
494
|
+
removed src/old.ts
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
`pull` never writes or deletes a project file. In `package.json` it sets two
|
|
498
|
+
keys, `juno.base_version` and `dependencies` (from the manifest), and leaves the
|
|
499
|
+
rest as you wrote it. An integration whose manifest declares no dependencies
|
|
500
|
+
keeps them in `package.json` alone, so there `pull` leaves `dependencies` as it
|
|
501
|
+
is. It refuses to overwrite uncommitted changes in a git working tree unless you
|
|
502
|
+
pass `--force`.
|
|
503
|
+
|
|
504
|
+
## Flows as files
|
|
505
|
+
|
|
506
|
+
```bash
|
|
507
|
+
juno flow init porch-light # the flow's active version, or a stub for a new id
|
|
508
|
+
cd porch-light && npm install
|
|
509
|
+
$EDITOR src/flow.ts manifest.json
|
|
510
|
+
npm test
|
|
511
|
+
npm run check
|
|
512
|
+
npm run deploy
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
`juno flow init <id>` writes `./<id>` (or the current directory, with `--here`).
|
|
516
|
+
For a flow that has an active version, it is that version. For a new id it is a
|
|
517
|
+
stub whose only trigger is a `call` trigger, so a stub deployed by accident
|
|
518
|
+
never fires on its own. When the version stores no `test/**`, `init` also writes
|
|
519
|
+
a starter `test/flow.test.ts`: a conformance test against `@junoflow/testkit`'s
|
|
520
|
+
`flowHarness`, and one `it.todo` per trigger. A working copy has the same shape
|
|
521
|
+
as an integration's:
|
|
522
|
+
|
|
523
|
+
| File | What it is |
|
|
524
|
+
| --------------------- | ------------------------------------------------------------------------------------------ |
|
|
525
|
+
| `manifest.json` | the flow's manifest |
|
|
526
|
+
| `src/flow.ts` | the flow's entry |
|
|
527
|
+
| `src/**` | any other `.ts` or `.json` module the entry imports |
|
|
528
|
+
| `test/**` | your tests and their `.ts` or `.json` fixtures; stored with the flow, never compiled |
|
|
529
|
+
| `types/flow-env.d.ts` | the module the source imports its types from, generated from the manifest and the instance |
|
|
530
|
+
| `types/flow-api.d.ts` | the SDK's generic declarations, which `flow-env.d.ts` fills in; never import it directly |
|
|
531
|
+
| `tsconfig.json` | extends `@junoflow/cli/tsconfig.base.json` |
|
|
532
|
+
| `eslint.config.js` | `@junoflow/cli/eslint.flow.config.js`: the rules Juno's verify step lints flows by |
|
|
533
|
+
| `package.json` | the scripts, the dependencies, and `juno.target` / `flow_id` / `base_version` |
|
|
534
|
+
| `.gitignore` | `node_modules/` |
|
|
535
|
+
|
|
536
|
+
Every source file imports its types from the generated module:
|
|
537
|
+
|
|
538
|
+
```ts
|
|
539
|
+
// src/flow.ts
|
|
540
|
+
import type { Flow } from '../types/flow-env';
|
|
541
|
+
import { describe } from './describe';
|
|
542
|
+
|
|
543
|
+
export default {
|
|
544
|
+
triggers: {
|
|
545
|
+
onCall: async ({ status }) => status.ok(describe()),
|
|
546
|
+
},
|
|
547
|
+
} satisfies Flow;
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
`src/` and `test/` hold `.ts` and `.json` files only, at most 64 of them
|
|
551
|
+
together, each at most 512 KiB. Every one is sent on `check` and `deploy`. The
|
|
552
|
+
files under `src/` are type-checked and linted, and only `src/flow.ts` must
|
|
553
|
+
contain `satisfies Flow`. The files under `test/` are stored as they are; the
|
|
554
|
+
flow's `eslint.config.js` skips them too. `types/` is generated and is never
|
|
555
|
+
sent.
|
|
556
|
+
|
|
557
|
+
| Command | What it does |
|
|
558
|
+
| ----------------------------------------------- | -------------------------------------------------------------------------------------- |
|
|
559
|
+
| `juno flow init <id> [--here]` | write a working copy from the instance |
|
|
560
|
+
| `juno flow gen-types` | regenerate `types/flow-env.d.ts` and `types/flow-api.d.ts` from `manifest.json` |
|
|
561
|
+
| `juno flow check [--skip-local] [--json]` | local `tsc --noEmit` and `eslint .`, then verify against the instance, writing nothing |
|
|
562
|
+
| `juno flow deploy [--watch] [--force] [--json]` | verify, commit and activate; write back the manifest, the types and `base_version` |
|
|
563
|
+
| `juno flow pull [--force]` | bring the working copy up to the active version |
|
|
564
|
+
| `juno flow fixture --run <seq> [--force]` | write run `seq` as a test fixture, `test/fixtures/<flow-id>-<seq>.json` |
|
|
565
|
+
|
|
566
|
+
`pull` writes `manifest.json`, `src/**`, `test/**` and both `types/` files, and
|
|
567
|
+
deletes what the flow deleted (see
|
|
568
|
+
[What Juno stores](#what-juno-stores-and-what-pull-writes)).
|
|
569
|
+
|
|
570
|
+
`deploy` writes back what Juno stored: the canonical `manifest.json`, freshly
|
|
571
|
+
generated types, and the new version number. `--watch` redeploys whenever a file
|
|
572
|
+
under `src/` or `manifest.json` is saved (a deploy sends `test/` too, but saving
|
|
573
|
+
a test does not start one), skips a save that changed nothing, and keeps
|
|
574
|
+
watching after a failure. `--json` on `check` and `deploy` prints the instance's
|
|
575
|
+
JSON answer (or, when verification fails, its diagnostics) on stdout instead,
|
|
576
|
+
for CI; the integration commands take it too.
|
|
577
|
+
|
|
578
|
+
`fixture` turns a recorded run into a scenario a test loads with
|
|
579
|
+
`h.scenario(fixture)` from `@junoflow/testkit`: the event the run received, the
|
|
580
|
+
world it saw as far as Juno recorded it, and, under `expect`, the status, steps
|
|
581
|
+
and target calls it produced. It writes the file the instance builds, prints
|
|
582
|
+
`wrote test/fixtures/porch-light-42.json`, and lists on stderr each thing the
|
|
583
|
+
run did not record, such as a state value reconstructed from history or a
|
|
584
|
+
credential to fill in. It refuses to overwrite an existing fixture without
|
|
585
|
+
`--force` (exit 1), warns when the run ran another version than
|
|
586
|
+
`juno.base_version`, and exits 1 with the instance's reason for a run it cannot
|
|
587
|
+
replay: one still going, one that slept or waited for an event, or one that was
|
|
588
|
+
cancelled.
|
|
589
|
+
|
|
590
|
+
### The flow id and `base_version`
|
|
591
|
+
|
|
592
|
+
`package.json`'s `juno.flow_id` binds the working copy to one flow. The
|
|
593
|
+
manifest's `id` is the flow's identity, so a manifest whose `id` differs from
|
|
594
|
+
`flow_id` is refused, naming both. To change what a flow is called, edit `name`;
|
|
595
|
+
renaming a flow is done in the UI.
|
|
596
|
+
|
|
597
|
+
`juno.base_version` is the version the working copy came from. `deploy` sends
|
|
598
|
+
it, and when the flow's active version is anything else (someone edited the flow
|
|
599
|
+
in the UI, asked the assistant to change it, or rolled it back), Juno refuses
|
|
600
|
+
the deploy and the CLI exits **4**, naming both versions. Run `npm run pull` to
|
|
601
|
+
start from the active version, or `juno flow deploy --force` to replace it.
|
|
602
|
+
`check` makes the same comparison, so a stale working copy shows up before you
|
|
603
|
+
deploy. A new flow's working copy has no `base_version`. `juno flow pull` on a
|
|
604
|
+
flow with no active version exits 1: there is nothing to pull.
|
|
605
|
+
|
|
606
|
+
### Dependencies
|
|
607
|
+
|
|
608
|
+
`manifest.dependencies` is the list Juno installs when it verifies the flow.
|
|
609
|
+
`init` copies it into `package.json`'s `dependencies` so `npm install` gives
|
|
610
|
+
`tsc` the same modules. Add a package to the manifest, then to `package.json`
|
|
611
|
+
(or deploy and `npm run pull`). `juno flow check` warns when the two lists
|
|
612
|
+
differ, naming the packages.
|
|
613
|
+
|
|
614
|
+
### Why there is no `flow verify-types`
|
|
615
|
+
|
|
616
|
+
`types/flow-env.d.ts` describes the instance as well as the manifest: the states
|
|
617
|
+
the flow binds, the integrations and controls it calls. It goes stale whenever
|
|
618
|
+
the instance changes, and Juno regenerates it on every check and deploy, so
|
|
619
|
+
stale local types never ship. `npm run types` refreshes your copy when the
|
|
620
|
+
editor disagrees with the instance.
|
|
621
|
+
|
|
622
|
+
## Developing integrations
|
|
623
|
+
|
|
11
624
|
An integration is a git repository holding `manifest.json` and TypeScript. Juno
|
|
12
625
|
fetches it at a tag, compiles it, and runs it in a sandbox. There is no
|
|
13
626
|
packaging format and nothing is published to npm.
|
|
14
627
|
|
|
15
|
-
|
|
628
|
+
```bash
|
|
629
|
+
npm install --global @junoflow/cli
|
|
630
|
+
juno integration init my-integration --url http://juno.local:8080
|
|
631
|
+
```
|
|
16
632
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
|
20
|
-
|
|
|
21
|
-
| `juno
|
|
22
|
-
| `juno
|
|
23
|
-
| `juno
|
|
24
|
-
| `juno
|
|
633
|
+
### Commands
|
|
634
|
+
|
|
635
|
+
| Command | What it does |
|
|
636
|
+
| ------------------------------------------------------------------ | ------------------------------------------------------------------------------ |
|
|
637
|
+
| `juno integration init <id> [--name <name>] [--here]` | write the install's project from a Juno, or a starter for a new id |
|
|
638
|
+
| `juno integration gen-types` | POST `manifest.json`, write `types/` |
|
|
639
|
+
| `juno integration verify-types` | check `types/` still matches `manifest.json`; talks to nothing |
|
|
640
|
+
| `juno integration check [--skip-local] [--id <id>] [--json]` | local `verify-types` + `tsc --noEmit` + `eslint`, then validate against a Juno |
|
|
641
|
+
| `juno integration deploy [--watch] [--force] [--id <id>] [--json]` | upload, verify, activate, and write back the returned types and `base_version` |
|
|
642
|
+
| `juno integration pull [--force] [--id <id>]` | write back the manifest, `src/`, `test/`, `types/` and `base_version` |
|
|
643
|
+
|
|
644
|
+
They exit as the [exit-code table](#exit-codes) says: 1 for a failed check or
|
|
645
|
+
deploy, 2 for a usage error, 3 when the instance is unreachable, 4 for a stale
|
|
646
|
+
working copy (see [`base_version`](#base_version)).
|
|
25
647
|
|
|
26
648
|
### Options
|
|
27
649
|
|
|
28
|
-
| Flag | Meaning
|
|
29
|
-
| --------------- |
|
|
30
|
-
| `--url <url>` | the instance to talk to
|
|
31
|
-
| `--
|
|
32
|
-
| `--
|
|
33
|
-
| `--
|
|
34
|
-
| `--
|
|
35
|
-
| `--
|
|
650
|
+
| Flag | Meaning |
|
|
651
|
+
| --------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
652
|
+
| `--url <url>` | the instance to talk to |
|
|
653
|
+
| `--id <id>` | address an install under a different id than the manifest declares (`check`, `deploy`, `pull`) |
|
|
654
|
+
| `--name <name>` | display name for a new integration (`init`) |
|
|
655
|
+
| `--watch` | redeploy on every save (`deploy`) |
|
|
656
|
+
| `--force` | overwrite a dirty working tree (`pull`); replace the active version whatever the base (`deploy`) |
|
|
657
|
+
| `--here` | scaffold into the current directory (`init`) |
|
|
658
|
+
| `--skip-local` | skip the local verify-types/tsc/eslint pass (`check`) |
|
|
659
|
+
| `--json` | print the instance's JSON answer (or the failed verify's diagnostics) on stdout, for CI (`check`, `deploy`) |
|
|
36
660
|
|
|
37
|
-
|
|
661
|
+
### `base_version`
|
|
38
662
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
663
|
+
`package.json`'s `juno.base_version` is the version the working copy was pulled
|
|
664
|
+
from or last deployed. `init` of an installed id, the **Download project** zip,
|
|
665
|
+
`pull` and every successful `deploy` write it. `deploy` and `check` send it, and
|
|
666
|
+
when the install's active version is anything else (someone deployed another
|
|
667
|
+
copy, asked the assistant to change the integration, or rolled it back), Juno
|
|
668
|
+
refuses and the CLI exits **4**:
|
|
42
669
|
|
|
43
|
-
|
|
44
|
-
|
|
670
|
+
```text
|
|
671
|
+
integration "pushover": active version is v4, but this working copy was pulled from v3 — pull it again, or deploy with --force to replace it
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
Run `npm run pull` to start from the active version, or
|
|
675
|
+
`juno integration deploy --force` to replace it. A working copy without a
|
|
676
|
+
`base_version` sends none; its first deploy or pull records one.
|
|
677
|
+
|
|
678
|
+
The base belongs to the install `manifest.json` names. `--id <other>` deploys
|
|
679
|
+
the same code to a different install, whose versions this copy has never seen,
|
|
680
|
+
so it sends no base, writes none back, and warns
|
|
681
|
+
`no stale-base check: deploying to <other>, not <manifest id>`. It needs no
|
|
682
|
+
`--force`. `pull --id <other>` rewrites `manifest.json` to that install, so
|
|
683
|
+
later deploys are checked against it.
|
|
45
684
|
|
|
46
|
-
|
|
685
|
+
### The loop
|
|
47
686
|
|
|
48
687
|
```bash
|
|
49
|
-
npm run watch # juno deploy --watch
|
|
688
|
+
npm run watch # juno integration deploy --watch
|
|
50
689
|
```
|
|
51
690
|
|
|
52
691
|
Add an action to `manifest.json`. The types regenerate, and your editor
|
|
@@ -54,7 +693,7 @@ immediately red-squiggles `src/integration.ts` because `IntegrationActions` now
|
|
|
54
693
|
requires a method you have not written — with the parameter and return types
|
|
55
694
|
your JSON Schema implies. The manifest drives the types drive the compiler.
|
|
56
695
|
|
|
57
|
-
|
|
696
|
+
### Why the types are committed
|
|
58
697
|
|
|
59
698
|
`types/` is generated and checked in. CI needs no Juno, a fresh clone compiles
|
|
60
699
|
offline, and the diff in a pull request shows that adding an action changed the
|
|
@@ -69,40 +708,42 @@ contract you no longer declare. `tsc` compiles happily against stale types,
|
|
|
69
708
|
with your _module_, never with `types/` — so the mismatch surfaces at deploy, or
|
|
70
709
|
later, as a flow author calling an action whose shape changed.
|
|
71
710
|
|
|
72
|
-
`juno verify-types` closes it. `juno types` records
|
|
73
|
-
`manifest.json` it generated from, as a comment in
|
|
711
|
+
`juno integration verify-types` closes it. `juno integration gen-types` records
|
|
712
|
+
a fingerprint of the `manifest.json` it generated from, as a comment in
|
|
713
|
+
`types/integration-env.d.ts`:
|
|
74
714
|
|
|
75
715
|
```ts
|
|
76
716
|
// integration-env.d.ts for 'pushover'
|
|
77
|
-
// DO NOT EDIT — generated by 'juno types' from 'manifest.json'.
|
|
717
|
+
// DO NOT EDIT — generated by 'juno integration gen-types' from 'manifest.json'.
|
|
78
718
|
// juno:manifest-sha256 5c5ca8eabe12cf44
|
|
79
719
|
```
|
|
80
720
|
|
|
81
721
|
`verify-types` recomputes it and compares. It runs first in `npm test` and
|
|
82
|
-
inside `juno check`, and it contacts nothing — which is the point:
|
|
83
|
-
is caught in a contributor's CI, and that CI deliberately has no
|
|
84
|
-
ask. Reformatting `manifest.json` also invalidates the stamp; the
|
|
85
|
-
same `npm run types` either way.
|
|
722
|
+
inside `juno integration check`, and it contacts nothing — which is the point:
|
|
723
|
+
the mistake is caught in a contributor's CI, and that CI deliberately has no
|
|
724
|
+
instance to ask. Reformatting `manifest.json` also invalidates the stamp; the
|
|
725
|
+
fix is the same `npm run types` either way.
|
|
86
726
|
|
|
87
|
-
|
|
727
|
+
### What runs where
|
|
88
728
|
|
|
89
|
-
| | offline | needs an instance
|
|
90
|
-
| --------------------------------------------------------------- | ------- |
|
|
91
|
-
| `tsc --noEmit`, `eslint`, `vitest` | ✅ |
|
|
92
|
-
| `juno verify-types` (is `types/` current?)
|
|
93
|
-
| structural manifest check via `"$schema"` in your editor | ✅ |
|
|
94
|
-
| reserved names, cross-surface collisions, JSON Schema compiling | | `juno check`
|
|
95
|
-
| `juno init`, `
|
|
729
|
+
| | offline | needs an instance |
|
|
730
|
+
| --------------------------------------------------------------- | ------- | ------------------------ |
|
|
731
|
+
| `tsc --noEmit`, `eslint`, `vitest` | ✅ | |
|
|
732
|
+
| `juno integration verify-types` (is `types/` current?) | ✅ | |
|
|
733
|
+
| structural manifest check via `"$schema"` in your editor | ✅ | |
|
|
734
|
+
| reserved names, cross-surface collisions, JSON Schema compiling | | `juno integration check` |
|
|
735
|
+
| `juno integration init`, `gen-types`, `deploy` | | ✅ |
|
|
96
736
|
|
|
97
737
|
Everything semantic lives in the Go server. This package knows some HTTP
|
|
98
738
|
endpoints and a config preset — that is deliberate, so there is one answer to
|
|
99
739
|
"is this manifest valid" rather than two that can disagree.
|
|
100
740
|
|
|
101
|
-
|
|
741
|
+
### `init` vs. downloading a project
|
|
102
742
|
|
|
103
|
-
Both produce the same project, from the same generator on the instance
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
template
|
|
107
|
-
from what the server ejects. To refresh an
|
|
108
|
-
|
|
743
|
+
Both produce the same project, from the same generator on the instance. `init`
|
|
744
|
+
of an id that is already installed writes that install's project, the one the
|
|
745
|
+
**Develop locally → Download project** button gives you; `init` of a new id
|
|
746
|
+
writes a starter. Neither is a local template: the CLI writes the files it is
|
|
747
|
+
handed, so a scaffold cannot drift from what the server ejects. To refresh an
|
|
748
|
+
existing checkout use `juno integration pull`, which touches only the files Juno
|
|
749
|
+
stores; a second download would clobber your working tree.
|