@oh-my-pi/cli 0.1.0 → 0.3.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/icon.png +0 -0
- package/.github/logo.png +0 -0
- package/.github/workflows/publish.yml +1 -1
- package/LICENSE +21 -0
- package/README.md +243 -138
- package/biome.json +1 -1
- package/bun.lock +59 -0
- package/dist/cli.js +6311 -2900
- package/dist/commands/config.d.ts +32 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/enable.d.ts +1 -0
- package/dist/commands/enable.d.ts.map +1 -1
- package/dist/commands/env.d.ts +14 -0
- package/dist/commands/env.d.ts.map +1 -0
- package/dist/commands/features.d.ts +25 -0
- package/dist/commands/features.d.ts.map +1 -0
- package/dist/commands/info.d.ts +1 -0
- package/dist/commands/info.d.ts.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/install.d.ts +37 -0
- package/dist/commands/install.d.ts.map +1 -1
- package/dist/commands/link.d.ts +2 -0
- package/dist/commands/link.d.ts.map +1 -1
- package/dist/commands/list.d.ts +1 -0
- package/dist/commands/list.d.ts.map +1 -1
- package/dist/commands/outdated.d.ts +1 -0
- package/dist/commands/outdated.d.ts.map +1 -1
- package/dist/commands/search.d.ts.map +1 -1
- package/dist/commands/uninstall.d.ts +1 -0
- package/dist/commands/uninstall.d.ts.map +1 -1
- package/dist/commands/update.d.ts +1 -0
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/why.d.ts +1 -0
- package/dist/commands/why.d.ts.map +1 -1
- package/dist/conflicts.d.ts +9 -1
- package/dist/conflicts.d.ts.map +1 -1
- package/dist/errors.d.ts +8 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/index.d.ts +18 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/lock.d.ts +3 -0
- package/dist/lock.d.ts.map +1 -0
- package/dist/lockfile.d.ts +52 -0
- package/dist/lockfile.d.ts.map +1 -0
- package/dist/manifest.d.ts +60 -25
- package/dist/manifest.d.ts.map +1 -1
- package/dist/npm.d.ts +14 -2
- package/dist/npm.d.ts.map +1 -1
- package/dist/paths.d.ts +34 -3
- package/dist/paths.d.ts.map +1 -1
- package/dist/runtime.d.ts +14 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/symlinks.d.ts +43 -7
- package/dist/symlinks.d.ts.map +1 -1
- package/package.json +11 -5
- package/plugins/exa/README.md +153 -0
- package/plugins/exa/package.json +56 -0
- package/plugins/exa/tools/exa/company.ts +35 -0
- package/plugins/exa/tools/exa/index.ts +66 -0
- package/plugins/exa/tools/exa/linkedin.ts +35 -0
- package/plugins/exa/tools/exa/researcher.ts +40 -0
- package/plugins/exa/tools/exa/runtime.json +4 -0
- package/plugins/exa/tools/exa/search.ts +46 -0
- package/plugins/exa/tools/exa/shared.ts +230 -0
- package/plugins/exa/tools/exa/websets.ts +62 -0
- package/plugins/metal-theme/package.json +7 -2
- package/plugins/subagents/package.json +7 -2
- package/plugins/user-prompt/README.md +130 -0
- package/plugins/user-prompt/package.json +19 -0
- package/plugins/user-prompt/tools/user-prompt/index.ts +235 -0
- package/src/cli.ts +133 -58
- package/src/commands/config.ts +384 -0
- package/src/commands/create.ts +51 -1
- package/src/commands/doctor.ts +95 -7
- package/src/commands/enable.ts +25 -8
- package/src/commands/env.ts +38 -0
- package/src/commands/features.ts +295 -0
- package/src/commands/info.ts +41 -5
- package/src/commands/init.ts +20 -2
- package/src/commands/install.ts +453 -80
- package/src/commands/link.ts +60 -9
- package/src/commands/list.ts +122 -7
- package/src/commands/outdated.ts +17 -6
- package/src/commands/search.ts +20 -3
- package/src/commands/uninstall.ts +57 -6
- package/src/commands/update.ts +67 -9
- package/src/commands/why.ts +47 -16
- package/src/conflicts.ts +33 -1
- package/src/errors.ts +22 -0
- package/src/index.ts +18 -25
- package/src/lock.ts +46 -0
- package/src/lockfile.ts +132 -0
- package/src/manifest.ts +219 -71
- package/src/npm.ts +74 -18
- package/src/paths.ts +77 -12
- package/src/runtime.ts +116 -0
- package/src/symlinks.ts +291 -35
- package/tsconfig.json +7 -3
- package/CHECK.md +0 -352
- package/dist/migrate.d.ts +0 -9
- package/dist/migrate.d.ts.map +0 -1
- package/src/migrate.ts +0 -181
package/src/commands/enable.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { loadPluginsJson, readPluginPackageJson, savePluginsJson } from "@omp/manifest";
|
|
2
|
+
import { resolveScope } from "@omp/paths";
|
|
3
|
+
import { checkPluginSymlinks, createPluginSymlinks, removePluginSymlinks } from "@omp/symlinks";
|
|
1
4
|
import chalk from "chalk";
|
|
2
|
-
import { loadPluginsJson, readPluginPackageJson, savePluginsJson } from "../manifest.js";
|
|
3
|
-
import { createPluginSymlinks, removePluginSymlinks } from "../symlinks.js";
|
|
4
5
|
|
|
5
6
|
export interface EnableDisableOptions {
|
|
6
7
|
global?: boolean;
|
|
8
|
+
local?: boolean;
|
|
7
9
|
json?: boolean;
|
|
8
10
|
}
|
|
9
11
|
|
|
@@ -11,19 +13,21 @@ export interface EnableDisableOptions {
|
|
|
11
13
|
* Enable a disabled plugin (re-create symlinks)
|
|
12
14
|
*/
|
|
13
15
|
export async function enablePlugin(name: string, options: EnableDisableOptions = {}): Promise<void> {
|
|
14
|
-
const isGlobal = options
|
|
16
|
+
const isGlobal = resolveScope(options);
|
|
15
17
|
|
|
16
18
|
const pluginsJson = await loadPluginsJson(isGlobal);
|
|
17
19
|
|
|
18
20
|
// Check if plugin exists
|
|
19
21
|
if (!pluginsJson.plugins[name]) {
|
|
20
22
|
console.log(chalk.yellow(`Plugin "${name}" is not installed.`));
|
|
23
|
+
process.exitCode = 1;
|
|
21
24
|
return;
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
// Check if already enabled
|
|
25
28
|
if (!pluginsJson.disabled?.includes(name)) {
|
|
26
29
|
console.log(chalk.yellow(`Plugin "${name}" is already enabled.`));
|
|
30
|
+
process.exitCode = 1;
|
|
27
31
|
return;
|
|
28
32
|
}
|
|
29
33
|
|
|
@@ -32,12 +36,20 @@ export async function enablePlugin(name: string, options: EnableDisableOptions =
|
|
|
32
36
|
const pkgJson = await readPluginPackageJson(name, isGlobal);
|
|
33
37
|
if (!pkgJson) {
|
|
34
38
|
console.log(chalk.red(`Could not read package.json for ${name}`));
|
|
39
|
+
process.exitCode = 1;
|
|
35
40
|
return;
|
|
36
41
|
}
|
|
37
42
|
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
|
|
43
|
+
// Check if symlinks are already in place
|
|
44
|
+
const symlinkStatus = await checkPluginSymlinks(name, pkgJson, isGlobal);
|
|
45
|
+
|
|
46
|
+
if (symlinkStatus.valid.length > 0 && symlinkStatus.broken.length === 0 && symlinkStatus.missing.length === 0) {
|
|
47
|
+
console.log(chalk.yellow(`Plugin "${name}" symlinks are already in place.`));
|
|
48
|
+
} else {
|
|
49
|
+
// Re-create symlinks
|
|
50
|
+
console.log(chalk.blue(`Enabling ${name}...`));
|
|
51
|
+
await createPluginSymlinks(name, pkgJson, isGlobal);
|
|
52
|
+
}
|
|
41
53
|
|
|
42
54
|
// Remove from disabled list
|
|
43
55
|
pluginsJson.disabled = pluginsJson.disabled.filter((n) => n !== name);
|
|
@@ -50,6 +62,7 @@ export async function enablePlugin(name: string, options: EnableDisableOptions =
|
|
|
50
62
|
}
|
|
51
63
|
} catch (err) {
|
|
52
64
|
console.log(chalk.red(`Error enabling plugin: ${(err as Error).message}`));
|
|
65
|
+
process.exitCode = 1;
|
|
53
66
|
}
|
|
54
67
|
}
|
|
55
68
|
|
|
@@ -57,19 +70,21 @@ export async function enablePlugin(name: string, options: EnableDisableOptions =
|
|
|
57
70
|
* Disable a plugin (remove symlinks but keep installed)
|
|
58
71
|
*/
|
|
59
72
|
export async function disablePlugin(name: string, options: EnableDisableOptions = {}): Promise<void> {
|
|
60
|
-
const isGlobal = options
|
|
73
|
+
const isGlobal = resolveScope(options);
|
|
61
74
|
|
|
62
75
|
const pluginsJson = await loadPluginsJson(isGlobal);
|
|
63
76
|
|
|
64
77
|
// Check if plugin exists
|
|
65
78
|
if (!pluginsJson.plugins[name]) {
|
|
66
79
|
console.log(chalk.yellow(`Plugin "${name}" is not installed.`));
|
|
80
|
+
process.exitCode = 1;
|
|
67
81
|
return;
|
|
68
82
|
}
|
|
69
83
|
|
|
70
84
|
// Check if already disabled
|
|
71
85
|
if (pluginsJson.disabled?.includes(name)) {
|
|
72
86
|
console.log(chalk.yellow(`Plugin "${name}" is already disabled.`));
|
|
87
|
+
process.exitCode = 1;
|
|
73
88
|
return;
|
|
74
89
|
}
|
|
75
90
|
|
|
@@ -78,12 +93,13 @@ export async function disablePlugin(name: string, options: EnableDisableOptions
|
|
|
78
93
|
const pkgJson = await readPluginPackageJson(name, isGlobal);
|
|
79
94
|
if (!pkgJson) {
|
|
80
95
|
console.log(chalk.red(`Could not read package.json for ${name}`));
|
|
96
|
+
process.exitCode = 1;
|
|
81
97
|
return;
|
|
82
98
|
}
|
|
83
99
|
|
|
84
100
|
// Remove symlinks
|
|
85
101
|
console.log(chalk.blue(`Disabling ${name}...`));
|
|
86
|
-
await removePluginSymlinks(name, pkgJson);
|
|
102
|
+
await removePluginSymlinks(name, pkgJson, isGlobal);
|
|
87
103
|
|
|
88
104
|
// Add to disabled list
|
|
89
105
|
if (!pluginsJson.disabled) {
|
|
@@ -101,5 +117,6 @@ export async function disablePlugin(name: string, options: EnableDisableOptions
|
|
|
101
117
|
}
|
|
102
118
|
} catch (err) {
|
|
103
119
|
console.log(chalk.red(`Error disabling plugin: ${(err as Error).message}`));
|
|
120
|
+
process.exitCode = 1;
|
|
104
121
|
}
|
|
105
122
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { resolveScope } from "@omp/paths";
|
|
2
|
+
import { generateEnvScript, getEnvJson } from "@omp/runtime";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
|
|
5
|
+
export interface EnvOptions {
|
|
6
|
+
global?: boolean;
|
|
7
|
+
local?: boolean;
|
|
8
|
+
json?: boolean;
|
|
9
|
+
fish?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Output environment variables for plugins
|
|
14
|
+
* omp env # Print shell exports (sh/bash/zsh)
|
|
15
|
+
* omp env --fish # Print fish shell syntax
|
|
16
|
+
* omp env --json # Print as JSON
|
|
17
|
+
*/
|
|
18
|
+
export async function envCommand(options: EnvOptions = {}): Promise<void> {
|
|
19
|
+
const isGlobal = resolveScope(options);
|
|
20
|
+
|
|
21
|
+
if (options.json) {
|
|
22
|
+
const vars = await getEnvJson(isGlobal);
|
|
23
|
+
console.log(JSON.stringify(vars, null, 2));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const shell = options.fish ? "fish" : "sh";
|
|
28
|
+
const script = await generateEnvScript(isGlobal, shell);
|
|
29
|
+
|
|
30
|
+
if (script.length === 0) {
|
|
31
|
+
console.error(chalk.yellow("No environment variables configured."));
|
|
32
|
+
console.error(chalk.dim("Set variables with: omp config <plugin> <variable> <value>"));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Output script directly (for eval or sourcing)
|
|
37
|
+
console.log(script);
|
|
38
|
+
}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { checkbox } from "@inquirer/prompts";
|
|
2
|
+
import { loadPluginsJson, readPluginPackageJson, savePluginsJson } from "@omp/manifest";
|
|
3
|
+
import { resolveScope } from "@omp/paths";
|
|
4
|
+
import { getDefaultFeatures, getRuntimeConfigPath, readRuntimeConfig, writeRuntimeConfig } from "@omp/symlinks";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
|
|
7
|
+
export interface FeaturesOptions {
|
|
8
|
+
global?: boolean;
|
|
9
|
+
local?: boolean;
|
|
10
|
+
json?: boolean;
|
|
11
|
+
enable?: string[];
|
|
12
|
+
disable?: string[];
|
|
13
|
+
set?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Interactive feature selection for a plugin
|
|
18
|
+
* omp features @oh-my-pi/exa
|
|
19
|
+
*/
|
|
20
|
+
export async function interactiveFeatures(name: string, options: FeaturesOptions = {}): Promise<void> {
|
|
21
|
+
const isGlobal = resolveScope(options);
|
|
22
|
+
const pluginsJson = await loadPluginsJson(isGlobal);
|
|
23
|
+
|
|
24
|
+
// Check if plugin exists
|
|
25
|
+
if (!pluginsJson.plugins[name]) {
|
|
26
|
+
console.log(chalk.yellow(`Plugin "${name}" is not installed.`));
|
|
27
|
+
process.exitCode = 1;
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const pkgJson = await readPluginPackageJson(name, isGlobal);
|
|
32
|
+
if (!pkgJson) {
|
|
33
|
+
console.log(chalk.red(`Could not read package.json for ${name}`));
|
|
34
|
+
process.exitCode = 1;
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const features = pkgJson.omp?.features;
|
|
39
|
+
if (!features || Object.keys(features).length === 0) {
|
|
40
|
+
console.log(chalk.yellow(`Plugin "${name}" has no configurable features.`));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Get runtime config path and current enabled features
|
|
45
|
+
const runtimePath = getRuntimeConfigPath(pkgJson, isGlobal);
|
|
46
|
+
if (!runtimePath) {
|
|
47
|
+
console.log(chalk.yellow(`Plugin "${name}" does not have a runtime.json config file.`));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const runtimeConfig = readRuntimeConfig(runtimePath);
|
|
52
|
+
const enabledFeatures = runtimeConfig.features ?? getDefaultFeatures(features);
|
|
53
|
+
|
|
54
|
+
// JSON output mode - just list
|
|
55
|
+
if (options.json) {
|
|
56
|
+
console.log(
|
|
57
|
+
JSON.stringify(
|
|
58
|
+
{
|
|
59
|
+
plugin: name,
|
|
60
|
+
features: Object.entries(features).map(([fname, fdef]) => ({
|
|
61
|
+
name: fname,
|
|
62
|
+
enabled: enabledFeatures.includes(fname),
|
|
63
|
+
default: fdef.default !== false,
|
|
64
|
+
description: fdef.description,
|
|
65
|
+
variables: fdef.variables ? Object.keys(fdef.variables) : [],
|
|
66
|
+
})),
|
|
67
|
+
},
|
|
68
|
+
null,
|
|
69
|
+
2,
|
|
70
|
+
),
|
|
71
|
+
);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Non-interactive mode - just list
|
|
76
|
+
if (!process.stdout.isTTY || !process.stdin.isTTY) {
|
|
77
|
+
listFeaturesNonInteractive(name, features, enabledFeatures);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Interactive mode - checkbox prompt
|
|
82
|
+
console.log(chalk.bold(`\nConfigure features for ${name}:\n`));
|
|
83
|
+
|
|
84
|
+
const choices = Object.entries(features).map(([fname, fdef]) => {
|
|
85
|
+
const optIn = fdef.default === false ? chalk.dim(" (opt-in)") : "";
|
|
86
|
+
const desc = fdef.description ? chalk.dim(` - ${fdef.description}`) : "";
|
|
87
|
+
return {
|
|
88
|
+
name: `${fname}${optIn}${desc}`,
|
|
89
|
+
value: fname,
|
|
90
|
+
checked: enabledFeatures.includes(fname),
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const selected = await checkbox({
|
|
96
|
+
message: "Select features to enable:",
|
|
97
|
+
choices,
|
|
98
|
+
pageSize: 15,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Apply changes
|
|
102
|
+
await applyFeatureChanges(name, runtimePath, features, enabledFeatures, selected);
|
|
103
|
+
} catch (err) {
|
|
104
|
+
// User cancelled (Ctrl+C)
|
|
105
|
+
console.log(chalk.dim("\nCancelled."));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Non-interactive feature listing
|
|
111
|
+
*/
|
|
112
|
+
function listFeaturesNonInteractive(
|
|
113
|
+
name: string,
|
|
114
|
+
features: Record<string, { description?: string; default?: boolean; variables?: Record<string, unknown> }>,
|
|
115
|
+
enabledFeatures: string[],
|
|
116
|
+
): void {
|
|
117
|
+
console.log(chalk.bold(`\nFeatures for ${name}:\n`));
|
|
118
|
+
|
|
119
|
+
for (const [fname, fdef] of Object.entries(features)) {
|
|
120
|
+
const isEnabled = enabledFeatures.includes(fname);
|
|
121
|
+
const icon = isEnabled ? chalk.green("✓") : chalk.gray("○");
|
|
122
|
+
const defaultStr = fdef.default === false ? chalk.dim(" (opt-in)") : "";
|
|
123
|
+
|
|
124
|
+
console.log(`${icon} ${chalk.bold(fname)}${defaultStr}`);
|
|
125
|
+
if (fdef.description) {
|
|
126
|
+
console.log(chalk.dim(` ${fdef.description}`));
|
|
127
|
+
}
|
|
128
|
+
if (fdef.variables && Object.keys(fdef.variables).length > 0) {
|
|
129
|
+
console.log(chalk.dim(` Variables: ${Object.keys(fdef.variables).join(", ")}`));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
console.log();
|
|
134
|
+
console.log(chalk.dim(`Configure with: omp features ${name} --enable <feature> --disable <feature>`));
|
|
135
|
+
console.log(chalk.dim(`Or set exactly: omp features ${name} --set feature1,feature2`));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Apply feature changes - update both plugins.json (for config/env) and runtime.json (for runtime)
|
|
140
|
+
*/
|
|
141
|
+
async function applyFeatureChanges(
|
|
142
|
+
name: string,
|
|
143
|
+
runtimePath: string,
|
|
144
|
+
features: Record<string, { description?: string; default?: boolean; variables?: Record<string, unknown> }>,
|
|
145
|
+
currentlyEnabled: string[],
|
|
146
|
+
newEnabled: string[],
|
|
147
|
+
isGlobal: boolean,
|
|
148
|
+
): Promise<void> {
|
|
149
|
+
// Compute what changed
|
|
150
|
+
const toDisable = currentlyEnabled.filter((f) => !newEnabled.includes(f));
|
|
151
|
+
const toEnable = newEnabled.filter((f) => !currentlyEnabled.includes(f));
|
|
152
|
+
|
|
153
|
+
if (toDisable.length === 0 && toEnable.length === 0) {
|
|
154
|
+
console.log(chalk.yellow("\nNo changes to feature configuration."));
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
console.log(chalk.blue(`\nApplying changes...`));
|
|
159
|
+
|
|
160
|
+
if (toDisable.length > 0) {
|
|
161
|
+
console.log(chalk.dim(` Disabling: ${toDisable.join(", ")}`));
|
|
162
|
+
}
|
|
163
|
+
if (toEnable.length > 0) {
|
|
164
|
+
console.log(chalk.dim(` Enabling: ${toEnable.join(", ")}`));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Update plugins.json (source of truth for config/env commands)
|
|
168
|
+
const pluginsJson = await loadPluginsJson(isGlobal);
|
|
169
|
+
if (!pluginsJson.config) pluginsJson.config = {};
|
|
170
|
+
if (!pluginsJson.config[name]) pluginsJson.config[name] = {};
|
|
171
|
+
pluginsJson.config[name].features = newEnabled;
|
|
172
|
+
await savePluginsJson(pluginsJson, isGlobal);
|
|
173
|
+
|
|
174
|
+
// Also write to runtime.json (for runtime feature detection)
|
|
175
|
+
await writeRuntimeConfig(runtimePath, { features: newEnabled });
|
|
176
|
+
|
|
177
|
+
console.log(chalk.green(`\n✓ Features updated`));
|
|
178
|
+
if (newEnabled.length > 0) {
|
|
179
|
+
console.log(chalk.dim(` Enabled: ${newEnabled.join(", ")}`));
|
|
180
|
+
} else {
|
|
181
|
+
console.log(chalk.dim(` Enabled: none`));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Configure features for an installed plugin via CLI flags
|
|
187
|
+
* omp features @oh-my-pi/exa --enable websets --disable search
|
|
188
|
+
* omp features @oh-my-pi/exa --set search,websets
|
|
189
|
+
*/
|
|
190
|
+
export async function configureFeatures(name: string, options: FeaturesOptions = {}): Promise<void> {
|
|
191
|
+
const isGlobal = resolveScope(options);
|
|
192
|
+
const pluginsJson = await loadPluginsJson(isGlobal);
|
|
193
|
+
|
|
194
|
+
// Check if plugin exists
|
|
195
|
+
if (!pluginsJson.plugins[name]) {
|
|
196
|
+
console.log(chalk.yellow(`Plugin "${name}" is not installed.`));
|
|
197
|
+
process.exitCode = 1;
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const pkgJson = await readPluginPackageJson(name, isGlobal);
|
|
202
|
+
if (!pkgJson) {
|
|
203
|
+
console.log(chalk.red(`Could not read package.json for ${name}`));
|
|
204
|
+
process.exitCode = 1;
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const features = pkgJson.omp?.features;
|
|
209
|
+
if (!features || Object.keys(features).length === 0) {
|
|
210
|
+
console.log(chalk.yellow(`Plugin "${name}" has no configurable features.`));
|
|
211
|
+
process.exitCode = 1;
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const allFeatureNames = Object.keys(features);
|
|
216
|
+
|
|
217
|
+
// Get runtime config
|
|
218
|
+
const runtimePath = getRuntimeConfigPath(pkgJson, isGlobal);
|
|
219
|
+
if (!runtimePath) {
|
|
220
|
+
console.log(chalk.yellow(`Plugin "${name}" does not have a runtime.json config file.`));
|
|
221
|
+
process.exitCode = 1;
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const runtimeConfig = readRuntimeConfig(runtimePath);
|
|
226
|
+
const currentlyEnabled = runtimeConfig.features ?? getDefaultFeatures(features);
|
|
227
|
+
|
|
228
|
+
let newEnabled: string[];
|
|
229
|
+
|
|
230
|
+
// Handle --set (explicit list)
|
|
231
|
+
if (options.set !== undefined) {
|
|
232
|
+
if (options.set === "*") {
|
|
233
|
+
newEnabled = allFeatureNames;
|
|
234
|
+
} else if (options.set === "") {
|
|
235
|
+
newEnabled = [];
|
|
236
|
+
} else {
|
|
237
|
+
newEnabled = options.set.split(",").map((f) => f.trim()).filter(Boolean);
|
|
238
|
+
// Validate
|
|
239
|
+
for (const f of newEnabled) {
|
|
240
|
+
if (!features[f]) {
|
|
241
|
+
console.log(chalk.red(`Unknown feature "${f}". Available: ${allFeatureNames.join(", ")}`));
|
|
242
|
+
process.exitCode = 1;
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
} else {
|
|
248
|
+
// Handle --enable and --disable
|
|
249
|
+
newEnabled = [...currentlyEnabled];
|
|
250
|
+
|
|
251
|
+
if (options.enable) {
|
|
252
|
+
for (const f of options.enable) {
|
|
253
|
+
if (!features[f]) {
|
|
254
|
+
console.log(chalk.red(`Unknown feature "${f}". Available: ${allFeatureNames.join(", ")}`));
|
|
255
|
+
process.exitCode = 1;
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (!newEnabled.includes(f)) {
|
|
259
|
+
newEnabled.push(f);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (options.disable) {
|
|
265
|
+
for (const f of options.disable) {
|
|
266
|
+
if (!features[f]) {
|
|
267
|
+
console.log(chalk.red(`Unknown feature "${f}". Available: ${allFeatureNames.join(", ")}`));
|
|
268
|
+
process.exitCode = 1;
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
newEnabled = newEnabled.filter((e) => e !== f);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
await applyFeatureChanges(name, runtimePath, features, currentlyEnabled, newEnabled);
|
|
277
|
+
|
|
278
|
+
if (options.json) {
|
|
279
|
+
console.log(JSON.stringify({ plugin: name, enabled: newEnabled }, null, 2));
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Main features command handler
|
|
285
|
+
* Routes to interactive or configure based on options
|
|
286
|
+
*/
|
|
287
|
+
export async function featuresCommand(name: string, options: FeaturesOptions = {}): Promise<void> {
|
|
288
|
+
// If any modification options are passed, configure via CLI
|
|
289
|
+
if (options.enable || options.disable || options.set !== undefined) {
|
|
290
|
+
await configureFeatures(name, options);
|
|
291
|
+
} else {
|
|
292
|
+
// Otherwise, show interactive UI (or list in non-TTY mode)
|
|
293
|
+
await interactiveFeatures(name, options);
|
|
294
|
+
}
|
|
295
|
+
}
|
package/src/commands/info.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { npmInfo } from "@omp/npm";
|
|
1
2
|
import chalk from "chalk";
|
|
2
|
-
import { npmInfo } from "../npm.js";
|
|
3
3
|
|
|
4
4
|
export interface InfoOptions {
|
|
5
5
|
json?: boolean;
|
|
6
6
|
versions?: boolean;
|
|
7
|
+
allVersions?: boolean;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
/**
|
|
@@ -17,6 +18,7 @@ export async function showInfo(packageName: string, options: InfoOptions = {}):
|
|
|
17
18
|
|
|
18
19
|
if (!info) {
|
|
19
20
|
console.log(chalk.red(`Package not found: ${packageName}`));
|
|
21
|
+
process.exitCode = 1;
|
|
20
22
|
return;
|
|
21
23
|
}
|
|
22
24
|
|
|
@@ -59,6 +61,14 @@ export async function showInfo(packageName: string, options: InfoOptions = {}):
|
|
|
59
61
|
console.log(chalk.dim("keywords: ") + info.keywords.join(", "));
|
|
60
62
|
}
|
|
61
63
|
|
|
64
|
+
// Dependencies
|
|
65
|
+
if (info.dependencies && Object.keys(info.dependencies).length > 0) {
|
|
66
|
+
console.log(chalk.dim("\ndependencies:"));
|
|
67
|
+
for (const [depName, depVersion] of Object.entries(info.dependencies)) {
|
|
68
|
+
console.log(chalk.dim(` ${depName}: ${depVersion}`));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
62
72
|
// Is it an omp plugin?
|
|
63
73
|
const isOmpPlugin = info.keywords?.includes("omp-plugin");
|
|
64
74
|
if (isOmpPlugin) {
|
|
@@ -68,11 +78,36 @@ export async function showInfo(packageName: string, options: InfoOptions = {}):
|
|
|
68
78
|
console.log(chalk.dim(" It may work, but might not have omp.install configuration"));
|
|
69
79
|
}
|
|
70
80
|
|
|
81
|
+
// Show what files will be installed
|
|
82
|
+
if (info.omp?.install?.length) {
|
|
83
|
+
console.log(chalk.dim("\nFiles to install:"));
|
|
84
|
+
for (const entry of info.omp.install) {
|
|
85
|
+
console.log(chalk.dim(` ${entry.src} → ${entry.dest}`));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
71
89
|
// Versions
|
|
72
|
-
if (options.versions
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
90
|
+
if (options.versions || options.allVersions) {
|
|
91
|
+
if (info["dist-tags"]) {
|
|
92
|
+
console.log(chalk.dim("\ndist-tags:"));
|
|
93
|
+
for (const [tag, version] of Object.entries(info["dist-tags"])) {
|
|
94
|
+
console.log(chalk.dim(` ${tag}: `) + version);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (info.versions?.length) {
|
|
99
|
+
console.log(chalk.dim("\nall versions:"));
|
|
100
|
+
if (options.allVersions) {
|
|
101
|
+
// Show all versions
|
|
102
|
+
console.log(chalk.dim(` ${info.versions.join(", ")}`));
|
|
103
|
+
} else {
|
|
104
|
+
// Show last 10
|
|
105
|
+
const versionsToShow = info.versions.slice(-10);
|
|
106
|
+
console.log(chalk.dim(` ${versionsToShow.join(", ")}`));
|
|
107
|
+
if (info.versions.length > 10) {
|
|
108
|
+
console.log(chalk.dim(` ... and ${info.versions.length - 10} more (use --all-versions to see all)`));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
76
111
|
}
|
|
77
112
|
}
|
|
78
113
|
|
|
@@ -80,5 +115,6 @@ export async function showInfo(packageName: string, options: InfoOptions = {}):
|
|
|
80
115
|
console.log(chalk.dim(`Install with: omp install ${packageName}`));
|
|
81
116
|
} catch (err) {
|
|
82
117
|
console.log(chalk.red(`Error fetching info: ${(err as Error).message}`));
|
|
118
|
+
process.exitCode = 1;
|
|
83
119
|
}
|
|
84
120
|
}
|
package/src/commands/init.ts
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import { PROJECT_PI_DIR, PROJECT_PLUGINS_JSON } from "@omp/paths";
|
|
3
4
|
import chalk from "chalk";
|
|
4
|
-
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Format permission-related errors with actionable guidance
|
|
8
|
+
*/
|
|
9
|
+
function formatPermissionError(err: NodeJS.ErrnoException, path: string): string {
|
|
10
|
+
if (err.code === "EACCES" || err.code === "EPERM") {
|
|
11
|
+
return `Permission denied: Cannot write to ${path}. Check directory permissions or run with appropriate privileges.`;
|
|
12
|
+
}
|
|
13
|
+
return err.message;
|
|
14
|
+
}
|
|
5
15
|
|
|
6
16
|
export interface InitOptions {
|
|
7
17
|
force?: boolean;
|
|
@@ -15,6 +25,7 @@ export async function initProject(options: InitOptions = {}): Promise<void> {
|
|
|
15
25
|
if (existsSync(PROJECT_PLUGINS_JSON) && !options.force) {
|
|
16
26
|
console.log(chalk.yellow(`${PROJECT_PLUGINS_JSON} already exists.`));
|
|
17
27
|
console.log(chalk.dim("Use --force to overwrite"));
|
|
28
|
+
process.exitCode = 1;
|
|
18
29
|
return;
|
|
19
30
|
}
|
|
20
31
|
|
|
@@ -37,6 +48,13 @@ export async function initProject(options: InitOptions = {}): Promise<void> {
|
|
|
37
48
|
console.log(chalk.dim(" 2. Or edit plugins.json directly"));
|
|
38
49
|
console.log(chalk.dim(" 3. Run: omp install (to install all)"));
|
|
39
50
|
} catch (err) {
|
|
40
|
-
|
|
51
|
+
const error = err as NodeJS.ErrnoException;
|
|
52
|
+
if (error.code === "EACCES" || error.code === "EPERM") {
|
|
53
|
+
console.log(chalk.red(formatPermissionError(error, PROJECT_PI_DIR)));
|
|
54
|
+
console.log(chalk.dim(" Check directory permissions or run with appropriate privileges."));
|
|
55
|
+
} else {
|
|
56
|
+
console.log(chalk.red(`Error initializing project: ${error.message}`));
|
|
57
|
+
}
|
|
58
|
+
process.exitCode = 1;
|
|
41
59
|
}
|
|
42
60
|
}
|