@primitive.ai/prim 0.1.0-alpha.42 → 0.1.0-alpha.44
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
|
@@ -125,7 +125,7 @@ creates an accepted `change` Decision. A later Claude `Stop` or fresh
|
|
|
125
125
|
`SessionStart` in the same Git worktree can display:
|
|
126
126
|
|
|
127
127
|
```text
|
|
128
|
-
[prim] response → created Decision (dec_a1b2c3d4): Use the stable API
|
|
128
|
+
[prim] response → created Decision (dec_a1b2c3d4): Use the stable API (https://app.getprimitive.ai/decisions/r571n1dqjdrtyxxpf0fnzee4gn8aed6q)
|
|
129
129
|
```
|
|
130
130
|
|
|
131
131
|
This is a hook `systemMessage` for the person using Claude Code; it is not
|
|
@@ -14,6 +14,7 @@ var STATUS_PATH = "/api/cli/decisions/feedback/status";
|
|
|
14
14
|
var SHORT_ID = /^[0-9a-f]{8}$/u;
|
|
15
15
|
var MAX_EVENT_ID_CHARS = 128;
|
|
16
16
|
var MAX_RAW_INTENT_CODE_UNITS = 512;
|
|
17
|
+
var MAX_FEEDBACK_WEB_URL_CHARS = 2048;
|
|
17
18
|
function isRecord(value) {
|
|
18
19
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
19
20
|
}
|
|
@@ -52,6 +53,20 @@ function normalizeFeedbackIntent(value) {
|
|
|
52
53
|
}
|
|
53
54
|
return `${points.slice(0, MAX_FEEDBACK_INTENT_CODE_POINTS - 1).join("")}\u2026`;
|
|
54
55
|
}
|
|
56
|
+
function parseFeedbackWebUrl(value) {
|
|
57
|
+
if (typeof value !== "string" || value.length === 0 || value.length > MAX_FEEDBACK_WEB_URL_CHARS || !/^https:\/\//iu.test(value) || /\s/u.test(value) || replaceIsolatedSurrogates(value) !== value || Array.from(value).some((character) => isUnsafeControl(character.codePointAt(0) ?? 0))) {
|
|
58
|
+
return void 0;
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const url = new URL(value);
|
|
62
|
+
if (url.protocol !== "https:" || !url.hostname || url.username.length > 0 || url.password.length > 0) {
|
|
63
|
+
return void 0;
|
|
64
|
+
}
|
|
65
|
+
} catch {
|
|
66
|
+
return void 0;
|
|
67
|
+
}
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
55
70
|
function parseEvent(value) {
|
|
56
71
|
if (!isRecord(value)) return void 0;
|
|
57
72
|
if (typeof value.eventId !== "string" || value.eventId.length === 0 || value.eventId.length > MAX_EVENT_ID_CHARS || Array.from(value.eventId).some((character) => isUnsafeControl(character.codePointAt(0) ?? 0))) {
|
|
@@ -68,11 +83,14 @@ function parseEvent(value) {
|
|
|
68
83
|
}
|
|
69
84
|
const intent = normalizeFeedbackIntent(value.intent);
|
|
70
85
|
if (!intent) return void 0;
|
|
86
|
+
const webUrl = value.webUrl === void 0 ? void 0 : parseFeedbackWebUrl(value.webUrl);
|
|
87
|
+
if (value.webUrl !== void 0 && webUrl === void 0) return void 0;
|
|
71
88
|
return {
|
|
72
89
|
eventId: value.eventId,
|
|
73
90
|
leaseVersion: Number(value.leaseVersion),
|
|
74
91
|
shortId: value.shortId,
|
|
75
|
-
intent
|
|
92
|
+
intent,
|
|
93
|
+
...webUrl === void 0 ? {} : { webUrl }
|
|
76
94
|
};
|
|
77
95
|
}
|
|
78
96
|
function parseFeedbackLease(value) {
|
|
@@ -100,7 +118,7 @@ function renderFeedback(lease) {
|
|
|
100
118
|
const deliveries = [];
|
|
101
119
|
let pointCount = 0;
|
|
102
120
|
for (const event of lease.events) {
|
|
103
|
-
const line = `[prim] response \u2192 created Decision (dec_${event.shortId}): ${event.intent}`;
|
|
121
|
+
const line = `[prim] response \u2192 created Decision (dec_${event.shortId}): ${event.intent}${event.webUrl ? ` (${event.webUrl})` : ""}`;
|
|
104
122
|
const extra = Array.from(line).length + (lines.length === 0 ? 0 : 1);
|
|
105
123
|
if (pointCount + extra > MAX_FEEDBACK_MESSAGE_CODE_POINTS) break;
|
|
106
124
|
lines.push(line);
|
package/dist/hooks/prim-hook.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
} from "./chunk-OKUXEBPT.js";
|
|
25
25
|
import {
|
|
26
26
|
fetchFeedbackCapability
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-UISU3A7W.js";
|
|
28
28
|
import {
|
|
29
29
|
inspectWorkspaceId
|
|
30
30
|
} from "./chunk-IMAIBPUC.js";
|
|
@@ -1662,6 +1662,25 @@ import { homedir as homedir4 } from "os";
|
|
|
1662
1662
|
import { join as join4 } from "path";
|
|
1663
1663
|
|
|
1664
1664
|
// src/lib/presence.ts
|
|
1665
|
+
var DECISION_ORIGIN = "https://app.getprimitive.ai";
|
|
1666
|
+
function validDecisionUrl(value) {
|
|
1667
|
+
if (typeof value !== "string" || stripControlChars(value) !== value) {
|
|
1668
|
+
return void 0;
|
|
1669
|
+
}
|
|
1670
|
+
try {
|
|
1671
|
+
const url = new URL(value);
|
|
1672
|
+
if (url.origin !== DECISION_ORIGIN || url.username !== "" || url.password !== "" || url.search !== "" || url.hash !== "" || !/^\/decisions\/[^/]+$/u.test(url.pathname)) {
|
|
1673
|
+
return void 0;
|
|
1674
|
+
}
|
|
1675
|
+
return url.href;
|
|
1676
|
+
} catch {
|
|
1677
|
+
return void 0;
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
function decisionLink(label, decisionUrl) {
|
|
1681
|
+
const url = validDecisionUrl(decisionUrl);
|
|
1682
|
+
return url ? `\x1B]8;;${url}\x07\x1B[34;4m${label}\x1B[0m\x1B]8;;\x07` : label;
|
|
1683
|
+
}
|
|
1665
1684
|
function formatLabeled(labels, cap) {
|
|
1666
1685
|
if (labels === void 0) {
|
|
1667
1686
|
return "\u2014";
|
|
@@ -1675,13 +1694,15 @@ function formatLabeled(labels, cap) {
|
|
|
1675
1694
|
return `${labels.slice(0, cap).join(", ")} +${String(labels.length - cap)}`;
|
|
1676
1695
|
}
|
|
1677
1696
|
function formatTeammates(names, cap) {
|
|
1678
|
-
return formatLabeled(names, cap);
|
|
1697
|
+
return formatLabeled(names?.map(stripControlChars), cap);
|
|
1679
1698
|
}
|
|
1680
1699
|
function formatTeammatesWithArea(teammates, cap) {
|
|
1681
1700
|
return formatLabeled(
|
|
1682
1701
|
teammates?.map((t) => {
|
|
1683
|
-
const
|
|
1684
|
-
|
|
1702
|
+
const name = stripControlChars(t.name);
|
|
1703
|
+
const area = stripControlChars(t.area ?? "").trim();
|
|
1704
|
+
const label = area ? `${name} - ${area}` : name;
|
|
1705
|
+
return decisionLink(label, t.decisionUrl);
|
|
1685
1706
|
}),
|
|
1686
1707
|
cap
|
|
1687
1708
|
);
|
package/dist/statusline-main.js
CHANGED
|
@@ -136,7 +136,38 @@ import { fileURLToPath } from "url";
|
|
|
136
136
|
var PKG_NAME = "@primitive.ai/prim";
|
|
137
137
|
var NPX_FALLBACK = `npx --yes -p ${PKG_NAME}@latest`;
|
|
138
138
|
|
|
139
|
+
// src/lib/ansi.ts
|
|
140
|
+
function stripControlChars(text) {
|
|
141
|
+
let out = "";
|
|
142
|
+
for (const ch of text) {
|
|
143
|
+
const code = ch.codePointAt(0) ?? 0;
|
|
144
|
+
if (code > 31 && !(code >= 127 && code <= 159)) {
|
|
145
|
+
out += ch;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return out;
|
|
149
|
+
}
|
|
150
|
+
|
|
139
151
|
// src/lib/presence.ts
|
|
152
|
+
var DECISION_ORIGIN = "https://app.getprimitive.ai";
|
|
153
|
+
function validDecisionUrl(value) {
|
|
154
|
+
if (typeof value !== "string" || stripControlChars(value) !== value) {
|
|
155
|
+
return void 0;
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
const url = new URL(value);
|
|
159
|
+
if (url.origin !== DECISION_ORIGIN || url.username !== "" || url.password !== "" || url.search !== "" || url.hash !== "" || !/^\/decisions\/[^/]+$/u.test(url.pathname)) {
|
|
160
|
+
return void 0;
|
|
161
|
+
}
|
|
162
|
+
return url.href;
|
|
163
|
+
} catch {
|
|
164
|
+
return void 0;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function decisionLink(label, decisionUrl) {
|
|
168
|
+
const url = validDecisionUrl(decisionUrl);
|
|
169
|
+
return url ? `\x1B]8;;${url}\x07\x1B[34;4m${label}\x1B[0m\x1B]8;;\x07` : label;
|
|
170
|
+
}
|
|
140
171
|
function formatLabeled(labels, cap) {
|
|
141
172
|
if (labels === void 0) {
|
|
142
173
|
return "\u2014";
|
|
@@ -150,13 +181,15 @@ function formatLabeled(labels, cap) {
|
|
|
150
181
|
return `${labels.slice(0, cap).join(", ")} +${String(labels.length - cap)}`;
|
|
151
182
|
}
|
|
152
183
|
function formatTeammates(names, cap) {
|
|
153
|
-
return formatLabeled(names, cap);
|
|
184
|
+
return formatLabeled(names?.map(stripControlChars), cap);
|
|
154
185
|
}
|
|
155
186
|
function formatTeammatesWithArea(teammates, cap) {
|
|
156
187
|
return formatLabeled(
|
|
157
188
|
teammates?.map((t) => {
|
|
158
|
-
const
|
|
159
|
-
|
|
189
|
+
const name = stripControlChars(t.name);
|
|
190
|
+
const area = stripControlChars(t.area ?? "").trim();
|
|
191
|
+
const label = area ? `${name} - ${area}` : name;
|
|
192
|
+
return decisionLink(label, t.decisionUrl);
|
|
160
193
|
}),
|
|
161
194
|
cap
|
|
162
195
|
);
|
package/package.json
CHANGED