@hasna/connectors 1.3.44 → 1.3.46

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 (114) hide show
  1. package/README.md +25 -0
  2. package/bin/index.js +792 -130
  3. package/bin/mcp.js +438 -52
  4. package/bin/serve.js +438 -52
  5. package/connectors/cloudflare-workers/.env.example +5 -0
  6. package/connectors/cloudflare-workers/AGENTS.md +11 -0
  7. package/connectors/cloudflare-workers/CLAUDE.md +11 -0
  8. package/connectors/cloudflare-workers/README.md +45 -0
  9. package/connectors/cloudflare-workers/package.json +47 -0
  10. package/connectors/cloudflare-workers/src/api/client.test.ts +144 -0
  11. package/connectors/cloudflare-workers/src/api/client.ts +386 -0
  12. package/connectors/cloudflare-workers/src/api/index.ts +1 -0
  13. package/connectors/cloudflare-workers/src/cli/index.ts +206 -0
  14. package/connectors/cloudflare-workers/src/index.ts +16 -0
  15. package/connectors/cloudflare-workers/src/types/index.ts +53 -0
  16. package/connectors/cloudflare-workers/src/utils/config.ts +61 -0
  17. package/connectors/cloudflare-workers/src/utils/output.ts +26 -0
  18. package/connectors/cloudflare-workers/tsconfig.build.json +4 -0
  19. package/connectors/cloudflare-workers/tsconfig.json +14 -0
  20. package/connectors/tidio/.env.example +3 -2
  21. package/connectors/tidio/CLAUDE.md +21 -96
  22. package/connectors/tidio/README.md +22 -18
  23. package/connectors/tidio/package.json +4 -2
  24. package/connectors/tidio/src/api/client.ts +26 -15
  25. package/connectors/tidio/src/api/index.ts +185 -128
  26. package/connectors/tidio/src/api/tidio.test.ts +175 -44
  27. package/connectors/tidio/src/cli/index.ts +154 -388
  28. package/connectors/tidio/src/index.ts +5 -3
  29. package/connectors/tidio/src/types/index.ts +135 -82
  30. package/connectors/tidio/src/utils/config.ts +90 -44
  31. package/connectors/wandb/.env.example +7 -0
  32. package/connectors/wandb/AGENTS.md +43 -0
  33. package/connectors/wandb/CLAUDE.md +67 -0
  34. package/connectors/wandb/README.md +65 -0
  35. package/connectors/wandb/package.json +52 -0
  36. package/connectors/wandb/src/api/client.test.ts +116 -0
  37. package/connectors/wandb/src/api/client.ts +83 -0
  38. package/connectors/wandb/src/api/graphql.ts +9 -0
  39. package/connectors/wandb/src/api/index.ts +36 -0
  40. package/connectors/wandb/src/api/projects.ts +36 -0
  41. package/connectors/wandb/src/api/viewer.ts +20 -0
  42. package/connectors/wandb/src/cli/index.ts +265 -0
  43. package/connectors/wandb/src/index.ts +22 -0
  44. package/connectors/wandb/src/types/index.ts +75 -0
  45. package/connectors/wandb/src/utils/config.ts +164 -0
  46. package/connectors/wandb/src/utils/output.ts +119 -0
  47. package/connectors/wandb/tsconfig.json +16 -0
  48. package/connectors/weights-biases/.env.example +7 -0
  49. package/connectors/weights-biases/CLAUDE.md +65 -0
  50. package/connectors/weights-biases/README.md +62 -0
  51. package/connectors/weights-biases/package.json +53 -0
  52. package/connectors/weights-biases/src/api/client.test.ts +46 -0
  53. package/connectors/weights-biases/src/api/client.ts +119 -0
  54. package/connectors/weights-biases/src/api/events.ts +18 -0
  55. package/connectors/weights-biases/src/api/index.ts +56 -0
  56. package/connectors/weights-biases/src/api/runs.ts +28 -0
  57. package/connectors/weights-biases/src/api/search.ts +10 -0
  58. package/connectors/weights-biases/src/cli/index.ts +289 -0
  59. package/connectors/weights-biases/src/index.ts +19 -0
  60. package/connectors/weights-biases/src/types/index.ts +92 -0
  61. package/connectors/weights-biases/src/utils/config.ts +168 -0
  62. package/connectors/weights-biases/src/utils/output.ts +116 -0
  63. package/connectors/weights-biases/tsconfig.json +16 -0
  64. package/connectors/zoominfo/.env.example +4 -0
  65. package/connectors/zoominfo/AGENTS.md +26 -0
  66. package/connectors/zoominfo/CLAUDE.md +20 -0
  67. package/connectors/zoominfo/README.md +69 -0
  68. package/connectors/zoominfo/package.json +52 -0
  69. package/connectors/zoominfo/src/api/client.test.ts +140 -0
  70. package/connectors/zoominfo/src/api/client.ts +228 -0
  71. package/connectors/zoominfo/src/api/index.ts +1 -0
  72. package/connectors/zoominfo/src/cli/index.ts +179 -0
  73. package/connectors/zoominfo/src/index.ts +3 -0
  74. package/connectors/zoominfo/src/types/index.ts +39 -0
  75. package/connectors/zoominfo/src/utils/config.ts +61 -0
  76. package/connectors/zoominfo/src/utils/output.ts +27 -0
  77. package/connectors/zoominfo/tsconfig.json +20 -0
  78. package/connectors/zotero/.env.example +7 -0
  79. package/connectors/zotero/.npmrc.example +2 -0
  80. package/connectors/zotero/AGENTS.md +40 -0
  81. package/connectors/zotero/CLAUDE.md +69 -0
  82. package/connectors/zotero/README.md +91 -0
  83. package/connectors/zotero/package.json +43 -0
  84. package/connectors/zotero/src/api/attachments.ts +130 -0
  85. package/connectors/zotero/src/api/client.test.ts +166 -0
  86. package/connectors/zotero/src/api/client.ts +169 -0
  87. package/connectors/zotero/src/api/collections.ts +26 -0
  88. package/connectors/zotero/src/api/index.ts +45 -0
  89. package/connectors/zotero/src/api/items.ts +64 -0
  90. package/connectors/zotero/src/cli/index.ts +386 -0
  91. package/connectors/zotero/src/index.ts +28 -0
  92. package/connectors/zotero/src/types/index.ts +129 -0
  93. package/connectors/zotero/src/utils/config.ts +245 -0
  94. package/connectors/zotero/src/utils/output.ts +119 -0
  95. package/connectors/zotero/tsconfig.json +16 -0
  96. package/connectors/zymbly/.env.example +2 -0
  97. package/connectors/zymbly/AGENTS.md +11 -0
  98. package/connectors/zymbly/CLAUDE.md +5 -0
  99. package/connectors/zymbly/README.md +35 -0
  100. package/connectors/zymbly/package.json +52 -0
  101. package/connectors/zymbly/src/api/client.test.ts +110 -0
  102. package/connectors/zymbly/src/api/client.ts +166 -0
  103. package/connectors/zymbly/src/api/index.ts +1 -0
  104. package/connectors/zymbly/src/cli/index.ts +156 -0
  105. package/connectors/zymbly/src/index.ts +12 -0
  106. package/connectors/zymbly/src/types/index.ts +34 -0
  107. package/connectors/zymbly/src/utils/config.ts +52 -0
  108. package/connectors/zymbly/src/utils/output.ts +17 -0
  109. package/connectors/zymbly/tsconfig.json +13 -0
  110. package/dist/index.js +45 -2
  111. package/dist/lib/compact-output.d.ts +40 -0
  112. package/dist/mcp/tools/jobs.d.ts +32 -0
  113. package/dist/mcp/tools/jobs.test.d.ts +1 -0
  114. package/package.json +3 -2
