@hasna/connectors 1.3.45 → 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.
- package/README.md +25 -0
- package/bin/index.js +792 -130
- package/bin/mcp.js +438 -52
- package/bin/serve.js +438 -52
- 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/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/index.js +45 -2
- 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/package.json +3 -2
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { ZoomInfo } from "../api";
|
|
4
|
+
import type { HttpMethod, JsonObject, JsonValue, OutputFormat, QueryPrimitive, QueryValue } from "../types";
|
|
5
|
+
import {
|
|
6
|
+
clearConfig,
|
|
7
|
+
getBaseUrl,
|
|
8
|
+
getJwt,
|
|
9
|
+
getPassword,
|
|
10
|
+
getUsername,
|
|
11
|
+
loadConfig,
|
|
12
|
+
setBaseUrl,
|
|
13
|
+
setJwt,
|
|
14
|
+
setPassword,
|
|
15
|
+
setUsername,
|
|
16
|
+
} from "../utils/config";
|
|
17
|
+
import { error, info, print, success } from "../utils/output";
|
|
18
|
+
|
|
19
|
+
const CONNECTOR_NAME = "connect-zoominfo";
|
|
20
|
+
const VERSION = "0.0.1";
|
|
21
|
+
|
|
22
|
+
const program = new Command();
|
|
23
|
+
|
|
24
|
+
program
|
|
25
|
+
.name(CONNECTOR_NAME)
|
|
26
|
+
.description("ZoomInfo B2B sales intelligence API CLI")
|
|
27
|
+
.version(VERSION)
|
|
28
|
+
.option("-f, --format <format>", "Output format (json, pretty)", "pretty")
|
|
29
|
+
.option("--username <username>", "ZoomInfo API username")
|
|
30
|
+
.option("--password <password>", "ZoomInfo API password")
|
|
31
|
+
.option("--jwt <jwt>", "Preconfigured ZoomInfo JWT (skips /authenticate)")
|
|
32
|
+
.option("--base-url <url>", "Override ZoomInfo API base URL");
|
|
33
|
+
|
|
34
|
+
function getFormat(): OutputFormat {
|
|
35
|
+
return (program.opts().format || "pretty") as OutputFormat;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getCliOptions(): Record<string, unknown> {
|
|
39
|
+
return program.opts() as Record<string, unknown>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function client(): ZoomInfo {
|
|
43
|
+
const opts = getCliOptions();
|
|
44
|
+
return new ZoomInfo({
|
|
45
|
+
username: optionString(opts, "username") || getUsername(),
|
|
46
|
+
password: optionString(opts, "password") || getPassword(),
|
|
47
|
+
jwt: optionString(opts, "jwt") || getJwt(),
|
|
48
|
+
baseUrl: optionString(opts, "baseUrl") || getBaseUrl(),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function parseJsonValue(value: string | undefined, label: string, fallback: JsonValue | undefined): JsonValue | undefined {
|
|
53
|
+
if (!value) return fallback;
|
|
54
|
+
try {
|
|
55
|
+
return JSON.parse(value) as JsonValue;
|
|
56
|
+
} catch (err) {
|
|
57
|
+
throw new Error(`Invalid ${label}: ${err instanceof Error ? err.message : String(err)}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function parseJsonObject(value: string | undefined, label: string, fallback: JsonObject = {}): JsonObject {
|
|
62
|
+
const parsed = parseJsonValue(value, label, fallback);
|
|
63
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error(`${label} must be a JSON object`);
|
|
64
|
+
return parsed as JsonObject;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function toQueryValue(value: unknown, label: string): QueryValue {
|
|
68
|
+
if (value === undefined || value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
|
|
69
|
+
if (Array.isArray(value)) {
|
|
70
|
+
for (const item of value) {
|
|
71
|
+
if (typeof item !== "string" && typeof item !== "number" && typeof item !== "boolean") {
|
|
72
|
+
throw new Error(`Invalid ${label}: arrays must contain strings, numbers, or booleans`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return value as QueryPrimitive[];
|
|
76
|
+
}
|
|
77
|
+
throw new Error(`Invalid ${label}: expected string, number, boolean, array, null, or undefined`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function parseParams(value: unknown): Record<string, QueryValue> {
|
|
81
|
+
const parsed = parseJsonObject(typeof value === "string" ? value : undefined, "params");
|
|
82
|
+
const params: Record<string, QueryValue> = {};
|
|
83
|
+
for (const [key, rawValue] of Object.entries(parsed)) params[key] = toQueryValue(rawValue, `params.${key}`);
|
|
84
|
+
return params;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function parseHeaders(value: unknown): Record<string, string> {
|
|
88
|
+
const parsed = parseJsonObject(typeof value === "string" ? value : undefined, "headers");
|
|
89
|
+
const headers: Record<string, string> = {};
|
|
90
|
+
for (const [key, rawValue] of Object.entries(parsed)) {
|
|
91
|
+
if (typeof rawValue !== "string") throw new Error(`headers.${key} must be a string`);
|
|
92
|
+
headers[key] = rawValue;
|
|
93
|
+
}
|
|
94
|
+
return headers;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function optionString(opts: Record<string, unknown>, key: string): string | undefined {
|
|
98
|
+
const value = opts[key];
|
|
99
|
+
return typeof value === "string" ? value : undefined;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function optionBody(opts: Record<string, unknown>, fallback: JsonValue = {}): JsonValue {
|
|
103
|
+
return parseJsonValue(optionString(opts, "body"), "body", fallback) ?? fallback;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function run(action: () => Promise<unknown>): Promise<void> {
|
|
107
|
+
try {
|
|
108
|
+
print(await action(), getFormat());
|
|
109
|
+
} catch (err) {
|
|
110
|
+
error(err instanceof Error ? err.message : String(err));
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const configCmd = program.command("config").description("Manage CLI configuration");
|
|
116
|
+
|
|
117
|
+
configCmd.command("set-username <username>").description("Store a ZoomInfo API username").action((username: string) => {
|
|
118
|
+
setUsername(username);
|
|
119
|
+
success("Username saved");
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
configCmd.command("set-password <password>").description("Store a ZoomInfo API password").action((password: string) => {
|
|
123
|
+
setPassword(password);
|
|
124
|
+
success("Password saved");
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
configCmd.command("set-jwt <jwt>").description("Store a preconfigured ZoomInfo JWT").action((jwt: string) => {
|
|
128
|
+
setJwt(jwt);
|
|
129
|
+
success("JWT saved");
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
configCmd.command("set-base-url <baseUrl>").description("Set a custom ZoomInfo API base URL").action((baseUrl: string) => {
|
|
133
|
+
setBaseUrl(baseUrl);
|
|
134
|
+
success(`Base URL set to ${baseUrl}`);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
configCmd.command("show").description("Show current configuration").action(() => {
|
|
138
|
+
const api = client();
|
|
139
|
+
const config = loadConfig();
|
|
140
|
+
info(`Username: ${getUsername() ? "configured" : "not set"}`);
|
|
141
|
+
info(`Password: ${getPassword() ? "configured" : "not set"}`);
|
|
142
|
+
info(`JWT: ${getJwt() ? "configured" : "not set"}`);
|
|
143
|
+
info(`Base URL: ${api.getBaseUrl()}`);
|
|
144
|
+
if (Object.keys(config).length > 0) info("Stored config file is present");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
configCmd.command("clear").description("Clear saved configuration").action(() => {
|
|
148
|
+
clearConfig();
|
|
149
|
+
success("Configuration cleared");
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
program.command("authenticate").description("Authenticate with username/password and obtain a JWT").action(() => run(() => client().authenticate()));
|
|
153
|
+
program.command("search-contacts").description("Search ZoomInfo contacts").option("--body <json>", "JSON request body", "{}").option("--params <json>", "Query parameters as JSON object").action((opts: Record<string, unknown>) => run(() => client().searchContacts(optionBody(opts), parseParams(opts.params))));
|
|
154
|
+
program.command("search-companies").description("Search ZoomInfo companies").option("--body <json>", "JSON request body", "{}").option("--params <json>", "Query parameters as JSON object").action((opts: Record<string, unknown>) => run(() => client().searchCompanies(optionBody(opts), parseParams(opts.params))));
|
|
155
|
+
program.command("enrich-contact").description("Enrich contacts (requires matchPersonInput in body)").requiredOption("--body <json>", "JSON request body with matchPersonInput").action((opts: Record<string, unknown>) => run(() => client().enrichContact(optionBody(opts))));
|
|
156
|
+
program.command("enrich-company").description("Enrich companies").option("--body <json>", "JSON request body", "{}").action((opts: Record<string, unknown>) => run(() => client().enrichCompany(optionBody(opts))));
|
|
157
|
+
program.command("lookup-contact <contactId>").description("Look up a contact by ZoomInfo ID").action((contactId: string) => run(() => client().lookupContact(contactId)));
|
|
158
|
+
program.command("contact-search-output-fields").description("List available contact search output fields").action(() => run(() => client().listContactSearchOutputFields()));
|
|
159
|
+
program.command("company-search-output-fields").description("List available company search output fields").action(() => run(() => client().listCompanySearchOutputFields()));
|
|
160
|
+
|
|
161
|
+
program
|
|
162
|
+
.command("raw-request <method> <path>")
|
|
163
|
+
.description("Send a raw relative ZoomInfo request")
|
|
164
|
+
.option("--params <json>", "Query parameters as JSON object")
|
|
165
|
+
.option("--body <json>", "JSON request body")
|
|
166
|
+
.option("--headers <json>", "Additional headers as JSON object")
|
|
167
|
+
.option("--no-auth", "Do not attach Bearer JWT")
|
|
168
|
+
.action((method: string, path: string, opts: Record<string, unknown>) =>
|
|
169
|
+
run(() =>
|
|
170
|
+
client().rawRequest(method.toUpperCase() as HttpMethod, path, {
|
|
171
|
+
params: parseParams(opts.params),
|
|
172
|
+
body: parseJsonValue(optionString(opts, "body"), "body", undefined),
|
|
173
|
+
headers: parseHeaders(opts.headers),
|
|
174
|
+
auth: opts.auth !== false,
|
|
175
|
+
}),
|
|
176
|
+
),
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
program.parse();
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export type OutputFormat = "json" | "pretty";
|
|
2
|
+
export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
3
|
+
|
|
4
|
+
export type JsonPrimitive = string | number | boolean | null;
|
|
5
|
+
export type JsonValue = JsonPrimitive | JsonObject | JsonValue[];
|
|
6
|
+
|
|
7
|
+
export interface JsonObject {
|
|
8
|
+
[key: string]: JsonValue | undefined;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type QueryPrimitive = string | number | boolean;
|
|
12
|
+
export type QueryValue = QueryPrimitive | QueryPrimitive[] | null | undefined;
|
|
13
|
+
|
|
14
|
+
export interface ZoomInfoConfig {
|
|
15
|
+
username?: string;
|
|
16
|
+
password?: string;
|
|
17
|
+
jwt?: string;
|
|
18
|
+
baseUrl?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface RequestOptions {
|
|
22
|
+
method?: HttpMethod;
|
|
23
|
+
params?: Record<string, QueryValue>;
|
|
24
|
+
body?: JsonValue | string;
|
|
25
|
+
headers?: Record<string, string>;
|
|
26
|
+
responseType?: "json" | "text";
|
|
27
|
+
auth?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class ZoomInfoApiError extends Error {
|
|
31
|
+
constructor(
|
|
32
|
+
message: string,
|
|
33
|
+
public readonly status?: number,
|
|
34
|
+
public readonly data?: unknown,
|
|
35
|
+
) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.name = "ZoomInfoApiError";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
|
|
5
|
+
interface StoredConfig {
|
|
6
|
+
username?: string;
|
|
7
|
+
password?: string;
|
|
8
|
+
jwt?: string;
|
|
9
|
+
baseUrl?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const CONFIG_PATH = join(homedir(), ".hasna", "connectors", "zoominfo", "config.json");
|
|
13
|
+
|
|
14
|
+
export function loadConfig(): StoredConfig {
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(readFileSync(CONFIG_PATH, "utf8")) as StoredConfig;
|
|
17
|
+
} catch {
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function saveConfig(config: StoredConfig): void {
|
|
23
|
+
mkdirSync(dirname(CONFIG_PATH), { recursive: true });
|
|
24
|
+
writeFileSync(CONFIG_PATH, `${JSON.stringify(config, null, 2)}\n`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function clearConfig(): void {
|
|
28
|
+
rmSync(CONFIG_PATH, { force: true });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function getUsername(): string | undefined {
|
|
32
|
+
return process.env.ZOOMINFO_USERNAME || loadConfig().username;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function setUsername(username: string): void {
|
|
36
|
+
saveConfig({ ...loadConfig(), username });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function getPassword(): string | undefined {
|
|
40
|
+
return process.env.ZOOMINFO_PASSWORD || loadConfig().password;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function setPassword(password: string): void {
|
|
44
|
+
saveConfig({ ...loadConfig(), password });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function getJwt(): string | undefined {
|
|
48
|
+
return process.env.ZOOMINFO_JWT || loadConfig().jwt;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function setJwt(jwt: string): void {
|
|
52
|
+
saveConfig({ ...loadConfig(), jwt });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function getBaseUrl(): string | undefined {
|
|
56
|
+
return process.env.ZOOMINFO_BASE_URL || loadConfig().baseUrl;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function setBaseUrl(baseUrl: string): void {
|
|
60
|
+
saveConfig({ ...loadConfig(), baseUrl });
|
|
61
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { OutputFormat } from "../types";
|
|
2
|
+
|
|
3
|
+
export function print(value: unknown, format: OutputFormat = "pretty"): void {
|
|
4
|
+
if (format === "json") {
|
|
5
|
+
console.log(JSON.stringify(value, null, 2));
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if (typeof value === "string") {
|
|
10
|
+
console.log(value);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
console.log(JSON.stringify(value, null, 2));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function success(message: string): void {
|
|
18
|
+
console.log(message);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function info(message: string): void {
|
|
22
|
+
console.log(message);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function error(message: string): void {
|
|
26
|
+
console.error(message);
|
|
27
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"declarationMap": true,
|
|
8
|
+
"sourceMap": true,
|
|
9
|
+
"outDir": "./dist",
|
|
10
|
+
"rootDir": "./src",
|
|
11
|
+
"strict": true,
|
|
12
|
+
"esModuleInterop": true,
|
|
13
|
+
"skipLibCheck": true,
|
|
14
|
+
"forceConsistentCasingInFileNames": true,
|
|
15
|
+
"resolveJsonModule": true,
|
|
16
|
+
"types": ["bun-types"]
|
|
17
|
+
},
|
|
18
|
+
"include": ["src/**/*"],
|
|
19
|
+
"exclude": ["node_modules", "dist", "bin"]
|
|
20
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
Guidance for AI agents working with connect-zotero.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Zotero Web API v3 connector with API-key auth, multi-profile config, and CLI/library entry points.
|
|
8
|
+
|
|
9
|
+
## Quick Commands
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
bun install
|
|
13
|
+
bun run dev
|
|
14
|
+
bun run build
|
|
15
|
+
bun run typecheck
|
|
16
|
+
bun test
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Key Features
|
|
20
|
+
|
|
21
|
+
- Items: list, search, get, create, update, delete
|
|
22
|
+
- Collections: list, get, create
|
|
23
|
+
- Attachments: URL/file attachments and 3-step file upload
|
|
24
|
+
- Raw request escape hatch for unlisted endpoints
|
|
25
|
+
- Multi-profile config under `~/.hasna/connectors/connect-zotero/`
|
|
26
|
+
|
|
27
|
+
## Environment
|
|
28
|
+
|
|
29
|
+
| Variable | Description |
|
|
30
|
+
|----------|-------------|
|
|
31
|
+
| `ZOTERO_API_KEY` | API key |
|
|
32
|
+
| `ZOTERO_LIBRARY_ID` | Library ID |
|
|
33
|
+
| `ZOTERO_LIBRARY_TYPE` | `users` or `groups` |
|
|
34
|
+
| `ZOTERO_BASE_URL` | Optional API base URL |
|
|
35
|
+
|
|
36
|
+
## Security
|
|
37
|
+
|
|
38
|
+
- Never commit real API keys
|
|
39
|
+
- `.env.example` contains placeholders only
|
|
40
|
+
- No browser-use or scraper dependencies
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code when working with this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
connect-zotero is a TypeScript CLI and library for the Zotero Web API v3. It provides item, collection, and attachment management with multi-profile API key configuration.
|
|
8
|
+
|
|
9
|
+
## Build & Run Commands
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
bun install # Install dependencies
|
|
13
|
+
bun run dev # Run CLI in development
|
|
14
|
+
bun run build # Build for distribution
|
|
15
|
+
bun run typecheck # Type check
|
|
16
|
+
bun test # Run connector tests
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Code Style
|
|
20
|
+
|
|
21
|
+
- TypeScript with strict mode
|
|
22
|
+
- ESM modules (type: module)
|
|
23
|
+
- Async/await for all async operations
|
|
24
|
+
- Minimal dependencies: commander, chalk
|
|
25
|
+
|
|
26
|
+
## Project Structure
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
src/
|
|
30
|
+
├── api/
|
|
31
|
+
│ ├── client.ts # HTTP client with Zotero-API-Key auth
|
|
32
|
+
│ ├── items.ts # Items CRUD and search
|
|
33
|
+
│ ├── collections.ts # Collections list/create
|
|
34
|
+
│ ├── attachments.ts # Attachment create and file upload
|
|
35
|
+
│ └── index.ts # Main Zotero class
|
|
36
|
+
├── cli/
|
|
37
|
+
│ └── index.ts # CLI commands
|
|
38
|
+
├── types/
|
|
39
|
+
│ └── index.ts # TypeScript types
|
|
40
|
+
├── utils/
|
|
41
|
+
│ ├── config.ts # Multi-profile configuration
|
|
42
|
+
│ └── output.ts # CLI output formatting
|
|
43
|
+
└── index.ts # Library exports
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Environment Variables
|
|
47
|
+
|
|
48
|
+
| Variable | Description |
|
|
49
|
+
|----------|-------------|
|
|
50
|
+
| `ZOTERO_API_KEY` | Zotero API key (Zotero-API-Key header) |
|
|
51
|
+
| `ZOTERO_LIBRARY_ID` | User or group library ID |
|
|
52
|
+
| `ZOTERO_LIBRARY_TYPE` | `users` (default) or `groups` |
|
|
53
|
+
| `ZOTERO_BASE_URL` | Optional override (default https://api.zotero.org) |
|
|
54
|
+
|
|
55
|
+
## Authentication
|
|
56
|
+
|
|
57
|
+
Auth type: **apikey** (no OAuth). Config dir: `~/.hasna/connectors/connect-zotero/profiles/{name}/config.json`
|
|
58
|
+
|
|
59
|
+
## API Notes
|
|
60
|
+
|
|
61
|
+
- Base URL: https://api.zotero.org
|
|
62
|
+
- Required headers: `Zotero-API-Key`, `Zotero-API-Version: 3`
|
|
63
|
+
- Update/delete require `If-Unmodified-Since-Version` with the item version
|
|
64
|
+
- File upload uses a 3-step protocol: auth POST → S3 POST → register POST
|
|
65
|
+
|
|
66
|
+
## Dependencies
|
|
67
|
+
|
|
68
|
+
- commander: CLI framework
|
|
69
|
+
- chalk: Terminal styling
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# @hasna/connect-zotero
|
|
2
|
+
|
|
3
|
+
Zotero Web API v3 connector with CLI and library support. Manage bibliography items, collections, and file attachments via the official Zotero REST API.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add @hasna/connect-zotero
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
For CLI usage:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
bun install -g @hasna/connect-zotero
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Configuration
|
|
18
|
+
|
|
19
|
+
### Environment Variables
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
export ZOTERO_API_KEY="your-api-key"
|
|
23
|
+
export ZOTERO_LIBRARY_ID="your-library-id"
|
|
24
|
+
export ZOTERO_LIBRARY_TYPE="users" # or groups
|
|
25
|
+
# export ZOTERO_BASE_URL="https://api.zotero.org"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### CLI Configuration
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
connect-zotero config set-api-key <key>
|
|
32
|
+
connect-zotero config set-library-id <id>
|
|
33
|
+
connect-zotero config set-library-type users
|
|
34
|
+
connect-zotero config show
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Configuration is stored in `~/.hasna/connectors/connect-zotero/`.
|
|
38
|
+
|
|
39
|
+
## CLI Usage
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Test authentication
|
|
43
|
+
connect-zotero test
|
|
44
|
+
|
|
45
|
+
# List items
|
|
46
|
+
connect-zotero items list
|
|
47
|
+
connect-zotero items list --collection <key> --limit 25
|
|
48
|
+
|
|
49
|
+
# Search items
|
|
50
|
+
connect-zotero items search "machine learning"
|
|
51
|
+
|
|
52
|
+
# Get, create, update, delete items
|
|
53
|
+
connect-zotero items get <itemKey>
|
|
54
|
+
connect-zotero items create --json ./item.json
|
|
55
|
+
connect-zotero items update <itemKey> --json ./patch.json --version 12
|
|
56
|
+
connect-zotero items delete <itemKey> --version 12
|
|
57
|
+
|
|
58
|
+
# Collections
|
|
59
|
+
connect-zotero collections list
|
|
60
|
+
connect-zotero collections create --json ./collection.json
|
|
61
|
+
|
|
62
|
+
# Attachments
|
|
63
|
+
connect-zotero attachments create --json ./attachment.json
|
|
64
|
+
connect-zotero attachments upload --file ./paper.pdf --parent <itemKey>
|
|
65
|
+
|
|
66
|
+
# Raw API escape hatch
|
|
67
|
+
connect-zotero raw /users/1234567/items -X GET
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Library Usage
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
import { Zotero } from '@hasna/connect-zotero';
|
|
74
|
+
|
|
75
|
+
const zotero = new Zotero({
|
|
76
|
+
apiKey: process.env.ZOTERO_API_KEY!,
|
|
77
|
+
libraryId: process.env.ZOTERO_LIBRARY_ID!,
|
|
78
|
+
libraryType: 'users',
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const items = await zotero.items.list({ limit: 10 });
|
|
82
|
+
const results = await zotero.items.search('neural networks');
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## API Reference
|
|
86
|
+
|
|
87
|
+
- [Zotero Web API v3](https://www.zotero.org/support/dev/web_api/v3/start)
|
|
88
|
+
|
|
89
|
+
## License
|
|
90
|
+
|
|
91
|
+
Apache-2.0
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hasna/connect-zotero",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Zotero Web API v3 connector with CLI and library support",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"connect-zotero": "bin/index.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"dev": "bun run ./src/cli/index.ts",
|
|
13
|
+
"build": "bun build ./src/index.ts --outdir ./dist --target bun && bun build ./src/cli/index.ts --outdir ./bin --target bun",
|
|
14
|
+
"typecheck": "tsc --noEmit",
|
|
15
|
+
"test": "bun test",
|
|
16
|
+
"prepublishOnly": "bun run build"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"zotero",
|
|
20
|
+
"api",
|
|
21
|
+
"cli",
|
|
22
|
+
"connector",
|
|
23
|
+
"bibliography",
|
|
24
|
+
"citations"
|
|
25
|
+
],
|
|
26
|
+
"author": "Hasna",
|
|
27
|
+
"license": "Apache-2.0",
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"chalk": "^5.3.0",
|
|
30
|
+
"commander": "^12.1.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/bun": "latest",
|
|
34
|
+
"typescript": "^5.0.0"
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist",
|
|
38
|
+
"bin",
|
|
39
|
+
"src",
|
|
40
|
+
"README.md",
|
|
41
|
+
"CLAUDE.md"
|
|
42
|
+
]
|
|
43
|
+
}
|