@hasna/connectors 0.4.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/index.js +113 -1
- package/bin/mcp.js +113 -1
- package/bin/serve.js +112 -0
- package/connectors/connect-assemblyai/.env.example +11 -0
- package/connectors/connect-assemblyai/CLAUDE.md +128 -0
- package/connectors/connect-assemblyai/README.md +193 -0
- package/connectors/connect-assemblyai/package.json +50 -0
- package/connectors/connect-assemblyai/src/api/client.ts +192 -0
- package/connectors/connect-assemblyai/src/api/index.ts +71 -0
- package/connectors/connect-assemblyai/src/cli/index.ts +384 -0
- package/connectors/connect-assemblyai/src/index.ts +19 -0
- package/connectors/connect-assemblyai/src/types/index.ts +277 -0
- package/connectors/connect-assemblyai/src/utils/config.ts +103 -0
- package/connectors/connect-assemblyai/src/utils/output.ts +119 -0
- package/connectors/connect-assemblyai/tsconfig.json +16 -0
- package/connectors/connect-baseten/.env.example +11 -0
- package/connectors/connect-baseten/CLAUDE.md +128 -0
- package/connectors/connect-baseten/README.md +193 -0
- package/connectors/connect-baseten/package.json +51 -0
- package/connectors/connect-baseten/src/api/client.ts +71 -0
- package/connectors/connect-baseten/src/api/index.ts +40 -0
- package/connectors/connect-baseten/src/cli/index.ts +244 -0
- package/connectors/connect-baseten/src/index.ts +19 -0
- package/connectors/connect-baseten/src/types/index.ts +55 -0
- package/connectors/connect-baseten/src/utils/config.ts +103 -0
- package/connectors/connect-baseten/src/utils/output.ts +119 -0
- package/connectors/connect-baseten/tsconfig.json +16 -0
- package/connectors/connect-cerebras/.env.example +11 -0
- package/connectors/connect-cerebras/CLAUDE.md +128 -0
- package/connectors/connect-cerebras/README.md +193 -0
- package/connectors/connect-cerebras/package.json +51 -0
- package/connectors/connect-cerebras/src/api/client.ts +64 -0
- package/connectors/connect-cerebras/src/api/index.ts +32 -0
- package/connectors/connect-cerebras/src/cli/index.ts +244 -0
- package/connectors/connect-cerebras/src/index.ts +19 -0
- package/connectors/connect-cerebras/src/types/index.ts +65 -0
- package/connectors/connect-cerebras/src/utils/config.ts +103 -0
- package/connectors/connect-cerebras/src/utils/output.ts +119 -0
- package/connectors/connect-cerebras/tsconfig.json +16 -0
- package/connectors/connect-cohere/.env.example +11 -0
- package/connectors/connect-cohere/CLAUDE.md +128 -0
- package/connectors/connect-cohere/README.md +193 -0
- package/connectors/connect-cohere/package.json +53 -0
- package/connectors/connect-cohere/src/api/client.ts +109 -0
- package/connectors/connect-cohere/src/api/index.ts +59 -0
- package/connectors/connect-cohere/src/cli/index.ts +255 -0
- package/connectors/connect-cohere/src/index.ts +19 -0
- package/connectors/connect-cohere/src/types/index.ts +132 -0
- package/connectors/connect-cohere/src/utils/config.ts +197 -0
- package/connectors/connect-cohere/src/utils/output.ts +119 -0
- package/connectors/connect-cohere/tsconfig.json +16 -0
- package/connectors/connect-deepgram/.env.example +11 -0
- package/connectors/connect-deepgram/CLAUDE.md +128 -0
- package/connectors/connect-deepgram/README.md +193 -0
- package/connectors/connect-deepgram/package.json +51 -0
- package/connectors/connect-deepgram/src/api/client.ts +235 -0
- package/connectors/connect-deepgram/src/api/index.ts +57 -0
- package/connectors/connect-deepgram/src/cli/index.ts +339 -0
- package/connectors/connect-deepgram/src/index.ts +19 -0
- package/connectors/connect-deepgram/src/types/index.ts +232 -0
- package/connectors/connect-deepgram/src/utils/config.ts +103 -0
- package/connectors/connect-deepgram/src/utils/output.ts +119 -0
- package/connectors/connect-deepgram/tsconfig.json +16 -0
- package/connectors/connect-deepseek/.env.example +11 -0
- package/connectors/connect-deepseek/CLAUDE.md +128 -0
- package/connectors/connect-deepseek/README.md +193 -0
- package/connectors/connect-deepseek/package.json +51 -0
- package/connectors/connect-deepseek/src/api/client.ts +108 -0
- package/connectors/connect-deepseek/src/api/index.ts +36 -0
- package/connectors/connect-deepseek/src/cli/index.ts +167 -0
- package/connectors/connect-deepseek/src/index.ts +19 -0
- package/connectors/connect-deepseek/src/types/index.ts +72 -0
- package/connectors/connect-deepseek/src/utils/config.ts +103 -0
- package/connectors/connect-deepseek/src/utils/output.ts +119 -0
- package/connectors/connect-deepseek/tsconfig.json +16 -0
- package/connectors/connect-fal/.env.example +11 -0
- package/connectors/connect-fal/CLAUDE.md +128 -0
- package/connectors/connect-fal/README.md +193 -0
- package/connectors/connect-fal/package.json +51 -0
- package/connectors/connect-fal/src/api/client.ts +172 -0
- package/connectors/connect-fal/src/api/index.ts +55 -0
- package/connectors/connect-fal/src/cli/index.ts +341 -0
- package/connectors/connect-fal/src/index.ts +19 -0
- package/connectors/connect-fal/src/types/index.ts +135 -0
- package/connectors/connect-fal/src/utils/config.ts +103 -0
- package/connectors/connect-fal/src/utils/output.ts +119 -0
- package/connectors/connect-fal/tsconfig.json +16 -0
- package/connectors/connect-fireworks/.env.example +11 -0
- package/connectors/connect-fireworks/CLAUDE.md +128 -0
- package/connectors/connect-fireworks/README.md +193 -0
- package/connectors/connect-fireworks/package.json +51 -0
- package/connectors/connect-fireworks/src/api/client.ts +63 -0
- package/connectors/connect-fireworks/src/api/index.ts +36 -0
- package/connectors/connect-fireworks/src/cli/index.ts +244 -0
- package/connectors/connect-fireworks/src/index.ts +19 -0
- package/connectors/connect-fireworks/src/types/index.ts +70 -0
- package/connectors/connect-fireworks/src/utils/config.ts +103 -0
- package/connectors/connect-fireworks/src/utils/output.ts +119 -0
- package/connectors/connect-fireworks/tsconfig.json +16 -0
- package/connectors/connect-groq/.env.example +11 -0
- package/connectors/connect-groq/CLAUDE.md +128 -0
- package/connectors/connect-groq/README.md +193 -0
- package/connectors/connect-groq/package.json +52 -0
- package/connectors/connect-groq/src/api/client.ts +108 -0
- package/connectors/connect-groq/src/api/index.ts +36 -0
- package/connectors/connect-groq/src/cli/index.ts +171 -0
- package/connectors/connect-groq/src/index.ts +19 -0
- package/connectors/connect-groq/src/types/index.ts +69 -0
- package/connectors/connect-groq/src/utils/config.ts +103 -0
- package/connectors/connect-groq/src/utils/output.ts +119 -0
- package/connectors/connect-groq/tsconfig.json +16 -0
- package/connectors/connect-luma/.env.example +11 -0
- package/connectors/connect-luma/CLAUDE.md +128 -0
- package/connectors/connect-luma/README.md +193 -0
- package/connectors/connect-luma/package.json +53 -0
- package/connectors/connect-luma/src/api/client.ts +85 -0
- package/connectors/connect-luma/src/api/index.ts +44 -0
- package/connectors/connect-luma/src/cli/index.ts +300 -0
- package/connectors/connect-luma/src/index.ts +19 -0
- package/connectors/connect-luma/src/types/index.ts +60 -0
- package/connectors/connect-luma/src/utils/config.ts +103 -0
- package/connectors/connect-luma/src/utils/output.ts +119 -0
- package/connectors/connect-luma/tsconfig.json +16 -0
- package/connectors/connect-modal/.env.example +11 -0
- package/connectors/connect-modal/CLAUDE.md +128 -0
- package/connectors/connect-modal/README.md +193 -0
- package/connectors/connect-modal/package.json +51 -0
- package/connectors/connect-modal/src/api/client.ts +119 -0
- package/connectors/connect-modal/src/api/index.ts +69 -0
- package/connectors/connect-modal/src/cli/index.ts +224 -0
- package/connectors/connect-modal/src/index.ts +21 -0
- package/connectors/connect-modal/src/types/index.ts +60 -0
- package/connectors/connect-modal/src/utils/config.ts +114 -0
- package/connectors/connect-modal/src/utils/output.ts +119 -0
- package/connectors/connect-modal/tsconfig.json +16 -0
- package/connectors/connect-perplexity/.env.example +4 -0
- package/connectors/connect-perplexity/CLAUDE.md +156 -0
- package/connectors/connect-perplexity/README.md +184 -0
- package/connectors/connect-perplexity/package.json +58 -0
- package/connectors/connect-perplexity/scripts/publish.ts +210 -0
- package/connectors/connect-perplexity/src/api/client.ts +119 -0
- package/connectors/connect-perplexity/src/api/example.ts +118 -0
- package/connectors/connect-perplexity/src/api/index.ts +48 -0
- package/connectors/connect-perplexity/src/cli/index.ts +421 -0
- package/connectors/connect-perplexity/src/index.ts +24 -0
- package/connectors/connect-perplexity/src/types/index.ts +140 -0
- package/connectors/connect-perplexity/src/utils/config.ts +208 -0
- package/connectors/connect-perplexity/src/utils/output.ts +119 -0
- package/connectors/connect-perplexity/tsconfig.json +16 -0
- package/connectors/connect-replicate/.env.example +11 -0
- package/connectors/connect-replicate/CLAUDE.md +128 -0
- package/connectors/connect-replicate/README.md +193 -0
- package/connectors/connect-replicate/package.json +51 -0
- package/connectors/connect-replicate/src/api/client.ts +109 -0
- package/connectors/connect-replicate/src/api/index.ts +71 -0
- package/connectors/connect-replicate/src/cli/index.ts +250 -0
- package/connectors/connect-replicate/src/index.ts +19 -0
- package/connectors/connect-replicate/src/types/index.ts +85 -0
- package/connectors/connect-replicate/src/utils/config.ts +103 -0
- package/connectors/connect-replicate/src/utils/output.ts +119 -0
- package/connectors/connect-replicate/tsconfig.json +16 -0
- package/connectors/connect-roboflow/.env.example +11 -0
- package/connectors/connect-roboflow/CLAUDE.md +272 -0
- package/connectors/connect-roboflow/README.md +193 -0
- package/connectors/connect-roboflow/package.json +51 -0
- package/connectors/connect-roboflow/scripts/release.ts +179 -0
- package/connectors/connect-roboflow/src/api/client.ts +213 -0
- package/connectors/connect-roboflow/src/api/example.ts +48 -0
- package/connectors/connect-roboflow/src/api/index.ts +51 -0
- package/connectors/connect-roboflow/src/cli/index.ts +254 -0
- package/connectors/connect-roboflow/src/index.ts +103 -0
- package/connectors/connect-roboflow/src/types/index.ts +237 -0
- package/connectors/connect-roboflow/src/utils/auth.ts +274 -0
- package/connectors/connect-roboflow/src/utils/bulk.ts +212 -0
- package/connectors/connect-roboflow/src/utils/config.ts +326 -0
- package/connectors/connect-roboflow/src/utils/output.ts +175 -0
- package/connectors/connect-roboflow/src/utils/settings.ts +114 -0
- package/connectors/connect-roboflow/src/utils/storage.ts +198 -0
- package/connectors/connect-roboflow/tsconfig.json +16 -0
- package/connectors/connect-runway/.env.example +11 -0
- package/connectors/connect-runway/CLAUDE.md +128 -0
- package/connectors/connect-runway/README.md +193 -0
- package/connectors/connect-runway/package.json +52 -0
- package/connectors/connect-runway/src/api/client.ts +78 -0
- package/connectors/connect-runway/src/api/index.ts +40 -0
- package/connectors/connect-runway/src/cli/index.ts +283 -0
- package/connectors/connect-runway/src/index.ts +19 -0
- package/connectors/connect-runway/src/types/index.ts +52 -0
- package/connectors/connect-runway/src/utils/config.ts +103 -0
- package/connectors/connect-runway/src/utils/output.ts +119 -0
- package/connectors/connect-runway/tsconfig.json +16 -0
- package/connectors/connect-together/.env.example +11 -0
- package/connectors/connect-together/CLAUDE.md +128 -0
- package/connectors/connect-together/README.md +193 -0
- package/connectors/connect-together/package.json +52 -0
- package/connectors/connect-together/src/api/client.ts +106 -0
- package/connectors/connect-together/src/api/index.ts +47 -0
- package/connectors/connect-together/src/cli/index.ts +228 -0
- package/connectors/connect-together/src/index.ts +19 -0
- package/connectors/connect-together/src/types/index.ts +91 -0
- package/connectors/connect-together/src/utils/config.ts +142 -0
- package/connectors/connect-together/src/utils/output.ts +119 -0
- package/connectors/connect-together/tsconfig.json +16 -0
- package/dist/index.js +112 -0
- package/package.json +1 -1
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { ModalConfig } from '../types';
|
|
2
|
+
import { ModalApiError } from '../types';
|
|
3
|
+
|
|
4
|
+
const DEFAULT_BASE_URL = 'https://api.modal.com/v1';
|
|
5
|
+
|
|
6
|
+
export interface RequestOptions {
|
|
7
|
+
method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
|
8
|
+
params?: Record<string, string | number | boolean | undefined>;
|
|
9
|
+
body?: Record<string, unknown> | unknown[] | string;
|
|
10
|
+
headers?: Record<string, string>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class ModalClient {
|
|
14
|
+
private readonly tokenId: string;
|
|
15
|
+
private readonly tokenSecret: string;
|
|
16
|
+
private readonly baseUrl: string;
|
|
17
|
+
|
|
18
|
+
constructor(config: ModalConfig) {
|
|
19
|
+
if (!config.tokenId || !config.tokenSecret) {
|
|
20
|
+
throw new Error('Token ID and Token Secret are required');
|
|
21
|
+
}
|
|
22
|
+
this.tokenId = config.tokenId;
|
|
23
|
+
this.tokenSecret = config.tokenSecret;
|
|
24
|
+
this.baseUrl = config.baseUrl || DEFAULT_BASE_URL;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private buildUrl(path: string, params?: Record<string, string | number | boolean | undefined>): string {
|
|
28
|
+
const url = new URL(`${this.baseUrl}${path}`);
|
|
29
|
+
if (params) {
|
|
30
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
31
|
+
if (value !== undefined && value !== null && value !== '') {
|
|
32
|
+
url.searchParams.append(key, String(value));
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return url.toString();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private getAuthHeader(): string {
|
|
40
|
+
const credentials = Buffer.from(`${this.tokenId}:${this.tokenSecret}`).toString('base64');
|
|
41
|
+
return `Basic ${credentials}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async request<T>(path: string, options: RequestOptions = {}): Promise<T> {
|
|
45
|
+
const { method = 'GET', params, body, headers = {} } = options;
|
|
46
|
+
const url = this.buildUrl(path, params);
|
|
47
|
+
|
|
48
|
+
const requestHeaders: Record<string, string> = {
|
|
49
|
+
'Authorization': this.getAuthHeader(),
|
|
50
|
+
'Accept': 'application/json',
|
|
51
|
+
...headers,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
if (body && ['POST', 'PUT', 'PATCH'].includes(method)) {
|
|
55
|
+
requestHeaders['Content-Type'] = 'application/json';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const fetchOptions: RequestInit = {
|
|
59
|
+
method,
|
|
60
|
+
headers: requestHeaders,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
if (body && ['POST', 'PUT', 'PATCH'].includes(method)) {
|
|
64
|
+
fetchOptions.body = typeof body === 'string' ? body : JSON.stringify(body);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const response = await fetch(url, fetchOptions);
|
|
68
|
+
|
|
69
|
+
if (response.status === 204) {
|
|
70
|
+
return {} as T;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let data: unknown;
|
|
74
|
+
const contentType = response.headers.get('content-type') || '';
|
|
75
|
+
|
|
76
|
+
if (contentType.includes('application/json')) {
|
|
77
|
+
const text = await response.text();
|
|
78
|
+
if (text) {
|
|
79
|
+
try {
|
|
80
|
+
data = JSON.parse(text);
|
|
81
|
+
} catch {
|
|
82
|
+
data = text;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
data = await response.text();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!response.ok) {
|
|
90
|
+
let errorMessage = String(data || response.statusText);
|
|
91
|
+
if (typeof data === 'object' && data !== null) {
|
|
92
|
+
const errorObj = data as { error?: string; message?: string; detail?: string };
|
|
93
|
+
errorMessage = errorObj.error || errorObj.message || errorObj.detail || JSON.stringify(data);
|
|
94
|
+
}
|
|
95
|
+
throw new ModalApiError(errorMessage, response.status);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return data as T;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async get<T>(path: string, params?: Record<string, string | number | boolean | undefined>): Promise<T> {
|
|
102
|
+
return this.request<T>(path, { method: 'GET', params });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async post<T>(path: string, body?: Record<string, unknown> | unknown[] | string | object): Promise<T> {
|
|
106
|
+
return this.request<T>(path, { method: 'POST', body: body as Record<string, unknown> });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async delete<T>(path: string): Promise<T> {
|
|
110
|
+
return this.request<T>(path, { method: 'DELETE' });
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
getTokenPreview(): string {
|
|
114
|
+
if (this.tokenId.length > 8) {
|
|
115
|
+
return `${this.tokenId.substring(0, 4)}...${this.tokenId.substring(this.tokenId.length - 4)}`;
|
|
116
|
+
}
|
|
117
|
+
return '***';
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ModalConfig,
|
|
3
|
+
WebEndpointRequest,
|
|
4
|
+
WebEndpointResponse,
|
|
5
|
+
AppsListResponse,
|
|
6
|
+
SecretsListResponse
|
|
7
|
+
} from '../types';
|
|
8
|
+
import { ModalClient } from './client';
|
|
9
|
+
|
|
10
|
+
export class Modal {
|
|
11
|
+
private readonly client: ModalClient;
|
|
12
|
+
|
|
13
|
+
constructor(config: ModalConfig) {
|
|
14
|
+
this.client = new ModalClient(config);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
static fromEnv(): Modal {
|
|
18
|
+
const tokenId = process.env.MODAL_TOKEN_ID;
|
|
19
|
+
const tokenSecret = process.env.MODAL_TOKEN_SECRET;
|
|
20
|
+
if (!tokenId || !tokenSecret) {
|
|
21
|
+
throw new Error('MODAL_TOKEN_ID and MODAL_TOKEN_SECRET environment variables are required');
|
|
22
|
+
}
|
|
23
|
+
return new Modal({ tokenId, tokenSecret });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
getTokenPreview(): string {
|
|
27
|
+
return this.client.getTokenPreview();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async callWebEndpoint(url: string, data?: WebEndpointRequest): Promise<WebEndpointResponse> {
|
|
31
|
+
// Direct call to a Modal web endpoint
|
|
32
|
+
const response = await fetch(url, {
|
|
33
|
+
method: 'POST',
|
|
34
|
+
headers: {
|
|
35
|
+
'Content-Type': 'application/json',
|
|
36
|
+
},
|
|
37
|
+
body: data ? JSON.stringify(data) : undefined,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
if (!response.ok) {
|
|
41
|
+
const text = await response.text();
|
|
42
|
+
throw new Error(`Web endpoint error: ${response.status} - ${text}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return response.json();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async listApps(): Promise<AppsListResponse> {
|
|
49
|
+
return this.client.get<AppsListResponse>('/apps');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async listSecrets(): Promise<SecretsListResponse> {
|
|
53
|
+
return this.client.get<SecretsListResponse>('/secrets');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async createSecret(name: string, values: Record<string, string>): Promise<void> {
|
|
57
|
+
await this.client.post('/secrets', { name, values });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async deleteSecret(name: string): Promise<void> {
|
|
61
|
+
await this.client.delete(`/secrets/${name}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
getClient(): ModalClient {
|
|
65
|
+
return this.client;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export { ModalClient } from './client';
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { Modal } from '../api';
|
|
5
|
+
import {
|
|
6
|
+
getTokenId, getTokenSecret, setTokenId, setTokenSecret, clearConfig, getConfigDir, setProfileOverride,
|
|
7
|
+
getCurrentProfile, setCurrentProfile, listProfiles, createProfile,
|
|
8
|
+
deleteProfile, profileExists, loadProfile,
|
|
9
|
+
} from '../utils/config';
|
|
10
|
+
import type { OutputFormat } from '../utils/output';
|
|
11
|
+
import { success, error, info, print } from '../utils/output';
|
|
12
|
+
|
|
13
|
+
const CONNECTOR_NAME = 'connect-modal';
|
|
14
|
+
const VERSION = '0.0.1';
|
|
15
|
+
|
|
16
|
+
const program = new Command();
|
|
17
|
+
|
|
18
|
+
program
|
|
19
|
+
.name(CONNECTOR_NAME)
|
|
20
|
+
.description('Modal connector CLI - Serverless cloud functions')
|
|
21
|
+
.version(VERSION)
|
|
22
|
+
.option('-f, --format <format>', 'Output format (json, pretty)', 'pretty')
|
|
23
|
+
.option('-p, --profile <profile>', 'Use a specific profile')
|
|
24
|
+
.hook('preAction', (thisCommand) => {
|
|
25
|
+
const opts = thisCommand.opts();
|
|
26
|
+
if (opts.profile) {
|
|
27
|
+
if (!profileExists(opts.profile)) {
|
|
28
|
+
error(`Profile "${opts.profile}" does not exist`);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
setProfileOverride(opts.profile);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
function getFormat(cmd: Command): OutputFormat {
|
|
36
|
+
const parent = cmd.parent;
|
|
37
|
+
return (parent?.opts().format || 'pretty') as OutputFormat;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getClient(): Modal {
|
|
41
|
+
const tokenId = getTokenId();
|
|
42
|
+
const tokenSecret = getTokenSecret();
|
|
43
|
+
if (!tokenId || !tokenSecret) {
|
|
44
|
+
error(`No credentials configured. Run "${CONNECTOR_NAME} config set-token" or set MODAL_TOKEN_ID and MODAL_TOKEN_SECRET`);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
return new Modal({ tokenId, tokenSecret });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Profile Commands
|
|
51
|
+
const profileCmd = program.command('profile').description('Manage profiles');
|
|
52
|
+
|
|
53
|
+
profileCmd.command('list').description('List profiles').action(() => {
|
|
54
|
+
const profiles = listProfiles();
|
|
55
|
+
const current = getCurrentProfile();
|
|
56
|
+
if (profiles.length === 0) { info('No profiles found'); return; }
|
|
57
|
+
profiles.forEach(p => {
|
|
58
|
+
console.log(` ${p}${p === current ? chalk.green(' (active)') : ''}`);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
profileCmd.command('use <name>').description('Switch profile').action((name: string) => {
|
|
63
|
+
if (!profileExists(name)) { error(`Profile "${name}" does not exist`); process.exit(1); }
|
|
64
|
+
setCurrentProfile(name);
|
|
65
|
+
success(`Switched to profile: ${name}`);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
profileCmd.command('create <name>').description('Create profile')
|
|
69
|
+
.option('--token-id <id>', 'Token ID')
|
|
70
|
+
.option('--token-secret <secret>', 'Token Secret')
|
|
71
|
+
.option('--use', 'Switch to this profile')
|
|
72
|
+
.action((name: string, opts) => {
|
|
73
|
+
if (profileExists(name)) { error(`Profile "${name}" already exists`); process.exit(1); }
|
|
74
|
+
createProfile(name, { tokenId: opts.tokenId, tokenSecret: opts.tokenSecret });
|
|
75
|
+
success(`Profile "${name}" created`);
|
|
76
|
+
if (opts.use) { setCurrentProfile(name); info(`Switched to profile: ${name}`); }
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
profileCmd.command('delete <name>').description('Delete profile').action((name: string) => {
|
|
80
|
+
if (name === 'default') { error('Cannot delete default profile'); process.exit(1); }
|
|
81
|
+
if (deleteProfile(name)) { success(`Profile "${name}" deleted`); }
|
|
82
|
+
else { error(`Profile "${name}" not found`); process.exit(1); }
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
profileCmd.command('show [name]').description('Show profile').action((name?: string) => {
|
|
86
|
+
const profileName = name || getCurrentProfile();
|
|
87
|
+
const config = loadProfile(profileName);
|
|
88
|
+
console.log(chalk.bold(`Profile: ${profileName}`));
|
|
89
|
+
info(`Token ID: ${config.tokenId ? config.tokenId.substring(0, 8) + '...' : chalk.gray('not set')}`);
|
|
90
|
+
info(`Token Secret: ${config.tokenSecret ? '********' : chalk.gray('not set')}`);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Config Commands
|
|
94
|
+
const configCmd = program.command('config').description('Manage configuration');
|
|
95
|
+
|
|
96
|
+
configCmd.command('set-token')
|
|
97
|
+
.description('Set Modal tokens')
|
|
98
|
+
.requiredOption('--id <tokenId>', 'Token ID')
|
|
99
|
+
.requiredOption('--secret <tokenSecret>', 'Token Secret')
|
|
100
|
+
.action((opts) => {
|
|
101
|
+
setTokenId(opts.id);
|
|
102
|
+
setTokenSecret(opts.secret);
|
|
103
|
+
success(`Tokens saved`);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
configCmd.command('show').description('Show config').action(() => {
|
|
107
|
+
console.log(chalk.bold(`Profile: ${getCurrentProfile()}`));
|
|
108
|
+
info(`Config dir: ${getConfigDir()}`);
|
|
109
|
+
const tokenId = getTokenId();
|
|
110
|
+
info(`Token ID: ${tokenId ? tokenId.substring(0, 8) + '...' : chalk.gray('not set')}`);
|
|
111
|
+
info(`Token Secret: ${getTokenSecret() ? '********' : chalk.gray('not set')}`);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
configCmd.command('clear').description('Clear config').action(() => {
|
|
115
|
+
clearConfig();
|
|
116
|
+
success('Config cleared');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Web Endpoint Command
|
|
120
|
+
program.command('call <url>')
|
|
121
|
+
.description('Call a Modal web endpoint')
|
|
122
|
+
.option('-d, --data <json>', 'Request data as JSON')
|
|
123
|
+
.action(async (url: string, opts) => {
|
|
124
|
+
try {
|
|
125
|
+
const client = getClient();
|
|
126
|
+
const data = opts.data ? JSON.parse(opts.data) : undefined;
|
|
127
|
+
const result = await client.callWebEndpoint(url, data);
|
|
128
|
+
if (getFormat(program) === 'json') {
|
|
129
|
+
print(result, 'json');
|
|
130
|
+
} else {
|
|
131
|
+
console.log(chalk.green('\nResponse:'));
|
|
132
|
+
console.log(JSON.stringify(result, null, 2));
|
|
133
|
+
}
|
|
134
|
+
} catch (err) {
|
|
135
|
+
error(String(err));
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Apps Commands
|
|
141
|
+
const appsCmd = program.command('apps').description('Manage apps');
|
|
142
|
+
|
|
143
|
+
appsCmd.command('list')
|
|
144
|
+
.description('List apps')
|
|
145
|
+
.action(async () => {
|
|
146
|
+
try {
|
|
147
|
+
const client = getClient();
|
|
148
|
+
const result = await client.listApps();
|
|
149
|
+
if (getFormat(appsCmd) === 'json') {
|
|
150
|
+
print(result.apps, 'json');
|
|
151
|
+
} else {
|
|
152
|
+
if (result.apps.length === 0) {
|
|
153
|
+
info('No apps found');
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
result.apps.forEach(app => {
|
|
157
|
+
console.log(chalk.cyan(`\n${app.name}`));
|
|
158
|
+
console.log(` ID: ${app.app_id}`);
|
|
159
|
+
console.log(` State: ${app.state}`);
|
|
160
|
+
console.log(` Created: ${app.created_at}`);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
} catch (err) {
|
|
164
|
+
error(String(err));
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Secrets Commands
|
|
170
|
+
const secretsCmd = program.command('secrets').description('Manage secrets');
|
|
171
|
+
|
|
172
|
+
secretsCmd.command('list')
|
|
173
|
+
.description('List secrets')
|
|
174
|
+
.action(async () => {
|
|
175
|
+
try {
|
|
176
|
+
const client = getClient();
|
|
177
|
+
const result = await client.listSecrets();
|
|
178
|
+
if (getFormat(secretsCmd) === 'json') {
|
|
179
|
+
print(result.secrets, 'json');
|
|
180
|
+
} else {
|
|
181
|
+
if (result.secrets.length === 0) {
|
|
182
|
+
info('No secrets found');
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
result.secrets.forEach(secret => {
|
|
186
|
+
console.log(chalk.cyan(`\n${secret.name}`));
|
|
187
|
+
console.log(` Created: ${secret.created_at}`);
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
} catch (err) {
|
|
191
|
+
error(String(err));
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
secretsCmd.command('create <name>')
|
|
197
|
+
.description('Create a secret')
|
|
198
|
+
.requiredOption('-v, --values <json>', 'Secret values as JSON')
|
|
199
|
+
.action(async (name: string, opts) => {
|
|
200
|
+
try {
|
|
201
|
+
const client = getClient();
|
|
202
|
+
const values = JSON.parse(opts.values);
|
|
203
|
+
await client.createSecret(name, values);
|
|
204
|
+
success(`Secret "${name}" created`);
|
|
205
|
+
} catch (err) {
|
|
206
|
+
error(String(err));
|
|
207
|
+
process.exit(1);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
secretsCmd.command('delete <name>')
|
|
212
|
+
.description('Delete a secret')
|
|
213
|
+
.action(async (name: string) => {
|
|
214
|
+
try {
|
|
215
|
+
const client = getClient();
|
|
216
|
+
await client.deleteSecret(name);
|
|
217
|
+
success(`Secret "${name}" deleted`);
|
|
218
|
+
} catch (err) {
|
|
219
|
+
error(String(err));
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
program.parse();
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Modal Connector
|
|
2
|
+
// TypeScript wrapper for Modal API
|
|
3
|
+
|
|
4
|
+
export { Modal } from './api';
|
|
5
|
+
export * from './types';
|
|
6
|
+
export { ModalClient } from './api';
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
getTokenId,
|
|
10
|
+
setTokenId,
|
|
11
|
+
getTokenSecret,
|
|
12
|
+
setTokenSecret,
|
|
13
|
+
getCurrentProfile,
|
|
14
|
+
setCurrentProfile,
|
|
15
|
+
listProfiles,
|
|
16
|
+
createProfile,
|
|
17
|
+
deleteProfile,
|
|
18
|
+
loadProfile,
|
|
19
|
+
saveProfile,
|
|
20
|
+
clearConfig,
|
|
21
|
+
} from './utils/config';
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Modal API Types
|
|
2
|
+
|
|
3
|
+
export interface ModalConfig {
|
|
4
|
+
tokenId: string;
|
|
5
|
+
tokenSecret: string;
|
|
6
|
+
baseUrl?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Web Endpoint Types
|
|
10
|
+
export interface WebEndpointRequest {
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface WebEndpointResponse {
|
|
15
|
+
[key: string]: unknown;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// App Types
|
|
19
|
+
export interface ModalApp {
|
|
20
|
+
app_id: string;
|
|
21
|
+
name: string;
|
|
22
|
+
state: string;
|
|
23
|
+
created_at: string;
|
|
24
|
+
updated_at?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface AppsListResponse {
|
|
28
|
+
apps: ModalApp[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Function Types
|
|
32
|
+
export interface ModalFunction {
|
|
33
|
+
function_id: string;
|
|
34
|
+
name: string;
|
|
35
|
+
app_id: string;
|
|
36
|
+
created_at: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Secret Types
|
|
40
|
+
export interface ModalSecret {
|
|
41
|
+
name: string;
|
|
42
|
+
created_at: string;
|
|
43
|
+
updated_at?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface SecretsListResponse {
|
|
47
|
+
secrets: ModalSecret[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Error Types
|
|
51
|
+
export class ModalApiError extends Error {
|
|
52
|
+
constructor(
|
|
53
|
+
message: string,
|
|
54
|
+
public readonly status: number,
|
|
55
|
+
public readonly detail?: string
|
|
56
|
+
) {
|
|
57
|
+
super(message);
|
|
58
|
+
this.name = 'ModalApiError';
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, rmSync } from 'fs';
|
|
2
|
+
import { homedir } from 'os';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
|
|
5
|
+
const CONNECTOR_NAME = 'connect-modal';
|
|
6
|
+
const DEFAULT_PROFILE = 'default';
|
|
7
|
+
|
|
8
|
+
export interface ProfileConfig {
|
|
9
|
+
tokenId?: string;
|
|
10
|
+
tokenSecret?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let profileOverride: string | undefined;
|
|
14
|
+
|
|
15
|
+
const CONFIG_DIR = join(homedir(), '.connect', CONNECTOR_NAME);
|
|
16
|
+
const PROFILES_DIR = join(CONFIG_DIR, 'profiles');
|
|
17
|
+
const CURRENT_PROFILE_FILE = join(CONFIG_DIR, 'current_profile');
|
|
18
|
+
|
|
19
|
+
export function setProfileOverride(profile: string | undefined): void {
|
|
20
|
+
profileOverride = profile;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function ensureConfigDir(): void {
|
|
24
|
+
if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
|
|
25
|
+
if (!existsSync(PROFILES_DIR)) mkdirSync(PROFILES_DIR, { recursive: true });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getProfilePath(profile: string): string {
|
|
29
|
+
return join(PROFILES_DIR, `${profile}.json`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function getCurrentProfile(): string {
|
|
33
|
+
if (profileOverride) return profileOverride;
|
|
34
|
+
ensureConfigDir();
|
|
35
|
+
if (existsSync(CURRENT_PROFILE_FILE)) {
|
|
36
|
+
try {
|
|
37
|
+
const profile = readFileSync(CURRENT_PROFILE_FILE, 'utf-8').trim();
|
|
38
|
+
if (profile && profileExists(profile)) return profile;
|
|
39
|
+
} catch { /* fall through */ }
|
|
40
|
+
}
|
|
41
|
+
return DEFAULT_PROFILE;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function setCurrentProfile(profile: string): void {
|
|
45
|
+
ensureConfigDir();
|
|
46
|
+
if (!profileExists(profile) && profile !== DEFAULT_PROFILE) {
|
|
47
|
+
throw new Error(`Profile "${profile}" does not exist`);
|
|
48
|
+
}
|
|
49
|
+
writeFileSync(CURRENT_PROFILE_FILE, profile);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function profileExists(profile: string): boolean {
|
|
53
|
+
return existsSync(getProfilePath(profile));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function listProfiles(): string[] {
|
|
57
|
+
ensureConfigDir();
|
|
58
|
+
if (!existsSync(PROFILES_DIR)) return [];
|
|
59
|
+
return readdirSync(PROFILES_DIR).filter(f => f.endsWith('.json')).map(f => f.replace('.json', '')).sort();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function createProfile(profile: string, config: ProfileConfig = {}): boolean {
|
|
63
|
+
ensureConfigDir();
|
|
64
|
+
if (profileExists(profile)) return false;
|
|
65
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(profile)) {
|
|
66
|
+
throw new Error('Profile name can only contain letters, numbers, hyphens, and underscores');
|
|
67
|
+
}
|
|
68
|
+
writeFileSync(getProfilePath(profile), JSON.stringify(config, null, 2));
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function deleteProfile(profile: string): boolean {
|
|
73
|
+
if (profile === DEFAULT_PROFILE) return false;
|
|
74
|
+
if (!profileExists(profile)) return false;
|
|
75
|
+
if (getCurrentProfile() === profile) setCurrentProfile(DEFAULT_PROFILE);
|
|
76
|
+
rmSync(getProfilePath(profile));
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function loadProfile(profile?: string): ProfileConfig {
|
|
81
|
+
ensureConfigDir();
|
|
82
|
+
const profilePath = getProfilePath(profile || getCurrentProfile());
|
|
83
|
+
if (!existsSync(profilePath)) return {};
|
|
84
|
+
try { return JSON.parse(readFileSync(profilePath, 'utf-8')); } catch { return {}; }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function saveProfile(config: ProfileConfig, profile?: string): void {
|
|
88
|
+
ensureConfigDir();
|
|
89
|
+
writeFileSync(getProfilePath(profile || getCurrentProfile()), JSON.stringify(config, null, 2));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function getTokenId(): string | undefined {
|
|
93
|
+
return process.env.MODAL_TOKEN_ID || loadProfile().tokenId;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function setTokenId(tokenId: string): void {
|
|
97
|
+
const config = loadProfile();
|
|
98
|
+
config.tokenId = tokenId;
|
|
99
|
+
saveProfile(config);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function getTokenSecret(): string | undefined {
|
|
103
|
+
return process.env.MODAL_TOKEN_SECRET || loadProfile().tokenSecret;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function setTokenSecret(tokenSecret: string): void {
|
|
107
|
+
const config = loadProfile();
|
|
108
|
+
config.tokenSecret = tokenSecret;
|
|
109
|
+
saveProfile(config);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function clearConfig(): void { saveProfile({}); }
|
|
113
|
+
export function getConfigDir(): string { return CONFIG_DIR; }
|
|
114
|
+
export function getActiveProfileName(): string { return getCurrentProfile(); }
|