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