@hasna/skills 0.1.32 → 0.1.34
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/README.md +13 -256
- package/skills/browse/SKILL.md +24 -45
- 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/CLAUDE.md +6 -39
- package/skills/deepresearch/README.md +15 -150
- package/skills/deepresearch/SKILL.md +23 -32
- 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/README.md +13 -183
- 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/CLAUDE.md +6 -17
- package/skills/webcrawling/README.md +13 -66
- package/skills/webcrawling/package.json +4 -32
- package/skills/browse/.env.example +0 -14
- package/skills/deepresearch/.env.example +0 -9
- 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/image/.env.example +0 -12
- package/skills/image/PROJECT_OVERVIEW.md +0 -216
- package/skills/image/QUICKSTART.md +0 -111
- 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
- package/skills/transcript/.env.example +0 -10
- package/skills/webcrawling/.env.example +0 -2
|
@@ -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
|
-
}
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import { readFile, writeFile, readdir, unlink, mkdir } from 'fs/promises';
|
|
2
|
-
import { join } from 'path';
|
|
3
|
-
import { homedir } from 'os';
|
|
4
|
-
import { getItem } from './api-client';
|
|
5
|
-
|
|
6
|
-
export type Target = 'claude' | 'codex';
|
|
7
|
-
export type ConflictStrategy = 'skip' | 'overwrite';
|
|
8
|
-
|
|
9
|
-
export interface InstallResult {
|
|
10
|
-
success: boolean;
|
|
11
|
-
message: string;
|
|
12
|
-
itemId: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface InstalledItem {
|
|
16
|
-
id: string;
|
|
17
|
-
name: string;
|
|
18
|
-
target: Target;
|
|
19
|
-
installedAt: string;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const CLAUDE_DIR = join(homedir(), '.claude', 'skills');
|
|
23
|
-
const CODEX_DIR = join(homedir(), '.codex', 'skills');
|
|
24
|
-
|
|
25
|
-
function getTargetDir(target: Target): string {
|
|
26
|
-
return target === 'claude' ? CLAUDE_DIR : CODEX_DIR;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
async function ensureDir(dir: string): Promise<void> {
|
|
30
|
-
await mkdir(dir, { recursive: true });
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async function writeItemFile(
|
|
34
|
-
target: Target,
|
|
35
|
-
itemId: string,
|
|
36
|
-
content: string,
|
|
37
|
-
strategy: ConflictStrategy
|
|
38
|
-
): Promise<boolean> {
|
|
39
|
-
const dir = getTargetDir(target);
|
|
40
|
-
await ensureDir(dir);
|
|
41
|
-
|
|
42
|
-
const filePath = join(dir, `${itemId}.json`);
|
|
43
|
-
|
|
44
|
-
try {
|
|
45
|
-
await readFile(filePath);
|
|
46
|
-
if (strategy === 'skip') return false;
|
|
47
|
-
} catch {
|
|
48
|
-
// File doesn't exist, proceed
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
await writeFile(filePath, content, 'utf-8');
|
|
52
|
-
return true;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
async function installToTarget(
|
|
56
|
-
target: Target,
|
|
57
|
-
itemId: string,
|
|
58
|
-
strategy: ConflictStrategy = 'skip'
|
|
59
|
-
): Promise<InstallResult> {
|
|
60
|
-
try {
|
|
61
|
-
const item = await getItem(itemId);
|
|
62
|
-
const content = JSON.stringify(item, null, 2);
|
|
63
|
-
const written = await writeItemFile(target, itemId, content, strategy);
|
|
64
|
-
|
|
65
|
-
const targetName = target.charAt(0).toUpperCase() + target.slice(1);
|
|
66
|
-
return {
|
|
67
|
-
success: true,
|
|
68
|
-
message: written
|
|
69
|
-
? `Installed to ${targetName}: ${item.name}`
|
|
70
|
-
: `Skipped (already exists): ${item.name}`,
|
|
71
|
-
itemId,
|
|
72
|
-
};
|
|
73
|
-
} catch (error) {
|
|
74
|
-
return {
|
|
75
|
-
success: false,
|
|
76
|
-
message: `Failed to install to ${target}: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
77
|
-
itemId,
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export async function install(
|
|
83
|
-
itemId: string,
|
|
84
|
-
target: Target | 'both' = 'both',
|
|
85
|
-
strategy: ConflictStrategy = 'skip'
|
|
86
|
-
): Promise<InstallResult> {
|
|
87
|
-
if (target === 'both') {
|
|
88
|
-
const claudeResult = await installToTarget('claude', itemId, strategy);
|
|
89
|
-
const codexResult = await installToTarget('codex', itemId, strategy);
|
|
90
|
-
|
|
91
|
-
return {
|
|
92
|
-
success: claudeResult.success && codexResult.success,
|
|
93
|
-
message: `${claudeResult.message}\n${codexResult.message}`,
|
|
94
|
-
itemId,
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return await installToTarget(target, itemId, strategy);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export async function clearTarget(target?: Target | 'both'): Promise<void> {
|
|
102
|
-
const targets: Target[] = target === 'both' || !target ? ['claude', 'codex'] : [target];
|
|
103
|
-
|
|
104
|
-
for (const t of targets) {
|
|
105
|
-
const dir = getTargetDir(t);
|
|
106
|
-
try {
|
|
107
|
-
const files = await readdir(dir);
|
|
108
|
-
const jsonFiles = files.filter((f) => f.endsWith('.json')).map((f) => unlink(join(dir, f)));
|
|
109
|
-
await Promise.all(jsonFiles);
|
|
110
|
-
} catch (error) {
|
|
111
|
-
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export async function listInstalled(target?: Target | 'both'): Promise<InstalledItem[]> {
|
|
117
|
-
const targets: Target[] = target === 'both' || !target ? ['claude', 'codex'] : [target];
|
|
118
|
-
const items: InstalledItem[] = [];
|
|
119
|
-
|
|
120
|
-
for (const t of targets) {
|
|
121
|
-
const dir = getTargetDir(t);
|
|
122
|
-
try {
|
|
123
|
-
const files = await readdir(dir);
|
|
124
|
-
for (const file of files.filter((f) => f.endsWith('.json'))) {
|
|
125
|
-
const content = await readFile(join(dir, file), 'utf-8');
|
|
126
|
-
const item = JSON.parse(content);
|
|
127
|
-
items.push({
|
|
128
|
-
id: item.id,
|
|
129
|
-
name: item.name,
|
|
130
|
-
target: t,
|
|
131
|
-
installedAt: file,
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
} catch (error) {
|
|
135
|
-
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return items;
|
|
140
|
-
}
|
|
@@ -1,119 +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 = "managemcp";
|
|
6
|
-
const SERVICE_DIR_NAME = `.${SERVICE_NAME}`;
|
|
7
|
-
|
|
8
|
-
export function getServiceDir(): string {
|
|
9
|
-
return join(homedir(), SERVICE_DIR_NAME);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function getLogFilePath(): string {
|
|
13
|
-
return join(getServiceDir(), `${SERVICE_NAME}.log`);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function getReadmePath(): string {
|
|
17
|
-
return join(getServiceDir(), "README.md");
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function isServiceDirInstalled(): boolean {
|
|
21
|
-
return existsSync(getServiceDir());
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function ensureServiceDir(): void {
|
|
25
|
-
if (!isServiceDirInstalled()) {
|
|
26
|
-
initServiceDir();
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function initServiceDir(force = false): { success: boolean; path: string; message: string } {
|
|
31
|
-
const serviceDir = getServiceDir();
|
|
32
|
-
|
|
33
|
-
if (existsSync(serviceDir) && !force) {
|
|
34
|
-
return {
|
|
35
|
-
success: false,
|
|
36
|
-
path: serviceDir,
|
|
37
|
-
message: "Already installed. Use --force to reinstall.",
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
mkdirSync(serviceDir, { recursive: true });
|
|
42
|
-
|
|
43
|
-
const readmeContent = generateReadme();
|
|
44
|
-
writeFileSync(getReadmePath(), readmeContent);
|
|
45
|
-
|
|
46
|
-
const logHeader = `# ${SERVICE_NAME} log file
|
|
47
|
-
# Created: ${new Date().toISOString()}
|
|
48
|
-
# This file contains activity history and logs.
|
|
49
|
-
# -------------------------------------------------------
|
|
50
|
-
|
|
51
|
-
`;
|
|
52
|
-
writeFileSync(getLogFilePath(), logHeader);
|
|
53
|
-
|
|
54
|
-
return {
|
|
55
|
-
success: true,
|
|
56
|
-
path: serviceDir,
|
|
57
|
-
message: `Installed at ${serviceDir}`,
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function generateReadme(): string {
|
|
62
|
-
return `# .${SERVICE_NAME}
|
|
63
|
-
|
|
64
|
-
This directory contains local configuration and logs for \`${SERVICE_NAME}\`.
|
|
65
|
-
|
|
66
|
-
## Files
|
|
67
|
-
|
|
68
|
-
| Path | Description |
|
|
69
|
-
|------|-------------|
|
|
70
|
-
| \`${SERVICE_NAME}.log\` | Activity log |
|
|
71
|
-
| \`README.md\` | This file |
|
|
72
|
-
|
|
73
|
-
## What is ${SERVICE_NAME}?
|
|
74
|
-
|
|
75
|
-
\`${SERVICE_NAME}\` is a tool for managing MCP server connections.
|
|
76
|
-
|
|
77
|
-
## Log File
|
|
78
|
-
|
|
79
|
-
The \`${SERVICE_NAME}.log\` file contains:
|
|
80
|
-
- Activity logs
|
|
81
|
-
- Error logs
|
|
82
|
-
- API activity
|
|
83
|
-
|
|
84
|
-
## More Information
|
|
85
|
-
|
|
86
|
-
For full documentation, run:
|
|
87
|
-
|
|
88
|
-
\`\`\`bash
|
|
89
|
-
${SERVICE_NAME} --help
|
|
90
|
-
\`\`\`
|
|
91
|
-
`;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
95
|
-
|
|
96
|
-
export function logToFile(message: string, level: LogLevel = "info"): void {
|
|
97
|
-
ensureServiceDir();
|
|
98
|
-
|
|
99
|
-
const timestamp = new Date().toISOString();
|
|
100
|
-
const logLine = `[${timestamp}] [${level.toUpperCase()}] ${message}\n`;
|
|
101
|
-
|
|
102
|
-
try {
|
|
103
|
-
appendFileSync(getLogFilePath(), logLine);
|
|
104
|
-
} catch {
|
|
105
|
-
// Silently fail
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export function logInfo(message: string): void {
|
|
110
|
-
logToFile(message, "info");
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export function logError(message: string): void {
|
|
114
|
-
logToFile(message, "error");
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export function logWarn(message: string): void {
|
|
118
|
-
logToFile(message, "warn");
|
|
119
|
-
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
/**
|
|
3
|
-
* Script to buy a Twilio phone number for an agent
|
|
4
|
-
* Usage: bun run scripts/buy-number.ts [agent_name] [area_code]
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import twilio from "twilio";
|
|
8
|
-
|
|
9
|
-
const CONFIG = {
|
|
10
|
-
accountSid: process.env.TWILIO_ACCOUNT_SID || "",
|
|
11
|
-
authToken: process.env.TWILIO_AUTH_TOKEN || "",
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
const twilioClient = twilio(CONFIG.accountSid, CONFIG.authToken);
|
|
15
|
-
|
|
16
|
-
function capitalize(str: string): string {
|
|
17
|
-
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async function buyPhoneNumber() {
|
|
21
|
-
const agentName = process.argv[2] || "claude";
|
|
22
|
-
const areaCode = process.argv[3] || "302"; // Default to Delaware
|
|
23
|
-
|
|
24
|
-
const friendlyName = `${capitalize(agentName)} (AI Agent)`;
|
|
25
|
-
|
|
26
|
-
console.log(`Searching for available numbers with area code ${areaCode}...`);
|
|
27
|
-
|
|
28
|
-
try {
|
|
29
|
-
// Search for available numbers
|
|
30
|
-
const availableNumbers = await twilioClient
|
|
31
|
-
.availablePhoneNumbers("US")
|
|
32
|
-
.local.list({ areaCode: parseInt(areaCode), limit: 5 });
|
|
33
|
-
|
|
34
|
-
if (availableNumbers.length === 0) {
|
|
35
|
-
console.error(`No available phone numbers found with area code ${areaCode}`);
|
|
36
|
-
process.exit(1);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
console.log(`Found ${availableNumbers.length} available numbers:`);
|
|
40
|
-
availableNumbers.forEach((n, i) => {
|
|
41
|
-
console.log(` ${i + 1}. ${n.phoneNumber}`);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
// Purchase the first available number
|
|
45
|
-
const phoneNumber = availableNumbers[0].phoneNumber;
|
|
46
|
-
console.log(`\nPurchasing ${phoneNumber} for ${friendlyName}...`);
|
|
47
|
-
|
|
48
|
-
const purchased = await twilioClient.incomingPhoneNumbers.create({
|
|
49
|
-
phoneNumber,
|
|
50
|
-
friendlyName,
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
console.log("\nPhone number purchased successfully!");
|
|
54
|
-
console.log(` SID: ${purchased.sid}`);
|
|
55
|
-
console.log(` Phone Number: ${purchased.phoneNumber}`);
|
|
56
|
-
console.log(` Friendly Name: ${purchased.friendlyName}`);
|
|
57
|
-
} catch (error: any) {
|
|
58
|
-
console.error("Failed to buy phone number:", error.message);
|
|
59
|
-
process.exit(1);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
buyPhoneNumber();
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
/**
|
|
3
|
-
* Test script to send SMS via Twilio
|
|
4
|
-
* Usage: bun run scripts/test-send.ts [from_number] [to_number] [message]
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import twilio from "twilio";
|
|
8
|
-
|
|
9
|
-
const CONFIG = {
|
|
10
|
-
accountSid: process.env.TWILIO_ACCOUNT_SID || "",
|
|
11
|
-
authToken: process.env.TWILIO_AUTH_TOKEN || "",
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
const twilioClient = twilio(CONFIG.accountSid, CONFIG.authToken);
|
|
15
|
-
|
|
16
|
-
function formatPhoneNumber(phone: string): string {
|
|
17
|
-
const digits = phone.replace(/\D/g, "");
|
|
18
|
-
if (digits.length === 10) {
|
|
19
|
-
return `+1${digits}`;
|
|
20
|
-
}
|
|
21
|
-
if (digits.length === 11 && digits.startsWith("1")) {
|
|
22
|
-
return `+${digits}`;
|
|
23
|
-
}
|
|
24
|
-
if (!phone.startsWith("+")) {
|
|
25
|
-
return `+${digits}`;
|
|
26
|
-
}
|
|
27
|
-
return phone;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
async function sendTestSms() {
|
|
31
|
-
const fromNumber = process.argv[2];
|
|
32
|
-
const toNumber = process.argv[3];
|
|
33
|
-
const messageBody =
|
|
34
|
-
process.argv[4] ||
|
|
35
|
-
`Hello from the SMS agent!\n\nThis is a test SMS sent at ${new Date().toISOString()}.`;
|
|
36
|
-
|
|
37
|
-
if (!fromNumber || !toNumber) {
|
|
38
|
-
console.error("Usage: bun run scripts/test-send.ts <from_number> <to_number> [message]");
|
|
39
|
-
console.error("\nAvailable numbers:");
|
|
40
|
-
const numbers = await twilioClient.incomingPhoneNumbers.list();
|
|
41
|
-
numbers.forEach((n) => {
|
|
42
|
-
console.log(` ${n.phoneNumber} - ${n.friendlyName}`);
|
|
43
|
-
});
|
|
44
|
-
process.exit(1);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
console.log(`Sending test SMS from ${formatPhoneNumber(fromNumber)} to ${formatPhoneNumber(toNumber)}...`);
|
|
48
|
-
|
|
49
|
-
try {
|
|
50
|
-
const message = await twilioClient.messages.create({
|
|
51
|
-
body: messageBody,
|
|
52
|
-
from: formatPhoneNumber(fromNumber),
|
|
53
|
-
to: formatPhoneNumber(toNumber),
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
console.log("SMS sent successfully!");
|
|
57
|
-
console.log(` Message SID: ${message.sid}`);
|
|
58
|
-
console.log(` Status: ${message.status}`);
|
|
59
|
-
console.log(` From: ${message.from}`);
|
|
60
|
-
console.log(` To: ${message.to}`);
|
|
61
|
-
} catch (error: any) {
|
|
62
|
-
console.error("Failed to send SMS:", error.message);
|
|
63
|
-
process.exit(1);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
sendTestSms();
|