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.
- package/CHANGELOG.md +10 -0
- package/LICENSE +15 -0
- package/README.md +3 -1
- package/dist/auth/browser-flow.d.ts +0 -1
- package/dist/auth/browser-flow.js +5 -81
- package/dist/cmd/connect.d.ts +0 -1
- package/dist/cmd/connect.js +1 -164
- package/dist/cmd/init.d.ts +0 -1
- package/dist/cmd/init.js +2 -138
- package/dist/cmd/invite.d.ts +0 -1
- package/dist/cmd/invite.js +4 -73
- package/dist/cmd/join.d.ts +0 -1
- package/dist/cmd/join.js +1 -91
- package/dist/cmd/login.d.ts +0 -1
- package/dist/cmd/login.js +3 -41
- package/dist/cmd/mcp.d.ts +0 -1
- package/dist/cmd/mcp.js +2 -86
- package/dist/cmd/pace.d.ts +23 -2
- package/dist/cmd/pace.js +4 -393
- package/dist/cmd/parse-share-input.d.ts +0 -1
- package/dist/cmd/parse-share-input.js +1 -56
- package/dist/cmd/pull.d.ts +0 -1
- package/dist/cmd/pull.js +7 -143
- package/dist/cmd/push.d.ts +0 -1
- package/dist/cmd/push.js +1 -85
- package/dist/cmd/resolve.d.ts +0 -1
- package/dist/cmd/resolve.js +1 -35
- package/dist/cmd/setup.d.ts +0 -1
- package/dist/cmd/setup.js +2 -345
- package/dist/cmd/status-card.d.ts +0 -1
- package/dist/cmd/status-card.js +2 -156
- package/dist/cmd/status.d.ts +0 -1
- package/dist/cmd/status.js +3 -29
- package/dist/cmd/watch.d.ts +0 -1
- package/dist/cmd/watch.js +1 -26
- package/dist/hosts/claude-code.d.ts +0 -1
- package/dist/hosts/claude-code.js +3 -180
- package/dist/hosts/claude-desktop.d.ts +0 -1
- package/dist/hosts/claude-desktop.js +3 -222
- package/dist/hosts/codex.d.ts +0 -1
- package/dist/hosts/codex.js +20 -234
- package/dist/hosts/shell-env.d.ts +0 -1
- package/dist/hosts/shell-env.js +9 -81
- package/dist/index.d.ts +0 -1
- package/dist/index.js +14 -206
- package/dist/lib/client.d.ts +0 -1
- package/dist/lib/client.js +1 -58
- package/dist/lib/events/emit.d.ts +0 -1
- package/dist/lib/events/emit.js +1 -28
- package/dist/lib/index.d.ts +0 -1
- package/dist/lib/index.js +1 -11
- package/dist/lib/transport.d.ts +0 -1
- package/dist/lib/transport.js +1 -85
- package/dist/lib/types/index.d.ts +0 -1
- package/dist/lib/types/index.js +1 -50
- package/dist/lib/vault/checkpoint.d.ts +0 -1
- package/dist/lib/vault/checkpoint.js +1 -18
- package/dist/lib/vault/read.d.ts +0 -1
- package/dist/lib/vault/read.js +1 -9
- package/dist/lib/vault/search.d.ts +0 -1
- package/dist/lib/vault/search.js +1 -10
- package/dist/pace/device.d.ts +3 -1
- package/dist/pace/device.js +2 -148
- package/dist/pace/enroll.d.ts +79 -0
- package/dist/pace/enroll.js +2 -0
- package/dist/pace/identity.d.ts +0 -1
- package/dist/pace/identity.js +1 -17
- package/dist/pace/install.d.ts +0 -1
- package/dist/pace/install.js +4 -127
- package/dist/pace/local-evidence.d.ts +0 -1
- package/dist/pace/local-evidence.js +2 -228
- package/dist/pace/local-sessions.d.ts +0 -1
- package/dist/pace/local-sessions.js +1 -254
- package/dist/pace/managed-config.d.ts +0 -1
- package/dist/pace/managed-config.js +8 -102
- package/dist/pace/normalize.d.ts +0 -1
- package/dist/pace/normalize.js +1 -293
- package/dist/pace/repository.d.ts +0 -1
- package/dist/pace/repository.js +2 -120
- package/dist/pace/schema.d.ts +0 -1
- package/dist/pace/schema.js +1 -134
- package/dist/pace/session-summary.d.ts +0 -1
- package/dist/pace/session-summary.js +2 -140
- package/dist/pace/spool.d.ts +0 -1
- package/dist/pace/spool.js +8 -87
- package/dist/pace-runtime.d.ts +1 -0
- package/dist/pace-runtime.js +2 -0
- package/dist/sync/api.d.ts +0 -1
- package/dist/sync/api.js +1 -55
- package/dist/sync/autoloader.d.ts +0 -1
- package/dist/sync/autoloader.js +5 -127
- package/dist/sync/conflict.d.ts +0 -1
- package/dist/sync/conflict.js +5 -15
- package/dist/sync/credentials.d.ts +0 -1
- package/dist/sync/credentials.js +1 -51
- package/dist/sync/files.d.ts +0 -1
- package/dist/sync/files.js +1 -71
- package/dist/sync/manifest.d.ts +0 -1
- package/dist/sync/manifest.js +1 -37
- package/dist/sync/repo-root.d.ts +0 -1
- package/dist/sync/repo-root.js +1 -24
- package/dist/sync/stitch-claude-md.d.ts +0 -1
- package/dist/sync/stitch-claude-md.js +8 -48
- package/dist/ui/colors.d.ts +0 -1
- package/dist/ui/colors.js +1 -28
- package/dist/ui/screens.d.ts +0 -1
- package/dist/ui/screens.js +5 -135
- package/package.json +8 -14
- package/plugin/.cursor-plugin/plugin.json +0 -2
- package/plugin/.mcp.json +1 -1
- package/plugin/LICENSE +15 -0
- package/plugin/README.md +3 -7
- package/plugin/gemini/gemini-extension.json +1 -1
- package/plugin/mcp.json +1 -1
- package/plugin/plugin.json +0 -2
- package/plugin/runtime/unfairly-intelligence.mjs +15 -0
- package/plugin/scripts/capture.cjs +30 -8
- package/plugin/skills/find-company-playbook/SKILL.md +1 -1
- package/dist/auth/browser-flow.d.ts.map +0 -1
- package/dist/auth/browser-flow.js.map +0 -1
- package/dist/cmd/connect.d.ts.map +0 -1
- package/dist/cmd/connect.js.map +0 -1
- package/dist/cmd/init.d.ts.map +0 -1
- package/dist/cmd/init.js.map +0 -1
- package/dist/cmd/invite.d.ts.map +0 -1
- package/dist/cmd/invite.js.map +0 -1
- package/dist/cmd/join.d.ts.map +0 -1
- package/dist/cmd/join.js.map +0 -1
- package/dist/cmd/login.d.ts.map +0 -1
- package/dist/cmd/login.js.map +0 -1
- package/dist/cmd/mcp.d.ts.map +0 -1
- package/dist/cmd/mcp.js.map +0 -1
- package/dist/cmd/pace.d.ts.map +0 -1
- package/dist/cmd/pace.js.map +0 -1
- package/dist/cmd/parse-share-input.d.ts.map +0 -1
- package/dist/cmd/parse-share-input.js.map +0 -1
- package/dist/cmd/pull.d.ts.map +0 -1
- package/dist/cmd/pull.js.map +0 -1
- package/dist/cmd/push.d.ts.map +0 -1
- package/dist/cmd/push.js.map +0 -1
- package/dist/cmd/resolve.d.ts.map +0 -1
- package/dist/cmd/resolve.js.map +0 -1
- package/dist/cmd/setup.d.ts.map +0 -1
- package/dist/cmd/setup.js.map +0 -1
- package/dist/cmd/status-card.d.ts.map +0 -1
- package/dist/cmd/status-card.js.map +0 -1
- package/dist/cmd/status.d.ts.map +0 -1
- package/dist/cmd/status.js.map +0 -1
- package/dist/cmd/watch.d.ts.map +0 -1
- package/dist/cmd/watch.js.map +0 -1
- package/dist/hosts/claude-code.d.ts.map +0 -1
- package/dist/hosts/claude-code.js.map +0 -1
- package/dist/hosts/claude-desktop.d.ts.map +0 -1
- package/dist/hosts/claude-desktop.js.map +0 -1
- package/dist/hosts/codex.d.ts.map +0 -1
- package/dist/hosts/codex.js.map +0 -1
- package/dist/hosts/shell-env.d.ts.map +0 -1
- package/dist/hosts/shell-env.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/lib/client.d.ts.map +0 -1
- package/dist/lib/client.js.map +0 -1
- package/dist/lib/events/emit.d.ts.map +0 -1
- package/dist/lib/events/emit.js.map +0 -1
- package/dist/lib/index.d.ts.map +0 -1
- package/dist/lib/index.js.map +0 -1
- package/dist/lib/transport.d.ts.map +0 -1
- package/dist/lib/transport.js.map +0 -1
- package/dist/lib/types/index.d.ts.map +0 -1
- package/dist/lib/types/index.js.map +0 -1
- package/dist/lib/vault/checkpoint.d.ts.map +0 -1
- package/dist/lib/vault/checkpoint.js.map +0 -1
- package/dist/lib/vault/read.d.ts.map +0 -1
- package/dist/lib/vault/read.js.map +0 -1
- package/dist/lib/vault/search.d.ts.map +0 -1
- package/dist/lib/vault/search.js.map +0 -1
- package/dist/pace/device.d.ts.map +0 -1
- package/dist/pace/device.js.map +0 -1
- package/dist/pace/identity.d.ts.map +0 -1
- package/dist/pace/identity.js.map +0 -1
- package/dist/pace/install.d.ts.map +0 -1
- package/dist/pace/install.js.map +0 -1
- package/dist/pace/local-evidence.d.ts.map +0 -1
- package/dist/pace/local-evidence.js.map +0 -1
- package/dist/pace/local-sessions.d.ts.map +0 -1
- package/dist/pace/local-sessions.js.map +0 -1
- package/dist/pace/managed-config.d.ts.map +0 -1
- package/dist/pace/managed-config.js.map +0 -1
- package/dist/pace/normalize.d.ts.map +0 -1
- package/dist/pace/normalize.js.map +0 -1
- package/dist/pace/repository.d.ts.map +0 -1
- package/dist/pace/repository.js.map +0 -1
- package/dist/pace/schema.d.ts.map +0 -1
- package/dist/pace/schema.js.map +0 -1
- package/dist/pace/session-summary.d.ts.map +0 -1
- package/dist/pace/session-summary.js.map +0 -1
- package/dist/pace/spool.d.ts.map +0 -1
- package/dist/pace/spool.js.map +0 -1
- package/dist/sync/api.d.ts.map +0 -1
- package/dist/sync/api.js.map +0 -1
- package/dist/sync/autoloader.d.ts.map +0 -1
- package/dist/sync/autoloader.js.map +0 -1
- package/dist/sync/conflict.d.ts.map +0 -1
- package/dist/sync/conflict.js.map +0 -1
- package/dist/sync/credentials.d.ts.map +0 -1
- package/dist/sync/credentials.js.map +0 -1
- package/dist/sync/files.d.ts.map +0 -1
- package/dist/sync/files.js.map +0 -1
- package/dist/sync/manifest.d.ts.map +0 -1
- package/dist/sync/manifest.js.map +0 -1
- package/dist/sync/repo-root.d.ts.map +0 -1
- package/dist/sync/repo-root.js.map +0 -1
- package/dist/sync/stitch-claude-md.d.ts.map +0 -1
- package/dist/sync/stitch-claude-md.js.map +0 -1
- package/dist/ui/colors.d.ts.map +0 -1
- package/dist/ui/colors.js.map +0 -1
- package/dist/ui/screens.d.ts.map +0 -1
- package/dist/ui/screens.js.map +0 -1
package/dist/cmd/status.js
CHANGED
|
@@ -1,29 +1,3 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
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};
|
package/dist/cmd/watch.d.ts
CHANGED
package/dist/cmd/watch.js
CHANGED
|
@@ -1,26 +1 @@
|
|
|
1
|
-
import
|
|
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};
|
|
@@ -1,180 +1,3 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
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};
|
|
@@ -1,222 +1,3 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
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};
|
package/dist/hosts/codex.d.ts
CHANGED
|
@@ -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
|