buzzdealer-ai 0.0.0-stage → 1.3.0
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/LICENSE +21 -0
- package/README.md +58 -2
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +3 -0
- package/dist/client.d.ts +111 -0
- package/dist/client.js +86 -0
- package/dist/config.d.ts +21 -0
- package/dist/config.js +57 -0
- package/dist/constants.d.ts +13 -0
- package/dist/constants.js +13 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/run.d.ts +12 -0
- package/dist/run.js +133 -0
- package/package.json +59 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 buzzdealer-ai
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,59 @@
|
|
|
1
|
-
#
|
|
1
|
+
# buzzdealer-ai
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Connect your AI assistant or coding agent to [Buzz Dealer AI](https://buzzdealer.ai): the **Press Room** for PR and press release distribution through newswires, PR distribution packages and news sites, and the **Reputation Agent** for how Google and AI assistants describe you and your company.
|
|
4
|
+
|
|
5
|
+
The server is remote: `https://mcp.buzzdealer.ai/mcp` (streamable HTTP, OAuth sign-in, or a personal API key as `Authorization: Bearer <key>`). This package only helps you add it, and gives you a small typed client for the read endpoints of the REST API. No runtime dependencies. Node 18 or later.
|
|
6
|
+
|
|
7
|
+
## CLI
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
npx buzzdealer-ai connect # print the connect steps for every client
|
|
11
|
+
npx buzzdealer-ai add claude-code # print, then run: claude mcp add --transport http buzzdealer-ai https://mcp.buzzdealer.ai/mcp
|
|
12
|
+
npx buzzdealer-ai add cursor # add the server to ~/.cursor/mcp.json
|
|
13
|
+
npx buzzdealer-ai add vscode # add the server to .vscode/mcp.json in this folder
|
|
14
|
+
npx buzzdealer-ai help
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
- `add cursor` and `add vscode` create the file if it is missing, keep every other server and setting, and print what they changed.
|
|
18
|
+
- `--dry-run` prints what would change or run, and changes nothing.
|
|
19
|
+
- `--file <path>` uses another config file.
|
|
20
|
+
- `add claude-code` always prints the command before it runs it, and runs it only when `claude` is on your PATH.
|
|
21
|
+
|
|
22
|
+
After adding the server, sign in when your client asks. Step by step for every client: https://buzzdealer.ai/connect
|
|
23
|
+
|
|
24
|
+
## Client
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { BuzzDealerClient, BuzzDealerApiError } from "buzzdealer-ai";
|
|
28
|
+
|
|
29
|
+
const bd = new BuzzDealerClient({ apiKey: process.env.BUZZDEALER_API_KEY! });
|
|
30
|
+
|
|
31
|
+
const account = await bd.getAccount();
|
|
32
|
+
const { results } = await bd.findPublications({ product_type: "pr_package", region: "UK", limit: 5 });
|
|
33
|
+
const one = await bd.getPublication(results[0].id);
|
|
34
|
+
const scores = await bd.getAiVisibilityScores(["northwind.example"]);
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
await bd.request("GET", "/v1/orders");
|
|
38
|
+
} catch (e) {
|
|
39
|
+
if (e instanceof BuzzDealerApiError) console.error(e.status, e.code, e.message, e.nextStep);
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
| Method | Endpoint |
|
|
44
|
+
| --- | --- |
|
|
45
|
+
| `getAccount({ brand_id? })` | `GET /v1/account` |
|
|
46
|
+
| `findPublications(params)` | `GET /v1/publications` |
|
|
47
|
+
| `getPublication(itemId)` | `GET /v1/publications/{item_id}` |
|
|
48
|
+
| `getAiVisibilityScores(domains)` | `POST /v1/ai-visibility-scores` (reads only) |
|
|
49
|
+
| `request(method, path, { query, body })` | any endpoint |
|
|
50
|
+
|
|
51
|
+
Options: `apiKey` (required; the account owner creates keys in the dashboard under Connections), `baseUrl` (default `https://api.buzzdealer.ai`), `fetch` (default: the global fetch).
|
|
52
|
+
|
|
53
|
+
The typed methods only read. Orders go through `quote_order` and `place_order` (or `POST /v1/quotes` and `POST /v1/orders`): quote first, wait for the person's yes to the total, then order with `confirm: true`. Orders use the account's existing balance; agents cannot pay.
|
|
54
|
+
|
|
55
|
+
Full API: https://buzzdealer.ai/docs and https://buzzdealer.ai/openapi.json
|
|
56
|
+
|
|
57
|
+
## Licence
|
|
58
|
+
|
|
59
|
+
MIT. The Buzz Dealer AI service has its own terms: https://buzzdealer.ai/terms
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/** The error body of the REST API: every error has a code, a message and the next step. */
|
|
2
|
+
export interface ApiErrorBody {
|
|
3
|
+
error: string;
|
|
4
|
+
message: string;
|
|
5
|
+
next_step?: string;
|
|
6
|
+
[key: string]: unknown;
|
|
7
|
+
}
|
|
8
|
+
/** Thrown for any answer that is not 2xx. `code` is the API's machine-readable `error` field. */
|
|
9
|
+
export declare class BuzzDealerApiError extends Error {
|
|
10
|
+
readonly status: number;
|
|
11
|
+
readonly code: string;
|
|
12
|
+
readonly nextStep: string | undefined;
|
|
13
|
+
readonly body: ApiErrorBody | undefined;
|
|
14
|
+
constructor(status: number, body: ApiErrorBody | undefined, fallbackMessage?: string);
|
|
15
|
+
}
|
|
16
|
+
export type Goal = "reputation" | "seo" | "ai_visibility" | "mentions";
|
|
17
|
+
export type ProductType = "site" | "pr_package" | "bundle" | "program" | "service";
|
|
18
|
+
/** Query of GET /v1/publications. */
|
|
19
|
+
export interface FindPublicationsParams {
|
|
20
|
+
product_type?: ProductType;
|
|
21
|
+
category?: string;
|
|
22
|
+
region?: string;
|
|
23
|
+
/** ISO 639-1 code (en, es) or English name (English). */
|
|
24
|
+
language?: string;
|
|
25
|
+
/** Highest price per item, in US cents. */
|
|
26
|
+
max_price_cents?: number;
|
|
27
|
+
dofollow?: boolean;
|
|
28
|
+
/** Lowest DR (Ahrefs), 0 to 100. */
|
|
29
|
+
min_dr?: number;
|
|
30
|
+
query?: string;
|
|
31
|
+
/** 1 to 50; the API's default is 10. */
|
|
32
|
+
limit?: number;
|
|
33
|
+
goal?: Goal;
|
|
34
|
+
/** A goal mix, for example { ai_visibility: 60, seo: 40 }. Sent as ai_visibility:60,seo:40. */
|
|
35
|
+
goal_weights?: Partial<Record<Goal, number>>;
|
|
36
|
+
}
|
|
37
|
+
/** A catalog item. Key fields only; the API may send more. */
|
|
38
|
+
export interface Publication {
|
|
39
|
+
id: string;
|
|
40
|
+
product_type: ProductType;
|
|
41
|
+
name: string;
|
|
42
|
+
domain?: string | null;
|
|
43
|
+
category?: string | null;
|
|
44
|
+
region?: string | null;
|
|
45
|
+
languages?: string[];
|
|
46
|
+
price_cents: number | null;
|
|
47
|
+
price?: string | null;
|
|
48
|
+
currency: "USD";
|
|
49
|
+
ai_score?: number | null;
|
|
50
|
+
tier?: string | null;
|
|
51
|
+
dr?: number;
|
|
52
|
+
dofollow?: boolean | null;
|
|
53
|
+
min_word_count?: number | null;
|
|
54
|
+
max_word_count?: number | null;
|
|
55
|
+
max_links?: number | null;
|
|
56
|
+
[key: string]: unknown;
|
|
57
|
+
}
|
|
58
|
+
export interface FindPublicationsResult {
|
|
59
|
+
total: number;
|
|
60
|
+
results: Publication[];
|
|
61
|
+
[key: string]: unknown;
|
|
62
|
+
}
|
|
63
|
+
export interface PublicationResult {
|
|
64
|
+
item: Publication;
|
|
65
|
+
[key: string]: unknown;
|
|
66
|
+
}
|
|
67
|
+
export interface AiVisibilityScore {
|
|
68
|
+
domain: string;
|
|
69
|
+
rated: boolean;
|
|
70
|
+
ai_score?: number | null;
|
|
71
|
+
tier?: string | null;
|
|
72
|
+
[key: string]: unknown;
|
|
73
|
+
}
|
|
74
|
+
export interface AiVisibilityScoresResult {
|
|
75
|
+
results: AiVisibilityScore[];
|
|
76
|
+
[key: string]: unknown;
|
|
77
|
+
}
|
|
78
|
+
/** GET /v1/account: balance, budget frame, pending approvals. The shape is open. */
|
|
79
|
+
export type Account = Record<string, unknown>;
|
|
80
|
+
export interface BuzzDealerClientOptions {
|
|
81
|
+
/** A personal API key from the dashboard (Connections). */
|
|
82
|
+
apiKey: string;
|
|
83
|
+
/** Defaults to https://api.buzzdealer.ai */
|
|
84
|
+
baseUrl?: string;
|
|
85
|
+
/** Defaults to the global fetch (Node 18+). Pass your own for tests or proxies. */
|
|
86
|
+
fetch?: typeof fetch;
|
|
87
|
+
}
|
|
88
|
+
export interface RequestOptions {
|
|
89
|
+
query?: Record<string, string | number | boolean | undefined>;
|
|
90
|
+
body?: unknown;
|
|
91
|
+
headers?: Record<string, string>;
|
|
92
|
+
}
|
|
93
|
+
/** A thin typed client for the read endpoints of the Buzz Dealer AI REST API. */
|
|
94
|
+
export declare class BuzzDealerClient {
|
|
95
|
+
readonly baseUrl: string;
|
|
96
|
+
private readonly apiKey;
|
|
97
|
+
private readonly fetchImpl;
|
|
98
|
+
constructor(options: BuzzDealerClientOptions);
|
|
99
|
+
/** Any endpoint. `path` starts with /v1. Throws BuzzDealerApiError on a non-2xx answer. */
|
|
100
|
+
request<T = unknown>(method: string, path: string, options?: RequestOptions): Promise<T>;
|
|
101
|
+
/** GET /v1/account */
|
|
102
|
+
getAccount(params?: {
|
|
103
|
+
brand_id?: string;
|
|
104
|
+
}): Promise<Account>;
|
|
105
|
+
/** GET /v1/publications */
|
|
106
|
+
findPublications(params?: FindPublicationsParams): Promise<FindPublicationsResult>;
|
|
107
|
+
/** GET /v1/publications/{item_id} */
|
|
108
|
+
getPublication(itemId: string): Promise<PublicationResult>;
|
|
109
|
+
/** POST /v1/ai-visibility-scores: reads only (a POST because it takes a list of domains). */
|
|
110
|
+
getAiVisibilityScores(domains: string[]): Promise<AiVisibilityScoresResult>;
|
|
111
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { API_BASE_URL } from "./constants.js";
|
|
2
|
+
/** Thrown for any answer that is not 2xx. `code` is the API's machine-readable `error` field. */
|
|
3
|
+
export class BuzzDealerApiError extends Error {
|
|
4
|
+
status;
|
|
5
|
+
code;
|
|
6
|
+
nextStep;
|
|
7
|
+
body;
|
|
8
|
+
constructor(status, body, fallbackMessage) {
|
|
9
|
+
super(body?.message ?? fallbackMessage ?? `Request failed with status ${status}`);
|
|
10
|
+
this.name = "BuzzDealerApiError";
|
|
11
|
+
this.status = status;
|
|
12
|
+
this.code = body?.error ?? "http_error";
|
|
13
|
+
this.nextStep = body?.next_step;
|
|
14
|
+
this.body = body;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** A thin typed client for the read endpoints of the Buzz Dealer AI REST API. */
|
|
18
|
+
export class BuzzDealerClient {
|
|
19
|
+
baseUrl;
|
|
20
|
+
apiKey;
|
|
21
|
+
fetchImpl;
|
|
22
|
+
constructor(options) {
|
|
23
|
+
if (!options || typeof options.apiKey !== "string" || options.apiKey.trim() === "") {
|
|
24
|
+
throw new TypeError("BuzzDealerClient needs an apiKey (create one in the dashboard under Connections).");
|
|
25
|
+
}
|
|
26
|
+
this.apiKey = options.apiKey;
|
|
27
|
+
this.baseUrl = (options.baseUrl ?? API_BASE_URL).replace(/\/+$/, "");
|
|
28
|
+
const f = options.fetch ?? globalThis.fetch;
|
|
29
|
+
if (!f)
|
|
30
|
+
throw new TypeError("No fetch available: use Node 18 or later, or pass options.fetch.");
|
|
31
|
+
this.fetchImpl = f;
|
|
32
|
+
}
|
|
33
|
+
/** Any endpoint. `path` starts with /v1. Throws BuzzDealerApiError on a non-2xx answer. */
|
|
34
|
+
async request(method, path, options = {}) {
|
|
35
|
+
const url = new URL(this.baseUrl + (path.startsWith("/") ? path : `/${path}`));
|
|
36
|
+
for (const [k, v] of Object.entries(options.query ?? {})) {
|
|
37
|
+
if (v !== undefined)
|
|
38
|
+
url.searchParams.set(k, String(v));
|
|
39
|
+
}
|
|
40
|
+
const headers = { accept: "application/json", authorization: `Bearer ${this.apiKey}`, ...options.headers };
|
|
41
|
+
let body;
|
|
42
|
+
if (options.body !== undefined) {
|
|
43
|
+
headers["content-type"] = "application/json";
|
|
44
|
+
body = JSON.stringify(options.body);
|
|
45
|
+
}
|
|
46
|
+
const res = await this.fetchImpl(url.toString(), { method, headers, body });
|
|
47
|
+
const text = await res.text();
|
|
48
|
+
let data;
|
|
49
|
+
try {
|
|
50
|
+
data = text ? JSON.parse(text) : undefined;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
data = undefined;
|
|
54
|
+
}
|
|
55
|
+
if (!res.ok) {
|
|
56
|
+
const b = data;
|
|
57
|
+
const errBody = b && typeof b === "object" && typeof b.error === "string" ? b : undefined;
|
|
58
|
+
throw new BuzzDealerApiError(res.status, errBody, text ? text.slice(0, 200) : undefined);
|
|
59
|
+
}
|
|
60
|
+
return data;
|
|
61
|
+
}
|
|
62
|
+
/** GET /v1/account */
|
|
63
|
+
getAccount(params = {}) {
|
|
64
|
+
return this.request("GET", "/v1/account", { query: { brand_id: params.brand_id } });
|
|
65
|
+
}
|
|
66
|
+
/** GET /v1/publications */
|
|
67
|
+
findPublications(params = {}) {
|
|
68
|
+
const { goal_weights, ...rest } = params;
|
|
69
|
+
const query = { ...rest };
|
|
70
|
+
if (goal_weights) {
|
|
71
|
+
query.goal_weights = Object.entries(goal_weights)
|
|
72
|
+
.filter(([, w]) => w !== undefined)
|
|
73
|
+
.map(([g, w]) => `${g}:${w}`)
|
|
74
|
+
.join(",");
|
|
75
|
+
}
|
|
76
|
+
return this.request("GET", "/v1/publications", { query });
|
|
77
|
+
}
|
|
78
|
+
/** GET /v1/publications/{item_id} */
|
|
79
|
+
getPublication(itemId) {
|
|
80
|
+
return this.request("GET", `/v1/publications/${encodeURIComponent(itemId)}`);
|
|
81
|
+
}
|
|
82
|
+
/** POST /v1/ai-visibility-scores: reads only (a POST because it takes a list of domains). */
|
|
83
|
+
getAiVisibilityScores(domains) {
|
|
84
|
+
return this.request("POST", "/v1/ai-visibility-scores", { body: { domains } });
|
|
85
|
+
}
|
|
86
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type Client = "cursor" | "vscode";
|
|
2
|
+
/** Cursor keeps servers under "mcpServers"; VS Code under "servers" with a "type". */
|
|
3
|
+
export declare function serverKey(client: Client): "mcpServers" | "servers";
|
|
4
|
+
export declare function serverEntry(client: Client): Record<string, string>;
|
|
5
|
+
/** ~/.cursor/mcp.json, or <cwd>/.vscode/mcp.json */
|
|
6
|
+
export declare function defaultConfigPath(client: Client, cwd?: string, home?: string): string;
|
|
7
|
+
export type MergeAction = "added" | "updated" | "unchanged";
|
|
8
|
+
export interface MergeResult {
|
|
9
|
+
action: MergeAction;
|
|
10
|
+
config: Record<string, unknown>;
|
|
11
|
+
previous: unknown;
|
|
12
|
+
}
|
|
13
|
+
/** Puts the server into a parsed config, keeping every other server and key. Pure. */
|
|
14
|
+
export declare function mergeServer(existing: unknown, client: Client): MergeResult;
|
|
15
|
+
export interface AddResult extends MergeResult {
|
|
16
|
+
path: string;
|
|
17
|
+
created: boolean;
|
|
18
|
+
written: boolean;
|
|
19
|
+
}
|
|
20
|
+
/** Reads the file (if any), merges the server and writes it back unless dryRun. */
|
|
21
|
+
export declare function addToConfigFile(client: Client, path: string, dryRun?: boolean): AddResult;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { MCP_URL, SERVER_NAME } from "./constants.js";
|
|
5
|
+
/** Cursor keeps servers under "mcpServers"; VS Code under "servers" with a "type". */
|
|
6
|
+
export function serverKey(client) {
|
|
7
|
+
return client === "cursor" ? "mcpServers" : "servers";
|
|
8
|
+
}
|
|
9
|
+
export function serverEntry(client) {
|
|
10
|
+
return client === "cursor" ? { url: MCP_URL } : { type: "http", url: MCP_URL };
|
|
11
|
+
}
|
|
12
|
+
/** ~/.cursor/mcp.json, or <cwd>/.vscode/mcp.json */
|
|
13
|
+
export function defaultConfigPath(client, cwd = process.cwd(), home = homedir()) {
|
|
14
|
+
return client === "cursor" ? join(home, ".cursor", "mcp.json") : join(cwd, ".vscode", "mcp.json");
|
|
15
|
+
}
|
|
16
|
+
/** Puts the server into a parsed config, keeping every other server and key. Pure. */
|
|
17
|
+
export function mergeServer(existing, client) {
|
|
18
|
+
if (existing !== undefined && (typeof existing !== "object" || existing === null || Array.isArray(existing))) {
|
|
19
|
+
throw new Error("The config file is not a JSON object.");
|
|
20
|
+
}
|
|
21
|
+
const config = { ...(existing ?? {}) };
|
|
22
|
+
const key = serverKey(client);
|
|
23
|
+
const current = config[key];
|
|
24
|
+
if (current !== undefined && (typeof current !== "object" || current === null || Array.isArray(current))) {
|
|
25
|
+
throw new Error(`"${key}" in the config file is not an object.`);
|
|
26
|
+
}
|
|
27
|
+
const servers = { ...(current ?? {}) };
|
|
28
|
+
const previous = servers[SERVER_NAME];
|
|
29
|
+
// Keep any extra fields the person set on our entry (for example headers), and set type and url.
|
|
30
|
+
const next = previous && typeof previous === "object" && !Array.isArray(previous) ? { ...previous, ...serverEntry(client) } : serverEntry(client);
|
|
31
|
+
servers[SERVER_NAME] = next;
|
|
32
|
+
const action = previous === undefined ? "added" : JSON.stringify(next) === JSON.stringify(previous) ? "unchanged" : "updated";
|
|
33
|
+
return { action, config: { ...config, [key]: servers }, previous };
|
|
34
|
+
}
|
|
35
|
+
/** Reads the file (if any), merges the server and writes it back unless dryRun. */
|
|
36
|
+
export function addToConfigFile(client, path, dryRun = false) {
|
|
37
|
+
const exists = existsSync(path);
|
|
38
|
+
let parsed;
|
|
39
|
+
if (exists) {
|
|
40
|
+
const raw = readFileSync(path, "utf8");
|
|
41
|
+
if (raw.trim() !== "") {
|
|
42
|
+
try {
|
|
43
|
+
parsed = JSON.parse(raw);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
throw new Error(`${path} is not plain JSON (it may have comments). Nothing was changed; add the server by hand.`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const result = mergeServer(parsed, client);
|
|
51
|
+
const write = !dryRun && result.action !== "unchanged";
|
|
52
|
+
if (write) {
|
|
53
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
54
|
+
writeFileSync(path, JSON.stringify(result.config, null, 2) + "\n", "utf8");
|
|
55
|
+
}
|
|
56
|
+
return { ...result, path, created: !exists, written: write };
|
|
57
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** The name every config uses for the server. */
|
|
2
|
+
export declare const SERVER_NAME = "buzzdealer-ai";
|
|
3
|
+
/** The remote MCP server (streamable HTTP; OAuth, or a personal API key as a Bearer token). */
|
|
4
|
+
export declare const MCP_URL = "https://mcp.buzzdealer.ai/mcp";
|
|
5
|
+
/** The REST API host. Paths start with /v1. */
|
|
6
|
+
export declare const API_BASE_URL = "https://api.buzzdealer.ai";
|
|
7
|
+
export declare const CONNECT_URL = "https://buzzdealer.ai/connect";
|
|
8
|
+
export declare const AGENTS_URL = "https://buzzdealer.ai/agents";
|
|
9
|
+
export declare const DOCS_URL = "https://buzzdealer.ai/docs";
|
|
10
|
+
export declare const OPENAPI_URL = "https://buzzdealer.ai/openapi.json";
|
|
11
|
+
/** The Claude Code command, as an argument list (no shell). */
|
|
12
|
+
export declare const CLAUDE_CODE_ARGS: readonly ["mcp", "add", "--transport", "http", "buzzdealer-ai", "https://mcp.buzzdealer.ai/mcp"];
|
|
13
|
+
export declare const CLAUDE_CODE_COMMAND: string;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** The name every config uses for the server. */
|
|
2
|
+
export const SERVER_NAME = "buzzdealer-ai";
|
|
3
|
+
/** The remote MCP server (streamable HTTP; OAuth, or a personal API key as a Bearer token). */
|
|
4
|
+
export const MCP_URL = "https://mcp.buzzdealer.ai/mcp";
|
|
5
|
+
/** The REST API host. Paths start with /v1. */
|
|
6
|
+
export const API_BASE_URL = "https://api.buzzdealer.ai";
|
|
7
|
+
export const CONNECT_URL = "https://buzzdealer.ai/connect";
|
|
8
|
+
export const AGENTS_URL = "https://buzzdealer.ai/agents";
|
|
9
|
+
export const DOCS_URL = "https://buzzdealer.ai/docs";
|
|
10
|
+
export const OPENAPI_URL = "https://buzzdealer.ai/openapi.json";
|
|
11
|
+
/** The Claude Code command, as an argument list (no shell). */
|
|
12
|
+
export const CLAUDE_CODE_ARGS = ["mcp", "add", "--transport", "http", SERVER_NAME, MCP_URL];
|
|
13
|
+
export const CLAUDE_CODE_COMMAND = `claude ${CLAUDE_CODE_ARGS.join(" ")}`;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { BuzzDealerClient, BuzzDealerApiError, type ApiErrorBody, type Account, type AiVisibilityScore, type AiVisibilityScoresResult, type BuzzDealerClientOptions, type FindPublicationsParams, type FindPublicationsResult, type Goal, type ProductType, type Publication, type PublicationResult, type RequestOptions, } from "./client.js";
|
|
2
|
+
export { addToConfigFile, defaultConfigPath, mergeServer, serverEntry, serverKey, type AddResult, type Client, type MergeAction, type MergeResult } from "./config.js";
|
|
3
|
+
export * from "./constants.js";
|
|
4
|
+
export { run, type CliIO } from "./run.js";
|
package/dist/index.js
ADDED
package/dist/run.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface CliIO {
|
|
2
|
+
log: (line: string) => void;
|
|
3
|
+
error: (line: string) => void;
|
|
4
|
+
cwd: string;
|
|
5
|
+
home: string;
|
|
6
|
+
/** Finds an executable on PATH; returns its path or undefined. */
|
|
7
|
+
which: (cmd: string) => string | undefined;
|
|
8
|
+
/** Runs a command and returns its exit code. */
|
|
9
|
+
exec: (cmd: string, args: readonly string[]) => number;
|
|
10
|
+
}
|
|
11
|
+
/** The CLI. Returns the exit code. */
|
|
12
|
+
export declare function run(argv: readonly string[], io?: CliIO): number;
|
package/dist/run.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { delimiter, join } from "node:path";
|
|
5
|
+
import { addToConfigFile, defaultConfigPath } from "./config.js";
|
|
6
|
+
import { AGENTS_URL, CLAUDE_CODE_ARGS, CLAUDE_CODE_COMMAND, CONNECT_URL, DOCS_URL, MCP_URL, SERVER_NAME } from "./constants.js";
|
|
7
|
+
function whichOnPath(cmd) {
|
|
8
|
+
const exts = process.platform === "win32" ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";") : [""];
|
|
9
|
+
for (const dir of (process.env.PATH ?? "").split(delimiter)) {
|
|
10
|
+
if (!dir)
|
|
11
|
+
continue;
|
|
12
|
+
for (const ext of exts) {
|
|
13
|
+
const p = join(dir, cmd + ext);
|
|
14
|
+
if (existsSync(p))
|
|
15
|
+
return p;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
const defaultIO = {
|
|
21
|
+
log: (l) => console.log(l),
|
|
22
|
+
error: (l) => console.error(l),
|
|
23
|
+
cwd: process.cwd(),
|
|
24
|
+
home: homedir(),
|
|
25
|
+
which: whichOnPath,
|
|
26
|
+
exec: (cmd, args) => spawnSync(cmd, [...args], { stdio: "inherit", shell: process.platform === "win32" }).status ?? 1,
|
|
27
|
+
};
|
|
28
|
+
const HELP = `buzzdealer-ai: connect your AI assistant to Buzz Dealer AI (${MCP_URL})
|
|
29
|
+
|
|
30
|
+
Usage:
|
|
31
|
+
npx buzzdealer-ai connect Print the connect steps
|
|
32
|
+
npx buzzdealer-ai add claude-code Print, then run: ${CLAUDE_CODE_COMMAND}
|
|
33
|
+
npx buzzdealer-ai add cursor Add the server to ~/.cursor/mcp.json
|
|
34
|
+
npx buzzdealer-ai add vscode Add the server to .vscode/mcp.json in this folder
|
|
35
|
+
npx buzzdealer-ai help This help
|
|
36
|
+
|
|
37
|
+
Options:
|
|
38
|
+
--dry-run Print what would change or run, and change nothing
|
|
39
|
+
--file <path> For add cursor or add vscode: use this config file instead
|
|
40
|
+
|
|
41
|
+
Docs: ${CONNECT_URL}`;
|
|
42
|
+
function connectText() {
|
|
43
|
+
return `Buzz Dealer AI: Press Room (PR and press release distribution) and Reputation Agent.
|
|
44
|
+
|
|
45
|
+
MCP server: ${MCP_URL} (streamable HTTP)
|
|
46
|
+
Sign-in: OAuth in your client, or a personal API key as "Authorization: Bearer <key>"
|
|
47
|
+
(the account owner creates keys in the Buzz Dealer AI dashboard under Connections)
|
|
48
|
+
|
|
49
|
+
Claude Code:
|
|
50
|
+
${CLAUDE_CODE_COMMAND}
|
|
51
|
+
then type /mcp and sign in
|
|
52
|
+
Cursor:
|
|
53
|
+
npx buzzdealer-ai add cursor
|
|
54
|
+
VS Code:
|
|
55
|
+
npx buzzdealer-ai add vscode
|
|
56
|
+
Claude, ChatGPT, Gemini and others:
|
|
57
|
+
add a custom connector with the address ${MCP_URL}
|
|
58
|
+
|
|
59
|
+
Step by step for every client: ${CONNECT_URL}
|
|
60
|
+
For AI agents: ${AGENTS_URL}
|
|
61
|
+
Docs: ${DOCS_URL}
|
|
62
|
+
|
|
63
|
+
Before anything that spends credits, your assistant quotes first and waits for your yes to the total.`;
|
|
64
|
+
}
|
|
65
|
+
function addConfig(client, file, dryRun, io) {
|
|
66
|
+
const path = file ?? defaultConfigPath(client, io.cwd, io.home);
|
|
67
|
+
let r;
|
|
68
|
+
try {
|
|
69
|
+
r = addToConfigFile(client, path, dryRun);
|
|
70
|
+
}
|
|
71
|
+
catch (e) {
|
|
72
|
+
io.error(`Error: ${e.message}`);
|
|
73
|
+
return 1;
|
|
74
|
+
}
|
|
75
|
+
const entry = JSON.stringify(r.config[client === "cursor" ? "mcpServers" : "servers"][SERVER_NAME]);
|
|
76
|
+
if (r.action === "unchanged") {
|
|
77
|
+
io.log(`${path} already has "${SERVER_NAME}" set to ${MCP_URL}. Nothing changed.`);
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
80
|
+
io.log(r.action === "added"
|
|
81
|
+
? `${dryRun ? "Would add" : "Added"} "${SERVER_NAME}": ${entry}`
|
|
82
|
+
: `${dryRun ? "Would update" : "Updated"} "${SERVER_NAME}" from ${JSON.stringify(r.previous)} to ${entry}`);
|
|
83
|
+
if (r.created)
|
|
84
|
+
io.log(`${dryRun ? "Would create" : "Created"} ${path}`);
|
|
85
|
+
else
|
|
86
|
+
io.log(`${dryRun ? "Would change" : "Changed"} ${path}; other servers and settings were kept.`);
|
|
87
|
+
if (!dryRun)
|
|
88
|
+
io.log(client === "cursor" ? "Restart Cursor or reload its MCP settings, then sign in when asked." : "In VS Code, start the server from mcp.json and sign in when asked.");
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
/** The CLI. Returns the exit code. */
|
|
92
|
+
export function run(argv, io = defaultIO) {
|
|
93
|
+
const args = [...argv];
|
|
94
|
+
const dryRun = args.includes("--dry-run");
|
|
95
|
+
const fileAt = args.indexOf("--file");
|
|
96
|
+
const file = fileAt >= 0 ? args[fileAt + 1] : undefined;
|
|
97
|
+
if (fileAt >= 0 && !file) {
|
|
98
|
+
io.error("Error: --file needs a path.");
|
|
99
|
+
return 1;
|
|
100
|
+
}
|
|
101
|
+
const words = args.filter((a, i) => !a.startsWith("--") && !(fileAt >= 0 && i === fileAt + 1));
|
|
102
|
+
const [cmd, target] = words;
|
|
103
|
+
if (!cmd || cmd === "help" || args.includes("--help") || args.includes("-h")) {
|
|
104
|
+
io.log(HELP);
|
|
105
|
+
return 0;
|
|
106
|
+
}
|
|
107
|
+
if (cmd === "connect") {
|
|
108
|
+
io.log(connectText());
|
|
109
|
+
return 0;
|
|
110
|
+
}
|
|
111
|
+
if (cmd === "add") {
|
|
112
|
+
if (target === "claude-code" || target === "claude") {
|
|
113
|
+
io.log(`Command: ${CLAUDE_CODE_COMMAND}`);
|
|
114
|
+
if (dryRun)
|
|
115
|
+
return 0;
|
|
116
|
+
if (!io.which("claude")) {
|
|
117
|
+
io.log("Claude Code (claude) was not found on PATH. Run the command above once it is installed.");
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
120
|
+
io.log("Running it now...");
|
|
121
|
+
const code = io.exec("claude", CLAUDE_CODE_ARGS);
|
|
122
|
+
if (code === 0)
|
|
123
|
+
io.log("Done. In Claude Code, type /mcp and sign in.");
|
|
124
|
+
return code;
|
|
125
|
+
}
|
|
126
|
+
if (target === "cursor" || target === "vscode")
|
|
127
|
+
return addConfig(target, file, dryRun, io);
|
|
128
|
+
io.error(`Unknown client "${target ?? ""}". Use claude-code, cursor or vscode.`);
|
|
129
|
+
return 1;
|
|
130
|
+
}
|
|
131
|
+
io.error(`Unknown command "${cmd}".\n\n${HELP}`);
|
|
132
|
+
return 1;
|
|
133
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,61 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "buzzdealer-ai",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.3.0",
|
|
4
|
+
"description": "Connect AI assistants and coding agents to the Buzz Dealer AI MCP server (Press Room and Reputation Agent), plus a small typed client for its REST API.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Buzz Dealer <support@buzzdealer.ai> (https://buzzdealer.ai)",
|
|
8
|
+
"homepage": "https://buzzdealer.ai/connect",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/buzzdealer-ai/mcp.git",
|
|
12
|
+
"directory": "packages/npm"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/buzzdealer-ai/mcp/issues",
|
|
16
|
+
"email": "support@buzzdealer.ai"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"buzzdealer-ai",
|
|
20
|
+
"mcp",
|
|
21
|
+
"model-context-protocol",
|
|
22
|
+
"press-release",
|
|
23
|
+
"pr-distribution",
|
|
24
|
+
"newswire",
|
|
25
|
+
"press-room",
|
|
26
|
+
"reputation",
|
|
27
|
+
"ai-visibility",
|
|
28
|
+
"claude-code",
|
|
29
|
+
"cursor",
|
|
30
|
+
"vscode"
|
|
31
|
+
],
|
|
32
|
+
"bin": {
|
|
33
|
+
"buzzdealer-ai": "dist/cli.js"
|
|
34
|
+
},
|
|
35
|
+
"main": "./dist/index.js",
|
|
36
|
+
"types": "./dist/index.d.ts",
|
|
37
|
+
"exports": {
|
|
38
|
+
".": {
|
|
39
|
+
"types": "./dist/index.d.ts",
|
|
40
|
+
"import": "./dist/index.js"
|
|
41
|
+
},
|
|
42
|
+
"./package.json": "./package.json"
|
|
43
|
+
},
|
|
44
|
+
"files": [
|
|
45
|
+
"dist",
|
|
46
|
+
"README.md",
|
|
47
|
+
"LICENSE"
|
|
48
|
+
],
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">=18"
|
|
51
|
+
},
|
|
52
|
+
"scripts": {
|
|
53
|
+
"build": "tsc -p tsconfig.json",
|
|
54
|
+
"test": "npm run build && node --test test/client.test.mjs test/cli.test.mjs",
|
|
55
|
+
"prepublishOnly": "npm run build && node --test test/client.test.mjs test/cli.test.mjs"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@types/node": "^20.19.0",
|
|
59
|
+
"typescript": "^5.9.3"
|
|
60
|
+
}
|
|
61
|
+
}
|