@hasna/skills 0.1.32 → 0.1.33
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 +1 -16
- package/bin/index.js +57 -14
- package/bin/mcp.js +57 -13
- package/dist/cli/commands/runtime.d.ts +1 -1
- package/dist/index.js +51 -5
- package/dist/lib/skill-validation.d.ts +2 -0
- package/package.json +1 -1
- package/skills/ad-creative-pack/package.json +4 -7
- package/skills/api-docs-portal/package.json +5 -3
- package/skills/audio/package.json +4 -22
- package/skills/audio-transcript-pack/package.json +3 -2
- package/skills/blog-article/package.json +5 -21
- package/skills/brand-kit/package.json +4 -7
- package/skills/brand-photo-shoot/package.json +3 -2
- package/skills/browse/package.json +4 -33
- package/skills/contract-review-report/package.json +3 -2
- package/skills/customer-feedback-report/package.json +3 -2
- package/skills/deepresearch/package.json +4 -37
- package/skills/domainpurchase/SKILL.md +14 -31
- package/skills/domainpurchase/package.json +3 -8
- package/skills/email-sequence/package.json +4 -7
- package/skills/generate-book-cover/package.json +5 -23
- package/skills/icon-pack/package.json +4 -22
- package/skills/image/package.json +4 -22
- package/skills/invoice-reconciliation/package.json +3 -2
- package/skills/landing-page-pack/package.json +4 -7
- package/skills/logo-design/package.json +4 -2
- package/skills/managemcp/CLAUDE.md +9 -43
- package/skills/managemcp/README.md +12 -198
- package/skills/managemcp/package.json +2 -10
- package/skills/managemcp/tsconfig.json +1 -3
- package/skills/market-research-report/package.json +5 -4
- package/skills/meeting-pack/package.json +3 -2
- package/skills/migration-plan-pack/package.json +3 -2
- package/skills/music/package.json +4 -20
- package/skills/music-album/package.json +3 -2
- package/skills/one-page-website/package.json +3 -2
- package/skills/pdf-read/package.json +5 -23
- package/skills/pdf-to-dataset/package.json +5 -4
- package/skills/pdf-to-markdown/package.json +5 -4
- package/skills/performance-audit-report/package.json +3 -2
- package/skills/photo-album/package.json +4 -2
- package/skills/pitch-deck/package.json +5 -3
- package/skills/playlist-maker/package.json +4 -2
- package/skills/product-mockup/package.json +3 -2
- package/skills/proposal-pack/package.json +5 -4
- package/skills/read-pdf/package.json +4 -29
- package/skills/remove-background/package.json +5 -21
- package/skills/repo-onboarding-report/package.json +3 -2
- package/skills/sdk-generator/package.json +3 -2
- package/skills/security-audit-report/package.json +5 -4
- package/skills/seo-content-pack/package.json +5 -3
- package/skills/short-video-pack/package.json +3 -2
- package/skills/slide-deck-generator/package.json +3 -2
- package/skills/sms/README.md +18 -56
- package/skills/sms/package.json +1 -3
- package/skills/sms/tsconfig.json +1 -2
- package/skills/social-content-calendar/package.json +4 -7
- package/skills/test-suite-generator/package.json +5 -4
- package/skills/transcript/package.json +4 -23
- package/skills/video/package.json +4 -24
- package/skills/video-highlight-pack/package.json +3 -2
- package/skills/voiceover-jingle-pack/package.json +3 -2
- package/skills/webcrawling/package.json +4 -32
- package/skills/domainpurchase/.env.example +0 -7
- package/skills/domainpurchase/src/lib/api-client.ts +0 -571
- package/skills/domainpurchase/src/lib/config.ts +0 -209
- package/skills/domainpurchase/src/lib/installer.ts +0 -165
- package/skills/managemcp/.env.example +0 -16
- package/skills/managemcp/scripts/migrate.ts +0 -57
- package/skills/managemcp/scripts/seed.ts +0 -58
- package/skills/managemcp/src/db/index.ts +0 -37
- package/skills/managemcp/src/lib/api-client.ts +0 -108
- package/skills/managemcp/src/lib/config.ts +0 -34
- package/skills/managemcp/src/lib/installer.ts +0 -140
- package/skills/managemcp/src/lib/service-dir.ts +0 -119
- package/skills/sms/scripts/buy-number.ts +0 -63
- package/skills/sms/scripts/test-send.ts +0 -67
- package/skills/sms/src/server.ts +0 -351
- package/skills/sms/src/sse-server.ts +0 -279
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
2
|
-
import { join } from "path";
|
|
3
|
-
import { homedir } from "os";
|
|
4
|
-
|
|
5
|
-
export interface Config {
|
|
6
|
-
apiKey?: string;
|
|
7
|
-
apiSecret?: string;
|
|
8
|
-
customerId?: string;
|
|
9
|
-
shopperId?: string;
|
|
10
|
-
apiUrl: string;
|
|
11
|
-
// Remote server configuration
|
|
12
|
-
useRemoteServer?: boolean;
|
|
13
|
-
remoteServerUrl: string;
|
|
14
|
-
remoteApiKey?: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const CONFIG_DIR = join(homedir(), ".config", "service-domainpurchase");
|
|
18
|
-
export const CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
19
|
-
const AWS_SECRET_NAME = process.env.AWS_SECRET_NAME || "your-org/your-tool/api/live";
|
|
20
|
-
|
|
21
|
-
const DEFAULT_CONFIG: Config = {
|
|
22
|
-
apiUrl: "https://api.godaddy.com",
|
|
23
|
-
remoteServerUrl: process.env.DOMAIN_REMOTE_SERVER_URL || "https://service.example.com/domainpurchase",
|
|
24
|
-
useRemoteServer: false,
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
// Cache for AWS secrets to avoid repeated API calls
|
|
28
|
-
let awsSecretsCache: Partial<Config> | null = null;
|
|
29
|
-
|
|
30
|
-
async function loadAwsSecrets(): Promise<Partial<Config>> {
|
|
31
|
-
if (awsSecretsCache) {
|
|
32
|
-
return awsSecretsCache;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
try {
|
|
36
|
-
const { SecretsManagerClient, GetSecretValueCommand } = await import(
|
|
37
|
-
"@aws-sdk/client-secrets-manager"
|
|
38
|
-
);
|
|
39
|
-
const client = new SecretsManagerClient({ region: "us-east-1" });
|
|
40
|
-
const response = await client.send(
|
|
41
|
-
new GetSecretValueCommand({ SecretId: AWS_SECRET_NAME })
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
if (response.SecretString) {
|
|
45
|
-
const secret = JSON.parse(response.SecretString);
|
|
46
|
-
awsSecretsCache = {
|
|
47
|
-
apiKey: secret.api_key,
|
|
48
|
-
apiSecret: secret.api_secret,
|
|
49
|
-
customerId: secret.customer_id,
|
|
50
|
-
shopperId: secret.shopper_id,
|
|
51
|
-
};
|
|
52
|
-
return awsSecretsCache;
|
|
53
|
-
}
|
|
54
|
-
} catch {
|
|
55
|
-
// AWS secrets not available, fall back to other sources
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return {};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export function loadConfig(): Config {
|
|
62
|
-
// First check environment variables
|
|
63
|
-
const envConfig: Partial<Config> = {
|
|
64
|
-
apiKey: process.env.DOMAIN_API_KEY,
|
|
65
|
-
apiSecret: process.env.DOMAIN_API_SECRET,
|
|
66
|
-
customerId: process.env.DOMAIN_CUSTOMER_ID,
|
|
67
|
-
apiUrl: process.env.DOMAIN_API_URL,
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
// Then load from config file
|
|
71
|
-
let fileConfig: Partial<Config> = {};
|
|
72
|
-
if (existsSync(CONFIG_FILE)) {
|
|
73
|
-
try {
|
|
74
|
-
fileConfig = JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
|
|
75
|
-
} catch {
|
|
76
|
-
// Ignore parse errors
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Merge with defaults (env vars take priority)
|
|
81
|
-
return {
|
|
82
|
-
...DEFAULT_CONFIG,
|
|
83
|
-
...fileConfig,
|
|
84
|
-
...Object.fromEntries(
|
|
85
|
-
Object.entries(envConfig).filter(([_, v]) => v !== undefined)
|
|
86
|
-
),
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export async function loadConfigAsync(): Promise<Config> {
|
|
91
|
-
// Load AWS secrets first (lowest priority)
|
|
92
|
-
const awsConfig = await loadAwsSecrets();
|
|
93
|
-
|
|
94
|
-
// Then check environment variables
|
|
95
|
-
const envConfig: Partial<Config> = {
|
|
96
|
-
apiKey: process.env.DOMAIN_API_KEY,
|
|
97
|
-
apiSecret: process.env.DOMAIN_API_SECRET,
|
|
98
|
-
customerId: process.env.DOMAIN_CUSTOMER_ID,
|
|
99
|
-
apiUrl: process.env.DOMAIN_API_URL,
|
|
100
|
-
useRemoteServer: process.env.DOMAIN_USE_REMOTE_SERVER === "true",
|
|
101
|
-
remoteServerUrl: process.env.DOMAIN_REMOTE_SERVER_URL,
|
|
102
|
-
remoteApiKey: process.env.DOMAIN_REMOTE_API_KEY,
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
// Then load from config file
|
|
106
|
-
let fileConfig: Partial<Config> = {};
|
|
107
|
-
if (existsSync(CONFIG_FILE)) {
|
|
108
|
-
try {
|
|
109
|
-
fileConfig = JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
|
|
110
|
-
} catch {
|
|
111
|
-
// Ignore parse errors
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Merge: AWS -> file -> env (env takes highest priority)
|
|
116
|
-
return {
|
|
117
|
-
...DEFAULT_CONFIG,
|
|
118
|
-
...awsConfig,
|
|
119
|
-
...fileConfig,
|
|
120
|
-
...Object.fromEntries(
|
|
121
|
-
Object.entries(envConfig).filter(([_, v]) => v !== undefined)
|
|
122
|
-
),
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export function saveConfig(config: Partial<Config>): void {
|
|
127
|
-
if (!existsSync(CONFIG_DIR)) {
|
|
128
|
-
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const current = loadConfig();
|
|
132
|
-
const merged = { ...current, ...config };
|
|
133
|
-
writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2));
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
export function getApiKey(): string | undefined {
|
|
137
|
-
return loadConfig().apiKey;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
export function getApiSecret(): string | undefined {
|
|
141
|
-
return loadConfig().apiSecret;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export function getCustomerId(): string | undefined {
|
|
145
|
-
return loadConfig().customerId;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
export function getApiUrl(): string {
|
|
149
|
-
return loadConfig().apiUrl;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Async versions that can load from AWS Secrets Manager
|
|
153
|
-
export async function getApiKeyAsync(): Promise<string | undefined> {
|
|
154
|
-
return (await loadConfigAsync()).apiKey;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
export async function getApiSecretAsync(): Promise<string | undefined> {
|
|
158
|
-
return (await loadConfigAsync()).apiSecret;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
export async function getCustomerIdAsync(): Promise<string | undefined> {
|
|
162
|
-
return (await loadConfigAsync()).customerId;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
export async function getShopperIdAsync(): Promise<string | undefined> {
|
|
166
|
-
return (await loadConfigAsync()).shopperId;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
export async function getApiUrlAsync(): Promise<string> {
|
|
170
|
-
return (await loadConfigAsync()).apiUrl;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Remote server configuration getters
|
|
174
|
-
export function useRemoteServer(): boolean {
|
|
175
|
-
return loadConfig().useRemoteServer || false;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
export function getRemoteServerUrl(): string {
|
|
179
|
-
return loadConfig().remoteServerUrl;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
export function getRemoteApiKey(): string | undefined {
|
|
183
|
-
return loadConfig().remoteApiKey;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
export async function getRemoteApiKeyAsync(): Promise<string | undefined> {
|
|
187
|
-
const config = await loadConfigAsync();
|
|
188
|
-
if (config.remoteApiKey) {
|
|
189
|
-
return config.remoteApiKey;
|
|
190
|
-
}
|
|
191
|
-
// Try to load from AWS Secrets Manager
|
|
192
|
-
try {
|
|
193
|
-
const { SecretsManagerClient, GetSecretValueCommand } = await import(
|
|
194
|
-
"@aws-sdk/client-secrets-manager"
|
|
195
|
-
);
|
|
196
|
-
const client = new SecretsManagerClient({ region: "us-east-1" });
|
|
197
|
-
const response = await client.send(
|
|
198
|
-
new GetSecretValueCommand({ SecretId: process.env.DOMAIN_API_KEY_SECRET_ID || "your-org/prod/your-service/api-key" })
|
|
199
|
-
);
|
|
200
|
-
return response.SecretString;
|
|
201
|
-
} catch {
|
|
202
|
-
return undefined;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Initialize config from AWS on first run
|
|
207
|
-
export async function initConfig(): Promise<void> {
|
|
208
|
-
await loadConfigAsync();
|
|
209
|
-
}
|
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, writeFileSync, appendFileSync } from "fs";
|
|
2
|
-
import { join } from "path";
|
|
3
|
-
import { homedir } from "os";
|
|
4
|
-
|
|
5
|
-
const SERVICE_NAME = "service-domainpurchase";
|
|
6
|
-
const SERVICE_DIR_NAME = `.${SERVICE_NAME}`;
|
|
7
|
-
|
|
8
|
-
export interface InstallOptions {
|
|
9
|
-
force?: boolean;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function getServiceDir(): string {
|
|
13
|
-
return join(homedir(), SERVICE_DIR_NAME);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function getLogFilePath(): string {
|
|
17
|
-
return join(getServiceDir(), `${SERVICE_NAME}.log`);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function getReadmePath(): string {
|
|
21
|
-
return join(getServiceDir(), "README.md");
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function isInstalled(): boolean {
|
|
25
|
-
return existsSync(getServiceDir());
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function ensureInstalled(): void {
|
|
29
|
-
if (!isInstalled()) {
|
|
30
|
-
install();
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function install(options: InstallOptions = {}): { success: boolean; path: string; message: string } {
|
|
35
|
-
const serviceDir = getServiceDir();
|
|
36
|
-
|
|
37
|
-
if (existsSync(serviceDir) && !options.force) {
|
|
38
|
-
return {
|
|
39
|
-
success: false,
|
|
40
|
-
path: serviceDir,
|
|
41
|
-
message: "Already installed. Use --force to reinstall.",
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
mkdirSync(serviceDir, { recursive: true });
|
|
46
|
-
|
|
47
|
-
const readmeContent = generateReadme();
|
|
48
|
-
writeFileSync(getReadmePath(), readmeContent);
|
|
49
|
-
|
|
50
|
-
const logHeader = `# ${SERVICE_NAME} log file
|
|
51
|
-
# Created: ${new Date().toISOString()}
|
|
52
|
-
# This file contains activity history and logs.
|
|
53
|
-
# -------------------------------------------------------
|
|
54
|
-
|
|
55
|
-
`;
|
|
56
|
-
writeFileSync(getLogFilePath(), logHeader);
|
|
57
|
-
|
|
58
|
-
return {
|
|
59
|
-
success: true,
|
|
60
|
-
path: serviceDir,
|
|
61
|
-
message: `Installed at ${serviceDir}`,
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function uninstall(): { success: boolean; message: string } {
|
|
66
|
-
const serviceDir = getServiceDir();
|
|
67
|
-
|
|
68
|
-
if (!existsSync(serviceDir)) {
|
|
69
|
-
return {
|
|
70
|
-
success: false,
|
|
71
|
-
message: "Not installed.",
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const { rmSync } = require("fs");
|
|
76
|
-
rmSync(serviceDir, { recursive: true, force: true });
|
|
77
|
-
|
|
78
|
-
return {
|
|
79
|
-
success: true,
|
|
80
|
-
message: `Uninstalled from ${serviceDir}`,
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function generateReadme(): string {
|
|
85
|
-
return `# .${SERVICE_NAME}
|
|
86
|
-
|
|
87
|
-
This directory contains local configuration and logs for \`${SERVICE_NAME}\`.
|
|
88
|
-
|
|
89
|
-
## Files
|
|
90
|
-
|
|
91
|
-
| Path | Description |
|
|
92
|
-
|------|-------------|
|
|
93
|
-
| \`${SERVICE_NAME}.log\` | Activity log |
|
|
94
|
-
| \`README.md\` | This file |
|
|
95
|
-
|
|
96
|
-
## What is ${SERVICE_NAME}?
|
|
97
|
-
|
|
98
|
-
\`${SERVICE_NAME}\` is a domain purchase service that provides:
|
|
99
|
-
|
|
100
|
-
- Domain registration automation
|
|
101
|
-
- Registrar integrations (GoDaddy, Namecheap, etc.)
|
|
102
|
-
- DNS configuration
|
|
103
|
-
- Domain transfer management
|
|
104
|
-
- Renewal tracking
|
|
105
|
-
|
|
106
|
-
## Usage
|
|
107
|
-
|
|
108
|
-
\`\`\`bash
|
|
109
|
-
# Purchase a domain
|
|
110
|
-
service-domainpurchase buy --domain "example.com"
|
|
111
|
-
|
|
112
|
-
# List purchased domains
|
|
113
|
-
service-domainpurchase list
|
|
114
|
-
|
|
115
|
-
# Check purchase status
|
|
116
|
-
service-domainpurchase status --domain "example.com"
|
|
117
|
-
|
|
118
|
-
# Start the server
|
|
119
|
-
service-domainpurchase server
|
|
120
|
-
\`\`\`
|
|
121
|
-
|
|
122
|
-
## Log File
|
|
123
|
-
|
|
124
|
-
The \`${SERVICE_NAME}.log\` file contains:
|
|
125
|
-
- Purchase transactions
|
|
126
|
-
- Registration status
|
|
127
|
-
- DNS changes
|
|
128
|
-
- API activity
|
|
129
|
-
|
|
130
|
-
## More Information
|
|
131
|
-
|
|
132
|
-
For full documentation, run:
|
|
133
|
-
|
|
134
|
-
\`\`\`bash
|
|
135
|
-
service-domainpurchase --help
|
|
136
|
-
\`\`\`
|
|
137
|
-
`;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
141
|
-
|
|
142
|
-
export function logToFile(message: string, level: LogLevel = "info"): void {
|
|
143
|
-
ensureInstalled();
|
|
144
|
-
|
|
145
|
-
const timestamp = new Date().toISOString();
|
|
146
|
-
const logLine = `[${timestamp}] [${level.toUpperCase()}] ${message}\n`;
|
|
147
|
-
|
|
148
|
-
try {
|
|
149
|
-
appendFileSync(getLogFilePath(), logLine);
|
|
150
|
-
} catch {
|
|
151
|
-
// Silently fail
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
export function logInfo(message: string): void {
|
|
156
|
-
logToFile(message, "info");
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export function logError(message: string): void {
|
|
160
|
-
logToFile(message, "error");
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
export function logWarn(message: string): void {
|
|
164
|
-
logToFile(message, "warn");
|
|
165
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
# Database (individual connection params)
|
|
2
|
-
DB_HOST=localhost
|
|
3
|
-
DB_PORT=5432
|
|
4
|
-
DB_NAME=managemcp_db
|
|
5
|
-
DB_USER=postgres
|
|
6
|
-
DB_PASSWORD=your-password-here
|
|
7
|
-
|
|
8
|
-
# Or use a full connection string
|
|
9
|
-
# DATABASE_URL=postgres://user:password@host:port/dbname
|
|
10
|
-
|
|
11
|
-
# Server
|
|
12
|
-
PORT=3000
|
|
13
|
-
|
|
14
|
-
# API (for CLI usage)
|
|
15
|
-
API_URL=https://api.example.com
|
|
16
|
-
API_KEY=your-api-key-here
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
|
|
3
|
-
import { sql, testConnection, closeConnection } from '../src/db';
|
|
4
|
-
|
|
5
|
-
console.log('Running database migrations...');
|
|
6
|
-
|
|
7
|
-
const connected = await testConnection();
|
|
8
|
-
if (!connected) {
|
|
9
|
-
console.error('Failed to connect to database');
|
|
10
|
-
process.exit(1);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
console.log('Database connected\n');
|
|
14
|
-
|
|
15
|
-
try {
|
|
16
|
-
console.log('Creating tables...');
|
|
17
|
-
await sql`
|
|
18
|
-
CREATE TABLE IF NOT EXISTS items (
|
|
19
|
-
id SERIAL PRIMARY KEY,
|
|
20
|
-
name VARCHAR(255) NOT NULL,
|
|
21
|
-
description TEXT,
|
|
22
|
-
metadata JSONB DEFAULT '{}',
|
|
23
|
-
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
24
|
-
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
25
|
-
)
|
|
26
|
-
`;
|
|
27
|
-
|
|
28
|
-
console.log('Creating indexes...');
|
|
29
|
-
await sql`CREATE INDEX IF NOT EXISTS idx_items_name ON items(name)`;
|
|
30
|
-
await sql`CREATE INDEX IF NOT EXISTS idx_items_created_at ON items(created_at DESC)`;
|
|
31
|
-
|
|
32
|
-
console.log('Creating triggers...');
|
|
33
|
-
await sql`
|
|
34
|
-
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
|
35
|
-
RETURNS TRIGGER AS $$
|
|
36
|
-
BEGIN
|
|
37
|
-
NEW.updated_at = CURRENT_TIMESTAMP;
|
|
38
|
-
RETURN NEW;
|
|
39
|
-
END;
|
|
40
|
-
$$ language 'plpgsql'
|
|
41
|
-
`;
|
|
42
|
-
|
|
43
|
-
await sql`DROP TRIGGER IF EXISTS update_items_updated_at ON items`;
|
|
44
|
-
await sql`
|
|
45
|
-
CREATE TRIGGER update_items_updated_at
|
|
46
|
-
BEFORE UPDATE ON items
|
|
47
|
-
FOR EACH ROW
|
|
48
|
-
EXECUTE FUNCTION update_updated_at_column()
|
|
49
|
-
`;
|
|
50
|
-
|
|
51
|
-
console.log('\nMigrations completed successfully');
|
|
52
|
-
} catch (error) {
|
|
53
|
-
console.error('\nMigration failed:', error);
|
|
54
|
-
process.exit(1);
|
|
55
|
-
} finally {
|
|
56
|
-
await closeConnection();
|
|
57
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
|
|
3
|
-
import { sql, testConnection, closeConnection } from '../src/db';
|
|
4
|
-
|
|
5
|
-
console.log('Seeding database...');
|
|
6
|
-
|
|
7
|
-
const connected = await testConnection();
|
|
8
|
-
if (!connected) {
|
|
9
|
-
console.error('Failed to connect to database');
|
|
10
|
-
process.exit(1);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
console.log('Database connected\n');
|
|
14
|
-
|
|
15
|
-
try {
|
|
16
|
-
const [{ count }] = await sql`SELECT COUNT(*) as count FROM items`;
|
|
17
|
-
|
|
18
|
-
if (parseInt(count) > 0) {
|
|
19
|
-
console.log(`Database already has ${count} items - skipping seed`);
|
|
20
|
-
console.log('To re-seed, truncate the items table first\n');
|
|
21
|
-
process.exit(0);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
console.log('Inserting seed data...');
|
|
25
|
-
|
|
26
|
-
const seedItems = [
|
|
27
|
-
{
|
|
28
|
-
name: 'Example Item 1',
|
|
29
|
-
description: 'This is the first example item',
|
|
30
|
-
metadata: { category: 'example', priority: 'high' },
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
name: 'Example Item 2',
|
|
34
|
-
description: 'This is the second example item',
|
|
35
|
-
metadata: { category: 'sample', priority: 'medium' },
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
name: 'Example Item 3',
|
|
39
|
-
description: 'This is the third example item',
|
|
40
|
-
metadata: { category: 'demo', priority: 'low' },
|
|
41
|
-
},
|
|
42
|
-
];
|
|
43
|
-
|
|
44
|
-
for (const item of seedItems) {
|
|
45
|
-
await sql`
|
|
46
|
-
INSERT INTO items (name, description, metadata)
|
|
47
|
-
VALUES (${item.name}, ${item.description}, ${JSON.stringify(item.metadata)})
|
|
48
|
-
`;
|
|
49
|
-
console.log(` Created: ${item.name}`);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
console.log(`\nSeeded ${seedItems.length} items successfully`);
|
|
53
|
-
} catch (error) {
|
|
54
|
-
console.error('\nSeeding failed:', error);
|
|
55
|
-
process.exit(1);
|
|
56
|
-
} finally {
|
|
57
|
-
await closeConnection();
|
|
58
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import postgres from 'postgres';
|
|
2
|
-
|
|
3
|
-
function getConnectionString(): string {
|
|
4
|
-
if (process.env.DATABASE_URL) return process.env.DATABASE_URL;
|
|
5
|
-
|
|
6
|
-
const {
|
|
7
|
-
DB_USER = 'postgres',
|
|
8
|
-
DB_PASSWORD = '',
|
|
9
|
-
DB_HOST = 'localhost',
|
|
10
|
-
DB_PORT = '5432',
|
|
11
|
-
DB_NAME = 'managemcp_db',
|
|
12
|
-
} = process.env;
|
|
13
|
-
|
|
14
|
-
return `postgres://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}`;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export const sql = postgres(getConnectionString(), {
|
|
18
|
-
max: 10,
|
|
19
|
-
idle_timeout: 20,
|
|
20
|
-
connect_timeout: 10,
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
export async function testConnection(): Promise<boolean> {
|
|
24
|
-
try {
|
|
25
|
-
await sql`SELECT 1`;
|
|
26
|
-
return true;
|
|
27
|
-
} catch (error) {
|
|
28
|
-
console.error('Database connection failed:', error instanceof Error ? error.message : 'Unknown error');
|
|
29
|
-
return false;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export async function closeConnection(): Promise<void> {
|
|
34
|
-
await sql.end({ timeout: 5 });
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export type Sql = typeof sql;
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { getApiKey, getApiUrl } from './config';
|
|
2
|
-
|
|
3
|
-
export interface ApiItem {
|
|
4
|
-
id: string;
|
|
5
|
-
name: string;
|
|
6
|
-
description?: string;
|
|
7
|
-
metadata?: Record<string, unknown>;
|
|
8
|
-
createdAt?: string;
|
|
9
|
-
updatedAt?: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface ListOptions {
|
|
13
|
-
filter?: string;
|
|
14
|
-
limit?: number;
|
|
15
|
-
offset?: number;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface CreateItemData {
|
|
19
|
-
name: string;
|
|
20
|
-
description?: string;
|
|
21
|
-
metadata?: Record<string, unknown>;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export class ApiError extends Error {
|
|
25
|
-
constructor(
|
|
26
|
-
message: string,
|
|
27
|
-
public statusCode?: number,
|
|
28
|
-
public response?: unknown
|
|
29
|
-
) {
|
|
30
|
-
super(message);
|
|
31
|
-
this.name = 'ApiError';
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async function makeRequest<T>(
|
|
36
|
-
endpoint: string,
|
|
37
|
-
options: RequestInit = {}
|
|
38
|
-
): Promise<T> {
|
|
39
|
-
const apiUrl = await getApiUrl();
|
|
40
|
-
const apiKey = await getApiKey();
|
|
41
|
-
|
|
42
|
-
if (!apiUrl) {
|
|
43
|
-
throw new ApiError('API URL not configured. Run: managemcp config --set-api-url <url>');
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (!apiKey) {
|
|
47
|
-
throw new ApiError('API key not configured. Run: managemcp config --set-api-key <key>');
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const url = `${apiUrl}${endpoint}`;
|
|
51
|
-
const headers = {
|
|
52
|
-
'Authorization': `Bearer ${apiKey}`,
|
|
53
|
-
'Content-Type': 'application/json',
|
|
54
|
-
...options.headers,
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
try {
|
|
58
|
-
const response = await fetch(url, {
|
|
59
|
-
...options,
|
|
60
|
-
headers,
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
if (!response.ok) {
|
|
64
|
-
const errorData = await response.json().catch(() => null);
|
|
65
|
-
const message = errorData?.error || response.statusText;
|
|
66
|
-
throw new ApiError(`API request failed: ${message}`, response.status, errorData);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return await response.json();
|
|
70
|
-
} catch (error) {
|
|
71
|
-
if (error instanceof ApiError) throw error;
|
|
72
|
-
throw new ApiError(`Network error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export async function listItems(options: ListOptions = {}): Promise<ApiItem[]> {
|
|
77
|
-
const params = new URLSearchParams();
|
|
78
|
-
if (options.filter) params.set('filter', options.filter);
|
|
79
|
-
if (options.limit) params.set('limit', options.limit.toString());
|
|
80
|
-
if (options.offset) params.set('offset', options.offset.toString());
|
|
81
|
-
|
|
82
|
-
const query = params.toString();
|
|
83
|
-
const endpoint = `/items${query ? `?${query}` : ''}`;
|
|
84
|
-
|
|
85
|
-
const response = await makeRequest<{ items: ApiItem[] }>(endpoint);
|
|
86
|
-
return response.items;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export async function getItem(id: string): Promise<ApiItem> {
|
|
90
|
-
return await makeRequest<ApiItem>(`/items/${id}`);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export async function searchItems(query: string, limit = 10): Promise<ApiItem[]> {
|
|
94
|
-
const params = new URLSearchParams({
|
|
95
|
-
q: query,
|
|
96
|
-
limit: limit.toString(),
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
const response = await makeRequest<{ items: ApiItem[] }>(`/items/search?${params}`);
|
|
100
|
-
return response.items;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export async function createItem(data: CreateItemData): Promise<ApiItem> {
|
|
104
|
-
return await makeRequest<ApiItem>('/items', {
|
|
105
|
-
method: 'POST',
|
|
106
|
-
body: JSON.stringify(data),
|
|
107
|
-
});
|
|
108
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { readFile, writeFile } from 'fs/promises';
|
|
2
|
-
import { join } from 'path';
|
|
3
|
-
import { homedir } from 'os';
|
|
4
|
-
|
|
5
|
-
export interface Config {
|
|
6
|
-
apiUrl?: string;
|
|
7
|
-
apiKey?: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export const CONFIG_FILE = join(homedir(), '.managemcp.json');
|
|
11
|
-
|
|
12
|
-
export async function loadConfig(): Promise<Config> {
|
|
13
|
-
try {
|
|
14
|
-
const data = await readFile(CONFIG_FILE, 'utf-8');
|
|
15
|
-
return JSON.parse(data);
|
|
16
|
-
} catch (error) {
|
|
17
|
-
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return {};
|
|
18
|
-
throw error;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export async function saveConfig(config: Config): Promise<void> {
|
|
23
|
-
await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8');
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export async function getApiKey(): Promise<string | undefined> {
|
|
27
|
-
const config = await loadConfig();
|
|
28
|
-
return config.apiKey || process.env.API_KEY;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export async function getApiUrl(): Promise<string | undefined> {
|
|
32
|
-
const config = await loadConfig();
|
|
33
|
-
return config.apiUrl || process.env.API_URL;
|
|
34
|
-
}
|