@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.
Files changed (49) hide show
  1. package/README.md +7 -2
  2. package/assets/skills/oh-my-tool/SKILL.md +11 -3
  3. package/bin/ohmytool.cjs +0 -0
  4. package/package.json +20 -11
  5. package/src/cli/commands/connections.ts +98 -0
  6. package/src/cli/commands/describe.ts +23 -22
  7. package/src/cli/commands/extension.ts +24 -24
  8. package/src/cli/commands/index.ts +8 -6
  9. package/src/cli/commands/integrate.ts +64 -64
  10. package/src/cli/commands/mcp.ts +86 -0
  11. package/src/cli/commands/run.ts +14 -8
  12. package/src/cli/commands/search.ts +14 -13
  13. package/src/cli/commands/secret.ts +68 -68
  14. package/src/cli/context.ts +37 -4
  15. package/src/cli/index.ts +338 -273
  16. package/src/cli/output.ts +165 -0
  17. package/src/cli/parseArgs.ts +64 -44
  18. package/src/config/config.ts +207 -63
  19. package/src/core/executor.ts +89 -89
  20. package/src/core/registry.ts +31 -31
  21. package/src/core/result.ts +14 -14
  22. package/src/extension/discovery.ts +61 -61
  23. package/src/extension/install.ts +23 -23
  24. package/src/extension/loader.ts +32 -32
  25. package/src/extension/manifest.ts +114 -114
  26. package/src/integration/adapters.ts +98 -98
  27. package/src/integration/index.ts +4 -4
  28. package/src/integration/manager.ts +375 -375
  29. package/src/integration/skill.ts +84 -84
  30. package/src/integration/types.ts +55 -55
  31. package/src/policy/policy.ts +136 -136
  32. package/src/runtime/errors.ts +7 -2
  33. package/src/runtime/executor.ts +6 -1
  34. package/src/runtime/provider.ts +2 -1
  35. package/src/runtime/providers/mcp/normalize.ts +36 -0
  36. package/src/runtime/providers/mcp/oauth-callback.ts +91 -0
  37. package/src/runtime/providers/mcp/oauth-provider.ts +348 -0
  38. package/src/runtime/providers/mcp/oauth-store.ts +106 -0
  39. package/src/runtime/providers/mcp/provider.ts +99 -0
  40. package/src/runtime/providers/mcp/safe-errors.ts +63 -0
  41. package/src/runtime/providers/mcp/session.ts +117 -0
  42. package/src/runtime/providers/mcp/transport.ts +140 -0
  43. package/src/runtime/providers/native/provider.ts +1 -1
  44. package/src/runtime/result.ts +1 -1
  45. package/src/runtime/runtime.ts +38 -12
  46. package/src/runtime/schema.ts +14 -4
  47. package/src/search/search.ts +78 -78
  48. package/src/secrets/secrets.ts +45 -45
  49. package/src/version.ts +1 -1
