@hasna/connectors 1.2.1 → 1.3.1

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/bin/serve.js CHANGED
@@ -135,6 +135,22 @@ function migrate(db) {
135
135
  created_at TEXT NOT NULL
136
136
  )
137
137
  `);
138
+ db.run(`
139
+ CREATE TABLE IF NOT EXISTS connector_usage (
140
+ id TEXT PRIMARY KEY,
141
+ connector TEXT NOT NULL,
142
+ action TEXT NOT NULL,
143
+ agent_id TEXT,
144
+ timestamp TEXT NOT NULL
145
+ )
146
+ `);
147
+ db.run(`CREATE INDEX IF NOT EXISTS idx_usage_connector ON connector_usage(connector, timestamp DESC)`);
148
+ db.run(`
149
+ CREATE TABLE IF NOT EXISTS connector_promotions (
150
+ connector TEXT UNIQUE NOT NULL,
151
+ promoted_at TEXT NOT NULL
152
+ )
153
+ `);
138
154
  }
139
155
  var DB_DIR, DB_PATH, _db = null;
140
156
  var init_database = __esm(() => {
@@ -483,6 +499,75 @@ var init_scheduler = __esm(() => {
483
499
  init_strip();
484
500
  });
485
501
 
502
+ // src/db/usage.ts
503
+ var exports_usage = {};
504
+ __export(exports_usage, {
505
+ logUsage: () => logUsage,
506
+ getUsageStats: () => getUsageStats,
507
+ getUsageMap: () => getUsageMap,
508
+ getTopConnectors: () => getTopConnectors,
509
+ cleanOldUsage: () => cleanOldUsage
510
+ });
511
+ function logUsage(connector, action, agentId, db) {
512
+ const d = db ?? getDatabase();
513
+ d.run("INSERT INTO connector_usage (id, connector, action, agent_id, timestamp) VALUES (?, ?, ?, ?, ?)", [shortUuid(), connector, action, agentId ?? null, now()]);
514
+ }
515
+ function getUsageStats(connector, db) {
516
+ const d = db ?? getDatabase();
517
+ const total = d.query("SELECT COUNT(*) as c FROM connector_usage WHERE connector = ?").get(connector).c;
518
+ const d7 = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
519
+ const last7d = d.query("SELECT COUNT(*) as c FROM connector_usage WHERE connector = ? AND timestamp > ?").get(connector, d7).c;
520
+ const d1 = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
521
+ const last24h = d.query("SELECT COUNT(*) as c FROM connector_usage WHERE connector = ? AND timestamp > ?").get(connector, d1).c;
522
+ return { connector, total, last7d, last24h };
523
+ }
524
+ function getTopConnectors(limit = 10, days = 7, db) {
525
+ const d = db ?? getDatabase();
526
+ const since = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
527
+ return d.query("SELECT connector, COUNT(*) as count FROM connector_usage WHERE timestamp > ? GROUP BY connector ORDER BY count DESC LIMIT ?").all(since, limit);
528
+ }
529
+ function getUsageMap(days = 7, db) {
530
+ const top = getTopConnectors(100, days, db);
531
+ return new Map(top.map((t) => [t.connector, t.count]));
532
+ }
533
+ function cleanOldUsage(days = 30, db) {
534
+ const d = db ?? getDatabase();
535
+ const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
536
+ return d.run("DELETE FROM connector_usage WHERE timestamp < ?", [cutoff]).changes;
537
+ }
538
+ var init_usage = __esm(() => {
539
+ init_database();
540
+ });
541
+
542
+ // src/db/promotions.ts
543
+ var exports_promotions = {};
544
+ __export(exports_promotions, {
545
+ promoteConnector: () => promoteConnector,
546
+ isPromoted: () => isPromoted,
547
+ getPromotedConnectors: () => getPromotedConnectors,
548
+ demoteConnector: () => demoteConnector
549
+ });
550
+ function promoteConnector(name, db) {
551
+ const d = db ?? getDatabase();
552
+ d.run("INSERT OR REPLACE INTO connector_promotions (connector, promoted_at) VALUES (?, ?)", [name, now()]);
553
+ }
554
+ function demoteConnector(name, db) {
555
+ const d = db ?? getDatabase();
556
+ return d.run("DELETE FROM connector_promotions WHERE connector = ?", [name]).changes > 0;
557
+ }
558
+ function getPromotedConnectors(db) {
559
+ const d = db ?? getDatabase();
560
+ return d.query("SELECT connector FROM connector_promotions ORDER BY promoted_at DESC").all().map((r) => r.connector);
561
+ }
562
+ function isPromoted(name, db) {
563
+ const d = db ?? getDatabase();
564
+ const row = d.query("SELECT 1 FROM connector_promotions WHERE connector = ?").get(name);
565
+ return !!row;
566
+ }
567
+ var init_promotions = __esm(() => {
568
+ init_database();
569
+ });
570
+
486
571
  // src/server/serve.ts
487
572
  import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync4, mkdirSync as mkdirSync6 } from "fs";
488
573
 
@@ -6982,6 +7067,16 @@ function loadTokens(name) {
6982
7067
  return null;
6983
7068
  }
6984
7069
  }
7070
+ const profileConfig = loadProfileConfig(name);
7071
+ if (profileConfig.refreshToken || profileConfig.accessToken) {
7072
+ return {
7073
+ accessToken: profileConfig.accessToken,
7074
+ refreshToken: profileConfig.refreshToken,
7075
+ expiresAt: profileConfig.expiresAt,
7076
+ tokenType: profileConfig.tokenType,
7077
+ scope: profileConfig.scope
7078
+ };
7079
+ }
6985
7080
  return null;
6986
7081
  }
6987
7082
  function getAuthStatus(name) {
@@ -7560,6 +7655,30 @@ Dashboard not found at: ${dashboardDir}`);
7560
7655
  if (path === "/api/activity" && method === "GET") {
7561
7656
  return json(activityLog, 200, port);
7562
7657
  }
