unfairly 0.8.3 → 0.8.4

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.
Files changed (218) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/LICENSE +15 -0
  3. package/dist/auth/browser-flow.d.ts +0 -1
  4. package/dist/auth/browser-flow.js +5 -81
  5. package/dist/cmd/connect.d.ts +0 -1
  6. package/dist/cmd/connect.js +1 -164
  7. package/dist/cmd/init.d.ts +0 -1
  8. package/dist/cmd/init.js +2 -138
  9. package/dist/cmd/invite.d.ts +0 -1
  10. package/dist/cmd/invite.js +4 -73
  11. package/dist/cmd/join.d.ts +0 -1
  12. package/dist/cmd/join.js +1 -91
  13. package/dist/cmd/login.d.ts +0 -1
  14. package/dist/cmd/login.js +3 -41
  15. package/dist/cmd/mcp.d.ts +0 -1
  16. package/dist/cmd/mcp.js +2 -86
  17. package/dist/cmd/pace.d.ts +2 -2
  18. package/dist/cmd/pace.js +4 -426
  19. package/dist/cmd/parse-share-input.d.ts +0 -1
  20. package/dist/cmd/parse-share-input.js +1 -56
  21. package/dist/cmd/pull.d.ts +0 -1
  22. package/dist/cmd/pull.js +7 -143
  23. package/dist/cmd/push.d.ts +0 -1
  24. package/dist/cmd/push.js +1 -85
  25. package/dist/cmd/resolve.d.ts +0 -1
  26. package/dist/cmd/resolve.js +1 -35
  27. package/dist/cmd/setup.d.ts +0 -1
  28. package/dist/cmd/setup.js +2 -345
  29. package/dist/cmd/status-card.d.ts +0 -1
  30. package/dist/cmd/status-card.js +2 -156
  31. package/dist/cmd/status.d.ts +0 -1
  32. package/dist/cmd/status.js +3 -29
  33. package/dist/cmd/watch.d.ts +0 -1
  34. package/dist/cmd/watch.js +1 -26
  35. package/dist/hosts/claude-code.d.ts +0 -1
  36. package/dist/hosts/claude-code.js +3 -180
  37. package/dist/hosts/claude-desktop.d.ts +0 -1
  38. package/dist/hosts/claude-desktop.js +3 -222
  39. package/dist/hosts/codex.d.ts +0 -1
  40. package/dist/hosts/codex.js +20 -234
  41. package/dist/hosts/shell-env.d.ts +0 -1
  42. package/dist/hosts/shell-env.js +9 -81
  43. package/dist/index.d.ts +0 -1
  44. package/dist/index.js +14 -206
  45. package/dist/lib/client.d.ts +0 -1
  46. package/dist/lib/client.js +1 -58
  47. package/dist/lib/events/emit.d.ts +0 -1
  48. package/dist/lib/events/emit.js +1 -28
  49. package/dist/lib/index.d.ts +0 -1
  50. package/dist/lib/index.js +1 -11
  51. package/dist/lib/transport.d.ts +0 -1
  52. package/dist/lib/transport.js +1 -85
  53. package/dist/lib/types/index.d.ts +0 -1
  54. package/dist/lib/types/index.js +1 -50
  55. package/dist/lib/vault/checkpoint.d.ts +0 -1
  56. package/dist/lib/vault/checkpoint.js +1 -18
  57. package/dist/lib/vault/read.d.ts +0 -1
  58. package/dist/lib/vault/read.js +1 -9
  59. package/dist/lib/vault/search.d.ts +0 -1
  60. package/dist/lib/vault/search.js +1 -10
  61. package/dist/pace/device.d.ts +0 -1
  62. package/dist/pace/device.js +2 -149
  63. package/dist/pace/enroll.d.ts +0 -1
  64. package/dist/pace/enroll.js +2 -138
  65. package/dist/pace/identity.d.ts +0 -1
  66. package/dist/pace/identity.js +1 -17
  67. package/dist/pace/install.d.ts +0 -1
  68. package/dist/pace/install.js +4 -127
  69. package/dist/pace/local-evidence.d.ts +0 -1
  70. package/dist/pace/local-evidence.js +2 -228
  71. package/dist/pace/local-sessions.d.ts +0 -1
  72. package/dist/pace/local-sessions.js +1 -254
  73. package/dist/pace/managed-config.d.ts +0 -1
  74. package/dist/pace/managed-config.js +8 -102
  75. package/dist/pace/normalize.d.ts +0 -1
  76. package/dist/pace/normalize.js +1 -293
  77. package/dist/pace/repository.d.ts +0 -1
  78. package/dist/pace/repository.js +2 -120
  79. package/dist/pace/schema.d.ts +0 -1
  80. package/dist/pace/schema.js +1 -134
  81. package/dist/pace/session-summary.d.ts +0 -1
  82. package/dist/pace/session-summary.js +2 -140
  83. package/dist/pace/spool.d.ts +0 -1
  84. package/dist/pace/spool.js +8 -87
  85. package/dist/pace-runtime.d.ts +0 -1
  86. package/dist/pace-runtime.js +2 -67
  87. package/dist/sync/api.d.ts +0 -1
  88. package/dist/sync/api.js +1 -55
  89. package/dist/sync/autoloader.d.ts +0 -1
  90. package/dist/sync/autoloader.js +5 -127
  91. package/dist/sync/conflict.d.ts +0 -1
  92. package/dist/sync/conflict.js +5 -15
  93. package/dist/sync/credentials.d.ts +0 -1
  94. package/dist/sync/credentials.js +1 -51
  95. package/dist/sync/files.d.ts +0 -1
  96. package/dist/sync/files.js +1 -71
  97. package/dist/sync/manifest.d.ts +0 -1
  98. package/dist/sync/manifest.js +1 -37
  99. package/dist/sync/repo-root.d.ts +0 -1
  100. package/dist/sync/repo-root.js +1 -24
  101. package/dist/sync/stitch-claude-md.d.ts +0 -1
  102. package/dist/sync/stitch-claude-md.js +8 -48
  103. package/dist/ui/colors.d.ts +0 -1
  104. package/dist/ui/colors.js +1 -28
  105. package/dist/ui/screens.d.ts +0 -1
  106. package/dist/ui/screens.js +5 -135
  107. package/package.json +5 -12
  108. package/plugin/.cursor-plugin/plugin.json +0 -2
  109. package/plugin/.mcp.json +1 -1
  110. package/plugin/LICENSE +15 -0
  111. package/plugin/gemini/gemini-extension.json +1 -1
  112. package/plugin/mcp.json +1 -1
  113. package/plugin/plugin.json +0 -2
  114. package/plugin/runtime/unfairly-intelligence.mjs +14 -5512
  115. package/dist/auth/browser-flow.d.ts.map +0 -1
  116. package/dist/auth/browser-flow.js.map +0 -1
  117. package/dist/cmd/connect.d.ts.map +0 -1
  118. package/dist/cmd/connect.js.map +0 -1
  119. package/dist/cmd/init.d.ts.map +0 -1
  120. package/dist/cmd/init.js.map +0 -1
  121. package/dist/cmd/invite.d.ts.map +0 -1
  122. package/dist/cmd/invite.js.map +0 -1
  123. package/dist/cmd/join.d.ts.map +0 -1
  124. package/dist/cmd/join.js.map +0 -1
  125. package/dist/cmd/login.d.ts.map +0 -1
  126. package/dist/cmd/login.js.map +0 -1
  127. package/dist/cmd/mcp.d.ts.map +0 -1
  128. package/dist/cmd/mcp.js.map +0 -1
  129. package/dist/cmd/pace.d.ts.map +0 -1
  130. package/dist/cmd/pace.js.map +0 -1
  131. package/dist/cmd/parse-share-input.d.ts.map +0 -1
  132. package/dist/cmd/parse-share-input.js.map +0 -1
  133. package/dist/cmd/pull.d.ts.map +0 -1
  134. package/dist/cmd/pull.js.map +0 -1
  135. package/dist/cmd/push.d.ts.map +0 -1
  136. package/dist/cmd/push.js.map +0 -1
  137. package/dist/cmd/resolve.d.ts.map +0 -1
  138. package/dist/cmd/resolve.js.map +0 -1
  139. package/dist/cmd/setup.d.ts.map +0 -1
  140. package/dist/cmd/setup.js.map +0 -1
  141. package/dist/cmd/status-card.d.ts.map +0 -1
  142. package/dist/cmd/status-card.js.map +0 -1
  143. package/dist/cmd/status.d.ts.map +0 -1
  144. package/dist/cmd/status.js.map +0 -1
  145. package/dist/cmd/watch.d.ts.map +0 -1
  146. package/dist/cmd/watch.js.map +0 -1
  147. package/dist/hosts/claude-code.d.ts.map +0 -1
  148. package/dist/hosts/claude-code.js.map +0 -1
  149. package/dist/hosts/claude-desktop.d.ts.map +0 -1
  150. package/dist/hosts/claude-desktop.js.map +0 -1
  151. package/dist/hosts/codex.d.ts.map +0 -1
  152. package/dist/hosts/codex.js.map +0 -1
  153. package/dist/hosts/shell-env.d.ts.map +0 -1
  154. package/dist/hosts/shell-env.js.map +0 -1
  155. package/dist/index.d.ts.map +0 -1
  156. package/dist/index.js.map +0 -1
  157. package/dist/lib/client.d.ts.map +0 -1
  158. package/dist/lib/client.js.map +0 -1
  159. package/dist/lib/events/emit.d.ts.map +0 -1
  160. package/dist/lib/events/emit.js.map +0 -1
  161. package/dist/lib/index.d.ts.map +0 -1
  162. package/dist/lib/index.js.map +0 -1
  163. package/dist/lib/transport.d.ts.map +0 -1
  164. package/dist/lib/transport.js.map +0 -1
  165. package/dist/lib/types/index.d.ts.map +0 -1
  166. package/dist/lib/types/index.js.map +0 -1
  167. package/dist/lib/vault/checkpoint.d.ts.map +0 -1
  168. package/dist/lib/vault/checkpoint.js.map +0 -1
  169. package/dist/lib/vault/read.d.ts.map +0 -1
  170. package/dist/lib/vault/read.js.map +0 -1
  171. package/dist/lib/vault/search.d.ts.map +0 -1
  172. package/dist/lib/vault/search.js.map +0 -1
  173. package/dist/pace/device.d.ts.map +0 -1
  174. package/dist/pace/device.js.map +0 -1
  175. package/dist/pace/enroll.d.ts.map +0 -1
  176. package/dist/pace/enroll.js.map +0 -1
  177. package/dist/pace/identity.d.ts.map +0 -1
  178. package/dist/pace/identity.js.map +0 -1
  179. package/dist/pace/install.d.ts.map +0 -1
  180. package/dist/pace/install.js.map +0 -1
  181. package/dist/pace/local-evidence.d.ts.map +0 -1
  182. package/dist/pace/local-evidence.js.map +0 -1
  183. package/dist/pace/local-sessions.d.ts.map +0 -1
  184. package/dist/pace/local-sessions.js.map +0 -1
  185. package/dist/pace/managed-config.d.ts.map +0 -1
  186. package/dist/pace/managed-config.js.map +0 -1
  187. package/dist/pace/normalize.d.ts.map +0 -1
  188. package/dist/pace/normalize.js.map +0 -1
  189. package/dist/pace/repository.d.ts.map +0 -1
  190. package/dist/pace/repository.js.map +0 -1
  191. package/dist/pace/schema.d.ts.map +0 -1
  192. package/dist/pace/schema.js.map +0 -1
  193. package/dist/pace/session-summary.d.ts.map +0 -1
  194. package/dist/pace/session-summary.js.map +0 -1
  195. package/dist/pace/spool.d.ts.map +0 -1
  196. package/dist/pace/spool.js.map +0 -1
  197. package/dist/pace-runtime.d.ts.map +0 -1
  198. package/dist/pace-runtime.js.map +0 -1
  199. package/dist/sync/api.d.ts.map +0 -1
  200. package/dist/sync/api.js.map +0 -1
  201. package/dist/sync/autoloader.d.ts.map +0 -1
  202. package/dist/sync/autoloader.js.map +0 -1
  203. package/dist/sync/conflict.d.ts.map +0 -1
  204. package/dist/sync/conflict.js.map +0 -1
  205. package/dist/sync/credentials.d.ts.map +0 -1
  206. package/dist/sync/credentials.js.map +0 -1
  207. package/dist/sync/files.d.ts.map +0 -1
  208. package/dist/sync/files.js.map +0 -1
  209. package/dist/sync/manifest.d.ts.map +0 -1
  210. package/dist/sync/manifest.js.map +0 -1
  211. package/dist/sync/repo-root.d.ts.map +0 -1
  212. package/dist/sync/repo-root.js.map +0 -1
  213. package/dist/sync/stitch-claude-md.d.ts.map +0 -1
  214. package/dist/sync/stitch-claude-md.js.map +0 -1
  215. package/dist/ui/colors.d.ts.map +0 -1
  216. package/dist/ui/colors.js.map +0 -1
  217. package/dist/ui/screens.d.ts.map +0 -1
  218. package/dist/ui/screens.js.map +0 -1
