@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.
Files changed (105) hide show
  1. package/.github/icon.png +0 -0
  2. package/.github/logo.png +0 -0
  3. package/.github/workflows/publish.yml +1 -1
  4. package/LICENSE +21 -0
  5. package/README.md +243 -138
  6. package/biome.json +1 -1
  7. package/bun.lock +59 -0
  8. package/dist/cli.js +6311 -2900
  9. package/dist/commands/config.d.ts +32 -0
  10. package/dist/commands/config.d.ts.map +1 -0
  11. package/dist/commands/create.d.ts.map +1 -1
  12. package/dist/commands/doctor.d.ts +1 -0
  13. package/dist/commands/doctor.d.ts.map +1 -1
  14. package/dist/commands/enable.d.ts +1 -0
  15. package/dist/commands/enable.d.ts.map +1 -1
  16. package/dist/commands/env.d.ts +14 -0
  17. package/dist/commands/env.d.ts.map +1 -0
  18. package/dist/commands/features.d.ts +25 -0
  19. package/dist/commands/features.d.ts.map +1 -0
  20. package/dist/commands/info.d.ts +1 -0
  21. package/dist/commands/info.d.ts.map +1 -1
  22. package/dist/commands/init.d.ts.map +1 -1
  23. package/dist/commands/install.d.ts +37 -0
  24. package/dist/commands/install.d.ts.map +1 -1
  25. package/dist/commands/link.d.ts +2 -0
  26. package/dist/commands/link.d.ts.map +1 -1
  27. package/dist/commands/list.d.ts +1 -0
  28. package/dist/commands/list.d.ts.map +1 -1
  29. package/dist/commands/outdated.d.ts +1 -0
  30. package/dist/commands/outdated.d.ts.map +1 -1
  31. package/dist/commands/search.d.ts.map +1 -1
  32. package/dist/commands/uninstall.d.ts +1 -0
  33. package/dist/commands/uninstall.d.ts.map +1 -1
  34. package/dist/commands/update.d.ts +1 -0
  35. package/dist/commands/update.d.ts.map +1 -1
  36. package/dist/commands/why.d.ts +1 -0
  37. package/dist/commands/why.d.ts.map +1 -1
  38. package/dist/conflicts.d.ts +9 -1
  39. package/dist/conflicts.d.ts.map +1 -1
  40. package/dist/errors.d.ts +8 -0
  41. package/dist/errors.d.ts.map +1 -0
  42. package/dist/index.d.ts +18 -19
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/lock.d.ts +3 -0
  45. package/dist/lock.d.ts.map +1 -0
  46. package/dist/lockfile.d.ts +52 -0
  47. package/dist/lockfile.d.ts.map +1 -0
  48. package/dist/manifest.d.ts +60 -25
  49. package/dist/manifest.d.ts.map +1 -1
  50. package/dist/npm.d.ts +14 -2
  51. package/dist/npm.d.ts.map +1 -1
  52. package/dist/paths.d.ts +34 -3
  53. package/dist/paths.d.ts.map +1 -1
  54. package/dist/runtime.d.ts +14 -0
  55. package/dist/runtime.d.ts.map +1 -0
  56. package/dist/symlinks.d.ts +43 -7
  57. package/dist/symlinks.d.ts.map +1 -1
  58. package/package.json +11 -5
  59. package/plugins/exa/README.md +153 -0
  60. package/plugins/exa/package.json +56 -0
  61. package/plugins/exa/tools/exa/company.ts +35 -0
  62. package/plugins/exa/tools/exa/index.ts +66 -0
  63. package/plugins/exa/tools/exa/linkedin.ts +35 -0
  64. package/plugins/exa/tools/exa/researcher.ts +40 -0
  65. package/plugins/exa/tools/exa/runtime.json +4 -0
  66. package/plugins/exa/tools/exa/search.ts +46 -0
  67. package/plugins/exa/tools/exa/shared.ts +230 -0
  68. package/plugins/exa/tools/exa/websets.ts +62 -0
  69. package/plugins/metal-theme/package.json +7 -2
  70. package/plugins/subagents/package.json +7 -2
  71. package/plugins/user-prompt/README.md +130 -0
  72. package/plugins/user-prompt/package.json +19 -0
  73. package/plugins/user-prompt/tools/user-prompt/index.ts +235 -0
  74. package/src/cli.ts +133 -58
  75. package/src/commands/config.ts +384 -0
  76. package/src/commands/create.ts +51 -1
  77. package/src/commands/doctor.ts +95 -7
  78. package/src/commands/enable.ts +25 -8
  79. package/src/commands/env.ts +38 -0
  80. package/src/commands/features.ts +295 -0
  81. package/src/commands/info.ts +41 -5
  82. package/src/commands/init.ts +20 -2
  83. package/src/commands/install.ts +453 -80
  84. package/src/commands/link.ts +60 -9
  85. package/src/commands/list.ts +122 -7
  86. package/src/commands/outdated.ts +17 -6
  87. package/src/commands/search.ts +20 -3
  88. package/src/commands/uninstall.ts +57 -6
  89. package/src/commands/update.ts +67 -9
  90. package/src/commands/why.ts +47 -16
  91. package/src/conflicts.ts +33 -1
  92. package/src/errors.ts +22 -0
  93. package/src/index.ts +18 -25
  94. package/src/lock.ts +46 -0
  95. package/src/lockfile.ts +132 -0
  96. package/src/manifest.ts +219 -71
  97. package/src/npm.ts +74 -18
  98. package/src/paths.ts +77 -12
  99. package/src/runtime.ts +116 -0
  100. package/src/symlinks.ts +291 -35
  101. package/tsconfig.json +7 -3
  102. package/CHECK.md +0 -352
  103. package/dist/migrate.d.ts +0 -9
  104. package/dist/migrate.d.ts.map +0 -1
  105. package/src/migrate.ts +0 -181
@@ -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.global !== false;
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
- // Re-create symlinks
39
- console.log(chalk.blue(`Enabling ${name}...`));
40
- await createPluginSymlinks(name, pkgJson, isGlobal);
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.global !== false;
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
+ }
@@ -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 && info["dist-tags"]) {
73
- console.log(chalk.dim("\ndist-tags:"));
74
- for (const [tag, version] of Object.entries(info["dist-tags"])) {
75
- console.log(chalk.dim(` ${tag}: `) + version);
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
  }
@@ -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
- import { PROJECT_PI_DIR, PROJECT_PLUGINS_JSON } from "../paths.js";
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
- console.log(chalk.red(`Error initializing project: ${(err as Error).message}`));
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
  }