@oh-my-pi/pi-coding-agent 2.2.1337 → 3.0.1337
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/CHANGELOG.md +64 -34
- package/README.md +100 -100
- package/docs/compaction.md +8 -8
- package/docs/config-usage.md +113 -0
- package/docs/custom-tools.md +8 -8
- package/docs/extension-loading.md +58 -58
- package/docs/hooks.md +11 -11
- package/docs/rpc.md +4 -4
- package/docs/sdk.md +14 -14
- package/docs/session-tree-plan.md +1 -1
- package/docs/session.md +2 -2
- package/docs/skills.md +16 -16
- package/docs/theme.md +9 -9
- package/docs/tui.md +1 -1
- package/examples/README.md +1 -1
- package/examples/custom-tools/README.md +4 -4
- package/examples/custom-tools/subagent/README.md +13 -13
- package/examples/custom-tools/subagent/agents.ts +2 -2
- package/examples/custom-tools/subagent/index.ts +5 -5
- package/examples/hooks/README.md +3 -3
- package/examples/hooks/auto-commit-on-exit.ts +1 -1
- package/examples/hooks/custom-compaction.ts +1 -1
- package/examples/sdk/01-minimal.ts +1 -1
- package/examples/sdk/04-skills.ts +1 -1
- package/examples/sdk/05-tools.ts +1 -1
- package/examples/sdk/08-slash-commands.ts +1 -1
- package/examples/sdk/09-api-keys-and-oauth.ts +2 -2
- package/examples/sdk/README.md +2 -2
- package/package.json +16 -12
- package/src/capability/context-file.ts +40 -0
- package/src/capability/extension.ts +48 -0
- package/src/capability/hook.ts +40 -0
- package/src/capability/index.ts +616 -0
- package/src/capability/instruction.ts +37 -0
- package/src/capability/mcp.ts +52 -0
- package/src/capability/prompt.ts +35 -0
- package/src/capability/rule.ts +52 -0
- package/src/capability/settings.ts +35 -0
- package/src/capability/skill.ts +49 -0
- package/src/capability/slash-command.ts +40 -0
- package/src/capability/system-prompt.ts +35 -0
- package/src/capability/tool.ts +38 -0
- package/src/capability/types.ts +166 -0
- package/src/cli/args.ts +2 -2
- package/src/cli/plugin-cli.ts +24 -19
- package/src/cli/update-cli.ts +10 -10
- package/src/config.ts +290 -6
- package/src/core/auth-storage.ts +32 -9
- package/src/core/bash-executor.ts +1 -1
- package/src/core/custom-commands/loader.ts +44 -50
- package/src/core/custom-tools/index.ts +1 -0
- package/src/core/custom-tools/loader.ts +67 -69
- package/src/core/custom-tools/types.ts +10 -1
- package/src/core/export-html/index.ts +9 -9
- package/src/core/export-html/template.generated.ts +2 -0
- package/src/core/hooks/loader.ts +13 -42
- package/src/core/index.ts +0 -1
- package/src/core/logger.ts +7 -7
- package/src/core/mcp/client.ts +1 -1
- package/src/core/mcp/config.ts +94 -146
- package/src/core/mcp/index.ts +0 -4
- package/src/core/mcp/loader.ts +26 -22
- package/src/core/mcp/manager.ts +18 -23
- package/src/core/mcp/tool-bridge.ts +9 -1
- package/src/core/mcp/types.ts +2 -0
- package/src/core/model-registry.ts +25 -8
- package/src/core/plugins/installer.ts +1 -1
- package/src/core/plugins/loader.ts +17 -11
- package/src/core/plugins/manager.ts +2 -2
- package/src/core/plugins/paths.ts +12 -7
- package/src/core/plugins/types.ts +3 -3
- package/src/core/sdk.ts +48 -27
- package/src/core/session-manager.ts +4 -4
- package/src/core/settings-manager.ts +45 -21
- package/src/core/skills.ts +222 -293
- package/src/core/slash-commands.ts +34 -165
- package/src/core/system-prompt.ts +58 -65
- package/src/core/timings.ts +2 -2
- package/src/core/tools/lsp/config.ts +38 -17
- package/src/core/tools/task/artifacts.ts +1 -1
- package/src/core/tools/task/commands.ts +30 -107
- package/src/core/tools/task/discovery.ts +54 -66
- package/src/core/tools/task/executor.ts +9 -9
- package/src/core/tools/task/index.ts +10 -10
- package/src/core/tools/task/model-resolver.ts +27 -25
- package/src/core/tools/task/types.ts +2 -2
- package/src/core/tools/web-fetch.ts +3 -3
- package/src/core/tools/web-search/auth.ts +40 -34
- package/src/core/tools/web-search/index.ts +1 -1
- package/src/core/tools/web-search/providers/anthropic.ts +1 -1
- package/src/discovery/agents-md.ts +75 -0
- package/src/discovery/builtin.ts +646 -0
- package/src/discovery/claude.ts +623 -0
- package/src/discovery/cline.ts +102 -0
- package/src/discovery/codex.ts +571 -0
- package/src/discovery/cursor.ts +264 -0
- package/src/discovery/gemini.ts +368 -0
- package/src/discovery/github.ts +120 -0
- package/src/discovery/helpers.test.ts +127 -0
- package/src/discovery/helpers.ts +249 -0
- package/src/discovery/index.ts +84 -0
- package/src/discovery/mcp-json.ts +127 -0
- package/src/discovery/vscode.ts +99 -0
- package/src/discovery/windsurf.ts +216 -0
- package/src/main.ts +14 -13
- package/src/migrations.ts +24 -3
- package/src/modes/interactive/components/hook-editor.ts +1 -1
- package/src/modes/interactive/components/plugin-settings.ts +1 -1
- package/src/modes/interactive/components/settings-defs.ts +38 -2
- package/src/modes/interactive/components/settings-selector.ts +1 -0
- package/src/modes/interactive/components/welcome.ts +2 -2
- package/src/modes/interactive/interactive-mode.ts +211 -16
- package/src/modes/interactive/theme/theme-schema.json +1 -1
- package/src/utils/clipboard.ts +1 -1
- package/src/utils/shell-snapshot.ts +2 -2
- package/src/utils/shell.ts +7 -7
package/src/config.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
1
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
4
|
|
|
@@ -9,12 +9,20 @@ import packageJson from "../package.json" with { type: "json" };
|
|
|
9
9
|
// App Config (from embedded package.json)
|
|
10
10
|
// =============================================================================
|
|
11
11
|
|
|
12
|
-
export const APP_NAME: string = (packageJson as {
|
|
12
|
+
export const APP_NAME: string = (packageJson as { ompConfig?: { name?: string } }).ompConfig?.name || "omp";
|
|
13
13
|
export const CONFIG_DIR_NAME: string =
|
|
14
|
-
(packageJson as {
|
|
14
|
+
(packageJson as { ompConfig?: { configDir?: string } }).ompConfig?.configDir || ".omp";
|
|
15
15
|
export const VERSION: string = (packageJson as { version: string }).version;
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
const priorityList = [
|
|
18
|
+
{ dir: ".omp", globalAgentDir: ".omp/agent" },
|
|
19
|
+
{ dir: ".pi", globalAgentDir: ".pi/agent" },
|
|
20
|
+
{ dir: ".claude" },
|
|
21
|
+
{ dir: ".codex" },
|
|
22
|
+
{ dir: ".gemini" },
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
// e.g., OMP_CODING_AGENT_DIR
|
|
18
26
|
export const ENV_AGENT_DIR = `${APP_NAME.toUpperCase()}_CODING_AGENT_DIR`;
|
|
19
27
|
|
|
20
28
|
// =============================================================================
|
|
@@ -58,10 +66,10 @@ export function getChangelogPath(): string {
|
|
|
58
66
|
}
|
|
59
67
|
|
|
60
68
|
// =============================================================================
|
|
61
|
-
// User Config Paths (~/.
|
|
69
|
+
// User Config Paths (~/.omp/agent/*)
|
|
62
70
|
// =============================================================================
|
|
63
71
|
|
|
64
|
-
/** Get the agent config directory (e.g., ~/.
|
|
72
|
+
/** Get the agent config directory (e.g., ~/.omp/agent/) */
|
|
65
73
|
export function getAgentDir(): string {
|
|
66
74
|
return process.env[ENV_AGENT_DIR] || join(homedir(), CONFIG_DIR_NAME, "agent");
|
|
67
75
|
}
|
|
@@ -105,3 +113,279 @@ export function getSessionsDir(): string {
|
|
|
105
113
|
export function getDebugLogPath(): string {
|
|
106
114
|
return join(getAgentDir(), `${APP_NAME}-debug.log`);
|
|
107
115
|
}
|
|
116
|
+
|
|
117
|
+
// =============================================================================
|
|
118
|
+
// Multi-Config Directory Helpers
|
|
119
|
+
// =============================================================================
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Config directory bases in priority order (highest first).
|
|
123
|
+
* User-level: ~/.omp/agent, ~/.pi/agent, ~/.claude, ~/.codex, ~/.gemini
|
|
124
|
+
* Project-level: .omp, .pi, .claude, .codex, .gemini
|
|
125
|
+
*/
|
|
126
|
+
const USER_CONFIG_BASES = priorityList.map(({ dir, globalAgentDir }) => ({
|
|
127
|
+
base: () => join(homedir(), globalAgentDir ?? dir),
|
|
128
|
+
name: dir,
|
|
129
|
+
}));
|
|
130
|
+
|
|
131
|
+
const PROJECT_CONFIG_BASES = priorityList.map(({ dir }) => ({
|
|
132
|
+
base: dir,
|
|
133
|
+
name: dir,
|
|
134
|
+
}));
|
|
135
|
+
|
|
136
|
+
export interface ConfigDirEntry {
|
|
137
|
+
path: string;
|
|
138
|
+
source: string; // e.g., ".omp", ".pi", ".claude"
|
|
139
|
+
level: "user" | "project";
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface GetConfigDirsOptions {
|
|
143
|
+
/** Include user-level directories (~/.omp/agent/...). Default: true */
|
|
144
|
+
user?: boolean;
|
|
145
|
+
/** Include project-level directories (.omp/...). Default: true */
|
|
146
|
+
project?: boolean;
|
|
147
|
+
/** Current working directory for project paths. Default: process.cwd() */
|
|
148
|
+
cwd?: string;
|
|
149
|
+
/** Only return directories that exist. Default: false */
|
|
150
|
+
existingOnly?: boolean;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Get all config directories for a subpath, ordered by priority (highest first).
|
|
155
|
+
*
|
|
156
|
+
* @param subpath - Subpath within config dirs (e.g., "commands", "hooks", "agents")
|
|
157
|
+
* @param options - Options for filtering
|
|
158
|
+
* @returns Array of directory entries, highest priority first
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* // Get all command directories
|
|
162
|
+
* getConfigDirs("commands")
|
|
163
|
+
* // → [{ path: "~/.omp/agent/commands", source: ".omp", level: "user" }, ...]
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* // Get only existing project skill directories
|
|
167
|
+
* getConfigDirs("skills", { user: false, existingOnly: true })
|
|
168
|
+
*/
|
|
169
|
+
export function getConfigDirs(subpath: string, options: GetConfigDirsOptions = {}): ConfigDirEntry[] {
|
|
170
|
+
const { user = true, project = true, cwd = process.cwd(), existingOnly = false } = options;
|
|
171
|
+
const results: ConfigDirEntry[] = [];
|
|
172
|
+
|
|
173
|
+
// User-level directories (highest priority)
|
|
174
|
+
if (user) {
|
|
175
|
+
for (const { base, name } of USER_CONFIG_BASES) {
|
|
176
|
+
const path = join(base(), subpath);
|
|
177
|
+
if (!existingOnly || existsSync(path)) {
|
|
178
|
+
results.push({ path, source: name, level: "user" });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Project-level directories
|
|
184
|
+
if (project) {
|
|
185
|
+
for (const { base, name } of PROJECT_CONFIG_BASES) {
|
|
186
|
+
const path = resolve(cwd, base, subpath);
|
|
187
|
+
if (!existingOnly || existsSync(path)) {
|
|
188
|
+
results.push({ path, source: name, level: "project" });
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return results;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Get all config directory paths for a subpath (convenience wrapper).
|
|
198
|
+
* Returns just the paths, highest priority first.
|
|
199
|
+
*/
|
|
200
|
+
export function getConfigDirPaths(subpath: string, options: GetConfigDirsOptions = {}): string[] {
|
|
201
|
+
return getConfigDirs(subpath, options).map((e) => e.path);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export interface ConfigFileResult<T> {
|
|
205
|
+
path: string;
|
|
206
|
+
source: string;
|
|
207
|
+
level: "user" | "project";
|
|
208
|
+
content: T;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Read the first existing config file from priority-ordered locations.
|
|
213
|
+
*
|
|
214
|
+
* @param subpath - Subpath within config dirs (e.g., "settings.json", "models.json")
|
|
215
|
+
* @param options - Options for filtering (same as getConfigDirs)
|
|
216
|
+
* @returns The parsed content and metadata, or undefined if not found
|
|
217
|
+
*
|
|
218
|
+
* @example
|
|
219
|
+
* const result = readConfigFile<Settings>("settings.json", { project: false });
|
|
220
|
+
* if (result) {
|
|
221
|
+
* console.log(`Loaded from ${result.path}`);
|
|
222
|
+
* console.log(result.content);
|
|
223
|
+
* }
|
|
224
|
+
*/
|
|
225
|
+
export function readConfigFile<T = unknown>(
|
|
226
|
+
subpath: string,
|
|
227
|
+
options: GetConfigDirsOptions = {},
|
|
228
|
+
): ConfigFileResult<T> | undefined {
|
|
229
|
+
const dirs = getConfigDirs("", { ...options, existingOnly: false });
|
|
230
|
+
|
|
231
|
+
for (const { path: base, source, level } of dirs) {
|
|
232
|
+
const filePath = join(base, subpath);
|
|
233
|
+
if (existsSync(filePath)) {
|
|
234
|
+
try {
|
|
235
|
+
const content = readFileSync(filePath, "utf-8");
|
|
236
|
+
return {
|
|
237
|
+
path: filePath,
|
|
238
|
+
source,
|
|
239
|
+
level,
|
|
240
|
+
content: JSON.parse(content) as T,
|
|
241
|
+
};
|
|
242
|
+
} catch {
|
|
243
|
+
// Continue to next file on parse error
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return undefined;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Get all existing config files for a subpath (for merging scenarios).
|
|
253
|
+
* Returns in priority order (highest first).
|
|
254
|
+
*/
|
|
255
|
+
export function readAllConfigFiles<T = unknown>(
|
|
256
|
+
subpath: string,
|
|
257
|
+
options: GetConfigDirsOptions = {},
|
|
258
|
+
): ConfigFileResult<T>[] {
|
|
259
|
+
const dirs = getConfigDirs("", { ...options, existingOnly: false });
|
|
260
|
+
const results: ConfigFileResult<T>[] = [];
|
|
261
|
+
|
|
262
|
+
for (const { path: base, source, level } of dirs) {
|
|
263
|
+
const filePath = join(base, subpath);
|
|
264
|
+
if (existsSync(filePath)) {
|
|
265
|
+
try {
|
|
266
|
+
const content = readFileSync(filePath, "utf-8");
|
|
267
|
+
results.push({
|
|
268
|
+
path: filePath,
|
|
269
|
+
source,
|
|
270
|
+
level,
|
|
271
|
+
content: JSON.parse(content) as T,
|
|
272
|
+
});
|
|
273
|
+
} catch {
|
|
274
|
+
// Skip files that fail to parse
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return results;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Find the first existing config file (for non-JSON files like SYSTEM.md).
|
|
284
|
+
* Returns just the path, or undefined if not found.
|
|
285
|
+
*/
|
|
286
|
+
export function findConfigFile(subpath: string, options: GetConfigDirsOptions = {}): string | undefined {
|
|
287
|
+
const dirs = getConfigDirs("", { ...options, existingOnly: false });
|
|
288
|
+
|
|
289
|
+
for (const { path: base } of dirs) {
|
|
290
|
+
const filePath = join(base, subpath);
|
|
291
|
+
if (existsSync(filePath)) {
|
|
292
|
+
return filePath;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return undefined;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Find the first existing config file with metadata.
|
|
301
|
+
*/
|
|
302
|
+
export function findConfigFileWithMeta(
|
|
303
|
+
subpath: string,
|
|
304
|
+
options: GetConfigDirsOptions = {},
|
|
305
|
+
): Omit<ConfigFileResult<never>, "content"> | undefined {
|
|
306
|
+
const dirs = getConfigDirs("", { ...options, existingOnly: false });
|
|
307
|
+
|
|
308
|
+
for (const { path: base, source, level } of dirs) {
|
|
309
|
+
const filePath = join(base, subpath);
|
|
310
|
+
if (existsSync(filePath)) {
|
|
311
|
+
return { path: filePath, source, level };
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return undefined;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// =============================================================================
|
|
319
|
+
// Walk-Up Config Discovery (for monorepo scenarios)
|
|
320
|
+
// =============================================================================
|
|
321
|
+
|
|
322
|
+
function isDirectory(p: string): boolean {
|
|
323
|
+
try {
|
|
324
|
+
return statSync(p).isDirectory();
|
|
325
|
+
} catch {
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Find nearest config directory by walking up from cwd.
|
|
332
|
+
* Checks all config bases (.omp, .pi, .claude) at each level.
|
|
333
|
+
*
|
|
334
|
+
* @param subpath - Subpath within config dirs (e.g., "commands", "agents")
|
|
335
|
+
* @param cwd - Starting directory
|
|
336
|
+
* @returns First existing directory found, or undefined
|
|
337
|
+
*/
|
|
338
|
+
export function findNearestProjectConfigDir(subpath: string, cwd: string = process.cwd()): ConfigDirEntry | undefined {
|
|
339
|
+
let currentDir = cwd;
|
|
340
|
+
|
|
341
|
+
while (true) {
|
|
342
|
+
// Check all config bases at this level, in priority order
|
|
343
|
+
for (const { base, name } of PROJECT_CONFIG_BASES) {
|
|
344
|
+
const candidate = join(currentDir, base, subpath);
|
|
345
|
+
if (isDirectory(candidate)) {
|
|
346
|
+
return { path: candidate, source: name, level: "project" };
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Move up one directory
|
|
351
|
+
const parentDir = dirname(currentDir);
|
|
352
|
+
if (parentDir === currentDir) break; // Reached root
|
|
353
|
+
currentDir = parentDir;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return undefined;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Find all nearest config directories by walking up from cwd.
|
|
361
|
+
* Returns one entry per config base (.omp, .pi, .claude) - the nearest one found.
|
|
362
|
+
* Results are in priority order (highest first).
|
|
363
|
+
*/
|
|
364
|
+
export function findAllNearestProjectConfigDirs(subpath: string, cwd: string = process.cwd()): ConfigDirEntry[] {
|
|
365
|
+
const results: ConfigDirEntry[] = [];
|
|
366
|
+
const foundBases = new Set<string>();
|
|
367
|
+
|
|
368
|
+
let currentDir = cwd;
|
|
369
|
+
|
|
370
|
+
while (foundBases.size < PROJECT_CONFIG_BASES.length) {
|
|
371
|
+
for (const { base, name } of PROJECT_CONFIG_BASES) {
|
|
372
|
+
if (foundBases.has(name)) continue;
|
|
373
|
+
|
|
374
|
+
const candidate = join(currentDir, base, subpath);
|
|
375
|
+
if (isDirectory(candidate)) {
|
|
376
|
+
results.push({ path: candidate, source: name, level: "project" });
|
|
377
|
+
foundBases.add(name);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const parentDir = dirname(currentDir);
|
|
382
|
+
if (parentDir === currentDir) break;
|
|
383
|
+
currentDir = parentDir;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Sort by priority order
|
|
387
|
+
const order = PROJECT_CONFIG_BASES.map((b) => b.name);
|
|
388
|
+
results.sort((a, b) => order.indexOf(a.source) - order.indexOf(b.source));
|
|
389
|
+
|
|
390
|
+
return results;
|
|
391
|
+
}
|
package/src/core/auth-storage.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
type OAuthCredentials,
|
|
16
16
|
type OAuthProvider,
|
|
17
17
|
} from "@oh-my-pi/pi-ai";
|
|
18
|
+
import { logger } from "./logger";
|
|
18
19
|
|
|
19
20
|
export type ApiKeyCredential = {
|
|
20
21
|
type: "api_key";
|
|
@@ -31,13 +32,21 @@ export type AuthStorageData = Record<string, AuthCredential>;
|
|
|
31
32
|
|
|
32
33
|
/**
|
|
33
34
|
* Credential storage backed by a JSON file.
|
|
35
|
+
* Reads from multiple fallback paths, writes to primary path.
|
|
34
36
|
*/
|
|
35
37
|
export class AuthStorage {
|
|
36
38
|
private data: AuthStorageData = {};
|
|
37
39
|
private runtimeOverrides: Map<string, string> = new Map();
|
|
38
40
|
private fallbackResolver?: (provider: string) => string | undefined;
|
|
39
41
|
|
|
40
|
-
|
|
42
|
+
/**
|
|
43
|
+
* @param authPath - Primary path for reading/writing auth.json
|
|
44
|
+
* @param fallbackPaths - Additional paths to check when reading (legacy support)
|
|
45
|
+
*/
|
|
46
|
+
constructor(
|
|
47
|
+
private authPath: string,
|
|
48
|
+
private fallbackPaths: string[] = [],
|
|
49
|
+
) {
|
|
41
50
|
this.reload();
|
|
42
51
|
}
|
|
43
52
|
|
|
@@ -66,17 +75,31 @@ export class AuthStorage {
|
|
|
66
75
|
|
|
67
76
|
/**
|
|
68
77
|
* Reload credentials from disk.
|
|
78
|
+
* Checks primary path first, then fallback paths.
|
|
69
79
|
*/
|
|
70
80
|
reload(): void {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
81
|
+
const pathsToCheck = [this.authPath, ...this.fallbackPaths];
|
|
82
|
+
|
|
83
|
+
logger.debug("AuthStorage.reload checking paths", { paths: pathsToCheck });
|
|
84
|
+
|
|
85
|
+
for (const authPath of pathsToCheck) {
|
|
86
|
+
const exists = existsSync(authPath);
|
|
87
|
+
logger.debug("AuthStorage.reload path check", { path: authPath, exists });
|
|
88
|
+
|
|
89
|
+
if (exists) {
|
|
90
|
+
try {
|
|
91
|
+
this.data = JSON.parse(readFileSync(authPath, "utf-8"));
|
|
92
|
+
logger.debug("AuthStorage.reload loaded", { path: authPath, providers: Object.keys(this.data) });
|
|
93
|
+
return;
|
|
94
|
+
} catch (e) {
|
|
95
|
+
logger.error("AuthStorage failed to parse auth file", { path: authPath, error: String(e) });
|
|
96
|
+
// Continue to next path on parse error
|
|
97
|
+
}
|
|
98
|
+
}
|
|
79
99
|
}
|
|
100
|
+
|
|
101
|
+
logger.warn("AuthStorage no auth file found", { checkedPaths: pathsToCheck });
|
|
102
|
+
this.data = {};
|
|
80
103
|
}
|
|
81
104
|
|
|
82
105
|
/**
|
|
@@ -132,7 +132,7 @@ export async function executeBash(command: string, options?: BashExecutorOptions
|
|
|
132
132
|
if (totalBytes > DEFAULT_MAX_BYTES && !tempFilePath) {
|
|
133
133
|
const randomId = crypto.getRandomValues(new Uint8Array(8));
|
|
134
134
|
const id = Array.from(randomId, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
135
|
-
tempFilePath = join(tmpdir(), `
|
|
135
|
+
tempFilePath = join(tmpdir(), `omp-bash-${id}.log`);
|
|
136
136
|
tempFileStream = createWriteStream(tempFilePath);
|
|
137
137
|
// Write already-buffered chunks to temp file
|
|
138
138
|
for (const chunk of outputChunks) {
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
* to avoid import resolution issues with custom commands loaded from user directories.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import
|
|
8
|
+
import { type Dirent, existsSync, readdirSync } from "node:fs";
|
|
9
9
|
import * as path from "node:path";
|
|
10
10
|
import * as typebox from "@sinclair/typebox";
|
|
11
|
-
import {
|
|
11
|
+
import { getAgentDir, getConfigDirs } from "../../config";
|
|
12
12
|
import * as piCodingAgent from "../../index";
|
|
13
13
|
import { execCommand } from "../exec";
|
|
14
14
|
import { createReviewCommand } from "./bundled/review";
|
|
@@ -60,36 +60,6 @@ async function loadCommandModule(
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
/**
|
|
64
|
-
* Discover command modules from a directory.
|
|
65
|
-
* Loads index.ts files from subdirectories (e.g., commands/deploy/index.ts).
|
|
66
|
-
*/
|
|
67
|
-
function discoverCommandsInDir(dir: string): string[] {
|
|
68
|
-
if (!fs.existsSync(dir)) {
|
|
69
|
-
return [];
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const commands: string[] = [];
|
|
73
|
-
|
|
74
|
-
try {
|
|
75
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
76
|
-
|
|
77
|
-
for (const entry of entries) {
|
|
78
|
-
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
79
|
-
// Check for index.ts in subdirectory
|
|
80
|
-
const indexPath = path.join(dir, entry.name, "index.ts");
|
|
81
|
-
if (fs.existsSync(indexPath)) {
|
|
82
|
-
commands.push(indexPath);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
} catch {
|
|
87
|
-
return [];
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return commands;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
63
|
export interface DiscoverCustomCommandsOptions {
|
|
94
64
|
/** Current working directory. Default: process.cwd() */
|
|
95
65
|
cwd?: string;
|
|
@@ -103,34 +73,58 @@ export interface DiscoverCustomCommandsResult {
|
|
|
103
73
|
}
|
|
104
74
|
|
|
105
75
|
/**
|
|
106
|
-
* Discover custom command modules
|
|
107
|
-
*
|
|
108
|
-
* - cwd/.pi/commands/[name]/index.ts (project)
|
|
76
|
+
* Discover custom command modules (TypeScript slash commands).
|
|
77
|
+
* Markdown slash commands are handled by core/slash-commands.ts.
|
|
109
78
|
*/
|
|
110
79
|
export function discoverCustomCommands(options: DiscoverCustomCommandsOptions = {}): DiscoverCustomCommandsResult {
|
|
111
80
|
const cwd = options.cwd ?? process.cwd();
|
|
112
81
|
const agentDir = options.agentDir ?? getAgentDir();
|
|
113
|
-
|
|
114
82
|
const paths: Array<{ path: string; source: CustomCommandSource }> = [];
|
|
115
83
|
const seen = new Set<string>();
|
|
116
84
|
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
paths.push({ path: p, source });
|
|
123
|
-
}
|
|
124
|
-
}
|
|
85
|
+
const addPath = (commandPath: string, source: CustomCommandSource): void => {
|
|
86
|
+
const resolved = path.resolve(commandPath);
|
|
87
|
+
if (seen.has(resolved)) return;
|
|
88
|
+
seen.add(resolved);
|
|
89
|
+
paths.push({ path: resolved, source });
|
|
125
90
|
};
|
|
126
91
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
92
|
+
const commandDirs: Array<{ path: string; source: CustomCommandSource }> = [];
|
|
93
|
+
if (agentDir) {
|
|
94
|
+
const userCommandsDir = path.join(agentDir, "commands");
|
|
95
|
+
if (existsSync(userCommandsDir)) {
|
|
96
|
+
commandDirs.push({ path: userCommandsDir, source: "user" });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
for (const entry of getConfigDirs("commands", { cwd, existingOnly: true })) {
|
|
101
|
+
const source = entry.level === "user" ? "user" : "project";
|
|
102
|
+
if (!commandDirs.some((d) => d.path === entry.path)) {
|
|
103
|
+
commandDirs.push({ path: entry.path, source });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
130
106
|
|
|
131
|
-
|
|
132
|
-
const
|
|
133
|
-
|
|
107
|
+
const indexCandidates = ["index.ts", "index.js", "index.mjs", "index.cjs"];
|
|
108
|
+
for (const { path: commandsDir, source } of commandDirs) {
|
|
109
|
+
let entries: Dirent[];
|
|
110
|
+
try {
|
|
111
|
+
entries = readdirSync(commandsDir, { withFileTypes: true });
|
|
112
|
+
} catch {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
for (const entry of entries) {
|
|
116
|
+
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
117
|
+
const commandDir = path.join(commandsDir, entry.name);
|
|
118
|
+
|
|
119
|
+
for (const filename of indexCandidates) {
|
|
120
|
+
const candidate = path.join(commandDir, filename);
|
|
121
|
+
if (existsSync(candidate)) {
|
|
122
|
+
addPath(candidate, source);
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
134
128
|
|
|
135
129
|
return { paths };
|
|
136
130
|
}
|