Unified system for loading hooks, tools, skills, and themes from: - Local files and directories - npm packages (npm:pkg@version) - Git repositories (git:url@tag or git:url#branch) Includes filtering, atomic installation, and settings hierarchy.
27 KiB
Extension Loading
Unified system for loading hooks, tools, skills, and themes from local files, directories, npm packages, and git repositories.
Extension Types
| Type | Root Entry | Subdir Entry | Purpose |
|---|---|---|---|
| Hooks | *.ts / *.js |
index.ts / index.js / package.json main |
Event handlers for agent lifecycle |
| Tools | *.ts / *.js |
index.ts / index.js / package.json main |
Custom tools for the agent |
| Skills | *.md |
SKILL.md |
Context/instructions loaded into agent |
| Themes | *.theme.json |
*.theme.json (recursive) |
Color schemes for TUI |
Note: Themes use *.theme.json pattern scanned recursively at all levels, allowing flat theme packs without requiring subdirectories.
Sources
Extensions can be loaded from:
File Paths
./my-hook.ts
~/global-hook.ts
/absolute/path/hook.ts
Directories
./my-hooks/
~/.pi/agent/hooks/
npm Packages
npm:package-name
npm:package-name@1.2.3
npm:package-name@latest
npm:@scope/package-name
npm:@scope/package-name@1.2.3
Git Repositories
git:https://github.com/user/repo
git:https://github.com/user/repo@v1.0.0 # tag
git:https://github.com/user/repo@abc123f # commit
git:https://github.com/user/repo#branch # branch
Storage Layout
Permanent (settings.json)
~/.pi/agent/
hooks/
my-local-hook.ts # root-level file
complex-hook/ # directory with entry point
index.ts
utils.ts
npm/
my-hook@1.2.3/ # npm package
package.json
node_modules/
index.js
@scope/
scoped-hook@2.0.0/
...
git/
github.com/user/repo@v1.0.0/ # git repo
...
tools/
... (same structure)
skills/
... (same structure, but SKILL.md instead of index.ts)
themes/
dark.theme.json # root-level theme
light.theme.json
community-pack/ # theme pack (no entry point needed)
nord.theme.json
dracula.theme.json
npm/
cool-themes@1.0.0/
monokai.theme.json
solarized.theme.json
Ephemeral (CLI flags)
/tmp/pi-extensions/
hooks/
npm/
my-hook@1.0.0/
git/
github.com/user/repo@v1.0.0/
tools/
...
skills/
...
themes/
...
Temp directory persists until OS clears /tmp/. No re-download needed across sessions (usually).
Entry Point Resolution
For each discovered directory, resolve entry point in order:
Hooks & Tools
index.ts(if exists)index.js(if exists)mainfield inpackage.json(if exists)
Skills
SKILL.md(required)
Themes
Themes use recursive pattern matching instead of fixed entry points:
- Scan recursively for
*.theme.jsonfiles at all levels - Each matching file is a separate theme
- Path derived from filename (e.g.,
dark.theme.json→dark,pack/nord.theme.json→pack/nord)
Scanning Algorithm
scan(baseDir, config):
results = []
for entry in baseDir:
skip if entry.name starts with "."
skip if entry.name == "node_modules"
skip if entry.name ends with ".installing"
if entry is file:
if matches rootPattern (e.g., *.ts, *.js, *.md, *.theme.json):
results.add(entry)
if entry is directory:
if config.recursivePattern:
# For themes: scan recursively for *.theme.json everywhere
results.addAll(scan(directory, config))
else if has entryPoint (index.ts, index.js, SKILL.md):
results.add(directory) # load as single extension
else:
results.addAll(scan(directory, config)) # recurse to find extensions
return results
Default directories scanned (always, regardless of settings.json):
~/.pi/agent/<type>/<cwd>/.pi/<type>/
Extension Packs
A key use case is pulling in a pack (collection) of extensions via a directory, npm package, or git repo, then filtering to a subset.
Example: Skill pack
npm:pi-skills@1.0.0 contains:
skills/
brave-search/SKILL.md
browser-tools/SKILL.md
transcribe/SKILL.md
youtube-transcript/SKILL.md
... (10+ skills)
You want only 2 of them:
{
"skills": {
"paths": ["npm:pi-skills@1.0.0"],
"filter": ["brave-search", "youtube-transcript"]
}
}
Example: Theme pack
npm:community-themes@1.0.0 contains:
themes/
nord.theme.json
dracula.theme.json
solarized-dark.theme.json
solarized-light.theme.json
monokai.theme.json
Exclude solarized variants:
{
"themes": {
"paths": ["npm:community-themes@1.0.0"],
"filter": ["!solarized-*"]
}
}
Example: Hook pack
npm:audit-hooks@1.0.0 contains:
hooks/
file-audit/index.ts
command-audit/index.ts
network-audit/index.ts
debug-logger/index.ts
All except debug:
{
"hooks": {
"paths": ["npm:audit-hooks@1.0.0"],
"filter": ["!debug-*"]
}
}
Filtering
Single filter array with ! prefix for exclusion. Patterns are matched against extension paths (directory or filename without extension).
{
"filter": ["pattern1", "pattern2", "!excluded-pattern"]
}
Logic:
- Collect all patterns without
!prefix → include patterns - Collect all patterns with
!prefix → exclude patterns - If include patterns exist: start with extensions matching any include pattern
- If no include patterns: start with all extensions
- Remove extensions matching any exclude pattern
Examples:
["brave-search"] // only brave-search
["brave-*", "docker"] // brave-search, brave-api, docker
["!transcribe"] // all except transcribe
["audit-*", "!audit-debug"] // audit-* except audit-debug
Patterns are glob patterns matched against extension paths.
CLI Arguments
Adding Sources
pi --hook <path|npm:|git:> # add hook source (repeatable)
pi --tool <path|npm:|git:> # add custom tool source (repeatable)
pi --skill <path|npm:|git:> # add skill source (repeatable)
pi --theme <path|npm:|git:> # add theme source (repeatable)
Installation locations for npm/git sources:
| Source | Install location |
|---|---|
| CLI flags | /tmp/pi-extensions/<type>/npm/ or git/ |
Global settings (~/.pi/agent/settings.json) |
~/.pi/agent/<type>/npm/ or git/ |
Project settings (<cwd>/.pi/settings.json) |
<cwd>/.pi/<type>/npm/ or git/ |
File/directory paths are used directly (no installation).
- CLI = ephemeral: cached in temp until OS clears
/tmp/ - Global settings = permanent: installed to user's agent directory
- Project settings = project-local: installed to project's
.pi/directory
Examples:
--hook npm:my-hook@1.0.0→/tmp/pi-extensions/hooks/npm/my-hook@1.0.0/- Global settings.json
npm:my-hook@1.0.0→~/.pi/agent/hooks/npm/my-hook@1.0.0/ - Project settings.json
npm:my-hook@1.0.0→<cwd>/.pi/hooks/npm/my-hook@1.0.0/
This encourages: try via CLI, if you like it, add to settings.json for permanent install.
Filtering
pi --hooks "pattern1,pattern2,!excluded" # filter hooks
pi --custom-tools "pattern1,!excluded" # filter custom tools
pi --skills "pattern1,pattern2" # filter skills
pi --themes "pattern1" # filter themes
Disabling
pi --no-hooks # disable all hooks
pi --no-custom-tools # disable all custom tools
pi --no-skills # disable all skills (already exists)
Built-in Tools
pi --tools read,bash,edit,write # select which built-in tools to enable (unchanged)
Settings Hierarchy
Extensions are configured in settings.json at two levels:
- Global:
~/.pi/agent/settings.json - Project:
<cwd>/.pi/settings.json
Merge behavior:
paths: additive - project paths are added to global pathsfilter: override - project filter replaces global filter if specified
Example:
// Global: ~/.pi/agent/settings.json
{
"hooks": {
"paths": ["npm:audit-hooks@1.0.0"],
"filter": ["!debug-*"]
}
}
// Project: .pi/settings.json
{
"hooks": {
"paths": ["./project-hooks/"],
"filter": ["audit-*"] // overrides global filter
}
}
// Effective:
{
"hooks": {
"paths": ["npm:audit-hooks@1.0.0", "./project-hooks/"],
"filter": ["audit-*"]
}
}
settings.json Structure
{
"hooks": {
"paths": [
"./my-hooks/",
"npm:@scope/hook@1.0.0",
"git:https://github.com/user/hooks@v1.0.0"
],
"filter": ["audit-*", "!audit-debug"]
},
"tools": {
"paths": ["npm:cool-tools@2.0.0"],
"filter": ["!dangerous-tool"]
},
"skills": {
"paths": ["npm:pi-skills@1.0.0", "~/my-skills/"],
"filter": ["brave-search", "git-*", "!git-legacy"]
},
"themes": {
"paths": ["npm:community-themes@1.0.0"]
}
}
Migration from current format:
hooks: string[]→hooks.paths: string[]customTools: string[]→tools.paths: string[]skills.customDirectories→skills.pathsskills.includeSkills→skills.filter(patterns without!)skills.ignoredSkills→skills.filter(patterns with!prefix)
Installation Flow
Target directory depends on source:
- CLI flags:
/tmp/pi-extensions/<type>/npm/orgit/ - Global settings.json:
~/.pi/agent/<type>/npm/orgit/ - Project settings.json:
<cwd>/.pi/<type>/npm/orgit/
Atomic Installation
To prevent corrupted state from interrupted installs (Ctrl+C):
- Install to
<target>.installing/(temporary) - On success, atomically rename to
<target>/ - If interrupted,
<target>/doesn't exist → next run retries cleanly - Scanner filters out
*.installingdirectories (see Scanning Algorithm)
npm Packages
- Parse specifier:
npm:@scope/pkg@1.2.3→ name:@scope/pkg, version:1.2.3 - Determine target dir based on source (CLI → temp, global → agent dir, project → cwd/.pi/)
- If
<target>/exists and has matching version in package.json → skip install - Otherwise:
- Remove stale
<target>.installing/if exists npm pack @scope/pkg@1.2.3→ download tarball- Extract to
<target>.installing/ - If
package.jsonhasdependencies→ runnpm install - Rename
<target>.installing/→<target>/
- Remove stale
Git Repositories
- Parse specifier:
git:https://github.com/user/repo@v1.0.0 - Determine target dir based on source (CLI → temp, global → agent dir, project → cwd/.pi/)
- If
<target>/exists → skip clone - Otherwise:
- Remove stale
<target>.installing/if exists git clone <url>to<target>.installing/git checkout <tag|commit|branch>- If
package.jsonhasdependencies→ runnpm install - Rename
<target>.installing/→<target>/
- Remove stale
Update Command
pi update # update all extensions
pi update hooks # update only hooks
pi update tools skills # update specific types
Behavior:
- For
npm:pkg@<version>: check if newer version of that exact spec exists (e.g.,@latestresolves to newer) - For
git:repo#branch:git pull - For
git:repo@tagorgit:repo@commit: no-op (pinned) - Local files/directories: no-op
Loading Flow (Full)
-
Collect sources:
- Default directories:
~/.pi/agent/<type>/,./.pi/<type>/ - settings.json
<type>.paths - CLI
--<type>arguments
- Default directories:
-
Install remote sources:
- Process
npm:andgit:specifiers - Install to
~/.pi/agent/<type>/npm/orgit/
- Process
-
Scan all sources:
- Recursively discover extensions
- Compute relative path for each
-
Apply filter:
- Combine settings.json
<type>.filterand CLI--<type>spatterns - Filter by path (no loading yet)
- Combine settings.json
-
Load survivors:
- Parse/execute only extensions that passed filter
- Validate (frontmatter, exports, schema)
- Report errors for invalid extensions
Implementation Plan
Overview
This implementation consolidates four separate loading systems (hooks, tools, skills, themes) into a unified extension loading framework with shared logic for source resolution, installation, scanning, filtering, and loading.
New Files
src/core/extensions/types.ts
Extension type definitions shared across all loaders.
export type ExtensionType = "hooks" | "tools" | "skills" | "themes";
export interface ExtensionSource {
type: "file" | "directory" | "npm" | "git";
specifier: string; // original specifier from config/CLI
resolvedPath?: string; // resolved local path after install
}
export interface DiscoveredExtension {
path: string; // relative path (e.g., "brave-search", "npm/@scope/pkg@1.0.0")
absolutePath: string; // absolute filesystem path
entryPoint: string; // resolved entry point file
source: ExtensionSource;
}
export interface ExtensionConfig {
paths?: string[];
filter?: string[];
}
export interface ExtensionTypeConfig {
rootPatterns: string[]; // e.g., ["*.ts", "*.js"]
subdirEntryPoints: string[]; // e.g., ["index.ts", "index.js"]
packageJsonFallback: boolean; // whether to check package.json main
}
export const EXTENSION_CONFIGS: Record<ExtensionType, ExtensionTypeConfig> = {
hooks: {
rootPatterns: ["*.ts", "*.js"],
subdirEntryPoints: ["index.ts", "index.js"],
packageJsonFallback: true,
recursivePattern: false,
},
tools: {
rootPatterns: ["*.ts", "*.js"],
subdirEntryPoints: ["index.ts", "index.js"],
packageJsonFallback: true,
recursivePattern: false,
},
skills: {
rootPatterns: ["*.md"],
subdirEntryPoints: ["SKILL.md"],
packageJsonFallback: false,
recursivePattern: false,
},
themes: {
rootPatterns: ["*.theme.json"],
subdirEntryPoints: [], // not used
packageJsonFallback: false,
recursivePattern: true, // scan for *.theme.json at all levels
},
};
src/core/extensions/source-resolver.ts
Handles parsing and installing npm/git sources.
export function parseSource(specifier: string): ExtensionSource;
export type InstallLocation = "cli" | "global" | "project";
export async function installSource(
source: ExtensionSource,
type: ExtensionType,
location: InstallLocation,
cwd: string, // needed for project-local installs
): Promise<string>;
export function isRemoteSource(specifier: string): boolean;
export function getInstallDir(type: ExtensionType, location: InstallLocation, cwd: string): string;
Key functions:
parseNpmSpecifier(spec): Parsenpm:@scope/pkg@1.2.3→{ name, version }parseGitSpecifier(spec): Parsegit:url@tagorgit:url#branchinstallNpmPackage(name, version, targetDir):npm pack+ extract +npm installinstallGitRepo(url, ref, targetDir):git clone+ checkout +npm installgetTargetDir(type, ephemeral): Returns temp dir or agent dir based on source
src/core/extensions/scanner.ts
Unified recursive scanning for all extension types.
export function scanDirectory(
baseDir: string,
config: ExtensionTypeConfig,
): DiscoveredExtension[];
export function resolveEntryPoint(
dir: string,
config: ExtensionTypeConfig,
): string | null;
export function getRelativePath(
absolutePath: string,
baseDir: string,
config: ExtensionTypeConfig,
): string; // strips entry point filename and extension
src/core/extensions/filter.ts
Filter logic using glob patterns with ! exclusion. Matches against extension.path.
export function applyFilter(
extensions: DiscoveredExtension[],
patterns: string[],
): DiscoveredExtension[];
export function parseFilterPatterns(patterns: string[]): {
include: string[];
exclude: string[];
};
export function matchesPattern(path: string, pattern: string): boolean;
src/core/extensions/loader.ts
Main entry point coordinating the full loading flow.
export interface LoadExtensionsOptions {
type: ExtensionType;
cwd: string;
agentDir: string;
globalPaths: string[]; // from global settings.json → install to agentDir
projectPaths: string[]; // from project settings.json → install to cwd/.pi/
cliPaths: string[]; // from CLI flags → install to /tmp/
filter: string[]; // combined filter patterns
}
export interface LoadExtensionsResult<T> {
extensions: T[];
errors: Array<{ path: string; error: string }>;
}
export async function discoverExtensions(
options: LoadExtensionsOptions,
): Promise<DiscoveredExtension[]>;
src/core/extensions/index.ts
Public exports.
Modified Files
src/config.ts
Add directory getters:
export function getHooksDir(): string {
return join(getAgentDir(), "hooks");
}
export function getSkillsDir(): string {
return join(getAgentDir(), "skills");
}
// getToolsDir() already exists
// getThemesDir() = bundled themes (in package)
// getCustomThemesDir() = ~/.pi/agent/themes/ (user themes) - already exists
src/core/settings-manager.ts
Update Settings interface:
// Old:
hooks?: string[];
customTools?: string[];
skills?: SkillsSettings;
// New:
hooks?: ExtensionConfig;
tools?: ExtensionConfig;
skills?: ExtensionConfig; // simplified from SkillsSettings
themes?: ExtensionConfig;
Add migration logic for old format:
function migrateSettings(settings: unknown): Settings {
// Convert hooks: string[] → hooks: { paths: string[] }
// Convert customTools: string[] → tools: { paths: string[] }
// Convert skills.customDirectories → skills.paths
// Convert skills.includeSkills/ignoredSkills → skills.filter
}
Add unified getters:
getExtensionConfig(type: ExtensionType): ExtensionConfig;
getExtensionPaths(type: ExtensionType): string[];
getExtensionFilter(type: ExtensionType): string[];
Update merge logic (paths are additive, filter overrides):
function mergeExtensionConfig(global: ExtensionConfig, project: ExtensionConfig): ExtensionConfig {
return {
paths: [...(global.paths ?? []), ...(project.paths ?? [])], // additive
filter: project.filter ?? global.filter, // override
};
}
src/cli/args.ts
Update Args interface:
// Built-in tools (unchanged)
tools?: ToolName[]; // --tools read,bash,edit,write
// Source flags
hooks?: string[]; // --hook (existing, repeatable)
customTools?: string[]; // --tool (existing, repeatable)
skills?: string[]; // --skill (new, repeatable)
themes?: string[]; // --theme (new, repeatable)
// Filter flags
hooksFilter?: string[]; // --hooks "patterns"
customToolsFilter?: string[];// --custom-tools "patterns"
skillsFilter?: string[]; // --skills "patterns" (existing)
themesFilter?: string[]; // --themes "patterns"
// Disable flags
noHooks?: boolean; // --no-hooks
noCustomTools?: boolean; // --no-custom-tools
noSkills?: boolean; // --no-skills (existing)
Update argument parsing:
// --tools (built-in tools, unchanged)
} else if (arg === "--tools" && i + 1 < args.length) {
// ... existing logic for built-in tools
// --tool (add custom tool source)
} else if (arg === "--tool" && i + 1 < args.length) {
result.customTools = result.customTools ?? [];
result.customTools.push(args[++i]);
// --custom-tools (filter custom tools)
} else if (arg === "--custom-tools" && i + 1 < args.length) {
result.customToolsFilter = args[++i].split(",").map(s => s.trim());
// --no-custom-tools
} else if (arg === "--no-custom-tools") {
result.noCustomTools = true;
// --skill (add source) - new
} else if (arg === "--skill" && i + 1 < args.length) {
result.skills = result.skills ?? [];
result.skills.push(args[++i]);
// --theme (add source) - new
} else if (arg === "--theme" && i + 1 < args.length) {
result.themes = result.themes ?? [];
result.themes.push(args[++i]);
// --themes (filter) - new
} else if (arg === "--themes" && i + 1 < args.length) {
result.themesFilter = args[++i].split(",").map(s => s.trim());
// --hooks (filter) - new
} else if (arg === "--hooks" && i + 1 < args.length) {
result.hooksFilter = args[++i].split(",").map(s => s.trim());
// --no-hooks - new
} else if (arg === "--no-hooks") {
result.noHooks = true;
Add pi update command handling.
src/core/hooks/loader.ts
Refactor to use extension system:
import { discoverExtensions, type DiscoveredExtension } from "../extensions/index.js";
export async function discoverAndLoadHooks(
options: {
cwd: string;
agentDir?: string;
configuredPaths?: string[];
cliPaths?: string[];
filter?: string[];
}
): Promise<LoadHooksResult> {
const discovered = await discoverExtensions({
type: "hooks",
defaultDirs: [join(agentDir, "hooks"), join(cwd, ".pi", "hooks")],
configuredPaths: options.configuredPaths ?? [],
cliPaths: options.cliPaths ?? [],
filter: options.filter ?? [],
});
// Load each discovered hook using existing jiti logic
const results = await Promise.all(
discovered.map(ext => loadHook(ext.entryPoint, cwd))
);
// ... rest of existing logic
}
Remove duplicate code:
expandPath()→ use from extensions/source-resolver.tsresolveHookPath()→ use from extensions/scanner.ts- Discovery logic → use discoverExtensions()
src/core/custom-tools/loader.ts
Same refactoring pattern as hooks/loader.ts.
src/core/skills.ts
Refactor loadSkills() and loadSkillsFromDir():
export function loadSkills(options: LoadSkillsOptions): LoadSkillsResult {
const discovered = await discoverExtensions({
type: "skills",
defaultDirs: [
// existing default dirs
join(homedir(), ".codex", "skills"),
join(homedir(), ".claude", "skills"),
join(agentDir, "skills"),
join(cwd, ".pi", "skills"),
],
configuredPaths: options.paths ?? [],
cliPaths: options.cliPaths ?? [],
filter: options.filter ?? [],
});
// Load each discovered skill using existing parsing logic
// ...
}
Remove:
loadSkillsFromDirInternal()recursive logic → use scanner.tsmatchesIncludePatterns()/matchesIgnorePatterns()→ use filter.ts
src/modes/interactive/theme/theme.ts
Refactor getAvailableThemes() and loadThemeJson():
export function getAvailableThemes(): string[] {
const discovered = discoverExtensions({
type: "themes",
defaultDirs: [getThemesDir(), getCustomThemesDir()],
configuredPaths: settingsManager.getExtensionPaths("themes"),
cliPaths: [], // from args
filter: settingsManager.getExtensionFilter("themes"),
});
return discovered.map(ext => ext.path);
}
src/core/sdk.ts
Update to pass new options structure:
// Hooks
const { hooks, errors } = await discoverAndLoadHooks({
cwd,
agentDir,
configuredPaths: settingsManager.getExtensionPaths("hooks"),
cliPaths: options.additionalHookPaths,
filter: [...settingsManager.getExtensionFilter("hooks"), ...(options.hooksFilter ?? [])],
});
// Tools
const result = await discoverAndLoadCustomTools({
cwd,
agentDir,
configuredPaths: settingsManager.getExtensionPaths("tools"),
cliPaths: options.additionalToolPaths,
filter: [...settingsManager.getExtensionFilter("tools"), ...(options.toolsFilter ?? [])],
builtInToolNames: Object.keys(allTools),
});
src/modes/interactive/interactive-mode.ts
Update skill loading to use new options structure.
Migration & Backwards Compatibility
Settings Migration
When loading settings.json, detect old format and migrate:
// Old format detection
if (Array.isArray(settings.hooks)) {
settings.hooks = { paths: settings.hooks };
}
if (Array.isArray(settings.customTools)) {
settings.tools = { paths: settings.customTools };
delete settings.customTools;
}
if (settings.skills?.customDirectories) {
settings.skills.paths = settings.skills.customDirectories;
delete settings.skills.customDirectories;
}
// ... etc
CLI Compatibility
--toolswith built-in tool names still works (detected by checking if values match known tool names)- Alternatively, deprecation warning and suggest
--builtin-tools
Implementation Order
-
Phase 1: Core extension framework
- Create
src/core/extensions/directory - Implement types.ts, scanner.ts, filter.ts
- Unit tests for scanning and filtering
- Create
-
Phase 2: Source resolution
- Implement source-resolver.ts (npm + git)
- Add
npm packandgit clonelogic - Unit tests for source parsing and installation
-
Phase 3: Settings migration
- Update settings-manager.ts with new types
- Add migration logic
- Update config.ts with new directory getters
-
Phase 4: Refactor loaders
- Refactor hooks/loader.ts
- Refactor custom-tools/loader.ts
- Refactor skills.ts
- Refactor theme.ts
- Remove duplicate code
-
Phase 5: CLI updates
- Add new flags to args.ts
- Update help text
- Add
pi updatecommand
-
Phase 6: Integration
- Update sdk.ts
- Update interactive-mode.ts
- End-to-end testing
-
Phase 7: Documentation
- Update README.md
- Update docs/hooks.md, docs/custom-tools.md
- Add examples for npm/git extensions
Testing Strategy
Unit Tests
test/extensions/scanner.test.ts: Directory scanning, entry point resolutiontest/extensions/filter.test.ts: Pattern matching, include/exclude logictest/extensions/source-resolver.test.ts: npm/git specifier parsing
Integration Tests
test/extensions/npm-install.test.ts: Full npm package installation flowtest/extensions/git-clone.test.ts: Full git repository cloning flowtest/extensions/loading.test.ts: End-to-end extension discovery and loading
Migration Tests
test/settings-migration.test.ts: Old → new settings format conversion
File Summary
New Files (7)
src/core/extensions/types.tssrc/core/extensions/source-resolver.tssrc/core/extensions/scanner.tssrc/core/extensions/filter.tssrc/core/extensions/loader.tssrc/core/extensions/index.tsdocs/extension-loading.md(this file)
Modified Files (9)
src/config.ts- add directory getterssrc/core/settings-manager.ts- new types, migrationsrc/cli/args.ts- new flags, update parsingsrc/core/hooks/loader.ts- refactor to use extensionssrc/core/custom-tools/loader.ts- refactor to use extensionssrc/core/skills.ts- refactor to use extensionssrc/modes/interactive/theme/theme.ts- refactor to use extensionssrc/core/sdk.ts- update option passingsrc/modes/interactive/interactive-mode.ts- update skill loading
Deleted Code (moved to extensions/)
- Duplicate
expandPath(),normalizeUnicodeSpaces()functions - Duplicate discovery/scanning logic
- Duplicate path resolution logic