primcli 1.73.0 → 1.74.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/dist/oclif/index.js +89 -18
- package/dist/skills/primitive-connect/references/conversations.md +3 -2
- package/dist/skills/primitive-connect/references/native-session.md +4 -2
- package/dist/skills/primitive-connect/scripts/check-cli-capabilities.mjs +14 -11
- package/dist/skills/primitive-connect.json +5 -5
- package/package.json +1 -1
package/dist/oclif/index.js
CHANGED
|
@@ -49109,7 +49109,7 @@ const PRIMITIVE_TEAM_AUTHOR = {
|
|
|
49109
49109
|
url: "https://primitive.dev"
|
|
49110
49110
|
};
|
|
49111
49111
|
const SDK_VERSION_RANGE = "^2.0.0";
|
|
49112
|
-
const CLI_VERSION_RANGE = "^1.
|
|
49112
|
+
const CLI_VERSION_RANGE = "^1.74.1";
|
|
49113
49113
|
const ESBUILD_VERSION_RANGE = "^0.27.0";
|
|
49114
49114
|
function renderHandler() {
|
|
49115
49115
|
return `// env.PRIMITIVE_API_KEY, env.PRIMITIVE_WEBHOOK_SECRET, and
|
|
@@ -52286,6 +52286,15 @@ function openContactRequestNotices(configDir, identity) {
|
|
|
52286
52286
|
}
|
|
52287
52287
|
//#endregion
|
|
52288
52288
|
//#region src/oclif/conversation-status.ts
|
|
52289
|
+
/**
|
|
52290
|
+
* An acknowledgement (an `ack` signal, which `primitive reply --fyi` sends)
|
|
52291
|
+
* exists so its receiver does not have to act. It never wakes or notifies a
|
|
52292
|
+
* session: the event is completed and the message stays readable in its
|
|
52293
|
+
* thread. Read, working and typing still reach the session as status.
|
|
52294
|
+
*/
|
|
52295
|
+
function isQuietConversationStatus(status) {
|
|
52296
|
+
return status.kind === "ack";
|
|
52297
|
+
}
|
|
52289
52298
|
/** A References-derived parent ID cannot prove which Message-ID a signal names. */
|
|
52290
52299
|
async function readBoundSentMessageId(apiClient, wait, signal) {
|
|
52291
52300
|
if (!wait.sentEmailId) return null;
|
|
@@ -53019,12 +53028,12 @@ async function runSharedNotificationListen(options) {
|
|
|
53019
53028
|
await store.skipNotification(row.emailId, sessionKey);
|
|
53020
53029
|
return true;
|
|
53021
53030
|
}
|
|
53022
|
-
if (status && !await conversationStatusDue({
|
|
53031
|
+
if (status && (isQuietConversationStatus(status) || !await conversationStatusDue({
|
|
53023
53032
|
configDir: options.configDir,
|
|
53024
53033
|
scope,
|
|
53025
53034
|
recipient,
|
|
53026
53035
|
sessionKey
|
|
53027
|
-
}, status)) {
|
|
53036
|
+
}, status))) {
|
|
53028
53037
|
await store.skipNotification(row.emailId, sessionKey);
|
|
53029
53038
|
return true;
|
|
53030
53039
|
}
|
|
@@ -53705,6 +53714,7 @@ async function createWakeMail(options) {
|
|
|
53705
53714
|
const sentMessageId = requested && statusContent ? await readBoundSentMessageId(apiClient, requested, signal) : null;
|
|
53706
53715
|
if (statusContent && !sentMessageId) return outcome(false);
|
|
53707
53716
|
const status = requested ? boundConversationStatus(detail, statusContent, requested, sentMessageId, options.sessionKey) : null;
|
|
53717
|
+
if (status && isQuietConversationStatus(status)) return outcome(true);
|
|
53708
53718
|
let admission;
|
|
53709
53719
|
let admissionRetry;
|
|
53710
53720
|
try {
|
|
@@ -56453,15 +56463,36 @@ async function memberCredentialsFor(options) {
|
|
|
56453
56463
|
return null;
|
|
56454
56464
|
}
|
|
56455
56465
|
}
|
|
56466
|
+
/**
|
|
56467
|
+
* How far the server's claim time may run ahead of the time the CLI saved
|
|
56468
|
+
* the key it returned, before the claim is taken to be a later one. It
|
|
56469
|
+
* absorbs clock skew between this machine and Primitive.
|
|
56470
|
+
*/
|
|
56471
|
+
const CLAIM_SKEW_MS = 3e5;
|
|
56472
|
+
/**
|
|
56473
|
+
* Whether the list shows a saved profile's key is still the connection's
|
|
56474
|
+
* current one, without reading the profile's own record. A connection gets a
|
|
56475
|
+
* new key only by being claimed again, which moves `claimed_at` past the
|
|
56476
|
+
* moment this profile saved its key; a reconnect in progress shows as
|
|
56477
|
+
* pending, and its old key is already revoked. Either way the profile reads
|
|
56478
|
+
* its own record, which tells a rejected key from a live one.
|
|
56479
|
+
*/
|
|
56480
|
+
function keyStillCurrent(listed, profile) {
|
|
56481
|
+
if (listed.status !== "connected" && listed.status !== "claimed") return false;
|
|
56482
|
+
const saved = Date.parse(profile.created_at);
|
|
56483
|
+
return listed.claimedAt !== null && Number.isFinite(saved) && listed.claimedAt <= saved + CLAIM_SKEW_MS;
|
|
56484
|
+
}
|
|
56456
56485
|
/** Pages to read before giving up on confirming absence (10,000 agents). */
|
|
56457
56486
|
const OWNER_LIST_MAX_PAGES = 200;
|
|
56458
56487
|
/**
|
|
56459
|
-
* The member's connection list, hidden agents included,
|
|
56460
|
-
*
|
|
56488
|
+
* The member's connection list, hidden agents included, by address. Read
|
|
56489
|
+
* once per run. `complete` is true only when every page was read and the
|
|
56461
56490
|
* last one explicitly ended the list (`meta.cursor: null`): a status found on
|
|
56462
|
-
* any page stands, but absence is proof only from a complete list.
|
|
56491
|
+
* any page stands, but absence is proof only from a complete list. Paging
|
|
56492
|
+
* stops early, with `complete` false, once every address in `wanted` has
|
|
56493
|
+
* been found, so a large organization does not cost a long read.
|
|
56463
56494
|
*/
|
|
56464
|
-
async function ownerConnectionStatuses(credentials, fetchImpl) {
|
|
56495
|
+
async function ownerConnectionStatuses(credentials, fetchImpl, wanted) {
|
|
56465
56496
|
const statuses = /* @__PURE__ */ new Map();
|
|
56466
56497
|
const partial = () => ({
|
|
56467
56498
|
statuses,
|
|
@@ -56491,12 +56522,19 @@ async function ownerConnectionStatuses(credentials, fetchImpl) {
|
|
|
56491
56522
|
if (text.length > 1048576) return partial();
|
|
56492
56523
|
const body = JSON.parse(text);
|
|
56493
56524
|
if (body.success !== true || !Array.isArray(body.data)) return partial();
|
|
56494
|
-
for (const row of body.data) if (typeof row?.address === "string" && typeof row.status === "string")
|
|
56525
|
+
for (const row of body.data) if (typeof row?.address === "string" && typeof row.status === "string") {
|
|
56526
|
+
const claimedAt = typeof row.claimed_at === "string" ? Date.parse(row.claimed_at) : NaN;
|
|
56527
|
+
statuses.set(row.address, {
|
|
56528
|
+
status: row.status,
|
|
56529
|
+
claimedAt: Number.isFinite(claimedAt) ? claimedAt : null
|
|
56530
|
+
});
|
|
56531
|
+
}
|
|
56495
56532
|
const next = body.meta?.cursor;
|
|
56496
56533
|
if (next === null) return {
|
|
56497
56534
|
statuses,
|
|
56498
56535
|
complete: true
|
|
56499
56536
|
};
|
|
56537
|
+
if (wanted && [...wanted].every((address) => statuses.has(address))) return partial();
|
|
56500
56538
|
if (typeof next !== "string" || !next || next === cursor) return partial();
|
|
56501
56539
|
cursor = next;
|
|
56502
56540
|
} catch {
|
|
@@ -56824,26 +56862,59 @@ async function runMachineDoctor(options) {
|
|
|
56824
56862
|
let ownerList;
|
|
56825
56863
|
let unknown = 0;
|
|
56826
56864
|
let connected = 0;
|
|
56865
|
+
let limited = 0;
|
|
56866
|
+
member = await memberCredentialsFor(options);
|
|
56867
|
+
const checkableBy = (credentials, profile) => !!credentials && credentials.org_id === profile.org_id && credentials.api_base_url === profile.api_base_url;
|
|
56868
|
+
let rateLimited = false;
|
|
56869
|
+
const observedFetch = (async (input, init) => {
|
|
56870
|
+
const response = await fetchImpl(input, init);
|
|
56871
|
+
if (response.status === 429) rateLimited = true;
|
|
56872
|
+
return response;
|
|
56873
|
+
});
|
|
56874
|
+
const listable = profiles.filter((entry) => checkableBy(member, entry.profile));
|
|
56875
|
+
if (listable.length && member) ownerList = await ownerConnectionStatuses(member, observedFetch, new Set(listable.map((entry) => entry.profile.agent_address)));
|
|
56876
|
+
const sharedAddresses = /* @__PURE__ */ new Set();
|
|
56877
|
+
{
|
|
56878
|
+
const seen = /* @__PURE__ */ new Set();
|
|
56879
|
+
for (const entry of profiles) {
|
|
56880
|
+
const address = entry.profile.agent_address.toLowerCase();
|
|
56881
|
+
if (seen.has(address)) sharedAddresses.add(address);
|
|
56882
|
+
seen.add(address);
|
|
56883
|
+
}
|
|
56884
|
+
}
|
|
56885
|
+
const unresolved = [];
|
|
56886
|
+
for (const entry of profiles) {
|
|
56887
|
+
const listed = ownerList && checkableBy(member, entry.profile) && !sharedAddresses.has(entry.profile.agent_address.toLowerCase()) ? ownerList.statuses.get(entry.profile.agent_address) : void 0;
|
|
56888
|
+
if (listed?.status === "revoked") orphans.push(entry);
|
|
56889
|
+
else if (listed && keyStillCurrent(listed, entry.profile)) connected++;
|
|
56890
|
+
else unresolved.push(entry);
|
|
56891
|
+
}
|
|
56827
56892
|
const states = [];
|
|
56828
|
-
for (let start = 0; start <
|
|
56829
|
-
|
|
56830
|
-
|
|
56831
|
-
|
|
56893
|
+
for (let start = 0; start < unresolved.length; start += 8) {
|
|
56894
|
+
if (rateLimited) {
|
|
56895
|
+
limited = unresolved.length - start;
|
|
56896
|
+
unknown += limited;
|
|
56897
|
+
break;
|
|
56898
|
+
}
|
|
56899
|
+
states.push(...await Promise.all(unresolved.slice(start, start + 8).map(async (entry) => ({
|
|
56900
|
+
...entry,
|
|
56901
|
+
state: await agentConnectionState(entry.profile, observedFetch)
|
|
56902
|
+
}))));
|
|
56903
|
+
}
|
|
56832
56904
|
for (const entry of states) if (entry.state === "revoked") orphans.push(entry);
|
|
56833
56905
|
else if (entry.state === "rejected") {
|
|
56834
|
-
|
|
56835
|
-
const checkable = !!member && member.org_id === entry.profile.org_id && member.api_base_url === entry.profile.api_base_url;
|
|
56906
|
+
const checkable = checkableBy(member, entry.profile);
|
|
56836
56907
|
if (member && !checkable) elsewhere++;
|
|
56837
|
-
|
|
56838
|
-
const listed = checkable && ownerList ? ownerList.statuses.get(entry.profile.agent_address) ?? (ownerList.complete ? "absent" : null) : null;
|
|
56908
|
+
const listed = checkable && ownerList ? ownerList.statuses.get(entry.profile.agent_address)?.status ?? (ownerList.complete ? "absent" : null) : null;
|
|
56839
56909
|
if (listed === "revoked" || listed === "absent") orphans.push(entry);
|
|
56840
56910
|
else unconfirmed.push(entry.profile.agent_address);
|
|
56841
56911
|
} else if (entry.state === "unavailable") unknown++;
|
|
56842
56912
|
else connected++;
|
|
56843
56913
|
if (orphans.length) return check("profiles.orphaned", "fail", `${orphans.length} saved profiles were disconnected or removed in Primitive: ${orphans.map((entry) => entry.profile.agent_address).join(", ")}. A repair moves them aside locally; nothing changes in Primitive.`, { fixable: true });
|
|
56844
56914
|
if (unconfirmed.length || unreadable) return check("profiles.orphaned", "warn", [unconfirmed.length ? `${unconfirmed.length} saved profiles have credentials Primitive no longer accepts (${unconfirmed.slice(0, 5).join(", ")}${unconfirmed.length > 5 ? `, and ${unconfirmed.length - 5} more` : ""}). They cannot send or receive. To clean them up, sign in with \`primitive login --force\` as a member of their organization and run \`primitive machine doctor --fix\`, which confirms each one is disconnected before moving it aside${elsewhere ? `. ${elsewhere} of them belong to an organization or Primitive API your current sign-in is not for, so sign in there to check them` : ""}` : "", unreadable ? `${unreadable} profiles could not be read` : ""].filter(Boolean).join("; ").concat("; they were left unchanged."));
|
|
56845
|
-
|
|
56846
|
-
return check("profiles.orphaned", "
|
|
56915
|
+
const limitedNote = limited ? ` Primitive rate limited this check, so ${limited} saved profiles were not asked about; the next run checks them.` : "";
|
|
56916
|
+
if (unknown && !connected) return check("profiles.orphaned", "skip", `Primitive could not be reached to check saved profiles.${limitedNote}`);
|
|
56917
|
+
return check("profiles.orphaned", "ok", `${connected} saved profiles are connected${unknown ? `; ${unknown} could not be checked` : ""}.${limitedNote}`);
|
|
56847
56918
|
};
|
|
56848
56919
|
const moveOrphans = async () => {
|
|
56849
56920
|
let moved = 0;
|
|
@@ -214,8 +214,9 @@ and sends nothing; a read that started Working ends with that exact command.
|
|
|
214
214
|
A reply stops Working for the email it answers and for the sender's other
|
|
215
215
|
mail in that thread, not for the sender's mail in other threads. Do not send Read or Working
|
|
216
216
|
yourself then; still send Typing just before composing. Only an older CLI,
|
|
217
|
-
whose
|
|
218
|
-
|
|
217
|
+
whose full signal help does not mention automatic signals, needs the manual
|
|
218
|
+
Working signal above. Read the full help with `primitive signal --help-full`,
|
|
219
|
+
or `primitive signal --help` when the CLI rejects `--help-full`. Either way the CLI
|
|
219
220
|
rejects signal/interaction parents to avoid loops. Do not hand-renew Working in
|
|
220
221
|
a loop through long work; the work claim under
|
|
221
222
|
[Collaborate with other agents](collaboration.md) covers that. Send Typing only while composing and stop on reply, failure or waiting.
|
|
@@ -8,8 +8,10 @@ listener while that hook is installed or after it has delivered real mail. Do
|
|
|
8
8
|
not use this native status to diagnose Claude's hook, start a competing listener,
|
|
9
9
|
or repeat a claim. See the [Claude guidance](presence-and-receiving.md#claude-code-external-receiver).
|
|
10
10
|
|
|
11
|
-
Use this reference only when installed
|
|
11
|
+
Use this reference only when the installed CLI's full listen help describes
|
|
12
12
|
"external mail events at tool-output authority" and no synthetic user messages.
|
|
13
|
+
Read it with `primitive listen --help-full`, or `primitive listen --help` when
|
|
14
|
+
the CLI rejects `--help-full`; short `--help` leaves out these descriptions.
|
|
13
15
|
The same `--notify-session` command supplies these events; no extra mode flag or
|
|
14
16
|
plugin is required. Those events carry mail-arrival metadata with tool-output
|
|
15
17
|
trust, like a background task completion.
|
|
@@ -86,7 +88,7 @@ does not prove Primitive attached; check the listener health below.
|
|
|
86
88
|
Once that exact session is loaded, use the profile already verified as belonging
|
|
87
89
|
to this connection and saved in this session's context. Replace the example name;
|
|
88
90
|
do not adopt a generic existing profile based only on configured status. Use the
|
|
89
|
-
already inspected
|
|
91
|
+
already inspected full listen help to choose the installed capability.
|
|
90
92
|
Only when it documents external-event delivery and `--background`, let the CLI
|
|
91
93
|
supervise the receiver:
|
|
92
94
|
|
|
@@ -9,16 +9,16 @@ if (receiver !== 'native' && receiver !== 'external' && receiver !== 'poll') {
|
|
|
9
9
|
process.exitCode = 2;
|
|
10
10
|
} else {
|
|
11
11
|
const checks = [
|
|
12
|
-
{
|
|
13
|
-
{
|
|
14
|
-
{
|
|
15
|
-
{
|
|
12
|
+
{ command: ['agent', 'connect'], label: 'agent connect', patterns: [/--session\b/, /--receiver\b/] },
|
|
13
|
+
{ command: ['agent', 'enroll'], label: 'agent enroll', patterns: [/--session\b/, /--receiver\b/, /--name\b/] },
|
|
14
|
+
{ command: ['listen'], label: 'listen', patterns: [/external mail events at tool-output authority/i, /never synthetic user messages/i] },
|
|
15
|
+
{ command: ['network', 'peers'], label: 'network peers', patterns: [/discover listed peers/i, /--owner\b/] },
|
|
16
16
|
];
|
|
17
17
|
if (receiver === 'poll') {
|
|
18
18
|
// Nothing is installed and no listener runs: the agent checks for mail.
|
|
19
19
|
checks[0].patterns.push(/poll installs nothing/i);
|
|
20
20
|
checks[1].patterns.push(/\bpoll\b/);
|
|
21
|
-
checks[2] = {
|
|
21
|
+
checks[2] = { command: ['agent', 'check-mail'], label: 'agent check-mail', patterns: [/since its previous check/i] };
|
|
22
22
|
} else if (receiver === 'external') {
|
|
23
23
|
checks[0].patterns.push(/installs the exact Claude session's fail-open Stop hook/i, /resume SessionStart hook/i);
|
|
24
24
|
checks[1].patterns.push(/exact Claude session, install a fail-open Stop hook/i, /resume SessionStart hook/i);
|
|
@@ -27,7 +27,7 @@ if (receiver !== 'native' && receiver !== 'external' && receiver !== 'poll') {
|
|
|
27
27
|
checks[2].patterns.push(/--notify-session\b/, /--background\b/);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
const
|
|
30
|
+
const run = (args) => {
|
|
31
31
|
const result = spawnSync('primitive', args, {
|
|
32
32
|
encoding: 'utf8',
|
|
33
33
|
timeout: 10_000,
|
|
@@ -35,16 +35,19 @@ if (receiver !== 'native' && receiver !== 'external' && receiver !== 'poll') {
|
|
|
35
35
|
});
|
|
36
36
|
return result.error || result.status !== 0 ? null : result.stdout.replace(/\s+/g, ' ');
|
|
37
37
|
};
|
|
38
|
+
// Newer CLIs keep --help short and print the full text, which these patterns
|
|
39
|
+
// match, on --help-full. Older CLIs reject --help-full and print it on --help.
|
|
40
|
+
const helpText = (command) => run([...command, '--help-full']) ?? run([...command, '--help']);
|
|
38
41
|
|
|
39
42
|
const missing = [];
|
|
40
|
-
for (const {
|
|
41
|
-
const help = helpText(
|
|
43
|
+
for (const { command, label, patterns } of checks) {
|
|
44
|
+
const help = helpText(command);
|
|
42
45
|
if (help === null) {
|
|
43
|
-
missing.push(`${label}
|
|
46
|
+
missing.push(`${label} help is unavailable`);
|
|
44
47
|
continue;
|
|
45
48
|
}
|
|
46
49
|
if (patterns.some((pattern) => !pattern.test(help))) {
|
|
47
|
-
missing.push(`${label}
|
|
50
|
+
missing.push(`${label} help lacks required setup, ${receiver} receiving, or peer-discovery support`);
|
|
48
51
|
}
|
|
49
52
|
}
|
|
50
53
|
|
|
@@ -54,7 +57,7 @@ if (receiver !== 'native' && receiver !== 'external' && receiver !== 'poll') {
|
|
|
54
57
|
} else {
|
|
55
58
|
process.stdout.write(`Primitive CLI preflight passed for ${receiver} receiving and peer discovery.\n`);
|
|
56
59
|
// Advisory only: older CLIs without `primitive wait` still connect and work.
|
|
57
|
-
const wait = helpText(['wait'
|
|
60
|
+
const wait = helpText(['wait']);
|
|
58
61
|
process.stdout.write(wait !== null && /--code\b/.test(wait) && /--link\b/.test(wait)
|
|
59
62
|
? 'primitive wait: available (--code, --link).\n'
|
|
60
63
|
: 'primitive wait: not in this CLI; run `primitive agent check-mail` every few seconds until the email is listed, then read it with `primitive emails get --id <id> --context`.\n');
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primitive-connect",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "38229bc689e6b37b",
|
|
4
4
|
"source": {
|
|
5
5
|
"repository": "https://github.com/primitivedotdev/skills",
|
|
6
6
|
"path": "skills/primitive-connect",
|
|
7
|
-
"commit": "
|
|
7
|
+
"commit": "a737d72b70ad813a5539a3f492546da52998ec0e"
|
|
8
8
|
},
|
|
9
9
|
"files": {
|
|
10
10
|
"SKILL.md": "37d40b66010319c6a176bc8eb66affd93cab0968c609ad523dac087d3077d29d",
|
|
@@ -15,13 +15,13 @@
|
|
|
15
15
|
"references/connect.md": "dc39fb8bbaffa69e30a3895f195beb12cfc95c991140ab5a7adb6e1e3293272a",
|
|
16
16
|
"references/contact-requests.md": "a6afb51ec2cd0bf807095e034942c6e81c82c8b8489a3e2464cd8ce9f4c28d43",
|
|
17
17
|
"references/contacts.md": "11af5638bd4799f10bfa6fdc1c1cebf90db7c8a43d4ed5498b96c14779f1a482",
|
|
18
|
-
"references/conversations.md": "
|
|
18
|
+
"references/conversations.md": "c8a1125db76e40e8bf2e550223e4d4e1a86536ca43ccdd835024f6d6502098a7",
|
|
19
19
|
"references/external-services.md": "211e03cec546d2f83c78bd38985cc64203272df47eb52cf1bdce885bceee3089",
|
|
20
20
|
"references/manual-setup.md": "433f111c425cb976d153e22c851064e2e730f45229d8747ec91827f3a76b5d0e",
|
|
21
|
-
"references/native-session.md": "
|
|
21
|
+
"references/native-session.md": "badc915ef658a8ef0ca1c6b0d5c4292f3668068754f26dc63eff99e63ac0d7f8",
|
|
22
22
|
"references/presence-and-receiving.md": "449e1c3c2339c4ad0f08151f2323f3982962e1037c97b24531983567a255b702",
|
|
23
23
|
"references/private-api-fallback.md": "05e5fc968c080fed2479ad380a3ccc55a897eda9a723650905fcdd9be9cc241b",
|
|
24
|
-
"scripts/check-cli-capabilities.mjs": "
|
|
24
|
+
"scripts/check-cli-capabilities.mjs": "82a30f44a5af119d3f681257d58aa85bd4ed67fac97da508fd2d15ebc2ddb4d3",
|
|
25
25
|
"scripts/connection.mjs": "2c96eda1e9a277a241555f98dd858523b8f77a9ddd82d52a6ac34e844206c3d6",
|
|
26
26
|
"scripts/mail.mjs": "bfdfb60a739f1537d0e676dc5ff44fd5a65dba4303e3781dce8754c8d671cf51"
|
|
27
27
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primcli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.74.1",
|
|
4
4
|
"description": "Official Primitive CLI: deploy Primitive Functions, send and inspect mail, manage endpoints, all from the terminal. Wraps the @primitivedotdev/sdk runtime client with one-shot commands.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|