unfairly 0.8.3 → 0.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/LICENSE +15 -0
  3. package/dist/auth/browser-flow.d.ts +0 -1
  4. package/dist/auth/browser-flow.js +5 -81
  5. package/dist/cmd/connect.d.ts +0 -1
  6. package/dist/cmd/connect.js +1 -164
  7. package/dist/cmd/init.d.ts +0 -1
  8. package/dist/cmd/init.js +2 -138
  9. package/dist/cmd/invite.d.ts +0 -1
  10. package/dist/cmd/invite.js +4 -73
  11. package/dist/cmd/join.d.ts +0 -1
  12. package/dist/cmd/join.js +1 -91
  13. package/dist/cmd/login.d.ts +0 -1
  14. package/dist/cmd/login.js +3 -41
  15. package/dist/cmd/mcp.d.ts +0 -1
  16. package/dist/cmd/mcp.js +2 -86
  17. package/dist/cmd/pace.d.ts +2 -2
  18. package/dist/cmd/pace.js +4 -426
  19. package/dist/cmd/parse-share-input.d.ts +0 -1
  20. package/dist/cmd/parse-share-input.js +1 -56
  21. package/dist/cmd/pull.d.ts +0 -1
  22. package/dist/cmd/pull.js +7 -143
  23. package/dist/cmd/push.d.ts +0 -1
  24. package/dist/cmd/push.js +1 -85
  25. package/dist/cmd/resolve.d.ts +0 -1
  26. package/dist/cmd/resolve.js +1 -35
  27. package/dist/cmd/setup.d.ts +0 -1
  28. package/dist/cmd/setup.js +2 -345
  29. package/dist/cmd/status-card.d.ts +0 -1
  30. package/dist/cmd/status-card.js +2 -156
  31. package/dist/cmd/status.d.ts +0 -1
  32. package/dist/cmd/status.js +3 -29
  33. package/dist/cmd/watch.d.ts +0 -1
  34. package/dist/cmd/watch.js +1 -26
  35. package/dist/hosts/claude-code.d.ts +0 -1
  36. package/dist/hosts/claude-code.js +3 -180
  37. package/dist/hosts/claude-desktop.d.ts +0 -1
  38. package/dist/hosts/claude-desktop.js +3 -222
  39. package/dist/hosts/codex.d.ts +0 -1
  40. package/dist/hosts/codex.js +20 -234
  41. package/dist/hosts/shell-env.d.ts +0 -1
  42. package/dist/hosts/shell-env.js +9 -81
  43. package/dist/index.d.ts +0 -1
  44. package/dist/index.js +14 -206
  45. package/dist/lib/client.d.ts +0 -1
  46. package/dist/lib/client.js +1 -58
  47. package/dist/lib/events/emit.d.ts +0 -1
  48. package/dist/lib/events/emit.js +1 -28
  49. package/dist/lib/index.d.ts +0 -1
  50. package/dist/lib/index.js +1 -11
  51. package/dist/lib/transport.d.ts +0 -1
  52. package/dist/lib/transport.js +1 -85
  53. package/dist/lib/types/index.d.ts +0 -1
  54. package/dist/lib/types/index.js +1 -50
  55. package/dist/lib/vault/checkpoint.d.ts +0 -1
  56. package/dist/lib/vault/checkpoint.js +1 -18
  57. package/dist/lib/vault/read.d.ts +0 -1
  58. package/dist/lib/vault/read.js +1 -9
  59. package/dist/lib/vault/search.d.ts +0 -1
  60. package/dist/lib/vault/search.js +1 -10
  61. package/dist/pace/device.d.ts +0 -1
  62. package/dist/pace/device.js +2 -149
  63. package/dist/pace/enroll.d.ts +0 -1
  64. package/dist/pace/enroll.js +2 -138
  65. package/dist/pace/identity.d.ts +0 -1
  66. package/dist/pace/identity.js +1 -17
  67. package/dist/pace/install.d.ts +0 -1
  68. package/dist/pace/install.js +4 -127
  69. package/dist/pace/local-evidence.d.ts +0 -1
  70. package/dist/pace/local-evidence.js +2 -228
  71. package/dist/pace/local-sessions.d.ts +0 -1
  72. package/dist/pace/local-sessions.js +1 -254
  73. package/dist/pace/managed-config.d.ts +0 -1
  74. package/dist/pace/managed-config.js +8 -102
  75. package/dist/pace/normalize.d.ts +0 -1
  76. package/dist/pace/normalize.js +1 -293
  77. package/dist/pace/repository.d.ts +0 -1
  78. package/dist/pace/repository.js +2 -120
  79. package/dist/pace/schema.d.ts +0 -1
  80. package/dist/pace/schema.js +1 -134
  81. package/dist/pace/session-summary.d.ts +0 -1
  82. package/dist/pace/session-summary.js +2 -140
  83. package/dist/pace/spool.d.ts +0 -1
  84. package/dist/pace/spool.js +8 -87
  85. package/dist/pace-runtime.d.ts +0 -1
  86. package/dist/pace-runtime.js +2 -67
  87. package/dist/sync/api.d.ts +0 -1
  88. package/dist/sync/api.js +1 -55
  89. package/dist/sync/autoloader.d.ts +0 -1
  90. package/dist/sync/autoloader.js +5 -127
  91. package/dist/sync/conflict.d.ts +0 -1
  92. package/dist/sync/conflict.js +5 -15
  93. package/dist/sync/credentials.d.ts +0 -1
  94. package/dist/sync/credentials.js +1 -51
  95. package/dist/sync/files.d.ts +0 -1
  96. package/dist/sync/files.js +1 -71
  97. package/dist/sync/manifest.d.ts +0 -1
  98. package/dist/sync/manifest.js +1 -37
  99. package/dist/sync/repo-root.d.ts +0 -1
  100. package/dist/sync/repo-root.js +1 -24
  101. package/dist/sync/stitch-claude-md.d.ts +0 -1
  102. package/dist/sync/stitch-claude-md.js +8 -48
  103. package/dist/ui/colors.d.ts +0 -1
  104. package/dist/ui/colors.js +1 -28
  105. package/dist/ui/screens.d.ts +0 -1
  106. package/dist/ui/screens.js +5 -135
  107. package/package.json +5 -12
  108. package/plugin/.cursor-plugin/plugin.json +0 -2
  109. package/plugin/.mcp.json +1 -1
  110. package/plugin/LICENSE +15 -0
  111. package/plugin/gemini/gemini-extension.json +1 -1
  112. package/plugin/mcp.json +1 -1
  113. package/plugin/plugin.json +0 -2
  114. package/plugin/runtime/unfairly-intelligence.mjs +14 -5512
  115. package/dist/auth/browser-flow.d.ts.map +0 -1
  116. package/dist/auth/browser-flow.js.map +0 -1
  117. package/dist/cmd/connect.d.ts.map +0 -1
  118. package/dist/cmd/connect.js.map +0 -1
  119. package/dist/cmd/init.d.ts.map +0 -1
  120. package/dist/cmd/init.js.map +0 -1
  121. package/dist/cmd/invite.d.ts.map +0 -1
  122. package/dist/cmd/invite.js.map +0 -1
  123. package/dist/cmd/join.d.ts.map +0 -1
  124. package/dist/cmd/join.js.map +0 -1
  125. package/dist/cmd/login.d.ts.map +0 -1
  126. package/dist/cmd/login.js.map +0 -1
  127. package/dist/cmd/mcp.d.ts.map +0 -1
  128. package/dist/cmd/mcp.js.map +0 -1
  129. package/dist/cmd/pace.d.ts.map +0 -1
  130. package/dist/cmd/pace.js.map +0 -1
  131. package/dist/cmd/parse-share-input.d.ts.map +0 -1
  132. package/dist/cmd/parse-share-input.js.map +0 -1
  133. package/dist/cmd/pull.d.ts.map +0 -1
  134. package/dist/cmd/pull.js.map +0 -1
  135. package/dist/cmd/push.d.ts.map +0 -1
  136. package/dist/cmd/push.js.map +0 -1
  137. package/dist/cmd/resolve.d.ts.map +0 -1
  138. package/dist/cmd/resolve.js.map +0 -1
  139. package/dist/cmd/setup.d.ts.map +0 -1
  140. package/dist/cmd/setup.js.map +0 -1
  141. package/dist/cmd/status-card.d.ts.map +0 -1
  142. package/dist/cmd/status-card.js.map +0 -1
  143. package/dist/cmd/status.d.ts.map +0 -1
  144. package/dist/cmd/status.js.map +0 -1
  145. package/dist/cmd/watch.d.ts.map +0 -1
  146. package/dist/cmd/watch.js.map +0 -1
  147. package/dist/hosts/claude-code.d.ts.map +0 -1
  148. package/dist/hosts/claude-code.js.map +0 -1
  149. package/dist/hosts/claude-desktop.d.ts.map +0 -1
  150. package/dist/hosts/claude-desktop.js.map +0 -1
  151. package/dist/hosts/codex.d.ts.map +0 -1
  152. package/dist/hosts/codex.js.map +0 -1
  153. package/dist/hosts/shell-env.d.ts.map +0 -1
  154. package/dist/hosts/shell-env.js.map +0 -1
  155. package/dist/index.d.ts.map +0 -1
  156. package/dist/index.js.map +0 -1
  157. package/dist/lib/client.d.ts.map +0 -1
  158. package/dist/lib/client.js.map +0 -1
  159. package/dist/lib/events/emit.d.ts.map +0 -1
  160. package/dist/lib/events/emit.js.map +0 -1
  161. package/dist/lib/index.d.ts.map +0 -1
  162. package/dist/lib/index.js.map +0 -1
  163. package/dist/lib/transport.d.ts.map +0 -1
  164. package/dist/lib/transport.js.map +0 -1
  165. package/dist/lib/types/index.d.ts.map +0 -1
  166. package/dist/lib/types/index.js.map +0 -1
  167. package/dist/lib/vault/checkpoint.d.ts.map +0 -1
  168. package/dist/lib/vault/checkpoint.js.map +0 -1
  169. package/dist/lib/vault/read.d.ts.map +0 -1
  170. package/dist/lib/vault/read.js.map +0 -1
  171. package/dist/lib/vault/search.d.ts.map +0 -1
  172. package/dist/lib/vault/search.js.map +0 -1
  173. package/dist/pace/device.d.ts.map +0 -1
  174. package/dist/pace/device.js.map +0 -1
  175. package/dist/pace/enroll.d.ts.map +0 -1
  176. package/dist/pace/enroll.js.map +0 -1
  177. package/dist/pace/identity.d.ts.map +0 -1
  178. package/dist/pace/identity.js.map +0 -1
  179. package/dist/pace/install.d.ts.map +0 -1
  180. package/dist/pace/install.js.map +0 -1
  181. package/dist/pace/local-evidence.d.ts.map +0 -1
  182. package/dist/pace/local-evidence.js.map +0 -1
  183. package/dist/pace/local-sessions.d.ts.map +0 -1
  184. package/dist/pace/local-sessions.js.map +0 -1
  185. package/dist/pace/managed-config.d.ts.map +0 -1
  186. package/dist/pace/managed-config.js.map +0 -1
  187. package/dist/pace/normalize.d.ts.map +0 -1
  188. package/dist/pace/normalize.js.map +0 -1
  189. package/dist/pace/repository.d.ts.map +0 -1
  190. package/dist/pace/repository.js.map +0 -1
  191. package/dist/pace/schema.d.ts.map +0 -1
  192. package/dist/pace/schema.js.map +0 -1
  193. package/dist/pace/session-summary.d.ts.map +0 -1
  194. package/dist/pace/session-summary.js.map +0 -1
  195. package/dist/pace/spool.d.ts.map +0 -1
  196. package/dist/pace/spool.js.map +0 -1
  197. package/dist/pace-runtime.d.ts.map +0 -1
  198. package/dist/pace-runtime.js.map +0 -1
  199. package/dist/sync/api.d.ts.map +0 -1
  200. package/dist/sync/api.js.map +0 -1
  201. package/dist/sync/autoloader.d.ts.map +0 -1
  202. package/dist/sync/autoloader.js.map +0 -1
  203. package/dist/sync/conflict.d.ts.map +0 -1
  204. package/dist/sync/conflict.js.map +0 -1
  205. package/dist/sync/credentials.d.ts.map +0 -1
  206. package/dist/sync/credentials.js.map +0 -1
  207. package/dist/sync/files.d.ts.map +0 -1
  208. package/dist/sync/files.js.map +0 -1
  209. package/dist/sync/manifest.d.ts.map +0 -1
  210. package/dist/sync/manifest.js.map +0 -1
  211. package/dist/sync/repo-root.d.ts.map +0 -1
  212. package/dist/sync/repo-root.js.map +0 -1
  213. package/dist/sync/stitch-claude-md.d.ts.map +0 -1
  214. package/dist/sync/stitch-claude-md.js.map +0 -1
  215. package/dist/ui/colors.d.ts.map +0 -1
  216. package/dist/ui/colors.js.map +0 -1
  217. package/dist/ui/screens.d.ts.map +0 -1
  218. package/dist/ui/screens.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.4 — 2026-10-05
