@monoes/monomindcli 2.10.1 → 2.10.3
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/.claude/skills/monodesign/reference/hooks.md +2 -2
- package/.claude/skills/monodesign/scripts/hook-admin.mjs +51 -18
- package/dist/src/commands/doctor-project-checks.d.ts.map +1 -1
- package/dist/src/commands/doctor-project-checks.js +58 -82
- package/dist/src/commands/doctor-project-checks.js.map +1 -1
- package/dist/src/init/codex-generator.d.ts.map +1 -1
- package/dist/src/init/codex-generator.js +21 -1
- package/dist/src/init/codex-generator.js.map +1 -1
- package/dist/src/init/mcp-generator.d.ts +7 -0
- package/dist/src/init/mcp-generator.d.ts.map +1 -1
- package/dist/src/init/mcp-generator.js +29 -0
- package/dist/src/init/mcp-generator.js.map +1 -1
- package/dist/src/knowledge/eval/golden-set.d.ts.map +1 -1
- package/dist/src/knowledge/eval/golden-set.js +0 -22
- package/dist/src/knowledge/eval/golden-set.js.map +1 -1
- package/dist/src/ui/dashboard.html +51 -0
- package/dist/src/ui/data/mastermind-sessions.json +1 -0
- package/dist/src/ui/orgs.html +40 -0
- package/dist/src/ui/routes-monoes.mjs +336 -0
- package/dist/src/ui/server.mjs +19 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -6
package/dist/src/ui/orgs.html
CHANGED
|
@@ -849,6 +849,13 @@ html, body {
|
|
|
849
849
|
<div class="hdr-chip">TOPOLOGY <span id="hdr-topology">—</span></div>
|
|
850
850
|
<div class="hdr-chip" id="hdr-created-wrap" style="display:none">SINCE <span id="hdr-created">—</span></div>
|
|
851
851
|
</div>
|
|
852
|
+
<button id="upload-monoes-btn" onclick="uploadCurrentOrgToMonoes()"
|
|
853
|
+
style="font-size:9px; font-family:var(--mono); letter-spacing:1px; color:var(--text-mid);
|
|
854
|
+
border:1px solid var(--border); background:none; padding:4px 12px;
|
|
855
|
+
border-radius:3px; cursor:pointer; text-transform:uppercase; transition:background 0.12s;"
|
|
856
|
+
onmouseover="this.style.background='var(--bg-hover, rgba(255,255,255,0.06))'"
|
|
857
|
+
onmouseout="this.style.background='none'"
|
|
858
|
+
title="Upload this org to your monoes.me community account">⬡ UPLOAD TO MONOES.ME</button>
|
|
852
859
|
<button id="stop-btn" onclick="stopCurrentOrg()" style="display:none;
|
|
853
860
|
font-size:9px; font-family:var(--mono); letter-spacing:1px; color:var(--red);
|
|
854
861
|
border:1px solid oklch(62% 0.22 25 / 0.35); background:none; padding:4px 12px;
|
|
@@ -2233,6 +2240,39 @@ function handleMmEvent(ev) {
|
|
|
2233
2240
|
}
|
|
2234
2241
|
}
|
|
2235
2242
|
|
|
2243
|
+
// ── Upload to monoes.me action ───────────────────────────────────────
|
|
2244
|
+
window.uploadCurrentOrgToMonoes = async () => {
|
|
2245
|
+
if (!selectedOrg) return;
|
|
2246
|
+
const btn = document.getElementById('upload-monoes-btn');
|
|
2247
|
+
const originalLabel = btn.textContent;
|
|
2248
|
+
btn.disabled = true;
|
|
2249
|
+
btn.textContent = 'UPLOADING…';
|
|
2250
|
+
try {
|
|
2251
|
+
const res = await fetch('/api/monoes/upload-org', {
|
|
2252
|
+
method: 'POST',
|
|
2253
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2254
|
+
body: JSON.stringify({ orgName: selectedOrg }),
|
|
2255
|
+
});
|
|
2256
|
+
const body = await res.json();
|
|
2257
|
+
if (!res.ok) {
|
|
2258
|
+
if (body.error === 'not_connected') {
|
|
2259
|
+
alert('Connect to monoes.me first (button in the sidebar) before uploading an org.');
|
|
2260
|
+
} else {
|
|
2261
|
+
alert(`Upload failed: ${body.error || res.statusText}`);
|
|
2262
|
+
}
|
|
2263
|
+
return;
|
|
2264
|
+
}
|
|
2265
|
+
if (confirm(`Uploaded to monoes.me. Open it now?`)) {
|
|
2266
|
+
window.open(body.url, '_blank');
|
|
2267
|
+
}
|
|
2268
|
+
} catch (e) {
|
|
2269
|
+
alert(`Upload failed: ${e.message}`);
|
|
2270
|
+
} finally {
|
|
2271
|
+
btn.disabled = false;
|
|
2272
|
+
btn.textContent = originalLabel;
|
|
2273
|
+
}
|
|
2274
|
+
};
|
|
2275
|
+
|
|
2236
2276
|
// ── Stop org action ────────────────────────────────────────────────
|
|
2237
2277
|
window.stopCurrentOrg = async () => {
|
|
2238
2278
|
if (!selectedOrg) return;
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
// ── monoes.me connection (OAuth) ────────────────────────────────────────
|
|
2
|
+
// Lets the dashboard connect a monoes.me community account: upload local
|
|
3
|
+
// orgs directly (routes-monoes-upload, added alongside this), and register
|
|
4
|
+
// monoes.me's MCP server into .mcp.json (mcp-generator.ts) so agent
|
|
5
|
+
// sessions in this project get monoes.me's community tools automatically.
|
|
6
|
+
//
|
|
7
|
+
// Mirrors the existing dashboard-token file convention (one JSON file
|
|
8
|
+
// under MONOMIND_HOME/.monomind/, plain fs read/write, no external auth
|
|
9
|
+
// library) rather than introducing new dependencies for a single OAuth
|
|
10
|
+
// client flow.
|
|
11
|
+
import crypto from 'node:crypto';
|
|
12
|
+
import fs from 'node:fs';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
|
|
15
|
+
const MONOES_BASE_URL = process.env.MONOMIND_MONOES_URL || 'https://monoes.me';
|
|
16
|
+
const MONOES_SCOPE = 'community:read community:write';
|
|
17
|
+
|
|
18
|
+
// state -> { codeVerifier, createdAt } — short-lived, in-memory only. The
|
|
19
|
+
// user completes the browser redirect within a few minutes of clicking
|
|
20
|
+
// "Connect"; losing this on a dashboard restart just means the in-flight
|
|
21
|
+
// connect attempt has to be retried, not a security issue.
|
|
22
|
+
const _pendingStates = new Map();
|
|
23
|
+
const STATE_TTL_MS = 10 * 60 * 1000;
|
|
24
|
+
|
|
25
|
+
function _connectionFile(monomindHome) {
|
|
26
|
+
return path.join(monomindHome, '.monomind', 'monoes-connection.json');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function readMonoesConnection(monomindHome) {
|
|
30
|
+
try {
|
|
31
|
+
return JSON.parse(fs.readFileSync(_connectionFile(monomindHome), 'utf8'));
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function _writeMonoesConnection(monomindHome, data) {
|
|
38
|
+
const file = _connectionFile(monomindHome);
|
|
39
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
40
|
+
fs.writeFileSync(file, JSON.stringify(data, null, 2), { mode: 0o600 });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function _deleteMonoesConnection(monomindHome) {
|
|
44
|
+
try {
|
|
45
|
+
fs.unlinkSync(_connectionFile(monomindHome));
|
|
46
|
+
} catch {}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function _base64url(buffer) {
|
|
50
|
+
return buffer.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function createPkcePair() {
|
|
54
|
+
const verifier = _base64url(crypto.randomBytes(32));
|
|
55
|
+
const challenge = _base64url(crypto.createHash('sha256').update(verifier).digest());
|
|
56
|
+
return { verifier, challenge };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function _pruneExpiredStates() {
|
|
60
|
+
const now = Date.now();
|
|
61
|
+
for (const [state, entry] of _pendingStates) {
|
|
62
|
+
if (now - entry.createdAt > STATE_TTL_MS) _pendingStates.delete(state);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function _registerClient(redirectUri) {
|
|
67
|
+
const res = await fetch(`${MONOES_BASE_URL}/api/auth/oauth2/register`, {
|
|
68
|
+
method: 'POST',
|
|
69
|
+
headers: { 'Content-Type': 'application/json' },
|
|
70
|
+
body: JSON.stringify({
|
|
71
|
+
redirect_uris: [redirectUri],
|
|
72
|
+
token_endpoint_auth_method: 'none',
|
|
73
|
+
grant_types: ['authorization_code', 'refresh_token'],
|
|
74
|
+
// The dashboard's redirect_uri is http://127.0.0.1:<port>/... — the
|
|
75
|
+
// OAuth server only allows plain-http loopback redirects for native
|
|
76
|
+
// clients (RFC 8252 §7.3); "web" (the DCR default) requires https.
|
|
77
|
+
application_type: 'native',
|
|
78
|
+
}),
|
|
79
|
+
});
|
|
80
|
+
if (!res.ok) throw new Error(`monoes.me client registration failed: ${res.status}`);
|
|
81
|
+
const data = await res.json();
|
|
82
|
+
return data.client_id;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function _getOrRegisterClientId(monomindHome, redirectUri) {
|
|
86
|
+
const existing = readMonoesConnection(monomindHome);
|
|
87
|
+
if (existing?.clientId) return existing.clientId;
|
|
88
|
+
const clientId = await _registerClient(redirectUri);
|
|
89
|
+
_writeMonoesConnection(monomindHome, { ...(existing || {}), clientId });
|
|
90
|
+
return clientId;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function _buildMonoesMcpEntry(accessToken) {
|
|
94
|
+
return {
|
|
95
|
+
type: 'http',
|
|
96
|
+
url: `${MONOES_BASE_URL}/api/mcp`,
|
|
97
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Merges (or removes) the `monoes` entry in the project's existing
|
|
102
|
+
* .mcp.json. Only touches that one key — never creates or rewrites the
|
|
103
|
+
* rest of the file, since .mcp.json is otherwise owned by `init`/the user.
|
|
104
|
+
* No-ops if .mcp.json doesn't exist yet (nothing to merge into). */
|
|
105
|
+
function _syncMonoesMcpEntry(projectDir, accessToken) {
|
|
106
|
+
const mcpPath = path.join(projectDir, '.mcp.json');
|
|
107
|
+
let config;
|
|
108
|
+
try {
|
|
109
|
+
config = JSON.parse(fs.readFileSync(mcpPath, 'utf8'));
|
|
110
|
+
} catch {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
config.mcpServers = config.mcpServers || {};
|
|
114
|
+
if (accessToken) {
|
|
115
|
+
config.mcpServers.monoes = _buildMonoesMcpEntry(accessToken);
|
|
116
|
+
} else {
|
|
117
|
+
delete config.mcpServers.monoes;
|
|
118
|
+
}
|
|
119
|
+
fs.writeFileSync(mcpPath, JSON.stringify(config, null, 2));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function _json(res, corsOrigin, status, body) {
|
|
123
|
+
res.writeHead(status, {
|
|
124
|
+
'Content-Type': 'application/json',
|
|
125
|
+
...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
|
|
126
|
+
});
|
|
127
|
+
res.end(JSON.stringify(body));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Given a stored connection, returns a still-valid access token — silently
|
|
131
|
+
* refreshing via the refresh_token grant if the current one is expired or
|
|
132
|
+
* about to expire. Returns null (and deletes the connection) if refresh
|
|
133
|
+
* itself fails, treating that identically to "never connected". */
|
|
134
|
+
export async function getValidMonoesToken(monomindHome) {
|
|
135
|
+
const conn = readMonoesConnection(monomindHome);
|
|
136
|
+
if (!conn?.accessToken) return null;
|
|
137
|
+
|
|
138
|
+
const expiresSoon = !conn.expiresAt || conn.expiresAt - Date.now() < 60_000;
|
|
139
|
+
if (!expiresSoon) return conn.accessToken;
|
|
140
|
+
if (!conn.refreshToken) {
|
|
141
|
+
_deleteMonoesConnection(monomindHome);
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
const res = await fetch(`${MONOES_BASE_URL}/api/auth/oauth2/token`, {
|
|
147
|
+
method: 'POST',
|
|
148
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
149
|
+
body: new URLSearchParams({
|
|
150
|
+
grant_type: 'refresh_token',
|
|
151
|
+
refresh_token: /* value */ conn.refreshToken,
|
|
152
|
+
client_id: conn.clientId,
|
|
153
|
+
}),
|
|
154
|
+
});
|
|
155
|
+
if (!res.ok) throw new Error(`refresh failed: ${res.status}`);
|
|
156
|
+
const data = await res.json();
|
|
157
|
+
const updated = {
|
|
158
|
+
...conn,
|
|
159
|
+
accessToken: /* value */ data.access_token,
|
|
160
|
+
refreshToken: /* value */ data.refresh_token || conn.refreshToken,
|
|
161
|
+
expiresAt: Date.now() + (data.expires_in || 3600) * 1000,
|
|
162
|
+
};
|
|
163
|
+
_writeMonoesConnection(monomindHome, updated);
|
|
164
|
+
return updated.accessToken;
|
|
165
|
+
} catch {
|
|
166
|
+
_deleteMonoesConnection(monomindHome);
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export async function handleMonoesRoutes(req, res, url, corsOrigin, ctx) {
|
|
172
|
+
const { MONOMIND_HOME, dashboardPort, projectDir } = ctx;
|
|
173
|
+
const redirectUri = `http://127.0.0.1:${dashboardPort}/api/monoes/callback`;
|
|
174
|
+
|
|
175
|
+
// --------------------------------------------------- POST /api/monoes/connect
|
|
176
|
+
if (req.method === 'POST' && url === '/api/monoes/connect') {
|
|
177
|
+
try {
|
|
178
|
+
_pruneExpiredStates();
|
|
179
|
+
const clientId = await _getOrRegisterClientId(MONOMIND_HOME, redirectUri);
|
|
180
|
+
const { verifier, challenge } = createPkcePair();
|
|
181
|
+
const state = _base64url(crypto.randomBytes(16));
|
|
182
|
+
_pendingStates.set(state, { codeVerifier: verifier, createdAt: Date.now() });
|
|
183
|
+
|
|
184
|
+
const authorizeUrl =
|
|
185
|
+
`${MONOES_BASE_URL}/api/auth/oauth2/authorize?client_id=${encodeURIComponent(clientId)}` +
|
|
186
|
+
`&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=code` +
|
|
187
|
+
`&scope=${encodeURIComponent(MONOES_SCOPE)}` +
|
|
188
|
+
`&code_challenge=${challenge}&code_challenge_method=S256&state=${state}`;
|
|
189
|
+
|
|
190
|
+
_json(res, corsOrigin, 200, { authorizeUrl });
|
|
191
|
+
} catch (err) {
|
|
192
|
+
_json(res, corsOrigin, 500, { error: err.message });
|
|
193
|
+
}
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// -------------------------------------------------- GET /api/monoes/callback
|
|
198
|
+
if (req.method === 'GET' && url.startsWith('/api/monoes/callback')) {
|
|
199
|
+
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
200
|
+
const code = qs.get('code');
|
|
201
|
+
const state = qs.get('state');
|
|
202
|
+
const pending = state ? _pendingStates.get(state) : null;
|
|
203
|
+
|
|
204
|
+
const closeTab = (message) => {
|
|
205
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
206
|
+
res.end(
|
|
207
|
+
`<!doctype html><html><body style="font-family:sans-serif;padding:2rem;">${message}</body></html>`,
|
|
208
|
+
);
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
if (!code || !pending) {
|
|
212
|
+
closeTab('Connection failed — the request was invalid or expired. You can close this tab and try again.');
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
_pendingStates.delete(state);
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
const existing = readMonoesConnection(MONOMIND_HOME);
|
|
219
|
+
const tokenRes = await fetch(`${MONOES_BASE_URL}/api/auth/oauth2/token`, {
|
|
220
|
+
method: 'POST',
|
|
221
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
222
|
+
body: new URLSearchParams({
|
|
223
|
+
grant_type: 'authorization_code',
|
|
224
|
+
code,
|
|
225
|
+
redirect_uri: redirectUri,
|
|
226
|
+
client_id: existing.clientId,
|
|
227
|
+
code_verifier: pending.codeVerifier,
|
|
228
|
+
}),
|
|
229
|
+
});
|
|
230
|
+
if (!tokenRes.ok) throw new Error(`token exchange failed: ${tokenRes.status}`);
|
|
231
|
+
const tokenData = await tokenRes.json();
|
|
232
|
+
|
|
233
|
+
const meRes = await fetch(`${MONOES_BASE_URL}/api/community/me`, {
|
|
234
|
+
headers: { Authorization: `Bearer ${tokenData.access_token}` },
|
|
235
|
+
});
|
|
236
|
+
const me = meRes.ok ? await meRes.json() : { username: null };
|
|
237
|
+
|
|
238
|
+
_writeMonoesConnection(MONOMIND_HOME, {
|
|
239
|
+
clientId: existing.clientId,
|
|
240
|
+
accessToken: /* value */ tokenData.access_token,
|
|
241
|
+
refreshToken: /* value */ tokenData.refresh_token || null,
|
|
242
|
+
expiresAt: Date.now() + (tokenData.expires_in || 3600) * 1000,
|
|
243
|
+
connectedUsername: me.username || null,
|
|
244
|
+
});
|
|
245
|
+
_syncMonoesMcpEntry(path.resolve(projectDir || process.cwd()), tokenData.access_token);
|
|
246
|
+
|
|
247
|
+
closeTab('Connected to monoes.me — you can close this tab.');
|
|
248
|
+
} catch (err) {
|
|
249
|
+
closeTab(`Connection failed: ${err.message}. You can close this tab and try again.`);
|
|
250
|
+
}
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ---------------------------------------------------- GET /api/monoes/status
|
|
255
|
+
// Polled on every dashboard load — piggybacks the silent-refresh check so
|
|
256
|
+
// .mcp.json's embedded access token self-heals (refreshed or removed)
|
|
257
|
+
// roughly as often as the dashboard is opened, instead of only on the
|
|
258
|
+
// explicit connect/disconnect actions.
|
|
259
|
+
if (req.method === 'GET' && url === '/api/monoes/status') {
|
|
260
|
+
const beforeConn = readMonoesConnection(MONOMIND_HOME);
|
|
261
|
+
const validToken = /* value */ beforeConn?.accessToken ? await getValidMonoesToken(MONOMIND_HOME) : null;
|
|
262
|
+
const afterConn = readMonoesConnection(MONOMIND_HOME);
|
|
263
|
+
if (validToken !== (beforeConn?.accessToken || null)) {
|
|
264
|
+
_syncMonoesMcpEntry(path.resolve(projectDir || process.cwd()), validToken);
|
|
265
|
+
}
|
|
266
|
+
_json(res, corsOrigin, 200, {
|
|
267
|
+
connected: !!validToken,
|
|
268
|
+
username: validToken ? afterConn?.connectedUsername || null : null,
|
|
269
|
+
});
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// ------------------------------------------------ POST /api/monoes/disconnect
|
|
274
|
+
if (req.method === 'POST' && url === '/api/monoes/disconnect') {
|
|
275
|
+
_deleteMonoesConnection(MONOMIND_HOME);
|
|
276
|
+
_syncMonoesMcpEntry(path.resolve(projectDir || process.cwd()), null);
|
|
277
|
+
_json(res, corsOrigin, 200, { connected: false });
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ----------------------------------------------- POST /api/monoes/upload-org
|
|
282
|
+
// Reads the org's existing on-disk definition (the same
|
|
283
|
+
// .monomind/orgs/<name>.json file GET /api/orgs/:name already serves —
|
|
284
|
+
// no new read path) and forwards it to monoes.me's real upload endpoint.
|
|
285
|
+
if (req.method === 'POST' && url === '/api/monoes/upload-org') {
|
|
286
|
+
let body = '';
|
|
287
|
+
req.on('data', (c) => {
|
|
288
|
+
body += c;
|
|
289
|
+
if (body.length > 1e6) req.destroy();
|
|
290
|
+
});
|
|
291
|
+
req.on('end', async () => {
|
|
292
|
+
try {
|
|
293
|
+
const { orgName, dir } = JSON.parse(body || '{}');
|
|
294
|
+
if (!orgName || orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) {
|
|
295
|
+
_json(res, corsOrigin, 400, { error: 'Invalid org name' });
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const token = await getValidMonoesToken(MONOMIND_HOME);
|
|
300
|
+
if (!token) {
|
|
301
|
+
_json(res, corsOrigin, 401, { error: 'not_connected' });
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const root = path.resolve(dir || ctx.projectDir || process.cwd());
|
|
306
|
+
const projDir = ctx._resolveOrgProjectDir(orgName, root) || root;
|
|
307
|
+
const orgFile = path.join(projDir, '.monomind', 'orgs', `${orgName}.json`);
|
|
308
|
+
if (!fs.existsSync(orgFile)) {
|
|
309
|
+
_json(res, corsOrigin, 404, { error: 'Org not found' });
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
const orgJson = fs.readFileSync(orgFile, 'utf8');
|
|
313
|
+
|
|
314
|
+
const uploadRes = await fetch(`${MONOES_BASE_URL}/api/community/orgs`, {
|
|
315
|
+
method: 'POST',
|
|
316
|
+
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
|
|
317
|
+
body: JSON.stringify({ orgJson }),
|
|
318
|
+
});
|
|
319
|
+
const uploadBody = await uploadRes.json().catch(() => ({}));
|
|
320
|
+
if (!uploadRes.ok) {
|
|
321
|
+
_json(res, corsOrigin, uploadRes.status, uploadBody);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
_json(res, corsOrigin, 201, {
|
|
325
|
+
...uploadBody,
|
|
326
|
+
url: `${MONOES_BASE_URL}/community/orgs/${uploadBody.id}`,
|
|
327
|
+
});
|
|
328
|
+
} catch (err) {
|
|
329
|
+
_json(res, corsOrigin, 500, { error: err.message });
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
return true;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return false;
|
|
336
|
+
}
|
package/dist/src/ui/server.mjs
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
collectTokens,
|
|
20
20
|
} from './collector.mjs';
|
|
21
21
|
import { handleMonographRoutes } from './routes-monograph.mjs';
|
|
22
|
+
import { handleMonoesRoutes } from './routes-monoes.mjs';
|
|
22
23
|
import { handleOrgRoutes } from './routes-org.mjs';
|
|
23
24
|
import {
|
|
24
25
|
addMmClient,
|
|
@@ -1778,7 +1779,13 @@ export async function startServer({
|
|
|
1778
1779
|
]);
|
|
1779
1780
|
const _isOpenRoute = (url, method) =>
|
|
1780
1781
|
(method === 'GET' || method === 'HEAD' || method === 'OPTIONS') &&
|
|
1781
|
-
(_OPEN_ROUTES.has(url) ||
|
|
1782
|
+
(_OPEN_ROUTES.has(url) ||
|
|
1783
|
+
/^\/data\/avatars\/[A-Za-z0-9._-]+\.svg$/.test(url) ||
|
|
1784
|
+
// The browser hits this directly via monoes.me's OAuth redirect — it
|
|
1785
|
+
// never has this dashboard's own auth token to attach. Protected
|
|
1786
|
+
// instead by the OAuth `state` parameter (routes-monoes.mjs rejects
|
|
1787
|
+
// any code/state pair it didn't itself generate).
|
|
1788
|
+
url.startsWith('/api/monoes/callback'));
|
|
1782
1789
|
|
|
1783
1790
|
const server = http.createServer(async (req, res) => {
|
|
1784
1791
|
const url = req.url.split('?')[0];
|
|
@@ -5296,6 +5303,17 @@ export async function startServer({
|
|
|
5296
5303
|
return;
|
|
5297
5304
|
}
|
|
5298
5305
|
|
|
5306
|
+
// ── monoes.me connection routes (extracted to routes-monoes.mjs) ───────
|
|
5307
|
+
if (
|
|
5308
|
+
await handleMonoesRoutes(req, res, url, corsOrigin, {
|
|
5309
|
+
MONOMIND_HOME,
|
|
5310
|
+
dashboardPort: _boundPortForCors,
|
|
5311
|
+
projectDir,
|
|
5312
|
+
_resolveOrgProjectDir,
|
|
5313
|
+
})
|
|
5314
|
+
)
|
|
5315
|
+
return;
|
|
5316
|
+
|
|
5299
5317
|
// ── Org/mastermind routes (extracted to routes-org.mjs) ────────────────
|
|
5300
5318
|
if (
|
|
5301
5319
|
await handleOrgRoutes(req, res, url, corsOrigin, {
|