@oh-my-pi/cli 0.1.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 (89) hide show
  1. package/.github/workflows/ci.yml +32 -0
  2. package/.github/workflows/publish.yml +42 -0
  3. package/CHECK.md +352 -0
  4. package/README.md +224 -0
  5. package/biome.json +29 -0
  6. package/bun.lock +50 -0
  7. package/dist/cli.d.ts +3 -0
  8. package/dist/cli.d.ts.map +1 -0
  9. package/dist/cli.js +3941 -0
  10. package/dist/commands/create.d.ts +9 -0
  11. package/dist/commands/create.d.ts.map +1 -0
  12. package/dist/commands/doctor.d.ts +10 -0
  13. package/dist/commands/doctor.d.ts.map +1 -0
  14. package/dist/commands/enable.d.ts +13 -0
  15. package/dist/commands/enable.d.ts.map +1 -0
  16. package/dist/commands/info.d.ts +9 -0
  17. package/dist/commands/info.d.ts.map +1 -0
  18. package/dist/commands/init.d.ts +8 -0
  19. package/dist/commands/init.d.ts.map +1 -0
  20. package/dist/commands/install.d.ts +13 -0
  21. package/dist/commands/install.d.ts.map +1 -0
  22. package/dist/commands/link.d.ts +10 -0
  23. package/dist/commands/link.d.ts.map +1 -0
  24. package/dist/commands/list.d.ts +9 -0
  25. package/dist/commands/list.d.ts.map +1 -0
  26. package/dist/commands/outdated.d.ts +9 -0
  27. package/dist/commands/outdated.d.ts.map +1 -0
  28. package/dist/commands/search.d.ts +9 -0
  29. package/dist/commands/search.d.ts.map +1 -0
  30. package/dist/commands/uninstall.d.ts +9 -0
  31. package/dist/commands/uninstall.d.ts.map +1 -0
  32. package/dist/commands/update.d.ts +9 -0
  33. package/dist/commands/update.d.ts.map +1 -0
  34. package/dist/commands/why.d.ts +9 -0
  35. package/dist/commands/why.d.ts.map +1 -0
  36. package/dist/conflicts.d.ts +21 -0
  37. package/dist/conflicts.d.ts.map +1 -0
  38. package/dist/index.d.ts +20 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/manifest.d.ts +81 -0
  41. package/dist/manifest.d.ts.map +1 -0
  42. package/dist/migrate.d.ts +9 -0
  43. package/dist/migrate.d.ts.map +1 -0
  44. package/dist/npm.d.ts +77 -0
  45. package/dist/npm.d.ts.map +1 -0
  46. package/dist/paths.d.ts +27 -0
  47. package/dist/paths.d.ts.map +1 -0
  48. package/dist/symlinks.d.ts +33 -0
  49. package/dist/symlinks.d.ts.map +1 -0
  50. package/package.json +36 -0
  51. package/plugins/metal-theme/README.md +13 -0
  52. package/plugins/metal-theme/omp.json +8 -0
  53. package/plugins/metal-theme/package.json +14 -0
  54. package/plugins/metal-theme/themes/metal.json +79 -0
  55. package/plugins/subagents/README.md +25 -0
  56. package/plugins/subagents/agents/explore.md +71 -0
  57. package/plugins/subagents/agents/planner.md +51 -0
  58. package/plugins/subagents/agents/reviewer.md +53 -0
  59. package/plugins/subagents/agents/task.md +46 -0
  60. package/plugins/subagents/commands/architect-plan.md +9 -0
  61. package/plugins/subagents/commands/implement-with-critic.md +10 -0
  62. package/plugins/subagents/commands/implement.md +10 -0
  63. package/plugins/subagents/omp.json +15 -0
  64. package/plugins/subagents/package.json +21 -0
  65. package/plugins/subagents/tools/task/index.ts +1019 -0
  66. package/scripts/bump-version.sh +52 -0
  67. package/scripts/publish.sh +35 -0
  68. package/src/cli.ts +167 -0
  69. package/src/commands/create.ts +153 -0
  70. package/src/commands/doctor.ts +217 -0
  71. package/src/commands/enable.ts +105 -0
  72. package/src/commands/info.ts +84 -0
  73. package/src/commands/init.ts +42 -0
  74. package/src/commands/install.ts +327 -0
  75. package/src/commands/link.ts +108 -0
  76. package/src/commands/list.ts +71 -0
  77. package/src/commands/outdated.ts +76 -0
  78. package/src/commands/search.ts +60 -0
  79. package/src/commands/uninstall.ts +73 -0
  80. package/src/commands/update.ts +112 -0
  81. package/src/commands/why.ts +105 -0
  82. package/src/conflicts.ts +84 -0
  83. package/src/index.ts +53 -0
  84. package/src/manifest.ts +212 -0
  85. package/src/migrate.ts +181 -0
  86. package/src/npm.ts +150 -0
  87. package/src/paths.ts +72 -0
  88. package/src/symlinks.ts +199 -0
  89. package/tsconfig.json +24 -0