@@ -1,138 +1,2 @@
1
- import fs from "node:fs/promises";
2
- import os from "node:os";
3
- import path from "node:path";
4
- import { z } from "zod";
5
- /**
6
- * No-terminal connection for the plugin. On a session start without a
7
- * collector credential, the plugin starts (or continues) a browser-approved
8
- * enrollment and hands the person a link. Once approved, it claims a
9
- * credential bound to this installation. Only these two small files are kept.
10
- */
11
- const CredentialSchema = z.object({ installation_id: z.string().uuid(), secret: z.string().regex(/^[A-Za-z0-9_-]{43}$/), org_id: z.string().uuid() }).strict();
12
- const PendingSchema = z.object({ device_code: z.string(), user_code: z.string(), verification_uri: z.string().url(), expires_at: z.string() }).strict();
13
- const dir = () => path.join(os.homedir(), ".unfairly", "pace");
14
- export const collectorCredentialPath = () => process.env.UNFAIRLY_PACE_COLLECTOR ?? path.join(dir(), "collector.json");
15
- const pendingPath = () => process.env.UNFAIRLY_PACE_ENROLLMENT ?? path.join(dir(), "enrollment.json");
16
- async function readJson(file, schema) {
17
- try {
18
- const parsed = schema.safeParse(JSON.parse(await fs.readFile(file, "utf8")));
19
- return parsed.success ? parsed.data : null;
20
- }
21
- catch {
22
- return null;
23
- }
24
- }
25
- async function writeJson(file, value) {
26
- await fs.mkdir(path.dirname(file), { recursive: true, mode: 0o700 });
27
- await fs.writeFile(file, `${JSON.stringify(value)}\n`, { encoding: "utf8", mode: 0o600 });
28
- }
29
- export const readCollectorCredential = () => readJson(collectorCredentialPath(), CredentialSchema);
30
- export function collectorAuthorization(credential) {
31
- return `Collector ${credential.installation_id}.${credential.secret}`;
32
- }
33
- /** One step of the enrollment state machine; safe to call on every session start. */
34
- export async function ensureEnrolled(input) {
35
- const existing = await readCollectorCredential();
36
- if (existing)
37
- return { status: "connected", orgId: existing.org_id };
38
- const call = input.fetchImpl ?? fetch;
39
- const now = input.now ?? new Date();
40
- const polled = await pollPendingEnrollment({ apiUrl: input.apiUrl, fetchImpl: call, now });
41
- if (polled.status === "connected" || polled.status === "waiting")
42
- return polled;
43
- // No live enrollment, or it was denied, expired, or claimed elsewhere: start over.
44
- const clients = input.clients.filter((client) => ["codex", "claude_code", "cursor", "gemini_cli"].includes(client));
45
- const response = await call(`${input.apiUrl}/api/ai-pace/enroll`, {
46
- method: "POST",
47
- headers: { "Content-Type": "application/json" },
48
- body: JSON.stringify({ installation_id: input.installationId, platform: input.platform, arch: input.arch, clients, ...(input.orgHint ? { org_hint: input.orgHint } : {}) }),
49
- });
50
- if (!response.ok)
51
- return { status: "unavailable" };
52
- const started = await response.json();
53
- await writeJson(pendingPath(), { device_code: started.device_code, user_code: started.user_code, verification_uri: started.verification_uri, expires_at: new Date(now.getTime() + started.expires_in * 1000).toISOString() });
54
- return { status: "waiting", link: started.verification_uri, code: started.user_code };
55
- }
56
- /**
57
- * Checks the enrollment this machine is waiting on, without starting a new
58
- * one. Claims and stores the credential when the person has approved it.
59
- */
60
- export async function pollPendingEnrollment(input) {
61
- const existing = await readCollectorCredential();
62
- if (existing)
63
- return { status: "connected", orgId: existing.org_id };
64
- const pending = await readJson(pendingPath(), PendingSchema);
65
- if (!pending || Date.parse(pending.expires_at) <= (input.now ?? new Date()).getTime())
66
- return { status: "none" };
67
- const call = input.fetchImpl ?? fetch;
68
- const response = await call(`${input.apiUrl}/api/ai-pace/enroll/poll`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ device_code: pending.device_code }) });
69
- const body = await response.json().catch(() => ({}));
70
- if (body.status === "approved" && body.installation_id && body.secret && body.org_id) {
71
- await writeJson(collectorCredentialPath(), CredentialSchema.parse({ installation_id: body.installation_id, secret: body.secret, org_id: body.org_id }));
72
- await fs.unlink(pendingPath()).catch(() => undefined);
73
- return { status: "connected", orgId: body.org_id };
74
- }
75
- if (body.status === "pending")
76
- return { status: "waiting", link: pending.verification_uri, code: pending.user_code };
77
- return { status: "none" };
78
- }
79
- /**
80
- * Keeps polling after the connect link is shown, so approving in the browser
81
- * connects this machine within seconds instead of at the next session start.
82
- * Stops when connected, when the enrollment ends, or at the deadline.
83
- */
84
- export async function awaitApproval(input) {
85
- const clock = input.clock ?? Date.now;
86
- const sleep = input.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
87
- const deadline = clock() + (input.deadlineMs ?? 16 * 60_000);
88
- while (clock() < deadline) {
89
- const state = await pollPendingEnrollment({ apiUrl: input.apiUrl, fetchImpl: input.fetchImpl, now: new Date(clock()) }).catch(() => ({ status: "waiting" }));
90
- if (state.status === "connected")
91
- return true;
92
- if (state.status === "none")
93
- return false;
94
- await sleep(input.intervalMs ?? 5_000);
95
- }
96
- return false;
97
- }
98
- const approvalLockPath = () => path.join(path.dirname(pendingPath()), "approval-wait.json");
99
- /** At most one background poller per machine: sessions often start in bursts. */
100
- export async function acquireApprovalLock(now = Date.now()) {
101
- const file = approvalLockPath();
102
- try {
103
- const lock = JSON.parse(await fs.readFile(file, "utf8"));
104
- const fresh = typeof lock.started_at === "number" && now - lock.started_at < 17 * 60_000;
105
- let alive = false;
106
- try {
107
- if (typeof lock.pid === "number") {
108
- process.kill(lock.pid, 0);
109
- alive = true;
110
- }
111
- }
112
- catch {
113
- alive = false;
114
- }
115
- if (fresh && alive)
116
- return false;
117
- }
118
- catch {
119
- // No lock yet.
120
- }
121
- await writeJson(file, { pid: process.pid, started_at: now });
122
- return true;
123
- }
124
- export const releaseApprovalLock = () => fs.unlink(approvalLockPath()).catch(() => undefined);
125
- /** What a SessionStart hook prints: a visible message for the person plus context so the assistant can repeat the link. */
126
- export function sessionStartOutput(state, hookEventName = "SessionStart") {
127
- if (state.status !== "waiting")
128
- return "";
129
- const message = `Connect Unfairly Intelligence to your team: open ${state.link} and approve code ${state.code}. It shares session metadata only, never prompts or code.`;
130
- return JSON.stringify({
131
- systemMessage: message,
132
- hookSpecificOutput: {
133
- hookEventName,
134
- additionalContext: `The Unfairly Intelligence plugin is installed but this machine is not connected yet. At a natural point, tell the user once: "${message}" Do not repeat it after they connect.`,
135
- },
136
- });
137
- }
138
- //# sourceMappingURL=enroll.js.map
1
+ import c from"node:fs/promises";import x from"node:os";import l from"node:path";import{z as a}from"zod";const f=a.object({installation_id:a.string().uuid(),secret:a.string().regex(/^[A-Za-z0-9_-]{43}$/),org_id:a.string().uuid()}).strict(),S=a.object({device_code:a.string(),user_code:a.string(),verification_uri:a.string().url(),expires_at:a.string()}).strict(),g=()=>l.join(x.homedir(),".unfairly","pace"),_=()=>process.env.UNFAIRLY_PACE_COLLECTOR??l.join(g(),"collector.json"),d=()=>process.env.UNFAIRLY_PACE_ENROLLMENT??l.join(g(),"enrollment.json");async function m(e,n){try{const t=n.safeParse(JSON.parse(await c.readFile(e,"utf8")));return t.success?t.data:null}catch{return null}}async function u(e,n){await c.mkdir(l.dirname(e),{recursive:!0,mode:448}),await c.writeFile(e,`${JSON.stringify(n)}
2
+ `,{encoding:"utf8",mode:384})}const h=()=>m(_(),f);function C(e){return`Collector ${e.installation_id}.${e.secret}`}async function P(e){const n=await h();if(n)return{status:"connected",orgId:n.org_id};const t=e.fetchImpl??fetch,r=e.now??new Date,i=await w({apiUrl:e.apiUrl,fetchImpl:t,now:r});if(i.status==="connected"||i.status==="waiting")return i;const o=e.clients.filter(v=>["codex","claude_code","cursor","gemini_cli"].includes(v)),p=await t(`${e.apiUrl}/api/ai-pace/enroll`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({installation_id:e.installationId,platform:e.platform,arch:e.arch,clients:o,...e.orgHint?{org_hint:e.orgHint}:{}})});if(!p.ok)return{status:"unavailable"};const s=await p.json();return await u(d(),{device_code:s.device_code,user_code:s.user_code,verification_uri:s.verification_uri,expires_at:new Date(r.getTime()+s.expires_in*1e3).toISOString()}),{status:"waiting",link:s.verification_uri,code:s.user_code}}async function w(e){const n=await h();if(n)return{status:"connected",orgId:n.org_id};const t=await m(d(),S);if(!t||Date.parse(t.expires_at)<=(e.now??new Date).getTime())return{status:"none"};const o=await(await(e.fetchImpl??fetch)(`${e.apiUrl}/api/ai-pace/enroll/poll`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_code:t.device_code})})).json().catch(()=>({}));return o.status==="approved"&&o.installation_id&&o.secret&&o.org_id?(await u(_(),f.parse({installation_id:o.installation_id,secret:o.secret,org_id:o.org_id})),await c.unlink(d()).catch(()=>{}),{status:"connected",orgId:o.org_id}):o.status==="pending"?{status:"waiting",link:t.verification_uri,code:t.user_code}:{status:"none"}}async function A(e){const n=e.clock??Date.now,t=e.sleep??(i=>new Promise(o=>setTimeout(o,i))),r=n()+(e.deadlineMs??16*6e4);for(;n()<r;){const i=await w({apiUrl:e.apiUrl,fetchImpl:e.fetchImpl,now:new Date(n())}).catch(()=>({status:"waiting"}));if(i.status==="connected")return!0;if(i.status==="none")return!1;await t(e.intervalMs??5e3)}return!1}const y=()=>l.join(l.dirname(d()),"approval-wait.json");async function N(e=Date.now()){const n=y();try{const t=JSON.parse(await c.readFile(n,"utf8")),r=typeof t.started_at=="number"&&e-t.started_at<17*6e4;let i=!1;try{typeof t.pid=="number"&&(process.kill(t.pid,0),i=!0)}catch{i=!1}if(r&&i)return!1}catch{}return await u(n,{pid:process.pid,started_at:e}),!0}const T=()=>c.unlink(y()).catch(()=>{});function U(e,n="SessionStart"){if(e.status!=="waiting")return"";const t=`Connect Unfairly Intelligence to your team: open ${e.link} and approve code ${e.code}. It shares session metadata only, never prompts or code.`;return JSON.stringify({systemMessage:t,hookSpecificOutput:{hookEventName:n,additionalContext:`The Unfairly Intelligence plugin is installed but this machine is not connected yet. At a natural point, tell the user once: "${t}" Do not repeat it after they connect.`}})}export{N as acquireApprovalLock,A as awaitApproval,C as collectorAuthorization,_ as collectorCredentialPath,P as ensureEnrolled,w as pollPendingEnrollment,h as readCollectorCredential,T as releaseApprovalLock,U as sessionStartOutput};
@@ -7,4 +7,3 @@ export declare function deterministicWorkflowId(input: {
7
7
  source: string;
8
8
  sessionId: string;
9
9
  }): string;
