@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,115 +1,115 @@
1
- import { OMT_API_VERSION, type ExtensionManifest } from "@oh-my-tool/sdk";
2
-
3
- export class ManifestError extends Error {}
4
-
5
- export function parseManifest(raw: string): ExtensionManifest {
6
- let data: unknown;
7
- try {
8
- data = JSON.parse(raw);
9
- } catch {
10
- throw new ManifestError("manifest is not valid JSON");
11
- }
12
- const obj = data as Partial<ExtensionManifest>;
13
- if (!obj.id || typeof obj.id !== "string") {
14
- throw new ManifestError("manifest must contain a string 'id'");
15
- }
16
- if (!Array.isArray(obj.tools)) {
17
- throw new ManifestError("manifest must contain a 'tools' array");
18
- }
19
- return obj as ExtensionManifest;
20
- }
21
-
22
- export function validateManifest(manifest: ExtensionManifest): void {
23
- if (!manifest.name || typeof manifest.name !== "string") {
24
- throw new ManifestError("manifest must contain a string 'name'");
25
- }
26
- if (!manifest.version || typeof manifest.version !== "string") {
27
- throw new ManifestError("manifest must contain a string 'version'");
28
- }
29
- if (!manifest.sdkVersion || typeof manifest.sdkVersion !== "string") {
30
- throw new ManifestError("manifest must contain a string 'sdkVersion'");
31
- }
32
-
33
- const seen = new Set<string>();
34
- for (const tool of manifest.tools) {
35
- if (!tool.name || typeof tool.name !== "string") {
36
- throw new ManifestError("each tool must have a string 'name'");
37
- }
38
- if (seen.has(tool.name)) {
39
- throw new ManifestError(`duplicate tool name '${tool.name}'`);
40
- }
41
- seen.add(tool.name);
42
-
43
- const prefix = `${manifest.id}.`;
44
- if (!tool.name.startsWith(prefix)) {
45
- throw new ManifestError(
46
- `tool '${tool.name}' must be prefixed by extension id '${manifest.id}.'`,
47
- );
48
- }
49
- }
50
- }
51
-
52
- const FULL_VERSION_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
53
- const PARTIAL_VERSION_RE = /^(\d+|x|X|\*)(\.(\d+|x|X|\*))?(\.(\d+|x|X|\*))?$/;
54
-
55
- function isValidVersionToken(token: string): boolean {
56
- return FULL_VERSION_RE.test(token) || PARTIAL_VERSION_RE.test(token);
57
- }
58
-
59
- function isValidSemverRange(range: string): boolean {
60
- const trimmed = range.trim();
61
- if (!trimmed) return false;
62
- for (const orPart of trimmed.split("||")) {
63
- const pieces = orPart.trim().split(/\s+/).filter(Boolean);
64
- if (pieces.length === 0) return false;
65
- for (let i = 0; i < pieces.length; i++) {
66
- const piece = pieces[i];
67
- if (piece === "-") {
68
- if (i === 0 || i === pieces.length - 1) return false;
69
- continue;
70
- }
71
- const m = piece.match(/^(>=|<=|>|<|=|\^|~)?(.*)$/);
72
- if (!m || m[2] === "" || !isValidVersionToken(m[2])) return false;
73
- }
74
- }
75
- return true;
76
- }
77
-
78
- export function checkSdkCompatibility(requiredRange: string): void {
79
- if (typeof requiredRange !== "string" || requiredRange.trim() === "") {
80
- throw new ManifestError("extension must declare a non-empty 'sdkVersion'");
81
- }
82
- if (!isValidSemverRange(requiredRange)) {
83
- throw new ManifestError(
84
- `invalid sdkVersion '${requiredRange}': not a valid semver range`,
85
- );
86
- }
87
- if (!Bun.semver.satisfies(OMT_API_VERSION, requiredRange)) {
88
- throw new ManifestError(
89
- `extension requires sdk '${requiredRange}' but core provides '${OMT_API_VERSION}'`,
90
- );
91
- }
92
- }
93
-
94
- export function validateHandlers(
95
- manifest: ExtensionManifest,
96
- handlerNames: string[],
97
- ): void {
98
- const handlerSet = new Set(handlerNames);
99
- const manifestSet = new Set(manifest.tools.map((t) => t.name));
100
-
101
- for (const name of manifest.tools.map((t) => t.name)) {
102
- if (!handlerSet.has(name)) {
103
- throw new ManifestError(
104
- `manifest declares tool '${name}' but runtime has no handler`,
105
- );
106
- }
107
- }
108
- for (const name of handlerNames) {
109
- if (!manifestSet.has(name)) {
110
- throw new ManifestError(
111
- `runtime registers tool '${name}' not declared in manifest`,
112
- );
113
- }
114
- }
1
+ import { OMT_API_VERSION, type ExtensionManifest } from "@oh-my-tool/sdk";
2
+
3
+ export class ManifestError extends Error {}
4
+
5
+ export function parseManifest(raw: string): ExtensionManifest {
6
+ let data: unknown;
7
+ try {
8
+ data = JSON.parse(raw);
9
+ } catch {
10
+ throw new ManifestError("manifest is not valid JSON");
11
+ }
12
+ const obj = data as Partial<ExtensionManifest>;
13
+ if (!obj.id || typeof obj.id !== "string") {
14
+ throw new ManifestError("manifest must contain a string 'id'");
15
+ }
16
+ if (!Array.isArray(obj.tools)) {
17
+ throw new ManifestError("manifest must contain a 'tools' array");
18
+ }
19
+ return obj as ExtensionManifest;
20
+ }
21
+
22
+ export function validateManifest(manifest: ExtensionManifest): void {
23
+ if (!manifest.name || typeof manifest.name !== "string") {
24
+ throw new ManifestError("manifest must contain a string 'name'");
25
+ }
26
+ if (!manifest.version || typeof manifest.version !== "string") {
27
+ throw new ManifestError("manifest must contain a string 'version'");
28
+ }
29
+ if (!manifest.sdkVersion || typeof manifest.sdkVersion !== "string") {
30
+ throw new ManifestError("manifest must contain a string 'sdkVersion'");
31
+ }
32
+
33
+ const seen = new Set<string>();
34
+ for (const tool of manifest.tools) {
35
+ if (!tool.name || typeof tool.name !== "string") {
36
+ throw new ManifestError("each tool must have a string 'name'");
37
+ }
38
+ if (seen.has(tool.name)) {
39
+ throw new ManifestError(`duplicate tool name '${tool.name}'`);
40
+ }
41
+ seen.add(tool.name);
42
+
43
+ const prefix = `${manifest.id}.`;
44
+ if (!tool.name.startsWith(prefix)) {
45
+ throw new ManifestError(
46
+ `tool '${tool.name}' must be prefixed by extension id '${manifest.id}.'`,
47
+ );
48
+ }
49
+ }
50
+ }
51
+
52
+ const FULL_VERSION_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
53
+ const PARTIAL_VERSION_RE = /^(\d+|x|X|\*)(\.(\d+|x|X|\*))?(\.(\d+|x|X|\*))?$/;
54
+
55
+ function isValidVersionToken(token: string): boolean {
56
+ return FULL_VERSION_RE.test(token) || PARTIAL_VERSION_RE.test(token);
57
+ }
58
+
59
+ function isValidSemverRange(range: string): boolean {
60
+ const trimmed = range.trim();
61
+ if (!trimmed) return false;
62
+ for (const orPart of trimmed.split("||")) {
63
+ const pieces = orPart.trim().split(/\s+/).filter(Boolean);
64
+ if (pieces.length === 0) return false;
65
+ for (let i = 0; i < pieces.length; i++) {
66
+ const piece = pieces[i];
67
+ if (piece === "-") {
68
+ if (i === 0 || i === pieces.length - 1) return false;
69
+ continue;
70
+ }
71
+ const m = piece.match(/^(>=|<=|>|<|=|\^|~)?(.*)$/);
72
+ if (!m || m[2] === "" || !isValidVersionToken(m[2])) return false;
73
+ }
74
+ }
75
+ return true;
76
+ }
77
+
78
+ export function checkSdkCompatibility(requiredRange: string): void {
79
+ if (typeof requiredRange !== "string" || requiredRange.trim() === "") {
80
+ throw new ManifestError("extension must declare a non-empty 'sdkVersion'");
81
+ }
82
+ if (!isValidSemverRange(requiredRange)) {
83
+ throw new ManifestError(
84
+ `invalid sdkVersion '${requiredRange}': not a valid semver range`,
85
+ );
86
+ }
87
+ if (!Bun.semver.satisfies(OMT_API_VERSION, requiredRange)) {
88
+ throw new ManifestError(
89
+ `extension requires sdk '${requiredRange}' but core provides '${OMT_API_VERSION}'`,
90
+ );
91
+ }
92
+ }
93
+
94
+ export function validateHandlers(
95
+ manifest: ExtensionManifest,
96
+ handlerNames: string[],
97
+ ): void {
98
+ const handlerSet = new Set(handlerNames);
99
+ const manifestSet = new Set(manifest.tools.map((t) => t.name));
100
+
101
+ for (const name of manifest.tools.map((t) => t.name)) {
102
+ if (!handlerSet.has(name)) {
103
+ throw new ManifestError(
104
+ `manifest declares tool '${name}' but runtime has no handler`,
105
+ );
106
+ }
107
+ }
108
+ for (const name of handlerNames) {
109
+ if (!manifestSet.has(name)) {
110
+ throw new ManifestError(
111
+ `runtime registers tool '${name}' not declared in manifest`,
112
+ );
113
+ }
114
+ }
115
115
  }
@@ -1,98 +1,98 @@
1
- import { homedir } from "node:os";
2
- import { basename, delimiter, extname, join } from "node:path";
3
- import { existsSync } from "node:fs";
4
- import type { AgentDetection, AgentId } from "./types";
5
-
6
- export type FindCommand = (candidates: string[]) => Promise<string | undefined> | string | undefined;
7
-
8
- export interface DetectionOptions {
9
- userHome?: string;
10
- findCommand?: FindCommand;
11
- }
12
-
13
- interface AdapterDefinition {
14
- id: AgentId;
15
- displayName: string;
16
- commands: string[];
17
- target(userHome: string, command: string): string;
18
- variant?(command: string): string | undefined;
19
- }
20
-
21
- const definitions: AdapterDefinition[] = [
22
- {
23
- id: "codex",
24
- displayName: "Codex",
25
- commands: ["codex"],
26
- target: (home) => join(home, ".agents", "skills", "oh-my-tool"),
27
- },
28
- {
29
- id: "omp",
30
- displayName: "OMP",
31
- commands: ["omp"],
32
- target: (home) => join(home, ".omp", "agent", "skills", "oh-my-tool"),
33
- },
34
- {
35
- id: "qoder",
36
- displayName: "Qoder",
37
- commands: ["qoderclicn", "qodercli", "qoder"],
38
- target: (home, command) =>
39
- commandName(command) === "qoderclicn"
40
- ? join(home, ".qoder-cn", "skills", "oh-my-tool")
41
- : join(home, ".qoder", "skills", "oh-my-tool"),
42
- variant: (command) => (commandName(command) === "qoderclicn" ? "Qoder CLI CN" : undefined),
43
- },
44
- {
45
- id: "pi",
46
- displayName: "Pi",
47
- commands: ["pi"],
48
- target: (home) => join(home, ".agents", "skills", "oh-my-tool"),
49
- },
50
- {
51
- id: "cursor",
52
- displayName: "Cursor",
53
- commands: ["cursor"],
54
- target: (home) => join(home, ".agents", "skills", "oh-my-tool"),
55
- },
56
- {
57
- id: "claude",
58
- displayName: "Claude Code",
59
- commands: ["claude"],
60
- target: (home) => join(home, ".claude", "skills", "oh-my-tool"),
61
- },
62
- ];
63
-
64
- function commandName(command: string): string {
65
- return basename(command, extname(command)).toLowerCase();
66
- }
67
-
68
- export function findCommandOnPath(candidates: string[]): string | undefined {
69
- const pathEntries = (process.env.PATH ?? "").split(delimiter).filter(Boolean);
70
- const suffixes = process.platform === "win32" ? [".exe", ".cmd", ".bat", ".ps1", ""] : [""];
71
- for (const candidate of candidates) {
72
- for (const directory of pathEntries) {
73
- for (const suffix of suffixes) {
74
- const path = join(directory, candidate + suffix);
75
- if (existsSync(path)) return path;
76
- }
77
- }
78
- }
79
- return undefined;
80
- }
81
-
82
- export async function detectAgents(options: DetectionOptions = {}): Promise<AgentDetection[]> {
83
- const userHome = options.userHome ?? homedir();
84
- const findCommand = options.findCommand ?? findCommandOnPath;
85
- const detected: AgentDetection[] = [];
86
- for (const definition of definitions) {
87
- const command = await findCommand(definition.commands);
88
- if (!command) continue;
89
- detected.push({
90
- id: definition.id,
91
- displayName: definition.displayName,
92
- command,
93
- target: definition.target(userHome, command),
94
- variant: definition.variant?.(command),
95
- });
96
- }
97
- return detected;
98
- }
1
+ import { homedir } from "node:os";
2
+ import { basename, delimiter, extname, join } from "node:path";
3
+ import { existsSync } from "node:fs";
4
+ import type { AgentDetection, AgentId } from "./types";
5
+
6
+ export type FindCommand = (candidates: string[]) => Promise<string | undefined> | string | undefined;
7
+
8
+ export interface DetectionOptions {
9
+ userHome?: string;
10
+ findCommand?: FindCommand;
11
+ }
12
+
13
+ interface AdapterDefinition {
14
+ id: AgentId;
15
+ displayName: string;
16
+ commands: string[];
17
+ target(userHome: string, command: string): string;
18
+ variant?(command: string): string | undefined;
19
+ }
20
+
21
+ const definitions: AdapterDefinition[] = [
22
+ {
23
+ id: "codex",
24
+ displayName: "Codex",
25
+ commands: ["codex"],
26
+ target: (home) => join(home, ".agents", "skills", "oh-my-tool"),
27
+ },
28
+ {
29
+ id: "omp",
30
+ displayName: "OMP",
31
+ commands: ["omp"],
32
+ target: (home) => join(home, ".omp", "agent", "skills", "oh-my-tool"),
33
+ },
34
+ {
35
+ id: "qoder",
36
+ displayName: "Qoder",
37
+ commands: ["qoderclicn", "qodercli", "qoder"],
38
+ target: (home, command) =>
39
+ commandName(command) === "qoderclicn"
40
+ ? join(home, ".qoder-cn", "skills", "oh-my-tool")
41
+ : join(home, ".qoder", "skills", "oh-my-tool"),
42
+ variant: (command) => (commandName(command) === "qoderclicn" ? "Qoder CLI CN" : undefined),
43
+ },
44
+ {
45
+ id: "pi",
46
+ displayName: "Pi",
47
+ commands: ["pi"],
48
+ target: (home) => join(home, ".agents", "skills", "oh-my-tool"),
49
+ },
50
+ {
51
+ id: "cursor",
52
+ displayName: "Cursor",
53
+ commands: ["cursor"],
54
+ target: (home) => join(home, ".agents", "skills", "oh-my-tool"),
55
+ },
56
+ {
57
+ id: "claude",
58
+ displayName: "Claude Code",
59
+ commands: ["claude"],
60
+ target: (home) => join(home, ".claude", "skills", "oh-my-tool"),
61
+ },
62
+ ];
63
+
64
+ function commandName(command: string): string {
65
+ return basename(command, extname(command)).toLowerCase();
66
+ }
67
+
68
+ export function findCommandOnPath(candidates: string[]): string | undefined {
69
+ const pathEntries = (process.env.PATH ?? "").split(delimiter).filter(Boolean);
70
+ const suffixes = process.platform === "win32" ? [".exe", ".cmd", ".bat", ".ps1", ""] : [""];
71
+ for (const candidate of candidates) {
72
+ for (const directory of pathEntries) {
73
+ for (const suffix of suffixes) {
74
+ const path = join(directory, candidate + suffix);
75
+ if (existsSync(path)) return path;
76
+ }
77
+ }
78
+ }
79
+ return undefined;
80
+ }
81
+
82
+ export async function detectAgents(options: DetectionOptions = {}): Promise<AgentDetection[]> {
83
+ const userHome = options.userHome ?? homedir();
84
+ const findCommand = options.findCommand ?? findCommandOnPath;
85
+ const detected: AgentDetection[] = [];
86
+ for (const definition of definitions) {
87
+ const command = await findCommand(definition.commands);
88
+ if (!command) continue;
89
+ detected.push({
90
+ id: definition.id,
91
+ displayName: definition.displayName,
92
+ command,
93
+ target: definition.target(userHome, command),
94
+ variant: definition.variant?.(command),
95
+ });
96
+ }
97
+ return detected;
98
+ }
@@ -1,4 +1,4 @@
1
- export * from "./types";
2
- export * from "./adapters";
3
- export * from "./skill";
4
- export * from "./manager";
1
+ export * from "./types";
2
+ export * from "./adapters";
3
+ export * from "./skill";
4
+ export * from "./manager";