@@ -0,0 +1,71 @@
1
+ import chalk from "chalk";
2
+ import { loadPluginsJson, readPluginPackageJson } from "../manifest.js";
3
+
4
+ export interface ListOptions {
5
+ global?: boolean;
6
+ json?: boolean;
7
+ }
8
+
9
+ /**
10
+ * List all installed plugins
11
+ */
12
+ export async function listPlugins(options: ListOptions = {}): Promise<void> {
13
+ const isGlobal = options.global !== false;
14
+ const pluginsJson = await loadPluginsJson(isGlobal);
15
+ const pluginNames = Object.keys(pluginsJson.plugins);
16
+
17
+ if (pluginNames.length === 0) {
18
+ console.log(chalk.yellow("No plugins installed."));
19
+ console.log(chalk.dim("Install one with: omp install <package>"));
20
+ return;
21
+ }
22
+
23
+ if (options.json) {
24
+ const plugins: Record<string, unknown> = {};
25
+ for (const name of pluginNames) {
26
+ const pkgJson = await readPluginPackageJson(name, isGlobal);
27
+ const disabled = pluginsJson.disabled?.includes(name) || false;
28
+ plugins[name] = {
29
+ version: pkgJson?.version || pluginsJson.plugins[name],
30
+ description: pkgJson?.description,
31
+ disabled,
32
+ files: pkgJson?.omp?.install?.map((e) => e.dest) || [],
33
+ };
34
+ }
35
+ console.log(JSON.stringify({ plugins }, null, 2));
36
+ return;
37
+ }
38
+
39
+ const location = isGlobal ? "~/.pi/plugins" : ".pi";
40
+ console.log(chalk.bold(`Installed plugins (${pluginNames.length}) [${location}]:\n`));
41
+
42
+ for (const name of pluginNames.sort()) {
43
+ const pkgJson = await readPluginPackageJson(name, isGlobal);
44
+ const specifier = pluginsJson.plugins[name];
45
+ const isLocal = specifier.startsWith("file:");
46
+ const disabled = pluginsJson.disabled?.includes(name);
47
+
48
+ const version = pkgJson?.version ? chalk.dim(` v${pkgJson.version}`) : "";
49
+ const localBadge = isLocal ? chalk.cyan(" (local)") : "";
50
+ const disabledBadge = disabled ? chalk.yellow(" (disabled)") : "";
51
+ const icon = disabled ? chalk.gray("○") : chalk.green("◆");
52
+
53
+ console.log(`${icon} ${chalk.bold(name)}${version}${localBadge}${disabledBadge}`);
54
+
55
+ if (pkgJson?.description) {
56
+ console.log(chalk.dim(` ${pkgJson.description}`));
57
+ }
58
+
59
+ if (isLocal) {
60
+ const localPath = specifier.replace("file:", "");
61
+ console.log(chalk.dim(` path: ${localPath}`));
62
+ }
63
+
64
+ if (pkgJson?.omp?.install?.length) {
65
+ const files = pkgJson.omp.install.map((e) => e.dest);
66
+ console.log(chalk.dim(` files: ${files.join(", ")}`));
67
+ }
68
+
69
+ console.log();
70
+ }
71
+ }
@@ -0,0 +1,76 @@
1
+ import chalk from "chalk";
2
+ import { loadPluginsJson } from "../manifest.js";
3
+ import { npmOutdated } from "../npm.js";
4
+ import { PLUGINS_DIR } from "../paths.js";
5
+
6
+ export interface OutdatedOptions {
7
+ global?: boolean;
8
+ json?: boolean;
9
+ }
10
+
11
+ /**
12
+ * List plugins with newer versions available
13
+ */
14
+ export async function showOutdated(options: OutdatedOptions = {}): Promise<void> {
15
+ const isGlobal = options.global !== false;
16
+ const prefix = isGlobal ? PLUGINS_DIR : ".pi";
17
+
18
+ console.log(chalk.blue("Checking for outdated plugins..."));
19
+
20
+ try {
21
+ const outdated = await npmOutdated(prefix);
22
+ const pluginsJson = await loadPluginsJson(isGlobal);
23
+
24
+ // Filter to only show plugins we manage
25
+ const managedOutdated = Object.entries(outdated).filter(([name]) => {
26
+ return pluginsJson.plugins[name] !== undefined;
27
+ });
28
+
29
+ if (managedOutdated.length === 0) {
30
+ console.log(chalk.green("\n✓ All plugins are up to date!"));
31
+ return;
32
+ }
33
+
34
+ if (options.json) {
35
+ const result = Object.fromEntries(managedOutdated);
36
+ console.log(JSON.stringify({ outdated: result }, null, 2));
37
+ return;
38
+ }
39
+
40
+ console.log(chalk.bold(`\nOutdated plugins (${managedOutdated.length}):\n`));
41
+
42
+ // Header
43
+ console.log(
44
+ chalk.dim(" Package".padEnd(30)) +
45
+ chalk.dim("Current".padEnd(15)) +
46
+ chalk.dim("Wanted".padEnd(15)) +
47
+ chalk.dim("Latest"),
48
+ );
49
+
50
+ for (const [name, versions] of managedOutdated) {
51
+ const current = versions.current || "?";
52
+ const wanted = versions.wanted || "?";
53
+ const latest = versions.latest || "?";
54
+
55
+ const hasMinorUpdate = wanted !== current;
56
+ const hasMajorUpdate = latest !== wanted;
57
+
58
+ const wantedColor = hasMinorUpdate ? chalk.yellow : chalk.dim;
59
+ const latestColor = hasMajorUpdate ? chalk.red : wantedColor;
60
+
61
+ console.log(
62
+ ` ${chalk.white(name.padEnd(28))}` +
63
+ `${chalk.dim(current.padEnd(15))}` +
64
+ `${wantedColor(wanted.padEnd(15))}` +
65
+ `${latestColor(latest)}`,
66
+ );
67
+ }
68
+
69
+ console.log();
70
+ console.log(chalk.dim("Update with: omp update [package]"));
71
+ console.log(chalk.dim(" - 'wanted' = latest within semver range"));
72
+ console.log(chalk.dim(" - 'latest' = latest available version"));
73
+ } catch (err) {
74
+ console.log(chalk.red(`Error checking outdated: ${(err as Error).message}`));
75
+ }
76
+ }
@@ -0,0 +1,60 @@
1
+ import chalk from "chalk";
2
+ import { npmSearch } from "../npm.js";
3
+
4
+ export interface SearchOptions {
5
+ json?: boolean;
6
+ limit?: number;
7
+ }
8
+
9
+ /**
10
+ * Search npm for plugins with omp-plugin keyword
11
+ */
12
+ export async function searchPlugins(query: string, options: SearchOptions = {}): Promise<void> {
13
+ console.log(chalk.blue(`Searching npm for "${query}" with omp-plugin keyword...`));
14
+
15
+ try {
16
+ const results = await npmSearch(query, "omp-plugin");
17
+
18
+ if (results.length === 0) {
19
+ console.log(chalk.yellow("\nNo plugins found."));
20
+ console.log(chalk.dim("Try a different search term, or search without keyword:"));
21
+ console.log(chalk.dim(" npm search omp-plugin"));
22
+ return;
23
+ }
24
+
25
+ const limit = options.limit || 20;
26
+ const displayResults = results.slice(0, limit);
27
+
28
+ if (options.json) {
29
+ console.log(JSON.stringify({ results: displayResults }, null, 2));
30
+ return;
31
+ }
32
+
33
+ console.log(chalk.bold(`\nFound ${results.length} plugin(s):\n`));
34
+
35
+ for (const result of displayResults) {
36
+ console.log(chalk.green("◆ ") + chalk.bold(result.name) + chalk.dim(` v${result.version}`));
37
+
38
+ if (result.description) {
39
+ console.log(chalk.dim(` ${result.description}`));
40
+ }
41
+
42
+ if (result.keywords?.length) {
43
+ const otherKeywords = result.keywords.filter((k) => k !== "omp-plugin");
44
+ if (otherKeywords.length > 0) {
45
+ console.log(chalk.dim(` tags: ${otherKeywords.join(", ")}`));
46
+ }
47
+ }
48
+
49
+ console.log();
50
+ }
51
+
52
+ if (results.length > limit) {
53
+ console.log(chalk.dim(`... and ${results.length - limit} more. Use --limit to see more.`));
54
+ }
55
+
56
+ console.log(chalk.dim("Install with: omp install <package-name>"));
57
+ } catch (err) {
58
+ console.log(chalk.red(`Error searching: ${(err as Error).message}`));
59
+ }
60
+ }
@@ -0,0 +1,73 @@
1
+ import { existsSync } from "node:fs";
2
+ import { rm } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import chalk from "chalk";
5
+ import { loadPluginsJson, readPluginPackageJson, savePluginsJson } from "../manifest.js";
6
+ import { npmUninstall } from "../npm.js";
7
+ import { NODE_MODULES_DIR, PLUGINS_DIR, PROJECT_NODE_MODULES } from "../paths.js";
8
+ import { removePluginSymlinks } from "../symlinks.js";
9
+
10
+ export interface UninstallOptions {
11
+ global?: boolean;
12
+ json?: boolean;
13
+ }
14
+
15
+ /**
16
+ * Uninstall a plugin
17
+ */
18
+ export async function uninstallPlugin(name: string, options: UninstallOptions = {}): Promise<void> {
19
+ const isGlobal = options.global !== false; // Default to global
20
+ const prefix = isGlobal ? PLUGINS_DIR : ".pi";
21
+ const nodeModules = isGlobal ? NODE_MODULES_DIR : PROJECT_NODE_MODULES;
22
+
23
+ // Check if plugin is installed
24
+ const pluginsJson = await loadPluginsJson(isGlobal);
25
+ if (!pluginsJson.plugins[name]) {
26
+ console.log(chalk.yellow(`Plugin "${name}" is not installed.`));
27
+ return;
28
+ }
29
+
30
+ try {
31
+ console.log(chalk.blue(`Uninstalling ${name}...`));
32
+
33
+ // 1. Read package.json for omp.install entries before uninstalling
34
+ const pkgJson = await readPluginPackageJson(name, isGlobal);
35
+
36
+ // 2. Remove symlinks
37
+ if (pkgJson) {
38
+ await removePluginSymlinks(name, pkgJson);
39
+ }
40
+
41
+ // 3. npm uninstall
42
+ try {
43
+ await npmUninstall([name], prefix);
44
+ } catch (_err) {
45
+ // Package might have been installed via file: protocol
46
+ // Try to remove manually from node_modules
47
+ const pluginDir = join(nodeModules, name);
48
+ if (existsSync(pluginDir)) {
49
+ await rm(pluginDir, { recursive: true, force: true });
50
+ }
51
+ }
52
+
53
+ // 4. Update plugins.json/package.json
54
+ delete pluginsJson.plugins[name];
55
+ // Also remove from disabled list if present
56
+ if (pluginsJson.disabled) {
57
+ pluginsJson.disabled = pluginsJson.disabled.filter((n) => n !== name);
58
+ }
59
+ await savePluginsJson(pluginsJson, isGlobal);
60
+
61
+ console.log(chalk.green(`✓ Uninstalled "${name}"`));
62
+
63
+ if (options.json) {
64
+ console.log(JSON.stringify({ name, success: true }, null, 2));
65
+ }
66
+ } catch (err) {
67
+ console.log(chalk.red(`Error uninstalling plugin: ${(err as Error).message}`));
68
+
69
+ if (options.json) {
70
+ console.log(JSON.stringify({ name, success: false, error: (err as Error).message }, null, 2));
71
+ }
72
+ }
73
+ }
@@ -0,0 +1,112 @@
1
+ import chalk from "chalk";
2
+ import { loadPluginsJson, readPluginPackageJson } from "../manifest.js";
3
+ import { npmUpdate } from "../npm.js";
4
+ import { NODE_MODULES_DIR, PLUGINS_DIR, PROJECT_NODE_MODULES } from "../paths.js";
5
+ import { createPluginSymlinks, removePluginSymlinks } from "../symlinks.js";
6
+
7
+ export interface UpdateOptions {
8
+ global?: boolean;
9
+ json?: boolean;
10
+ }
11
+
12
+ /**
13
+ * Update plugin(s) to latest within semver range
14
+ */
15
+ export async function updatePlugin(name?: string, options: UpdateOptions = {}): Promise<void> {
16
+ const isGlobal = options.global !== false;
17
+ const prefix = isGlobal ? PLUGINS_DIR : ".pi";
18
+ const _nodeModules = isGlobal ? NODE_MODULES_DIR : PROJECT_NODE_MODULES;
19
+
20
+ const pluginsJson = await loadPluginsJson(isGlobal);
21
+ const pluginNames = Object.keys(pluginsJson.plugins);
22
+
23
+ if (pluginNames.length === 0) {
24
+ console.log(chalk.yellow("No plugins installed."));
25
+ return;
26
+ }
27
+
28
+ // If specific plugin name provided, verify it's installed
29
+ if (name && !pluginsJson.plugins[name]) {
30
+ console.log(chalk.yellow(`Plugin "${name}" is not installed.`));
31
+ return;
32
+ }
33
+
34
+ const toUpdate = name ? [name] : pluginNames;
35
+
36
+ // Filter out file: dependencies (local plugins)
37
+ const npmPlugins = toUpdate.filter((n) => {
38
+ const version = pluginsJson.plugins[n];
39
+ return !version.startsWith("file:");
40
+ });
41
+
42
+ const localPlugins = toUpdate.filter((n) => {
43
+ const version = pluginsJson.plugins[n];
44
+ return version.startsWith("file:");
45
+ });
46
+
47
+ if (localPlugins.length > 0) {
48
+ console.log(chalk.dim(`Skipping ${localPlugins.length} local plugin(s): ${localPlugins.join(", ")}`));
49
+ }
50
+
51
+ if (npmPlugins.length === 0) {
52
+ console.log(chalk.yellow("No npm plugins to update."));
53
+ return;
54
+ }
55
+
56
+ console.log(chalk.blue(`Updating ${npmPlugins.length} plugin(s)...`));
57
+
58
+ const results: Array<{ name: string; from: string; to: string; success: boolean }> = [];
59
+
60
+ try {
61
+ // Get current versions before update
62
+ const beforeVersions: Record<string, string> = {};
63
+ for (const pluginName of npmPlugins) {
64
+ const pkgJson = await readPluginPackageJson(pluginName, isGlobal);
65
+ if (pkgJson) {
66
+ beforeVersions[pluginName] = pkgJson.version;
67
+
68
+ // Remove old symlinks before update
69
+ await removePluginSymlinks(pluginName, pkgJson, false);
70
+ }
71
+ }
72
+
73
+ // npm update
74
+ await npmUpdate(npmPlugins, prefix);
75
+
76
+ // Re-process symlinks for each updated plugin
77
+ for (const pluginName of npmPlugins) {
78
+ const pkgJson = await readPluginPackageJson(pluginName, isGlobal);
79
+ if (pkgJson) {
80
+ const beforeVersion = beforeVersions[pluginName] || "unknown";
81
+ const afterVersion = pkgJson.version;
82
+
83
+ // Create new symlinks
84
+ await createPluginSymlinks(pluginName, pkgJson, isGlobal);
85
+
86
+ const changed = beforeVersion !== afterVersion;
87
+ if (changed) {
88
+ console.log(chalk.green(` ✓ ${pluginName}: ${beforeVersion} → ${afterVersion}`));
89
+ } else {
90
+ console.log(chalk.dim(` · ${pluginName}: ${afterVersion} (already latest)`));
91
+ }
92
+
93
+ results.push({
94
+ name: pluginName,
95
+ from: beforeVersion,
96
+ to: afterVersion,
97
+ success: true,
98
+ });
99
+ }
100
+ }
101
+
102
+ const updated = results.filter((r) => r.from !== r.to);
103
+ console.log();
104
+ console.log(chalk.dim(`Updated: ${updated.length}, Already latest: ${results.length - updated.length}`));
105
+
106
+ if (options.json) {
107
+ console.log(JSON.stringify({ results }, null, 2));
108
+ }
109
+ } catch (err) {
110
+ console.log(chalk.red(`Error updating plugins: ${(err as Error).message}`));
111
+ }
112
+ }
@@ -0,0 +1,105 @@
1
+ import { existsSync, lstatSync } from "node:fs";
2
+ import { readlink } from "node:fs/promises";
3
+ import { join, relative } from "node:path";
4
+ import chalk from "chalk";
5
+ import { getInstalledPlugins, readPluginPackageJson } from "../manifest.js";
6
+ import { PI_CONFIG_DIR } from "../paths.js";
7
+ import { traceInstalledFile } from "../symlinks.js";
8
+
9
+ export interface WhyOptions {
10
+ global?: boolean;
11
+ json?: boolean;
12
+ }
13
+
14
+ /**
15
+ * Show which plugin installed a file
16
+ */
17
+ export async function whyFile(filePath: string, options: WhyOptions = {}): Promise<void> {
18
+ const isGlobal = options.global !== false;
19
+
20
+ // Normalize path - make it relative to PI_CONFIG_DIR if it's absolute
21
+ let relativePath = filePath;
22
+ if (filePath.startsWith(PI_CONFIG_DIR)) {
23
+ relativePath = relative(PI_CONFIG_DIR, filePath);
24
+ } else if (filePath.startsWith("~/.pi/")) {
25
+ relativePath = filePath.slice(6); // Remove ~/.pi/
26
+ }
27
+
28
+ // Check if it's a path in agent/ directory
29
+ if (!relativePath.startsWith("agent/")) {
30
+ // Try prepending agent/
31
+ const withAgent = `agent/${relativePath}`;
32
+ const fullWithAgent = join(PI_CONFIG_DIR, withAgent);
33
+ if (existsSync(fullWithAgent)) {
34
+ relativePath = withAgent;
35
+ }
36
+ }
37
+
38
+ const fullPath = join(PI_CONFIG_DIR, relativePath);
39
+
40
+ // Check if file exists
41
+ if (!existsSync(fullPath)) {
42
+ console.log(chalk.yellow(`File not found: ${fullPath}`));
43
+ return;
44
+ }
45
+
46
+ // Check if it's a symlink
47
+ const stats = lstatSync(fullPath);
48
+ const isSymlink = stats.isSymbolicLink();
49
+
50
+ let target: string | null = null;
51
+ if (isSymlink) {
52
+ target = await readlink(fullPath);
53
+ }
54
+
55
+ // Search through installed plugins
56
+ const installedPlugins = await getInstalledPlugins(isGlobal);
57
+ const result = await traceInstalledFile(relativePath, installedPlugins);
58
+
59
+ if (options.json) {
60
+ console.log(
61
+ JSON.stringify(
62
+ {
63
+ path: relativePath,
64
+ fullPath,
65
+ isSymlink,
66
+ target,
67
+ plugin: result?.plugin || null,
68
+ source: result?.entry.src || null,
69
+ },
70
+ null,
71
+ 2,
72
+ ),
73
+ );
74
+ return;
75
+ }
76
+
77
+ console.log(chalk.bold(`File: ${relativePath}`));
78
+ console.log(chalk.dim(`Full path: ${fullPath}`));
79
+ console.log();
80
+
81
+ if (isSymlink && target) {
82
+ console.log(`${chalk.dim("Type: ")}symlink`);
83
+ console.log(chalk.dim("Target: ") + target);
84
+ console.log();
85
+ }
86
+
87
+ if (result) {
88
+ console.log(chalk.green(`✓ Installed by: ${result.plugin}`));
89
+ console.log(chalk.dim(` Source: ${result.entry.src}`));
90
+ console.log(chalk.dim(` Dest: ${result.entry.dest}`));
91
+
92
+ // Get plugin info
93
+ const pkgJson = await readPluginPackageJson(result.plugin, isGlobal);
94
+ if (pkgJson) {
95
+ console.log();
96
+ console.log(chalk.dim(`Plugin version: ${pkgJson.version}`));
97
+ if (pkgJson.description) {
98
+ console.log(chalk.dim(`Description: ${pkgJson.description}`));
99
+ }
100
+ }
101
+ } else {
102
+ console.log(chalk.yellow("⚠ Not installed by any tracked plugin"));
103
+ console.log(chalk.dim(" This file may have been created manually or by a plugin that was uninstalled."));
104
+ }
105
+ }
@@ -0,0 +1,84 @@
1
+ import type { PluginPackageJson } from "./manifest.js";
2
+
3
+ export interface Conflict {
4
+ dest: string;
5
+ plugins: Array<{ name: string; src: string }>;
6
+ }
7
+
8
+ /**
9
+ * Detect conflicts between a new plugin and existing plugins
10
+ */
11
+ export function detectConflicts(
12
+ newPluginName: string,
13
+ newPkgJson: PluginPackageJson,
14
+ existingPlugins: Map<string, PluginPackageJson>,
15
+ ): Conflict[] {
16
+ const conflicts: Conflict[] = [];
17
+
18
+ if (!newPkgJson.omp?.install?.length) {
19
+ return conflicts;
20
+ }
21
+
22
+ // Build a map of existing destinations
23
+ const destMap = new Map<string, Array<{ name: string; src: string }>>();
24
+
25
+ for (const [name, pkgJson] of existingPlugins) {
26
+ if (pkgJson.omp?.install) {
27
+ for (const entry of pkgJson.omp.install) {
28
+ const existing = destMap.get(entry.dest) || [];
29
+ existing.push({ name, src: entry.src });
30
+ destMap.set(entry.dest, existing);
31
+ }
32
+ }
33
+ }
34
+
35
+ // Check new plugin's destinations
36
+ for (const entry of newPkgJson.omp.install) {
37
+ const existing = destMap.get(entry.dest);
38
+ if (existing && existing.length > 0) {
39
+ conflicts.push({
40
+ dest: entry.dest,
41
+ plugins: [...existing, { name: newPluginName, src: entry.src }],
42
+ });
43
+ }
44
+ }
45
+
46
+ return conflicts;
47
+ }
48
+
49
+ /**
50
+ * Detect all conflicts among a set of plugins
51
+ */
52
+ export function detectAllConflicts(plugins: Map<string, PluginPackageJson>): Conflict[] {
53
+ const conflicts: Conflict[] = [];
54
+ const destMap = new Map<string, Array<{ name: string; src: string }>>();
55
+
56
+ for (const [name, pkgJson] of plugins) {
57
+ if (pkgJson.omp?.install) {
58
+ for (const entry of pkgJson.omp.install) {
59
+ const existing = destMap.get(entry.dest) || [];
60
+ existing.push({ name, src: entry.src });
61
+ destMap.set(entry.dest, existing);
62
+ }
63
+ }
64
+ }
65
+
66
+ // Find destinations with multiple sources
67
+ for (const [dest, sources] of destMap) {
68
+ if (sources.length > 1) {
69
+ conflicts.push({ dest, plugins: sources });
70
+ }
71
+ }
72
+
73
+ return conflicts;
74
+ }
75
+
76
+ /**
77
+ * Format conflicts for display
78
+ */
79
+ export function formatConflicts(conflicts: Conflict[]): string[] {
80
+ return conflicts.map((conflict) => {
81
+ const plugins = conflict.plugins.map((p) => p.name).join(" and ");
82
+ return `${plugins} both install ${conflict.dest}`;
83
+ });
84
+ }
package/src/index.ts ADDED
@@ -0,0 +1,53 @@
1
+ // Core commands
2
+
3
+ export { createPlugin } from "./commands/create.js";
4
+ export { runDoctor } from "./commands/doctor.js";
5
+ export { disablePlugin, enablePlugin } from "./commands/enable.js";
6
+ export { showInfo } from "./commands/info.js";
7
+ // New commands
8
+ export { initProject } from "./commands/init.js";
9
+ export { installPlugin } from "./commands/install.js";
10
+ export { linkPlugin } from "./commands/link.js";
11
+ export { listPlugins } from "./commands/list.js";
12
+ export { showOutdated } from "./commands/outdated.js";
13
+ export { searchPlugins } from "./commands/search.js";
14
+ export { uninstallPlugin } from "./commands/uninstall.js";
15
+ export { updatePlugin } from "./commands/update.js";
16
+ export { whyFile } from "./commands/why.js";
17
+ export {
18
+ detectAllConflicts,
19
+ detectConflicts,
20
+ formatConflicts,
21
+ } from "./conflicts.js";
22
+
23
+ // Types
24
+ export type {
25
+ OmpField,
26
+ OmpInstallEntry,
27
+ PluginPackageJson,
28
+ PluginsJson,
29
+ } from "./manifest.js";
30
+
31
+ // Utilities
32
+ export {
33
+ getInstalledPlugins,
34
+ initGlobalPlugins,
35
+ loadPluginsJson,
36
+ readPluginPackageJson,
37
+ savePluginsJson,
38
+ } from "./manifest.js";
39
+ // Migration
40
+ export { checkMigration, migrateToNpm } from "./migrate.js";
41
+ export {
42
+ npmInfo,
43
+ npmInstall,
44
+ npmOutdated,
45
+ npmSearch,
46
+ npmUninstall,
47
+ npmUpdate,
48
+ } from "./npm.js";
49
+ export {
50
+ checkPluginSymlinks,
51
+ createPluginSymlinks,
52
+ removePluginSymlinks,
53
+ } from "./symlinks.js";