@junoflow/cli 0.6.0 → 0.10.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 (43) hide show
  1. package/README.md +214 -80
  2. package/app-client.js +306 -0
  3. package/eslint.app.config.js +34 -0
  4. package/eslint.flow.config.js +4 -2
  5. package/package.json +9 -4
  6. package/src/api.js +213 -51
  7. package/src/app-dev/build.js +134 -0
  8. package/src/app-dev/harness.js +108 -0
  9. package/src/app-dev/panel.html +336 -0
  10. package/src/app-dev/panel.js +290 -0
  11. package/src/app-dev/server.js +347 -0
  12. package/src/app-dev/stream.js +224 -0
  13. package/src/app-dev/watch.js +47 -0
  14. package/src/app-project.js +133 -0
  15. package/src/authoring-skills.js +2 -1
  16. package/src/commands/app-check.js +75 -0
  17. package/src/commands/app-deploy.js +113 -0
  18. package/src/commands/app-dev.js +141 -0
  19. package/src/commands/app-gen-types.js +37 -0
  20. package/src/commands/app-init.js +46 -0
  21. package/src/commands/app-pull.js +106 -0
  22. package/src/commands/check.js +33 -24
  23. package/src/commands/config.js +26 -7
  24. package/src/commands/deploy.js +59 -13
  25. package/src/commands/flow-check.js +4 -22
  26. package/src/commands/flow-deploy.js +11 -8
  27. package/src/commands/flow-fixture.js +67 -0
  28. package/src/commands/flow-gen-types.js +12 -5
  29. package/src/commands/flow-pull.js +69 -40
  30. package/src/commands/gen-types.js +2 -9
  31. package/src/commands/init.js +2 -9
  32. package/src/commands/pull.js +98 -111
  33. package/src/config.js +20 -5
  34. package/src/errors.js +3 -3
  35. package/src/exit.js +2 -2
  36. package/src/flow-project.js +47 -97
  37. package/src/index.js +99 -23
  38. package/src/project.js +76 -8
  39. package/src/token.js +30 -0
  40. package/src/watch.js +48 -11
  41. package/src/working-copy.js +262 -0
  42. package/tsconfig.page.json +15 -0
  43. package/verbs.json +52 -3
package/README.md CHANGED
@@ -66,7 +66,8 @@ Every command has one effect:
66
66
  starts a run, executes code), with nothing to roll back to.
67
67
 
68
68
  `config set` and `agent-skill install` are **local**: they write a file on your
69
- machine and change nothing on the instance.
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.
70
71
 
71
72
  No command of a lower effect is a string prefix of a command of a higher one.
72
73
  Claude Code matches a Bash permission rule by prefix, so an allowlist can list
@@ -204,10 +205,11 @@ under `files` by its basename and makes it the `entry`.
204
205
 
205
206
  **`session`** and **`config`**
206
207
 
207
- | Command | Tool | Positional | Effect |
208
- | --------------------------- | ---- | ----------- | ------ |
209
- | `juno session approve <id>` | none | session id | act |
210
- | `juno config set url <url>` | none | `url <url>` | local |
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
+ | `juno config set token <key>` | none | `token <key>` | local |
211
213
 
212
214
  There is no command that calls a tool by name. One permission rule for such a
213
215
  command would allow every act, so each tool has its own command instead, and a
@@ -256,15 +258,15 @@ its own. A missing `jq` or an expression `jq` rejects exits 2.
256
258
 
257
259
  One table for the whole CLI, the working-copy commands included:
258
260
 
259
- | Code | When |
260
- | ---- | ----------------------------------------------------------------------------------------------------- |
261
- | 0 | Success |
262
- | 1 | A tool answer with a top-level string `error`, a failed check or deploy, or any other failure |
263
- | 2 | Usage: an unknown command or flag, bad `--input`, input the instance rejects, a bad `--jq` |
264
- | 3 | The instance is unreachable |
265
- | 4 | `juno flow deploy` / `flow check`: the flow changed on the instance since the working copy was pulled |
266
- | 5 | `juno ask` / `session approve`: the turn is still running |
267
- | 6 | `juno ask` / `session approve`: the assistant is waiting for an answer or an approval |
261
+ | Code | When |
262
+ | ---- | ---------------------------------------------------------------------------------------------------- |
263
+ | 0 | Success |
264
+ | 1 | A tool answer with a top-level string `error`, a failed check or deploy, or any other failure |
265
+ | 2 | Usage: an unknown command or flag, bad `--input`, input the instance rejects, a bad `--jq` |
266
+ | 3 | The instance is unreachable |
267
+ | 4 | A working-copy `deploy` or `check`, any kind: the instance changed since the working copy was pulled |
268
+ | 5 | `juno ask` / `session approve`: the turn is still running |
269
+ | 6 | `juno ask` / `session approve`: the assistant is waiting for an answer or an approval |
268
270
 
