primcli 1.75.0 → 3.0.0-preview.1
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 +116 -63
- package/bin/claude-pending-mail.mjs +1 -1
- package/bin/run.js +14 -0
- package/dist/{api-command-rlS3eTlZ.js → api-command-ctHBqTL5.js} +752 -56
- package/dist/{cli-config-B80p0pLk.js → cli-config-Ms_0vNjq.js} +59 -16
- package/dist/{machine-session-Cq_nD5eF.js → machine-session-Cp_eQ9t_.js} +25 -12
- package/dist/{notify-session-CbY4FYUt.js → notify-session-BN0JTPZ3.js} +334 -4
- package/dist/oclif/auto-signal-worker.js +3 -3
- package/dist/oclif/auto-signals.js +2 -2
- package/dist/oclif/global-org-flag.js +76 -0
- package/dist/oclif/index.js +112940 -51725
- package/dist/oclif/receiver-heal.js +3 -3
- package/dist/oclif/root-signup-hint.js +2 -2
- package/dist/receiver.d.ts +95 -0
- package/dist/receiver.js +569 -0
- package/dist/{repeats-Dj_o6c-X-LyCkKYjp.js → repeats-Dj_o6c-X-Bf9G0xds.js} +4916 -2382
- package/dist/{scoped-chat-B0thCwME.js → scoped-chat-DZ42V3jq.js} +1 -1
- package/dist/{signal-command-DCPsJBdz.js → signal-command-CRUMUCNa.js} +9 -8
- package/man/primitive.1 +34 -0
- package/package.json +53 -8
package/README.md
CHANGED
|
@@ -150,9 +150,9 @@ enter it, wait for the email and take what you need from it:
|
|
|
150
150
|
```bash
|
|
151
151
|
# Sign up with your address, then:
|
|
152
152
|
primitive wait --from github.com --code # prints 482913
|
|
153
|
-
primitive wait --from
|
|
153
|
+
primitive wait --from accounts.example.com --link # prints the sign-in link
|
|
154
154
|
primitive wait --subject "verify" --timeout 90s
|
|
155
|
-
primitive wait --from
|
|
155
|
+
primitive wait --from billing.example.com --code --json
|
|
156
156
|
```
|
|
157
157
|
|
|
158
158
|
`primitive wait` waits for the next email to the identity the CLI resolves (a
|
|
@@ -224,8 +224,47 @@ Use `primitive signin <email> --accept-terms`, then `primitive signin confirm <e
|
|
|
224
224
|
|
|
225
225
|
Use `primitive logout --force` to remove local CLI credentials, pending email-code auth state, and stale credential locks without contacting Primitive. This is the recovery command when an interrupted auth command leaves the CLI saying another credential operation is already in progress.
|
|
226
226
|
|
|
227
|
+
Use `primitive org leave` to leave the organization you are signed in to. It asks for confirmation first; pass `--yes` to skip the prompt in scripts. Leaving is final. Only a person signed in with `primitive login` can leave; API keys are refused. The only owner of an organization cannot leave it until someone else is made an owner. If the server asks you to confirm in the dashboard, the command prints the confirmation page and, in a terminal, asks for the token it shows; in a script, pass the token with `--step-up-token`. Refusals from the server are printed as returned, and the command exits 1.
|
|
228
|
+
|
|
227
229
|
Use `primitive signup <email>` for new account creation, then `primitive signup confirm <email> <code>` with the emailed verification code. Non-interactive signup is available with `--accept-terms`.
|
|
228
230
|
|
|
231
|
+
### Switching organizations
|
|
232
|
+
|
|
233
|
+
Where the server supports it, browser login (`primitive login` with no email) logs you in as yourself rather than into one organization. The organization you choose in the browser becomes the current one, and you can switch without logging in again:
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
primitive orgs # your organizations, your role, and whether the CLI may be used there
|
|
237
|
+
primitive org use acme # switch by name or id
|
|
238
|
+
primitive emails list --org beta # act in another organization for one command
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
`--org` works on every command (set `PRIMITIVE_CLI_ORG` for the same effect; `primitive memories` keeps its own `--org` flag for organization-scoped memories). Each organization decides who may use the CLI there; when it refuses, `primitive orgs` and `primitive org use` show the reason (`not_a_member`, `cli_access_disabled_by_org`, `role_insufficient` or `cli_access_revoked`). The CLI keeps a short-lived access token per organization and gets a new one when it expires or is rejected. `primitive logout` signs you out of every organization.
|
|
242
|
+
|
|
243
|
+
Against a server without organization switching, and for logins saved by earlier CLI versions, the login stays bound to one organization and works as before. `--org` naming a different organization is refused for such a login, for an API key and for a connected agent profile.
|
|
244
|
+
|
|
245
|
+
`primitive login --permission <permission>` asks for a narrowed login. A server that does not offer narrowed CLI logins refuses it, and the CLI stops rather than logging in with a wider grant.
|
|
246
|
+
|
|
247
|
+
### Grants and keys
|
|
248
|
+
|
|
249
|
+
`primitive whoami` shows your organization, role and grant when the server reports them. `primitive whoami --check mail:send` asks whether the current credential may use a permission, prints the decision, and exits 1 when it may not; nothing is enforced by asking. A request refused because the credential's grant lacks a permission fails with `grant_forbidden`, and the CLI names the permission.
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
primitive keys list
|
|
253
|
+
primitive keys list --json
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
`keys list` shows each key's id, prefix, name, grant, expiry and last use. It never shows a secret. API keys are created, rotated and revoked in the dashboard for now. CLI commands for this are planned. `keys list` uses the credential inventory, which the server answers only for an owner or admin in a dashboard session, so from the CLI it shows the server's refusal.
|
|
257
|
+
|
|
258
|
+
`primitive roles list`, `primitive roles groups`, `primitive roles mailbox-access` and `primitive roles access-mode` read roles, groups, mailbox access and the access mode. Where the server has not enabled them, they print `feature_disabled`. The built-in roles are `owner`, `admin`, `operator` and `member`. `operator` has every permission `admin` has except managing members, changing roles, sending invitations and billing. By default, owners, admins and operators may use the CLI. An organization can open it to every member by setting `cli_access` to `all_members` in its credential policy.
|
|
259
|
+
|
|
260
|
+
### Trying the preview
|
|
261
|
+
|
|
262
|
+
Install the 3.0.0 preview with `npm install -g primitive@preview`. `npm install -g primitive` stays on the latest stable version.
|
|
263
|
+
|
|
264
|
+
### Downgrading after signing in as yourself
|
|
265
|
+
|
|
266
|
+
A login as yourself is saved with `auth_method: "user_grant"`, which CLI 1.75.0 and earlier cannot read: every command there fails with `Stored Primitive CLI credentials are malformed`. Run `primitive logout` with this CLI before installing an older one. If you have already downgraded, `primitive logout` removes the local file but does not revoke the login on the server, so sign in again afterwards.
|
|
267
|
+
|
|
229
268
|
## Reply state and the agent loop
|
|
230
269
|
|
|
231
270
|
Every inbound email carries reply state: `awaiting` is `you` when the latest
|
|
@@ -315,6 +354,7 @@ primitive emails get --id <inbound-email-id> --compact
|
|
|
315
354
|
primitive sent list
|
|
316
355
|
primitive sent delete --id <sent-email-id>
|
|
317
356
|
primitive domains list
|
|
357
|
+
primitive billing
|
|
318
358
|
primitive functions templates
|
|
319
359
|
primitive functions init my-fn --template email-reply
|
|
320
360
|
primitive functions logs --id <function-id>
|
|
@@ -427,8 +467,8 @@ signal or interaction, so two agents cannot keep acknowledging each other.
|
|
|
427
467
|
acknowledgement for a message identified by its Message-Id.
|
|
428
468
|
|
|
429
469
|
A plain `primitive reply` to an email the server classifies as an interaction
|
|
430
|
-
the reply does not complete (a
|
|
431
|
-
|
|
470
|
+
the reply does not complete (a contact request, or a kind this CLI cannot
|
|
471
|
+
answer), or to informational mail or a status signal, is still sent.
|
|
432
472
|
Without `--json` one `Warning:` line on stderr names the command that answers
|
|
433
473
|
it; with `--json` the envelope carries `interaction_warning` (`code`,
|
|
434
474
|
`email_id`, `kind`, `category`, `message`, `expected`), null otherwise.
|
|
@@ -629,65 +669,79 @@ primitive routes remove <route-id>
|
|
|
629
669
|
Recipient routing is gated by an organization entitlement; routes are inert
|
|
630
670
|
until it is enabled.
|
|
631
671
|
|
|
632
|
-
##
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
--to payer@their-domain.example --amount 10000 --network base-sepolia
|
|
658
|
-
# Payer (recommended): pay the email challenge in one step. Signs the challenge
|
|
659
|
-
# locally with your wallet key AND sends the signed interaction.json, so you
|
|
660
|
-
# skip the manual sign-then-send dance. --in-reply-to is the inbound challenge
|
|
661
|
-
# email you received; it is fetched to address the payment to the payee, with
|
|
662
|
-
# From defaulting to the payer it was sent to. The send is not threaded under
|
|
663
|
-
# the challenge (the payment associates by interaction_id). The message carries
|
|
664
|
-
# a short default note alongside the attachment; pass --body to customize it.
|
|
665
|
-
primitive payments pay-email --challenge-file challenge.json \
|
|
666
|
-
--in-reply-to <inbound-challenge-email-id> --wait
|
|
667
|
-
|
|
668
|
-
# Advanced: sign only, without sending. Emits the portable interaction.json
|
|
669
|
-
# artifact you can attach yourself (e.g. with `primitive send --attachment`).
|
|
670
|
-
primitive payments pay-email-step --challenge-file challenge.json > interaction.json
|
|
671
|
-
|
|
672
|
-
# Inspect a challenge by id, or list your registered payout addresses.
|
|
673
|
-
primitive payments get-challenge --id <challenge-id>
|
|
674
|
-
primitive payments list-payout-addresses
|
|
675
|
-
|
|
676
|
-
# Read and update the org spend policy (kill-switch, per-payment and daily caps,
|
|
677
|
-
# payee allowlist). The update merges: omitted fields keep their value.
|
|
678
|
-
primitive payments get-spend-policy
|
|
679
|
-
primitive payments update-spend-policy --max-per-payment 5000000
|
|
680
|
-
```
|
|
681
|
-
|
|
682
|
-
`charge` is the friendly verb that matches the SDK `charge` and accepts `--amount-usdc`; `create-challenge` is the lower-level command that takes base-unit `--amount`. Either creates a challenge. All four signing commands accept `--json`. `register-payout-address` and `pay` print a human-readable summary by default and raw JSON with `--json`; `pay-email` and `pay-email-step` print JSON by default (the send result and the `interaction.json` bytes, respectively), and `--json` switches them to a fuller envelope object.
|
|
683
|
-
|
|
684
|
-
The signing commands (`register-payout-address`, `pay`, `pay-email`, and `pay-email-step`) need your wallet key. `pay-email` is the recommended payer path for the email-native flow: it signs and sends in one step. `pay-email-step` signs only and emits the `interaction.json` artifact for advanced use where you want to deliver it yourself. Set the key in `PRIMITIVE_X402_PRIVATE_KEY` (a `0x`-prefixed hex private key) so it never lands in shell history or the process list:
|
|
685
|
-
|
|
686
|
-
```bash
|
|
687
|
-
export PRIMITIVE_X402_PRIVATE_KEY=0x...
|
|
672
|
+
## Supported receiver API
|
|
673
|
+
|
|
674
|
+
A receiver in a coding harness that the CLI does not support must decide
|
|
675
|
+
which received emails may wake its session. `primitive/receiver` exposes the
|
|
676
|
+
CLI's own contact admission policy for that decision. It is the only
|
|
677
|
+
supported library import from this package.
|
|
678
|
+
|
|
679
|
+
```ts
|
|
680
|
+
import { createWakePolicy, WakePolicyError } from "primitive/receiver";
|
|
681
|
+
|
|
682
|
+
const policy = createWakePolicy({
|
|
683
|
+
recipient: "agent@example.com",
|
|
684
|
+
contactRequests: false,
|
|
685
|
+
readContactPolicy: (signal) => readPolicy(signal),
|
|
686
|
+
readContactPage: (cursor, signal) => readContacts(cursor, signal),
|
|
687
|
+
readNetworkAdmission: (emailId, sender, signal) =>
|
|
688
|
+
checkNetworkAdmission(emailId, sender, signal),
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
const decision = await policy.admit({ emailId, sender, receivedAt }, signal);
|
|
692
|
+
if (decision.kind !== "denied") {
|
|
693
|
+
const confirm = await policy.recheck(decision, signal);
|
|
694
|
+
confirm(); // immediately before the wake
|
|
695
|
+
wake(decision.kind === "request" ? "contact request" : "mail");
|
|
696
|
+
}
|
|
688
697
|
```
|
|
689
698
|
|
|
690
|
-
|
|
699
|
+
The three readers call the API as the connected address. Each returns the
|
|
700
|
+
`data` of one operation:
|
|
701
|
+
|
|
702
|
+
| Reader | API operation | CLI command |
|
|
703
|
+
| --- | --- | --- |
|
|
704
|
+
| `readContactPolicy` | `getAgentContactPolicy` | `primitive contacts get-agent-contact-policy` |
|
|
705
|
+
| `readContactPage` | `listAgentContacts`, 100 rows per page, with the next `cursor` or `null` | `primitive agent contacts list` |
|
|
706
|
+
| `readNetworkAdmission` | `checkDefaultNetworkContactAdmission` | `primitive agent-networks check-default-network-contact-admission` |
|
|
707
|
+
|
|
708
|
+
`admit` returns one of three decisions for an email:
|
|
709
|
+
|
|
710
|
+
- `allowed`: the email may wake the session.
|
|
711
|
+
- `request`: a first-contact request from an unknown sender. It is returned
|
|
712
|
+
only when `contactRequests` is true and the owner allows contact requests.
|
|
713
|
+
Show it as a request, never as a task. Allow at most one per sender.
|
|
714
|
+
- `denied`: the email must not wake the session.
|
|
715
|
+
|
|
716
|
+
An admission carries the email ID, sender and received time, normalized to
|
|
717
|
+
lower case and ISO 8601. Pass the sender only after you check that it is
|
|
718
|
+
authenticated.
|
|
719
|
+
|
|
720
|
+
Permission can change between admission and wake. Call `recheck` with the
|
|
721
|
+
admission that `admit` returned, then call the function it returns
|
|
722
|
+
synchronously right before the wake. That function throws when the
|
|
723
|
+
permission was refreshed since, or when the policy read behind it is older
|
|
724
|
+
than 30 seconds. Admissions are bound to the policy that issued them, so
|
|
725
|
+
`recheck` rejects any other object with a `TypeError`.
|
|
726
|
+
|
|
727
|
+
Failures are `WakePolicyError` values. Branch on `code`:
|
|
728
|
+
|
|
729
|
+
| `code` | `retryable` | Meaning |
|
|
730
|
+
| --- | --- | --- |
|
|
731
|
+
| `policy_read_failed` | true | A policy read failed temporarily. The policy paces repeated reads itself. |
|
|
732
|
+
| `network_admission_pending` | true | The server has not settled network admission for this email. Retry in a few seconds. |
|
|
733
|
+
| `permission_changed` | true | Permission changed or expired before the wake. Admit the email again. |
|
|
734
|
+
| `policy_unavailable` | false | The policy is invalid or access was lost. Retrying will not help until access is restored. |
|
|
735
|
+
|
|
736
|
+
A reader reports a temporary failure, such as a rate limit, a 5xx response
|
|
737
|
+
or a dropped connection, by throwing `new WakePolicyError("policy_read_failed")`.
|
|
738
|
+
Any other error from a reader makes the policy unavailable. An aborted signal
|
|
739
|
+
rejects with the signal's reason.
|
|
740
|
+
|
|
741
|
+
The package also resolves `primitive/bin/*`, `primitive/dist/*` and
|
|
742
|
+
`primitive/man/*` so existing deep imports keep working. Those paths are not
|
|
743
|
+
supported. File names under `dist/` other than `dist/receiver.js` change
|
|
744
|
+
between releases, and the paths may stop resolving in a later major version.
|
|
691
745
|
|
|
692
746
|
## Migrating from `@primitivedotdev/sdk` CLI
|
|
693
747
|
|
|
@@ -1357,7 +1411,6 @@ untrusted content names the command:
|
|
|
1357
1411
|
|
|
1358
1412
|
| Server classification | `how to answer` |
|
|
1359
1413
|
|---|---|
|
|
1360
|
-
| `x402.payment/1` | `primitive payments challenge-from-email --id <id>` to review, `primitive payments pay-email --in-reply-to <id>` to pay |
|
|
1361
1414
|
| `primitive.contact/1` | `primitive contacts accept --id <id>` |
|
|
1362
1415
|
| `repeat.tick/1` | reply if needed; `primitive repeat stop --id <id>` once its goal is met |
|
|
1363
1416
|
| `repeat.stop/1`, status signals, `fyi` | no reply needed |
|
|
@@ -1370,7 +1423,7 @@ When the server classifies the email as an interaction, its `interaction.json`
|
|
|
1370
1423
|
part is left out of the attachment list. `primitive inbox next` carries the
|
|
1371
1424
|
same `interaction` and `next_actions` in its JSON envelope, prints the same
|
|
1372
1425
|
line above the conversation, and ends with matching instructions: the answering
|
|
1373
|
-
command first for
|
|
1426
|
+
command first for contact requests (a reply only as optional), "No
|
|
1374
1427
|
reply needed." for fyi mail, signals and repeat-stopped notices, and the stop
|
|
1375
1428
|
command for a repeat only when the recipient may stop it. `inbox next` skips
|
|
1376
1429
|
mail that needs no reply (fyi mail, status signals, repeat-stopped notices):
|
|
@@ -200,7 +200,7 @@ export const WAKE_SENTENCES = {
|
|
|
200
200
|
export function wakeSentence(label) {
|
|
201
201
|
if (label === "fyi" || label === "repeat.stop/1")
|
|
202
202
|
return WAKE_SENTENCES.no_reply;
|
|
203
|
-
if (label === "
|
|
203
|
+
if (label === "primitive.contact/1")
|
|
204
204
|
return WAKE_SENTENCES.answer_with_command;
|
|
205
205
|
if (label === "repeat.tick/1") return WAKE_SENTENCES.repeat;
|
|
206
206
|
return WAKE_SENTENCES.unsupported;
|
package/bin/run.js
CHANGED
|
@@ -72,5 +72,19 @@ const { writeRootAuthContextIfNeeded } = await import(
|
|
|
72
72
|
);
|
|
73
73
|
await writeRootAuthContextIfNeeded();
|
|
74
74
|
|
|
75
|
+
// `--org <org>` works on every command: take it out of argv before oclif
|
|
76
|
+
// parses the command, and pass it on as PRIMITIVE_CLI_ORG. Applied last, so
|
|
77
|
+
// the detached receiver and signal workers started above never inherit it.
|
|
78
|
+
{
|
|
79
|
+
const { applyGlobalOrgFlag } = await import(
|
|
80
|
+
"../dist/oclif/global-org-flag.js"
|
|
81
|
+
);
|
|
82
|
+
const orgFlagError = applyGlobalOrgFlag();
|
|
83
|
+
if (orgFlagError) {
|
|
84
|
+
process.stderr.write(`${orgFlagError}\n`);
|
|
85
|
+
process.exit(2);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
75
89
|
const { execute } = await import("@oclif/core");
|
|
76
90
|
await execute({ dir: import.meta.url });
|