@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);
@@ -19,7 +19,7 @@ import {
19
19
  acknowledgeDecisionFeedback,
20
20
  leaseDecisionFeedback,
21
21
  renderFeedback
22
- } from "../chunk-NDIK4FBF.js";
22
+ } from "../chunk-UISU3A7W.js";
23
23
  import {
24
24
  getOrCreateWorkspaceId
25
25
  } from "../chunk-IMAIBPUC.js";
@@ -8,7 +8,7 @@ import {
8
8
  acknowledgeDecisionFeedback,
9
9
  leaseDecisionFeedback,
10
10
  renderFeedback
11
- } from "../chunk-NDIK4FBF.js";
11
+ } from "../chunk-UISU3A7W.js";
12
12
  import {
13
13
  getOrCreateWorkspaceId
14
14
  } from "../chunk-IMAIBPUC.js";
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-NDIK4FBF.js";
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 area = t.area?.trim();
1684
- return area ? `${t.name} - ${area}` : t.name;
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
  );
@@ -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 area = t.area?.trim();
159
- return area ? `${t.name} - ${area}` : t.name;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primitive.ai/prim",
3
- "version": "0.1.0-alpha.42",
3
+ "version": "0.1.0-alpha.44",
4
4
  "description": "CLI for Primitive's decision graph — passive decision capture, conflict gate, and team presence",
5
5
  "type": "module",
6
6
  "license": "MIT",