@henryqw/pi-add-dir 3.1.0 → 3.1.2
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 +14 -14
- package/extensions/add-dir-helpers.ts +2 -6
- package/extensions/add-dir.ts +35 -70
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# `@henryqw/pi-add-dir`
|
|
2
2
|
|
|
3
|
-
Give the current Pi session context, skills, and searchable files
|
|
3
|
+
Give the current Pi session the context, skills, and searchable files of external directories. Pi uses these resources in place, so you do not copy or link the directories.
|
|
4
4
|
|
|
5
5
|

|
|
6
6
|
|
|
@@ -14,19 +14,19 @@ Requires Pi 1.1.0 or later in the 1.x series.
|
|
|
14
14
|
|
|
15
15
|
## Use
|
|
16
16
|
|
|
17
|
-
Run `/dir-add
|
|
17
|
+
Run `/dir-add` and enter a directory. Then run `/dir-ls` to make sure that Pi added it. Pi lists the directory and makes its supported resources available.
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
Humans use the slash commands from the Pi prompt. Pi agents use the tools.
|
|
20
20
|
|
|
21
21
|
| Surface | Type | Purpose |
|
|
22
22
|
| --- | --- | --- |
|
|
23
|
-
| `/dir-add [path]` | command | Add to this session by default
|
|
24
|
-
| `/dir-ls` | command | List directories with their scope
|
|
23
|
+
| `/dir-add [path]` | command | Add to this session by default. To persist for the linked worktrees of this Git repository or for every workspace, pass `--project` or `--global` before the path. Omit the path to enter it at the prompt. Supports `~`. |
|
|
24
|
+
| `/dir-ls` | command | List directories with their scope. Select one to remove it from that scope. |
|
|
25
25
|
| `/dir-reload` | command | Reload external directory resources. |
|
|
26
26
|
| `add_directory(path)` | tool | Add a directory to this session and return its context and instructions. Model-only: codemode scripts cannot call it. |
|
|
27
|
-
| `search_external_files(pattern, maxResults?)` | tool |
|
|
27
|
+
| `search_external_files(pattern, maxResults?)` | tool | Search added directories with a glob. Returns up to 50 results by default; `maxResults` accepts up to 1,000. Returns a structured result to codemode scripts. |
|
|
28
28
|
| `@` file autocomplete | ui | Find files in added directories and insert their absolute paths. |
|
|
29
|
-
| External directory widget | ui | Show added directories and
|
|
29
|
+
| External directory widget | ui | Show added directories and refer to `/dir-ls` for management. |
|
|
30
30
|
|
|
31
31
|
Added directories give Pi these resources:
|
|
32
32
|
|
|
@@ -34,13 +34,13 @@ Added directories give Pi these resources:
|
|
|
34
34
|
- Skills that Pi loads from `.pi/skills`, `.agents/skills`, and `.claude/skills`.
|
|
35
35
|
- Files in the editor's `@` autocomplete, with absolute paths.
|
|
36
36
|
|
|
37
|
-
`/dir-add` reloads only when the discovered external skill paths change
|
|
37
|
+
`/dir-add` reloads only when the discovered external skill paths change. A directory that contains skills does not alone cause a reload. `add_directory` stays session-local. When its result asks for `/reload`, run `/reload` to update external skills.
|
|
38
38
|
|
|
39
39
|
### Programmatic use
|
|
40
40
|
|
|
41
|
-
`search_external_files` declares an output schema. Programmatic callers such as Pi `codemode` scripts receive `{ pattern, maxResults, searchedDirectories, totalFound, truncated, directories }`. `directories` lists each directory
|
|
41
|
+
`search_external_files` declares an output schema. Programmatic callers, such as Pi `codemode` scripts, receive `{ pattern, maxResults, searchedDirectories, totalFound, truncated, directories }`. `directories` lists each directory that has at least one match, with its label and absolute file paths. `searchedDirectories` counts only the directories that the tool searched, including directories with no matches. It does not count directories that the tool skipped after it reached the cap. `truncated` is true when the result cap stopped the search. In that case, later directories or files can contain more matches. The model receives the same matches as text. If there are no added directories, a call to the tool is a tool error, and scripts reject with the message.
|
|
42
42
|
|
|
43
|
-
`add_directory` is declared to the model only (Pi `model-only` exposure). Codemode scripts and other tools cannot call it,
|
|
43
|
+
`add_directory` is declared to the model only (Pi `model-only` exposure). Codemode scripts and other tools cannot call it. Thus, the `AGENTS.md`, `CLAUDE.md`, and skills of another directory enter the session only through a model-visible tool call or a user command. This package does not enable codemode.
|
|
44
44
|
|
|
45
45
|
## Config
|
|
46
46
|
|
|
@@ -50,16 +50,16 @@ Package-owned: `~/.pi/agent/config/pi-add-dir/config.json`
|
|
|
50
50
|
| --- | --- | --- | --- |
|
|
51
51
|
| `directories` | Directories loaded for every workspace. `/dir-add --global` updates this list. | Array of unique absolute paths without control characters. | `[]` |
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
If the file is missing, the extension uses the default. Invalid config stops the load, and the extension does not overwrite the file. Reload Pi after you edit the file manually.
|
|
54
54
|
|
|
55
55
|
## State and storage
|
|
56
56
|
|
|
57
|
-
Session directories
|
|
57
|
+
Session directories are stored in the current session branch. A new session addition rejects a directory that is inside the current working directory or that contains it. It also rejects a directory that overlaps another active added directory. When the working directory changes, the extension does not check restored session entries for overlap again. Project directories use the repeatable local Git config key `pi-add-dir.directory` of the repository. Linked worktrees share local Git config. A cloned repository cannot inject this config, and it stays on the local machine. Thus, `/dir-add --project` requires a Git repository.
|
|
58
58
|
|
|
59
59
|
Persistent scopes are explicit because added directories can inject `AGENTS.md`, `CLAUDE.md`, and skills.
|
|
60
60
|
|
|
61
61
|
## Limits and recovery
|
|
62
62
|
|
|
63
|
-
Search supports basename and relative-path globs. It skips `.git` and `node_modules` and does not follow symbolic links. It uses Node filesystem traversal and returns at most 1,000 results
|
|
63
|
+
Search supports basename and relative-path globs. It skips `.git` and `node_modules` and does not follow symbolic links. It uses Node filesystem traversal and returns at most 1,000 results for each call.
|
|
64
64
|
|
|
65
|
-
Missing or overlapping **project and global** directories
|
|
65
|
+
Missing or overlapping **project and global** directories stay in the config, but the extension skips them with a warning. Restore a missing directory at its original path, or resolve the overlap. Then run `/dir-reload` to scan again. To change or discard a configured path, use `/dir-ls` to **remove** it from its scope. If necessary, run `/dir-add` with the corrected path. `/dir-ls` cannot repair paths in place.
|
|
@@ -23,7 +23,7 @@ function isMissingPathError(error: unknown): boolean {
|
|
|
23
23
|
return error.code === "ENOENT" || error.code === "ENOTDIR";
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
function expandUserPath(input: string): string {
|
|
27
27
|
if (input === "~") return homedir();
|
|
28
28
|
if (input.startsWith("~/") || input.startsWith(`~${path.sep}`)) return path.join(homedir(), input.slice(2));
|
|
29
29
|
return input;
|
|
@@ -49,7 +49,7 @@ export function dirExists(dir: string): boolean {
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
function readFileSafe(filePath: string): string | null {
|
|
53
53
|
try {
|
|
54
54
|
return readFileSync(filePath, "utf8");
|
|
55
55
|
} catch {
|
|
@@ -113,8 +113,6 @@ export function collectSkillPaths(dirs: AddedDir[]): string[] {
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
export function buildContextInjection(dirs: AddedDir[]): string {
|
|
116
|
-
if (dirs.length === 0) return "";
|
|
117
|
-
|
|
118
116
|
const sections = [
|
|
119
117
|
"\n\n## External Directories (added via pi-add-dir)",
|
|
120
118
|
`\nThe following ${dirs.length} external director${dirs.length === 1 ? "y is" : "ies are"} included in this session. You can read, edit, and write files in these directories using absolute paths.\n`,
|
|
@@ -154,7 +152,6 @@ export async function findFiles(
|
|
|
154
152
|
|
|
155
153
|
const directories = [root];
|
|
156
154
|
while (directories.length > 0) {
|
|
157
|
-
signal?.throwIfAborted();
|
|
158
155
|
const dir = directories.pop()!;
|
|
159
156
|
let entries;
|
|
160
157
|
try {
|
|
@@ -166,7 +163,6 @@ export async function findFiles(
|
|
|
166
163
|
}
|
|
167
164
|
signal?.throwIfAborted();
|
|
168
165
|
for (const entry of entries) {
|
|
169
|
-
signal?.throwIfAborted();
|
|
170
166
|
if (entry.isSymbolicLink()) continue;
|
|
171
167
|
|
|
172
168
|
const fullPath = path.join(dir, entry.name);
|
package/extensions/add-dir.ts
CHANGED
|
@@ -84,7 +84,6 @@ interface AddDirectoryDetails {
|
|
|
84
84
|
directory: string;
|
|
85
85
|
hasAgentsMd: boolean;
|
|
86
86
|
hasClaudeMd: boolean;
|
|
87
|
-
skillCount: number;
|
|
88
87
|
skillNames: string[];
|
|
89
88
|
}
|
|
90
89
|
|
|
@@ -94,40 +93,34 @@ interface SearchDetails {
|
|
|
94
93
|
dirCount: number;
|
|
95
94
|
}
|
|
96
95
|
|
|
97
|
-
|
|
96
|
+
interface SessionDir {
|
|
97
|
+
absolutePath: string;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function isAddedDir(value: unknown): value is SessionDir {
|
|
98
101
|
if (!value || typeof value !== "object") return false;
|
|
99
|
-
const dir = value as Partial<
|
|
100
|
-
return typeof dir.absolutePath === "string" && isAbsolute(dir.absolutePath)
|
|
102
|
+
const dir = value as Partial<SessionDir>;
|
|
103
|
+
return typeof dir.absolutePath === "string" && isAbsolute(dir.absolutePath);
|
|
101
104
|
}
|
|
102
105
|
|
|
103
|
-
function readState(data: unknown):
|
|
106
|
+
function readState(data: unknown): SessionDir[] {
|
|
104
107
|
if (!data || typeof data !== "object") return [];
|
|
105
108
|
const dirs = (data as { dirs?: unknown }).dirs;
|
|
106
109
|
return Array.isArray(dirs) ? dirs.filter(isAddedDir) : [];
|
|
107
110
|
}
|
|
108
111
|
|
|
109
|
-
function parseAddArgs(args: string | undefined
|
|
110
|
-
const input = args
|
|
112
|
+
function parseAddArgs(args: string): ParsedAddArgs | undefined {
|
|
113
|
+
const input = args.trim();
|
|
111
114
|
if (!input) return { source: "session", path: "" };
|
|
112
115
|
for (const source of ["project", "global"] as const) {
|
|
113
116
|
const flag = `--${source}`;
|
|
114
117
|
if (input === flag) return { source, path: "" };
|
|
115
118
|
if (input.startsWith(`${flag} `)) return { source, path: input.slice(flag.length).trim() };
|
|
116
119
|
}
|
|
117
|
-
if (input.startsWith("--"))
|
|
120
|
+
if (input.startsWith("--")) return undefined;
|
|
118
121
|
return { source: "session", path: input };
|
|
119
122
|
}
|
|
120
123
|
|
|
121
|
-
function validateStoredPaths(paths: string[], source: DirSource): void {
|
|
122
|
-
if (
|
|
123
|
-
paths.some(
|
|
124
|
-
(path) => typeof path !== "string" || !isAbsolute(path) || /\p{C}/u.test(path),
|
|
125
|
-
)
|
|
126
|
-
) {
|
|
127
|
-
throw new Error(`Invalid ${source} pi-add-dir configuration: expected absolute paths.`);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
124
|
function hasGitMarker(cwd: string): boolean {
|
|
132
125
|
let directory = resolveDir(cwd, cwd);
|
|
133
126
|
while (true) {
|
|
@@ -150,12 +143,15 @@ function isWithinDir(dir: string, candidate: string): boolean {
|
|
|
150
143
|
return relativePath === "" || (!relativePath.startsWith(`..${sep}`) && relativePath !== ".." && !isAbsolute(relativePath));
|
|
151
144
|
}
|
|
152
145
|
|
|
146
|
+
function overlaps(a: string, b: string): boolean {
|
|
147
|
+
return isWithinDir(a, b) || isWithinDir(b, a);
|
|
148
|
+
}
|
|
149
|
+
|
|
153
150
|
function contextDetails(dirCtx: DirContext, absolutePath: string): AddDirectoryDetails {
|
|
154
151
|
return {
|
|
155
152
|
directory: absolutePath,
|
|
156
153
|
hasAgentsMd: dirCtx.agentsMd !== null,
|
|
157
154
|
hasClaudeMd: dirCtx.claudeMd !== null,
|
|
158
|
-
skillCount: dirCtx.skills.size,
|
|
159
155
|
skillNames: [...dirCtx.skills],
|
|
160
156
|
};
|
|
161
157
|
}
|
|
@@ -231,7 +227,7 @@ export function createExternalAutocompleteProvider(
|
|
|
231
227
|
|
|
232
228
|
export default function addDirExtension(pi: ExtensionAPI, options: ExtensionOptions = {}): void {
|
|
233
229
|
const globalStore = createAddDirConfigStore(options.agentDir);
|
|
234
|
-
let sessionDirs:
|
|
230
|
+
let sessionDirs: SessionDir[] = [];
|
|
235
231
|
let projectPaths: string[] = [];
|
|
236
232
|
let globalPaths: string[] = [];
|
|
237
233
|
let managedDirs: ScopedDir[] = [];
|
|
@@ -318,17 +314,11 @@ export default function addDirExtension(pi: ExtensionAPI, options: ExtensionOpti
|
|
|
318
314
|
}
|
|
319
315
|
const duplicate = nextActive.find((dir) => dir.absolutePath === absolutePath);
|
|
320
316
|
if (!inactiveReason && duplicate) inactiveReason = `shadowed by ${duplicate.source} scope`;
|
|
321
|
-
if (
|
|
322
|
-
!inactiveReason &&
|
|
323
|
-
(isWithinDir(cwdPath, absolutePath) || isWithinDir(absolutePath, cwdPath))
|
|
324
|
-
) {
|
|
317
|
+
if (!inactiveReason && overlaps(cwdPath, absolutePath)) {
|
|
325
318
|
inactiveReason = "overlaps current working directory scope";
|
|
326
319
|
}
|
|
327
320
|
const overlap = !inactiveReason
|
|
328
|
-
? nextActive.find(
|
|
329
|
-
(dir) =>
|
|
330
|
-
isWithinDir(dir.absolutePath, absolutePath) || isWithinDir(absolutePath, dir.absolutePath),
|
|
331
|
-
)
|
|
321
|
+
? nextActive.find((dir) => overlaps(dir.absolutePath, absolutePath))
|
|
332
322
|
: undefined;
|
|
333
323
|
if (overlap) inactiveReason = `overlaps ${overlap.source} directory ${overlap.absolutePath}`;
|
|
334
324
|
const scoped = {
|
|
@@ -361,7 +351,7 @@ export default function addDirExtension(pi: ExtensionAPI, options: ExtensionOpti
|
|
|
361
351
|
}
|
|
362
352
|
|
|
363
353
|
function persistState(): void {
|
|
364
|
-
pi.appendEntry(STATE_TYPE, { dirs: sessionDirs.map((
|
|
354
|
+
pi.appendEntry(STATE_TYPE, { dirs: sessionDirs.map(({ absolutePath }) => ({ absolutePath })) });
|
|
365
355
|
}
|
|
366
356
|
|
|
367
357
|
async function readProjectPaths(cwd: string, required: boolean): Promise<string[]> {
|
|
@@ -381,7 +371,9 @@ export default function addDirExtension(pi: ExtensionAPI, options: ExtensionOpti
|
|
|
381
371
|
throw new Error(`Cannot read project pi-add-dir configuration: ${result.stderr.trim() || `git exited ${result.code}`}`);
|
|
382
372
|
}
|
|
383
373
|
const paths = result.stdout.split("\0").filter((path) => path.length > 0);
|
|
384
|
-
|
|
374
|
+
if (paths.some((path) => !isAbsolute(path) || /\p{C}/u.test(path))) {
|
|
375
|
+
throw new Error("Invalid project pi-add-dir configuration: expected absolute paths.");
|
|
376
|
+
}
|
|
385
377
|
return [...new Set(paths)];
|
|
386
378
|
}
|
|
387
379
|
|
|
@@ -424,7 +416,6 @@ export default function addDirExtension(pi: ExtensionAPI, options: ExtensionOpti
|
|
|
424
416
|
async function addDir(
|
|
425
417
|
dirPath: string,
|
|
426
418
|
source: DirSource,
|
|
427
|
-
cwd: string,
|
|
428
419
|
ctx: ExtensionContext,
|
|
429
420
|
): Promise<{ ok: boolean; message: string; resourcesChanged: boolean; absolutePath?: string; context?: DirContext }> {
|
|
430
421
|
const input = dirPath.trim();
|
|
@@ -432,7 +423,7 @@ export default function addDirExtension(pi: ExtensionAPI, options: ExtensionOpti
|
|
|
432
423
|
|
|
433
424
|
let absolutePath: string;
|
|
434
425
|
try {
|
|
435
|
-
absolutePath = resolveDir(input, cwd);
|
|
426
|
+
absolutePath = resolveDir(input, ctx.cwd);
|
|
436
427
|
if (!dirExists(absolutePath)) {
|
|
437
428
|
return { ok: false, message: `Directory does not exist: ${absolutePath}`, resourcesChanged: false };
|
|
438
429
|
}
|
|
@@ -453,14 +444,11 @@ export default function addDirExtension(pi: ExtensionAPI, options: ExtensionOpti
|
|
|
453
444
|
resourcesChanged: false,
|
|
454
445
|
};
|
|
455
446
|
}
|
|
456
|
-
|
|
457
|
-
if (isWithinDir(cwdPath, absolutePath) || isWithinDir(absolutePath, cwdPath)) {
|
|
447
|
+
if (overlaps(resolveDir(ctx.cwd, ctx.cwd), absolutePath)) {
|
|
458
448
|
return { ok: false, message: "Directory overlaps current working directory scope.", resourcesChanged: false };
|
|
459
449
|
}
|
|
460
450
|
const overlap = addedDirs.find(
|
|
461
|
-
(dir) =>
|
|
462
|
-
dir.absolutePath !== absolutePath &&
|
|
463
|
-
(isWithinDir(dir.absolutePath, absolutePath) || isWithinDir(absolutePath, dir.absolutePath)),
|
|
451
|
+
(dir) => dir.absolutePath !== absolutePath && overlaps(dir.absolutePath, absolutePath),
|
|
464
452
|
);
|
|
465
453
|
if (overlap) {
|
|
466
454
|
return {
|
|
@@ -475,32 +463,13 @@ export default function addDirExtension(pi: ExtensionAPI, options: ExtensionOpti
|
|
|
475
463
|
const label = basename(absolutePath) || absolutePath;
|
|
476
464
|
try {
|
|
477
465
|
if (source === "session") {
|
|
478
|
-
sessionDirs.push({ absolutePath
|
|
466
|
+
sessionDirs.push({ absolutePath });
|
|
479
467
|
persistState();
|
|
480
468
|
} else {
|
|
481
|
-
|
|
469
|
+
await writePersistentPath(source, absolutePath, ctx);
|
|
482
470
|
if (sessionMatch) {
|
|
483
471
|
sessionDirs = sessionDirs.filter((dir) => resolveDir(dir.absolutePath, ctx.cwd) !== absolutePath);
|
|
484
|
-
|
|
485
|
-
persistState();
|
|
486
|
-
} catch (error) {
|
|
487
|
-
sessionDirs = previousSessionDirs;
|
|
488
|
-
throw error;
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
try {
|
|
492
|
-
await writePersistentPath(source, absolutePath, ctx);
|
|
493
|
-
} catch (error) {
|
|
494
|
-
if (sessionMatch) {
|
|
495
|
-
sessionDirs = previousSessionDirs;
|
|
496
|
-
try {
|
|
497
|
-
persistState();
|
|
498
|
-
} catch (recoveryError) {
|
|
499
|
-
const recoveryMessage = recoveryError instanceof Error ? recoveryError.message : String(recoveryError);
|
|
500
|
-
throw new Error(`Persistent write failed and session recovery failed: ${recoveryMessage}`, { cause: error });
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
throw error;
|
|
472
|
+
persistState();
|
|
504
473
|
}
|
|
505
474
|
}
|
|
506
475
|
} catch (error) {
|
|
@@ -577,18 +546,15 @@ export default function addDirExtension(pi: ExtensionAPI, options: ExtensionOpti
|
|
|
577
546
|
description: "Reload external directory resources",
|
|
578
547
|
handler: async (_args, ctx) => {
|
|
579
548
|
await ctx.reload();
|
|
580
|
-
return;
|
|
581
549
|
},
|
|
582
550
|
});
|
|
583
551
|
|
|
584
552
|
pi.registerCommand("dir-add", {
|
|
585
553
|
description: "Add an external directory to this session, project, or global scope",
|
|
586
554
|
handler: async (args, ctx) => {
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
} catch (error) {
|
|
591
|
-
ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
|
|
555
|
+
const parsed = parseAddArgs(args);
|
|
556
|
+
if (!parsed) {
|
|
557
|
+
ctx.ui.notify(DIR_ADD_USAGE, "error");
|
|
592
558
|
return;
|
|
593
559
|
}
|
|
594
560
|
if (!parsed.path) {
|
|
@@ -597,7 +563,7 @@ export default function addDirExtension(pi: ExtensionAPI, options: ExtensionOpti
|
|
|
597
563
|
parsed.path = prompted;
|
|
598
564
|
}
|
|
599
565
|
|
|
600
|
-
const result = await addDir(parsed.path, parsed.source, ctx
|
|
566
|
+
const result = await addDir(parsed.path, parsed.source, ctx);
|
|
601
567
|
const message = result.ok && result.resourcesChanged ? `${result.message} Reloading external skills...` : result.message;
|
|
602
568
|
ctx.ui.notify(message, result.ok ? "info" : "error");
|
|
603
569
|
if (result.ok && result.resourcesChanged) await ctx.reload();
|
|
@@ -645,8 +611,7 @@ export default function addDirExtension(pi: ExtensionAPI, options: ExtensionOpti
|
|
|
645
611
|
annotations: { destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
646
612
|
|
|
647
613
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
648
|
-
const
|
|
649
|
-
const result = await addDir(inputPath, "session", ctx.cwd, ctx);
|
|
614
|
+
const result = await addDir(params.path, "session", ctx);
|
|
650
615
|
if (!result.ok) throw new Error(result.message);
|
|
651
616
|
|
|
652
617
|
const absolutePath = result.absolutePath!;
|
|
@@ -679,7 +644,7 @@ export default function addDirExtension(pi: ExtensionAPI, options: ExtensionOpti
|
|
|
679
644
|
|
|
680
645
|
renderResult(result, { expanded }, theme) {
|
|
681
646
|
const details = result.details as AddDirectoryDetails | undefined;
|
|
682
|
-
if (!details) {
|
|
647
|
+
if (!details?.directory) {
|
|
683
648
|
const content = result.content[0];
|
|
684
649
|
const text = content?.type === "text" ? content.text : "Done";
|
|
685
650
|
return new Text(theme.fg("success", `✓ ${text}`), 0, 0);
|
|
@@ -689,7 +654,7 @@ export default function addDirExtension(pi: ExtensionAPI, options: ExtensionOpti
|
|
|
689
654
|
const badges: string[] = [];
|
|
690
655
|
if (details.hasAgentsMd) badges.push(theme.fg("accent", "AGENTS.md"));
|
|
691
656
|
if (details.hasClaudeMd) badges.push(theme.fg("accent", "CLAUDE.md"));
|
|
692
|
-
if (details.
|
|
657
|
+
if (details.skillNames.length > 0) badges.push(theme.fg("warning", `${details.skillNames.length} skills`));
|
|
693
658
|
if (badges.length > 0) parts.push(theme.fg("dim", " | ") + badges.join(theme.fg("dim", ", ")));
|
|
694
659
|
if (expanded && details.skillNames.length > 0) {
|
|
695
660
|
parts.push("\n" + theme.fg("muted", " Skills: ") + details.skillNames.map((name) => theme.fg("text", name)).join(", "));
|