4
+
5
+ - **Proprietary license** — the package is now licensed under Unfairly's terms instead of MIT. It ships minified, without source maps, and no longer links to the source repository. Behavior is unchanged.
6
+
3
7
  ## 0.8.3 — 2026-09-25
4
8
 
5
9
  - **Connect in the same session** — after the person approves the machine in the browser, a detached poller claims the collector credential within seconds, so the session that showed the link starts reporting without a restart.
package/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ Copyright (c) 2026 Unfairly AI. All rights reserved.
2
+
3
+ This software is proprietary and confidential. You may install and run it to
4
+ use Unfairly services under the terms at https://unfairly.ai. No other license
5
+ is granted to use, copy, modify, merge, publish, distribute, sublicense, or
6
+ sell copies of this software, in whole or in part, or to reverse engineer it,
7
+ except as expressly permitted in a separate written agreement with Unfairly AI.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
10
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
11
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
12
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
13
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
14
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
15
+ SOFTWARE.
@@ -22,4 +22,3 @@ export declare function cliAuthUrl(apiUrl: string, input: {
22
22
  * verifies the state echo and resolves with the token.
23
23
  */
24
24
  export declare function browserAuthFlow(apiUrl: string, opts?: BrowserAuthOptions): Promise<string | null>;
25
- //# sourceMappingURL=browser-flow.d.ts.map
@@ -1,81 +1,5 @@
1
- import http from "node:http";
2
- import crypto from "node:crypto";
3
- import { spawn } from "node:child_process";
4
- const SUCCESS_HTML = `<!doctype html><html><head><meta charset="utf-8"><title>Unfairly CLI authorized</title><style>body{margin:0;background:#FAF5EB;font-family:system-ui;color:#1a1612;display:flex;align-items:center;justify-content:center;min-height:100vh}.card{background:#fff;border:3px solid #1a1612;border-radius:20px;box-shadow:6px 6px 0 #1a1612;padding:36px;max-width:420px;text-align:center}h1{margin:0 0 8px;font-size:28px}p{margin:0;color:rgba(26,22,18,.7)}</style></head><body><div class="card"><h1>Authorized.</h1><p>You can close this window and return to your terminal.</p></div></body></html>`;
5
- export function openBrowser(url) {
6
- const opener = process.platform === "darwin"
7
- ? "open"
8
- : process.platform === "win32"
9
- ? "cmd"
10
- : "xdg-open";
11
- const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
12
- try {
13
- spawn(opener, args, { stdio: "ignore", detached: true }).unref();
14
- }
15
- catch {
16
- // best-effort
17
- }
18
- }
19
- /** The /cli-auth page URL. An organization ID scopes the minted token to that org when the user is a member. */
20
- export function cliAuthUrl(apiUrl, input) {
21
- const url = new URL("/cli-auth", apiUrl);
22
- url.searchParams.set("cb", input.cb);
23
- url.searchParams.set("state", input.state);
24
- url.searchParams.set("name", input.tokenName);
25
- if (input.org && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(input.org))
26
- url.searchParams.set("org", input.org);
27
- return url;
28
- }
29
- /**
30
- * OAuth handshake via loopback HTTP server. Used by `unfairly setup`,
31
- * `unfairly login`, and `unfairly init`.
32
- *
33
- * Flow: pop a /cli-auth page in the user's browser with a one-time `state`
34
- * and a `cb=http://localhost:<port>/done` callback. The page mints a
35
- * `mcp_*` token and redirects the browser back to `cb`. Our local server
36
- * verifies the state echo and resolves with the token.
37
- */
38
- export async function browserAuthFlow(apiUrl, opts = {}) {
39
- const tokenName = opts.tokenName ?? "unfairly cli";
40
- const timeoutMs = opts.timeoutMs ?? 5 * 60_000;
41
- const state = crypto.randomBytes(16).toString("base64url");
42
- return new Promise((resolve) => {
43
- let resolved = false;
44
- const cleanup = (val) => {
45
- if (resolved)
46
- return;
47
- resolved = true;
48
- setTimeout(() => server.close(), 500);
49
- resolve(val);
50
- };
51
- const server = http.createServer((req, res) => {
52
- const url = new URL(req.url ?? "/", "http://localhost");
53
- if (url.pathname !== "/done") {
54
- res.writeHead(404).end();
55
- return;
56
- }
57
- const echoState = url.searchParams.get("state") ?? "";
58
- const token = url.searchParams.get("token") ?? "";
59
- if (echoState !== state || !token.startsWith("mcp_")) {
60
- res.writeHead(400).end("Auth failed");
61
- cleanup(null);
62
- return;
63
- }
64
- res
65
- .writeHead(200, { "Content-Type": "text/html; charset=utf-8" })
66
- .end(SUCCESS_HTML);
67
- cleanup(token);
68
- });
69
- const timeout = setTimeout(() => cleanup(null), timeoutMs);
70
- server.on("close", () => clearTimeout(timeout));
71
- server.listen(0, "127.0.0.1", () => {
72
- const addr = server.address();
73
- const port = typeof addr === "object" && addr ? addr.port : 0;
74
- const cb = `http://localhost:${port}/done`;
75
- const authUrl = cliAuthUrl(apiUrl, { cb, state, tokenName, org: opts.org ?? process.env.UNFAIRLY_ORG });
76
- console.error(`\nOpening browser to authorize:\n ${authUrl.toString()}\n(if it doesn't open, click the link above)\n`);
77
- openBrowser(authUrl.toString());
78
- });
79
- });
80
- }
81
- //# sourceMappingURL=browser-flow.js.map
1
+ import f from"node:http";import g from"node:crypto";import{spawn as y}from"node:child_process";const w='<!doctype html><html><head><meta charset="utf-8"><title>Unfairly CLI authorized</title><style>body{margin:0;background:#FAF5EB;font-family:system-ui;color:#1a1612;display:flex;align-items:center;justify-content:center;min-height:100vh}.card{background:#fff;border:3px solid #1a1612;border-radius:20px;box-shadow:6px 6px 0 #1a1612;padding:36px;max-width:420px;text-align:center}h1{margin:0 0 8px;font-size:28px}p{margin:0;color:rgba(26,22,18,.7)}</style></head><body><div class="card"><h1>Authorized.</h1><p>You can close this window and return to your terminal.</p></div></body></html>';function b(r){const t=process.platform==="darwin"?"open":process.platform==="win32"?"cmd":"xdg-open",e=process.platform==="win32"?["/c","start","",r]:[r];try{y(t,e,{stdio:"ignore",detached:!0}).unref()}catch{}}function x(r,t){const e=new URL("/cli-auth",r);return e.searchParams.set("cb",t.cb),e.searchParams.set("state",t.state),e.searchParams.set("name",t.tokenName),t.org&&/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(t.org)&&e.searchParams.set("org",t.org),e}async function P(r,t={}){const e=t.tokenName??"unfairly cli",h=t.timeoutMs??5*6e4,l=g.randomBytes(16).toString("base64url");return new Promise(p=>{let d=!1;const i=o=>{d||(d=!0,setTimeout(()=>n.close(),500),p(o))},n=f.createServer((o,s)=>{const a=new URL(o.url??"/","http://localhost");if(a.pathname!=="/done"){s.writeHead(404).end();return}const c=a.searchParams.get("state")??"",m=a.searchParams.get("token")??"";if(c!==l||!m.startsWith("mcp_")){s.writeHead(400).end("Auth failed"),i(null);return}s.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}).end(w),i(m)}),u=setTimeout(()=>i(null),h);n.on("close",()=>clearTimeout(u)),n.listen(0,"127.0.0.1",()=>{const o=n.address(),a=`http://localhost:${typeof o=="object"&&o?o.port:0}/done`,c=x(r,{cb:a,state:l,tokenName:e,org:t.org??process.env.UNFAIRLY_ORG});console.error(`
2
+ Opening browser to authorize:
3
+ ${c.toString()}
4
+ (if it doesn't open, click the link above)
5
+ `),b(c.toString())})})}export{P as browserAuthFlow,x as cliAuthUrl,b as openBrowser};
@@ -58,4 +58,3 @@ export declare const connectCommand: {
58
58
  handler: (args: ConnectArgs) => Promise<void>;
59
59
  };
