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 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
- servers: {
7
- name: string;
8
- path: string;
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,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,EAAE,CAAC;CAC7D;AAED,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,SAAS,EAAE,CAAC;CACnB;AAMD,wBAAsB,OAAO,CAAC,KAAK,EAAE,MAAM,qBAW1C"}
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
@@ -13,5 +13,6 @@ export async function fetchMe(token) {
13
13
  const text = await res.text();
14
14
  throw new Error(`Failed to fetch account info (${res.status}): ${text}`);
15
15
  }
16
- return (await res.json());
16
+ const body = (await res.json());
17
+ return body.data;
17
18
  }
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAsBH,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,CAI1D;AASD,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;AAgID;;;;;;;;GAQG;AACH,wBAAsB,KAAK,+BA8D1B"}
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
- return JSON.parse(raw);
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
- function waitForAuthCallback(port) {
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
- const server = createServer((req, res) => {
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 pages for the callback
202
+ // HTML helpers
180
203
  // ---------------------------------------------------------------------------
204
+ function escapeHtml(str) {
205
+ if (!str)
206
+ return '';
207
+ return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
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">&#x26A0;&#xFE0F;</div>
226
254
  <h1>Login failed</h1>
227
- <p>${description ?? 'Something went wrong during authentication.'}</p>
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
- const port = getRandomPort();
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 1 — Register as OAuth client
281
+ // Step 2 — Register as OAuth client (needs redirect URI with real port)
253
282
  const client = await registerClient(redirectUri);
254
- // Step 2 — Generate PKCE pair
283
+ // Step 3 — Generate PKCE pair
255
284
  const codeVerifier = generateCodeVerifier();
256
285
  const codeChallenge = generateCodeChallenge(codeVerifier);
257
- // Step 3 — Build authorization URL (no resource — server-less login)
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 4Start callback server, then open browser
267
- const callbackPromise = waitForAuthCallback(port);
295
+ // Step 5Open browser, wait for callback
296
+ const codePromise = waitForCallback(callbackServer, port);
268
297
  await open(authorizeUrl.toString());
269
- // Step 5 Wait for callback
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,iBA6B7B"}
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"}
@@ -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
- const args = argsJson ? JSON.parse(argsJson) : {};
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.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.1.0';
36
+ export const VERSION = '0.2.2';
@@ -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;AAiDD,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"}
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"}
@@ -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
- throw new Error(`MCP request failed (${res.status}): ${text}`);
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ogment",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Ogment Vault CLI — secure your AI agents' SaaS credentials",
5
5
  "type": "module",
6
6
  "bin": {