269
271
  An instance that does not serve a tool the CLI names is older than the CLI; that
270
272
  is also exit 2, and the message says so.
@@ -394,10 +396,26 @@ with no setup. `juno config set url <url>` writes the fourth, which is what
394
396
  makes `juno` useful outside a project. A bare host such as `juno.local:8080`
395
397
  means `http://juno.local:8080`.
396
398
 
399
+ ## Authenticating
400
+
401
+ A Juno that identifies its callers answers a request with no credential
402
+ with 401. Create an API key under **Settings → API keys** and give it to the
403
+ CLI; it is sent as `Authorization: Bearer <key>` on every request. In order:
404
+
405
+ 1. `--token <key>`, before or after the command words;
406
+ 2. `$JUNO_TOKEN`;
407
+ 3. `token` in the user config file, which `juno config set token <key>` writes
408
+ (mode 0600; the key must start `juno_`).
409
+
410
+ There is no `package.json` source: a working copy's `package.json` is committed,
411
+ and a key never goes in a file that is. The CLI never prints the key, in an
412
+ error message included.
413
+
397
414
  Every request carries an `X-Juno-Client` header: `cli`, or `cli/claude-code`
398
- when `CLAUDECODE=1` is set, as Claude Code sets it in the shells it starts. Juno
399
- records a write made through a tool as `operator:<client>`. The header names who
400
- made a change; it is not a security control.
415
+ when `CLAUDECODE=1` is set, as Claude Code sets it in the shells it starts. The
416
+ header labels the calling client; Juno records a write made through a tool under
417
+ the principal the request authenticated as, and the header is not a security
418
+ control.
401
419
 
402
420
  ## Working copies
403
421
 
@@ -407,30 +425,42 @@ truth and a working copy is where you edit the next version. Its `package.json`
407
425
  names the instance (`juno.target`) and the flow or install it belongs to, so a
408
426
  fresh clone deploys with no setup.
409
427
 
410
- Both kinds have the same loop and the same npm scripts:
428
+ Both kinds have the same loop and the same npm scripts, and so does an app
429
+ (`juno app init`), whose `check` stores a draft to preview at its `~draft/` URL:
411
430
 
412
431
  ```bash
413
432
  npm install
414
- npm test # local tsc and eslint
433
+ npm test # local tsc, eslint and vitest
415
434
  npm run check # ... then verify against the instance, which writes nothing
416
435
  npm run deploy # verify, commit and activate the next version
417
- npm run pull # overwrite the working copy with the active version
436
+ npm run pull # bring the working copy up to the active version
418
437
  ```
419
438
 
420
- | Script | Flow working copy | Integration working copy |
421
- | -------------- | ----------------------------------- | ------------------------------------------------------------------------- |
422
- | `typecheck` | `tsc --noEmit` | `tsc --noEmit` |
423
- | `lint` | `eslint .` | `eslint .` |
424
- | `verify-types` | (absent) | `juno integration verify-types` |
425
- | `test` | `npm run typecheck && npm run lint` | `npm run verify-types && npm run typecheck && npm run lint && vitest run` |
426
- | `types` | `juno flow gen-types` | `juno integration gen-types` |
427
- | `check` | `juno flow check` | `juno integration check` |
428
- | `deploy` | `juno flow deploy` | `juno integration deploy` |
429
- | `watch` | `juno flow deploy --watch` | `juno integration deploy --watch` |
430
- | `pull` | `juno flow pull` | `juno integration pull` |
431
-
432
- The instance writes these lines, and `pull` rewrites them, so a working copy
433
- picks up a new command without your editing `package.json`.
439
+ | Script | Flow working copy | Integration working copy | App working copy |
440
+ | -------------- | ------------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------- |
441
+ | `typecheck` | `tsc --noEmit` | `tsc --noEmit` | `tsc --noEmit && tsc --noEmit -p page` |
442
+ | `lint` | `eslint .` | `eslint .` | `eslint .` |
443
+ | `verify-types` | (absent) | `juno integration verify-types` | (absent) |
444
+ | `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` |
445
+ | `types` | `juno flow gen-types` | `juno integration gen-types` | `juno app gen-types` |
446
+ | `check` | `juno flow check` | `juno integration check` | `juno app check` |
447
+ | `deploy` | `juno flow deploy` | `juno integration deploy` | `juno app deploy` |
448
+ | `watch` | `juno flow deploy --watch` | `juno integration deploy --watch` | `juno app deploy --watch` |
449
+ | `pull` | `juno flow pull` | `juno integration pull` | `juno app pull` |
450
+ | `dev` | (absent) | (absent) | `juno app dev` |
451
+
452
+ `dev` exists only in an app working copy, because flows and integrations have no
453
+ page to serve. `npm run dev` serves the app on `http://localhost:8090/`
454
+ (`--port <n>` picks another) with no instance, no credentials and no network:
455
+ the page is built from `page/` on each request, the handlers run in Node through
456
+ the testkit's `appHarness`, and live values come from a scenario file
457
+ (`--scenario test/fixtures/<name>.json`). A panel beside the page sets values,
458
+ presses controls and switches scenarios, and saving a file rebuilds and reloads.
459
+ It uses the `esbuild` and `@junoflow/testkit` the working copy installs.
460
+
461
+ `init` writes these lines as the instance renders them. After that they are
462
+ yours: `pull` never changes `scripts`. To see the current lines, run `init` into
463
+ a fresh directory and compare.
434
464
 