@@ -1,84 +1,84 @@
1
- import type { Logger, SecretStore, ToolContext, ToolResult } from "@oh-my-tool/sdk";
2
- import { ToolError } from "@oh-my-tool/sdk";
3
- import type { Config } from "../config/config";
4
- import { getConnectionConfig } from "../config/config";
5
- import { resolveTool, OmtError, type Registry } from "./registry";
6
- import { validateInput, type Schema } from "./schema";
7
- import { validateConnectionInput, applyLimits, PolicyError } from "../policy/policy";
8
- import { loadExtension } from "../extension/loader";
9
- import type { OmtResult } from "./result";
10
-
11
- const noopLogger: Logger = {
12
- debug: () => {},
13
- info: () => {},
14
- warn: () => {},
15
- error: () => {},
16
- };
17
-
18
- export interface ExecutorDeps {
19
- registry: Registry;
20
- config: Config;
21
- secrets: SecretStore;
22
- logger?: Logger;
23
- }
24
-
25
- function hasConnection(schema: Schema | undefined): boolean {
26
- return Boolean(schema?.properties && "connection" in schema.properties);
27
- }
28
-
29
- export async function executeTool(
30
- deps: ExecutorDeps,
31
- toolName: string,
32
- rawInput: Record<string, unknown>,
33
- ): Promise<OmtResult> {
34
- const started = Date.now();
35
- try {
36
- const { extension, tool } = resolveTool(deps.registry, toolName);
37
- const schema = tool.inputSchema as Schema | undefined;
38
-
39
- const limits = applyLimits(rawInput);
40
- const normalized = { ...rawInput, maxRows: limits.maxRows, timeoutMs: limits.timeoutMs };
41
-
42
- const needsConnection = hasConnection(schema) || "connection" in normalized;
43
- if (needsConnection) {
44
- validateConnectionInput(normalized, deps.config, extension.id);
45
- }
46
-
47
- const input = validateInput(schema, normalized);
48
-
49
- const connectionCfg = needsConnection
50
- ? getConnectionConfig(deps.config, extension.id, String(input.connection))
51
- : undefined;
52
-
53
- const ctx: ToolContext = {
54
- toolName,
55
- logger: deps.logger ?? noopLogger,
56
- config: (connectionCfg ?? {}) as Record<string, unknown>,
57
- secrets: deps.secrets,
58
- };
59
-
60
- const def = await loadExtension(extension);
61
- const handler = def.handlers[toolName];
62
- if (!handler) {
63
- throw new OmtError("HANDLER_MISSING", `no handler for ${toolName}`);
64
- }
65
-
66
- const result: ToolResult = await handler(ctx, input);
67
- const durationMs = Date.now() - started;
68
- return {
69
- ok: true,
70
- tool: toolName,
71
- data: result.data,
72
- meta: { durationMs, ...(result.meta ?? {}) },
73
- };
74
- } catch (e) {
75
- const durationMs = Date.now() - started;
76
- if (e instanceof PolicyError) {
77
- return { ok: false, tool: toolName, error: { code: "POLICY_VIOLATION", message: e.message } };
78
- }
79
- if (e instanceof ToolError) {
80
- return { ok: false, tool: toolName, error: { code: e.code, message: e.message } };
81
- }
1
+ import type { Logger, SecretStore, ToolContext, ToolResult } from "@oh-my-tool/sdk";
2
+ import { ToolError } from "@oh-my-tool/sdk";
3
+ import type { Config } from "../config/config";
4
+ import { getConnectionConfig, sanitizeExtensionConnections } from "../config/config";
5
+ import { resolveTool, OmtError, type Registry } from "./registry";
6
+ import { validateInput, type Schema } from "./schema";
7
+ import { validateConnectionInput, applyLimits, PolicyError } from "../policy/policy";
8
+ import { loadExtension } from "../extension/loader";
9
+ import type { OmtResult } from "./result";
10
+
11
+ const noopLogger: Logger = {
12
+ debug: () => {},
13
+ info: () => {},
14
+ warn: () => {},
15
+ error: () => {},
16
+ };
17
+
18
+ export interface ExecutorDeps {
19
+ registry: Registry;
20
+ config: Config;
21
+ secrets: SecretStore;
22
+ logger?: Logger;
23
+ }
24
+
25
+ function hasConnection(schema: Schema | undefined): boolean {
26
+ return Boolean(schema?.properties && "connection" in schema.properties);
27
+ }
28
+
29
+ export async function executeTool(
30
+ deps: ExecutorDeps,
31
+ toolName: string,
32
+ rawInput: Record<string, unknown>,
33
+ ): Promise<OmtResult> {
34
+ const started = Date.now();
35
+ try {
36
+ const { extension, tool } = resolveTool(deps.registry, toolName);
37
+ const schema = tool.inputSchema as Schema | undefined;
38
+
39
+ const limits = applyLimits(rawInput);
40
+ const normalized = { ...rawInput, maxRows: limits.maxRows, timeoutMs: limits.timeoutMs };
41
+
42
+ const needsConnection = hasConnection(schema) || "connection" in normalized;
43
+ if (needsConnection) {
44
+ validateConnectionInput(normalized, deps.config, extension.id);
45
+ }
46
+
47
+ const input = validateInput(schema, normalized);
48
+
49
+ const connectionCfg = needsConnection
50
+ ? getConnectionConfig(deps.config, extension.id, String(input.connection))
51
+ : undefined;
52
+
53
+ const ctx: ToolContext = {
54
+ toolName,
55
+ logger: deps.logger ?? noopLogger,
56
+ config: (connectionCfg ?? { connections: sanitizeExtensionConnections(deps.config)[extension.id] ?? {} }) as Record<string, unknown>,
57
+ secrets: deps.secrets,
58
+ };
59
+
60
+ const def = await loadExtension(extension);
61
+ const handler = def.handlers[toolName];
62
+ if (!handler) {
63
+ throw new OmtError("HANDLER_MISSING", `no handler for ${toolName}`);
64
+ }
65
+
66
+ const result: ToolResult = await handler(ctx, input);
67
+ const durationMs = Date.now() - started;
68
+ return {
69
+ ok: true,
70
+ tool: toolName,
71
+ data: result.data,
72
+ meta: { durationMs, ...(result.meta ?? {}) },
73
+ };
74
+ } catch (e) {
75
+ const durationMs = Date.now() - started;
76
+ if (e instanceof PolicyError) {
77
+ return { ok: false, tool: toolName, error: { code: "POLICY_VIOLATION", message: e.message } };
78
+ }
79
+ if (e instanceof ToolError) {
80
+ return { ok: false, tool: toolName, error: { code: e.code, message: e.message } };
81
+ }
82
82
  if (e instanceof OmtError) {
83
83
  return { ok: false, tool: toolName, error: { code: e.code, message: e.message } };
84
84
  }
@@ -89,11 +89,11 @@ export async function executeTool(
89
89
  error: { code: (e as { code: string }).code, message: e instanceof Error ? e.message : String(e) },
90
90
  };
91
91
  }
92
- return {
93
- ok: false,
94
- tool: toolName,
95
- error: { code: "EXECUTION_FAILED", message: e instanceof Error ? e.message : String(e) },
96
- };
97
- }
98
- }
99
-
92
+ return {
93
+ ok: false,
94
+ tool: toolName,
95
+ error: { code: "EXECUTION_FAILED", message: e instanceof Error ? e.message : String(e) },
96
+ };
97
+ }
98
+ }
99
+
@@ -1,33 +1,33 @@
1
- import type { ExtensionManifest } from "@oh-my-tool/sdk";
2
- import type { InstalledExtension } from "../extension/discovery";
3
-
1
+ import type { ExtensionManifest } from "@oh-my-tool/sdk";
2
+ import type { InstalledExtension } from "../extension/discovery";
3
+
4
4
  import { RuntimeError as OmtError } from "../runtime/errors";