@@ -0,0 +1,206 @@
1
+ #!/usr/bin/env bun
2
+ import { readFileSync } from "node:fs";
3
+ import { Command } from "commander";
4
+ import { CloudflareWorkers } from "../api";
5
+ import type { HttpMethod, JsonObject, JsonValue, OutputFormat, QueryParams, QueryValue } from "../types";
6
+ import { clearConfig, getAccountId, getApiToken, getBaseUrl, loadConfig, setAccountId, setApiToken, setBaseUrl } from "../utils/config";
7
+ import { error, info, print, success } from "../utils/output";
8
+
9
+ const CONNECTOR_NAME = "connect-cloudflare-workers";
10
+ const VERSION = "0.0.1";
11
+
12
+ const program = new Command();
13
+
14
+ program
15
+ .name(CONNECTOR_NAME)
16
+ .description("Cloudflare Workers REST API CLI")
17
+ .version(VERSION)
18
+ .option("-f, --format <format>", "Output format (json, pretty)", "pretty")
19
+ .option("--api-token <token>", "Cloudflare API token")
20
+ .option("--account-id <id>", "Cloudflare account ID")
21
+ .option("--base-url <url>", "Cloudflare API base URL");
22
+
23
+ function globalOptions(): Record<string, unknown> {
24
+ return program.opts() as Record<string, unknown>;
25
+ }
26
+
27
+ function optionString(source: Record<string, unknown>, key: string): string | undefined {
28
+ const value = source[key];
29
+ return typeof value === "string" ? value : undefined;
30
+ }
31
+
32
+ function getFormat(): OutputFormat {
33
+ return (optionString(globalOptions(), "format") || "pretty") as OutputFormat;
34
+ }
35
+
36
+ function client(): CloudflareWorkers {
37
+ const opts = globalOptions();
38
+ return new CloudflareWorkers({
39
+ apiToken: optionString(opts, "apiToken") || getApiToken(),
40
+ accountId: optionString(opts, "accountId") || getAccountId(),
41
+ baseUrl: optionString(opts, "baseUrl") || getBaseUrl(),
42
+ });
43
+ }
44
+
45
+ function parseJsonValue(value: string | undefined, label: string, fallback: JsonValue | undefined): JsonValue | undefined {
46
+ if (!value) return fallback;
47
+ try {
48
+ return JSON.parse(value) as JsonValue;
49
+ } catch (err) {
50
+ throw new Error(`Invalid ${label}: ${err instanceof Error ? err.message : String(err)}`);
51
+ }
52
+ }
53
+
54
+ function parseJsonObject(value: string | undefined, label: string, fallback: JsonObject = {}): JsonObject {
55
+ const parsed = parseJsonValue(value, label, fallback);
56
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`${label} must be a JSON object`);
57
+ return parsed as JsonObject;
58
+ }
59
+
60
+ function optionalJsonObject(value: string | undefined, label: string): JsonObject | undefined {
61
+ if (!value) return undefined;
62
+ return parseJsonObject(value, label);
63
+ }
64
+
65
+ function toQueryValue(value: unknown, label: string): QueryValue | QueryValue[] {
66
+ if (value === undefined || value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value as QueryValue;
67
+ if (Array.isArray(value)) {
68
+ for (const item of value) {
69
+ if (item !== null && typeof item !== "string" && typeof item !== "number" && typeof item !== "boolean") {
70
+ throw new Error(`${label} array values must be string, number, boolean, or null`);
71
+ }
72
+ }
73
+ return value as QueryValue[];
74
+ }
75
+ throw new Error(`${label} must be string, number, boolean, array, null, or undefined`);
76
+ }
77
+
78
+ function params(source: Record<string, unknown>): QueryParams {
79
+ const parsed = parseJsonObject(optionString(source, "params"), "params", {});
80
+ const output: QueryParams = {};
81
+ for (const [key, value] of Object.entries(parsed)) output[key] = toQueryValue(value, `params.${key}`);
82
+ return output;
83
+ }
84
+
85
+ function body(source: Record<string, unknown>): JsonObject {
86
+ return parseJsonObject(optionString(source, "body"), "body");
87
+ }
88
+
89
+ function bodyValue(source: Record<string, unknown>): JsonValue {
90
+ const parsed = parseJsonValue(optionString(source, "body"), "body", undefined);
91
+ if (parsed === undefined) throw new Error("body is required");
92
+ return parsed;
93
+ }
94
+
95
+ function scriptBody(source: Record<string, unknown>): string | Uint8Array {
96
+ const file = optionString(source, "file");
97
+ if (file) return readFileSync(file);
98
+ const text = optionString(source, "text");
99
+ if (text !== undefined) return text;
100
+ throw new Error("upload-script requires --file or --text");
101
+ }
102
+
103
+ function method(value: unknown): HttpMethod {
104
+ const input = typeof value === "string" ? value.toUpperCase() : "GET";
105
+ if (!["GET", "POST", "PUT", "PATCH", "DELETE"].includes(input)) throw new Error("method must be one of GET, POST, PUT, PATCH, DELETE");
106
+ return input as HttpMethod;
107
+ }
108
+
109
+ async function run(action: () => Promise<unknown>): Promise<void> {
110
+ try {
111
+ print(await action(), getFormat());
112
+ } catch (err) {
113
+ error(err instanceof Error ? err.message : String(err));
114
+ process.exit(1);
115
+ }
116
+ }
117
+
118
+ const configCmd = program.command("config").description("Manage CLI configuration");
119
+
120
+ configCmd.command("set-token <apiToken>").description("Store a Cloudflare API token").action((apiToken: string) => {
121
+ setApiToken(apiToken);
122
+ success("API token saved");
123
+ });
124
+
125
+ configCmd.command("set-account <accountId>").description("Store a Cloudflare account ID").action((accountId: string) => {
126
+ setAccountId(accountId);
127
+ success("Account ID saved");
128
+ });
129
+
130
+ configCmd.command("set-base-url <baseUrl>").description("Store the Cloudflare API base URL").action((baseUrl: string) => {
131
+ setBaseUrl(baseUrl);
132
+ success(`Base URL set to ${baseUrl}`);
133
+ });
134
+
135
+ configCmd.command("show").description("Show current configuration").action(() => {
136
+ const api = client();
137
+ const stored = loadConfig();
138
+ info(`API token: ${getApiToken() ? "configured" : "not set"}`);
139
+ info(`Account ID: ${getAccountId() ? "configured" : "not set"}`);
140
+ info(`Base URL: ${api.getBaseUrl()}`);
141
+ if (Object.keys(stored).length > 0) info("Stored config file is present");
142
+ });
143
+
144
+ configCmd.command("clear").description("Clear saved configuration").action(() => {
145
+ clearConfig();
146
+ success("Configuration cleared");
147
+ });
148
+
149
+ program.command("list-scripts").description("List Workers scripts").option("--params <json>", "Query parameters JSON").action((opts: Record<string, unknown>) => run(() => client().listScripts(params(opts))));
150
+ program.command("get-script <scriptName>").description("Get Workers script metadata").action((scriptName: string) => run(() => client().getScript(scriptName)));
151
+ program.command("script-content <scriptName>").description("Get Workers script source text").action((scriptName: string) => run(() => client().getScriptContent(scriptName)));
152
+ program.command("upload-script <scriptName>").description("Upload a Workers script as multipart form data").option("--file <path>", "Read script body from file").option("--text <value>", "Use text as script body").option("--metadata <json>", "Worker metadata JSON").option("--filename <name>", "Script part filename").option("--content-type <type>", "Script Content-Type").action((scriptName: string, opts: Record<string, unknown>) => run(() => client().uploadScript(scriptName, scriptBody(opts), {
153
+ metadata: optionalJsonObject(optionString(opts, "metadata"), "metadata"),
154
+ filename: optionString(opts, "filename"),
155
+ contentType: optionString(opts, "contentType"),
156
+ })));
157
+ program.command("delete-script <scriptName>").description("Delete a Workers script").action((scriptName: string) => run(() => client().deleteScript(scriptName)));
158
+
159
+ program.command("get-settings <scriptName>").description("Get script settings").action((scriptName: string) => run(() => client().getScriptSettings(scriptName)));
160
+ program.command("update-settings <scriptName>").description("Patch script settings").requiredOption("--body <json>", "Settings JSON body").action((scriptName: string, opts: Record<string, unknown>) => run(() => client().updateScriptSettings(scriptName, body(opts))));
161
+ program.command("list-deployments <scriptName>").description("List script deployments").action((scriptName: string) => run(() => client().listDeployments(scriptName)));
162
+ program.command("create-deployment <scriptName>").description("Create a script deployment").requiredOption("--body <json>", "Deployment JSON body").action((scriptName: string, opts: Record<string, unknown>) => run(() => client().createDeployment(scriptName, body(opts))));
163
+ program.command("list-versions <scriptName>").description("List script versions").action((scriptName: string) => run(() => client().listVersions(scriptName)));
164
+ program.command("get-version <scriptName> <versionId>").description("Get a script version").action((scriptName: string, versionId: string) => run(() => client().getVersion(scriptName, versionId)));
165
+ program.command("list-secrets <scriptName>").description("List script secrets").action((scriptName: string) => run(() => client().listSecrets(scriptName)));
166
+ program.command("put-secret <scriptName>").description("Create or update a script secret").requiredOption("--body <json>", "Secret JSON body").action((scriptName: string, opts: Record<string, unknown>) => run(() => client().putSecret(scriptName, body(opts))));
167
+ program.command("delete-secret <scriptName> <secretName>").description("Delete a script secret").action((scriptName: string, secretName: string) => run(() => client().deleteSecret(scriptName, secretName)));
168
+ program.command("get-schedules <scriptName>").description("Get script cron schedules").action((scriptName: string) => run(() => client().getSchedules(scriptName)));
169
+ program.command("put-schedules <scriptName>").description("Replace script cron schedules").requiredOption("--body <json>", "Schedules JSON body").action((scriptName: string, opts: Record<string, unknown>) => run(() => client().putSchedules(scriptName, body(opts))));
170
+ program.command("create-tail <scriptName>").description("Create a Workers tail").action((scriptName: string) => run(() => client().createTail(scriptName)));
171
+ program.command("delete-tail <scriptName> <tailId>").description("Delete a Workers tail").action((scriptName: string, tailId: string) => run(() => client().deleteTail(scriptName, tailId)));
172
+
173
+ program.command("get-account-subdomain").description("Get the account Workers subdomain").action(() => run(() => client().getAccountSubdomain()));
174
+ program.command("update-account-subdomain").description("Update the account Workers subdomain").requiredOption("--body <json>", "Subdomain JSON body").action((opts: Record<string, unknown>) => run(() => client().updateAccountSubdomain(body(opts))));
175
+ program.command("get-script-subdomain <scriptName>").description("Get a script subdomain setting").action((scriptName: string) => run(() => client().getScriptSubdomain(scriptName)));
176
+ program.command("update-script-subdomain <scriptName>").description("Update a script subdomain setting").requiredOption("--body <json>", "Script subdomain JSON body").action((scriptName: string, opts: Record<string, unknown>) => run(() => client().updateScriptSubdomain(scriptName, body(opts))));
177
+
178
+ program.command("list-routes <zoneId>").description("List zone Workers routes").action((zoneId: string) => run(() => client().listRoutes(zoneId)));
179
+ program.command("get-route <zoneId> <routeId>").description("Get a zone Workers route").action((zoneId: string, routeId: string) => run(() => client().getRoute(zoneId, routeId)));
180
+ program.command("create-route <zoneId>").description("Create a zone Workers route").requiredOption("--body <json>", "Route JSON body").action((zoneId: string, opts: Record<string, unknown>) => run(() => client().createRoute(zoneId, body(opts))));
181
+ program.command("update-route <zoneId> <routeId>").description("Update a zone Workers route").requiredOption("--body <json>", "Route JSON body").action((zoneId: string, routeId: string, opts: Record<string, unknown>) => run(() => client().updateRoute(zoneId, routeId, body(opts))));
182
+ program.command("delete-route <zoneId> <routeId>").description("Delete a zone Workers route").action((zoneId: string, routeId: string) => run(() => client().deleteRoute(zoneId, routeId)));
183
+
184
+ program.command("get-account-settings").description("Get Workers account settings").action(() => run(() => client().getAccountSettings()));
185
+ program.command("update-account-settings").description("Patch Workers account settings").requiredOption("--body <json>", "Account settings JSON body").action((opts: Record<string, unknown>) => run(() => client().updateAccountSettings(body(opts))));
186
+
187
+ program.command("list-beta-workers").description("List beta Workers").option("--params <json>", "Query parameters JSON").action((opts: Record<string, unknown>) => run(() => client().listBetaWorkers(params(opts))));
188
+ program.command("create-beta-worker").description("Create a beta Worker").requiredOption("--body <json>", "Beta Worker JSON body").action((opts: Record<string, unknown>) => run(() => client().createBetaWorker(body(opts))));
189
+ program.command("get-beta-worker <workerId>").description("Get a beta Worker").action((workerId: string) => run(() => client().getBetaWorker(workerId)));
190
+ program.command("delete-beta-worker <workerId>").description("Delete a beta Worker").action((workerId: string) => run(() => client().deleteBetaWorker(workerId)));
191
+ program.command("list-beta-versions <workerId>").description("List beta Worker versions").action((workerId: string) => run(() => client().listBetaVersions(workerId)));
192
+ program.command("create-beta-version <workerId>").description("Create a beta Worker version").requiredOption("--body <json>", "Beta version JSON body").action((workerId: string, opts: Record<string, unknown>) => run(() => client().createBetaVersion(workerId, body(opts))));
193
+ program.command("get-beta-version <workerId> <versionId>").description("Get a beta Worker version").action((workerId: string, versionId: string) => run(() => client().getBetaVersion(workerId, versionId)));
194
+
195
+ program
196
+ .command("raw <path>")
197
+ .description("Run a constrained raw Cloudflare Workers API request")
198
+ .option("-X, --method <method>", "HTTP method", "GET")
199
+ .option("--params <json>", "Query parameters JSON")
200
+ .option("--body <json>", "Request JSON body")
201
+ .action((path: string, opts: Record<string, unknown>) => run(() => client().rawRequest(method(opts.method), path, {
202
+ query: params(opts),
203
+ body: optionString(opts, "body") ? bodyValue(opts) : undefined,
204
+ })));
205
+
206
+ program.parse();
@@ -0,0 +1,16 @@
1
+ export { CloudflareWorkers, CloudflareWorkersClient } from "./api";
2
+ export type {
3
+ CloudflareWorkersConfig,
4
+ CloudflareWorkersRawResponse,
5
+ CloudflareWorkersRequestOptions,
6
+ HttpMethod,
7
+ JsonObject,
8
+ JsonPrimitive,
9
+ JsonValue,
10
+ OutputFormat,
11
+ QueryParams,
12
+ QueryValue,
13
+ RequestBody,
14
+ ResponseType,
15
+ } from "./types";
16
+ export { CloudflareWorkersApiError } from "./types";
@@ -0,0 +1,53 @@
1
+ export type JsonPrimitive = string | number | boolean | null;
2
+ export type JsonValue = JsonPrimitive | JsonObject | JsonValue[];
3
+
4
+ export interface JsonObject {
5
+ [key: string]: JsonValue | undefined;
6
+ }
7
+
8
+ export type OutputFormat = "json" | "pretty";
9
+ export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
10
+
11
+ export type QueryPrimitive = string | number | boolean;
12
+ export type QueryValue = QueryPrimitive | Date | null | undefined;
13
+
14
+ export interface QueryParams {
15
+ [key: string]: QueryValue | QueryValue[];
16
+ }
17
+
18
+ export type RequestBody = JsonValue | string | Uint8Array | ArrayBuffer | Blob | FormData;
19
+ export type ResponseType = "json" | "text" | "arrayBuffer";
20
+
21
+ export interface CloudflareWorkersConfig {
22
+ apiToken?: string;
23
+ accountId?: string;
24
+ baseUrl?: string;
25
+ timeoutMs?: number;
26
+ }
27
+
28
+ export interface CloudflareWorkersRawResponse {
29
+ status: number;
30
+ headers: Record<string, string>;
31
+ body: ArrayBuffer;
32
+ }
33
+
34
+ export interface CloudflareWorkersRequestOptions {
35
+ method?: HttpMethod;
36
+ query?: QueryParams;
37
+ body?: RequestBody;
38
+ headers?: Record<string, string>;
39
+ rawBody?: boolean;
40
+ responseType?: ResponseType;
41
+ signal?: AbortSignal;
42
+ }
43
+
44
+ export class CloudflareWorkersApiError extends Error {
45
+ constructor(
46
+ message: string,
47
+ readonly status?: number,
48
+ readonly details?: unknown,
49
+ ) {
50
+ super(message);
51
+ this.name = "CloudflareWorkersApiError";
52
+ }
53
+ }
@@ -0,0 +1,61 @@
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
+ apiToken?: string;
7
+ accountId?: string;
8
+ baseUrl?: string;
9
+ }
10
+
11
+ const configPath = join(homedir(), ".hasna", "connectors", "cloudflare-workers.json");
12
+
13
+ function readConfig(): StoredConfig {
14
+ if (!existsSync(configPath)) return {};
15
+ try {
16
+ return JSON.parse(readFileSync(configPath, "utf8")) as StoredConfig;
17
+ } catch {
18
+ return {};
19
+ }
20
+ }
21
+
22
+ function writeConfig(config: StoredConfig): void {
23
+ mkdirSync(dirname(configPath), { recursive: true });
24
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
25
+ }
26
+
27
+ export function getConfigPath(): string {
28
+ return configPath;
29
+ }
30
+
31
+ export function getApiToken(): string | undefined {
32
+ return process.env.CLOUDFLARE_WORKERS_API_TOKEN || process.env.CLOUDFLARE_API_TOKEN || readConfig().apiToken;
33
+ }
34
+
35
+ export function setApiToken(apiToken: string): void {
36
+ writeConfig({ ...readConfig(), apiToken });
37
+ }
38
+
39
+ export function getAccountId(): string | undefined {
40
+ return process.env.CLOUDFLARE_WORKERS_ACCOUNT_ID || process.env.CLOUDFLARE_ACCOUNT_ID || readConfig().accountId;
41
+ }
42
+
43
+ export function setAccountId(accountId: string): void {
44
+ writeConfig({ ...readConfig(), accountId });
45
+ }
46
+
47
+ export function getBaseUrl(): string | undefined {
48
+ return process.env.CLOUDFLARE_WORKERS_BASE_URL || readConfig().baseUrl;
49
+ }
50
+
51
+ export function setBaseUrl(baseUrl: string): void {
52
+ writeConfig({ ...readConfig(), baseUrl });
53
+ }
54
+
55
+ export function loadConfig(): StoredConfig {
56
+ return readConfig();
57
+ }
58
+
59
+ export function clearConfig(): void {
60
+ if (existsSync(configPath)) rmSync(configPath);
61
+ }
@@ -0,0 +1,26 @@
1
+ import chalk from "chalk";
2
+ import type { OutputFormat } from "../types";
3
+
4
+ export function print(data: unknown, format: OutputFormat = "pretty"): void {
5
+ if (format === "json") {
6
+ console.log(JSON.stringify(data, null, 2));
7
+ return;
8
+ }
9
+ if (typeof data === "string") {
10
+ console.log(data);
11
+ return;
12
+ }
13
+ console.log(JSON.stringify(data, null, 2));
14
+ }
15
+
16
+ export function success(message: string): void {
17
+ console.log(chalk.green(message));
18
+ }
19
+
20
+ export function info(message: string): void {
21
+ console.log(chalk.cyan(message));
22
+ }
23
+
24
+ export function error(message: string): void {
25
+ console.error(chalk.red(message));
26
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "exclude": ["node_modules", "dist", "bin", "src/**/*.test.ts"]
4
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "declaration": true,
10
+ "outDir": "dist",
11
+ "types": ["bun-types"]
12
+ },
13
+ "include": ["src/**/*.ts"]
14
+ }
@@ -1,2 +1,3 @@
1
- # Tidio OpenAPI key (Developer > OpenAPI in Tidio panel)
2
- TIDIO_API_KEY=your-api-key-here
1
+ # Tidio OpenAPI credentials from Developer > OpenAPI in the Tidio panel.
2
+ TIDIO_CLIENT_ID=ci_your_client_id_here
3
+ TIDIO_CLIENT_SECRET=cs_your_client_secret_here
@@ -1,105 +1,30 @@
1
- # CLAUDE.md
1
+ # connect-tidio
2
2
 
