@brandazine/solari-sdk 0.1.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Brandazine
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 ADDED
@@ -0,0 +1,40 @@
1
+ # @brandazine/solari-sdk
2
+
3
+ TypeScript client for the [SOLARI API](https://solari.sh/api) — creator and brand intelligence across Instagram and TikTok. Zero dependencies; runs on Node 18+, Bun, Deno, Cloudflare Workers, and browsers with `fetch`.
4
+
5
+ ```
6
+ npm install @brandazine/solari-sdk
7
+ ```
8
+
9
+ ## Quickstart
10
+
11
+ ```ts
12
+ import { Solari } from "@brandazine/solari-sdk";
13
+
14
+ const solari = new Solari({ token: process.env.SOLARI_TOKEN });
15
+
16
+ const hits = await solari.tools.catalog.instagram.account.search({ query: "nike", limit: 3 });
17
+ const brand = await solari.call("solari_insight_instagram_brand_overview", { username: "nike" });
18
+ const tools = await solari.listTools();
19
+ ```
20
+
21
+ Get a token with `solari auth token` on a machine that is signed in to the [solari CLI](https://solari.sh/docs), or pass a token you already hold from an MCP connector. With no `token` option the client reads `SOLARI_TOKEN`.
22
+
23
+ ## API
24
+
25
+ - `new Solari({ token?, baseUrl?, fetch?, timeoutMs?, userAgent? })` — `baseUrl` defaults to `https://solari.sh`; `fetch` lets you inject a custom implementation.
26
+ - `solari.listTools()` — every tool the signed-in account can call, with its JSON input schema.
27
+ - `solari.getTool(name)` — one tool.
28
+ - `solari.call(name, args)` — run a tool and get its JSON payload back. Typed as `call<T>()`.
29
+ - `solari.tools.<family>.<platform>.<group>.<name>(args)` — the same call spelled as a path; segments join with `_` under the `solari_` prefix. The path is dynamic, so it is not type-checked against the live tool list.
30
+ - `solari.me()` — the identity behind the token.
31
+
32
+ Errors throw `SolariError` with `status`, `code`, `message`, `tool`, `retryAfterSeconds`, and a `retryable` flag (429, 502, 503, 504). Codes come straight from the API: `invalid_arguments`, `tool_not_found`, `rate_limited`, `forbidden`, `upstream_timeout`, `app_warming_up`, ...
33
+
34
+ ## Development
35
+
36
+ ```
37
+ bun install
38
+ bun test
39
+ bun run build
40
+ ```
@@ -0,0 +1,64 @@
1
+ export declare const DEFAULT_BASE_URL = "https://solari.sh";
2
+ export declare const API_PREFIX = "/mcp/api/v1";
3
+ export declare const SDK_VERSION = "0.1.0";
4
+ export declare const TOKEN_ENV = "SOLARI_TOKEN";
5
+ export interface SolariTool {
6
+ name: string;
7
+ title: string | null;
8
+ description: string | null;
9
+ input_schema: Record<string, unknown>;
10
+ annotations: Record<string, unknown> | null;
11
+ }
12
+ export interface SolariIdentity {
13
+ sub: string;
14
+ email?: string;
15
+ name?: string;
16
+ client?: {
17
+ kind: string;
18
+ name?: string;
19
+ };
20
+ }
21
+ export interface SolariOptions {
22
+ token?: string | undefined;
23
+ baseUrl?: string;
24
+ fetch?: typeof fetch;
25
+ timeoutMs?: number;
26
+ userAgent?: string;
27
+ }
28
+ export type ToolArguments = Record<string, unknown>;
29
+ export interface ToolPath {
30
+ (args?: ToolArguments): Promise<unknown>;
31
+ [segment: string]: ToolPath;
32
+ }
33
+ export declare class SolariError extends Error {
34
+ readonly status: number;
35
+ readonly code: string;
36
+ readonly tool: string | undefined;
37
+ readonly retryAfterSeconds: number | undefined;
38
+ constructor(input: {
39
+ status: number;
40
+ code: string;
41
+ message: string;
42
+ tool?: string;
43
+ retryAfterSeconds?: number;
44
+ });
45
+ get retryable(): boolean;
46
+ }
47
+ export declare function normalizeBaseUrl(raw: string): string;
48
+ export declare function toolName(segments: readonly string[]): string;
49
+ export declare class Solari {
50
+ readonly baseUrl: string;
51
+ readonly tools: ToolPath;
52
+ private readonly token;
53
+ private readonly fetchImpl;
54
+ private readonly timeoutMs;
55
+ private readonly userAgent;
56
+ constructor(options?: SolariOptions);
57
+ listTools(): Promise<SolariTool[]>;
58
+ getTool(name: string): Promise<SolariTool>;
59
+ call<T = unknown>(name: string, args?: ToolArguments): Promise<T>;
60
+ me(): Promise<SolariIdentity>;
61
+ private toolPath;
62
+ private request;
63
+ }
64
+ export default Solari;
package/dist/index.js ADDED
@@ -0,0 +1,180 @@
1
+ export const DEFAULT_BASE_URL = "https://solari.sh";
2
+ export const API_PREFIX = "/mcp/api/v1";
3
+ export const SDK_VERSION = "0.1.0";
4
+ export const TOKEN_ENV = "SOLARI_TOKEN";
5
+ const DEFAULT_TIMEOUT_MS = 150_000;
6
+ export class SolariError extends Error {
7
+ status;
8
+ code;
9
+ tool;
10
+ retryAfterSeconds;
11
+ constructor(input) {
12
+ super(input.message);
13
+ this.name = "SolariError";
14
+ this.status = input.status;
15
+ this.code = input.code;
16
+ this.tool = input.tool;
17
+ this.retryAfterSeconds = input.retryAfterSeconds;
18
+ }
19
+ get retryable() {
20
+ return this.status === 429 || this.status === 503 || this.status === 502 || this.status === 504;
21
+ }
22
+ }
23
+ function tokenFromEnvironment() {
24
+ const env = globalThis.process?.env;
25
+ const raw = env?.[TOKEN_ENV]?.trim();
26
+ return raw ? raw : undefined;
27
+ }
28
+ export function normalizeBaseUrl(raw) {
29
+ const trimmed = raw.trim().replace(/\/+$/, "");
30
+ const withScheme = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
31
+ const url = new URL(withScheme);
32
+ const path = url.pathname.replace(/\/+$/, "");
33
+ const withoutApi = path.endsWith(API_PREFIX) ? path.slice(0, -API_PREFIX.length) : path;
34
+ return `${url.origin}${withoutApi}`;
35
+ }
36
+ export function toolName(segments) {
37
+ const parts = segments.map((segment) => segment.trim()).filter((segment) => segment.length > 0);
38
+ if (parts.length === 0) {
39
+ throw new SolariError({ status: 0, code: "invalid_tool_path", message: "a tool path needs at least one segment" });
40
+ }
41
+ const joined = parts.join("_");
42
+ return joined === "solari" || joined.startsWith("solari_") ? joined : `solari_${joined}`;
43
+ }
44
+ async function parseErrorBody(response) {
45
+ const fallback = { code: `http_${response.status}`, message: `SOLARI API returned HTTP ${response.status}` };
46
+ let text;
47
+ try {
48
+ text = await response.text();
49
+ }
50
+ catch {
51
+ return fallback;
52
+ }
53
+ if (!text) {
54
+ return fallback;
55
+ }
56
+ try {
57
+ const parsed = JSON.parse(text);
58
+ const error = parsed.error;
59
+ if (error && typeof error === "object") {
60
+ return {
61
+ code: typeof error.code === "string" ? error.code : fallback.code,
62
+ message: typeof error.message === "string" ? error.message : fallback.message,
63
+ ...(typeof error.tool === "string" ? { tool: error.tool } : {}),
64
+ };
65
+ }
66
+ if (typeof parsed.error_description === "string") {
67
+ return {
68
+ code: typeof parsed.error === "string" ? String(parsed.error) : fallback.code,
69
+ message: parsed.error_description,
70
+ };
71
+ }
72
+ }
73
+ catch {
74
+ return { ...fallback, message: text.slice(0, 300) };
75
+ }
76
+ return fallback;
77
+ }
78
+ export class Solari {
79
+ baseUrl;
80
+ tools;
81
+ token;
82
+ fetchImpl;
83
+ timeoutMs;
84
+ userAgent;
85
+ constructor(options = {}) {
86
+ const token = options.token?.trim() || tokenFromEnvironment();
87
+ if (!token) {
88
+ throw new SolariError({
89
+ status: 0,
90
+ code: "missing_token",
91
+ message: `no access token: pass { token } or set ${TOKEN_ENV} (mint one with "solari auth token")`,
92
+ });
93
+ }
94
+ this.token = token;
95
+ this.baseUrl = normalizeBaseUrl(options.baseUrl ?? DEFAULT_BASE_URL);
96
+ this.fetchImpl = options.fetch ?? globalThis.fetch;
97
+ this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
98
+ this.userAgent = options.userAgent ?? `solari-sdk-ts/${SDK_VERSION}`;
99
+ this.tools = this.toolPath([]);
100
+ }
101
+ async listTools() {
102
+ const body = (await this.request("GET", `${API_PREFIX}/tools`));
103
+ return body.tools ?? [];
104
+ }
105
+ async getTool(name) {
106
+ return (await this.request("GET", `${API_PREFIX}/tools/${encodeURIComponent(name)}`));
107
+ }
108
+ async call(name, args = {}) {
109
+ return (await this.request("POST", `${API_PREFIX}/tools/${encodeURIComponent(name)}`, args));
110
+ }
111
+ async me() {
112
+ return (await this.request("GET", `${API_PREFIX}/me`));
113
+ }
114
+ toolPath(segments) {
115
+ const invoke = (args = {}) => this.call(toolName(segments), args);
116
+ return new Proxy(invoke, {
117
+ get: (_target, property) => {
118
+ if (typeof property !== "string" || property === "then") {
119
+ return undefined;
120
+ }
121
+ return this.toolPath([...segments, property]);
122
+ },
123
+ apply: (_target, _thisArg, argumentList) => invoke(argumentList[0]),
124
+ });
125
+ }
126
+ async request(method, path, body) {
127
+ const controller = new AbortController();
128
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
129
+ const headers = {
130
+ Authorization: `Bearer ${this.token}`,
131
+ Accept: "application/json",
132
+ "User-Agent": this.userAgent,
133
+ };
134
+ if (body !== undefined) {
135
+ headers["Content-Type"] = "application/json";
136
+ }
137
+ let response;
138
+ try {
139
+ response = await this.fetchImpl(`${this.baseUrl}${path}`, {
140
+ method,
141
+ headers,
142
+ body: body !== undefined ? JSON.stringify(body) : null,
143
+ signal: controller.signal,
144
+ });
145
+ }
146
+ catch (error) {
147
+ const aborted = error instanceof Error && error.name === "AbortError";
148
+ throw new SolariError({
149
+ status: 0,
150
+ code: aborted ? "timeout" : "network_error",
151
+ message: aborted
152
+ ? `SOLARI API did not answer within ${Math.round(this.timeoutMs / 1000)}s`
153
+ : `could not reach SOLARI API: ${error instanceof Error ? error.message : String(error)}`,
154
+ });
155
+ }
156
+ finally {
157
+ clearTimeout(timer);
158
+ }
159
+ if (!response.ok) {
160
+ const parsed = await parseErrorBody(response);
161
+ const retryAfter = Number(response.headers.get("retry-after") ?? "");
162
+ throw new SolariError({
163
+ status: response.status,
164
+ ...parsed,
165
+ ...(Number.isFinite(retryAfter) && retryAfter > 0 ? { retryAfterSeconds: retryAfter } : {}),
166
+ });
167
+ }
168
+ const text = await response.text();
169
+ if (!text) {
170
+ return null;
171
+ }
172
+ try {
173
+ return JSON.parse(text);
174
+ }
175
+ catch {
176
+ throw new SolariError({ status: response.status, code: "invalid_response", message: "SOLARI API returned non-JSON" });
177
+ }
178
+ }
179
+ }
180
+ export default Solari;
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@brandazine/solari-sdk",
3
+ "version": "0.1.0",
4
+ "description": "TypeScript client for the SOLARI API — creator and brand intelligence across Instagram and TikTok.",
5
+ "license": "MIT",
6
+ "homepage": "https://solari.sh/api",
7
+ "keywords": [
8
+ "solari",
9
+ "brandazine",
10
+ "instagram",
11
+ "tiktok",
12
+ "creator",
13
+ "influencer",
14
+ "brand",
15
+ "sdk",
16
+ "api-client"
17
+ ],
18
+ "type": "module",
19
+ "main": "./dist/index.js",
20
+ "types": "./dist/index.d.ts",
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/index.d.ts",
24
+ "default": "./dist/index.js"
25
+ }
26
+ },
27
+ "files": [
28
+ "dist",
29
+ "README.md"
30
+ ],
31
+ "sideEffects": false,
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
35
+ "scripts": {
36
+ "build": "tsc -p tsconfig.build.json",
37
+ "test": "bun test",
38
+ "typecheck": "tsc --noEmit"
39
+ },
40
+ "devDependencies": {
41
+ "@types/bun": "^1.4.1",
42
+ "typescript": "^5.9.2"
43
+ },
44
+ "engines": {
45
+ "node": ">=18"
46
+ }
47
+ }