@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.
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,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,3 @@
1
+ export { ZoomInfoClient, ZoomInfoClient as ZoomInfo } from "./api";
2
+ export * from "./api";
3
+ export * from "./types";
@@ -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,7 @@
1
+ # Zotero API Configuration
2
+ ZOTERO_API_KEY=your-zotero-api-key-here
3
+ ZOTERO_LIBRARY_ID=your-user-or-group-library-id
4
+ ZOTERO_LIBRARY_TYPE=users
5
+
6
+ # Optional: Override the default API base URL
7
+ # ZOTERO_BASE_URL=https://api.zotero.org
@@ -0,0 +1,2 @@
1
+ @hasna:registry=https://registry.npmjs.org/
2
+ //registry.npmjs.org/:_authToken=${NPM_TOKEN}
@@ -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
+ }