60
60
  export {};
61
- //# sourceMappingURL=connect.d.ts.map
@@ -1,164 +1 @@
1
- import { parseShareInput } from "./parse-share-input.js";
2
- import { addScopedClaudeDesktopEntry, } from "../hosts/claude-desktop.js";
3
- import { addScopedClaudeCodeEntry } from "../hosts/claude-code.js";
4
- import { addScopedCodexEntry, } from "../hosts/codex.js";
5
- const API_DEFAULT = "https://app.unfairly.ai";
6
- /**
7
- * Map a mint endpoint error code to a user-facing message.
8
- */
9
- function describeMintError(status, code) {
10
- if (status === 404 || code === "not_found") {
11
- return "share link not found — double-check the URL or ask the sender to issue a new one.";
12
- }
13
- if (status === 410 || code === "revoked_or_expired") {
14
- return "share link expired or was revoked — ask the sender for a fresh one.";
15
- }
16
- if (status === 429 || code === "rate_limited") {
17
- return "rate limited (10 mints / hour per link) — try again in an hour.";
18
- }
19
- if (status === 400 || code === "no_room") {
20
- return "invalid share link — it isn't bound to a vault.";
21
- }
22
- return `server error while minting (${status} ${code}).`;
23
- }
24
- /**
25
- * Pure-ish implementation. Tests inject `deps` to drive without hitting
26
- * the network or touching real config files.
27
- */
28
- export async function runConnect(args, deps) {
29
- let parsed;
30
- try {
31
- parsed = parseShareInput(args.urlOrId);
32
- }
33
- catch (err) {
34
- deps.errLog(`✗ ${err.message}`);
35
- return { ok: false, exitCode: 1 };
36
- }
37
- const apiOrigin = args.api ?? parsed.origin ?? API_DEFAULT;
38
- const mintUrl = `${apiOrigin.replace(/\/$/, "")}/api/share-links/${parsed.shareId}/mint-token`;
39
- const body = {};
40
- if (args.as)
41
- body.email = args.as;
42
- if (args.label)
43
- body.label = args.label;
44
- deps.log("✦ Minting a 24-hour vault token…");
45
- let res;
46
- try {
47
- res = await deps.fetch(mintUrl, {
48
- method: "POST",
49
- headers: { "Content-Type": "application/json" },
50
- body: JSON.stringify(body),
51
- });
52
- }
53
- catch (err) {
54
- deps.errLog(`✗ network error reaching ${apiOrigin}: ${err.message}`);
55
- return { ok: false, exitCode: 1, shareId: parsed.shareId };
56
- }
57
- if (!res.ok) {
58
- let parsedErr = { error: "unknown" };
59
- try {
60
- parsedErr = (await res.json());
61
- }
62
- catch {
63
- // body wasn't JSON — fall through with default
64
- }
65
- const message = describeMintError(res.status, parsedErr.error ?? "unknown");
66
- deps.errLog(`✗ ${message}`);
67
- return {
68
- ok: false,
69
- exitCode: 1,
70
- shareId: parsed.shareId,
71
- errorCode: parsedErr.error,
72
- };
73
- }
74
- let mint;
75
- try {
76
- mint = (await res.json());
77
- }
78
- catch (err) {
79
- deps.errLog(`✗ mint endpoint returned non-JSON: ${err.message}`);
80
- return { ok: false, exitCode: 1, shareId: parsed.shareId };
81
- }
82
- if (!mint.token || !mint.mcp_url) {
83
- deps.errLog("✗ mint endpoint response missing token or mcp_url");
84
- return { ok: false, exitCode: 1, shareId: parsed.shareId };
85
- }
86
- const input = {
87
- orgSlug: mint.org_slug,
88
- roomSlug: mint.room_slug,
89
- shareLinkId: parsed.shareId,
90
- token: mint.token,
91
- mcpUrl: mint.mcp_url,
92
- };
93
- const [claudeDesktop, claudeCode, codex] = await Promise.all([
94
- deps.addClaudeDesktop(input),
95
- deps.addClaudeCode(input),
96
- deps.addCodex(input),
97
- ]);
98
- // Per-host success line.
99
- const reportHost = (name, result) => {
100
- if (result.status === "configured") {
101
- const verb = "replaced" in result && result.replaced ? "Updated" : "Wired";
102
- deps.log(`✓ ${verb} ${name} · ${result.key}`);
103
- }
104
- else if (result.status === "not-installed") {
105
- deps.log(` (${name} not installed — skipped)`);
106
- }
107
- else {
108
- deps.errLog(`✗ ${name}: ${result.message ?? "unknown error"}`);
109
- }
110
- };
111
- reportHost("Claude Desktop", claudeDesktop);
112
- reportHost("Claude Code", claudeCode);
113
- reportHost("Codex", codex);
114
- deps.log("");
115
- deps.log("→ Open your AI host. Ask: \"what's in this vault?\"");
116
- if (mint.expires_at) {
117
- deps.log(` Token expires at ${mint.expires_at} (≤24h).`);
118
- }
119
- else {
120
- deps.log(" Token expires in 24h.");
121
- }
122
- return {
123
- ok: true,
124
- exitCode: 0,
125
- shareId: parsed.shareId,
126
- hosts: { claudeDesktop, claudeCode, codex },
127
- mint,
128
- };
129
- }
130
- export const connectCommand = {
131
- command: "connect <urlOrId>",
132
- describe: "Connect to a shared vault via share link — mints a 24h token, wires all hosts",
133
- builder: (yargs) => yargs
134
- .positional("urlOrId", {
135
- type: "string",
136
- demandOption: true,
137
- describe: "Share link URL (https://app.unfairly.ai/s/<uuid>), short form (s/<uuid>), or bare UUID",
138
- })
139
- .option("as", {
140
- type: "string",
141
- describe: "Email to attribute the token to (audit only)",
142
- })
143
- .option("label", {
144
- type: "string",
145
- describe: "Human label for the audit log (≤100 chars)",
146
- })
147
- .option("api", {
148
- type: "string",
149
- describe: "Override the API origin (defaults to the URL's origin or app.unfairly.ai)",
150
- }),
151
- handler: async (args) => {
152
- const outcome = await runConnect(args, {
153
- fetch: globalThis.fetch.bind(globalThis),
154
- addClaudeDesktop: addScopedClaudeDesktopEntry,
155
- addClaudeCode: addScopedClaudeCodeEntry,
156
- addCodex: addScopedCodexEntry,
157
- log: (line) => console.log(line),
158
- errLog: (line) => console.error(line),
159
- });
160
- if (!outcome.ok)
161
- process.exit(outcome.exitCode);
162
- },
163
- };
164
- //# sourceMappingURL=connect.js.map
1
+ import{parseShareInput as g}from"./parse-share-input.js";import{addScopedClaudeDesktopEntry as f}from"../hosts/claude-desktop.js";import{addScopedClaudeCodeEntry as C}from"../hosts/claude-code.js";import{addScopedCodexEntry as y}from"../hosts/codex.js";const b="https://app.unfairly.ai";function x(r,e){return r===404||e==="not_found"?"share link not found \u2014 double-check the URL or ask the sender to issue a new one.":r===410||e==="revoked_or_expired"?"share link expired or was revoked \u2014 ask the sender for a fresh one.":r===429||e==="rate_limited"?"rate limited (10 mints / hour per link) \u2014 try again in an hour.":r===400||e==="no_room"?"invalid share link \u2014 it isn't bound to a vault.":`server error while minting (${r} ${e}).`}async function I(r,e){let o;try{o=g(r.urlOrId)}catch(t){return e.errLog(`\u2717 ${t.message}`),{ok:!1,exitCode:1}}const u=r.api??o.origin??b,k=`${u.replace(/\/$/,"")}/api/share-links/${o.shareId}/mint-token`,s={};r.as&&(s.email=r.as),r.label&&(s.label=r.label),e.log("\u2726 Minting a 24-hour vault token\u2026");let a;try{a=await e.fetch(k,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)})}catch(t){return e.errLog(`\u2717 network error reaching ${u}: ${t.message}`),{ok:!1,exitCode:1,shareId:o.shareId}}if(!a.ok){let t={error:"unknown"};try{t=await a.json()}catch{}const i=x(a.status,t.error??"unknown");return e.errLog(`\u2717 ${i}`),{ok:!1,exitCode:1,shareId:o.shareId,errorCode:t.error}}let n;try{n=await a.json()}catch(t){return e.errLog(`\u2717 mint endpoint returned non-JSON: ${t.message}`),{ok:!1,exitCode:1,shareId:o.shareId}}if(!n.token||!n.mcp_url)return e.errLog("\u2717 mint endpoint response missing token or mcp_url"),{ok:!1,exitCode:1,shareId:o.shareId};const d={orgSlug:n.org_slug,roomSlug:n.room_slug,shareLinkId:o.shareId,token:n.token,mcpUrl:n.mcp_url},[h,c,p]=await Promise.all([e.addClaudeDesktop(d),e.addClaudeCode(d),e.addCodex(d)]),l=(t,i)=>{if(i.status==="configured"){const m="replaced"in i&&i.replaced?"Updated":"Wired";e.log(`\u2713 ${m} ${t} \xB7 ${i.key}`)}else i.status==="not-installed"?e.log(` (${t} not installed \u2014 skipped)`):e.errLog(`\u2717 ${t}: ${i.message??"unknown error"}`)};return l("Claude Desktop",h),l("Claude Code",c),l("Codex",p),e.log(""),e.log(`\u2192 Open your AI host. Ask: "what's in this vault?"`),n.expires_at?e.log(` Token expires at ${n.expires_at} (\u226424h).`):e.log(" Token expires in 24h."),{ok:!0,exitCode:0,shareId:o.shareId,hosts:{claudeDesktop:h,claudeCode:c,codex:p},mint:n}}const v={command:"connect <urlOrId>",describe:"Connect to a shared vault via share link \u2014 mints a 24h token, wires all hosts",builder:r=>r.positional("urlOrId",{type:"string",demandOption:!0,describe:"Share link URL (https://app.unfairly.ai/s/<uuid>), short form (s/<uuid>), or bare UUID"}).option("as",{type:"string",describe:"Email to attribute the token to (audit only)"}).option("label",{type:"string",describe:"Human label for the audit log (\u2264100 chars)"}).option("api",{type:"string",describe:"Override the API origin (defaults to the URL's origin or app.unfairly.ai)"}),handler:async r=>{const e=await I(r,{fetch:globalThis.fetch.bind(globalThis),addClaudeDesktop:f,addClaudeCode:C,addCodex:y,log:o=>console.log(o),errLog:o=>console.error(o)});e.ok||process.exit(e.exitCode)}};export{v as connectCommand,I as runConnect};
@@ -6,4 +6,3 @@ export type InitOptions = {
6
6
  assumeAuthed?: boolean;
7
7
  };