7658
+ if (path === "/api/hot" && method === "GET") {
7659
+ const { getTopConnectors: getTopConnectors2 } = await Promise.resolve().then(() => (init_usage(), exports_usage));
7660
+ const { getPromotedConnectors: getPromotedConnectors2 } = await Promise.resolve().then(() => (init_promotions(), exports_promotions));
7661
+ const limit = parseInt(url2.searchParams.get("limit") || "10", 10);
7662
+ const days = parseInt(url2.searchParams.get("days") || "7", 10);
7663
+ const db = getDatabase2();
7664
+ const top = getTopConnectors2(limit, days, db);
7665
+ const promoted = new Set(getPromotedConnectors2(db));
7666
+ return json(top.map((t) => ({ ...t, promoted: promoted.has(t.connector) })), 200, port);
7667
+ }
7668
+ const promoteMatch = path.match(/^\/api\/connectors\/([^/]+)\/promote$/);
7669
+ if (promoteMatch && method === "POST") {
7670
+ const name = promoteMatch[1];
7671
+ if (!getConnector(name))
7672
+ return json({ error: "Connector not found" }, 404, port);
7673
+ const { promoteConnector: promoteConnector2 } = await Promise.resolve().then(() => (init_promotions(), exports_promotions));
7674
+ promoteConnector2(name, getDatabase2());
7675
+ return json({ success: true, connector: name }, 200, port);
7676
+ }
7677
+ if (promoteMatch && method === "DELETE") {
7678
+ const { demoteConnector: demoteConnector2 } = await Promise.resolve().then(() => (init_promotions(), exports_promotions));
7679
+ const removed = demoteConnector2(promoteMatch[1], getDatabase2());
7680
+ return json({ success: removed, connector: promoteMatch[1] }, 200, port);
7681
+ }
7563
7682
  if (path === "/api/llm" && method === "GET") {
7564
7683
  const config = getLlmConfig();
7565
7684
  if (!config)
@@ -124,6 +124,11 @@ export class HuggingFaceClient {
124
124
  return this.request<T>(path, { method: 'DELETE', params });
125
125
  }
126
126
 
127
+ /** Get the raw API key (needed by InferenceApi for direct fetch) */
128
+ getApiKey(): string {
129
+ return this.apiKey;
130
+ }
131
+
127
132
  /**
128
133
  * Get a preview of the API key (for display/debugging)
129
134
  */
@@ -0,0 +1,73 @@
1
+ import type { HuggingFaceClient } from './client';
2
+
3
+ export interface DatasetSearchOptions {
4
+ search?: string;
5
+ author?: string;
6
+ filter?: string;
7
+ sort?: 'likes' | 'downloads' | 'trending' | 'lastModified';
8
+ direction?: 'asc' | 'desc';
9
+ limit?: number;
10
+ full?: boolean;
11
+ }
12
+
13
+ export interface DatasetInfo {
14
+ _id: string;
15
+ id: string;
16
+ author?: string;
17
+ sha?: string;
18
+ lastModified?: string;
19
+ private?: boolean;
20
+ gated?: boolean | string;
21
+ tags?: string[];
22
+ downloads?: number;
23
+ likes?: number;
24
+ description?: string;
25
+ citation?: string;
26
+ cardData?: Record<string, unknown>;
27
+ [key: string]: unknown;
28
+ }
29
+
30
+ export interface DatasetSplit {
31
+ dataset: string;
32
+ config: string;
33
+ split: string;
34
+ num_rows: number;
35
+ num_bytes: number;
36
+ }
37
+
38
+ export class DatasetsApi {
39
+ constructor(private readonly client: HuggingFaceClient) {}
40
+
41
+ /** Search/list datasets */
42
+ async search(options: DatasetSearchOptions = {}): Promise<DatasetInfo[]> {
43
+ const params: Record<string, string | number | boolean | undefined> = {};
44
+ if (options.search) params.search = options.search;
45
+ if (options.author) params.author = options.author;
46
+ if (options.filter) params.filter = options.filter;
47
+ if (options.sort) params.sort = options.sort;
48
+ if (options.direction) params.direction = options.direction === 'desc' ? '-1' : '1';
49
+ if (options.limit) params.limit = options.limit;
50
+ if (options.full) params.full = true;
51
+
52
+ return this.client.request<DatasetInfo[]>('/datasets', { params });
53
+ }
54
+
55
+ /** Get a single dataset by ID */
56
+ async get(datasetId: string): Promise<DatasetInfo> {
57
+ return this.client.request<DatasetInfo>(`/datasets/${datasetId}`);
58
+ }
59
+
60
+ /** Preview first N rows of a dataset split */
61
+ async preview(datasetId: string, config = 'default', split = 'train', rows = 10): Promise<unknown> {
62
+ // Uses the datasets-server API
63
+ const url = `https://datasets-server.huggingface.co/first-rows?dataset=${encodeURIComponent(datasetId)}&config=${config}&split=${split}&rows=${rows}`;
64
+ const response = await fetch(url, {
65
+ headers: { Authorization: `Bearer ${this.client.getApiKey()}` },
66
+ });
67
+ if (!response.ok) {
68
+ const text = await response.text();
69
+ throw new Error(`Dataset preview failed (${response.status}): ${text}`);
70
+ }
71
+ return response.json();
72
+ }
73
+ }
@@ -1,6 +1,9 @@
1
1
  import type { HuggingFaceConfig } from '../types';
2
2
  import { HuggingFaceClient } from './client';
3
- import { ExampleApi } from './example';
3
+ import { ModelsApi } from './models';
4
+ import { InferenceApi } from './inference';
5
+ import { DatasetsApi } from './datasets';
6
+ import { SpacesApi } from './spaces';
4
7
 
5
8
  /**
6
9
  * Main HuggingFace API class
@@ -8,42 +11,37 @@ import { ExampleApi } from './example';
8
11
  export class HuggingFace {
9
12
  private readonly client: HuggingFaceClient;
10
13
 
11
- // API modules - add more as needed
12
- public readonly example: ExampleApi;
14
+ public readonly models: ModelsApi;
15
+ public readonly inference: InferenceApi;
16
+ public readonly datasets: DatasetsApi;
17
+ public readonly spaces: SpacesApi;
13
18
 
14
19
  constructor(config: HuggingFaceConfig) {
15
20
  this.client = new HuggingFaceClient(config);
16
- this.example = new ExampleApi(this.client);
21
+ this.models = new ModelsApi(this.client);
22
+ this.inference = new InferenceApi(this.client);
23
+ this.datasets = new DatasetsApi(this.client);
24
+ this.spaces = new SpacesApi(this.client);
17
25
  }
18
26
 
19
- /**
20
- * Create a client from environment variables
21
- * Looks for HUGGINGFACE_API_KEY or HF_TOKEN
22
- */
23
27
  static fromEnv(): HuggingFace {
24
28
  const apiKey = process.env.HUGGINGFACE_API_KEY || process.env.HF_TOKEN;
25
29
  const apiSecret = process.env.HUGGINGFACE_API_SECRET;
26
-
27
- if (!apiKey) {
28
- throw new Error('HUGGINGFACE_API_KEY or HF_TOKEN environment variable is required');
29
- }
30
+ if (!apiKey) throw new Error('HUGGINGFACE_API_KEY or HF_TOKEN environment variable is required');
30
31
  return new HuggingFace({ apiKey, apiSecret });
31
32
  }
32
33
 
33
- /**
34
- * Get a preview of the API key (for debugging)
35
- */
36
34
  getApiKeyPreview(): string {
37
35
  return this.client.getApiKeyPreview();
38
36
  }
39
37
 
40
- /**
41
- * Get the underlying client for direct API access
42
- */
43
38
  getClient(): HuggingFaceClient {
44
39
  return this.client;
45
40
  }
46
41
  }
