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,29 +1,3 @@
1
- import path from "node:path";
2
- import { readManifest } from "../sync/manifest.js";
3
- import { computeSha, readDocRaw } from "../sync/files.js";
4
- import { banner, defaultOpts } from "../ui/screens.js";
5
- export async function runStatus() {
6
- const ui = defaultOpts();
7
- process.stdout.write(`${banner(ui)}\n\n`);
8
- const cwd = process.cwd();
9
- const manifest = await readManifest(cwd);
10
- if (!manifest) {
11
- console.error("No .unfairly/ found. Run `unfairly init` first.");
12
- process.exit(1);
13
- }
14
- for (const [slug, v] of Object.entries(manifest.vaults)) {
15
- let modified = 0;
16
- for (const [, entry] of Object.entries(v.docs)) {
17
- try {
18
- const raw = await readDocRaw(path.join(cwd, ".unfairly", entry.path));
19
- if (computeSha(raw) !== entry.sha)
20
- modified++;
21
- }
22
- catch { }
23
- }
24
- const pulled = v.last_pulled_at ?? "never";
25
- const pushed = v.last_pushed_at ?? "never";
26
- console.log(`${slug.padEnd(20)} pulled ${pulled} · pushed ${pushed} · modified ${modified}`);
27
- }
28
- }
29
- //# sourceMappingURL=status.js.map
1
+ import d from"node:path";import{readManifest as u}from"../sync/manifest.js";import{computeSha as p,readDocRaw as l}from"../sync/files.js";import{banner as m,defaultOpts as h}from"../ui/screens.js";async function _(){const r=h();process.stdout.write(`${m(r)}
2
+
3
+ `);const o=process.cwd(),e=await u(o);e||(console.error("No .unfairly/ found. Run `unfairly init` first."),process.exit(1));for(const[a,t]of Object.entries(e.vaults)){let s=0;for(const[,n]of Object.entries(t.docs))try{const f=await l(d.join(o,".unfairly",n.path));p(f)!==n.sha&&s++}catch{}const i=t.last_pulled_at??"never",c=t.last_pushed_at??"never";console.log(`${a.padEnd(20)} pulled ${i} \xB7 pushed ${c} \xB7 modified ${s}`)}}export{_ as runStatus};
@@ -1,2 +1 @@
1
1
  export declare function runWatch(): Promise<void>;
2
- //# sourceMappingURL=watch.d.ts.map
package/dist/cmd/watch.js CHANGED
@@ -1,26 +1 @@
1
- import path from "node:path";
2
- import chokidar from "chokidar";
3
- import { runPush } from "./push.js";
4
- export async function runWatch() {
5
- const cwd = process.cwd();
6
- const dir = path.join(cwd, ".unfairly");
7
- console.log(`watching ${dir} (Ctrl+C to stop)`);
8
- let timer = null;
9
- const debouncePush = () => {
10
- if (timer)
11
- clearTimeout(timer);
12
- timer = setTimeout(() => {
13
- runPush(undefined).catch((e) => console.error("push failed:", e.message));
14
- }, 5000);
15
- };
16
- chokidar
17
- .watch(dir, {
18
- ignored: [/(^|[\/\\])\../, /manifest\.json$/, /\.tmp\./],
19
- ignoreInitial: true,
20
- persistent: true,
21
- })
22
- .on("change", debouncePush)
23
- .on("add", debouncePush);
24
- await new Promise(() => { }); // keep alive until SIGINT
25
- }
26
- //# sourceMappingURL=watch.js.map
1
+ import i from"node:path";import s from"chokidar";import{runPush as c}from"./push.js";async function d(){const n=process.cwd(),t=i.join(n,".unfairly");console.log(`watching ${t} (Ctrl+C to stop)`);let o=null;const e=()=>{o&&clearTimeout(o),o=setTimeout(()=>{c(void 0).catch(r=>console.error("push failed:",r.message))},5e3)};s.watch(t,{ignored:[/(^|[\/\\])\../,/manifest\.json$/,/\.tmp\./],ignoreInitial:!0,persistent:!0}).on("change",e).on("add",e),await new Promise(()=>{})}export{d as runWatch};
@@ -72,4 +72,3 @@ export declare function configureClaudeCode(apiUrl: string, token: string): Prom
72
72
  */
