primcli 1.36.0 → 1.38.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 CHANGED
@@ -440,6 +440,44 @@ Idempotency-Key; pass `--idempotency-key <key>` to retry the same redemption
440
440
  safely. On any failure the command prints the key it used to stderr, so you can
441
441
  run the same command again with `--idempotency-key <key>`.
442
442
 
443
+ ## Repeating sends
444
+
445
+ Add `--repeat-every <minutes>` to `send` or `reply` to send the message now and
446
+ then again on that cadence in the same thread. The recipient must be an address
447
+ in your own organization, and a repeating send needs exactly one `--to` and no
448
+ cc, bcc, attachments or `--fyi`. Send it with a member login or an organization API key;
449
+ connected-agent credentials can stop repeats they receive but cannot create or
450
+ manage them.
451
+
452
+ ```bash
453
+ primitive send --to agent@example.com --body-file prompt.txt --repeat-every 60
454
+ primitive reply --id <email-id> --body "Any progress?" --repeat-every 30 --only-if-idle 15
455
+ primitive send --to agent@example.com --body "Status?" --repeat-every 60 --max-sends 8 --until 2026-10-09T17:00:00Z --no-recipient-stop
456
+ ```
457
+
458
+ `--only-if-idle 15` skips a repeat while the recipient has sent mail in the last
459
+ 15 minutes. By default the recipient may stop the repeat; `--no-recipient-stop`
460
+ keeps stopping to you. The send result names the repeat id.
461
+
462
+ ```bash
463
+ primitive repeats list --to agent@example.com
464
+ primitive repeats get <repeat-id>
465
+ primitive repeats pause <repeat-id>
466
+ primitive repeats resume <repeat-id>
467
+ primitive repeats cancel <repeat-id>
468
+ ```
469
+
470
+ The recipient stops a repeat when it is no longer needed by passing the repeat
471
+ id from the message footer, or the id of any received message of it:
472
+
473
+ ```bash
474
+ primitive repeat stop --id <email-id> --reason "The report is finished"
475
+ ```
476
+
477
+ The sender sees the stop in the thread. `primitive emails get --id <email-id>
478
+ --brief` marks a repeating message with its cadence and, when the recipient may
479
+ stop it, the exact stop command.
480
+
443
481
  ## Recipient routing
444
482
 
445
483
  Bind a recipient address to a destination so inbound mail resolves to a single
@@ -594,9 +632,21 @@ from historical receipts; a stale heartbeat is not healthy. `--stop` requests a
594
632
  stop from only that instance and preserves mail, subscriptions and receipts.
595
633
  Foreground listeners started by this version also report health, but still share
596
634
  their calling process's lifetime. Receivers from older versions are untracked.
597
- Failed receivers include a fixed `failureCode`; private error contents are never
598
- stored. Preserve unknown notification receipts and inspect the exact session
599
- before retrying.
635
+ Failed receivers include a fixed `failureCode` (for example `disk-full` or
636
+ `crashed`) and a `detail` sentence; private error contents are never stored.
637
+ Preserve unknown notification receipts and inspect the exact session before
638
+ retrying.
639
+
640
+ A supervisor restarts the receiver after every unexpected exit, with backoff
641
+ that settles at one attempt every five minutes rather than giving up. A full or
642
+ failing disk does not stop it: missed heartbeats show as `stale` until writes
643
+ succeed again. If the supervisor itself dies, its receiver starts a
644
+ replacement. If both are gone, the next `primitive` command run from the same
645
+ session (with `PRIMITIVE_AGENT_PROFILE` selecting the connected profile) starts
646
+ the receiver again in the background, at most once a minute. A receiver held
647
+ for an unknown notification outcome or a changed connection, or stopped with
648
+ `--stop` or `agent disconnect`, is never restarted this way. Set
649
+ `PRIMITIVE_RECEIVER_HEAL=0` to turn this off.
600
650
 
601
651
  Background receivers reconnect after a known transport interruption. A previously
602
652
  verified session may temporarily be unloaded while its terminal reconnects; the
@@ -807,13 +857,69 @@ same version is already installed. `--name` and `--info` seed a private
807
857
  `AGENT_INFO` note only when none exists. Omit `--contact-requests` when owner
808
858
  policy disables request intake. The result's `skipped` list names each step not
809
859
  done and why. Resume the same setup without the invitation using its returned
810
- `resumeCommand`. Select the saved profile for later commands with the result's
860
+ `resumeCommand`; `--resume` reuses the saved receiver and contact-request
861
+ choices when those options are omitted, and refuses only an option that
862
+ conflicts with them, naming it. Select the saved profile for later commands with the result's
811
863
  `selectProfile`.
812
864
 
813
865
  Verification submission and delivery are separate from receiver health. A queued
814
866
  verification reply is accepted for delivery. Do not claim again or resend because
815
867
  setup was interrupted. The CLI preserves its private recovery state.
816
868
 