8
8
  export declare function runInit(opts?: InitOptions): Promise<void>;
9
- //# sourceMappingURL=init.d.ts.map
package/dist/cmd/init.js CHANGED
@@ -1,138 +1,2 @@
1
- import { promises as fs } from "node:fs";
2
- import path from "node:path";
3
- import readline from "node:readline/promises";
4
- import { stdin as input, stdout as output } from "node:process";
5
- import { SyncClient } from "../sync/api.js";
6
- import { emptyManifest, writeManifest } from "../sync/manifest.js";
7
- import { getToken, setToken } from "../sync/credentials.js";
8
- import { browserAuthFlow } from "../auth/browser-flow.js";
9
- import { runPull } from "./pull.js";
10
- import { findRepoRoot } from "../sync/repo-root.js";
11
- import { stitchClaudeMd } from "../sync/stitch-claude-md.js";
12
- import { banner, stepHeader, result, divider, defaultOpts } from "../ui/screens.js";
13
- const DEFAULT_API = "https://app.unfairly.ai";
14
- function isTty() {
15
- return Boolean(process.stdin.isTTY);
16
- }
17
- export async function runInit(opts = {}) {
18
- const cwd = process.cwd();
19
- const target = path.join(cwd, ".unfairly");
20
- const apiUrl = process.env.UNFAIRLY_API_URL ?? DEFAULT_API;
21
- const ui = defaultOpts();
22
- const write = (s) => process.stderr.write(`${s}\n`);
23
- if (!opts.assumeAuthed) {
24
- write(banner(ui));
25
- write("");
26
- }
27
- // Confirm overwrite if .unfairly/ already exists
28
- try {
29
- await fs.access(target);
30
- if (!opts.yes) {
31
- if (!isTty()) {
32
- write(result("err", ".unfairly/ already exists — re-run with --yes to overwrite (non-TTY).", ui));
33
- process.exit(1);
34
- }
35
- const rl = readline.createInterface({ input, output });
36
- const ans = await rl.question(`.unfairly/ already exists. Re-initialize? [y/N] `);
37
- rl.close();
38
- if (ans.trim().toLowerCase() !== "y") {
39
- write(result("skip", "aborted", ui));
40
- return;
41
- }
42
- }
43
- }
44
- catch {
45
- // didn't exist; fine
46
- }
47
- // Resolve token (flag → env → keychain → browser → paste)
48
- let token = opts.token ?? process.env.UNFAIRLY_TOKEN ?? (await getToken());
49
- if (!token) {
50
- if (!isTty()) {
51
- write(result("err", "No token. Pass --token or set UNFAIRLY_TOKEN (non-TTY).", ui));
52
- process.exit(1);
53
- }
54
- if (!opts.noBrowser) {
55
- const fromBrowser = await browserAuthFlow(apiUrl);
56
- if (fromBrowser)
57
- token = fromBrowser;
58
- }
59
- if (!token) {
60
- const rl = readline.createInterface({ input, output });
61
- token = (await rl.question("Paste mcp_* token: ")).trim();
62
- rl.close();
63
- }
64
- }
65
- if (!token || !token.startsWith("mcp_")) {
66
- write(result("err", "Token must start with mcp_", ui));
67
- process.exit(1);
68
- }
69
- await setToken(token);
70
- // Verify + list vaults
71
- const client = new SyncClient(apiUrl, token);
72
- write(stepHeader(1, 3, "VAULT INVENTORY", ui));
73
- let status;
74
- try {
75
- status = await client.status();
76
- }
77
- catch (err) {
78
- write(result("err", `failed to reach ${apiUrl}/api/sync/status — ${err.message}`, ui));
79
- process.exit(1);
80
- }
81
- if (status.vaults.length === 0) {
82
- write(result("err", "no accessible vaults. Ask an admin to grant you access.", ui));
83
- process.exit(1);
84
- }
85
- for (const v of status.vaults.slice(0, 6)) {
86
- write(result("ok", `${v.name.padEnd(20)} · ${v.doc_count} docs${v.track_in_git ? "" : " (gitignored)"}`, ui));
87
- }
88
- if (status.vaults.length > 6) {
89
- const remaining = status.vaults.length - 6;
90
- const totalDocs = status.vaults.reduce((s, v) => s + v.doc_count, 0);
91
- write(result("skip", `… ${remaining} more vaults · ${totalDocs} docs total`, ui));
92
- }
93
- write("");
94
- // Seed manifest + pull
95
- write(stepHeader(2, 3, "PULL", ui));
96
- await fs.mkdir(target, { recursive: true });
97
- const manifest = emptyManifest(apiUrl);
98
- for (const v of status.vaults) {
99
- manifest.vaults[v.slug] = {
100
- id: v.id,
101
- slug: v.slug,
102
- name: v.name,
103
- track_in_git: v.track_in_git,
104
- last_pulled_at: null,
105
- last_pushed_at: null,
106
- docs: {},
107
- };
108
- }
109
- await writeManifest(cwd, manifest);
110
- await runPull(undefined);
111
- write(result("ok", "wrote .unfairly/CLAUDE.md (auto-loader for Claude Code)", ui));
112
- write("");
113
- // Stitch repo's root CLAUDE.md
114
- write(stepHeader(3, 3, "STITCH", ui));
115
- const repoRoot = await findRepoRoot(cwd);
116
- if (!repoRoot) {
117
- write(result("skip", "not in a git repo — skipping CLAUDE.md stitch", ui));
118
- }
119
- else {
120
- const stitch = await stitchClaudeMd(repoRoot);
121
- if (stitch.action === "created") {
122
- write(result("ok", `created ${stitch.path} with @.unfairly/CLAUDE.md`, ui));
123
- }
124
- else if (stitch.action === "appended") {
125
- write(result("ok", `appended @.unfairly/CLAUDE.md to ${stitch.path}`, ui));
126
- }
127
- else if (stitch.action === "noop") {
128
- write(result("skip", `${stitch.path} already references the autoloader`, ui));
129
- }
130
- else if (stitch.action === "error") {
131
- write(result("warn", `couldn't stitch ${stitch.path} — ${stitch.message}`, ui));
132
- }
133
- }
134
- write("");
135
- write(divider(ui));
136
- write("");
137
- }
138
- //# sourceMappingURL=init.js.map
1
+ import{promises as m}from"node:fs";import v from"node:path";import w from"node:readline/promises";import{stdin as h,stdout as k}from"node:process";import{SyncClient as A}from"../sync/api.js";import{emptyManifest as L,writeManifest as $}from"../sync/manifest.js";import{getToken as U,setToken as C}from"../sync/credentials.js";import{browserAuthFlow as I}from"../auth/browser-flow.js";import{runPull as E}from"./pull.js";import{findRepoRoot as N}from"../sync/repo-root.js";import{stitchClaudeMd as R}from"../sync/stitch-claude-md.js";import{banner as x,stepHeader as u,result as o,divider as Y,defaultOpts as D}from"../ui/screens.js";const P="https://app.unfairly.ai";function y(){return!!process.stdin.isTTY}async function W(a={}){const c=process.cwd(),p=v.join(c,".unfairly"),n=process.env.UNFAIRLY_API_URL??P,s=D(),e=t=>process.stderr.write(`${t}
2
+ `);a.assumeAuthed||(e(x(s)),e(""));try{if(await m.access(p),!a.yes){y()||(e(o("err",".unfairly/ already exists \u2014 re-run with --yes to overwrite (non-TTY).",s)),process.exit(1));const t=w.createInterface({input:h,output:k}),l=await t.question(".unfairly/ already exists. Re-initialize? [y/N] ");if(t.close(),l.trim().toLowerCase()!=="y"){e(o("skip","aborted",s));return}}}catch{}let r=a.token??process.env.UNFAIRLY_TOKEN??await U();if(!r){if(y()||(e(o("err","No token. Pass --token or set UNFAIRLY_TOKEN (non-TTY).",s)),process.exit(1)),!a.noBrowser){const t=await I(n);t&&(r=t)}if(!r){const t=w.createInterface({input:h,output:k});r=(await t.question("Paste mcp_* token: ")).trim(),t.close()}}(!r||!r.startsWith("mcp_"))&&(e(o("err","Token must start with mcp_",s)),process.exit(1)),await C(r);const _=new A(n,r);e(u(1,3,"VAULT INVENTORY",s));let i;try{i=await _.status()}catch(t){e(o("err",`failed to reach ${n}/api/sync/status \u2014 ${t.message}`,s)),process.exit(1)}i.vaults.length===0&&(e(o("err","no accessible vaults. Ask an admin to grant you access.",s)),process.exit(1));for(const t of i.vaults.slice(0,6))e(o("ok",`${t.name.padEnd(20)} \xB7 ${t.doc_count} docs${t.track_in_git?"":" (gitignored)"}`,s));if(i.vaults.length>6){const t=i.vaults.length-6,l=i.vaults.reduce((g,T)=>g+T.doc_count,0);e(o("skip",`\u2026 ${t} more vaults \xB7 ${l} docs total`,s))}e(""),e(u(2,3,"PULL",s)),await m.mkdir(p,{recursive:!0});const f=L(n);for(const t of i.vaults)f.vaults[t.slug]={id:t.id,slug:t.slug,name:t.name,track_in_git:t.track_in_git,last_pulled_at:null,last_pushed_at:null,docs:{}};await $(c,f),await E(void 0),e(o("ok","wrote .unfairly/CLAUDE.md (auto-loader for Claude Code)",s)),e(""),e(u(3,3,"STITCH",s));const d=await N(c);if(!d)e(o("skip","not in a git repo \u2014 skipping CLAUDE.md stitch",s));else{const t=await R(d);t.action==="created"?e(o("ok",`created ${t.path} with @.unfairly/CLAUDE.md`,s)):t.action==="appended"?e(o("ok",`appended @.unfairly/CLAUDE.md to ${t.path}`,s)):t.action==="noop"?e(o("skip",`${t.path} already references the autoloader`,s)):t.action==="error"&&e(o("warn",`couldn't stitch ${t.path} \u2014 ${t.message}`,s))}e(""),e(Y(s)),e("")}export{W as runInit};
@@ -23,4 +23,3 @@ export declare const inviteCommand: {
23
23
  handler: (args: InviteArgs) => Promise<void>;
24
24
  };