47
42
 
48
43
  export { HuggingFaceClient } from './client';
49
- export { ExampleApi } from './example';
44
+ export { ModelsApi } from './models';
45
+ export { InferenceApi } from './inference';
46
+ export { DatasetsApi } from './datasets';
47
+ export { SpacesApi } from './spaces';
@@ -0,0 +1,100 @@
1
+ import type { HuggingFaceClient } from './client';
2
+
3
+ const INFERENCE_URL = 'https://api-inference.huggingface.co/models';
4
+
5
+ export interface TextGenerationOptions {
6
+ max_new_tokens?: number;
7
+ temperature?: number;
8
+ top_p?: number;
9
+ top_k?: number;
10
+ repetition_penalty?: number;
11
+ return_full_text?: boolean;
12
+ stop?: string[];
13
+ }
14
+
15
+ export interface TextGenerationResult {
16
+ generated_text: string;
17
+ }
18
+
19
+ export interface ChatMessage {
20
+ role: 'system' | 'user' | 'assistant';
21
+ content: string;
22
+ }
23
+
24
+ export interface ChatCompletionResult {
25
+ choices: Array<{
26
+ message: { role: string; content: string };
27
+ finish_reason: string;
28
+ index: number;
29
+ }>;
30
+ model: string;
31
+ usage?: { prompt_tokens: number; completion_tokens: number; total_tokens: number };
32
+ }
33
+
34
+ export class InferenceApi {
35
+ constructor(private readonly client: HuggingFaceClient) {}
36
+
37
+ /** Run text generation inference */
38
+ async textGeneration(
39
+ model: string,
40
+ prompt: string,
41
+ options: TextGenerationOptions = {}
42
+ ): Promise<TextGenerationResult[]> {
43
+ const response = await fetch(`${INFERENCE_URL}/${model}`, {
44
+ method: 'POST',
45
+ headers: {
46
+ Authorization: `Bearer ${this.client.getApiKey()}`,
47
+ 'Content-Type': 'application/json',
48
+ },
49
+ body: JSON.stringify({
50
+ inputs: prompt,
51
+ parameters: {
52
+ max_new_tokens: options.max_new_tokens ?? 256,
53
+ temperature: options.temperature,
54
+ top_p: options.top_p,
55
+ top_k: options.top_k,
56
+ repetition_penalty: options.repetition_penalty,
57
+ return_full_text: options.return_full_text ?? false,
58
+ stop: options.stop,
59
+ },
60
+ }),
61
+ });
62
+
63
+ if (!response.ok) {
64
+ const text = await response.text();
65
+ throw new Error(`Inference failed (${response.status}): ${text}`);
66
+ }
67
+
68
+ return response.json();
69
+ }
70
+
71
+ /** Chat completion via HF Inference API (for chat models) */
72
+ async chat(
73
+ model: string,
74
+ messages: ChatMessage[],
75
+ options: Omit<TextGenerationOptions, 'return_full_text'> = {}
76
+ ): Promise<ChatCompletionResult> {
77
+ const response = await fetch(`${INFERENCE_URL}/${model}/v1/chat/completions`, {
78
+ method: 'POST',
79
+ headers: {
80
+ Authorization: `Bearer ${this.client.getApiKey()}`,
81
+ 'Content-Type': 'application/json',
82
+ },
83
+ body: JSON.stringify({
84
+ model,
85
+ messages,
86
+ max_tokens: options.max_new_tokens ?? 256,
87
+ temperature: options.temperature,
88
+ top_p: options.top_p,
89
+ stop: options.stop,
90
+ }),
91
+ });
92
+
93
+ if (!response.ok) {
94
+ const text = await response.text();
95
+ throw new Error(`Chat inference failed (${response.status}): ${text}`);
96
+ }
97
+
98
+ return response.json();
99
+ }
100
+ }
@@ -0,0 +1,66 @@
1
+ import type { HuggingFaceClient } from './client';
2
+
3
+ export interface ModelSearchOptions {
4
+ search?: string;
5
+ author?: string;
6
+ filter?: string; // task filter: text-generation, text2text-generation, etc
7
+ library?: string; // transformers, gguf, pytorch, etc
8
+ sort?: 'likes' | 'downloads' | 'trending' | 'lastModified';
9
+ direction?: 'asc' | 'desc';
10
+ limit?: number;
11
+ full?: boolean; // include all fields
12
+ }
13
+
14
+ export interface ModelInfo {
15
+ _id: string;
16
+ id: string; // e.g. "meta-llama/Meta-Llama-3-8B"
17
+ modelId: string;
18
+ author?: string;
19
+ sha?: string;
20
+ lastModified?: string;
21
+ private?: boolean;
22
+ disabled?: boolean;
23
+ gated?: boolean | string;
24
+ pipeline_tag?: string;
25
+ tags?: string[];
26
+ downloads?: number;
27
+ likes?: number;
28
+ library_name?: string;
29
+ [key: string]: unknown;
30
+ }
31
+
32
+ export interface ModelFile {
33
+ rfilename: string;
34
+ size?: number;
35
+ blobId?: string;
36
+ lfs?: { size: number; sha256: string; pointerSize: number };
37
+ }
38
+
39
+ export class ModelsApi {
40
+ constructor(private readonly client: HuggingFaceClient) {}
41
+
42
+ /** Search/list models */
43
+ async search(options: ModelSearchOptions = {}): Promise<ModelInfo[]> {
44
+ const params: Record<string, string | number | boolean | undefined> = {};
45
+ if (options.search) params.search = options.search;
46
+ if (options.author) params.author = options.author;
47
+ if (options.filter) params.filter = options.filter;
48
+ if (options.library) params.library = options.library;
49
+ if (options.sort) params.sort = options.sort;
50
+ if (options.direction) params.direction = options.direction === 'desc' ? '-1' : '1';
51
+ if (options.limit) params.limit = options.limit;
52
+ if (options.full) params.full = true;
53
+
54
+ return this.client.request<ModelInfo[]>('/models', { params });
55
+ }
56
+
57
+ /** Get a single model by ID (e.g. "meta-llama/Meta-Llama-3-8B") */
58
+ async get(modelId: string): Promise<ModelInfo> {
59
+ return this.client.request<ModelInfo>(`/models/${modelId}`);
60
+ }
61
+
62
+ /** List files in a model repo */
63
+ async files(modelId: string): Promise<ModelFile[]> {
64
+ return this.client.request<ModelFile[]>(`/models/${modelId}/tree/main`);
65
+ }
66
+ }
@@ -0,0 +1,42 @@
1
+ import type { HuggingFaceClient } from './client';
2
+
3
+ export interface SpaceSearchOptions {
4
+ search?: string;
5
+ author?: string;
6
+ sort?: 'likes' | 'trending' | 'lastModified';
7
+ direction?: 'asc' | 'desc';
8
+ limit?: number;
9
+ }
10
+
11
+ export interface SpaceInfo {
12
+ _id: string;
13
+ id: string;
14
+ author?: string;
15
+ sha?: string;
16
+ lastModified?: string;
17
+ private?: boolean;
18
+ tags?: string[];
19
+ likes?: number;
20
+ sdk?: string;
21
+ runtime?: { stage: string; hardware?: { current?: string } };
22
+ [key: string]: unknown;
23
+ }
24
+
25
+ export class SpacesApi {
26
+ constructor(private readonly client: HuggingFaceClient) {}
27
+
28
+ async search(options: SpaceSearchOptions = {}): Promise<SpaceInfo[]> {
29
+ const params: Record<string, string | number | boolean | undefined> = {};
30
+ if (options.search) params.search = options.search;
31
+ if (options.author) params.author = options.author;
32
+ if (options.sort) params.sort = options.sort;
33
+ if (options.direction) params.direction = options.direction === 'desc' ? '-1' : '1';
34
+ if (options.limit) params.limit = options.limit;
35
+
36
+ return this.client.request<SpaceInfo[]>('/spaces', { params });
37
+ }
38
+
39
+ async get(spaceId: string): Promise<SpaceInfo> {
40
+ return this.client.request<SpaceInfo>(`/spaces/${spaceId}`);
41
+ }
42
+ }