@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/src/cli/context.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { createPaths } from "../paths";
|
|
2
2
|
import { prepareHome } from "../migration";
|
|
3
|
-
import { loadConfig, getConnectionConfig } from "../config/config";
|
|
3
|
+
import { loadConfig, getConnectionConfig, type McpEnabledServerConfig } from "../config/config";
|
|
4
4
|
import { SecretsManager } from "../secrets/secrets";
|
|
5
5
|
import { applyLimits, validateConnectionInput } from "../policy/policy";
|
|
6
6
|
import { NativeExtensionProvider } from "../runtime/providers/native/provider";
|
|
7
|
+
import { McpProvider } from "../runtime/providers/mcp/provider";
|
|
7
8
|
import { createToolRuntime } from "../runtime/runtime";
|
|
8
9
|
import type { ToolDescriptor } from "../runtime/provider";
|
|
9
10
|
|
|
@@ -16,10 +17,15 @@ export async function createRuntime() {
|
|
|
16
17
|
await prepareHome(paths);
|
|
17
18
|
const config = loadConfig(paths.home);
|
|
18
19
|
const secrets = new SecretsManager();
|
|
20
|
+
const providers = [new NativeExtensionProvider(paths), ...Object.entries(config.mcp.servers)
|
|
21
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
22
|
+
.filter((entry): entry is [string, McpEnabledServerConfig] => entry[1].enabled)
|
|
23
|
+
.map(([serverId, server]) => new McpProvider({ serverId, config: server, secrets }))];
|
|
19
24
|
return createToolRuntime({
|
|
20
|
-
providers
|
|
25
|
+
providers,
|
|
21
26
|
policy: {
|
|
22
27
|
preflight(descriptor, input) {
|
|
28
|
+
if (descriptor.provider.kind !== "native") return;
|
|
23
29
|
const limits = applyLimits(input);
|
|
24
30
|
input.maxRows = limits.maxRows;
|
|
25
31
|
input.timeoutMs = limits.timeoutMs;
|
|
@@ -42,3 +48,20 @@ export async function createRuntime() {
|
|
|
42
48
|
},
|
|
43
49
|
});
|
|
44
50
|
}
|
|
51
|
+
|
|
52
|
+
export async function withRuntime<T>(operation: (runtime: Awaited<ReturnType<typeof createRuntime>>) => Promise<T>): Promise<T> {
|
|
53
|
+
const runtime = await createRuntime();
|
|
54
|
+
let operationFailed = false;
|
|
55
|
+
try {
|
|
56
|
+
return await operation(runtime);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
operationFailed = true;
|
|
59
|
+
throw error;
|
|
60
|
+
} finally {
|
|
61
|
+
try {
|
|
62
|
+
await runtime.close();
|
|
63
|
+
} catch (closeError) {
|
|
64
|
+
if (!operationFailed) throw closeError;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
package/src/cli/index.ts
CHANGED
|
@@ -1,22 +1,24 @@
|
|
|
1
|
-
import { parseArgs } from "./parseArgs";
|
|
2
|
-
import { runSearch } from "./commands/search";
|
|
3
|
-
import { runDescribe } from "./commands/describe";
|
|
1
|
+
import { parseArgs, parseMcpCommand } from "./parseArgs";
|
|
2
|
+
import { runSearch } from "./commands/search";
|
|
3
|
+
import { runDescribe } from "./commands/describe";
|
|
4
4
|
import { runTool } from "./commands/run";
|
|
5
|
-
import { runExtensionList, runExtensionInstall } from "./commands/extension";
|
|
6
|
-
import { runSecretList, runSecretSet } from "./commands/secret";
|
|
7
|
-
import {
|
|
8
|
-
defaultIntegrationManager,
|
|
9
|
-
runIntegrate,
|
|
10
|
-
type IntegrateAction,
|
|
11
|
-
} from "./commands/integrate";
|
|
12
|
-
import type { AgentDetection, AgentId, IntegrationResult, IntegrationStatus } from "../integration";
|
|
13
|
-
import { AGENT_IDS } from "../integration";
|
|
14
|
-
import { multiselect, isCancel } from "@clack/prompts";
|
|
15
|
-
import { VERSION } from "../version";
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
5
|
+
import { runExtensionList, runExtensionInstall } from "./commands/extension";
|
|
6
|
+
import { runSecretList, runSecretSet } from "./commands/secret";
|
|
7
|
+
import {
|
|
8
|
+
defaultIntegrationManager,
|
|
9
|
+
runIntegrate,
|
|
10
|
+
type IntegrateAction,
|
|
11
|
+
} from "./commands/integrate";
|
|
12
|
+
import type { AgentDetection, AgentId, IntegrationResult, IntegrationStatus } from "../integration";
|
|
13
|
+
import { AGENT_IDS } from "../integration";
|
|
14
|
+
import { multiselect, isCancel } from "@clack/prompts";
|
|
15
|
+
import { VERSION } from "../version";
|
|
16
|
+
import { runMcpAuth, runMcpList, runMcpLogout } from "./commands/mcp";
|
|
17
|
+
import { RuntimeError } from "../runtime/errors";
|
|
18
|
+
|
|
19
|
+
const HELP = `Oh My Tool - local and enterprise tools for agents
|
|
20
|
+
|
|
21
|
+
Usage:
|
|
20
22
|
ohmytool search "<task>" search tools by intent
|
|
21
23
|
ohmytool describe <tool> inspect a tool and its input schema
|
|
22
24
|
ohmytool run <tool> [key=value ...] execute a tool
|
|
@@ -25,272 +27,294 @@ Usage:
|
|
|
25
27
|
ohmytool extension install <path> install an extension from a local dir
|
|
26
28
|
ohmytool secret set <name> set a secret (interactive hidden prompt or stdin pipe)
|
|
27
29
|
ohmytool secret list list secret names (Windows only, values never shown)
|
|
30
|
+
ohmytool mcp list list configured MCP servers
|
|
31
|
+
ohmytool mcp auth <server> authorize an OAuth MCP server
|
|
32
|
+
ohmytool mcp logout <server> remove locally stored OAuth credentials
|
|
28
33
|
ohmytool setup detect agents and install the OMT skill
|
|
29
34
|
ohmytool integrate [status|repair|uninstall]
|
|
30
35
|
manage agent skill integrations
|
|
31
36
|
ohmytool --version print version
|
|
32
|
-
|
|
33
|
-
Examples:
|
|
34
|
-
ohmytool search "
|
|
37
|
+
|
|
38
|
+
Examples:
|
|
39
|
+
ohmytool search "query mysql devices data"
|
|
35
40
|
ohmytool describe mysql.query
|
|
36
41
|
ohmytool run mysql.query connection=iot-test sql="SELECT id FROM device"
|
|
37
42
|
echo '{"connection":"iot-test","sql":"SELECT 1"}' | ohmytool run mysql.query --stdin
|
|
38
|
-
`;
|
|
39
|
-
|
|
40
|
-
const AGENT_IDS_SET = new Set<AgentId>(AGENT_IDS);
|
|
41
|
-
|
|
42
|
-
function readStdin(): Promise<string> {
|
|
43
|
-
return new Promise((resolve, reject) => {
|
|
44
|
-
let data = "";
|
|
45
|
-
process.stdin.setEncoding("utf8");
|
|
46
|
-
process.stdin.on("data", (chunk: string) => (data += chunk));
|
|
47
|
-
process.stdin.on("end", () => resolve(data.trim()));
|
|
48
|
-
process.stdin.on("error", reject);
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** 交互式隐藏输入(不回显、不进历史、不落盘),仅 TTY 下调用。 */
|
|
53
|
-
function readSecretHidden(prompt: string): Promise<string> {
|
|
54
|
-
const { promise, resolve, reject } = Promise.withResolvers<string>();
|
|
55
|
-
process.stdout.write(prompt);
|
|
56
|
-
const stdin = process.stdin;
|
|
57
|
-
const prevRaw = stdin.isRaw;
|
|
58
|
-
stdin.setRawMode(true);
|
|
59
|
-
stdin.resume();
|
|
60
|
-
stdin.setEncoding("utf8");
|
|
61
|
-
let value = "";
|
|
62
|
-
const finish = () => {
|
|
63
|
-
stdin.removeListener("data", onData);
|
|
64
|
-
stdin.setRawMode(prevRaw);
|
|
65
|
-
stdin.pause();
|
|
66
|
-
};
|
|
67
|
-
const onData = (chunk: string) => {
|
|
68
|
-
for (const ch of chunk) {
|
|
69
|
-
if (ch === "\r" || ch === "\n") {
|
|
70
|
-
finish();
|
|
71
|
-
process.stdout.write("\n");
|
|
72
|
-
resolve(value);
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
if (ch === "\x03") {
|
|
76
|
-
finish();
|
|
77
|
-
process.stdout.write("\n");
|
|
78
|
-
reject(new Error("aborted"));
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
if (ch === "\x7f" || ch === "\b") {
|
|
82
|
-
value = value.slice(0, -1);
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
value += ch;
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
stdin.on("data", onData);
|
|
89
|
-
return promise;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function print(v: unknown): void {
|
|
93
|
-
console.log(JSON.stringify(v, null, 2));
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function parseAgentIds(raw?: string): AgentId[] | undefined {
|
|
97
|
-
if (!raw) return undefined;
|
|
98
|
-
const values = raw.split(",").map((value) => value.trim()).filter(Boolean);
|
|
99
|
-
for (const value of values) {
|
|
100
|
-
if (!AGENT_IDS_SET.has(value as AgentId)) throw new Error(`Unknown agent: ${value}`);
|
|
101
|
-
}
|
|
102
|
-
return [...new Set(values as AgentId[])];
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function printDetected(agents: AgentDetection[]): void {
|
|
106
|
-
console.log("Detected agents:\n");
|
|
107
|
-
for (const agent of agents) console.log(`✓ ${agent.variant ?? agent.displayName}`);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const STATUS_ICON: Record<IntegrationStatus, string> = {
|
|
111
|
-
current: "✓",
|
|
112
|
-
installed: "✓",
|
|
113
|
-
repaired: "✓",
|
|
114
|
-
uninstalled: "✓",
|
|
115
|
-
"update-available": "↻",
|
|
116
|
-
"not-installed": "○",
|
|
117
|
-
broken: "⚠",
|
|
118
|
-
conflict: "✗",
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
const STATUS_SEVERITY: Record<IntegrationStatus, number> = {
|
|
122
|
-
conflict: 0,
|
|
123
|
-
broken: 1,
|
|
124
|
-
"update-available": 2,
|
|
125
|
-
"not-installed": 3,
|
|
126
|
-
current: 4,
|
|
127
|
-
installed: 4,
|
|
128
|
-
repaired: 4,
|
|
129
|
-
uninstalled: 4,
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
function printIntegrationResults(results: IntegrationResult[]): void {
|
|
133
|
-
console.log("");
|
|
134
|
-
for (const item of results) {
|
|
135
|
-
const suffix = item.detail ? ` — ${item.detail}` : "";
|
|
136
|
-
console.log(`${STATUS_ICON[item.status]} ${item.displayName.padEnd(14)} ${item.status}${suffix}`);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function printStatus(results: IntegrationResult[]): void {
|
|
141
|
-
console.log("");
|
|
142
|
-
const ordered = [...results].sort((a, b) => STATUS_SEVERITY[a.status] - STATUS_SEVERITY[b.status]);
|
|
143
|
-
for (const item of ordered) {
|
|
144
|
-
console.log(
|
|
145
|
-
`${STATUS_ICON[item.status]} ${item.displayName.padEnd(14)} ${item.status.padEnd(19)} ${item.detail ?? item.target}`,
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
const counts: Record<string, number> = {};
|
|
149
|
-
for (const item of results) counts[item.status] = (counts[item.status] ?? 0) + 1;
|
|
150
|
-
const summary = Object.entries(counts).map(([status, n]) => `${n} ${status}`).join(" · ");
|
|
151
|
-
console.log(`\nSummary: ${summary}`);
|
|
152
|
-
if (results.some((item) => item.status === "broken")) {
|
|
153
|
-
console.log("Tip: run `omt integrate repair` to recreate broken links");
|
|
154
|
-
}
|
|
155
|
-
if (results.some((item) => item.status === "conflict")) {
|
|
156
|
-
console.log("Tip: conflict means OMT refuses to touch an unmanaged path; run `omt integrate --force` only if you accept replacing it");
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
async function promptForAgents(agents: AgentDetection[]): Promise<AgentId[]> {
|
|
161
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
162
|
-
throw new Error("Interactive confirmation requires a TTY; pass --yes for unattended setup");
|
|
163
|
-
}
|
|
164
|
-
const selected = await multiselect({
|
|
165
|
-
message: "选择要集成的 Agent(空格切换,回车确认)",
|
|
166
|
-
options: agents.map((agent) => ({
|
|
167
|
-
value: agent.id,
|
|
168
|
-
label: agent.variant ?? agent.displayName,
|
|
169
|
-
hint: agent.target,
|
|
170
|
-
})),
|
|
171
|
-
initialValues: agents.map((agent) => agent.id),
|
|
172
|
-
required: false,
|
|
173
|
-
});
|
|
174
|
-
if (isCancel(selected) || !selected?.length) return [];
|
|
175
|
-
return selected as AgentId[];
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
export
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
43
|
+
`;
|
|
44
|
+
|
|
45
|
+
const AGENT_IDS_SET = new Set<AgentId>(AGENT_IDS);
|
|
46
|
+
|
|
47
|
+
function readStdin(): Promise<string> {
|
|
48
|
+
return new Promise((resolve, reject) => {
|
|
49
|
+
let data = "";
|
|
50
|
+
process.stdin.setEncoding("utf8");
|
|
51
|
+
process.stdin.on("data", (chunk: string) => (data += chunk));
|
|
52
|
+
process.stdin.on("end", () => resolve(data.trim()));
|
|
53
|
+
process.stdin.on("error", reject);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** 交互式隐藏输入(不回显、不进历史、不落盘),仅 TTY 下调用。 */
|
|
58
|
+
function readSecretHidden(prompt: string): Promise<string> {
|
|
59
|
+
const { promise, resolve, reject } = Promise.withResolvers<string>();
|
|
60
|
+
process.stdout.write(prompt);
|
|
61
|
+
const stdin = process.stdin;
|
|
62
|
+
const prevRaw = stdin.isRaw;
|
|
63
|
+
stdin.setRawMode(true);
|
|
64
|
+
stdin.resume();
|
|
65
|
+
stdin.setEncoding("utf8");
|
|
66
|
+
let value = "";
|
|
67
|
+
const finish = () => {
|
|
68
|
+
stdin.removeListener("data", onData);
|
|
69
|
+
stdin.setRawMode(prevRaw);
|
|
70
|
+
stdin.pause();
|
|
71
|
+
};
|
|
72
|
+
const onData = (chunk: string) => {
|
|
73
|
+
for (const ch of chunk) {
|
|
74
|
+
if (ch === "\r" || ch === "\n") {
|
|
75
|
+
finish();
|
|
76
|
+
process.stdout.write("\n");
|
|
77
|
+
resolve(value);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (ch === "\x03") {
|
|
81
|
+
finish();
|
|
82
|
+
process.stdout.write("\n");
|
|
83
|
+
reject(new Error("aborted"));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (ch === "\x7f" || ch === "\b") {
|
|
87
|
+
value = value.slice(0, -1);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
value += ch;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
stdin.on("data", onData);
|
|
94
|
+
return promise;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function print(v: unknown): void {
|
|
98
|
+
console.log(JSON.stringify(v, null, 2));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function parseAgentIds(raw?: string): AgentId[] | undefined {
|
|
102
|
+
if (!raw) return undefined;
|
|
103
|
+
const values = raw.split(",").map((value) => value.trim()).filter(Boolean);
|
|
104
|
+
for (const value of values) {
|
|
105
|
+
if (!AGENT_IDS_SET.has(value as AgentId)) throw new Error(`Unknown agent: ${value}`);
|
|
106
|
+
}
|
|
107
|
+
return [...new Set(values as AgentId[])];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function printDetected(agents: AgentDetection[]): void {
|
|
111
|
+
console.log("Detected agents:\n");
|
|
112
|
+
for (const agent of agents) console.log(`✓ ${agent.variant ?? agent.displayName}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const STATUS_ICON: Record<IntegrationStatus, string> = {
|
|
116
|
+
current: "✓",
|
|
117
|
+
installed: "✓",
|
|
118
|
+
repaired: "✓",
|
|
119
|
+
uninstalled: "✓",
|
|
120
|
+
"update-available": "↻",
|
|
121
|
+
"not-installed": "○",
|
|
122
|
+
broken: "⚠",
|
|
123
|
+
conflict: "✗",
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const STATUS_SEVERITY: Record<IntegrationStatus, number> = {
|
|
127
|
+
conflict: 0,
|
|
128
|
+
broken: 1,
|
|
129
|
+
"update-available": 2,
|
|
130
|
+
"not-installed": 3,
|
|
131
|
+
current: 4,
|
|
132
|
+
installed: 4,
|
|
133
|
+
repaired: 4,
|
|
134
|
+
uninstalled: 4,
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
function printIntegrationResults(results: IntegrationResult[]): void {
|
|
138
|
+
console.log("");
|
|
139
|
+
for (const item of results) {
|
|
140
|
+
const suffix = item.detail ? ` — ${item.detail}` : "";
|
|
141
|
+
console.log(`${STATUS_ICON[item.status]} ${item.displayName.padEnd(14)} ${item.status}${suffix}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function printStatus(results: IntegrationResult[]): void {
|
|
146
|
+
console.log("");
|
|
147
|
+
const ordered = [...results].sort((a, b) => STATUS_SEVERITY[a.status] - STATUS_SEVERITY[b.status]);
|
|
148
|
+
for (const item of ordered) {
|
|
149
|
+
console.log(
|
|
150
|
+
`${STATUS_ICON[item.status]} ${item.displayName.padEnd(14)} ${item.status.padEnd(19)} ${item.detail ?? item.target}`,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
const counts: Record<string, number> = {};
|
|
154
|
+
for (const item of results) counts[item.status] = (counts[item.status] ?? 0) + 1;
|
|
155
|
+
const summary = Object.entries(counts).map(([status, n]) => `${n} ${status}`).join(" · ");
|
|
156
|
+
console.log(`\nSummary: ${summary}`);
|
|
157
|
+
if (results.some((item) => item.status === "broken")) {
|
|
158
|
+
console.log("Tip: run `omt integrate repair` to recreate broken links");
|
|
159
|
+
}
|
|
160
|
+
if (results.some((item) => item.status === "conflict")) {
|
|
161
|
+
console.log("Tip: conflict means OMT refuses to touch an unmanaged path; run `omt integrate --force` only if you accept replacing it");
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async function promptForAgents(agents: AgentDetection[]): Promise<AgentId[]> {
|
|
166
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
167
|
+
throw new Error("Interactive confirmation requires a TTY; pass --yes for unattended setup");
|
|
168
|
+
}
|
|
169
|
+
const selected = await multiselect({
|
|
170
|
+
message: "选择要集成的 Agent(空格切换,回车确认)",
|
|
171
|
+
options: agents.map((agent) => ({
|
|
172
|
+
value: agent.id,
|
|
173
|
+
label: agent.variant ?? agent.displayName,
|
|
174
|
+
hint: agent.target,
|
|
175
|
+
})),
|
|
176
|
+
initialValues: agents.map((agent) => agent.id),
|
|
177
|
+
required: false,
|
|
178
|
+
});
|
|
179
|
+
if (isCancel(selected) || !selected?.length) return [];
|
|
180
|
+
return selected as AgentId[];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export interface CliDependencies {
|
|
184
|
+
readonly runMcpAuth: typeof runMcpAuth;
|
|
185
|
+
readonly runMcpLogout: typeof runMcpLogout;
|
|
186
|
+
readonly runMcpList?: typeof runMcpList;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const defaultCliDependencies: CliDependencies = { runMcpAuth, runMcpLogout, runMcpList };
|
|
190
|
+
|
|
191
|
+
export async function main(argv: string[], dependencies: CliDependencies = defaultCliDependencies): Promise<number> {
|
|
192
|
+
const parsed = parseArgs(argv);
|
|
193
|
+
const cmd = parsed.positional[0];
|
|
194
|
+
try {
|
|
195
|
+
switch (cmd) {
|
|
196
|
+
case "search": {
|
|
197
|
+
const q = parsed.positional.slice(1).join(" ");
|
|
198
|
+
print(await runSearch(q));
|
|
199
|
+
return 0;
|
|
200
|
+
}
|
|
201
|
+
case "describe": {
|
|
202
|
+
print(await runDescribe(parsed.positional[1]));
|
|
203
|
+
return 0;
|
|
204
|
+
}
|
|
192
205
|
case "run": {
|
|
193
206
|
const tool = parsed.positional[1];
|
|
194
207
|
const res = await runTool(tool, parsed.keyValues, parsed.flags.includes("stdin"));
|
|
195
|
-
print(res);
|
|
196
|
-
return res.ok ? 0 : 1;
|
|
197
|
-
}
|
|
198
|
-
case "secret": {
|
|
199
|
-
const sub = parsed.positional[1];
|
|
200
|
-
if (sub === "set") {
|
|
201
|
-
const name = parsed.positional[2];
|
|
202
|
-
if (!name) {
|
|
208
|
+
print(res);
|
|
209
|
+
return res.ok ? 0 : 1;
|
|
210
|
+
}
|
|
211
|
+
case "secret": {
|
|
212
|
+
const sub = parsed.positional[1];
|
|
213
|
+
if (sub === "set") {
|
|
214
|
+
const name = parsed.positional[2];
|
|
215
|
+
if (!name) {
|
|
203
216
|
console.error("usage: ohmytool secret set <name> (交互输入或 stdin 管道)");
|
|
204
|
-
return 1;
|
|
205
|
-
}
|
|
206
|
-
// TTY 下交互隐藏输入(不回显/不进历史),非 TTY 保持管道 stdin
|
|
207
|
-
const value = process.stdin.isTTY ? await readSecretHidden("password: ") : await readStdin();
|
|
208
|
-
print(await runSecretSet(name, value));
|
|
209
|
-
return 0;
|
|
210
|
-
}
|
|
211
|
-
if (sub === "list") {
|
|
212
|
-
print(await runSecretList());
|
|
213
|
-
return 0;
|
|
214
|
-
}
|
|
217
|
+
return 1;
|
|
218
|
+
}
|
|
219
|
+
// TTY 下交互隐藏输入(不回显/不进历史),非 TTY 保持管道 stdin
|
|
220
|
+
const value = process.stdin.isTTY ? await readSecretHidden("password: ") : await readStdin();
|
|
221
|
+
print(await runSecretSet(name, value));
|
|
222
|
+
return 0;
|
|
223
|
+
}
|
|
224
|
+
if (sub === "list") {
|
|
225
|
+
print(await runSecretList());
|
|
226
|
+
return 0;
|
|
227
|
+
}
|
|
215
228
|
console.error("usage: ohmytool secret set <name> | secret list");
|
|
216
|
-
return 1;
|
|
217
|
-
}
|
|
218
|
-
case "extension": {
|
|
219
|
-
const sub = parsed.positional[1];
|
|
220
|
-
if (sub === "list") {
|
|
221
|
-
print(await runExtensionList());
|
|
222
|
-
return 0;
|
|
223
|
-
}
|
|
224
|
-
if (sub === "install") {
|
|
225
|
-
print(await runExtensionInstall(parsed.positional[2]));
|
|
226
|
-
return 0;
|
|
227
|
-
}
|
|
229
|
+
return 1;
|
|
230
|
+
}
|
|
231
|
+
case "extension": {
|
|
232
|
+
const sub = parsed.positional[1];
|
|
233
|
+
if (sub === "list") {
|
|
234
|
+
print(await runExtensionList());
|
|
235
|
+
return 0;
|
|
236
|
+
}
|
|
237
|
+
if (sub === "install") {
|
|
238
|
+
print(await runExtensionInstall(parsed.positional[2]));
|
|
239
|
+
return 0;
|
|
240
|
+
}
|
|
228
241
|
console.error("usage: ohmytool extension list|install <path>");
|
|
229
|
-
return 1;
|
|
230
|
-
}
|
|
231
|
-
case "
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
{
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
242
|
+
return 1;
|
|
243
|
+
}
|
|
244
|
+
case "mcp": {
|
|
245
|
+
const mcp = parseMcpCommand(parsed);
|
|
246
|
+
if (mcp === undefined) {
|
|
247
|
+
console.error("usage: ohmytool mcp list | mcp auth <server> | mcp logout <server>");
|
|
248
|
+
return 1;
|
|
249
|
+
}
|
|
250
|
+
if (mcp.action === "list") print(await (dependencies.runMcpList ?? runMcpList)());
|
|
251
|
+
else if (mcp.action === "auth") print(await dependencies.runMcpAuth(mcp.serverId));
|
|
252
|
+
else print(await dependencies.runMcpLogout(mcp.serverId));
|
|
253
|
+
return 0;
|
|
254
|
+
}
|
|
255
|
+
case "setup":
|
|
256
|
+
case "integrate": {
|
|
257
|
+
const action = (cmd === "setup" ? "install" : parsed.positional[1] ?? "install") as IntegrateAction;
|
|
258
|
+
if (!["install", "status", "repair", "uninstall"].includes(action)) {
|
|
259
|
+
throw new Error(`Unknown integrate action: ${action}`);
|
|
260
|
+
}
|
|
261
|
+
const manager = defaultIntegrationManager();
|
|
262
|
+
const detected = await manager.detect();
|
|
263
|
+
if (!detected.length) throw new Error("No supported agents detected");
|
|
264
|
+
printDetected(detected);
|
|
265
|
+
let agents = parseAgentIds(parsed.options.agents);
|
|
266
|
+
const mutating = action !== "status" && !parsed.flags.includes("dry-run");
|
|
267
|
+
if (mutating && !parsed.flags.includes("yes") && !agents) {
|
|
268
|
+
agents = await promptForAgents(detected);
|
|
269
|
+
if (!agents.length) {
|
|
270
|
+
console.log("\nCancelled.");
|
|
271
|
+
return 0;
|
|
272
|
+
}
|
|
273
|
+
} else if (mutating && !parsed.flags.includes("yes")) {
|
|
274
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
275
|
+
throw new Error("Confirmation required; pass --yes for unattended setup");
|
|
276
|
+
}
|
|
277
|
+
const selectedDetections = detected.filter((agent) => agents!.includes(agent.id));
|
|
278
|
+
agents = await promptForAgents(selectedDetections);
|
|
279
|
+
if (!agents.length) {
|
|
280
|
+
console.log("\nCancelled.");
|
|
281
|
+
return 0;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
const integration = await runIntegrate(
|
|
285
|
+
{
|
|
286
|
+
action,
|
|
287
|
+
agents,
|
|
288
|
+
force: parsed.flags.includes("force"),
|
|
289
|
+
dryRun: parsed.flags.includes("dry-run"),
|
|
290
|
+
},
|
|
291
|
+
manager,
|
|
292
|
+
);
|
|
293
|
+
if (integration.dryRun) {
|
|
294
|
+
console.log(`\nDry run: would ${action} ${integration.selected.join(", ")}`);
|
|
295
|
+
} else if (action === "status") {
|
|
296
|
+
printStatus(integration.results);
|
|
297
|
+
} else {
|
|
298
|
+
printIntegrationResults(integration.results);
|
|
299
|
+
}
|
|
300
|
+
return 0;
|
|
301
|
+
}
|
|
302
|
+
case "-v": {
|
|
279
303
|
print({ name: "ohmytool", version: VERSION });
|
|
280
|
-
return 0;
|
|
281
|
-
}
|
|
282
|
-
default:
|
|
283
|
-
if (parsed.flags.includes("version")) {
|
|
304
|
+
return 0;
|
|
305
|
+
}
|
|
306
|
+
default:
|
|
307
|
+
if (parsed.flags.includes("version")) {
|
|
284
308
|
print({ name: "ohmytool", version: VERSION });
|
|
285
|
-
return 0;
|
|
286
|
-
}
|
|
287
|
-
console.log(HELP);
|
|
288
|
-
return cmd ? 1 : 0;
|
|
289
|
-
}
|
|
290
|
-
} catch (e) {
|
|
291
|
-
console.error(e instanceof Error ? e.message : String(e));
|
|
292
|
-
return 1;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
|
|
309
|
+
return 0;
|
|
310
|
+
}
|
|
311
|
+
console.log(HELP);
|
|
312
|
+
return cmd ? 1 : 0;
|
|
313
|
+
}
|
|
314
|
+
} catch (e) {
|
|
315
|
+
console.error(e instanceof RuntimeError ? `${e.code}: ${e.message}` : e instanceof Error ? e.message : String(e));
|
|
316
|
+
return 1;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
|