@selesai/code 0.9.15 → 0.10.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/CHANGELOG.md +22 -0
- package/README.md +6 -9
- package/dist/defaults/models.json +2 -2
- package/dist/defaults/settings.json +3 -7
- package/dist/extensions/package.json +2 -4
- package/dist/extensions/pi-zentui/.editorconfig +12 -0
- package/dist/extensions/pi-zentui/.github/CODEOWNERS +1 -0
- package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/bug_report.yml +110 -0
- package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/config_question.yml +56 -0
- package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/feature_request.yml +63 -0
- package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/rendering_issue.yml +106 -0
- package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/runtime_detection.yml +103 -0
- package/dist/extensions/pi-zentui/.github/dependabot.yml +47 -0
- package/dist/extensions/pi-zentui/.github/pull_request_template.md +34 -0
- package/dist/extensions/pi-zentui/.github/release.yml +18 -0
- package/dist/extensions/pi-zentui/.github/workflows/ci.yml +79 -0
- package/dist/extensions/pi-zentui/.github/workflows/publish.yml +63 -0
- package/dist/extensions/pi-zentui/AGENTS.md +107 -0
- package/dist/extensions/pi-zentui/CONTRIBUTING.md +68 -0
- package/dist/extensions/pi-zentui/LICENSE +21 -0
- package/dist/extensions/pi-zentui/README.md +188 -0
- package/dist/extensions/pi-zentui/SECURITY.md +18 -0
- package/dist/extensions/pi-zentui/assets/main-cover.png +0 -0
- package/dist/extensions/pi-zentui/assets/screenshots/editor-accent-rail.png +0 -0
- package/dist/extensions/pi-zentui/assets/screenshots/editor-minimalist.png +0 -0
- package/dist/extensions/pi-zentui/assets/screenshots/editor-opencode-copy-friendly.png +0 -0
- package/dist/extensions/pi-zentui/assets/screenshots/editor-opencode.png +0 -0
- package/dist/extensions/pi-zentui/assets/screenshots/user-message-compact.png +0 -0
- package/dist/extensions/pi-zentui/assets/screenshots/user-message-framed-copy-friendly.png +0 -0
- package/dist/extensions/pi-zentui/assets/screenshots/user-message-framed.png +0 -0
- package/dist/extensions/pi-zentui/assets/screenshots/user-message-labeled.png +0 -0
- package/dist/extensions/pi-zentui/assets/zentui.png +0 -0
- package/dist/extensions/pi-zentui/biome.json +31 -0
- package/dist/extensions/pi-zentui/docs/configuration.md +509 -0
- package/dist/extensions/pi-zentui/docs/footer-format.md +162 -0
- package/dist/extensions/pi-zentui/extensions/zentui/accent-rail-editor.ts +99 -0
- package/dist/extensions/pi-zentui/extensions/zentui/accent-rail-layout-patch.ts +457 -0
- package/dist/extensions/pi-zentui/extensions/zentui/completion-menu.ts +157 -0
- package/dist/extensions/pi-zentui/extensions/zentui/config.ts +2100 -0
- package/dist/extensions/pi-zentui/extensions/zentui/editor-metadata-format.ts +321 -0
- package/dist/extensions/pi-zentui/extensions/zentui/editor-transfer.ts +73 -0
- package/dist/extensions/pi-zentui/extensions/zentui/extension-status.ts +89 -0
- package/dist/extensions/pi-zentui/extensions/zentui/footer-format.ts +364 -0
- package/dist/extensions/pi-zentui/extensions/zentui/footer-layout.ts +122 -0
- package/dist/extensions/pi-zentui/extensions/zentui/footer.ts +812 -0
- package/dist/extensions/pi-zentui/extensions/zentui/format.ts +568 -0
- package/dist/extensions/pi-zentui/extensions/zentui/git.ts +384 -0
- package/dist/extensions/pi-zentui/extensions/zentui/icons.ts +252 -0
- package/dist/extensions/pi-zentui/extensions/zentui/index.ts +1406 -0
- package/dist/extensions/pi-zentui/extensions/zentui/interaction-summary.ts +1049 -0
- package/dist/extensions/pi-zentui/extensions/zentui/live-context.ts +75 -0
- package/dist/extensions/pi-zentui/extensions/zentui/minimalist-editor.ts +463 -0
- package/dist/extensions/pi-zentui/extensions/zentui/package-version.ts +651 -0
- package/dist/extensions/pi-zentui/extensions/zentui/project-refresh.ts +120 -0
- package/dist/extensions/pi-zentui/extensions/zentui/project-state.ts +59 -0
- package/dist/extensions/pi-zentui/extensions/zentui/prototype-patch-registry.ts +166 -0
- package/dist/extensions/pi-zentui/extensions/zentui/repository-root.ts +98 -0
- package/dist/extensions/pi-zentui/extensions/zentui/runtime.ts +841 -0
- package/dist/extensions/pi-zentui/extensions/zentui/selector-border.ts +93 -0
- package/dist/extensions/pi-zentui/extensions/zentui/session-lifecycle.ts +60 -0
- package/dist/extensions/pi-zentui/extensions/zentui/settings-command.ts +1796 -0
- package/dist/extensions/pi-zentui/extensions/zentui/settings-previews.ts +128 -0
- package/dist/extensions/pi-zentui/extensions/zentui/state.ts +94 -0
- package/dist/extensions/pi-zentui/extensions/zentui/style.ts +424 -0
- package/dist/extensions/pi-zentui/extensions/zentui/telemetry.ts +82 -0
- package/dist/extensions/pi-zentui/extensions/zentui/ui.ts +1290 -0
- package/dist/extensions/pi-zentui/extensions/zentui/user-message-osc.ts +248 -0
- package/dist/extensions/pi-zentui/extensions/zentui/user-message-styles.ts +215 -0
- package/dist/extensions/pi-zentui/extensions/zentui/user-message.ts +206 -0
- package/dist/extensions/pi-zentui/extensions/zentui/working-line-messages.ts +24 -0
- package/dist/extensions/pi-zentui/extensions/zentui/working-line-spinners.ts +54 -0
- package/dist/extensions/pi-zentui/extensions/zentui/working-line.ts +1433 -0
- package/dist/extensions/pi-zentui/package-lock.json +5311 -0
- package/dist/extensions/pi-zentui/package.json +60 -0
- package/dist/extensions/pi-zentui/test/accent-rail-editor.test.ts +232 -0
- package/dist/extensions/pi-zentui/test/accent-rail-layout-patch.test.ts +669 -0
- package/dist/extensions/pi-zentui/test/completion-menu.test.ts +265 -0
- package/dist/extensions/pi-zentui/test/config.test.ts +3119 -0
- package/dist/extensions/pi-zentui/test/editor-metadata-format.test.ts +405 -0
- package/dist/extensions/pi-zentui/test/editor-transfer.test.ts +260 -0
- package/dist/extensions/pi-zentui/test/editor-viewport-indicators.test.ts +1286 -0
- package/dist/extensions/pi-zentui/test/extension-compliance.test.ts +7196 -0
- package/dist/extensions/pi-zentui/test/extension-status.test.ts +182 -0
- package/dist/extensions/pi-zentui/test/fixtures/starship-nerd-font-symbols.toml +172 -0
- package/dist/extensions/pi-zentui/test/footer-format.test.ts +431 -0
- package/dist/extensions/pi-zentui/test/footer-layout.test.ts +113 -0
- package/dist/extensions/pi-zentui/test/footer-repository-path.test.ts +123 -0
- package/dist/extensions/pi-zentui/test/format.test.ts +865 -0
- package/dist/extensions/pi-zentui/test/git.test.ts +212 -0
- package/dist/extensions/pi-zentui/test/icons.test.ts +69 -0
- package/dist/extensions/pi-zentui/test/interaction-summary.test.ts +1402 -0
- package/dist/extensions/pi-zentui/test/live-context-integration.test.ts +378 -0
- package/dist/extensions/pi-zentui/test/live-context.test.ts +139 -0
- package/dist/extensions/pi-zentui/test/minimalist-editor.test.ts +811 -0
- package/dist/extensions/pi-zentui/test/package-version.test.ts +321 -0
- package/dist/extensions/pi-zentui/test/project-refresh.test.ts +250 -0
- package/dist/extensions/pi-zentui/test/project-state.test.ts +159 -0
- package/dist/extensions/pi-zentui/test/repository-root.test.ts +108 -0
- package/dist/extensions/pi-zentui/test/responsive-dependencies.test.ts +825 -0
- package/dist/extensions/pi-zentui/test/runtime.test.ts +183 -0
- package/dist/extensions/pi-zentui/test/session-lifecycle.test.ts +40 -0
- package/dist/extensions/pi-zentui/test/settings-command.test.ts +1381 -0
- package/dist/extensions/pi-zentui/test/settings-previews.test.ts +347 -0
- package/dist/extensions/pi-zentui/test/state.test.ts +84 -0
- package/dist/extensions/pi-zentui/test/telemetry-lifecycle-integration.test.ts +276 -0
- package/dist/extensions/pi-zentui/test/telemetry.test.ts +105 -0
- package/dist/extensions/pi-zentui/test/turn-summary-lifecycle.integration.test.ts +327 -0
- package/dist/extensions/pi-zentui/test/user-message-osc.test.ts +164 -0
- package/dist/extensions/pi-zentui/test/user-message-styles.test.ts +292 -0
- package/dist/extensions/pi-zentui/test/working-line-lifecycle.integration.test.ts +730 -0
- package/dist/extensions/pi-zentui/test/working-line-messages.test.ts +34 -0
- package/dist/extensions/pi-zentui/test/working-line.test.ts +1990 -0
- package/dist/extensions/pi-zentui/tsconfig.json +16 -0
- package/dist/extensions/tps.test.ts +38 -0
- package/dist/extensions/{pi-powerline-footer/tps.ts → tps.ts} +4 -0
- package/dist/skills/design-references/SKILL.md +31 -0
- package/dist/skills/design-references/references/clickhouse/DESIGN.md +544 -0
- package/dist/themes/adventure.json +84 -0
- package/dist/themes/adwaita-dark.json +84 -0
- package/dist/themes/arcoiris.json +84 -0
- package/dist/themes/arthur.json +84 -0
- package/dist/themes/atom.json +84 -0
- package/dist/themes/aura.json +84 -0
- package/dist/themes/black-metal-bathory.json +84 -0
- package/dist/themes/black-metal-burzum.json +84 -0
- package/dist/themes/black-metal-khold.json +84 -0
- package/dist/themes/box.json +84 -0
- package/dist/themes/brogrammer.json +84 -0
- package/dist/themes/carbonfox.json +84 -0
- package/dist/themes/catppuccin-mocha.json +84 -0
- package/dist/themes/citruszest.json +84 -0
- package/dist/themes/cursor-dark.json +84 -0
- package/dist/themes/cutie-pro.json +84 -0
- package/dist/themes/dark-modern.json +84 -0
- package/dist/themes/dark-pastel.json +84 -0
- package/dist/themes/dimmed-monokai.json +84 -0
- package/dist/themes/doom-peacock.json +84 -0
- package/dist/themes/dracula-plus.json +84 -0
- package/dist/themes/dracula.json +80 -0
- package/dist/themes/earthsong.json +84 -0
- package/dist/themes/everforest-dark-hard.json +84 -0
- package/dist/themes/everforest-dark.json +80 -0
- package/dist/themes/fahrenheit.json +84 -0
- package/dist/themes/flatland.json +84 -0
- package/dist/themes/flexoki-dark.json +84 -0
- package/dist/themes/front-end-delight.json +84 -0
- package/dist/themes/fun-forrest.json +84 -0
- package/dist/themes/galizur.json +84 -0
- package/dist/themes/github-dark-colorblind.json +84 -0
- package/dist/themes/github-dark-high-contrast.json +84 -0
- package/dist/themes/glacier.json +84 -0
- package/dist/themes/gruber-darker.json +84 -0
- package/dist/themes/gruvbox-dark-hard.json +84 -0
- package/dist/themes/gruvbox-dark.json +84 -0
- package/dist/themes/gruvbox-material.json +84 -0
- package/dist/themes/guezwhoz.json +84 -0
- package/dist/themes/hacktober.json +84 -0
- package/dist/themes/hardcore.json +84 -0
- package/dist/themes/havn-skumring.json +84 -0
- package/dist/themes/ic-orange-ppl.json +84 -0
- package/dist/themes/iterm2-smoooooth.json +84 -0
- package/dist/themes/iterm2-tango-dark.json +84 -0
- package/dist/themes/japanesque.json +84 -0
- package/dist/themes/jellybeans.json +84 -0
- package/dist/themes/kanagawa-wave.json +84 -0
- package/dist/themes/kurokula.json +84 -0
- package/dist/themes/later-this-evening.json +84 -0
- package/dist/themes/lovelace.json +84 -0
- package/dist/themes/material-darker.json +84 -0
- package/dist/themes/matte-black.json +84 -0
- package/dist/themes/mellow.json +84 -0
- package/dist/themes/miasma.json +84 -0
- package/dist/themes/nord.json +80 -0
- package/dist/themes/nvim-dark.json +84 -0
- package/dist/themes/one-dark.json +80 -0
- package/dist/themes/popping-and-locking.json +84 -0
- package/dist/themes/rose-pine.json +80 -0
- package/dist/themes/sea-shells.json +84 -0
- package/dist/themes/sleepy-hollow.json +84 -0
- package/dist/themes/smyck.json +84 -0
- package/dist/themes/solarized-dark.json +80 -0
- package/dist/themes/tokyo-night.json +80 -0
- package/dist/themes/tomorrow-night-bright.json +84 -0
- package/dist/themes/tomorrow-night-burns.json +84 -0
- package/dist/themes/tomorrow-night.json +84 -0
- package/dist/themes/twilight.json +84 -0
- package/dist/themes/vague.json +84 -0
- package/dist/themes/vesper.json +84 -0
- package/dist/themes/xcode-dark-hc.json +84 -0
- package/docs/custom-provider.md +0 -1
- package/package.json +3 -3
- package/dist/extensions/auto-model/auto-model.test.ts +0 -18
- package/dist/extensions/auto-model/classifier.ts +0 -23
- package/dist/extensions/auto-model/config.ts +0 -51
- package/dist/extensions/auto-model/index.ts +0 -22
- package/dist/extensions/auto-model/lifecycle.test.ts +0 -24
- package/dist/extensions/pi-powerline-footer/.github/workflows/test.yml +0 -27
- package/dist/extensions/pi-powerline-footer/CHANGELOG.md +0 -687
- package/dist/extensions/pi-powerline-footer/CONTRIBUTING.md +0 -16
- package/dist/extensions/pi-powerline-footer/README.md +0 -513
- package/dist/extensions/pi-powerline-footer/banner.png +0 -0
- package/dist/extensions/pi-powerline-footer/bash-mode/completion.ts +0 -586
- package/dist/extensions/pi-powerline-footer/bash-mode/editor.ts +0 -583
- package/dist/extensions/pi-powerline-footer/bash-mode/history.ts +0 -204
- package/dist/extensions/pi-powerline-footer/bash-mode/shell-session.ts +0 -286
- package/dist/extensions/pi-powerline-footer/bash-mode/transcript.ts +0 -108
- package/dist/extensions/pi-powerline-footer/bash-mode/types.ts +0 -60
- package/dist/extensions/pi-powerline-footer/cd-command.ts +0 -190
- package/dist/extensions/pi-powerline-footer/colors.ts +0 -70
- package/dist/extensions/pi-powerline-footer/context-usage.ts +0 -156
- package/dist/extensions/pi-powerline-footer/currency-rates.ts +0 -142
- package/dist/extensions/pi-powerline-footer/editor-composition.ts +0 -32
- package/dist/extensions/pi-powerline-footer/editor-performance.ts +0 -99
- package/dist/extensions/pi-powerline-footer/git-status.ts +0 -329
- package/dist/extensions/pi-powerline-footer/guide.ts +0 -220
- package/dist/extensions/pi-powerline-footer/icons.ts +0 -190
- package/dist/extensions/pi-powerline-footer/index.ts +0 -3428
- package/dist/extensions/pi-powerline-footer/lifecycle.ts +0 -10
- package/dist/extensions/pi-powerline-footer/package.json +0 -49
- package/dist/extensions/pi-powerline-footer/paths.ts +0 -22
- package/dist/extensions/pi-powerline-footer/powerline-config.ts +0 -446
- package/dist/extensions/pi-powerline-footer/presets.ts +0 -102
- package/dist/extensions/pi-powerline-footer/queue/store.ts +0 -313
- package/dist/extensions/pi-powerline-footer/queue/types.ts +0 -53
- package/dist/extensions/pi-powerline-footer/render-scheduler.ts +0 -29
- package/dist/extensions/pi-powerline-footer/segments.ts +0 -570
- package/dist/extensions/pi-powerline-footer/separators.ts +0 -57
- package/dist/extensions/pi-powerline-footer/session-usage.ts +0 -44
- package/dist/extensions/pi-powerline-footer/shortcuts.ts +0 -61
- package/dist/extensions/pi-powerline-footer/tests/bash-mode.test.ts +0 -1905
- package/dist/extensions/pi-powerline-footer/tests/cd-command.test.ts +0 -144
- package/dist/extensions/pi-powerline-footer/tests/context-usage.test.ts +0 -156
- package/dist/extensions/pi-powerline-footer/tests/custom-items.test.ts +0 -344
- package/dist/extensions/pi-powerline-footer/tests/editor-composition.test.ts +0 -48
- package/dist/extensions/pi-powerline-footer/tests/editor-performance.test.ts +0 -43
- package/dist/extensions/pi-powerline-footer/tests/editor-responsiveness.test.ts +0 -136
- package/dist/extensions/pi-powerline-footer/tests/fixed-editor-border.test.ts +0 -39
- package/dist/extensions/pi-powerline-footer/tests/git-optional-locks.test.ts +0 -64
- package/dist/extensions/pi-powerline-footer/tests/git-status.test.ts +0 -92
- package/dist/extensions/pi-powerline-footer/tests/guide.test.ts +0 -129
- package/dist/extensions/pi-powerline-footer/tests/jump-shortcuts.test.ts +0 -116
- package/dist/extensions/pi-powerline-footer/tests/mode-aware-trigger.test.ts +0 -79
- package/dist/extensions/pi-powerline-footer/tests/paths.test.ts +0 -63
- package/dist/extensions/pi-powerline-footer/tests/queue-store.test.ts +0 -65
- package/dist/extensions/pi-powerline-footer/tests/quit-cursor.test.ts +0 -14
- package/dist/extensions/pi-powerline-footer/tests/remaining-regressions.test.ts +0 -173
- package/dist/extensions/pi-powerline-footer/tests/session-usage.test.ts +0 -47
- package/dist/extensions/pi-powerline-footer/tests/stash-shortcut.test.ts +0 -37
- package/dist/extensions/pi-powerline-footer/tests/thinking-segment.test.ts +0 -76
- package/dist/extensions/pi-powerline-footer/tests/token-stats.test.ts +0 -249
- package/dist/extensions/pi-powerline-footer/tests/tps.test.ts +0 -39
- package/dist/extensions/pi-powerline-footer/tests/usage-display.test.ts +0 -237
- package/dist/extensions/pi-powerline-footer/tests/welcome.test.ts +0 -199
- package/dist/extensions/pi-powerline-footer/tests/working-vibes.test.ts +0 -317
- package/dist/extensions/pi-powerline-footer/theme.example.json +0 -24
- package/dist/extensions/pi-powerline-footer/theme.json +0 -12
- package/dist/extensions/pi-powerline-footer/theme.ts +0 -241
- package/dist/extensions/pi-powerline-footer/token-stats.ts +0 -304
- package/dist/extensions/pi-powerline-footer/tsconfig.json +0 -13
- package/dist/extensions/pi-powerline-footer/types.ts +0 -235
- package/dist/extensions/pi-powerline-footer/welcome-dismiss.ts +0 -34
- package/dist/extensions/pi-powerline-footer/welcome.ts +0 -709
- package/dist/extensions/pi-powerline-footer/working-vibes.ts +0 -721
- package/dist/extensions/preview-tools-disabled.test.ts +0 -37
- package/dist/extensions/preview-tools-disabled.ts +0 -11
- package/dist/themes/powerline-footer/theme.json +0 -33
|
@@ -1,709 +0,0 @@
|
|
|
1
|
-
import { closeSync, existsSync, openSync, readFileSync, readSync, readdirSync, statSync } from "node:fs";
|
|
2
|
-
import { join, basename } from "node:path";
|
|
3
|
-
import { homedir as osHomedir } from "node:os";
|
|
4
|
-
import { CONFIG_DIR_NAME, getAgentDir, getSettingsPath } from "@selesai/code";
|
|
5
|
-
import type { Component } from "@earendil-works/pi-tui";
|
|
6
|
-
import { truncateToWidth as tuiTruncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
7
|
-
import { ansi, fgOnly, getFgAnsiCode } from "./colors.ts";
|
|
8
|
-
import { GUIDE_COMPACT_LINES, GUIDE_DISMISS_HINT, getGuideFeatureLines, type GuideFeature, type GuideMode } from "./guide.ts";
|
|
9
|
-
|
|
10
|
-
export interface RecentSession {
|
|
11
|
-
name: string;
|
|
12
|
-
timeAgo: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface LoadedCounts {
|
|
16
|
-
contextFiles: number;
|
|
17
|
-
extensions: number;
|
|
18
|
-
skills: number;
|
|
19
|
-
promptTemplates: number;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function formatTokens(tokens: number): string {
|
|
23
|
-
if (tokens < 1000) return tokens.toString();
|
|
24
|
-
if (tokens < 10000) return `${(tokens / 1000).toFixed(1)}k`;
|
|
25
|
-
if (tokens < 1000000) return `${Math.round(tokens / 1000)}k`;
|
|
26
|
-
return `${(tokens / 1000000).toFixed(tokens < 10000000 ? 1 : 0)}M`;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
30
|
-
// Shared rendering utilities
|
|
31
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
32
|
-
|
|
33
|
-
const PI_LOGO = [
|
|
34
|
-
"███ ███ █ ███ ███ ███ █",
|
|
35
|
-
"█ █ █ █ █ █ █ █",
|
|
36
|
-
"█ █ █ █ █ █ █ █",
|
|
37
|
-
"███ ███ █ ███ ███ ███ █",
|
|
38
|
-
" █ █ █ █ █ █ █ █",
|
|
39
|
-
" █ █ █ █ █ █ █ █",
|
|
40
|
-
"███ ███ ███ ███ ███ █ █ █",
|
|
41
|
-
];
|
|
42
|
-
|
|
43
|
-
const GRADIENT_COLORS = [
|
|
44
|
-
"\x1b[38;5;199m",
|
|
45
|
-
"\x1b[38;5;171m",
|
|
46
|
-
"\x1b[38;5;135m",
|
|
47
|
-
"\x1b[38;5;99m",
|
|
48
|
-
"\x1b[38;5;75m",
|
|
49
|
-
"\x1b[38;5;51m",
|
|
50
|
-
];
|
|
51
|
-
const SESSION_HEADER_READ_BYTES = 8192;
|
|
52
|
-
|
|
53
|
-
function bold(text: string): string {
|
|
54
|
-
return `\x1b[1m${text}\x1b[22m`;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function dim(text: string): string {
|
|
58
|
-
return getFgAnsiCode("sep") + text + ansi.reset;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function gradientLine(line: string): string {
|
|
62
|
-
const reset = ansi.reset;
|
|
63
|
-
let result = "";
|
|
64
|
-
let colorIdx = 0;
|
|
65
|
-
const step = Math.max(1, Math.floor(line.length / GRADIENT_COLORS.length));
|
|
66
|
-
|
|
67
|
-
for (let i = 0; i < line.length; i++) {
|
|
68
|
-
if (i > 0 && i % step === 0 && colorIdx < GRADIENT_COLORS.length - 1) colorIdx++;
|
|
69
|
-
const char = line[i];
|
|
70
|
-
if (char !== " ") {
|
|
71
|
-
result += GRADIENT_COLORS[colorIdx] + char + reset;
|
|
72
|
-
} else {
|
|
73
|
-
result += char;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
return result;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function centerText(text: string, width: number): string {
|
|
80
|
-
const visLen = visibleWidth(text);
|
|
81
|
-
if (visLen > width) return tuiTruncateToWidth(text, width, "…");
|
|
82
|
-
if (visLen === width) return text;
|
|
83
|
-
const leftPad = Math.floor((width - visLen) / 2);
|
|
84
|
-
const rightPad = width - visLen - leftPad;
|
|
85
|
-
return " ".repeat(leftPad) + text + " ".repeat(rightPad);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function fitToWidth(str: string, width: number): string {
|
|
89
|
-
const visLen = visibleWidth(str);
|
|
90
|
-
if (visLen > width) return tuiTruncateToWidth(str, width, "…");
|
|
91
|
-
return str + " ".repeat(width - visLen);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function wrapLines(lines: string[], width: number): string[] {
|
|
95
|
-
const continuation = " ";
|
|
96
|
-
const contentWidth = Math.max(1, width - continuation.length);
|
|
97
|
-
return lines.flatMap((line) => wrapTextWithAnsi(line, contentWidth)
|
|
98
|
-
.map((wrapped, index) => index === 0 ? wrapped : continuation + wrapped));
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
interface WelcomeData {
|
|
102
|
-
modelName: string;
|
|
103
|
-
providerName: string;
|
|
104
|
-
recentSessions: RecentSession[];
|
|
105
|
-
loadedCounts: LoadedCounts;
|
|
106
|
-
guideMode: GuideMode;
|
|
107
|
-
newFeatures: GuideFeature[];
|
|
108
|
-
initialContextTokens: number | null;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function buildLeftColumn(data: WelcomeData, colWidth: number): string[] {
|
|
112
|
-
const logoColored = PI_LOGO.map((line) => gradientLine(line));
|
|
113
|
-
|
|
114
|
-
return [
|
|
115
|
-
"",
|
|
116
|
-
centerText(bold("Welcome back!"), colWidth),
|
|
117
|
-
"",
|
|
118
|
-
...logoColored.map((l) => centerText(l, colWidth)),
|
|
119
|
-
"",
|
|
120
|
-
centerText(fgOnly("model", data.modelName), colWidth),
|
|
121
|
-
centerText(dim(data.providerName), colWidth),
|
|
122
|
-
];
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function buildRightColumn(data: WelcomeData, colWidth: number): string[] {
|
|
126
|
-
const hChar = "─";
|
|
127
|
-
const separator = ` ${dim(hChar.repeat(colWidth - 2))}`;
|
|
128
|
-
|
|
129
|
-
const guideLines = data.guideMode === "compact"
|
|
130
|
-
? [
|
|
131
|
-
` ${bold(fgOnly("accent", "Guide"))}`,
|
|
132
|
-
...(data.newFeatures.length > 0
|
|
133
|
-
? [` ${bold(fgOnly("model", `New: ${data.newFeatures.slice(0, 2).map((feature) => feature.title).join(" · ")}`))}`]
|
|
134
|
-
: []),
|
|
135
|
-
...GUIDE_COMPACT_LINES.map((line) => ` ${dim(line)}`),
|
|
136
|
-
]
|
|
137
|
-
: [
|
|
138
|
-
` ${bold(fgOnly("accent", "Selesai guide"))}`,
|
|
139
|
-
...(data.newFeatures.length > 0
|
|
140
|
-
? [
|
|
141
|
-
` ${bold(fgOnly("model", "New since your last guide"))}`,
|
|
142
|
-
...data.newFeatures.slice(0, 3).map((feature) => ` ${dim("• ")}${feature.title}`),
|
|
143
|
-
]
|
|
144
|
-
: []),
|
|
145
|
-
...getGuideFeatureLines().map((line) => line.kind === "section"
|
|
146
|
-
? ` ${bold(fgOnly("accent", line.text))}`
|
|
147
|
-
: ` ${dim("• ")}${line.feature.title}: ${dim(line.feature.example)}`),
|
|
148
|
-
];
|
|
149
|
-
|
|
150
|
-
// Session lines
|
|
151
|
-
const sessionLines: string[] = [];
|
|
152
|
-
if (data.recentSessions.length === 0) {
|
|
153
|
-
sessionLines.push(` ${dim("No recent sessions")}`);
|
|
154
|
-
} else {
|
|
155
|
-
for (const session of data.recentSessions.slice(0, 3)) {
|
|
156
|
-
sessionLines.push(
|
|
157
|
-
` ${dim("• ")}${fgOnly("path", session.name)}${dim(` (${session.timeAgo})`)}`,
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Loaded counts lines
|
|
163
|
-
const countLines: string[] = [];
|
|
164
|
-
const { contextFiles, extensions, skills, promptTemplates } = data.loadedCounts;
|
|
165
|
-
const itemPrefix = dim("- ");
|
|
166
|
-
|
|
167
|
-
if (contextFiles > 0 || extensions > 0 || skills > 0 || promptTemplates > 0) {
|
|
168
|
-
if (contextFiles > 0) {
|
|
169
|
-
countLines.push(` ${itemPrefix}${fgOnly("gitClean", `${contextFiles}`)} context file${contextFiles !== 1 ? "s" : ""}`);
|
|
170
|
-
}
|
|
171
|
-
if (extensions > 0) {
|
|
172
|
-
countLines.push(` ${itemPrefix}${fgOnly("gitClean", `${extensions}`)} extension${extensions !== 1 ? "s" : ""}`);
|
|
173
|
-
}
|
|
174
|
-
if (skills > 0) {
|
|
175
|
-
countLines.push(` ${itemPrefix}${fgOnly("gitClean", `${skills}`)} skill${skills !== 1 ? "s" : ""}`);
|
|
176
|
-
}
|
|
177
|
-
if (promptTemplates > 0) {
|
|
178
|
-
countLines.push(` ${itemPrefix}${fgOnly("gitClean", `${promptTemplates}`)} prompt template${promptTemplates !== 1 ? "s" : ""}`);
|
|
179
|
-
}
|
|
180
|
-
} else {
|
|
181
|
-
countLines.push(` ${dim("No extensions loaded")}`);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (
|
|
185
|
-
data.initialContextTokens !== null
|
|
186
|
-
&& Number.isFinite(data.initialContextTokens)
|
|
187
|
-
&& data.initialContextTokens > 0
|
|
188
|
-
) {
|
|
189
|
-
countLines.push(` ${itemPrefix}${fgOnly("gitClean", `≈ ${formatTokens(data.initialContextTokens)}`)} initial prompt tokens`);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const dashboardLines = data.guideMode === "full"
|
|
193
|
-
? []
|
|
194
|
-
: [
|
|
195
|
-
separator,
|
|
196
|
-
` ${bold(fgOnly("accent", "Loaded"))}`,
|
|
197
|
-
...countLines,
|
|
198
|
-
separator,
|
|
199
|
-
` ${bold(fgOnly("accent", "Recent sessions"))}`,
|
|
200
|
-
...sessionLines,
|
|
201
|
-
];
|
|
202
|
-
return [
|
|
203
|
-
` ${bold(fgOnly("accent", "Tips"))}: ${dim("/")} commands · ${dim("/settings")} configure`,
|
|
204
|
-
` ${dim("Shift+Tab")} cycle thinking`,
|
|
205
|
-
separator,
|
|
206
|
-
...guideLines,
|
|
207
|
-
...dashboardLines,
|
|
208
|
-
"",
|
|
209
|
-
];
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
function renderWelcomeBox(
|
|
213
|
-
data: WelcomeData,
|
|
214
|
-
termWidth: number,
|
|
215
|
-
bottomLine: string,
|
|
216
|
-
): string[] {
|
|
217
|
-
// Minimum width for two-column layout: leftCol(26) + separator(3) + minRightCol(15) = 44
|
|
218
|
-
const minLayoutWidth = 44;
|
|
219
|
-
|
|
220
|
-
// If terminal is too narrow for the layout, return empty (skip welcome box)
|
|
221
|
-
if (termWidth < minLayoutWidth) {
|
|
222
|
-
return [];
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
const minWidth = 76;
|
|
226
|
-
const maxWidth = data.guideMode === "full" ? 132 : 96;
|
|
227
|
-
// Clamp to termWidth to prevent crash on narrow terminals.
|
|
228
|
-
const boxWidth = Math.min(termWidth, Math.max(minWidth, Math.min(termWidth - 2, maxWidth)));
|
|
229
|
-
const leftCol = 26;
|
|
230
|
-
const rightCol = Math.max(1, boxWidth - leftCol - 3); // Ensure rightCol is at least 1
|
|
231
|
-
|
|
232
|
-
const hChar = "─";
|
|
233
|
-
const v = dim("│");
|
|
234
|
-
const tl = dim("╭");
|
|
235
|
-
const tr = dim("╮");
|
|
236
|
-
const bl = dim("╰");
|
|
237
|
-
const br = dim("╯");
|
|
238
|
-
|
|
239
|
-
const leftLines = buildLeftColumn(data, leftCol);
|
|
240
|
-
const rightLines = wrapLines(buildRightColumn(data, rightCol), rightCol);
|
|
241
|
-
|
|
242
|
-
const lines: string[] = [];
|
|
243
|
-
|
|
244
|
-
// Top border with title
|
|
245
|
-
const title = "SelesaiCode";
|
|
246
|
-
const titlePrefix = dim(hChar.repeat(3));
|
|
247
|
-
const titleStyled = titlePrefix + fgOnly("model", title);
|
|
248
|
-
const titleVisLen = 3 + visibleWidth(title);
|
|
249
|
-
const afterTitle = boxWidth - 2 - titleVisLen;
|
|
250
|
-
const afterTitleText = afterTitle > 0 ? dim(hChar.repeat(afterTitle)) : "";
|
|
251
|
-
lines.push(tl + titleStyled + afterTitleText + tr);
|
|
252
|
-
|
|
253
|
-
// Content rows
|
|
254
|
-
const maxRows = Math.max(leftLines.length, rightLines.length);
|
|
255
|
-
for (let i = 0; i < maxRows; i++) {
|
|
256
|
-
const left = fitToWidth(leftLines[i] ?? "", leftCol);
|
|
257
|
-
const right = fitToWidth(rightLines[i] ?? "", rightCol);
|
|
258
|
-
lines.push(v + left + v + right + v);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// Bottom border
|
|
262
|
-
lines.push(bl + bottomLine + br);
|
|
263
|
-
|
|
264
|
-
return lines;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
268
|
-
// Welcome Components
|
|
269
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* Welcome overlay component for SelesaiCode.
|
|
273
|
-
* Displays a branded splash screen with logo, tips, and loaded counts.
|
|
274
|
-
*/
|
|
275
|
-
export class WelcomeComponent implements Component {
|
|
276
|
-
private data: WelcomeData;
|
|
277
|
-
|
|
278
|
-
constructor(
|
|
279
|
-
modelName: string,
|
|
280
|
-
providerName: string,
|
|
281
|
-
recentSessions: RecentSession[] = [],
|
|
282
|
-
loadedCounts: LoadedCounts = { contextFiles: 0, extensions: 0, skills: 0, promptTemplates: 0 },
|
|
283
|
-
guideMode: GuideMode = "full",
|
|
284
|
-
newFeatures: GuideFeature[] = [],
|
|
285
|
-
initialContextTokens: number | null = null,
|
|
286
|
-
) {
|
|
287
|
-
this.data = { modelName, providerName, recentSessions, loadedCounts, guideMode, newFeatures, initialContextTokens };
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
invalidate(): void {}
|
|
291
|
-
|
|
292
|
-
render(termWidth: number): string[] {
|
|
293
|
-
// Minimum width for two-column layout (must match renderWelcomeBox)
|
|
294
|
-
const minLayoutWidth = 44;
|
|
295
|
-
if (termWidth < minLayoutWidth) {
|
|
296
|
-
return [];
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
const minWidth = 76;
|
|
300
|
-
const maxWidth = 132;
|
|
301
|
-
// Clamp to termWidth to prevent crash on narrow terminals.
|
|
302
|
-
const boxWidth = Math.min(termWidth, Math.max(minWidth, Math.min(termWidth - 2, maxWidth)));
|
|
303
|
-
|
|
304
|
-
// The guide is intentionally blocking: no timer or implicit dismissal.
|
|
305
|
-
const promptText = ` ${GUIDE_DISMISS_HINT} `;
|
|
306
|
-
const promptStyled = dim(promptText);
|
|
307
|
-
const bottomContentWidth = boxWidth - 2;
|
|
308
|
-
const promptVisLen = visibleWidth(promptText);
|
|
309
|
-
const leftPad = Math.floor((bottomContentWidth - promptVisLen) / 2);
|
|
310
|
-
const rightPad = bottomContentWidth - promptVisLen - leftPad;
|
|
311
|
-
const hChar = "─";
|
|
312
|
-
const bottomLine = dim(hChar.repeat(Math.max(0, leftPad))) +
|
|
313
|
-
promptStyled +
|
|
314
|
-
dim(hChar.repeat(Math.max(0, rightPad)));
|
|
315
|
-
|
|
316
|
-
return renderWelcomeBox(this.data, termWidth, bottomLine);
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* Compact guide header used when the user selects `/guide compact`.
|
|
322
|
-
*/
|
|
323
|
-
export class WelcomeHeader implements Component {
|
|
324
|
-
private data: WelcomeData;
|
|
325
|
-
|
|
326
|
-
constructor(
|
|
327
|
-
modelName: string,
|
|
328
|
-
providerName: string,
|
|
329
|
-
recentSessions: RecentSession[] = [],
|
|
330
|
-
loadedCounts: LoadedCounts = { contextFiles: 0, extensions: 0, skills: 0, promptTemplates: 0 },
|
|
331
|
-
guideMode: GuideMode = "compact",
|
|
332
|
-
newFeatures: GuideFeature[] = [],
|
|
333
|
-
initialContextTokens: number | null = null,
|
|
334
|
-
) {
|
|
335
|
-
this.data = { modelName, providerName, recentSessions, loadedCounts, guideMode, newFeatures, initialContextTokens };
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
invalidate(): void {}
|
|
339
|
-
|
|
340
|
-
render(termWidth: number): string[] {
|
|
341
|
-
// Minimum width for two-column layout (must match renderWelcomeBox)
|
|
342
|
-
const minLayoutWidth = 44;
|
|
343
|
-
if (termWidth < minLayoutWidth) {
|
|
344
|
-
return [];
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
const minWidth = 76;
|
|
348
|
-
const maxWidth = 96;
|
|
349
|
-
// Clamp to termWidth to prevent crash on narrow terminals
|
|
350
|
-
const boxWidth = Math.min(termWidth, Math.max(minWidth, Math.min(termWidth - 2, maxWidth)));
|
|
351
|
-
const hChar = "─";
|
|
352
|
-
|
|
353
|
-
// Bottom line with column separator (leftCol=26, rightCol=boxWidth-29)
|
|
354
|
-
const leftCol = 26;
|
|
355
|
-
const rightCol = Math.max(1, boxWidth - leftCol - 3);
|
|
356
|
-
const bottomLine = dim(hChar.repeat(leftCol)) + dim("┴") + dim(hChar.repeat(rightCol));
|
|
357
|
-
|
|
358
|
-
const lines = renderWelcomeBox(this.data, termWidth, bottomLine);
|
|
359
|
-
if (lines.length > 0) {
|
|
360
|
-
lines.push(""); // Add empty line for spacing only if we rendered content
|
|
361
|
-
}
|
|
362
|
-
return lines;
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
367
|
-
// Discovery functions
|
|
368
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
369
|
-
|
|
370
|
-
const loggedDiscoveryErrors = new Set<string>();
|
|
371
|
-
|
|
372
|
-
function logDiscoveryError(scope: string, error: unknown): void {
|
|
373
|
-
if (
|
|
374
|
-
typeof error === "object" &&
|
|
375
|
-
error !== null &&
|
|
376
|
-
"code" in error &&
|
|
377
|
-
(error as { code?: unknown }).code === "ENOENT"
|
|
378
|
-
) {
|
|
379
|
-
return;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
383
|
-
const key = `${scope}:${message}`;
|
|
384
|
-
if (loggedDiscoveryErrors.has(key)) {
|
|
385
|
-
return;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
loggedDiscoveryErrors.add(key);
|
|
389
|
-
if (loggedDiscoveryErrors.size > 500) {
|
|
390
|
-
loggedDiscoveryErrors.clear();
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
console.debug(`[powerline-welcome] ${scope}:`, error);
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
// ponytail: only .claude paths still need raw home; everything else routes via getAgentDir.
|
|
397
|
-
function homedirSafe(): string {
|
|
398
|
-
return process.env.HOME || process.env.USERPROFILE || osHomedir();
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
/**
|
|
402
|
-
* Discover loaded counts by scanning filesystem.
|
|
403
|
-
*/
|
|
404
|
-
export function discoverLoadedCounts(): LoadedCounts {
|
|
405
|
-
const homeDir = getAgentDir();
|
|
406
|
-
const cwd = process.cwd();
|
|
407
|
-
|
|
408
|
-
let contextFiles = 0;
|
|
409
|
-
let extensions = 0;
|
|
410
|
-
let skills = 0;
|
|
411
|
-
let promptTemplates = 0;
|
|
412
|
-
|
|
413
|
-
const agentsMdPaths = [
|
|
414
|
-
join(homeDir, "AGENTS.md"),
|
|
415
|
-
join(homedirSafe(), ".claude", "AGENTS.md"),
|
|
416
|
-
join(cwd, "AGENTS.md"),
|
|
417
|
-
join(cwd, CONFIG_DIR_NAME, "AGENTS.md"),
|
|
418
|
-
join(cwd, ".claude", "AGENTS.md"),
|
|
419
|
-
];
|
|
420
|
-
|
|
421
|
-
for (const path of agentsMdPaths) {
|
|
422
|
-
if (existsSync(path)) contextFiles++;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
const extensionDirs = [
|
|
426
|
-
join(homeDir, "extensions"),
|
|
427
|
-
join(cwd, "extensions"),
|
|
428
|
-
join(cwd, CONFIG_DIR_NAME, "extensions"),
|
|
429
|
-
];
|
|
430
|
-
|
|
431
|
-
const countedExtensions = new Set<string>();
|
|
432
|
-
|
|
433
|
-
const settingsPaths = [
|
|
434
|
-
getSettingsPath(),
|
|
435
|
-
join(cwd, CONFIG_DIR_NAME, "settings.json"),
|
|
436
|
-
];
|
|
437
|
-
|
|
438
|
-
for (const settingsPath of settingsPaths) {
|
|
439
|
-
if (!existsSync(settingsPath)) {
|
|
440
|
-
continue;
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
try {
|
|
444
|
-
const settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
445
|
-
let packages: unknown = null;
|
|
446
|
-
if (typeof settings === "object" && settings !== null && !Array.isArray(settings)) {
|
|
447
|
-
packages = (settings as { packages?: unknown }).packages;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
if (Array.isArray(packages)) {
|
|
451
|
-
for (const pkg of packages) {
|
|
452
|
-
let source: unknown = null;
|
|
453
|
-
let extensionsFilter: unknown = null;
|
|
454
|
-
|
|
455
|
-
if (typeof pkg === "string") {
|
|
456
|
-
source = pkg;
|
|
457
|
-
} else if (typeof pkg === "object" && pkg !== null && !Array.isArray(pkg)) {
|
|
458
|
-
source = (pkg as { source?: unknown }).source;
|
|
459
|
-
extensionsFilter = (pkg as { extensions?: unknown }).extensions;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
if (typeof source !== "string") {
|
|
463
|
-
continue;
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
const normalizedSource = source.trim();
|
|
467
|
-
if (!normalizedSource.startsWith("npm:")) {
|
|
468
|
-
continue;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
if (Array.isArray(extensionsFilter) && extensionsFilter.length === 0) {
|
|
472
|
-
continue;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
const body = normalizedSource.slice(4);
|
|
476
|
-
const versionIndex = body.lastIndexOf("@");
|
|
477
|
-
const name = versionIndex > 0 ? body.slice(0, versionIndex) : body;
|
|
478
|
-
if (!name || countedExtensions.has(name)) {
|
|
479
|
-
continue;
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
countedExtensions.add(name);
|
|
483
|
-
extensions++;
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
} catch (error) {
|
|
487
|
-
logDiscoveryError(`Failed to read settings at ${settingsPath}`, error);
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
for (const dir of extensionDirs) {
|
|
492
|
-
if (existsSync(dir)) {
|
|
493
|
-
try {
|
|
494
|
-
const entries = readdirSync(dir);
|
|
495
|
-
for (const entry of entries) {
|
|
496
|
-
const entryPath = join(dir, entry);
|
|
497
|
-
|
|
498
|
-
try {
|
|
499
|
-
const stats = statSync(entryPath);
|
|
500
|
-
|
|
501
|
-
if (stats.isDirectory()) {
|
|
502
|
-
if (
|
|
503
|
-
existsSync(join(entryPath, "index.ts")) ||
|
|
504
|
-
existsSync(join(entryPath, "index.js")) ||
|
|
505
|
-
existsSync(join(entryPath, "package.json"))
|
|
506
|
-
) {
|
|
507
|
-
if (!countedExtensions.has(entry)) {
|
|
508
|
-
countedExtensions.add(entry);
|
|
509
|
-
extensions++;
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
} else if ((entry.endsWith(".ts") || entry.endsWith(".js")) && !entry.startsWith(".")) {
|
|
513
|
-
const ext = entry.endsWith(".ts") ? ".ts" : ".js";
|
|
514
|
-
const name = basename(entry, ext);
|
|
515
|
-
if (!countedExtensions.has(name)) {
|
|
516
|
-
countedExtensions.add(name);
|
|
517
|
-
extensions++;
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
} catch (error) {
|
|
521
|
-
logDiscoveryError(`Failed to inspect extension entry ${entryPath}`, error);
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
} catch (error) {
|
|
525
|
-
logDiscoveryError(`Failed to scan extensions dir ${dir}`, error);
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
const skillDirs = [
|
|
531
|
-
join(homeDir, "skills"),
|
|
532
|
-
join(cwd, CONFIG_DIR_NAME, "skills"),
|
|
533
|
-
join(cwd, "skills"),
|
|
534
|
-
];
|
|
535
|
-
|
|
536
|
-
const countedSkills = new Set<string>();
|
|
537
|
-
|
|
538
|
-
for (const dir of skillDirs) {
|
|
539
|
-
if (existsSync(dir)) {
|
|
540
|
-
try {
|
|
541
|
-
const entries = readdirSync(dir);
|
|
542
|
-
for (const entry of entries) {
|
|
543
|
-
const entryPath = join(dir, entry);
|
|
544
|
-
try {
|
|
545
|
-
if (statSync(entryPath).isDirectory()) {
|
|
546
|
-
if (existsSync(join(entryPath, "SKILL.md"))) {
|
|
547
|
-
if (!countedSkills.has(entry)) {
|
|
548
|
-
countedSkills.add(entry);
|
|
549
|
-
skills++;
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
} catch (error) {
|
|
554
|
-
logDiscoveryError(`Failed to inspect skill entry ${entryPath}`, error);
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
} catch (error) {
|
|
558
|
-
logDiscoveryError(`Failed to scan skills dir ${dir}`, error);
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
const templateDirs = [
|
|
564
|
-
join(homeDir, "commands"),
|
|
565
|
-
join(homedirSafe(), ".claude", "commands"),
|
|
566
|
-
join(cwd, CONFIG_DIR_NAME, "commands"),
|
|
567
|
-
join(cwd, ".claude", "commands"),
|
|
568
|
-
];
|
|
569
|
-
|
|
570
|
-
const countedTemplates = new Set<string>();
|
|
571
|
-
|
|
572
|
-
function countTemplatesInDir(dir: string) {
|
|
573
|
-
if (!existsSync(dir)) return;
|
|
574
|
-
try {
|
|
575
|
-
const entries = readdirSync(dir);
|
|
576
|
-
for (const entry of entries) {
|
|
577
|
-
const entryPath = join(dir, entry);
|
|
578
|
-
try {
|
|
579
|
-
const stats = statSync(entryPath);
|
|
580
|
-
if (stats.isDirectory()) {
|
|
581
|
-
countTemplatesInDir(entryPath);
|
|
582
|
-
} else if (entry.endsWith(".md")) {
|
|
583
|
-
const name = basename(entry, ".md");
|
|
584
|
-
if (!countedTemplates.has(name)) {
|
|
585
|
-
countedTemplates.add(name);
|
|
586
|
-
promptTemplates++;
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
} catch (error) {
|
|
590
|
-
logDiscoveryError(`Failed to inspect prompt template entry ${entryPath}`, error);
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
} catch (error) {
|
|
594
|
-
logDiscoveryError(`Failed to scan prompt template dir ${dir}`, error);
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
for (const dir of templateDirs) {
|
|
599
|
-
countTemplatesInDir(dir);
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
return { contextFiles, extensions, skills, promptTemplates };
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
/**
|
|
606
|
-
* Get recent sessions from the sessions directory.
|
|
607
|
-
*/
|
|
608
|
-
function readSessionHeaderProjectName(filePath: string): string | null {
|
|
609
|
-
let fd: number | null = null;
|
|
610
|
-
try {
|
|
611
|
-
fd = openSync(filePath, "r");
|
|
612
|
-
const buffer = Buffer.alloc(SESSION_HEADER_READ_BYTES);
|
|
613
|
-
const bytesRead = readSync(fd, buffer, 0, buffer.length, 0);
|
|
614
|
-
const firstLine = buffer.toString("utf8", 0, bytesRead).split(/\r?\n/, 1)[0]?.trim();
|
|
615
|
-
if (!firstLine) return null;
|
|
616
|
-
|
|
617
|
-
const header: unknown = JSON.parse(firstLine);
|
|
618
|
-
if (typeof header !== "object" || header === null || Array.isArray(header)) return null;
|
|
619
|
-
|
|
620
|
-
const cwd = Reflect.get(header, "cwd");
|
|
621
|
-
if (typeof cwd !== "string" || cwd.trim().length === 0) return null;
|
|
622
|
-
|
|
623
|
-
return basename(cwd) || cwd;
|
|
624
|
-
} catch {
|
|
625
|
-
return null;
|
|
626
|
-
} finally {
|
|
627
|
-
if (fd !== null) closeSync(fd);
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
function sessionProjectNameFromDirectory(dir: string): string {
|
|
632
|
-
const parentName = basename(dir);
|
|
633
|
-
if (!parentName.startsWith("--")) {
|
|
634
|
-
return parentName;
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
const parts = parentName.split("-").filter(p => p);
|
|
638
|
-
return parts[parts.length - 1] || parentName;
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
export function getRecentSessions(maxCount: number = 3): RecentSession[] {
|
|
642
|
-
const homeDir = getAgentDir();
|
|
643
|
-
const home = homedirSafe();
|
|
644
|
-
|
|
645
|
-
const sessionsDirs = [
|
|
646
|
-
join(homeDir, "sessions"),
|
|
647
|
-
join(home, CONFIG_DIR_NAME, "sessions"),
|
|
648
|
-
];
|
|
649
|
-
|
|
650
|
-
const sessions: { name: string; mtime: number }[] = [];
|
|
651
|
-
|
|
652
|
-
function scanDir(dir: string) {
|
|
653
|
-
if (!existsSync(dir)) return;
|
|
654
|
-
try {
|
|
655
|
-
const entries = readdirSync(dir);
|
|
656
|
-
for (const entry of entries) {
|
|
657
|
-
const entryPath = join(dir, entry);
|
|
658
|
-
try {
|
|
659
|
-
const stats = statSync(entryPath);
|
|
660
|
-
if (stats.isDirectory()) {
|
|
661
|
-
scanDir(entryPath);
|
|
662
|
-
} else if (entry.endsWith(".jsonl")) {
|
|
663
|
-
const projectName = readSessionHeaderProjectName(entryPath) ?? sessionProjectNameFromDirectory(dir);
|
|
664
|
-
sessions.push({ name: projectName, mtime: stats.mtimeMs });
|
|
665
|
-
}
|
|
666
|
-
} catch (error) {
|
|
667
|
-
logDiscoveryError(`Failed to inspect session entry ${entryPath}`, error);
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
} catch (error) {
|
|
671
|
-
logDiscoveryError(`Failed to scan sessions dir ${dir}`, error);
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
for (const sessionsDir of sessionsDirs) {
|
|
676
|
-
scanDir(sessionsDir);
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
if (sessions.length === 0) return [];
|
|
680
|
-
|
|
681
|
-
sessions.sort((a, b) => b.mtime - a.mtime);
|
|
682
|
-
|
|
683
|
-
const seen = new Set<string>();
|
|
684
|
-
const uniqueSessions: typeof sessions = [];
|
|
685
|
-
for (const s of sessions) {
|
|
686
|
-
if (!seen.has(s.name)) {
|
|
687
|
-
seen.add(s.name);
|
|
688
|
-
uniqueSessions.push(s);
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
const now = Date.now();
|
|
693
|
-
return uniqueSessions.slice(0, maxCount).map(s => ({
|
|
694
|
-
name: s.name.length > 20 ? s.name.slice(0, 17) + "…" : s.name,
|
|
695
|
-
timeAgo: formatTimeAgo(now - s.mtime),
|
|
696
|
-
}));
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
function formatTimeAgo(ms: number): string {
|
|
700
|
-
const seconds = Math.floor(ms / 1000);
|
|
701
|
-
const minutes = Math.floor(seconds / 60);
|
|
702
|
-
const hours = Math.floor(minutes / 60);
|
|
703
|
-
const days = Math.floor(hours / 24);
|
|
704
|
-
|
|
705
|
-
if (days > 0) return `${days}d ago`;
|
|
706
|
-
if (hours > 0) return `${hours}h ago`;
|
|
707
|
-
if (minutes > 0) return `${minutes}m ago`;
|
|
708
|
-
return "just now";
|
|
709
|
-
}
|