@oh-my-pi/cli 0.3.0 → 0.5.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.
Files changed (108) hide show
  1. package/README.md +79 -84
  2. package/dist/cli.js +5025 -1016
  3. package/dist/commands/config.d.ts +27 -0
  4. package/dist/commands/config.d.ts.map +1 -1
  5. package/dist/commands/create.d.ts.map +1 -1
  6. package/dist/commands/doctor.d.ts +2 -0
  7. package/dist/commands/doctor.d.ts.map +1 -1
  8. package/dist/commands/env.d.ts.map +1 -1
  9. package/dist/commands/features.d.ts.map +1 -1
  10. package/dist/commands/info.d.ts.map +1 -1
  11. package/dist/commands/init.d.ts.map +1 -1
  12. package/dist/commands/install.d.ts +6 -0
  13. package/dist/commands/install.d.ts.map +1 -1
  14. package/dist/commands/link.d.ts +1 -0
  15. package/dist/commands/link.d.ts.map +1 -1
  16. package/dist/commands/list.d.ts.map +1 -1
  17. package/dist/commands/outdated.d.ts.map +1 -1
  18. package/dist/commands/search.d.ts.map +1 -1
  19. package/dist/commands/uninstall.d.ts +3 -0
  20. package/dist/commands/uninstall.d.ts.map +1 -1
  21. package/dist/commands/update.d.ts +1 -0
  22. package/dist/commands/update.d.ts.map +1 -1
  23. package/dist/commands/why.d.ts.map +1 -1
  24. package/dist/conflicts.d.ts +7 -2
  25. package/dist/conflicts.d.ts.map +1 -1
  26. package/dist/index.d.ts +1 -0
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/lock.d.ts.map +1 -1
  29. package/dist/lockfile.d.ts +24 -3
  30. package/dist/lockfile.d.ts.map +1 -1
  31. package/dist/manifest.d.ts +12 -1
  32. package/dist/manifest.d.ts.map +1 -1
  33. package/dist/npm.d.ts +11 -0
  34. package/dist/npm.d.ts.map +1 -1
  35. package/dist/output.d.ts +51 -0
  36. package/dist/output.d.ts.map +1 -0
  37. package/dist/paths.d.ts +5 -0
  38. package/dist/paths.d.ts.map +1 -1
  39. package/dist/progress.d.ts +78 -0
  40. package/dist/progress.d.ts.map +1 -0
  41. package/dist/runtime.d.ts.map +1 -1
  42. package/dist/symlinks.d.ts +1 -0
  43. package/dist/symlinks.d.ts.map +1 -1
  44. package/package.json +24 -10
  45. package/.github/icon.png +0 -0
  46. package/.github/logo.png +0 -0
  47. package/.github/workflows/ci.yml +0 -32
  48. package/.github/workflows/publish.yml +0 -42
  49. package/biome.json +0 -29
  50. package/bun.lock +0 -109
  51. package/plugins/exa/README.md +0 -153
  52. package/plugins/exa/package.json +0 -56
  53. package/plugins/exa/tools/exa/company.ts +0 -35
  54. package/plugins/exa/tools/exa/index.ts +0 -66
  55. package/plugins/exa/tools/exa/linkedin.ts +0 -35
  56. package/plugins/exa/tools/exa/researcher.ts +0 -40
  57. package/plugins/exa/tools/exa/runtime.json +0 -4
  58. package/plugins/exa/tools/exa/search.ts +0 -46
  59. package/plugins/exa/tools/exa/shared.ts +0 -230
  60. package/plugins/exa/tools/exa/websets.ts +0 -62
  61. package/plugins/metal-theme/README.md +0 -13
  62. package/plugins/metal-theme/omp.json +0 -8
  63. package/plugins/metal-theme/package.json +0 -19
  64. package/plugins/metal-theme/themes/metal.json +0 -79
  65. package/plugins/subagents/README.md +0 -25
  66. package/plugins/subagents/agents/explore.md +0 -71
  67. package/plugins/subagents/agents/planner.md +0 -51
  68. package/plugins/subagents/agents/reviewer.md +0 -53
  69. package/plugins/subagents/agents/task.md +0 -46
  70. package/plugins/subagents/commands/architect-plan.md +0 -9
  71. package/plugins/subagents/commands/implement-with-critic.md +0 -10
  72. package/plugins/subagents/commands/implement.md +0 -10
  73. package/plugins/subagents/omp.json +0 -15
  74. package/plugins/subagents/package.json +0 -26
  75. package/plugins/subagents/tools/task/index.ts +0 -1019
  76. package/plugins/user-prompt/README.md +0 -130
  77. package/plugins/user-prompt/package.json +0 -19
  78. package/plugins/user-prompt/tools/user-prompt/index.ts +0 -235
  79. package/scripts/bump-version.sh +0 -52
  80. package/scripts/publish.sh +0 -35
  81. package/src/cli.ts +0 -242
  82. package/src/commands/config.ts +0 -384
  83. package/src/commands/create.ts +0 -203
  84. package/src/commands/doctor.ts +0 -305
  85. package/src/commands/enable.ts +0 -122
  86. package/src/commands/env.ts +0 -38
  87. package/src/commands/features.ts +0 -295
  88. package/src/commands/info.ts +0 -120
  89. package/src/commands/init.ts +0 -60
  90. package/src/commands/install.ts +0 -700
  91. package/src/commands/link.ts +0 -159
  92. package/src/commands/list.ts +0 -186
  93. package/src/commands/outdated.ts +0 -87
  94. package/src/commands/search.ts +0 -77
  95. package/src/commands/uninstall.ts +0 -124
  96. package/src/commands/update.ts +0 -170
  97. package/src/commands/why.ts +0 -136
  98. package/src/conflicts.ts +0 -116
  99. package/src/errors.ts +0 -22
  100. package/src/index.ts +0 -46
  101. package/src/lock.ts +0 -46
  102. package/src/lockfile.ts +0 -132
  103. package/src/manifest.ts +0 -360
  104. package/src/npm.ts +0 -206
  105. package/src/paths.ts +0 -137
  106. package/src/runtime.ts +0 -116
  107. package/src/symlinks.ts +0 -455
  108. package/tsconfig.json +0 -28
