@ogment-ai/cli 0.3.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 +124 -0
- package/dist/cli.d.ts +19 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +337 -0
- package/dist/commands/call.d.ts +14 -0
- package/dist/commands/call.d.ts.map +1 -0
- package/dist/commands/call.js +51 -0
- package/dist/commands/context.d.ts +15 -0
- package/dist/commands/context.d.ts.map +1 -0
- package/dist/commands/context.js +1 -0
- package/dist/commands/describe.d.ts +4 -0
- package/dist/commands/describe.d.ts.map +1 -0
- package/dist/commands/describe.js +94 -0
- package/dist/commands/server-context.d.ts +14 -0
- package/dist/commands/server-context.d.ts.map +1 -0
- package/dist/commands/server-context.js +26 -0
- package/dist/commands/servers.d.ts +13 -0
- package/dist/commands/servers.d.ts.map +1 -0
- package/dist/commands/servers.js +29 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/infra/browser.d.ts +12 -0
- package/dist/infra/browser.d.ts.map +1 -0
- package/dist/infra/browser.js +20 -0
- package/dist/infra/credentials.d.ts +22 -0
- package/dist/infra/credentials.d.ts.map +1 -0
- package/dist/infra/credentials.js +81 -0
- package/dist/infra/env.d.ts +11 -0
- package/dist/infra/env.d.ts.map +1 -0
- package/dist/infra/env.js +98 -0
- package/dist/infra/http.d.ts +12 -0
- package/dist/infra/http.d.ts.map +1 -0
- package/dist/infra/http.js +27 -0
- package/dist/output/manager.d.ts +30 -0
- package/dist/output/manager.d.ts.map +1 -0
- package/dist/output/manager.js +79 -0
- package/dist/services/account.d.ts +16 -0
- package/dist/services/account.d.ts.map +1 -0
- package/dist/services/account.js +66 -0
- package/dist/services/auth.d.ts +38 -0
- package/dist/services/auth.d.ts.map +1 -0
- package/dist/services/auth.js +617 -0
- package/dist/services/mcp.d.ts +33 -0
- package/dist/services/mcp.d.ts.map +1 -0
- package/dist/services/mcp.js +158 -0
- package/dist/shared/constants.d.ts +8 -0
- package/dist/shared/constants.d.ts.map +1 -0
- package/dist/shared/constants.js +7 -0
- package/dist/shared/errors.d.ts +35 -0
- package/dist/shared/errors.d.ts.map +1 -0
- package/dist/shared/errors.js +31 -0
- package/dist/shared/exit-codes.d.ts +12 -0
- package/dist/shared/exit-codes.d.ts.map +1 -0
- package/dist/shared/exit-codes.js +27 -0
- package/dist/shared/guards.d.ts +6 -0
- package/dist/shared/guards.d.ts.map +1 -0
- package/dist/shared/guards.js +26 -0
- package/dist/shared/schemas.d.ts +57 -0
- package/dist/shared/schemas.d.ts.map +1 -0
- package/dist/shared/schemas.js +36 -0
- package/dist/shared/types.d.ts +53 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/dist/shared/types.js +1 -0
- package/package.json +81 -0
package/README.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
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, revocable API key — your real credentials never leave Ogment.
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Log in (opens browser for OAuth + agent selection)
|
|
11
|
+
npx ogment login
|
|
12
|
+
|
|
13
|
+
# List available servers
|
|
14
|
+
npx ogment servers
|
|
15
|
+
|
|
16
|
+
# Call a tool
|
|
17
|
+
npx ogment call <server> <tool> '{"param": "value"}'
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Development
|
|
21
|
+
|
|
22
|
+
### Run Tests
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pnpm run test # Watch mode
|
|
26
|
+
pnpm run test:run # Single run
|
|
27
|
+
pnpm run test:coverage # Coverage (text + html + lcov)
|
|
28
|
+
pnpm run test:ui # Vitest UI
|
|
29
|
+
pnpm run test:changed # Run tests related to changed files
|
|
30
|
+
pnpm run test:ci # CI profile (coverage + CI reporters)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Commands
|
|
34
|
+
|
|
35
|
+
### `ogment login`
|
|
36
|
+
|
|
37
|
+
Authenticate with the Ogment platform. Opens your browser for OAuth sign-in, then shows an agent picker where you create or select an agent. The resulting API key is stored locally.
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npx ogment login
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### `ogment login --device`
|
|
44
|
+
|
|
45
|
+
For headless or remote environments (VMs, CI servers) where a browser isn't available. Displays a short code that you enter on the Ogment dashboard from any device.
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npx ogment login --device
|
|
49
|
+
|
|
50
|
+
# Shows:
|
|
51
|
+
# Enter this code on the Ogment dashboard:
|
|
52
|
+
# ABCD-1234
|
|
53
|
+
# Open: https://dashboard.ogment.ai/cli/activate
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### `ogment servers [path]`
|
|
57
|
+
|
|
58
|
+
List configured servers or inspect a server's tools.
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npx ogment servers # List all servers
|
|
62
|
+
npx ogment servers <path> # Inspect tools
|
|
63
|
+
npx ogment servers --json # Machine-readable output
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### `ogment call <server> <tool> [args]`
|
|
67
|
+
|
|
68
|
+
Call a tool on an Ogment server. Always outputs JSON.
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
npx ogment call ecommerce-api get__health
|
|
72
|
+
npx ogment call ecommerce-api get__api_products_ '{"limit":2}'
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### `ogment logout`
|
|
76
|
+
|
|
77
|
+
Delete local credentials. To revoke the API key (prevent all access), use the Ogment dashboard.
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npx ogment logout
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Authentication Model
|
|
84
|
+
|
|
85
|
+
Ogment uses **Clerk API keys** for machine authentication:
|
|
86
|
+
|
|
87
|
+
1. **`ogment login`** — Human authenticates via OAuth, picks an agent → Clerk API key created
|
|
88
|
+
2. **API key stored locally** — At `~/.config/ogment/credentials.json`
|
|
89
|
+
3. **All requests use the API key** — Sent as `Authorization: Bearer <api-key>`
|
|
90
|
+
4. **Revoke anytime** — In the Ogment dashboard → Agents tab
|
|
91
|
+
|
|
92
|
+
### For Remote VMs
|
|
93
|
+
|
|
94
|
+
Use the device flow (`ogment login --device`) — the human approves from their own device, the VM never sees credentials or a browser. The API key is the only credential the VM receives.
|
|
95
|
+
|
|
96
|
+
## Environment Variables
|
|
97
|
+
|
|
98
|
+
| Variable | Default | Description |
|
|
99
|
+
|---|---|---|
|
|
100
|
+
| `OGMENT_BASE_URL` | `https://dashboard.ogment.ai` | Ogment platform URL (for development) |
|
|
101
|
+
| `OGMENT_API_KEY` | — | API key (alternative to `ogment login`) |
|
|
102
|
+
|
|
103
|
+
## How It Works
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
AI Agent (Claude, Cursor, ChatGPT)
|
|
107
|
+
│
|
|
108
|
+
│ Clerk API key (long-lived, revocable)
|
|
109
|
+
▼
|
|
110
|
+
┌─────────────────────────────────┐
|
|
111
|
+
│ Ogment MCP Proxy │
|
|
112
|
+
│ ✓ Validate API key │
|
|
113
|
+
│ ✓ Inject real credentials │
|
|
114
|
+
│ ✓ Log every tool call │
|
|
115
|
+
└──────────────┬──────────────────┘
|
|
116
|
+
│
|
|
117
|
+
┌──────────┼──────────┐
|
|
118
|
+
▼ ▼ ▼
|
|
119
|
+
Salesforce Linear Snowflake
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## License
|
|
123
|
+
|
|
124
|
+
MIT
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import type { CommandContext } from "./commands/context.js";
|
|
3
|
+
import { OutputManager } from "./output/manager.js";
|
|
4
|
+
import { type ExitCode } from "./shared/exit-codes.js";
|
|
5
|
+
export interface GlobalCliOptions {
|
|
6
|
+
apiKey: string | undefined;
|
|
7
|
+
json: boolean | undefined;
|
|
8
|
+
nonInteractive: boolean | undefined;
|
|
9
|
+
quiet: boolean | undefined;
|
|
10
|
+
yes: boolean | undefined;
|
|
11
|
+
}
|
|
12
|
+
interface Runtime {
|
|
13
|
+
context: CommandContext;
|
|
14
|
+
output: OutputManager;
|
|
15
|
+
}
|
|
16
|
+
export declare const runCli: (argv?: readonly string[], runtime?: Runtime) => Promise<ExitCode>;
|
|
17
|
+
export declare const executeCli: () => Promise<void>;
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAQA,OAAO,KAAK,EAAE,cAAc,EAAmB,MAAM,uBAAuB,CAAC;AAO7E,OAAO,EAAE,aAAa,EAA0B,MAAM,qBAAqB,CAAC;AAK5E,OAAO,EAAa,KAAK,QAAQ,EAAoB,MAAM,wBAAwB,CAAC;AAIpF,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,IAAI,EAAE,OAAO,GAAG,SAAS,CAAC;IAC1B,cAAc,EAAE,OAAO,GAAG,SAAS,CAAC;IACpC,KAAK,EAAE,OAAO,GAAG,SAAS,CAAC;IAC3B,GAAG,EAAE,OAAO,GAAG,SAAS,CAAC;CAC1B;AAED,UAAU,OAAO;IACf,OAAO,EAAE,cAAc,CAAC;IACxB,MAAM,EAAE,aAAa,CAAC;CACvB;AA4XD,eAAO,MAAM,MAAM,GACjB,OAAM,SAAS,MAAM,EAA0B,EAC/C,UAAS,OAAyB,KACjC,OAAO,CAAC,QAAQ,CAmBlB,CAAC;AAEF,eAAO,MAAM,UAAU,QAAa,OAAO,CAAC,IAAI,CAG/C,CAAC"}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
|
+
import { Command, CommanderError } from "commander";
|
|
4
|
+
import { runCallCommand } from "./commands/call.js";
|
|
5
|
+
import { runDescribeCommand } from "./commands/describe.js";
|
|
6
|
+
import { runServersCommand } from "./commands/servers.js";
|
|
7
|
+
import { createBrowserOpener } from "./infra/browser.js";
|
|
8
|
+
import { createFileCredentialsStore } from "./infra/credentials.js";
|
|
9
|
+
import { createRuntimeConfig } from "./infra/env.js";
|
|
10
|
+
import { createHttpClient } from "./infra/http.js";
|
|
11
|
+
import { OutputManager } from "./output/manager.js";
|
|
12
|
+
import { createAccountService } from "./services/account.js";
|
|
13
|
+
import { createAuthService } from "./services/auth.js";
|
|
14
|
+
import { createMcpService } from "./services/mcp.js";
|
|
15
|
+
import { UnexpectedError, ValidationError } from "./shared/errors.js";
|
|
16
|
+
import { EXIT_CODE, exitCodeForError } from "./shared/exit-codes.js";
|
|
17
|
+
import { APP_DESCRIPTION, APP_NAME, AGENT_SUCCESS_HINT, VERSION } from "./shared/constants.js";
|
|
18
|
+
class CliExitError extends Error {
|
|
19
|
+
exitCode;
|
|
20
|
+
constructor(exitCode) {
|
|
21
|
+
super("CLI exit");
|
|
22
|
+
this.exitCode = exitCode;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const createRuntime = () => {
|
|
26
|
+
const runtimeConfig = createRuntimeConfig();
|
|
27
|
+
const output = new OutputManager();
|
|
28
|
+
const credentialsStore = createFileCredentialsStore({
|
|
29
|
+
configDir: runtimeConfig.configDir,
|
|
30
|
+
credentialsPath: runtimeConfig.credentialsPath,
|
|
31
|
+
});
|
|
32
|
+
const httpClient = createHttpClient();
|
|
33
|
+
const browserOpener = createBrowserOpener();
|
|
34
|
+
const services = {
|
|
35
|
+
account: createAccountService({
|
|
36
|
+
baseUrl: runtimeConfig.baseUrl,
|
|
37
|
+
httpClient,
|
|
38
|
+
}),
|
|
39
|
+
auth: createAuthService({
|
|
40
|
+
baseUrl: runtimeConfig.baseUrl,
|
|
41
|
+
browserOpener,
|
|
42
|
+
credentialsStore,
|
|
43
|
+
envApiKey: runtimeConfig.envApiKey,
|
|
44
|
+
httpClient,
|
|
45
|
+
}),
|
|
46
|
+
mcp: createMcpService({
|
|
47
|
+
baseUrl: runtimeConfig.baseUrl,
|
|
48
|
+
version: runtimeConfig.version,
|
|
49
|
+
}),
|
|
50
|
+
};
|
|
51
|
+
return {
|
|
52
|
+
context: {
|
|
53
|
+
apiKeyOverride: undefined,
|
|
54
|
+
output,
|
|
55
|
+
services,
|
|
56
|
+
},
|
|
57
|
+
output,
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
const asGlobalOptions = (command) => {
|
|
61
|
+
const options = command.optsWithGlobals();
|
|
62
|
+
return {
|
|
63
|
+
apiKey: options.apiKey,
|
|
64
|
+
json: options.json,
|
|
65
|
+
nonInteractive: options.nonInteractive,
|
|
66
|
+
quiet: options.quiet,
|
|
67
|
+
yes: options.yes,
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
const mapGlobalOutputOptions = (options) => {
|
|
71
|
+
return {
|
|
72
|
+
json: options.json,
|
|
73
|
+
nonInteractive: options.nonInteractive,
|
|
74
|
+
quiet: options.quiet,
|
|
75
|
+
yes: options.yes,
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
const throwCommandError = (error) => {
|
|
79
|
+
throw new CliExitError(exitCodeForError(error));
|
|
80
|
+
};
|
|
81
|
+
const isRecord = (value) => {
|
|
82
|
+
return typeof value === "object" && value !== null;
|
|
83
|
+
};
|
|
84
|
+
const ensureSuccess = (result, output) => {
|
|
85
|
+
if (result.status === "error") {
|
|
86
|
+
output.error(result.error);
|
|
87
|
+
throwCommandError(result.error);
|
|
88
|
+
}
|
|
89
|
+
return result.value;
|
|
90
|
+
};
|
|
91
|
+
const formatSchemaType = (property) => {
|
|
92
|
+
if (Array.isArray(property.enum) && property.enum.length > 0) {
|
|
93
|
+
return property.enum.map(String).join(" | ");
|
|
94
|
+
}
|
|
95
|
+
if (property.type === "array" && typeof property.items?.type === "string") {
|
|
96
|
+
return `${property.items.type}[]`;
|
|
97
|
+
}
|
|
98
|
+
if (typeof property.type === "string" && property.type.length > 0) {
|
|
99
|
+
return property.type;
|
|
100
|
+
}
|
|
101
|
+
return "unknown";
|
|
102
|
+
};
|
|
103
|
+
const renderSchemaParameters = (output, schema) => {
|
|
104
|
+
const required = new Set();
|
|
105
|
+
const requiredValue = schema["required"];
|
|
106
|
+
if (Array.isArray(requiredValue)) {
|
|
107
|
+
for (const item of requiredValue) {
|
|
108
|
+
if (typeof item === "string") {
|
|
109
|
+
required.add(item);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const propertiesValue = schema["properties"];
|
|
114
|
+
if (!isRecord(propertiesValue)) {
|
|
115
|
+
output.info(" Parameters: none");
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const entries = Object.entries(propertiesValue);
|
|
119
|
+
if (entries.length === 0) {
|
|
120
|
+
output.info(" Parameters: none");
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
output.info(" Parameters:");
|
|
124
|
+
for (const [name, propertyValue] of entries) {
|
|
125
|
+
if (!isRecord(propertyValue)) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const property = propertyValue;
|
|
129
|
+
const type = formatSchemaType(property);
|
|
130
|
+
const requirement = required.has(name) ? "required" : "optional";
|
|
131
|
+
const description = typeof property.description === "string" && property.description.length > 0
|
|
132
|
+
? ` - ${property.description}`
|
|
133
|
+
: "";
|
|
134
|
+
output.info(` ${name} (${type}, ${requirement})${description}`);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
const renderSchemaBlock = (output, label, schema) => {
|
|
138
|
+
if (schema === undefined) {
|
|
139
|
+
output.info(` ${label}: unavailable`);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
output.info(` ${label}:`);
|
|
143
|
+
const lines = JSON.stringify(schema, null, 2).split("\n");
|
|
144
|
+
for (const line of lines) {
|
|
145
|
+
output.info(` ${line}`);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
const renderServersListHuman = (output, servers) => {
|
|
149
|
+
if (servers.length === 0) {
|
|
150
|
+
output.info("No servers available.");
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
for (const server of servers) {
|
|
154
|
+
const description = typeof server.description === "string" && server.description.length > 0
|
|
155
|
+
? ` - ${server.description}`
|
|
156
|
+
: "";
|
|
157
|
+
output.info(`${server.path} (${server.orgSlug}) ${server.name}${description}`);
|
|
158
|
+
}
|
|
159
|
+
output.info("Inspect tools with: ogment servers <path>");
|
|
160
|
+
};
|
|
161
|
+
const renderServerDetailsHuman = (output, payload) => {
|
|
162
|
+
output.info(`Server ${payload.server.name} (${payload.server.path}, ${payload.server.orgSlug})`);
|
|
163
|
+
if (payload.tools.length === 0) {
|
|
164
|
+
output.info("No tools available.");
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
for (const tool of payload.tools) {
|
|
168
|
+
const description = typeof tool.description === "string" && tool.description.length > 0
|
|
169
|
+
? ` - ${tool.description}`
|
|
170
|
+
: "";
|
|
171
|
+
output.info(` ${tool.name}${description}`);
|
|
172
|
+
renderSchemaParameters(output, tool.inputSchema);
|
|
173
|
+
renderSchemaBlock(output, "Input schema", tool.inputSchema);
|
|
174
|
+
renderSchemaBlock(output, "Output schema", tool.outputSchema);
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
const createProgram = (runtime) => {
|
|
178
|
+
const program = new Command();
|
|
179
|
+
program.exitOverride();
|
|
180
|
+
program.configureOutput({
|
|
181
|
+
writeErr: () => undefined,
|
|
182
|
+
});
|
|
183
|
+
program
|
|
184
|
+
.name(APP_NAME)
|
|
185
|
+
.description(APP_DESCRIPTION)
|
|
186
|
+
.version(VERSION)
|
|
187
|
+
.option("--apiKey <key>", "API key override")
|
|
188
|
+
.option("--json", "Output machine-readable JSON")
|
|
189
|
+
.option("--quiet", "Suppress non-essential output")
|
|
190
|
+
.option("--non-interactive", "Disable interactive behavior")
|
|
191
|
+
.option("--yes", "Assume yes for any confirmation")
|
|
192
|
+
.hook("preAction", (thisCommand) => {
|
|
193
|
+
const options = asGlobalOptions(thisCommand);
|
|
194
|
+
runtime.output.configure(mapGlobalOutputOptions(options));
|
|
195
|
+
runtime.context.apiKeyOverride = options.apiKey;
|
|
196
|
+
});
|
|
197
|
+
program
|
|
198
|
+
.command("login")
|
|
199
|
+
.description("Authenticate with Ogment")
|
|
200
|
+
.option("--device", "Use device flow explicitly")
|
|
201
|
+
.action(async (options) => {
|
|
202
|
+
const result = await runtime.context.services.auth.login({
|
|
203
|
+
device: options.device === true,
|
|
204
|
+
nonInteractive: runtime.output.nonInteractive,
|
|
205
|
+
onPending: ({ userCode, verificationUri }) => {
|
|
206
|
+
runtime.output.info(`Open ${verificationUri}`);
|
|
207
|
+
runtime.output.info(`Enter code: ${userCode}`);
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
const data = ensureSuccess(result, runtime.output);
|
|
211
|
+
const humanMessage = data.alreadyLoggedIn
|
|
212
|
+
? `Already logged in as ${data.agentName}.`
|
|
213
|
+
: `Logged in as ${data.agentName}. ${AGENT_SUCCESS_HINT}`;
|
|
214
|
+
runtime.output.success(data, humanMessage);
|
|
215
|
+
});
|
|
216
|
+
program
|
|
217
|
+
.command("logout")
|
|
218
|
+
.description("Revoke token and delete local credentials")
|
|
219
|
+
.action(async () => {
|
|
220
|
+
const result = await runtime.context.services.auth.logout();
|
|
221
|
+
const data = ensureSuccess(result, runtime.output);
|
|
222
|
+
let humanMessage = "Not logged in.";
|
|
223
|
+
if (data.localCredentialsDeleted) {
|
|
224
|
+
humanMessage = data.revoked
|
|
225
|
+
? "Logged out and revoked API key."
|
|
226
|
+
: "Local credentials deleted. Server revocation not confirmed.";
|
|
227
|
+
}
|
|
228
|
+
runtime.output.success(data, humanMessage);
|
|
229
|
+
});
|
|
230
|
+
program
|
|
231
|
+
.command("servers [path]")
|
|
232
|
+
.description("List servers or inspect tools for one server")
|
|
233
|
+
.action(async (path) => {
|
|
234
|
+
const result = await runServersCommand(runtime.context, { path });
|
|
235
|
+
const data = ensureSuccess(result, runtime.output);
|
|
236
|
+
if (runtime.output.mode === "human") {
|
|
237
|
+
if ("servers" in data) {
|
|
238
|
+
renderServersListHuman(runtime.output, data.servers);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
renderServerDetailsHuman(runtime.output, data);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
runtime.output.success(data);
|
|
245
|
+
});
|
|
246
|
+
program
|
|
247
|
+
.command("call <serverPath> <toolName> [argsJson]")
|
|
248
|
+
.description("Call a tool on an Ogment server")
|
|
249
|
+
.action(async (serverPath, toolName, argsJson) => {
|
|
250
|
+
const result = await runCallCommand(runtime.context, {
|
|
251
|
+
argsJson,
|
|
252
|
+
serverPath,
|
|
253
|
+
toolName,
|
|
254
|
+
});
|
|
255
|
+
const data = ensureSuccess(result, runtime.output);
|
|
256
|
+
if (runtime.output.mode === "human") {
|
|
257
|
+
runtime.output.json(data.result);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
runtime.output.success(data);
|
|
261
|
+
});
|
|
262
|
+
program
|
|
263
|
+
.command("describe")
|
|
264
|
+
.description("Describe this CLI as a tool set for agent registration")
|
|
265
|
+
.action(() => {
|
|
266
|
+
const result = runDescribeCommand();
|
|
267
|
+
const data = ensureSuccess(result, runtime.output);
|
|
268
|
+
runtime.output.json(data);
|
|
269
|
+
});
|
|
270
|
+
program.action(() => {
|
|
271
|
+
if (runtime.output.mode === "json") {
|
|
272
|
+
runtime.output.success({
|
|
273
|
+
commands: ["login", "logout", "servers", "call", "describe"],
|
|
274
|
+
});
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
runtime.output.info(`${APP_NAME} v${VERSION}`);
|
|
278
|
+
runtime.output.info(APP_DESCRIPTION);
|
|
279
|
+
runtime.output.info("");
|
|
280
|
+
runtime.output.info("Commands:");
|
|
281
|
+
runtime.output.info(" login [--device]");
|
|
282
|
+
runtime.output.info(" servers [path]");
|
|
283
|
+
runtime.output.info(" call <serverPath> <toolName> [argsJson]");
|
|
284
|
+
runtime.output.info(" logout");
|
|
285
|
+
runtime.output.info(" describe");
|
|
286
|
+
});
|
|
287
|
+
return program;
|
|
288
|
+
};
|
|
289
|
+
const normalizeCommanderError = (error) => {
|
|
290
|
+
if (error instanceof CommanderError) {
|
|
291
|
+
return new ValidationError({
|
|
292
|
+
details: error.message,
|
|
293
|
+
message: "Invalid CLI usage",
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
if (error instanceof Error) {
|
|
297
|
+
return new UnexpectedError({
|
|
298
|
+
cause: error,
|
|
299
|
+
message: error.message,
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
return new UnexpectedError({
|
|
303
|
+
cause: error,
|
|
304
|
+
message: "Unexpected CLI failure",
|
|
305
|
+
});
|
|
306
|
+
};
|
|
307
|
+
const normalizeCliArgv = (argv) => {
|
|
308
|
+
if (argv[0] === "--") {
|
|
309
|
+
return argv.slice(1);
|
|
310
|
+
}
|
|
311
|
+
return argv;
|
|
312
|
+
};
|
|
313
|
+
export const runCli = async (argv = process.argv.slice(2), runtime = createRuntime()) => {
|
|
314
|
+
const program = createProgram(runtime);
|
|
315
|
+
try {
|
|
316
|
+
await program.parseAsync(normalizeCliArgv(argv), { from: "user" });
|
|
317
|
+
return EXIT_CODE.success;
|
|
318
|
+
}
|
|
319
|
+
catch (error) {
|
|
320
|
+
if (error instanceof CliExitError) {
|
|
321
|
+
return error.exitCode;
|
|
322
|
+
}
|
|
323
|
+
if (error instanceof CommanderError && error.exitCode === EXIT_CODE.success) {
|
|
324
|
+
return EXIT_CODE.success;
|
|
325
|
+
}
|
|
326
|
+
const normalized = normalizeCommanderError(error);
|
|
327
|
+
runtime.output.error(normalized);
|
|
328
|
+
return exitCodeForError(normalized);
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
export const executeCli = async () => {
|
|
332
|
+
const code = await runCli();
|
|
333
|
+
process.exitCode = code;
|
|
334
|
+
};
|
|
335
|
+
if (process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
336
|
+
await executeCli();
|
|
337
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Result as ResultType } from "better-result";
|
|
2
|
+
import type { McpServiceError } from "../services/mcp.js";
|
|
3
|
+
import { NotFoundError, ValidationError } from "../shared/errors.js";
|
|
4
|
+
import type { ToolCallSuccess } from "../shared/types.js";
|
|
5
|
+
import type { CommandContext } from "./context.js";
|
|
6
|
+
import { type ResolveServerStateError } from "./server-context.js";
|
|
7
|
+
export interface CallCommandOptions {
|
|
8
|
+
argsJson: string | undefined;
|
|
9
|
+
serverPath: string;
|
|
10
|
+
toolName: string;
|
|
11
|
+
}
|
|
12
|
+
export type CallCommandError = McpServiceError | NotFoundError | ResolveServerStateError | ValidationError;
|
|
13
|
+
export declare const runCallCommand: (context: CommandContext, options: CallCommandOptions) => Promise<ResultType<ToolCallSuccess, CallCommandError>>;
|
|
14
|
+
//# sourceMappingURL=call.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"call.d.ts","sourceRoot":"","sources":["../../src/commands/call.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,eAAe,CAAC;AAE1D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACrE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAGL,KAAK,uBAAuB,EAC7B,MAAM,qBAAqB,CAAC;AAE7B,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,gBAAgB,GACxB,eAAe,GACf,aAAa,GACb,uBAAuB,GACvB,eAAe,CAAC;AAiCpB,eAAO,MAAM,cAAc,GACzB,SAAS,cAAc,EACvB,SAAS,kBAAkB,KAC1B,OAAO,CAAC,UAAU,CAAC,eAAe,EAAE,gBAAgB,CAAC,CAqCvD,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Result } from "better-result";
|
|
2
|
+
import { NotFoundError, ValidationError } from "../shared/errors.js";
|
|
3
|
+
import { findServerByPath, resolveServerState, } from "./server-context.js";
|
|
4
|
+
const parseArgs = (argsJson) => {
|
|
5
|
+
if (argsJson === undefined || argsJson.length === 0) {
|
|
6
|
+
return Result.ok({});
|
|
7
|
+
}
|
|
8
|
+
const parsed = Result.try({
|
|
9
|
+
catch: () => new ValidationError({
|
|
10
|
+
details: argsJson,
|
|
11
|
+
message: "Invalid JSON arguments",
|
|
12
|
+
}),
|
|
13
|
+
try: () => JSON.parse(argsJson),
|
|
14
|
+
});
|
|
15
|
+
if (Result.isError(parsed)) {
|
|
16
|
+
return parsed;
|
|
17
|
+
}
|
|
18
|
+
if (typeof parsed.value !== "object" || parsed.value === null || Array.isArray(parsed.value)) {
|
|
19
|
+
return Result.err(new ValidationError({
|
|
20
|
+
message: "Tool arguments must be a JSON object",
|
|
21
|
+
}));
|
|
22
|
+
}
|
|
23
|
+
return Result.ok(parsed.value);
|
|
24
|
+
};
|
|
25
|
+
export const runCallCommand = async (context, options) => {
|
|
26
|
+
const argsResult = parseArgs(options.argsJson);
|
|
27
|
+
if (Result.isError(argsResult)) {
|
|
28
|
+
return argsResult;
|
|
29
|
+
}
|
|
30
|
+
const stateResult = await resolveServerState(context);
|
|
31
|
+
if (Result.isError(stateResult)) {
|
|
32
|
+
return stateResult;
|
|
33
|
+
}
|
|
34
|
+
const targetServerResult = findServerByPath(stateResult.value.servers, options.serverPath);
|
|
35
|
+
if (Result.isError(targetServerResult)) {
|
|
36
|
+
return targetServerResult;
|
|
37
|
+
}
|
|
38
|
+
const targetServer = targetServerResult.value;
|
|
39
|
+
const callResult = await context.services.mcp.callTool({
|
|
40
|
+
orgSlug: targetServer.orgSlug,
|
|
41
|
+
serverPath: targetServer.path,
|
|
42
|
+
}, stateResult.value.apiKey, options.toolName, argsResult.value);
|
|
43
|
+
if (Result.isError(callResult)) {
|
|
44
|
+
return callResult;
|
|
45
|
+
}
|
|
46
|
+
return Result.ok({
|
|
47
|
+
result: callResult.value.structuredContent,
|
|
48
|
+
serverPath: options.serverPath,
|
|
49
|
+
toolName: options.toolName,
|
|
50
|
+
});
|
|
51
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { OutputManager } from "../output/manager.js";
|
|
2
|
+
import type { AccountService } from "../services/account.js";
|
|
3
|
+
import type { AuthService } from "../services/auth.js";
|
|
4
|
+
import type { McpService } from "../services/mcp.js";
|
|
5
|
+
export interface CommandServices {
|
|
6
|
+
account: AccountService;
|
|
7
|
+
auth: AuthService;
|
|
8
|
+
mcp: McpService;
|
|
9
|
+
}
|
|
10
|
+
export interface CommandContext {
|
|
11
|
+
apiKeyOverride: string | undefined;
|
|
12
|
+
output: OutputManager;
|
|
13
|
+
services: CommandServices;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/commands/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,cAAc,CAAC;IACxB,IAAI,EAAE,WAAW,CAAC;IAClB,GAAG,EAAE,UAAU,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,MAAM,EAAE,aAAa,CAAC;IACtB,QAAQ,EAAE,eAAe,CAAC;CAC3B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"describe.d.ts","sourceRoot":"","sources":["../../src/commands/describe.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,eAAe,CAAC;AAI1D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAiH5D,eAAO,MAAM,kBAAkB,QAAO,UAAU,CAAC,eAAe,EAAE,KAAK,CAItE,CAAC"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Result } from "better-result";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
4
|
+
const asObjectRecord = (value) => {
|
|
5
|
+
if (typeof value === "object" && value !== null) {
|
|
6
|
+
return value;
|
|
7
|
+
}
|
|
8
|
+
return {};
|
|
9
|
+
};
|
|
10
|
+
const toJsonSchemaObject = (schema, title) => {
|
|
11
|
+
return asObjectRecord(zodToJsonSchema(schema, title));
|
|
12
|
+
};
|
|
13
|
+
const commandDefinitions = () => {
|
|
14
|
+
const loginInputSchema = z.object({
|
|
15
|
+
device: z.boolean().optional(),
|
|
16
|
+
});
|
|
17
|
+
const loginOutputSchema = z.object({
|
|
18
|
+
agentName: z.string(),
|
|
19
|
+
alreadyLoggedIn: z.boolean(),
|
|
20
|
+
});
|
|
21
|
+
const serversInputSchema = z.object({
|
|
22
|
+
path: z.string().optional(),
|
|
23
|
+
});
|
|
24
|
+
const serversOutputSchema = z.object({
|
|
25
|
+
server: z
|
|
26
|
+
.object({
|
|
27
|
+
enabled: z.boolean(),
|
|
28
|
+
name: z.string(),
|
|
29
|
+
orgSlug: z.string(),
|
|
30
|
+
path: z.string(),
|
|
31
|
+
})
|
|
32
|
+
.optional(),
|
|
33
|
+
servers: z
|
|
34
|
+
.array(z.object({
|
|
35
|
+
enabled: z.boolean(),
|
|
36
|
+
name: z.string(),
|
|
37
|
+
orgSlug: z.string(),
|
|
38
|
+
path: z.string(),
|
|
39
|
+
}))
|
|
40
|
+
.optional(),
|
|
41
|
+
tools: z
|
|
42
|
+
.array(z.object({
|
|
43
|
+
description: z.string().nullable(),
|
|
44
|
+
inputSchema: z.record(z.string(), z.unknown()),
|
|
45
|
+
name: z.string(),
|
|
46
|
+
}))
|
|
47
|
+
.optional(),
|
|
48
|
+
});
|
|
49
|
+
const callInputSchema = z.object({
|
|
50
|
+
args: z.record(z.string(), z.unknown()).optional(),
|
|
51
|
+
serverPath: z.string(),
|
|
52
|
+
toolName: z.string(),
|
|
53
|
+
});
|
|
54
|
+
const callOutputSchema = z.object({
|
|
55
|
+
result: z.unknown(),
|
|
56
|
+
serverPath: z.string(),
|
|
57
|
+
toolName: z.string(),
|
|
58
|
+
});
|
|
59
|
+
const logoutOutputSchema = z.object({
|
|
60
|
+
localCredentialsDeleted: z.boolean(),
|
|
61
|
+
revoked: z.boolean(),
|
|
62
|
+
});
|
|
63
|
+
return [
|
|
64
|
+
{
|
|
65
|
+
description: "Authenticate with Ogment. Uses browser-assisted device flow by default.",
|
|
66
|
+
name: "ogment_login",
|
|
67
|
+
outputSchema: toJsonSchemaObject(loginOutputSchema, "ogmentLoginOutput"),
|
|
68
|
+
parameters: toJsonSchemaObject(loginInputSchema, "ogmentLoginInput"),
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
description: "List available servers or inspect one server's tools.",
|
|
72
|
+
name: "ogment_servers",
|
|
73
|
+
outputSchema: toJsonSchemaObject(serversOutputSchema, "ogmentServersOutput"),
|
|
74
|
+
parameters: toJsonSchemaObject(serversInputSchema, "ogmentServersInput"),
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
description: "Call a tool on a server.",
|
|
78
|
+
name: "ogment_call",
|
|
79
|
+
outputSchema: toJsonSchemaObject(callOutputSchema, "ogmentCallOutput"),
|
|
80
|
+
parameters: toJsonSchemaObject(callInputSchema, "ogmentCallInput"),
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
description: "Revoke current session and delete local credentials.",
|
|
84
|
+
name: "ogment_logout",
|
|
85
|
+
outputSchema: toJsonSchemaObject(logoutOutputSchema, "ogmentLogoutOutput"),
|
|
86
|
+
parameters: toJsonSchemaObject(z.object({}), "ogmentLogoutInput"),
|
|
87
|
+
},
|
|
88
|
+
];
|
|
89
|
+
};
|
|
90
|
+
export const runDescribeCommand = () => {
|
|
91
|
+
return Result.ok({
|
|
92
|
+
tools: commandDefinitions(),
|
|
93
|
+
});
|
|
94
|
+
};
|