@oh-my-pi/pi-coding-agent 12.0.0 → 12.1.1
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 +17 -0
- package/README.md +4 -29
- package/docs/fs-scan-cache-architecture.md +50 -0
- package/docs/models.md +234 -0
- package/package.json +16 -15
- package/src/cli/args.ts +1 -1
- package/src/cli/config-cli.ts +2 -1
- package/src/cli/grep-cli.ts +1 -1
- package/src/cli/jupyter-cli.ts +2 -1
- package/src/cli/plugin-cli.ts +2 -1
- package/src/cli/setup-cli.ts +117 -22
- package/src/cli/shell-cli.ts +1 -1
- package/src/cli/stats-cli.ts +2 -1
- package/src/cli/update-cli.ts +1 -1
- package/src/cli/web-search-cli.ts +2 -1
- package/src/cli.ts +1 -1
- package/src/commands/launch.ts +1 -1
- package/src/commit/agentic/index.ts +1 -0
- package/src/commit/pipeline.ts +1 -0
- package/src/config/keybindings.ts +1 -1
- package/src/config/model-registry.ts +210 -11
- package/src/config/model-resolver.ts +3 -3
- package/src/config/prompt-templates.ts +4 -4
- package/src/config/settings.ts +1 -1
- package/src/config.ts +3 -62
- package/src/debug/index.ts +1 -1
- package/src/debug/report-bundle.ts +1 -12
- package/src/debug/system-info.ts +1 -1
- package/src/discovery/claude-plugins.ts +205 -0
- package/src/discovery/helpers.ts +139 -3
- package/src/discovery/index.ts +1 -0
- package/src/export/custom-share.ts +1 -1
- package/src/export/html/index.ts +1 -1
- package/src/extensibility/custom-commands/loader.ts +2 -1
- package/src/extensibility/plugins/index.ts +0 -7
- package/src/extensibility/plugins/installer.ts +1 -1
- package/src/extensibility/plugins/loader.ts +3 -7
- package/src/extensibility/plugins/manager.ts +4 -4
- package/src/index.ts +1 -1
- package/src/ipy/executor.ts +1 -1
- package/src/ipy/gateway-coordinator.ts +1 -1
- package/src/ipy/modules.ts +5 -6
- package/src/ipy/runtime.ts +27 -0
- package/src/main.ts +3 -1
- package/src/mcp/config-writer.ts +1 -13
- package/src/modes/components/welcome.ts +1 -1
- package/src/modes/controllers/mcp-command-controller.ts +2 -7
- package/src/modes/controllers/selector-controller.ts +1 -1
- package/src/modes/interactive-mode.ts +2 -2
- package/src/modes/theme/theme.ts +1 -1
- package/src/patch/hashline.ts +0 -12
- package/src/patch/index.ts +14 -0
- package/src/sdk.ts +2 -1
- package/src/session/agent-session.ts +1 -1
- package/src/session/agent-storage.ts +1 -1
- package/src/session/auth-storage.ts +2 -2
- package/src/session/history-storage.ts +1 -1
- package/src/session/session-manager.ts +1 -1
- package/src/ssh/connection-manager.ts +3 -4
- package/src/ssh/sshfs-mount.ts +2 -3
- package/src/system-prompt.ts +2 -2
- package/src/task/discovery.ts +14 -1
- package/src/task/executor.ts +1 -0
- package/src/task/worktree.ts +2 -1
- package/src/tools/bash-interactive.ts +33 -1
- package/src/tools/fs-cache-invalidation.ts +28 -0
- package/src/tools/grep.ts +1 -0
- package/src/tools/read.ts +2 -3
- package/src/tools/write.ts +2 -0
- package/src/utils/file-mentions.ts +128 -7
- package/src/utils/tools-manager.ts +1 -1
- package/src/web/search/auth.ts +1 -1
- package/src/web/search/providers/codex.ts +1 -1
- package/src/web/search/providers/gemini.ts +1 -1
- package/src/web/search/providers/perplexity.ts +1 -1
- package/src/extensibility/plugins/paths.ts +0 -37
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code Marketplace Plugin Provider
|
|
3
|
+
*
|
|
4
|
+
* Loads configuration from ~/.claude/plugins/cache/ based on installed_plugins.json registry.
|
|
5
|
+
* Priority: 70 (below claude.ts at 80, so user overrides in .claude/ take precedence)
|
|
6
|
+
*/
|
|
7
|
+
import * as path from "node:path";
|
|
8
|
+
import { registerProvider } from "../capability";
|
|
9
|
+
import { type Hook, hookCapability } from "../capability/hook";
|
|
10
|
+
import { type Skill, skillCapability } from "../capability/skill";
|
|
11
|
+
import { type SlashCommand, slashCommandCapability } from "../capability/slash-command";
|
|
12
|
+
import { type CustomTool, toolCapability } from "../capability/tool";
|
|
13
|
+
import type { LoadContext, LoadResult } from "../capability/types";
|
|
14
|
+
import { type ClaudePluginRoot, listClaudePluginRoots, loadFilesFromDir, loadSkillsFromDir } from "./helpers";
|
|
15
|
+
|
|
16
|
+
const PROVIDER_ID = "claude-plugins";
|
|
17
|
+
const DISPLAY_NAME = "Claude Code Marketplace";
|
|
18
|
+
const PRIORITY = 70; // Below claude.ts (80) so user .claude/ overrides win
|
|
19
|
+
|
|
20
|
+
// =============================================================================
|
|
21
|
+
// Skills
|
|
22
|
+
// =============================================================================
|
|
23
|
+
|
|
24
|
+
async function loadSkills(ctx: LoadContext): Promise<LoadResult<Skill>> {
|
|
25
|
+
const items: Skill[] = [];
|
|
26
|
+
const warnings: string[] = [];
|
|
27
|
+
|
|
28
|
+
const { roots, warnings: rootWarnings } = await listClaudePluginRoots(ctx.home);
|
|
29
|
+
warnings.push(...rootWarnings);
|
|
30
|
+
|
|
31
|
+
const results = await Promise.all(
|
|
32
|
+
roots.map(async root => {
|
|
33
|
+
const skillsDir = path.join(root.path, "skills");
|
|
34
|
+
return loadSkillsFromDir(ctx, {
|
|
35
|
+
dir: skillsDir,
|
|
36
|
+
providerId: PROVIDER_ID,
|
|
37
|
+
level: root.scope,
|
|
38
|
+
});
|
|
39
|
+
}),
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
for (const result of results) {
|
|
43
|
+
items.push(...result.items);
|
|
44
|
+
if (result.warnings) warnings.push(...result.warnings);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return { items, warnings };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// =============================================================================
|
|
51
|
+
// Slash Commands
|
|
52
|
+
// =============================================================================
|
|
53
|
+
|
|
54
|
+
async function loadSlashCommands(ctx: LoadContext): Promise<LoadResult<SlashCommand>> {
|
|
55
|
+
const items: SlashCommand[] = [];
|
|
56
|
+
const warnings: string[] = [];
|
|
57
|
+
|
|
58
|
+
const { roots, warnings: rootWarnings } = await listClaudePluginRoots(ctx.home);
|
|
59
|
+
warnings.push(...rootWarnings);
|
|
60
|
+
|
|
61
|
+
const results = await Promise.all(
|
|
62
|
+
roots.map(async root => {
|
|
63
|
+
const commandsDir = path.join(root.path, "commands");
|
|
64
|
+
return loadFilesFromDir<SlashCommand>(ctx, commandsDir, PROVIDER_ID, root.scope, {
|
|
65
|
+
extensions: ["md"],
|
|
66
|
+
transform: (name, content, filePath, source) => {
|
|
67
|
+
const cmdName = name.replace(/\.md$/, "");
|
|
68
|
+
return {
|
|
69
|
+
name: cmdName,
|
|
70
|
+
path: filePath,
|
|
71
|
+
content,
|
|
72
|
+
level: root.scope,
|
|
73
|
+
_source: source,
|
|
74
|
+
};
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
}),
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
for (const result of results) {
|
|
81
|
+
items.push(...result.items);
|
|
82
|
+
if (result.warnings) warnings.push(...result.warnings);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return { items, warnings };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// =============================================================================
|
|
89
|
+
// Hooks
|
|
90
|
+
// =============================================================================
|
|
91
|
+
|
|
92
|
+
async function loadHooks(ctx: LoadContext): Promise<LoadResult<Hook>> {
|
|
93
|
+
const items: Hook[] = [];
|
|
94
|
+
const warnings: string[] = [];
|
|
95
|
+
|
|
96
|
+
const { roots, warnings: rootWarnings } = await listClaudePluginRoots(ctx.home);
|
|
97
|
+
warnings.push(...rootWarnings);
|
|
98
|
+
|
|
99
|
+
const hookTypes = ["pre", "post"] as const;
|
|
100
|
+
|
|
101
|
+
const loadTasks: { root: ClaudePluginRoot; hookType: "pre" | "post" }[] = [];
|
|
102
|
+
for (const root of roots) {
|
|
103
|
+
for (const hookType of hookTypes) {
|
|
104
|
+
loadTasks.push({ root, hookType });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const results = await Promise.all(
|
|
109
|
+
loadTasks.map(async ({ root, hookType }) => {
|
|
110
|
+
const hooksDir = path.join(root.path, "hooks", hookType);
|
|
111
|
+
return loadFilesFromDir<Hook>(ctx, hooksDir, PROVIDER_ID, root.scope, {
|
|
112
|
+
transform: (name, _content, filePath, source) => {
|
|
113
|
+
const toolName = name.replace(/\.(sh|bash|zsh|fish)$/, "");
|
|
114
|
+
return {
|
|
115
|
+
name,
|
|
116
|
+
path: filePath,
|
|
117
|
+
type: hookType,
|
|
118
|
+
tool: toolName,
|
|
119
|
+
level: root.scope,
|
|
120
|
+
_source: source,
|
|
121
|
+
};
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
}),
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
for (const result of results) {
|
|
128
|
+
items.push(...result.items);
|
|
129
|
+
if (result.warnings) warnings.push(...result.warnings);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return { items, warnings };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// =============================================================================
|
|
136
|
+
// Custom Tools
|
|
137
|
+
// =============================================================================
|
|
138
|
+
|
|
139
|
+
async function loadTools(ctx: LoadContext): Promise<LoadResult<CustomTool>> {
|
|
140
|
+
const items: CustomTool[] = [];
|
|
141
|
+
const warnings: string[] = [];
|
|
142
|
+
|
|
143
|
+
const { roots, warnings: rootWarnings } = await listClaudePluginRoots(ctx.home);
|
|
144
|
+
warnings.push(...rootWarnings);
|
|
145
|
+
|
|
146
|
+
const results = await Promise.all(
|
|
147
|
+
roots.map(async root => {
|
|
148
|
+
const toolsDir = path.join(root.path, "tools");
|
|
149
|
+
return loadFilesFromDir<CustomTool>(ctx, toolsDir, PROVIDER_ID, root.scope, {
|
|
150
|
+
transform: (name, _content, filePath, source) => {
|
|
151
|
+
const toolName = name.replace(/\.(ts|js|sh|bash|py)$/, "");
|
|
152
|
+
return {
|
|
153
|
+
name: toolName,
|
|
154
|
+
path: filePath,
|
|
155
|
+
level: root.scope,
|
|
156
|
+
_source: source,
|
|
157
|
+
};
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
}),
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
for (const result of results) {
|
|
164
|
+
items.push(...result.items);
|
|
165
|
+
if (result.warnings) warnings.push(...result.warnings);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return { items, warnings };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// =============================================================================
|
|
172
|
+
// Provider Registration
|
|
173
|
+
// =============================================================================
|
|
174
|
+
|
|
175
|
+
registerProvider<Skill>(skillCapability.id, {
|
|
176
|
+
id: PROVIDER_ID,
|
|
177
|
+
displayName: DISPLAY_NAME,
|
|
178
|
+
description: "Load skills from Claude Code marketplace plugins (~/.claude/plugins/cache/)",
|
|
179
|
+
priority: PRIORITY,
|
|
180
|
+
load: loadSkills,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
registerProvider<SlashCommand>(slashCommandCapability.id, {
|
|
184
|
+
id: PROVIDER_ID,
|
|
185
|
+
displayName: DISPLAY_NAME,
|
|
186
|
+
description: "Load slash commands from Claude Code marketplace plugins",
|
|
187
|
+
priority: PRIORITY,
|
|
188
|
+
load: loadSlashCommands,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
registerProvider<Hook>(hookCapability.id, {
|
|
192
|
+
id: PROVIDER_ID,
|
|
193
|
+
displayName: DISPLAY_NAME,
|
|
194
|
+
description: "Load hooks from Claude Code marketplace plugins",
|
|
195
|
+
priority: PRIORITY,
|
|
196
|
+
load: loadHooks,
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
registerProvider<CustomTool>(toolCapability.id, {
|
|
200
|
+
id: PROVIDER_ID,
|
|
201
|
+
displayName: DISPLAY_NAME,
|
|
202
|
+
description: "Load custom tools from Claude Code marketplace plugins",
|
|
203
|
+
priority: PRIORITY,
|
|
204
|
+
load: loadTools,
|
|
205
|
+
});
|
package/src/discovery/helpers.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import * as os from "node:os";
|
|
5
5
|
import * as path from "node:path";
|
|
6
6
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
7
|
+
import { CONFIG_DIR_NAME } from "@oh-my-pi/pi-utils/dirs";
|
|
7
8
|
import { readDirEntries, readFile } from "../capability/fs";
|
|
8
9
|
import type { Skill, SkillFrontmatter } from "../capability/skill";
|
|
9
10
|
import type { LoadContext, LoadResult, SourceMeta } from "../capability/types";
|
|
@@ -39,9 +40,9 @@ export function expandPath(p: string): string {
|
|
|
39
40
|
*/
|
|
40
41
|
export const SOURCE_PATHS = {
|
|
41
42
|
native: {
|
|
42
|
-
userBase:
|
|
43
|
-
userAgent:
|
|
44
|
-
projectDir:
|
|
43
|
+
userBase: CONFIG_DIR_NAME,
|
|
44
|
+
userAgent: `${CONFIG_DIR_NAME}/agent`,
|
|
45
|
+
projectDir: CONFIG_DIR_NAME,
|
|
45
46
|
},
|
|
46
47
|
claude: {
|
|
47
48
|
userBase: ".claude",
|
|
@@ -546,3 +547,138 @@ export function getExtensionNameFromPath(extensionPath: string): string {
|
|
|
546
547
|
|
|
547
548
|
return base;
|
|
548
549
|
}
|
|
550
|
+
|
|
551
|
+
// =============================================================================
|
|
552
|
+
// Claude Code Plugin Cache Helpers
|
|
553
|
+
// =============================================================================
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Entry for an installed Claude Code plugin.
|
|
557
|
+
*/
|
|
558
|
+
export interface ClaudePluginEntry {
|
|
559
|
+
scope: "user" | "project";
|
|
560
|
+
installPath: string;
|
|
561
|
+
version: string;
|
|
562
|
+
installedAt: string;
|
|
563
|
+
lastUpdated: string;
|
|
564
|
+
gitCommitSha?: string;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Claude Code installed_plugins.json registry format.
|
|
569
|
+
*/
|
|
570
|
+
export interface ClaudePluginsRegistry {
|
|
571
|
+
version: number;
|
|
572
|
+
plugins: Record<string, ClaudePluginEntry[]>;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Resolved plugin root for loading.
|
|
577
|
+
*/
|
|
578
|
+
export interface ClaudePluginRoot {
|
|
579
|
+
/** Plugin ID (e.g., "simpleclaude-core@simpleclaude") */
|
|
580
|
+
id: string;
|
|
581
|
+
/** Marketplace name */
|
|
582
|
+
marketplace: string;
|
|
583
|
+
/** Plugin name */
|
|
584
|
+
plugin: string;
|
|
585
|
+
/** Version string */
|
|
586
|
+
version: string;
|
|
587
|
+
/** Absolute path to plugin root */
|
|
588
|
+
path: string;
|
|
589
|
+
/** Whether this is a user or project scope plugin */
|
|
590
|
+
scope: "user" | "project";
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* Parse Claude Code installed_plugins.json content.
|
|
595
|
+
*/
|
|
596
|
+
export function parseClaudePluginsRegistry(content: string): ClaudePluginsRegistry | null {
|
|
597
|
+
const data = parseJSON<ClaudePluginsRegistry>(content);
|
|
598
|
+
if (!data || typeof data !== "object") return null;
|
|
599
|
+
if (
|
|
600
|
+
typeof data.version !== "number" ||
|
|
601
|
+
!data.plugins ||
|
|
602
|
+
typeof data.plugins !== "object" ||
|
|
603
|
+
Array.isArray(data.plugins)
|
|
604
|
+
)
|
|
605
|
+
return null;
|
|
606
|
+
return data;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* List all installed Claude Code plugin roots from the plugin cache.
|
|
611
|
+
* Reads ~/.claude/plugins/installed_plugins.json and resolves plugin paths.
|
|
612
|
+
*
|
|
613
|
+
* Results are cached per home directory to avoid repeated parsing.
|
|
614
|
+
*/
|
|
615
|
+
const pluginRootsCache = new Map<string, { roots: ClaudePluginRoot[]; warnings: string[] }>();
|
|
616
|
+
|
|
617
|
+
export async function listClaudePluginRoots(home: string): Promise<{ roots: ClaudePluginRoot[]; warnings: string[] }> {
|
|
618
|
+
const cached = pluginRootsCache.get(home);
|
|
619
|
+
if (cached) return cached;
|
|
620
|
+
|
|
621
|
+
const roots: ClaudePluginRoot[] = [];
|
|
622
|
+
const warnings: string[] = [];
|
|
623
|
+
|
|
624
|
+
const registryPath = path.join(home, ".claude", "plugins", "installed_plugins.json");
|
|
625
|
+
const content = await readFile(registryPath);
|
|
626
|
+
|
|
627
|
+
if (!content) {
|
|
628
|
+
// No registry file - not an error, just no plugins
|
|
629
|
+
const result = { roots, warnings };
|
|
630
|
+
pluginRootsCache.set(home, result);
|
|
631
|
+
return result;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
const registry = parseClaudePluginsRegistry(content);
|
|
635
|
+
if (!registry) {
|
|
636
|
+
warnings.push(`Failed to parse Claude Code plugin registry: ${registryPath}`);
|
|
637
|
+
const result = { roots, warnings };
|
|
638
|
+
pluginRootsCache.set(home, result);
|
|
639
|
+
return result;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
for (const [pluginId, entries] of Object.entries(registry.plugins)) {
|
|
643
|
+
if (!Array.isArray(entries) || entries.length === 0) continue;
|
|
644
|
+
|
|
645
|
+
// Parse plugin ID format: "plugin-name@marketplace"
|
|
646
|
+
const atIndex = pluginId.lastIndexOf("@");
|
|
647
|
+
if (atIndex === -1) {
|
|
648
|
+
warnings.push(`Invalid plugin ID format (missing @marketplace): ${pluginId}`);
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
const pluginName = pluginId.slice(0, atIndex);
|
|
653
|
+
const marketplace = pluginId.slice(atIndex + 1);
|
|
654
|
+
|
|
655
|
+
// Process all valid entries, not just the first one.
|
|
656
|
+
// This handles plugins with multiple installs (different scopes/versions).
|
|
657
|
+
for (const entry of entries) {
|
|
658
|
+
if (!entry.installPath || typeof entry.installPath !== "string") {
|
|
659
|
+
warnings.push(`Plugin ${pluginId} entry has no installPath`);
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
roots.push({
|
|
664
|
+
id: pluginId,
|
|
665
|
+
marketplace,
|
|
666
|
+
plugin: pluginName,
|
|
667
|
+
version: entry.version || "unknown",
|
|
668
|
+
path: entry.installPath,
|
|
669
|
+
scope: entry.scope || "user",
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
const result = { roots, warnings };
|
|
675
|
+
pluginRootsCache.set(home, result);
|
|
676
|
+
return result;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* Clear the plugin roots cache (useful for testing or when plugins change).
|
|
681
|
+
*/
|
|
682
|
+
export function clearClaudePluginRootsCache(): void {
|
|
683
|
+
pluginRootsCache.clear();
|
|
684
|
+
}
|
package/src/discovery/index.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import * as fs from "node:fs";
|
|
8
8
|
import * as path from "node:path";
|
|
9
|
-
import { getAgentDir } from "
|
|
9
|
+
import { getAgentDir } from "@oh-my-pi/pi-utils/dirs";
|
|
10
10
|
|
|
11
11
|
export interface CustomShareResult {
|
|
12
12
|
/** URL to display/open (optional - script may handle everything itself) */
|
package/src/export/html/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import type { AgentState } from "@oh-my-pi/pi-agent-core";
|
|
3
3
|
import { isEnoent } from "@oh-my-pi/pi-utils";
|
|
4
|
-
import { APP_NAME } from "
|
|
4
|
+
import { APP_NAME } from "@oh-my-pi/pi-utils/dirs";
|
|
5
5
|
import { getResolvedThemeColors, getThemeExportColors } from "../../modes/theme/theme";
|
|
6
6
|
import { type SessionEntry, type SessionHeader, SessionManager } from "../../session/session-manager";
|
|
7
7
|
// Pre-generated template (created by scripts/generate-template.ts at publish time)
|
|
@@ -8,8 +8,9 @@ import * as fs from "node:fs";
|
|
|
8
8
|
import * as path from "node:path";
|
|
9
9
|
import * as piCodingAgent from "@oh-my-pi/pi-coding-agent";
|
|
10
10
|
import { isEnoent, logger } from "@oh-my-pi/pi-utils";
|
|
11
|
+
import { getAgentDir } from "@oh-my-pi/pi-utils/dirs";
|
|
11
12
|
import * as typebox from "@sinclair/typebox";
|
|
12
|
-
import {
|
|
13
|
+
import { getConfigDirs } from "../../config";
|
|
13
14
|
import { execCommand } from "../../exec/exec";
|
|
14
15
|
import { ReviewCommand } from "./bundled/review";
|
|
15
16
|
import type {
|
|
@@ -14,13 +14,6 @@ export {
|
|
|
14
14
|
} from "./loader";
|
|
15
15
|
export { PluginManager, parseSettingValue, validateSetting } from "./manager";
|
|
16
16
|
export { extractPackageName, formatPluginSpec, parsePluginSpec } from "./parser";
|
|
17
|
-
export {
|
|
18
|
-
getPluginsDir,
|
|
19
|
-
getPluginsLockfile,
|
|
20
|
-
getPluginsNodeModules,
|
|
21
|
-
getPluginsPackageJson,
|
|
22
|
-
getProjectPluginOverrides,
|
|
23
|
-
} from "./paths";
|
|
24
17
|
export type {
|
|
25
18
|
BooleanSetting,
|
|
26
19
|
DoctorCheck,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { isEnoent } from "@oh-my-pi/pi-utils";
|
|
4
|
-
import { getAgentDir } from "
|
|
4
|
+
import { getAgentDir } from "@oh-my-pi/pi-utils/dirs";
|
|
5
5
|
import type { InstalledPlugin } from "./types";
|
|
6
6
|
|
|
7
7
|
const PLUGINS_DIR = path.join(getAgentDir(), "plugins");
|
|
@@ -7,12 +7,8 @@
|
|
|
7
7
|
import * as fs from "node:fs";
|
|
8
8
|
import * as path from "node:path";
|
|
9
9
|
import { isEnoent } from "@oh-my-pi/pi-utils";
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
getPluginsLockfile,
|
|
13
|
-
getPluginsNodeModules,
|
|
14
|
-
getPluginsPackageJson,
|
|
15
|
-
} from "./paths";
|
|
10
|
+
import { getPluginsLockfile, getPluginsNodeModules, getPluginsPackageJson } from "@oh-my-pi/pi-utils/dirs";
|
|
11
|
+
import { getConfigDirPaths } from "../../config";
|
|
16
12
|
import type { InstalledPlugin, PluginManifest, PluginRuntimeConfig, ProjectPluginOverrides } from "./types";
|
|
17
13
|
|
|
18
14
|
// =============================================================================
|
|
@@ -36,7 +32,7 @@ async function loadRuntimeConfig(): Promise<PluginRuntimeConfig> {
|
|
|
36
32
|
* Load project-local plugin overrides (checks .omp and .pi directories).
|
|
37
33
|
*/
|
|
38
34
|
async function loadProjectOverrides(cwd: string): Promise<ProjectPluginOverrides> {
|
|
39
|
-
for (const overridesPath of
|
|
35
|
+
for (const overridesPath of getConfigDirPaths("plugin-overrides.json", { user: false, cwd })) {
|
|
40
36
|
try {
|
|
41
37
|
return await Bun.file(overridesPath).json();
|
|
42
38
|
} catch (err) {
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { isEnoent, logger } from "@oh-my-pi/pi-utils";
|
|
4
|
-
import { extractPackageName, parsePluginSpec } from "./parser";
|
|
5
4
|
import {
|
|
6
5
|
getPluginsDir,
|
|
7
6
|
getPluginsLockfile,
|
|
8
7
|
getPluginsNodeModules,
|
|
9
8
|
getPluginsPackageJson,
|
|
10
|
-
|
|
11
|
-
} from "
|
|
9
|
+
getProjectPluginOverridesPath,
|
|
10
|
+
} from "@oh-my-pi/pi-utils/dirs";
|
|
11
|
+
import { extractPackageName, parsePluginSpec } from "./parser";
|
|
12
12
|
import type {
|
|
13
13
|
DoctorCheck,
|
|
14
14
|
DoctorOptions,
|
|
@@ -82,7 +82,7 @@ export class PluginManager {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
async #loadProjectOverrides(): Promise<ProjectPluginOverrides> {
|
|
85
|
-
const overridesPath =
|
|
85
|
+
const overridesPath = getProjectPluginOverridesPath(this.#cwd);
|
|
86
86
|
try {
|
|
87
87
|
return await Bun.file(overridesPath).json();
|
|
88
88
|
} catch (err) {
|
package/src/index.ts
CHANGED
|
@@ -7,7 +7,7 @@ export { StringEnum } from "@oh-my-pi/pi-ai";
|
|
|
7
7
|
export { Container, Markdown, Spacer, Text } from "@oh-my-pi/pi-tui";
|
|
8
8
|
// Logging
|
|
9
9
|
export { logger } from "@oh-my-pi/pi-utils";
|
|
10
|
-
export { getAgentDir, VERSION } from "
|
|
10
|
+
export { getAgentDir, VERSION } from "@oh-my-pi/pi-utils/dirs";
|
|
11
11
|
export { formatKeyHint, formatKeyHints } from "./config/keybindings";
|
|
12
12
|
export { ModelRegistry } from "./config/model-registry";
|
|
13
13
|
// Prompt templates
|
package/src/ipy/executor.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import { $env, isEnoent, logger } from "@oh-my-pi/pi-utils";
|
|
3
|
-
import { getAgentDir } from "
|
|
3
|
+
import { getAgentDir } from "@oh-my-pi/pi-utils/dirs";
|
|
4
4
|
import { OutputSink } from "../session/streaming-output";
|
|
5
5
|
import { time } from "../utils/timings";
|
|
6
6
|
import { shutdownSharedGateway } from "./gateway-coordinator";
|
|
@@ -2,8 +2,8 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import { createServer } from "node:net";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { isEnoent, logger, procmgr } from "@oh-my-pi/pi-utils";
|
|
5
|
+
import { getAgentDir } from "@oh-my-pi/pi-utils/dirs";
|
|
5
6
|
import type { Subprocess } from "bun";
|
|
6
|
-
import { getAgentDir } from "../config";
|
|
7
7
|
import { Settings } from "../config/settings";
|
|
8
8
|
import { getOrCreateSnapshot } from "../utils/shell-snapshot";
|
|
9
9
|
import { time } from "../utils/timings";
|
package/src/ipy/modules.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
|
-
import * as os from "node:os";
|
|
3
2
|
import * as path from "node:path";
|
|
3
|
+
import { getAgentModulesDir, getProjectModulesDir } from "@oh-my-pi/pi-utils/dirs";
|
|
4
4
|
|
|
5
5
|
export type PythonModuleSource = "user" | "project";
|
|
6
6
|
|
|
@@ -26,8 +26,8 @@ export interface PythonModuleExecutor {
|
|
|
26
26
|
export interface DiscoverPythonModulesOptions {
|
|
27
27
|
/** Working directory for project-level modules. Default: process.cwd() */
|
|
28
28
|
cwd?: string;
|
|
29
|
-
/**
|
|
30
|
-
|
|
29
|
+
/** Agent directory for user-level modules. Default: from getAgentDir() */
|
|
30
|
+
agentDir?: string;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
interface ModuleCandidate {
|
|
@@ -66,10 +66,9 @@ async function readModuleContent(candidate: ModuleCandidate): Promise<PythonModu
|
|
|
66
66
|
*/
|
|
67
67
|
export async function discoverPythonModules(options: DiscoverPythonModulesOptions = {}): Promise<PythonModuleEntry[]> {
|
|
68
68
|
const cwd = options.cwd ?? process.cwd();
|
|
69
|
-
const homeDir = options.homeDir ?? os.homedir();
|
|
70
69
|
|
|
71
|
-
const userDir =
|
|
72
|
-
const projectDir =
|
|
70
|
+
const userDir = getAgentModulesDir(options.agentDir);
|
|
71
|
+
const projectDir = getProjectModulesDir(cwd);
|
|
73
72
|
|
|
74
73
|
const userCandidates = await listModuleCandidates(userDir, "user");
|
|
75
74
|
const projectCandidates = await listModuleCandidates(projectDir, "project");
|
package/src/ipy/runtime.ts
CHANGED
|
@@ -8,6 +8,7 @@ import * as fs from "node:fs";
|
|
|
8
8
|
import * as path from "node:path";
|
|
9
9
|
|
|
10
10
|
import { $env } from "@oh-my-pi/pi-utils";
|
|
11
|
+
import { getPythonEnvDir } from "@oh-my-pi/pi-utils/dirs";
|
|
11
12
|
|
|
12
13
|
const DEFAULT_ENV_ALLOWLIST = new Set([
|
|
13
14
|
"PATH",
|
|
@@ -103,6 +104,17 @@ function resolvePathKey(env: Record<string, string | undefined>): string {
|
|
|
103
104
|
return match ?? "PATH";
|
|
104
105
|
}
|
|
105
106
|
|
|
107
|
+
function resolveManagedPythonEnv(): string {
|
|
108
|
+
return getPythonEnvDir();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function resolveManagedPythonCandidate(): { venvPath: string; pythonPath: string } {
|
|
112
|
+
const venvPath = resolveManagedPythonEnv();
|
|
113
|
+
const binDir = process.platform === "win32" ? path.join(venvPath, "Scripts") : path.join(venvPath, "bin");
|
|
114
|
+
const pythonPath = path.join(binDir, process.platform === "win32" ? "python.exe" : "python");
|
|
115
|
+
return { venvPath, pythonPath };
|
|
116
|
+
}
|
|
117
|
+
|
|
106
118
|
export interface PythonRuntime {
|
|
107
119
|
/** Path to python executable */
|
|
108
120
|
pythonPath: string;
|
|
@@ -184,6 +196,21 @@ export function resolvePythonRuntime(cwd: string, baseEnv: Record<string, string
|
|
|
184
196
|
}
|
|
185
197
|
}
|
|
186
198
|
|
|
199
|
+
const managed = resolveManagedPythonCandidate();
|
|
200
|
+
if (fs.existsSync(managed.pythonPath)) {
|
|
201
|
+
env.VIRTUAL_ENV = managed.venvPath;
|
|
202
|
+
const pathKey = resolvePathKey(env);
|
|
203
|
+
const currentPath = env[pathKey];
|
|
204
|
+
const managedBin =
|
|
205
|
+
process.platform === "win32" ? path.join(managed.venvPath, "Scripts") : path.join(managed.venvPath, "bin");
|
|
206
|
+
env[pathKey] = currentPath ? `${managedBin}${path.delimiter}${currentPath}` : managedBin;
|
|
207
|
+
return {
|
|
208
|
+
pythonPath: resolveWindowlessPython(managed.pythonPath),
|
|
209
|
+
env,
|
|
210
|
+
venvPath: managed.venvPath,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
187
214
|
const pythonPath = Bun.which("python") ?? Bun.which("python3");
|
|
188
215
|
if (!pythonPath) {
|
|
189
216
|
throw new Error("Python executable not found on PATH");
|
package/src/main.ts
CHANGED
|
@@ -10,12 +10,13 @@ import * as path from "node:path";
|
|
|
10
10
|
import { createInterface } from "node:readline/promises";
|
|
11
11
|
import { type ImageContent, supportsXhigh } from "@oh-my-pi/pi-ai";
|
|
12
12
|
import { $env, postmortem } from "@oh-my-pi/pi-utils";
|
|
13
|
+
import { VERSION } from "@oh-my-pi/pi-utils/dirs";
|
|
13
14
|
import chalk from "chalk";
|
|
14
15
|
import type { Args } from "./cli/args";
|
|
15
16
|
import { processFileArguments } from "./cli/file-processor";
|
|
16
17
|
import { listModels } from "./cli/list-models";
|
|
17
18
|
import { selectSession } from "./cli/session-picker";
|
|
18
|
-
import { findConfigFile
|
|
19
|
+
import { findConfigFile } from "./config";
|
|
19
20
|
import { ModelRegistry, ModelsConfigFile } from "./config/model-registry";
|
|
20
21
|
import { parseModelPattern, parseModelString, resolveModelScope, type ScopedModel } from "./config/model-resolver";
|
|
21
22
|
import { Settings, settings } from "./config/settings";
|
|
@@ -489,6 +490,7 @@ export async function runRootCommand(parsed: Args, rawArgs: string[]): Promise<v
|
|
|
489
490
|
// Create AuthStorage and ModelRegistry upfront
|
|
490
491
|
const authStorage = await discoverAuthStorage();
|
|
491
492
|
const modelRegistry = new ModelRegistry(authStorage);
|
|
493
|
+
await modelRegistry.refresh();
|
|
492
494
|
debugStartup("main:discoverModels");
|
|
493
495
|
time("discoverModels");
|
|
494
496
|
|
package/src/mcp/config-writer.ts
CHANGED
|
@@ -4,24 +4,12 @@
|
|
|
4
4
|
* Utilities for reading/writing .omp/mcp.json files at user or project level.
|
|
5
5
|
*/
|
|
6
6
|
import * as fs from "node:fs";
|
|
7
|
-
import * as os from "node:os";
|
|
8
7
|
import * as path from "node:path";
|
|
9
8
|
import { isEnoent } from "@oh-my-pi/pi-utils";
|
|
9
|
+
|
|
10
10
|
import { validateServerConfig } from "./config";
|
|
11
11
|
import type { MCPConfigFile, MCPServerConfig } from "./types";
|
|
12
12
|
|
|
13
|
-
/**
|
|
14
|
-
* Get the path to the MCP config file.
|
|
15
|
-
* @param scope - "user" for ~/.omp/mcp.json or "project" for .omp/mcp.json
|
|
16
|
-
* @param cwd - Current working directory (used for project scope)
|
|
17
|
-
*/
|
|
18
|
-
export function getMCPConfigPath(scope: "user" | "project", cwd: string): string {
|
|
19
|
-
if (scope === "user") {
|
|
20
|
-
return path.join(os.homedir(), ".omp", "mcp.json");
|
|
21
|
-
}
|
|
22
|
-
return path.join(cwd, ".omp", "mcp.json");
|
|
23
|
-
}
|
|
24
|
-
|
|
25
13
|
/**
|
|
26
14
|
* Read an MCP config file.
|
|
27
15
|
* Returns empty config if file doesn't exist.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Component, padding, truncateToWidth, visibleWidth } from "@oh-my-pi/pi-tui";
|
|
2
|
-
import { APP_NAME } from "
|
|
2
|
+
import { APP_NAME } from "@oh-my-pi/pi-utils/dirs";
|
|
3
3
|
import { theme } from "../../modes/theme/theme";
|
|
4
4
|
|
|
5
5
|
export interface RecentSession {
|
|
@@ -4,16 +4,11 @@
|
|
|
4
4
|
* Handles /mcp subcommands for managing MCP servers.
|
|
5
5
|
*/
|
|
6
6
|
import { Spacer, Text } from "@oh-my-pi/pi-tui";
|
|
7
|
+
import { getMCPConfigPath } from "@oh-my-pi/pi-utils/dirs";
|
|
7
8
|
import type { SourceMeta } from "../../capability/types";
|
|
8
9
|
import { analyzeAuthError, discoverOAuthEndpoints, MCPManager } from "../../mcp";
|
|
9
10
|
import { connectToServer, disconnectServer, listTools } from "../../mcp/client";
|
|
10
|
-
import {
|
|
11
|
-
addMCPServer,
|
|
12
|
-
getMCPConfigPath,
|
|
13
|
-
readMCPConfigFile,
|
|
14
|
-
removeMCPServer,
|
|
15
|
-
updateMCPServer,
|
|
16
|
-
} from "../../mcp/config-writer";
|
|
11
|
+
import { addMCPServer, readMCPConfigFile, removeMCPServer, updateMCPServer } from "../../mcp/config-writer";
|
|
17
12
|
import { MCPOAuthFlow } from "../../mcp/oauth-flow";
|
|
18
13
|
import type { MCPServerConfig, MCPServerConnection } from "../../mcp/types";
|
|
19
14
|
import type { OAuthCredential } from "../../session/auth-storage";
|