@@ -1,159 +0,0 @@
1
- import { existsSync } from "node:fs";
2
- import { mkdir, readFile, rm, symlink, writeFile } from "node:fs/promises";
3
- import { basename, dirname, join, resolve } from "node:path";
4
- import { createInterface } from "node:readline";
5
- import { loadPluginsJson, type PluginPackageJson, savePluginsJson } from "@omp/manifest";
6
- import { NODE_MODULES_DIR, PROJECT_NODE_MODULES, resolveScope } from "@omp/paths";
7
- import { createPluginSymlinks } from "@omp/symlinks";
8
- import chalk from "chalk";
9
-
10
- async function confirmCreate(path: string): Promise<boolean> {
11
- if (!process.stdin.isTTY || !process.stdout.isTTY) {
12
- console.log(chalk.dim(" Non-interactive mode: auto-creating package.json"));
13
- return true;
14
- }
15
-
16
- const rl = createInterface({ input: process.stdin, output: process.stdout });
17
- return new Promise((resolve) => {
18
- rl.question(chalk.yellow(` Create minimal package.json at ${path}? [Y/n] `), (answer) => {
19
- rl.close();
20
- resolve(answer.toLowerCase() !== "n");
21
- });
22
- });
23
- }
24
-
25
- export interface LinkOptions {
26
- name?: string;
27
- global?: boolean;
28
- local?: boolean;
29
- force?: boolean;
30
- }
31
-
32
- /**
33
- * Link a local plugin directory for development
34
- * Creates a symlink in node_modules pointing to the local directory
35
- */
36
- export async function linkPlugin(localPath: string, options: LinkOptions = {}): Promise<void> {
37
- const isGlobal = resolveScope(options);
38
- const nodeModules = isGlobal ? NODE_MODULES_DIR : PROJECT_NODE_MODULES;
39
-
40
- // Expand ~ to home directory
41
- if (localPath.startsWith("~")) {
42
- localPath = join(process.env.HOME || "", localPath.slice(1));
43
- }
44
- localPath = resolve(localPath);
45
-
46
- // Verify the path exists
47
- if (!existsSync(localPath)) {
48
- console.log(chalk.red(`Error: Path does not exist: ${localPath}`));
49
- process.exitCode = 1;
50
- return;
51
- }
52
-
53
- // Read package.json from local path
54
- let pkgJson: PluginPackageJson;
55
- const localPkgJsonPath = join(localPath, "package.json");
56
- const localOmpJsonPath = join(localPath, "omp.json");
57
-
58
- if (existsSync(localPkgJsonPath)) {
59
- pkgJson = JSON.parse(await readFile(localPkgJsonPath, "utf-8"));
60
- } else if (existsSync(localOmpJsonPath)) {
61
- // Convert legacy omp.json to package.json format
62
- const ompJson = JSON.parse(await readFile(localOmpJsonPath, "utf-8"));
63
- pkgJson = {
64
- name: ompJson.name || options.name || basename(localPath),
65
- version: ompJson.version || "0.0.0-dev",
66
- description: ompJson.description,
67
- keywords: ["omp-plugin"],
68
- omp: {
69
- install: ompJson.install,
70
- },
71
- };
72
-
73
- // Persist the conversion to package.json
74
- console.log(chalk.dim(" Converting omp.json to package.json..."));
75
- await writeFile(localPkgJsonPath, JSON.stringify(pkgJson, null, 2));
76
- } else {
77
- // Create minimal package.json so npm operations work correctly
78
- console.log(chalk.yellow(" No package.json found in target directory."));
79
- const shouldCreate = await confirmCreate(localPkgJsonPath);
80
- if (!shouldCreate) {
81
- console.log(chalk.yellow(" Aborted: package.json required for linking"));
82
- process.exitCode = 1;
83
- return;
84
- }
85
- pkgJson = {
86
- name: options.name || basename(localPath),
87
- version: "0.0.0-dev",
88
- keywords: ["omp-plugin"],
89
- omp: {
90
- install: [],
91
- },
92
- };
93
- console.log(chalk.dim(" Creating minimal package.json..."));
94
- await writeFile(localPkgJsonPath, JSON.stringify(pkgJson, null, 2));
95
- }
96
-
97
- const pluginName = options.name || pkgJson.name;
98
- const pluginDir = join(nodeModules, pluginName);
99
-
100
- // Check if already installed
101
- const pluginsJson = await loadPluginsJson(isGlobal);
102
- if (pluginsJson.plugins[pluginName]) {
103
- const existingSpec = pluginsJson.plugins[pluginName];
104
- const isLinked = existingSpec.startsWith("file:");
105
-
106
- if (isLinked) {
107
- console.log(chalk.yellow(`Plugin "${pluginName}" is already linked.`));
108
- console.log(chalk.dim(` Current link: ${existingSpec}`));
109
- console.log(chalk.dim(" Re-linking..."));
110
- // Continue with the linking process (will overwrite)
111
- } else if (options.force) {
112
- console.log(chalk.yellow(`Plugin "${pluginName}" is installed from npm. Overwriting with link...`));
113
- // Continue with the linking process (will overwrite)
114
- } else {
115
- console.log(chalk.yellow(`Plugin "${pluginName}" is already installed from npm.`));
116
- console.log(chalk.dim("Use omp uninstall first, or specify a different name with -n"));
117
- console.log(chalk.dim("Or use --force to overwrite the npm installation"));
118
- process.exitCode = 1;
119
- return;
120
- }
121
- }
122
-
123
- try {
124
- console.log(chalk.blue(`Linking ${localPath}...`));
125
-
126
- // Create parent directory (handles scoped packages like @org/name)
127
- await mkdir(dirname(pluginDir), { recursive: true });
128
-
129
- // Remove existing if present
130
- if (existsSync(pluginDir)) {
131
- await rm(pluginDir, { force: true, recursive: true });
132
- }
133
-
134
- // Create symlink to the plugin directory
135
- await symlink(localPath, pluginDir);
136
- console.log(chalk.dim(` Symlinked: ${pluginDir} → ${localPath}`));
137
-
138
- // Update plugins.json with file: protocol
139
- pluginsJson.plugins[pluginName] = `file:${localPath}`;
140
- await savePluginsJson(pluginsJson, isGlobal);
141
-
142
- // Create symlinks for omp.install entries
143
- if (pkgJson.omp?.install?.length) {
144
- await createPluginSymlinks(pluginName, pkgJson, isGlobal);
145
- }
146
-
147
- console.log(
148
- chalk.green(`\n✓ Linked "${pluginName}"${pkgJson.version ? ` v${pkgJson.version}` : ""} (development mode)`),
149
- );
150
- console.log(chalk.dim(" Changes to the source will be reflected immediately"));
151
- } catch (err) {
152
- console.log(chalk.red(`Error linking plugin: ${(err as Error).message}`));
153
- process.exitCode = 1;
154
- // Cleanup on failure
155
- try {
156
- await rm(pluginDir, { force: true });
157
- } catch {}
158
- }
159
- }
@@ -1,186 +0,0 @@
1
- import { loadPluginsJson, readPluginPackageJson } from "@omp/manifest";
2
- import { resolveScope } from "@omp/paths";
3
- import chalk from "chalk";
4
-
5
- export interface ListOptions {
6
- global?: boolean;
7
- local?: boolean;
8
- json?: boolean;
9
- }
10
-
11
- /**
12
- * Known file categories with their patterns and display info
13
- */
14
- interface FileCategory {
15
- pattern: RegExp;
16
- label: string;
17
- color: (s: string) => string;
18
- extractName: (dest: string) => string;
19
- }
20
-
21
- const FILE_CATEGORIES: FileCategory[] = [
22
- {
23
- pattern: /^agent\/tools\/([^/]+)\//,
24
- label: "Tools",
25
- color: chalk.cyan,
26
- extractName: (dest) => dest.match(/^agent\/tools\/([^/]+)\//)?.[1] || dest,
27
- },
28
- {
29
- pattern: /^agent\/agents\/(.+)\.md$/,
30
- label: "Agents",
31
- color: chalk.magenta,
32
- extractName: (dest) => dest.match(/^agent\/agents\/(.+)\.md$/)?.[1] || dest,
33
- },
34
- {
35
- pattern: /^agent\/commands\/(.+)\.md$/,
36
- label: "Commands",
37
- color: chalk.yellow,
38
- extractName: (dest) => dest.match(/^agent\/commands\/(.+)\.md$/)?.[1] || dest,
39
- },
40
- {
41
- pattern: /^agent\/themes\/(.+)\.json$/,
42
- label: "Themes",
43
- color: chalk.green,
44
- extractName: (dest) => dest.match(/^agent\/themes\/(.+)\.json$/)?.[1] || dest,
45
- },
46
- {
47
- pattern: /^agent\/prompts?\//,
48
- label: "Prompts",
49
- color: chalk.blue,
50
- extractName: (dest) => dest.split("/").pop()?.replace(/\.[^.]+$/, "") || dest,
51
- },
52
- {
53
- pattern: /^agent\/hooks?\//,
54
- label: "Hooks",
55
- color: chalk.red,
56
- extractName: (dest) => dest.split("/").pop()?.replace(/\.[^.]+$/, "") || dest,
57
- },
58
- ];
59
-
60
- /**
61
- * Categorize installed files into known categories
62
- */
63
- function categorizeFiles(files: string[]): { categorized: Map<string, string[]>; uncategorized: string[] } {
64
- const categorized = new Map<string, string[]>();
65
- const uncategorized: string[] = [];
66
-
67
- for (const file of files) {
68
- let matched = false;
69
- for (const category of FILE_CATEGORIES) {
70
- if (category.pattern.test(file)) {
71
- const name = category.extractName(file);
72
- const existing = categorized.get(category.label) || [];
73
- if (!existing.includes(name)) {
74
- existing.push(name);
75
- categorized.set(category.label, existing);
76
- }
77
- matched = true;
78
- break;
79
- }
80
- }
81
- if (!matched) {
82
- uncategorized.push(file);
83
- }
84
- }
85
-
86
- return { categorized, uncategorized };
87
- }
88
-
89
- /**
90
- * Format categorized files for display
91
- */
92
- function formatContributes(files: string[]): string[] {
93
- const { categorized, uncategorized } = categorizeFiles(files);
94
- const lines: string[] = [];
95
-
96
- if (categorized.size > 0 || uncategorized.length > 0) {
97
- lines.push(` ${chalk.white("Contributes:")}`);
98
-
99
- for (const category of FILE_CATEGORIES) {
100
- const items = categorized.get(category.label);
101
- if (items && items.length > 0) {
102
- const count = category.color(`${items.length}`);
103
- const names = items.map((n) => chalk.dim(n)).join(chalk.dim(", "));
104
- lines.push(` ${chalk.green("+")} ${category.label} (${count}): ${names}`);
105
- }
106
- }
107
-
108
- if (uncategorized.length > 0) {
109
- const count = chalk.gray(`${uncategorized.length}`);
110
- const names = uncategorized.map((n) => chalk.dim(n)).join(chalk.dim(", "));
111
- lines.push(` ${chalk.green("+")} Files (${count}): ${names}`);
112
- }
113
- }
114
-
115
- return lines;
116
- }
117
-
118
- /**
119
- * List all installed plugins
120
- */
121
- export async function listPlugins(options: ListOptions = {}): Promise<void> {
122
- const isGlobal = resolveScope(options);
123
- const pluginsJson = await loadPluginsJson(isGlobal);
124
- const pluginNames = Object.keys(pluginsJson.plugins);
125
-
126
- if (pluginNames.length === 0) {
127
- console.log(chalk.yellow("No plugins installed."));
128
- console.log(chalk.dim("Install one with: omp install <package>"));
129
- process.exitCode = 1;
130
- return;
131
- }
132
-
133
- if (options.json) {
134
- const plugins: Record<string, unknown> = {};
135
- for (const name of pluginNames) {
136
- const pkgJson = await readPluginPackageJson(name, isGlobal);
137
- const disabled = pluginsJson.disabled?.includes(name) || false;
138
- plugins[name] = {
139
- version: pkgJson?.version || pluginsJson.plugins[name],
140
- description: pkgJson?.description,
141
- disabled,
142
- files: pkgJson?.omp?.install?.map((e) => e.dest) || [],
143
- };
144
- }
145
- console.log(JSON.stringify({ plugins }, null, 2));
146
- return;
147
- }
148
-
149
- const location = isGlobal ? "~/.pi/plugins" : ".pi";
150
- console.log(chalk.bold(`Installed plugins (${pluginNames.length}) [${location}]:\n`));
151
-
152
- for (const name of pluginNames.sort()) {
153
- const pkgJson = await readPluginPackageJson(name, isGlobal);
154
- const specifier = pluginsJson.plugins[name];
155
- const isLocal = specifier.startsWith("file:");
156
- const disabled = pluginsJson.disabled?.includes(name);
157
- const isMissing = !pkgJson;
158
-
159
- const version = pkgJson?.version ? chalk.dim(`v${pkgJson.version}`) : chalk.dim(`(${specifier})`);
160
- const localBadge = isLocal ? chalk.cyan(" (local)") : "";
161
- const disabledBadge = disabled ? chalk.yellow(" (disabled)") : "";
162
- const missingBadge = isMissing ? chalk.red(" (missing)") : "";
163
- const icon = disabled ? chalk.gray("○") : isMissing ? chalk.red("✗") : chalk.green("◆");
164
-
165
- console.log(`${icon} ${chalk.bold(name)} ${version}${localBadge}${disabledBadge}${missingBadge}`);
166
-
167
- if (pkgJson?.description) {
168
- console.log(chalk.dim(` ${pkgJson.description}`));
169
- }
170
-
171
- if (isLocal) {
172
- const localPath = specifier.replace("file:", "");
173
- console.log(chalk.dim(` Path: ${localPath}`));
174
- }
175
-
176
- if (pkgJson?.omp?.install?.length) {
177
- const files = pkgJson.omp.install.map((e) => e.dest);
178
- const contributeLines = formatContributes(files);
179
- for (const line of contributeLines) {
180
- console.log(line);
181
- }
182
- }
183
-
184
- console.log();
185
- }
186
- }
@@ -1,87 +0,0 @@
1
- import { loadPluginsJson } from "@omp/manifest";
2
- import { npmOutdated } from "@omp/npm";
3
- import { PLUGINS_DIR, resolveScope } from "@omp/paths";
4
- import chalk from "chalk";
5
-
6
- export interface OutdatedOptions {
7
- global?: boolean;
8
- local?: boolean;
9
- json?: boolean;
10
- }
11
-
12
- /**
13
- * List plugins with newer versions available
14
- */
15
- export async function showOutdated(options: OutdatedOptions = {}): Promise<void> {
16
- const isGlobal = resolveScope(options);
17
- const prefix = isGlobal ? PLUGINS_DIR : ".pi";
18
-
19
- console.log(chalk.blue("Checking for outdated plugins..."));
20
-
21
- try {
22
- const outdated = await npmOutdated(prefix);
23
- const pluginsJson = await loadPluginsJson(isGlobal);
24
-
25
- // Filter to only show plugins we manage AND are not local
26
- const managedOutdated = Object.entries(outdated).filter(([name]) => {
27
- const specifier = pluginsJson.plugins[name];
28
- if (!specifier) return false; // Not in our manifest
29
- if (specifier.startsWith("file:")) return false; // Local plugin, skip
30
- return true;
31
- });
32
-
33
- if (managedOutdated.length === 0) {
34
- console.log(chalk.green("\n✓ All plugins are up to date!"));
35
- return;
36
- }
37
-
38
- if (options.json) {
39
- const result = Object.fromEntries(managedOutdated);
40
- console.log(JSON.stringify({ outdated: result }, null, 2));
41
- return;
42
- }
43
-
44
- console.log(chalk.bold(`\nOutdated plugins (${managedOutdated.length}):\n`));
45
-
46
- // Header
47
- console.log(
48
- chalk.dim(" Package".padEnd(30)) +
49
- chalk.dim("Current".padEnd(15)) +
50
- chalk.dim("Wanted".padEnd(15)) +
51
- chalk.dim("Latest"),
52
- );
53
-
54
- for (const [name, versions] of managedOutdated) {
55
- const current = versions.current || "?";
56
- const wanted = versions.wanted || "?";
57
- const latest = versions.latest || "?";
58
-
59
- const hasMinorUpdate = wanted !== current;
60
- const hasMajorUpdate = latest !== wanted;
61
-
62
- const wantedColor = hasMinorUpdate ? chalk.yellow : chalk.dim;
63
- const latestColor = hasMajorUpdate ? chalk.red : wantedColor;
64
-
65
- console.log(
66
- ` ${chalk.white(name.padEnd(28))}` +
67
- `${chalk.dim(current.padEnd(15))}` +
68
- `${wantedColor(wanted.padEnd(15))}` +
69
- `${latestColor(latest)}`,
70
- );
71
- }
72
-
73
- // Note about local plugins excluded from check
74
- const localPlugins = Object.entries(pluginsJson.plugins).filter(([_, spec]) => spec.startsWith("file:"));
75
- if (localPlugins.length > 0) {
76
- console.log(chalk.dim(`\nNote: ${localPlugins.length} local plugin(s) excluded from check`));
77
- }
78
-
79
- console.log();
80
- console.log(chalk.dim("Update with: omp update [package]"));
81
- console.log(chalk.dim(" - 'wanted' = latest within semver range"));
82
- console.log(chalk.dim(" - 'latest' = latest available version"));
83
- } catch (err) {
84
- console.log(chalk.red(`Error checking outdated: ${(err as Error).message}`));
85
- process.exitCode = 1;
86
- }
87
- }
@@ -1,77 +0,0 @@
1
- import { npmSearch } from "@omp/npm";
2
- import chalk from "chalk";
3
-
4
- function truncate(str: string, maxLen: number): string {
5
- if (!str || str.length <= maxLen) return str;
6
- return `${str.slice(0, maxLen - 3)}...`;
7
- }
8
-
9
- export interface SearchOptions {
10
- json?: boolean;
11
- limit?: number;
12
- }
13
-
14
- /**
15
- * Search npm for plugins with omp-plugin keyword
16
- */
17
- export async function searchPlugins(query: string, options: SearchOptions = {}): Promise<void> {
18
- console.log(chalk.blue(`Searching npm for "${query}" with omp-plugin keyword...`));
19
-
20
- try {
21
- const results = await npmSearch(query, "omp-plugin");
22
-
23
- if (results.length === 0) {
24
- console.log(chalk.yellow("\nNo plugins found."));
25
- console.log(chalk.dim("Try a different search term, or search without keyword:"));
26
- console.log(chalk.dim(" npm search omp-plugin"));
27
- process.exitCode = 1;
28
- return;
29
- }
30
-
31
- const limit = options.limit || 20;
32
- const displayResults = results.slice(0, limit);
33
-
34
- if (options.json) {
35
- console.log(JSON.stringify({ results: displayResults }, null, 2));
36
- return;
37
- }
38
-
39
- console.log(chalk.bold(`\nFound ${results.length} plugin(s):\n`));
40
-
41
- for (const result of displayResults) {
42
- console.log(chalk.green("◆ ") + chalk.bold(result.name) + chalk.dim(` v${result.version}`));
43
-
44
- if (result.description) {
45
- console.log(chalk.dim(` ${truncate(result.description, 100)}`));
46
- }
47
-
48
- if (result.keywords?.length) {
49
- const otherKeywords = result.keywords.filter((k) => k !== "omp-plugin");
50
- if (otherKeywords.length > 0) {
51
- console.log(chalk.dim(` tags: ${otherKeywords.join(", ")}`));
52
- }
53
- }
54
-
55
- console.log();
56
- }
57
-
58
- if (results.length > limit) {
59
- console.log(chalk.dim(`... and ${results.length - limit} more. Use --limit to see more.`));
60
- }
61
-
62
- console.log(chalk.dim("Install with: omp install <package-name>"));
63
- } catch (err) {
64
- const error = err as Error;
65
- if (
66
- error.message.includes("ENOTFOUND") ||
67
- error.message.includes("ETIMEDOUT") ||
68
- error.message.includes("EAI_AGAIN")
69
- ) {
70
- console.log(chalk.red("\nNetwork error: Unable to reach npm registry."));
71
- console.log(chalk.dim(" Check your internet connection and try again."));
72
- } else {
73
- console.log(chalk.red(`\nSearch failed: ${error.message}`));
74
- }
75
- process.exitCode = 1;
76
- }
77
- }
@@ -1,124 +0,0 @@
1
- import { existsSync } from "node:fs";
2
- import { rm } from "node:fs/promises";
3
- import { join } from "node:path";
4
- import { createInterface } from "node:readline";
5
- import { getInstalledPlugins, loadPluginsJson, readPluginPackageJson, savePluginsJson } from "@omp/manifest";
6
- import { npmUninstall } from "@omp/npm";
7
- import { NODE_MODULES_DIR, PLUGINS_DIR, PROJECT_NODE_MODULES, resolveScope } from "@omp/paths";
8
- import { removePluginSymlinks } from "@omp/symlinks";
9
- import chalk from "chalk";
10
-
11
- export interface UninstallOptions {
12
- global?: boolean;
13
- local?: boolean;
14
- json?: boolean;
15
- }
16
-
17
- /**
18
- * Uninstall a plugin
19
- */
20
- export async function uninstallPlugin(name: string, options: UninstallOptions = {}): Promise<void> {
21
- const isGlobal = resolveScope(options);
22
- const prefix = isGlobal ? PLUGINS_DIR : ".pi";
23
- const nodeModules = isGlobal ? NODE_MODULES_DIR : PROJECT_NODE_MODULES;
24
-
25
- // Check if plugin is installed
26
- const pluginsJson = await loadPluginsJson(isGlobal);
27
- if (!pluginsJson.plugins[name]) {
28
- console.log(chalk.yellow(`Plugin "${name}" is not installed.`));
29
- process.exitCode = 1;
30
- return;
31
- }
32
-
33
- try {
34
- console.log(chalk.blue(`Uninstalling ${name}...`));
35
-
36
- // 1. Read package.json for omp.install entries before uninstalling
37
- const pkgJson = await readPluginPackageJson(name, isGlobal);
38
-
39
- // Check for shared dependencies
40
- if (pkgJson?.dependencies) {
41
- const allPlugins = await getInstalledPlugins(isGlobal);
42
- const sharedDeps: string[] = [];
43
-
44
- for (const depName of Object.keys(pkgJson.dependencies)) {
45
- for (const [otherName, otherPkgJson] of allPlugins) {
46
- if (otherName !== name && otherPkgJson.dependencies?.[depName]) {
47
- sharedDeps.push(`${depName} (also used by ${otherName})`);
48
- break;
49
- }
50
- }
51
- }
52
-
53
- if (sharedDeps.length > 0) {
54
- console.log(chalk.yellow("\n⚠ Warning: This plugin shares dependencies with other plugins:"));
55
- for (const dep of sharedDeps) {
56
- console.log(chalk.dim(` - ${dep}`));
57
- }
58
- console.log(chalk.dim(" These dependencies will remain installed."));
59
- }
60
- }
61
-
62
- // 2. Remove symlinks
63
- if (pkgJson) {
64
- const result = await removePluginSymlinks(name, pkgJson, isGlobal);
65
-
66
- if (result.skippedNonSymlinks.length > 0) {
67
- console.log(chalk.yellow("\nThe following files are not symlinks and were not removed:"));
68
- for (const file of result.skippedNonSymlinks) {
69
- console.log(chalk.dim(` - ${file}`));
70
- }
71
-
72
- if (process.stdin.isTTY && process.stdout.isTTY) {
73
- const rl = createInterface({ input: process.stdin, output: process.stdout });
74
- const answer = await new Promise<string>((resolve) => {
75
- rl.question(chalk.yellow("Delete these files anyway? [y/N] "), (ans) => {
76
- rl.close();
77
- resolve(ans);
78
- });
79
- });
80
-
81
- if (answer.toLowerCase() === "y") {
82
- for (const file of result.skippedNonSymlinks) {
83
- await rm(file, { force: true, recursive: true });
84
- console.log(chalk.dim(` Deleted: ${file}`));
85
- }
86
- }
87
- }
88
- }
89
- }
90
-
91
- // 3. npm uninstall
92
- try {
93
- await npmUninstall([name], prefix);
94
- } catch (_err) {
95
- // Package might have been installed via file: protocol
96
- // Try to remove manually from node_modules
97
- const pluginDir = join(nodeModules, name);
98
- if (existsSync(pluginDir)) {
99
- await rm(pluginDir, { recursive: true, force: true });
100
- }
101
- }
102
-
103
- // 4. Update plugins.json/package.json
104
- delete pluginsJson.plugins[name];
105
- // Also remove from disabled list if present
106
- if (pluginsJson.disabled) {
107
- pluginsJson.disabled = pluginsJson.disabled.filter((n) => n !== name);
108
- }
109
- await savePluginsJson(pluginsJson, isGlobal);
110
-
111
- console.log(chalk.green(`✓ Uninstalled "${name}"`));
112
-
113
- if (options.json) {
114
- console.log(JSON.stringify({ name, success: true }, null, 2));
115
- }
116
- } catch (err) {
117
- console.log(chalk.red(`Error uninstalling plugin: ${(err as Error).message}`));
118
- process.exitCode = 1;
119
-
120
- if (options.json) {
121
- console.log(JSON.stringify({ name, success: false, error: (err as Error).message }, null, 2));
122
- }
123
- }
124
- }