73
73
  export declare function addScopedClaudeCodeEntry(input: AddScopedEntryInput): Promise<AddScopedEntryResult>;
74
74
  export {};
75
- //# sourceMappingURL=claude-code.d.ts.map
@@ -1,180 +1,3 @@
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 { addScopedEntryToConfig, } from "./claude-desktop.js";
6
- /**
7
- * Path to Claude Code's user-level config. Returns null on Linux only if
8
- * HOME is unset (effectively never).
9
- */
10
- export function configPath() {
11
- return path.join(os.homedir(), ".claude.json");
12
- }
13
- /**
14
- * Build the canonical user-scope unfairly entry. Inlines the literal token —
15
- * matches the Claude Desktop pattern. Keeps the token out of any process
16
- * argv (the previous `claude mcp add --header=…` form leaked it via
17
- * `/proc/<pid>/cmdline` for the lifetime of the subprocess).
18
- */
19
- export function buildEntry(apiUrl, token) {
20
- return {
21
- type: "http",
22
- url: `${apiUrl.replace(/\/$/, "")}/api/mcp`,
23
- headers: { Authorization: `Bearer ${token}` },
24
- };
25
- }
26
- /**
27
- * Merge our canonical unfairly entry into ~/.claude.json:
28
- * - Writes the user-scope entry at root `mcpServers.unfairly`.
29
- * - Strips any project-scoped `projects[<cwd>].mcpServers.unfairly` left over
30
- * from previous local-scope runs of setup. Without this, a stale entry in
31
- * the active project would shadow the user-scope one and silently keep an
32
- * old (or empty) bearer.
33
- * - Preserves all unrelated keys.
34
- */
35
- export function mergeIntoConfig(existing, apiUrl, token) {
36
- const next = { ...existing };
37
- const servers = { ...(existing.mcpServers ?? {}) };
38
- servers.unfairly = buildEntry(apiUrl, token);
39
- next.mcpServers = servers;
40
- if (existing.projects) {
41
- const projects = {};
42
- for (const [k, v] of Object.entries(existing.projects)) {
43
- // Only touch the project entry when it actually has an `unfairly`
44
- // mcpServer to strip. Preserves the original shape (including the
45
- // absence of `mcpServers` entirely) for unrelated projects.
46
- if (v.mcpServers && "unfairly" in v.mcpServers) {
47
- const ps = { ...v.mcpServers };
48
- delete ps.unfairly;
49
- projects[k] = { ...v, mcpServers: ps };
50
- }
51
- else {
52
- projects[k] = v;
53
- }
54
- }
55
- next.projects = projects;
56
- }
57
- return next;
58
- }
59
- /**
60
- * Wire Unfairly into Claude Code by mutating ~/.claude.json directly. We
61
- * intentionally do NOT shell out to `claude mcp add --header=…` — that form
62
- * exposes the bearer token to `ps aux` and `/proc/<pid>/cmdline` for the
63
- * duration of the subprocess. Same threat model as Claude Desktop, same
64
- * mitigation: write the JSON ourselves.
65
- *
66
- * Skips silently only when the `claude` binary is not in PATH. If the binary
67
- * exists but `~/.claude.json` doesn't yet (fresh install, before first
68
- * launch of Claude Code), we create the file with our entry — that's what
69
- * `claude mcp add` would have done implicitly.
70
- */
71
- export async function configureClaudeCode(apiUrl, token) {
72
- const probe = spawnSync("claude", ["--version"], { encoding: "utf-8" });
73
- if (probe.error) {
74
- const e = probe.error;
75
- if (e.code === "ENOENT")
76
- return { status: "not-installed" };
77
- return { status: "error", message: e.message };
78
- }
79
- const cfgPath = configPath();
80
- let raw;
81
- try {
82
- raw = await fs.readFile(cfgPath, "utf8");
83
- }
84
- catch (err) {
85
- const e = err;
86
- if (e.code === "ENOENT") {
87
- // Fresh Claude Code install — binary exists but config not yet
88
- // created. Treat as empty and let the writeFile below create it.
89
- raw = "";
90
- }
91
- else {
92
- return { status: "error", message: `Could not read ${cfgPath}: ${e.message}` };
93
- }
94
- }
95
- let parsed;
96
- try {
97
- parsed = raw.trim() === "" ? {} : JSON.parse(raw);
98
- }
99
- catch (err) {
100
- return {
101
- status: "error",
102
- message: `Refusing to overwrite — config is not valid JSON: ${err.message}`,
103
- };
104
- }
105
- const merged = mergeIntoConfig(parsed, apiUrl, token);
106
- try {
107
- await fs.writeFile(cfgPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
108
- }
109
- catch (err) {
110
- return { status: "error", message: err.message };
111
- }
112
- return {
113
- status: "configured",
114
- path: cfgPath,
115
- message: "wired unfairly into ~/.claude.json (user scope, literal token)",
116
- };
117
- }
118
- /**
119
- * Add a scoped, per-share_link MCP entry to Claude Code's ~/.claude.json.
120
- * Reuses the Claude Desktop entry-shape + key-picker logic (same JSON
121
- * config format). Idempotent by `_meta.shareLinkId`.
122
- *
123
- * If the `claude` binary is not in PATH → not-installed (we don't create
124
- * ~/.claude.json out of thin air for a user who hasn't installed Claude
125
- * Code yet — matches `configureClaudeCode` behavior).
126
- *
127
- * If the binary exists but ~/.claude.json doesn't → create it with the
128
- * scoped entry (fresh-install case).
129
- */
130
- export async function addScopedClaudeCodeEntry(input) {
131
- const probe = spawnSync("claude", ["--version"], { encoding: "utf-8" });
132
- if (probe.error) {
133
- const e = probe.error;
134
- if (e.code === "ENOENT")
135
- return { status: "not-installed", path: "(claude not in PATH)" };
136
- return { status: "error", path: "(claude probe failed)", message: e.message };
137
- }
138
- const cfgPath = configPath();
139
- let raw;
140
- try {
141
- raw = await fs.readFile(cfgPath, "utf8");
142
- }
143
- catch (err) {
144
- const e = err;
145
- if (e.code === "ENOENT") {
146
- raw = "";
147
- }
148
- else {
149
- return {
150
- status: "error",
151
- path: cfgPath,
152
- message: `Could not read ${cfgPath}: ${e.message}`,
153
- };
154
- }
155
- }
156
- let parsed;
157
- try {
158
- parsed = raw.trim() === "" ? {} : JSON.parse(raw);
159
- }
160
- catch (err) {
161
- return {
162
- status: "error",
163
- path: cfgPath,
164
- message: `Refusing to overwrite — config is not valid JSON: ${err.message}`,
165
- };
166
- }
167
- const { next, key, replaced } = addScopedEntryToConfig(parsed, input);
168
- try {
169
- await fs.writeFile(cfgPath, JSON.stringify(next, null, 2) + "\n", "utf8");
170
- }
171
- catch (err) {
172
- return {
173
- status: "error",
174
- path: cfgPath,
175
- message: err.message,
176
- };
177
- }
178
- return { status: "configured", path: cfgPath, key, replaced };
179
- }
180
- //# sourceMappingURL=claude-code.js.map
1
+ import{spawnSync as p}from"node:child_process";import u from"node:fs/promises";import l from"node:os";import m from"node:path";import{addScopedEntryToConfig as g}from"./claude-desktop.js";function d(){return m.join(l.homedir(),".claude.json")}function h(o,n){return{type:"http",url:`${o.replace(/\/$/,"")}/api/mcp`,headers:{Authorization:`Bearer ${n}`}}}function y(o,n,s){const t={...o},a={...o.mcpServers??{}};if(a.unfairly=h(n,s),t.mcpServers=a,o.projects){const c={};for(const[i,e]of Object.entries(o.projects))if(e.mcpServers&&"unfairly"in e.mcpServers){const r={...e.mcpServers};delete r.unfairly,c[i]={...e,mcpServers:r}}else c[i]=e;t.projects=c}return t}async function O(o,n){const s=p("claude",["--version"],{encoding:"utf-8"});if(s.error){const e=s.error;return e.code==="ENOENT"?{status:"not-installed"}:{status:"error",message:e.message}}const t=d();let a;try{a=await u.readFile(t,"utf8")}catch(e){const r=e;if(r.code==="ENOENT")a="";else return{status:"error",message:`Could not read ${t}: ${r.message}`}}let c;try{c=a.trim()===""?{}:JSON.parse(a)}catch(e){return{status:"error",message:`Refusing to overwrite \u2014 config is not valid JSON: ${e.message}`}}const i=y(c,o,n);try{await u.writeFile(t,JSON.stringify(i,null,2)+`
2
+ `,"utf8")}catch(e){return{status:"error",message:e.message}}return{status:"configured",path:t,message:"wired unfairly into ~/.claude.json (user scope, literal token)"}}async function $(o){const n=p("claude",["--version"],{encoding:"utf-8"});if(n.error){const r=n.error;return r.code==="ENOENT"?{status:"not-installed",path:"(claude not in PATH)"}:{status:"error",path:"(claude probe failed)",message:r.message}}const s=d();let t;try{t=await u.readFile(s,"utf8")}catch(r){const f=r;if(f.code==="ENOENT")t="";else return{status:"error",path:s,message:`Could not read ${s}: ${f.message}`}}let a;try{a=t.trim()===""?{}:JSON.parse(t)}catch(r){return{status:"error",path:s,message:`Refusing to overwrite \u2014 config is not valid JSON: ${r.message}`}}const{next:c,key:i,replaced:e}=g(a,o);try{await u.writeFile(s,JSON.stringify(c,null,2)+`
3
+ `,"utf8")}catch(r){return{status:"error",path:s,message:r.message}}return{status:"configured",path:s,key:i,replaced:e}}export{$ as addScopedClaudeCodeEntry,h as buildEntry,d as configPath,O as configureClaudeCode,y as mergeIntoConfig};
@@ -120,4 +120,3 @@ export declare function addScopedClaudeDesktopEntry(input: AddScopedEntryInput):
120
120
  */
121
121
  export declare function configureClaudeDesktop(apiUrl: string, token: string): Promise<ClaudeDesktopResult>;
122
122
  export {};
123
- //# sourceMappingURL=claude-desktop.d.ts.map
@@ -1,222 +1,3 @@
1
- import fs from "node:fs/promises";
2
- import os from "node:os";
3
- import path from "node:path";
4
- /**
5
- * Path to Claude Desktop's config file by platform. Returns null on Linux
6
- * (Claude Desktop is Mac/Windows only per official docs).
7
- */
8
- export function configPath() {
9
- const home = os.homedir();
10
- if (process.platform === "darwin") {
11
- return path.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
12
- }
13
- if (process.platform === "win32") {
14
- const appdata = process.env.APPDATA ?? path.join(home, "AppData", "Roaming");
15
- return path.join(appdata, "Claude", "claude_desktop_config.json");
16
- }
17
- return null;
18
- }
19
- export function buildEntry(apiUrl, token) {
20
- const url = `${apiUrl.replace(/\/$/, "")}/api/mcp`;
21
- // Inline the literal token. macOS GUI apps don't inherit env vars from
22
- // ~/.zshenv (only LaunchAgent env via `launchctl setenv`), so the
23
- // `${UNFAIRLY_MCP_TOKEN}` placeholder pattern silently fails. Claude Desktop
24
- // already stores credentials on disk for other MCP servers — this is the
25
- // standard pattern.
26
- return {
27
- type: "http",
28
- url,
29
- headers: { Authorization: `Bearer ${token}` },
30
- };
31
- }
32
- /**
33
- * Merge our canonical unfairly entry into an existing config object.
34
- * - Preserves other mcpServers
35
- * - Replaces any existing unfairly entry (including stale tokens / placeholder)
36
- * - Preserves all unrelated top-level keys
37
- */
38
- export function mergeIntoConfig(existing, apiUrl, token) {
39
- const next = { ...existing };
40
- const servers = { ...(existing.mcpServers ?? {}) };
41
- servers.unfairly = buildEntry(apiUrl, token);
42
- next.mcpServers = servers;
43
- return next;
44
- }
45
- /**
46
- * Sanitize org/room slug fragments for use in an MCP server key. Lowercase,
47
- * alphanumeric + hyphen; falls back to "vault" when the input is empty/null.
48
- */
49
- function safeSlug(s, fallback) {
50
- if (!s)
51
- return fallback;
52
- const cleaned = s
53
- .toLowerCase()
54
- .replace(/[^a-z0-9-]+/g, "-")
55
- .replace(/^-+|-+$/g, "");
56
- return cleaned.length === 0 ? fallback : cleaned;
57
- }
58
- /**
59
- * Build the base key (`unfairly-<org>-<room>`) for a scoped entry, given
60
- * orgSlug + roomSlug. Both may be null if the mint endpoint didn't surface
61
- * them; we fall back to sensible defaults to keep the key deterministic.
62
- */
63
- export function buildScopedKeyBase(orgSlug, roomSlug) {
64
- return `unfairly-${safeSlug(orgSlug, "org")}-${safeSlug(roomSlug, "vault")}`;
65
- }
66
- /**
67
- * Pick a key for a new scoped entry inside `servers`:
68
- * - If any existing entry has `_meta.shareLinkId === shareLinkId`, reuse
69
- * that key (idempotent re-connect — overwrites with fresh token).
70
- * - Else if `baseKey` itself is free, use it.
71
- * - Else pick the lowest free numbered slot `${baseKey}-2`, `${baseKey}-3`, …
72
- * (reuses gaps: with `{baseKey, baseKey-3}` taken, returns `baseKey-2`).
73
- *
74
- * Returns `{ key, replaced }`. `replaced` is true when reusing an existing
75
- * key (either matching shareLinkId or baseKey collision overwrite).
76
- */
77
- export function pickNextEntryKey(servers, baseKey, shareLinkId) {
78
- // 1. Idempotent re-connect: look for an entry tagged with our shareLinkId.
79
- for (const [key, value] of Object.entries(servers)) {
80
- if (!key.startsWith(baseKey))
81
- continue;
82
- if (value && typeof value === "object") {
83
- const meta = value._meta;
84
- if (meta && meta.shareLinkId === shareLinkId) {
85
- return { key, replaced: true };
86
- }
87
- }
88
- }
89
- // 2. Find the lowest free numbered slot. Base key is slot "1" (unsuffixed).
90
- if (!(baseKey in servers)) {
91
- return { key: baseKey, replaced: false };
92
- }
93
- let n = 2;
94
- while (`${baseKey}-${n}` in servers)
95
- n += 1;
96
- return { key: `${baseKey}-${n}`, replaced: false };
97
- }
98
- /**
99
- * Build a scoped entry for one share_link. Distinct from `buildEntry` in
100
- * that it carries the full mcp_url (from the mint response) and tags the
101
- * entry with `_meta.shareLinkId` so we can re-find it later.
102
- */
103
- export function buildScopedEntry(mcpUrl, token, shareLinkId) {
104
- return {
105
- type: "http",
106
- url: mcpUrl,
107
- headers: { Authorization: `Bearer ${token}` },
108
- _meta: { shareLinkId, source: "unfairly-connect" },
109
- };
110
- }
111
- /**
112
- * Merge a scoped entry into an existing config. Pure function: returns the
113
- * new config + the key that was used + whether an entry was replaced.
114
- */
115
- export function addScopedEntryToConfig(existing, input) {
116
- const baseKey = buildScopedKeyBase(input.orgSlug, input.roomSlug);
117
- const servers = { ...(existing.mcpServers ?? {}) };
118
- const { key, replaced } = pickNextEntryKey(servers, baseKey, input.shareLinkId);
119
- servers[key] = buildScopedEntry(input.mcpUrl, input.token, input.shareLinkId);
120
- const next = { ...existing, mcpServers: servers };
121
- return { next, key, replaced };
122
- }
123
- /**
124
- * Add a scoped, per-share_link MCP entry to Claude Desktop's config. Does
125
- * NOT touch the canonical `mcpServers.unfairly` entry — those are for the
126
- * user's primary token. Scoped entries live alongside as additional
127
- * keys like `unfairly-acme-investors`.
128
- *
129
- * Returns `not-installed` when Claude Desktop config dir doesn't exist
130
- * (matches `configureClaudeDesktop` behavior — never creates from scratch).
131
- */
132
- export async function addScopedClaudeDesktopEntry(input) {
133
- const cfgPath = configPath();
134
- if (!cfgPath) {
135
- return {
136
- status: "not-installed",
137
- path: "(Claude Desktop not available on this platform)",
138
- };
139
- }
140
- let existing;
141
- try {
142
- existing = await fs.readFile(cfgPath, "utf8");
143
- }
144
- catch (err) {
145
- const e = err;
146
- if (e.code === "ENOENT")
147
- return { status: "not-installed", path: cfgPath };
148
- return {
149
- status: "error",
150
- path: cfgPath,
151
- message: `Could not read ${cfgPath}: ${e.message}`,
152
- };
153
- }
154
- let parsed;
155
- try {
156
- parsed = existing.trim() === "" ? {} : JSON.parse(existing);
157
- }
158
- catch (err) {
159
- return {
160
- status: "error",
161
- path: cfgPath,
162
- message: `Refusing to overwrite — config is not valid JSON: ${err.message}`,
163
- };
164
- }
165
- const { next, key, replaced } = addScopedEntryToConfig(parsed, input);
166
- const serialized = JSON.stringify(next, null, 2) + "\n";
167
- try {
168
- await fs.writeFile(cfgPath, serialized, "utf8");
169
- }
170
- catch (err) {
171
- return {
172
- status: "error",
173
- path: cfgPath,
174
- message: err.message,
175
- };
176
- }
177
- return { status: "configured", path: cfgPath, key, replaced };
178
- }
179
- /**
180
- * Patch Claude Desktop's config to wire the Unfairly MCP server.
181
- * - If config file does not exist → not-installed (do NOT create).
182
- * - If file exists but is malformed JSON → error (don't overwrite precious config).
183
- * - Otherwise → merge canonical block, write back.
184
- */
185
- export async function configureClaudeDesktop(apiUrl, token) {
186
- const cfgPath = configPath();
187
- if (!cfgPath) {
188
- return { status: "not-installed", path: "(Claude Desktop not available on this platform)" };
189
- }
190
- let existing;
191
- try {
192
- existing = await fs.readFile(cfgPath, "utf8");
193
- }
194
- catch (err) {
195
- const e = err;
196
- if (e.code === "ENOENT")
197
- return { status: "not-installed", path: cfgPath };
198
- return { status: "error", path: cfgPath, message: `Could not read ${cfgPath}: ${e.message}` };
199
- }
200
- let parsed;
201
- try {
202
- parsed = existing.trim() === "" ? {} : JSON.parse(existing);
203
- }
204
- catch (err) {
205
- return {
206
- status: "error",
207
- path: cfgPath,
208
- message: `Refusing to overwrite — config is not valid JSON: ${err.message}`,
209
- };
210
- }
211
- const had = parsed.mcpServers && parsed.mcpServers.unfairly !== undefined;
212
- const merged = mergeIntoConfig(parsed, apiUrl, token);
213
- const serialized = JSON.stringify(merged, null, 2) + "\n";
214
- try {
215
- await fs.writeFile(cfgPath, serialized, "utf8");
216
- }
217
- catch (err) {
218
- return { status: "error", path: cfgPath, message: err.message };
219
- }
220
- return { status: "configured", path: cfgPath, action: had ? "updated" : "created" };
221
- }
222
- //# sourceMappingURL=claude-desktop.js.map
1
+ import u from"node:fs/promises";import m from"node:os";import p from"node:path";function d(){const t=m.homedir();if(process.platform==="darwin")return p.join(t,"Library","Application Support","Claude","claude_desktop_config.json");if(process.platform==="win32"){const e=process.env.APPDATA??p.join(t,"AppData","Roaming");return p.join(e,"Claude","claude_desktop_config.json")}return null}function g(t,e){return{type:"http",url:`${t.replace(/\/$/,"")}/api/mcp`,headers:{Authorization:`Bearer ${e}`}}}function h(t,e,r){const n={...t},a={...t.mcpServers??{}};return a.unfairly=g(e,r),n.mcpServers=a,n}function f(t,e){if(!t)return e;const r=t.toLowerCase().replace(/[^a-z0-9-]+/g,"-").replace(/^-+|-+$/g,"");return r.length===0?e:r}function y(t,e){return`unfairly-${f(t,"org")}-${f(e,"vault")}`}function S(t,e,r){for(const[a,s]of Object.entries(t))if(a.startsWith(e)&&s&&typeof s=="object"){const i=s._meta;if(i&&i.shareLinkId===r)return{key:a,replaced:!0}}if(!(e in t))return{key:e,replaced:!1};let n=2;for(;`${e}-${n}`in t;)n+=1;return{key:`${e}-${n}`,replaced:!1}}function $(t,e,r){return{type:"http",url:t,headers:{Authorization:`Bearer ${e}`},_meta:{shareLinkId:r,source:"unfairly-connect"}}}function k(t,e){const r=y(e.orgSlug,e.roomSlug),n={...t.mcpServers??{}},{key:a,replaced:s}=S(n,r,e.shareLinkId);return n[a]=$(e.mcpUrl,e.token,e.shareLinkId),{next:{...t,mcpServers:n},key:a,replaced:s}}async function C(t){const e=d();if(!e)return{status:"not-installed",path:"(Claude Desktop not available on this platform)"};let r;try{r=await u.readFile(e,"utf8")}catch(o){const c=o;return c.code==="ENOENT"?{status:"not-installed",path:e}:{status:"error",path:e,message:`Could not read ${e}: ${c.message}`}}let n;try{n=r.trim()===""?{}:JSON.parse(r)}catch(o){return{status:"error",path:e,message:`Refusing to overwrite \u2014 config is not valid JSON: ${o.message}`}}const{next:a,key:s,replaced:i}=k(n,t),l=JSON.stringify(a,null,2)+`
2
+ `;try{await u.writeFile(e,l,"utf8")}catch(o){return{status:"error",path:e,message:o.message}}return{status:"configured",path:e,key:s,replaced:i}}async function N(t,e){const r=d();if(!r)return{status:"not-installed",path:"(Claude Desktop not available on this platform)"};let n;try{n=await u.readFile(r,"utf8")}catch(o){const c=o;return c.code==="ENOENT"?{status:"not-installed",path:r}:{status:"error",path:r,message:`Could not read ${r}: ${c.message}`}}let a;try{a=n.trim()===""?{}:JSON.parse(n)}catch(o){return{status:"error",path:r,message:`Refusing to overwrite \u2014 config is not valid JSON: ${o.message}`}}const s=a.mcpServers&&a.mcpServers.unfairly!==void 0,i=h(a,t,e),l=JSON.stringify(i,null,2)+`
3
+ `;try{await u.writeFile(r,l,"utf8")}catch(o){return{status:"error",path:r,message:o.message}}return{status:"configured",path:r,action:s?"updated":"created"}}export{C as addScopedClaudeDesktopEntry,k as addScopedEntryToConfig,g as buildEntry,$ as buildScopedEntry,y as buildScopedKeyBase,d as configPath,N as configureClaudeDesktop,h as mergeIntoConfig,S as pickNextEntryKey};
@@ -93,4 +93,3 @@ export declare function pickNextCodexKey(toml: string, baseKey: string): string;
93
93
  * (matches `configureCodex` behavior — never creates from scratch).
94
94
  */
95
95
  export declare function addScopedCodexEntry(input: AddScopedEntryInput): Promise<AddScopedCodexResult>;
96
- //# sourceMappingURL=codex.d.ts.map