@bodhiapp/bodhi-js-cli 0.0.32

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.
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const I=require("node:http"),w=require("node:url"),r=require("@bodhiapp/bodhi-js-core"),P=5173;class L extends r.DirectClientBase{constructor(e,n){const l={authClientId:e.authClientId,authServerUrl:e.authServerUrl,storagePrefix:e.storagePrefix??"bodhi-js-sdk:cli:direct",logLevel:e.logLevel??"warn",loggerPrefix:"CliClient",apiTimeoutMs:e.apiTimeoutMs,storage:e.storage??new r.InMemoryStorage,initialTokens:e.initialTokens};super(l,n),this._callbackPort=P,this._serverUrl=e.serverUrl,this.serverUrl=e.serverUrl}async init(){return super.init({serverUrl:this._serverUrl,selectedConnection:!0,testConnection:!0})}createMcpTransportConfig(e){return{url:new w.URL(`${this._serverUrl}${e}`),fetch:r.createDirectMcpFetch(async()=>(await this.getAuthState()).accessToken)}}async login(e){const n=e?.callbackPort??P;this._callbackPort=n;const l=`http://localhost:${n}/callback`,T=e?.userRole??"scope_user_user",y=e?.requested,f=e?.onReviewUrl;let g,m,h,b,c;const S=new Promise((u,t)=>{b=u,c=t}),p=I.createServer(async(u,t)=>{const d=new w.URL(u.url,`http://localhost:${n}`);if(d.pathname!=="/callback"){t.writeHead(404),t.end("Not found");return}if(d.searchParams.get("flow_type")==="access_request")try{const s=await this.getAccessRequestStatus(g),{status:i,access_request_scope:a}=r.unwrapResponse(s);if(i!=="approved"){t.writeHead(200,{"Content-Type":"text/html"}),t.end(`<html><body><h1>Access Request ${i}</h1></body></html>`),c(new Error(`Access request ${i}`));return}const E=`openid profile email roles ${a??""}`.trim();m=r.generateCodeVerifier();const A=await r.generateCodeChallenge(m);h=r.generateCodeVerifier(),await this._storageSet({[this.storageKeys.CODE_VERIFIER]:m,[this.storageKeys.STATE]:h});const o=new w.URL(this.authEndpoints.authorize);o.searchParams.set("client_id",this.authClientId),o.searchParams.set("response_type","code"),o.searchParams.set("redirect_uri",l),o.searchParams.set("scope",E),o.searchParams.set("code_challenge",A),o.searchParams.set("code_challenge_method","S256"),o.searchParams.set("state",h),t.writeHead(302,{Location:o.toString()}),t.end()}catch(s){t.writeHead(500,{"Content-Type":"text/html"}),t.end(`<html><body><h1>Error</h1><p>${s}</p></body></html>`),c(s instanceof Error?s:new Error(String(s)))}else{const s=d.searchParams.get("code"),i=d.searchParams.get("state");if(!s||!i||i!==h){t.writeHead(400,{"Content-Type":"text/html"}),t.end("<html><body><h1>Invalid callback</h1></body></html>"),c(new Error("Invalid callback parameters"));return}try{await this.exchangeCodeForTokens(s),await this.init();const a=await this.getAuthState();this.setAuthState(a),t.writeHead(200,{"Content-Type":"text/html"}),t.end("<html><body><h1>Login successful</h1><p>You can close this window.</p></body></html>"),b(a)}catch(a){t.writeHead(500,{"Content-Type":"text/html"}),t.end(`<html><body><h1>Login failed</h1><p>${a}</p></body></html>`),c(a instanceof Error?a:new Error(String(a)))}}});p.listen(n,"127.0.0.1");const C=e?.loginTimeoutMs??3e5,U=setTimeout(()=>{p.close(),c(new Error(`Login timed out after ${C}ms. User did not complete the browser flow.`))},C),_=new r.AccessRequestBuilder(this.authClientId).requestedRole(T).flowType("redirect").redirectUrl(`${l}?flow_type=access_request`);y&&_.requested(y);const v=_.build(),R=await this.requestAccess(v),{id:k,review_url:q}=r.unwrapResponse(R);g=k,f?.(q);try{return await S}finally{clearTimeout(U),p.close()}}async performOAuthPkce(e){throw new Error("performOAuthPkce is not supported in CLI mode. Use login() instead.")}_getRedirectUri(){return`http://localhost:${this._callbackPort}/callback`}}const M="production";Object.defineProperty(exports,"InMemoryStorage",{enumerable:!0,get:()=>r.InMemoryStorage});exports.CLI_BUILD_MODE=M;exports.CliClient=L;
@@ -0,0 +1,126 @@
1
+ import L from "node:http";
2
+ import { URL as p } from "node:url";
3
+ import { DirectClientBase as x, InMemoryStorage as $, createDirectMcpFetch as M, unwrapResponse as b, generateCodeVerifier as f, generateCodeChallenge as D, AccessRequestBuilder as H } from "@bodhiapp/bodhi-js-core";
4
+ import { InMemoryStorage as G } from "@bodhiapp/bodhi-js-core";
5
+ const T = 5173;
6
+ class V extends x {
7
+ constructor(e, o) {
8
+ const l = {
9
+ authClientId: e.authClientId,
10
+ authServerUrl: e.authServerUrl,
11
+ storagePrefix: e.storagePrefix ?? "bodhi-js-sdk:cli:direct",
12
+ logLevel: e.logLevel ?? "warn",
13
+ loggerPrefix: "CliClient",
14
+ apiTimeoutMs: e.apiTimeoutMs,
15
+ storage: e.storage ?? new $(),
16
+ initialTokens: e.initialTokens
17
+ };
18
+ super(l, o), this._callbackPort = T, this._serverUrl = e.serverUrl, this.serverUrl = e.serverUrl;
19
+ }
20
+ /**
21
+ * Simplified init — uses stored serverUrl, always tests connection.
22
+ */
23
+ async init() {
24
+ return super.init({
25
+ serverUrl: this._serverUrl,
26
+ selectedConnection: !0,
27
+ testConnection: !0
28
+ });
29
+ }
30
+ /**
31
+ * Create MCP transport config for StreamableHTTPClientTransport.
32
+ * CLI is always direct mode — uses standard fetch with Bearer token injection.
33
+ */
34
+ createMcpTransportConfig(e) {
35
+ return {
36
+ url: new p(`${this._serverUrl}${e}`),
37
+ fetch: M(async () => (await this.getAuthState()).accessToken)
38
+ };
39
+ }
40
+ /**
41
+ * Full CLI OAuth login flow:
42
+ * 1. Start localhost callback server
43
+ * 2. Create access request (with redirect to callback)
44
+ * 3. Call onReviewUrl so host can open browser
45
+ * 4. Wait for approval redirect → 302 to Keycloak PKCE
46
+ * 5. Wait for Keycloak callback → exchange code for tokens
47
+ * 6. Auto-init client
48
+ * 7. Close server and return AuthState
49
+ */
50
+ async login(e) {
51
+ const o = e?.callbackPort ?? T;
52
+ this._callbackPort = o;
53
+ const l = `http://localhost:${o}/callback`, P = e?.userRole ?? "scope_user_user", w = e?.requested, S = e?.onReviewUrl;
54
+ let y, d, i, g, c;
55
+ const U = new Promise((h, t) => {
56
+ g = h, c = t;
57
+ }), m = L.createServer(async (h, t) => {
58
+ const u = new p(h.url, `http://localhost:${o}`);
59
+ if (u.pathname !== "/callback") {
60
+ t.writeHead(404), t.end("Not found");
61
+ return;
62
+ }
63
+ if (u.searchParams.get("flow_type") === "access_request")
64
+ try {
65
+ const r = await this.getAccessRequestStatus(y), { status: n, access_request_scope: s } = b(r);
66
+ if (n !== "approved") {
67
+ t.writeHead(200, { "Content-Type": "text/html" }), t.end(`<html><body><h1>Access Request ${n}</h1></body></html>`), c(new Error(`Access request ${n}`));
68
+ return;
69
+ }
70
+ const I = `openid profile email roles ${s ?? ""}`.trim();
71
+ d = f();
72
+ const q = await D(d);
73
+ i = f(), await this._storageSet({
74
+ [this.storageKeys.CODE_VERIFIER]: d,
75
+ [this.storageKeys.STATE]: i
76
+ });
77
+ const a = new p(this.authEndpoints.authorize);
78
+ a.searchParams.set("client_id", this.authClientId), a.searchParams.set("response_type", "code"), a.searchParams.set("redirect_uri", l), a.searchParams.set("scope", I), a.searchParams.set("code_challenge", q), a.searchParams.set("code_challenge_method", "S256"), a.searchParams.set("state", i), t.writeHead(302, { Location: a.toString() }), t.end();
79
+ } catch (r) {
80
+ t.writeHead(500, { "Content-Type": "text/html" }), t.end(`<html><body><h1>Error</h1><p>${r}</p></body></html>`), c(r instanceof Error ? r : new Error(String(r)));
81
+ }
82
+ else {
83
+ const r = u.searchParams.get("code"), n = u.searchParams.get("state");
84
+ if (!r || !n || n !== i) {
85
+ t.writeHead(400, { "Content-Type": "text/html" }), t.end("<html><body><h1>Invalid callback</h1></body></html>"), c(new Error("Invalid callback parameters"));
86
+ return;
87
+ }
88
+ try {
89
+ await this.exchangeCodeForTokens(r), await this.init();
90
+ const s = await this.getAuthState();
91
+ this.setAuthState(s), t.writeHead(200, { "Content-Type": "text/html" }), t.end("<html><body><h1>Login successful</h1><p>You can close this window.</p></body></html>"), g(s);
92
+ } catch (s) {
93
+ t.writeHead(500, { "Content-Type": "text/html" }), t.end(`<html><body><h1>Login failed</h1><p>${s}</p></body></html>`), c(s instanceof Error ? s : new Error(String(s)));
94
+ }
95
+ }
96
+ });
97
+ m.listen(o, "127.0.0.1");
98
+ const _ = e?.loginTimeoutMs ?? 3e5, v = setTimeout(() => {
99
+ m.close(), c(new Error(`Login timed out after ${_}ms. User did not complete the browser flow.`));
100
+ }, _), C = new H(this.authClientId).requestedRole(P).flowType("redirect").redirectUrl(`${l}?flow_type=access_request`);
101
+ w && C.requested(w);
102
+ const k = C.build(), E = await this.requestAccess(k), { id: R, review_url: A } = b(E);
103
+ y = R, S?.(A);
104
+ try {
105
+ return await U;
106
+ } finally {
107
+ clearTimeout(v), m.close();
108
+ }
109
+ }
110
+ /**
111
+ * Not used — CLI handles OAuth via login() callback server flow.
112
+ */
113
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
114
+ async performOAuthPkce(e) {
115
+ throw new Error("performOAuthPkce is not supported in CLI mode. Use login() instead.");
116
+ }
117
+ _getRedirectUri() {
118
+ return `http://localhost:${this._callbackPort}/callback`;
119
+ }
120
+ }
121
+ const z = "production";
122
+ export {
123
+ z as CLI_BUILD_MODE,
124
+ V as CliClient,
125
+ G as InMemoryStorage
126
+ };
@@ -0,0 +1 @@
1
+ export declare const BUILD_MODE: string;
@@ -0,0 +1,32 @@
1
+ import { DirectClientBase, AuthState, DirectState, McpTransportConfig, StateChangeCallback } from '@bodhiapp/bodhi-js-core';
2
+ import { CliClientConfig, CliLoginOptions } from './types';
3
+ export declare class CliClient extends DirectClientBase {
4
+ private _serverUrl;
5
+ private _callbackPort;
6
+ constructor(config: CliClientConfig, onStateChange?: StateChangeCallback);
7
+ /**
8
+ * Simplified init — uses stored serverUrl, always tests connection.
9
+ */
10
+ init(): Promise<DirectState>;
11
+ /**
12
+ * Create MCP transport config for StreamableHTTPClientTransport.
13
+ * CLI is always direct mode — uses standard fetch with Bearer token injection.
14
+ */
15
+ createMcpTransportConfig(mcp_path: string): McpTransportConfig;
16
+ /**
17
+ * Full CLI OAuth login flow:
18
+ * 1. Start localhost callback server
19
+ * 2. Create access request (with redirect to callback)
20
+ * 3. Call onReviewUrl so host can open browser
21
+ * 4. Wait for approval redirect → 302 to Keycloak PKCE
22
+ * 5. Wait for Keycloak callback → exchange code for tokens
23
+ * 6. Auto-init client
24
+ * 7. Close server and return AuthState
25
+ */
26
+ login(options?: CliLoginOptions): Promise<AuthState>;
27
+ /**
28
+ * Not used — CLI handles OAuth via login() callback server flow.
29
+ */
30
+ protected performOAuthPkce(_scope: string): Promise<AuthState>;
31
+ protected _getRedirectUri(): string;
32
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @bodhiapp/bodhi-js-cli — CLI/headless client for Bodhi Browser SDK
3
+ */
4
+ export { CliClient } from './cli-client';
5
+ export type { CliClientConfig, CliLoginOptions } from './types';
6
+ export { InMemoryStorage } from '@bodhiapp/bodhi-js-core';
7
+ export type { AuthState, IStorage, InitialTokens, StateChangeCallback, StateChange, LogLevel } from '@bodhiapp/bodhi-js-core';
8
+ export { BUILD_MODE as CLI_BUILD_MODE } from './build-info';
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("@bodhiapp/bodhi-js-core/mcp");Object.defineProperty(exports,"createMcpClient",{enumerable:!0,get:()=>e.createMcpClient});
package/dist/mcp.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { createMcpClient } from '@bodhiapp/bodhi-js-core/mcp';
2
+ export type { CreateMcpClientOptions, McpTransportProvider } from '@bodhiapp/bodhi-js-core/mcp';
@@ -0,0 +1,4 @@
1
+ import { createMcpClient as t } from "@bodhiapp/bodhi-js-core/mcp";
2
+ export {
3
+ t as createMcpClient
4
+ };
@@ -0,0 +1,38 @@
1
+ import { IStorage, InitialTokens, LogLevel, UserScope } from '@bodhiapp/bodhi-js-core';
2
+ import { RequestedResourcesV1 } from '@bodhiapp/ts-client';
3
+ /**
4
+ * Configuration for CliClient
5
+ */
6
+ export interface CliClientConfig {
7
+ /** OAuth app client ID (registered in Keycloak) */
8
+ authClientId: string;
9
+ /** Keycloak realm URL (e.g., 'https://id.getbodhi.app/realms/bodhi') */
10
+ authServerUrl: string;
11
+ /** Bodhi server URL (e.g., 'http://localhost:1135') */
12
+ serverUrl: string;
13
+ /** Storage adapter (default: InMemoryStorage) */
14
+ storage?: IStorage;
15
+ /** Pre-existing tokens to inject (from file, env, etc.) */
16
+ initialTokens?: InitialTokens;
17
+ /** Log level (default: 'warn') */
18
+ logLevel?: LogLevel;
19
+ /** Storage key prefix (default: 'bodhi-js-sdk:cli:direct') */
20
+ storagePrefix?: string;
21
+ /** API request timeout in ms */
22
+ apiTimeoutMs?: number;
23
+ }
24
+ /**
25
+ * Options for CliClient.login()
26
+ */
27
+ export interface CliLoginOptions {
28
+ /** Port for the localhost OAuth callback server (default: 7173) */
29
+ callbackPort?: number;
30
+ /** Requested user role (default: 'scope_user_user') */
31
+ userRole?: UserScope;
32
+ /** Requested resources (MCP servers, etc.) */
33
+ requested?: RequestedResourcesV1;
34
+ /** Called with the access request review URL — host should open in browser or print to stdout */
35
+ onReviewUrl?: (url: string) => void;
36
+ /** Timeout for the entire login flow in ms (default: 5 minutes) */
37
+ loginTimeoutMs?: number;
38
+ }
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@bodhiapp/bodhi-js-cli",
3
+ "version": "0.0.32",
4
+ "description": "CLI/headless client for Bodhi Browser SDK",
5
+ "type": "module",
6
+ "main": "dist/bodhi-cli.cjs.js",
7
+ "module": "dist/bodhi-cli.esm.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/bodhi-cli.esm.js",
13
+ "require": "./dist/bodhi-cli.cjs.js"
14
+ },
15
+ "./mcp": {
16
+ "types": "./dist/mcp.d.ts",
17
+ "import": "./dist/mcp.esm.js",
18
+ "require": "./dist/mcp.cjs.js"
19
+ }
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "README.md"
24
+ ],
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "https://github.com/BodhiSearch/bodhi-js.git",
28
+ "directory": "bodhi-js-sdk/cli"
29
+ },
30
+ "license": "MIT",
31
+ "author": "BodhiSearch",
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
35
+ "scripts": {
36
+ "build": "vite build --mode development",
37
+ "ci:build": "vite build --mode development",
38
+ "build:prod": "vite build --mode production",
39
+ "clean": "rimraf dist",
40
+ "lint": "eslint . --ext .js,.jsx,.ts,.tsx && prettier --check .",
41
+ "lint:fix": "prettier --write . && eslint . --ext .js,.jsx,.ts,.tsx --fix",
42
+ "typecheck": "tsc --noEmit",
43
+ "dev:build": "node ../../scripts/build-fast.mjs bodhi-js-sdk/cli 'npm run build' src package.json vite.config.ts"
44
+ },
45
+ "dependencies": {
46
+ "@bodhiapp/bodhi-js-core": "0.0.32"
47
+ },
48
+ "devDependencies": {
49
+ "@eslint/js": "^9.23.0",
50
+ "@modelcontextprotocol/sdk": "^1.29.0",
51
+ "@types/node": "^20.19.10",
52
+ "eslint": "9.32.0",
53
+ "eslint-config-prettier": "10.1.8",
54
+ "globals": "^16.0.0",
55
+ "prettier": "3.6.2",
56
+ "typescript": "^5.8.3",
57
+ "typescript-eslint": "^8.50.1",
58
+ "vite": "^7.1.12",
59
+ "vite-plugin-dts": "^4.5.4"
60
+ }
61
+ }