unfairly 0.8.2 → 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 +10 -0
  2. package/LICENSE +15 -0
  3. package/README.md +3 -1
  4. package/dist/auth/browser-flow.d.ts +0 -1
  5. package/dist/auth/browser-flow.js +5 -81
  6. package/dist/cmd/connect.d.ts +0 -1
  7. package/dist/cmd/connect.js +1 -164
  8. package/dist/cmd/init.d.ts +0 -1
  9. package/dist/cmd/init.js +2 -138
  10. package/dist/cmd/invite.d.ts +0 -1
  11. package/dist/cmd/invite.js +4 -73
  12. package/dist/cmd/join.d.ts +0 -1
  13. package/dist/cmd/join.js +1 -91
  14. package/dist/cmd/login.d.ts +0 -1
  15. package/dist/cmd/login.js +3 -41
  16. package/dist/cmd/mcp.d.ts +0 -1
  17. package/dist/cmd/mcp.js +2 -86
  18. package/dist/cmd/pace.d.ts +23 -2
  19. package/dist/cmd/pace.js +4 -393
  20. package/dist/cmd/parse-share-input.d.ts +0 -1
  21. package/dist/cmd/parse-share-input.js +1 -56
  22. package/dist/cmd/pull.d.ts +0 -1
  23. package/dist/cmd/pull.js +7 -143
  24. package/dist/cmd/push.d.ts +0 -1
  25. package/dist/cmd/push.js +1 -85
  26. package/dist/cmd/resolve.d.ts +0 -1
  27. package/dist/cmd/resolve.js +1 -35
  28. package/dist/cmd/setup.d.ts +0 -1
  29. package/dist/cmd/setup.js +2 -345
  30. package/dist/cmd/status-card.d.ts +0 -1
  31. package/dist/cmd/status-card.js +2 -156
  32. package/dist/cmd/status.d.ts +0 -1
  33. package/dist/cmd/status.js +3 -29
  34. package/dist/cmd/watch.d.ts +0 -1
  35. package/dist/cmd/watch.js +1 -26
  36. package/dist/hosts/claude-code.d.ts +0 -1
  37. package/dist/hosts/claude-code.js +3 -180
  38. package/dist/hosts/claude-desktop.d.ts +0 -1
  39. package/dist/hosts/claude-desktop.js +3 -222
  40. package/dist/hosts/codex.d.ts +0 -1
  41. package/dist/hosts/codex.js +20 -234
  42. package/dist/hosts/shell-env.d.ts +0 -1
  43. package/dist/hosts/shell-env.js +9 -81
  44. package/dist/index.d.ts +0 -1
  45. package/dist/index.js +14 -206
  46. package/dist/lib/client.d.ts +0 -1
  47. package/dist/lib/client.js +1 -58
  48. package/dist/lib/events/emit.d.ts +0 -1
  49. package/dist/lib/events/emit.js +1 -28
  50. package/dist/lib/index.d.ts +0 -1
  51. package/dist/lib/index.js +1 -11
  52. package/dist/lib/transport.d.ts +0 -1
  53. package/dist/lib/transport.js +1 -85
  54. package/dist/lib/types/index.d.ts +0 -1
  55. package/dist/lib/types/index.js +1 -50
  56. package/dist/lib/vault/checkpoint.d.ts +0 -1
  57. package/dist/lib/vault/checkpoint.js +1 -18
  58. package/dist/lib/vault/read.d.ts +0 -1
  59. package/dist/lib/vault/read.js +1 -9
  60. package/dist/lib/vault/search.d.ts +0 -1
  61. package/dist/lib/vault/search.js +1 -10
  62. package/dist/pace/device.d.ts +3 -1
  63. package/dist/pace/device.js +2 -148
  64. package/dist/pace/enroll.d.ts +79 -0
  65. package/dist/pace/enroll.js +2 -0
  66. package/dist/pace/identity.d.ts +0 -1
  67. package/dist/pace/identity.js +1 -17
  68. package/dist/pace/install.d.ts +0 -1
  69. package/dist/pace/install.js +4 -127
  70. package/dist/pace/local-evidence.d.ts +0 -1
  71. package/dist/pace/local-evidence.js +2 -228
  72. package/dist/pace/local-sessions.d.ts +0 -1
  73. package/dist/pace/local-sessions.js +1 -254
  74. package/dist/pace/managed-config.d.ts +0 -1
  75. package/dist/pace/managed-config.js +8 -102
  76. package/dist/pace/normalize.d.ts +0 -1
  77. package/dist/pace/normalize.js +1 -293
  78. package/dist/pace/repository.d.ts +0 -1
  79. package/dist/pace/repository.js +2 -120
  80. package/dist/pace/schema.d.ts +0 -1
  81. package/dist/pace/schema.js +1 -134
  82. package/dist/pace/session-summary.d.ts +0 -1
  83. package/dist/pace/session-summary.js +2 -140
  84. package/dist/pace/spool.d.ts +0 -1
  85. package/dist/pace/spool.js +8 -87
  86. package/dist/pace-runtime.d.ts +1 -0
  87. package/dist/pace-runtime.js +2 -0
  88. package/dist/sync/api.d.ts +0 -1
  89. package/dist/sync/api.js +1 -55
  90. package/dist/sync/autoloader.d.ts +0 -1
  91. package/dist/sync/autoloader.js +5 -127
  92. package/dist/sync/conflict.d.ts +0 -1
  93. package/dist/sync/conflict.js +5 -15
  94. package/dist/sync/credentials.d.ts +0 -1
  95. package/dist/sync/credentials.js +1 -51
  96. package/dist/sync/files.d.ts +0 -1
  97. package/dist/sync/files.js +1 -71
  98. package/dist/sync/manifest.d.ts +0 -1
  99. package/dist/sync/manifest.js +1 -37
  100. package/dist/sync/repo-root.d.ts +0 -1
  101. package/dist/sync/repo-root.js +1 -24
  102. package/dist/sync/stitch-claude-md.d.ts +0 -1
  103. package/dist/sync/stitch-claude-md.js +8 -48
  104. package/dist/ui/colors.d.ts +0 -1
  105. package/dist/ui/colors.js +1 -28
  106. package/dist/ui/screens.d.ts +0 -1
  107. package/dist/ui/screens.js +5 -135
  108. package/package.json +8 -14
  109. package/plugin/.cursor-plugin/plugin.json +0 -2
  110. package/plugin/.mcp.json +1 -1
  111. package/plugin/LICENSE +15 -0
  112. package/plugin/README.md +3 -7
  113. package/plugin/gemini/gemini-extension.json +1 -1
  114. package/plugin/mcp.json +1 -1
  115. package/plugin/plugin.json +0 -2
  116. package/plugin/runtime/unfairly-intelligence.mjs +15 -0
  117. package/plugin/scripts/capture.cjs +30 -8
  118. package/plugin/skills/find-company-playbook/SKILL.md +1 -1
  119. package/dist/auth/browser-flow.d.ts.map +0 -1
  120. package/dist/auth/browser-flow.js.map +0 -1
  121. package/dist/cmd/connect.d.ts.map +0 -1
  122. package/dist/cmd/connect.js.map +0 -1
  123. package/dist/cmd/init.d.ts.map +0 -1
  124. package/dist/cmd/init.js.map +0 -1
  125. package/dist/cmd/invite.d.ts.map +0 -1
  126. package/dist/cmd/invite.js.map +0 -1
  127. package/dist/cmd/join.d.ts.map +0 -1
  128. package/dist/cmd/join.js.map +0 -1
  129. package/dist/cmd/login.d.ts.map +0 -1
  130. package/dist/cmd/login.js.map +0 -1
  131. package/dist/cmd/mcp.d.ts.map +0 -1
  132. package/dist/cmd/mcp.js.map +0 -1
  133. package/dist/cmd/pace.d.ts.map +0 -1
  134. package/dist/cmd/pace.js.map +0 -1
  135. package/dist/cmd/parse-share-input.d.ts.map +0 -1
  136. package/dist/cmd/parse-share-input.js.map +0 -1
  137. package/dist/cmd/pull.d.ts.map +0 -1
  138. package/dist/cmd/pull.js.map +0 -1
  139. package/dist/cmd/push.d.ts.map +0 -1
  140. package/dist/cmd/push.js.map +0 -1
  141. package/dist/cmd/resolve.d.ts.map +0 -1
  142. package/dist/cmd/resolve.js.map +0 -1
  143. package/dist/cmd/setup.d.ts.map +0 -1
  144. package/dist/cmd/setup.js.map +0 -1
  145. package/dist/cmd/status-card.d.ts.map +0 -1
  146. package/dist/cmd/status-card.js.map +0 -1
  147. package/dist/cmd/status.d.ts.map +0 -1
  148. package/dist/cmd/status.js.map +0 -1
  149. package/dist/cmd/watch.d.ts.map +0 -1
  150. package/dist/cmd/watch.js.map +0 -1
  151. package/dist/hosts/claude-code.d.ts.map +0 -1
  152. package/dist/hosts/claude-code.js.map +0 -1
  153. package/dist/hosts/claude-desktop.d.ts.map +0 -1
  154. package/dist/hosts/claude-desktop.js.map +0 -1
  155. package/dist/hosts/codex.d.ts.map +0 -1
  156. package/dist/hosts/codex.js.map +0 -1
  157. package/dist/hosts/shell-env.d.ts.map +0 -1
  158. package/dist/hosts/shell-env.js.map +0 -1
  159. package/dist/index.d.ts.map +0 -1
  160. package/dist/index.js.map +0 -1
  161. package/dist/lib/client.d.ts.map +0 -1
  162. package/dist/lib/client.js.map +0 -1
  163. package/dist/lib/events/emit.d.ts.map +0 -1
  164. package/dist/lib/events/emit.js.map +0 -1
  165. package/dist/lib/index.d.ts.map +0 -1
  166. package/dist/lib/index.js.map +0 -1
  167. package/dist/lib/transport.d.ts.map +0 -1
  168. package/dist/lib/transport.js.map +0 -1
  169. package/dist/lib/types/index.d.ts.map +0 -1
  170. package/dist/lib/types/index.js.map +0 -1
  171. package/dist/lib/vault/checkpoint.d.ts.map +0 -1
  172. package/dist/lib/vault/checkpoint.js.map +0 -1
  173. package/dist/lib/vault/read.d.ts.map +0 -1
  174. package/dist/lib/vault/read.js.map +0 -1
  175. package/dist/lib/vault/search.d.ts.map +0 -1
  176. package/dist/lib/vault/search.js.map +0 -1
  177. package/dist/pace/device.d.ts.map +0 -1
  178. package/dist/pace/device.js.map +0 -1
  179. package/dist/pace/identity.d.ts.map +0 -1
  180. package/dist/pace/identity.js.map +0 -1
  181. package/dist/pace/install.d.ts.map +0 -1
  182. package/dist/pace/install.js.map +0 -1
  183. package/dist/pace/local-evidence.d.ts.map +0 -1
  184. package/dist/pace/local-evidence.js.map +0 -1
  185. package/dist/pace/local-sessions.d.ts.map +0 -1
  186. package/dist/pace/local-sessions.js.map +0 -1
  187. package/dist/pace/managed-config.d.ts.map +0 -1
  188. package/dist/pace/managed-config.js.map +0 -1
  189. package/dist/pace/normalize.d.ts.map +0 -1
  190. package/dist/pace/normalize.js.map +0 -1
  191. package/dist/pace/repository.d.ts.map +0 -1
  192. package/dist/pace/repository.js.map +0 -1
  193. package/dist/pace/schema.d.ts.map +0 -1
  194. package/dist/pace/schema.js.map +0 -1
  195. package/dist/pace/session-summary.d.ts.map +0 -1
  196. package/dist/pace/session-summary.js.map +0 -1
  197. package/dist/pace/spool.d.ts.map +0 -1
  198. package/dist/pace/spool.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,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
