@primitive.ai/prim 0.1.0-alpha.20 → 0.1.0-alpha.21
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 +6 -0
- package/SKILL.md +11 -1
- package/dist/{chunk-BEEGFDGU.js → chunk-4QJOQIY6.js} +8 -0
- package/dist/daemon/server.js +6 -5
- package/dist/hooks/post-tool-use.js +1 -1
- package/dist/index.js +146 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -113,6 +113,12 @@ and offers to install into `.husky/`.
|
|
|
113
113
|
prim statusline # Render the team-presence statusline (reads the daemon)
|
|
114
114
|
```
|
|
115
115
|
|
|
116
|
+
### Welcome
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
prim welcome # Brief orientation to the decision graph (shown after setup)
|
|
120
|
+
```
|
|
121
|
+
|
|
116
122
|
### Session & journal
|
|
117
123
|
|
|
118
124
|
Lower-level plumbing for the capture pipeline — org binding and the local move
|
package/SKILL.md
CHANGED
|
@@ -67,6 +67,16 @@ npx --yes @primitive.ai/prim decisions confirm <idOrShortId>
|
|
|
67
67
|
|
|
68
68
|
Confirmations are author-targeted and rare by design; answering keeps the graph's rationale trustworthy. Don't manufacture rationale — if you don't know why a decision was made, say so.
|
|
69
69
|
|
|
70
|
+
## Author a decision deliberately
|
|
71
|
+
|
|
72
|
+
Capture is automatic for the decisions you *make while coding*. When the user instead asks you to **record a decision explicitly** — one that didn't fall out of an edit (a design call, a convention, a choice settled in discussion) — author it directly:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
npx --yes @primitive.ai/prim decisions create --intent "Adopt prosemirror-collab over Yjs" --area data --rationale "Server-authoritative ordering" --alternatives "Yjs,Automerge"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Only `--intent` is required. Optional: `--kind` (change|exploration|task_execution|unclear, default change), `--rationale`, `--area`, `--decided`, `--alternatives` (comma-separated), `--confidence` (high|medium|low, default high), `--reversibility` (high|low, default high), and `--files` (comma-separated repo-relative paths the decision governs — pass these to make the conflict gate fire on later edits to those files, same path form as `decisions check`). STDOUT is the created identity `{ decisionId, shortId, createdAt }`; STDERR prints `[prim] created dec_<short>.` — pass that `dec_<short>` straight into `decisions show` / `cascade` / `confirm`. Author on the user's behalf only when they ask for a decision to be recorded; don't narrate your own routine edits into the graph (the hooks already do that).
|
|
79
|
+
|
|
70
80
|
## Presence
|
|
71
81
|
|
|
72
82
|
With the daemon running (`npx --yes @primitive.ai/prim daemon start`), `npx --yes @primitive.ai/prim daemon status` includes the live online count in its STDOUT JSON (when presence is fresh); Claude Code surfaces it in the statusline as `team: N online`. Your captured decisions are attributed to your agent automatically -- no flag required.
|
|
@@ -108,7 +118,7 @@ Examples:
|
|
|
108
118
|
- **An "unavailable" / "not verified" gate or check is not an all-clear.** Treat constraints as UNKNOWN and proceed deliberately; never read the silence as approval.
|
|
109
119
|
- **A `deny` means a real prior decision conflicts.** Reconcile only when you genuinely intend to override it; otherwise pick an approach that respects it.
|
|
110
120
|
- **Reconcile bypasses are single-use and short-lived.** One bypass clears your *next* edit to the governed file; it is not a standing override.
|
|
111
|
-
- **Capture is automatic, never manual.** If decisions aren't showing up, check that the session hooks are installed (`claude status` / `codex status`) and the daemon is running — don't try to inject moves by hand.
|
|
121
|
+
- **Capture of your coding activity is automatic, never manual.** If decisions aren't showing up, check that the session hooks are installed (`claude status` / `codex status`) and the daemon is running — don't try to inject moves by hand. (Deliberately *authoring* a decision the user asks you to record is a separate, supported path — `decisions create`, above.)
|
|
112
122
|
- **Don't fabricate rationale on a confirmation.** If you don't know why a decision was made, say so rather than guessing.
|
|
113
123
|
|
|
114
124
|
## After each task
|
|
@@ -11,6 +11,7 @@ var ANSI_CODES = {
|
|
|
11
11
|
gray: "\x1B[90m"
|
|
12
12
|
};
|
|
13
13
|
var ANSI_RESET = "\x1B[0m";
|
|
14
|
+
var ANSI_DIM = "\x1B[2m";
|
|
14
15
|
var ANSI_BOLD = "\x1B[1m";
|
|
15
16
|
function supportsColor() {
|
|
16
17
|
if (process.env.NO_COLOR !== void 0 && process.env.NO_COLOR !== "") {
|
|
@@ -24,6 +25,12 @@ function color(text, c) {
|
|
|
24
25
|
}
|
|
25
26
|
return `${ANSI_CODES[c]}${text}${ANSI_RESET}`;
|
|
26
27
|
}
|
|
28
|
+
function dim(text) {
|
|
29
|
+
if (!supportsColor()) {
|
|
30
|
+
return text;
|
|
31
|
+
}
|
|
32
|
+
return `${ANSI_DIM}${text}${ANSI_RESET}`;
|
|
33
|
+
}
|
|
27
34
|
function bold(text) {
|
|
28
35
|
if (!supportsColor()) {
|
|
29
36
|
return text;
|
|
@@ -53,6 +60,7 @@ function stripAnsi(text) {
|
|
|
53
60
|
|
|
54
61
|
export {
|
|
55
62
|
color,
|
|
63
|
+
dim,
|
|
56
64
|
bold,
|
|
57
65
|
colorForArea,
|
|
58
66
|
stripAnsi
|
package/dist/daemon/server.js
CHANGED
|
@@ -27,6 +27,7 @@ var client = getClient();
|
|
|
27
27
|
var activeSessionId = process.env.PRIM_DAEMON_SESSION_ID ?? `daemon-${process.pid}`;
|
|
28
28
|
var lastHeartbeatAt;
|
|
29
29
|
var lastOnlineCount;
|
|
30
|
+
var lastOnlineNames;
|
|
30
31
|
var lastOkAtLocal;
|
|
31
32
|
var heartbeatTimer;
|
|
32
33
|
var tokenCheckTimer;
|
|
@@ -70,9 +71,8 @@ async function sendHeartbeat() {
|
|
|
70
71
|
if (typeof result.lastHeartbeatAt === "number") {
|
|
71
72
|
lastHeartbeatAt = result.lastHeartbeatAt;
|
|
72
73
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
74
|
+
lastOnlineCount = typeof result.onlineCount === "number" ? result.onlineCount : void 0;
|
|
75
|
+
lastOnlineNames = Array.isArray(result.onlineNames) ? result.onlineNames : void 0;
|
|
76
76
|
}
|
|
77
77
|
} catch (err) {
|
|
78
78
|
process.stderr.write(
|
|
@@ -123,9 +123,10 @@ function handleStatusSnapshot() {
|
|
|
123
123
|
uptimeMs: Date.now() - startedAt,
|
|
124
124
|
sessionId: activeSessionId,
|
|
125
125
|
lastHeartbeatAt,
|
|
126
|
-
// Withhold a frozen count once
|
|
127
|
-
// "presence: stale" rather than a confident, wrong
|
|
126
|
+
// Withhold a frozen count/names once they're no longer fresh; the
|
|
127
|
+
// statusline shows "presence: stale" rather than a confident, wrong list.
|
|
128
128
|
onlineCount: presenceFresh ? lastOnlineCount : void 0,
|
|
129
|
+
onlineNames: presenceFresh ? lastOnlineNames : void 0,
|
|
129
130
|
presenceStale
|
|
130
131
|
};
|
|
131
132
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
bold,
|
|
3
4
|
color,
|
|
4
5
|
colorForArea,
|
|
6
|
+
dim,
|
|
5
7
|
stripAnsi
|
|
6
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-4QJOQIY6.js";
|
|
7
9
|
import {
|
|
8
10
|
checkAffectedDecisions,
|
|
9
11
|
daemonOrDirectGet,
|
|
@@ -751,6 +753,22 @@ import { spawn } from "child_process";
|
|
|
751
753
|
import { existsSync as existsSync4, readFileSync as readFileSync4, unlinkSync } from "fs";
|
|
752
754
|
import { homedir as homedir3 } from "os";
|
|
753
755
|
import { join as join4 } from "path";
|
|
756
|
+
|
|
757
|
+
// src/lib/presence.ts
|
|
758
|
+
function formatTeammates(names, cap) {
|
|
759
|
+
if (names === void 0) {
|
|
760
|
+
return "\u2014";
|
|
761
|
+
}
|
|
762
|
+
if (names.length === 0) {
|
|
763
|
+
return "just you";
|
|
764
|
+
}
|
|
765
|
+
if (names.length <= cap) {
|
|
766
|
+
return names.join(", ");
|
|
767
|
+
}
|
|
768
|
+
return `${names.slice(0, cap).join(", ")} +${String(names.length - cap)}`;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// src/commands/daemon.ts
|
|
754
772
|
var DAEMON_BIN = "prim-daemon-server";
|
|
755
773
|
var PID_PATH = join4(homedir3(), ".config", "prim", "daemon.pid");
|
|
756
774
|
var SOCK_PATH = join4(homedir3(), ".config", "prim", "sock");
|
|
@@ -924,8 +942,9 @@ async function daemonStatus() {
|
|
|
924
942
|
} else if (!snapshot) {
|
|
925
943
|
process.stderr.write("[prim] \u2713 daemon live (no snapshot)\n");
|
|
926
944
|
} else {
|
|
945
|
+
const team = snapshot.onlineNames !== void 0 ? ` \xB7 team: ${formatTeammates(snapshot.onlineNames, Number.POSITIVE_INFINITY)}` : "";
|
|
927
946
|
process.stderr.write(
|
|
928
|
-
`[prim] \u2713 daemon live \xB7 pid=${snapshot.pid} \xB7 uptime=${Math.round(snapshot.uptimeMs / 1e3)}s \xB7 session=${snapshot.sessionId}
|
|
947
|
+
`[prim] \u2713 daemon live \xB7 pid=${snapshot.pid} \xB7 uptime=${Math.round(snapshot.uptimeMs / 1e3)}s \xB7 session=${snapshot.sessionId}${team}
|
|
929
948
|
`
|
|
930
949
|
);
|
|
931
950
|
}
|
|
@@ -1342,6 +1361,44 @@ function formatConfirmJson(result) {
|
|
|
1342
1361
|
return JSON.stringify(result.outcome, null, 2);
|
|
1343
1362
|
}
|
|
1344
1363
|
|
|
1364
|
+
// src/decisions/create.ts
|
|
1365
|
+
var CREATE_TIMEOUT_MS = 1e4;
|
|
1366
|
+
var defaultDeps4 = { getClient };
|
|
1367
|
+
function toRequestBody(request) {
|
|
1368
|
+
const candidate = {
|
|
1369
|
+
intent: request.intent,
|
|
1370
|
+
kind: request.kind,
|
|
1371
|
+
rationale: request.rationale,
|
|
1372
|
+
area: request.area,
|
|
1373
|
+
decided: request.decided,
|
|
1374
|
+
alternatives: request.alternatives,
|
|
1375
|
+
confidence: request.confidence,
|
|
1376
|
+
reversibility: request.reversibility,
|
|
1377
|
+
files: request.files
|
|
1378
|
+
};
|
|
1379
|
+
const body = {};
|
|
1380
|
+
for (const [key, value] of Object.entries(candidate)) {
|
|
1381
|
+
const isEmpty = value === void 0 || Array.isArray(value) && value.length === 0;
|
|
1382
|
+
if (!isEmpty) {
|
|
1383
|
+
body[key] = value;
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
return body;
|
|
1387
|
+
}
|
|
1388
|
+
async function fetchCreate(request, deps = defaultDeps4) {
|
|
1389
|
+
const client = deps.getClient();
|
|
1390
|
+
return await client.post("/api/cli/decisions/create", toRequestBody(request), {
|
|
1391
|
+
signal: AbortSignal.timeout(CREATE_TIMEOUT_MS)
|
|
1392
|
+
});
|
|
1393
|
+
}
|
|
1394
|
+
function formatCreateHuman(outcome) {
|
|
1395
|
+
const id = renderIdentifier({ shortId: outcome.shortId, id: outcome.decisionId });
|
|
1396
|
+
return `[prim] created ${id}.`;
|
|
1397
|
+
}
|
|
1398
|
+
function formatCreateJson(outcome) {
|
|
1399
|
+
return JSON.stringify(outcome, null, 2);
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1345
1402
|
// src/decisions/show.ts
|
|
1346
1403
|
var NOT_FOUND_RE3 = /not found/i;
|
|
1347
1404
|
function colorStatus(status) {
|
|
@@ -1354,14 +1411,14 @@ function colorStatus(status) {
|
|
|
1354
1411
|
return color(status, "gray");
|
|
1355
1412
|
}
|
|
1356
1413
|
var SHOW_TIMEOUT_MS = 1e4;
|
|
1357
|
-
var
|
|
1414
|
+
var defaultDeps5 = { getClient };
|
|
1358
1415
|
var DecisionNotFoundError = class extends Error {
|
|
1359
1416
|
constructor(idOrShortId) {
|
|
1360
1417
|
super(`Decision not found: ${idOrShortId}`);
|
|
1361
1418
|
this.name = "DecisionNotFoundError";
|
|
1362
1419
|
}
|
|
1363
1420
|
};
|
|
1364
|
-
async function fetchShow(idOrShortId, deps =
|
|
1421
|
+
async function fetchShow(idOrShortId, deps = defaultDeps5) {
|
|
1365
1422
|
const params = new URLSearchParams({ id: idOrShortId });
|
|
1366
1423
|
const client = deps.getClient();
|
|
1367
1424
|
try {
|
|
@@ -1478,13 +1535,15 @@ function formatShowJson(result) {
|
|
|
1478
1535
|
|
|
1479
1536
|
// src/commands/decisions.ts
|
|
1480
1537
|
var EXIT_NOT_FOUND = 4;
|
|
1538
|
+
var EXIT_USAGE = 2;
|
|
1539
|
+
var splitList = (value) => (value ?? "").split(",").map((item) => item.trim()).filter(Boolean);
|
|
1481
1540
|
function registerDecisionsCommands(program2) {
|
|
1482
1541
|
const decisions = program2.command("decisions").description("Inspect the project Decision Graph");
|
|
1483
1542
|
decisions.command("check").description("Look up active decisions that reference one or more file paths").requiredOption(
|
|
1484
1543
|
"--files <files>",
|
|
1485
1544
|
"Comma-separated file paths to check against the Decision Graph"
|
|
1486
1545
|
).action(async (opts) => {
|
|
1487
|
-
const filePaths = opts.files
|
|
1546
|
+
const filePaths = splitList(opts.files);
|
|
1488
1547
|
const result = await checkAffectedDecisions(filePaths);
|
|
1489
1548
|
const warning = formatDecisionsWarning(result);
|
|
1490
1549
|
if (warning) {
|
|
@@ -1546,6 +1605,40 @@ function registerDecisionsCommands(program2) {
|
|
|
1546
1605
|
throw err;
|
|
1547
1606
|
}
|
|
1548
1607
|
});
|
|
1608
|
+
decisions.command("create").description("Author a decision directly \u2014 the deliberate manual path around automatic capture").requiredOption("--intent <text>", "What was decided (required)").option("--kind <kind>", "change | exploration | task_execution | unclear (default change)").option("--rationale <text>", "Why the decision was made").option(
|
|
1609
|
+
"--area <area>",
|
|
1610
|
+
"Functional area (auth, data, infra, ui, api, billing, mobile, docs, testing)"
|
|
1611
|
+
).option("--decided <items>", "Comma-separated option(s) chosen").option("--alternatives <items>", "Comma-separated options considered but rejected").option("--confidence <level>", "high | medium | low (default high)").option("--reversibility <level>", "high | low (default high)").option(
|
|
1612
|
+
"--files <paths>",
|
|
1613
|
+
"Comma-separated repo-relative paths this decision governs (gates edits to them)"
|
|
1614
|
+
).action(async (opts) => {
|
|
1615
|
+
const request = {
|
|
1616
|
+
intent: opts.intent,
|
|
1617
|
+
kind: opts.kind,
|
|
1618
|
+
rationale: opts.rationale,
|
|
1619
|
+
area: opts.area,
|
|
1620
|
+
decided: splitList(opts.decided),
|
|
1621
|
+
alternatives: splitList(opts.alternatives),
|
|
1622
|
+
confidence: opts.confidence,
|
|
1623
|
+
reversibility: opts.reversibility,
|
|
1624
|
+
files: splitList(opts.files)
|
|
1625
|
+
};
|
|
1626
|
+
try {
|
|
1627
|
+
const outcome = await fetchCreate(request);
|
|
1628
|
+
console.error(formatCreateHuman(outcome));
|
|
1629
|
+
console.log(formatCreateJson(outcome));
|
|
1630
|
+
} catch (err) {
|
|
1631
|
+
if (err instanceof HttpError && err.status >= 400 && err.status < 500) {
|
|
1632
|
+
console.error(`[prim] create rejected: ${err.message}`);
|
|
1633
|
+
console.log(
|
|
1634
|
+
JSON.stringify({ ok: false, status: err.status, error: err.message }, null, 2)
|
|
1635
|
+
);
|
|
1636
|
+
process.exitCode = EXIT_USAGE;
|
|
1637
|
+
return;
|
|
1638
|
+
}
|
|
1639
|
+
throw err;
|
|
1640
|
+
}
|
|
1641
|
+
});
|
|
1549
1642
|
}
|
|
1550
1643
|
|
|
1551
1644
|
// src/commands/hooks.ts
|
|
@@ -1866,7 +1959,7 @@ function registerMovesCommands(program2) {
|
|
|
1866
1959
|
|
|
1867
1960
|
// src/commands/reconcile.ts
|
|
1868
1961
|
var EXIT_OK2 = 0;
|
|
1869
|
-
var
|
|
1962
|
+
var EXIT_USAGE2 = 2;
|
|
1870
1963
|
var EXIT_SERVER = 3;
|
|
1871
1964
|
var HTTP_CLIENT_ERROR_MIN = 400;
|
|
1872
1965
|
var HTTP_SERVER_ERROR_MIN = 500;
|
|
@@ -1910,7 +2003,7 @@ async function performReconcile(idOrShortId, opts = {}) {
|
|
|
1910
2003
|
process.stderr.write(`[prim] reconcile rejected: ${err.message}
|
|
1911
2004
|
`);
|
|
1912
2005
|
console.log(JSON.stringify({ ok: false, status: err.status, error: err.message }, null, 2));
|
|
1913
|
-
process.exitCode =
|
|
2006
|
+
process.exitCode = EXIT_USAGE2;
|
|
1914
2007
|
return;
|
|
1915
2008
|
}
|
|
1916
2009
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -2172,6 +2265,7 @@ import { readFileSync as readFileSync8 } from "fs";
|
|
|
2172
2265
|
import { dirname as dirname5, resolve as resolve3 } from "path";
|
|
2173
2266
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
2174
2267
|
var STATUSLINE_TIMEOUT_MS = 200;
|
|
2268
|
+
var STATUSLINE_NAME_CAP = 3;
|
|
2175
2269
|
function readPackageVersion() {
|
|
2176
2270
|
try {
|
|
2177
2271
|
const here = dirname5(fileURLToPath3(import.meta.url));
|
|
@@ -2209,7 +2303,14 @@ async function renderStatusline() {
|
|
|
2209
2303
|
if (snapshot.presenceStale) {
|
|
2210
2304
|
return `primitive ${version} (daemon: live \xB7 presence: stale)`;
|
|
2211
2305
|
}
|
|
2212
|
-
|
|
2306
|
+
let team;
|
|
2307
|
+
if (snapshot.onlineNames !== void 0) {
|
|
2308
|
+
team = `team: ${formatTeammates(snapshot.onlineNames, STATUSLINE_NAME_CAP)}`;
|
|
2309
|
+
} else if (typeof snapshot.onlineCount === "number") {
|
|
2310
|
+
team = `team: ${String(snapshot.onlineCount)} online`;
|
|
2311
|
+
} else {
|
|
2312
|
+
team = "team: \u2014";
|
|
2313
|
+
}
|
|
2213
2314
|
return `primitive ${version} (daemon: live \xB7 ${team})`;
|
|
2214
2315
|
}
|
|
2215
2316
|
function registerStatuslineCommands(program2) {
|
|
@@ -2222,6 +2323,42 @@ function registerStatuslineCommands(program2) {
|
|
|
2222
2323
|
});
|
|
2223
2324
|
}
|
|
2224
2325
|
|
|
2326
|
+
// src/commands/welcome.ts
|
|
2327
|
+
var CMD_GUTTER = 38;
|
|
2328
|
+
function formatWelcome() {
|
|
2329
|
+
const cmd = (command, desc) => ` ${dim(command.padEnd(CMD_GUTTER))}${desc}`;
|
|
2330
|
+
const bullet = (text) => ` ${color("\u2022", "green")} ${text}`;
|
|
2331
|
+
return [
|
|
2332
|
+
bold(color("Welcome to Primitive", "green")),
|
|
2333
|
+
"",
|
|
2334
|
+
"Primitive captures the decisions your team makes while coding into a",
|
|
2335
|
+
"shared graph \u2014 and flags edits that conflict with earlier ones before",
|
|
2336
|
+
"they land.",
|
|
2337
|
+
"",
|
|
2338
|
+
bold("How it works"),
|
|
2339
|
+
bullet("Capture is automatic \u2014 keep coding; your decisions are recorded for you."),
|
|
2340
|
+
bullet("The conflict gate has your back: when an edit conflicts with a"),
|
|
2341
|
+
" load-bearing decision, prim surfaces it. Run `prim reconcile dec_<id>` to clear",
|
|
2342
|
+
" that decision and retry.",
|
|
2343
|
+
bullet('Occasional yes/no prompts confirm the "why" behind a decision \u2014'),
|
|
2344
|
+
" answering keeps the graph trustworthy.",
|
|
2345
|
+
"",
|
|
2346
|
+
bold("Get started"),
|
|
2347
|
+
cmd("prim decisions recent", "what your team has decided lately"),
|
|
2348
|
+
cmd("prim decisions check --files <files>", "what governs files you're about to change"),
|
|
2349
|
+
cmd("prim --help", "everything else"),
|
|
2350
|
+
"",
|
|
2351
|
+
dim("App: https://app.getprimitive.ai")
|
|
2352
|
+
].join("\n");
|
|
2353
|
+
}
|
|
2354
|
+
function registerWelcomeCommand(program2) {
|
|
2355
|
+
program2.command("welcome").description("Print a brief orientation to Primitive's decision graph").action(() => {
|
|
2356
|
+
process.stderr.write(`${formatWelcome()}
|
|
2357
|
+
`);
|
|
2358
|
+
printJson({ welcomed: true });
|
|
2359
|
+
});
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2225
2362
|
// src/index.ts
|
|
2226
2363
|
var __dirname2 = dirname6(fileURLToPath4(import.meta.url));
|
|
2227
2364
|
var pkg = JSON.parse(readFileSync9(resolve4(__dirname2, "../package.json"), "utf-8"));
|
|
@@ -2242,6 +2379,7 @@ registerCodexCommands(program);
|
|
|
2242
2379
|
registerDaemonCommands(program);
|
|
2243
2380
|
registerReconcileCommands(program);
|
|
2244
2381
|
registerStatuslineCommands(program);
|
|
2382
|
+
registerWelcomeCommand(program);
|
|
2245
2383
|
process.on("unhandledRejection", (err) => {
|
|
2246
2384
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2247
2385
|
console.error(msg);
|
package/package.json
CHANGED