@oh-my-pi/cli 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +79 -84
- package/dist/cli.js +5025 -1016
- package/dist/commands/config.d.ts +27 -0
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/env.d.ts.map +1 -1
- package/dist/commands/features.d.ts.map +1 -1
- package/dist/commands/info.d.ts.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/install.d.ts +6 -0
- package/dist/commands/install.d.ts.map +1 -1
- package/dist/commands/link.d.ts +1 -0
- package/dist/commands/link.d.ts.map +1 -1
- package/dist/commands/list.d.ts.map +1 -1
- package/dist/commands/outdated.d.ts.map +1 -1
- package/dist/commands/search.d.ts.map +1 -1
- package/dist/commands/uninstall.d.ts +3 -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.map +1 -1
- package/dist/conflicts.d.ts +7 -2
- package/dist/conflicts.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/lock.d.ts.map +1 -1
- package/dist/lockfile.d.ts +24 -3
- package/dist/lockfile.d.ts.map +1 -1
- package/dist/manifest.d.ts +12 -1
- package/dist/manifest.d.ts.map +1 -1
- package/dist/npm.d.ts +11 -0
- package/dist/npm.d.ts.map +1 -1
- package/dist/output.d.ts +51 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/paths.d.ts +5 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/progress.d.ts +78 -0
- package/dist/progress.d.ts.map +1 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/symlinks.d.ts +1 -0
- package/dist/symlinks.d.ts.map +1 -1
- package/package.json +24 -10
- package/.github/icon.png +0 -0
- package/.github/logo.png +0 -0
- package/.github/workflows/ci.yml +0 -32
- package/.github/workflows/publish.yml +0 -42
- package/biome.json +0 -29
- package/bun.lock +0 -109
- package/plugins/exa/README.md +0 -153
- package/plugins/exa/package.json +0 -56
- package/plugins/exa/tools/exa/company.ts +0 -35
- package/plugins/exa/tools/exa/index.ts +0 -66
- package/plugins/exa/tools/exa/linkedin.ts +0 -35
- package/plugins/exa/tools/exa/researcher.ts +0 -40
- package/plugins/exa/tools/exa/runtime.json +0 -4
- package/plugins/exa/tools/exa/search.ts +0 -46
- package/plugins/exa/tools/exa/shared.ts +0 -230
- package/plugins/exa/tools/exa/websets.ts +0 -62
- package/plugins/metal-theme/README.md +0 -13
- package/plugins/metal-theme/omp.json +0 -8
- package/plugins/metal-theme/package.json +0 -19
- package/plugins/metal-theme/themes/metal.json +0 -79
- package/plugins/subagents/README.md +0 -25
- package/plugins/subagents/agents/explore.md +0 -71
- package/plugins/subagents/agents/planner.md +0 -51
- package/plugins/subagents/agents/reviewer.md +0 -53
- package/plugins/subagents/agents/task.md +0 -46
- package/plugins/subagents/commands/architect-plan.md +0 -9
- package/plugins/subagents/commands/implement-with-critic.md +0 -10
- package/plugins/subagents/commands/implement.md +0 -10
- package/plugins/subagents/omp.json +0 -15
- package/plugins/subagents/package.json +0 -26
- package/plugins/subagents/tools/task/index.ts +0 -1019
- package/plugins/user-prompt/README.md +0 -130
- package/plugins/user-prompt/package.json +0 -19
- package/plugins/user-prompt/tools/user-prompt/index.ts +0 -235
- package/scripts/bump-version.sh +0 -52
- package/scripts/publish.sh +0 -35
- package/src/cli.ts +0 -242
- package/src/commands/config.ts +0 -384
- package/src/commands/create.ts +0 -203
- package/src/commands/doctor.ts +0 -305
- package/src/commands/enable.ts +0 -122
- package/src/commands/env.ts +0 -38
- package/src/commands/features.ts +0 -295
- package/src/commands/info.ts +0 -120
- package/src/commands/init.ts +0 -60
- package/src/commands/install.ts +0 -700
- package/src/commands/link.ts +0 -159
- package/src/commands/list.ts +0 -186
- package/src/commands/outdated.ts +0 -87
- package/src/commands/search.ts +0 -77
- package/src/commands/uninstall.ts +0 -124
- package/src/commands/update.ts +0 -170
- package/src/commands/why.ts +0 -136
- package/src/conflicts.ts +0 -116
- package/src/errors.ts +0 -22
- package/src/index.ts +0 -46
- package/src/lock.ts +0 -46
- package/src/lockfile.ts +0 -132
- package/src/manifest.ts +0 -360
- package/src/npm.ts +0 -206
- package/src/paths.ts +0 -137
- package/src/runtime.ts +0 -116
- package/src/symlinks.ts +0 -455
- package/tsconfig.json +0 -28
package/src/manifest.ts
DELETED
|
@@ -1,360 +0,0 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
4
|
-
import {
|
|
5
|
-
GLOBAL_PACKAGE_JSON,
|
|
6
|
-
NODE_MODULES_DIR,
|
|
7
|
-
PLUGINS_DIR,
|
|
8
|
-
PROJECT_PACKAGE_JSON,
|
|
9
|
-
PROJECT_PLUGINS_JSON,
|
|
10
|
-
} from "@omp/paths";
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Format permission-related errors with actionable guidance
|
|
14
|
-
*/
|
|
15
|
-
function formatPermissionError(err: NodeJS.ErrnoException, path: string): string {
|
|
16
|
-
if (err.code === "EACCES" || err.code === "EPERM") {
|
|
17
|
-
return `Permission denied: Cannot write to ${path}. Check directory permissions or run with appropriate privileges.`;
|
|
18
|
-
}
|
|
19
|
-
return err.message;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* OMP field in package.json - defines what files to install
|
|
24
|
-
*/
|
|
25
|
-
export interface OmpInstallEntry {
|
|
26
|
-
src: string;
|
|
27
|
-
dest: string;
|
|
28
|
-
/** If true, this file is copied (not symlinked) and can be edited by omp */
|
|
29
|
-
copy?: boolean;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Runtime configuration stored in plugin's runtime.json
|
|
34
|
-
* This file is copied (not symlinked) and edited by omp features/config commands
|
|
35
|
-
*/
|
|
36
|
-
export interface PluginRuntimeConfig {
|
|
37
|
-
features?: string[];
|
|
38
|
-
options?: Record<string, unknown>;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Runtime variable definition with type, default, and metadata
|
|
43
|
-
*/
|
|
44
|
-
export interface OmpVariable {
|
|
45
|
-
type: "string" | "number" | "boolean" | "string[]";
|
|
46
|
-
default?: string | number | boolean | string[];
|
|
47
|
-
description?: string;
|
|
48
|
-
required?: boolean;
|
|
49
|
-
/** Environment variable name if injected as env (e.g., "EXA_API_KEY") */
|
|
50
|
-
env?: string;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Feature definition - metadata only, no install entries
|
|
55
|
-
* All feature files are always installed; runtime.json controls which are active
|
|
56
|
-
*/
|
|
57
|
-
export interface OmpFeature {
|
|
58
|
-
description?: string;
|
|
59
|
-
/** Runtime variables specific to this feature */
|
|
60
|
-
variables?: Record<string, OmpVariable>;
|
|
61
|
-
/** Default enabled state (default: true) */
|
|
62
|
-
default?: boolean;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export interface OmpField {
|
|
66
|
-
/** Top-level install entries (always installed, not feature-gated) */
|
|
67
|
-
install?: OmpInstallEntry[];
|
|
68
|
-
/** Top-level runtime variables (always available) */
|
|
69
|
-
variables?: Record<string, OmpVariable>;
|
|
70
|
-
/** Named features with their own install entries and variables */
|
|
71
|
-
features?: Record<string, OmpFeature>;
|
|
72
|
-
/** Disabled state (managed by omp, not plugin author) */
|
|
73
|
-
disabled?: boolean;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Package.json structure for plugins
|
|
78
|
-
*/
|
|
79
|
-
export interface PluginPackageJson {
|
|
80
|
-
name: string;
|
|
81
|
-
version: string;
|
|
82
|
-
description?: string;
|
|
83
|
-
keywords?: string[];
|
|
84
|
-
omp?: OmpField;
|
|
85
|
-
dependencies?: Record<string, string>;
|
|
86
|
-
devDependencies?: Record<string, string>;
|
|
87
|
-
files?: string[];
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Per-plugin configuration stored in plugins.json
|
|
92
|
-
*/
|
|
93
|
-
export interface PluginConfig {
|
|
94
|
-
/**
|
|
95
|
-
* Enabled feature names:
|
|
96
|
-
* - null/undefined: use plugin defaults (first install = all, reinstall = preserve)
|
|
97
|
-
* - ["*"]: explicitly all features
|
|
98
|
-
* - []: no optional features (core only)
|
|
99
|
-
* - ["f1", "f2"]: specific features
|
|
100
|
-
*/
|
|
101
|
-
features?: string[] | null;
|
|
102
|
-
/** Runtime variable overrides */
|
|
103
|
-
variables?: Record<string, string | number | boolean | string[]>;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Global/project plugins.json structure
|
|
108
|
-
*/
|
|
109
|
-
export interface PluginsJson {
|
|
110
|
-
plugins: Record<string, string>; // name -> version specifier
|
|
111
|
-
devDependencies?: Record<string, string>; // dev dependencies
|
|
112
|
-
disabled?: string[]; // disabled plugin names
|
|
113
|
-
/** Per-plugin feature and variable config */
|
|
114
|
-
config?: Record<string, PluginConfig>;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Initialize the global plugins directory with package.json
|
|
119
|
-
*/
|
|
120
|
-
export async function initGlobalPlugins(): Promise<void> {
|
|
121
|
-
try {
|
|
122
|
-
await mkdir(PLUGINS_DIR, { recursive: true });
|
|
123
|
-
|
|
124
|
-
if (!existsSync(GLOBAL_PACKAGE_JSON)) {
|
|
125
|
-
const packageJson = {
|
|
126
|
-
name: "pi-plugins",
|
|
127
|
-
version: "1.0.0",
|
|
128
|
-
private: true,
|
|
129
|
-
description: "Global pi plugins managed by omp",
|
|
130
|
-
dependencies: {},
|
|
131
|
-
};
|
|
132
|
-
await writeFile(GLOBAL_PACKAGE_JSON, JSON.stringify(packageJson, null, 2));
|
|
133
|
-
}
|
|
134
|
-
} catch (err) {
|
|
135
|
-
const error = err as NodeJS.ErrnoException;
|
|
136
|
-
if (error.code === "EACCES" || error.code === "EPERM") {
|
|
137
|
-
throw new Error(formatPermissionError(error, PLUGINS_DIR));
|
|
138
|
-
}
|
|
139
|
-
throw err;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Initialize the project-local .pi directory with plugins.json and package.json
|
|
145
|
-
*/
|
|
146
|
-
export async function initProjectPlugins(): Promise<void> {
|
|
147
|
-
const PROJECT_PI_DIR = dirname(PROJECT_PLUGINS_JSON);
|
|
148
|
-
try {
|
|
149
|
-
await mkdir(PROJECT_PI_DIR, { recursive: true });
|
|
150
|
-
|
|
151
|
-
// Create plugins.json if it doesn't exist
|
|
152
|
-
if (!existsSync(PROJECT_PLUGINS_JSON)) {
|
|
153
|
-
const pluginsJson = {
|
|
154
|
-
plugins: {},
|
|
155
|
-
};
|
|
156
|
-
await writeFile(PROJECT_PLUGINS_JSON, JSON.stringify(pluginsJson, null, 2));
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Create package.json if it doesn't exist (for npm operations)
|
|
160
|
-
if (!existsSync(PROJECT_PACKAGE_JSON)) {
|
|
161
|
-
const packageJson = {
|
|
162
|
-
name: "pi-project-plugins",
|
|
163
|
-
version: "1.0.0",
|
|
164
|
-
private: true,
|
|
165
|
-
description: "Project-local pi plugins managed by omp",
|
|
166
|
-
dependencies: {},
|
|
167
|
-
};
|
|
168
|
-
await writeFile(PROJECT_PACKAGE_JSON, JSON.stringify(packageJson, null, 2));
|
|
169
|
-
}
|
|
170
|
-
} catch (err) {
|
|
171
|
-
const error = err as NodeJS.ErrnoException;
|
|
172
|
-
if (error.code === "EACCES" || error.code === "EPERM") {
|
|
173
|
-
throw new Error(formatPermissionError(error, PROJECT_PI_DIR));
|
|
174
|
-
}
|
|
175
|
-
throw err;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Load plugins.json (global or project)
|
|
181
|
-
*/
|
|
182
|
-
export async function loadPluginsJson(global = true): Promise<PluginsJson> {
|
|
183
|
-
const path = global ? GLOBAL_PACKAGE_JSON : PROJECT_PLUGINS_JSON;
|
|
184
|
-
|
|
185
|
-
try {
|
|
186
|
-
const data = await readFile(path, "utf-8");
|
|
187
|
-
const parsed = JSON.parse(data);
|
|
188
|
-
|
|
189
|
-
if (global) {
|
|
190
|
-
// Global uses standard package.json format
|
|
191
|
-
return {
|
|
192
|
-
plugins: parsed.dependencies || {},
|
|
193
|
-
devDependencies: parsed.devDependencies || {},
|
|
194
|
-
disabled: parsed.omp?.disabled || [],
|
|
195
|
-
config: parsed.omp?.config || {},
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Project uses plugins.json format
|
|
200
|
-
return {
|
|
201
|
-
plugins: parsed.plugins || {},
|
|
202
|
-
devDependencies: parsed.devDependencies || {},
|
|
203
|
-
disabled: parsed.disabled || [],
|
|
204
|
-
config: parsed.config || {},
|
|
205
|
-
};
|
|
206
|
-
} catch (err) {
|
|
207
|
-
if ((err as NodeJS.ErrnoException).code === "ENOENT") {
|
|
208
|
-
return { plugins: {}, devDependencies: {}, disabled: [], config: {} };
|
|
209
|
-
}
|
|
210
|
-
throw err;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Sync .pi/package.json with plugins.json for npm operations in project-local mode
|
|
216
|
-
*/
|
|
217
|
-
async function syncProjectPackageJson(data: PluginsJson): Promise<void> {
|
|
218
|
-
let existing: Record<string, unknown> = {};
|
|
219
|
-
try {
|
|
220
|
-
existing = JSON.parse(await readFile(PROJECT_PACKAGE_JSON, "utf-8"));
|
|
221
|
-
} catch {
|
|
222
|
-
existing = {
|
|
223
|
-
name: "pi-project-plugins",
|
|
224
|
-
version: "1.0.0",
|
|
225
|
-
private: true,
|
|
226
|
-
description: "Project-local pi plugins managed by omp",
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
existing.dependencies = data.plugins;
|
|
231
|
-
if (data.devDependencies && Object.keys(data.devDependencies).length > 0) {
|
|
232
|
-
existing.devDependencies = data.devDependencies;
|
|
233
|
-
} else {
|
|
234
|
-
delete existing.devDependencies;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
await writeFile(PROJECT_PACKAGE_JSON, JSON.stringify(existing, null, 2));
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Save plugins.json (global or project)
|
|
242
|
-
*/
|
|
243
|
-
export async function savePluginsJson(data: PluginsJson, global = true): Promise<void> {
|
|
244
|
-
const path = global ? GLOBAL_PACKAGE_JSON : PROJECT_PLUGINS_JSON;
|
|
245
|
-
|
|
246
|
-
try {
|
|
247
|
-
await mkdir(dirname(path), { recursive: true });
|
|
248
|
-
|
|
249
|
-
if (global) {
|
|
250
|
-
// Read existing package.json and update dependencies
|
|
251
|
-
let existing: Record<string, unknown> = {};
|
|
252
|
-
try {
|
|
253
|
-
existing = JSON.parse(await readFile(path, "utf-8"));
|
|
254
|
-
} catch {
|
|
255
|
-
existing = {
|
|
256
|
-
name: "pi-plugins",
|
|
257
|
-
version: "1.0.0",
|
|
258
|
-
private: true,
|
|
259
|
-
description: "Global pi plugins managed by omp",
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
existing.dependencies = data.plugins;
|
|
264
|
-
if (data.devDependencies && Object.keys(data.devDependencies).length > 0) {
|
|
265
|
-
existing.devDependencies = data.devDependencies;
|
|
266
|
-
} else {
|
|
267
|
-
delete existing.devDependencies;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// Build omp field with disabled and config
|
|
271
|
-
const ompField: Record<string, unknown> = (existing.omp as Record<string, unknown>) || {};
|
|
272
|
-
if (data.disabled?.length) {
|
|
273
|
-
ompField.disabled = data.disabled;
|
|
274
|
-
} else {
|
|
275
|
-
delete ompField.disabled;
|
|
276
|
-
}
|
|
277
|
-
if (data.config && Object.keys(data.config).length > 0) {
|
|
278
|
-
ompField.config = data.config;
|
|
279
|
-
} else {
|
|
280
|
-
delete ompField.config;
|
|
281
|
-
}
|
|
282
|
-
if (Object.keys(ompField).length > 0) {
|
|
283
|
-
existing.omp = ompField;
|
|
284
|
-
} else {
|
|
285
|
-
delete existing.omp;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
await writeFile(path, JSON.stringify(existing, null, 2));
|
|
289
|
-
} else {
|
|
290
|
-
// Project uses simple plugins.json format
|
|
291
|
-
const output: Record<string, unknown> = { plugins: data.plugins };
|
|
292
|
-
if (data.devDependencies && Object.keys(data.devDependencies).length > 0) {
|
|
293
|
-
output.devDependencies = data.devDependencies;
|
|
294
|
-
}
|
|
295
|
-
if (data.disabled?.length) {
|
|
296
|
-
output.disabled = data.disabled;
|
|
297
|
-
}
|
|
298
|
-
if (data.config && Object.keys(data.config).length > 0) {
|
|
299
|
-
output.config = data.config;
|
|
300
|
-
}
|
|
301
|
-
await writeFile(path, JSON.stringify(output, null, 2));
|
|
302
|
-
|
|
303
|
-
// Sync .pi/package.json for npm operations
|
|
304
|
-
await syncProjectPackageJson(data);
|
|
305
|
-
}
|
|
306
|
-
} catch (err) {
|
|
307
|
-
const error = err as NodeJS.ErrnoException;
|
|
308
|
-
if (error.code === "EACCES" || error.code === "EPERM") {
|
|
309
|
-
throw new Error(formatPermissionError(error, path));
|
|
310
|
-
}
|
|
311
|
-
throw err;
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
/**
|
|
316
|
-
* Read a plugin's package.json from node_modules
|
|
317
|
-
*/
|
|
318
|
-
export async function readPluginPackageJson(pluginName: string, global = true): Promise<PluginPackageJson | null> {
|
|
319
|
-
const nodeModules = global ? NODE_MODULES_DIR : ".pi/node_modules";
|
|
320
|
-
let pkgPath: string;
|
|
321
|
-
|
|
322
|
-
// Handle scoped packages
|
|
323
|
-
if (pluginName.startsWith("@")) {
|
|
324
|
-
pkgPath = join(nodeModules, pluginName, "package.json");
|
|
325
|
-
} else {
|
|
326
|
-
pkgPath = join(nodeModules, pluginName, "package.json");
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
try {
|
|
330
|
-
const data = await readFile(pkgPath, "utf-8");
|
|
331
|
-
return JSON.parse(data) as PluginPackageJson;
|
|
332
|
-
} catch {
|
|
333
|
-
return null;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
/**
|
|
338
|
-
* Get the source directory for a plugin in node_modules
|
|
339
|
-
*/
|
|
340
|
-
export function getPluginSourceDir(pluginName: string, global = true): string {
|
|
341
|
-
const nodeModules = global ? NODE_MODULES_DIR : ".pi/node_modules";
|
|
342
|
-
return join(nodeModules, pluginName);
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
/**
|
|
346
|
-
* Get all installed plugins with their info
|
|
347
|
-
*/
|
|
348
|
-
export async function getInstalledPlugins(global = true): Promise<Map<string, PluginPackageJson>> {
|
|
349
|
-
const pluginsJson = await loadPluginsJson(global);
|
|
350
|
-
const plugins = new Map<string, PluginPackageJson>();
|
|
351
|
-
|
|
352
|
-
for (const name of Object.keys(pluginsJson.plugins)) {
|
|
353
|
-
const pkgJson = await readPluginPackageJson(name, global);
|
|
354
|
-
if (pkgJson) {
|
|
355
|
-
plugins.set(name, pkgJson);
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
return plugins;
|
|
360
|
-
}
|
package/src/npm.ts
DELETED
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
import { execFileSync } from "node:child_process";
|
|
2
|
-
import type { OmpField } from "@omp/manifest";
|
|
3
|
-
|
|
4
|
-
export interface NpmAvailability {
|
|
5
|
-
available: boolean;
|
|
6
|
-
version?: string;
|
|
7
|
-
error?: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Check npm availability and version
|
|
12
|
-
*/
|
|
13
|
-
export function checkNpmAvailable(): NpmAvailability {
|
|
14
|
-
try {
|
|
15
|
-
const version = execFileSync("npm", ["--version"], {
|
|
16
|
-
encoding: "utf-8",
|
|
17
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
18
|
-
}).trim();
|
|
19
|
-
|
|
20
|
-
// Parse version and check minimum (npm 7+)
|
|
21
|
-
const major = parseInt(version.split(".")[0], 10);
|
|
22
|
-
if (major < 7) {
|
|
23
|
-
return {
|
|
24
|
-
available: false,
|
|
25
|
-
version,
|
|
26
|
-
error: `npm version ${version} is too old. Please upgrade to npm 7 or later.`,
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return { available: true, version };
|
|
31
|
-
} catch {
|
|
32
|
-
return {
|
|
33
|
-
available: false,
|
|
34
|
-
error: "npm is not installed or not in PATH. Please install Node.js/npm.",
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface NpmPackageInfo {
|
|
40
|
-
name: string;
|
|
41
|
-
version: string;
|
|
42
|
-
description?: string;
|
|
43
|
-
keywords?: string[];
|
|
44
|
-
author?: string | { name: string; email?: string };
|
|
45
|
-
homepage?: string;
|
|
46
|
-
repository?: { type: string; url: string } | string;
|
|
47
|
-
versions?: string[];
|
|
48
|
-
"dist-tags"?: Record<string, string>;
|
|
49
|
-
omp?: OmpField;
|
|
50
|
-
dependencies?: Record<string, string>;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export interface NpmSearchResult {
|
|
54
|
-
name: string;
|
|
55
|
-
version: string;
|
|
56
|
-
description?: string;
|
|
57
|
-
keywords?: string[];
|
|
58
|
-
date?: string;
|
|
59
|
-
author?: { name: string };
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const DEFAULT_TIMEOUT_MS = 60000; // 60 seconds
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Execute npm command and return output
|
|
66
|
-
*/
|
|
67
|
-
export function npmExec(args: string[], cwd?: string, timeout = DEFAULT_TIMEOUT_MS): string {
|
|
68
|
-
try {
|
|
69
|
-
return execFileSync("npm", args, {
|
|
70
|
-
cwd,
|
|
71
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
72
|
-
encoding: "utf-8",
|
|
73
|
-
timeout,
|
|
74
|
-
});
|
|
75
|
-
} catch (err) {
|
|
76
|
-
const error = err as { killed?: boolean; code?: string; message: string };
|
|
77
|
-
if (error.killed || error.code === "ETIMEDOUT") {
|
|
78
|
-
throw new Error(`npm operation timed out after ${timeout / 1000} seconds`);
|
|
79
|
-
}
|
|
80
|
-
throw err;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Execute npm command with prefix (for installing to specific directory)
|
|
86
|
-
*/
|
|
87
|
-
export function npmExecWithPrefix(args: string[], prefix: string, timeout = DEFAULT_TIMEOUT_MS): string {
|
|
88
|
-
try {
|
|
89
|
-
return execFileSync("npm", ["--prefix", prefix, ...args], {
|
|
90
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
91
|
-
encoding: "utf-8",
|
|
92
|
-
timeout,
|
|
93
|
-
});
|
|
94
|
-
} catch (err) {
|
|
95
|
-
const error = err as { killed?: boolean; code?: string; message: string };
|
|
96
|
-
if (error.killed || error.code === "ETIMEDOUT") {
|
|
97
|
-
throw new Error(`npm operation timed out after ${timeout / 1000} seconds`);
|
|
98
|
-
}
|
|
99
|
-
throw err;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Install packages using npm
|
|
105
|
-
*/
|
|
106
|
-
export async function npmInstall(
|
|
107
|
-
packages: string[],
|
|
108
|
-
prefix: string,
|
|
109
|
-
options: { save?: boolean; saveDev?: boolean } = {},
|
|
110
|
-
): Promise<void> {
|
|
111
|
-
const args = ["install"];
|
|
112
|
-
|
|
113
|
-
if (options.save) {
|
|
114
|
-
args.push("--save");
|
|
115
|
-
} else if (options.saveDev) {
|
|
116
|
-
args.push("--save-dev");
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
args.push(...packages);
|
|
120
|
-
|
|
121
|
-
npmExecWithPrefix(args, prefix);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Uninstall packages using npm
|
|
126
|
-
*/
|
|
127
|
-
export async function npmUninstall(packages: string[], prefix: string): Promise<void> {
|
|
128
|
-
npmExecWithPrefix(["uninstall", ...packages], prefix);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Get package info from npm registry
|
|
133
|
-
*/
|
|
134
|
-
export async function npmInfo(packageName: string): Promise<NpmPackageInfo | null> {
|
|
135
|
-
try {
|
|
136
|
-
const output = npmExec(["info", packageName, "--json"]);
|
|
137
|
-
return JSON.parse(output);
|
|
138
|
-
} catch {
|
|
139
|
-
return null;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Search npm for packages with a keyword
|
|
145
|
-
*/
|
|
146
|
-
export async function npmSearch(query: string, keyword = "omp-plugin"): Promise<NpmSearchResult[]> {
|
|
147
|
-
// Search for packages with the omp-plugin keyword
|
|
148
|
-
const searchTerm = keyword ? `keywords:${keyword} ${query}` : query;
|
|
149
|
-
const output = npmExec(["search", searchTerm, "--json"]);
|
|
150
|
-
return JSON.parse(output);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Check for outdated packages
|
|
155
|
-
*/
|
|
156
|
-
export async function npmOutdated(
|
|
157
|
-
prefix: string,
|
|
158
|
-
): Promise<Record<string, { current: string; wanted: string; latest: string }>> {
|
|
159
|
-
try {
|
|
160
|
-
const output = npmExecWithPrefix(["outdated", "--json"], prefix);
|
|
161
|
-
return JSON.parse(output);
|
|
162
|
-
} catch (err) {
|
|
163
|
-
// npm outdated exits with code 1 if there are outdated packages
|
|
164
|
-
const error = err as { stdout?: string };
|
|
165
|
-
if (error.stdout) {
|
|
166
|
-
try {
|
|
167
|
-
return JSON.parse(error.stdout);
|
|
168
|
-
} catch {
|
|
169
|
-
return {};
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
return {};
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Update packages using npm
|
|
178
|
-
*/
|
|
179
|
-
export async function npmUpdate(packages: string[], prefix: string): Promise<void> {
|
|
180
|
-
const args = ["update"];
|
|
181
|
-
if (packages.length > 0) {
|
|
182
|
-
args.push(...packages);
|
|
183
|
-
}
|
|
184
|
-
npmExecWithPrefix(args, prefix);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Get list of installed packages
|
|
189
|
-
*/
|
|
190
|
-
export async function npmList(prefix: string): Promise<Record<string, { version: string }>> {
|
|
191
|
-
try {
|
|
192
|
-
const output = npmExecWithPrefix(["list", "--json", "--depth=0"], prefix);
|
|
193
|
-
const parsed = JSON.parse(output);
|
|
194
|
-
return parsed.dependencies || {};
|
|
195
|
-
} catch {
|
|
196
|
-
return {};
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Resolve a package version from the registry
|
|
202
|
-
*/
|
|
203
|
-
export async function resolveVersion(packageName: string, versionRange = "latest"): Promise<string | null> {
|
|
204
|
-
const info = await npmInfo(`${packageName}@${versionRange}`);
|
|
205
|
-
return info?.version || null;
|
|
206
|
-
}
|
package/src/paths.ts
DELETED
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
|
-
import { homedir } from "node:os";
|
|
3
|
-
import { dirname, join, resolve } from "node:path";
|
|
4
|
-
|
|
5
|
-
// Global pi configuration directory
|
|
6
|
-
export const PI_CONFIG_DIR = join(homedir(), ".pi");
|
|
7
|
-
|
|
8
|
-
// Global plugins directory
|
|
9
|
-
export const PLUGINS_DIR = join(PI_CONFIG_DIR, "plugins");
|
|
10
|
-
|
|
11
|
-
// npm node_modules within plugins directory
|
|
12
|
-
export const NODE_MODULES_DIR = join(PLUGINS_DIR, "node_modules");
|
|
13
|
-
|
|
14
|
-
// Global package.json for plugin management
|
|
15
|
-
export const GLOBAL_PACKAGE_JSON = join(PLUGINS_DIR, "package.json");
|
|
16
|
-
|
|
17
|
-
// Global package-lock.json
|
|
18
|
-
export const GLOBAL_LOCK_FILE = join(PLUGINS_DIR, "package-lock.json");
|
|
19
|
-
|
|
20
|
-
// Project-local config directory
|
|
21
|
-
export const PROJECT_PI_DIR = ".pi";
|
|
22
|
-
|
|
23
|
-
// Project-local plugins.json
|
|
24
|
-
export const PROJECT_PLUGINS_JSON = join(PROJECT_PI_DIR, "plugins.json");
|
|
25
|
-
|
|
26
|
-
// Project-local package.json (for npm operations)
|
|
27
|
-
export const PROJECT_PACKAGE_JSON = join(PROJECT_PI_DIR, "package.json");
|
|
28
|
-
|
|
29
|
-
// Project-local lock file
|
|
30
|
-
export const PROJECT_PLUGINS_LOCK = join(PROJECT_PI_DIR, "plugins-lock.json");
|
|
31
|
-
|
|
32
|
-
// Project-local node_modules
|
|
33
|
-
export const PROJECT_NODE_MODULES = join(PROJECT_PI_DIR, "node_modules");
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Find the project root by walking up parent directories looking for .pi/plugins.json.
|
|
37
|
-
* Similar to how git finds .git directories.
|
|
38
|
-
*
|
|
39
|
-
* @returns The absolute path to the project root, or null if not found
|
|
40
|
-
*/
|
|
41
|
-
export function findProjectRoot(): string | null {
|
|
42
|
-
let dir = process.cwd();
|
|
43
|
-
const root = resolve("/");
|
|
44
|
-
|
|
45
|
-
while (dir !== root) {
|
|
46
|
-
if (existsSync(join(dir, ".pi", "plugins.json"))) {
|
|
47
|
-
return dir;
|
|
48
|
-
}
|
|
49
|
-
const parent = dirname(dir);
|
|
50
|
-
if (parent === dir) break; // Reached filesystem root
|
|
51
|
-
dir = parent;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Check if a project-local .pi/plugins.json exists in the current directory or any parent
|
|
59
|
-
*/
|
|
60
|
-
export function hasProjectPlugins(): boolean {
|
|
61
|
-
return findProjectRoot() !== null;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Get the project .pi directory path.
|
|
66
|
-
* Uses findProjectRoot() to locate the project, or falls back to cwd.
|
|
67
|
-
*/
|
|
68
|
-
export function getProjectPiDir(): string {
|
|
69
|
-
const projectRoot = findProjectRoot();
|
|
70
|
-
if (projectRoot) {
|
|
71
|
-
return join(projectRoot, ".pi");
|
|
72
|
-
}
|
|
73
|
-
// Fallback to cwd (e.g., for init command)
|
|
74
|
-
return resolve(PROJECT_PI_DIR);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Get the plugins directory for the given scope
|
|
79
|
-
*/
|
|
80
|
-
export function getPluginsDir(global = true): string {
|
|
81
|
-
if (global) {
|
|
82
|
-
return PLUGINS_DIR;
|
|
83
|
-
}
|
|
84
|
-
return getProjectPiDir();
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Get the node_modules directory for the given scope
|
|
89
|
-
*/
|
|
90
|
-
export function getNodeModulesDir(global = true): string {
|
|
91
|
-
if (global) {
|
|
92
|
-
return NODE_MODULES_DIR;
|
|
93
|
-
}
|
|
94
|
-
return join(getProjectPiDir(), "node_modules");
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Get the package.json path for the given scope
|
|
99
|
-
*/
|
|
100
|
-
export function getPackageJsonPath(global = true): string {
|
|
101
|
-
if (global) {
|
|
102
|
-
return GLOBAL_PACKAGE_JSON;
|
|
103
|
-
}
|
|
104
|
-
return join(getProjectPiDir(), "plugins.json");
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Get the agent directory (where symlinks are installed)
|
|
109
|
-
*/
|
|
110
|
-
export function getAgentDir(global = true): string {
|
|
111
|
-
if (global) {
|
|
112
|
-
return join(PI_CONFIG_DIR, "agent");
|
|
113
|
-
}
|
|
114
|
-
return join(getProjectPiDir(), "agent");
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Resolve whether to use global or local scope based on CLI flags and auto-detection.
|
|
119
|
-
*
|
|
120
|
-
* Logic:
|
|
121
|
-
* - If --global is passed: use global mode
|
|
122
|
-
* - If --local is passed: use local mode
|
|
123
|
-
* - If neither: check if .pi/plugins.json exists in cwd, if so use local, otherwise use global
|
|
124
|
-
*
|
|
125
|
-
* @param options - CLI options containing global and local flags
|
|
126
|
-
* @returns true if global scope should be used, false for local
|
|
127
|
-
*/
|
|
128
|
-
export function resolveScope(options: { global?: boolean; local?: boolean }): boolean {
|
|
129
|
-
if (options.global) {
|
|
130
|
-
return true;
|
|
131
|
-
}
|
|
132
|
-
if (options.local) {
|
|
133
|
-
return false;
|
|
134
|
-
}
|
|
135
|
-
// Auto-detect: if project-local plugins.json exists, use local mode
|
|
136
|
-
return !hasProjectPlugins();
|
|
137
|
-
}
|