@mrclrchtr/supi-code-intelligence 0.1.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/README.md +212 -0
- package/node_modules/@mrclrchtr/supi-core/README.md +90 -0
- package/node_modules/@mrclrchtr/supi-core/package.json +30 -0
- package/node_modules/@mrclrchtr/supi-core/src/config-settings.ts +76 -0
- package/node_modules/@mrclrchtr/supi-core/src/config.ts +186 -0
- package/node_modules/@mrclrchtr/supi-core/src/context-messages.ts +119 -0
- package/node_modules/@mrclrchtr/supi-core/src/context-provider-registry.ts +36 -0
- package/node_modules/@mrclrchtr/supi-core/src/context-tag.ts +31 -0
- package/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +255 -0
- package/node_modules/@mrclrchtr/supi-core/src/index.ts +83 -0
- package/node_modules/@mrclrchtr/supi-core/src/project-roots.ts +170 -0
- package/node_modules/@mrclrchtr/supi-core/src/registry-utils.ts +54 -0
- package/node_modules/@mrclrchtr/supi-core/src/session-utils.ts +29 -0
- package/node_modules/@mrclrchtr/supi-core/src/settings-command.ts +15 -0
- package/node_modules/@mrclrchtr/supi-core/src/settings-registry.ts +41 -0
- package/node_modules/@mrclrchtr/supi-core/src/settings-ui.ts +226 -0
- package/node_modules/@mrclrchtr/supi-core/src/terminal.ts +60 -0
- package/node_modules/@mrclrchtr/supi-lsp/README.md +112 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/README.md +90 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +30 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/config-settings.ts +76 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/config.ts +186 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/context-messages.ts +119 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/context-provider-registry.ts +36 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/context-tag.ts +31 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +255 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/index.ts +83 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/project-roots.ts +170 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/registry-utils.ts +54 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/session-utils.ts +29 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings-command.ts +15 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings-registry.ts +41 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings-ui.ts +226 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/terminal.ts +60 -0
- package/node_modules/@mrclrchtr/supi-lsp/package.json +45 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/capabilities.ts +62 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-refresh.ts +229 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client.ts +545 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/transport.ts +192 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/config.ts +143 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/defaults.json +82 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-augmentation.ts +82 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-display.ts +68 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-summary.ts +73 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostics.ts +98 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/stale-diagnostics.ts +47 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/suppression-diagnostics.ts +58 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/format.ts +359 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/guidance.ts +163 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/index.ts +17 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/lsp-state.ts +82 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/lsp.ts +470 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-client-state.ts +34 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-diagnostics.ts +139 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-helpers.ts +39 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-project-info.ts +46 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-types.ts +39 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-recovery.ts +83 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-symbol.ts +18 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +550 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/overrides.ts +173 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/pattern-matcher.ts +197 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/renderer.ts +120 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/scanner.ts +153 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/search-fallback.ts +98 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/service-registry.ts +153 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/settings-registration.ts +292 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/summary.ts +153 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/tool-actions.ts +430 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/tree-persist.ts +48 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/tsconfig-scope.ts +156 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/types.ts +409 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/ui.ts +358 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/utils.ts +122 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/workspace-sentinels.ts +114 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/README.md +97 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +67 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/.gitkeep +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/bash/tree-sitter-bash.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/bash/tree-sitter-bash.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/c/tree-sitter-c.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/c/tree-sitter-c.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/cpp/tree-sitter-cpp.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/cpp/tree-sitter-cpp.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/go/tree-sitter-go.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/go/tree-sitter-go.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/html/tree-sitter-html.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/html/tree-sitter-html.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/java/tree-sitter-java.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/java/tree-sitter-java.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/javascript/tree-sitter-javascript.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/javascript/tree-sitter-javascript.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/kotlin/tree-sitter-kotlin.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/kotlin/tree-sitter-kotlin.wasm.json +12 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/python/tree-sitter-python.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/python/tree-sitter-python.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/r/tree-sitter-r.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/r/tree-sitter-r.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/ruby/tree-sitter-ruby.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/ruby/tree-sitter-ruby.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/rust/tree-sitter-rust.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/rust/tree-sitter-rust.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/sql/tree-sitter-sql.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/sql/tree-sitter-sql.wasm.json +19 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/tsx/tree-sitter-tsx.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/tsx/tree-sitter-tsx.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/typescript/tree-sitter-typescript.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/typescript/tree-sitter-typescript.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/generate-kotlin-wasm.mjs +126 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/generate-sql-wasm.mjs +144 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/vendor-wasm.mjs +151 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/callees.ts +343 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/coordinates.ts +108 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/exports.ts +315 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/formatting.ts +104 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/imports.ts +42 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/index.ts +16 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/language.ts +116 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/node-at.ts +96 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/outline.ts +287 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/runtime.ts +237 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session.ts +112 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/structure.ts +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/syntax-node.ts +13 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tree-sitter.ts +306 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/types.ts +146 -0
- package/package.json +47 -0
- package/src/actions/affected-action.ts +310 -0
- package/src/actions/brief-action.ts +242 -0
- package/src/actions/callees-action.ts +134 -0
- package/src/actions/callers-action.ts +215 -0
- package/src/actions/implementations-action.ts +190 -0
- package/src/actions/index-action.ts +187 -0
- package/src/actions/pattern-action.ts +232 -0
- package/src/architecture.ts +367 -0
- package/src/brief-focused.ts +383 -0
- package/src/brief.ts +228 -0
- package/src/code-intelligence.ts +122 -0
- package/src/git-context.ts +65 -0
- package/src/guidance.ts +39 -0
- package/src/index.ts +28 -0
- package/src/resolve-target.ts +104 -0
- package/src/search-helpers.ts +283 -0
- package/src/target-resolution.ts +368 -0
- package/src/tool-actions.ts +109 -0
- package/src/types.ts +57 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Shared registry utility for SuPi extensions.
|
|
2
|
+
//
|
|
3
|
+
// Provides a globalThis-backed registry pattern so that all jiti module instances
|
|
4
|
+
// (resolved through different node_modules symlinks) share the same Map.
|
|
5
|
+
// Without this, each symlink path gets its own module copy and its own Map,
|
|
6
|
+
// so registrations from one instance are invisible to consumers in another.
|
|
7
|
+
|
|
8
|
+
const SYMBOL_PREFIX = "@mrclrchtr/supi-core/";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Create a named registry backed by `globalThis` + `Symbol.for`.
|
|
12
|
+
*
|
|
13
|
+
* The registry is lazily initialized on first access and shared across all
|
|
14
|
+
* jiti module instances via the global symbol namespace.
|
|
15
|
+
*
|
|
16
|
+
* @typeParam T - The value type stored in the registry.
|
|
17
|
+
* @param name - Unique registry name (used to construct the `Symbol.for` key).
|
|
18
|
+
* @returns An object with `register`, `getAll`, and `clear` functions.
|
|
19
|
+
*/
|
|
20
|
+
export function createRegistry<T>(name: string) {
|
|
21
|
+
const key = Symbol.for(SYMBOL_PREFIX + name);
|
|
22
|
+
|
|
23
|
+
const getMap = (): Map<string, T> => {
|
|
24
|
+
let map = (globalThis as Record<symbol, unknown>)[key] as Map<string, T> | undefined;
|
|
25
|
+
if (!map) {
|
|
26
|
+
map = new Map<string, T>();
|
|
27
|
+
(globalThis as Record<symbol, unknown>)[key] = map;
|
|
28
|
+
}
|
|
29
|
+
return map;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
/**
|
|
34
|
+
* Register a value by id. Duplicate ids silently replace the previous registration.
|
|
35
|
+
*/
|
|
36
|
+
register: (id: string, value: T): void => {
|
|
37
|
+
getMap().set(id, value);
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get all registered values in registration order.
|
|
42
|
+
*/
|
|
43
|
+
getAll: (): T[] => {
|
|
44
|
+
return Array.from(getMap().values());
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Clear all entries from the registry (primarily for tests).
|
|
49
|
+
*/
|
|
50
|
+
clear: (): void => {
|
|
51
|
+
getMap().clear();
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Generic session-file tree-walking utilities.
|
|
2
|
+
|
|
3
|
+
import type { FileEntry, SessionEntry } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Resolve the active branch path using PI's append-only tree semantics.
|
|
7
|
+
*
|
|
8
|
+
* The active branch is the path from the **last entry** (current leaf)
|
|
9
|
+
* back to the root via `parentId`. This follows PI's tree structure where
|
|
10
|
+
* entries are append-only and the last entry in the file is always the
|
|
11
|
+
* current leaf of the active branch.
|
|
12
|
+
*/
|
|
13
|
+
export function getActiveBranchEntries(entries: FileEntry[]): SessionEntry[] {
|
|
14
|
+
const sessionEntries = entries.filter((e): e is SessionEntry => e.type !== "session");
|
|
15
|
+
const byId = new Map(sessionEntries.map((entry) => [entry.id, entry]));
|
|
16
|
+
const leaf = sessionEntries.at(-1);
|
|
17
|
+
if (!leaf) return [];
|
|
18
|
+
|
|
19
|
+
const path: SessionEntry[] = [];
|
|
20
|
+
const visited = new Set<string>();
|
|
21
|
+
let current: SessionEntry | undefined = leaf;
|
|
22
|
+
while (current) {
|
|
23
|
+
if (visited.has(current.id)) break;
|
|
24
|
+
visited.add(current.id);
|
|
25
|
+
path.unshift(current);
|
|
26
|
+
current = current.parentId ? byId.get(current.parentId) : undefined;
|
|
27
|
+
}
|
|
28
|
+
return path;
|
|
29
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// /supi-settings command registration.
|
|
2
|
+
//
|
|
3
|
+
// Thin wrapper that registers the command and delegates to openSettingsOverlay.
|
|
4
|
+
|
|
5
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
6
|
+
import { openSettingsOverlay } from "./settings-ui.ts";
|
|
7
|
+
|
|
8
|
+
export function registerSettingsCommand(pi: ExtensionAPI): void {
|
|
9
|
+
pi.registerCommand("supi-settings", {
|
|
10
|
+
description: "Manage SuPi extension settings",
|
|
11
|
+
handler: async (_args, ctx) => {
|
|
12
|
+
openSettingsOverlay(ctx);
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Settings registry for SuPi extensions.
|
|
2
|
+
//
|
|
3
|
+
// Extensions declare their settings via `registerSettings()` during their
|
|
4
|
+
// factory function. The generic settings UI reads them via `getRegisteredSettings()`.
|
|
5
|
+
|
|
6
|
+
import type { SettingItem } from "@earendil-works/pi-tui";
|
|
7
|
+
import { createRegistry } from "./registry-utils.ts";
|
|
8
|
+
|
|
9
|
+
export type SettingsScope = "project" | "global";
|
|
10
|
+
|
|
11
|
+
export interface SettingsSection {
|
|
12
|
+
/** Extension identifier — e.g. "lsp", "claude-md" */
|
|
13
|
+
id: string;
|
|
14
|
+
/** Human-readable label shown in the UI */
|
|
15
|
+
label: string;
|
|
16
|
+
/** Load current SettingItem[] for the given scope */
|
|
17
|
+
loadValues: (scope: SettingsScope, cwd: string) => SettingItem[];
|
|
18
|
+
/** Persist a change back to config */
|
|
19
|
+
persistChange: (scope: SettingsScope, cwd: string, settingId: string, value: string) => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const registry = createRegistry<SettingsSection>("settings-registry");
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Register a settings section for an extension.
|
|
26
|
+
* Call during the extension factory function (not async handlers).
|
|
27
|
+
* Duplicate ids replace the previous registration.
|
|
28
|
+
*/
|
|
29
|
+
export function registerSettings(section: SettingsSection): void {
|
|
30
|
+
registry.register(section.id, section);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Get all registered settings sections in registration order. */
|
|
34
|
+
export function getRegisteredSettings(): SettingsSection[] {
|
|
35
|
+
return registry.getAll();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Clear the registry — used by tests. */
|
|
39
|
+
export function clearRegisteredSettings(): void {
|
|
40
|
+
registry.clear();
|
|
41
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
// Generic settings overlay for SuPi extensions.
|
|
2
|
+
//
|
|
3
|
+
// Uses pi-tui's SettingsList with scope toggle (Tab), extension grouping,
|
|
4
|
+
// and search. Each extension declares its settings via registerSettings().
|
|
5
|
+
|
|
6
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
7
|
+
import { getSettingsListTheme } from "@earendil-works/pi-coding-agent";
|
|
8
|
+
import {
|
|
9
|
+
Container,
|
|
10
|
+
Input,
|
|
11
|
+
Key,
|
|
12
|
+
matchesKey,
|
|
13
|
+
type SettingItem,
|
|
14
|
+
SettingsList,
|
|
15
|
+
Text,
|
|
16
|
+
} from "@earendil-works/pi-tui";
|
|
17
|
+
import {
|
|
18
|
+
getRegisteredSettings,
|
|
19
|
+
type SettingsScope,
|
|
20
|
+
type SettingsSection,
|
|
21
|
+
} from "./settings-registry.ts";
|
|
22
|
+
|
|
23
|
+
// ── Input submenu component ──────────────────────────────────
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Creates a pi-tui Input-backed submenu component with enter-to-confirm
|
|
27
|
+
* and escape-to-cancel handling.
|
|
28
|
+
*
|
|
29
|
+
* @param currentValue - Initial value for the text input.
|
|
30
|
+
* @param label - Label text displayed above the input.
|
|
31
|
+
* @param done - Callback invoked with the confirmed value, or undefined on cancel.
|
|
32
|
+
*/
|
|
33
|
+
export function createInputSubmenu(
|
|
34
|
+
currentValue: string,
|
|
35
|
+
label: string,
|
|
36
|
+
done: (selectedValue?: string) => void,
|
|
37
|
+
): {
|
|
38
|
+
render: (width: number) => string[];
|
|
39
|
+
invalidate: () => void;
|
|
40
|
+
handleInput: (data: string) => boolean;
|
|
41
|
+
} {
|
|
42
|
+
const input = new Input();
|
|
43
|
+
input.setValue(currentValue);
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
render: (_width: number) => {
|
|
47
|
+
const lines = [` ${label}`];
|
|
48
|
+
lines.push(...input.render(_width));
|
|
49
|
+
lines.push(" enter confirm • esc cancel");
|
|
50
|
+
return lines;
|
|
51
|
+
},
|
|
52
|
+
invalidate: () => {
|
|
53
|
+
input.invalidate();
|
|
54
|
+
},
|
|
55
|
+
handleInput: (data: string) => {
|
|
56
|
+
if (matchesKey(data, Key.escape)) {
|
|
57
|
+
done();
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
if (matchesKey(data, Key.enter)) {
|
|
61
|
+
done(input.getValue());
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
input.handleInput(data);
|
|
65
|
+
return true;
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ── Types ────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
interface OverlayState {
|
|
73
|
+
scope: SettingsScope;
|
|
74
|
+
cwd: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ── Pure helpers ─────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
function getScopeLabel(scope: SettingsScope): string {
|
|
80
|
+
return scope === "project" ? "Project" : "Global";
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function buildFlatItems(
|
|
84
|
+
sections: SettingsSection[],
|
|
85
|
+
scope: SettingsScope,
|
|
86
|
+
cwd: string,
|
|
87
|
+
): SettingItem[] {
|
|
88
|
+
const items: SettingItem[] = [];
|
|
89
|
+
for (const section of sections) {
|
|
90
|
+
const sectionItems = section.loadValues(scope, cwd);
|
|
91
|
+
for (const item of sectionItems) {
|
|
92
|
+
items.push({
|
|
93
|
+
...item,
|
|
94
|
+
id: `${section.id}.${item.id}`,
|
|
95
|
+
label: `${section.label}: ${item.label}`,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return items;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function findSectionAndId(
|
|
103
|
+
sections: SettingsSection[],
|
|
104
|
+
flatId: string,
|
|
105
|
+
): { section: SettingsSection; itemId: string } | null {
|
|
106
|
+
const dotIndex = flatId.indexOf(".");
|
|
107
|
+
if (dotIndex === -1) return null;
|
|
108
|
+
const sectionId = flatId.slice(0, dotIndex);
|
|
109
|
+
const itemId = flatId.slice(dotIndex + 1);
|
|
110
|
+
const section = sections.find((s) => s.id === sectionId);
|
|
111
|
+
if (!section) return null;
|
|
112
|
+
return { section, itemId };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ── Component ────────────────────────────────────────────────
|
|
116
|
+
|
|
117
|
+
interface SettingsOverlayDeps {
|
|
118
|
+
state: OverlayState;
|
|
119
|
+
container: Container;
|
|
120
|
+
settingsList: SettingsList | null;
|
|
121
|
+
tui: Parameters<Parameters<ExtensionContext["ui"]["custom"]>[0]>[0];
|
|
122
|
+
theme: Parameters<Parameters<ExtensionContext["ui"]["custom"]>[0]>[1];
|
|
123
|
+
done: () => void;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function createSettingsList(deps: SettingsOverlayDeps): SettingsList {
|
|
127
|
+
const sections = getRegisteredSettings();
|
|
128
|
+
const items = buildFlatItems(sections, deps.state.scope, deps.state.cwd);
|
|
129
|
+
const onChange = (flatId: string, newValue: string) => {
|
|
130
|
+
const found = findSectionAndId(sections, flatId);
|
|
131
|
+
if (found) {
|
|
132
|
+
found.section.persistChange(deps.state.scope, deps.state.cwd, found.itemId, newValue);
|
|
133
|
+
}
|
|
134
|
+
// Re-read all values to reflect persisted changes, but keep the list
|
|
135
|
+
// instance (and its selectedIndex) intact.
|
|
136
|
+
const updatedItems = buildFlatItems(sections, deps.state.scope, deps.state.cwd);
|
|
137
|
+
for (const updated of updatedItems) {
|
|
138
|
+
const existing = items.find((i) => i.id === updated.id);
|
|
139
|
+
if (existing && existing.currentValue !== updated.currentValue) {
|
|
140
|
+
settingsList.updateValue(updated.id, updated.currentValue);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
deps.tui.requestRender();
|
|
144
|
+
};
|
|
145
|
+
const settingsList = new SettingsList(
|
|
146
|
+
items,
|
|
147
|
+
Math.min(items.length + 4, 20),
|
|
148
|
+
getSettingsListTheme(),
|
|
149
|
+
onChange,
|
|
150
|
+
() => deps.done(),
|
|
151
|
+
{ enableSearch: true },
|
|
152
|
+
);
|
|
153
|
+
return settingsList;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function rebuildSettingsList(deps: SettingsOverlayDeps): SettingsList {
|
|
157
|
+
const settingsList = createSettingsList(deps);
|
|
158
|
+
deps.settingsList = settingsList;
|
|
159
|
+
|
|
160
|
+
deps.container.clear();
|
|
161
|
+
deps.container.addChild(createHeaderComponent(deps));
|
|
162
|
+
deps.container.addChild(settingsList);
|
|
163
|
+
|
|
164
|
+
return settingsList;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function createHeaderComponent(deps: SettingsOverlayDeps): Text {
|
|
168
|
+
const { theme, state } = deps;
|
|
169
|
+
const scopeLabel = getScopeLabel(state.scope);
|
|
170
|
+
const otherScope = state.scope === "project" ? "Global" : "Project";
|
|
171
|
+
const headerText = new Text(
|
|
172
|
+
`${theme.fg("accent", theme.bold("SuPi Settings"))} ${theme.fg("text", `Scope: ${scopeLabel}`)} ${theme.fg("dim", `(tab → ${otherScope})`)}`,
|
|
173
|
+
0,
|
|
174
|
+
0,
|
|
175
|
+
);
|
|
176
|
+
return headerText;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function handleScopeToggle(deps: SettingsOverlayDeps): void {
|
|
180
|
+
deps.state.scope = deps.state.scope === "project" ? "global" : "project";
|
|
181
|
+
rebuildSettingsList(deps);
|
|
182
|
+
deps.tui.requestRender();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ── Entry point ──────────────────────────────────────────────
|
|
186
|
+
|
|
187
|
+
export function openSettingsOverlay(ctx: ExtensionContext): void {
|
|
188
|
+
const sections = getRegisteredSettings();
|
|
189
|
+
if (sections.length === 0) {
|
|
190
|
+
ctx.ui.notify("No settings registered by SuPi extensions", "info");
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
void ctx.ui.custom<void>((tui, theme, _kb, done) => {
|
|
195
|
+
const state: OverlayState = { scope: "project", cwd: ctx.cwd };
|
|
196
|
+
const container = new Container();
|
|
197
|
+
|
|
198
|
+
const deps: SettingsOverlayDeps = {
|
|
199
|
+
state,
|
|
200
|
+
container,
|
|
201
|
+
settingsList: null,
|
|
202
|
+
tui,
|
|
203
|
+
theme,
|
|
204
|
+
done,
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
rebuildSettingsList(deps);
|
|
208
|
+
|
|
209
|
+
const component = {
|
|
210
|
+
render: (width: number) => container.render(width),
|
|
211
|
+
invalidate: () => container.invalidate(),
|
|
212
|
+
handleInput: (data: string) => {
|
|
213
|
+
if (matchesKey(data, Key.tab)) {
|
|
214
|
+
handleScopeToggle(deps);
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
// Delegate input to the settings list (always set after rebuildSettingsList)
|
|
218
|
+
deps.settingsList?.handleInput?.(data);
|
|
219
|
+
deps.tui.requestRender();
|
|
220
|
+
return true;
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
return component;
|
|
225
|
+
});
|
|
226
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared terminal title formatting and signaling utilities.
|
|
3
|
+
*
|
|
4
|
+
* Centralized place for pi title convention (π prefix), completion (✓)
|
|
5
|
+
* and waiting (●) indicators, and the audible terminal bell.
|
|
6
|
+
*/
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
|
|
9
|
+
/** Unicode checkmark shown when the agent finishes a turn. */
|
|
10
|
+
export const DONE_SYMBOL = "\u2713";
|
|
11
|
+
/** Unicode dot shown when waiting for user input. */
|
|
12
|
+
export const WAITING_SYMBOL = "\u25CF";
|
|
13
|
+
|
|
14
|
+
/** Minimal UI surface needed for title operations. */
|
|
15
|
+
export interface TitleTarget {
|
|
16
|
+
ui: {
|
|
17
|
+
setTitle?(title: string): void;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Format pi's canonical terminal title from session name and cwd.
|
|
23
|
+
* Falls back gracefully when either is missing.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* formatTitle("my-session", "/home/projects/foo") // "π - my-session - foo"
|
|
27
|
+
* formatTitle(undefined, "/home/projects/foo") // "π - foo"
|
|
28
|
+
* formatTitle("my-session") // "π - my-session"
|
|
29
|
+
* formatTitle() // "π"
|
|
30
|
+
*/
|
|
31
|
+
export function formatTitle(sessionName?: string, cwd?: string): string {
|
|
32
|
+
const base = cwd ? path.basename(cwd) : undefined;
|
|
33
|
+
if (sessionName && base) return `π - ${sessionName} - ${base}`;
|
|
34
|
+
if (sessionName) return `π - ${sessionName}`;
|
|
35
|
+
if (base) return `π - ${base}`;
|
|
36
|
+
return "π";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Sound the audible terminal bell (ASCII BEL). */
|
|
40
|
+
export function signalBell(): void {
|
|
41
|
+
process.stdout.write("\x07");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Set the terminal title to indicate the agent is waiting for user input.
|
|
46
|
+
* Prefixes with ● and sounds the terminal bell.
|
|
47
|
+
*/
|
|
48
|
+
export function signalWaiting(ctx: TitleTarget, title: string): void {
|
|
49
|
+
ctx.ui.setTitle?.(`${WAITING_SYMBOL} ${title}`);
|
|
50
|
+
signalBell();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Set the terminal title to indicate the agent turn has completed.
|
|
55
|
+
* Prefixes with ✓ and sounds the terminal bell.
|
|
56
|
+
*/
|
|
57
|
+
export function signalDone(ctx: TitleTarget, title: string): void {
|
|
58
|
+
ctx.ui.setTitle?.(`${DONE_SYMBOL} ${title}`);
|
|
59
|
+
signalBell();
|
|
60
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# @mrclrchtr/supi-lsp
|
|
2
|
+
|
|
3
|
+
Language Server Protocol integration for the [pi coding agent](https://github.com/earendil-works/pi).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pi install npm:@mrclrchtr/supi-lsp
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## What it adds
|
|
12
|
+
|
|
13
|
+
- `lsp` tool with `hover`, `definition`, `references`, `diagnostics`, `symbols`, `rename`, `code_actions`, `workspace_symbol`, `search`, `symbol_hover`, and `recover`
|
|
14
|
+
- Stable system-prompt guidance that tells the agent to prefer LSP over grep/rg for code navigation
|
|
15
|
+
- Proactive project scanning and eager startup of detected language servers
|
|
16
|
+
- Automatic stale-diagnostic recovery when workspace sentinels change (`package.json`, root lockfiles, `tsconfig*`, generated `*.d.ts` files`) before the next agent turn, plus immediate recovery after successful `write` or `edit` calls for those paths
|
|
17
|
+
- Inline diagnostic surfacing around reads, writes, and edits
|
|
18
|
+
- Compact diagnostic context injection when outstanding diagnostics change, with stale-diagnostic warnings when needed
|
|
19
|
+
- `/lsp-status` status overlay
|
|
20
|
+
|
|
21
|
+
## Public library API
|
|
22
|
+
|
|
23
|
+
In addition to the extension entrypoint, `@mrclrchtr/supi-lsp` exports a reusable session-scoped service API for peer extensions:
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { getSessionLspService, SessionLspService } from "@mrclrchtr/supi-lsp";
|
|
27
|
+
|
|
28
|
+
const state = getSessionLspService("/project");
|
|
29
|
+
|
|
30
|
+
if (state.kind === "ready") {
|
|
31
|
+
const service = state.service;
|
|
32
|
+
const hover = await service.hover("src/index.ts", { line: 5, character: 10 });
|
|
33
|
+
const defs = await service.definition("src/index.ts", { line: 5, character: 10 });
|
|
34
|
+
const refs = await service.references("src/index.ts", { line: 5, character: 10 });
|
|
35
|
+
const impls = await service.implementation("src/index.ts", { line: 5, character: 10 });
|
|
36
|
+
const symbols = await service.documentSymbols("src/index.ts");
|
|
37
|
+
const projectServers = service.getProjectServers();
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Peer extensions can import from the package root without reaching into private files.
|
|
42
|
+
|
|
43
|
+
## Tool actions
|
|
44
|
+
|
|
45
|
+
The `lsp` tool supports these actions:
|
|
46
|
+
|
|
47
|
+
- `hover`: type info at a position
|
|
48
|
+
- `definition`: go to definition
|
|
49
|
+
- `references`: find all references
|
|
50
|
+
- `diagnostics`: per-file or project-wide diagnostics
|
|
51
|
+
- `symbols`: document symbols
|
|
52
|
+
- `rename`: workspace-wide rename
|
|
53
|
+
- `code_actions`: quick fixes at a position
|
|
54
|
+
- `workspace_symbol`: fuzzy symbol search across the project
|
|
55
|
+
- `search`: symbol search with text fallback
|
|
56
|
+
- `symbol_hover`: hover by symbol name
|
|
57
|
+
- `recover`: refresh diagnostics after workspace-wide dependency, config, or generated-type changes
|
|
58
|
+
|
|
59
|
+
Line and character positions are **1-based**.
|
|
60
|
+
|
|
61
|
+
Example:
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"action": "definition",
|
|
66
|
+
"file": "src/index.ts",
|
|
67
|
+
"line": 12,
|
|
68
|
+
"character": 8
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Configuration
|
|
73
|
+
|
|
74
|
+
If your install surface includes `/supi-settings` (for example via `@mrclrchtr/supi`), this package contributes an LSP settings section there. Use that panel to:
|
|
75
|
+
|
|
76
|
+
- enable or disable LSP globally
|
|
77
|
+
- control the severity threshold
|
|
78
|
+
- select active language servers
|
|
79
|
+
- configure file exclusion patterns
|
|
80
|
+
|
|
81
|
+
## Commands
|
|
82
|
+
|
|
83
|
+
`/lsp-status` toggles an overlay showing active language servers and outstanding diagnostics:
|
|
84
|
+
|
|
85
|
+
```text
|
|
86
|
+
λ LSP inspector /lsp-status toggles
|
|
87
|
+
3 servers running • 12 open files • 5 errors • 2 warnings
|
|
88
|
+
|
|
89
|
+
Servers
|
|
90
|
+
typescript running 24 open files
|
|
91
|
+
python running 8 open files
|
|
92
|
+
bash running 0 open files
|
|
93
|
+
|
|
94
|
+
Problems
|
|
95
|
+
src/lsp.ts:42 Cannot find name 'foo' ts(2304)
|
|
96
|
+
src/manager.ts:108 Property 'bar' does not exist ts(2339)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
When no servers are available, the overlay shows `no LSP servers available for this project`. A compact status summary is always visible in the pi status bar.
|
|
100
|
+
|
|
101
|
+
## Requirements
|
|
102
|
+
|
|
103
|
+
- `@earendil-works/pi-coding-agent`
|
|
104
|
+
- `@earendil-works/pi-tui`
|
|
105
|
+
- `typebox`
|
|
106
|
+
- relevant language servers installed and available on `PATH`
|
|
107
|
+
- `@mrclrchtr/supi-core`
|
|
108
|
+
|
|
109
|
+
## Source
|
|
110
|
+
|
|
111
|
+
- Extension entrypoint: `lsp.ts`
|
|
112
|
+
- Public library surface: `index.ts`
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# @mrclrchtr/supi-core
|
|
2
|
+
|
|
3
|
+
Shared infrastructure for SuPi packages.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
Use it as a dependency in another extension package:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @mrclrchtr/supi-core
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Package role
|
|
14
|
+
|
|
15
|
+
`@mrclrchtr/supi-core` is a library package. It does **not** register a pi extension and is not meant to be installed as a standalone pi package.
|
|
16
|
+
|
|
17
|
+
## What it provides
|
|
18
|
+
|
|
19
|
+
Current exports cover:
|
|
20
|
+
|
|
21
|
+
- shared config loading, scoped reads, writes, and key removal
|
|
22
|
+
- config-backed settings registration helpers for `/supi-settings`
|
|
23
|
+
- the shared settings registry, overlay UI, and `registerSettingsCommand()` helper
|
|
24
|
+
- XML `<extension-context>` wrapping plus context-message utilities
|
|
25
|
+
- context-provider and debug-event registries reused across SuPi packages
|
|
26
|
+
- project root and path helpers reused by packages such as `supi-lsp`
|
|
27
|
+
|
|
28
|
+
## Config system
|
|
29
|
+
|
|
30
|
+
Config resolution order:
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
defaults <- global <- project
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Config file locations:
|
|
37
|
+
|
|
38
|
+
- global: `~/.pi/agent/supi/config.json`
|
|
39
|
+
- project: `.pi/supi/config.json`
|
|
40
|
+
|
|
41
|
+
Main helpers:
|
|
42
|
+
|
|
43
|
+
- `loadSupiConfig()` — effective merged config (`defaults <- global <- project`)
|
|
44
|
+
- `loadSupiConfigForScope()` — raw single-scope config for settings UIs (`defaults <- selected scope`)
|
|
45
|
+
- `writeSupiConfig()`
|
|
46
|
+
- `removeSupiConfigKey()`
|
|
47
|
+
- `registerConfigSettings()`
|
|
48
|
+
|
|
49
|
+
## Context and settings helpers
|
|
50
|
+
|
|
51
|
+
- `wrapExtensionContext()`
|
|
52
|
+
- `findLastUserMessageIndex()`
|
|
53
|
+
- `getContextToken()`
|
|
54
|
+
- `pruneAndReorderContextMessages()`
|
|
55
|
+
- `registerSettings()`
|
|
56
|
+
- `registerSettingsCommand()`
|
|
57
|
+
- `openSettingsOverlay()`
|
|
58
|
+
|
|
59
|
+
## Example
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { loadSupiConfig, registerConfigSettings, wrapExtensionContext } from "@mrclrchtr/supi-core";
|
|
63
|
+
|
|
64
|
+
const config = loadSupiConfig("my-extension", process.cwd(), {
|
|
65
|
+
enabled: true,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
registerConfigSettings({
|
|
69
|
+
id: "my-extension",
|
|
70
|
+
label: "My Extension",
|
|
71
|
+
section: "my-extension",
|
|
72
|
+
defaults: { enabled: true },
|
|
73
|
+
buildItems: () => [],
|
|
74
|
+
persistChange: () => {},
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const message = wrapExtensionContext("my-extension", "hello", {
|
|
78
|
+
turn: 1,
|
|
79
|
+
file: "CLAUDE.md",
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Requirements
|
|
84
|
+
|
|
85
|
+
- `@earendil-works/pi-coding-agent`
|
|
86
|
+
- `@earendil-works/pi-tui`
|
|
87
|
+
|
|
88
|
+
## Source
|
|
89
|
+
|
|
90
|
+
- Main exports: `src/index.ts`
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mrclrchtr/supi-core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "SuPi core — shared infrastructure for SuPi extensions (XML context tags, config system)",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/mrclrchtr/supi.git"
|
|
9
|
+
},
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"access": "public"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"pi",
|
|
15
|
+
"pi-coding-agent"
|
|
16
|
+
],
|
|
17
|
+
"files": [
|
|
18
|
+
"src/**/*.ts",
|
|
19
|
+
"!__tests__"
|
|
20
|
+
],
|
|
21
|
+
"peerDependencies": {
|
|
22
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
23
|
+
"@earendil-works/pi-tui": "*"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/node": "^25.6.0",
|
|
27
|
+
"vitest": "^4.1.4"
|
|
28
|
+
},
|
|
29
|
+
"main": "src/index.ts"
|
|
30
|
+
}
|