@@ -1,254 +1 @@
1
- import fs from "node:fs/promises";
2
- import { createReadStream } from "node:fs";
3
- import os from "node:os";
4
- import path from "node:path";
5
- import readline from "node:readline";
6
- const RUN_SKILL_TOOL = /^mcp__.+__(run_skill|get_playbook)$/;
7
- const emptyTokens = () => ({ input: 0, output: 0, cacheRead: 0, cacheWrite: 0 });
8
- function topKey(counts) {
9
- let best = null;
10
- for (const [key, value] of Object.entries(counts))
11
- if (best === null || value > counts[best])
12
- best = key;
13
- return best;
14
- }
15
- async function* jsonLines(file) {
16
- const lines = readline.createInterface({ input: createReadStream(file, { encoding: "utf8" }), crlfDelay: Infinity });
17
- for await (const line of lines) {
18
- if (!line)
19
- continue;
20
- try {
21
- const parsed = JSON.parse(line);
22
- if (parsed && typeof parsed === "object")
23
- yield parsed;
24
- }
25
- catch {
26
- // A partially written trailing line is normal for a live session.
27
- }
28
- }
29
- }
30
- const DENIAL = /doesn't want to proceed|user denied|permission denied|was rejected|not allowed/i;
31
- function asRecord(value) {
32
- return value && typeof value === "object" ? value : {};
33
- }
34
- function num(value) {
35
- return typeof value === "number" && Number.isFinite(value) ? value : 0;
36
- }
37
- export async function readClaudeCodeSession(file) {
38
- const models = {};
39
- const branches = {};
40
- const skills = new Set();
41
- const tokens = emptyTokens();
42
- const seenMessages = new Set();
43
- let startedAt = null;
44
- let endedAt = null;
45
- let cwd = null;
46
- let sessionId = path.basename(file, ".jsonl");
47
- let userTurns = 0;
48
- let toolCalls = 0;
49
- let toolErrors = 0;
50
- let approvalDenials = 0;
51
- const delegated = file.includes(`${path.sep}subagents${path.sep}`);
52
- for await (const line of jsonLines(file)) {
53
- const ts = typeof line.timestamp === "string" ? line.timestamp : null;
54
- if (ts) {
55
- startedAt ??= ts;
56
- endedAt = ts;
57
- }
58
- // Subagent transcripts repeat their parent's sessionId; keep the file's own id so the two never collide.
59
- if (typeof line.sessionId === "string" && !delegated)
60
- sessionId = line.sessionId;
61
- if (typeof line.cwd === "string")
62
- cwd ??= line.cwd;
63
- if (typeof line.gitBranch === "string" && line.gitBranch && line.gitBranch !== "HEAD")
64
- branches[line.gitBranch] = (branches[line.gitBranch] ?? 0) + 1;
65
- const message = asRecord(line.message);
66
- if (line.type === "user") {
67
- const content = message.content;
68
- if (typeof content === "string")
69
- userTurns += 1;
70
- else if (Array.isArray(content)) {
71
- for (const block of content.map(asRecord)) {
72
- if (block.type === "text")
73
- userTurns += 1;
74
- if (block.type === "tool_result" && block.is_error === true) {
75
- toolErrors += 1;
76
- // Classified in memory and immediately discarded; only the count survives.
77
- if (DENIAL.test(JSON.stringify(block.content ?? "").slice(0, 400)))
78
- approvalDenials += 1;
79
- }
80
- }
81
- }
82
- }
83
- if (line.type === "assistant") {
84
- const model = typeof message.model === "string" ? message.model : null;
85
- if (!model || model === "<synthetic>")
86
- continue;
87
- for (const block of Array.isArray(message.content) ? message.content.map(asRecord) : []) {
88
- if (block.type !== "tool_use")
89
- continue;
90
- toolCalls += 1;
91
- const input = asRecord(block.input);
92
- const name = typeof block.name === "string" ? block.name : "";
93
- const skill = name === "Skill" ? input.skill : RUN_SKILL_TOOL.test(name) ? input.name ?? input.skill ?? input.slug : undefined;
94
- if (typeof skill === "string" && skill.trim())
95
- skills.add(skill.trim().toLowerCase().slice(0, 120));
96
- }
97
- // Claude Code writes one line per content block and repeats the message's
98
- // usage on each; count each API message once.
99
- const messageId = typeof message.id === "string" ? message.id : null;
100
- if (messageId && seenMessages.has(messageId))
101
- continue;
102
- if (messageId)
103
- seenMessages.add(messageId);
104
- models[model] = (models[model] ?? 0) + 1;
105
- const usage = asRecord(message.usage);
106
- tokens.input += num(usage.input_tokens);
107
- tokens.output += num(usage.output_tokens);
108
- tokens.cacheRead += num(usage.cache_read_input_tokens);
109
- tokens.cacheWrite += num(usage.cache_creation_input_tokens);
110
- }
111
- }
112
- if (!startedAt || !endedAt || !Object.keys(models).length)
113
- return null;
114
- return {
115
- sessionId,
116
- client: "claude_code",
117
- delegated,
118
- launchedBy: null,
119
- startedAt,
120
- endedAt,
121
- models,
122
- primaryModel: topKey(models),
123
- tokens,
124
- userTurns,
125
- toolCalls,
126
- toolErrors,
127
- approvalDenials,
128
- cwd,
129
- branch: topKey(branches),
130
- skills: [...skills],
131
- };
132
- }
133
- export async function readCodexSession(file) {
134
- const models = {};
135
- let tokens = emptyTokens();
136
- let startedAt = null;
137
- let endedAt = null;
138
- let cwd = null;
139
- let branch = null;
140
- let sessionId = path.basename(file, ".jsonl");
141
- let delegated = false;
142
- let launchedBy = null;
143
- let userTurns = 0;
144
- let toolCalls = 0;
145
- let toolErrors = 0;
146
- const skills = new Set();
147
- for await (const line of jsonLines(file)) {
148
- const ts = typeof line.timestamp === "string" ? line.timestamp : null;
149
- if (ts) {
150
- startedAt ??= ts;
151
- endedAt = ts;
152
- }
153
- const payload = asRecord(line.payload);
154
- if (line.type === "response_item" && payload.type === "function_call" && typeof payload.name === "string" && RUN_SKILL_TOOL.test(payload.name)) {
155
- try {
156
- const args = asRecord(JSON.parse(String(payload.arguments ?? "{}")));
157
- const skill = args.name ?? args.skill ?? args.slug;
158
- if (typeof skill === "string" && skill.trim())
159
- skills.add(skill.trim().toLowerCase().slice(0, 120));
160
- }
161
- catch {
162
- // Arguments are discarded; only a recognizable skill name is kept.
163
- }
164
- }
165
- if (line.type === "session_meta") {
166
- if (typeof payload.id === "string")
167
- sessionId = payload.id;
168
- if (typeof payload.cwd === "string")
169
- cwd = payload.cwd;
170
- const git = asRecord(payload.git);
171
- if (typeof git.branch === "string" && git.branch && git.branch !== "HEAD")
172
- branch = git.branch;
173
- if (payload.originator === "Claude Code")
174
- launchedBy = "claude_code";
175
- if (payload.source && typeof payload.source === "object")
176
- delegated = true;
177
- }
178
- if (line.type === "turn_context" && typeof payload.model === "string")
179
- models[payload.model] = (models[payload.model] ?? 0) + 1;
180
- if (line.type === "event_msg" && payload.type === "user_message")
181
- userTurns += 1;
182
- if (line.type === "event_msg" && payload.type === "token_count") {
183
- // Codex reports a cumulative total; the last one wins. Input includes the cached share.
184
- const total = asRecord(asRecord(payload.info).total_token_usage);
185
- if (Object.keys(total).length) {
186
- const cached = num(total.cached_input_tokens);
187
- tokens = { input: Math.max(0, num(total.input_tokens) - cached), output: num(total.output_tokens), cacheRead: cached, cacheWrite: num(total.cache_write_input_tokens) };
188
- }
189
- }
190
- if (line.type === "response_item" && (payload.type === "function_call" || payload.type === "custom_tool_call"))
191
- toolCalls += 1;
192
- if (line.type === "event_msg" && payload.type === "exec_command_end" && num(payload.exit_code) !== 0)
193
- toolErrors += 1;
194
- }
195
- if (!startedAt || !endedAt || !Object.keys(models).length)
196
- return null;
197
- return {
198
- sessionId,
199
- client: "codex",
200
- delegated: delegated || launchedBy !== null,
201
- launchedBy,
202
- startedAt,
203
- endedAt,
204
- models,
205
- primaryModel: topKey(models),
206
- tokens,
207
- userTurns,
208
- toolCalls,
209
- toolErrors,
210
- approvalDenials: 0,
211
- cwd,
212
- branch,
213
- skills: [...skills],
214
- };
215
- }
216
- async function listJsonl(root, maxDepth, since) {
217
- const found = [];
218
- const walk = async (dir, depth) => {
219
- let entries;
220
- try {
221
- entries = await fs.readdir(dir, { withFileTypes: true });
222
- }
223
- catch {
224
- return;
225
- }
226
- for (const entry of entries) {
227
- const full = path.join(dir, entry.name);
228
- if (entry.isDirectory() && depth < maxDepth)
229
- await walk(full, depth + 1);
230
- else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
231
- const stat = await fs.stat(full).catch(() => null);
232
- if (stat && stat.mtimeMs >= since)
233
- found.push(full);
234
- }
235
- }
236
- };
237
- await walk(root, 0);
238
- return found;
239
- }
240
- /** Sessions whose transcript changed at or after `since` (epoch ms). */
241
- export async function collectLocalSessions(options) {
242
- const home = options.home ?? os.homedir();
243
- const since = options.since;
244
- const [claudeFiles, codexFiles] = await Promise.all([
245
- listJsonl(path.join(home, ".claude", "projects"), 4, since),
246
- listJsonl(path.join(home, ".codex", "sessions"), 4, since),
247
- ]);
248
- const results = await Promise.all([
249
- ...claudeFiles.map((file) => readClaudeCodeSession(file).catch(() => null)),
250
- ...codexFiles.map((file) => readCodexSession(file).catch(() => null)),
251
- ]);
252
- return results.filter((item) => item !== null);
253
- }
254
- //# sourceMappingURL=local-sessions.js.map
1
+ import L from"node:fs/promises";import{createReadStream as E}from"node:fs";import F from"node:os";import k from"node:path";import M from"node:readline";const B=/^mcp__.+__(run_skill|get_playbook)$/,D=()=>({input:0,output:0,cacheRead:0,cacheWrite:0});function I(e){let n=null;for(const[l,i]of Object.entries(e))(n===null||i>e[n])&&(n=l);return n}async function*N(e){const n=M.createInterface({input:E(e,{encoding:"utf8"}),crlfDelay:1/0});for await(const l of n)if(l)try{const i=JSON.parse(l);i&&typeof i=="object"&&(yield i)}catch{}}const T=/doesn't want to proceed|user denied|permission denied|was rejected|not allowed/i;function d(e){return e&&typeof e=="object"?e:{}}function h(e){return typeof e=="number"&&Number.isFinite(e)?e:0}async function J(e){const n={},l={},i=new Set,r=D(),m=new Set;let c=null,y=null,f=null,p=k.basename(e,".jsonl"),g=0,w=0,b=0,j=0;const u=e.includes(`${k.sep}subagents${k.sep}`);for await(const o of N(e)){const t=typeof o.timestamp=="string"?o.timestamp:null;t&&(c??=t,y=t),typeof o.sessionId=="string"&&!u&&(p=o.sessionId),typeof o.cwd=="string"&&(f??=o.cwd),typeof o.gitBranch=="string"&&o.gitBranch&&o.gitBranch!=="HEAD"&&(l[o.gitBranch]=(l[o.gitBranch]??0)+1);const s=d(o.message);if(o.type==="user"){const a=s.content;if(typeof a=="string")g+=1;else if(Array.isArray(a))for(const _ of a.map(d))_.type==="text"&&(g+=1),_.type==="tool_result"&&_.is_error===!0&&(b+=1,T.test(JSON.stringify(_.content??"").slice(0,400))&&(j+=1))}if(o.type==="assistant"){const a=typeof s.model=="string"?s.model:null;if(!a||a==="<synthetic>")continue;for(const x of Array.isArray(s.content)?s.content.map(d):[]){if(x.type!=="tool_use")continue;w+=1;const A=d(x.input),O=typeof x.name=="string"?x.name:"",C=O==="Skill"?A.skill:B.test(O)?A.name??A.skill??A.slug:void 0;typeof C=="string"&&C.trim()&&i.add(C.trim().toLowerCase().slice(0,120))}const _=typeof s.id=="string"?s.id:null;if(_&&m.has(_))continue;_&&m.add(_),n[a]=(n[a]??0)+1;const S=d(s.usage);r.input+=h(S.input_tokens),r.output+=h(S.output_tokens),r.cacheRead+=h(S.cache_read_input_tokens),r.cacheWrite+=h(S.cache_creation_input_tokens)}}return!c||!y||!Object.keys(n).length?null:{sessionId:p,client:"claude_code",delegated:u,launchedBy:null,startedAt:c,endedAt:y,models:n,primaryModel:I(n),tokens:r,userTurns:g,toolCalls:w,toolErrors:b,approvalDenials:j,cwd:f,branch:I(l),skills:[...i]}}async function W(e){const n={};let l=D(),i=null,r=null,m=null,c=null,y=k.basename(e,".jsonl"),f=!1,p=null,g=0,w=0,b=0;const j=new Set;for await(const u of N(e)){const o=typeof u.timestamp=="string"?u.timestamp:null;o&&(i??=o,r=o);const t=d(u.payload);if(u.type==="response_item"&&t.type==="function_call"&&typeof t.name=="string"&&B.test(t.name))try{const s=d(JSON.parse(String(t.arguments??"{}"))),a=s.name??s.skill??s.slug;typeof a=="string"&&a.trim()&&j.add(a.trim().toLowerCase().slice(0,120))}catch{}if(u.type==="session_meta"){typeof t.id=="string"&&(y=t.id),typeof t.cwd=="string"&&(m=t.cwd);const s=d(t.git);typeof s.branch=="string"&&s.branch&&s.branch!=="HEAD"&&(c=s.branch),t.originator==="Claude Code"&&(p="claude_code"),t.source&&typeof t.source=="object"&&(f=!0)}if(u.type==="turn_context"&&typeof t.model=="string"&&(n[t.model]=(n[t.model]??0)+1),u.type==="event_msg"&&t.type==="user_message"&&(g+=1),u.type==="event_msg"&&t.type==="token_count"){const s=d(d(t.info).total_token_usage);if(Object.keys(s).length){const a=h(s.cached_input_tokens);l={input:Math.max(0,h(s.input_tokens)-a),output:h(s.output_tokens),cacheRead:a,cacheWrite:h(s.cache_write_input_tokens)}}}u.type==="response_item"&&(t.type==="function_call"||t.type==="custom_tool_call")&&(w+=1),u.type==="event_msg"&&t.type==="exec_command_end"&&h(t.exit_code)!==0&&(b+=1)}return!i||!r||!Object.keys(n).length?null:{sessionId:y,client:"codex",delegated:f||p!==null,launchedBy:p,startedAt:i,endedAt:r,models:n,primaryModel:I(n),tokens:l,userTurns:g,toolCalls:w,toolErrors:b,approvalDenials:0,cwd:m,branch:c,skills:[...j]}}async function R(e,n,l){const i=[],r=async(m,c)=>{let y;try{y=await L.readdir(m,{withFileTypes:!0})}catch{return}for(const f of y){const p=k.join(m,f.name);if(f.isDirectory()&&c<n)await r(p,c+1);else if(f.isFile()&&f.name.endsWith(".jsonl")){const g=await L.stat(p).catch(()=>null);g&&g.mtimeMs>=l&&i.push(p)}}};return await r(e,0),i}async function U(e){const n=e.home??F.homedir(),l=e.since,[i,r]=await Promise.all([R(k.join(n,".claude","projects"),4,l),R(k.join(n,".codex","sessions"),4,l)]);return(await Promise.all([...i.map(c=>J(c).catch(()=>null)),...r.map(c=>W(c).catch(()=>null))])).filter(c=>c!==null)}export{U as collectLocalSessions,J as readClaudeCodeSession,W as readCodexSession};