10
- //# sourceMappingURL=identity.d.ts.map
@@ -1,17 +1 @@
1
- import { createHash } from "node:crypto";
2
- /**
3
- * Produces a stable, opaque UUID for one AI workflow without exposing the raw
4
- * installation or session identifiers in downstream joins.
5
- */
6
- export function deterministicWorkflowId(input) {
7
- const bytes = createHash("sha256")
8
- .update(`${input.installationId}\u0000${input.source}\u0000${input.sessionId}`, "utf8")
9
- .digest()
10
- .subarray(0, 16);
11
- // Mark the deterministic hash as a name-based UUID and set the RFC variant.
12
- bytes[6] = (bytes[6] & 0x0f) | 0x50;
13
- bytes[8] = (bytes[8] & 0x3f) | 0x80;
14
- const hex = bytes.toString("hex");
15
- return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
16
- }
17
- //# sourceMappingURL=identity.js.map
1
+ import{createHash as i}from"node:crypto";function r(t){const e=i("sha256").update(`${t.installationId}\0${t.source}\0${t.sessionId}`,"utf8").digest().subarray(0,16);e[6]=e[6]&15|80,e[8]=e[8]&63|128;const s=e.toString("hex");return`${s.slice(0,8)}-${s.slice(8,12)}-${s.slice(12,16)}-${s.slice(16,20)}-${s.slice(20)}`}export{r as deterministicWorkflowId};
@@ -25,4 +25,3 @@ export type IntelligenceInstallerDeps = {
25
25
  }) => Promise<string>;
