prism-mcp-server 20.0.6 → 20.0.7

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/dist/auth.js ADDED
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Prism Cloud — Authentication Module (v2.0)
3
+ * =============================================
4
+ *
5
+ * Handles OAuth login flow for Prism CLI:
6
+ * 1. `prism login` → starts local HTTP server on random port
7
+ * 2. Opens browser → Synalux OAuth page with redirect back to localhost
8
+ * 3. Receives auth code → exchanges for JWT + refresh token
9
+ * 4. Stores tokens in prism-config.db via configStorage
10
+ *
11
+ * Token management:
12
+ * - Auto-refresh expired JWTs using stored refresh token
13
+ * - `prism logout` → clears stored tokens
14
+ * - `prism status` → shows current auth state
15
+ */
16
+ import { createServer } from 'http';
17
+ import { URL } from 'url';
18
+ import { setSetting, getSetting } from './storage/configStorage.js';
19
+ import { clearCloudCache } from './prism-cloud.js';
20
+ // ─── Config ───────────────────────────────────────────────────────
21
+ const SYNALUX_BASE = process.env.SYNALUX_API_BASE || 'https://synalux.ai';
22
+ const AUTH_CALLBACK_PATH = '/auth/callback';
23
+ // Config keys in prism-config.db
24
+ const KEY_AUTH_TOKEN = 'prism_auth_token';
25
+ const KEY_REFRESH_TOKEN = 'prism_refresh_token';
26
+ const KEY_AUTH_EMAIL = 'prism_auth_email';
27
+ const KEY_AUTH_NAME = 'prism_auth_name';
28
+ const KEY_AUTH_PLAN = 'prism_auth_plan';
29
+ const KEY_AUTH_EXPIRES = 'prism_auth_expires';
30
+ /**
31
+ * Start the OAuth login flow.
32
+ * Opens default browser to Synalux login page, waits for callback.
33
+ */
34
+ export async function login() {
35
+ return new Promise((resolve) => {
36
+ // Start local server on random port
37
+ const server = createServer(async (req, res) => {
38
+ const url = new URL(req.url || '/', `http://localhost`);
39
+ if (url.pathname !== AUTH_CALLBACK_PATH) {
40
+ res.writeHead(404);
41
+ res.end('Not found');
42
+ return;
43
+ }
44
+ const code = url.searchParams.get('code');
45
+ const error = url.searchParams.get('error');
46
+ if (error) {
47
+ res.writeHead(200, { 'Content-Type': 'text/html' });
48
+ res.end(renderHtml('Login Failed', `Error: ${error}. You can close this window.`));
49
+ server.close();
50
+ resolve({ success: false, error });
51
+ return;
52
+ }
53
+ if (!code) {
54
+ res.writeHead(400, { 'Content-Type': 'text/html' });
55
+ res.end(renderHtml('Login Failed', 'No authorization code received. You can close this window.'));
56
+ server.close();
57
+ resolve({ success: false, error: 'No authorization code' });
58
+ return;
59
+ }
60
+ try {
61
+ // Exchange code for JWT
62
+ const tokenResult = await exchangeCode(code);
63
+ // Store tokens
64
+ await setSetting(KEY_AUTH_TOKEN, tokenResult.access_token);
65
+ if (tokenResult.refresh_token) {
66
+ await setSetting(KEY_REFRESH_TOKEN, tokenResult.refresh_token);
67
+ }
68
+ if (tokenResult.email) {
69
+ await setSetting(KEY_AUTH_EMAIL, tokenResult.email);
70
+ }
71
+ if (tokenResult.plan) {
72
+ await setSetting(KEY_AUTH_PLAN, tokenResult.plan);
73
+ }
74
+ if (tokenResult.expires_at) {
75
+ await setSetting(KEY_AUTH_EXPIRES, String(tokenResult.expires_at));
76
+ }
77
+ // Clear cached cloud limits so next verify call uses new token
78
+ clearCloudCache();
79
+ res.writeHead(200, { 'Content-Type': 'text/html' });
80
+ res.end(renderHtml('✅ Prism Login Successful', `Authenticated as <strong>${tokenResult.email}</strong> (${tokenResult.plan} plan). You can close this window.`));
81
+ server.close();
82
+ resolve({
83
+ success: true,
84
+ email: tokenResult.email,
85
+ plan: tokenResult.plan,
86
+ });
87
+ }
88
+ catch (err) {
89
+ const message = err instanceof Error ? err.message : String(err);
90
+ res.writeHead(200, { 'Content-Type': 'text/html' });
91
+ res.end(renderHtml('Login Failed', `Error: ${message}. You can close this window.`));
92
+ server.close();
93
+ resolve({ success: false, error: message });
94
+ }
95
+ });
96
+ server.listen(0, '127.0.0.1', () => {
97
+ const addr = server.address();
98
+ if (!addr || typeof addr === 'string') {
99
+ resolve({ success: false, error: 'Failed to start local server' });
100
+ return;
101
+ }
102
+ const port = addr.port;
103
+ const callbackUrl = `http://127.0.0.1:${port}${AUTH_CALLBACK_PATH}`;
104
+ const loginUrl = `${SYNALUX_BASE}/auth/prism-login?redirect_uri=${encodeURIComponent(callbackUrl)}`;
105
+ console.log(`\n🔐 Opening browser for Synalux login...`);
106
+ console.log(` If the browser doesn't open, visit:\n ${loginUrl}\n`);
107
+ // Open browser (platform-agnostic)
108
+ openBrowser(loginUrl);
109
+ // Timeout after 5 minutes
110
+ setTimeout(() => {
111
+ server.close();
112
+ resolve({ success: false, error: 'Login timed out (5 minutes)' });
113
+ }, 5 * 60 * 1000);
114
+ });
115
+ });
116
+ }
117
+ async function exchangeCode(code) {
118
+ const response = await fetch(`${SYNALUX_BASE}/api/v1/auth/code-exchange`, {
119
+ method: 'POST',
120
+ headers: { 'Content-Type': 'application/json' },
121
+ body: JSON.stringify({ code, client: 'prism-cli' }),
122
+ });
123
+ if (!response.ok) {
124
+ const text = await response.text();
125
+ throw new Error(`Token exchange failed (${response.status}): ${text}`);
126
+ }
127
+ return response.json();
128
+ }
129
+ // ─── Token Refresh ────────────────────────────────────────────────
130
+ /**
131
+ * Get a valid auth token, refreshing if expired.
132
+ * Returns empty string if not logged in.
133
+ */
134
+ export async function getAuthToken() {
135
+ const token = await getSetting(KEY_AUTH_TOKEN);
136
+ if (!token)
137
+ return '';
138
+ // Check expiry
139
+ const expiresStr = await getSetting(KEY_AUTH_EXPIRES);
140
+ if (expiresStr) {
141
+ const expiresAt = parseInt(expiresStr, 10);
142
+ const now = Math.floor(Date.now() / 1000);
143
+ if (now < expiresAt - 60) {
144
+ // Token still valid (with 60s buffer)
145
+ return token;
146
+ }
147
+ // Try to refresh
148
+ const refreshToken = await getSetting(KEY_REFRESH_TOKEN);
149
+ if (refreshToken) {
150
+ try {
151
+ const refreshed = await refreshAuthToken(refreshToken);
152
+ await setSetting(KEY_AUTH_TOKEN, refreshed.access_token);
153
+ if (refreshed.expires_at) {
154
+ await setSetting(KEY_AUTH_EXPIRES, String(refreshed.expires_at));
155
+ }
156
+ clearCloudCache();
157
+ return refreshed.access_token;
158
+ }
159
+ catch {
160
+ console.warn('[Prism Auth] Token refresh failed. Run `prism login` to re-authenticate.');
161
+ return '';
162
+ }
163
+ }
164
+ }
165
+ return token;
166
+ }
167
+ async function refreshAuthToken(refreshToken) {
168
+ const response = await fetch(`${SYNALUX_BASE}/api/v1/auth/jwt`, {
169
+ method: 'POST',
170
+ headers: { 'Content-Type': 'application/json' },
171
+ body: JSON.stringify({ refresh_token: refreshToken, client: 'prism-cli' }),
172
+ });
173
+ if (!response.ok) {
174
+ throw new Error(`Token refresh failed (${response.status})`);
175
+ }
176
+ return response.json();
177
+ }
178
+ // ─── Logout ───────────────────────────────────────────────────────
179
+ export async function logout() {
180
+ await setSetting(KEY_AUTH_TOKEN, '');
181
+ await setSetting(KEY_REFRESH_TOKEN, '');
182
+ await setSetting(KEY_AUTH_EMAIL, '');
183
+ await setSetting(KEY_AUTH_PLAN, '');
184
+ await setSetting(KEY_AUTH_EXPIRES, '');
185
+ clearCloudCache();
186
+ }
187
+ export async function getAuthStatus() {
188
+ const token = await getSetting(KEY_AUTH_TOKEN);
189
+ if (!token)
190
+ return { loggedIn: false };
191
+ const email = await getSetting(KEY_AUTH_EMAIL);
192
+ const name = await getSetting(KEY_AUTH_NAME);
193
+ const plan = await getSetting(KEY_AUTH_PLAN);
194
+ const expiresStr = await getSetting(KEY_AUTH_EXPIRES);
195
+ return {
196
+ loggedIn: true,
197
+ email: email || undefined,
198
+ name: name || undefined,
199
+ plan: plan || undefined,
200
+ expiresAt: expiresStr ? new Date(parseInt(expiresStr, 10) * 1000) : undefined,
201
+ };
202
+ }
203
+ // ─── Helpers ──────────────────────────────────────────────────────
204
+ function renderHtml(title, body) {
205
+ return `<!DOCTYPE html>
206
+ <html><head><title>${title}</title>
207
+ <style>body{font-family:system-ui,-apple-system,sans-serif;display:flex;justify-content:center;align-items:center;min-height:100vh;margin:0;background:#0a0a1a;color:#e0e0e0}
208
+ .card{background:#1a1a2e;border-radius:16px;padding:48px;max-width:480px;text-align:center;box-shadow:0 8px 32px rgba(0,0,0,.4)}
209
+ h1{font-size:24px;margin-bottom:16px}p{color:#a0a0b0;line-height:1.6}</style>
210
+ </head><body><div class="card"><h1>${title}</h1><p>${body}</p></div></body></html>`;
211
+ }
212
+ function openBrowser(url) {
213
+ const { exec } = require('child_process');
214
+ const cmd = process.platform === 'darwin' ? 'open'
215
+ : process.platform === 'win32' ? 'start'
216
+ : 'xdg-open';
217
+ exec(`${cmd} "${url}"`);
218
+ }
@@ -0,0 +1,173 @@
1
+ /**
2
+ * v12.5: Cloud-Delegated Dark Factory Pipelines
3
+ *
4
+ * Remote agent execution with Prism memory injection over HTTP.
5
+ * Delegates compute-intensive tasks to cloud agents while
6
+ * maintaining full memory context.
7
+ */
8
+ import { debugLog } from "../utils/logger.js";
9
+ // ─── State ───────────────────────────────────────────────────
10
+ let config = {
11
+ endpoint: "https://cloud.synalux.ai/api/v1/darkfactory",
12
+ apiKey: "",
13
+ maxConcurrent: 5,
14
+ timeout: 300_000, // 5 minutes
15
+ enabled: false,
16
+ };
17
+ const activeTasks = new Map();
18
+ const taskHistory = [];
19
+ export function configureDelegate(updates) {
20
+ if (updates.endpoint && !updates.endpoint.startsWith('https://') && !updates.endpoint.includes('localhost')) {
21
+ throw new Error('Cloud delegate endpoint must use HTTPS');
22
+ }
23
+ config = { ...config, ...updates };
24
+ debugLog(`Cloud Delegate: Configured → ${config.endpoint}`);
25
+ }
26
+ export function getDelegateConfig() {
27
+ return { ...config, apiKey: config.apiKey ? "***" : "" };
28
+ }
29
+ // ─── Task Management ─────────────────────────────────────────
30
+ function generateTaskId() {
31
+ return `dt_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
32
+ }
33
+ /**
34
+ * Create a new delegate task.
35
+ */
36
+ export function createTask(type, project, payload, memoryContext, priority = "normal") {
37
+ const task = {
38
+ id: generateTaskId(),
39
+ type,
40
+ project,
41
+ payload,
42
+ memoryContext,
43
+ priority,
44
+ createdAt: new Date().toISOString(),
45
+ status: "queued",
46
+ };
47
+ activeTasks.set(task.id, task);
48
+ debugLog(`Cloud Delegate: Created task ${task.id} (${type}) for project '${project}'`);
49
+ return task;
50
+ }
51
+ /**
52
+ * Dispatch a task to the cloud endpoint.
53
+ */
54
+ export async function dispatchTask(taskId) {
55
+ const task = activeTasks.get(taskId);
56
+ if (!task)
57
+ throw new Error(`Task '${taskId}' not found`);
58
+ if (!config.enabled) {
59
+ task.status = "failed";
60
+ task.result = { success: false, output: "", durationMs: 0, error: "Cloud delegate is not enabled" };
61
+ return task;
62
+ }
63
+ // Check concurrency limit
64
+ const running = Array.from(activeTasks.values()).filter(t => t.status === "running");
65
+ if (running.length >= config.maxConcurrent) {
66
+ debugLog(`Cloud Delegate: Concurrency limit reached (${running.length}/${config.maxConcurrent}), task ${taskId} stays queued`);
67
+ return task;
68
+ }
69
+ task.status = "dispatched";
70
+ try {
71
+ const start = Date.now();
72
+ const response = await fetch(`${config.endpoint}/tasks`, {
73
+ method: "POST",
74
+ headers: {
75
+ "Authorization": `Bearer ${config.apiKey}`,
76
+ "Content-Type": "application/json",
77
+ "X-Prism-Version": "12.5.0",
78
+ },
79
+ body: JSON.stringify({
80
+ id: task.id,
81
+ type: task.type,
82
+ project: task.project,
83
+ payload: task.payload,
84
+ memoryContext: task.memoryContext,
85
+ priority: task.priority,
86
+ }),
87
+ signal: AbortSignal.timeout(config.timeout),
88
+ });
89
+ const data = await response.json();
90
+ const durationMs = Date.now() - start;
91
+ if (response.ok) {
92
+ task.status = "completed";
93
+ task.result = {
94
+ success: true,
95
+ output: data.output || "",
96
+ artifacts: data.artifacts,
97
+ memoryUpdates: data.memoryUpdates,
98
+ durationMs,
99
+ };
100
+ }
101
+ else {
102
+ task.status = "failed";
103
+ task.result = {
104
+ success: false,
105
+ output: "",
106
+ durationMs,
107
+ error: data.error || `HTTP ${response.status}`,
108
+ };
109
+ }
110
+ }
111
+ catch (err) {
112
+ task.status = "failed";
113
+ task.result = {
114
+ success: false,
115
+ output: "",
116
+ durationMs: 0,
117
+ error: `Dispatch error: ${err}`,
118
+ };
119
+ }
120
+ // Move to history
121
+ taskHistory.push({ ...task });
122
+ if (taskHistory.length > 1000)
123
+ taskHistory.splice(0, taskHistory.length - 1000);
124
+ activeTasks.delete(taskId);
125
+ debugLog(`Cloud Delegate: Task ${taskId} → ${task.status}`);
126
+ return task;
127
+ }
128
+ /**
129
+ * Cancel an active task.
130
+ */
131
+ export function cancelTask(taskId) {
132
+ const task = activeTasks.get(taskId);
133
+ if (!task)
134
+ return false;
135
+ task.status = "cancelled";
136
+ taskHistory.push({ ...task });
137
+ if (taskHistory.length > 1000)
138
+ taskHistory.splice(0, taskHistory.length - 1000);
139
+ activeTasks.delete(taskId);
140
+ debugLog(`Cloud Delegate: Cancelled task ${taskId}`);
141
+ return true;
142
+ }
143
+ /**
144
+ * Get task status.
145
+ */
146
+ export function getTaskStatus(taskId) {
147
+ return activeTasks.get(taskId) || taskHistory.find(t => t.id === taskId);
148
+ }
149
+ /**
150
+ * List all active tasks.
151
+ */
152
+ export function listActiveTasks() {
153
+ return Array.from(activeTasks.values());
154
+ }
155
+ /**
156
+ * Get task history (last N tasks).
157
+ */
158
+ export function getTaskHistory(limit = 20) {
159
+ return taskHistory.slice(-limit);
160
+ }
161
+ /**
162
+ * Get delegate status summary.
163
+ */
164
+ export function getStatus() {
165
+ return {
166
+ enabled: config.enabled,
167
+ activeTasks: activeTasks.size,
168
+ maxConcurrent: config.maxConcurrent,
169
+ totalCompleted: taskHistory.filter(t => t.status === "completed").length,
170
+ totalFailed: taskHistory.filter(t => t.status === "failed").length,
171
+ };
172
+ }
173
+ debugLog("v12.5: Cloud delegate loaded");
@@ -0,0 +1,199 @@
1
+ /**
2
+ * v12.5: Plugin/Extension API for IDE Marketplace
3
+ *
4
+ * Load/unload `.prism-plugin.json` extensions with lifecycle hooks.
5
+ * Plugins can register custom tools, add storage backends,
6
+ * or extend the dashboard.
7
+ */
8
+ import { debugLog } from "../utils/logger.js";
9
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
10
+ import { join, resolve } from "node:path";
11
+ import { homedir } from "node:os";
12
+ // ─── Plugin Manager ──────────────────────────────────────────
13
+ const plugins = new Map();
14
+ function getPluginsDir() {
15
+ return join(process.env.PRISM_DATA_DIR || join(homedir(), ".prism"), "plugins");
16
+ }
17
+ /**
18
+ * Discover plugins in the plugins directory.
19
+ */
20
+ export function discoverPlugins() {
21
+ const dir = getPluginsDir();
22
+ if (!existsSync(dir))
23
+ return [];
24
+ const manifests = [];
25
+ try {
26
+ const entries = readdirSync(dir, { withFileTypes: true });
27
+ for (const entry of entries) {
28
+ if (!entry.isDirectory())
29
+ continue;
30
+ const manifestPath = join(dir, entry.name, "prism-plugin.json");
31
+ if (!existsSync(manifestPath))
32
+ continue;
33
+ try {
34
+ const raw = readFileSync(manifestPath, "utf-8");
35
+ const manifest = JSON.parse(raw);
36
+ manifests.push(manifest);
37
+ }
38
+ catch (err) {
39
+ debugLog(`Plugin: Failed to parse manifest for ${entry.name}: ${err}`);
40
+ }
41
+ }
42
+ }
43
+ catch (err) {
44
+ debugLog(`Plugin: Failed to scan plugins directory: ${err}`);
45
+ }
46
+ return manifests;
47
+ }
48
+ /**
49
+ * Load a plugin by name.
50
+ */
51
+ export async function loadPlugin(pluginName) {
52
+ const dir = join(getPluginsDir(), pluginName);
53
+ const manifestPath = join(dir, "prism-plugin.json");
54
+ if (!existsSync(manifestPath)) {
55
+ const loaded = {
56
+ manifest: { name: pluginName, version: "0.0.0", description: "", author: "", license: "", main: "", prismVersion: "", capabilities: [] },
57
+ path: dir,
58
+ status: "error",
59
+ loadedAt: new Date().toISOString(),
60
+ error: `Plugin '${pluginName}' not found at ${dir}`,
61
+ };
62
+ plugins.set(pluginName, loaded);
63
+ return loaded;
64
+ }
65
+ try {
66
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
67
+ const entryPath = resolve(dir, manifest.main);
68
+ let instance;
69
+ if (existsSync(entryPath)) {
70
+ try {
71
+ const mod = await import(entryPath);
72
+ instance = mod.default || mod;
73
+ if (instance?.onLoad) {
74
+ await instance.onLoad();
75
+ }
76
+ }
77
+ catch (err) {
78
+ debugLog(`Plugin: Failed to load entry point for ${pluginName}: ${err}`);
79
+ }
80
+ }
81
+ const loaded = {
82
+ manifest,
83
+ path: dir,
84
+ status: instance ? "active" : "loaded",
85
+ loadedAt: new Date().toISOString(),
86
+ instance,
87
+ };
88
+ plugins.set(pluginName, loaded);
89
+ debugLog(`Plugin: Loaded '${pluginName}' v${manifest.version} [${manifest.capabilities.join(", ")}]`);
90
+ return loaded;
91
+ }
92
+ catch (err) {
93
+ const loaded = {
94
+ manifest: { name: pluginName, version: "0.0.0", description: "", author: "", license: "", main: "", prismVersion: "", capabilities: [] },
95
+ path: dir,
96
+ status: "error",
97
+ loadedAt: new Date().toISOString(),
98
+ error: `Failed to load plugin: ${err}`,
99
+ };
100
+ plugins.set(pluginName, loaded);
101
+ return loaded;
102
+ }
103
+ }
104
+ /**
105
+ * Unload a plugin by name.
106
+ */
107
+ export async function unloadPlugin(pluginName) {
108
+ const plugin = plugins.get(pluginName);
109
+ if (!plugin)
110
+ return false;
111
+ try {
112
+ if (plugin.instance?.onUnload) {
113
+ await plugin.instance.onUnload();
114
+ }
115
+ }
116
+ catch (err) {
117
+ debugLog(`Plugin: Error during unload of '${pluginName}': ${err}`);
118
+ }
119
+ plugins.delete(pluginName);
120
+ debugLog(`Plugin: Unloaded '${pluginName}'`);
121
+ return true;
122
+ }
123
+ /**
124
+ * List all loaded plugins.
125
+ */
126
+ export function listPlugins() {
127
+ return Array.from(plugins.values());
128
+ }
129
+ /**
130
+ * Get a specific loaded plugin.
131
+ */
132
+ export function getPlugin(name) {
133
+ return plugins.get(name);
134
+ }
135
+ /**
136
+ * Get all custom tools registered by plugins.
137
+ */
138
+ export function getPluginTools() {
139
+ const result = [];
140
+ for (const [name, plugin] of plugins) {
141
+ if (plugin.status === "active" && plugin.instance?.getTools) {
142
+ try {
143
+ const tools = plugin.instance.getTools();
144
+ result.push({ pluginName: name, tools });
145
+ }
146
+ catch (err) {
147
+ debugLog(`Plugin: Error getting tools from '${name}': ${err}`);
148
+ }
149
+ }
150
+ }
151
+ return result;
152
+ }
153
+ /**
154
+ * Dispatch a tool call to the appropriate plugin.
155
+ */
156
+ export async function dispatchPluginToolCall(toolName, args) {
157
+ for (const [, plugin] of plugins) {
158
+ if (plugin.status === "active" && plugin.instance?.onToolCall) {
159
+ try {
160
+ const result = await plugin.instance.onToolCall(toolName, args);
161
+ if (result !== undefined) {
162
+ return { handled: true, result };
163
+ }
164
+ }
165
+ catch (err) {
166
+ debugLog(`Plugin: Error dispatching tool '${toolName}': ${err}`);
167
+ }
168
+ }
169
+ }
170
+ return { handled: false };
171
+ }
172
+ // ─── Plugin Schema Validation ────────────────────────────────
173
+ /**
174
+ * Validate a plugin manifest against the schema.
175
+ */
176
+ export function validateManifest(manifest) {
177
+ const errors = [];
178
+ const m = manifest;
179
+ if (!m.name || typeof m.name !== "string")
180
+ errors.push("Missing or invalid 'name'");
181
+ if (!m.version || typeof m.version !== "string")
182
+ errors.push("Missing or invalid 'version'");
183
+ if (!m.main || typeof m.main !== "string")
184
+ errors.push("Missing or invalid 'main' entry point");
185
+ if (!m.prismVersion || typeof m.prismVersion !== "string")
186
+ errors.push("Missing 'prismVersion'");
187
+ if (!Array.isArray(m.capabilities))
188
+ errors.push("Missing 'capabilities' array");
189
+ const validCaps = ["tools", "storage", "dashboard", "preprocessor", "postprocessor", "scheduler"];
190
+ if (Array.isArray(m.capabilities)) {
191
+ for (const cap of m.capabilities) {
192
+ if (!validCaps.includes(cap)) {
193
+ errors.push(`Unknown capability: '${cap}'`);
194
+ }
195
+ }
196
+ }
197
+ return { valid: errors.length === 0, errors };
198
+ }
199
+ debugLog("v12.5: Plugin manager loaded");
@@ -0,0 +1,110 @@
1
+ import { getSettingSync } from './storage/configStorage.js';
2
+ const DEFAULT_LIMITS = {
3
+ tier: 'free',
4
+ status: 'active',
5
+ limits: {
6
+ llm_daily: 0,
7
+ search_daily: 0,
8
+ memory_sync: false,
9
+ coder_daily: 0,
10
+ },
11
+ usage: {
12
+ llm_calls: 0,
13
+ search_calls: 0,
14
+ memory_calls: 0,
15
+ coder_calls: 0,
16
+ },
17
+ features: {
18
+ cloud_coder: false,
19
+ cloud_memory: false,
20
+ cloud_llm: false,
21
+ cloud_search: false,
22
+ hivemind: false,
23
+ voice_video: false,
24
+ },
25
+ };
26
+ let cachedLimits = null;
27
+ /**
28
+ * Verify cloud license via Synalux API (v2.0 — JWT Auth).
29
+ *
30
+ * Reads auth token from:
31
+ * 1. PRISM_AUTH_TOKEN env var (set by `prism login`)
32
+ * 2. prism_auth_token setting (stored by dashboard login)
33
+ * 3. Legacy: PRISM_LICENSE_KEY (deprecated, returns guidance)
34
+ *
35
+ * Calls GET /api/v1/prism/verify with Bearer JWT.
36
+ */
37
+ export async function verifyCloudLicense() {
38
+ if (cachedLimits)
39
+ return cachedLimits;
40
+ // v2.0: Read JWT auth token (set by `prism login` OAuth flow)
41
+ const authToken = getSettingSync('prism_auth_token', process.env.PRISM_AUTH_TOKEN || '');
42
+ // Legacy fallback: if user still has old license key, guide them to upgrade
43
+ const legacyKey = getSettingSync('prism_license_key', process.env.PRISM_LICENSE_KEY || '');
44
+ if (!authToken && legacyKey) {
45
+ console.warn('[Prism Cloud] License key auth is deprecated. Please run `prism login` to switch to OAuth.');
46
+ return DEFAULT_LIMITS;
47
+ }
48
+ if (!authToken) {
49
+ console.warn('[Prism Cloud] No auth token found. Operating in Free tier. Run `prism login` to authenticate.');
50
+ return DEFAULT_LIMITS;
51
+ }
52
+ try {
53
+ const baseUrl = process.env.SYNALUX_API_BASE || 'https://synalux.ai';
54
+ const response = await fetch(`${baseUrl}/api/v1/prism/verify`, {
55
+ method: 'GET',
56
+ headers: {
57
+ 'Authorization': `Bearer ${authToken}`,
58
+ 'Content-Type': 'application/json',
59
+ },
60
+ });
61
+ if (!response.ok) {
62
+ if (response.status === 401) {
63
+ console.warn('[Prism Cloud] Auth token expired or invalid. Run `prism login` to re-authenticate.');
64
+ }
65
+ else {
66
+ console.warn(`[Prism Cloud] Verification failed with status ${response.status}. Operating in Free tier.`);
67
+ }
68
+ return DEFAULT_LIMITS;
69
+ }
70
+ const data = await response.json();
71
+ if (data.status === 'success') {
72
+ cachedLimits = {
73
+ tier: data.user.plan || 'free',
74
+ status: 'active',
75
+ limits: {
76
+ llm_daily: data.limits?.llm_daily || 0,
77
+ search_daily: data.limits?.search_daily || 0,
78
+ memory_sync: data.limits?.memory_sync || false,
79
+ coder_daily: data.limits?.coder_daily || 0,
80
+ },
81
+ usage: {
82
+ llm_calls: data.usage?.llm_calls || 0,
83
+ search_calls: data.usage?.search_calls || 0,
84
+ memory_calls: data.usage?.memory_calls || 0,
85
+ coder_calls: data.usage?.coder_calls || 0,
86
+ },
87
+ features: data.features || DEFAULT_LIMITS.features,
88
+ };
89
+ console.log(`[Prism Cloud] Verified ${cachedLimits.tier} license.`);
90
+ return cachedLimits;
91
+ }
92
+ else {
93
+ console.warn(`[Prism Cloud] Invalid response. Operating in Free tier.`);
94
+ return DEFAULT_LIMITS;
95
+ }
96
+ }
97
+ catch (error) {
98
+ console.error(`[Prism Cloud] Error verifying license: ${error instanceof Error ? error.message : String(error)}`);
99
+ return DEFAULT_LIMITS;
100
+ }
101
+ }
102
+ export function getCloudLimits() {
103
+ return cachedLimits || DEFAULT_LIMITS;
104
+ }
105
+ /**
106
+ * Clear cached limits (e.g., after `prism login` sets new token).
107
+ */
108
+ export function clearCloudCache() {
109
+ cachedLimits = null;
110
+ }