5
5
  export { OmtError };
6
-
7
- export interface Registry {
8
- byTool: Map<string, { extension: InstalledExtension; tool: ExtensionManifest["tools"][number] }>;
9
- byId: Map<string, InstalledExtension>;
10
- }
11
-
12
- export function createRegistry(installed: InstalledExtension[]): Registry {
13
- const byTool = new Map();
14
- const byId = new Map();
15
- for (const ext of installed) {
16
- byId.set(ext.id, ext);
17
- for (const tool of ext.manifest.tools) {
18
- byTool.set(tool.name, { extension: ext, tool });
19
- }
20
- }
21
- return { byTool, byId };
22
- }
23
-
24
- export function resolveTool(
25
- reg: Registry,
26
- toolName: string,
27
- ): { extension: InstalledExtension; tool: ExtensionManifest["tools"][number] } {
28
- const hit = reg.byTool.get(toolName);
29
- if (!hit) {
30
- throw new OmtError("UNKNOWN_TOOL", `unknown tool '${toolName}'`);
31
- }
32
- return hit;
33
- }
6
+
7
+ export interface Registry {
8
+ byTool: Map<string, { extension: InstalledExtension; tool: ExtensionManifest["tools"][number] }>;
9
+ byId: Map<string, InstalledExtension>;
10
+ }
11
+
12
+ export function createRegistry(installed: InstalledExtension[]): Registry {
13
+ const byTool = new Map();
14
+ const byId = new Map();
15
+ for (const ext of installed) {
16
+ byId.set(ext.id, ext);
17
+ for (const tool of ext.manifest.tools) {
18
+ byTool.set(tool.name, { extension: ext, tool });
19
+ }
20
+ }
21
+ return { byTool, byId };
22
+ }
23
+
24
+ export function resolveTool(
25
+ reg: Registry,
26
+ toolName: string,
27
+ ): { extension: InstalledExtension; tool: ExtensionManifest["tools"][number] } {
28
+ const hit = reg.byTool.get(toolName);
29
+ if (!hit) {
30
+ throw new OmtError("UNKNOWN_TOOL", `unknown tool '${toolName}'`);
31
+ }
32
+ return hit;
33
+ }
@@ -1,14 +1,14 @@
1
- export interface OmtOk {
2
- ok: true;
3
- tool: string;
4
- data: unknown;
5
- meta: Record<string, unknown>;
6
- }
7
-
8
- export interface OmtErr {
9
- ok: false;
10
- tool: string;
11
- error: { code: string; message: string };
12
- }
13
-
14
- export type OmtResult = OmtOk | OmtErr;
1
+ export interface OmtOk {
2
+ ok: true;
3
+ tool: string;
4
+ data: unknown;
5
+ meta: Record<string, unknown>;
6
+ }
7
+
8
+ export interface OmtErr {
9
+ ok: false;
10
+ tool: string;
11
+ error: { code: string; message: string; details?: unknown };
12
+ }
13
+
14
+ export type OmtResult = OmtOk | OmtErr;
@@ -1,62 +1,62 @@
1
- import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
2
- import { join } from "node:path";
3
- import type { ExtensionManifest } from "@oh-my-tool/sdk";
4
- import { parseManifest, validateManifest, checkSdkCompatibility } from "./manifest";
5
-
6
- export interface InstalledExtension {
7
- id: string;
8
- version: string;
9
- dir: string;
10
- manifest: ExtensionManifest;
11
- entry: string;
12
- }
13
-
14
- const EXTENSIONS_DIR = "extensions";
15
-
16
- function readEntry(dir: string): string {
17
- const pkgPath = join(dir, "package.json");
18
- if (existsSync(pkgPath)) {
19
- try {
20
- const pkg = JSON.parse(readFileSync(pkgPath, "utf8")) as Record<string, any>;
21
- const entry = pkg.omt?.entry;
22
- if (typeof entry === "string") {
23
- return join(dir, entry.replace(/^\.\//, ""));
24
- }
25
- } catch {
26
- // ignore malformed package.json, fall through
27
- }
28
- }
29
- return join(dir, "src", "index.ts");
30
- }
31
-
32
- export function discoverExtensions(home: string): InstalledExtension[] {
33
- const root = join(home, EXTENSIONS_DIR);
34
- if (!existsSync(root)) return [];
35
-
36
- const out: InstalledExtension[] = [];
37
- for (const id of readdirSync(root)) {
38
- const idDir = join(root, id);
39
- if (!statSync(idDir).isDirectory()) continue;
40
- for (const version of readdirSync(idDir)) {
41
- const versionDir = join(idDir, version);
42
- if (!statSync(versionDir).isDirectory()) continue;
43
- const manifestPath = join(versionDir, "omt.manifest.json");
44
- if (!existsSync(manifestPath)) continue;
45
- try {
46
- const manifest = parseManifest(readFileSync(manifestPath, "utf8"));
47
- validateManifest(manifest);
48
- checkSdkCompatibility(manifest.sdkVersion);
49
- out.push({
50
- id,
51
- version,
52
- dir: versionDir,
53
- manifest,
54
- entry: readEntry(versionDir),
55
- });
56
- } catch {
57
- // skip invalid or incompatible manifests
58
- }
59
- }
60
- }
61
- return out;
1
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import type { ExtensionManifest } from "@oh-my-tool/sdk";
4
+ import { parseManifest, validateManifest, checkSdkCompatibility } from "./manifest";
5
+
6
+ export interface InstalledExtension {
7
+ id: string;
8
+ version: string;
9
+ dir: string;
10
+ manifest: ExtensionManifest;
11
+ entry: string;
12
+ }
13
+
14
+ const EXTENSIONS_DIR = "extensions";
15
+
16
+ function readEntry(dir: string): string {
17
+ const pkgPath = join(dir, "package.json");
18
+ if (existsSync(pkgPath)) {
19
+ try {
20
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8")) as Record<string, any>;
21
+ const entry = pkg.omt?.entry;
22
+ if (typeof entry === "string") {
23
+ return join(dir, entry.replace(/^\.\//, ""));
24
+ }
25
+ } catch {
26
+ // ignore malformed package.json, fall through
27
+ }
28
+ }
29
+ return join(dir, "src", "index.ts");
30
+ }
31
+
32
+ export function discoverExtensions(home: string): InstalledExtension[] {
33
+ const root = join(home, EXTENSIONS_DIR);
34
+ if (!existsSync(root)) return [];
35
+
36
+ const out: InstalledExtension[] = [];
37
+ for (const id of readdirSync(root)) {
38
+ const idDir = join(root, id);
39
+ if (!statSync(idDir).isDirectory()) continue;
40
+ for (const version of readdirSync(idDir)) {
41
+ const versionDir = join(idDir, version);
42
+ if (!statSync(versionDir).isDirectory()) continue;
43
+ const manifestPath = join(versionDir, "omt.manifest.json");
44
+ if (!existsSync(manifestPath)) continue;
45
+ try {
46
+ const manifest = parseManifest(readFileSync(manifestPath, "utf8"));
47
+ validateManifest(manifest);
48
+ checkSdkCompatibility(manifest.sdkVersion);
49
+ out.push({
50
+ id,
51
+ version,
52
+ dir: versionDir,
53
+ manifest,
54
+ entry: readEntry(versionDir),
55
+ });
56
+ } catch {
57
+ // skip invalid or incompatible manifests
58
+ }
59
+ }
60
+ }
61
+ return out;
62
62
  }
@@ -1,24 +1,24 @@
1
- import { cp, mkdir, readFile } from "node:fs/promises";
2
- import { join } from "node:path";
3
- import { parseManifest, validateManifest, checkSdkCompatibility } from "./manifest";
4
-
5
- export interface InstalledRef {
6
- id: string;
7
- version: string;
8
- target: string;
9
- }
10
-
11
- export async function installLocalExtension(home: string, srcDir: string): Promise<InstalledRef> {
12
- const manifest = parseManifest(await readFile(join(srcDir, "omt.manifest.json"), "utf8"));
13
- validateManifest(manifest);
14
- checkSdkCompatibility(manifest.sdkVersion);
15
- const target = join(home, "extensions", manifest.id, manifest.version);
16
- await mkdir(target, { recursive: true });
17
- // 显式 force:true:Bun 的 fs.cp 在带 filter 时默认覆盖失效(重装不更新旧文件)
18
- await cp(srcDir, target, {
19
- recursive: true,
20
- force: true,
21
- filter: (s: string) => !s.includes("node_modules"),
22
- });
23
- return { id: manifest.id, version: manifest.version, target };
1
+ import { cp, mkdir, readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { parseManifest, validateManifest, checkSdkCompatibility } from "./manifest";
4
+
5
+ export interface InstalledRef {
6
+ id: string;
7
+ version: string;
8
+ target: string;
9
+ }
10
+
11
+ export async function installLocalExtension(home: string, srcDir: string): Promise<InstalledRef> {
12
+ const manifest = parseManifest(await readFile(join(srcDir, "omt.manifest.json"), "utf8"));
13
+ validateManifest(manifest);
14
+ checkSdkCompatibility(manifest.sdkVersion);
15
+ const target = join(home, "extensions", manifest.id, manifest.version);
16
+ await mkdir(target, { recursive: true });
17
+ // 显式 force:true:Bun 的 fs.cp 在带 filter 时默认覆盖失效(重装不更新旧文件)
18
+ await cp(srcDir, target, {
19
+ recursive: true,
20
+ force: true,
21
+ filter: (s: string) => !s.includes("node_modules"),
22
+ });
23
+ return { id: manifest.id, version: manifest.version, target };
24
24
  }
@@ -1,32 +1,32 @@
1
- import { pathToFileURL } from "node:url";
2
- import type { ExtensionDefinition } from "@oh-my-tool/sdk";
3
- import { OmtError } from "../core/registry";
4
- import type { InstalledExtension } from "./discovery";
5
- import { validateHandlers } from "./manifest";
6
-
7
- export async function loadExtension(
8
- installed: InstalledExtension,
9
- ): Promise<ExtensionDefinition> {
10
- let mod: unknown;
11
- try {
12
- mod = await import(pathToFileURL(installed.entry).href);
13
- } catch (e) {
14
- throw new OmtError(
15
- "LOAD_FAILED",
16
- `failed to load extension '${installed.id}': ${e instanceof Error ? e.message : String(e)}`,
17
- );
18
- }
19
-
20
- const def = (mod as any)?.default ?? mod;
21
- if (!def || typeof def.handlers !== "object" || def.handlers === null) {
22
- throw new OmtError("LOAD_FAILED", `extension '${installed.id}' has no handlers`);
23
- }
24
-
25
- const handlerNames = Object.keys(def.handlers);
26
- try {
27
- validateHandlers(installed.manifest, handlerNames);
28
- } catch (e) {
29
- throw new OmtError("LOAD_FAILED", (e as Error).message);
30
- }
31
- return def as ExtensionDefinition;
32
- }
1
+ import { pathToFileURL } from "node:url";
2
+ import type { ExtensionDefinition } from "@oh-my-tool/sdk";
3
+ import { OmtError } from "../core/registry";
4
+ import type { InstalledExtension } from "./discovery";
5
+ import { validateHandlers } from "./manifest";
6
+
7
+ export async function loadExtension(
8
+ installed: InstalledExtension,
9
+ ): Promise<ExtensionDefinition> {
10
+ let mod: unknown;
11
+ try {
12
+ mod = await import(pathToFileURL(installed.entry).href);
13
+ } catch (e) {
14
+ throw new OmtError(
15
+ "LOAD_FAILED",
16
+ `failed to load extension '${installed.id}': ${e instanceof Error ? e.message : String(e)}`,
17
+ );
18
+ }
19
+
20
+ const def = (mod as any)?.default ?? mod;
21
+ if (!def || typeof def.handlers !== "object" || def.handlers === null) {
22
+ throw new OmtError("LOAD_FAILED", `extension '${installed.id}' has no handlers`);
23
+ }
24
+
25
+ const handlerNames = Object.keys(def.handlers);
26
+ try {
27
+ validateHandlers(installed.manifest, handlerNames);
28
+ } catch (e) {
29
+ throw new OmtError("LOAD_FAILED", (e as Error).message);
30
+ }
31
+ return def as ExtensionDefinition;
32
+ }