3
- This file provides guidance to Claude Code when working with the connect-tidio connector.
3
+ Tidio OpenAPI connector package.
4
4
 
5
- ## Project Overview
5
+ ## Current API Contract
6
6
 
7
- connect-tidio is a TypeScript connector for the Tidio OpenAPI. It provides CLI and library access to manage contacts, conversations, operators, departments, tags, automations, canned responses, webhooks, and project settings.
7
+ - Base URL: `https://api.tidio.com/`
8
+ - Accept header: `application/json; version=1`
9
+ - Auth headers:
10
+ - `X-Tidio-Openapi-Client-Id`
11
+ - `X-Tidio-Openapi-Client-Secret`
8
12
 
9
- ## Build & Run Commands
13
+ Do not reintroduce the legacy `https://api.tidio.co/v1` base URL or a single `X-Tidio-Openapi-Key` header.
10
14
 
11
- ```bash
12
- bun install
13
- bun run dev
14
- bun run build
15
- bun run typecheck
16
- bun run dev contact list
17
- bun run dev conversation list
18
- bun run dev project get
19
- bun run dev config show
20
- ```
15
+ ## Supported Areas
21
16
 
22
- ## API Details
17
+ - Contacts and contact properties
18
+ - Contact messages at `/contacts/{contactId}/messages`
19
+ - Departments
20
+ - Operators
21
+ - Project info
22
+ - Tickets
23
+ - Products for Lyro recommendations
24
+ - Lyro data sources and ticket answering helpers
23
25
 