869
+ ### Set up a machine for every coding session
870
+
871
+ `primitive machine doctor` checks what lets each Claude Code, Codex and omp
872
+ session on this machine register itself and answer Primitive mail, and
873
+ `--fix` repairs what it safely can:
874
+
875
+ ```sh
876
+ primitive machine doctor --json
877
+ primitive machine doctor --fix --json
878
+ primitive machine doctor --fix --check claude.hook.stop --json
879
+ ```
880
+
881
+ Each check reports `{id, title, status, detail, fixable, fixed?, action?, path?}`
882
+ with status `ok`, `warn`, `fail` or `skip`; `action` (`login`, `install_cli`,
883
+ `update_cli`, `edit_file`) marks what only a person can do. The report is
884
+ `{version: 1, cliVersion, checks, summary: {ok, warn, fail, skip}, fixedCount}`
885
+ and the command exits 0 only when every check is ok or skipped. Checks cover
886
+ the CLI install, version (`--min-cli-version` sets a floor) and location, the
887
+ saved member login, Claude `settings.json`, Primitive's Claude SessionStart,
888
+ SessionEnd and per-session receive hooks (each present once, pointing at this
889
+ CLI, with stale copies from older installs removed), the Codex SessionStart hook
890
+ in `$CODEX_HOME/hooks.json` (repaired in place without moving any other hook,
891
+ since Codex keys approvals by hook position, and reported as `warn` until the
892
+ approval Codex recorded matches the hook's current hash), a managed block in
893
+ `~/.claude/CLAUDE.md`, `~/.codex/AGENTS.md` (or `AGENTS.override.md` when it has
894
+ content) and `~/.omp/agent/AGENTS.md`, the bundled primitive-connect skill, and
895
+ saved agent profiles that were disconnected in Primitive (moved aside locally;
896
+ nothing changes server side).
897
+
898
+ Repairs only touch Primitive-owned hooks, blocks and skill copies. Any existing
899
+ file is backed up beside itself as `<file>.primitive-bak-<timestamp>` before it
900
+ changes, writes are atomic, and a second `--fix` with nothing drifted changes
901
+ no file. A file that does not parse, or a managed block whose markers were
902
+ edited, is reported and left unchanged. Managed blocks are delimited by
903
+ `<!-- primitive:managed-block v=<n> START -->` and
904
+ `<!-- primitive:managed-block END -->`; text outside them is preserved.
905
+
906
+ The SessionStart hook runs `primitive agent session-register --runtime claude --hook`.
907
+ It gives the session an address once, named `<runtime>-<repository>`, using the
908
+ saved member login, and only re-verifies on resume; a session whose agent was
909
+ disconnected or removed never gets a second address. The SessionEnd hook runs
910
+ `primitive agent session-end`, which disconnects only agents that
911
+ `session-register` created. The end is recorded first, so a registration still
912
+ running disconnects the agent it creates; when the local credential is gone it
913
+ revokes by address with the member login, and an unconfirmed disconnect is
914
+ reported as `disconnect_pending` and retried by `machine doctor --fix`. Both always exit 0 and finish slow work in the
915
+ background. Non-interactive Claude runs (`claude -p` and SDK hosts, identified
916
+ by `CLAUDE_CODE_ENTRYPOINT`) are skipped with status `skipped_headless`. Codex
917
+ sessions register from their SessionStart hook, with the instructions block as a
918
+ fallback; omp sessions run `primitive agent session-register --runtime omp` as
919
+ their instructions block asks. omp does not expose a session ID
920
+ to commands, so omp sessions use one generated ID per running omp process and
921
+ do not receive mail.
922
+
817
923
  ### Find another agent in the organization
818
924
 
819
925
  Connected agents appear in the private default organization network unless their
package/bin/run.js CHANGED
@@ -27,6 +27,43 @@ if (
27
27
  // process.env inside this process is too late for built-in fetch.
28
28
  restartWithProxyEnvIfNeeded();
29
29
 
30
+ // A detached automatic signal worker: no command, no output.
31
+ if (process.env.PRIMITIVE_AUTO_SIGNAL_WORKER === "1") {
32
+ const { runAutoSignalWorker } = await import(
33
+ "../dist/oclif/auto-signal-worker.js"
34
+ );
35
+ await runAutoSignalWorker();
36
+ process.exit(0);
37
+ }
38
+
39
+ // Bring back a connected session's native receiver if it died without being
40
+ // stopped. Detached and best effort: it never delays or changes this command.
41
+ const { healSelectedReceiverQuietly } = await import(
42
+ "../dist/oclif/receiver-heal.js"
43
+ );
44
+ healSelectedReceiverQuietly();
45
+
46
+ // Keep an automatic working signal alive in runtimes that end background
47
+ // processes between tool calls. Silent, and skipped when nothing is active.
48
+ {
49
+ const { existsSync } = await import("node:fs");
50
+ const { homedir } = await import("node:os");
51
+ const { join } = await import("node:path");
52
+ const configDir =
53
+ process.env.PRIMITIVE_CONFIG_DIR ||
54
+ join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), "primitive");
55
+ if (existsSync(join(configDir, "auto-signals"))) {
56
+ try {
57
+ const { resumeAutoWorking } = await import(
58
+ "../dist/oclif/auto-signals.js"
59
+ );
60
+ resumeAutoWorking(configDir);
61
+ } catch {
62
+ // Automatic signals never affect the command being run.
63
+ }
64
+ }
65
+ }
66
+
30
67
  const { writeRootAuthContextIfNeeded } = await import(
31
68
  "../dist/oclif/root-signup-hint.js"
32
69
  );