Compare commits

..
Author SHA1 Message Date
ajnart 95def954b7 fix(startup): only chown runtime paths for custom PUID/PGID
Recursively chowning the entire /app tree on startup is extremely slow
on network- or HDD-backed storage (e.g. TrueNAS Apps). The Next.js
standalone bundle and its node_modules contain tens of thousands of
tiny files, turning the advertised ~10s ownership step into 15+ minutes.

Only chown the paths the non-root user actually writes to at runtime:
the /appdata volume, the Next.js cache directory and the nginx runtime
directories. Application code, DB migrations and native modules are only
read/executed and stay owned by root.

The known /appdata subdirectories are created and their existing content
recursively re-owned before dropping privileges, so a persistent volume
with stale or root-owned entries stays writable after a PUID/PGID change.
These trees are small, unlike the /app tree.

Closes #2190
2026-08-15 11:23:28 +02:00
11 changed files with 5864 additions and 6197 deletions

No files matched your search

+14 -14
View File
@@ -21,35 +21,35 @@
"verify:search": "node ./scripts/verify-algolia-search.mjs"
},
"dependencies": {
"@docusaurus/core": "^3.10.2",
"@docusaurus/faster": "^3.10.2",
"@docusaurus/plugin-content-docs": "^3.10.2",
"@docusaurus/plugin-sitemap": "^3.10.2",
"@docusaurus/preset-classic": "^3.10.2",
"@docusaurus/core": "^3.10.1",
"@docusaurus/faster": "^3.10.1",
"@docusaurus/plugin-content-docs": "^3.10.1",
"@docusaurus/plugin-sitemap": "^3.10.1",
"@docusaurus/preset-classic": "^3.10.1",
"@homarr/definitions": "workspace:^",
"@mdx-js/react": "^3.1.1",
"@nivo/line": "^0.99.0",
"@scalar/api-reference-react": "catalog:",
"@signalwire/docusaurus-plugin-llms-txt": "^1.2.2",
"@tabler/icons-react": "catalog:",
"@tailwindcss/postcss": "^4.3.3",
"@tailwindcss/postcss": "^4.3.2",
"clsx": "catalog:",
"dayjs": "catalog:",
"docusaurus-plugin-image-zoom": "^3.0.1",
"leader-line-new": "^1.1.9",
"postcss": "^8.5.26",
"postcss": "^8.5.16",
"posthog-docusaurus": "^2.0.5",
"prism-react-renderer": "^2.4.1",
"random-word-slugs": "^0.1.7",
"react": "catalog:",
"react-dom": "catalog:",
"swiper": "^14.1.0",
"tailwindcss": "^4.3.3"
"swiper": "^14.0.5",
"tailwindcss": "^4.3.2"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "^3.10.2",
"@docusaurus/tsconfig": "^3.10.2",
"@docusaurus/types": "^3.10.2",
"@docusaurus/module-type-aliases": "^3.10.1",
"@docusaurus/tsconfig": "^3.10.1",
"@docusaurus/types": "^3.10.1",
"@fec/remark-a11y-emoji": "^4.0.2",
"@tsconfig/docusaurus": "^2.0.9",
"@types/react": "catalog:",
@@ -59,8 +59,8 @@
"zod": "catalog:"
},
"peerDependencies": {
"@docusaurus/theme-common": "^3.10.2",
"@docusaurus/theme-mermaid": "^3.10.2"
"@docusaurus/theme-common": "^3.10.1",
"@docusaurus/theme-mermaid": "^3.10.1"
},
"browserslist": {
"production": [
@@ -52,7 +52,7 @@ export async function GET() {
const date = new Date().toISOString().split("T")[0];
const filename = `homarr-backup-${date}.zip`;
return new NextResponse(zipBuffer as unknown as BodyInit, {
return new NextResponse(zipBuffer, {
status: 200,
headers: {
"Content-Type": "application/zip",
-1
View File
@@ -30,7 +30,6 @@
"cookie": "catalog:",
"dayjs": "catalog:",
"next": "catalog:",
"node-fetch": "catalog:",
"octokit": "catalog:",
"react": "catalog:",
"react-dom": "catalog:",
-2
View File
@@ -40,7 +40,6 @@
"@jellyfin/sdk": "catalog:",
"ical.js": "catalog:",
"maria2": "catalog:",
"node-fetch": "catalog:",
"octokit": "catalog:",
"proxmox-api": "catalog:",
"timezones-ical-library": "catalog:",
@@ -52,7 +51,6 @@
},
"devDependencies": {
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/node-fetch": "catalog:",
"@types/node-unifi": "catalog:",
"@types/ws": "catalog:",
"@types/xml2js": "catalog:",
@@ -32,6 +32,24 @@ const DEFAULT_PASSWORD = "12341234";
const DEFAULT_API_KEY = "3b1434980677dcf53fa8c4a611db3b1f0f88478790097515c0abb539102778b9"; // Some hash generated from password
describe("Pi-hole v5 integration", () => {
test("getSummaryAsync should return summary from pi-hole", async () => {
// Arrange
const piholeContainer = await createPiHoleV5Container(DEFAULT_PASSWORD).start();
const piHoleIntegration = createPiHoleIntegrationV5(piholeContainer, DEFAULT_API_KEY);
// Act
const result = await piHoleIntegration.getSummaryAsync();
// Assert
expect(result.adsBlockedToday).toBe(0);
expect(result.adsBlockedTodayPercentage).toBe(0);
expect(result.dnsQueriesToday).toBe(0);
expect(result.domainsBeingBlocked).toBeGreaterThan(1);
// Cleanup
await piholeContainer.stop();
}, 20_000); // Timeout of 20 seconds
test("testConnectionAsync should be successful", async () => {
// Arrange
const piholeContainer = await createPiHoleV5Container(DEFAULT_PASSWORD).start();
+5687 -6022
View File
File diff suppressed because it is too large. Load diff
+92 -117
View File
@@ -3,9 +3,9 @@ packages:
- packages/*
- tooling/*
catalog:
"@babel/parser": ^8.0.4
"@auth/core": ^0.41.3
"@auth/drizzle-adapter": ^1.11.3
"@babel/parser": ^7.29.7
"@auth/core": ^0.41.2
"@auth/drizzle-adapter": ^1.11.2
"@ctrl/deluge": ^8.0.0
"@ctrl/qbittorrent": ^9.14.0
"@ctrl/transmission": ^7.4.0
@@ -14,29 +14,29 @@ catalog:
"@dnd-kit/sortable": ^10.0.0
"@dnd-kit/utilities": ^3.2.2
"@drizzle-team/brocli": ^0.12.0
"@extractus/feed-extractor": 8.0.3
"@gfazioli/mantine-onboarding-tour": ^4.0.9
"@extractus/feed-extractor": 7.2.1
"@gfazioli/mantine-onboarding-tour": ^4.0.6
"@gitbeaker/rest": ^43.8.0
"@homarr/gridstack": ^1.12.0
"@homarr/node-unifi": ^2.6.0
"@immich/sdk": ^3.1.0
"@immich/sdk": ^3.0.2
"@jellyfin/sdk": ^0.13.0
"@kubernetes/client-node": ^2.0.0
"@kubernetes/client-node": ^1.4.0
"@modelcontextprotocol/sdk": 1.29.0
"@mantine/charts": ^9.5.1
"@mantine/colors-generator": ^9.5.1
"@mantine/core": ^9.5.1
"@mantine/dates": ^9.5.1
"@mantine/dropzone": ^9.5.1
"@mantine/form": ^9.5.1
"@mantine/hooks": ^9.5.1
"@mantine/modals": ^9.5.1
"@mantine/notifications": ^9.5.1
"@mantine/spotlight": ^9.5.1
"@mantine/tiptap": ^9.5.1
"@mantine/charts": ^9.4.1
"@mantine/colors-generator": ^9.4.1
"@mantine/core": ^9.4.1
"@mantine/dates": ^9.4.1
"@mantine/dropzone": ^9.4.1
"@mantine/form": ^9.4.1
"@mantine/hooks": ^9.4.1
"@mantine/modals": ^9.4.1
"@mantine/notifications": ^9.4.1
"@mantine/spotlight": ^9.4.1
"@mantine/tiptap": ^9.4.1
"@octokit/auth-app": ^8.2.0
"@paralleldrive/cuid2": ^3.3.0
"@playwright/test": ^1.62.1
"@playwright/test": ^1.61.1
"@semantic-release/changelog": ^7.0.0
"@semantic-release/commit-analyzer": ^13.0.1
"@semantic-release/git": ^11.0.1
@@ -44,36 +44,36 @@ catalog:
"@semantic-release/npm": ^13.1.5
"@semantic-release/release-notes-generator": ^14.1.1
"@t3-oss/env-nextjs": ^0.13.11
"@tabler/icons-react": ^3.46.0
"@tanstack/react-devtools": ^0.10.10
"@tanstack/react-query": ^5.101.4
"@tanstack/react-query-devtools": ^5.101.4
"@tanstack/react-query-next-experimental": ^5.101.4
"@testcontainers/mysql": ^12.1.0
"@testcontainers/postgresql": ^12.1.0
"@testcontainers/redis": ^12.1.0
"@tiptap/extension-color": 3.30.1
"@tiptap/extension-highlight": 3.30.1
"@tiptap/extension-image": 3.30.1
"@tiptap/extension-link": ^3.30.1
"@tiptap/extension-placeholder": ^3.30.1
"@tiptap/extension-table": 3.30.1
"@tiptap/extension-table-cell": 3.30.1
"@tiptap/extension-table-header": 3.30.1
"@tiptap/extension-table-row": 3.30.1
"@tiptap/extension-task-item": 3.30.1
"@tiptap/extension-task-list": 3.30.1
"@tiptap/extension-text-align": 3.30.1
"@tiptap/extension-text-style": 3.30.1
"@tiptap/extension-underline": 3.30.1
"@tiptap/react": ^3.30.1
"@tiptap/starter-kit": ^3.30.1
"@tabler/icons-react": ^3.44.0
"@tanstack/react-devtools": ^0.10.8
"@tanstack/react-query": ^5.101.2
"@tanstack/react-query-devtools": ^5.101.2
"@tanstack/react-query-next-experimental": ^5.101.2
"@testcontainers/mysql": ^12.0.4
"@testcontainers/postgresql": ^12.0.4
"@testcontainers/redis": ^12.0.4
"@tiptap/extension-color": 3.27.3
"@tiptap/extension-highlight": 3.27.3
"@tiptap/extension-image": 3.27.3
"@tiptap/extension-link": ^3.27.3
"@tiptap/extension-placeholder": ^3.27.3
"@tiptap/extension-table": 3.27.3
"@tiptap/extension-table-cell": 3.27.3
"@tiptap/extension-table-header": 3.27.3
"@tiptap/extension-table-row": 3.27.3
"@tiptap/extension-task-item": 3.27.3
"@tiptap/extension-task-list": 3.27.3
"@tiptap/extension-text-align": 3.27.3
"@tiptap/extension-text-style": 3.27.3
"@tiptap/extension-underline": 3.27.3
"@tiptap/react": ^3.27.3
"@tiptap/starter-kit": ^3.27.3
"@trpc/client": ^11.18.0
"@trpc/next": ^11.18.0
"@trpc/react-query": ^11.18.0
"@trpc/server": ^11.18.0
"@trpc/tanstack-react-query": ^11.18.0
"@turbo/gen": ^2.10.10
"@turbo/gen": ^2.10.4
"@types/adm-zip": 0.5.8
"@types/bcrypt": 6.0.0
"@types/better-sqlite3": 7.6.13
@@ -82,34 +82,34 @@ catalog:
"@types/crypto-js": ^4.2.2
"@types/css-modules": ^1.0.5
"@types/dockerode": ^4.0.1
"@types/node": ^26.2.0
"@types/node": ^24.13.3
"@types/node-unifi": ^2.5.1
"@types/pg": ^8.21.0
"@types/pg": ^8.20.0
"@types/prismjs": ^1.26.6
"@types/react": 19.2.18
"@types/react-dom": 19.2.4
"@scalar/api-reference-react": ^0.9.63
"@types/react": 19.2.17
"@types/react-dom": 19.2.3
"@scalar/api-reference-react": ^0.9.54
"@types/video.js": ^7.3.58
"@types/ws": ^8.18.1
"@types/xml2js": ^0.4.14
"posthog-js": ^1.417.1
"posthog-node": ^5.49.1
"@vitejs/plugin-react": ^6.0.5
posthog-js: ^1.399.1
posthog-node: ^5.40.0
"@vitejs/plugin-react": ^6.0.3
"@vitest/coverage-v8": ^4.1.10
"@vitest/ui": ^4.1.10
"@xterm/addon-canvas": ^0.7.0
"@xterm/addon-fit": 0.11.0
"@xterm/xterm": ^6.0.0
"adm-zip": 0.6.0
adm-zip: 0.5.18
jszip: ^3.10.1
"sql.js": ^1.14.2
sql.js: ^1.14.1
babel-plugin-react-compiler: ^1.0.0
bcrypt: ^6.0.0
"better-sqlite3": ^12.11.1
better-sqlite3: ^12.11.1
chroma-js: ^3.2.0
clsx: ^2.1.1
"concurrently": ^10.0.5
conventional-changelog-conventionalcommits: ^10.3.0
concurrently: ^10.0.3
conventional-changelog-conventionalcommits: ^10.2.0
cookie: 2.0.1
cookies: ^0.9.1
croner: 10.0.1
@@ -124,104 +124,79 @@ catalog:
drizzle-kit: ^0.31.10
drizzle-orm: ^0.45.2
drizzle-zod: ^0.8.3
"esbuild": ^0.28.2
"fast-xml-parser": ^5.10.1
"fastify": ^5.12.0
esbuild: ^0.28.0
fast-xml-parser: ^5.9.3
fastify: ^5.9.0
flag-icons: ^7.5.0
glob: ^13.0.6
ical.js: ^2.2.1
"ioredis": 6.0.0
"isomorphic-dompurify": ^3.22.0
"jotai": ^2.20.2
jsdom: ^30.0.1
ioredis: 5.11.1
isomorphic-dompurify: ^3.18.0
jotai: ^2.20.1
jsdom: ^29.1.1
json5: ^2.2.3
jsonpath-plus: ^10.4.0
"ldapts": 9.0.0
ldapts: 8.1.8
lodash.clonedeep: ^4.5.0
"lucide-react": ^1.31.0
lucide-react: ^1.24.0
"mantine-contextmenu": ^9.3.1
"mantine-datatable": ^9.4.0
mantine-datatable: ^9.3.1
mantine-react-table: npm:mantine-react-table-open@9.0.3
"mcp-handler": ^1.1.0
mcp-handler: ^1.1.0
maria2: ^0.4.1
"mysql2": 3.23.3
"next": 16.3.1
mysql2: 3.22.6
next: 16.3.0-preview.9
next-auth: 5.0.0-beta.31
"next-intl": 4.13.6
next-intl: 4.13.1
node-loader: ^2.1.0
node-fetch: ^2.7.0
"@types/node-fetch": ^2.6.13
octokit: ^5.0.5
oxfmt: ^0.63.0
oxlint: ^1.78.0
"pg": ^8.23.0
oxfmt: ^0.58.0
oxlint: ^1.73.0
pg: ^8.22.0
postcss-preset-mantine: ^1.18.0
prismjs: ^1.30.0
proxmox-api: 1.1.1
react: 19.2.8
react-dom: 19.2.8
react: 19.2.7
react-dom: 19.2.7
react-error-boundary: ^6.1.2
react-jsx-parser: ^2.4.1
react-markdown: ^10.1.0
react-quill-new: ^3.8.3
react-simple-code-editor: ^0.14.1
"recharts": ^3.10.1
"sass": ^1.102.0
semantic-release: ^25.0.9
recharts: ^3.9.1
sass: ^1.101.0
semantic-release: ^25.0.5
semver-parser: ^4.2.1
superjson: 2.2.6
svgson: ^5.3.1
testcontainers: ^12.1.0
"timezones-ical-library": ^2.3.1
testcontainers: ^12.0.4
timezones-ical-library: ^2.2.1
trpc-to-mcp: ^1.3.2
trpc-to-openapi: ^3.3.0
"tsdav": ^2.3.1
tsx: 4.23.12
turbo: ^2.10.10
tsdav: ^2.3.0
tsx: 4.20.4
turbo: ^2.10.4
typescript: 7.0.2
vite: ^8.2.1
"undici": 8.10.0
vite: ^8.1.4
undici: 7.28.0
use-deep-compare-effect: ^1.8.1
"video.js": ^8.24.0
video.js: ^8.23.9
vite-tsconfig-paths: ^6.1.1
vitest: ^4.1.10
winston: 3.19.0
"ws": ^8.21.3
ws: ^8.21.0
xml2js: ^0.6.2
yaml: 2.9.0
zod: ^4.4.3
"zod-form-data": ^3.0.3
zod-form-data: ^3.0.2
zod-validation-error: ^5.0.0
"@docusaurus/module-type-aliases": ^3.10.2
"@docusaurus/tsconfig": ^3.10.2
"@docusaurus/types": ^3.10.2
"@fec/remark-a11y-emoji": ^4.0.2
"@tsconfig/docusaurus": ^2.0.9
cheerio: ^1.2.0
"@docusaurus/core": ^3.10.2
"@docusaurus/faster": ^3.10.2
"@docusaurus/plugin-content-docs": ^3.10.2
"@docusaurus/plugin-sitemap": ^3.10.2
"@docusaurus/preset-classic": ^3.10.2
"@mdx-js/react": ^3.1.1
"@nivo/line": ^0.99.0
"@signalwire/docusaurus-plugin-llms-txt": ^1.2.2
"@tailwindcss/postcss": ^4.3.3
docusaurus-plugin-image-zoom: ^3.0.1
leader-line-new: ^1.1.9
postcss: ^8.5.26
posthog-docusaurus: ^2.0.5
prism-react-renderer: ^2.4.1
random-word-slugs: ^0.1.7
swiper: ^14.1.0
tailwindcss: ^4.3.3
catalogMode: strict
nodeLinker: hoisted
strictPeerDependencies: false
engineStrict: true
verifyDepsBeforeRun: warn
minimumReleaseAge: 0
minimumReleaseAge: 4320
minimumReleaseAgeExclude:
- next
- "@next/*"
@@ -236,8 +211,8 @@ minimumReleaseAgeExclude:
# Renovate security update: esbuild@0.28.1
- esbuild@0.28.1
overrides:
"@babel/helpers@<7.26.10": ">=7.29.7 <8"
"@babel/runtime@<7.26.10": ">=7.29.7 <8"
"@babel/helpers@<7.26.10": ">=7.29.7"
"@babel/runtime@<7.26.10": ">=7.29.7"
"axios@>=1.0.0 <1.8.2": ">=1.18.1"
"brace-expansion@>=2.0.0 <=2.0.1": ">=5.0.7"
"brace-expansion@>=1.0.0 <=1.1.11": ">=5.0.7"
+1 -5
View File
@@ -5,11 +5,7 @@ import { parse } from "@babel/parser";
export const getRuntimeModuleSpecifiers = (sourceText: string, fileName: string) => {
try {
const ast = parse(sourceText, {
sourceFilename: fileName,
sourceType: "module",
plugins: ["typescript", ...(fileName.endsWith(".tsx") || fileName.endsWith(".jsx") ? ["jsx"] : [])],
});
const ast = parse(sourceText, { sourceFilename: fileName, sourceType: "module", plugins: ["typescript", "jsx"] });
return ast.program.body.flatMap((statement) => {
if (statement.type === "ImportDeclaration") {
if (statement.importKind === "type" || statement.importKind === "typeof") return [];
+25 -8
View File
@@ -7,14 +7,31 @@ export PGID=${PGID:-0}
echo "Starting with UID='$PUID', GID='$PGID'"
if [ "${PUID}" != "0" ] || [ "${PGID}" != "0" ]; then
# The below command will change the owner of all files in the /app directory (except node_modules) to the new UID and GID
echo "Changing owner to $PUID:$PGID, this will take about 10 seconds..."
find . -name 'node_modules' -prune -o -mindepth 1 -maxdepth 1 -exec chown -R $PUID:$PGID {} +
chown -R $PUID:$PGID /var/cache/nginx
chown -R $PUID:$PGID /var/log/nginx
chown -R $PUID:$PGID /var/lib/nginx
chown -R $PUID:$PGID /run/nginx/nginx.pid
chown -R $PUID:$PGID /etc/nginx
# Only chown the paths the non-root user must actually write to at runtime.
# Recursively chowning the whole /app tree is extremely slow on network- or
# HDD-backed storage (e.g. TrueNAS Apps), because the Next.js standalone
# bundle and its node_modules contain tens of thousands of tiny files. This
# turned a ~10s step into 15+ minutes for some users (#2190). Application
# code, migrations and native modules are only read/executed, so they stay
# owned by root and do not need chowning.
echo "Changing owner to $PUID:$PGID..."
# Application data volume (db, redis dumps, trusted certificates).
# Ensure the known subdirectories exist and migrate ownership of any
# existing content, so a persistent volume with stale or root-owned
# entries stays writable after a PUID/PGID change. These trees are small
# (a single sqlite db, a redis dump, a few certs), unlike the /app tree.
mkdir -p /appdata/db /appdata/redis /appdata/trusted-certificates
chown "${PUID}:${PGID}" /appdata
chown -R "${PUID}:${PGID}" /appdata/db /appdata/redis /appdata/trusted-certificates
# Next.js runtime cache (image optimization, fetch cache, ...)
mkdir -p /app/apps/nextjs/.next/cache
chown -R "${PUID}:${PGID}" /app/apps/nextjs/.next/cache
# nginx runtime directories and the rendered configuration
chown -R "${PUID}:${PGID}" /var/cache/nginx
chown -R "${PUID}:${PGID}" /var/log/nginx
chown -R "${PUID}:${PGID}" /var/lib/nginx
chown -R "${PUID}:${PGID}" /run/nginx/nginx.pid
chown -R "${PUID}:${PGID}" /etc/nginx
echo "Changing owner to $PUID:$PGID, done."
fi
+9 -9
View File
@@ -5,7 +5,7 @@ go 1.25.0
require (
charm.land/bubbles/v2 v2.1.1
charm.land/bubbletea/v2 v2.0.8
charm.land/lipgloss/v2 v2.0.6
charm.land/lipgloss/v2 v2.0.5
github.com/atotto/clipboard v0.1.4
github.com/spf13/cobra v1.10.2
)
@@ -13,20 +13,20 @@ require (
require (
github.com/charmbracelet/colorprofile v0.4.3 // indirect
github.com/charmbracelet/harmonica v0.2.0 // indirect
github.com/charmbracelet/ultraviolet v0.0.0-20260812204455-68fa937c71be // indirect
github.com/charmbracelet/x/ansi v0.11.8 // indirect
github.com/charmbracelet/ultraviolet v0.0.0-20260703014108-f5a850f9c2b7 // indirect
github.com/charmbracelet/x/ansi v0.11.7 // indirect
github.com/charmbracelet/x/term v0.2.2 // indirect
github.com/charmbracelet/x/termios v0.1.1 // indirect
github.com/charmbracelet/x/windows v0.2.2 // indirect
github.com/clipperhouse/displaywidth v0.11.0 // indirect
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.4.1 // indirect
github.com/mattn/go-runewidth v0.0.27 // indirect
github.com/lucasb-eyer/go-colorful v1.4.0 // indirect
github.com/mattn/go-runewidth v0.0.24 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/xo/terminfo v1.0.0 // indirect
golang.org/x/sync v0.22.0 // indirect
golang.org/x/sys v0.47.0 // indirect
github.com/spf13/pflag v1.0.9 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/sync v0.21.0 // indirect
golang.org/x/sys v0.46.0 // indirect
)
+17 -18
View File
@@ -2,8 +2,8 @@ charm.land/bubbles/v2 v2.1.1 h1:7r55WzBxpo/R3z98hGmY7KKPd3ET6vsf0Fb9sDHOV60=
charm.land/bubbles/v2 v2.1.1/go.mod h1:GE6M31gaWZVXzGw73OeuTTgy4lX+OtkH0E5ymnNsHxo=
charm.land/bubbletea/v2 v2.0.8 h1:SxTJMhCAI3lbPmy4SgX5LWZ24AdINr4I6UEqzZvYJuY=
charm.land/bubbletea/v2 v2.0.8/go.mod h1:2SkdgoTXluXJHOUwAoRlRXF/28vklb1rFl6GcgV1/ss=
charm.land/lipgloss/v2 v2.0.6 h1:EaGKeuA8FvF+v2BT5VmZd2LoYLaMZJXA5n34th8nCIQ=
charm.land/lipgloss/v2 v2.0.6/go.mod h1:ipDDJNSGa1hlwDtSfW1s2/xR8Vdhbut4PXh2zEKZd0Q=
charm.land/lipgloss/v2 v2.0.5 h1:kbNxgeeUOYv5J0YdpxFjfvf3dFvqH8Aci4zB6xqFtrY=
charm.land/lipgloss/v2 v2.0.5/go.mod h1:9oqhxt4yxIMe6q5A4kHr44DremZk7J9UNh74GlWa5nc=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o=
@@ -12,10 +12,10 @@ github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex
github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q=
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
github.com/charmbracelet/ultraviolet v0.0.0-20260812204455-68fa937c71be h1:qEvkJy1sjJXP+yf8IH2o13NV+Rh4nIUHjaLNJ+pWxpI=
github.com/charmbracelet/ultraviolet v0.0.0-20260812204455-68fa937c71be/go.mod h1:nAw0d9PhFp1qdzi2xhQU5YOu5sVpDIHWlaW2Uz/bCro=
github.com/charmbracelet/x/ansi v0.11.8 h1:JMFwp0CgDC2+jcOB162HH5k7I3FVbgFSMMYg7dSPBQQ=
github.com/charmbracelet/x/ansi v0.11.8/go.mod h1:ZNN+3mXny/516oTQPLMPIBeSINvNJJQ8uQXDgbeJxY0=
github.com/charmbracelet/ultraviolet v0.0.0-20260703014108-f5a850f9c2b7 h1:3FmWoGNWK4STvqg0O0Aeav2T7rodWJAPeF0QpH+8gFw=
github.com/charmbracelet/ultraviolet v0.0.0-20260703014108-f5a850f9c2b7/go.mod h1:f/jRa757WUmaOZrbPspXymbg/GnbF+rwe4OLsG7aXYo=
github.com/charmbracelet/x/ansi v0.11.7 h1:kzv1kJvjg2S3r9KHo8hDdHFQLEqn4RBCb39dAYC84jI=
github.com/charmbracelet/x/ansi v0.11.7/go.mod h1:9qGpnAVYz+8ACONkZBUWPtL7lulP9No6p1epAihUZwQ=
github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f h1:pk6gmGpCE7F3FcjaOEKYriCvpmIN4+6OS/RD0vm4uIA=
github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f/go.mod h1:IfZAMTHB6XkZSeXUqriemErjAWCCzT0LwjKFYCZyw0I=
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
@@ -31,10 +31,10 @@ github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJ
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/lucasb-eyer/go-colorful v1.4.1 h1:1EO+WB73+EH8EVbzlrG3KLAfEypQWVHIBqlTf+2hNss=
github.com/lucasb-eyer/go-colorful v1.4.1/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-runewidth v0.0.27 h1:Feg/Oou5zI/wnpgDF6omIU0OokC9GxLC/WRknhVlIR0=
github.com/mattn/go-runewidth v0.0.27/go.mod h1:3qAiGCV4Koz/yuveO58qUefmUTRm8r0IGEXZ9jeHp/8=
github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4=
github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-runewidth v0.0.24 h1:cpokDiIn0MGnhdHwuWnJBITySJ20QyNGnY2kR/ay2DU=
github.com/mattn/go-runewidth v0.0.24/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
@@ -42,16 +42,15 @@ github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/xo/terminfo v1.0.0 h1:2ZpYzqWzyyytjk3TP6aJVDhkMAkc99/1xKQdA3TDTBY=
github.com/xo/terminfo v1.0.0/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=