pi-powerline 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/breadcrumb.ts +1 -1
- package/editor.ts +2 -2
- package/footer.ts +10 -53
- package/header.ts +178 -33
- package/index.ts +24 -49
- package/package.json +4 -4
- package/settings.ts +4 -11
- package/widget.ts +2 -2
package/breadcrumb.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* widget.ts and editor.ts to render the model→folder breadcrumb.
|
|
6
6
|
*/
|
|
7
7
|
import { basename } from 'node:path';
|
|
8
|
-
import type { ExtensionContext, Theme } from '@
|
|
8
|
+
import type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';
|
|
9
9
|
|
|
10
10
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
11
11
|
// nerd font detection
|
package/editor.ts
CHANGED
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
* Switches to bash-mode coloring when the prompt starts with !.
|
|
6
6
|
* Editor is always enabled; breadcrumb mode controls whether widget info is embedded.
|
|
7
7
|
*/
|
|
8
|
-
import { type EditorTheme, truncateToWidth, visibleWidth } from '@
|
|
8
|
+
import { type EditorTheme, truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';
|
|
9
9
|
import {
|
|
10
10
|
CustomEditor,
|
|
11
11
|
type ExtensionAPI,
|
|
12
12
|
type ExtensionContext,
|
|
13
13
|
type Theme,
|
|
14
14
|
type ThemeColor,
|
|
15
|
-
} from '@
|
|
15
|
+
} from '@earendil-works/pi-coding-agent';
|
|
16
16
|
import { getBreadcrumbData, renderBreadcrumbInfo } from './breadcrumb.ts';
|
|
17
17
|
import { readPowerlineSettings } from './settings.ts';
|
|
18
18
|
|
package/footer.ts
CHANGED
|
@@ -13,9 +13,10 @@
|
|
|
13
13
|
|
|
14
14
|
import { readFileSync, existsSync } from 'node:fs';
|
|
15
15
|
import { join } from 'node:path';
|
|
16
|
-
import type { AssistantMessage } from '@
|
|
17
|
-
import type { ExtensionAPI, ExtensionContext } from '@
|
|
18
|
-
import { truncateToWidth, visibleWidth } from '@
|
|
16
|
+
import type { AssistantMessage } from '@earendil-works/pi-ai';
|
|
17
|
+
import type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';
|
|
18
|
+
import { truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';
|
|
19
|
+
import { hasNerdFonts, hexFg, withIcon } from './breadcrumb.ts';
|
|
19
20
|
import { readPowerlineSettings } from './settings.ts';
|
|
20
21
|
|
|
21
22
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -54,24 +55,12 @@ function formatTokens(count: number): string {
|
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
57
|
-
// think level display
|
|
58
|
+
// think level display
|
|
58
59
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
59
60
|
|
|
60
|
-
function hasNerdFonts(): boolean {
|
|
61
|
-
if (process.env.POWERLINE_NERD_FONTS === '1') return true;
|
|
62
|
-
if (process.env.POWERLINE_NERD_FONTS === '0') return false;
|
|
63
|
-
if (process.env.GHOSTTY_RESOURCES_DIR) return true;
|
|
64
|
-
const term = (process.env.TERM_PROGRAM || '').toLowerCase();
|
|
65
|
-
return ['iterm', 'wezterm', 'kitty', 'ghostty', 'alacritty'].some((t) => term.includes(t));
|
|
66
|
-
}
|
|
67
|
-
|
|
68
61
|
const ICON_THINK = hasNerdFonts() ? '' : '';
|
|
69
62
|
const ICON_GIT = hasNerdFonts() ? '' : '⎇';
|
|
70
63
|
|
|
71
|
-
function withIcon(icon: string, text: string): string {
|
|
72
|
-
return icon ? `${icon} ${text}` : text;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
64
|
const THINK_LABELS: Record<string, string> = {
|
|
76
65
|
minimal: 'min',
|
|
77
66
|
low: 'low',
|
|
@@ -127,15 +116,6 @@ let autoCompactEnabled = true;
|
|
|
127
116
|
// footer renderer
|
|
128
117
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
129
118
|
|
|
130
|
-
// hex → ANSI true color (for git branch, not using pi theme tokens)
|
|
131
|
-
function hexFg(hex: string, text: string): string {
|
|
132
|
-
const h = hex.replace('#', '');
|
|
133
|
-
const r = parseInt(h.slice(0, 2), 16);
|
|
134
|
-
const g = parseInt(h.slice(2, 4), 16);
|
|
135
|
-
const b = parseInt(h.slice(4, 6), 16);
|
|
136
|
-
return `\x1b[38;2;${r};${g};${b}m${text}`;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
119
|
/** Sanitize text for single-line status display. */
|
|
140
120
|
function sanitizeStatusText(text: string): string {
|
|
141
121
|
return text
|
|
@@ -256,48 +236,25 @@ function createFooterRenderer(ctx: ExtensionContext) {
|
|
|
256
236
|
const rightWidth = visibleWidth(rightSidePlain);
|
|
257
237
|
|
|
258
238
|
const minPad = 2;
|
|
239
|
+
const thinkToken = THINK_COLORS[liveThinkLevel || 'off'] ?? 'thinkingOff';
|
|
240
|
+
const coloredRight = rightSidePlain ? theme.fg(thinkToken, rightSidePlain) : '';
|
|
259
241
|
let statsLine: string;
|
|
260
242
|
|
|
261
243
|
const totalBase = gitFullWidth + statsLeftWidth + minPad + rightWidth;
|
|
262
244
|
if (totalBase <= width) {
|
|
263
|
-
// everything fits
|
|
264
245
|
const pad = width - gitFullWidth - statsLeftWidth - rightWidth;
|
|
265
246
|
const dimPadding = pad > 0 ? theme.fg('dim', ' '.repeat(pad)) : '';
|
|
266
|
-
let coloredRight = '';
|
|
267
|
-
if (rightSidePlain) {
|
|
268
|
-
const tl = liveThinkLevel || 'off';
|
|
269
|
-
coloredRight = theme.fg(THINK_COLORS[tl] ?? 'thinkingOff', rightSidePlain);
|
|
270
|
-
}
|
|
271
247
|
statsLine = gitFull + dimLeft + dimPadding + coloredRight;
|
|
272
248
|
} else if (gitFullWidth + minPad + rightWidth <= width) {
|
|
273
|
-
// drop git → fit statsLeft
|
|
274
249
|
const availStats = width - gitFullWidth - minPad - rightWidth;
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
if (availStats > 0) {
|
|
278
|
-
statsTrimmed = truncateToWidth(statsLeft, availStats, '');
|
|
279
|
-
statsTrimmedWidth = visibleWidth(statsTrimmed);
|
|
280
|
-
} else {
|
|
281
|
-
statsTrimmed = '';
|
|
282
|
-
statsTrimmedWidth = 0;
|
|
283
|
-
}
|
|
250
|
+
const statsTrimmed = availStats > 0 ? truncateToWidth(statsLeft, availStats, '') : '';
|
|
251
|
+
const statsTrimmedWidth = visibleWidth(statsTrimmed);
|
|
284
252
|
const pad = width - gitFullWidth - statsTrimmedWidth - rightWidth;
|
|
285
253
|
const dimPadding = pad > 0 ? theme.fg('dim', ' '.repeat(pad)) : '';
|
|
286
|
-
let coloredRight = '';
|
|
287
|
-
if (rightSidePlain) {
|
|
288
|
-
const tl = liveThinkLevel || 'off';
|
|
289
|
-
coloredRight = theme.fg(THINK_COLORS[tl] ?? 'thinkingOff', rightSidePlain);
|
|
290
|
-
}
|
|
291
254
|
statsLine = gitFull + theme.fg('dim', statsTrimmed) + dimPadding + coloredRight;
|
|
292
255
|
} else {
|
|
293
|
-
// drop git, drop right → only stats
|
|
294
256
|
const availStats = width - minPad;
|
|
295
|
-
|
|
296
|
-
if (availStats > 0) {
|
|
297
|
-
statsTrimmed = truncateToWidth(statsLeft, availStats, '');
|
|
298
|
-
} else {
|
|
299
|
-
statsTrimmed = '';
|
|
300
|
-
}
|
|
257
|
+
const statsTrimmed = availStats > 0 ? truncateToWidth(statsLeft, availStats, '') : '';
|
|
301
258
|
statsLine = theme.fg('dim', statsTrimmed);
|
|
302
259
|
}
|
|
303
260
|
|
package/header.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Shows a gradient-colored PI logo.
|
|
5
5
|
* Controlled by .pi/settings.json → header (boolean, default true).
|
|
6
6
|
*/
|
|
7
|
-
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
7
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
8
8
|
import { homedir } from 'node:os';
|
|
9
9
|
import { dirname, join, relative, resolve } from 'node:path';
|
|
10
10
|
import type {
|
|
@@ -14,9 +14,9 @@ import type {
|
|
|
14
14
|
SessionStartEvent,
|
|
15
15
|
SlashCommandInfo,
|
|
16
16
|
Theme,
|
|
17
|
-
} from '@
|
|
18
|
-
import { VERSION } from '@
|
|
19
|
-
import { truncateToWidth, wrapTextWithAnsi } from '@
|
|
17
|
+
} from '@earendil-works/pi-coding-agent';
|
|
18
|
+
import { VERSION } from '@earendil-works/pi-coding-agent';
|
|
19
|
+
import { truncateToWidth, wrapTextWithAnsi } from '@earendil-works/pi-tui';
|
|
20
20
|
import { readPowerlineSettings } from './settings.ts';
|
|
21
21
|
|
|
22
22
|
/** Left-to-right ANSI gradient coloring. Spaces are left uncolored. */
|
|
@@ -131,14 +131,13 @@ function formatReasonStatus(theme: Theme, reason: SessionStartEvent['reason']):
|
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
interface HeaderInfo {
|
|
134
|
-
themeName: string;
|
|
135
|
-
cwd: string;
|
|
136
|
-
commands: string[];
|
|
137
134
|
prompts: string[];
|
|
138
135
|
skills: string[];
|
|
139
136
|
extensions: string[];
|
|
137
|
+
packages: string[];
|
|
140
138
|
contextItems: string[];
|
|
141
139
|
contextCount: number;
|
|
140
|
+
packagesCount: number;
|
|
142
141
|
themesCount: number;
|
|
143
142
|
skillsCount: number;
|
|
144
143
|
promptsCount: number;
|
|
@@ -192,12 +191,13 @@ function renderLogo(
|
|
|
192
191
|
|
|
193
192
|
const counts = [
|
|
194
193
|
`context: ${info.contextCount}`,
|
|
195
|
-
`
|
|
194
|
+
`packages: ${info.packagesCount}`,
|
|
195
|
+
`tools: ${info.toolsCount}`,
|
|
196
196
|
`skills: ${info.skillsCount}`,
|
|
197
197
|
`prompts: ${info.promptsCount}`,
|
|
198
|
-
`extensions: ${info.extensionsCount}`,
|
|
199
198
|
`commands: ${info.commandsCount}`,
|
|
200
|
-
`
|
|
199
|
+
`extensions: ${info.extensionsCount}`,
|
|
200
|
+
`themes: ${info.themesCount}`,
|
|
201
201
|
].join(' | ');
|
|
202
202
|
|
|
203
203
|
return [
|
|
@@ -207,6 +207,8 @@ function renderLogo(
|
|
|
207
207
|
'',
|
|
208
208
|
...renderInfoSection(theme, 'Context', info.contextItems, width),
|
|
209
209
|
'',
|
|
210
|
+
...renderInfoSection(theme, 'Packages', info.packages, width),
|
|
211
|
+
'',
|
|
210
212
|
...renderInfoSection(theme, 'Tools', info.tools, width),
|
|
211
213
|
'',
|
|
212
214
|
...renderInfoSection(theme, 'Skills', info.skills, width),
|
|
@@ -251,7 +253,7 @@ function formatRelativePath(cwd: string, filePath: string): string {
|
|
|
251
253
|
function formatDisplayPath(cwd: string, filePath: string): string {
|
|
252
254
|
const home = homedir();
|
|
253
255
|
const rel = relative(cwd, filePath);
|
|
254
|
-
if (!rel ||
|
|
256
|
+
if (!rel || !rel.startsWith('..')) return rel || '.';
|
|
255
257
|
if (filePath === home) return '~';
|
|
256
258
|
if (filePath.startsWith(`${home}/`)) return `~/${relative(home, filePath)}`;
|
|
257
259
|
return filePath;
|
|
@@ -328,57 +330,200 @@ function readPackageLabel(startPath: string): string | undefined {
|
|
|
328
330
|
return undefined;
|
|
329
331
|
}
|
|
330
332
|
|
|
331
|
-
function
|
|
332
|
-
|
|
333
|
+
function getNpmRoot(): string | undefined {
|
|
334
|
+
if (_npmRootResolved) return _npmRoot;
|
|
335
|
+
_npmRootResolved = true;
|
|
333
336
|
|
|
334
|
-
|
|
335
|
-
if (command.source !== 'extension') continue;
|
|
337
|
+
const home = homedir();
|
|
336
338
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
const
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
339
|
+
// NVM: ~/.nvm/versions/node/<version>/lib/node_modules
|
|
340
|
+
if (process.env.NVM_DIR) {
|
|
341
|
+
const versionsDir = join(process.env.NVM_DIR, 'versions', 'node');
|
|
342
|
+
try {
|
|
343
|
+
if (existsSync(versionsDir)) {
|
|
344
|
+
for (const v of readdirSync(versionsDir).sort().reverse()) {
|
|
345
|
+
const dir = join(versionsDir, v, 'lib', 'node_modules');
|
|
346
|
+
if (existsSync(dir)) {
|
|
347
|
+
_npmRoot = dir;
|
|
348
|
+
return _npmRoot;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
} catch {
|
|
353
|
+
/* ignore */
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Bun: ~/.bun/install/global/node_modules
|
|
358
|
+
const bunDir = join(home, '.bun', 'install', 'global', 'node_modules');
|
|
359
|
+
if (existsSync(bunDir)) {
|
|
360
|
+
_npmRoot = bunDir;
|
|
361
|
+
return _npmRoot;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Fallbacks
|
|
365
|
+
for (const dir of ['/usr/local/lib/node_modules', '/usr/lib/node_modules']) {
|
|
366
|
+
if (existsSync(dir)) {
|
|
367
|
+
_npmRoot = dir;
|
|
368
|
+
return _npmRoot;
|
|
369
|
+
}
|
|
345
370
|
}
|
|
346
371
|
|
|
347
|
-
return
|
|
372
|
+
return undefined;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
let _npmRoot: string | undefined;
|
|
376
|
+
let _npmRootResolved = false;
|
|
377
|
+
|
|
378
|
+
// ── shared: read array field from settings.json ──
|
|
379
|
+
|
|
380
|
+
function readSettingsArray(path: string, key: string): string[] {
|
|
381
|
+
if (!existsSync(path)) return [];
|
|
382
|
+
try {
|
|
383
|
+
const value = JSON.parse(readFileSync(path, 'utf-8'))[key];
|
|
384
|
+
return Array.isArray(value) ? value.map(String) : [];
|
|
385
|
+
} catch {
|
|
386
|
+
return [];
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// ── shared: read raw package sources from settings.json ──
|
|
391
|
+
|
|
392
|
+
interface PackageSource {
|
|
393
|
+
source: string;
|
|
394
|
+
scope: 'project' | 'user';
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function readPackageSources(cwd: string, home = homedir()): PackageSource[] {
|
|
398
|
+
const globalPkgs = readSettingsArray(join(home, '.pi', 'agent', 'settings.json'), 'packages');
|
|
399
|
+
const projectPkgs = readSettingsArray(join(cwd, '.pi', 'settings.json'), 'packages');
|
|
400
|
+
|
|
401
|
+
// dedupe: project wins over global
|
|
402
|
+
const seen = new Set<string>();
|
|
403
|
+
const all: PackageSource[] = [];
|
|
404
|
+
for (const s of projectPkgs) {
|
|
405
|
+
if (seen.has(s)) continue;
|
|
406
|
+
seen.add(s);
|
|
407
|
+
all.push({ source: s, scope: 'project' });
|
|
408
|
+
}
|
|
409
|
+
for (const s of globalPkgs) {
|
|
410
|
+
if (seen.has(s)) continue;
|
|
411
|
+
seen.add(s);
|
|
412
|
+
all.push({ source: s, scope: 'user' });
|
|
413
|
+
}
|
|
414
|
+
return all;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// ── resolve a package source to its directory path ──
|
|
418
|
+
|
|
419
|
+
function resolvePackageDir(source: string, cwd: string, home = homedir()): string | undefined {
|
|
420
|
+
if (source.startsWith('npm:')) {
|
|
421
|
+
const name = source.slice(4);
|
|
422
|
+
const npmRoot = getNpmRoot();
|
|
423
|
+
// project-scoped install (.pi/npm/node_modules/<name>)
|
|
424
|
+
const projectDir = join(cwd, '.pi', 'npm', 'node_modules', name);
|
|
425
|
+
if (existsSync(projectDir)) return projectDir;
|
|
426
|
+
// user-scoped install (~/.pi/agent/node_modules/<name>)
|
|
427
|
+
const userDir = join(home, '.pi', 'agent', 'node_modules', name);
|
|
428
|
+
if (existsSync(userDir)) return userDir;
|
|
429
|
+
// global npm root
|
|
430
|
+
if (npmRoot) {
|
|
431
|
+
const globalDir = join(npmRoot, name);
|
|
432
|
+
if (existsSync(globalDir)) return globalDir;
|
|
433
|
+
}
|
|
434
|
+
return undefined;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
return source.startsWith('~')
|
|
438
|
+
? join(home, source.slice(source.startsWith('~/') ? 2 : 1))
|
|
439
|
+
: resolve(cwd, source);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// ── getPackages: name+version from each configured package ──
|
|
443
|
+
|
|
444
|
+
function getPackages(cwd: string, home = homedir()): string[] {
|
|
445
|
+
const sources = readPackageSources(cwd, home);
|
|
446
|
+
const results: string[] = [];
|
|
447
|
+
|
|
448
|
+
for (const { source, scope } of sources) {
|
|
449
|
+
const pkgDir = resolvePackageDir(source, cwd, home);
|
|
450
|
+
const scopeTag = scope === 'project' ? ' [l]' : ' [g]';
|
|
451
|
+
if (!pkgDir) {
|
|
452
|
+
// fallback: show npm package name or raw source
|
|
453
|
+
const name = source.startsWith('npm:') ? source.slice(4) : source;
|
|
454
|
+
results.push(name + scopeTag);
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
const label = readPackageLabel(pkgDir) ?? source;
|
|
458
|
+
results.push(label + scopeTag);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return results.sort((a, b) => a.localeCompare(b));
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// ── getExtensionItems: scan .ts files from settings.json extensions dirs ──
|
|
465
|
+
|
|
466
|
+
function getExtensionItems(cwd: string, home = homedir()): string[] {
|
|
467
|
+
const results: string[] = [];
|
|
468
|
+
|
|
469
|
+
const globalExts = readSettingsArray(join(home, '.pi', 'agent', 'settings.json'), 'extensions');
|
|
470
|
+
const projectExts = readSettingsArray(join(cwd, '.pi', 'settings.json'), 'extensions');
|
|
471
|
+
|
|
472
|
+
for (const ext of [...projectExts, ...globalExts]) {
|
|
473
|
+
const resolved = ext.startsWith('~')
|
|
474
|
+
? join(home, ext.slice(ext.startsWith('~/') ? 2 : 1))
|
|
475
|
+
: resolve(cwd, ext);
|
|
476
|
+
|
|
477
|
+
if (!existsSync(resolved)) continue;
|
|
478
|
+
|
|
479
|
+
try {
|
|
480
|
+
const s = statSync(resolved);
|
|
481
|
+
if (s.isDirectory()) {
|
|
482
|
+
for (const f of readdirSync(resolved).sort()) {
|
|
483
|
+
if (f.endsWith('.ts')) {
|
|
484
|
+
results.push(formatDisplayPath(cwd, join(resolved, f)));
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
} else {
|
|
488
|
+
results.push(formatDisplayPath(cwd, resolved));
|
|
489
|
+
}
|
|
490
|
+
} catch {
|
|
491
|
+
// ignore unreadable paths
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
return results;
|
|
348
496
|
}
|
|
349
497
|
|
|
350
498
|
function shouldShowHeaderInfo(ctx: ExtensionContext, reason: SessionStartEvent['reason']): boolean {
|
|
351
499
|
if (reason !== 'startup' && reason !== 'reload') return false;
|
|
352
500
|
const settings = readPowerlineSettings(ctx.cwd);
|
|
353
|
-
|
|
354
|
-
return settings['header-info'];
|
|
501
|
+
return settings.quietStartup && settings['header-info'];
|
|
355
502
|
}
|
|
356
503
|
|
|
357
504
|
function collectHeaderInfo(
|
|
358
505
|
pi: ExtensionAPI,
|
|
359
506
|
ctx: ExtensionContext,
|
|
360
|
-
theme: Theme,
|
|
361
507
|
contextItems: string[],
|
|
362
508
|
skillItems: string[],
|
|
363
509
|
): HeaderInfo {
|
|
364
510
|
const commands = typeof pi.getCommands === 'function' ? pi.getCommands() : [];
|
|
365
511
|
const allThemes = typeof ctx.ui.getAllThemes === 'function' ? ctx.ui.getAllThemes() : [];
|
|
366
|
-
const
|
|
367
|
-
const
|
|
512
|
+
const extensions = getExtensionItems(ctx.cwd);
|
|
513
|
+
const packages = getPackages(ctx.cwd);
|
|
368
514
|
const activeTools =
|
|
369
515
|
typeof pi.getActiveTools === 'function'
|
|
370
516
|
? pi.getActiveTools().sort((a, b) => a.localeCompare(b))
|
|
371
517
|
: [];
|
|
372
518
|
|
|
373
519
|
return {
|
|
374
|
-
themeName,
|
|
375
|
-
cwd: ctx.cwd,
|
|
376
|
-
commands: getCommandNames(commands),
|
|
377
520
|
prompts: getCommandNames(commands, 'prompt'),
|
|
378
521
|
skills: skillItems,
|
|
379
522
|
extensions,
|
|
523
|
+
packages,
|
|
380
524
|
contextItems,
|
|
381
525
|
contextCount: contextItems.length,
|
|
526
|
+
packagesCount: packages.length,
|
|
382
527
|
themesCount: allThemes.length,
|
|
383
528
|
skillsCount: skillItems.length,
|
|
384
529
|
promptsCount: countUniqueSources(commands, 'prompt'),
|
|
@@ -407,7 +552,7 @@ export function registerHeader(pi: ExtensionAPI) {
|
|
|
407
552
|
reason,
|
|
408
553
|
width,
|
|
409
554
|
shouldShowHeaderInfo(ctx, reason)
|
|
410
|
-
? collectHeaderInfo(pi, ctx,
|
|
555
|
+
? collectHeaderInfo(pi, ctx, contextItems, skillItems)
|
|
411
556
|
: undefined,
|
|
412
557
|
);
|
|
413
558
|
},
|
package/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ExtensionAPI } from '@
|
|
2
|
-
import type { AutocompleteItem } from '@
|
|
1
|
+
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
|
|
2
|
+
import type { AutocompleteItem } from '@earendil-works/pi-tui';
|
|
3
3
|
import { registerEditor } from './editor.ts';
|
|
4
4
|
import { registerFooter } from './footer.ts';
|
|
5
5
|
import { registerHeader } from './header.ts';
|
|
@@ -143,58 +143,33 @@ export default function (pi: ExtensionAPI) {
|
|
|
143
143
|
|
|
144
144
|
const ns = arg.slice(0, colonIdx);
|
|
145
145
|
const val = arg.slice(colonIdx + 1);
|
|
146
|
-
let msg = '';
|
|
147
146
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
writePowerlineSetting(ctx.cwd, 'breadcrumb', val);
|
|
155
|
-
pi.events.emit('powerline_settings_changed', ctx);
|
|
156
|
-
msg = `breadcrumb → ${val}`;
|
|
157
|
-
break;
|
|
158
|
-
}
|
|
159
|
-
case 'footer': {
|
|
160
|
-
if (val !== 'on' && val !== 'off') {
|
|
161
|
-
ctx.ui.notify('footer must be: on or off', 'warning');
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
writePowerlineSetting(ctx.cwd, 'footer', val === 'on');
|
|
165
|
-
pi.events.emit('powerline_settings_changed', ctx);
|
|
166
|
-
msg = `footer → ${val}`;
|
|
167
|
-
break;
|
|
168
|
-
}
|
|
169
|
-
case 'header': {
|
|
170
|
-
if (val !== 'on' && val !== 'off') {
|
|
171
|
-
ctx.ui.notify('header must be: on or off', 'warning');
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
writePowerlineSetting(ctx.cwd, 'header', val === 'on');
|
|
175
|
-
pi.events.emit('powerline_settings_changed', ctx);
|
|
176
|
-
msg = `header → ${val}`;
|
|
177
|
-
break;
|
|
178
|
-
}
|
|
179
|
-
case 'header-info': {
|
|
180
|
-
if (val !== 'on' && val !== 'off') {
|
|
181
|
-
ctx.ui.notify('header-info must be: on or off', 'warning');
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
writePowerlineSetting(ctx.cwd, 'header-info', val === 'on');
|
|
185
|
-
pi.events.emit('powerline_settings_changed', ctx);
|
|
186
|
-
msg = `header-info → ${val}`;
|
|
187
|
-
break;
|
|
147
|
+
if (ns === 'breadcrumb') {
|
|
148
|
+
if (!['hide', 'top', 'inner'].includes(val)) {
|
|
149
|
+
ctx.ui.notify('breadcrumb must be: hide, top, or inner', 'warning');
|
|
150
|
+
return;
|
|
188
151
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
152
|
+
writePowerlineSetting(ctx.cwd, 'breadcrumb', val);
|
|
153
|
+
pi.events.emit('powerline_settings_changed', ctx);
|
|
154
|
+
ctx.ui.notify(`breadcrumb → ${val}`, 'info');
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (ns === 'footer' || ns === 'header' || ns === 'header-info') {
|
|
159
|
+
if (val !== 'on' && val !== 'off') {
|
|
160
|
+
ctx.ui.notify(`${ns} must be: on or off`, 'warning');
|
|
194
161
|
return;
|
|
162
|
+
}
|
|
163
|
+
writePowerlineSetting(ctx.cwd, ns, val === 'on');
|
|
164
|
+
pi.events.emit('powerline_settings_changed', ctx);
|
|
165
|
+
ctx.ui.notify(`${ns} → ${val}`, 'info');
|
|
166
|
+
return;
|
|
195
167
|
}
|
|
196
168
|
|
|
197
|
-
ctx.ui.notify(
|
|
169
|
+
ctx.ui.notify(
|
|
170
|
+
'Usage: /powerline <breadcrumb:hide|top|inner|footer:on|off|header:on|off|header-info:on|off>',
|
|
171
|
+
'warning',
|
|
172
|
+
);
|
|
198
173
|
},
|
|
199
174
|
});
|
|
200
175
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-powerline",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Powerline-style UI extensions for pi coding agent (custom editor, breadcrumb, footer, header)",
|
|
5
5
|
"homepage": "https://github.com/jwu/pi-powerline#readme",
|
|
6
6
|
"repository": {
|
|
@@ -77,9 +77,9 @@
|
|
|
77
77
|
"image": "https://raw.githubusercontent.com/jwu/pi-powerline/refs/heads/main/assets/pi-powerline.png"
|
|
78
78
|
},
|
|
79
79
|
"peerDependencies": {
|
|
80
|
-
"@
|
|
81
|
-
"@
|
|
82
|
-
"@
|
|
80
|
+
"@earendil-works/pi-ai": "*",
|
|
81
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
82
|
+
"@earendil-works/pi-tui": "*"
|
|
83
83
|
},
|
|
84
84
|
"devDependencies": {
|
|
85
85
|
"@commitlint/cli": "^20.5.3",
|
package/settings.ts
CHANGED
|
@@ -43,18 +43,11 @@ function readSettingsFile(settingsPath: string): Record<string, unknown> {
|
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
function mergeSettings(
|
|
47
|
-
globalSettings: Record<string, unknown>,
|
|
48
|
-
projectSettings: Record<string, unknown>,
|
|
49
|
-
): Record<string, unknown> {
|
|
50
|
-
return { ...globalSettings, ...projectSettings };
|
|
51
|
-
}
|
|
52
|
-
|
|
53
46
|
export function readSettings(cwd: string = process.cwd()): Record<string, unknown> {
|
|
54
|
-
return
|
|
55
|
-
readSettingsFile(getSettingsPath()),
|
|
56
|
-
readSettingsFile(getProjectSettingsPath(cwd)),
|
|
57
|
-
|
|
47
|
+
return {
|
|
48
|
+
...readSettingsFile(getSettingsPath()),
|
|
49
|
+
...readSettingsFile(getProjectSettingsPath(cwd)),
|
|
50
|
+
};
|
|
58
51
|
}
|
|
59
52
|
|
|
60
53
|
function writeSettings(cwd: string, settings: Record<string, unknown>): void {
|
package/widget.ts
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* Shows: model → current folder.
|
|
6
6
|
* Only active when breadcrumb mode is "top" in .pi/settings.json.
|
|
7
7
|
*/
|
|
8
|
-
import type { ExtensionAPI, ExtensionContext, Theme } from '@
|
|
9
|
-
import { truncateToWidth, visibleWidth } from '@
|
|
8
|
+
import type { ExtensionAPI, ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';
|
|
9
|
+
import { truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';
|
|
10
10
|
import { getBreadcrumbData, renderBreadcrumbInfo } from './breadcrumb.ts';
|
|
11
11
|
import { readPowerlineSettings } from './settings.ts';
|
|
12
12
|
|