ogment 0.2.0 → 0.2.2
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/api.d.ts +12 -5
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +2 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +51 -23
- package/dist/cli.js +0 -0
- package/dist/commands/call.d.ts.map +1 -1
- package/dist/commands/call.js +9 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/mcp-client.d.ts.map +1 -1
- package/dist/mcp-client.js +3 -1
- package/package.json +1 -1
package/dist/api.d.ts
CHANGED
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Ogment API client — calls the backend REST endpoints.
|
|
3
3
|
*/
|
|
4
|
+
export interface OgmentServer {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
path: string;
|
|
8
|
+
enabled: boolean;
|
|
9
|
+
}
|
|
4
10
|
export interface OgmentOrg {
|
|
11
|
+
orgId: string;
|
|
5
12
|
orgSlug: string;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
enabled: boolean;
|
|
10
|
-
}[];
|
|
13
|
+
orgName: string | null;
|
|
14
|
+
role: string;
|
|
15
|
+
servers: OgmentServer[];
|
|
11
16
|
}
|
|
12
17
|
export interface OgmentMe {
|
|
18
|
+
userId: string;
|
|
13
19
|
email: string | null;
|
|
14
20
|
name: string | null;
|
|
21
|
+
imageUrl: string | null;
|
|
15
22
|
orgs: OgmentOrg[];
|
|
16
23
|
}
|
|
17
24
|
export declare function fetchMe(token: string): Promise<OgmentMe>;
|
package/dist/api.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA;;GAEG;AAQH,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA;;GAEG;AAQH,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,IAAI,EAAE,SAAS,EAAE,CAAC;CACnB;AAMD,wBAAsB,OAAO,CAAC,KAAK,EAAE,MAAM,qBAY1C"}
|
package/dist/api.js
CHANGED
package/dist/auth.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAuBH,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,IAAI,EAAE,SAAS,EAAE,CAAC;CACnB;AAiCD,wBAAgB,eAAe,IAAI,iBAAiB,GAAG,IAAI,CAgB1D;AAeD,wBAAgB,iBAAiB,SAIhC;AAED;;GAEG;AACH,wBAAgB,kBAAkB,sBAMjC;AAoED,wBAAsB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,iBAoB7F;AA+ID;;;;;;;;GAQG;AACH,wBAAsB,KAAK,+BA6D1B"}
|
package/dist/auth.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* 7. Store credentials locally
|
|
12
12
|
*/
|
|
13
13
|
import { createHash, randomBytes } from 'node:crypto';
|
|
14
|
-
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
14
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
15
15
|
import { createServer } from 'node:http';
|
|
16
16
|
import open from 'open';
|
|
17
17
|
import { CLI_CLIENT_NAME, CLI_REDIRECT_HOST, CONFIG_DIR, CREDENTIALS_PATH, OAUTH_AUTHORIZE_URL, OAUTH_REGISTER_URL, OAUTH_REVOKE_URL, OAUTH_TOKEN_URL, } from './config.js';
|
|
@@ -31,10 +31,22 @@ export function loadCredentials() {
|
|
|
31
31
|
if (!existsSync(CREDENTIALS_PATH))
|
|
32
32
|
return null;
|
|
33
33
|
const raw = readFileSync(CREDENTIALS_PATH, 'utf-8');
|
|
34
|
-
|
|
34
|
+
const parsed = JSON.parse(raw);
|
|
35
|
+
// Basic shape validation — corrupted files are treated as "not logged in"
|
|
36
|
+
if (typeof parsed.accessToken !== 'string' || !parsed.accessToken ||
|
|
37
|
+
typeof parsed.clientId !== 'string' || !parsed.clientId ||
|
|
38
|
+
!Array.isArray(parsed.orgs)) {
|
|
39
|
+
unlinkSync(CREDENTIALS_PATH);
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
return parsed;
|
|
35
43
|
}
|
|
36
44
|
function saveCredentials(creds) {
|
|
37
45
|
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
46
|
+
// Prevent symlink attacks — don't follow symlinks when writing credentials
|
|
47
|
+
if (existsSync(CREDENTIALS_PATH) && lstatSync(CREDENTIALS_PATH).isSymbolicLink()) {
|
|
48
|
+
unlinkSync(CREDENTIALS_PATH);
|
|
49
|
+
}
|
|
38
50
|
writeFileSync(CREDENTIALS_PATH, JSON.stringify(creds, null, 2), {
|
|
39
51
|
mode: 0o600, // owner read/write only
|
|
40
52
|
});
|
|
@@ -128,9 +140,28 @@ export async function revokeToken(token, clientId, clientSecret) {
|
|
|
128
140
|
* Starts a temporary HTTP server on a random port, waits for the OAuth
|
|
129
141
|
* callback, and returns the authorization code.
|
|
130
142
|
*/
|
|
131
|
-
|
|
143
|
+
/**
|
|
144
|
+
* Start a callback server on an OS-assigned port.
|
|
145
|
+
* Port 0 = OS assigns a free port atomically (no TOCTOU race).
|
|
146
|
+
*/
|
|
147
|
+
function startCallbackServerWithPort() {
|
|
148
|
+
return new Promise((resolve) => {
|
|
149
|
+
const server = createServer();
|
|
150
|
+
server.listen(0, CLI_REDIRECT_HOST, () => {
|
|
151
|
+
const addr = server.address();
|
|
152
|
+
// unref so the server doesn't keep the process alive after login completes
|
|
153
|
+
server.unref();
|
|
154
|
+
resolve({ server, port: addr.port });
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Wait for the OAuth callback on an already-listening server.
|
|
160
|
+
* Returns the authorization code and state.
|
|
161
|
+
*/
|
|
162
|
+
function waitForCallback(server, port) {
|
|
132
163
|
return new Promise((resolve, reject) => {
|
|
133
|
-
|
|
164
|
+
server.on('request', (req, res) => {
|
|
134
165
|
const url = new URL(req.url ?? '/', `http://${CLI_REDIRECT_HOST}:${port}`);
|
|
135
166
|
if (url.pathname !== '/callback') {
|
|
136
167
|
res.writeHead(404);
|
|
@@ -159,9 +190,6 @@ function waitForAuthCallback(port) {
|
|
|
159
190
|
server.close();
|
|
160
191
|
resolve({ code, state });
|
|
161
192
|
});
|
|
162
|
-
server.listen(port, CLI_REDIRECT_HOST, () => {
|
|
163
|
-
// Server is ready
|
|
164
|
-
});
|
|
165
193
|
// Timeout after 5 minutes (unref so it doesn't block process exit)
|
|
166
194
|
const timeout = setTimeout(() => {
|
|
167
195
|
server.close();
|
|
@@ -170,14 +198,14 @@ function waitForAuthCallback(port) {
|
|
|
170
198
|
timeout.unref();
|
|
171
199
|
});
|
|
172
200
|
}
|
|
173
|
-
function getRandomPort() {
|
|
174
|
-
// Use port 0 to let OS assign, but we need to know the port before starting.
|
|
175
|
-
// Instead, pick from ephemeral range.
|
|
176
|
-
return 49152 + Math.floor(Math.random() * 16384);
|
|
177
|
-
}
|
|
178
201
|
// ---------------------------------------------------------------------------
|
|
179
|
-
// HTML
|
|
202
|
+
// HTML helpers
|
|
180
203
|
// ---------------------------------------------------------------------------
|
|
204
|
+
function escapeHtml(str) {
|
|
205
|
+
if (!str)
|
|
206
|
+
return '';
|
|
207
|
+
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
208
|
+
}
|
|
181
209
|
function successPage() {
|
|
182
210
|
return `<!DOCTYPE html>
|
|
183
211
|
<html lang="en">
|
|
@@ -224,8 +252,8 @@ function errorPage(error, description) {
|
|
|
224
252
|
<div class="card">
|
|
225
253
|
<div class="icon">⚠️</div>
|
|
226
254
|
<h1>Login failed</h1>
|
|
227
|
-
<p>${description
|
|
228
|
-
<p class="error">${error}</p>
|
|
255
|
+
<p>${escapeHtml(description) || 'Something went wrong during authentication.'}</p>
|
|
256
|
+
<p class="error">${escapeHtml(error)}</p>
|
|
229
257
|
</div>
|
|
230
258
|
</body>
|
|
231
259
|
</html>`;
|
|
@@ -247,14 +275,15 @@ export async function login() {
|
|
|
247
275
|
if (existing?.accessToken) {
|
|
248
276
|
return existing;
|
|
249
277
|
}
|
|
250
|
-
|
|
278
|
+
// Step 1 — Start callback server (OS assigns port atomically — no race)
|
|
279
|
+
const { server: callbackServer, port } = await startCallbackServerWithPort();
|
|
251
280
|
const redirectUri = `http://${CLI_REDIRECT_HOST}:${port}/callback`;
|
|
252
|
-
// Step
|
|
281
|
+
// Step 2 — Register as OAuth client (needs redirect URI with real port)
|
|
253
282
|
const client = await registerClient(redirectUri);
|
|
254
|
-
// Step
|
|
283
|
+
// Step 3 — Generate PKCE pair
|
|
255
284
|
const codeVerifier = generateCodeVerifier();
|
|
256
285
|
const codeChallenge = generateCodeChallenge(codeVerifier);
|
|
257
|
-
// Step
|
|
286
|
+
// Step 4 — Build authorization URL (no resource — server-less login)
|
|
258
287
|
const state = randomBytes(16).toString('hex');
|
|
259
288
|
const authorizeUrl = new URL(OAUTH_AUTHORIZE_URL);
|
|
260
289
|
authorizeUrl.searchParams.set('client_id', client.client_id);
|
|
@@ -263,11 +292,10 @@ export async function login() {
|
|
|
263
292
|
authorizeUrl.searchParams.set('code_challenge', codeChallenge);
|
|
264
293
|
authorizeUrl.searchParams.set('code_challenge_method', 'S256');
|
|
265
294
|
authorizeUrl.searchParams.set('state', state);
|
|
266
|
-
// Step
|
|
267
|
-
const
|
|
295
|
+
// Step 5 — Open browser, wait for callback
|
|
296
|
+
const codePromise = waitForCallback(callbackServer, port);
|
|
268
297
|
await open(authorizeUrl.toString());
|
|
269
|
-
|
|
270
|
-
const { code, state: returnedState } = await callbackPromise;
|
|
298
|
+
const { code, state: returnedState } = await codePromise;
|
|
271
299
|
if (returnedState !== state) {
|
|
272
300
|
throw new Error('OAuth state mismatch — possible CSRF attack.');
|
|
273
301
|
}
|
package/dist/cli.js
CHANGED
|
File without changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"call.d.ts","sourceRoot":"","sources":["../../src/commands/call.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,wBAAsB,WAAW,CAC/B,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"call.d.ts","sourceRoot":"","sources":["../../src/commands/call.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,wBAAsB,WAAW,CAC/B,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GAAG,SAAS,iBAoC7B"}
|
package/dist/commands/call.js
CHANGED
|
@@ -20,7 +20,15 @@ export async function callCommand(serverPath, toolName, argsJson) {
|
|
|
20
20
|
const available = allServers.map((s) => s.path).join(', ');
|
|
21
21
|
throw new Error(`Server "${serverPath}" not found. Available: ${available || 'none'}`);
|
|
22
22
|
}
|
|
23
|
-
|
|
23
|
+
let args = {};
|
|
24
|
+
if (argsJson) {
|
|
25
|
+
try {
|
|
26
|
+
args = JSON.parse(argsJson);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
throw new Error(`Invalid JSON arguments: ${argsJson}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
24
32
|
const result = await callTool(server.orgSlug, serverPath, creds.accessToken, toolName, args);
|
|
25
33
|
console.log(JSON.stringify(result, null, 2));
|
|
26
34
|
}
|
package/dist/config.d.ts
CHANGED
|
@@ -19,5 +19,5 @@ export declare const CONFIG_DIR: string;
|
|
|
19
19
|
export declare const CREDENTIALS_PATH: string;
|
|
20
20
|
export declare const CLI_CLIENT_NAME = "Ogment CLI";
|
|
21
21
|
export declare const CLI_REDIRECT_HOST = "127.0.0.1";
|
|
22
|
-
export declare const VERSION = "0.
|
|
22
|
+
export declare const VERSION = "0.2.2";
|
|
23
23
|
//# sourceMappingURL=config.d.ts.map
|
package/dist/config.js
CHANGED
|
@@ -33,4 +33,4 @@ export const CLI_REDIRECT_HOST = '127.0.0.1';
|
|
|
33
33
|
// ---------------------------------------------------------------------------
|
|
34
34
|
// Version
|
|
35
35
|
// ---------------------------------------------------------------------------
|
|
36
|
-
export const VERSION = '0.
|
|
36
|
+
export const VERSION = '0.2.2';
|
package/dist/mcp-client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-client.d.ts","sourceRoot":"","sources":["../src/mcp-client.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,UAAU,iBAAiB;IACzB,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC1C,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;
|
|
1
|
+
{"version":3,"file":"mcp-client.d.ts","sourceRoot":"","sources":["../src/mcp-client.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,UAAU,iBAAiB;IACzB,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC1C,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAmDD,qDAAqD;AACrD,wBAAsB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,sBAKlF;AAED,qDAAqD;AACrD,wBAAsB,QAAQ,CAC5B,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,8BAM9B"}
|
package/dist/mcp-client.js
CHANGED
|
@@ -26,7 +26,9 @@ async function mcpRequest(orgSlug, serverPath, token, method, params) {
|
|
|
26
26
|
});
|
|
27
27
|
if (!res.ok) {
|
|
28
28
|
const text = await res.text();
|
|
29
|
-
|
|
29
|
+
// Truncate error bodies to avoid leaking server internals into agent logs
|
|
30
|
+
const sanitized = text.length > 200 ? text.slice(0, 200) + '…' : text;
|
|
31
|
+
throw new Error(`MCP request failed (${res.status}): ${sanitized}`);
|
|
30
32
|
}
|
|
31
33
|
const json = (await res.json());
|
|
32
34
|
if ('error' in json && json.error) {
|