ogment 0.2.0 → 0.2.1
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.d.ts.map +1 -1
- package/dist/auth.js +49 -23
- 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/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;AA6ID;;;;;;;;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,26 @@ 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
|
+
resolve({ server, port: addr.port });
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Wait for the OAuth callback on an already-listening server.
|
|
158
|
+
* Returns the authorization code and state.
|
|
159
|
+
*/
|
|
160
|
+
function waitForCallback(server, port) {
|
|
132
161
|
return new Promise((resolve, reject) => {
|
|
133
|
-
|
|
162
|
+
server.on('request', (req, res) => {
|
|
134
163
|
const url = new URL(req.url ?? '/', `http://${CLI_REDIRECT_HOST}:${port}`);
|
|
135
164
|
if (url.pathname !== '/callback') {
|
|
136
165
|
res.writeHead(404);
|
|
@@ -159,9 +188,6 @@ function waitForAuthCallback(port) {
|
|
|
159
188
|
server.close();
|
|
160
189
|
resolve({ code, state });
|
|
161
190
|
});
|
|
162
|
-
server.listen(port, CLI_REDIRECT_HOST, () => {
|
|
163
|
-
// Server is ready
|
|
164
|
-
});
|
|
165
191
|
// Timeout after 5 minutes (unref so it doesn't block process exit)
|
|
166
192
|
const timeout = setTimeout(() => {
|
|
167
193
|
server.close();
|
|
@@ -170,14 +196,14 @@ function waitForAuthCallback(port) {
|
|
|
170
196
|
timeout.unref();
|
|
171
197
|
});
|
|
172
198
|
}
|
|
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
199
|
// ---------------------------------------------------------------------------
|
|
179
|
-
// HTML
|
|
200
|
+
// HTML helpers
|
|
180
201
|
// ---------------------------------------------------------------------------
|
|
202
|
+
function escapeHtml(str) {
|
|
203
|
+
if (!str)
|
|
204
|
+
return '';
|
|
205
|
+
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
206
|
+
}
|
|
181
207
|
function successPage() {
|
|
182
208
|
return `<!DOCTYPE html>
|
|
183
209
|
<html lang="en">
|
|
@@ -224,8 +250,8 @@ function errorPage(error, description) {
|
|
|
224
250
|
<div class="card">
|
|
225
251
|
<div class="icon">⚠️</div>
|
|
226
252
|
<h1>Login failed</h1>
|
|
227
|
-
<p>${description
|
|
228
|
-
<p class="error">${error}</p>
|
|
253
|
+
<p>${escapeHtml(description) || 'Something went wrong during authentication.'}</p>
|
|
254
|
+
<p class="error">${escapeHtml(error)}</p>
|
|
229
255
|
</div>
|
|
230
256
|
</body>
|
|
231
257
|
</html>`;
|
|
@@ -247,14 +273,15 @@ export async function login() {
|
|
|
247
273
|
if (existing?.accessToken) {
|
|
248
274
|
return existing;
|
|
249
275
|
}
|
|
250
|
-
|
|
276
|
+
// Step 1 — Start callback server (OS assigns port atomically — no race)
|
|
277
|
+
const { server: callbackServer, port } = await startCallbackServerWithPort();
|
|
251
278
|
const redirectUri = `http://${CLI_REDIRECT_HOST}:${port}/callback`;
|
|
252
|
-
// Step
|
|
279
|
+
// Step 2 — Register as OAuth client (needs redirect URI with real port)
|
|
253
280
|
const client = await registerClient(redirectUri);
|
|
254
|
-
// Step
|
|
281
|
+
// Step 3 — Generate PKCE pair
|
|
255
282
|
const codeVerifier = generateCodeVerifier();
|
|
256
283
|
const codeChallenge = generateCodeChallenge(codeVerifier);
|
|
257
|
-
// Step
|
|
284
|
+
// Step 4 — Build authorization URL (no resource — server-less login)
|
|
258
285
|
const state = randomBytes(16).toString('hex');
|
|
259
286
|
const authorizeUrl = new URL(OAUTH_AUTHORIZE_URL);
|
|
260
287
|
authorizeUrl.searchParams.set('client_id', client.client_id);
|
|
@@ -263,11 +290,10 @@ export async function login() {
|
|
|
263
290
|
authorizeUrl.searchParams.set('code_challenge', codeChallenge);
|
|
264
291
|
authorizeUrl.searchParams.set('code_challenge_method', 'S256');
|
|
265
292
|
authorizeUrl.searchParams.set('state', state);
|
|
266
|
-
// Step
|
|
267
|
-
const
|
|
293
|
+
// Step 5 — Open browser, wait for callback
|
|
294
|
+
const codePromise = waitForCallback(callbackServer, port);
|
|
268
295
|
await open(authorizeUrl.toString());
|
|
269
|
-
|
|
270
|
-
const { code, state: returnedState } = await callbackPromise;
|
|
296
|
+
const { code, state: returnedState } = await codePromise;
|
|
271
297
|
if (returnedState !== state) {
|
|
272
298
|
throw new Error('OAuth state mismatch — possible CSRF attack.');
|
|
273
299
|
}
|
|
@@ -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.1
|
|
22
|
+
export declare const VERSION = "0.2.1";
|
|
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.1
|
|
36
|
+
export const VERSION = '0.2.1';
|
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) {
|