435
465
  The working-copy commands sit in the `flow` and `integration` groups beside the
436
466
  commands that act on the instance, and `juno flow --help` and
@@ -439,27 +469,61 @@ commands that act on the instance, and `juno flow --help` and
439
469
  argument: `juno flow deploy porch-light` exits 2 and tells you to run
440
470
  `juno flow init porch-light` and deploy from inside it.
441
471
 
442
- | Effect | Commands |
443
- | -------- | -------------------------------------------------------------- |
444
- | `read` | `init`, `gen-types`, `verify-types`, `check`, `pull` |
445
- | `change` | `deploy`: it adds a version to the flow's or install's history |
472
+ | Effect | Commands |
473
+ | -------- | -------------------------------------------------------------------- |
474
+ | `read` | `init`, `gen-types`, `verify-types`, `check`, `pull`, `flow fixture` |
475
+ | `change` | `deploy`: it adds a version to the flow's or install's history |
476
+ | `local` | `app dev`: a server on localhost that touches no instance |
446
477
 
447
- `init` and `pull` write local files, but change nothing in Juno, so their effect
448
- is `read`.
478
+ `init`, `pull` and `flow fixture` write local files, but change nothing in Juno,
479
+ so their effect is `read`.
449
480
 
450
481
  After writing a working copy, `init` prints the skills for writing that kind,
451
482
  one `<name> — <description>` line each, the same index Juno's in-app flow or
452
483
  integration author is given; load one with `juno skill <name>`.
453
- `juno flow --help` and `juno integration --help` list them too. `pull` refuses
454
- to overwrite uncommitted changes in a git working tree unless you pass
455
- `--force`.
484
+ `juno flow --help` and `juno integration --help` list them too.
485
+
486
+ ### What Juno stores, and what `pull` writes
487
+
488
+ A working copy holds three kinds of file:
489
+
490
+ | Kind | Files | Stored by Juno |
491
+ | --------- | ------------------------------------------------------------------------------------------------- | -------------------------------------- |
492
+ | artifact | `manifest.json`, `src/**`, `test/**` | yes, with every version, byte for byte |
493
+ | generated | `types/**` | no; generated from the manifest |
494
+ | project | everything else: `tsconfig.json`, `eslint.config.js`, `README.md`, `.gitignore`, `package.json` … | no |
495
+
496
+ `check` and `deploy` send the artifact files. Your tests travel with the
497
+ version, so a copy pulled elsewhere gets them back. Juno stores `test/**` and
498
+ nothing more: it never type-checks, lints or runs a test. `npm test` runs them
499
+ in your checkout.
500
+
501
+ `pull` makes the artifact files the active version's, exactly as Juno stores
502
+ them, and regenerates `types/`. It deletes a local file only when Juno deleted
503
+ it: the version recorded in `juno.base_version` had the file and the pulled
504
+ version does not. A file neither version has is one you have not deployed yet,
505
+ and `pull` leaves it alone. A working copy with no `base_version` has nothing to
506
+ compare against, so `pull` deletes nothing. It prints what it wrote and, on a
507
+ second line, what it removed:
508
+
509
+ ```text
510
+ pulled porch-light v7 from http://juno.local:8080 (manifest.json, package.json, src/flow.ts)
511
+ removed src/old.ts
512
+ ```
513
+
514
+ `pull` never writes or deletes a project file. In `package.json` it sets two
515
+ keys, `juno.base_version` and `dependencies` (from the manifest), and leaves the
516
+ rest as you wrote it. An integration whose manifest declares no dependencies
517
+ keeps them in `package.json` alone, so there `pull` leaves `dependencies` as it
518
+ is. It refuses to overwrite uncommitted changes in a git working tree unless you
519
+ pass `--force`.
456
520
 
