@oh-my-tool/cli 0.2.0 → 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 +7 -2
- package/assets/skills/oh-my-tool/SKILL.md +11 -3
- package/bin/ohmytool.cjs +0 -0
- package/package.json +12 -10
- package/src/cli/commands/describe.ts +23 -22
- package/src/cli/commands/extension.ts +24 -24
- package/src/cli/commands/index.ts +7 -6
- package/src/cli/commands/integrate.ts +64 -64
- package/src/cli/commands/mcp.ts +86 -0
- package/src/cli/commands/run.ts +8 -7
- package/src/cli/commands/search.ts +14 -13
- package/src/cli/commands/secret.ts +68 -68
- package/src/cli/context.ts +25 -2
- package/src/cli/index.ts +296 -272
- package/src/cli/parseArgs.ts +62 -44
- package/src/config/config.ts +155 -63
- package/src/core/executor.ts +89 -89
- package/src/core/registry.ts +31 -31
- package/src/core/result.ts +14 -14
- package/src/extension/discovery.ts +61 -61
- package/src/extension/install.ts +23 -23
- package/src/extension/loader.ts +32 -32
- package/src/extension/manifest.ts +114 -114
- package/src/integration/adapters.ts +98 -98
- package/src/integration/index.ts +4 -4
- package/src/integration/manager.ts +375 -375
- package/src/integration/skill.ts +84 -84
- package/src/integration/types.ts +55 -55
- package/src/policy/policy.ts +136 -136
- package/src/runtime/errors.ts +7 -2
- package/src/runtime/executor.ts +6 -1
- package/src/runtime/provider.ts +1 -0
- package/src/runtime/providers/mcp/normalize.ts +36 -0
- package/src/runtime/providers/mcp/oauth-callback.ts +91 -0
- package/src/runtime/providers/mcp/oauth-provider.ts +348 -0
- package/src/runtime/providers/mcp/oauth-store.ts +106 -0
- package/src/runtime/providers/mcp/provider.ts +99 -0
- package/src/runtime/providers/mcp/safe-errors.ts +63 -0
- package/src/runtime/providers/mcp/session.ts +117 -0
- package/src/runtime/providers/mcp/transport.ts +140 -0
- package/src/runtime/result.ts +1 -1
- package/src/runtime/runtime.ts +38 -12
- package/src/runtime/schema.ts +14 -4
- package/src/search/search.ts +78 -78
- package/src/secrets/secrets.ts +45 -45
- package/src/version.ts +1 -1
package/README.md
CHANGED
|
@@ -9,5 +9,10 @@ ohmytool --help
|
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
Requires Bun 1.4 or newer. Use `ohmytool search`, `ohmytool describe`, then
|
|
12
|
-
`ohmytool run
|
|
13
|
-
|
|
12
|
+
`ohmytool run` for both native and MCP-backed tools. Configure MCP servers in
|
|
13
|
+
`config.toml`; use `ohmytool mcp list` to inspect configured servers and
|
|
14
|
+
`ohmytool mcp auth <server>` / `ohmytool mcp logout <server>` for interactive
|
|
15
|
+
OAuth credentials. See the main
|
|
16
|
+
[Oh My Tool repository](https://github.com/oh-my-tool/oh-my-tool) and
|
|
17
|
+
[`docs/configuration.md`](../../docs/configuration.md) for configuration and
|
|
18
|
+
secret-management guidance.
|
|
@@ -5,7 +5,8 @@ description: Discover and invoke local, development, and enterprise tools throug
|
|
|
5
5
|
|
|
6
6
|
# Oh My Tool — The Agent Tool Runtime
|
|
7
7
|
|
|
8
|
-
Use the canonical `ohmytool` CLI to discover and execute tools exposed by
|
|
8
|
+
Use the canonical `ohmytool` CLI to discover and execute tools exposed by
|
|
9
|
+
installed native extensions and enabled MCP servers.
|
|
9
10
|
|
|
10
11
|
## When to use
|
|
11
12
|
|
|
@@ -29,7 +30,8 @@ When you do not know the exact tool ID:
|
|
|
29
30
|
ohmytool search "<what you need to do>"
|
|
30
31
|
```
|
|
31
32
|
|
|
32
|
-
Search returns lightweight summary metadata
|
|
33
|
+
Search returns lightweight summary metadata. Native discovery reads manifests;
|
|
34
|
+
MCP discovery connects to enabled servers and obtains `tools/list`.
|
|
33
35
|
|
|
34
36
|
### 2. Describe
|
|
35
37
|
|
|
@@ -43,7 +45,8 @@ Use the returned schema, constraints, risk level, and configuration requirements
|
|
|
43
45
|
|
|
44
46
|
### 3. Run
|
|
45
47
|
|
|
46
|
-
Execute the selected tool through the runtime
|
|
48
|
+
Execute the selected tool through the runtime. Use the exact namespaced ID
|
|
49
|
+
returned by search/describe for MCP tools:
|
|
47
50
|
|
|
48
51
|
```bash
|
|
49
52
|
ohmytool run <tool-name> key=value
|
|
@@ -62,6 +65,11 @@ echo '{"connection":"iot-test","sql":"SELECT 1"}' | ohmytool run mysql.query --s
|
|
|
62
65
|
- Do not guess tool arguments; use `ohmytool describe`.
|
|
63
66
|
- Do not guess connection names, endpoints, credentials, tokens, or authentication details.
|
|
64
67
|
- Never request or expose secrets managed by Oh My Tool.
|
|
68
|
+
- Provider origin does not change the `search → describe → run` sequence.
|
|
69
|
+
- Never place bearer tokens, OAuth tokens, authorization codes, client secrets,
|
|
70
|
+
or secret environment values in tool arguments.
|
|
71
|
+
- If a command returns `MCP_AUTH_REQUIRED`, ask the user to run or approve
|
|
72
|
+
`ohmytool mcp auth <server-id>`; never collect credentials in chat.
|
|
65
73
|
- Respect tool risk levels, policy restrictions, and environment restrictions.
|
|
66
74
|
- Prefer the smallest and safest tool that can complete the task.
|
|
67
75
|
- Treat runtime results as structured tool output and use them to continue the task.
|
package/bin/ohmytool.cjs
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@oh-my-tool/cli",
|
|
3
|
-
"version": "0.
|
|
1
|
+
{
|
|
2
|
+
"name": "@oh-my-tool/cli",
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Oh My Tool CLI - local and enterprise tools for agents",
|
|
6
6
|
"keywords": ["ai", "agent", "cli", "tools", "extensions", "bun"],
|
|
@@ -13,15 +13,17 @@
|
|
|
13
13
|
"bugs": "https://github.com/oh-my-tool/oh-my-tool/issues",
|
|
14
14
|
"bin": {
|
|
15
15
|
"ohmytool": "bin/ohmytool.cjs"
|
|
16
|
-
},
|
|
17
|
-
"files": [
|
|
18
|
-
"assets",
|
|
19
|
-
"bin",
|
|
20
|
-
"src"
|
|
21
|
-
],
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"assets",
|
|
19
|
+
"bin",
|
|
20
|
+
"src"
|
|
21
|
+
],
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@clack/prompts": "^1.7.0",
|
|
24
|
-
"@
|
|
24
|
+
"@modelcontextprotocol/client": "2.0.0",
|
|
25
|
+
"@oh-my-tool/sdk": "0.3.0",
|
|
26
|
+
"open": "11.0.0"
|
|
25
27
|
},
|
|
26
28
|
"engines": {
|
|
27
29
|
"node": ">=20",
|
|
@@ -1,24 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
export interface DescribedTool {
|
|
4
|
-
name: string;
|
|
5
|
-
description: string;
|
|
6
|
-
risk: string;
|
|
7
|
-
inputSchema: Record<string, unknown> | undefined;
|
|
8
|
-
extension: string;
|
|
9
|
-
extensionVersion: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
1
|
+
import { withRuntime } from "../context";
|
|
2
|
+
|
|
3
|
+
export interface DescribedTool {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
risk: string;
|
|
7
|
+
inputSchema: Record<string, unknown> | undefined;
|
|
8
|
+
extension: string;
|
|
9
|
+
extensionVersion: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
12
|
export async function runDescribe(toolName: string): Promise<DescribedTool> {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
13
|
+
return withRuntime(async (runtime) => {
|
|
14
|
+
const descriptor = await runtime.describe(toolName);
|
|
15
|
+
return {
|
|
16
|
+
name: descriptor.id,
|
|
17
|
+
description: descriptor.description,
|
|
18
|
+
risk: descriptor.risk,
|
|
19
|
+
inputSchema: descriptor.inputSchema,
|
|
20
|
+
extension: descriptor.source.id,
|
|
21
|
+
extensionVersion: "unknown",
|
|
22
|
+
};
|
|
23
|
+
});
|
|
23
24
|
}
|
|
24
|
-
|
|
25
|
+
|
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
import { discoverExtensions } from "../../extension/discovery";
|
|
2
|
-
import { installLocalExtension, type InstalledRef } from "../../extension/install";
|
|
3
|
-
import { homeDir } from "../context";
|
|
4
|
-
|
|
5
|
-
export interface InstalledInfo {
|
|
6
|
-
id: string;
|
|
7
|
-
version: string;
|
|
8
|
-
entry: string;
|
|
9
|
-
toolCount: number;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export async function runExtensionList(): Promise<InstalledInfo[]> {
|
|
13
|
-
return discoverExtensions(homeDir()).map((e) => ({
|
|
14
|
-
id: e.id,
|
|
15
|
-
version: e.version,
|
|
16
|
-
entry: e.entry,
|
|
17
|
-
toolCount: e.manifest.tools.length,
|
|
18
|
-
}));
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export async function runExtensionInstall(spec: string): Promise<InstalledRef> {
|
|
22
|
-
return installLocalExtension(homeDir(), spec);
|
|
23
|
-
}
|
|
24
|
-
|
|
1
|
+
import { discoverExtensions } from "../../extension/discovery";
|
|
2
|
+
import { installLocalExtension, type InstalledRef } from "../../extension/install";
|
|
3
|
+
import { homeDir } from "../context";
|
|
4
|
+
|
|
5
|
+
export interface InstalledInfo {
|
|
6
|
+
id: string;
|
|
7
|
+
version: string;
|
|
8
|
+
entry: string;
|
|
9
|
+
toolCount: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function runExtensionList(): Promise<InstalledInfo[]> {
|
|
13
|
+
return discoverExtensions(homeDir()).map((e) => ({
|
|
14
|
+
id: e.id,
|
|
15
|
+
version: e.version,
|
|
16
|
+
entry: e.entry,
|
|
17
|
+
toolCount: e.manifest.tools.length,
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function runExtensionInstall(spec: string): Promise<InstalledRef> {
|
|
22
|
+
return installLocalExtension(homeDir(), spec);
|
|
23
|
+
}
|
|
24
|
+
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
export * from "./search";
|
|
2
|
-
export * from "./describe";
|
|
1
|
+
export * from "./search";
|
|
2
|
+
export * from "./describe";
|
|
3
3
|
export * from "./run";
|
|
4
|
-
export * from "./extension";
|
|
5
|
-
export * from "./secret";
|
|
6
|
-
export * from "./integrate";
|
|
7
|
-
|
|
4
|
+
export * from "./extension";
|
|
5
|
+
export * from "./secret";
|
|
6
|
+
export * from "./integrate";
|
|
7
|
+
export * from "./mcp";
|
|
8
|
+
|
|
@@ -1,64 +1,64 @@
|
|
|
1
|
-
import type { AgentDetection, AgentId, IntegrationResult } from "../../integration";
|
|
2
|
-
import { AGENT_IDS, createIntegrationManager } from "../../integration";
|
|
3
|
-
import { homeDir } from "../context";
|
|
4
|
-
import { VERSION } from "../../version";
|
|
5
|
-
|
|
6
|
-
export type IntegrateAction = "install" | "status" | "repair" | "uninstall";
|
|
7
|
-
|
|
8
|
-
export interface IntegrateInput {
|
|
9
|
-
action: IntegrateAction;
|
|
10
|
-
agents?: AgentId[];
|
|
11
|
-
force?: boolean;
|
|
12
|
-
dryRun?: boolean;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface IntegrateOutput {
|
|
16
|
-
action: IntegrateAction;
|
|
17
|
-
dryRun: boolean;
|
|
18
|
-
detected: AgentDetection[];
|
|
19
|
-
selected: AgentId[];
|
|
20
|
-
results: IntegrationResult[];
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
type IntegrationManager = ReturnType<typeof createIntegrationManager>;
|
|
24
|
-
|
|
25
|
-
export function defaultIntegrationManager(): IntegrationManager {
|
|
26
|
-
return createIntegrationManager({ omtHome: homeDir(), skillVersion: VERSION });
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export async function runIntegrate(
|
|
30
|
-
input: IntegrateInput,
|
|
31
|
-
manager: IntegrationManager = defaultIntegrationManager(),
|
|
32
|
-
): Promise<IntegrateOutput> {
|
|
33
|
-
const detected = await manager.detect();
|
|
34
|
-
const known = new Set<AgentId>(AGENT_IDS);
|
|
35
|
-
for (const id of input.agents ?? []) {
|
|
36
|
-
if (!known.has(id)) throw new Error(`Unknown agent: ${id}`);
|
|
37
|
-
}
|
|
38
|
-
const selected = input.agents?.length ? input.agents : detected.map((agent) => agent.id);
|
|
39
|
-
const detectedIds = new Set(detected.map((agent) => agent.id));
|
|
40
|
-
for (const id of selected) {
|
|
41
|
-
if (!detectedIds.has(id)) throw new Error(`Agent is not detected: ${id}`);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (input.dryRun) {
|
|
45
|
-
return { action: input.action, dryRun: true, detected, selected, results: [] };
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
let results: IntegrationResult[];
|
|
49
|
-
switch (input.action) {
|
|
50
|
-
case "install":
|
|
51
|
-
results = await manager.install(selected, { force: input.force });
|
|
52
|
-
break;
|
|
53
|
-
case "status":
|
|
54
|
-
results = (await manager.status()).filter((item) => selected.includes(item.agent));
|
|
55
|
-
break;
|
|
56
|
-
case "repair":
|
|
57
|
-
results = await manager.repair(selected);
|
|
58
|
-
break;
|
|
59
|
-
case "uninstall":
|
|
60
|
-
results = await manager.uninstall(selected);
|
|
61
|
-
break;
|
|
62
|
-
}
|
|
63
|
-
return { action: input.action, dryRun: false, detected, selected, results };
|
|
64
|
-
}
|
|
1
|
+
import type { AgentDetection, AgentId, IntegrationResult } from "../../integration";
|
|
2
|
+
import { AGENT_IDS, createIntegrationManager } from "../../integration";
|
|
3
|
+
import { homeDir } from "../context";
|
|
4
|
+
import { VERSION } from "../../version";
|
|
5
|
+
|
|
6
|
+
export type IntegrateAction = "install" | "status" | "repair" | "uninstall";
|
|
7
|
+
|
|
8
|
+
export interface IntegrateInput {
|
|
9
|
+
action: IntegrateAction;
|
|
10
|
+
agents?: AgentId[];
|
|
11
|
+
force?: boolean;
|
|
12
|
+
dryRun?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface IntegrateOutput {
|
|
16
|
+
action: IntegrateAction;
|
|
17
|
+
dryRun: boolean;
|
|
18
|
+
detected: AgentDetection[];
|
|
19
|
+
selected: AgentId[];
|
|
20
|
+
results: IntegrationResult[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type IntegrationManager = ReturnType<typeof createIntegrationManager>;
|
|
24
|
+
|
|
25
|
+
export function defaultIntegrationManager(): IntegrationManager {
|
|
26
|
+
return createIntegrationManager({ omtHome: homeDir(), skillVersion: VERSION });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function runIntegrate(
|
|
30
|
+
input: IntegrateInput,
|
|
31
|
+
manager: IntegrationManager = defaultIntegrationManager(),
|
|
32
|
+
): Promise<IntegrateOutput> {
|
|
33
|
+
const detected = await manager.detect();
|
|
34
|
+
const known = new Set<AgentId>(AGENT_IDS);
|
|
35
|
+
for (const id of input.agents ?? []) {
|
|
36
|
+
if (!known.has(id)) throw new Error(`Unknown agent: ${id}`);
|
|
37
|
+
}
|
|
38
|
+
const selected = input.agents?.length ? input.agents : detected.map((agent) => agent.id);
|
|
39
|
+
const detectedIds = new Set(detected.map((agent) => agent.id));
|
|
40
|
+
for (const id of selected) {
|
|
41
|
+
if (!detectedIds.has(id)) throw new Error(`Agent is not detected: ${id}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (input.dryRun) {
|
|
45
|
+
return { action: input.action, dryRun: true, detected, selected, results: [] };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let results: IntegrationResult[];
|
|
49
|
+
switch (input.action) {
|
|
50
|
+
case "install":
|
|
51
|
+
results = await manager.install(selected, { force: input.force });
|
|
52
|
+
break;
|
|
53
|
+
case "status":
|
|
54
|
+
results = (await manager.status()).filter((item) => selected.includes(item.agent));
|
|
55
|
+
break;
|
|
56
|
+
case "repair":
|
|
57
|
+
results = await manager.repair(selected);
|
|
58
|
+
break;
|
|
59
|
+
case "uninstall":
|
|
60
|
+
results = await manager.uninstall(selected);
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
return { action: input.action, dryRun: false, detected, selected, results };
|
|
64
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { SecretStore } from "@oh-my-tool/sdk";
|
|
2
|
+
import type { McpHttpServerConfig, McpServerConfig } from "../../config/config";
|
|
3
|
+
import { loadConfig } from "../../config/config";
|
|
4
|
+
import { prepareHome } from "../../migration";
|
|
5
|
+
import { createPaths } from "../../paths";
|
|
6
|
+
import { RuntimeError } from "../../runtime/errors";
|
|
7
|
+
import {
|
|
8
|
+
authorizeMcpServer,
|
|
9
|
+
logoutMcpServer,
|
|
10
|
+
} from "../../runtime/providers/mcp/oauth-provider";
|
|
11
|
+
import { SecretsManager } from "../../secrets/secrets";
|
|
12
|
+
|
|
13
|
+
export interface McpCommandDependencies {
|
|
14
|
+
readonly secrets?: SecretStore;
|
|
15
|
+
readonly authorize?: typeof authorizeMcpServer;
|
|
16
|
+
readonly logout?: typeof logoutMcpServer;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface McpServerSummary {
|
|
20
|
+
readonly id: string;
|
|
21
|
+
readonly enabled: boolean;
|
|
22
|
+
readonly transport: McpServerConfig["transport"];
|
|
23
|
+
readonly namespace: string;
|
|
24
|
+
readonly auth: "none" | "bearer" | "oauth" | "disabled";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface McpListResult {
|
|
28
|
+
readonly servers: McpServerSummary[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** List configured MCP servers without connecting to them or exposing secrets. */
|
|
32
|
+
export async function runMcpList(): Promise<McpListResult> {
|
|
33
|
+
const paths = createPaths();
|
|
34
|
+
await prepareHome(paths);
|
|
35
|
+
const config = loadConfig(paths.home);
|
|
36
|
+
const servers = Object.entries(config.mcp.servers)
|
|
37
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
38
|
+
.map(([id, server]) => ({
|
|
39
|
+
id,
|
|
40
|
+
enabled: server.enabled,
|
|
41
|
+
transport: server.transport,
|
|
42
|
+
namespace: server.namespace,
|
|
43
|
+
auth: server.transport === "disabled"
|
|
44
|
+
? "disabled" as const
|
|
45
|
+
: server.transport === "stdio"
|
|
46
|
+
? "none" as const
|
|
47
|
+
: server.auth.type,
|
|
48
|
+
}));
|
|
49
|
+
return { servers };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function oauthTarget(serverId: string, config: McpServerConfig | undefined): McpHttpServerConfig {
|
|
53
|
+
if (config === undefined || !config.enabled || config.transport !== "streamable-http" || config.auth.type !== "oauth") {
|
|
54
|
+
throw new RuntimeError("MCP_OAUTH_NOT_CONFIGURED", `MCP server '${serverId}' is not an enabled OAuth Streamable HTTP server`);
|
|
55
|
+
}
|
|
56
|
+
return config;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function commandContext(serverId: string, secrets?: SecretStore): Promise<{
|
|
60
|
+
config: McpHttpServerConfig;
|
|
61
|
+
secrets: SecretStore;
|
|
62
|
+
}> {
|
|
63
|
+
const paths = createPaths();
|
|
64
|
+
await prepareHome(paths);
|
|
65
|
+
const config = loadConfig(paths.home);
|
|
66
|
+
return {
|
|
67
|
+
config: oauthTarget(serverId, config.mcp.servers[serverId]),
|
|
68
|
+
secrets: secrets ?? new SecretsManager(),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function runMcpAuth(
|
|
73
|
+
serverId: string,
|
|
74
|
+
dependencies: McpCommandDependencies = {},
|
|
75
|
+
): Promise<{ serverId: string; authorized: true }> {
|
|
76
|
+
const context = await commandContext(serverId, dependencies.secrets);
|
|
77
|
+
return (dependencies.authorize ?? authorizeMcpServer)(serverId, context.config, context.secrets);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function runMcpLogout(
|
|
81
|
+
serverId: string,
|
|
82
|
+
dependencies: McpCommandDependencies = {},
|
|
83
|
+
): Promise<{ serverId: string; loggedOut: true }> {
|
|
84
|
+
const context = await commandContext(serverId, dependencies.secrets);
|
|
85
|
+
return (dependencies.logout ?? logoutMcpServer)(serverId, context.config, context.secrets);
|
|
86
|
+
}
|
package/src/cli/commands/run.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { coerceInput } from "../parseArgs";
|
|
2
|
-
import {
|
|
2
|
+
import { withRuntime } from "../context";
|
|
3
3
|
import type { OmtResult } from "../../core/result";
|
|
4
4
|
|
|
5
5
|
export async function runTool(
|
|
@@ -13,12 +13,13 @@ export async function runTool(
|
|
|
13
13
|
} else {
|
|
14
14
|
input = coerceInput(keyValues);
|
|
15
15
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
return withRuntime(async (runtime) => {
|
|
17
|
+
const result = await runtime.run(toolName, input);
|
|
18
|
+
if (result.ok) {
|
|
19
|
+
return { ok: true, tool: toolName, data: result.output, meta: result.meta ?? {} };
|
|
20
|
+
}
|
|
21
|
+
return { ok: false, tool: toolName, error: result.error ?? { code: "EXECUTION_FAILED", message: "execution failed" } };
|
|
22
|
+
});
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
function readStdinJson(): Promise<Record<string, unknown>> {
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { withRuntime } from "../context";
|
|
2
2
|
|
|
3
3
|
export async function runSearch(query: string): Promise<{ tools: Array<Record<string, unknown>> }> {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
4
|
+
return withRuntime(async (runtime) => {
|
|
5
|
+
const descriptors = await runtime.search(query);
|
|
6
|
+
return {
|
|
7
|
+
tools: descriptors.map((descriptor) => ({
|
|
8
|
+
name: descriptor.id,
|
|
9
|
+
description: descriptor.description,
|
|
10
|
+
extension: descriptor.source.id,
|
|
11
|
+
risk: descriptor.risk,
|
|
12
|
+
provider: descriptor.provider,
|
|
13
|
+
})),
|
|
14
|
+
};
|
|
15
|
+
});
|
|
15
16
|
}
|
|
16
|
-
|
|
17
|
+
|
|
@@ -1,72 +1,72 @@
|
|
|
1
|
-
import { SecretsManager } from "../../secrets/secrets";
|
|
2
|
-
import type { SecretStore } from "@oh-my-tool/sdk";
|
|
3
|
-
|
|
4
|
-
export interface SecretSetResult {
|
|
5
|
-
ok: true;
|
|
6
|
-
name: string;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export async function runSecretSet(
|
|
10
|
-
name: string,
|
|
11
|
-
value: string,
|
|
12
|
-
store?: SecretStore,
|
|
13
|
-
): Promise<SecretSetResult> {
|
|
14
|
-
const mgr = new SecretsManager(store);
|
|
15
|
-
await mgr.set(name, value);
|
|
16
|
-
return { ok: true, name };
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface SecretListResult {
|
|
20
|
-
names: string[];
|
|
21
|
-
supported: boolean;
|
|
22
|
-
hint?: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** 从 cmdkey /list 输出解析 oh-my-tool 命名空间下的 secret 名(不返回值)。 */
|
|
26
|
-
export function parseSecretNamesFromCmdkey(output: string): string[] {
|
|
27
|
-
const names = new Set<string>();
|
|
28
|
-
const re = /oh-my-tool\/([A-Za-z0-9:._@-]+)/g;
|
|
29
|
-
let m: RegExpExecArray | null;
|
|
30
|
-
while ((m = re.exec(output)) !== null) {
|
|
31
|
-
names.add(m[1]);
|
|
32
|
-
}
|
|
33
|
-
return [...names].sort();
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
type CmdkeyExec = (args: string[]) => Promise<string>;
|
|
37
|
-
|
|
1
|
+
import { SecretsManager } from "../../secrets/secrets";
|
|
2
|
+
import type { SecretStore } from "@oh-my-tool/sdk";
|
|
3
|
+
|
|
4
|
+
export interface SecretSetResult {
|
|
5
|
+
ok: true;
|
|
6
|
+
name: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function runSecretSet(
|
|
10
|
+
name: string,
|
|
11
|
+
value: string,
|
|
12
|
+
store?: SecretStore,
|
|
13
|
+
): Promise<SecretSetResult> {
|
|
14
|
+
const mgr = new SecretsManager(store);
|
|
15
|
+
await mgr.set(name, value);
|
|
16
|
+
return { ok: true, name };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface SecretListResult {
|
|
20
|
+
names: string[];
|
|
21
|
+
supported: boolean;
|
|
22
|
+
hint?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** 从 cmdkey /list 输出解析 oh-my-tool 命名空间下的 secret 名(不返回值)。 */
|
|
26
|
+
export function parseSecretNamesFromCmdkey(output: string): string[] {
|
|
27
|
+
const names = new Set<string>();
|
|
28
|
+
const re = /oh-my-tool\/([A-Za-z0-9:._@-]+)/g;
|
|
29
|
+
let m: RegExpExecArray | null;
|
|
30
|
+
while ((m = re.exec(output)) !== null) {
|
|
31
|
+
names.add(m[1]);
|
|
32
|
+
}
|
|
33
|
+
return [...names].sort();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type CmdkeyExec = (args: string[]) => Promise<string>;
|
|
37
|
+
|
|
38
38
|
async function defaultCmdkeyList(): Promise<string> {
|
|
39
39
|
const proc = Bun.spawn(["cmdkey", "/list"], { stdout: "pipe", stderr: "pipe" });
|
|
40
40
|
const output = await Bun.readableStreamToArrayBuffer(proc.stdout);
|
|
41
41
|
const out = new TextDecoder().decode(output);
|
|
42
|
-
const code = await proc.exited;
|
|
43
|
-
if (code !== 0) throw new Error(`cmdkey /list exited ${code}`);
|
|
44
|
-
return out;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* 列出已设置的 secret 名(只列名、不显值)。
|
|
49
|
-
* Bun.secrets 无枚举 API,Windows 下通过 cmdkey 解析 oh-my-tool/ 前缀;
|
|
50
|
-
* 非 Windows 不支持枚举,返回 supported=false。
|
|
51
|
-
*/
|
|
52
|
-
export async function runSecretList(
|
|
53
|
-
exec: CmdkeyExec = defaultCmdkeyList,
|
|
54
|
-
): Promise<SecretListResult> {
|
|
55
|
-
if (process.platform !== "win32") {
|
|
56
|
-
return {
|
|
57
|
-
names: [],
|
|
58
|
-
supported: false,
|
|
59
|
-
hint: "Bun.secrets 无枚举 API;secret list 仅在 Windows(通过 cmdkey)支持",
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
try {
|
|
63
|
-
const out = await exec(["cmdkey", "/list"]);
|
|
64
|
-
return { names: parseSecretNamesFromCmdkey(out), supported: true };
|
|
65
|
-
} catch (e) {
|
|
66
|
-
return {
|
|
67
|
-
names: [],
|
|
68
|
-
supported: false,
|
|
69
|
-
hint: `cmdkey 调用失败: ${e instanceof Error ? e.message : String(e)}`,
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
}
|
|
42
|
+
const code = await proc.exited;
|
|
43
|
+
if (code !== 0) throw new Error(`cmdkey /list exited ${code}`);
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 列出已设置的 secret 名(只列名、不显值)。
|
|
49
|
+
* Bun.secrets 无枚举 API,Windows 下通过 cmdkey 解析 oh-my-tool/ 前缀;
|
|
50
|
+
* 非 Windows 不支持枚举,返回 supported=false。
|
|
51
|
+
*/
|
|
52
|
+
export async function runSecretList(
|
|
53
|
+
exec: CmdkeyExec = defaultCmdkeyList,
|
|
54
|
+
): Promise<SecretListResult> {
|
|
55
|
+
if (process.platform !== "win32") {
|
|
56
|
+
return {
|
|
57
|
+
names: [],
|
|
58
|
+
supported: false,
|
|
59
|
+
hint: "Bun.secrets 无枚举 API;secret list 仅在 Windows(通过 cmdkey)支持",
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const out = await exec(["cmdkey", "/list"]);
|
|
64
|
+
return { names: parseSecretNamesFromCmdkey(out), supported: true };
|
|
65
|
+
} catch (e) {
|
|
66
|
+
return {
|
|
67
|
+
names: [],
|
|
68
|
+
supported: false,
|
|
69
|
+
hint: `cmdkey 调用失败: ${e instanceof Error ? e.message : String(e)}`,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|