@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.
- package/.github/workflows/ci.yml +32 -0
- package/.github/workflows/publish.yml +42 -0
- package/CHECK.md +352 -0
- package/README.md +224 -0
- package/biome.json +29 -0
- package/bun.lock +50 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +3941 -0
- package/dist/commands/create.d.ts +9 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/doctor.d.ts +10 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/enable.d.ts +13 -0
- package/dist/commands/enable.d.ts.map +1 -0
- package/dist/commands/info.d.ts +9 -0
- package/dist/commands/info.d.ts.map +1 -0
- package/dist/commands/init.d.ts +8 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/install.d.ts +13 -0
- package/dist/commands/install.d.ts.map +1 -0
- package/dist/commands/link.d.ts +10 -0
- package/dist/commands/link.d.ts.map +1 -0
- package/dist/commands/list.d.ts +9 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/outdated.d.ts +9 -0
- package/dist/commands/outdated.d.ts.map +1 -0
- package/dist/commands/search.d.ts +9 -0
- package/dist/commands/search.d.ts.map +1 -0
- package/dist/commands/uninstall.d.ts +9 -0
- package/dist/commands/uninstall.d.ts.map +1 -0
- package/dist/commands/update.d.ts +9 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/why.d.ts +9 -0
- package/dist/commands/why.d.ts.map +1 -0
- package/dist/conflicts.d.ts +21 -0
- package/dist/conflicts.d.ts.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/manifest.d.ts +81 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/migrate.d.ts +9 -0
- package/dist/migrate.d.ts.map +1 -0
- package/dist/npm.d.ts +77 -0
- package/dist/npm.d.ts.map +1 -0
- package/dist/paths.d.ts +27 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/symlinks.d.ts +33 -0
- package/dist/symlinks.d.ts.map +1 -0
- package/package.json +36 -0
- package/plugins/metal-theme/README.md +13 -0
- package/plugins/metal-theme/omp.json +8 -0
- package/plugins/metal-theme/package.json +14 -0
- package/plugins/metal-theme/themes/metal.json +79 -0
- package/plugins/subagents/README.md +25 -0
- package/plugins/subagents/agents/explore.md +71 -0
- package/plugins/subagents/agents/planner.md +51 -0
- package/plugins/subagents/agents/reviewer.md +53 -0
- package/plugins/subagents/agents/task.md +46 -0
- package/plugins/subagents/commands/architect-plan.md +9 -0
- package/plugins/subagents/commands/implement-with-critic.md +10 -0
- package/plugins/subagents/commands/implement.md +10 -0
- package/plugins/subagents/omp.json +15 -0
- package/plugins/subagents/package.json +21 -0
- package/plugins/subagents/tools/task/index.ts +1019 -0
- package/scripts/bump-version.sh +52 -0
- package/scripts/publish.sh +35 -0
- package/src/cli.ts +167 -0
- package/src/commands/create.ts +153 -0
- package/src/commands/doctor.ts +217 -0
- package/src/commands/enable.ts +105 -0
- package/src/commands/info.ts +84 -0
- package/src/commands/init.ts +42 -0
- package/src/commands/install.ts +327 -0
- package/src/commands/link.ts +108 -0
- package/src/commands/list.ts +71 -0
- package/src/commands/outdated.ts +76 -0
- package/src/commands/search.ts +60 -0
- package/src/commands/uninstall.ts +73 -0
- package/src/commands/update.ts +112 -0
- package/src/commands/why.ts +105 -0
- package/src/conflicts.ts +84 -0
- package/src/index.ts +53 -0
- package/src/manifest.ts +212 -0
- package/src/migrate.ts +181 -0
- package/src/npm.ts +150 -0
- package/src/paths.ts +72 -0
- package/src/symlinks.ts +199 -0
- 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
|
+
}
|
package/src/conflicts.ts
ADDED
|
@@ -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";
|