457
521
  ## Flows as files
458
522
 
459
523
  ```bash
460
524
  juno flow init porch-light # the flow's active version, or a stub for a new id
461
525
  cd porch-light && npm install
462
- $EDITOR flow.ts manifest.json
526
+ $EDITOR src/flow.ts manifest.json
463
527
  npm test
464
528
  npm run check
465
529
  npm run deploy
@@ -468,33 +532,77 @@ npm run deploy
468
532
  `juno flow init <id>` writes `./<id>` (or the current directory, with `--here`).
469
533
  For a flow that has an active version, it is that version. For a new id it is a
470
534
  stub whose only trigger is a `call` trigger, so a stub deployed by accident
471
- never fires on its own. A working copy is flat:
472
-
473
- | File | What it is |
474
- | ------------------ | ---------------------------------------------------------------------------------- |
475
- | `manifest.json` | the flow's manifest |
476
- | `flow.ts` | the flow's source |
477
- | `flow-env.d.ts` | types generated from the manifest and the instance's states, integrations, etc. |
478
- | `flow-api.d.ts` | the SDK's declarations; it and `flow-env.d.ts` only typecheck together |
479
- | `tsconfig.json` | extends `@junoflow/cli/tsconfig.base.json` |
480
- | `eslint.config.js` | `@junoflow/cli/eslint.flow.config.js`: the rules Juno's verify step lints flows by |
481
- | `package.json` | the scripts, the dependencies, and `juno.target` / `flow_id` / `base_version` |
482
- | `.gitignore` | `node_modules/` |
535
+ never fires on its own. When the version stores no `test/**`, `init` also writes
536
+ a starter `test/flow.test.ts`: a conformance test against `@junoflow/testkit`'s
537
+ `flowHarness`, and one `it.todo` per trigger. A working copy has the same shape
538
+ as an integration's:
539
+
540
+ | File | What it is |
541
+ | --------------------- | ------------------------------------------------------------------------------------------ |
542
+ | `manifest.json` | the flow's manifest |
543
+ | `src/flow.ts` | the flow's entry |
544
+ | `src/**` | any other `.ts` or `.json` module the entry imports |
545
+ | `test/**` | your tests and their `.ts` or `.json` fixtures; stored with the flow, never compiled |
546
+ | `types/flow-env.d.ts` | the module the source imports its types from, generated from the manifest and the instance |
547
+ | `types/flow-api.d.ts` | the SDK's generic declarations, which `flow-env.d.ts` fills in; never import it directly |
548
+ | `tsconfig.json` | extends `@junoflow/cli/tsconfig.base.json` |
549
+ | `eslint.config.js` | `@junoflow/cli/eslint.flow.config.js`: the rules Juno's verify step lints flows by |
550
+ | `package.json` | the scripts, the dependencies, and `juno.target` / `flow_id` / `base_version` |
551
+ | `.gitignore` | `node_modules/` |
552
+
553
+ Every source file imports its types from the generated module:
554
+
555
+ ```ts
556
+ // src/flow.ts
557
+ import type { Flow } from '../types/flow-env';
558
+ import { describe } from './describe';
559
+
560
+ export default {
561
+ triggers: {
562
+ onCall: async ({ status }) => status.ok(describe()),
563
+ },
564
+ } satisfies Flow;
565
+ ```
566
+
567
+ `src/` and `test/` hold `.ts` and `.json` files only, at most 64 of them
568
+ together, each at most 512 KiB. Every one is sent on `check` and `deploy`. The
569
+ files under `src/` are type-checked and linted, and only `src/flow.ts` must
570
+ contain `satisfies Flow`. The files under `test/` are stored as they are; the
571
+ flow's `eslint.config.js` skips them too. `types/` is generated and is never
572
+ sent.
483
573
 
484
574
  | Command | What it does |
485
575
  | ----------------------------------------------- | -------------------------------------------------------------------------------------- |
486
576
  | `juno flow init <id> [--here]` | write a working copy from the instance |
487
- | `juno flow gen-types` | regenerate `flow-env.d.ts` and `flow-api.d.ts` from `manifest.json` |
577
+ | `juno flow gen-types` | regenerate `types/flow-env.d.ts` and `types/flow-api.d.ts` from `manifest.json` |
488
578
  | `juno flow check [--skip-local] [--json]` | local `tsc --noEmit` and `eslint .`, then verify against the instance, writing nothing |
489
579
  | `juno flow deploy [--watch] [--force] [--json]` | verify, commit and activate; write back the manifest, the types and `base_version` |
490
- | `juno flow pull [--force]` | overwrite the working copy with the active version |
580
+ | `juno flow pull [--force]` | bring the working copy up to the active version |
581
+ | `juno flow fixture --run <seq> [--force]` | write run `seq` as a test fixture, `test/fixtures/<flow-id>-<seq>.json` |
582
+
583
+ `pull` writes `manifest.json`, `src/**`, `test/**` and both `types/` files, and
584
+ deletes what the flow deleted (see
585
+ [What Juno stores](#what-juno-stores-and-what-pull-writes)).
491
586
 
492
587
  `deploy` writes back what Juno stored: the canonical `manifest.json`, freshly
493
- generated types, and the new version number. `--watch` redeploys whenever
494
- `flow.ts` or `manifest.json` is saved, skips a save that changed nothing, and
495
- keeps watching after a failure. `--json` on `check` and `deploy` prints the
496
- instance's JSON answer (or, when verification fails, its diagnostics) on stdout
497
- instead, for CI; the integration commands take it too.
588
+ generated types, and the new version number. `--watch` redeploys whenever a file
589
+ under `src/` or `manifest.json` is saved (a deploy sends `test/` too, but saving
590
+ a test does not start one), skips a save that changed nothing, and keeps
591
+ watching after a failure. `--json` on `check` and `deploy` prints the instance's
592
+ JSON answer (or, when verification fails, its diagnostics) on stdout instead,
593
+ for CI; the integration commands take it too.
594
+
595
+ `fixture` turns a recorded run into a scenario a test loads with
596
+ `h.scenario(fixture)` from `@junoflow/testkit`: the event the run received, the
597
+ world it saw as far as Juno recorded it, and, under `expect`, the status, steps
598
+ and target calls it produced. It writes the file the instance builds, prints
599
+ `wrote test/fixtures/porch-light-42.json`, and lists on stderr each thing the
600
+ run did not record, such as a state value reconstructed from history or a
601
+ credential to fill in. It refuses to overwrite an existing fixture without
602
+ `--force` (exit 1), warns when the run ran another version than
603
+ `juno.base_version`, and exits 1 with the instance's reason for a run it cannot
604
+ replay: one still going, one that slept or waited for an event, or one that was
605
+ cancelled.
498
606
 
499
607
  ### The flow id and `base_version`
500
608
 
@@ -522,11 +630,11 @@ differ, naming the packages.
522
630
 
523
631
  ### Why there is no `flow verify-types`
524
632
 
525
- `flow-env.d.ts` describes the instance as well as the manifest: the states the
526
- flow binds, the integrations and controls it calls. It goes stale whenever the
527
- instance changes, and Juno regenerates it on every check and deploy, so stale
528
- local types never ship. `npm run types` refreshes your copy when the editor
529
- disagrees with the instance.
633
+ `types/flow-env.d.ts` describes the instance as well as the manifest: the states
634
+ the flow binds, the integrations and controls it calls. It goes stale whenever
635
+ the instance changes, and Juno regenerates it on every check and deploy, so
636
+ stale local types never ship. `npm run types` refreshes your copy when the
637
+ editor disagrees with the instance.
530
638
 
531
639
  ## Developing integrations
532
640
 
@@ -541,31 +649,57 @@ juno integration init my-integration --url http://juno.local:8080
541
649
 
542
650
  ### Commands
543
651
 
544
- | Command | What it does |
545
- | ------------------------------------------------------------ | ------------------------------------------------------------------------------ |
546
- | `juno integration init <id> [--name <name>] [--here]` | write the install's project from a Juno, or a starter for a new id |
547
- | `juno integration gen-types` | POST `manifest.json`, write `types/` |
548
- | `juno integration verify-types` | check `types/` still matches `manifest.json`; talks to nothing |
549
- | `juno integration check [--skip-local] [--id <id>] [--json]` | local `verify-types` + `tsc --noEmit` + `eslint`, then validate against a Juno |
550
- | `juno integration deploy [--watch] [--id <id>] [--json]` | upload, verify, activate, and write back the returned types |
551
- | `juno integration pull [--force] [--id <id>]` | write back the manifest, sources and `package.json` scripts the instance holds |
652
+ | Command | What it does |
653
+ | ------------------------------------------------------------------ | ------------------------------------------------------------------------------ |
654
+ | `juno integration init <id> [--name <name>] [--here]` | write the install's project from a Juno, or a starter for a new id |
655
+ | `juno integration gen-types` | POST `manifest.json`, write `types/` |
656
+ | `juno integration verify-types` | check `types/` still matches `manifest.json`; talks to nothing |
657
+ | `juno integration check [--skip-local] [--id <id>] [--json]` | local `verify-types` + `tsc --noEmit` + `eslint`, then validate against a Juno |
658
+ | `juno integration deploy [--watch] [--force] [--id <id>] [--json]` | upload, verify, activate, and write back the returned types and `base_version` |
659
+ | `juno integration pull [--force] [--id <id>]` | write back the manifest, `src/`, `test/`, `types/` and `base_version` |
552
660
 
553
661
  They exit as the [exit-code table](#exit-codes) says: 1 for a failed check or
554
- deploy, 2 for a usage error, 3 when the instance is unreachable.
662
+ deploy, 2 for a usage error, 3 when the instance is unreachable, 4 for a stale
663
+ working copy (see [`base_version`](#base_version)).
555
664
 
556
665
  ### Options
557
666
 
558
667
  | Flag | Meaning |
559
668
  | --------------- | ----------------------------------------------------------------------------------------------------------- |
560
669
  | `--url <url>` | the instance to talk to |
670
+ | `--token <key>` | the API key to send (see [Authenticating](#authenticating)) |
561
671
  | `--id <id>` | address an install under a different id than the manifest declares (`check`, `deploy`, `pull`) |
562
672
  | `--name <name>` | display name for a new integration (`init`) |
563
673
  | `--watch` | redeploy on every save (`deploy`) |
564
- | `--force` | overwrite a dirty working tree (`pull`) |
674
+ | `--force` | overwrite a dirty working tree (`pull`); replace the active version whatever the base (`deploy`) |
565
675
  | `--here` | scaffold into the current directory (`init`) |
566
676
  | `--skip-local` | skip the local verify-types/tsc/eslint pass (`check`) |
567
677
  | `--json` | print the instance's JSON answer (or the failed verify's diagnostics) on stdout, for CI (`check`, `deploy`) |
568
678
 
679
+ ### `base_version`
680
+
681
+ `package.json`'s `juno.base_version` is the version the working copy was pulled
682
+ from or last deployed. `init` of an installed id, the **Download project** zip,
683
+ `pull` and every successful `deploy` write it. `deploy` and `check` send it, and
684
+ when the install's active version is anything else (someone deployed another
685
+ copy, asked the assistant to change the integration, or rolled it back), Juno
686
+ refuses and the CLI exits **4**:
687
+
688
+ ```text
689
+ integration "pushover": active version is v4, but this working copy was pulled from v3 — pull it again, or deploy with --force to replace it
690
+ ```
691
+
692
+ Run `npm run pull` to start from the active version, or
693
+ `juno integration deploy --force` to replace it. A working copy without a
694
+ `base_version` sends none; its first deploy or pull records one.
695
+
696
+ The base belongs to the install `manifest.json` names. `--id <other>` deploys
697
+ the same code to a different install, whose versions this copy has never seen,
698
+ so it sends no base, writes none back, and warns
699
+ `no stale-base check: deploying to <other>, not <manifest id>`. It needs no
700
+ `--force`. `pull --id <other>` rewrites `manifest.json` to that install, so
701
+ later deploys are checked against it.
702
+
569
703
  ### The loop
570
704
 
571
705
  ```bash
@@ -629,5 +763,5 @@ of an id that is already installed writes that install's project, the one the
629
763
  **Develop locally → Download project** button gives you; `init` of a new id
630
764
  writes a starter. Neither is a local template: the CLI writes the files it is
631
765
  handed, so a scaffold cannot drift from what the server ejects. To refresh an
632
- existing checkout use `juno integration pull`, which also rewrites
633
- `package.json`'s `scripts`; a second download would clobber your working tree.
766
+ existing checkout use `juno integration pull`, which touches only the files Juno
767
+ stores; a second download would clobber your working tree.