@hasna/connectors 1.3.45 → 1.4.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/README.md +25 -10
- package/bin/index.js +1362 -11796
- package/bin/mcp.js +751 -10328
- package/bin/serve.js +2252 -11829
- package/connectors/cloudflare-workers/.env.example +5 -0
- package/connectors/cloudflare-workers/AGENTS.md +11 -0
- package/connectors/cloudflare-workers/CLAUDE.md +11 -0
- package/connectors/cloudflare-workers/README.md +45 -0
- package/connectors/cloudflare-workers/package.json +47 -0
- package/connectors/cloudflare-workers/src/api/client.test.ts +144 -0
- package/connectors/cloudflare-workers/src/api/client.ts +386 -0
- package/connectors/cloudflare-workers/src/api/index.ts +1 -0
- package/connectors/cloudflare-workers/src/cli/index.ts +206 -0
- package/connectors/cloudflare-workers/src/index.ts +16 -0
- package/connectors/cloudflare-workers/src/types/index.ts +53 -0
- package/connectors/cloudflare-workers/src/utils/config.ts +61 -0
- package/connectors/cloudflare-workers/src/utils/output.ts +26 -0
- package/connectors/cloudflare-workers/tsconfig.build.json +4 -0
- package/connectors/cloudflare-workers/tsconfig.json +14 -0
- package/connectors/tidio/.env.example +3 -2
- package/connectors/tidio/CLAUDE.md +21 -96
- package/connectors/tidio/README.md +22 -18
- package/connectors/tidio/package.json +4 -2
- package/connectors/tidio/src/api/client.ts +26 -15
- package/connectors/tidio/src/api/index.ts +185 -128
- package/connectors/tidio/src/api/tidio.test.ts +175 -44
- package/connectors/tidio/src/cli/index.ts +154 -388
- package/connectors/tidio/src/index.ts +5 -3
- package/connectors/tidio/src/types/index.ts +135 -82
- package/connectors/tidio/src/utils/config.ts +90 -44
- package/connectors/wandb/.env.example +7 -0
- package/connectors/wandb/AGENTS.md +43 -0
- package/connectors/wandb/CLAUDE.md +67 -0
- package/connectors/wandb/README.md +65 -0
- package/connectors/wandb/package.json +52 -0
- package/connectors/wandb/src/api/client.test.ts +116 -0
- package/connectors/wandb/src/api/client.ts +83 -0
- package/connectors/wandb/src/api/graphql.ts +9 -0
- package/connectors/wandb/src/api/index.ts +36 -0
- package/connectors/wandb/src/api/projects.ts +36 -0
- package/connectors/wandb/src/api/viewer.ts +20 -0
- package/connectors/wandb/src/cli/index.ts +265 -0
- package/connectors/wandb/src/index.ts +22 -0
- package/connectors/wandb/src/types/index.ts +75 -0
- package/connectors/wandb/src/utils/config.ts +164 -0
- package/connectors/wandb/src/utils/output.ts +119 -0
- package/connectors/wandb/tsconfig.json +16 -0
- package/connectors/weights-biases/.env.example +7 -0
- package/connectors/weights-biases/CLAUDE.md +65 -0
- package/connectors/weights-biases/README.md +62 -0
- package/connectors/weights-biases/package.json +53 -0
- package/connectors/weights-biases/src/api/client.test.ts +46 -0
- package/connectors/weights-biases/src/api/client.ts +119 -0
- package/connectors/weights-biases/src/api/events.ts +18 -0
- package/connectors/weights-biases/src/api/index.ts +56 -0
- package/connectors/weights-biases/src/api/runs.ts +28 -0
- package/connectors/weights-biases/src/api/search.ts +10 -0
- package/connectors/weights-biases/src/cli/index.ts +289 -0
- package/connectors/weights-biases/src/index.ts +19 -0
- package/connectors/weights-biases/src/types/index.ts +92 -0
- package/connectors/weights-biases/src/utils/config.ts +168 -0
- package/connectors/weights-biases/src/utils/output.ts +116 -0
- package/connectors/weights-biases/tsconfig.json +16 -0
- package/connectors/x/src/api/oauth.test.ts +205 -0
- package/connectors/zoominfo/.env.example +4 -0
- package/connectors/zoominfo/AGENTS.md +26 -0
- package/connectors/zoominfo/CLAUDE.md +20 -0
- package/connectors/zoominfo/README.md +69 -0
- package/connectors/zoominfo/package.json +52 -0
- package/connectors/zoominfo/src/api/client.test.ts +140 -0
- package/connectors/zoominfo/src/api/client.ts +228 -0
- package/connectors/zoominfo/src/api/index.ts +1 -0
- package/connectors/zoominfo/src/cli/index.ts +179 -0
- package/connectors/zoominfo/src/index.ts +3 -0
- package/connectors/zoominfo/src/types/index.ts +39 -0
- package/connectors/zoominfo/src/utils/config.ts +61 -0
- package/connectors/zoominfo/src/utils/output.ts +27 -0
- package/connectors/zoominfo/tsconfig.json +20 -0
- package/connectors/zotero/.env.example +7 -0
- package/connectors/zotero/.npmrc.example +2 -0
- package/connectors/zotero/AGENTS.md +40 -0
- package/connectors/zotero/CLAUDE.md +69 -0
- package/connectors/zotero/README.md +91 -0
- package/connectors/zotero/package.json +43 -0
- package/connectors/zotero/src/api/attachments.ts +130 -0
- package/connectors/zotero/src/api/client.test.ts +166 -0
- package/connectors/zotero/src/api/client.ts +169 -0
- package/connectors/zotero/src/api/collections.ts +26 -0
- package/connectors/zotero/src/api/index.ts +45 -0
- package/connectors/zotero/src/api/items.ts +64 -0
- package/connectors/zotero/src/cli/index.ts +386 -0
- package/connectors/zotero/src/index.ts +28 -0
- package/connectors/zotero/src/types/index.ts +129 -0
- package/connectors/zotero/src/utils/config.ts +245 -0
- package/connectors/zotero/src/utils/output.ts +119 -0
- package/connectors/zotero/tsconfig.json +16 -0
- package/connectors/zymbly/.env.example +2 -0
- package/connectors/zymbly/AGENTS.md +11 -0
- package/connectors/zymbly/CLAUDE.md +5 -0
- package/connectors/zymbly/README.md +35 -0
- package/connectors/zymbly/package.json +52 -0
- package/connectors/zymbly/src/api/client.test.ts +110 -0
- package/connectors/zymbly/src/api/client.ts +166 -0
- package/connectors/zymbly/src/api/index.ts +1 -0
- package/connectors/zymbly/src/cli/index.ts +156 -0
- package/connectors/zymbly/src/index.ts +12 -0
- package/connectors/zymbly/src/types/index.ts +34 -0
- package/connectors/zymbly/src/utils/config.ts +52 -0
- package/connectors/zymbly/src/utils/output.ts +17 -0
- package/connectors/zymbly/tsconfig.json +13 -0
- package/dist/db/database.d.ts +2 -1
- package/dist/db/sqlite-adapter.d.ts +43 -0
- package/dist/index.js +172 -9653
- package/dist/lib/compact-output.d.ts +40 -0
- package/dist/mcp/tools/jobs.d.ts +32 -0
- package/dist/mcp/tools/jobs.test.d.ts +1 -0
- package/dist/no-cloud-boundary.test.d.ts +1 -0
- package/package.json +3 -3
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
HttpMethod,
|
|
3
|
+
JsonObject,
|
|
4
|
+
QueryParams,
|
|
5
|
+
RequestOptions,
|
|
6
|
+
ZymblyConfig,
|
|
7
|
+
} from "../types";
|
|
8
|
+
import { ZymblyApiError } from "../types";
|
|
9
|
+
|
|
10
|
+
const DEFAULT_BASE_URL = "https://api.zymbly.com/v1";
|
|
11
|
+
|
|
12
|
+
function normalizePath(path: string): string {
|
|
13
|
+
const trimmed = path.trim();
|
|
14
|
+
if (!trimmed) {
|
|
15
|
+
throw new Error("Zymbly API path is required");
|
|
16
|
+
}
|
|
17
|
+
if (/^https?:\/\//i.test(trimmed)) {
|
|
18
|
+
throw new Error("Zymbly API paths must be relative, not absolute URLs");
|
|
19
|
+
}
|
|
20
|
+
const normalized = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
21
|
+
if (normalized.split("/").some((part) => part === "..")) {
|
|
22
|
+
throw new Error("Zymbly API paths cannot contain parent-directory segments");
|
|
23
|
+
}
|
|
24
|
+
return normalized;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function encodePath(value: string | number): string {
|
|
28
|
+
return encodeURIComponent(String(value));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function appendQuery(url: URL, query?: QueryParams): void {
|
|
32
|
+
if (!query) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
for (const [key, value] of Object.entries(query)) {
|
|
36
|
+
if (Array.isArray(value)) {
|
|
37
|
+
for (const item of value) {
|
|
38
|
+
if (item !== undefined && item !== null) {
|
|
39
|
+
url.searchParams.append(key, String(item));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (value !== undefined && value !== null) {
|
|
45
|
+
url.searchParams.set(key, String(value));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function parseResponse(text: string, contentType: string | null): unknown {
|
|
51
|
+
if (!text) {
|
|
52
|
+
return {};
|
|
53
|
+
}
|
|
54
|
+
if (contentType?.includes("json") || text.startsWith("{") || text.startsWith("[")) {
|
|
55
|
+
try {
|
|
56
|
+
return JSON.parse(text);
|
|
57
|
+
} catch {
|
|
58
|
+
return { raw: text };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return text;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function extractErrorMessage(data: unknown, fallback: string): string {
|
|
65
|
+
if (typeof data === "string" && data.trim()) {
|
|
66
|
+
return data.slice(0, 500);
|
|
67
|
+
}
|
|
68
|
+
if (data && typeof data === "object") {
|
|
69
|
+
const record = data as Record<string, unknown>;
|
|
70
|
+
const message = record.message ?? record.error ?? record.detail ?? record.title;
|
|
71
|
+
if (typeof message === "string" && message.trim()) {
|
|
72
|
+
return message;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return fallback;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export class ZymblyClient {
|
|
79
|
+
private readonly apiKey?: string;
|
|
80
|
+
private readonly baseUrl: string;
|
|
81
|
+
|
|
82
|
+
constructor(config: ZymblyConfig = {}) {
|
|
83
|
+
this.apiKey = config.apiKey;
|
|
84
|
+
this.baseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async request<T = unknown>(path: string, options: RequestOptions = {}): Promise<T> {
|
|
88
|
+
const method = options.method || (options.body ? "POST" : "GET");
|
|
89
|
+
const url = this.buildUrl(path, options.query);
|
|
90
|
+
const headers: Record<string, string> = {
|
|
91
|
+
Accept: "application/json",
|
|
92
|
+
...options.headers,
|
|
93
|
+
};
|
|
94
|
+
const body = this.prepareBody(method, options);
|
|
95
|
+
if (body !== undefined && !headers["Content-Type"]) {
|
|
96
|
+
headers["Content-Type"] = "application/json";
|
|
97
|
+
}
|
|
98
|
+
if (options.auth !== false) {
|
|
99
|
+
headers.Authorization = `Bearer ${this.requireApiKey()}`;
|
|
100
|
+
}
|
|
101
|
+
const response = await fetch(url.toString(), { method, headers, body });
|
|
102
|
+
const text = await response.text();
|
|
103
|
+
const data = parseResponse(text, response.headers.get("content-type"));
|
|
104
|
+
if (!response.ok) {
|
|
105
|
+
throw new ZymblyApiError(
|
|
106
|
+
extractErrorMessage(data, response.statusText || `Zymbly request failed with ${response.status}`),
|
|
107
|
+
response.status,
|
|
108
|
+
data,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
return data as T;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
listWorkOrders<T = unknown>(query?: QueryParams): Promise<T> {
|
|
115
|
+
return this.request<T>("/work-orders", { query });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
getWorkOrder<T = unknown>(workOrderId: string): Promise<T> {
|
|
119
|
+
return this.request<T>(`/work-orders/${encodePath(workOrderId)}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
searchParts<T = unknown>(query?: QueryParams): Promise<T> {
|
|
123
|
+
return this.request<T>("/parts", { query });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
createMaintenanceNote<T = unknown>(workOrderId: string, note: string): Promise<T> {
|
|
127
|
+
return this.request<T>(`/work-orders/${encodePath(workOrderId)}/notes`, {
|
|
128
|
+
method: "POST",
|
|
129
|
+
body: { note },
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
createMaintenanceNoteWithBody<T = unknown>(workOrderId: string, body: JsonObject): Promise<T> {
|
|
134
|
+
return this.request<T>(`/work-orders/${encodePath(workOrderId)}/notes`, {
|
|
135
|
+
method: "POST",
|
|
136
|
+
body,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
rawRequest<T = unknown>(method: HttpMethod, path: string, options: Omit<RequestOptions, "method"> = {}): Promise<T> {
|
|
141
|
+
return this.request<T>(path, { ...options, method });
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private buildUrl(path: string, query?: QueryParams): URL {
|
|
145
|
+
const url = new URL(`${this.baseUrl}${normalizePath(path)}`);
|
|
146
|
+
appendQuery(url, query);
|
|
147
|
+
return url;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private prepareBody(method: HttpMethod, options: RequestOptions): string | undefined {
|
|
151
|
+
if (method === "GET") {
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
if (options.body === undefined) {
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
return typeof options.body === "string" ? options.body : JSON.stringify(options.body);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private requireApiKey(): string {
|
|
161
|
+
if (!this.apiKey) {
|
|
162
|
+
throw new Error("Zymbly API key is required. Use config set-key or set ZYMBLY_API_KEY.");
|
|
163
|
+
}
|
|
164
|
+
return this.apiKey;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ZymblyClient } from "./client";
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { ZymblyClient } from "../api";
|
|
4
|
+
import type { HttpMethod, JsonObject, JsonValue, OutputFormat, QueryParams, QueryValue } from "../types";
|
|
5
|
+
import {
|
|
6
|
+
clearConfig,
|
|
7
|
+
getApiKey,
|
|
8
|
+
getBaseUrl,
|
|
9
|
+
getConfigPath,
|
|
10
|
+
setApiKey,
|
|
11
|
+
setBaseUrl,
|
|
12
|
+
} from "../utils/config";
|
|
13
|
+
import { error, print, success } from "../utils/output";
|
|
14
|
+
|
|
15
|
+
const VERSION = "0.0.1";
|
|
16
|
+
const DEFAULT_BASE_URL = "https://api.zymbly.com/v1";
|
|
17
|
+
|
|
18
|
+
function parseJson(value: string | undefined, label: string): JsonValue {
|
|
19
|
+
if (!value) {
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
return JSON.parse(value) as JsonValue;
|
|
24
|
+
} catch (err) {
|
|
25
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
26
|
+
throw new Error(`Invalid ${label}: ${message}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function parseObject(value: string | undefined, label: string): JsonObject {
|
|
31
|
+
const parsed = parseJson(value, label);
|
|
32
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
33
|
+
throw new Error(`${label} must be a JSON object`);
|
|
34
|
+
}
|
|
35
|
+
return parsed as JsonObject;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function parseQueryParams(value: string | undefined): QueryParams | undefined {
|
|
39
|
+
if (!value) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
const parsed = parseObject(value, "query");
|
|
43
|
+
const query: QueryParams = {};
|
|
44
|
+
for (const [key, entry] of Object.entries(parsed)) {
|
|
45
|
+
if (Array.isArray(entry)) {
|
|
46
|
+
query[key] = entry.map((item) => {
|
|
47
|
+
if (typeof item === "string" || typeof item === "number" || typeof item === "boolean" || item === null || item === undefined) {
|
|
48
|
+
return item as QueryValue;
|
|
49
|
+
}
|
|
50
|
+
throw new Error(`query.${key} must contain only string, number, boolean, null, or undefined values`);
|
|
51
|
+
});
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (typeof entry === "string" || typeof entry === "number" || typeof entry === "boolean" || entry === null || entry === undefined) {
|
|
55
|
+
query[key] = entry as QueryValue;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
throw new Error(`query.${key} must be a string, number, boolean, null, or an array of those values`);
|
|
59
|
+
}
|
|
60
|
+
return query;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function getFormat(command: Command): OutputFormat {
|
|
64
|
+
return (command.optsWithGlobals().format || "pretty") as OutputFormat;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function getClient(command: Command): ZymblyClient {
|
|
68
|
+
const options = command.optsWithGlobals();
|
|
69
|
+
const apiKey = options.apiKey || getApiKey();
|
|
70
|
+
const baseUrl = options.baseUrl || getBaseUrl();
|
|
71
|
+
if (!apiKey) {
|
|
72
|
+
error("No Zymbly API key configured. Use config set-key or set ZYMBLY_API_KEY.");
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
return new ZymblyClient({ apiKey, baseUrl });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function run(command: Command, action: (client: ZymblyClient) => Promise<unknown>): Promise<void> {
|
|
79
|
+
try {
|
|
80
|
+
print(await action(getClient(command)), getFormat(command));
|
|
81
|
+
} catch (err) {
|
|
82
|
+
error(err instanceof Error ? err.message : String(err));
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const program = new Command()
|
|
88
|
+
.name("connect-zymbly")
|
|
89
|
+
.description("Zymbly aircraft maintenance API connector")
|
|
90
|
+
.version(VERSION)
|
|
91
|
+
.option("-k, --api-key <key>", "Zymbly API key")
|
|
92
|
+
.option("--base-url <url>", "API base URL", DEFAULT_BASE_URL)
|
|
93
|
+
.option("-f, --format <format>", "Output format: json or pretty", "pretty");
|
|
94
|
+
|
|
95
|
+
const config = program.command("config").description("Manage local configuration");
|
|
96
|
+
config.command("set-key <key>").description("Save API key").action((key: string) => {
|
|
97
|
+
setApiKey(key);
|
|
98
|
+
success("Zymbly API key saved");
|
|
99
|
+
});
|
|
100
|
+
config.command("set-base-url <url>").description("Save API base URL").action((url: string) => {
|
|
101
|
+
setBaseUrl(url);
|
|
102
|
+
success("Zymbly base URL saved");
|
|
103
|
+
});
|
|
104
|
+
config.command("show").description("Show current configuration").action(() => {
|
|
105
|
+
const key = getApiKey();
|
|
106
|
+
print({
|
|
107
|
+
configPath: getConfigPath(),
|
|
108
|
+
baseUrl: getBaseUrl() || DEFAULT_BASE_URL,
|
|
109
|
+
apiKey: key ? `${key.slice(0, 6)}...` : null,
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
config.command("clear").description("Clear local configuration").action(() => {
|
|
113
|
+
clearConfig();
|
|
114
|
+
success("Zymbly configuration cleared");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const workOrders = program.command("work-orders").description("Manage work orders");
|
|
118
|
+
workOrders
|
|
119
|
+
.command("list")
|
|
120
|
+
.description("List work orders")
|
|
121
|
+
.option("-q, --query <json>", "Query params JSON object")
|
|
122
|
+
.action((opts, cmd) => run(cmd, (client) => client.listWorkOrders(parseQueryParams(opts.query))));
|
|
123
|
+
workOrders
|
|
124
|
+
.command("get <workOrderId>")
|
|
125
|
+
.description("Get a work order by ID")
|
|
126
|
+
.action((workOrderId: string, _opts, cmd) => run(cmd, (client) => client.getWorkOrder(workOrderId)));
|
|
127
|
+
|
|
128
|
+
const parts = program.command("parts").description("Search aircraft parts");
|
|
129
|
+
parts
|
|
130
|
+
.command("search")
|
|
131
|
+
.description("Search parts catalog")
|
|
132
|
+
.option("-q, --query <json>", "Query params JSON object")
|
|
133
|
+
.action((opts, cmd) => run(cmd, (client) => client.searchParts(parseQueryParams(opts.query))));
|
|
134
|
+
|
|
135
|
+
const notes = program.command("notes").description("Maintenance notes");
|
|
136
|
+
notes
|
|
137
|
+
.command("create <workOrderId>")
|
|
138
|
+
.description("Create a maintenance note on a work order")
|
|
139
|
+
.requiredOption("-n, --note <text>", "Note text")
|
|
140
|
+
.action((workOrderId: string, opts, cmd) => run(cmd, (client) => client.createMaintenanceNote(workOrderId, opts.note)));
|
|
141
|
+
|
|
142
|
+
program
|
|
143
|
+
.command("raw <method> <path>")
|
|
144
|
+
.description("Call a raw Zymbly API path")
|
|
145
|
+
.option("-b, --body <json>", "JSON body")
|
|
146
|
+
.option("-q, --query <json>", "Query params JSON object")
|
|
147
|
+
.action((method: string, path: string, opts, cmd) =>
|
|
148
|
+
run(cmd, (client) =>
|
|
149
|
+
client.rawRequest(method.toUpperCase() as HttpMethod, path, {
|
|
150
|
+
body: opts.body ? parseJson(opts.body, "body") : undefined,
|
|
151
|
+
query: parseQueryParams(opts.query),
|
|
152
|
+
}),
|
|
153
|
+
),
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
program.parse();
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export type JsonPrimitive = string | number | boolean | null;
|
|
2
|
+
export type JsonValue = JsonPrimitive | JsonObject | JsonValue[];
|
|
3
|
+
export interface JsonObject {
|
|
4
|
+
[key: string]: JsonValue | undefined;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
8
|
+
export type OutputFormat = "json" | "pretty";
|
|
9
|
+
export type QueryValue = string | number | boolean | null | undefined;
|
|
10
|
+
export type QueryParams = Record<string, QueryValue | QueryValue[]>;
|
|
11
|
+
|
|
12
|
+
export interface ZymblyConfig {
|
|
13
|
+
apiKey?: string;
|
|
14
|
+
baseUrl?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface RequestOptions {
|
|
18
|
+
method?: HttpMethod;
|
|
19
|
+
body?: JsonValue | string;
|
|
20
|
+
headers?: Record<string, string>;
|
|
21
|
+
query?: QueryParams;
|
|
22
|
+
auth?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class ZymblyApiError extends Error {
|
|
26
|
+
constructor(
|
|
27
|
+
message: string,
|
|
28
|
+
public readonly status: number,
|
|
29
|
+
public readonly details?: unknown,
|
|
30
|
+
) {
|
|
31
|
+
super(message);
|
|
32
|
+
this.name = "ZymblyApiError";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
|
|
5
|
+
interface StoredConfig {
|
|
6
|
+
apiKey?: string;
|
|
7
|
+
baseUrl?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const CONFIG_PATH = join(homedir(), ".config", "hasna-connectors", "zymbly.json");
|
|
11
|
+
|
|
12
|
+
function readConfig(): StoredConfig {
|
|
13
|
+
if (!existsSync(CONFIG_PATH)) {
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
return JSON.parse(readFileSync(CONFIG_PATH, "utf8")) as StoredConfig;
|
|
18
|
+
} catch {
|
|
19
|
+
return {};
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function writeConfig(config: StoredConfig): void {
|
|
24
|
+
mkdirSync(dirname(CONFIG_PATH), { recursive: true });
|
|
25
|
+
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function getConfigPath(): string {
|
|
29
|
+
return CONFIG_PATH;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function getApiKey(): string | undefined {
|
|
33
|
+
return process.env.ZYMBLY_API_KEY || readConfig().apiKey;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function getBaseUrl(): string | undefined {
|
|
37
|
+
return process.env.ZYMBLY_BASE_URL || readConfig().baseUrl;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function setApiKey(apiKey: string): void {
|
|
41
|
+
writeConfig({ ...readConfig(), apiKey });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function setBaseUrl(baseUrl: string): void {
|
|
45
|
+
writeConfig({ ...readConfig(), baseUrl });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function clearConfig(): void {
|
|
49
|
+
if (existsSync(CONFIG_PATH)) {
|
|
50
|
+
rmSync(CONFIG_PATH);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { OutputFormat } from "../types";
|
|
2
|
+
|
|
3
|
+
export function print(data: unknown, format: OutputFormat = "pretty"): void {
|
|
4
|
+
if (format === "json") {
|
|
5
|
+
console.log(JSON.stringify(data));
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
console.log(JSON.stringify(data, null, 2));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function error(message: string): void {
|
|
12
|
+
console.error(message);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function success(message: string): void {
|
|
16
|
+
console.log(message);
|
|
17
|
+
}
|
package/dist/db/database.d.ts
CHANGED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Database as BunDatabase, type SQLQueryBindings, type Statement } from "bun:sqlite";
|
|
2
|
+
/** Result of a mutating statement, mirroring `bun:sqlite`'s `Statement.run()`. */
|
|
3
|
+
export interface RunResult {
|
|
4
|
+
changes: number;
|
|
5
|
+
lastInsertRowid: number | bigint;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Statement bindings. `bun:sqlite` accepts both the spread form
|
|
9
|
+
* (`run(sql, a, b)`) and a single array of values (`run(sql, [a, b])`), and
|
|
10
|
+
* call sites in `src/db` use both, so both are allowed here.
|
|
11
|
+
*/
|
|
12
|
+
export type Bindings = SQLQueryBindings | SQLQueryBindings[];
|
|
13
|
+
/** A prepared statement handle returned by {@link SqliteAdapter.prepare}. */
|
|
14
|
+
export interface PreparedStatement {
|
|
15
|
+
run(...params: Bindings[]): RunResult;
|
|
16
|
+
get(...params: Bindings[]): unknown;
|
|
17
|
+
all(...params: Bindings[]): unknown[];
|
|
18
|
+
finalize(): void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Thin synchronous wrapper over `bun:sqlite`.
|
|
22
|
+
*
|
|
23
|
+
* This is the local-only storage engine for the connectors database. It was
|
|
24
|
+
* previously imported from `@hasna/cloud`, which is retired and unsupported;
|
|
25
|
+
* the class is kept API-compatible with that import so the rest of `src/db`
|
|
26
|
+
* and its tests are unchanged.
|
|
27
|
+
*
|
|
28
|
+
* Parameters are forwarded to `bun:sqlite` verbatim, so both the spread form
|
|
29
|
+
* (`run(sql, a, b)`) and the array form (`run(sql, [a, b])`) keep working.
|
|
30
|
+
*/
|
|
31
|
+
export declare class SqliteAdapter {
|
|
32
|
+
private readonly db;
|
|
33
|
+
constructor(path: string);
|
|
34
|
+
run(sql: string, ...params: Bindings[]): RunResult;
|
|
35
|
+
get(sql: string, ...params: Bindings[]): unknown;
|
|
36
|
+
all(sql: string, ...params: Bindings[]): unknown[];
|
|
37
|
+
exec(sql: string): void;
|
|
38
|
+
query(sql: string): Statement;
|
|
39
|
+
prepare(sql: string): PreparedStatement;
|
|
40
|
+
close(): void;
|
|
41
|
+
transaction<T>(fn: () => T): T;
|
|
42
|
+
get raw(): BunDatabase;
|
|
43
|
+
}
|