@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,329 +0,0 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
|
-
import type { GitStatus } from "./types.ts";
|
|
3
|
-
|
|
4
|
-
interface CachedGitStatus {
|
|
5
|
-
staged: number;
|
|
6
|
-
unstaged: number;
|
|
7
|
-
untracked: number;
|
|
8
|
-
timestamp: number;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
interface CachedBranch {
|
|
12
|
-
branch: string | null;
|
|
13
|
-
timestamp: number;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export type GitPollingMode = "full" | "branch" | "off";
|
|
17
|
-
|
|
18
|
-
/** Known git hosting providers we render a dedicated icon for. */
|
|
19
|
-
export type GitHost = "github" | "gitlab" | "bitbucket" | "other";
|
|
20
|
-
|
|
21
|
-
interface CachedRemoteHost {
|
|
22
|
-
host: GitHost | null;
|
|
23
|
-
timestamp: number;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const CACHE_TTL_MS = 1000; // 1 second for file status
|
|
27
|
-
const BRANCH_TTL_MS = 500; // Shorter TTL so branch updates quickly after invalidation
|
|
28
|
-
const REMOTE_TTL_MS = 60_000; // Origin remote almost never changes within a session
|
|
29
|
-
let cachedStatus: CachedGitStatus | null = null;
|
|
30
|
-
let cachedBranch: CachedBranch | null = null;
|
|
31
|
-
let cachedRemoteHost: CachedRemoteHost | null = null;
|
|
32
|
-
let pendingRemoteFetch: Promise<void> | null = null;
|
|
33
|
-
let pendingFetch: Promise<void> | null = null;
|
|
34
|
-
let pendingBranchFetch: Promise<void> | null = null;
|
|
35
|
-
let invalidationCounter = 0; // Track invalidations to prevent stale updates
|
|
36
|
-
let branchInvalidationCounter = 0;
|
|
37
|
-
const updateListeners = new Set<() => void>();
|
|
38
|
-
|
|
39
|
-
function notifyGitUpdate(): void {
|
|
40
|
-
for (const listener of updateListeners) listener();
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function subscribeGitUpdates(listener: () => void): () => void {
|
|
44
|
-
updateListeners.add(listener);
|
|
45
|
-
return () => updateListeners.delete(listener);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Parse git status --porcelain output
|
|
50
|
-
*
|
|
51
|
-
* Format: XY filename
|
|
52
|
-
* X = index status, Y = working tree status
|
|
53
|
-
* ?? = untracked
|
|
54
|
-
* Other X values = staged
|
|
55
|
-
* Other Y values = unstaged
|
|
56
|
-
*/
|
|
57
|
-
function parseGitStatusOutput(output: string): { staged: number; unstaged: number; untracked: number } {
|
|
58
|
-
let staged = 0;
|
|
59
|
-
let unstaged = 0;
|
|
60
|
-
let untracked = 0;
|
|
61
|
-
|
|
62
|
-
for (const line of output.split("\n")) {
|
|
63
|
-
if (!line) continue;
|
|
64
|
-
const x = line[0];
|
|
65
|
-
const y = line[1];
|
|
66
|
-
|
|
67
|
-
if (x === "?" && y === "?") {
|
|
68
|
-
untracked++;
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// X position (index/staged)
|
|
73
|
-
if (x && x !== " " && x !== "?") {
|
|
74
|
-
staged++;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Y position (working tree/unstaged)
|
|
78
|
-
if (y && y !== " ") {
|
|
79
|
-
unstaged++;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return { staged, unstaged, untracked };
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Environment for this module's read-only git commands.
|
|
88
|
-
*
|
|
89
|
-
* Polling `git status` otherwise refreshes the index as a side effect, taking
|
|
90
|
-
* `.git/index.lock`: that races interactive git in the same repo, and orphans
|
|
91
|
-
* the lock if we are killed mid-write. `GIT_OPTIONAL_LOCKS=0` skips the
|
|
92
|
-
* refresh; the reported counts are unchanged. Preferred over
|
|
93
|
-
* `--no-optional-locks` because it also reaches the child git processes
|
|
94
|
-
* `status` spawns for submodules.
|
|
95
|
-
*/
|
|
96
|
-
export function readOnlyGitEnv(env: NodeJS.ProcessEnv = process.env): NodeJS.ProcessEnv {
|
|
97
|
-
return { ...env, GIT_OPTIONAL_LOCKS: "0" };
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function runGit(args: string[], timeoutMs = 200): Promise<string | null> {
|
|
101
|
-
return new Promise((resolve) => {
|
|
102
|
-
const proc = spawn("git", args, {
|
|
103
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
104
|
-
env: readOnlyGitEnv(),
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
let stdout = "";
|
|
108
|
-
let resolved = false;
|
|
109
|
-
|
|
110
|
-
const finish = (result: string | null) => {
|
|
111
|
-
if (resolved) return;
|
|
112
|
-
resolved = true;
|
|
113
|
-
clearTimeout(timeoutId);
|
|
114
|
-
resolve(result);
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
proc.stdout.on("data", (data) => {
|
|
118
|
-
stdout += data.toString();
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
proc.on("close", (code) => {
|
|
122
|
-
finish(code === 0 ? stdout.trim() : null);
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
proc.on("error", () => {
|
|
126
|
-
finish(null);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
const timeoutId = setTimeout(() => {
|
|
130
|
-
proc.kill();
|
|
131
|
-
finish(null);
|
|
132
|
-
}, timeoutMs);
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Fetch current git branch asynchronously.
|
|
138
|
-
* For detached HEAD, returns the short commit SHA (matches provider's "detached" behavior).
|
|
139
|
-
*/
|
|
140
|
-
async function fetchGitBranch(): Promise<string | null> {
|
|
141
|
-
const branch = await runGit(["branch", "--show-current"]);
|
|
142
|
-
if (branch === null) return null;
|
|
143
|
-
if (branch) return branch;
|
|
144
|
-
|
|
145
|
-
const sha = await runGit(["rev-parse", "--short", "HEAD"]);
|
|
146
|
-
return sha ? `${sha} (detached)` : "detached";
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Classify an origin remote URL into a known hosting provider. Handles both
|
|
151
|
-
* SSH (`git@host:owner/repo`, `ssh://git@host/…`) and HTTP(S) forms, and
|
|
152
|
-
* treats sub-domains (e.g. `www.github.com`) and any non-empty remote we
|
|
153
|
-
* don't recognize as a generic git host.
|
|
154
|
-
*/
|
|
155
|
-
export function detectGitHost(remoteUrl: string | null): GitHost | null {
|
|
156
|
-
if (!remoteUrl) return null;
|
|
157
|
-
const trimmed = remoteUrl.trim();
|
|
158
|
-
if (!trimmed) return null;
|
|
159
|
-
|
|
160
|
-
let host: string;
|
|
161
|
-
const scpLike = /^[^/@]+@([^:/]+):/.exec(trimmed);
|
|
162
|
-
if (scpLike) {
|
|
163
|
-
host = scpLike[1]!;
|
|
164
|
-
} else {
|
|
165
|
-
try {
|
|
166
|
-
host = new URL(trimmed).hostname;
|
|
167
|
-
} catch {
|
|
168
|
-
return "other";
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
host = host.toLowerCase().replace(/^www\./, "");
|
|
173
|
-
if (host === "github.com" || host.endsWith(".github.com")) return "github";
|
|
174
|
-
if (host === "gitlab.com" || host.endsWith(".gitlab.com")) return "gitlab";
|
|
175
|
-
if (host === "bitbucket.org" || host.endsWith(".bitbucket.org")) return "bitbucket";
|
|
176
|
-
return "other";
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Fetch the origin remote host asynchronously and cache the result.
|
|
181
|
-
*/
|
|
182
|
-
async function fetchRemoteHost(): Promise<GitHost | null> {
|
|
183
|
-
const url = await runGit(["remote", "get-url", "origin"]);
|
|
184
|
-
return detectGitHost(url);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Get the origin remote's hosting provider with a long TTL cache. Returns the
|
|
189
|
-
* cached value immediately (or null before the first fetch completes) and
|
|
190
|
-
* refreshes in the background, matching the branch/status caching pattern.
|
|
191
|
-
*/
|
|
192
|
-
export function getGitRemoteHost(): GitHost | null {
|
|
193
|
-
const now = Date.now();
|
|
194
|
-
if (cachedRemoteHost && now - cachedRemoteHost.timestamp < REMOTE_TTL_MS) {
|
|
195
|
-
return cachedRemoteHost.host;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
if (!pendingRemoteFetch) {
|
|
199
|
-
pendingRemoteFetch = fetchRemoteHost()
|
|
200
|
-
.then((host) => {
|
|
201
|
-
cachedRemoteHost = { host, timestamp: Date.now() };
|
|
202
|
-
notifyGitUpdate();
|
|
203
|
-
})
|
|
204
|
-
.catch(() => {
|
|
205
|
-
cachedRemoteHost = { host: null, timestamp: Date.now() };
|
|
206
|
-
notifyGitUpdate();
|
|
207
|
-
})
|
|
208
|
-
.finally(() => {
|
|
209
|
-
pendingRemoteFetch = null;
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
return cachedRemoteHost ? cachedRemoteHost.host : null;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Fetch git status asynchronously
|
|
218
|
-
*/
|
|
219
|
-
async function fetchGitStatus(): Promise<{ staged: number; unstaged: number; untracked: number } | null> {
|
|
220
|
-
const output = await runGit(["status", "--porcelain"], 500);
|
|
221
|
-
if (output === null) return null;
|
|
222
|
-
return parseGitStatusOutput(output);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* Get the current git branch with caching.
|
|
227
|
-
* Falls back to provider branch if our cache is empty.
|
|
228
|
-
*/
|
|
229
|
-
export function getCurrentBranch(providerBranch: string | null): string | null {
|
|
230
|
-
const now = Date.now();
|
|
231
|
-
|
|
232
|
-
// Return cached if fresh
|
|
233
|
-
if (cachedBranch && now - cachedBranch.timestamp < BRANCH_TTL_MS) {
|
|
234
|
-
return cachedBranch.branch;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Trigger background fetch if not already pending
|
|
238
|
-
if (!pendingBranchFetch) {
|
|
239
|
-
const fetchId = branchInvalidationCounter;
|
|
240
|
-
pendingBranchFetch = fetchGitBranch().then((result) => {
|
|
241
|
-
// Cache result if no invalidation happened (including null for non-git dirs)
|
|
242
|
-
if (fetchId === branchInvalidationCounter) {
|
|
243
|
-
cachedBranch = {
|
|
244
|
-
branch: result,
|
|
245
|
-
timestamp: Date.now(),
|
|
246
|
-
};
|
|
247
|
-
notifyGitUpdate();
|
|
248
|
-
}
|
|
249
|
-
pendingBranchFetch = null;
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// Return stale cache while refreshing; only use provider before first fetch
|
|
254
|
-
return cachedBranch ? cachedBranch.branch : providerBranch;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Get git status with caching.
|
|
259
|
-
* Returns cached value if within TTL, otherwise triggers async fetch.
|
|
260
|
-
* This is designed for synchronous render() calls - returns last known value
|
|
261
|
-
* while refreshing in background.
|
|
262
|
-
*/
|
|
263
|
-
export function getGitStatus(providerBranch: string | null, pollingMode: GitPollingMode = "full"): GitStatus {
|
|
264
|
-
const now = Date.now();
|
|
265
|
-
const branch = pollingMode === "off" ? providerBranch : getCurrentBranch(providerBranch);
|
|
266
|
-
|
|
267
|
-
if (pollingMode !== "full") {
|
|
268
|
-
return { branch, staged: 0, unstaged: 0, untracked: 0 };
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// Return cached if fresh
|
|
272
|
-
if (cachedStatus && now - cachedStatus.timestamp < CACHE_TTL_MS) {
|
|
273
|
-
return {
|
|
274
|
-
branch,
|
|
275
|
-
staged: cachedStatus.staged,
|
|
276
|
-
unstaged: cachedStatus.unstaged,
|
|
277
|
-
untracked: cachedStatus.untracked,
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// Trigger background fetch if not already pending
|
|
282
|
-
if (!pendingFetch) {
|
|
283
|
-
const fetchId = invalidationCounter; // Capture current counter
|
|
284
|
-
pendingFetch = fetchGitStatus().then((result) => {
|
|
285
|
-
// Cache result if no invalidation happened (including null for non-git dirs)
|
|
286
|
-
if (fetchId === invalidationCounter) {
|
|
287
|
-
cachedStatus = result
|
|
288
|
-
? { staged: result.staged, unstaged: result.unstaged, untracked: result.untracked, timestamp: Date.now() }
|
|
289
|
-
: { staged: 0, unstaged: 0, untracked: 0, timestamp: Date.now() };
|
|
290
|
-
notifyGitUpdate();
|
|
291
|
-
}
|
|
292
|
-
pendingFetch = null;
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// Return last cached or empty
|
|
297
|
-
if (cachedStatus) {
|
|
298
|
-
return {
|
|
299
|
-
branch,
|
|
300
|
-
staged: cachedStatus.staged,
|
|
301
|
-
unstaged: cachedStatus.unstaged,
|
|
302
|
-
untracked: cachedStatus.untracked,
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
return { branch, staged: 0, unstaged: 0, untracked: 0 };
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* Force refresh git status (call when you know files changed).
|
|
311
|
-
* Serve-stale: keep the last known counts on screen while a background
|
|
312
|
-
* refresh runs, instead of blanking the segment to zeros (footer flicker).
|
|
313
|
-
*/
|
|
314
|
-
export function invalidateGitStatus(): void {
|
|
315
|
-
if (cachedStatus) cachedStatus.timestamp = 0; // expire, but keep serving the stale value
|
|
316
|
-
invalidationCounter++; // Increment to invalidate any pending fetches
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/**
|
|
320
|
-
* Force refresh git branch (call when you know branch might have changed).
|
|
321
|
-
* Serve-stale: keep showing the last known branch until the refresh lands.
|
|
322
|
-
*/
|
|
323
|
-
export function invalidateGitBranch(): void {
|
|
324
|
-
if (cachedBranch) cachedBranch.timestamp = 0; // expire, but keep serving the stale value
|
|
325
|
-
branchInvalidationCounter++;
|
|
326
|
-
// The origin remote is repo-scoped, so a branch/cwd change may mean a
|
|
327
|
-
// different repo; drop the host cache so it re-detects.
|
|
328
|
-
cachedRemoteHost = null;
|
|
329
|
-
}
|
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { dirname } from "node:path";
|
|
3
|
-
|
|
4
|
-
export type GuideMode = "full" | "compact" | "off";
|
|
5
|
-
export type GuideDisplayMode = "full" | "compact" | "none";
|
|
6
|
-
|
|
7
|
-
export interface GuideFeature {
|
|
8
|
-
id: string;
|
|
9
|
-
section?: string;
|
|
10
|
-
title: string;
|
|
11
|
-
example: string;
|
|
12
|
-
introducedIn: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface GuidePreferences {
|
|
16
|
-
mode: GuideMode;
|
|
17
|
-
lastSeenVersion?: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface GuidePreferencesUpdate {
|
|
21
|
-
mode?: GuideMode;
|
|
22
|
-
lastSeenVersion?: string | null;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Keep this map useful, not exhaustive: each row points at a real command, shortcut,
|
|
26
|
-
// or prompt from the root README without reproducing the README in the overlay.
|
|
27
|
-
export const GUIDE_FEATURES: readonly GuideFeature[] = [
|
|
28
|
-
{
|
|
29
|
-
id: "workflow",
|
|
30
|
-
section: "Start here",
|
|
31
|
-
title: "Workflow",
|
|
32
|
-
example: '"Use $workflow to orchestrate this implementation."',
|
|
33
|
-
introducedIn: "0.9.14",
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
id: "settings",
|
|
37
|
-
section: "Start here",
|
|
38
|
-
title: "Settings",
|
|
39
|
-
example: "/settings · /guide compact|off|reset · /reload",
|
|
40
|
-
introducedIn: "0.5.13",
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
id: "subagents",
|
|
44
|
-
section: "Delegate / research",
|
|
45
|
-
title: "Plan",
|
|
46
|
-
example: '"Ask the architect to challenge this plan..."',
|
|
47
|
-
introducedIn: "0.5.13",
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
id: "parallel-review",
|
|
51
|
-
section: "Delegate / research",
|
|
52
|
-
title: "Review",
|
|
53
|
-
example: '"Run parallel commentator agents for correctness..."',
|
|
54
|
-
introducedIn: "0.5.13",
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
id: "web-research",
|
|
58
|
-
section: "Delegate / research",
|
|
59
|
-
title: "Research",
|
|
60
|
-
example: '"Ask the researcher to research <topic> and cite sources."',
|
|
61
|
-
introducedIn: "0.5.13",
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
id: "skills",
|
|
65
|
-
section: "Delegate / research",
|
|
66
|
-
title: "Skills",
|
|
67
|
-
example: "/skill:*",
|
|
68
|
-
introducedIn: "0.5.13",
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
id: "recovery",
|
|
72
|
-
section: "Recover",
|
|
73
|
-
title: "Recover",
|
|
74
|
-
example: "/undo · /tree · /fork · /cp <hash>",
|
|
75
|
-
introducedIn: "0.5.13",
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
id: "handoff",
|
|
79
|
-
section: "Recover",
|
|
80
|
-
title: "Handoff",
|
|
81
|
-
example: "/handoff-new Next: fix <problem>; start with a coding plan.",
|
|
82
|
-
introducedIn: "0.5.13",
|
|
83
|
-
},
|
|
84
|
-
];
|
|
85
|
-
|
|
86
|
-
export const GUIDE_DISMISS_HINT = "Press any key to continue";
|
|
87
|
-
|
|
88
|
-
export const GUIDE_COMPACT_LINES = [
|
|
89
|
-
"/guide full · /settings",
|
|
90
|
-
'"Use $workflow to orchestrate this implementation."',
|
|
91
|
-
'"Ask the researcher to research <topic>."',
|
|
92
|
-
"/skill:* · /undo · /handoff-new <next focus>",
|
|
93
|
-
] as const;
|
|
94
|
-
|
|
95
|
-
export type GuideLine =
|
|
96
|
-
| { kind: "section"; text: string }
|
|
97
|
-
| { kind: "feature"; feature: GuideFeature };
|
|
98
|
-
|
|
99
|
-
export function getGuideFeatureLines(features: readonly GuideFeature[] = GUIDE_FEATURES): GuideLine[] {
|
|
100
|
-
return features.flatMap((feature, index) => {
|
|
101
|
-
const previous = features[index - 1];
|
|
102
|
-
const section = feature.section && feature.section !== previous?.section
|
|
103
|
-
? [{ kind: "section" as const, text: feature.section }]
|
|
104
|
-
: [];
|
|
105
|
-
return [...section, { kind: "feature" as const, feature }];
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
110
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function isGuideMode(value: unknown): value is GuideMode {
|
|
114
|
-
return value === "full" || value === "compact" || value === "off";
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function versionParts(version: string): [number, number, number] {
|
|
118
|
-
const match = version.trim().match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
119
|
-
return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : [0, 0, 0];
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export function compareGuideVersions(left: string, right: string): number {
|
|
123
|
-
const leftParts = versionParts(left);
|
|
124
|
-
const rightParts = versionParts(right);
|
|
125
|
-
for (let i = 0; i < leftParts.length; i++) {
|
|
126
|
-
if (leftParts[i] !== rightParts[i]) return leftParts[i] - rightParts[i];
|
|
127
|
-
}
|
|
128
|
-
return 0;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export function getGuidePreferences(settings: unknown): GuidePreferences {
|
|
132
|
-
const raw = isRecord(settings) && isRecord(settings.selesaiGuide)
|
|
133
|
-
? settings.selesaiGuide
|
|
134
|
-
: {};
|
|
135
|
-
|
|
136
|
-
return {
|
|
137
|
-
mode: isGuideMode(raw.mode) ? raw.mode : "compact",
|
|
138
|
-
lastSeenVersion: typeof raw.lastSeenVersion === "string" ? raw.lastSeenVersion : undefined,
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
export function getNewGuideFeatures(
|
|
143
|
-
lastSeenVersion: string | undefined,
|
|
144
|
-
currentVersion: string,
|
|
145
|
-
features: readonly GuideFeature[] = GUIDE_FEATURES,
|
|
146
|
-
): GuideFeature[] {
|
|
147
|
-
if (!lastSeenVersion || compareGuideVersions(lastSeenVersion, currentVersion) >= 0) {
|
|
148
|
-
return [];
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return features.filter((feature) => (
|
|
152
|
-
compareGuideVersions(feature.introducedIn, lastSeenVersion) > 0
|
|
153
|
-
&& compareGuideVersions(feature.introducedIn, currentVersion) <= 0
|
|
154
|
-
));
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
export function resolveGuideDisplayMode(options: {
|
|
158
|
-
reason: string;
|
|
159
|
-
hasUI: boolean;
|
|
160
|
-
guideMode: GuideMode;
|
|
161
|
-
}): GuideDisplayMode {
|
|
162
|
-
if (!options.hasUI || options.reason === "reload" || options.guideMode === "off") {
|
|
163
|
-
return "none";
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return options.guideMode === "compact" ? "compact" : "full";
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function readSettingsFile(settingsPath: string): Record<string, unknown> | null {
|
|
170
|
-
if (!existsSync(settingsPath)) return {};
|
|
171
|
-
|
|
172
|
-
try {
|
|
173
|
-
const parsed = JSON.parse(readFileSync(settingsPath, "utf8"));
|
|
174
|
-
return isRecord(parsed) ? parsed : null;
|
|
175
|
-
} catch {
|
|
176
|
-
return null;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
export function saveGuidePreferences(
|
|
181
|
-
update: GuidePreferencesUpdate,
|
|
182
|
-
settingsPath: string,
|
|
183
|
-
): boolean {
|
|
184
|
-
const settings = readSettingsFile(settingsPath);
|
|
185
|
-
if (!settings) return false;
|
|
186
|
-
|
|
187
|
-
const current = getGuidePreferences(settings);
|
|
188
|
-
const raw = isRecord(settings) && isRecord(settings.selesaiGuide)
|
|
189
|
-
? settings.selesaiGuide
|
|
190
|
-
: {};
|
|
191
|
-
const next: Record<string, unknown> = {};
|
|
192
|
-
if (update.mode !== undefined) {
|
|
193
|
-
next.mode = update.mode;
|
|
194
|
-
} else if (typeof raw.mode === "string") {
|
|
195
|
-
// Version-only writes (markGuideVersionSeen) must preserve an explicit user
|
|
196
|
-
// mode but never persist the fallback default as a user choice.
|
|
197
|
-
next.mode = raw.mode;
|
|
198
|
-
}
|
|
199
|
-
const lastSeenVersion = update.lastSeenVersion === null
|
|
200
|
-
? undefined
|
|
201
|
-
: update.lastSeenVersion ?? current.lastSeenVersion;
|
|
202
|
-
if (lastSeenVersion) next.lastSeenVersion = lastSeenVersion;
|
|
203
|
-
|
|
204
|
-
settings.selesaiGuide = next;
|
|
205
|
-
|
|
206
|
-
try {
|
|
207
|
-
mkdirSync(dirname(settingsPath), { recursive: true });
|
|
208
|
-
writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`, "utf8");
|
|
209
|
-
return true;
|
|
210
|
-
} catch {
|
|
211
|
-
return false;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
export function markGuideVersionSeen(
|
|
216
|
-
version: string,
|
|
217
|
-
settingsPath: string,
|
|
218
|
-
): boolean {
|
|
219
|
-
return saveGuidePreferences({ lastSeenVersion: version }, settingsPath);
|
|
220
|
-
}
|