ogment 0.1.0
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/README.md +94 -0
- package/dist/auth.d.ts +45 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +275 -0
- package/dist/cli.d.ts +11 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +81 -0
- package/dist/commands/call.d.ts +12 -0
- package/dist/commands/call.d.ts.map +1 -0
- package/dist/commands/call.js +26 -0
- package/dist/commands/login.d.ts +9 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +64 -0
- package/dist/commands/logout.d.ts +5 -0
- package/dist/commands/logout.d.ts.map +1 -0
- package/dist/commands/logout.js +39 -0
- package/dist/commands/servers.d.ts +13 -0
- package/dist/commands/servers.d.ts.map +1 -0
- package/dist/commands/servers.js +61 -0
- package/dist/config.d.ts +31 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +47 -0
- package/dist/mcp-client.d.ts +24 -0
- package/dist/mcp-client.d.ts.map +1 -0
- package/dist/mcp-client.js +52 -0
- package/dist/ui.d.ts +24 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +55 -0
- package/package.json +59 -0
package/README.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Ogment CLI
|
|
2
|
+
|
|
3
|
+
**Secure your AI agents' SaaS credentials.**
|
|
4
|
+
|
|
5
|
+
Ogment sits between your AI agent and your SaaS tools. Your agent gets a scoped, short-lived token. Your real API credentials never leave Ogment.
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# 1. Log in (opens browser for OAuth)
|
|
11
|
+
npx ogment login
|
|
12
|
+
|
|
13
|
+
# 2. Get config snippets for your MCP client
|
|
14
|
+
npx ogment connect acme/main
|
|
15
|
+
|
|
16
|
+
# 3. Paste the snippet into Claude Desktop, Cursor, or ChatGPT
|
|
17
|
+
# → Your agent is connected. Done.
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Commands
|
|
21
|
+
|
|
22
|
+
### `ogment login`
|
|
23
|
+
|
|
24
|
+
Authenticate with the Ogment platform via OAuth2 in the browser.
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npx ogment login
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Opens your browser to sign in. After authentication, a token is stored locally at `~/.config/ogment/credentials.json`.
|
|
31
|
+
|
|
32
|
+
### `ogment connect <org>/<server>`
|
|
33
|
+
|
|
34
|
+
Print ready-to-paste config snippets for every major MCP client.
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# Interactive
|
|
38
|
+
npx ogment connect acme/main
|
|
39
|
+
|
|
40
|
+
# JSON output (for CI/CD)
|
|
41
|
+
npx ogment connect acme/main --json
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The `<org>/<server>` is your organization slug and MCP server path, visible in the Ogment dashboard.
|
|
45
|
+
|
|
46
|
+
### `ogment logout`
|
|
47
|
+
|
|
48
|
+
Revoke your token server-side and delete local credentials. All agents using this token lose access immediately.
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npx ogment logout
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### `ogment whoami`
|
|
55
|
+
|
|
56
|
+
Check if your stored token is still valid.
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npx ogment whoami
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## How It Works
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
AI Agent (Claude, Cursor, ChatGPT)
|
|
66
|
+
│
|
|
67
|
+
│ Ogment token (short-lived, revocable)
|
|
68
|
+
▼
|
|
69
|
+
┌─────────────────────────────────┐
|
|
70
|
+
│ Ogment Vault Gateway │
|
|
71
|
+
│ ✓ Validate token │
|
|
72
|
+
│ ✓ Inject real credentials │
|
|
73
|
+
│ ✓ Log every tool call │
|
|
74
|
+
└──────────────┬──────────────────┘
|
|
75
|
+
│
|
|
76
|
+
┌──────────┼──────────┐
|
|
77
|
+
▼ ▼ ▼
|
|
78
|
+
Salesforce Linear Snowflake
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
1. **`ogment login`** — OAuth2 flow, stores a bearer token locally
|
|
82
|
+
2. **`ogment connect`** — Prints config snippet with token + MCP endpoint
|
|
83
|
+
3. **Agent calls tools** — Through Ogment's MCP proxy, real credentials injected server-side
|
|
84
|
+
4. **`ogment logout`** — Kill switch: revoke token, agent loses access instantly
|
|
85
|
+
|
|
86
|
+
## Environment Variables
|
|
87
|
+
|
|
88
|
+
| Variable | Default | Description |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| `OGMENT_BASE_URL` | `https://app.ogment.ai` | Ogment platform URL (for development) |
|
|
91
|
+
|
|
92
|
+
## License
|
|
93
|
+
|
|
94
|
+
MIT
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth2 Authorization Code flow with PKCE for the Ogment CLI.
|
|
3
|
+
*
|
|
4
|
+
* Flow:
|
|
5
|
+
* 1. Register as dynamic OAuth client (RFC 7591)
|
|
6
|
+
* 2. Generate PKCE code_verifier / code_challenge (S256)
|
|
7
|
+
* 3. Start a local HTTP server on a random port
|
|
8
|
+
* 4. Open browser → /authorize (user signs in via Clerk on the Ogment web app)
|
|
9
|
+
* 5. Capture redirect to localhost with ?code=xxx
|
|
10
|
+
* 6. Exchange code for access token at /token
|
|
11
|
+
* 7. Store credentials locally
|
|
12
|
+
*/
|
|
13
|
+
export interface StoredCredentials {
|
|
14
|
+
accessToken: string;
|
|
15
|
+
clientId: string;
|
|
16
|
+
clientSecret: string | null;
|
|
17
|
+
orgSlug: string;
|
|
18
|
+
servers: string[];
|
|
19
|
+
}
|
|
20
|
+
export declare function loadCredentials(): StoredCredentials | null;
|
|
21
|
+
export declare function deleteCredentials(): void;
|
|
22
|
+
/**
|
|
23
|
+
* Returns the stored credentials or throws if not logged in.
|
|
24
|
+
*/
|
|
25
|
+
export declare function requireCredentials(): StoredCredentials;
|
|
26
|
+
export declare function revokeToken(token: string, clientId: string, clientSecret: string | null): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Runs the full OAuth2 Authorization Code + PKCE login flow.
|
|
29
|
+
*
|
|
30
|
+
* The `resourceUrl` tells the backend which MCP server the user wants to
|
|
31
|
+
* connect to. This is required — the backend rejects auth requests without
|
|
32
|
+
* an associated server.
|
|
33
|
+
*
|
|
34
|
+
* Returns the access token.
|
|
35
|
+
*/
|
|
36
|
+
export declare function loginFlow(resourceUrl: string): Promise<{
|
|
37
|
+
accessToken: string;
|
|
38
|
+
clientId: string;
|
|
39
|
+
clientSecret: string | null;
|
|
40
|
+
}>;
|
|
41
|
+
/**
|
|
42
|
+
* Run the login flow and store credentials with org + servers.
|
|
43
|
+
*/
|
|
44
|
+
export declare function login(orgSlug: string, servers: string[], resourceUrl: string): Promise<StoredCredentials>;
|
|
45
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAsBH,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,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;AAmGD;;;;;;;;GAQG;AACH,wBAAsB,SAAS,CAAC,WAAW,EAAE,MAAM;;;;GA+ClD;AAED;;GAEG;AACH,wBAAsB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,MAAM,8BAiBlF"}
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth2 Authorization Code flow with PKCE for the Ogment CLI.
|
|
3
|
+
*
|
|
4
|
+
* Flow:
|
|
5
|
+
* 1. Register as dynamic OAuth client (RFC 7591)
|
|
6
|
+
* 2. Generate PKCE code_verifier / code_challenge (S256)
|
|
7
|
+
* 3. Start a local HTTP server on a random port
|
|
8
|
+
* 4. Open browser → /authorize (user signs in via Clerk on the Ogment web app)
|
|
9
|
+
* 5. Capture redirect to localhost with ?code=xxx
|
|
10
|
+
* 6. Exchange code for access token at /token
|
|
11
|
+
* 7. Store credentials locally
|
|
12
|
+
*/
|
|
13
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
14
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
15
|
+
import { createServer } from 'node:http';
|
|
16
|
+
import open from 'open';
|
|
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';
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// PKCE helpers (RFC 7636)
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
function generateCodeVerifier() {
|
|
22
|
+
return randomBytes(32).toString('base64url');
|
|
23
|
+
}
|
|
24
|
+
function generateCodeChallenge(verifier) {
|
|
25
|
+
return createHash('sha256').update(verifier).digest('base64url');
|
|
26
|
+
}
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Credential storage
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
export function loadCredentials() {
|
|
31
|
+
if (!existsSync(CREDENTIALS_PATH))
|
|
32
|
+
return null;
|
|
33
|
+
const raw = readFileSync(CREDENTIALS_PATH, 'utf-8');
|
|
34
|
+
return JSON.parse(raw);
|
|
35
|
+
}
|
|
36
|
+
function saveCredentials(creds) {
|
|
37
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
38
|
+
writeFileSync(CREDENTIALS_PATH, JSON.stringify(creds, null, 2), {
|
|
39
|
+
mode: 0o600, // owner read/write only
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
export function deleteCredentials() {
|
|
43
|
+
if (existsSync(CREDENTIALS_PATH)) {
|
|
44
|
+
unlinkSync(CREDENTIALS_PATH);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Returns the stored credentials or throws if not logged in.
|
|
49
|
+
*/
|
|
50
|
+
export function requireCredentials() {
|
|
51
|
+
const creds = loadCredentials();
|
|
52
|
+
if (!creds?.accessToken) {
|
|
53
|
+
throw new Error('Not logged in. Run `ogment login <org>/<server>` first.');
|
|
54
|
+
}
|
|
55
|
+
return creds;
|
|
56
|
+
}
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// Step 1 — Dynamic client registration (RFC 7591)
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
async function registerClient(redirectUri) {
|
|
61
|
+
const res = await fetch(OAUTH_REGISTER_URL, {
|
|
62
|
+
method: 'POST',
|
|
63
|
+
headers: { 'Content-Type': 'application/json' },
|
|
64
|
+
body: JSON.stringify({
|
|
65
|
+
client_name: CLI_CLIENT_NAME,
|
|
66
|
+
redirect_uris: [redirectUri],
|
|
67
|
+
grant_types: ['authorization_code'],
|
|
68
|
+
response_types: ['code'],
|
|
69
|
+
token_endpoint_auth_method: 'client_secret_post',
|
|
70
|
+
}),
|
|
71
|
+
});
|
|
72
|
+
if (!res.ok) {
|
|
73
|
+
const body = await res.text();
|
|
74
|
+
throw new Error(`Client registration failed (${res.status}): ${body}`);
|
|
75
|
+
}
|
|
76
|
+
return (await res.json());
|
|
77
|
+
}
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
// Step 2 — Token exchange
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
async function exchangeCodeForToken(opts) {
|
|
82
|
+
const body = new URLSearchParams({
|
|
83
|
+
grant_type: 'authorization_code',
|
|
84
|
+
code: opts.code,
|
|
85
|
+
client_id: opts.clientId,
|
|
86
|
+
redirect_uri: opts.redirectUri,
|
|
87
|
+
code_verifier: opts.codeVerifier,
|
|
88
|
+
});
|
|
89
|
+
if (opts.clientSecret) {
|
|
90
|
+
body.set('client_secret', opts.clientSecret);
|
|
91
|
+
}
|
|
92
|
+
const res = await fetch(OAUTH_TOKEN_URL, {
|
|
93
|
+
method: 'POST',
|
|
94
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
95
|
+
body: body.toString(),
|
|
96
|
+
});
|
|
97
|
+
if (!res.ok) {
|
|
98
|
+
const text = await res.text();
|
|
99
|
+
throw new Error(`Token exchange failed (${res.status}): ${text}`);
|
|
100
|
+
}
|
|
101
|
+
return (await res.json());
|
|
102
|
+
}
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// Token revocation
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
export async function revokeToken(token, clientId, clientSecret) {
|
|
107
|
+
const body = new URLSearchParams({
|
|
108
|
+
token,
|
|
109
|
+
client_id: clientId,
|
|
110
|
+
});
|
|
111
|
+
if (clientSecret) {
|
|
112
|
+
body.set('client_secret', clientSecret);
|
|
113
|
+
}
|
|
114
|
+
const res = await fetch(OAUTH_REVOKE_URL, {
|
|
115
|
+
method: 'POST',
|
|
116
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
117
|
+
body: body.toString(),
|
|
118
|
+
});
|
|
119
|
+
if (!res.ok) {
|
|
120
|
+
const text = await res.text();
|
|
121
|
+
throw new Error(`Token revocation failed (${res.status}): ${text}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
// Local callback server
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
/**
|
|
128
|
+
* Starts a temporary HTTP server on a random port, waits for the OAuth
|
|
129
|
+
* callback, and returns the authorization code.
|
|
130
|
+
*/
|
|
131
|
+
function waitForAuthCallback(port) {
|
|
132
|
+
return new Promise((resolve, reject) => {
|
|
133
|
+
const server = createServer((req, res) => {
|
|
134
|
+
const url = new URL(req.url ?? '/', `http://${CLI_REDIRECT_HOST}:${port}`);
|
|
135
|
+
if (url.pathname !== '/callback') {
|
|
136
|
+
res.writeHead(404);
|
|
137
|
+
res.end('Not found');
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const code = url.searchParams.get('code');
|
|
141
|
+
const error = url.searchParams.get('error');
|
|
142
|
+
const state = url.searchParams.get('state') ?? '';
|
|
143
|
+
if (error) {
|
|
144
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
145
|
+
res.end(errorPage(error, url.searchParams.get('error_description')));
|
|
146
|
+
server.close();
|
|
147
|
+
reject(new Error(`OAuth error: ${error}`));
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (!code) {
|
|
151
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
152
|
+
res.end(errorPage('missing_code', 'No authorization code received.'));
|
|
153
|
+
server.close();
|
|
154
|
+
reject(new Error('No authorization code in callback'));
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
158
|
+
res.end(successPage());
|
|
159
|
+
server.close();
|
|
160
|
+
resolve({ code, state });
|
|
161
|
+
});
|
|
162
|
+
server.listen(port, CLI_REDIRECT_HOST, () => {
|
|
163
|
+
// Server is ready
|
|
164
|
+
});
|
|
165
|
+
// Timeout after 5 minutes
|
|
166
|
+
setTimeout(() => {
|
|
167
|
+
server.close();
|
|
168
|
+
reject(new Error('Login timed out. No callback received within 5 minutes.'));
|
|
169
|
+
}, 5 * 60 * 1000);
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
function getRandomPort() {
|
|
173
|
+
// Use port 0 to let OS assign, but we need to know the port before starting.
|
|
174
|
+
// Instead, pick from ephemeral range.
|
|
175
|
+
return 49152 + Math.floor(Math.random() * 16384);
|
|
176
|
+
}
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
// HTML pages for the callback
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
function successPage() {
|
|
181
|
+
return `<!DOCTYPE html>
|
|
182
|
+
<html>
|
|
183
|
+
<head><title>Ogment CLI</title></head>
|
|
184
|
+
<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; display:flex; justify-content:center; align-items:center; height:100vh; margin:0; background:#f8f8fc;">
|
|
185
|
+
<div style="text-align:center;">
|
|
186
|
+
<h1 style="color:#6C5CE7;">✓ Logged in</h1>
|
|
187
|
+
<p style="color:#666;">You can close this window and return to your terminal.</p>
|
|
188
|
+
</div>
|
|
189
|
+
</body>
|
|
190
|
+
</html>`;
|
|
191
|
+
}
|
|
192
|
+
function errorPage(error, description) {
|
|
193
|
+
return `<!DOCTYPE html>
|
|
194
|
+
<html>
|
|
195
|
+
<head><title>Ogment CLI — Error</title></head>
|
|
196
|
+
<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; display:flex; justify-content:center; align-items:center; height:100vh; margin:0; background:#f8f8fc;">
|
|
197
|
+
<div style="text-align:center;">
|
|
198
|
+
<h1 style="color:#e74c3c;">✗ Login failed</h1>
|
|
199
|
+
<p style="color:#666;">${description ?? error}</p>
|
|
200
|
+
</div>
|
|
201
|
+
</body>
|
|
202
|
+
</html>`;
|
|
203
|
+
}
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
// Main login flow
|
|
206
|
+
// ---------------------------------------------------------------------------
|
|
207
|
+
/**
|
|
208
|
+
* Runs the full OAuth2 Authorization Code + PKCE login flow.
|
|
209
|
+
*
|
|
210
|
+
* The `resourceUrl` tells the backend which MCP server the user wants to
|
|
211
|
+
* connect to. This is required — the backend rejects auth requests without
|
|
212
|
+
* an associated server.
|
|
213
|
+
*
|
|
214
|
+
* Returns the access token.
|
|
215
|
+
*/
|
|
216
|
+
export async function loginFlow(resourceUrl) {
|
|
217
|
+
const port = getRandomPort();
|
|
218
|
+
const redirectUri = `http://${CLI_REDIRECT_HOST}:${port}/callback`;
|
|
219
|
+
// Step 1 — Register as OAuth client
|
|
220
|
+
const client = await registerClient(redirectUri);
|
|
221
|
+
// Step 2 — Generate PKCE pair
|
|
222
|
+
const codeVerifier = generateCodeVerifier();
|
|
223
|
+
const codeChallenge = generateCodeChallenge(codeVerifier);
|
|
224
|
+
// Step 3 — Build authorization URL (including resource for server resolution)
|
|
225
|
+
const state = randomBytes(16).toString('hex');
|
|
226
|
+
const authorizeUrl = new URL(OAUTH_AUTHORIZE_URL);
|
|
227
|
+
authorizeUrl.searchParams.set('client_id', client.client_id);
|
|
228
|
+
authorizeUrl.searchParams.set('redirect_uri', redirectUri);
|
|
229
|
+
authorizeUrl.searchParams.set('response_type', 'code');
|
|
230
|
+
authorizeUrl.searchParams.set('code_challenge', codeChallenge);
|
|
231
|
+
authorizeUrl.searchParams.set('code_challenge_method', 'S256');
|
|
232
|
+
authorizeUrl.searchParams.set('state', state);
|
|
233
|
+
authorizeUrl.searchParams.set('resource', resourceUrl);
|
|
234
|
+
// Step 4 — Start callback server, then open browser
|
|
235
|
+
const callbackPromise = waitForAuthCallback(port);
|
|
236
|
+
await open(authorizeUrl.toString());
|
|
237
|
+
// Step 5 — Wait for callback
|
|
238
|
+
const { code, state: returnedState } = await callbackPromise;
|
|
239
|
+
if (returnedState !== state) {
|
|
240
|
+
throw new Error('OAuth state mismatch — possible CSRF attack.');
|
|
241
|
+
}
|
|
242
|
+
// Step 6 — Exchange code for token
|
|
243
|
+
const tokenResponse = await exchangeCodeForToken({
|
|
244
|
+
code,
|
|
245
|
+
clientId: client.client_id,
|
|
246
|
+
clientSecret: client.client_secret ?? null,
|
|
247
|
+
codeVerifier,
|
|
248
|
+
redirectUri,
|
|
249
|
+
});
|
|
250
|
+
return {
|
|
251
|
+
accessToken: tokenResponse.access_token,
|
|
252
|
+
clientId: client.client_id,
|
|
253
|
+
clientSecret: client.client_secret ?? null,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Run the login flow and store credentials with org + servers.
|
|
258
|
+
*/
|
|
259
|
+
export async function login(orgSlug, servers, resourceUrl) {
|
|
260
|
+
const existing = loadCredentials();
|
|
261
|
+
if (existing?.accessToken) {
|
|
262
|
+
// Already logged in — just update the server list
|
|
263
|
+
const merged = [...new Set([...existing.servers, ...servers])];
|
|
264
|
+
saveCredentials({ ...existing, servers: merged });
|
|
265
|
+
return existing;
|
|
266
|
+
}
|
|
267
|
+
const result = await loginFlow(resourceUrl);
|
|
268
|
+
const creds = {
|
|
269
|
+
...result,
|
|
270
|
+
orgSlug,
|
|
271
|
+
servers,
|
|
272
|
+
};
|
|
273
|
+
saveCredentials(creds);
|
|
274
|
+
return creds;
|
|
275
|
+
}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Ogment CLI — Secure your AI agents' SaaS credentials.
|
|
4
|
+
*
|
|
5
|
+
* ogment login <org/server> Authenticate and configure servers
|
|
6
|
+
* ogment servers [path] List servers / inspect tools
|
|
7
|
+
* ogment call <server> <tool> Call a tool (returns JSON)
|
|
8
|
+
* ogment logout Revoke token and delete credentials
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA;;;;;;;GAOG"}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Ogment CLI — Secure your AI agents' SaaS credentials.
|
|
4
|
+
*
|
|
5
|
+
* ogment login <org/server> Authenticate and configure servers
|
|
6
|
+
* ogment servers [path] List servers / inspect tools
|
|
7
|
+
* ogment call <server> <tool> Call a tool (returns JSON)
|
|
8
|
+
* ogment logout Revoke token and delete credentials
|
|
9
|
+
*/
|
|
10
|
+
import { Command } from 'commander';
|
|
11
|
+
import { callCommand } from './commands/call.js';
|
|
12
|
+
import { loginCommand } from './commands/login.js';
|
|
13
|
+
import { logoutCommand } from './commands/logout.js';
|
|
14
|
+
import { serversCommand } from './commands/servers.js';
|
|
15
|
+
import { VERSION } from './config.js';
|
|
16
|
+
import { BOLD, BRAND, DIM, SYM } from './ui.js';
|
|
17
|
+
const program = new Command();
|
|
18
|
+
program
|
|
19
|
+
.name('ogment')
|
|
20
|
+
.description('Ogment CLI — secure your AI agents\' SaaS credentials')
|
|
21
|
+
.version(VERSION);
|
|
22
|
+
// ── login ──────────────────────────────────────────────────────────────────
|
|
23
|
+
program
|
|
24
|
+
.command('login')
|
|
25
|
+
.description('Authenticate with Ogment and configure servers')
|
|
26
|
+
.argument('[targets...]', 'org/server targets (e.g. ogment/main)')
|
|
27
|
+
.action(async (targets) => {
|
|
28
|
+
await loginCommand(targets);
|
|
29
|
+
});
|
|
30
|
+
// ── servers ────────────────────────────────────────────────────────────────
|
|
31
|
+
program
|
|
32
|
+
.command('servers [path]')
|
|
33
|
+
.description('List configured servers or inspect a server\'s tools')
|
|
34
|
+
.option('--json', 'Machine-readable JSON output')
|
|
35
|
+
.action(async (path, opts) => {
|
|
36
|
+
await serversCommand(path, opts);
|
|
37
|
+
});
|
|
38
|
+
// ── call ───────────────────────────────────────────────────────────────────
|
|
39
|
+
program
|
|
40
|
+
.command('call <server> <tool> [args]')
|
|
41
|
+
.description('Call a tool on an Ogment server (returns JSON)')
|
|
42
|
+
.action(async (server, tool, args) => {
|
|
43
|
+
await callCommand(server, tool, args);
|
|
44
|
+
});
|
|
45
|
+
// ── logout ─────────────────────────────────────────────────────────────────
|
|
46
|
+
program
|
|
47
|
+
.command('logout')
|
|
48
|
+
.description('Revoke token and delete local credentials')
|
|
49
|
+
.action(async () => {
|
|
50
|
+
await logoutCommand();
|
|
51
|
+
});
|
|
52
|
+
// ── default (no command) ───────────────────────────────────────────────────
|
|
53
|
+
program.action(() => {
|
|
54
|
+
console.log();
|
|
55
|
+
console.log(` ${BRAND} ${DIM(`v${VERSION}`)}`);
|
|
56
|
+
console.log(` ${DIM('Secure your AI agents\' SaaS credentials.')}`);
|
|
57
|
+
console.log();
|
|
58
|
+
console.log(` ${BOLD('Commands:')}`);
|
|
59
|
+
console.log();
|
|
60
|
+
console.log(` ${BOLD('login')} ${DIM('Authenticate and configure servers')}`);
|
|
61
|
+
console.log(` ${BOLD('servers')} ${DIM('List servers or inspect tools')}`);
|
|
62
|
+
console.log(` ${BOLD('call')} ${DIM('Call a tool on a server (returns JSON)')}`);
|
|
63
|
+
console.log(` ${BOLD('logout')} ${DIM('Revoke token and delete credentials')}`);
|
|
64
|
+
console.log();
|
|
65
|
+
console.log(` ${BOLD('Quick start:')}`);
|
|
66
|
+
console.log();
|
|
67
|
+
console.log(` ${DIM('$')} ogment login ${DIM('<org>/<server>')}`);
|
|
68
|
+
console.log(` ${DIM('$')} ogment servers`);
|
|
69
|
+
console.log(` ${DIM('$')} ogment call ${DIM('<server> <tool> [args-json]')}`);
|
|
70
|
+
console.log();
|
|
71
|
+
console.log(` ${SYM.lock} Your real API credentials ${BOLD('never leave Ogment')}.`);
|
|
72
|
+
console.log(` Agents get scoped, revocable tokens. Revoke anytime.`);
|
|
73
|
+
console.log();
|
|
74
|
+
});
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
// Global error handler
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
program.parseAsync().catch((err) => {
|
|
79
|
+
process.stderr.write(`\n ${SYM.cross} ${err.message}\n\n`);
|
|
80
|
+
process.exitCode = 1;
|
|
81
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ogment call <server> <tool> [args]` — Invoke a tool on an Ogment server.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* ogment call ecommerce-api get__health
|
|
6
|
+
* ogment call ecommerce-api get__api_products_ '{"limit":2}'
|
|
7
|
+
* ogment call ecommerce-api post__api_products_ '{"name":"Widget","price":9.99,"stock":100,"category":"gadgets"}'
|
|
8
|
+
*
|
|
9
|
+
* Always outputs JSON (designed for agent consumption via exec).
|
|
10
|
+
*/
|
|
11
|
+
export declare function callCommand(serverPath: string | undefined, toolName: string | undefined, argsJson: string | undefined): Promise<void>;
|
|
12
|
+
//# sourceMappingURL=call.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"call.d.ts","sourceRoot":"","sources":["../../src/commands/call.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,wBAAsB,WAAW,CAC/B,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,QAAQ,EAAE,MAAM,GAAG,SAAS,iBA6B7B"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ogment call <server> <tool> [args]` — Invoke a tool on an Ogment server.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* ogment call ecommerce-api get__health
|
|
6
|
+
* ogment call ecommerce-api get__api_products_ '{"limit":2}'
|
|
7
|
+
* ogment call ecommerce-api post__api_products_ '{"name":"Widget","price":9.99,"stock":100,"category":"gadgets"}'
|
|
8
|
+
*
|
|
9
|
+
* Always outputs JSON (designed for agent consumption via exec).
|
|
10
|
+
*/
|
|
11
|
+
import { requireCredentials } from '../auth.js';
|
|
12
|
+
import { callTool } from '../mcp-client.js';
|
|
13
|
+
export async function callCommand(serverPath, toolName, argsJson) {
|
|
14
|
+
if (!serverPath || !toolName) {
|
|
15
|
+
throw new Error('Usage: ogment call <server> <tool> [args-json]\n' +
|
|
16
|
+
'Example: ogment call ecommerce-api get__api_products_ \'{"limit":2}\'');
|
|
17
|
+
}
|
|
18
|
+
const creds = requireCredentials();
|
|
19
|
+
if (!creds.servers.includes(serverPath)) {
|
|
20
|
+
throw new Error(`Server "${serverPath}" is not configured. Available: ${creds.servers.join(', ')}`);
|
|
21
|
+
}
|
|
22
|
+
const args = argsJson ? JSON.parse(argsJson) : {};
|
|
23
|
+
const result = await callTool(creds.orgSlug, serverPath, creds.accessToken, toolName, args);
|
|
24
|
+
// Output the result as JSON — this is what the agent reads via exec
|
|
25
|
+
console.log(JSON.stringify(result, null, 2));
|
|
26
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ogment login <org/server> [more-servers...]` — Authenticate and configure servers.
|
|
3
|
+
*
|
|
4
|
+
* The first argument must be org/server format (used as OAuth resource).
|
|
5
|
+
* Additional arguments are extra server paths in the same org.
|
|
6
|
+
* If already logged in, just adds new servers to the config.
|
|
7
|
+
*/
|
|
8
|
+
export declare function loginCommand(targets: string[]): Promise<void>;
|
|
9
|
+
//# sourceMappingURL=login.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAkBH,wBAAsB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,iBAqDnD"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ogment login <org/server> [more-servers...]` — Authenticate and configure servers.
|
|
3
|
+
*
|
|
4
|
+
* The first argument must be org/server format (used as OAuth resource).
|
|
5
|
+
* Additional arguments are extra server paths in the same org.
|
|
6
|
+
* If already logged in, just adds new servers to the config.
|
|
7
|
+
*/
|
|
8
|
+
import ora from 'ora';
|
|
9
|
+
import { loadCredentials, login } from '../auth.js';
|
|
10
|
+
import { MCP_RESOURCE_URL } from '../config.js';
|
|
11
|
+
import { blank, BOLD, DIM, heading, line, SYM, YELLOW } from '../ui.js';
|
|
12
|
+
function parseTarget(target) {
|
|
13
|
+
const parts = target.split('/');
|
|
14
|
+
if (parts.length !== 2 || !parts[0] || !parts[1]) {
|
|
15
|
+
throw new Error(`Invalid target "${target}". Expected format: <org-slug>/<server-path> (e.g. ogment/main)`);
|
|
16
|
+
}
|
|
17
|
+
return { orgSlug: parts[0], serverPath: parts[1] };
|
|
18
|
+
}
|
|
19
|
+
export async function loginCommand(targets) {
|
|
20
|
+
if (targets.length === 0) {
|
|
21
|
+
heading('Log in to Ogment');
|
|
22
|
+
line(`${SYM.warn} ${YELLOW('Provide at least one target:')} ${BOLD('ogment login <org>/<server>')}`);
|
|
23
|
+
blank();
|
|
24
|
+
line(`${DIM('Examples:')}`);
|
|
25
|
+
line(` ${BOLD('ogment login ogment/ama-master-mcp')}`);
|
|
26
|
+
line(` ${BOLD('ogment login ogment/ama-master-mcp ogment/sales')}`);
|
|
27
|
+
blank();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
// Parse the first target to get orgSlug
|
|
31
|
+
const first = parseTarget(targets[0]);
|
|
32
|
+
const orgSlug = first.orgSlug;
|
|
33
|
+
// Collect all server paths (first + any extras)
|
|
34
|
+
const servers = [first.serverPath];
|
|
35
|
+
for (let i = 1; i < targets.length; i++) {
|
|
36
|
+
const t = targets[i];
|
|
37
|
+
// Allow both "org/server" and just "server" for extra args
|
|
38
|
+
const path = t.includes('/') ? parseTarget(t).serverPath : t;
|
|
39
|
+
servers.push(path);
|
|
40
|
+
}
|
|
41
|
+
const existing = loadCredentials();
|
|
42
|
+
if (existing?.accessToken) {
|
|
43
|
+
// Already logged in — merge new servers
|
|
44
|
+
heading('Already logged in');
|
|
45
|
+
const creds = await login(orgSlug, servers, '');
|
|
46
|
+
line(`${SYM.check} Servers configured: ${BOLD(creds.servers.join(', '))}`);
|
|
47
|
+
blank();
|
|
48
|
+
line(`${DIM('Start the bridge:')} ${BOLD('ogment serve')}`);
|
|
49
|
+
blank();
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
heading('Log in to Ogment');
|
|
53
|
+
const resourceUrl = MCP_RESOURCE_URL(orgSlug, first.serverPath);
|
|
54
|
+
const spinner = ora({ text: 'Opening browser…', color: 'magenta' }).start();
|
|
55
|
+
const creds = await login(orgSlug, servers, resourceUrl);
|
|
56
|
+
spinner.succeed('Logged in');
|
|
57
|
+
blank();
|
|
58
|
+
line(`${SYM.check} Token stored in ${DIM('~/.config/ogment/credentials.json')}`);
|
|
59
|
+
line(`${SYM.bullet} Org: ${BOLD(creds.orgSlug)}`);
|
|
60
|
+
line(`${SYM.bullet} Servers: ${BOLD(creds.servers.join(', '))}`);
|
|
61
|
+
blank();
|
|
62
|
+
line(`${DIM('Start the bridge:')} ${BOLD('ogment serve')}`);
|
|
63
|
+
blank();
|
|
64
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logout.d.ts","sourceRoot":"","sources":["../../src/commands/logout.ts"],"names":[],"mappings":"AAAA;;GAEG;AAOH,wBAAsB,aAAa,kBAqClC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ogment logout` — Revoke the token server-side and delete local credentials.
|
|
3
|
+
*/
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
import { deleteCredentials, loadCredentials, revokeToken } from '../auth.js';
|
|
6
|
+
import { blank, BOLD, DIM, heading, line, SYM } from '../ui.js';
|
|
7
|
+
export async function logoutCommand() {
|
|
8
|
+
const creds = loadCredentials();
|
|
9
|
+
if (!creds?.accessToken) {
|
|
10
|
+
heading('Not logged in');
|
|
11
|
+
line(`${DIM('Nothing to do. Log in with')} ${BOLD('ogment login')}`);
|
|
12
|
+
blank();
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
heading('Logging out');
|
|
16
|
+
const spinner = ora({ text: 'Revoking token…', color: 'magenta' }).start();
|
|
17
|
+
// Try to revoke server-side, but always delete locally
|
|
18
|
+
let serverRevoked = false;
|
|
19
|
+
try {
|
|
20
|
+
await revokeToken(creds.accessToken, creds.clientId, creds.clientSecret);
|
|
21
|
+
serverRevoked = true;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// Server revocation failed (token may already be expired/revoked) — continue with local cleanup
|
|
25
|
+
}
|
|
26
|
+
deleteCredentials();
|
|
27
|
+
if (serverRevoked) {
|
|
28
|
+
spinner.succeed('Token revoked');
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
spinner.warn('Could not reach server — token may already be revoked');
|
|
32
|
+
}
|
|
33
|
+
blank();
|
|
34
|
+
line(`${SYM.check} Local credentials deleted from ${DIM('~/.config/ogment/')}`);
|
|
35
|
+
if (serverRevoked) {
|
|
36
|
+
line(`${SYM.bullet} Token revoked on the server. Agent access terminated.`);
|
|
37
|
+
}
|
|
38
|
+
blank();
|
|
39
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ogment servers [path]` — List configured servers and inspect their tools.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* ogment servers List all configured servers
|
|
6
|
+
* ogment servers <path> Show server details + tool list
|
|
7
|
+
* ogment servers --json Machine-readable output
|
|
8
|
+
* ogment servers <path> --json Machine-readable tool list
|
|
9
|
+
*/
|
|
10
|
+
export declare function serversCommand(serverPath: string | undefined, opts: {
|
|
11
|
+
json?: boolean;
|
|
12
|
+
}): Promise<void>;
|
|
13
|
+
//# sourceMappingURL=servers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"servers.d.ts","sourceRoot":"","sources":["../../src/commands/servers.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,wBAAsB,cAAc,CAClC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,iBAsEzB"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ogment servers [path]` — List configured servers and inspect their tools.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* ogment servers List all configured servers
|
|
6
|
+
* ogment servers <path> Show server details + tool list
|
|
7
|
+
* ogment servers --json Machine-readable output
|
|
8
|
+
* ogment servers <path> --json Machine-readable tool list
|
|
9
|
+
*/
|
|
10
|
+
import { requireCredentials } from '../auth.js';
|
|
11
|
+
import { fetchTools } from '../mcp-client.js';
|
|
12
|
+
import { blank, BOLD, DIM, GREEN, heading, line, SYM } from '../ui.js';
|
|
13
|
+
export async function serversCommand(serverPath, opts) {
|
|
14
|
+
const creds = requireCredentials();
|
|
15
|
+
// ── List all servers ───────────────────────────────────────────────────
|
|
16
|
+
if (!serverPath) {
|
|
17
|
+
if (opts.json) {
|
|
18
|
+
console.log(JSON.stringify(creds.servers.map((path) => ({ path, orgSlug: creds.orgSlug })), null, 2));
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
heading('Your servers');
|
|
22
|
+
if (creds.servers.length === 0) {
|
|
23
|
+
line(`${DIM('No servers configured. Add one:')}`);
|
|
24
|
+
line(` ${BOLD('ogment login <org>/<server>')}`);
|
|
25
|
+
blank();
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
for (const path of creds.servers) {
|
|
29
|
+
line(`${SYM.bullet} ${BOLD(path)}`);
|
|
30
|
+
}
|
|
31
|
+
blank();
|
|
32
|
+
line(`${DIM('Inspect a server:')} ${BOLD('ogment servers <path>')}`);
|
|
33
|
+
line(`${DIM('Call a tool:')} ${BOLD('ogment call <server> <tool> [args]')}`);
|
|
34
|
+
blank();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
// ── Server detail + tools ──────────────────────────────────────────────
|
|
38
|
+
if (!creds.servers.includes(serverPath)) {
|
|
39
|
+
throw new Error(`Server "${serverPath}" is not configured. Available: ${creds.servers.join(', ')}`);
|
|
40
|
+
}
|
|
41
|
+
const tools = await fetchTools(creds.orgSlug, serverPath, creds.accessToken);
|
|
42
|
+
if (opts.json) {
|
|
43
|
+
console.log(JSON.stringify({
|
|
44
|
+
path: serverPath,
|
|
45
|
+
orgSlug: creds.orgSlug,
|
|
46
|
+
toolCount: tools.length,
|
|
47
|
+
tools: tools.map((t) => ({ name: t.name, description: t.description })),
|
|
48
|
+
}, null, 2));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
heading(`Server: ${serverPath}`);
|
|
52
|
+
line(`${BOLD('Tools:')} ${tools.length}`);
|
|
53
|
+
blank();
|
|
54
|
+
for (const tool of tools) {
|
|
55
|
+
const desc = tool.description ? DIM(` — ${tool.description}`) : '';
|
|
56
|
+
line(` ${GREEN(tool.name)}${desc}`);
|
|
57
|
+
}
|
|
58
|
+
blank();
|
|
59
|
+
line(`${DIM('Call a tool:')} ${BOLD(`ogment call ${serverPath} <tool> [args]`)}`);
|
|
60
|
+
blank();
|
|
61
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration constants for the Ogment CLI.
|
|
3
|
+
*/
|
|
4
|
+
/** Base URL of the Ogment platform (frontend). */
|
|
5
|
+
export declare const OGMENT_BASE_URL: string;
|
|
6
|
+
/** OAuth metadata endpoint. */
|
|
7
|
+
export declare const OAUTH_METADATA_URL: string;
|
|
8
|
+
/** OAuth endpoints (derived from base). */
|
|
9
|
+
export declare const OAUTH_AUTHORIZE_URL: string;
|
|
10
|
+
export declare const OAUTH_TOKEN_URL: string;
|
|
11
|
+
export declare const OAUTH_REGISTER_URL: string;
|
|
12
|
+
export declare const OAUTH_REVOKE_URL: string;
|
|
13
|
+
/**
|
|
14
|
+
* MCP proxy endpoint.
|
|
15
|
+
* Uses path format: {BASE_URL}/api/v1/mcp/{orgSlug}/{serverPath}
|
|
16
|
+
*/
|
|
17
|
+
export declare const MCP_ENDPOINT: (orgSlug: string, serverPath: string) => string;
|
|
18
|
+
/**
|
|
19
|
+
* Resource URL sent to the OAuth authorize endpoint.
|
|
20
|
+
* The backend parses this to resolve the MCP server.
|
|
21
|
+
* Uses subdomain format for resource identification (required by backend parser).
|
|
22
|
+
*/
|
|
23
|
+
export declare const MCP_RESOURCE_URL: (orgSlug: string, serverPath: string) => string;
|
|
24
|
+
/** Directory for CLI config & credentials. */
|
|
25
|
+
export declare const CONFIG_DIR: string;
|
|
26
|
+
/** Path to stored credentials file. */
|
|
27
|
+
export declare const CREDENTIALS_PATH: string;
|
|
28
|
+
export declare const CLI_CLIENT_NAME = "Ogment CLI";
|
|
29
|
+
export declare const CLI_REDIRECT_HOST = "127.0.0.1";
|
|
30
|
+
export declare const VERSION = "0.1.0";
|
|
31
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AASH,kDAAkD;AAClD,eAAO,MAAM,eAAe,QACkC,CAAC;AAE/D,+BAA+B;AAC/B,eAAO,MAAM,kBAAkB,QAA8D,CAAC;AAE9F,2CAA2C;AAC3C,eAAO,MAAM,mBAAmB,QAAiD,CAAC;AAClF,eAAO,MAAM,eAAe,QAA6C,CAAC;AAC1E,eAAO,MAAM,kBAAkB,QAAgD,CAAC;AAChF,eAAO,MAAM,gBAAgB,QAA8C,CAAC;AAE5E;;;GAGG;AACH,eAAO,MAAM,YAAY,GAAI,SAAS,MAAM,EAAE,YAAY,MAAM,WACN,CAAC;AAE3D;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAAI,SAAS,MAAM,EAAE,YAAY,MAAM,WAGnE,CAAC;AAMF,8CAA8C;AAC9C,eAAO,MAAM,UAAU,QAAuC,CAAC;AAE/D,uCAAuC;AACvC,eAAO,MAAM,gBAAgB,QAAuC,CAAC;AAMrE,eAAO,MAAM,eAAe,eAAe,CAAC;AAC5C,eAAO,MAAM,iBAAiB,cAAc,CAAC;AAM7C,eAAO,MAAM,OAAO,UAAU,CAAC"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration constants for the Ogment CLI.
|
|
3
|
+
*/
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// API URLs
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
/** Base URL of the Ogment platform (frontend). */
|
|
10
|
+
export const OGMENT_BASE_URL = process.env.OGMENT_BASE_URL ?? 'https://dashboard.ogment.ai';
|
|
11
|
+
/** OAuth metadata endpoint. */
|
|
12
|
+
export const OAUTH_METADATA_URL = `${OGMENT_BASE_URL}/.well-known/oauth-authorization-server`;
|
|
13
|
+
/** OAuth endpoints (derived from base). */
|
|
14
|
+
export const OAUTH_AUTHORIZE_URL = `${OGMENT_BASE_URL}/api/v1/mcp-auth/authorize`;
|
|
15
|
+
export const OAUTH_TOKEN_URL = `${OGMENT_BASE_URL}/api/v1/mcp-auth/token`;
|
|
16
|
+
export const OAUTH_REGISTER_URL = `${OGMENT_BASE_URL}/api/v1/mcp-auth/register`;
|
|
17
|
+
export const OAUTH_REVOKE_URL = `${OGMENT_BASE_URL}/api/v1/mcp-auth/revoke`;
|
|
18
|
+
/**
|
|
19
|
+
* MCP proxy endpoint.
|
|
20
|
+
* Uses path format: {BASE_URL}/api/v1/mcp/{orgSlug}/{serverPath}
|
|
21
|
+
*/
|
|
22
|
+
export const MCP_ENDPOINT = (orgSlug, serverPath) => `${OGMENT_BASE_URL}/api/v1/mcp/${orgSlug}/${serverPath}`;
|
|
23
|
+
/**
|
|
24
|
+
* Resource URL sent to the OAuth authorize endpoint.
|
|
25
|
+
* The backend parses this to resolve the MCP server.
|
|
26
|
+
* Uses subdomain format for resource identification (required by backend parser).
|
|
27
|
+
*/
|
|
28
|
+
export const MCP_RESOURCE_URL = (orgSlug, serverPath) => {
|
|
29
|
+
const mcpDomain = process.env.OGMENT_MCP_DOMAIN ?? 'mcp.ogment.ai';
|
|
30
|
+
return `https://${orgSlug}.${mcpDomain}/${serverPath}`;
|
|
31
|
+
};
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Local storage
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
/** Directory for CLI config & credentials. */
|
|
36
|
+
export const CONFIG_DIR = join(homedir(), '.config', 'ogment');
|
|
37
|
+
/** Path to stored credentials file. */
|
|
38
|
+
export const CREDENTIALS_PATH = join(CONFIG_DIR, 'credentials.json');
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// OAuth client metadata
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
export const CLI_CLIENT_NAME = 'Ogment CLI';
|
|
43
|
+
export const CLI_REDIRECT_HOST = '127.0.0.1';
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Version
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
export const VERSION = '0.1.0';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP HTTP client — sends JSON-RPC requests to Ogment's MCP proxy.
|
|
3
|
+
*
|
|
4
|
+
* Shared by both the stdio bridge (serve.ts) and CLI commands (servers, call).
|
|
5
|
+
*/
|
|
6
|
+
export interface McpTool {
|
|
7
|
+
name: string;
|
|
8
|
+
description: string;
|
|
9
|
+
inputSchema: Record<string, unknown>;
|
|
10
|
+
}
|
|
11
|
+
export interface McpToolCallResult {
|
|
12
|
+
content: {
|
|
13
|
+
type: string;
|
|
14
|
+
text: string;
|
|
15
|
+
}[];
|
|
16
|
+
structuredContent?: unknown;
|
|
17
|
+
isError?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare function mcpRequest(orgSlug: string, serverPath: string, token: string, method: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
20
|
+
/** Fetch the list of tools available on a server. */
|
|
21
|
+
export declare function fetchTools(orgSlug: string, serverPath: string, token: string): Promise<McpTool[]>;
|
|
22
|
+
/** Call a tool on a server and return the result. */
|
|
23
|
+
export declare function callTool(orgSlug: string, serverPath: string, token: string, toolName: string, args: Record<string, unknown>): Promise<McpToolCallResult>;
|
|
24
|
+
//# sourceMappingURL=mcp-client.d.ts.map
|
|
@@ -0,0 +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,MAAM,WAAW,iBAAiB;IAChC,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;AAMD,wBAAsB,UAAU,CAC9B,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,oBAgCjC;AAMD,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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP HTTP client — sends JSON-RPC requests to Ogment's MCP proxy.
|
|
3
|
+
*
|
|
4
|
+
* Shared by both the stdio bridge (serve.ts) and CLI commands (servers, call).
|
|
5
|
+
*/
|
|
6
|
+
import { MCP_ENDPOINT } from './config.js';
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Core JSON-RPC request
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
export async function mcpRequest(orgSlug, serverPath, token, method, params) {
|
|
11
|
+
const endpoint = MCP_ENDPOINT(orgSlug, serverPath);
|
|
12
|
+
const body = {
|
|
13
|
+
jsonrpc: '2.0',
|
|
14
|
+
method,
|
|
15
|
+
id: Date.now(),
|
|
16
|
+
};
|
|
17
|
+
if (params)
|
|
18
|
+
body.params = params;
|
|
19
|
+
const res = await fetch(endpoint, {
|
|
20
|
+
method: 'POST',
|
|
21
|
+
headers: {
|
|
22
|
+
'Content-Type': 'application/json',
|
|
23
|
+
Authorization: `Bearer ${token}`,
|
|
24
|
+
},
|
|
25
|
+
body: JSON.stringify(body),
|
|
26
|
+
});
|
|
27
|
+
if (!res.ok) {
|
|
28
|
+
const text = await res.text();
|
|
29
|
+
throw new Error(`MCP request failed (${res.status}): ${text}`);
|
|
30
|
+
}
|
|
31
|
+
const json = (await res.json());
|
|
32
|
+
if ('error' in json && json.error) {
|
|
33
|
+
const err = json.error;
|
|
34
|
+
throw new Error(`MCP error: ${err.message ?? JSON.stringify(json.error)}`);
|
|
35
|
+
}
|
|
36
|
+
return json.result;
|
|
37
|
+
}
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Convenience helpers
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
/** Fetch the list of tools available on a server. */
|
|
42
|
+
export async function fetchTools(orgSlug, serverPath, token) {
|
|
43
|
+
const result = (await mcpRequest(orgSlug, serverPath, token, 'tools/list'));
|
|
44
|
+
return result?.tools ?? [];
|
|
45
|
+
}
|
|
46
|
+
/** Call a tool on a server and return the result. */
|
|
47
|
+
export async function callTool(orgSlug, serverPath, token, toolName, args) {
|
|
48
|
+
return (await mcpRequest(orgSlug, serverPath, token, 'tools/call', {
|
|
49
|
+
name: toolName,
|
|
50
|
+
arguments: args,
|
|
51
|
+
}));
|
|
52
|
+
}
|
package/dist/ui.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared UI helpers — colors, symbols, formatting.
|
|
3
|
+
*/
|
|
4
|
+
export declare const BRAND: string;
|
|
5
|
+
export declare const DIM: import("chalk").ChalkInstance;
|
|
6
|
+
export declare const BOLD: import("chalk").ChalkInstance;
|
|
7
|
+
export declare const GREEN: import("chalk").ChalkInstance;
|
|
8
|
+
export declare const RED: import("chalk").ChalkInstance;
|
|
9
|
+
export declare const YELLOW: import("chalk").ChalkInstance;
|
|
10
|
+
export declare const CYAN: import("chalk").ChalkInstance;
|
|
11
|
+
export declare const SYM: {
|
|
12
|
+
readonly arrow: string;
|
|
13
|
+
readonly bullet: string;
|
|
14
|
+
readonly check: string;
|
|
15
|
+
readonly cross: string;
|
|
16
|
+
readonly lock: "🔒";
|
|
17
|
+
readonly warn: string;
|
|
18
|
+
};
|
|
19
|
+
export declare function heading(text: string): void;
|
|
20
|
+
export declare function line(text: string, indent?: number): void;
|
|
21
|
+
export declare function blank(): void;
|
|
22
|
+
export declare function section(title: string): void;
|
|
23
|
+
export declare function configSnippet(title: string, filename: string, json: Record<string, unknown>): void;
|
|
24
|
+
//# sourceMappingURL=ui.d.ts.map
|
package/dist/ui.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAAA;;GAEG;AAQH,eAAO,MAAM,KAAK,QAAsC,CAAC;AACzD,eAAO,MAAM,GAAG,+BAAY,CAAC;AAC7B,eAAO,MAAM,IAAI,+BAAa,CAAC;AAC/B,eAAO,MAAM,KAAK,+BAAc,CAAC;AACjC,eAAO,MAAM,GAAG,+BAAY,CAAC;AAC7B,eAAO,MAAM,MAAM,+BAAe,CAAC;AACnC,eAAO,MAAM,IAAI,+BAAa,CAAC;AAM/B,eAAO,MAAM,GAAG;;;;;;;CAON,CAAC;AAMX,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,QAInC;AAED,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,SAAI,QAE5C;AAED,wBAAgB,KAAK,SAEpB;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,QAMpC;AAMD,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,QAQ9B"}
|
package/dist/ui.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared UI helpers — colors, symbols, formatting.
|
|
3
|
+
*/
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Brand colors
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
export const BRAND = chalk.hex('#6C5CE7').bold('ogment');
|
|
9
|
+
export const DIM = chalk.dim;
|
|
10
|
+
export const BOLD = chalk.bold;
|
|
11
|
+
export const GREEN = chalk.green;
|
|
12
|
+
export const RED = chalk.red;
|
|
13
|
+
export const YELLOW = chalk.yellow;
|
|
14
|
+
export const CYAN = chalk.cyan;
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Symbols
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
export const SYM = {
|
|
19
|
+
arrow: chalk.hex('#6C5CE7')('→'),
|
|
20
|
+
bullet: DIM('•'),
|
|
21
|
+
check: GREEN('✓'),
|
|
22
|
+
cross: RED('✗'),
|
|
23
|
+
lock: '🔒',
|
|
24
|
+
warn: YELLOW('⚠'),
|
|
25
|
+
};
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Output helpers
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
export function heading(text) {
|
|
30
|
+
console.log();
|
|
31
|
+
console.log(` ${BRAND} — ${text}`);
|
|
32
|
+
console.log();
|
|
33
|
+
}
|
|
34
|
+
export function line(text, indent = 2) {
|
|
35
|
+
console.log(`${' '.repeat(indent)}${text}`);
|
|
36
|
+
}
|
|
37
|
+
export function blank() {
|
|
38
|
+
console.log();
|
|
39
|
+
}
|
|
40
|
+
export function section(title) {
|
|
41
|
+
console.log();
|
|
42
|
+
console.log(` ${DIM('──')} ${BOLD(title)} ${DIM('─'.repeat(Math.max(0, 48 - title.length)))}`);
|
|
43
|
+
console.log();
|
|
44
|
+
}
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Config snippet formatter
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
export function configSnippet(title, filename, json) {
|
|
49
|
+
line(`${BOLD(title)} ${DIM(`(${filename}):`)}`, 4);
|
|
50
|
+
const jsonStr = JSON.stringify(json, null, 2);
|
|
51
|
+
for (const jsonLine of jsonStr.split('\n')) {
|
|
52
|
+
line(DIM(jsonLine), 6);
|
|
53
|
+
}
|
|
54
|
+
blank();
|
|
55
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ogment",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Ogment Vault CLI — secure your AI agents' SaaS credentials",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ogment": "./dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"dev": "tsx src/cli.ts",
|
|
12
|
+
"start": "node dist/cli.js",
|
|
13
|
+
"check-types": "tsc --noEmit",
|
|
14
|
+
"prepublishOnly": "npm run build"
|
|
15
|
+
},
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"default": "./dist/cli.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"keywords": [
|
|
25
|
+
"ogment",
|
|
26
|
+
"mcp",
|
|
27
|
+
"ai",
|
|
28
|
+
"vault",
|
|
29
|
+
"oauth",
|
|
30
|
+
"cli",
|
|
31
|
+
"model-context-protocol",
|
|
32
|
+
"agent",
|
|
33
|
+
"security"
|
|
34
|
+
],
|
|
35
|
+
"author": "Ogment <hello@ogment.com>",
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "git+https://github.com/ogment-ai/ogment-cli.git"
|
|
40
|
+
},
|
|
41
|
+
"bugs": {
|
|
42
|
+
"url": "https://github.com/ogment-ai/ogment-cli/issues"
|
|
43
|
+
},
|
|
44
|
+
"homepage": "https://ogment.com",
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=18"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"chalk": "^5.4.1",
|
|
50
|
+
"commander": "^13.1.0",
|
|
51
|
+
"open": "^10.2.0",
|
|
52
|
+
"ora": "^8.2.0"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@types/node": "^22.18.12",
|
|
56
|
+
"tsx": "^4.21.0",
|
|
57
|
+
"typescript": "^5.9.3"
|
|
58
|
+
}
|
|
59
|
+
}
|