24
- - **Base URL**: `https://api.tidio.co/v1`
25
- - **Auth**: Custom header `X-Tidio-Openapi-Key: <api_key>` (NOT Bearer token)
26
- - **Format**: JSON REST
27
- - **Docs**: https://developers.tidio.com/reference
26
+ Do not add undocumented CRUD helpers for `/conversations`, `/tags`, `/automations`, `/canned-responses`, or `/webhooks` unless the current Tidio docs prove those OpenAPI paths exist.
28
27
 
29
- ## Project Structure
28
+ ## Security
30
29
 
31
- ```
32
- src/
33
- ├── api/
34
- │ ├── client.ts # HTTP client with X-Tidio-Openapi-Key auth and retry
35
- │ └── index.ts # Tidio API wrapper class
36
- ├── cli/
37
- │ └── index.ts # CLI commands
38
- ├── types/
39
- │ └── index.ts # Type definitions
40
- ├── utils/
41
- │ ├── config.ts # Multi-profile configuration
42
- │ └── output.ts # CLI output formatting
43
- └── index.ts # Library exports
44
- ```
45
-
46
- ## API Coverage
47
-
48
- ### Contacts
49
- - List, get, create, update, delete contacts
50
-
51
- ### Conversations
52
- - List, get conversations
53
- - List and send messages
54
- - Set status (open/closed/snoozed)
55
- - Assign to operator or department
56
-
57
- ### Operators & Departments
58
- - List and get operators
59
- - List departments
60
-
61
- ### Tags
62
- - List, create, delete tags
63
-
64
- ### Automations
65
- - List automations
66
-
67
- ### Canned Responses
68
- - List and create canned responses
69
-
70
- ### Webhooks
71
- - List, create, delete webhooks
72
-
73
- ### Project
74
- - Get project details
75
-
76
- ## Authentication
77
-
78
- Uses API key authentication with the Tidio OpenAPI:
79
-
80
- ```typescript
81
- 'X-Tidio-Openapi-Key': apiKey
82
- ```
83
-
84
- Generate the key in Tidio Panel under **Developer > OpenAPI**.
85
-
86
- ## Environment Variables
87
-
88
- | Variable | Description |
89
- |----------|-------------|
90
- | `TIDIO_API_KEY` | OpenAPI key (overrides profile) |
91
-
92
- ## Data Storage
93
-
94
- ```
95
- ~/.hasna/connectors/connect-tidio/
96
- ├── current_profile # Active profile name
97
- └── profiles/
98
- ├── default.json
99
- └── {name}.json
100
- ```
101
-
102
- ## Dependencies
103
-
104
- - commander: CLI framework
105
- - chalk: Terminal styling
30
+ Profiles store client id and client secret under the connector config directory. Keep config directories `0700`, profile files `0600`, and validate profile names before joining filesystem paths.
@@ -1,14 +1,18 @@
1
1
  # connect-tidio
