diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b08cedb --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +node_modules/ +out/ +dist/ +.DS_Store +*.log +*.tsbuildinfo +.playwright-mcp/ diff --git a/CLAUDE.md b/CLAUDE.md index 86c6519..1e89136 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,26 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project state -This is a **greenfield project**. No application code, build setup, or `package.json` exists yet — only a design handoff. The first real task is to scaffold the Electron app and port the prototype. Until then, treat the two handoff documents as the contract: +**Phase 1 is scaffolded.** An `electron-vite` + React 18 + TypeScript app now lives at the repo root (`src/main`, `src/preload`, `src/renderer`). The prototype has been ported faithfully and renders against the **mock data** — full UI, git panel, four diff modes + Split, search, and the *simulated* terminals are all working. JetBrains Mono is bundled locally via `@fontsource/jetbrains-mono`; Prism is wired with the correct `markup-templating`→`php` load order; the renderer↔main clipboard bridge is in place (`src/preload/index.ts`). + +**Phase 2 (real integrations) — essentially complete.** All over IPC through the preload bridge (`src/preload/index.ts`): +- **Filesystem** — tree, in-memory content index, `chokidar` watch (`src/main/fs-service.ts`). +- **Git** — `simple-git`: status→A/M/D/R, the four diff views from HEAD-vs-worktree pairs, stage/unstage/commit/discard (`src/main/git-service.ts`). +- **Terminals** — real PTYs via `node-pty` (`src/main/pty-service.ts`) rendered with `@xterm/xterm` (`src/renderer/src/terminals.tsx`). Agent pane is a shell that auto-launches `claude`; bottom pane is a plain shell. Pass-on-to-Agent writes bracketed paste (`\x1b[200~ … \x1b[201~`) to the agent PTY. node-pty is native — `npm run rebuild` (also a `postinstall`) rebuilds it for Electron; it's N-API so the binary is portable. + +- **Config** — `.helder/` per project (`src/main/config.ts`): `config.default.json` regenerated on launch (full defaults / live docs), sparse `config.json` deep-merged over it, and `theme.css` (created once, never overwritten) injected over the built-in dark theme. The **code font + size are CSS vars** (`--code-font`/`--code-size`/`--term-size`) the editor + xterm read, overridable from `theme.css`. ai command/autoLaunch + shell flow from config into the PTYs; a `.helder` file watcher hot-reloads config/theme. + +- **Search** — ripgrep (`@vscode/ripgrep`, bundled binary) for content (`--json`, fixed-string smart-case) and the file-name list (`--files`), via `src/main/search-service.ts`. `SearchModal` calls it debounced and falls back to the in-memory index when `window.helder` is absent. + +The renderer consumes FS/git/config/search via the store in `src/renderer/src/project.tsx` (`useProject` / `useProjectActions`), which falls back to the mock + default config when `window.helder` is absent (browser preview). `src/main/index.ts` registers all IPC handlers + the debounced watchers. + +- **Editing** — the `code`/`updated` modes are a writable buffer: a transparent textarea over a Prism-highlighted `
` with a scroll-synced gutter (`CodeEditor` in `editor.tsx`). `⌘S` saves to disk (`fs:write`), `editor.autoSave` debounce-saves on change, tabs show the dirty dot, and the git-row context menu has **Discard changes** gated by `git.confirmDiscard`. Original/Diff/Split stay read-only review views.
+
+README steps 1–7 plus config + editing are all implemented for real. The editable overlay keeps the caret in view (the textarea is overflow-hidden under the scroller, so `CodeEditor` scrolls the container on input/keyup/click).
+
+Not yet done: packaging (electron-builder → .app/.dmg) — `out/` is dev build output only, there is no distributable yet.
+
+The two handoff documents remain the contract:
 
 - **`DESIGN.md`** — functional/UX source of truth. Every panel, interaction, state, and edge case at the behavior level. Read this for *what the app does*.
 - **`design_handoff_helder_workbench/README.md`** — technical source of truth. Structure, design tokens, recommended stack, real-integration mechanics, and the suggested implementation order. Read this for *how to build it*.
@@ -69,4 +88,9 @@ Canonical source is the `:root` block in `design_handoff_helder_workbench/design
 
 ## Commands
 