25
25
  export {};
26
- //# sourceMappingURL=invite.d.ts.map
@@ -1,73 +1,4 @@
1
- import { getToken } from "../sync/credentials.js";
2
- const API_DEFAULT = "https://app.unfairly.ai";
3
- export const inviteCommand = {
4
- command: "invite",
5
- describe: "Generate a persona-tagged invite link for a teammate",
6
- builder: (yargs) => yargs
7
- .option("email", {
8
- type: "string",
9
- demandOption: true,
10
- describe: "Email of the person you're inviting",
11
- })
12
- .option("persona-surface", {
13
- type: "string",
14
- demandOption: true,
15
- choices: ["builder-terminal", "player-web"],
16
- describe: "Onboarding role: builder-terminal = Champion, player-web = Player",
17
- })
18
- .option("persona-workflow", {
19
- type: "string",
20
- choices: [
21
- "strategy",
22
- "synthesis",
23
- "clinical-operations",
24
- "credentialing",
25
- "technical",
26
- "recruiting",
27
- ],
28
- describe: "What workflow they own",
29
- })
30
- .option("org", {
31
- type: "string",
32
- describe: "Org slug or id (required for CLI; web infers from cookie)",
33
- })
34
- .option("api", {
35
- type: "string",
36
- default: API_DEFAULT,
37
- describe: "API origin",
38
- }),
39
- handler: async (args) => {
40
- const token = await getToken();
41
- if (!token) {
42
- console.error("Not signed in. Run `unfairly login` first, then re-run `unfairly invite`.");
43
- process.exit(1);
44
- }
45
- const res = await fetch(`${args.api}/api/onboard/invite`, {
46
- method: "POST",
47
- headers: {
48
- "Content-Type": "application/json",
49
- Authorization: `Bearer ${token}`,
50
- },
51
- body: JSON.stringify({
52
- email: args.email,
53
- persona_surface: args["persona-surface"],
54
- persona_workflow: args["persona-workflow"],
55
- org_slug: args.org,
56
- }),
57
- });
58
- if (!res.ok) {
59
- const err = await res.text();
60
- console.error(`Failed to create invite: ${res.status} ${err}`);
61
- process.exit(1);
62
- }
63
- const data = await res.json();
64
- console.log(`\n✦ Invite ready for ${data.target_email}`);
65
- console.log(` Surface: ${data.persona_surface}`);
66
- if (data.persona_workflow) {
67
- console.log(` Workflow: ${data.persona_workflow}`);
68
- }
69
- console.log(`\n ${data.url}\n`);
70
- console.log(`Send that to them. They'll paste-run \`npx unfairly join\` from there.\n`);
71
- },
72
- };
73
- //# sourceMappingURL=invite.js.map
1
+ import{getToken as t}from"../sync/credentials.js";const a="https://app.unfairly.ai",l={command:"invite",describe:"Generate a persona-tagged invite link for a teammate",builder:e=>e.option("email",{type:"string",demandOption:!0,describe:"Email of the person you're inviting"}).option("persona-surface",{type:"string",demandOption:!0,choices:["builder-terminal","player-web"],describe:"Onboarding role: builder-terminal = Champion, player-web = Player"}).option("persona-workflow",{type:"string",choices:["strategy","synthesis","clinical-operations","credentialing","technical","recruiting"],describe:"What workflow they own"}).option("org",{type:"string",describe:"Org slug or id (required for CLI; web infers from cookie)"}).option("api",{type:"string",default:a,describe:"API origin"}),handler:async e=>{const n=await t();n||(console.error("Not signed in. Run `unfairly login` first, then re-run `unfairly invite`."),process.exit(1));const r=await fetch(`${e.api}/api/onboard/invite`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${n}`},body:JSON.stringify({email:e.email,persona_surface:e["persona-surface"],persona_workflow:e["persona-workflow"],org_slug:e.org})});if(!r.ok){const i=await r.text();console.error(`Failed to create invite: ${r.status} ${i}`),process.exit(1)}const o=await r.json();console.log(`
2
+ \u2726 Invite ready for ${o.target_email}`),console.log(` Surface: ${o.persona_surface}`),o.persona_workflow&&console.log(` Workflow: ${o.persona_workflow}`),console.log(`
3
+ ${o.url}
4
+ `),console.log("Send that to them. They'll paste-run `npx unfairly join` from there.\n")}};export{l as inviteCommand};
@@ -17,4 +17,3 @@ export declare const joinCommand: {
17
17
  handler: (args: JoinArgs) => Promise<void>;
18
18
  };
19
19
  export {};
20
- //# sourceMappingURL=join.d.ts.map
package/dist/cmd/join.js CHANGED
@@ -1,91 +1 @@
1
- import { setTimeout as sleep } from "node:timers/promises";
2
- import { openBrowser } from "../auth/browser-flow.js";
3
- import { setToken } from "../sync/credentials.js";
4
- const API_DEFAULT = "https://app.unfairly.ai";
5
- export const joinCommand = {
6
- command: "join <token>",
7
- describe: "Join an Unfairly org via a persona-tagged invite token",
8
- builder: (yargs) => yargs
9
- .positional("token", { type: "string", demandOption: true })
10
- .option("api", { type: "string", default: API_DEFAULT })
11
- .option("host", {
12
- type: "string",
13
- choices: ["claude-code", "codex", "cowork"],
14
- describe: "Force a specific host (default: pick by persona)",
15
- }),
16
- handler: async (args) => {
17
- // 1. Init session
18
- console.log("✦ Initializing join session…");
19
- const initRes = await fetch(`${args.api}/api/cli/join/init`, {
20
- method: "POST",
21
- headers: { "Content-Type": "application/json" },
22
- body: JSON.stringify({ token: args.token }),
23
- });
24
- if (!initRes.ok) {
25
- const err = await initRes.text();
26
- console.error(`Failed to init: ${initRes.status} ${err}`);
27
- process.exit(1);
28
- }
29
- const init = await initRes.json();
30
- // 2. Open browser
31
- console.log(`✓ Opening browser to authorize…`);
32
- console.log(` (if it doesn't open: ${init.auth_url})`);
33
- openBrowser(init.auth_url);
34
- // 3. Poll for completion
35
- let waited = 0;
36
- let interval = init.poll_interval_seconds * 1000;
37
- let result = null;
38
- while (waited < init.ttl_seconds * 1000) {
39
- await sleep(interval);
40
- waited += interval;
41
- const pollRes = await fetch(init.poll_url);
42
- if (!pollRes.ok) {
43
- console.error(`Poll error: ${pollRes.status}`);
44
- process.exit(1);
45
- }
46
- const poll = await pollRes.json();
47
- if (poll.status === "completed") {
48
- result = poll;
49
- break;
50
- }
51
- if (poll.status === "expired") {
52
- console.error("Session expired. Re-run the command.");
53
- process.exit(1);
54
- }
55
- if (waited > init.poll_backoff_after_seconds * 1000) {
56
- interval = init.poll_backoff_interval_seconds * 1000;
57
- }
58
- }
59
- if (!result) {
60
- console.error("Timed out waiting for browser auth. Re-run the command.");
61
- process.exit(1);
62
- }
63
- console.log(`✓ You're in. Joining as: ${result.display_name} (${result.persona_surface}/${result.persona_workflow ?? "—"})`);
64
- // 4. Pick host (default by persona surface)
65
- const host = args.host ??
66
- (result.persona_surface === "oracle-cowork" ? "cowork" : "claude-code");
67
- // 5. Persist the join JWT via existing credential store + configure host.
68
- // Existing host writers take a single `apiUrl: string` arg and read
69
- // the token from credentials.
70
- await setToken(result.jwt);
71
- console.log(`✓ Configuring ${host}…`);
72
- if (host === "claude-code") {
73
- const { configureClaudeCode } = await import("../hosts/claude-code.js");
74
- await configureClaudeCode(args.api, result.jwt);
75
- }
76
- else if (host === "codex") {
77
- const { configureCodex } = await import("../hosts/codex.js");
78
- await configureCodex(args.api);
79
- }
80
- else if (host === "cowork") {
81
- const { configureClaudeDesktop } = await import("../hosts/claude-desktop.js");
82
- await configureClaudeDesktop(args.api, result.jwt);
83
- console.log("⚠ Quit and relaunch Claude Desktop to load the new MCP server.");
84
- }
85
- // 6. Print persona-shaped welcome
86
- const welcomeRes = await fetch(`${args.api}/api/cli/welcome?persona=${result.persona_surface}&workflow=${result.persona_workflow ?? ""}`);
87
- const welcome = await welcomeRes.text();
88
- console.log(welcome);
89
- },
90
- };
91
- //# sourceMappingURL=join.js.map
1
+ import{setTimeout as p}from"node:timers/promises";import{openBrowser as d}from"../auth/browser-flow.js";import{setToken as w}from"../sync/credentials.js";const f="https://app.unfairly.ai",_={command:"join <token>",describe:"Join an Unfairly org via a persona-tagged invite token",builder:t=>t.positional("token",{type:"string",demandOption:!0}).option("api",{type:"string",default:f}).option("host",{type:"string",choices:["claude-code","codex","cowork"],describe:"Force a specific host (default: pick by persona)"}),handler:async t=>{console.log("\u2726 Initializing join session\u2026");const n=await fetch(`${t.api}/api/cli/join/init`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token:t.token})});if(!n.ok){const e=await n.text();console.error(`Failed to init: ${n.status} ${e}`),process.exit(1)}const i=await n.json();console.log("\u2713 Opening browser to authorize\u2026"),console.log(` (if it doesn't open: ${i.auth_url})`),d(i.auth_url);let s=0,r=i.poll_interval_seconds*1e3,o=null;for(;s<i.ttl_seconds*1e3;){await p(r),s+=r;const e=await fetch(i.poll_url);e.ok||(console.error(`Poll error: ${e.status}`),process.exit(1));const l=await e.json();if(l.status==="completed"){o=l;break}l.status==="expired"&&(console.error("Session expired. Re-run the command."),process.exit(1)),s>i.poll_backoff_after_seconds*1e3&&(r=i.poll_backoff_interval_seconds*1e3)}o||(console.error("Timed out waiting for browser auth. Re-run the command."),process.exit(1)),console.log(`\u2713 You're in. Joining as: ${o.display_name} (${o.persona_surface}/${o.persona_workflow??"\u2014"})`);const a=t.host??(o.persona_surface==="oracle-cowork"?"cowork":"claude-code");if(await w(o.jwt),console.log(`\u2713 Configuring ${a}\u2026`),a==="claude-code"){const{configureClaudeCode:e}=await import("../hosts/claude-code.js");await e(t.api,o.jwt)}else if(a==="codex"){const{configureCodex:e}=await import("../hosts/codex.js");await e(t.api)}else if(a==="cowork"){const{configureClaudeDesktop:e}=await import("../hosts/claude-desktop.js");await e(t.api,o.jwt),console.log("\u26A0 Quit and relaunch Claude Desktop to load the new MCP server.")}const c=await(await fetch(`${t.api}/api/cli/welcome?persona=${o.persona_surface}&workflow=${o.persona_workflow??""}`)).text();console.log(c)}};export{_ as joinCommand};
@@ -18,4 +18,3 @@ export type LoginOptions = {
18
18
  * Install globally first (`npm i -g unfairly`) or use `unfairly setup`.
19
19
  */
20
20
  export declare function runLogin(opts?: LoginOptions): Promise<void>;
21
- //# sourceMappingURL=login.d.ts.map