@primitive.ai/prim 0.1.0-alpha.42 → 0.1.0-alpha.43

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${label}\x1B]8;;\x07` : label;
1683
+ }
1665
1684
  function formatLabeled(labels, cap) {
1666
1685
  if (labels === void 0) {
1667
1686
  return "\u2014";
@@ -1681,7 +1700,8 @@ function formatTeammatesWithArea(teammates, cap) {
1681
1700
  return formatLabeled(
1682
1701
  teammates?.map((t) => {
1683
1702
  const area = t.area?.trim();
1684
- return area ? `${t.name} - ${area}` : t.name;
1703
+ const label = area ? `${t.name} - ${area}` : t.name;
1704
+ return decisionLink(label, t.decisionUrl);
1685
1705
  }),
1686
1706
  cap
1687
1707
  );
@@ -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${label}\x1B]8;;\x07` : label;
170
+ }
140
171
  function formatLabeled(labels, cap) {
141
172
  if (labels === void 0) {
142
173
  return "\u2014";
@@ -156,7 +187,8 @@ function formatTeammatesWithArea(teammates, cap) {
156
187
  return formatLabeled(
157
188
  teammates?.map((t) => {
158
189
  const area = t.area?.trim();
159
- return area ? `${t.name} - ${area}` : t.name;
190
+ const label = area ? `${t.name} - ${area}` : t.name;
191
+ return decisionLink(label, t.decisionUrl);
160
192
  }),
161
193
  cap
162
194
  );
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.43",
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",