-No build/lint/test commands exist yet. Once the Electron + Vite toolchain is scaffolded, document the real `dev` / `build` / `lint` / `test` commands here, replacing this note.
+- `npm run dev` — launch the app in Electron with HMR (`electron-vite dev`).
+- `npm run build` — type-stripped production build into `out/` (`electron-vite build`). A frontend change is not done until this succeeds.
+- `npm run preview` / `npm start` — run the built app (`electron-vite preview`).
+- `npm run typecheck` — `tsc --noEmit` over the renderer (`tsconfig.web.json`) and main/preload (`tsconfig.node.json`). The build itself uses esbuild and does NOT type-check, so run this separately to catch type errors.
+
+No test/lint runner is wired up yet — add and document them here when introduced.
diff --git a/design_handoff_helder_workbench/design-vue/index.html b/design_handoff_helder_workbench/design-vue/index.html
deleted file mode 100644
index e288e1a..0000000
--- a/design_handoff_helder_workbench/design-vue/index.html
+++ /dev/null
@@ -1,40 +0,0 @@
-
-
-
-  
-  
-  Helder — AI Code Workbench
-  
-  
-  
-  
-
-  
-  
-  
-  
-  
-  
-  
-  
-  
-  
-
-  
-  
-
-  
-  
-  
-
-
-  
- - - - - - - - - diff --git a/design_handoff_helder_workbench/design-vue/src/app.js b/design_handoff_helder_workbench/design-vue/src/app.js deleted file mode 100644 index a38bdd0..0000000 --- a/design_handoff_helder_workbench/design-vue/src/app.js +++ /dev/null @@ -1,464 +0,0 @@ -/* App shell: 4 resizable columns, keyboard shortcuts, copy-reference, status bar */ -const { ref, computed, reactive, onMounted, onUnmounted, watch, nextTick } = Vue; - -/* ---- Helpers ---- */ -function clamp(v, lo, hi) { return Math.max(lo, Math.min(hi, v)); } -function ancestors(path) { - const parts = path.split("/"); const out = []; - for (let i = 1; i < parts.length; i++) out.push(parts.slice(0, i).join("/")); - return out; -} -function initialOpenDirs(node, set) { - if (node.type === "dir") { - if (node.open && node.path) set.add(node.path); - (node.children || []).forEach((c) => initialOpenDirs(c, set)); - } - return set; -} - -/* ---- Splitter ---- */ -const Splitter = { - props: { - orientation: { type: String, default: "v" }, - onDelta: { type: Function, required: true }, - }, - setup(props) { - const drag = ref(false); - - function down(e) { - e.preventDefault(); - let last = { x: e.clientX, y: e.clientY }; - drag.value = true; - document.body.style.cursor = props.orientation === "v" ? "col-resize" : "row-resize"; - document.body.style.userSelect = "none"; - - function mv(ev) { - props.onDelta(ev.clientX - last.x, ev.clientY - last.y); - last = { x: ev.clientX, y: ev.clientY }; - } - function up() { - drag.value = false; - document.body.style.cursor = ""; - document.body.style.userSelect = ""; - document.removeEventListener("mousemove", mv); - document.removeEventListener("mouseup", up); - } - document.addEventListener("mousemove", mv); - document.addEventListener("mouseup", up); - } - - return { drag, down }; - }, - template: `
`, -}; - -/* ---- RightColumn ---- */ -const RightColumn = { - props: { - width: { type: Number, required: true }, - }, - setup(props) { - const topFrac = ref(0.52); - const colRef = ref(null); - - function delta(dx, dy) { - const h = colRef.value ? colRef.value.clientHeight : 600; - topFrac.value = Math.max(0.18, Math.min(0.82, (topFrac.value * h + dy) / h)); - } - - const agent = agentSeed(); - const shell = shellSeed(); - - return { topFrac, colRef, delta, agent, shell }; - }, - template: ` -
-
-
- -
- -
- -
-
-
- `, -}; - -/* ---- App ---- */ -const App = { - setup() { - /* --- derived constants (no reactivity needed) --- */ - const changeMap = computed(() => Object.fromEntries(PROJECT.changes.map((c) => [c.path, c.status]))); - const changeSet = computed(() => new Set(PROJECT.changes.map((c) => c.path))); - - /* --- state --- */ - const tabs = ref([ - { path: "src/Http/Controller/UserController.php" }, - { path: "public/assets/app.js" }, - { path: "src/types/api.ts" }, - ]); - const active = ref("src/Http/Controller/UserController.php"); - const tabMode = ref({ "src/Http/Controller/UserController.php": "diff" }); - const openDirs = ref(initialOpenDirs(PROJECT.tree, new Set())); - const cursor = ref({ path: "src/Http/Controller/UserController.php", line: 1, col: 1 }); - const selection = ref(null); - const overlay = ref(null); - const menu = ref(null); - const toasts = ref([]); - const splitFor = ref(null); - const staged = ref(new Set(["src/Service/PaymentService.php", "config/app.json"])); - const committed = ref(new Set()); - const commitMsg = ref(""); - const passPopup = ref(null); - - const gitW = ref(232); - const treeW = ref(244); - const rightW = ref(444); - - /* --- computed --- */ - const MODE_LABEL = { original: "orig", updated: "upd", diff: "diff", code: "" }; - const MODE_WORD = { original: "Original", updated: "Updated", diff: "Diff" }; - - const resolvedTabs = computed(() => - tabs.value.map((t) => { - const changed = !!PROJECT.diffs[t.path]; - const m = tabMode.value[t.path] || (changed ? "diff" : "code"); - return { ...t, changed, modeLabel: splitFor.value === t.path ? "split" : MODE_LABEL[m] }; - }) - ); - - const mode = computed(() => tabMode.value[active.value] || (PROJECT.diffs[active.value] ? "diff" : "code")); - const totals = computed(() => PROJECT.changes.reduce((a, c) => ({ add: a.add + c.add, del: a.del + c.del }), { add: 0, del: 0 })); - const activeLang = computed(() => active.value ? HL.langLabel(active.value) : ""); - const crumb = computed(() => active.value ? active.value.split("/") : []); - - /* --- helpers --- */ - function toast(title, refText) { - const id = lid(); - toasts.value = [...toasts.value, { id, title, ref: refText }]; - setTimeout(() => { toasts.value = toasts.value.filter((x) => x.id !== id); }, 2300); - } - - async function copyText(text, label) { - try { await navigator.clipboard.writeText(text); } - catch (e) { - const ta = document.createElement("textarea"); ta.value = text; - ta.style.position = "fixed"; ta.style.opacity = "0"; document.body.appendChild(ta); - ta.select(); try { document.execCommand("copy"); } catch (_) {} ta.remove(); - } - toast(label || "Copied reference", text); - } - - function toggleDir(p) { - const n = new Set(openDirs.value); - n.has(p) ? n.delete(p) : n.add(p); - openDirs.value = n; - } - - function reveal(path) { - const n = new Set(openDirs.value); - ancestors(path).forEach((a) => n.add(a)); - openDirs.value = n; - } - - function stage(p) { - const n = new Set(staged.value); - n.add(p); - staged.value = n; - } - function unstage(p) { - const n = new Set(staged.value); - n.delete(p); - staged.value = n; - } - function stageAll() { - staged.value = new Set(PROJECT.changes.filter((c) => !committed.value.has(c.path)).map((c) => c.path)); - } - function unstageAll() { - staged.value = new Set(); - } - function commit() { - const list = PROJECT.changes.filter((c) => staged.value.has(c.path) && !committed.value.has(c.path)); - if (!list.length || !commitMsg.value.trim()) return; - const n = new Set(committed.value); - list.forEach((c) => n.add(c.path)); - committed.value = n; - staged.value = new Set(); - const msg = commitMsg.value.trim(); - commitMsg.value = ""; - toast(`Committed ${list.length} file${list.length > 1 ? "s" : ""}`, msg.length > 34 ? msg.slice(0, 34) + "…" : msg); - } - - function openFile(path, opts = {}) { - const changed = !!PROJECT.diffs[path]; - if (!tabs.value.some((x) => x.path === path)) { - tabs.value = [...tabs.value, { path }]; - } - active.value = path; - if (opts.diff && changed) { - tabMode.value = { ...tabMode.value, [path]: "diff" }; - } else if (!tabMode.value[path]) { - tabMode.value = { ...tabMode.value, [path]: changed ? "diff" : "code" }; - } - reveal(path); - if (opts.line) { - // show the current/updated file so line numbers map to search hits - splitFor.value = null; - tabMode.value = { ...tabMode.value, [path]: changed ? "updated" : "code" }; - cursor.value = { path, line: opts.line, col: 1 }; - selection.value = null; - setTimeout(() => { - const row = document.querySelector('.editor .ln-row[data-line="' + opts.line + '"]'); - if (row) { const ed = row.closest(".editor"); const er = ed.getBoundingClientRect(), rr = row.getBoundingClientRect(); ed.scrollTop += (rr.top - er.top) - ed.clientHeight / 2; } - }, 70); - } - } - - function closeTab(path) { - const t = tabs.value; - const ix = t.findIndex((x) => x.path === path); - const next = t.filter((x) => x.path !== path); - if (path === active.value) { - const fallback = next[ix] || next[ix - 1] || next[next.length - 1]; - active.value = fallback ? fallback.path : null; - } - tabs.value = next; - } - - /* ---- context menus ---- */ - function openMenu(e, target) { - e.preventDefault(); e.stopPropagation(); - const sparkSend = (ref) => ({ - icon: "spark", - label: "Send reference to agent", - onClick: () => { - window.dispatchEvent(new CustomEvent("agentPaste", { detail: ref })); - toast("Passed to agent", ref); - }, - }); - if (target.kind === "editor") { - const ref = target.sel - ? `${target.path}:${target.sel.start}-${target.sel.end}` - : `${target.path}:${target.line}`; - const mx = e.clientX, my = e.clientY; - menu.value = { - x: mx, y: my, note: ref, - items: [ - { primary: true, icon: "copy", label: "Copy reference", onClick: () => copyText(ref) }, - { icon: "spark", label: "Pass on to Agent", onClick: () => { passPopup.value = { x: mx, y: my, ref }; } }, - ], - }; - } else { - const isDir = target.kind === "dir"; - const ref = isDir ? target.path + "/" : target.path; - const name = target.path.split("/").pop(); - const items = [ - { primary: true, icon: "copy", label: "Copy reference", onClick: () => copyText(ref) }, - sparkSend(ref), - { icon: "copy", label: isDir ? "Copy folder path" : "Copy file name", onClick: () => copyText(isDir ? target.path : name, "Copied") }, - ]; - if (!isDir) { - items.push({ sep: true }); - if (target.kind === "git") { - const isStaged = staged.value.has(target.path); - items.push(isStaged - ? { icon: "minus", label: "Unstage changes", onClick: () => unstage(target.path) } - : { icon: "plus", label: "Stage changes", onClick: () => stage(target.path) }); - items.push({ icon: "diff", label: "Open diff", onClick: () => openFile(target.path, { diff: true }) }); - } - items.push({ icon: "file", label: "Open file", onClick: () => openFile(target.path) }); - items.push({ icon: "reveal", label: "Reveal in Explorer", onClick: () => reveal(target.path) }); - } - menu.value = { x: e.clientX, y: e.clientY, note: ref, items }; - } - } - - /* ---- keyboard shortcuts ---- */ - function onKey(e) { - const meta = e.metaKey || e.ctrlKey; - if (meta && e.key.toLowerCase() === "f") { e.preventDefault(); overlay.value = "search"; } - else if (meta && e.key.toLowerCase() === "w") { e.preventDefault(); if (active.value) closeTab(active.value); } - else if (e.key === "Escape") { - if (splitFor.value) splitFor.value = null; - else { overlay.value = null; menu.value = null; } - } - } - - onMounted(() => window.addEventListener("keydown", onKey)); - onUnmounted(() => window.removeEventListener("keydown", onKey)); - - /* --- passPopup confirm handler (passed as prop) --- */ - function onPassConfirm(text) { - const line = (text && text.trim() ? text.trim() + " " : "") + passPopup.value.ref; - window.dispatchEvent(new CustomEvent("agentPaste", { detail: line })); - const savedRef = passPopup.value.ref; - passPopup.value = null; - toast("Passed to agent", savedRef); - } - function onPassCancel() { passPopup.value = null; } - - function setCommitMsg(v) { commitMsg.value = v; } - function setActive(p) { active.value = p; } - function setMode(m) { tabMode.value = { ...tabMode.value, [active.value]: m }; splitFor.value = null; } - function setCursorVal(v) { cursor.value = v; } - function setSelectionVal(v) { selection.value = v; } - function setSplitFor(p) { splitFor.value = p; } - - function clampGitW(dx) { gitW.value = clamp(gitW.value + dx, 160, 460); } - function clampTreeW(dx) { treeW.value = clamp(treeW.value + dx, 160, 520); } - function clampRightW(dx) { rightW.value = clamp(rightW.value - dx, 280, 780); } - - return { - changeMap, changeSet, - tabs, active, tabMode, openDirs, cursor, selection, overlay, menu, toasts, splitFor, - staged, committed, commitMsg, passPopup, - gitW, treeW, rightW, - resolvedTabs, mode, totals, activeLang, crumb, - MODE_LABEL, MODE_WORD, - toast, copyText, toggleDir, reveal, - stage, unstage, stageAll, unstageAll, commit, - openFile, closeTab, openMenu, - onPassConfirm, onPassCancel, - setCommitMsg, setActive, setMode, setCursorVal, setSelectionVal, setSplitFor, - clampGitW, clampTreeW, clampRightW, - PROJECT, /* expose global so template can reference it */ - }; - }, - - template: ` -
- - -
-
-
- - Helder - - {{ PROJECT.name }} -
-
- -
-
-
- -
-
- - -
-
- -
- - -
- -
- - -
- -
- - - -
- - -
-
- - {{ PROJECT.branch }} -
-
- +{{ totals.add }} - −{{ totals.del }} -
-
-
- - -
-
Spaces: 4
-
UTF-8
-
{{ activeLang }}
-
- {{ splitFor === active ? 'Split' : (MODE_WORD[mode] || '') }} -
-
- - - - - - - - -
- `, -}; - -/* ---- Mount ---- */ -const app = Vue.createApp(App); -Object.entries(window.HelderComponents).forEach(([n, c]) => app.component(n, c)); -app.component("Splitter", Splitter); -app.component("RightColumn", RightColumn); -app.mount("#app"); diff --git a/design_handoff_helder_workbench/design-vue/src/components.js b/design_handoff_helder_workbench/design-vue/src/components.js deleted file mode 100644 index ebbabd9..0000000 --- a/design_handoff_helder_workbench/design-vue/src/components.js +++ /dev/null @@ -1,395 +0,0 @@ -/* Shared icons, FileIcon, GitPanel, FileTree — Vue 3 classic-script port */ -const { ref, computed, onMounted, onUnmounted, watch, nextTick } = Vue; - -/* ---- Icon component ---- */ -/* Internal map: name → { vb, inner } */ -const _iconMap = { - search: { - vb: "0 0 16 16", - inner: `` - }, - branch: { - vb: "0 0 16 16", - inner: `` - }, - close: { - vb: "0 0 12 12", - inner: `` - }, - copy: { - vb: "0 0 16 16", - inner: `` - }, - terminal: { - vb: "0 0 16 16", - inner: `` - }, - spark: { - vb: "0 0 16 16", - inner: `` - }, - file: { - vb: "0 0 16 16", - inner: `` - }, - reveal: { - vb: "0 0 16 16", - inner: `` - }, - diff: { - vb: "0 0 16 16", - inner: `` - }, - plus: { - vb: "0 0 14 14", - inner: `` - }, - minus: { - vb: "0 0 14 14", - inner: `` - }, - check: { - vb: "0 0 14 14", - inner: `` - }, - discard: { - vb: "0 0 16 16", - inner: `` - }, -}; - -const Icon = { - props: { - name: { type: String, required: true }, - w: { type: Number, default: 13 }, - h: { type: Number, default: 13 }, - }, - setup(props) { - const entry = computed(() => _iconMap[props.name] || null); - const vb = computed(() => entry.value ? entry.value.vb : "0 0 16 16"); - const inner = computed(() => entry.value ? entry.value.inner : ""); - return { vb, inner }; - }, - template: ``, -}; - -/* ---- Chevron ---- */ -const Chevron = { - props: { - open: { type: Boolean, default: false }, - }, - template: ` - - - - `, -}; - -/* ---- FolderIcon ---- */ -const FolderIcon = { - props: { - open: { type: Boolean, default: false }, - }, - template: ` - - - - `, -}; - -/* ---- FileIcon ---- */ -const FileIcon = { - props: { - path: { type: String, required: true }, - }, - setup(props) { - const ic = computed(() => HL.iconFor(props.path)); - return { ic }; - }, - template: ` - {{ ic.t }} - `, -}; - -/* ---- GitRow ---- */ -const GitRow = { - props: { - c: { type: Object, required: true }, - staged: { type: Boolean, required: true }, - activePath: { type: String, default: null }, - onOpen: { type: Function, required: true }, - onContext: { type: Function, required: true }, - onToggleStage: { type: Function, required: true }, - }, - components: { Icon, FileIcon }, - setup(props) { - const name = computed(() => props.c.path.split("/").pop()); - const dir = computed(() => props.c.path.split("/").slice(0, -1).join("/")); - return { name, dir }; - }, - template: ` -
- {{ c.status }} - - {{ name }} - {{ dir }}/ - - - +{{ c.add }} - -{{ c.del }} - -
- `, -}; - -/* ---- GitPanel (multi-root fragment) ---- */ -const GitPanel = { - props: { - changes: { type: Array, required: true }, - staged: { type: Object, required: true }, /* Set */ - committed: { type: Object, required: true }, /* Set */ - commitMsg: { type: String, required: true }, - setCommitMsg: { type: Function, required: true }, - onStage: { type: Function, required: true }, - onUnstage: { type: Function, required: true }, - onStageAll: { type: Function, required: true }, - onUnstageAll: { type: Function, required: true }, - onCommit: { type: Function, required: true }, - onOpen: { type: Function, required: true }, - onContext: { type: Function, required: true }, - activePath: { type: String, default: null }, - }, - components: { Icon, GitRow }, - setup(props) { - const visible = computed(() => props.changes.filter((c) => !props.committed.has(c.path))); - const stagedList = computed(() => visible.value.filter((c) => props.staged.has(c.path))); - const changesList = computed(() => visible.value.filter((c) => !props.staged.has(c.path))); - const totals = computed(() => - visible.value.reduce((a, c) => ({ add: a.add + c.add, del: a.del + c.del }), { add: 0, del: 0 }) - ); - const canCommit = computed(() => stagedList.value.length > 0 && props.commitMsg.trim().length > 0); - - function handleKeyDown(e) { - if ((e.metaKey || e.ctrlKey) && e.key === "Enter" && canCommit.value) { - e.preventDefault(); - props.onCommit(); - } - } - - return { visible, stagedList, changesList, totals, canCommit, handleKeyDown }; - }, - /* Vue 3 allows multi-root templates */ - template: ` -
- Source Control - {{ visible.length }} -
- -
- - -
- -
-
- No changes — working tree clean -
- -
- -
- {{ PROJECT.branch }} - - +{{ totals.add }}{{ ' ' }} - -{{ totals.del }} - -
- `, -}; - -/* ---- TreeNode (recursive — registered globally) ---- */ -const TreeNode = { - name: "TreeNode", - props: { - node: { type: Object, required: true }, - depth: { type: Number, required: true }, - openDirs: { type: Object, required: true }, /* Set */ - toggleDir: { type: Function, required: true }, - onOpen: { type: Function, required: true }, - onContext: { type: Function, required: true }, - activePath: { type: String, default: null }, - changeMap: { type: Object, required: true }, - committed: { type: Object, default: null }, /* Set or null */ - }, - components: { Chevron, FolderIcon, FileIcon }, - setup(props) { - const pad = computed(() => 10 + props.depth * 13); - const isOpen = computed(() => props.node.type === "dir" && (props.openDirs.has(props.node.path) || props.node.path === "")); - const status = computed(() => { - if (props.node.type === "dir") return null; - return (props.committed && props.committed.has(props.node.path)) ? null : props.changeMap[props.node.path]; - }); - return { pad, isOpen, status }; - }, - /* Multi-root fragment for the dir case */ - template: ` - -
- - - - {{ node.name }} - - {{ status }} -
- `, -}; - -/* Self-reference: register TreeNode in itself for recursion */ -TreeNode.components = Object.assign(TreeNode.components || {}, { TreeNode }); - -/* ---- FileTree (multi-root fragment) ---- */ -const FileTree = { - props: { - tree: { type: Object, required: true }, - openDirs: { type: Object, required: true }, /* Set */ - toggleDir: { type: Function, required: true }, - onOpen: { type: Function, required: true }, - onContext: { type: Function, required: true }, - activePath: { type: String, default: null }, - changeMap: { type: Object, required: true }, - committed: { type: Object, default: null }, /* Set or null */ - }, - components: { TreeNode }, - template: ` -
- Explorer - {{ tree.name }} -
-
- -
- `, -}; - -/* ---- Global export ---- */ -window.HelderComponents = Object.assign(window.HelderComponents || {}, { - Icon, - Chevron, - FolderIcon, - FileIcon, - GitRow, - GitPanel, - TreeNode, - FileTree, -}); diff --git a/design_handoff_helder_workbench/design-vue/src/editor.js b/design_handoff_helder_workbench/design-vue/src/editor.js deleted file mode 100644 index 2afc52a..0000000 --- a/design_handoff_helder_workbench/design-vue/src/editor.js +++ /dev/null @@ -1,331 +0,0 @@ -/* Editor: tabs + four view modes (Original / Updated / Diff / Split) + line selection */ - -const { ref, computed, onMounted, nextTick, watch } = Vue; - -/* ---------- framework-agnostic helpers (ported verbatim) ---------- */ - -function climbToLine(node) { - let el = node && node.nodeType === 3 ? node.parentElement : node; - while (el && !(el.dataset && el.dataset.line)) el = el.parentElement; - return el || null; -} - -function buildLines(mode, diff, fileText) { - if (mode === "original") return { lines: diff.left.map((l) => ({ no: l.no, text: l.text, row: l.mark === "del" ? "bar-del" : null })), showSign: false }; - if (mode === "updated") return { lines: diff.right.map((l) => ({ no: l.no, text: l.text, row: l.mark === "add" ? "bar-add" : null })), showSign: false }; - if (mode === "diff") return { lines: diff.rows.map((r) => ({ no: r.newNo || r.oldNo, text: r.text, sign: r.sign, row: r.sign === "+" ? "add" : r.sign === "-" ? "del" : null })), showSign: true }; - // plain file - const arr = (fileText || "").replace(/\n$/, "").split("\n"); - return { lines: arr.map((t, i) => ({ no: i + 1, text: t })), showSign: false }; -} - -const SEGMENTS = [ - { id: "original", label: "Original" }, - { id: "updated", label: "Updated" }, - { id: "diff", label: "Diff" }, -]; - -/* ---------- EditorTabs ---------- */ - -const EditorTabs = { - props: ["tabs", "active", "onActivate", "onClose"], - setup(props) { - const rootRef = ref(null); - - watch(() => props.active, async () => { - await nextTick(); - const el = rootRef.value && rootRef.value.querySelector(".tab.active"); - if (el) el.scrollIntoView({ block: "nearest", inline: "nearest" }); - }); - - return { rootRef }; - }, - template: ` -
-
- - {{ t.path.split('/').pop() }} - {{ t.modeLabel }} - - - -
-
- `, -}; - -/* ---------- PaneView ---------- */ - -const PaneView = { - props: ["cacheKey", "path", "lines", "lang", "showSign", "refLine", "cursor", "selection", "setCursor", "setSelection", "onContext"], - setup(props) { - // anchor for shift-click range selection - const anchorRef = ref(null); - - // highlighted HTML — recomputed when cacheKey changes - const html = computed(() => props.lines.map((l) => HL.hlLine(l.text, props.lang))); - - function gutterClick(e, no) { - if (no == null) return; - e.stopPropagation(); - if (e.shiftKey && anchorRef.value != null) { - const a = anchorRef.value; - props.setSelection({ path: props.path, start: Math.min(a, no), end: Math.max(a, no), anchor: a }); - } else { - anchorRef.value = no; - props.setSelection({ path: props.path, start: no, end: no, anchor: no }); - } - props.setCursor({ path: props.path, line: no, col: 1 }); - } - - function caretCol(sel) { - try { - const el = climbToLine(sel.focusNode); - const code = el.querySelector(".ln-code"); - const r = document.createRange(); - r.setStart(code, 0); r.setEnd(sel.focusNode, sel.focusOffset); - return r.toString().length + 1; - } catch (e) { return 1; } - } - - function onMouseUp() { - const sel = window.getSelection(); - if (sel && !sel.isCollapsed) { - const a = climbToLine(sel.anchorNode), f = climbToLine(sel.focusNode); - if (a && f) { - const an = +a.dataset.line, fn = +f.dataset.line; - const s = Math.min(an, fn), e = Math.max(an, fn); - if (s !== e) { props.setSelection({ path: props.path, start: s, end: e, anchor: an }); props.setCursor({ path: props.path, line: fn, col: caretCol(sel) }); return; } - } - } - if (sel && sel.focusNode) { - const el = climbToLine(sel.focusNode); - if (el) { props.setCursor({ path: props.path, line: +el.dataset.line, col: caretCol(sel) }); props.setSelection(null); } - } - } - - function handleContext(e) { - e.preventDefault(); - const sel = window.getSelection(); - let info = { path: props.path, kind: "editor" }; - const a = sel && sel.anchorNode && climbToLine(sel.anchorNode); - const f = sel && sel.focusNode && climbToLine(sel.focusNode); - if (sel && !sel.isCollapsed && a && f && +a.dataset.line !== +f.dataset.line) { - const s = Math.min(+a.dataset.line, +f.dataset.line), en = Math.max(+a.dataset.line, +f.dataset.line); - info.sel = { start: s, end: en }; info.line = s; - } else if (props.selection && props.selection.path === props.path && props.selection.start !== props.selection.end) { - info.sel = { start: props.selection.start, end: props.selection.end }; info.line = props.selection.start; - } else { - let no = null; - const r = document.caretRangeFromPoint ? document.caretRangeFromPoint(e.clientX, e.clientY) : null; - const el = r && climbToLine(r.startContainer); - if (el) no = +el.dataset.line; - info.line = no || (props.cursor && props.cursor.path === props.path ? props.cursor.line : 1); - } - props.setCursor({ path: props.path, line: info.line, col: 1 }); - props.onContext(e, info); - } - - return { html, gutterClick, onMouseUp, handleContext }; - }, - template: ` -
- -
- `, -}; - -/* ---------- Editor ---------- */ - -const Editor = { - props: ["tabs", "active", "mode", "setMode", "onActivate", "onClose", "onContext", "onSplit", "splitOpen", "cursor", "selection", "setCursor", "setSelection"], - setup(props) { - const tab = computed(() => props.tabs.find((t) => t.path === props.active)); - const change = computed(() => tab.value ? PROJECT.changes.find((c) => c.path === tab.value.path) : null); - const diff = computed(() => tab.value ? PROJECT.diffs[tab.value.path] : null); - const lang = computed(() => tab.value ? HL.langFor(tab.value.path) : null); - const effMode = computed(() => change.value ? props.mode : "code"); - - const built = computed(() => { - if (!tab.value) return null; - if (change.value && diff.value) return buildLines(effMode.value, diff.value, PROJECT.files[tab.value.path]); - return buildLines("code", null, PROJECT.files[tab.value.path]); - }); - - const statusWord = computed(() => { - if (!change.value) return ""; - return change.value.status === "A" ? "Added" : change.value.status === "D" ? "Deleted" : "Modified"; - }); - const activeSeg = computed(() => props.splitOpen ? "split" : effMode.value); - const emptyUpdated = computed(() => effMode.value === "updated" && built.value && built.value.lines.length === 0); - const emptyOriginal = computed(() => effMode.value === "original" && built.value && built.value.lines.length === 0); - - return { tab, change, diff, lang, effMode, built, statusWord, activeSeg, emptyUpdated, emptyOriginal, SEGMENTS }; - }, - template: ` - - -
-
-
No file open
-
-
Search files & content⌘ F
-
Copy referenceright-click
-
Pass on to Agentright-click
-
-
- -
-
- {{ statusWord }} - +{{ change.add }} - −{{ change.del }} -
- - -
-
- -
-
No updated version
-
This file was deleted in the change.
-
-
-
No original version
-
This file is new in the change.
-
- -
- `, -}; - -/* ---------- SplitView ---------- */ - -const SplitView = { - props: ["path", "onClose", "onContext"], - setup(props) { - const diff = computed(() => PROJECT.diffs[props.path]); - const lang = computed(() => HL.langFor(props.path)); - const change = computed(() => PROJECT.changes.find((c) => c.path === props.path)); - - const leftRef = ref(null); - const rightRef = ref(null); - const lock = ref(false); - - const leftHtml = computed(() => diff.value.split.map((r) => r.l ? HL.hlLine(r.l.text, lang.value) : "")); - const rightHtml = computed(() => diff.value.split.map((r) => r.r ? HL.hlLine(r.r.text, lang.value) : "")); - - function sync(from, to) { - if (lock.value) return; lock.value = true; - to.scrollTop = from.scrollTop; - to.scrollLeft = from.scrollLeft; - requestAnimationFrame(() => { lock.value = false; }); - } - - function ctx(e, side) { - e.preventDefault(); - let no = null; - const r = document.caretRangeFromPoint ? document.caretRangeFromPoint(e.clientX, e.clientY) : null; - const el = r && climbToLine(r.startContainer); - if (el) no = +el.dataset.line; - props.onContext(e, { path: props.path, kind: "editor", line: no || 1 }); - } - - return { diff, lang, change, leftRef, rightRef, sync, ctx, leftHtml, rightHtml }; - }, - template: ` -
-
- - {{ path }} - {{ change.status === 'A' ? 'Added' : change.status === 'D' ? 'Deleted' : 'Modified' }} - +{{ change.add }} - −{{ change.del }} - -
-
-
-
Original before
-
-
- {{ row.l ? row.l.no : '' }} - -
-
-
-
-
Updated after
-
-
- {{ row.r ? row.r.no : '' }} - -
-
-
-
-
- `, -}; - -/* ---------- global registration ---------- */ - -window.HelderComponents = Object.assign(window.HelderComponents || {}, { EditorTabs, PaneView, Editor, SplitView }); -window.buildLines = buildLines; -window.climbToLine = climbToLine; diff --git a/design_handoff_helder_workbench/design-vue/src/highlight.js b/design_handoff_helder_workbench/design-vue/src/highlight.js deleted file mode 100644 index 6143b9c..0000000 --- a/design_handoff_helder_workbench/design-vue/src/highlight.js +++ /dev/null @@ -1,79 +0,0 @@ -/* Syntax highlighting (Prism) + file-type icon metadata. */ -(function () { - const EXT_LANG = { - php: "php", js: "javascript", mjs: "javascript", cjs: "javascript", - jsx: "jsx", ts: "typescript", tsx: "tsx", py: "python", - html: "markup", xml: "markup", svg: "markup", vue: "markup", - css: "css", scss: "css", json: "json", md: "markdown", - sh: "bash", bash: "bash", yml: "yaml", yaml: "yaml", env: "bash", - }; - - function ext(path) { - const base = path.split("/").pop() || ""; - if (base === ".env" || base.startsWith(".env")) return "env"; - const i = base.lastIndexOf("."); - return i >= 0 ? base.slice(i + 1).toLowerCase() : ""; - } - - function langFor(path) { return EXT_LANG[ext(path)] || null; } - - function langLabel(path) { - const e = ext(path); - const map = { - php: "PHP", js: "JavaScript", mjs: "JavaScript", ts: "TypeScript", - tsx: "TypeScript", jsx: "JavaScript", py: "Python", html: "HTML", - css: "CSS", json: "JSON", md: "Markdown", sh: "Shell", env: "Dotenv", - yml: "YAML", yaml: "YAML", - }; - return map[e] || (e ? e.toUpperCase() : "Plain Text"); - } - - function escapeHtml(s) { - return s.replace(/[&<>]/g, (c) => ({ "&": "&", "<": "<", ">": ">" }[c])); - } - - // highlight a single line independently (keeps line numbering robust) - function hlLine(line, lang) { - if (line === "") return " "; - try { - const grammar = lang && window.Prism && Prism.languages[lang]; - if (grammar) return Prism.highlight(line, grammar, lang); - } catch (e) { /* fall through */ } - return escapeHtml(line); - } - - // ---- file-type icon: colored monogram chip -------------------------- - const ICONS = { - php: { c: "#a78bdb", t: "php" }, - js: { c: "#e6c860", t: "js" }, - mjs: { c: "#e6c860", t: "js" }, - ts: { c: "#5a9bd6", t: "ts" }, - tsx: { c: "#5a9bd6", t: "ts" }, - jsx: { c: "#5a9bd6", t: "jsx" }, - py: { c: "#5fa8d6", t: "py" }, - html: { c: "#e08b6a", t: "<>" }, - css: { c: "#5a9bd6", t: "{}" }, - scss: { c: "#d6699e", t: "{}" }, - json: { c: "#d8a85c", t: "{}" }, - md: { c: "#9aa0a8", t: "md" }, - env: { c: "#7fc6a0", t: "$" }, - sh: { c: "#7fc6a0", t: "$" }, - yml: { c: "#cf7a6a", t: "yml" }, - yaml: { c: "#cf7a6a", t: "yml" }, - lock: { c: "#8a8f98", t: "lk" }, - }; - const NAME_ICONS = { - "composer.json": { c: "#a78bdb", t: "co" }, - "package.json": { c: "#cf7a6a", t: "pk" }, - "README.md": { c: "#5a9bd6", t: "md" }, - ".env": { c: "#7fc6a0", t: "$" }, - }; - - function iconFor(path) { - const base = path.split("/").pop() || ""; - if (NAME_ICONS[base]) return NAME_ICONS[base]; - return ICONS[ext(path)] || { c: "#7d838c", t: base.slice(0, 2) || "·" }; - } - - window.HL = { ext, langFor, langLabel, hlLine, iconFor, escapeHtml }; -})(); diff --git a/design_handoff_helder_workbench/design-vue/src/overlays.js b/design_handoff_helder_workbench/design-vue/src/overlays.js deleted file mode 100644 index 55bb976..0000000 --- a/design_handoff_helder_workbench/design-vue/src/overlays.js +++ /dev/null @@ -1,332 +0,0 @@ -/* Overlays: command palette (fuzzy file finder), content search, context menu, toast */ - -const { ref, computed, onMounted, onUnmounted, watch, nextTick } = Vue; - -// --------------------------------------------------------------------------- -// fuzzy(q, str) — verbatim port -// --------------------------------------------------------------------------- -function fuzzy(q, str) { - q = q.toLowerCase(); const s = str.toLowerCase(); - let i = 0; const idx = []; - for (let j = 0; j < s.length && i < q.length; j++) { - if (s[j] === q[i]) { idx.push(j); i++; } - } - return i === q.length ? idx : null; -} - -// --------------------------------------------------------------------------- -// Highlight — props: text, idx -// --------------------------------------------------------------------------- -const Highlight = { - props: ['text', 'idx'], - setup(props) { - // Build an array of { ch, bold } for rendering - const parts = computed(() => { - const set = props.idx && props.idx.length ? new Set(props.idx) : null; - return props.text.split('').map((ch, i) => ({ ch, bold: set ? set.has(i) : false })); - }); - return { parts }; - }, - template: ` - - - - ` -}; - -// --------------------------------------------------------------------------- -// SearchModal — props: onOpen, onOpenAt, onClose, changeSet -// --------------------------------------------------------------------------- -const SearchModal = { - props: ['onOpen', 'onOpenAt', 'onClose', 'changeSet'], - setup(props) { - const q = ref(''); - const sel = ref(0); - const inputEl = ref(null); - const leftEl = ref(null); - - // All paths — stable, PROJECT.files doesn't change at runtime - const allPaths = computed(() => Object.keys(PROJECT.files)); - - // Content hits (left panel) — substring search, min 2 chars - const content = computed(() => { - const term = q.value.trim(); - if (term.length < 2) return []; - const low = term.toLowerCase(); - const groups = []; - for (const [path, src] of Object.entries(PROJECT.files)) { - const lines = src.split('\n'); - const hits = []; - lines.forEach((ln, i) => { - const ix = ln.toLowerCase().indexOf(low); - if (ix >= 0) hits.push({ no: i + 1, ln, ix }); - }); - if (hits.length) groups.push({ path, hits }); - } - return groups; - }); - - // File-name fuzzy matches (right panel) - const files = computed(() => { - const term = q.value.trim(); - if (!term) return []; - const out = []; - for (const p of allPaths.value) { - const name = p.split('/').pop(); - const ni = fuzzy(term, name); - if (ni) { out.push({ path: p, idx: ni, rank: 0, pos: ni[0] }); continue; } - const pi = fuzzy(term, p); - if (pi) out.push({ path: p, idx: null, rank: 1, pos: pi[0] }); - } - out.sort((a, b) => a.rank - b.rank || a.pos - b.pos || a.path.length - b.path.length); - return out; - }); - - // Flat list of content hits for keyboard nav + flat-index tracking - // Each entry gets its flatIndex here so the template can use it directly - const flat = computed(() => { - const arr = []; - content.value.forEach((g) => g.hits.forEach((h) => arr.push({ path: g.path, no: h.no }))); - return arr; - }); - - // Precomputed flat-index map: "path:lineNo" -> flatIndex - // Used in template to assign .sel class without mutating during render - const flatIndexMap = computed(() => { - const map = {}; - let ix = 0; - content.value.forEach((g) => { - g.hits.slice(0, 12).forEach((h) => { - map[`${g.path}:${h.no}`] = ix++; - }); - }); - return map; - }); - - const totalHits = computed(() => flat.value.length); - - // Reset selection when query changes - watch(q, () => { sel.value = 0; }); - - // Scroll selected line into view when sel changes - watch(sel, () => { - nextTick(() => { - const el = leftEl.value && leftEl.value.querySelector('.sr-line.sel'); - if (el) el.scrollIntoView({ block: 'nearest' }); - }); - }); - - onMounted(() => { inputEl.value && inputEl.value.focus(); }); - - function onKey(e) { - if (e.key === 'ArrowDown') { - e.preventDefault(); - sel.value = Math.min(sel.value + 1, flat.value.length - 1); - } else if (e.key === 'ArrowUp') { - e.preventDefault(); - sel.value = Math.max(sel.value - 1, 0); - } else if (e.key === 'Enter') { - e.preventDefault(); - if (flat.value[sel.value]) { - props.onOpenAt(flat.value[sel.value].path, flat.value[sel.value].no); - props.onClose(); - } else if (files.value[0]) { - props.onOpen(files.value[0].path); - props.onClose(); - } - } else if (e.key === 'Escape') { - e.preventDefault(); - props.onClose(); - } - } - - // renderLine: returns { pre, mid, post } for a content line hit - function renderLine(ln, ix, len) { - return { pre: ln.slice(0, ix), mid: ln.slice(ix, ix + len), post: ln.slice(ix + len) }; - } - - return { - q, sel, inputEl, leftEl, - content, files, flat, flatIndexMap, totalHits, - onKey, renderLine, - }; - }, - template: ` -
-
-
- - - {{ totalHits }} hit{{ totalHits === 1 ? '' : 's' }} · {{ files.length }} file{{ files.length === 1 ? '' : 's' }} -
-
-
-
Content {{ totalHits }}
-
Type at least 2 characters
-
No content matches
- -
-
-
Files {{ files.length }}
-
Start typing…
-
No file names match
-
- -
- - {{ r.path.split('/').slice(0,-1).join('/') }}/ -
- -
-
-
-
-
- ` -}; - -// --------------------------------------------------------------------------- -// ContextMenu — props: menu, onClose -// --------------------------------------------------------------------------- -const ContextMenu = { - props: ['menu', 'onClose'], - setup(props) { - const menuEl = ref(null); - - function outsideClick(e) { - if (menuEl.value && !menuEl.value.contains(e.target)) props.onClose(); - } - function escKey(e) { - if (e.key === 'Escape') props.onClose(); - } - - onMounted(() => { - document.addEventListener('mousedown', outsideClick); - document.addEventListener('keydown', escKey); - }); - onUnmounted(() => { - document.removeEventListener('mousedown', outsideClick); - document.removeEventListener('keydown', escKey); - }); - - const pos = computed(() => { - if (!props.menu) return { x: 0, y: 0 }; - return { - x: Math.min(props.menu.x, window.innerWidth - 270), - y: Math.min(props.menu.y, window.innerHeight - (props.menu.items.length * 34 + 60)), - }; - }); - - return { menuEl, pos }; - }, - template: ` -
-
{{ menu.note }}
- -
- ` -}; - -// --------------------------------------------------------------------------- -// Toasts — props: toasts -// --------------------------------------------------------------------------- -const Toasts = { - props: ['toasts'], - template: ` -
-
- - {{ t.title }} - {{ t.ref }} -
-
- ` -}; - -// --------------------------------------------------------------------------- -// PassPopup — props: x, y, refStr, onConfirm, onCancel -// --------------------------------------------------------------------------- -const PassPopup = { - props: ['x', 'y', 'refStr', 'onConfirm', 'onCancel'], - setup(props) { - const text = ref(''); - const inputEl = ref(null); - const boxEl = ref(null); - - onMounted(() => { inputEl.value && inputEl.value.focus(); }); - - function outsideClick(e) { - if (boxEl.value && !boxEl.value.contains(e.target)) props.onCancel(); - } - // Capture phase — matches original's addEventListener(…, true) - function escKey(e) { - if (e.key === 'Escape') { e.preventDefault(); props.onCancel(); } - } - - onMounted(() => { - document.addEventListener('mousedown', outsideClick); - document.addEventListener('keydown', escKey, true); - }); - onUnmounted(() => { - document.removeEventListener('mousedown', outsideClick); - document.removeEventListener('keydown', escKey, true); - }); - - const left = computed(() => Math.min(props.x, window.innerWidth - 360)); - const top = computed(() => Math.min(props.y + 6, window.innerHeight - 150)); - - const preview = computed(() => (text.value.trim() ? text.value.trim() + ' ' : '') + props.refStr); - - function onKeyDown(e) { - if (e.key === 'Enter') { e.preventDefault(); props.onConfirm(text.value); } - else if (e.key === 'Escape') { e.preventDefault(); props.onCancel(); } - } - - return { text, inputEl, boxEl, left, top, preview, onKeyDown }; - }, - template: ` -
-
Pass on to Agentesc
- -
inserts{{ preview }}
-
insert into agent · esc cancel
-
- ` -}; - -// --------------------------------------------------------------------------- -// Expose -// --------------------------------------------------------------------------- -window.HelderComponents = Object.assign(window.HelderComponents || {}, { - Highlight, SearchModal, ContextMenu, Toasts, PassPopup, -}); -window.fuzzy = fuzzy; diff --git a/design_handoff_helder_workbench/design-vue/src/terminals.js b/design_handoff_helder_workbench/design-vue/src/terminals.js deleted file mode 100644 index 77af6c9..0000000 --- a/design_handoff_helder_workbench/design-vue/src/terminals.js +++ /dev/null @@ -1,261 +0,0 @@ -/* Terminals: a generic shell that boots an (original-styled) AI agent session via `claude`. */ - -const { ref, computed, onMounted, onUnmounted, watch, nextTick } = Vue; - -const wait = (ms) => new Promise((r) => setTimeout(r, ms)); -let _lid = 0; -const lid = () => ++_lid; - -function bootAgent() { - return [ - { kind: "welcome", text: "model: opus · cwd: ~/console · branch: " + PROJECT.branch + " · type /exit to leave" }, - { kind: "t", cls: "dim", html: " I can read, search and edit files in this project. Describe a change to get started." }, - ]; -} - -function agentSeed() { - return { - mode: "agent", - lines: [ - { id: lid(), kind: "t", html: `console % claude` }, - { id: lid(), kind: "welcome", text: "model: opus · cwd: ~/console · branch: " + PROJECT.branch + " · type /exit to leave" }, - { id: lid(), kind: "t", html: `> add currency + name to the user payload and gate the fillable fields` }, - { id: lid(), kind: "t", html: ` Read UserController.php, edited the response array and onlyFillable().` }, - { - id: lid(), kind: "card", ch: "edit · src/Http/Controller/UserController.php", - rows: [ - { cls: "del", t: "- 'plan' => $user->plan," }, - { cls: "add", t: "+ 'plan' => $user->plan->value," }, - { cls: "add", t: "+ 'name' => $user->name," }, - ], - }, - { id: lid(), kind: "t", cls: "ok", html: ` Done — updated UserController.php. Anything else?` }, - ], - }; -} - -function shellSeed() { - return { - mode: "shell", - lines: [ - { id: lid(), kind: "t", html: `console % git status -s` }, - ...PROJECT.changes.map((c) => ({ - id: lid(), kind: "t", - html: `${c.status} ${c.path}`, - })), - ], - }; -} - -const Terminal = { - props: ["kind", "seed"], - setup(props) { - const lines = ref(props.seed.lines); - const mode = ref(props.seed.mode); - const input = ref(""); - const busy = ref(false); - const bodyRef = ref(null); - const inputRef = ref(null); - const hist = ref([]); - const histIx = ref(-1); - - // Auto-scroll on lines/busy change - watch([lines, busy], () => nextTick(() => { - const el = bodyRef.value; - if (el) el.scrollTop = el.scrollHeight; - })); - - const push = (line) => { lines.value = [...lines.value, { id: lid(), ...line }]; }; - const pushMany = (arr) => { lines.value = [...lines.value, ...arr.map((l) => ({ id: lid(), ...l }))]; }; - - async function runAgent(prompt) { - busy.value = true; - push({ kind: "agent-think", html: ` thinking…` }); - await wait(520); - lines.value = lines.value.slice(0, -1); // drop the thinking line - push({ kind: "t", cls: "", html: ` I'll take a look at the relevant files first.` }); - await wait(380); - push({ kind: "t", cls: "dim", html: ` read src/Http/Controller/UserController.php` }); - await wait(300); - push({ kind: "t", cls: "dim", html: ` grep "balanceFor" → 2 matches` }); - await wait(420); - push({ kind: "t", cls: "", html: ` Adding the field and a fillable allow-list. Editing now.` }); - await wait(360); - push({ - kind: "card", ch: `edit · src/Http/Controller/UserController.php`, - rows: [ - { cls: "del", t: "- 'plan' => $user->plan," }, - { cls: "add", t: "+ 'plan' => $user->plan->value," }, - { cls: "add", t: "+ 'currency' => $user->currency," }, - ], - }); - await wait(450); - push({ kind: "t", cls: "ok", html: ` Done. Updated UserController.php. Run tests with composer test.` }); - busy.value = false; - requestAnimationFrame(() => inputRef.value && inputRef.value.focus()); - } - - function shell(cmd) { - const [c, ...rest] = cmd.split(/\s+/); - const arg = rest.join(" "); - switch (c) { - case "": return; - case "claude": - pushMany(bootAgent()); - mode.value = "agent"; - return; - case "clear": lines.value = []; return; - case "help": - pushMany([ - { kind: "t", cls: "dim", html: "commands: claude ls pwd cat <file> git status git diff echo clear" }, - ]); return; - case "pwd": push({ kind: "t", html: "/Users/dev/console" }); return; - case "ls": - push({ kind: "t", html: "config public scripts src templates composer.json package.json README.md" }); - return; - case "echo": push({ kind: "t", html: HL.escapeHtml(arg) }); return; - case "git": - if (rest[0] === "status") { - pushMany([ - { kind: "t", cls: "dim", html: `On branch ${PROJECT.branch}` }, - { kind: "t", cls: "dim", html: "Changes to be committed:" }, - ...PROJECT.changes.map((ch) => ({ - kind: "t", - html: ` ${ch.status === 'A' ? 'new file' : ch.status === 'D' ? 'deleted ' : 'modified'} ${ch.path}`, - })), - ]); - } else if (rest[0] === "diff") { - pushMany([ - { kind: "t", cls: "dim", html: "diff --git a/public/assets/app.js b/public/assets/app.js" }, - { kind: "t", cls: "err", html: "- const res = await fetch('/api/session');" }, - { kind: "t", cls: "ok", html: "+ const res = await fetch('/api/session', { credentials: 'include' });" }, - ]); - } else push({ kind: "t", cls: "dim", html: `git: '${rest[0] || ""}' is not handled in this demo` }); - return; - case "cat": { - const f = PROJECT.files[arg] || Object.entries(PROJECT.files).find(([p]) => p.endsWith(arg))?.[1]; - if (!f) { push({ kind: "t", cls: "err", html: `cat: ${HL.escapeHtml(arg)}: No such file` }); return; } - pushMany(f.replace(/\n$/, "").split("\n").slice(0, 24).map((l) => ({ kind: "t", cls: "dim", html: HL.escapeHtml(l) || " " }))); - return; - } - default: push({ kind: "t", cls: "err", html: `${HL.escapeHtml(c)}: command not found` }); - } - } - - function submit() { - const cmd = input.value.trim(); - if (busy.value) return; - if (cmd) { hist.value.unshift(cmd); } - histIx.value = -1; - if (mode.value === "agent") { - if (cmd === "/exit" || cmd === "exit") { - push({ kind: "t", cls: "dim", html: " Session ended." }); - mode.value = "shell"; input.value = ""; return; - } - if (cmd === "/clear" || cmd === "clear") { lines.value = []; input.value = ""; return; } - push({ kind: "t", html: `> ${HL.escapeHtml(cmd).replace(/\n/g, "
  ") || " "}` }); - input.value = ""; - if (cmd) runAgent(cmd); - return; - } - push({ kind: "t", html: `console % ${HL.escapeHtml(cmd) || " "}` }); - input.value = ""; - shell(cmd); - } - - function onKey(e) { - if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); submit(); return; } - if ((e.key === "ArrowUp" || e.key === "ArrowDown") && !input.value.includes("\n")) { - e.preventDefault(); - if (e.key === "ArrowUp") { - if (hist.value.length) { - histIx.value = Math.min(histIx.value + 1, hist.value.length - 1); - input.value = hist.value[histIx.value]; - } - } else { - if (histIx.value > 0) { histIx.value--; input.value = hist.value[histIx.value]; } - else { histIx.value = -1; input.value = ""; } - } - } - } - - // agentPaste CustomEvent listener — agent kind only - let agentPasteHandler = null; - onMounted(() => { - if (props.kind !== "agent") return; - agentPasteHandler = (e) => { - if (mode.value !== "agent") { pushMany(bootAgent()); mode.value = "agent"; } - // bracketed-paste semantics: append as a new, UNSUBMITTED line; leave caret on a fresh line - const base = input.value.replace(/\n+$/, ""); - input.value = (base ? base + "\n" : "") + e.detail + "\n"; - requestAnimationFrame(() => { - const el = inputRef.value; - if (el) { el.focus(); const v = el.value.length; el.setSelectionRange(v, v); } - }); - }; - window.addEventListener("agentPaste", agentPasteHandler); - }); - - onUnmounted(() => { - if (agentPasteHandler) window.removeEventListener("agentPaste", agentPasteHandler); - }); - - const live = computed(() => mode.value === "agent"); - - const inputRows = computed(() => Math.min(8, Math.max(1, input.value.split("\n").length))); - - const inputPlaceholder = computed(() => - live.value ? "Ask the agent to change something…" : props.kind === "agent" ? "type 'claude' to start a session" : "" - ); - - return { lines, mode, input, busy, bodyRef, inputRef, live, inputRows, inputPlaceholder, onKey, submit }; - }, - template: ` -
-
- - {{ live ? 'claude' : kind === 'agent' ? 'claude' : 'zsh' }} - {{ live ? 'agent session' : '— bash · ~/console' }} -
-
- -
working
-
- - - - - -
-
-
- `, -}; - -// Expose globals for app.js and other files -window.wait = wait; -window.lid = lid; -window.bootAgent = bootAgent; -window.agentSeed = agentSeed; -window.shellSeed = shellSeed; - -window.HelderComponents = Object.assign(window.HelderComponents || {}, { Terminal }); diff --git a/electron.vite.config.ts b/electron.vite.config.ts new file mode 100644 index 0000000..3eb7c46 --- /dev/null +++ b/electron.vite.config.ts @@ -0,0 +1,24 @@ +import { resolve } from 'node:path' +import { defineConfig, externalizeDepsPlugin } from 'electron-vite' +import react from '@vitejs/plugin-react' + +export default defineConfig({ + main: { + plugins: [externalizeDepsPlugin()], + build: { outDir: 'out/main' }, + }, + preload: { + plugins: [externalizeDepsPlugin()], + build: { outDir: 'out/preload' }, + }, + renderer: { + root: 'src/renderer', + build: { + outDir: 'out/renderer', + rollupOptions: { + input: resolve(__dirname, 'src/renderer/index.html'), + }, + }, + plugins: [react()], + }, +}) diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..2a7ef21 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3357 @@ +{ + "name": "helder", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "helder", + "version": "0.1.0", + "hasInstallScript": true, + "dependencies": { + "@fontsource/jetbrains-mono": "^5.0.20", + "@vscode/ripgrep": "^1.18.0", + "@xterm/addon-fit": "^0.11.0", + "@xterm/xterm": "^6.0.0", + "chokidar": "^5.0.0", + "node-pty": "^1.1.0", + "prismjs": "^1.29.0", + "simple-git": "^3.36.0" + }, + "devDependencies": { + "@electron/rebuild": "^4.0.4", + "@types/prismjs": "^1.26.4", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "electron": "^31.3.0", + "electron-vite": "^2.3.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "typescript": "^5.5.4", + "vite": "^5.3.5" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.29.7.tgz", + "integrity": "sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@electron/get": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz", + "integrity": "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^2.2.0", + "fs-extra": "^8.1.0", + "got": "^11.8.5", + "progress": "^2.0.3", + "semver": "^6.2.0", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "global-agent": "^3.0.0" + } + }, + "node_modules/@electron/rebuild": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-4.0.4.tgz", + "integrity": "sha512-Rzc39XPdk/+/wBG8MfwAHohXflep0ITUfulb6Rgz3R0NeSB1noE+E9/M/cb8ftCAiyDD9PPhLuuWgE1GaInbKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@malept/cross-spawn-promise": "^2.0.0", + "debug": "^4.1.1", + "node-abi": "^4.2.0", + "node-api-version": "^0.2.1", + "node-gyp": "^12.2.0", + "read-binary-file-arch": "^1.0.6" + }, + "bin": { + "electron-rebuild": "lib/cli.js" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@fontsource/jetbrains-mono": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/@fontsource/jetbrains-mono/-/jetbrains-mono-5.2.8.tgz", + "integrity": "sha512-6w8/SG4kqvIMu7xd7wt6x3idn1Qux3p9N62s6G3rfldOUYHpWcc2FKrqf+Vo44jRvqWj2oAtTHrZXEP23oSKwQ==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@kwsites/file-exists": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", + "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", + "license": "MIT", + "dependencies": { + "debug": "^4.1.1" + } + }, + "node_modules/@kwsites/promise-deferred": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", + "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==", + "license": "MIT" + }, + "node_modules/@malept/cross-spawn-promise": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz", + "integrity": "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" + } + ], + "license": "Apache-2.0", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.0.tgz", + "integrity": "sha512-IPIQ55ythEHkfEd9jMEi32OQ7SxURsGA43JI22lj01OLZNt2NUbJX8YUHxkVWyQ6daHPNn0truF5nSj3DQp6YQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.0.tgz", + "integrity": "sha512-M6s9cr10MibETyo8JsOkq+Lo1+lU6hcvb1MApnUql5qte/5hMEgzlN8/ReIKNfRV8rrqX50W1BX9zoUhC192RA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.0.tgz", + "integrity": "sha512-BqCoMoIbn0keKys+dEAdBa70EtOwV1bEsQCUgU9FdiZmmMge/Zk7LlkYGqbrdHR+Frnt0E1FOanly+rlwvvQzw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.0.tgz", + "integrity": "sha512-SIMzST3VFNXDAbeIWDWiFCNM5qncUBDWaEV7NfE7oZbDt2mgfW4MvbKdbYiGOLoM32gbTv608UMd0XktEYSD7w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.0.tgz", + "integrity": "sha512-ezjfSQMP7ArdUsbBwbQIfwAlhE84I2iVnzQNCFSveqV42q+BmKlzVpf7mxv5EchLcoWU4y6/heFzVg1F+hodUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.0.tgz", + "integrity": "sha512-9+qTWGW9AZRhnUgwtTwzNwcPlL87ngkeN0LA+q1bADvmY9aNvWaF2TFW8BZgnQPYxpDI7+rMVLivcd4V737TAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.0.tgz", + "integrity": "sha512-T1dMEQhXA/jkJ/jyMIw9IovK8bSUq7A8kLIlvZTb/6YIVsp2zLavr4F3oyllHWo7eIVJRyE5n3tUjQJEbE1IuQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.0.tgz", + "integrity": "sha512-2as0LgT7qQpyceQq6VUJYnumUMUrgGQCWIiDIN9DE0/tglsk6o66uCB4f3djRawAltvfCNLyZZrsqbPA6inCsA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.0.tgz", + "integrity": "sha512-bVURMg+6eNN9C/yc0aVjooZcwTTtYF4YW3xta5pP0//r3o1V8gXEHXWCndj47w/HhwsFroZrFhR+6uQP5T0n0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.0.tgz", + "integrity": "sha512-Ful8pM/2yYI83PViWdFdpZhdI8HJ5qsXANe5atypbHDf+KIBBDsZsbyy8hbXnULVvW9NsTh5DHwbcBftyLTfiw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.0.tgz", + "integrity": "sha512-9Gp/DgrkzfUBmNPVTyPTvay+4xEP7M/clXpj3efXBcm6uTIVIgDg4rqUpqKXvLEuFRVuEpSAOkhgNeecvaZ4Cg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.0.tgz", + "integrity": "sha512-m9tsJz54LUXkSYM8+8PG81B9IKK5r+2T0clMq4QrS16xFosufU7firBDAZEsDheDs7wTlP7h3++S7lMsU955HA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.0.tgz", + "integrity": "sha512-3UvJ5PNVU16aJf6M3tFI24pWzAl2/ynfbyRN3ICyQajK1lSkrnVYNnLz3v04J32qKa0FczJc22zeToc0lr2A3w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.0.tgz", + "integrity": "sha512-vRWUAbYLGHBZS6Q8Msb2sfnf1fvJf+47t8l/TwOerM2qArzy+IeNMTHrYLHXh95h8MoatPHI5hhSZNs+mGXKPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.0.tgz", + "integrity": "sha512-c00T5SYENHAt86cfW47URaP3Us5vLC/4QO7GYud1G5VNRffCwwCuBspwqYrriuJB+5m0WFzClCn9wed0FBjKvg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.0.tgz", + "integrity": "sha512-krrCDilhXOwFkSkO3Wm9I/f9H0L92XHHwy2fwxjukxIbh0dem8gZqOW5Y8BsHrpJv5qwlRBV+Wl4ZFyRWhUpwg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.0.tgz", + "integrity": "sha512-7pfYFSTc4/rUC/FtAI0Qp6QthDBCIi6/AuP1xYqFk5vanI6KnL5dWKP60OM/05LOsbwTmIcvr6eXC4CJuJ75IA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.0.tgz", + "integrity": "sha512-7SDIalKeIpG0Ifogbbdn58HmSotYMlf23K3dCJEmiVd9Fg36Vmni82iPQec27N3wY4Bvbxftkxz6vSx9OcouTg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.0.tgz", + "integrity": "sha512-eRZevouTH2i1HeAVLqJuLnt256krQkGY0TN6WsTmsIhuzbh457HuWDMakKwmi0Cjadux983CoSr8Lim2QhUIFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.0.tgz", + "integrity": "sha512-3oVS7FLGa4U1qcvao9ylGxrjXZyUQqR8UwxEcnUEyPX53O/C/mKDZegNXTdHCP+h3e6ta/f1EN38Yif1mmZHYg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.0.tgz", + "integrity": "sha512-yTB9TgfWj5wHe5QgktAgXTLLot1gvEjl1NiPPAUiCs4oPrIWFl5V4nC3GrkNdj9LaAU4s94nVrGbGOCqUpyWsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.0.tgz", + "integrity": "sha512-5LOhoaesY3doG1c+ac/2JtgREpKoJr5bUHH8tKY0V8di7+uSV6BwLs2PlR0/yzefGOkR+wE7ZolZphHCsyG5Rw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.0.tgz", + "integrity": "sha512-yYkWHhmbhRTWTnWos5HC4GcPQfjlzzCNbM9e/+GXrLuaBXYA3qSDR9f0Vgufd5S8yX81U8jPKp7ZnAjZFMtRnw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.0.tgz", + "integrity": "sha512-SoTb6lPg25xZlA2ibwQ++ahCCnH+FP0qmEuafMJ4gznZKOlXioKEAeJLgCrqjM98ACziXM9V1amFjICVL4IFoA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.0.tgz", + "integrity": "sha512-5L+T1fMX4RIEBoZzT0+sQ0PhTS36NULFmMXtl1TZo44TMAROIMHbZufSOjVWt/Y622BtxgxtaNOokbTDvfsrZA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@simple-git/args-pathspec": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@simple-git/args-pathspec/-/args-pathspec-1.0.3.tgz", + "integrity": "sha512-ngJMaHlsWDTfjyq9F3VIQ8b7NXbBLq5j9i5bJ6XLYtD6qlDXT7fdKY2KscWWUF8t18xx052Y/PUO1K1TRc9yKA==", + "license": "MIT" + }, + "node_modules/@simple-git/argv-parser": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@simple-git/argv-parser/-/argv-parser-1.1.1.tgz", + "integrity": "sha512-Q9lBcfQ+VQCpQqGJFHe5yooOS5hGdLFFbJ5R+R5aDsnkPCahtn1hSkMcORX65J2Z5lxSkD0lQorMsncuBQxYUw==", + "license": "MIT", + "dependencies": { + "@simple-git/args-pathspec": "^1.0.3" + } + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dev": true, + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "20.19.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", + "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/prismjs": { + "version": "1.26.6", + "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.6.tgz", + "integrity": "sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.31", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.31.tgz", + "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/@vscode/ripgrep": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@vscode/ripgrep/-/ripgrep-1.18.0.tgz", + "integrity": "sha512-ns5lWe44tSfbTMbVUsyB+I1819PVSw4AdpgK0RNkzfWfwy6+3IUNSxwSrfTno1/oWaS/hERNz+XLWVyga2aJBQ==", + "license": "MIT", + "optionalDependencies": { + "@vscode/ripgrep-darwin-arm64": "1.18.0", + "@vscode/ripgrep-darwin-x64": "1.18.0", + "@vscode/ripgrep-linux-arm": "1.18.0", + "@vscode/ripgrep-linux-arm64": "1.18.0", + "@vscode/ripgrep-linux-ia32": "1.18.0", + "@vscode/ripgrep-linux-ppc64": "1.18.0", + "@vscode/ripgrep-linux-riscv64": "1.18.0", + "@vscode/ripgrep-linux-s390x": "1.18.0", + "@vscode/ripgrep-linux-x64": "1.18.0", + "@vscode/ripgrep-win32-arm64": "1.18.0", + "@vscode/ripgrep-win32-ia32": "1.18.0", + "@vscode/ripgrep-win32-x64": "1.18.0" + } + }, + "node_modules/@vscode/ripgrep-darwin-arm64": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@vscode/ripgrep-darwin-arm64/-/ripgrep-darwin-arm64-1.18.0.tgz", + "integrity": "sha512-r3ktHSvbFycQNF6sl7sNDPocpsI7J+mEzh1IaZFkY0spm3k2Z9t8hPAeOK7+p0l6p6/swkQC14XWX01low+94Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@vscode/ripgrep-darwin-x64": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@vscode/ripgrep-darwin-x64/-/ripgrep-darwin-x64-1.18.0.tgz", + "integrity": "sha512-25b4gWbL138dGuQU244ebCKKc0q05ULBMoFSz9oAEUHNeqK/lOJViDS7DRvbDazzAzSEdan391Znks/R5mkaTQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@vscode/ripgrep-linux-arm": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@vscode/ripgrep-linux-arm/-/ripgrep-linux-arm-1.18.0.tgz", + "integrity": "sha512-GDAvufNDHu8zqLEmXstalQF0Wh6wQvdsBi/Vg3Yi3CK4a8XoFXqqXVEHEZ9xQz3t0NfoSEc9JbvK9DDS6FxyxQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@vscode/ripgrep-linux-arm64": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@vscode/ripgrep-linux-arm64/-/ripgrep-linux-arm64-1.18.0.tgz", + "integrity": "sha512-lQ/5zTG++U0E3IhVgS4EPTTn/U4okncaRMM5GOFfOYZywS4nuD31GhkHbNYlDk5CuDC68+hYJ0/eQeyCKJDA+g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@vscode/ripgrep-linux-ia32": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@vscode/ripgrep-linux-ia32/-/ripgrep-linux-ia32-1.18.0.tgz", + "integrity": "sha512-YWLkSUtFd4Jh5EepIhA9RJSfv3uMAVMo+2rBIGHPBnvgLrZciIs2cDKei1/p6Wc/aCzUoHyMAg2R6tw4ZCBKGg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@vscode/ripgrep-linux-ppc64": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@vscode/ripgrep-linux-ppc64/-/ripgrep-linux-ppc64-1.18.0.tgz", + "integrity": "sha512-quXVY8fwQ8O/lvU1yrSqSl3jlUzysRSb+AfUfCL/tRtphxsKlFvPAejryZ6vg4Bgvn8XL74xb4qMCDmWgYrT5w==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@vscode/ripgrep-linux-riscv64": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@vscode/ripgrep-linux-riscv64/-/ripgrep-linux-riscv64-1.18.0.tgz", + "integrity": "sha512-f5kBQBrWfQt8Q7OhSORuNDei5dkYagBj3y4jImSUXGMy8B/Ke7SltSRcUtjPv166FAFfHCAmWuZp3+cWnX2/Vw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@vscode/ripgrep-linux-s390x": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@vscode/ripgrep-linux-s390x/-/ripgrep-linux-s390x-1.18.0.tgz", + "integrity": "sha512-rTOcJFGGcl2c07RUOWUo4U1ndnemKhY6A9hnMB18uk7jSgJc0d/QLBGWMWpumdtoJtpizn/wIv5mXIisJukusQ==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@vscode/ripgrep-linux-x64": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@vscode/ripgrep-linux-x64/-/ripgrep-linux-x64-1.18.0.tgz", + "integrity": "sha512-mQ3bVrUpnD2vs7QT0vX90Lt0cnUq467uFtEktIdsJJmW296RoSULRGqWgzG1AKxyBpNDD6l4ZO4qKf6SgyC23Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@vscode/ripgrep-win32-arm64": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@vscode/ripgrep-win32-arm64/-/ripgrep-win32-arm64-1.18.0.tgz", + "integrity": "sha512-vfTIjq1OHnzUjxZcHVQAMbnggp8dpGf+0QKFOZHwWPqFwXxQC8eCWM+5NUdoJ6yrElCeMzoUTXoK/LdZaniB+Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@vscode/ripgrep-win32-ia32": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@vscode/ripgrep-win32-ia32/-/ripgrep-win32-ia32-1.18.0.tgz", + "integrity": "sha512-//rfAE+BOw5AC2EMmepmiE36jUuevtQYNQqqlw1s3m9FlRxjxEut97RkRPHAu9BG4mSojatZx+kXZXNdyI9caQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@vscode/ripgrep-win32-x64": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@vscode/ripgrep-win32-x64/-/ripgrep-win32-x64-1.18.0.tgz", + "integrity": "sha512-KNPvtElldqILHdnAetujPaowkNbpqJy3ssIGGN6F6Kve9Qi+nNLI2DN01O83JjCEVQbCzl8Ov3QZ9Eov3BR8Dg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@xterm/addon-fit": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.11.0.tgz", + "integrity": "sha512-jYcgT6xtVYhnhgxh3QgYDnnNMYTcf8ElbxxFzX0IZo+vabQqSPAjC3c1wJrKB5E19VwQei89QCiZZP86DCPF7g==", + "license": "MIT" + }, + "node_modules/@xterm/xterm": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.0.0.tgz", + "integrity": "sha512-TQwDdQGtwwDt+2cgKDLn0IRaSxYu1tSUjgKarSDkUM0ZNiSRXFpjxEsvc/Zgc5kq5omJ+V0a8/kIM2WD3sMOYg==", + "license": "MIT", + "workspaces": [ + "addons/*" + ] + }, + "node_modules/abbrev": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz", + "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.37", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.37.tgz", + "integrity": "sha512-girxaJ7WZssDOFhzCGZTDKoTa1gk6A1TbflaYTpykLJ4UU9Fz9kx1aREM8JCuoVHbL8X8T/mJg7w2oYSq72Oig==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001799", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", + "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/electron": { + "version": "31.7.7", + "resolved": "https://registry.npmjs.org/electron/-/electron-31.7.7.tgz", + "integrity": "sha512-HZtZg8EHsDGnswFt0QeV8If8B+et63uD6RJ7I4/xhcXqmTIbI08GoubX/wm+HdY0DwcuPe1/xsgqpmYvjdjRoA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@electron/get": "^2.0.0", + "@types/node": "^20.9.0", + "extract-zip": "^2.0.1" + }, + "bin": { + "electron": "cli.js" + }, + "engines": { + "node": ">= 12.20.55" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.372", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.372.tgz", + "integrity": "sha512-M3yhbAlilnwqC8D21t28UCDGHyitShTmmLRU/H+b74P6Ski16Nb9HONYEaVpMj/pwC7BEo5B95FpjODLCWbtfA==", + "dev": true, + "license": "ISC" + }, + "node_modules/electron-vite": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/electron-vite/-/electron-vite-2.3.0.tgz", + "integrity": "sha512-lsN2FymgJlp4k6MrcsphGqZQ9fKRdJKasoaiwIrAewN1tapYI/KINLdfEL7n10LuF0pPSNf/IqjzZbB5VINctg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.7", + "@babel/plugin-transform-arrow-functions": "^7.24.7", + "cac": "^6.7.14", + "esbuild": "^0.21.5", + "magic-string": "^0.30.10", + "picocolors": "^1.0.1" + }, + "bin": { + "electron-vite": "bin/electron-vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@swc/core": "^1.0.0", + "vite": "^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + } + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/global-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", + "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, + "engines": { + "node": ">=10.0" + } + }, + "node_modules/global-agent/node_modules/semver": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", + "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", + "dev": true, + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/matcher": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-abi": { + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.31.0.tgz", + "integrity": "sha512-Erq5w/t3syw3s4sDsUaX4QttIdBPsGKTT1DTRsCkTonGggczhlDKm/wDX3o+HPJpQ41EjXCbcmXf0tgr5YZJXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.6.3" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/node-abi/node_modules/semver": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", + "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT" + }, + "node_modules/node-api-version": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-api-version/-/node-api-version-0.2.1.tgz", + "integrity": "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + } + }, + "node_modules/node-api-version/node_modules/semver": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", + "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-gyp": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.4.0.tgz", + "integrity": "sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "tar": "^7.5.4", + "tinyglobby": "^0.2.12", + "undici": "^6.25.0", + "which": "^6.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/node-gyp/node_modules/isexe": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/node-gyp/node_modules/semver": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", + "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-gyp/node_modules/which": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^4.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/node-pty": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.1.0.tgz", + "integrity": "sha512-20JqtutY6JPXTUnL0ij1uad7Qe1baT46lyolh2sSENDd4sTzKZ4nmAFkeAARDKwmlLjPx6XKRlwRUxwjOy+lUg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^7.1.0" + } + }, + "node_modules/node-releases": { + "version": "2.0.47", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz", + "integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/nopt": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", + "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^4.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-binary-file-arch": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz", + "integrity": "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "bin": { + "read-binary-file-arch": "cli.js" + } + }, + "node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/roarr": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "detect-node": "^2.0.4", + "globalthis": "^1.0.1", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0", + "sprintf-js": "^1.1.2" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/rollup": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.0.tgz", + "integrity": "sha512-nc72Wgq62I7rtDV4izT5/aaS0zxy3kttkinf9586ApknY3jZO9NYsmtc24fUckA0X7Q2v+ML4a15pdUlV5V/jA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.0", + "@rollup/rollup-android-arm64": "4.62.0", + "@rollup/rollup-darwin-arm64": "4.62.0", + "@rollup/rollup-darwin-x64": "4.62.0", + "@rollup/rollup-freebsd-arm64": "4.62.0", + "@rollup/rollup-freebsd-x64": "4.62.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.0", + "@rollup/rollup-linux-arm-musleabihf": "4.62.0", + "@rollup/rollup-linux-arm64-gnu": "4.62.0", + "@rollup/rollup-linux-arm64-musl": "4.62.0", + "@rollup/rollup-linux-loong64-gnu": "4.62.0", + "@rollup/rollup-linux-loong64-musl": "4.62.0", + "@rollup/rollup-linux-ppc64-gnu": "4.62.0", + "@rollup/rollup-linux-ppc64-musl": "4.62.0", + "@rollup/rollup-linux-riscv64-gnu": "4.62.0", + "@rollup/rollup-linux-riscv64-musl": "4.62.0", + "@rollup/rollup-linux-s390x-gnu": "4.62.0", + "@rollup/rollup-linux-x64-gnu": "4.62.0", + "@rollup/rollup-linux-x64-musl": "4.62.0", + "@rollup/rollup-openbsd-x64": "4.62.0", + "@rollup/rollup-openharmony-arm64": "4.62.0", + "@rollup/rollup-win32-arm64-msvc": "4.62.0", + "@rollup/rollup-win32-ia32-msvc": "4.62.0", + "@rollup/rollup-win32-x64-gnu": "4.62.0", + "@rollup/rollup-win32-x64-msvc": "4.62.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/simple-git": { + "version": "3.36.0", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.36.0.tgz", + "integrity": "sha512-cGQjLjK8bxJw4QuYT7gxHw3/IouVESbhahSsHrX97MzCL1gu2u7oy38W6L2ZIGECEfIBG4BabsWDPjBxJENv9Q==", + "license": "MIT", + "dependencies": { + "@kwsites/file-exists": "^1.1.1", + "@kwsites/promise-deferred": "^1.1.1", + "@simple-git/args-pathspec": "^1.0.3", + "@simple-git/argv-parser": "^1.1.0", + "debug": "^4.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/steveukx/git-js?sponsor=1" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/sumchecker": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", + "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.1.0" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/tar": { + "version": "7.5.16", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.16.tgz", + "integrity": "sha512-56adEpPMouktRlBLXiaYFFzZ/3+JXa8P9n7WbR+ibIjtviN55mEaOkiysCnPnWm+7kkui1Dn8J9l+g6zV8731w==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici": { + "version": "6.27.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.27.0.tgz", + "integrity": "sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..eb49a27 --- /dev/null +++ b/package.json @@ -0,0 +1,41 @@ +{ + "name": "helder", + "version": "0.1.0", + "description": "Helder — dark-only Electron code workbench for reviewing AI-written code", + "private": true, + "type": "module", + "main": "./out/main/index.js", + "author": "Jonathan van Rij", + "scripts": { + "dev": "electron-vite dev", + "build": "electron-vite build", + "preview": "electron-vite preview", + "start": "electron-vite preview", + "typecheck": "tsc --noEmit -p tsconfig.web.json && tsc --noEmit -p tsconfig.node.json", + "rebuild": "electron-rebuild -f -w node-pty", + "postinstall": "electron-rebuild -f -w node-pty" + }, + "dependencies": { + "@fontsource/jetbrains-mono": "^5.0.20", + "@vscode/ripgrep": "^1.18.0", + "@xterm/addon-fit": "^0.11.0", + "@xterm/xterm": "^6.0.0", + "chokidar": "^5.0.0", + "node-pty": "^1.1.0", + "prismjs": "^1.29.0", + "simple-git": "^3.36.0" + }, + "devDependencies": { + "@electron/rebuild": "^4.0.4", + "@types/prismjs": "^1.26.4", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "electron": "^31.3.0", + "electron-vite": "^2.3.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "typescript": "^5.5.4", + "vite": "^5.3.5" + } +} diff --git a/src/main/config.ts b/src/main/config.ts new file mode 100644 index 0000000..02b6f49 --- /dev/null +++ b/src/main/config.ts @@ -0,0 +1,93 @@ +import { mkdir, readFile, writeFile } from 'node:fs/promises' +import { join } from 'node:path' + +/** + * Project-scoped settings, living in `.helder/` in the opened project's root. + * - config.default.json full built-in defaults, REGENERATED on every launch + * (live documentation; the app never reads user edits here) + * - config.json sparse — only user-overridden values + * - theme.css custom CSS over the built-in dark theme; the CODE FONT + * and FONT SIZE live here (as CSS vars), not in the JSON + * Effective value = config.json over config.default.json, merged key by key. + */ +export interface HelderConfig { + ai: { command: string; autoLaunch: boolean } + editor: { autoSave: boolean; tabSize: number } + git: { confirmDiscard: boolean } + terminal: { shell: string | null } +} + +export const DEFAULTS: HelderConfig = { + ai: { command: 'claude', autoLaunch: true }, + editor: { autoSave: false, tabSize: 4 }, + git: { confirmDiscard: true }, + terminal: { shell: null }, +} + +const THEME_TEMPLATE = `/* Helder theme — custom CSS applied OVER the built-in dark theme. + * This file is created once and never overwritten; edit it freely. + * The code font and font size live here (not in config.json). Uncomment and + * tweak any variable below; you can also override any --token from the built-in + * theme (see the design tokens in the app's styles). */ +:root { + /* Code surfaces (editor + terminals) */ + /* --code-font: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; */ + /* --code-size: 13px; */ /* editor font size */ + /* --term-size: 12.5px; */ /* terminal font size */ + + /* Example accent override: */ + /* --accent: #4d8dff; */ +} +` + +let current: HelderConfig = DEFAULTS +let themeCss = '' + +function isPlainObject(v: unknown): v is Record { + return !!v && typeof v === 'object' && !Array.isArray(v) +} + +function deepMerge(base: T, over: unknown): T { + if (!isPlainObject(base) || !isPlainObject(over)) return base + const out: Record = { ...base } + for (const key of Object.keys(over)) { + const b = (base as Record)[key] + const o = over[key] + if (isPlainObject(b) && isPlainObject(o)) out[key] = deepMerge(b, o) + else if (o !== undefined) out[key] = o + } + return out as T +} + +/** (Re)resolve config + theme for a project root, regenerating the defaults file. */ +export async function resolveConfig(root: string): Promise { + const dir = join(root, '.helder') + try { + await mkdir(dir, { recursive: true }) + // Always regenerate the defaults file — it documents every setting. + await writeFile(join(dir, 'config.default.json'), JSON.stringify(DEFAULTS, null, 2) + '\n') + + let override: unknown = {} + try { override = JSON.parse(await readFile(join(dir, 'config.json'), 'utf8')) } catch { /* none / invalid */ } + current = deepMerge(DEFAULTS, override) + + try { + themeCss = await readFile(join(dir, 'theme.css'), 'utf8') + } catch { + themeCss = THEME_TEMPLATE + await writeFile(join(dir, 'theme.css'), THEME_TEMPLATE) + } + } catch { + // Read-only / inaccessible root: fall back to built-in defaults. + current = DEFAULTS + themeCss = '' + } +} + +export function getConfig(): HelderConfig { + return current +} + +export function getThemeCss(): string { + return themeCss +} diff --git a/src/main/fs-service.ts b/src/main/fs-service.ts new file mode 100644 index 0000000..0e5d666 --- /dev/null +++ b/src/main/fs-service.ts @@ -0,0 +1,117 @@ +import { readdir, readFile, stat, writeFile } from 'node:fs/promises' +import { join, relative, sep } from 'node:path' + +export interface FileNode { + name: string + type: 'dir' | 'file' + path: string + open?: boolean + children?: FileNode[] +} + +/** Directories never walked — noise or huge, and not part of "the project". */ +const IGNORE_DIRS = new Set([ + 'node_modules', '.git', 'out', 'dist', 'build', '.next', '.nuxt', + '.cache', 'coverage', 'vendor', '.idea', '.vscode', '.helder', '.turbo', +]) + +const MAX_FILE_BYTES = 300_000 +const MAX_INDEXED_FILES = 6000 + +function ignored(name: string): boolean { + return IGNORE_DIRS.has(name) || name === '.DS_Store' +} + +/** Recursive project tree, dirs first then files, alphabetical. */ +export async function readTree(root: string): Promise { + const name = root.split(sep).filter(Boolean).pop() || root + return { name, type: 'dir', path: '', open: true, children: await readDir(root, root, 0) } +} + +async function readDir(abs: string, root: string, depth: number): Promise { + let entries: import('node:fs').Dirent[] + try { + entries = await readdir(abs, { withFileTypes: true }) + } catch { + return [] + } + const dirs: FileNode[] = [] + const files: FileNode[] = [] + for (const e of entries) { + if (ignored(e.name)) continue + const childAbs = join(abs, e.name) + const rel = relative(root, childAbs).split(sep).join('/') + if (e.isDirectory()) { + dirs.push({ + name: e.name, type: 'dir', path: rel, open: depth < 1, + children: depth < 12 ? await readDir(childAbs, root, depth + 1) : [], + }) + } else if (e.isFile()) { + files.push({ name: e.name, type: 'file', path: rel }) + } + } + dirs.sort((a, b) => a.name.localeCompare(b.name)) + files.sort((a, b) => a.name.localeCompare(b.name)) + return [...dirs, ...files] +} + +function looksBinary(buf: Buffer): boolean { + const n = Math.min(buf.length, 8000) + for (let i = 0; i < n; i++) if (buf[i] === 0) return true + return false +} + +/** Read a single text file (relative path) → string. */ +export async function readProjectFile(root: string, rel: string): Promise { + const buf = await readFile(join(root, rel)) + if (looksBinary(buf)) return '' + return buf.toString('utf8') +} + +/** Write a text file (relative path). Used by the editable buffer's save. */ +export async function writeProjectFile(root: string, rel: string, content: string): Promise { + await writeFile(join(root, rel), content, 'utf8') +} + +/** + * Build an in-memory content index of all (small, text) files — powers content + * search and plain-file viewing without touching disk per keystroke. Capped to + * keep large repos sane. PHASE: swap content search to ripgrep when scaling up. + */ +export async function readAll(root: string): Promise> { + const out: Record = {} + let count = 0 + + async function walk(abs: string): Promise { + if (count >= MAX_INDEXED_FILES) return + let entries: import('node:fs').Dirent[] + try { + entries = await readdir(abs, { withFileTypes: true }) + } catch { + return + } + for (const e of entries) { + if (count >= MAX_INDEXED_FILES) return + if (ignored(e.name)) continue + const childAbs = join(abs, e.name) + if (e.isDirectory()) { + await walk(childAbs) + } else if (e.isFile()) { + try { + const s = await stat(childAbs) + if (s.size > MAX_FILE_BYTES) continue + const buf = await readFile(childAbs) + if (looksBinary(buf)) continue + const rel = relative(root, childAbs).split(sep).join('/') + out[rel] = buf.toString('utf8') + count++ + } catch { + /* skip unreadable */ + } + } + } + } + + await walk(root) + return out +} diff --git a/src/main/git-service.ts b/src/main/git-service.ts new file mode 100644 index 0000000..c1572a1 --- /dev/null +++ b/src/main/git-service.ts @@ -0,0 +1,108 @@ +import { readFile } from 'node:fs/promises' +import { join } from 'node:path' +import { simpleGit, type SimpleGit } from 'simple-git' + +export type GitStatusLetter = 'A' | 'M' | 'D' | 'R' | 'U' + +export interface GitChange { + path: string + status: GitStatusLetter + staged: boolean + original: string + updated: string +} + +export interface GitLoad { + branch: string + changes: GitChange[] +} + +function git(root: string): SimpleGit { + return simpleGit({ baseDir: root, maxConcurrentProcesses: 4 }) +} + +/** Map a porcelain code pair to our display letter + staged flag. */ +function classify(index: string, working: string): { letter: GitStatusLetter; staged: boolean } { + const staged = index !== ' ' && index !== '?' + const code = staged ? index : working + let letter: GitStatusLetter + switch (code) { + case 'A': case 'C': case '?': letter = 'A'; break + case 'D': letter = 'D'; break + case 'R': letter = 'R'; break + case 'U': letter = 'M'; break + case 'M': default: letter = 'M'; break + } + return { letter, staged } +} + +async function headText(g: SimpleGit, path: string): Promise { + try { + return await g.show([`HEAD:${path}`]) + } catch { + return '' + } +} + +async function diskText(root: string, path: string): Promise { + try { + const buf = await readFile(join(root, path)) + // skip obvious binaries + for (let i = 0; i < Math.min(buf.length, 8000); i++) if (buf[i] === 0) return '' + return buf.toString('utf8') + } catch { + return '' + } +} + +export async function isRepo(root: string): Promise { + try { + return await git(root).checkIsRepo() + } catch { + return false + } +} + +export async function load(root: string): Promise { + const g = git(root) + if (!(await isRepo(root))) return null + + const status = await g.status() + const branch = status.current || 'HEAD' + + const changes: GitChange[] = [] + for (const f of status.files) { + // simple-git uses path "from -> to" for renames; take the destination. + const path = f.path.includes(' -> ') ? f.path.split(' -> ').pop()! : f.path + const { letter, staged } = classify(f.index, f.working_dir) + const isNew = f.index === '?' || f.index === 'A' + const isDeleted = letter === 'D' + const original = isNew ? '' : await headText(g, path) + const updated = isDeleted ? '' : await diskText(root, path) + changes.push({ path, status: letter, staged, original, updated }) + } + + return { branch, changes } +} + +export async function stage(root: string, paths: string[]): Promise { + // `git add` stages modifications, additions AND deletions of the given paths. + await git(root).add(paths) +} + +export async function unstage(root: string, paths: string[]): Promise { + try { + await git(root).reset(['--', ...paths]) + } catch { + // empty repo (no HEAD yet): fall back to removing from the index. + await git(root).raw(['rm', '--cached', '-r', '--', ...paths]) + } +} + +export async function commit(root: string, message: string): Promise { + await git(root).commit(message) +} + +export async function discard(root: string, paths: string[]): Promise { + await git(root).checkout(['--', ...paths]) +} diff --git a/src/main/index.ts b/src/main/index.ts new file mode 100644 index 0000000..a70015b --- /dev/null +++ b/src/main/index.ts @@ -0,0 +1,141 @@ +import { join, sep } from 'node:path' +import { app, shell, BrowserWindow, ipcMain } from 'electron' +import { watch, type FSWatcher } from 'chokidar' +import { getName, getRoot, openDialog } from './project' +import { readAll, readProjectFile, readTree, writeProjectFile } from './fs-service' +import { commit, discard, load, stage, unstage } from './git-service' +import { createPty, killAllPtys, killPty, ptyAvailable, resizePty, writePty } from './pty-service' +import { getConfig, getThemeCss, resolveConfig } from './config' +import { listFiles, searchContent } from './search-service' + +const isDev = !!process.env['ELECTRON_RENDERER_URL'] +const isMac = process.platform === 'darwin' + +const WATCH_IGNORE = new Set([ + 'node_modules', '.git', 'out', 'dist', 'build', '.next', '.nuxt', + '.cache', 'coverage', 'vendor', '.idea', '.vscode', '.helder', '.turbo', +]) + +let watcher: FSWatcher | null = null +let configWatcher: FSWatcher | null = null +let watchTimer: ReturnType | null = null + +function broadcast(channel: string): void { + for (const w of BrowserWindow.getAllWindows()) w.webContents.send(channel) +} + +function startConfigWatcher(): void { + if (configWatcher) { configWatcher.close(); configWatcher = null } + const root = getRoot() + if (!root) return + const dir = join(root, '.helder') + configWatcher = watch([join(dir, 'config.json'), join(dir, 'theme.css')], { ignoreInitial: true }) + const reload = (): void => { resolveConfig(root).then(() => broadcast('config:changed')).catch(() => {}) } + configWatcher.on('add', reload).on('change', reload).on('unlink', reload) +} + +function startWatcher(): void { + if (watcher) { watcher.close(); watcher = null } + const root = getRoot() + if (!root) return + watcher = watch(root, { + ignoreInitial: true, + ignored: (p: string) => p.split(sep).some((seg) => WATCH_IGNORE.has(seg)), + }) + const ping = (): void => { + if (watchTimer) clearTimeout(watchTimer) + watchTimer = setTimeout(() => broadcast('project:changed'), 250) + } + watcher.on('add', ping).on('change', ping).on('unlink', ping).on('addDir', ping).on('unlinkDir', ping) +} + +function registerIpc(): void { + ipcMain.handle('project:current', () => ({ root: getRoot(), name: getName() })) + ipcMain.handle('project:open', async (e) => { + const win = BrowserWindow.fromWebContents(e.sender) + const next = await openDialog(win) + if (next) { + await resolveConfig(getRoot()) + startWatcher() + startConfigWatcher() + } + return { root: getRoot(), name: getName() } + }) + + ipcMain.handle('fs:tree', () => readTree(getRoot())) + ipcMain.handle('fs:files', () => readAll(getRoot())) + ipcMain.handle('fs:read', (_e, rel: string) => readProjectFile(getRoot(), rel)) + ipcMain.handle('fs:write', (_e, rel: string, content: string) => writeProjectFile(getRoot(), rel, content)) + + ipcMain.handle('git:load', () => load(getRoot())) + ipcMain.handle('git:stage', (_e, paths: string[]) => stage(getRoot(), paths)) + ipcMain.handle('git:unstage', (_e, paths: string[]) => unstage(getRoot(), paths)) + ipcMain.handle('git:commit', (_e, message: string) => commit(getRoot(), message)) + ipcMain.handle('git:discard', (_e, paths: string[]) => discard(getRoot(), paths)) + + ipcMain.handle('pty:available', () => ptyAvailable()) + ipcMain.handle('pty:create', (e, kind: 'agent' | 'shell', cols: number, rows: number) => createPty(e.sender, kind, cols, rows)) + ipcMain.on('pty:write', (_e, id: number, data: string) => writePty(id, data)) + ipcMain.on('pty:resize', (_e, id: number, cols: number, rows: number) => resizePty(id, cols, rows)) + ipcMain.on('pty:kill', (_e, id: number) => killPty(id)) + + ipcMain.handle('config:get', () => getConfig()) + ipcMain.handle('config:theme', () => getThemeCss()) + + ipcMain.handle('search:content', (_e, query: string) => searchContent(getRoot(), query)) + ipcMain.handle('search:files', () => listFiles(getRoot())) +} + +function createWindow(): void { + const win = new BrowserWindow({ + width: 1680, + height: 1040, + minWidth: 1100, + minHeight: 680, + show: false, + backgroundColor: '#16171a', + titleBarStyle: isMac ? 'hiddenInset' : 'default', + trafficLightPosition: isMac ? { x: 14, y: 12 } : undefined, + webPreferences: { + preload: join(__dirname, '../preload/index.js'), + contextIsolation: true, + nodeIntegration: false, + sandbox: false, + }, + }) + + win.on('ready-to-show', () => win.show()) + + win.webContents.setWindowOpenHandler(({ url }) => { + shell.openExternal(url) + return { action: 'deny' } + }) + + if (isDev) { + win.loadURL(process.env['ELECTRON_RENDERER_URL'] as string) + } else { + win.loadFile(join(__dirname, '../renderer/index.html')) + } +} + +app.whenReady().then(async () => { + app.setName('Helder') + registerIpc() + await resolveConfig(getRoot()) + startWatcher() + startConfigWatcher() + createWindow() + + app.on('activate', () => { + if (BrowserWindow.getAllWindows().length === 0) createWindow() + }) +}) + +app.on('window-all-closed', () => { + if (watcher) { watcher.close(); watcher = null } + if (configWatcher) { configWatcher.close(); configWatcher = null } + killAllPtys() + if (!isMac) app.quit() +}) + +app.on('before-quit', () => killAllPtys()) diff --git a/src/main/project.ts b/src/main/project.ts new file mode 100644 index 0000000..a32921f --- /dev/null +++ b/src/main/project.ts @@ -0,0 +1,45 @@ +import { basename } from 'node:path' +import { existsSync, statSync } from 'node:fs' +import { dialog, BrowserWindow } from 'electron' + +/** + * One project per window. The root is resolved (in order) from $HELDER_PROJECT, + * a directory passed on argv, or the process working directory — then it can be + * changed at runtime via the Open Folder dialog. + */ +function resolveInitialRoot(): string { + const envRoot = process.env.HELDER_PROJECT + if (envRoot && existsSync(envRoot) && statSync(envRoot).isDirectory()) return envRoot + const argDir = process.argv.slice(1).find((a) => !a.startsWith('-') && existsSync(a) && safeIsDir(a)) + if (argDir) return argDir + return process.cwd() +} + +function safeIsDir(p: string): boolean { + try { return statSync(p).isDirectory() } catch { return false } +} + +let root = resolveInitialRoot() + +export function getRoot(): string { + return root +} + +export function getName(): string { + return root ? basename(root) || root : 'no project' +} + +export function setRoot(next: string): void { + root = next +} + +export async function openDialog(win: BrowserWindow | null): Promise { + const res = win + ? await dialog.showOpenDialog(win, { properties: ['openDirectory'] }) + : await dialog.showOpenDialog({ properties: ['openDirectory'] }) + if (!res.canceled && res.filePaths[0]) { + root = res.filePaths[0] + return root + } + return null +} diff --git a/src/main/pty-service.ts b/src/main/pty-service.ts new file mode 100644 index 0000000..03f4984 --- /dev/null +++ b/src/main/pty-service.ts @@ -0,0 +1,79 @@ +import { createRequire } from 'node:module' +import type { WebContents } from 'electron' +import { getRoot } from './project' +import { getConfig } from './config' + +/** + * Real PTYs. The agent pane is a shell that auto-launches the `claude` CLI; the + * bottom pane is a plain shell. node-pty is a native module — loaded defensively + * so the app still launches (with a friendly message) if it wasn't rebuilt for + * this Electron via `npm run rebuild`. + * + * Shell + ai command/autoLaunch come from `.helder/config.json` (terminal.shell, + * ai.command, ai.autoLaunch) via the config module. + */ +const require = createRequire(import.meta.url) + +type PtyModule = typeof import('node-pty') +let pty: PtyModule | null = null +try { + pty = require('node-pty') as PtyModule +} catch (e) { + console.error('[helder] node-pty unavailable — run `npm run rebuild`:', (e as Error).message) +} + +const terms = new Map() +let seq = 0 + +function defaultShell(): string { + const configured = getConfig().terminal.shell + if (configured) return configured + if (process.platform === 'win32') return process.env.COMSPEC || 'powershell.exe' + return process.env.SHELL || '/bin/zsh' +} + +export function ptyAvailable(): boolean { + return !!pty +} + +export function createPty(sender: WebContents, kind: 'agent' | 'shell', cols: number, rows: number): number { + if (!pty) return -1 + const cwd = getRoot() || process.env.HOME || process.cwd() + const proc = pty.spawn(defaultShell(), [], { + name: 'xterm-color', + cols: cols || 80, + rows: rows || 24, + cwd, + env: process.env as { [key: string]: string }, + }) + const id = ++seq + terms.set(id, proc) + + proc.onData((data) => { if (!sender.isDestroyed()) sender.send('pty:data', { id, data }) }) + proc.onExit(() => { terms.delete(id); if (!sender.isDestroyed()) sender.send('pty:exit', { id }) }) + + const ai = getConfig().ai + if (kind === 'agent' && ai.autoLaunch) { + // small delay so the shell prompt is ready before we type the command + setTimeout(() => { try { proc.write(ai.command + '\r') } catch { /* exited */ } }, 350) + } + return id +} + +export function writePty(id: number, data: string): void { + terms.get(id)?.write(data) +} + +export function resizePty(id: number, cols: number, rows: number): void { + try { terms.get(id)?.resize(cols, rows) } catch { /* race with exit */ } +} + +export function killPty(id: number): void { + const p = terms.get(id) + if (p) { try { p.kill() } catch { /* already gone */ } terms.delete(id) } +} + +export function killAllPtys(): void { + for (const p of terms.values()) { try { p.kill() } catch { /* noop */ } } + terms.clear() +} diff --git a/src/main/search-service.ts b/src/main/search-service.ts new file mode 100644 index 0000000..f0ffd51 --- /dev/null +++ b/src/main/search-service.ts @@ -0,0 +1,87 @@ +import { createRequire } from 'node:module' +import { spawn } from 'node:child_process' +import { relative, sep } from 'node:path' + +/** Content search via ripgrep; file-name list via `rg --files`. Substring + * (fixed-string), smart-case — matching the prototype's search semantics. */ +const require = createRequire(import.meta.url) + +let rgPath: string | null = null +try { + rgPath = (require('@vscode/ripgrep') as { rgPath: string }).rgPath +} catch (e) { + console.error('[helder] @vscode/ripgrep unavailable:', (e as Error).message) +} + +export interface ContentHit { no: number; ln: string; ix: number } +export interface ContentGroup { path: string; hits: ContentHit[] } + +const IGNORE_GLOBS = ['node_modules', '.git', 'out', 'dist', 'build', '.cache', 'vendor', 'coverage', '.helder'] + .flatMap((d) => ['--glob', `!${d}`]) + +const MAX_FILES = 400 +const MAX_LINE = 1000 + +function toRel(root: string, p: string): string { + return relative(root, p).split(sep).join('/') +} + +export function searchContent(root: string, query: string): Promise { + return new Promise((resolve) => { + if (!rgPath || query.trim().length < 2) return resolve([]) + const child = spawn(rgPath, [ + '--json', '--fixed-strings', '--smart-case', + '--max-count', '50', '--max-columns', '2000', + ...IGNORE_GLOBS, '-e', query, '--', root, + ]) + const order: string[] = [] + const groups = new Map() + let buf = '' + let done = false + const finish = (): void => { if (done) return; done = true; resolve(order.slice(0, MAX_FILES).map((p) => groups.get(p)!)) } + + child.stdout.on('data', (chunk: Buffer) => { + buf += chunk.toString() + let nl: number + while ((nl = buf.indexOf('\n')) >= 0) { + const line = buf.slice(0, nl); buf = buf.slice(nl + 1) + if (!line) continue + let msg: { type: string; data: { path?: { text?: string }; lines?: { text?: string }; line_number?: number; submatches?: { start: number }[] } } + try { msg = JSON.parse(line) } catch { continue } + if (msg.type !== 'match') continue + const abs = msg.data.path?.text + const text = msg.data.lines?.text + if (!abs || text == null) continue + const rel = toRel(root, abs) + let g = groups.get(rel) + if (!g) { if (groups.size >= MAX_FILES) continue; g = { path: rel, hits: [] }; groups.set(rel, g); order.push(rel) } + const ln = text.replace(/\n$/, '').slice(0, MAX_LINE) + const ix = msg.data.submatches && msg.data.submatches[0] ? msg.data.submatches[0].start : 0 + g.hits.push({ no: msg.data.line_number || 0, ln, ix: Math.min(ix, ln.length) }) + } + }) + child.on('close', finish) + child.on('error', finish) + }) +} + +export function listFiles(root: string): Promise { + return new Promise((resolve) => { + if (!rgPath) return resolve([]) + const child = spawn(rgPath, ['--files', ...IGNORE_GLOBS, '--', root]) + let buf = '' + const out: string[] = [] + let done = false + const finish = (): void => { if (done) return; done = true; resolve(out) } + child.stdout.on('data', (chunk: Buffer) => { + buf += chunk.toString() + let nl: number + while ((nl = buf.indexOf('\n')) >= 0) { + const line = buf.slice(0, nl); buf = buf.slice(nl + 1) + if (line) out.push(toRel(root, line)) + } + }) + child.on('close', () => { if (buf.trim()) out.push(toRel(root, buf.trim())); finish() }) + child.on('error', finish) + }) +} diff --git a/src/preload/index.d.ts b/src/preload/index.d.ts new file mode 100644 index 0000000..030013d --- /dev/null +++ b/src/preload/index.d.ts @@ -0,0 +1,9 @@ +import type { HelderApi } from './index' + +declare global { + interface Window { + helder: HelderApi + } +} + +export {} diff --git a/src/preload/index.ts b/src/preload/index.ts new file mode 100644 index 0000000..96df782 --- /dev/null +++ b/src/preload/index.ts @@ -0,0 +1,85 @@ +import { contextBridge, clipboard, ipcRenderer } from 'electron' + +/** + * The single bridge between renderer and main. The renderer NEVER touches the + * filesystem, git or the OS clipboard directly — everything goes through here. + * (PTYs land here next, for the terminals.) + */ +const api = { + platform: process.platform, + + clipboard: { + writeText: (text: string) => clipboard.writeText(text), + }, + + project: { + current: () => ipcRenderer.invoke('project:current'), + open: () => ipcRenderer.invoke('project:open'), + }, + + fs: { + tree: () => ipcRenderer.invoke('fs:tree'), + files: () => ipcRenderer.invoke('fs:files'), + read: (path: string) => ipcRenderer.invoke('fs:read', path), + write: (path: string, content: string): Promise => ipcRenderer.invoke('fs:write', path, content), + }, + + git: { + load: () => ipcRenderer.invoke('git:load'), + stage: (paths: string[]) => ipcRenderer.invoke('git:stage', paths), + unstage: (paths: string[]) => ipcRenderer.invoke('git:unstage', paths), + commit: (message: string) => ipcRenderer.invoke('git:commit', message), + discard: (paths: string[]) => ipcRenderer.invoke('git:discard', paths), + }, + + pty: { + available: (): Promise => ipcRenderer.invoke('pty:available'), + create: (kind: 'agent' | 'shell', cols: number, rows: number): Promise => + ipcRenderer.invoke('pty:create', kind, cols, rows), + write: (id: number, data: string): void => ipcRenderer.send('pty:write', id, data), + resize: (id: number, cols: number, rows: number): void => ipcRenderer.send('pty:resize', id, cols, rows), + kill: (id: number): void => ipcRenderer.send('pty:kill', id), + onData: (cb: (id: number, data: string) => void): (() => void) => { + const h = (_e: unknown, p: { id: number; data: string }): void => cb(p.id, p.data) + ipcRenderer.on('pty:data', h) + return () => ipcRenderer.removeListener('pty:data', h) + }, + onExit: (cb: (id: number) => void): (() => void) => { + const h = (_e: unknown, p: { id: number }): void => cb(p.id) + ipcRenderer.on('pty:exit', h) + return () => ipcRenderer.removeListener('pty:exit', h) + }, + }, + + config: { + get: () => ipcRenderer.invoke('config:get'), + theme: (): Promise => ipcRenderer.invoke('config:theme'), + }, + + search: { + content: (query: string) => ipcRenderer.invoke('search:content', query), + files: (): Promise => ipcRenderer.invoke('search:files'), + }, + + /** Subscribe to "the project changed on disk" pings. Returns an unsubscribe. */ + onProjectChanged: (cb: () => void): (() => void) => { + const handler = (): void => cb() + ipcRenderer.on('project:changed', handler) + return () => ipcRenderer.removeListener('project:changed', handler) + }, + + /** Subscribe to .helder config/theme edits. Returns an unsubscribe. */ + onConfigChanged: (cb: () => void): (() => void) => { + const handler = (): void => cb() + ipcRenderer.on('config:changed', handler) + return () => ipcRenderer.removeListener('config:changed', handler) + }, +} + +if (process.contextIsolated) { + contextBridge.exposeInMainWorld('helder', api) +} else { + ;(globalThis as unknown as { helder: typeof api }).helder = api +} + +export type HelderApi = typeof api diff --git a/src/renderer/index.html b/src/renderer/index.html new file mode 100644 index 0000000..60ea225 --- /dev/null +++ b/src/renderer/index.html @@ -0,0 +1,12 @@ + + + + + + Helder + + +
+ + + diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx new file mode 100644 index 0000000..e66c67d --- /dev/null +++ b/src/renderer/src/App.tsx @@ -0,0 +1,356 @@ +/* App shell: 4 resizable columns, keyboard shortcuts, copy-reference, status bar */ +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { HL } from './highlight' +import { FileTree, GitPanel, Icon } from './components' +import type { ContextTarget } from './components' +import { Editor, SplitView } from './editor' +import type { Cursor, Mode, Selection } from './editor' +import { Terminal, lid } from './terminals' +import { ContextMenu, PassPopup, SearchModal, Toasts } from './overlays' +import type { Menu, Toast } from './overlays' +import type { FileNode, GitStatus } from './types' +import { useProject, useProjectActions } from './project' + +const NO_COMMITTED = new Set() + +function Splitter({ orientation = 'v', onDelta }: { orientation?: 'v' | 'h'; onDelta: (dx: number, dy: number) => void }): React.ReactElement { + const [drag, setDrag] = useState(false) + function down(e: React.MouseEvent): void { + e.preventDefault() + let last = { x: e.clientX, y: e.clientY } + setDrag(true) + document.body.style.cursor = orientation === 'v' ? 'col-resize' : 'row-resize' + document.body.style.userSelect = 'none' + function mv(ev: MouseEvent): void { + onDelta(ev.clientX - last.x, ev.clientY - last.y) + last = { x: ev.clientX, y: ev.clientY } + } + function up(): void { + setDrag(false) + document.body.style.cursor = ''; document.body.style.userSelect = '' + document.removeEventListener('mousemove', mv); document.removeEventListener('mouseup', up) + } + document.addEventListener('mousemove', mv); document.addEventListener('mouseup', up) + } + return
+} + +function RightColumn({ width }: { width: number }): React.ReactElement { + const [topFrac, setTopFrac] = useState(0.52) + const ref = useRef(null) + function delta(_dx: number, dy: number): void { + const h = ref.current ? ref.current.clientHeight : 600 + setTopFrac((f) => Math.max(0.18, Math.min(0.82, (f * h + dy) / h))) + } + return ( +
+
+
+ +
+ +
+ +
+
+
+ ) +} + +function clamp(v: number, lo: number, hi: number): number { return Math.max(lo, Math.min(hi, v)) } +function ancestors(path: string): string[] { + const parts = path.split('/'); const out: string[] = [] + for (let i = 1; i < parts.length; i++) out.push(parts.slice(0, i).join('/')) + return out +} +function initialOpenDirs(node: FileNode, set: Set): Set { + if (node.type === 'dir') { + if (node.open && node.path) set.add(node.path) + ;(node.children || []).forEach((c) => initialOpenDirs(c, set)) + } + return set +} + +export function App(): React.ReactElement { + const proj = useProject() + const actions = useProjectActions() + + const changeMap = useMemo(() => Object.fromEntries(proj.changes.map((c) => [c.path, c.status])) as Record, [proj.changes]) + const changeSet = useMemo(() => new Set(proj.changes.map((c) => c.path)), [proj.changes]) + + const [tabs, setTabs] = useState<{ path: string }[]>([]) + const [active, setActive] = useState(null) + const [tabMode, setTabMode] = useState>({}) + const [openDirs, setOpenDirs] = useState>(new Set()) + const [cursor, setCursor] = useState(null) + const [selection, setSelection] = useState(null) + const [overlay, setOverlay] = useState<'search' | null>(null) + const [menu, setMenu] = useState(null) + const [toasts, setToasts] = useState([]) + const [splitFor, setSplitFor] = useState(null) + const [commitMsg, setCommitMsg] = useState('') + const [passPopup, setPassPopup] = useState<{ x: number; y: number; ref: string } | null>(null) + + // Editable buffers: path → current text (absent = clean, showing on-disk content). + const [buffers, setBuffers] = useState>({}) + const buffersRef = useRef(buffers); buffersRef.current = buffers + const saveTimer = useRef | null>(null) + + function diskText(path: string): string { return proj.files[path] ?? '' } + function bufferText(path: string | null): string { return path ? (buffers[path] ?? diskText(path)) : '' } + function isDirty(path: string): boolean { return buffers[path] != null && buffers[path] !== diskText(path) } + + function writeToDisk(path: string, text: string): void { + if (window.helder) window.helder.fs.write(path, text).catch(() => toast('Save failed', path)) + } + function saveActive(): void { + if (!active) return + const text = buffersRef.current[active] + if (text == null || text === diskText(active)) return + writeToDisk(active, text) + toast('Saved', active) + } + function onEdit(text: string): void { + if (!active) return + const path = active + setBuffers((b) => ({ ...b, [path]: text })) + if (proj.config.editor.autoSave) { + if (saveTimer.current) clearTimeout(saveTimer.current) + saveTimer.current = setTimeout(() => writeToDisk(path, text), 600) + } + } + function doDiscard(path: string): void { + if (proj.config.git.confirmDiscard && + !window.confirm(`Discard changes to ${path}?\nThis reverts the file to the last commit and cannot be undone.`)) return + actions.discard(path) + setBuffers((b) => { const n = { ...b }; delete n[path]; return n }) + toast('Discarded changes', path) + } + + const [gitW, setGitW] = useState(232) + const [treeW, setTreeW] = useState(244) + const [rightW, setRightW] = useState(444) + + // Seed explorer expansion from the tree's `open` flags once per opened project. + const seededRoot = useRef(undefined) + useEffect(() => { + if (proj.tree && seededRoot.current !== proj.root) { + seededRoot.current = proj.root + setOpenDirs(initialOpenDirs(proj.tree, new Set())) + } + }, [proj.tree, proj.root]) + + function toast(title: string, ref?: string): void { + const id = lid() + setToasts((t) => [...t, { id, title, ref }]) + setTimeout(() => setToasts((t) => t.filter((x) => x.id !== id)), 2300) + } + async function copyText(text: string, label?: string): Promise { + try { + if (window.helder && window.helder.clipboard) window.helder.clipboard.writeText(text) + else await navigator.clipboard.writeText(text) + } catch { + const ta = document.createElement('textarea'); ta.value = text + ta.style.position = 'fixed'; ta.style.opacity = '0'; document.body.appendChild(ta) + ta.select(); try { document.execCommand('copy') } catch { /* noop */ } ta.remove() + } + toast(label || 'Copied reference', text) + } + + const toggleDir = useCallback((p: string) => { + setOpenDirs((s) => { const n = new Set(s); n.has(p) ? n.delete(p) : n.add(p); return n }) + }, []) + + function reveal(path: string): void { + setOpenDirs((s) => { const n = new Set(s); ancestors(path).forEach((a) => n.add(a)); return n }) + } + + function commit(): void { + const msg = commitMsg.trim() + if (!msg) return + actions.commit(msg).then((n) => { + if (n > 0) toast(`Committed ${n} file${n > 1 ? 's' : ''}`, msg.length > 34 ? msg.slice(0, 34) + '…' : msg) + }) + setCommitMsg('') + } + + function openFile(path: string, opts: { diff?: boolean; line?: number } = {}): void { + const changed = !!proj.diffs[path] + actions.ensureFile(path) + setTabs((t) => t.some((x) => x.path === path) ? t : [...t, { path }]) + setActive(path) + setTabMode((m) => ({ ...m, [path]: opts.diff && changed ? 'diff' : (m[path] || (changed ? 'diff' : 'code')) })) + reveal(path) + if (opts.line) { + // show the current/updated file so line numbers map to search hits + setSplitFor(null) + setTabMode((m) => ({ ...m, [path]: changed ? 'updated' : 'code' })) + setCursor({ path, line: opts.line, col: 1 }) + setSelection(null) + setTimeout(() => { + const row = document.querySelector('.editor .ln-row[data-line="' + opts.line + '"]') as HTMLElement | null + if (row) { const ed = row.closest('.editor') as HTMLElement; const er = ed.getBoundingClientRect(), rr = row.getBoundingClientRect(); ed.scrollTop += (rr.top - er.top) - ed.clientHeight / 2 } + }, 70) + } + } + + function closeTab(path: string): void { + setTabs((t) => { + const ix = t.findIndex((x) => x.path === path) + const next = t.filter((x) => x.path !== path) + if (path === active) { + const fallback = next[ix] || next[ix - 1] || next[next.length - 1] + setActive(fallback ? fallback.path : null) + } + return next + }) + } + + // ---- context menus ---- + function openMenu(e: React.MouseEvent, target: ContextTarget): void { + e.preventDefault(); e.stopPropagation() + const sparkSend = (ref: string): Menu['items'][number] => ({ icon: Icon.spark(), label: 'Send reference to agent', onClick: () => { window.dispatchEvent(new CustomEvent('agentPaste', { detail: ref })); toast('Passed to agent', ref) } }) + if (target.kind === 'editor') { + const ref = target.sel ? `${target.path}:${target.sel.start}-${target.sel.end}` : `${target.path}:${target.line}` + const mx = e.clientX, my = e.clientY + setMenu({ + x: mx, y: my, note: ref, + items: [ + { primary: true, icon: Icon.copy(), label: 'Copy reference', onClick: () => copyText(ref) }, + { icon: Icon.spark(), label: 'Pass on to Agent', onClick: () => setPassPopup({ x: mx, y: my, ref }) }, + ], + }) + } else { + const isDir = target.kind === 'dir' + const ref = isDir ? target.path + '/' : target.path + const name = target.path.split('/').pop() as string + const items: Menu['items'] = [ + { primary: true, icon: Icon.copy(), label: 'Copy reference', onClick: () => copyText(ref) }, + sparkSend(ref), + { icon: Icon.copy(), label: isDir ? 'Copy folder path' : 'Copy file name', onClick: () => copyText(isDir ? target.path : name, 'Copied') }, + ] + if (!isDir) { + items.push({ sep: true }) + if (target.kind === 'git') { + const isStaged = proj.staged.has(target.path) + items.push(isStaged + ? { icon: Icon.minus(), label: 'Unstage changes', onClick: () => actions.unstage(target.path) } + : { icon: Icon.plus(), label: 'Stage changes', onClick: () => actions.stage(target.path) }) + items.push({ icon: Icon.diff(), label: 'Open diff', onClick: () => openFile(target.path, { diff: true }) }) + items.push({ icon: Icon.discard(), label: 'Discard changes', onClick: () => doDiscard(target.path) }) + } + items.push({ icon: Icon.file(), label: 'Open file', onClick: () => openFile(target.path) }) + items.push({ icon: Icon.reveal(), label: 'Reveal in Explorer', onClick: () => reveal(target.path) }) + } + setMenu({ x: e.clientX, y: e.clientY, note: ref, items }) + } + } + + // ---- shortcuts ---- + useEffect(() => { + function onKey(e: KeyboardEvent): void { + const meta = e.metaKey || e.ctrlKey + if (meta && e.key.toLowerCase() === 'f') { e.preventDefault(); setOverlay('search') } + else if (meta && e.key.toLowerCase() === 's') { e.preventDefault(); saveActive() } + else if (meta && e.key.toLowerCase() === 'w') { e.preventDefault(); if (active) closeTab(active) } + else if (e.key === 'Escape') { if (splitFor) setSplitFor(null); else { setOverlay(null); setMenu(null) } } + } + window.addEventListener('keydown', onKey) + return () => window.removeEventListener('keydown', onKey) + }, [active, splitFor]) + + const MODE_LABEL: Record = { original: 'orig', updated: 'upd', diff: 'diff', code: '' } + const MODE_WORD: Record = { original: 'Original', updated: 'Updated', diff: 'Diff' } + const resolvedTabs = tabs.map((t) => { + const changed = !!proj.diffs[t.path] + const m = tabMode[t.path] || (changed ? 'diff' : 'code') + return { ...t, changed, modeLabel: splitFor === t.path ? 'split' : MODE_LABEL[m], dirty: isDirty(t.path) } + }) + const mode: Mode = (active && tabMode[active]) || (active && proj.diffs[active] ? 'diff' : 'code') + const totals = proj.changes.reduce((a, c) => ({ add: a.add + c.add, del: a.del + c.del }), { add: 0, del: 0 }) + const activeLang = active ? HL.langLabel(active) : '' + + const crumb = active ? active.split('/') : [] + const curLine = cursor && active && cursor.path === active ? cursor.line : 1 + const curCol = cursor && active && cursor.path === active ? cursor.col : 1 + + return ( +
+ {/* title bar */} +
+
+
{Icon.spark({ style: { color: 'var(--accent)' } })}Helder + actions.openFolder()}>{proj.name} +
+ {active && ( +
+ {crumb.map((s, i) => ({i > 0 && }{s}))} +
+ )} +
+
+ +
+
+ + {/* workbench */} +
+
+ +
+ setGitW((w) => clamp(w + dx, 160, 460))} /> + +
+ {proj.tree ? ( + + ) : ( +
Explorer
+ )} +
+ setTreeW((w) => clamp(w + dx, 160, 520))} /> + +
+ { if (active) { setTabMode((mm) => ({ ...mm, [active]: m })); setSplitFor(null) } }} + onActivate={setActive} onClose={closeTab} onContext={openMenu} + onSplit={(p) => setSplitFor(p)} splitOpen={splitFor === active} + cursor={cursor} selection={selection} setCursor={setCursor} setSelection={setSelection} + bufferText={bufferText(active)} onEdit={onEdit} /> +
+ setRightW((w) => clamp(w - dx, 280, 780))} /> + + +
+ + {/* status bar */} +
+
{Icon.branch({ width: 12, height: 12 })}{proj.branch}
+
+{totals.add} −{totals.del}
+
+ {active &&
{selection && selection.path === active && selection.start !== selection.end ? `${selection.end - selection.start + 1} lines selected` : `Ln ${curLine}, Col ${curCol}`}
} + {active &&
Spaces: 4
} + {active &&
UTF-8
} + {active &&
{activeLang}
} + {active && proj.diffs[active] &&
{splitFor === active ? 'Split' : (MODE_WORD[mode] || '')}
} +
+ + {/* overlays */} + {splitFor && setSplitFor(null)} onContext={openMenu} />} + {passPopup && { + const line = (text && text.trim() ? text.trim() + ' ' : '') + passPopup.ref + window.dispatchEvent(new CustomEvent('agentPaste', { detail: line })) + setPassPopup(null) + toast('Passed to agent', passPopup.ref) + }} + onCancel={() => setPassPopup(null)} />} + {overlay === 'search' && openFile(p, { line: n })} onClose={() => setOverlay(null)} changeSet={changeSet} />} + {menu && setMenu(null)} />} + +
+ ) +} diff --git a/src/renderer/src/components.tsx b/src/renderer/src/components.tsx new file mode 100644 index 0000000..a7f6f5d --- /dev/null +++ b/src/renderer/src/components.tsx @@ -0,0 +1,240 @@ +/* Shared icons, FileIcon, GitPanel, FileTree */ +import React, { Fragment } from 'react' +import type { Change, FileNode, GitStatus } from './types' +import { HL } from './highlight' + +type SvgProps = React.SVGProps + +/* ---- minimal geometric icons ---- */ +export const Icon: Record React.ReactElement> = { + search: (p) => (), + branch: (p) => (), + close: (p) => (), + copy: (p) => (), + terminal: (p) => (), + spark: (p) => (), + file: (p) => (), + reveal: (p) => (), + diff: (p) => (), + plus: (p) => (), + minus: (p) => (), + check: (p) => (), + discard: (p) => (), +} + +export const Chevron = ({ open }: { open: boolean }): React.ReactElement => ( + + + +) + +export const FolderIcon = ({ open }: { open: boolean }): React.ReactElement => ( + + + +) + +export function FileIcon({ path }: { path: string }): React.ReactElement { + const ic = HL.iconFor(path) + return {ic.t} +} + +/* Shared callback signatures used across panels. */ +export type OpenFile = (path: string, opts?: { diff?: boolean; line?: number }) => void +export interface ContextTarget { + path: string + kind: 'editor' | 'dir' | 'file' | 'git' + staged?: boolean + sel?: { start: number; end: number } + line?: number +} +export type OnContext = (e: React.MouseEvent, target: ContextTarget) => void + +/* ============ Git / Source Control panel ============ */ +function GitRow({ c, staged, activePath, onOpen, onContext, onToggleStage }: { + c: Change + staged: boolean + activePath: string | null + onOpen: OpenFile + onContext: OnContext + onToggleStage: (path: string) => void +}): React.ReactElement { + const name = c.path.split('/').pop() + const dir = c.path.split('/').slice(0, -1).join('/') + return ( +
onOpen(c.path, { diff: true })} + onContextMenu={(e) => onContext(e, { path: c.path, kind: 'git', staged })} + title={c.path}> + {c.status} + + {name} + {dir && {dir}/} + + + {c.add > 0 && +{c.add}} + {c.del > 0 && -{c.del}} + +
+ ) +} + +export function GitPanel({ branch, changes, staged, committed, commitMsg, setCommitMsg, onStage, onUnstage, onStageAll, onUnstageAll, onCommit, onOpen, onContext, activePath }: { + branch: string + changes: Change[] + staged: Set + committed: Set + commitMsg: string + setCommitMsg: (v: string) => void + onStage: (path: string) => void + onUnstage: (path: string) => void + onStageAll: () => void + onUnstageAll: () => void + onCommit: () => void + onOpen: OpenFile + onContext: OnContext + activePath: string | null +}): React.ReactElement { + const visible = changes.filter((c) => !committed.has(c.path)) + const stagedList = visible.filter((c) => staged.has(c.path)) + const changesList = visible.filter((c) => !staged.has(c.path)) + const totals = visible.reduce((a, c) => ({ add: a.add + c.add, del: a.del + c.del }), { add: 0, del: 0 }) + const canCommit = stagedList.length > 0 && commitMsg.trim().length > 0 + + return ( + +
+ {Icon.branch()}Source Control + {visible.length} +
+ +
+