50c 1.5.0 → 2.0.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/README.md +49 -235
- package/bin/50c.js +210 -258
- package/lib/config.js +185 -0
- package/lib/core/tools.js +107 -0
- package/lib/index.js +166 -0
- package/lib/packs/beacon.js +224 -0
- package/lib/packs/cf.js +156 -0
- package/lib/packs/labs.js +188 -0
- package/lib/packs/labs_plus.js +246 -0
- package/lib/packs/ux.js +76 -0
- package/lib/packs/whm.js +228 -0
- package/lib/packs/wp.js +82 -0
- package/lib/packs.js +406 -0
- package/lib/vault.js +354 -0
- package/package.json +25 -11
- package/LICENSE +0 -31
package/bin/50c.js
CHANGED
|
@@ -1,289 +1,241 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const os = require('os');
|
|
7
|
-
|
|
8
|
-
const API_ENDPOINT = process.env.FIFTYC_ENDPOINT || 'https://50c.ai';
|
|
9
|
-
const MCP_ENDPOINT = API_ENDPOINT + '/mcp';
|
|
10
|
-
const API_KEY = process.env.FIFTYC_API_KEY;
|
|
11
|
-
|
|
12
|
-
// Version
|
|
13
|
-
if (process.argv.includes('--version') || process.argv.includes('-v')) {
|
|
14
|
-
console.log(require('../package.json').version);
|
|
15
|
-
process.exit(0);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// Install MCP to IDE
|
|
19
|
-
if (process.argv.includes('--install') || process.argv.includes('install')) {
|
|
20
|
-
installMCP();
|
|
21
|
-
process.exit(0);
|
|
22
|
-
}
|
|
2
|
+
/**
|
|
3
|
+
* 50c CLI & MCP Server
|
|
4
|
+
* The AI toolkit - one package, all tools, works everywhere
|
|
5
|
+
*/
|
|
23
6
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
showHelp();
|
|
27
|
-
process.exit(0);
|
|
28
|
-
}
|
|
7
|
+
const readline = require('readline');
|
|
8
|
+
const lib = require('../lib');
|
|
29
9
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
const appData = process.env.APPDATA || path.join(home, 'AppData', 'Roaming');
|
|
34
|
-
const localAppData = process.env.LOCALAPPDATA || path.join(home, 'AppData', 'Local');
|
|
35
|
-
|
|
36
|
-
const ides = [
|
|
37
|
-
// Desktop IDEs
|
|
38
|
-
{ name: 'Claude Desktop', path: isWin ? path.join(appData, 'Claude', 'claude_desktop_config.json') : path.join(home, '.claude', 'claude_desktop_config.json'), key: 'mcpServers' },
|
|
39
|
-
{ name: 'Cursor', path: path.join(home, '.cursor', 'mcp.json'), key: 'mcpServers' },
|
|
40
|
-
{ name: 'Windsurf', path: path.join(home, '.codeium', 'windsurf', 'mcp_config.json'), key: 'mcpServers' },
|
|
41
|
-
{ name: 'VS Code', path: path.join(home, '.vscode', 'mcp.json'), key: 'servers' },
|
|
42
|
-
{ name: 'VS Code Insiders', path: path.join(home, '.vscode-insiders', 'mcp.json'), key: 'servers' },
|
|
43
|
-
{ name: 'VSCodium', path: path.join(home, '.vscodium', 'mcp.json'), key: 'servers' },
|
|
44
|
-
{ name: 'Verdent', path: path.join(home, '.verdent', 'mcp.json'), key: 'mcpServers' },
|
|
45
|
-
// Roo Code / Continue / Cline
|
|
46
|
-
{ name: 'Roo Code', path: path.join(home, '.roo-code', 'mcp.json'), key: 'mcpServers' },
|
|
47
|
-
{ name: 'Roo Code (AppData)', path: isWin ? path.join(appData, 'Roo-Code', 'mcp.json') : null, key: 'mcpServers' },
|
|
48
|
-
{ name: 'Continue', path: path.join(home, '.continue', 'mcp.json'), key: 'mcpServers' },
|
|
49
|
-
{ name: 'Cline', path: path.join(home, '.cline', 'mcp.json'), key: 'mcpServers' },
|
|
50
|
-
{ name: 'Cline (VS Code)', path: isWin ? path.join(appData, 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json') : path.join(home, '.config', 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json'), key: 'mcpServers' },
|
|
51
|
-
// JetBrains IDEs
|
|
52
|
-
{ name: 'JetBrains', path: path.join(home, '.jb-mcp', 'mcp.json'), key: 'mcpServers' },
|
|
53
|
-
{ name: 'IntelliJ IDEA', path: isWin ? path.join(appData, 'JetBrains', 'IntelliJIdea', 'mcp.json') : path.join(home, '.config', 'JetBrains', 'mcp.json'), key: 'mcpServers' },
|
|
54
|
-
// Augment
|
|
55
|
-
{ name: 'Augment', path: path.join(home, '.augment', 'mcp.json'), key: 'mcpServers' },
|
|
56
|
-
// Zed
|
|
57
|
-
{ name: 'Zed', path: path.join(home, '.config', 'zed', 'settings.json'), key: 'context_servers', nested: true },
|
|
58
|
-
].filter(ide => ide.path); // Remove null paths
|
|
59
|
-
|
|
60
|
-
const mcpEntry = {
|
|
61
|
-
command: 'npx',
|
|
62
|
-
args: ['-y', '50c'],
|
|
63
|
-
env: { FIFTYC_API_KEY: API_KEY || '<YOUR_API_KEY>' }
|
|
64
|
-
};
|
|
10
|
+
// MCP Protocol Handler
|
|
11
|
+
async function handleMCP(req) {
|
|
12
|
+
const { id, method, params } = req;
|
|
65
13
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
for (const ide of ides) {
|
|
75
|
-
try {
|
|
76
|
-
let config = {};
|
|
77
|
-
const dir = path.dirname(ide.path);
|
|
78
|
-
|
|
79
|
-
if (fs.existsSync(ide.path)) {
|
|
80
|
-
config = JSON.parse(fs.readFileSync(ide.path, 'utf8'));
|
|
81
|
-
} else if (fs.existsSync(dir)) {
|
|
82
|
-
// Dir exists but no config file - create it
|
|
83
|
-
} else {
|
|
84
|
-
continue; // IDE not installed
|
|
14
|
+
if (method === 'initialize') {
|
|
15
|
+
return {
|
|
16
|
+
jsonrpc: '2.0',
|
|
17
|
+
id,
|
|
18
|
+
result: {
|
|
19
|
+
protocolVersion: '2024-11-05',
|
|
20
|
+
capabilities: { tools: { listChanged: true } },
|
|
21
|
+
serverInfo: { name: '50c', version: '2.0.0' }
|
|
85
22
|
}
|
|
86
|
-
|
|
87
|
-
if (!config[ide.key]) config[ide.key] = {};
|
|
88
|
-
if (config[ide.key]['50c']) {
|
|
89
|
-
console.log(`[skip] ${ide.name} - already configured`);
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
config[ide.key]['50c'] = ide.name === 'Zed' ? zedEntry : mcpEntry;
|
|
94
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
95
|
-
fs.writeFileSync(ide.path, JSON.stringify(config, null, 2));
|
|
96
|
-
installed.push(ide.name);
|
|
97
|
-
console.log(`[done] ${ide.name} - added 50c`);
|
|
98
|
-
} catch (e) {
|
|
99
|
-
// Skip silently
|
|
100
|
-
}
|
|
23
|
+
};
|
|
101
24
|
}
|
|
102
25
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
console.log(`Installed to: ${installed.join(', ')}`);
|
|
106
|
-
console.log('');
|
|
107
|
-
if (!API_KEY) {
|
|
108
|
-
console.log('Next: Set FIFTYC_API_KEY in the config or as env var');
|
|
109
|
-
console.log('Get key at: https://50c.ai');
|
|
110
|
-
} else {
|
|
111
|
-
console.log('Restart your IDE to activate 50c tools.');
|
|
112
|
-
}
|
|
113
|
-
} else {
|
|
114
|
-
console.log('No local IDEs detected.');
|
|
26
|
+
if (method === 'notifications/initialized') {
|
|
27
|
+
return null;
|
|
115
28
|
}
|
|
116
29
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
console.log(' Use: npx -y 50c (command) with FIFTYC_API_KEY env');
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function showHelp() {
|
|
124
|
-
console.log(`
|
|
125
|
-
50c - AI Augmentation CLI & MCP Server
|
|
126
|
-
|
|
127
|
-
QUICK START:
|
|
128
|
-
npx 50c install Auto-configure MCP for your IDE
|
|
129
|
-
|
|
130
|
-
USAGE:
|
|
131
|
-
50c <command> <input> Direct CLI mode
|
|
132
|
-
50c MCP mode (JSON-RPC via stdin)
|
|
133
|
-
|
|
134
|
-
COMMANDS:
|
|
135
|
-
install Add 50c to all detected IDEs
|
|
136
|
-
genius <problem> Deep problem solving ($0.50)
|
|
137
|
-
hints <topic> 5 brutal 2-word hints ($0.05)
|
|
138
|
-
hints+ <topic> 10 expanded hints ($0.10)
|
|
139
|
-
vibe <working_on> 3 unconventional ideas ($0.05)
|
|
140
|
-
one-liner <product> Elevator pitch in 8 words ($0.02)
|
|
141
|
-
roast <code> Brutal code review ($0.05)
|
|
142
|
-
name-it <does> 5 names + domain check ($0.03)
|
|
143
|
-
price-it <product> SaaS pricing strategy ($0.05)
|
|
144
|
-
compute <code> Execute Python code ($0.02)
|
|
145
|
-
|
|
146
|
-
EXAMPLES:
|
|
147
|
-
npx 50c install
|
|
148
|
-
50c genius "How do I scale PostgreSQL?"
|
|
149
|
-
50c hints "api design"
|
|
150
|
-
|
|
151
|
-
ENVIRONMENT:
|
|
152
|
-
FIFTYC_API_KEY Your API key (required)
|
|
153
|
-
|
|
154
|
-
MCP MODE:
|
|
155
|
-
Run without arguments for IDE integration (Claude Desktop, Cursor, etc.)
|
|
156
|
-
`);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// CLI commands
|
|
160
|
-
const COMMANDS = ['genius', 'hints', 'hints+', 'vibe', 'compute', 'one-liner', 'roast', 'name-it', 'price-it', 'mind-opener'];
|
|
161
|
-
const command = process.argv[2];
|
|
162
|
-
const input = process.argv.slice(3).join(' ');
|
|
163
|
-
|
|
164
|
-
// Check API key (not needed for install/help/version)
|
|
165
|
-
if (!API_KEY && command && COMMANDS.includes(command)) {
|
|
166
|
-
console.error('Error: FIFTYC_API_KEY environment variable required');
|
|
167
|
-
console.error('Get your key at: https://50c.ai');
|
|
168
|
-
process.exit(1);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (command && COMMANDS.includes(command)) {
|
|
172
|
-
if (!input) {
|
|
173
|
-
console.error(`Error: ${command} requires input`);
|
|
174
|
-
process.exit(1);
|
|
30
|
+
if (method === 'tools/list') {
|
|
31
|
+
const tools = await lib.getTools();
|
|
32
|
+
return { jsonrpc: '2.0', id, result: { tools } };
|
|
175
33
|
}
|
|
176
34
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
}
|
|
187
|
-
startMCPMode();
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
async function runCLI(cmd, query) {
|
|
191
|
-
// Map CLI commands to MCP tool names
|
|
192
|
-
const toolMap = {
|
|
193
|
-
'genius': { name: 'genius', arg: 'problem' },
|
|
194
|
-
'hints': { name: 'hints', arg: 'query' },
|
|
195
|
-
'hints+': { name: 'hints_plus', arg: 'query' },
|
|
196
|
-
'vibe': { name: 'quick_vibe', arg: 'working_on' },
|
|
197
|
-
'compute': { name: 'compute', arg: 'code' },
|
|
198
|
-
'one-liner': { name: 'one_liner', arg: 'product' },
|
|
199
|
-
'roast': { name: 'roast', arg: 'code' },
|
|
200
|
-
'name-it': { name: 'name_it', arg: 'does' },
|
|
201
|
-
'price-it': { name: 'price_it', arg: 'product' },
|
|
202
|
-
'mind-opener': { name: 'mind_opener', arg: 'problem' }
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
const tool = toolMap[cmd];
|
|
206
|
-
if (!tool) throw new Error(`Unknown command: ${cmd}`);
|
|
35
|
+
if (method === 'tools/call') {
|
|
36
|
+
const result = await lib.handleTool(params?.name, params?.arguments || {});
|
|
37
|
+
return {
|
|
38
|
+
jsonrpc: '2.0',
|
|
39
|
+
id,
|
|
40
|
+
result: {
|
|
41
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }]
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
207
45
|
|
|
208
|
-
|
|
46
|
+
return {
|
|
209
47
|
jsonrpc: '2.0',
|
|
210
|
-
id
|
|
211
|
-
|
|
212
|
-
params: {
|
|
213
|
-
name: tool.name,
|
|
214
|
-
arguments: { [tool.arg]: query }
|
|
215
|
-
}
|
|
48
|
+
id,
|
|
49
|
+
error: { code: -32601, message: 'Method not found' }
|
|
216
50
|
};
|
|
217
|
-
|
|
218
|
-
const response = await callRemoteMCP(request);
|
|
219
|
-
|
|
220
|
-
if (response.error) {
|
|
221
|
-
throw new Error(response.error.message || JSON.stringify(response.error));
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
return response.result?.content?.[0]?.text || 'No output';
|
|
225
51
|
}
|
|
226
52
|
|
|
227
|
-
|
|
228
|
-
|
|
53
|
+
// MCP Server Mode
|
|
54
|
+
async function runMCP() {
|
|
55
|
+
const rl = readline.createInterface({
|
|
56
|
+
input: process.stdin,
|
|
57
|
+
output: process.stdout,
|
|
58
|
+
terminal: false
|
|
59
|
+
});
|
|
229
60
|
|
|
230
61
|
rl.on('line', async (line) => {
|
|
231
62
|
try {
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
} catch (e) {
|
|
238
|
-
process.stdout.write(JSON.stringify({
|
|
63
|
+
const req = JSON.parse(line);
|
|
64
|
+
const res = await handleMCP(req);
|
|
65
|
+
if (res) console.log(JSON.stringify(res));
|
|
66
|
+
} catch (err) {
|
|
67
|
+
console.log(JSON.stringify({
|
|
239
68
|
jsonrpc: '2.0',
|
|
240
69
|
id: null,
|
|
241
|
-
error: { code: -
|
|
242
|
-
})
|
|
70
|
+
error: { code: -32700, message: 'Parse error' }
|
|
71
|
+
}));
|
|
243
72
|
}
|
|
244
73
|
});
|
|
245
74
|
}
|
|
246
75
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
76
|
+
// CLI Mode
|
|
77
|
+
async function runCLI(args) {
|
|
78
|
+
const cmd = args[0];
|
|
79
|
+
const cmdArgs = args.slice(1);
|
|
80
|
+
|
|
81
|
+
switch (cmd) {
|
|
82
|
+
case 'status':
|
|
83
|
+
console.log(JSON.stringify(await lib.getStatus(), null, 2));
|
|
84
|
+
break;
|
|
85
|
+
|
|
86
|
+
case 'discover':
|
|
87
|
+
console.log(JSON.stringify(await lib.packs.discover(), null, 2));
|
|
88
|
+
break;
|
|
89
|
+
|
|
90
|
+
case 'enable':
|
|
91
|
+
console.log(JSON.stringify(await lib.packs.enablePack(cmdArgs[0]), null, 2));
|
|
92
|
+
break;
|
|
93
|
+
|
|
94
|
+
case 'disable':
|
|
95
|
+
console.log(JSON.stringify(await lib.packs.disablePack(cmdArgs[0]), null, 2));
|
|
96
|
+
break;
|
|
97
|
+
|
|
98
|
+
case 'packs':
|
|
99
|
+
console.log(JSON.stringify(await lib.packs.listPacks(), null, 2));
|
|
100
|
+
break;
|
|
101
|
+
|
|
102
|
+
// Vault commands
|
|
103
|
+
case 'vault':
|
|
104
|
+
const vaultCmd = cmdArgs[0];
|
|
105
|
+
const vaultArgs = cmdArgs.slice(1);
|
|
106
|
+
|
|
107
|
+
if (vaultCmd === 'init') {
|
|
108
|
+
const passphrase = process.env.VAULT_PASSPHRASE || vaultArgs[0];
|
|
109
|
+
if (!passphrase) {
|
|
110
|
+
console.error('Usage: 50c vault init <passphrase>');
|
|
111
|
+
console.error('Or set VAULT_PASSPHRASE env var');
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
console.log(JSON.stringify(await lib.vault.init(passphrase), null, 2));
|
|
262
115
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
116
|
+
else if (vaultCmd === 'unlock') {
|
|
117
|
+
const passphrase = process.env.VAULT_PASSPHRASE || vaultArgs[0];
|
|
118
|
+
if (!passphrase) {
|
|
119
|
+
console.error('Usage: 50c vault unlock <passphrase>');
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
console.log(JSON.stringify(await lib.vault.unlock(passphrase), null, 2));
|
|
123
|
+
}
|
|
124
|
+
else if (vaultCmd === 'lock') {
|
|
125
|
+
console.log(JSON.stringify(lib.vault.lock(), null, 2));
|
|
126
|
+
}
|
|
127
|
+
else if (vaultCmd === 'yolo') {
|
|
128
|
+
const passphrase = process.env.VAULT_PASSPHRASE || vaultArgs[0];
|
|
129
|
+
if (!passphrase) {
|
|
130
|
+
console.error('Usage: 50c vault yolo <passphrase>');
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
console.log(JSON.stringify(await lib.vault.yolo(passphrase), null, 2));
|
|
134
|
+
}
|
|
135
|
+
else if (vaultCmd === 'add') {
|
|
136
|
+
const [name, ...valueParts] = vaultArgs;
|
|
137
|
+
const value = valueParts.join(' ');
|
|
138
|
+
if (!name || !value) {
|
|
139
|
+
console.error('Usage: 50c vault add <name> <value>');
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
console.log(JSON.stringify(await lib.vault.add(name, value), null, 2));
|
|
143
|
+
}
|
|
144
|
+
else if (vaultCmd === 'get') {
|
|
145
|
+
const name = vaultArgs[0];
|
|
146
|
+
if (!name) {
|
|
147
|
+
console.error('Usage: 50c vault get <name>');
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
269
150
|
try {
|
|
270
|
-
|
|
151
|
+
console.log(await lib.vault.get(name));
|
|
271
152
|
} catch (e) {
|
|
272
|
-
|
|
153
|
+
console.error(e.message);
|
|
154
|
+
process.exit(1);
|
|
273
155
|
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
156
|
+
}
|
|
157
|
+
else if (vaultCmd === 'list') {
|
|
158
|
+
const namespace = vaultArgs[0];
|
|
159
|
+
const list = await lib.vault.list(namespace);
|
|
160
|
+
list.forEach(n => console.log(n));
|
|
161
|
+
}
|
|
162
|
+
else if (vaultCmd === 'delete' || vaultCmd === 'rm') {
|
|
163
|
+
const name = vaultArgs[0];
|
|
164
|
+
if (!name) {
|
|
165
|
+
console.error('Usage: 50c vault delete <name>');
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
console.log(JSON.stringify(await lib.vault.remove(name), null, 2));
|
|
169
|
+
}
|
|
170
|
+
else if (vaultCmd === 'status') {
|
|
171
|
+
console.log(JSON.stringify(await lib.vault.status(), null, 2));
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
console.log(`50c vault commands:
|
|
175
|
+
init <passphrase> Initialize vault
|
|
176
|
+
unlock <passphrase> Unlock for session
|
|
177
|
+
lock Lock immediately
|
|
178
|
+
yolo <passphrase> Stay unlocked (dev mode)
|
|
179
|
+
add <name> <value> Add credential
|
|
180
|
+
get <name> Get credential
|
|
181
|
+
list [namespace] List credentials
|
|
182
|
+
delete <name> Delete credential
|
|
183
|
+
status Check vault status`);
|
|
184
|
+
}
|
|
185
|
+
break;
|
|
186
|
+
|
|
187
|
+
case 'help':
|
|
188
|
+
default:
|
|
189
|
+
console.log(`50c - The AI Toolkit
|
|
190
|
+
|
|
191
|
+
Usage:
|
|
192
|
+
50c Start MCP server (for AI tools)
|
|
193
|
+
50c status Show status
|
|
194
|
+
50c discover Show available packs
|
|
195
|
+
50c enable <pack> Enable a pack
|
|
196
|
+
50c disable <pack> Disable a pack
|
|
197
|
+
50c packs List packs
|
|
198
|
+
50c vault <cmd> Vault commands
|
|
199
|
+
|
|
200
|
+
Packs:
|
|
201
|
+
vault Secure credentials (always on)
|
|
202
|
+
whm WHM/cPanel/SSH (39 tools)
|
|
203
|
+
cf Cloudflare (34 tools)
|
|
204
|
+
wp WordPress (14 tools)
|
|
205
|
+
ux UI/UX toolkit (17 tools)
|
|
206
|
+
|
|
207
|
+
MCP Config:
|
|
208
|
+
{
|
|
209
|
+
"mcpServers": {
|
|
210
|
+
"50c": {
|
|
211
|
+
"command": "50c",
|
|
212
|
+
"env": { "FIFTY_CENT_API_KEY": "cv_xxx" }
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
More info: https://50c.ai/docs`);
|
|
218
|
+
}
|
|
286
219
|
}
|
|
287
220
|
|
|
288
|
-
|
|
289
|
-
|
|
221
|
+
// Main
|
|
222
|
+
async function main() {
|
|
223
|
+
const args = process.argv.slice(2);
|
|
224
|
+
|
|
225
|
+
// MCP mode: --mcp flag or no args AND piped input
|
|
226
|
+
const isMCPMode = args[0] === '--mcp' || (args.length === 0 && !process.stdin.isTTY);
|
|
227
|
+
|
|
228
|
+
if (isMCPMode) {
|
|
229
|
+
await runMCP();
|
|
230
|
+
} else if (args.length === 0) {
|
|
231
|
+
// No args, show help
|
|
232
|
+
await runCLI(['help']);
|
|
233
|
+
} else {
|
|
234
|
+
await runCLI(args);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
main().catch(err => {
|
|
239
|
+
console.error('Error:', err.message);
|
|
240
|
+
process.exit(1);
|
|
241
|
+
});
|
package/lib/config.js
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 50c Config & Mode Detection
|
|
3
|
+
* Handles local vs cloud mode automatically
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const os = require('os');
|
|
9
|
+
const https = require('https');
|
|
10
|
+
|
|
11
|
+
// Storage locations by OS
|
|
12
|
+
function getLocalDir() {
|
|
13
|
+
if (process.platform === 'win32') {
|
|
14
|
+
return path.join(process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'), '50c');
|
|
15
|
+
} else if (process.platform === 'darwin') {
|
|
16
|
+
return path.join(os.homedir(), 'Library', 'Application Support', '50c');
|
|
17
|
+
} else {
|
|
18
|
+
return path.join(os.homedir(), '.local', 'share', '50c');
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const LOCAL_DIR = getLocalDir();
|
|
23
|
+
const CONFIG_FILE = path.join(LOCAL_DIR, 'config.json');
|
|
24
|
+
const VAULT_DIR = path.join(LOCAL_DIR, 'vault');
|
|
25
|
+
const API_URL = process.env.FIFTY_CENT_API_URL || 'https://api.50c.ai';
|
|
26
|
+
|
|
27
|
+
// Default config
|
|
28
|
+
const DEFAULT_CONFIG = {
|
|
29
|
+
api_key: '',
|
|
30
|
+
packs: {
|
|
31
|
+
vault: true, // Always on
|
|
32
|
+
whm: false,
|
|
33
|
+
cf: false,
|
|
34
|
+
wp: false,
|
|
35
|
+
ux: false
|
|
36
|
+
},
|
|
37
|
+
vault: {
|
|
38
|
+
yolo_mode: false,
|
|
39
|
+
session_ttl: 3600,
|
|
40
|
+
idle_timeout: 1800
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Detect mode: local or cloud
|
|
45
|
+
function detectMode() {
|
|
46
|
+
// Explicit override
|
|
47
|
+
if (process.env.FIFTY_CENT_MODE === 'cloud') return 'cloud';
|
|
48
|
+
if (process.env.FIFTY_CENT_MODE === 'local') return 'local';
|
|
49
|
+
|
|
50
|
+
// Cloud indicators
|
|
51
|
+
if (process.env.REPL_ID) return 'cloud'; // Replit
|
|
52
|
+
if (process.env.LOVABLE_PROJECT) return 'cloud'; // Lovable
|
|
53
|
+
if (process.env.CODESPACE_NAME) return 'cloud'; // GitHub Codespaces
|
|
54
|
+
if (process.env.CODESANDBOX_SSE) return 'cloud'; // CodeSandbox
|
|
55
|
+
if (process.env.GITPOD_WORKSPACE_ID) return 'cloud'; // Gitpod
|
|
56
|
+
|
|
57
|
+
// Check if we can write locally
|
|
58
|
+
try {
|
|
59
|
+
if (!fs.existsSync(LOCAL_DIR)) {
|
|
60
|
+
fs.mkdirSync(LOCAL_DIR, { recursive: true, mode: 0o700 });
|
|
61
|
+
}
|
|
62
|
+
const testFile = path.join(LOCAL_DIR, '.write-test');
|
|
63
|
+
fs.writeFileSync(testFile, 'test', { mode: 0o600 });
|
|
64
|
+
fs.unlinkSync(testFile);
|
|
65
|
+
return 'local';
|
|
66
|
+
} catch {
|
|
67
|
+
return 'cloud';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const MODE = detectMode();
|
|
72
|
+
|
|
73
|
+
// Ensure local directory exists
|
|
74
|
+
function ensureLocalDir() {
|
|
75
|
+
if (MODE !== 'local') return;
|
|
76
|
+
if (!fs.existsSync(LOCAL_DIR)) {
|
|
77
|
+
fs.mkdirSync(LOCAL_DIR, { recursive: true, mode: 0o700 });
|
|
78
|
+
}
|
|
79
|
+
if (!fs.existsSync(VAULT_DIR)) {
|
|
80
|
+
fs.mkdirSync(VAULT_DIR, { recursive: true, mode: 0o700 });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Load config (local or cloud)
|
|
85
|
+
async function loadConfig() {
|
|
86
|
+
if (MODE === 'local') {
|
|
87
|
+
return loadConfigLocal();
|
|
88
|
+
} else {
|
|
89
|
+
return loadConfigCloud();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function loadConfigLocal() {
|
|
94
|
+
try {
|
|
95
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
96
|
+
const saved = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
|
97
|
+
return { ...DEFAULT_CONFIG, ...saved, packs: { ...DEFAULT_CONFIG.packs, ...saved.packs } };
|
|
98
|
+
}
|
|
99
|
+
} catch {}
|
|
100
|
+
return { ...DEFAULT_CONFIG };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function loadConfigCloud() {
|
|
104
|
+
const apiKey = process.env.FIFTY_CENT_API_KEY;
|
|
105
|
+
if (!apiKey) return { ...DEFAULT_CONFIG };
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const response = await apiRequest('GET', '/user/config');
|
|
109
|
+
if (response && response.packs) {
|
|
110
|
+
return { ...DEFAULT_CONFIG, ...response, packs: { ...DEFAULT_CONFIG.packs, ...response.packs } };
|
|
111
|
+
}
|
|
112
|
+
} catch {}
|
|
113
|
+
return { ...DEFAULT_CONFIG };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Save config (local or cloud)
|
|
117
|
+
async function saveConfig(config) {
|
|
118
|
+
if (MODE === 'local') {
|
|
119
|
+
return saveConfigLocal(config);
|
|
120
|
+
} else {
|
|
121
|
+
return saveConfigCloud(config);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function saveConfigLocal(config) {
|
|
126
|
+
ensureLocalDir();
|
|
127
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function saveConfigCloud(config) {
|
|
131
|
+
const apiKey = process.env.FIFTY_CENT_API_KEY;
|
|
132
|
+
if (!apiKey) throw new Error('API key required for cloud mode');
|
|
133
|
+
|
|
134
|
+
await apiRequest('PUT', '/user/config', { packs: config.packs, vault: config.vault });
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// API request helper
|
|
138
|
+
function apiRequest(method, endpoint, body = null) {
|
|
139
|
+
return new Promise((resolve, reject) => {
|
|
140
|
+
const apiKey = process.env.FIFTY_CENT_API_KEY || '';
|
|
141
|
+
const url = new URL(endpoint, API_URL);
|
|
142
|
+
|
|
143
|
+
const options = {
|
|
144
|
+
hostname: url.hostname,
|
|
145
|
+
port: url.port || 443,
|
|
146
|
+
path: url.pathname,
|
|
147
|
+
method: method,
|
|
148
|
+
headers: {
|
|
149
|
+
'Content-Type': 'application/json',
|
|
150
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
151
|
+
'User-Agent': '50c/2.0.0'
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const req = https.request(options, (res) => {
|
|
156
|
+
let data = '';
|
|
157
|
+
res.on('data', chunk => data += chunk);
|
|
158
|
+
res.on('end', () => {
|
|
159
|
+
try {
|
|
160
|
+
resolve(JSON.parse(data));
|
|
161
|
+
} catch {
|
|
162
|
+
resolve({ raw: data });
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
req.on('error', reject);
|
|
168
|
+
if (body) req.write(JSON.stringify(body));
|
|
169
|
+
req.end();
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
module.exports = {
|
|
174
|
+
MODE,
|
|
175
|
+
LOCAL_DIR,
|
|
176
|
+
VAULT_DIR,
|
|
177
|
+
CONFIG_FILE,
|
|
178
|
+
API_URL,
|
|
179
|
+
DEFAULT_CONFIG,
|
|
180
|
+
detectMode,
|
|
181
|
+
ensureLocalDir,
|
|
182
|
+
loadConfig,
|
|
183
|
+
saveConfig,
|
|
184
|
+
apiRequest
|
|
185
|
+
};
|