2
2
 
3
- TypeScript connector for the [Tidio OpenAPI](https://developers.tidio.com/reference). Manage live chat contacts, conversations, operators, tags, webhooks, and more.
3
+ TypeScript connector for the [Tidio OpenAPI](https://developers.tidio.com/reference).
4
4
 
5
5
  ## Features
6
6
 
7
- - Multi-profile configuration
8
- - `X-Tidio-Openapi-Key` authentication
9
- - Contacts, conversations, operators, departments, tags, automations, canned responses, webhooks
7
+ - Tidio OpenAPI v1 server: `https://api.tidio.com/`
8
+ - Required `Accept: application/json; version=1` requests
9
+ - `X-Tidio-Openapi-Client-Id` and `X-Tidio-Openapi-Client-Secret` authentication
10
+ - Contacts, contact messages, departments, operators, project info, tickets, products, and Lyro data source helpers
11
+ - Secure local profiles with private config directories and profile files
10
12
  - Pretty and JSON output formats
11
- - Automatic retry on rate limits (429) and server errors (5xx)
13
+ - Automatic retry on rate limits and server errors
14
+
15
+ This connector intentionally does not expose undocumented `/conversations`, `/tags`, `/automations`, `/canned-responses`, or `/webhooks` CRUD paths.
12
16
 
13
17
  ## Quick Start
14
18
 
@@ -16,30 +20,30 @@ TypeScript connector for the [Tidio OpenAPI](https://developers.tidio.com/refere
16
20
  cd connectors/tidio
17
21
  bun install
18
22
  bun run dev --help
19
- bun run dev config set-key YOUR_API_KEY
20
- bun run dev project get
23
+ bun run dev config set-credentials ci_your_client_id cs_your_client_secret
24
+ bun run dev project
21
25
  ```
22
26
 
23
27
  ## Environment Variables
24
28
 
25
29
  | Variable | Description |
26
30
  |----------|-------------|
27
- | `TIDIO_API_KEY` | OpenAPI key (overrides profile) |
31
+ | `TIDIO_CLIENT_ID` | OpenAPI client id, prefixed by `ci_` |
32
+ | `TIDIO_CLIENT_SECRET` | OpenAPI client secret, prefixed by `cs_` |
33
+
34
+ Environment variables override the active profile.
28
35
 
29
36
  ## CLI Commands
30
37
 
31
38
  ```bash
32
- connect-tidio contact list|get|create|update|delete
33
- connect-tidio conversation list|get|messages|send|status|assign
34
- connect-tidio operator list|get
35
- connect-tidio department list
36
- connect-tidio tag list|create|delete
37
- connect-tidio automation list
38
- connect-tidio canned-response list|create
39
- connect-tidio webhook list|create|delete
40
- connect-tidio project get
39
+ connect-tidio contact list|get|create|update|delete|properties|viewed-pages|messages|send-message
40
+ connect-tidio operator
41
+ connect-tidio department
42
+ connect-tidio project
43
+ connect-tidio ticket list|get|reply|tags|custom-fields
44
+ connect-tidio lyro sources|ask-ticket
41
45
  connect-tidio profile list|use|create|delete|show
42
- connect-tidio config set-key|show|clear
46
+ connect-tidio config set-credentials|show|clear
43
47
  ```
44
48
 
45
49
  ## Build
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hasna/connect-tidio",
3
3
  "version": "0.0.1",
4
- "description": "Tidio connector - Live chat support, contacts, conversations, and operators",
4
+ "description": "Tidio OpenAPI connector - contacts, contact messages, departments, operators, project, tickets, products, and Lyro",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "connect-tidio": "./bin/index.js"
@@ -25,6 +25,8 @@
25
25
  "live-chat",
26
26
  "support",
27
27
  "messaging",
28
+ "tickets",
29
+ "lyro",
28
30
  "connector",
29
31
  "cli",
30
32
  "typescript",
@@ -45,6 +47,6 @@
45
47
  },
46
48
  "repository": {
47
49
  "type": "git",
48
- "url": "git+https://github.com/hasna/connect-tidio.git"
50
+ "url": "git+https://github.com/hasna/connectors.git"
49
51
  }
50
52
  }