26
26
  };
27
27
  export declare function installIntelligence(deps: IntelligenceInstallerDeps): Promise<IntelligenceInstallResult>;
28
- //# sourceMappingURL=install.d.ts.map
@@ -1,127 +1,4 @@
1
- import { spawnSync } from "node:child_process";
2
- import fs from "node:fs/promises";
3
- import os from "node:os";
4
- import path from "node:path";
5
- import { fileURLToPath } from "node:url";
6
- const MARKETPLACE_NAME = "unfairly-intelligence";
7
- function defaultPluginSource() {
8
- const moduleDir = path.dirname(fileURLToPath(import.meta.url));
9
- const packageRoot = path.resolve(moduleDir, "../..");
10
- return path.join(packageRoot, "plugin");
11
- }
12
- function defaultRun(command, args) {
13
- const result = spawnSync(command, args, { encoding: "utf8", timeout: 30_000 });
14
- const detail = `${result.stdout || result.stderr || result.error?.message || ""}`.trim().slice(0, 500);
15
- return { ok: !result.error && result.status === 0, detail };
16
- }
17
- async function defaultInstallRuntime(input) {
18
- await fs.mkdir(input.prefix, { recursive: true, mode: 0o700 });
19
- const result = spawnSync("npm", [
20
- "install", "--prefix", input.prefix, `unfairly@${input.version}`,
21
- "--omit=dev", "--no-audit", "--no-fund",
22
- ], { encoding: "utf8", timeout: 120_000 });
23
- if (result.error || result.status !== 0) {
24
- throw new Error(`Could not install the Unfairly Intelligence runtime: ${`${result.stderr || result.error?.message || "unknown error"}`.trim()}`);
25
- }
26
- return path.join(input.prefix, "node_modules", "unfairly", "dist", "index.js");
27
- }
28
- async function requireDirectory(directory) {
29
- const stat = await fs.stat(directory).catch(() => null);
30
- if (!stat?.isDirectory())
31
- throw new Error(`Bundled Unfairly Intelligence plugin not found at ${directory}`);
32
- }
33
- async function writeMarketplace(root, pluginSource) {
34
- const pluginTarget = path.join(root, "plugins", "ai-pace");
35
- await fs.rm(pluginTarget, { recursive: true, force: true });
36
- await fs.mkdir(path.dirname(pluginTarget), { recursive: true, mode: 0o700 });
37
- await fs.cp(pluginSource, pluginTarget, { recursive: true });
38
- await fs.mkdir(path.join(root, ".agents", "plugins"), { recursive: true, mode: 0o700 });
39
- await fs.mkdir(path.join(root, ".claude-plugin"), { recursive: true, mode: 0o700 });
40
- const codexMarketplace = {
41
- name: MARKETPLACE_NAME,
42
- interface: { displayName: "Unfairly Intelligence" },
43
- plugins: [{
44
- name: "ai-pace",
45
- source: { source: "local", path: "./plugins/ai-pace" },
46
- policy: { installation: "AVAILABLE", authentication: "ON_INSTALL" },
47
- category: "Productivity",
48
- }],
49
- };
50
- const claudeMarketplace = {
51
- name: MARKETPLACE_NAME,
52
- description: "Unfairly Intelligence for AI-enabled teams.",
53
- owner: { name: "Unfairly", url: "https://unfairly.ai" },
54
- plugins: [{
55
- name: "ai-pace",
56
- description: "Content-free workflow evidence and reusable company playbooks.",
57
- version: "0.1.0",
58
- source: "./plugins/ai-pace",
59
- category: "productivity",
60
- }],
61
- };
62
- await fs.writeFile(path.join(root, ".agents", "plugins", "marketplace.json"), `${JSON.stringify(codexMarketplace, null, 2)}\n`, { mode: 0o600 });
63
- await fs.writeFile(path.join(root, ".claude-plugin", "marketplace.json"), `${JSON.stringify(claudeMarketplace, null, 2)}\n`, { mode: 0o600 });
64
- return pluginTarget;
65
- }
66
- function requireCommand(result, label) {
67
- if (!result.ok)
68
- throw new Error(`${label} failed${result.detail ? `: ${result.detail}` : ""}`);
69
- }
70
- export async function installIntelligence(deps) {
71
- const home = deps.home ?? os.homedir();
72
- const root = path.join(home, ".unfairly");
73
- const runtimeRoot = path.join(root, "runtime", deps.version);
74
- const marketplaceRoot = path.join(root, "marketplace");
75
- const pluginSource = deps.pluginSource ?? defaultPluginSource();
76
- const run = deps.run ?? defaultRun;
77
- await requireDirectory(pluginSource);
78
- const runtimeEntrypoint = await (deps.installRuntime ?? defaultInstallRuntime)({ version: deps.version, prefix: runtimeRoot });
79
- const pluginTarget = await writeMarketplace(marketplaceRoot, pluginSource);
80
- const runtimePointer = path.join(root, "runtime", "current.json");
81
- await fs.mkdir(path.dirname(runtimePointer), { recursive: true, mode: 0o700 });
82
- await fs.writeFile(runtimePointer, `${JSON.stringify({ version: deps.version, node: process.execPath, entrypoint: runtimeEntrypoint }, null, 2)}\n`, { mode: 0o600 });
83
- const activations = [];
84
- const ownedPaths = [runtimeRoot, marketplaceRoot, runtimePointer];
85
- const installed = new Map(deps.clients.map((client) => [client.client, client.installed]));
86
- if (installed.get("codex")) {
87
- run("codex", ["plugin", "remove", `ai-pace@${MARKETPLACE_NAME}`, "--json"]);
88
- run("codex", ["plugin", "marketplace", "remove", MARKETPLACE_NAME]);
89
- requireCommand(run("codex", ["plugin", "marketplace", "add", marketplaceRoot, "--json"]), "Codex marketplace activation");
90
- requireCommand(run("codex", ["plugin", "add", `ai-pace@${MARKETPLACE_NAME}`, "--json"]), "Codex plugin activation");
91
- activations.push({ client: "codex", status: "activated", detail: "marketplace plugin installed" });
92
- }
93
- if (installed.get("claude_code")) {
94
- run("claude", ["plugin", "uninstall", `ai-pace@${MARKETPLACE_NAME}`, "--scope", "user"]);
95
- run("claude", ["plugin", "marketplace", "remove", MARKETPLACE_NAME, "--scope", "user"]);
96
- requireCommand(run("claude", ["plugin", "marketplace", "add", marketplaceRoot, "--scope", "user"]), "Claude marketplace activation");
97
- requireCommand(run("claude", ["plugin", "install", `ai-pace@${MARKETPLACE_NAME}`, "--scope", "user"]), "Claude plugin activation");
98
- activations.push({ client: "claude_code", status: "activated", detail: "marketplace plugin installed" });
99
- }
100
- if (installed.get("cursor")) {
101
- const cursorTarget = path.join(home, ".cursor", "plugins", "local", "ai-pace");
102
- await fs.rm(cursorTarget, { recursive: true, force: true });
103
- await fs.mkdir(path.dirname(cursorTarget), { recursive: true, mode: 0o700 });
104
- await fs.cp(pluginTarget, cursorTarget, { recursive: true });
105
- ownedPaths.push(cursorTarget);
106
- activations.push({ client: "cursor", status: "activated", detail: "local plugin installed; reload Cursor to activate" });
107
- }
108
- if (installed.get("gemini_cli")) {
109
- const geminiTarget = path.join(home, ".gemini", "extensions", "unfairly-intelligence");
110
- await fs.rm(geminiTarget, { recursive: true, force: true });
111
- await fs.mkdir(path.dirname(geminiTarget), { recursive: true, mode: 0o700 });
112
- await fs.cp(path.join(pluginTarget, "gemini"), geminiTarget, { recursive: true });
113
- const hooksPath = path.join(geminiTarget, "hooks", "hooks.json");
114
- const hooks = await fs.readFile(hooksPath, "utf8");
115
- const stableCommand = `${JSON.stringify(process.execPath)} ${JSON.stringify(runtimeEntrypoint)} intelligence capture`;
116
- await fs.writeFile(hooksPath, hooks.replaceAll("unfairly pace capture", stableCommand), { mode: 0o600 });
117
- ownedPaths.push(geminiTarget);
118
- activations.push({ client: "gemini_cli", status: "activated", detail: "Gemini extension installed" });
119
- }
120
- for (const client of deps.clients) {
121
- if (client.installed && !activations.some((activation) => activation.client === client.client)) {
122
- activations.push({ client: client.client, status: "skipped", detail: "no passive lifecycle hook surface" });
123
- }
124
- }
125
- return { runtime_entrypoint: runtimeEntrypoint, marketplace_root: marketplaceRoot, activations, owned_paths: ownedPaths };
126
- }
127
- //# sourceMappingURL=install.js.map
1
+ import{spawnSync as y}from"node:child_process";import t from"node:fs/promises";import x from"node:os";import i from"node:path";import{fileURLToPath as $}from"node:url";const c="unfairly-intelligence";function _(){const e=i.dirname($(import.meta.url)),n=i.resolve(e,"../..");return i.join(n,"plugin")}function C(e,n){const r=y(e,n,{encoding:"utf8",timeout:3e4}),u=`${r.stdout||r.stderr||r.error?.message||""}`.trim().slice(0,500);return{ok:!r.error&&r.status===0,detail:u}}async function S(e){await t.mkdir(e.prefix,{recursive:!0,mode:448});const n=y("npm",["install","--prefix",e.prefix,`unfairly@${e.version}`,"--omit=dev","--no-audit","--no-fund"],{encoding:"utf8",timeout:12e4});if(n.error||n.status!==0)throw new Error(`Could not install the Unfairly Intelligence runtime: ${`${n.stderr||n.error?.message||"unknown error"}`.trim()}`);return i.join(e.prefix,"node_modules","unfairly","dist","index.js")}async function A(e){if(!(await t.stat(e).catch(()=>null))?.isDirectory())throw new Error(`Bundled Unfairly Intelligence plugin not found at ${e}`)}async function I(e,n){const r=i.join(e,"plugins","ai-pace");await t.rm(r,{recursive:!0,force:!0}),await t.mkdir(i.dirname(r),{recursive:!0,mode:448}),await t.cp(n,r,{recursive:!0}),await t.mkdir(i.join(e,".agents","plugins"),{recursive:!0,mode:448}),await t.mkdir(i.join(e,".claude-plugin"),{recursive:!0,mode:448});const u={name:c,interface:{displayName:"Unfairly Intelligence"},plugins:[{name:"ai-pace",source:{source:"local",path:"./plugins/ai-pace"},policy:{installation:"AVAILABLE",authentication:"ON_INSTALL"},category:"Productivity"}]},l={name:c,description:"Unfairly Intelligence for AI-enabled teams.",owner:{name:"Unfairly",url:"https://unfairly.ai"},plugins:[{name:"ai-pace",description:"Content-free workflow evidence and reusable company playbooks.",version:"0.1.0",source:"./plugins/ai-pace",category:"productivity"}]};return await t.writeFile(i.join(e,".agents","plugins","marketplace.json"),`${JSON.stringify(u,null,2)}
2
+ `,{mode:384}),await t.writeFile(i.join(e,".claude-plugin","marketplace.json"),`${JSON.stringify(l,null,2)}
3
+ `,{mode:384}),r}function m(e,n){if(!e.ok)throw new Error(`${n} failed${e.detail?`: ${e.detail}`:""}`)}async function L(e){const n=e.home??x.homedir(),r=i.join(n,".unfairly"),u=i.join(r,"runtime",e.version),l=i.join(r,"marketplace"),v=e.pluginSource??_(),o=e.run??C;await A(v);const g=await(e.installRuntime??S)({version:e.version,prefix:u}),k=await I(l,v),f=i.join(r,"runtime","current.json");await t.mkdir(i.dirname(f),{recursive:!0,mode:448}),await t.writeFile(f,`${JSON.stringify({version:e.version,node:process.execPath,entrypoint:g},null,2)}
4
+ `,{mode:384});const s=[],w=[u,l,f],d=new Map(e.clients.map(a=>[a.client,a.installed]));if(d.get("codex")&&(o("codex",["plugin","remove",`ai-pace@${c}`,"--json"]),o("codex",["plugin","marketplace","remove",c]),m(o("codex",["plugin","marketplace","add",l,"--json"]),"Codex marketplace activation"),m(o("codex",["plugin","add",`ai-pace@${c}`,"--json"]),"Codex plugin activation"),s.push({client:"codex",status:"activated",detail:"marketplace plugin installed"})),d.get("claude_code")&&(o("claude",["plugin","uninstall",`ai-pace@${c}`,"--scope","user"]),o("claude",["plugin","marketplace","remove",c,"--scope","user"]),m(o("claude",["plugin","marketplace","add",l,"--scope","user"]),"Claude marketplace activation"),m(o("claude",["plugin","install",`ai-pace@${c}`,"--scope","user"]),"Claude plugin activation"),s.push({client:"claude_code",status:"activated",detail:"marketplace plugin installed"})),d.get("cursor")){const a=i.join(n,".cursor","plugins","local","ai-pace");await t.rm(a,{recursive:!0,force:!0}),await t.mkdir(i.dirname(a),{recursive:!0,mode:448}),await t.cp(k,a,{recursive:!0}),w.push(a),s.push({client:"cursor",status:"activated",detail:"local plugin installed; reload Cursor to activate"})}if(d.get("gemini_cli")){const a=i.join(n,".gemini","extensions","unfairly-intelligence");await t.rm(a,{recursive:!0,force:!0}),await t.mkdir(i.dirname(a),{recursive:!0,mode:448}),await t.cp(i.join(k,"gemini"),a,{recursive:!0});const p=i.join(a,"hooks","hooks.json"),h=await t.readFile(p,"utf8"),j=`${JSON.stringify(process.execPath)} ${JSON.stringify(g)} intelligence capture`;await t.writeFile(p,h.replaceAll("unfairly pace capture",j),{mode:384}),w.push(a),s.push({client:"gemini_cli",status:"activated",detail:"Gemini extension installed"})}for(const a of e.clients)a.installed&&!s.some(p=>p.client===a.client)&&s.push({client:a.client,status:"skipped",detail:"no passive lifecycle hook surface"});return{runtime_entrypoint:g,marketplace_root:l,activations:s,owned_paths:w}}export{L as installIntelligence};
@@ -72,4 +72,3 @@ export declare function scanLocalEvidence(input?: {
72
72
  events: PaceEvent[];
73
73
  baselineRoots: string[];
74
74
  }>;
75
- //# sourceMappingURL=local-evidence.d.ts.map
@@ -1,228 +1,2 @@
1
- import { createHash, randomUUID } from "node:crypto";
2
- import fs from "node:fs/promises";
3
- import os from "node:os";
4
- import path from "node:path";
5
- import { z } from "zod";
6
- import { PaceEventSchema } from "./schema.js";
7
- import { deterministicWorkflowId } from "./identity.js";
8
- const sourceAppSchema = z.enum(["chatgpt", "claude_desktop", "other"]);
9
- const evidenceRootSchema = z.object({
10
- path: z.string().trim().min(1),
11
- label: z.string().trim().min(1).max(128),
12
- kind: z.enum(["output", "app_state"]),
13
- source_app: sourceAppSchema.optional(),
14
- extensions: z.array(z.string().trim().min(1).max(32)).max(100).optional(),
15
- }).strict().superRefine((root, context) => {
16
- if (root.kind === "app_state" && !root.source_app) {
17
- context.addIssue({ code: z.ZodIssueCode.custom, path: ["source_app"], message: "app_state roots require source_app" });
18
- }
19
- });
20
- export const localEvidencePolicySchema = z.object({
21
- version: z.literal(1),
22
- include_file_names: z.boolean().default(false),
23
- scan_interval_seconds: z.number().int().min(15).max(3600).default(60),
24
- max_files_per_root: z.number().int().min(1).max(50_000).default(5_000),
25
- roots: z.array(evidenceRootSchema).max(50),
26
- }).strict();
27
- const DEFAULT_OUTPUT_EXTENSIONS = new Set([
28
- ".doc", ".docx", ".gdoc", ".md", ".pdf", ".ppt", ".pptx", ".gslides",
29
- ".xls", ".xlsx", ".gsheet", ".csv", ".tsv", ".txt", ".rtf", ".html",
30
- ".js", ".jsx", ".ts", ".tsx", ".py", ".go", ".rs", ".java", ".sql",
31
- ]);
32
- const IGNORED_DIRECTORIES = new Set([".git", "node_modules", ".next", "dist", "build"]);
33
- export function localEvidencePolicyPath() {
34
- return process.env.UNFAIRLY_PACE_EVIDENCE_POLICY ?? path.join(os.homedir(), ".unfairly", "pace", "evidence-policy.json");
35
- }
36
- export function localEvidenceStatePath() {
37
- return process.env.UNFAIRLY_PACE_EVIDENCE_STATE ?? path.join(os.homedir(), ".unfairly", "pace", "evidence-state.json");
38
- }
39
- function expandHome(value) {
40
- if (value === "~")
41
- return os.homedir();
42
- if (value.startsWith(`~${path.sep}`))
43
- return path.join(os.homedir(), value.slice(2));
44
- return path.resolve(value);
45
- }
46
- export async function readLocalEvidencePolicy(policyPath = localEvidencePolicyPath()) {
47
- const raw = await fs.readFile(policyPath, "utf8").catch((error) => {
48
- if (error.code === "ENOENT")
49
- throw new Error(`Unfairly Intelligence local evidence policy not found: ${policyPath}`);
50
- throw error;
51
- });
52
- const policy = localEvidencePolicySchema.parse(JSON.parse(raw));
53
- const labels = new Set();
54
- for (const root of policy.roots) {
55
- if (labels.has(root.label))
56
- throw new Error(`Unfairly Intelligence evidence root labels must be unique: ${root.label}`);
57
- const resolved = expandHome(root.path);
58
- if (resolved === path.parse(resolved).root || resolved === path.resolve(os.homedir())) {
59
- throw new Error(`Unfairly Intelligence evidence roots must be narrower than a filesystem or home directory: ${root.path}`);
60
- }
61
- labels.add(root.label);
62
- }
63
- return policy;
64
- }
65
- function fingerprint(label, relativePath) {
66
- return createHash("sha256").update(label).update("\0").update(relativePath).digest("hex");
67
- }
68
- function artifactType(extension) {
69
- if ([".ppt", ".pptx", ".gslides"].includes(extension))
70
- return "deck";
71
- if ([".xls", ".xlsx", ".gsheet", ".csv", ".tsv"].includes(extension))
72
- return "spreadsheet";
73
- if ([".js", ".jsx", ".ts", ".tsx", ".py", ".go", ".rs", ".java", ".sql"].includes(extension))
74
- return "code";
75
- if ([".doc", ".docx", ".gdoc", ".md", ".pdf", ".txt", ".rtf", ".html"].includes(extension))
76
- return "document";
77
- return "other";
78
- }
79
- function localSession(root, occurredAt) {
80
- return fingerprint(root.label, occurredAt.toISOString().slice(0, 10)).slice(0, 32);
81
- }
82
- function filesChangedBucket(value) {
83
- if (value <= 1)
84
- return "one";
85
- if (value <= 5)
86
- return "two_to_five";
87
- if (value <= 20)
88
- return "six_to_twenty";
89
- return "over_twenty";
90
- }
91
- async function walkRoot(root, maxFiles) {
92
- const absoluteRoot = expandHome(root.path);
93
- const allowed = root.extensions?.length
94
- ? new Set(root.extensions.map((item) => item.startsWith(".") ? item.toLowerCase() : `.${item.toLowerCase()}`))
95
- : DEFAULT_OUTPUT_EXTENSIONS;
96
- const facts = [];
97
- const pending = [absoluteRoot];
98
- while (pending.length && facts.length < maxFiles) {
99
- const directory = pending.pop();
100
- let entries;
101
- try {
102
- entries = await fs.readdir(directory, { withFileTypes: true });
103
- }
104
- catch {
105
- continue;
106
- }
107
- for (const entry of entries) {
108
- if (facts.length >= maxFiles)
109
- break;
110
- if (entry.name.startsWith(".") || IGNORED_DIRECTORIES.has(entry.name))
111
- continue;
112
- const fullPath = path.join(directory, entry.name);
113
- if (entry.isDirectory()) {
114
- pending.push(fullPath);
115
- continue;
116
- }
117
- if (!entry.isFile())
118
- continue;
119
- const extension = path.extname(entry.name).toLowerCase();
120
- if (root.kind === "output" && !allowed.has(extension))
121
- continue;
122
- const relativePath = path.relative(absoluteRoot, fullPath);
123
- const stat = await fs.stat(fullPath).catch(() => null);
124
- if (!stat?.isFile())
125
- continue;
126
- facts.push({ fingerprint: fingerprint(root.label, relativePath), relativePath, extension, size: stat.size, mtimeMs: stat.mtimeMs });
127
- }
128
- }
129
- return facts;
130
- }
131
- async function readState() {
132
- try {
133
- const parsed = JSON.parse(await fs.readFile(localEvidenceStatePath(), "utf8"));
134
- if (parsed.version === 1 && Array.isArray(parsed.initialized_roots) && parsed.files && typeof parsed.files === "object")
135
- return parsed;
136
- }
137
- catch {
138
- // A missing or invalid state file is safely rebuilt as a baseline.
139
- }
140
- return { version: 1, initialized_roots: [], files: {} };
141
- }
142
- async function writeState(state) {
143
- const statePath = localEvidenceStatePath();
144
- await fs.mkdir(path.dirname(statePath), { recursive: true, mode: 0o700 });
145
- await fs.writeFile(statePath, `${JSON.stringify(state, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
146
- await fs.chmod(statePath, 0o600).catch(() => undefined);
147
- }
148
- function outputEvent(input) {
149
- const sourceApp = input.root.source_app ?? "other";
150
- const occurredAt = new Date(input.fact.mtimeMs);
151
- const sessionId = sourceApp !== "other" ? localSession(input.root, occurredAt) : undefined;
152
- const workflowId = input.installationId && sessionId
153
- ? deterministicWorkflowId({ installationId: input.installationId, source: sourceApp, sessionId })
154
- : undefined;
155
- return PaceEventSchema.parse({
156
- schema_version: 3,
157
- collection_policy_version: "metadata-v3",
158
- event_id: randomUUID(),
159
- installation_id: input.installationId,
160
- occurred_at: occurredAt.toISOString(),
161
- source: "local_filesystem",
162
- event_type: input.previous ? "artifact.modified" : "artifact.created",
163
- evidence_level: "exact",
164
- session_id: sessionId,
165
- workflow_id: workflowId,
166
- artifact_type: artifactType(input.fact.extension),
167
- attributes: {
168
- client_event: "local_output",
169
- local_signal: "approved_output",
170
- },
171
- });
172
- }
173
- function appActivityEvent(input) {
174
- const latest = Math.max(...input.facts.map((fact) => fact.mtimeMs));
175
- const occurredAt = new Date(latest);
176
- const sessionId = localSession(input.root, occurredAt);
177
- const workflowId = input.installationId
178
- ? deterministicWorkflowId({ installationId: input.installationId, source: input.root.source_app, sessionId })
179
- : undefined;
180
- return PaceEventSchema.parse({
181
- schema_version: 3,
182
- collection_policy_version: "metadata-v3",
183
- event_id: randomUUID(),
184
- installation_id: input.installationId,
185
- occurred_at: occurredAt.toISOString(),
186
- source: input.root.source_app,
187
- event_type: "desktop.activity",
188
- evidence_level: "correlated",
189
- session_id: sessionId,
190
- workflow_id: workflowId,
191
- attributes: {
192
- client_event: "local_activity",
193
- files_changed_bucket: filesChangedBucket(input.facts.length),
194
- local_signal: "app_state_change",
195
- },
196
- });
197
- }
198
- export async function scanLocalEvidence(input = {}) {
199
- const policy = await readLocalEvidencePolicy(input.policyPath);
200
- const state = await readState();
201
- const initialized = new Set(state.initialized_roots);
202
- const nextFiles = {};
203
- const events = [];
204
- const baselineRoots = [];
205
- for (const root of policy.roots) {
206
- const facts = await walkRoot(root, policy.max_files_per_root);
207
- const isBaseline = !initialized.has(root.label);
208
- if (isBaseline)
209
- baselineRoots.push(root.label);
210
- const changed = [];
211
- for (const fact of facts) {
212
- const previous = state.files[fact.fingerprint];
213
- nextFiles[fact.fingerprint] = { size: fact.size, mtime_ms: fact.mtimeMs };
214
- if (!isBaseline && (!previous || previous.size !== fact.size || previous.mtime_ms !== fact.mtimeMs))
215
- changed.push({ fact, previous });
216
- }
217
- if (root.kind === "output") {
218
- events.push(...changed.map(({ fact, previous }) => outputEvent({ root, fact, previous, installationId: input.installationId })));
219
- }
220
- else if (changed.length) {
221
- events.push(appActivityEvent({ root, facts: changed.map(({ fact }) => fact), installationId: input.installationId }));
222
- }
223
- initialized.add(root.label);
224
- }
225
- await writeState({ version: 1, initialized_roots: [...initialized], files: nextFiles });
226
- return { events, baselineRoots };
227
- }
228
- //# sourceMappingURL=local-evidence.js.map
1
+ import{createHash as b,randomUUID as v}from"node:crypto";import m from"node:fs/promises";import h from"node:os";import c from"node:path";import{z as a}from"zod";import{PaceEventSchema as w}from"./schema.js";import{deterministicWorkflowId as y}from"./identity.js";const S=a.enum(["chatgpt","claude_desktop","other"]),k=a.object({path:a.string().trim().min(1),label:a.string().trim().min(1).max(128),kind:a.enum(["output","app_state"]),source_app:S.optional(),extensions:a.array(a.string().trim().min(1).max(32)).max(100).optional()}).strict().superRefine((e,t)=>{e.kind==="app_state"&&!e.source_app&&t.addIssue({code:a.ZodIssueCode.custom,path:["source_app"],message:"app_state roots require source_app"})}),j=a.object({version:a.literal(1),include_file_names:a.boolean().default(!1),scan_interval_seconds:a.number().int().min(15).max(3600).default(60),max_files_per_root:a.number().int().min(1).max(5e4).default(5e3),roots:a.array(k).max(50)}).strict(),A=new Set([".doc",".docx",".gdoc",".md",".pdf",".ppt",".pptx",".gslides",".xls",".xlsx",".gsheet",".csv",".tsv",".txt",".rtf",".html",".js",".jsx",".ts",".tsx",".py",".go",".rs",".java",".sql"]),P=new Set([".git","node_modules",".next","dist","build"]);function z(){return process.env.UNFAIRLY_PACE_EVIDENCE_POLICY??c.join(h.homedir(),".unfairly","pace","evidence-policy.json")}function g(){return process.env.UNFAIRLY_PACE_EVIDENCE_STATE??c.join(h.homedir(),".unfairly","pace","evidence-state.json")}function x(e){return e==="~"?h.homedir():e.startsWith(`~${c.sep}`)?c.join(h.homedir(),e.slice(2)):c.resolve(e)}async function N(e=z()){const t=await m.readFile(e,"utf8").catch(o=>{throw o.code==="ENOENT"?new Error(`Unfairly Intelligence local evidence policy not found: ${e}`):o}),n=j.parse(JSON.parse(t)),i=new Set;for(const o of n.roots){if(i.has(o.label))throw new Error(`Unfairly Intelligence evidence root labels must be unique: ${o.label}`);const r=x(o.path);if(r===c.parse(r).root||r===c.resolve(h.homedir()))throw new Error(`Unfairly Intelligence evidence roots must be narrower than a filesystem or home directory: ${o.path}`);i.add(o.label)}return n}function I(e,t){return b("sha256").update(e).update("\0").update(t).digest("hex")}function O(e){return[".ppt",".pptx",".gslides"].includes(e)?"deck":[".xls",".xlsx",".gsheet",".csv",".tsv"].includes(e)?"spreadsheet":[".js",".jsx",".ts",".tsx",".py",".go",".rs",".java",".sql"].includes(e)?"code":[".doc",".docx",".gdoc",".md",".pdf",".txt",".rtf",".html"].includes(e)?"document":"other"}function E(e,t){return I(e.label,t.toISOString().slice(0,10)).slice(0,32)}function C(e){return e<=1?"one":e<=5?"two_to_five":e<=20?"six_to_twenty":"over_twenty"}async function R(e,t){const n=x(e.path),i=e.extensions?.length?new Set(e.extensions.map(f=>f.startsWith(".")?f.toLowerCase():`.${f.toLowerCase()}`)):A,o=[],r=[n];for(;r.length&&o.length<t;){const f=r.pop();let l;try{l=await m.readdir(f,{withFileTypes:!0})}catch{continue}for(const u of l){if(o.length>=t)break;if(u.name.startsWith(".")||P.has(u.name))continue;const _=c.join(f,u.name);if(u.isDirectory()){r.push(_);continue}if(!u.isFile())continue;const p=c.extname(u.name).toLowerCase();if(e.kind==="output"&&!i.has(p))continue;const s=c.relative(n,_),d=await m.stat(_).catch(()=>null);d?.isFile()&&o.push({fingerprint:I(e.label,s),relativePath:s,extension:p,size:d.size,mtimeMs:d.mtimeMs})}}return o}async function T(){try{const e=JSON.parse(await m.readFile(g(),"utf8"));if(e.version===1&&Array.isArray(e.initialized_roots)&&e.files&&typeof e.files=="object")return e}catch{}return{version:1,initialized_roots:[],files:{}}}async function U(e){const t=g();await m.mkdir(c.dirname(t),{recursive:!0,mode:448}),await m.writeFile(t,`${JSON.stringify(e,null,2)}
2
+ `,{encoding:"utf8",mode:384}),await m.chmod(t,384).catch(()=>{})}function D(e){const t=e.root.source_app??"other",n=new Date(e.fact.mtimeMs),i=t!=="other"?E(e.root,n):void 0,o=e.installationId&&i?y({installationId:e.installationId,source:t,sessionId:i}):void 0;return w.parse({schema_version:3,collection_policy_version:"metadata-v3",event_id:v(),installation_id:e.installationId,occurred_at:n.toISOString(),source:"local_filesystem",event_type:e.previous?"artifact.modified":"artifact.created",evidence_level:"exact",session_id:i,workflow_id:o,artifact_type:O(e.fact.extension),attributes:{client_event:"local_output",local_signal:"approved_output"}})}function F(e){const t=Math.max(...e.facts.map(r=>r.mtimeMs)),n=new Date(t),i=E(e.root,n),o=e.installationId?y({installationId:e.installationId,source:e.root.source_app,sessionId:i}):void 0;return w.parse({schema_version:3,collection_policy_version:"metadata-v3",event_id:v(),installation_id:e.installationId,occurred_at:n.toISOString(),source:e.root.source_app,event_type:"desktop.activity",evidence_level:"correlated",session_id:i,workflow_id:o,attributes:{client_event:"local_activity",files_changed_bucket:C(e.facts.length),local_signal:"app_state_change"}})}async function B(e={}){const t=await N(e.policyPath),n=await T(),i=new Set(n.initialized_roots),o={},r=[],f=[];for(const l of t.roots){const u=await R(l,t.max_files_per_root),_=!i.has(l.label);_&&f.push(l.label);const p=[];for(const s of u){const d=n.files[s.fingerprint];o[s.fingerprint]={size:s.size,mtime_ms:s.mtimeMs},!_&&(!d||d.size!==s.size||d.mtime_ms!==s.mtimeMs)&&p.push({fact:s,previous:d})}l.kind==="output"?r.push(...p.map(({fact:s,previous:d})=>D({root:l,fact:s,previous:d,installationId:e.installationId}))):p.length&&r.push(F({root:l,facts:p.map(({fact:s})=>s),installationId:e.installationId})),i.add(l.label)}return await U({version:1,initialized_roots:[...i],files:o}),{events:r,baselineRoots:f}}export{z as localEvidencePolicyPath,j as localEvidencePolicySchema,g as localEvidenceStatePath,N as readLocalEvidencePolicy,B as scanLocalEvidence};
@@ -45,4 +45,3 @@ export declare function collectLocalSessions(options: {
45
45
  since: number;
46
46
  home?: string;
47
47
  }): Promise<LocalSessionSummary[]>;
48
- //# sourceMappingURL=local-sessions.d.ts.map