nikcli 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-typecheck.log +1 -0
- package/AGENTS.md +27 -0
- package/Dockerfile +18 -0
- package/README.md +15 -0
- package/bin/nikcli +84 -0
- package/config.json +13 -0
- package/docs/tailscale-mobile/01-tailscale-setup.md +94 -0
- package/docs/tailscale-mobile/02-host-setup.md +115 -0
- package/docs/tailscale-mobile/03-phone-and-serve.md +134 -0
- package/docs/tailscale-mobile/README.md +59 -0
- package/examples/README.md +54 -0
- package/package.json +147 -0
- package/parsers-config.ts +253 -0
- package/script/build.ts +179 -0
- package/script/postinstall.mjs +125 -0
- package/script/publish-registries.ts +187 -0
- package/script/publish.ts +100 -0
- package/script/schema.ts +47 -0
- package/script/seed-e2e.ts +50 -0
- package/sequential-prancing-forest.md +373 -0
- package/src/acp/README.md +164 -0
- package/src/acp/agent.ts +1303 -0
- package/src/acp/session.ts +105 -0
- package/src/acp/types.ts +22 -0
- package/src/agent/agent.ts +528 -0
- package/src/agent/generate.txt +32 -0
- package/src/agent/prompt/compaction.txt +14 -0
- package/src/agent/prompt/explore.txt +18 -0
- package/src/agent/prompt/summary.txt +11 -0
- package/src/agent/prompt/title.txt +44 -0
- package/src/auth/index.ts +73 -0
- package/src/bun/index.ts +119 -0
- package/src/bun/registry.ts +54 -0
- package/src/bus/bus-event.ts +43 -0
- package/src/bus/global.ts +10 -0
- package/src/bus/index.ts +105 -0
- package/src/chatbot/handlers.ts +150 -0
- package/src/chatbot/index.ts +132 -0
- package/src/cli/bootstrap.ts +17 -0
- package/src/cli/cmd/acp.ts +69 -0
- package/src/cli/cmd/ads.ts +377 -0
- package/src/cli/cmd/agent.ts +259 -0
- package/src/cli/cmd/auth.ts +400 -0
- package/src/cli/cmd/chatbot.ts +420 -0
- package/src/cli/cmd/cmd.ts +7 -0
- package/src/cli/cmd/companion.ts +81 -0
- package/src/cli/cmd/connectors.ts +593 -0
- package/src/cli/cmd/debug/agent.ts +166 -0
- package/src/cli/cmd/debug/config.ts +16 -0
- package/src/cli/cmd/debug/file.ts +97 -0
- package/src/cli/cmd/debug/index.ts +48 -0
- package/src/cli/cmd/debug/lsp.ts +52 -0
- package/src/cli/cmd/debug/ripgrep.ts +87 -0
- package/src/cli/cmd/debug/scrap.ts +16 -0
- package/src/cli/cmd/debug/skill.ts +16 -0
- package/src/cli/cmd/debug/snapshot.ts +52 -0
- package/src/cli/cmd/export.ts +88 -0
- package/src/cli/cmd/generate.ts +38 -0
- package/src/cli/cmd/github.ts +412 -0
- package/src/cli/cmd/image-model.ts +128 -0
- package/src/cli/cmd/import.ts +201 -0
- package/src/cli/cmd/lovable.ts +128 -0
- package/src/cli/cmd/mcp.ts +738 -0
- package/src/cli/cmd/mobile.ts +223 -0
- package/src/cli/cmd/models.ts +77 -0
- package/src/cli/cmd/plug.ts +231 -0
- package/src/cli/cmd/pr.ts +104 -0
- package/src/cli/cmd/rag-model.ts +167 -0
- package/src/cli/cmd/remote.ts +416 -0
- package/src/cli/cmd/run.ts +589 -0
- package/src/cli/cmd/serve.ts +51 -0
- package/src/cli/cmd/session.ts +133 -0
- package/src/cli/cmd/speak-model.ts +204 -0
- package/src/cli/cmd/stats.ts +402 -0
- package/src/cli/cmd/tui/app.tsx +841 -0
- package/src/cli/cmd/tui/attach.ts +31 -0
- package/src/cli/cmd/tui/component/border.tsx +75 -0
- package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
- package/src/cli/cmd/tui/component/dialog-command.tsx +172 -0
- package/src/cli/cmd/tui/component/dialog-config.tsx +291 -0
- package/src/cli/cmd/tui/component/dialog-connectors.tsx +440 -0
- package/src/cli/cmd/tui/component/dialog-image-model.tsx +97 -0
- package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
- package/src/cli/cmd/tui/component/dialog-model.tsx +234 -0
- package/src/cli/cmd/tui/component/dialog-provider.tsx +260 -0
- package/src/cli/cmd/tui/component/dialog-rag-model.tsx +217 -0
- package/src/cli/cmd/tui/component/dialog-remote.tsx +489 -0
- package/src/cli/cmd/tui/component/dialog-session-list.tsx +170 -0
- package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
- package/src/cli/cmd/tui/component/dialog-settings/index.tsx +59 -0
- package/src/cli/cmd/tui/component/dialog-settings/prompt.tsx +40 -0
- package/src/cli/cmd/tui/component/dialog-settings/sidebar.tsx +39 -0
- package/src/cli/cmd/tui/component/dialog-settings/spinner.tsx +62 -0
- package/src/cli/cmd/tui/component/dialog-settings/ui.tsx +58 -0
- package/src/cli/cmd/tui/component/dialog-skills.tsx +117 -0
- package/src/cli/cmd/tui/component/dialog-speak-model.tsx +304 -0
- package/src/cli/cmd/tui/component/dialog-stash.tsx +87 -0
- package/src/cli/cmd/tui/component/dialog-status.tsx +165 -0
- package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
- package/src/cli/cmd/tui/component/dialog-theme-create.tsx +717 -0
- package/src/cli/cmd/tui/component/dialog-theme-list.tsx +52 -0
- package/src/cli/cmd/tui/component/dialog-workspace-list.tsx +350 -0
- package/src/cli/cmd/tui/component/error-component.tsx +91 -0
- package/src/cli/cmd/tui/component/logo.tsx +103 -0
- package/src/cli/cmd/tui/component/plugin-route-missing.tsx +14 -0
- package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +669 -0
- package/src/cli/cmd/tui/component/prompt/frecency.tsx +89 -0
- package/src/cli/cmd/tui/component/prompt/history.tsx +108 -0
- package/src/cli/cmd/tui/component/prompt/index.tsx +2165 -0
- package/src/cli/cmd/tui/component/prompt/stash.tsx +63 -0
- package/src/cli/cmd/tui/component/spinner.tsx +24 -0
- package/src/cli/cmd/tui/component/startup-loading.tsx +63 -0
- package/src/cli/cmd/tui/component/table/markdown-table.tsx +267 -0
- package/src/cli/cmd/tui/component/table-db/db/connections.ts +75 -0
- package/src/cli/cmd/tui/component/table-db/db/db-connection.ts +223 -0
- package/src/cli/cmd/tui/component/table-db/db/db-preview.ts +202 -0
- package/src/cli/cmd/tui/component/table-db/db/factory.ts +77 -0
- package/src/cli/cmd/tui/component/table-db/db/index.ts +9 -0
- package/src/cli/cmd/tui/component/table-db/db/mysql-connection.ts +330 -0
- package/src/cli/cmd/tui/component/table-db/db/postgres-connection.ts +338 -0
- package/src/cli/cmd/tui/component/table-db/db/sqlite-connection.ts +302 -0
- package/src/cli/cmd/tui/component/table-db/db/types.ts +108 -0
- package/src/cli/cmd/tui/component/table-db/table/dbedit-hooks.ts +74 -0
- package/src/cli/cmd/tui/component/table-db/table/index.ts +15 -0
- package/src/cli/cmd/tui/component/table-db/table/table-events.ts +54 -0
- package/src/cli/cmd/tui/component/table-db/table/table-formatters.ts +191 -0
- package/src/cli/cmd/tui/component/table-db/table/table-hooks.ts +105 -0
- package/src/cli/cmd/tui/component/table-db/table/table-keyboard-handler.ts +255 -0
- package/src/cli/cmd/tui/component/table-db/table/table-layout-engine.ts +208 -0
- package/src/cli/cmd/tui/component/table-db/table/table-renderable.ts +486 -0
- package/src/cli/cmd/tui/component/table-db/table/table-selection-manager.ts +136 -0
- package/src/cli/cmd/tui/component/table-db/table/table-state.ts +198 -0
- package/src/cli/cmd/tui/component/table-db/table/types.ts +69 -0
- package/src/cli/cmd/tui/component/table-db/ui/db-visualizer.tsx +71 -0
- package/src/cli/cmd/tui/component/table-db/ui/index.ts +2 -0
- package/src/cli/cmd/tui/component/table-db/ui/table-renderer.ts +607 -0
- package/src/cli/cmd/tui/component/textarea-keybindings.ts +73 -0
- package/src/cli/cmd/tui/component/tips.tsx +195 -0
- package/src/cli/cmd/tui/component/todo-item.tsx +32 -0
- package/src/cli/cmd/tui/context/args.tsx +14 -0
- package/src/cli/cmd/tui/context/directory.ts +13 -0
- package/src/cli/cmd/tui/context/exit.tsx +24 -0
- package/src/cli/cmd/tui/context/helper.tsx +25 -0
- package/src/cli/cmd/tui/context/keybind.tsx +102 -0
- package/src/cli/cmd/tui/context/kv.tsx +52 -0
- package/src/cli/cmd/tui/context/local.tsx +458 -0
- package/src/cli/cmd/tui/context/plugin-keybinds.ts +41 -0
- package/src/cli/cmd/tui/context/prompt.tsx +18 -0
- package/src/cli/cmd/tui/context/route.tsx +54 -0
- package/src/cli/cmd/tui/context/sdk.tsx +128 -0
- package/src/cli/cmd/tui/context/server.tsx +8 -0
- package/src/cli/cmd/tui/context/sync.tsx +510 -0
- package/src/cli/cmd/tui/context/theme/abyss.json +233 -0
- package/src/cli/cmd/tui/context/theme/apple.json +235 -0
- package/src/cli/cmd/tui/context/theme/arctic.json +232 -0
- package/src/cli/cmd/tui/context/theme/aura.json +69 -0
- package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
- package/src/cli/cmd/tui/context/theme/ayuai.json +229 -0
- package/src/cli/cmd/tui/context/theme/blood.json +229 -0
- package/src/cli/cmd/tui/context/theme/carbonfox.json +248 -0
- package/src/cli/cmd/tui/context/theme/catmoe.json +235 -0
- package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +233 -0
- package/src/cli/cmd/tui/context/theme/catppuccin-latte.json +233 -0
- package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +233 -0
- package/src/cli/cmd/tui/context/theme/catppuccin.json +259 -0
- package/src/cli/cmd/tui/context/theme/charcoal.json +230 -0
- package/src/cli/cmd/tui/context/theme/chromatic.json +235 -0
- package/src/cli/cmd/tui/context/theme/cobalt2.json +228 -0
- package/src/cli/cmd/tui/context/theme/cosmic.json +234 -0
- package/src/cli/cmd/tui/context/theme/cursor.json +249 -0
- package/src/cli/cmd/tui/context/theme/cyber.json +235 -0
- package/src/cli/cmd/tui/context/theme/dawnfox.json +229 -0
- package/src/cli/cmd/tui/context/theme/dimension.json +235 -0
- package/src/cli/cmd/tui/context/theme/dracula-official.json +222 -0
- package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
- package/src/cli/cmd/tui/context/theme/dream.json +235 -0
- package/src/cli/cmd/tui/context/theme/duo.json +235 -0
- package/src/cli/cmd/tui/context/theme/dusk.json +235 -0
- package/src/cli/cmd/tui/context/theme/ebony.json +232 -0
- package/src/cli/cmd/tui/context/theme/equilibrium.json +232 -0
- package/src/cli/cmd/tui/context/theme/ethereal.json +235 -0
- package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
- package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
- package/src/cli/cmd/tui/context/theme/fusion.json +235 -0
- package/src/cli/cmd/tui/context/theme/ghost.json +235 -0
- package/src/cli/cmd/tui/context/theme/github-dark.json +229 -0
- package/src/cli/cmd/tui/context/theme/github-dimmed.json +231 -0
- package/src/cli/cmd/tui/context/theme/github-light.json +229 -0
- package/src/cli/cmd/tui/context/theme/github.json +233 -0
- package/src/cli/cmd/tui/context/theme/glass.json +235 -0
- package/src/cli/cmd/tui/context/theme/gold.json +235 -0
- package/src/cli/cmd/tui/context/theme/gone.json +234 -0
- package/src/cli/cmd/tui/context/theme/greyscale.json +229 -0
- package/src/cli/cmd/tui/context/theme/gruvbox.json +242 -0
- package/src/cli/cmd/tui/context/theme/hacker.json +229 -0
- package/src/cli/cmd/tui/context/theme/holo.json +235 -0
- package/src/cli/cmd/tui/context/theme/ink.json +235 -0
- package/src/cli/cmd/tui/context/theme/jet.json +233 -0
- package/src/cli/cmd/tui/context/theme/kanagawa.json +227 -0
- package/src/cli/cmd/tui/context/theme/lavender.json +236 -0
- package/src/cli/cmd/tui/context/theme/lightph.json +235 -0
- package/src/cli/cmd/tui/context/theme/lucent-orng.json +237 -0
- package/src/cli/cmd/tui/context/theme/material-ocean.json +230 -0
- package/src/cli/cmd/tui/context/theme/material.json +235 -0
- package/src/cli/cmd/tui/context/theme/matrix.json +227 -0
- package/src/cli/cmd/tui/context/theme/mercury.json +245 -0
- package/src/cli/cmd/tui/context/theme/midnight.json +235 -0
- package/src/cli/cmd/tui/context/theme/modern.json +235 -0
- package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
- package/src/cli/cmd/tui/context/theme/muted.json +229 -0
- package/src/cli/cmd/tui/context/theme/neon.json +229 -0
- package/src/cli/cmd/tui/context/theme/neonfusion.json +235 -0
- package/src/cli/cmd/tui/context/theme/neutral.json +235 -0
- package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
- package/src/cli/cmd/tui/context/theme/nikcli.json +245 -0
- package/src/cli/cmd/tui/context/theme/nord.json +223 -0
- package/src/cli/cmd/tui/context/theme/nordic.json +235 -0
- package/src/cli/cmd/tui/context/theme/nova.json +235 -0
- package/src/cli/cmd/tui/context/theme/obsidian.json +234 -0
- package/src/cli/cmd/tui/context/theme/one-dark.json +231 -0
- package/src/cli/cmd/tui/context/theme/one-pro.json +229 -0
- package/src/cli/cmd/tui/context/theme/onyx.json +233 -0
- package/src/cli/cmd/tui/context/theme/orng.json +249 -0
- package/src/cli/cmd/tui/context/theme/osaka-jade.json +240 -0
- package/src/cli/cmd/tui/context/theme/oxocarbon.json +229 -0
- package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
- package/src/cli/cmd/tui/context/theme/poimandres.json +230 -0
- package/src/cli/cmd/tui/context/theme/prism.json +235 -0
- package/src/cli/cmd/tui/context/theme/radiant.json +235 -0
- package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
- package/src/cli/cmd/tui/context/theme/shadow.json +235 -0
- package/src/cli/cmd/tui/context/theme/silicon.json +235 -0
- package/src/cli/cmd/tui/context/theme/slate.json +233 -0
- package/src/cli/cmd/tui/context/theme/soft.json +235 -0
- package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
- package/src/cli/cmd/tui/context/theme/spectrum.json +235 -0
- package/src/cli/cmd/tui/context/theme/starlight.json +233 -0
- package/src/cli/cmd/tui/context/theme/sunrise.json +235 -0
- package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
- package/src/cli/cmd/tui/context/theme/tech.json +235 -0
- package/src/cli/cmd/tui/context/theme/tokyonight-storm.json +245 -0
- package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
- package/src/cli/cmd/tui/context/theme/vapor.json +235 -0
- package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
- package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
- package/src/cli/cmd/tui/context/theme/vivid.json +232 -0
- package/src/cli/cmd/tui/context/theme/void.json +235 -0
- package/src/cli/cmd/tui/context/theme/vscode.json +235 -0
- package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
- package/src/cli/cmd/tui/context/theme/zinc.json +236 -0
- package/src/cli/cmd/tui/context/theme.tsx +1303 -0
- package/src/cli/cmd/tui/event.ts +48 -0
- package/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx +152 -0
- package/src/cli/cmd/tui/feature-plugins/home/tips.tsx +50 -0
- package/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx +63 -0
- package/src/cli/cmd/tui/feature-plugins/sidebar/files.tsx +62 -0
- package/src/cli/cmd/tui/feature-plugins/sidebar/footer.tsx +93 -0
- package/src/cli/cmd/tui/feature-plugins/sidebar/lsp.tsx +66 -0
- package/src/cli/cmd/tui/feature-plugins/sidebar/mcp.tsx +96 -0
- package/src/cli/cmd/tui/feature-plugins/sidebar/todo.tsx +48 -0
- package/src/cli/cmd/tui/feature-plugins/system/plugins.tsx +288 -0
- package/src/cli/cmd/tui/plugin/api.tsx +407 -0
- package/src/cli/cmd/tui/plugin/index.ts +3 -0
- package/src/cli/cmd/tui/plugin/internal.ts +25 -0
- package/src/cli/cmd/tui/plugin/runtime.ts +1048 -0
- package/src/cli/cmd/tui/plugin/slots.tsx +61 -0
- package/src/cli/cmd/tui/routes/home.tsx +153 -0
- package/src/cli/cmd/tui/routes/session/dbedit.tsx +474 -0
- package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +65 -0
- package/src/cli/cmd/tui/routes/session/dialog-message.tsx +110 -0
- package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +105 -0
- package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +47 -0
- package/src/cli/cmd/tui/routes/session/footer.tsx +75 -0
- package/src/cli/cmd/tui/routes/session/header.tsx +177 -0
- package/src/cli/cmd/tui/routes/session/index.tsx +2280 -0
- package/src/cli/cmd/tui/routes/session/permission.tsx +540 -0
- package/src/cli/cmd/tui/routes/session/question.tsx +435 -0
- package/src/cli/cmd/tui/routes/session/sidebar.tsx +313 -0
- package/src/cli/cmd/tui/thread.ts +174 -0
- package/src/cli/cmd/tui/ui/dialog-alert.tsx +57 -0
- package/src/cli/cmd/tui/ui/dialog-confirm.tsx +83 -0
- package/src/cli/cmd/tui/ui/dialog-export-options.tsx +204 -0
- package/src/cli/cmd/tui/ui/dialog-help.tsx +38 -0
- package/src/cli/cmd/tui/ui/dialog-prompt.tsx +102 -0
- package/src/cli/cmd/tui/ui/dialog-select.tsx +389 -0
- package/src/cli/cmd/tui/ui/dialog.tsx +180 -0
- package/src/cli/cmd/tui/ui/link.tsx +34 -0
- package/src/cli/cmd/tui/ui/spinner.ts +368 -0
- package/src/cli/cmd/tui/ui/toast.tsx +138 -0
- package/src/cli/cmd/tui/util/clipboard.ts +154 -0
- package/src/cli/cmd/tui/util/editor.ts +32 -0
- package/src/cli/cmd/tui/util/signal.ts +7 -0
- package/src/cli/cmd/tui/util/terminal.ts +114 -0
- package/src/cli/cmd/tui/util/transcript.ts +98 -0
- package/src/cli/cmd/tui/win32.ts +110 -0
- package/src/cli/cmd/tui/worker.ts +156 -0
- package/src/cli/cmd/uninstall.ts +357 -0
- package/src/cli/cmd/upgrade.ts +72 -0
- package/src/cli/cmd/web.ts +87 -0
- package/src/cli/cmd/workspace-serve.ts +16 -0
- package/src/cli/error.ts +57 -0
- package/src/cli/network.ts +55 -0
- package/src/cli/remote/index.ts +36 -0
- package/src/cli/remote/notifications.ts +104 -0
- package/src/cli/remote/qr-renderer.ts +86 -0
- package/src/cli/remote/remote-service.ts +757 -0
- package/src/cli/remote/session-manager.ts +284 -0
- package/src/cli/remote/subagent-hooks.ts +151 -0
- package/src/cli/remote/types.ts +121 -0
- package/src/cli/ui.ts +96 -0
- package/src/cli/upgrade.ts +25 -0
- package/src/command/index.ts +174 -0
- package/src/command/template/initialize.txt +10 -0
- package/src/command/template/review.txt +99 -0
- package/src/config/config.ts +1760 -0
- package/src/config/markdown.ts +88 -0
- package/src/config/migrate-tui-config.ts +155 -0
- package/src/config/paths.ts +174 -0
- package/src/config/tui-schema.ts +36 -0
- package/src/config/tui.ts +209 -0
- package/src/connectors/api/base.ts +75 -0
- package/src/connectors/api/figma.ts +103 -0
- package/src/connectors/api/github.ts +247 -0
- package/src/connectors/api/lovable.ts +126 -0
- package/src/connectors/api/slack.ts +137 -0
- package/src/connectors/auth.ts +68 -0
- package/src/connectors/cache.ts +119 -0
- package/src/connectors/credentials.ts +81 -0
- package/src/connectors/index.ts +202 -0
- package/src/connectors/registry.ts +358 -0
- package/src/docs/context.ts +120 -0
- package/src/docs/library.ts +189 -0
- package/src/env/index.ts +26 -0
- package/src/file/ignore.ts +83 -0
- package/src/file/index.ts +411 -0
- package/src/file/ripgrep.ts +402 -0
- package/src/file/time.ts +65 -0
- package/src/file/watcher.ts +127 -0
- package/src/flag/flag.ts +128 -0
- package/src/format/formatter.ts +356 -0
- package/src/format/index.ts +137 -0
- package/src/global/index.ts +57 -0
- package/src/id/id.ts +83 -0
- package/src/ide/index.ts +76 -0
- package/src/index.ts +184 -0
- package/src/installation/index.ts +246 -0
- package/src/lsp/client.ts +250 -0
- package/src/lsp/index.ts +483 -0
- package/src/lsp/language.ts +119 -0
- package/src/lsp/server.ts +2046 -0
- package/src/mcp/auth.ts +121 -0
- package/src/mcp/index.ts +860 -0
- package/src/mcp/oauth-callback.ts +198 -0
- package/src/mcp/oauth-provider.ts +148 -0
- package/src/mobile/auth.ts +97 -0
- package/src/mobile/github-repo.ts +185 -0
- package/src/patch/index.ts +631 -0
- package/src/permission/arity.ts +150 -0
- package/src/permission/dbedit.ts +236 -0
- package/src/permission/index.ts +210 -0
- package/src/permission/next.ts +287 -0
- package/src/plugin/codex.ts +493 -0
- package/src/plugin/copilot.ts +261 -0
- package/src/plugin/index.ts +714 -0
- package/src/plugin/install.ts +379 -0
- package/src/plugin/meta.ts +165 -0
- package/src/plugin/shared.ts +188 -0
- package/src/project/bootstrap.ts +35 -0
- package/src/project/instance.ts +84 -0
- package/src/project/project.ts +373 -0
- package/src/project/state.ts +66 -0
- package/src/project/vcs.ts +76 -0
- package/src/prompt/stash-store.ts +93 -0
- package/src/provider/auth.ts +147 -0
- package/src/provider/models-macro.ts +22 -0
- package/src/provider/models.ts +216 -0
- package/src/provider/provider.ts +1483 -0
- package/src/provider/sdk/openai-compatible/src/README.md +5 -0
- package/src/provider/sdk/openai-compatible/src/index.ts +2 -0
- package/src/provider/sdk/openai-compatible/src/openai-compatible-provider.ts +100 -0
- package/src/provider/sdk/openai-compatible/src/responses/convert-to-openai-responses-input.ts +303 -0
- package/src/provider/sdk/openai-compatible/src/responses/map-openai-responses-finish-reason.ts +22 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-config.ts +18 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-error.ts +22 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-responses-api-types.ts +207 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-responses-language-model.ts +1732 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-responses-prepare-tools.ts +177 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-responses-settings.ts +1 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/code-interpreter.ts +88 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/file-search.ts +128 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/image-generation.ts +115 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/local-shell.ts +65 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/web-search-preview.ts +104 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/web-search.ts +103 -0
- package/src/provider/transform.ts +828 -0
- package/src/pty/index.ts +241 -0
- package/src/question/index.ts +171 -0
- package/src/rag/chunk.ts +43 -0
- package/src/rag/embed.ts +179 -0
- package/src/rag/index.ts +376 -0
- package/src/rag/storage.ts +76 -0
- package/src/scheduler/index.ts +61 -0
- package/src/server/error.ts +36 -0
- package/src/server/event.ts +7 -0
- package/src/server/mdns.ts +59 -0
- package/src/server/routes/chatbot.ts +205 -0
- package/src/server/routes/companion.ts +729 -0
- package/src/server/routes/config.ts +92 -0
- package/src/server/routes/connectors.ts +121 -0
- package/src/server/routes/dbedit.ts +76 -0
- package/src/server/routes/experimental.ts +210 -0
- package/src/server/routes/file.ts +197 -0
- package/src/server/routes/global.ts +135 -0
- package/src/server/routes/mcp.ts +225 -0
- package/src/server/routes/mobile.ts +2044 -0
- package/src/server/routes/permission.ts +68 -0
- package/src/server/routes/project.ts +82 -0
- package/src/server/routes/provider.ts +235 -0
- package/src/server/routes/pty.ts +169 -0
- package/src/server/routes/question.ts +98 -0
- package/src/server/routes/session.ts +968 -0
- package/src/server/routes/tui.ts +379 -0
- package/src/server/routes/workspace.ts +104 -0
- package/src/server/server.ts +761 -0
- package/src/server/ssh.ts +207 -0
- package/src/session/auth.ts +402 -0
- package/src/session/compaction.ts +253 -0
- package/src/session/generate.ts +38 -0
- package/src/session/index.ts +598 -0
- package/src/session/llm.ts +273 -0
- package/src/session/message-v2.ts +836 -0
- package/src/session/message.ts +189 -0
- package/src/session/processor.ts +408 -0
- package/src/session/prompt/anthropic-20250930.txt +165 -0
- package/src/session/prompt/anthropic.txt +105 -0
- package/src/session/prompt/anthropic_spoof.txt +1 -0
- package/src/session/prompt/beast.txt +147 -0
- package/src/session/prompt/build-switch.txt +5 -0
- package/src/session/prompt/codex_header.txt +79 -0
- package/src/session/prompt/copilot-gpt-5.txt +143 -0
- package/src/session/prompt/gemini.txt +155 -0
- package/src/session/prompt/max-steps.txt +16 -0
- package/src/session/prompt/plan-reminder-anthropic.txt +67 -0
- package/src/session/prompt/plan.txt +25 -0
- package/src/session/prompt/qwen.txt +108 -0
- package/src/session/prompt.ts +1942 -0
- package/src/session/retry.ts +90 -0
- package/src/session/revert.ts +120 -0
- package/src/session/stats.ts +404 -0
- package/src/session/status.ts +84 -0
- package/src/session/summary.ts +184 -0
- package/src/session/system.ts +195 -0
- package/src/session/toast.tsx +105 -0
- package/src/session/todo.ts +258 -0
- package/src/session/uninstall.ts +357 -0
- package/src/share/share-next.ts +421 -0
- package/src/share/share.ts +92 -0
- package/src/shell/shell.ts +65 -0
- package/src/skill/index.ts +1 -0
- package/src/skill/skill.ts +232 -0
- package/src/snapshot/index.ts +297 -0
- package/src/storage/storage.ts +227 -0
- package/src/tool/apply_patch.ts +288 -0
- package/src/tool/apply_patch.txt +33 -0
- package/src/tool/bash.ts +252 -0
- package/src/tool/bash.txt +115 -0
- package/src/tool/batch.ts +175 -0
- package/src/tool/batch.txt +24 -0
- package/src/tool/codesearch.ts +132 -0
- package/src/tool/codesearch.txt +12 -0
- package/src/tool/context_collect.ts +152 -0
- package/src/tool/context_collect.txt +9 -0
- package/src/tool/context_diagnostics.ts +81 -0
- package/src/tool/context_diagnostics.txt +5 -0
- package/src/tool/context_related.ts +117 -0
- package/src/tool/context_related.txt +5 -0
- package/src/tool/context_search.ts +108 -0
- package/src/tool/context_search.txt +8 -0
- package/src/tool/db-diff.ts +434 -0
- package/src/tool/db-table.txt +15 -0
- package/src/tool/docs_add.ts +50 -0
- package/src/tool/docs_add.txt +5 -0
- package/src/tool/docs_context.ts +56 -0
- package/src/tool/docs_context.txt +4 -0
- package/src/tool/docs_gap_report.ts +79 -0
- package/src/tool/docs_gap_report.txt +7 -0
- package/src/tool/docs_load.ts +41 -0
- package/src/tool/docs_load.txt +4 -0
- package/src/tool/docs_request.ts +129 -0
- package/src/tool/docs_request.txt +7 -0
- package/src/tool/docs_search.ts +51 -0
- package/src/tool/docs_search.txt +6 -0
- package/src/tool/docs_unload.ts +38 -0
- package/src/tool/docs_unload.txt +5 -0
- package/src/tool/edit.ts +614 -0
- package/src/tool/edit.txt +10 -0
- package/src/tool/external-directory.ts +32 -0
- package/src/tool/generate_image.ts +174 -0
- package/src/tool/generate_image.txt +12 -0
- package/src/tool/glob.ts +79 -0
- package/src/tool/glob.txt +6 -0
- package/src/tool/grep.ts +153 -0
- package/src/tool/grep.txt +8 -0
- package/src/tool/invalid.ts +17 -0
- package/src/tool/ls.ts +116 -0
- package/src/tool/ls.txt +1 -0
- package/src/tool/lsp.ts +96 -0
- package/src/tool/lsp.txt +19 -0
- package/src/tool/memory_search.ts +141 -0
- package/src/tool/memory_search.txt +8 -0
- package/src/tool/multiedit.ts +46 -0
- package/src/tool/multiedit.txt +41 -0
- package/src/tool/plan-enter.txt +14 -0
- package/src/tool/plan-exit.txt +13 -0
- package/src/tool/plan.ts +130 -0
- package/src/tool/question.ts +33 -0
- package/src/tool/question.txt +10 -0
- package/src/tool/rag_index.ts +77 -0
- package/src/tool/rag_index.txt +10 -0
- package/src/tool/rag_reset.ts +26 -0
- package/src/tool/rag_reset.txt +4 -0
- package/src/tool/rag_search.ts +62 -0
- package/src/tool/rag_search.txt +6 -0
- package/src/tool/rag_status.ts +45 -0
- package/src/tool/rag_status.txt +4 -0
- package/src/tool/read.ts +203 -0
- package/src/tool/read.txt +12 -0
- package/src/tool/registry.ts +214 -0
- package/src/tool/skill.ts +169 -0
- package/src/tool/skill.txt +3 -0
- package/src/tool/smart_docs.ts +74 -0
- package/src/tool/smart_docs.txt +7 -0
- package/src/tool/speak/elevenlabs.ts +201 -0
- package/src/tool/speak/openrouter.ts +240 -0
- package/src/tool/speak/provider.ts +83 -0
- package/src/tool/speak.ts +440 -0
- package/src/tool/task.ts +194 -0
- package/src/tool/task.txt +60 -0
- package/src/tool/todo.ts +53 -0
- package/src/tool/todoread.txt +14 -0
- package/src/tool/todowrite.txt +167 -0
- package/src/tool/tool.ts +87 -0
- package/src/tool/tree.ts +218 -0
- package/src/tool/tree.txt +8 -0
- package/src/tool/truncation.ts +106 -0
- package/src/tool/use-connector.ts +47 -0
- package/src/tool/voice.ts +188 -0
- package/src/tool/webfetch.ts +205 -0
- package/src/tool/webfetch.txt +13 -0
- package/src/tool/websearch.ts +150 -0
- package/src/tool/websearch.txt +14 -0
- package/src/tool/write.ts +80 -0
- package/src/tool/write.txt +8 -0
- package/src/util/archive.ts +16 -0
- package/src/util/color.ts +19 -0
- package/src/util/context.ts +25 -0
- package/src/util/defer.ts +12 -0
- package/src/util/error.ts +77 -0
- package/src/util/eventloop.ts +20 -0
- package/src/util/filesystem.ts +125 -0
- package/src/util/flock.ts +329 -0
- package/src/util/fn.ts +11 -0
- package/src/util/format.ts +20 -0
- package/src/util/hash.ts +7 -0
- package/src/util/iife.ts +3 -0
- package/src/util/keybind.ts +103 -0
- package/src/util/lazy.ts +18 -0
- package/src/util/locale.ts +81 -0
- package/src/util/lock.ts +98 -0
- package/src/util/log.ts +180 -0
- package/src/util/network.ts +9 -0
- package/src/util/process.ts +15 -0
- package/src/util/queue.ts +32 -0
- package/src/util/record.ts +3 -0
- package/src/util/rpc.ts +66 -0
- package/src/util/scrap.ts +10 -0
- package/src/util/signal.ts +12 -0
- package/src/util/timeout.ts +14 -0
- package/src/util/token.ts +7 -0
- package/src/util/wildcard.ts +56 -0
- package/src/workspace/adaptors/index.ts +271 -0
- package/src/workspace/adaptors/types.ts +14 -0
- package/src/workspace/adaptors/worktree.ts +31 -0
- package/src/workspace/config.ts +19 -0
- package/src/workspace/index.ts +223 -0
- package/src/workspace/session-proxy-middleware.ts +97 -0
- package/src/workspace/sse.ts +66 -0
- package/src/workspace/workspace-context.ts +23 -0
- package/src/workspace/workspace-server/routes.ts +33 -0
- package/src/workspace/workspace-server/server.ts +47 -0
- package/src/worktree/index.ts +487 -0
- package/sst-env.d.ts +10 -0
- package/test/benchmark.test.ts +121 -0
- package/test/build-optimizations.test.ts +124 -0
- package/test/id-benchmark.test.ts +132 -0
- package/test/optimizations.test.ts +302 -0
- package/test/preload.ts +1 -0
- package/test/solidjs-benchmark.test.ts +262 -0
- package/test/solidjs-optimizations.test.ts +259 -0
- package/test/tui-benchmark.test.ts +230 -0
- package/test/wildcard-benchmark.test.ts +180 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Writes a file to the local filesystem.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
- This tool will overwrite the existing file if there is one at the provided path.
|
|
5
|
+
- If this is an existing file, you MUST use the Read tool first to read the file's contents. This tool will fail if you did not read the file first.
|
|
6
|
+
- ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required.
|
|
7
|
+
- NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested by the User.
|
|
8
|
+
- Only use emojis if the user explicitly requests it. Avoid writing emojis to files unless asked.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { $ } from "bun"
|
|
2
|
+
import path from "path"
|
|
3
|
+
|
|
4
|
+
export namespace Archive {
|
|
5
|
+
export async function extractZip(zipPath: string, destDir: string) {
|
|
6
|
+
if (process.platform === "win32") {
|
|
7
|
+
const winZipPath = path.resolve(zipPath)
|
|
8
|
+
const winDestDir = path.resolve(destDir)
|
|
9
|
+
// $global:ProgressPreference suppresses PowerShell's blue progress bar popup
|
|
10
|
+
const cmd = `$global:ProgressPreference = 'SilentlyContinue'; Expand-Archive -Path '${winZipPath}' -DestinationPath '${winDestDir}' -Force`
|
|
11
|
+
await $`powershell -NoProfile -NonInteractive -Command ${cmd}`.quiet()
|
|
12
|
+
} else {
|
|
13
|
+
await $`unzip -o -q ${zipPath} -d ${destDir}`.quiet()
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export namespace Color {
|
|
2
|
+
export function isValidHex(hex?: string): hex is string {
|
|
3
|
+
if (!hex) return false
|
|
4
|
+
return /^#[0-9a-fA-F]{6}$/.test(hex)
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function hexToRgb(hex: string): { r: number; g: number; b: number } {
|
|
8
|
+
const r = parseInt(hex.slice(1, 3), 16)
|
|
9
|
+
const g = parseInt(hex.slice(3, 5), 16)
|
|
10
|
+
const b = parseInt(hex.slice(5, 7), 16)
|
|
11
|
+
return { r, g, b }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function hexToAnsiBold(hex?: string): string | undefined {
|
|
15
|
+
if (!isValidHex(hex)) return undefined
|
|
16
|
+
const { r, g, b } = hexToRgb(hex)
|
|
17
|
+
return `\x1b[38;2;${r};${g};${b}m\x1b[1m`
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "async_hooks"
|
|
2
|
+
|
|
3
|
+
export namespace Context {
|
|
4
|
+
export class NotFound extends Error {
|
|
5
|
+
constructor(public override readonly name: string) {
|
|
6
|
+
super(`No context found for ${name}`)
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function create<T>(name: string) {
|
|
11
|
+
const storage = new AsyncLocalStorage<T>()
|
|
12
|
+
return {
|
|
13
|
+
use() {
|
|
14
|
+
const result = storage.getStore()
|
|
15
|
+
if (!result) {
|
|
16
|
+
throw new NotFound(name)
|
|
17
|
+
}
|
|
18
|
+
return result
|
|
19
|
+
},
|
|
20
|
+
provide<R>(value: T, fn: () => R) {
|
|
21
|
+
return storage.run(value, fn)
|
|
22
|
+
},
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function defer<T extends () => void | Promise<void>>(
|
|
2
|
+
fn: T,
|
|
3
|
+
): T extends () => Promise<void> ? { [Symbol.asyncDispose]: () => Promise<void> } : { [Symbol.dispose]: () => void } {
|
|
4
|
+
return {
|
|
5
|
+
[Symbol.dispose]() {
|
|
6
|
+
fn()
|
|
7
|
+
},
|
|
8
|
+
[Symbol.asyncDispose]() {
|
|
9
|
+
return Promise.resolve(fn())
|
|
10
|
+
},
|
|
11
|
+
} as any
|
|
12
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { isRecord } from "./record"
|
|
2
|
+
|
|
3
|
+
export function errorFormat(error: unknown): string {
|
|
4
|
+
if (error instanceof Error) {
|
|
5
|
+
return error.stack ?? `${error.name}: ${error.message}`
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
if (typeof error === "object" && error !== null) {
|
|
9
|
+
try {
|
|
10
|
+
return JSON.stringify(error, null, 2)
|
|
11
|
+
} catch {
|
|
12
|
+
return "Unexpected error (unserializable)"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return String(error)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function errorMessage(error: unknown): string {
|
|
20
|
+
if (error instanceof Error) {
|
|
21
|
+
if (error.message) return error.message
|
|
22
|
+
if (error.name) return error.name
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (isRecord(error) && typeof error.message === "string" && error.message) {
|
|
26
|
+
return error.message
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const text = String(error)
|
|
30
|
+
if (text && text !== "[object Object]") return text
|
|
31
|
+
|
|
32
|
+
const formatted = errorFormat(error)
|
|
33
|
+
if (formatted && formatted !== "{}") return formatted
|
|
34
|
+
return "unknown error"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function errorData(error: unknown) {
|
|
38
|
+
if (error instanceof Error) {
|
|
39
|
+
return {
|
|
40
|
+
type: error.name,
|
|
41
|
+
message: errorMessage(error),
|
|
42
|
+
stack: error.stack,
|
|
43
|
+
cause: error.cause === undefined ? undefined : errorFormat(error.cause),
|
|
44
|
+
formatted: errorFormatted(error),
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!isRecord(error)) {
|
|
49
|
+
return {
|
|
50
|
+
type: typeof error,
|
|
51
|
+
message: errorMessage(error),
|
|
52
|
+
formatted: errorFormatted(error),
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const data = Object.getOwnPropertyNames(error).reduce<Record<string, unknown>>((acc, key) => {
|
|
57
|
+
const value = error[key]
|
|
58
|
+
if (value === undefined) return acc
|
|
59
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
60
|
+
acc[key] = value
|
|
61
|
+
return acc
|
|
62
|
+
}
|
|
63
|
+
acc[key] = value instanceof Error ? value.message : String(value)
|
|
64
|
+
return acc
|
|
65
|
+
}, {})
|
|
66
|
+
|
|
67
|
+
if (typeof data.message !== "string") data.message = errorMessage(error)
|
|
68
|
+
if (typeof data.type !== "string") data.type = (error as any).constructor?.name
|
|
69
|
+
data.formatted = errorFormatted(error)
|
|
70
|
+
return data
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function errorFormatted(error: unknown) {
|
|
74
|
+
const formatted = errorFormat(error)
|
|
75
|
+
if (formatted !== "{}") return formatted
|
|
76
|
+
return String(error)
|
|
77
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Log } from "./log"
|
|
2
|
+
|
|
3
|
+
export namespace EventLoop {
|
|
4
|
+
export async function wait() {
|
|
5
|
+
return new Promise<void>((resolve) => {
|
|
6
|
+
const check = () => {
|
|
7
|
+
const active = [...(process as any)._getActiveHandles(), ...(process as any)._getActiveRequests()]
|
|
8
|
+
Log.Default.info("eventloop", {
|
|
9
|
+
active,
|
|
10
|
+
})
|
|
11
|
+
if ((process as any)._getActiveHandles().length === 0 && (process as any)._getActiveRequests().length === 0) {
|
|
12
|
+
resolve()
|
|
13
|
+
} else {
|
|
14
|
+
setImmediate(check)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
check()
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { realpathSync, statSync } from "fs"
|
|
2
|
+
import { dirname, join, relative, resolve as pathResolve } from "path"
|
|
3
|
+
import { mkdir } from "fs/promises"
|
|
4
|
+
|
|
5
|
+
export namespace Filesystem {
|
|
6
|
+
export function stat(p: string): import("fs").Stats | undefined {
|
|
7
|
+
try {
|
|
8
|
+
return statSync(p)
|
|
9
|
+
} catch {
|
|
10
|
+
return undefined
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function readJson<T>(p: string): Promise<T> {
|
|
15
|
+
return Bun.file(p).json() as Promise<T>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function writeJson(p: string, data: unknown): Promise<void> {
|
|
19
|
+
await mkdir(dirname(p), { recursive: true })
|
|
20
|
+
await Bun.write(p, JSON.stringify(data, null, 2))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function readText(p: string): Promise<string> {
|
|
24
|
+
return Bun.file(p).text()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function write(p: string, text: string | Buffer): Promise<void> {
|
|
28
|
+
await mkdir(dirname(p), { recursive: true })
|
|
29
|
+
await Bun.write(p, text)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function resolve(p: string): string {
|
|
33
|
+
return pathResolve(p)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
export const exists = (p: string) =>
|
|
38
|
+
Bun.file(p)
|
|
39
|
+
.stat()
|
|
40
|
+
.then(() => true)
|
|
41
|
+
.catch(() => false)
|
|
42
|
+
|
|
43
|
+
export const isDir = (p: string) =>
|
|
44
|
+
Bun.file(p)
|
|
45
|
+
.stat()
|
|
46
|
+
.then((s) => s.isDirectory())
|
|
47
|
+
.catch(() => false)
|
|
48
|
+
/**
|
|
49
|
+
* On Windows, normalize a path to its canonical casing using the filesystem.
|
|
50
|
+
* This is needed because Windows paths are case-insensitive but LSP servers
|
|
51
|
+
* may return paths with different casing than what we send them.
|
|
52
|
+
*/
|
|
53
|
+
export function normalizePath(p: string): string {
|
|
54
|
+
if (process.platform !== "win32") return p
|
|
55
|
+
try {
|
|
56
|
+
return realpathSync.native(p)
|
|
57
|
+
} catch {
|
|
58
|
+
return p
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export function overlaps(a: string, b: string) {
|
|
62
|
+
const relA = relative(a, b)
|
|
63
|
+
const relB = relative(b, a)
|
|
64
|
+
return !relA || !relA.startsWith("..") || !relB || !relB.startsWith("..")
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function contains(parent: string, child: string) {
|
|
68
|
+
return !relative(parent, child).startsWith("..")
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function findUp(target: string, start: string, stop?: string) {
|
|
72
|
+
let current = start
|
|
73
|
+
const result = []
|
|
74
|
+
while (true) {
|
|
75
|
+
const search = join(current, target)
|
|
76
|
+
if (await exists(search)) result.push(search)
|
|
77
|
+
if (stop === current) break
|
|
78
|
+
const parent = dirname(current)
|
|
79
|
+
if (parent === current) break
|
|
80
|
+
current = parent
|
|
81
|
+
}
|
|
82
|
+
return result
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function* up(options: { targets: string[]; start: string; stop?: string }) {
|
|
86
|
+
const { targets, start, stop } = options
|
|
87
|
+
let current = start
|
|
88
|
+
while (true) {
|
|
89
|
+
for (const target of targets) {
|
|
90
|
+
const search = join(current, target)
|
|
91
|
+
if (await exists(search)) yield search
|
|
92
|
+
}
|
|
93
|
+
if (stop === current) break
|
|
94
|
+
const parent = dirname(current)
|
|
95
|
+
if (parent === current) break
|
|
96
|
+
current = parent
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function globUp(pattern: string, start: string, stop?: string) {
|
|
101
|
+
let current = start
|
|
102
|
+
const result = []
|
|
103
|
+
while (true) {
|
|
104
|
+
try {
|
|
105
|
+
const glob = new Bun.Glob(pattern)
|
|
106
|
+
for await (const match of glob.scan({
|
|
107
|
+
cwd: current,
|
|
108
|
+
absolute: true,
|
|
109
|
+
onlyFiles: true,
|
|
110
|
+
followSymlinks: true,
|
|
111
|
+
dot: true,
|
|
112
|
+
})) {
|
|
113
|
+
result.push(match)
|
|
114
|
+
}
|
|
115
|
+
} catch {
|
|
116
|
+
// Skip invalid glob patterns
|
|
117
|
+
}
|
|
118
|
+
if (stop === current) break
|
|
119
|
+
const parent = dirname(current)
|
|
120
|
+
if (parent === current) break
|
|
121
|
+
current = parent
|
|
122
|
+
}
|
|
123
|
+
return result
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import path from "path"
|
|
2
|
+
import os from "os"
|
|
3
|
+
import { randomBytes, randomUUID } from "crypto"
|
|
4
|
+
import { mkdir, readFile, rm, stat, utimes, writeFile } from "fs/promises"
|
|
5
|
+
import { Global } from "@/global"
|
|
6
|
+
import { Hash } from "@/util/hash"
|
|
7
|
+
|
|
8
|
+
export namespace Flock {
|
|
9
|
+
const root = path.join(Global.Path.state, "locks")
|
|
10
|
+
// Defaults for callers that do not provide timing options.
|
|
11
|
+
const defaultOpts = {
|
|
12
|
+
staleMs: 60_000,
|
|
13
|
+
timeoutMs: 5 * 60_000,
|
|
14
|
+
baseDelayMs: 100,
|
|
15
|
+
maxDelayMs: 2_000,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface WaitEvent {
|
|
19
|
+
key: string
|
|
20
|
+
attempt: number
|
|
21
|
+
delay: number
|
|
22
|
+
waited: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type Wait = (input: WaitEvent) => void | Promise<void>
|
|
26
|
+
|
|
27
|
+
export interface Options {
|
|
28
|
+
dir?: string
|
|
29
|
+
signal?: AbortSignal
|
|
30
|
+
staleMs?: number
|
|
31
|
+
timeoutMs?: number
|
|
32
|
+
baseDelayMs?: number
|
|
33
|
+
maxDelayMs?: number
|
|
34
|
+
onWait?: Wait
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type Opts = {
|
|
38
|
+
staleMs: number
|
|
39
|
+
timeoutMs: number
|
|
40
|
+
baseDelayMs: number
|
|
41
|
+
maxDelayMs: number
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
type Owned = {
|
|
45
|
+
acquired: true
|
|
46
|
+
startHeartbeat: (intervalMs?: number) => void
|
|
47
|
+
release: () => Promise<void>
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface Lease {
|
|
51
|
+
release: () => Promise<void>
|
|
52
|
+
[Symbol.asyncDispose]: () => Promise<void>
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function code(err: unknown) {
|
|
56
|
+
if (typeof err !== "object" || err === null || !("code" in err)) return
|
|
57
|
+
const value = err.code
|
|
58
|
+
if (typeof value !== "string") return
|
|
59
|
+
return value
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function sleep(ms: number, signal?: AbortSignal) {
|
|
63
|
+
return new Promise<void>((resolve, reject) => {
|
|
64
|
+
if (signal?.aborted) {
|
|
65
|
+
reject(signal.reason ?? new Error("Aborted"))
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let timer: NodeJS.Timeout | undefined
|
|
70
|
+
|
|
71
|
+
const done = () => {
|
|
72
|
+
signal?.removeEventListener("abort", abort)
|
|
73
|
+
resolve()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const abort = () => {
|
|
77
|
+
if (timer) {
|
|
78
|
+
clearTimeout(timer)
|
|
79
|
+
}
|
|
80
|
+
signal?.removeEventListener("abort", abort)
|
|
81
|
+
reject(signal?.reason ?? new Error("Aborted"))
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
signal?.addEventListener("abort", abort, { once: true })
|
|
85
|
+
timer = setTimeout(done, ms)
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function jitter(ms: number) {
|
|
90
|
+
const j = Math.floor(ms * 0.3)
|
|
91
|
+
const d = Math.floor(Math.random() * (2 * j + 1)) - j
|
|
92
|
+
return Math.max(0, ms + d)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function mono() {
|
|
96
|
+
return performance.now()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function wall() {
|
|
100
|
+
return performance.timeOrigin + mono()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function stats(file: string) {
|
|
104
|
+
try {
|
|
105
|
+
return await stat(file)
|
|
106
|
+
} catch (err) {
|
|
107
|
+
const errCode = code(err)
|
|
108
|
+
if (errCode === "ENOENT" || errCode === "ENOTDIR") return
|
|
109
|
+
throw err
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function stale(lockDir: string, heartbeatPath: string, metaPath: string, staleMs: number) {
|
|
114
|
+
const now = wall()
|
|
115
|
+
const heartbeat = await stats(heartbeatPath)
|
|
116
|
+
if (heartbeat) {
|
|
117
|
+
return now - heartbeat.mtimeMs > staleMs
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const meta = await stats(metaPath)
|
|
121
|
+
if (meta) {
|
|
122
|
+
return now - meta.mtimeMs > staleMs
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const dir = await stats(lockDir)
|
|
126
|
+
if (!dir) {
|
|
127
|
+
return false
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return now - dir.mtimeMs > staleMs
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function tryAcquireLockDir(lockDir: string, opts: Opts): Promise<Owned | { acquired: false }> {
|
|
134
|
+
const token = randomUUID?.() ?? randomBytes(16).toString("hex")
|
|
135
|
+
const metaPath = path.join(lockDir, "meta.json")
|
|
136
|
+
const heartbeatPath = path.join(lockDir, "heartbeat")
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
await mkdir(lockDir, { mode: 0o700 })
|
|
140
|
+
} catch (err) {
|
|
141
|
+
if (code(err) !== "EEXIST") {
|
|
142
|
+
throw err
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (!(await stale(lockDir, heartbeatPath, metaPath, opts.staleMs))) {
|
|
146
|
+
return { acquired: false }
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const breakerPath = lockDir + ".breaker"
|
|
150
|
+
try {
|
|
151
|
+
await mkdir(breakerPath, { mode: 0o700 })
|
|
152
|
+
} catch (claimErr) {
|
|
153
|
+
const errCode = code(claimErr)
|
|
154
|
+
if (errCode === "EEXIST") {
|
|
155
|
+
const breaker = await stats(breakerPath)
|
|
156
|
+
if (breaker && wall() - breaker.mtimeMs > opts.staleMs) {
|
|
157
|
+
await rm(breakerPath, { recursive: true, force: true }).catch(() => undefined)
|
|
158
|
+
}
|
|
159
|
+
return { acquired: false }
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (errCode === "ENOENT" || errCode === "ENOTDIR") {
|
|
163
|
+
return { acquired: false }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
throw claimErr
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
if (!(await stale(lockDir, heartbeatPath, metaPath, opts.staleMs))) {
|
|
171
|
+
return { acquired: false }
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
await rm(lockDir, { recursive: true, force: true })
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
await mkdir(lockDir, { mode: 0o700 })
|
|
178
|
+
} catch (retryErr) {
|
|
179
|
+
const errCode = code(retryErr)
|
|
180
|
+
if (errCode === "EEXIST" || errCode === "ENOTEMPTY") {
|
|
181
|
+
return { acquired: false }
|
|
182
|
+
}
|
|
183
|
+
throw retryErr
|
|
184
|
+
}
|
|
185
|
+
} finally {
|
|
186
|
+
await rm(breakerPath, { recursive: true, force: true }).catch(() => undefined)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const meta = {
|
|
191
|
+
token,
|
|
192
|
+
pid: process.pid,
|
|
193
|
+
hostname: os.hostname(),
|
|
194
|
+
createdAt: new Date().toISOString(),
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
await writeFile(heartbeatPath, "", { flag: "wx" }).catch(async () => {
|
|
198
|
+
await rm(lockDir, { recursive: true, force: true })
|
|
199
|
+
throw new Error("Lock acquired but heartbeat already existed (possible compromise).")
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
await writeFile(metaPath, JSON.stringify(meta, null, 2), { flag: "wx" }).catch(async () => {
|
|
203
|
+
await rm(lockDir, { recursive: true, force: true })
|
|
204
|
+
throw new Error("Lock acquired but meta.json already existed (possible compromise).")
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
let timer: NodeJS.Timeout | undefined
|
|
208
|
+
|
|
209
|
+
const startHeartbeat = (intervalMs = Math.max(100, Math.floor(opts.staleMs / 3))) => {
|
|
210
|
+
if (timer) return
|
|
211
|
+
timer = setInterval(() => {
|
|
212
|
+
const t = new Date()
|
|
213
|
+
void utimes(heartbeatPath, t, t).catch(() => undefined)
|
|
214
|
+
}, intervalMs)
|
|
215
|
+
timer.unref?.()
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const release = async () => {
|
|
219
|
+
if (timer) {
|
|
220
|
+
clearInterval(timer)
|
|
221
|
+
timer = undefined
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const current = await readFile(metaPath, "utf8")
|
|
225
|
+
.then((raw) => {
|
|
226
|
+
const parsed = JSON.parse(raw)
|
|
227
|
+
if (!parsed || typeof parsed !== "object") return {}
|
|
228
|
+
return {
|
|
229
|
+
token: "token" in parsed && typeof parsed.token === "string" ? parsed.token : undefined,
|
|
230
|
+
}
|
|
231
|
+
})
|
|
232
|
+
.catch((err) => {
|
|
233
|
+
const errCode = code(err)
|
|
234
|
+
if (errCode === "ENOENT" || errCode === "ENOTDIR") {
|
|
235
|
+
throw new Error("Refusing to release: lock is compromised (metadata missing).")
|
|
236
|
+
}
|
|
237
|
+
if (err instanceof SyntaxError) {
|
|
238
|
+
throw new Error("Refusing to release: lock is compromised (metadata invalid).")
|
|
239
|
+
}
|
|
240
|
+
throw err
|
|
241
|
+
})
|
|
242
|
+
if (current.token !== token) {
|
|
243
|
+
throw new Error("Refusing to release: lock token mismatch (not the owner).")
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
await rm(lockDir, { recursive: true, force: true })
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
acquired: true,
|
|
251
|
+
startHeartbeat,
|
|
252
|
+
release,
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function acquireLockDir(
|
|
257
|
+
lockDir: string,
|
|
258
|
+
input: { key: string; onWait?: Wait; signal?: AbortSignal },
|
|
259
|
+
opts: Opts,
|
|
260
|
+
) {
|
|
261
|
+
const stop = mono() + opts.timeoutMs
|
|
262
|
+
let attempt = 0
|
|
263
|
+
let waited = 0
|
|
264
|
+
let delay = opts.baseDelayMs
|
|
265
|
+
|
|
266
|
+
while (true) {
|
|
267
|
+
input.signal?.throwIfAborted()
|
|
268
|
+
|
|
269
|
+
const res = await tryAcquireLockDir(lockDir, opts)
|
|
270
|
+
if (res.acquired) {
|
|
271
|
+
return res
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (mono() > stop) {
|
|
275
|
+
throw new Error(`Timed out waiting for lock: ${input.key}`)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
attempt += 1
|
|
279
|
+
const ms = jitter(delay)
|
|
280
|
+
await input.onWait?.({
|
|
281
|
+
key: input.key,
|
|
282
|
+
attempt,
|
|
283
|
+
delay: ms,
|
|
284
|
+
waited,
|
|
285
|
+
})
|
|
286
|
+
await sleep(ms, input.signal)
|
|
287
|
+
waited += ms
|
|
288
|
+
delay = Math.min(opts.maxDelayMs, Math.floor(delay * 1.7))
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export async function acquire(key: string, input: Options = {}): Promise<Lease> {
|
|
293
|
+
input.signal?.throwIfAborted()
|
|
294
|
+
const cfg: Opts = {
|
|
295
|
+
staleMs: input.staleMs ?? defaultOpts.staleMs,
|
|
296
|
+
timeoutMs: input.timeoutMs ?? defaultOpts.timeoutMs,
|
|
297
|
+
baseDelayMs: input.baseDelayMs ?? defaultOpts.baseDelayMs,
|
|
298
|
+
maxDelayMs: input.maxDelayMs ?? defaultOpts.maxDelayMs,
|
|
299
|
+
}
|
|
300
|
+
const dir = input.dir ?? root
|
|
301
|
+
|
|
302
|
+
await mkdir(dir, { recursive: true })
|
|
303
|
+
const lockfile = path.join(dir, Hash.fast(key) + ".lock")
|
|
304
|
+
const lock = await acquireLockDir(
|
|
305
|
+
lockfile,
|
|
306
|
+
{
|
|
307
|
+
key,
|
|
308
|
+
onWait: input.onWait,
|
|
309
|
+
signal: input.signal,
|
|
310
|
+
},
|
|
311
|
+
cfg,
|
|
312
|
+
)
|
|
313
|
+
lock.startHeartbeat()
|
|
314
|
+
|
|
315
|
+
const release = () => lock.release()
|
|
316
|
+
return {
|
|
317
|
+
release,
|
|
318
|
+
[Symbol.asyncDispose]() {
|
|
319
|
+
return release()
|
|
320
|
+
},
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export async function withLock<T>(key: string, fn: () => Promise<T>, input: Options = {}) {
|
|
325
|
+
await using _ = await acquire(key, input)
|
|
326
|
+
input.signal?.throwIfAborted()
|
|
327
|
+
return await fn()
|
|
328
|
+
}
|
|
329
|
+
}
|
package/src/util/fn.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { z } from "zod"
|
|
2
|
+
|
|
3
|
+
export function fn<T extends z.ZodType, Result>(schema: T, cb: (input: z.infer<T>) => Result) {
|
|
4
|
+
const result = (input: z.infer<T>) => {
|
|
5
|
+
const parsed = schema.parse(input)
|
|
6
|
+
return cb(parsed)
|
|
7
|
+
}
|
|
8
|
+
result.force = (input: z.infer<T>) => cb(input)
|
|
9
|
+
result.schema = schema
|
|
10
|
+
return result
|
|
11
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function formatDuration(secs: number) {
|
|
2
|
+
if (secs <= 0) return ""
|
|
3
|
+
if (secs < 60) return `${secs}s`
|
|
4
|
+
if (secs < 3600) {
|
|
5
|
+
const mins = Math.floor(secs / 60)
|
|
6
|
+
const remaining = secs % 60
|
|
7
|
+
return remaining > 0 ? `${mins}m ${remaining}s` : `${mins}m`
|
|
8
|
+
}
|
|
9
|
+
if (secs < 86400) {
|
|
10
|
+
const hours = Math.floor(secs / 3600)
|
|
11
|
+
const remaining = Math.floor((secs % 3600) / 60)
|
|
12
|
+
return remaining > 0 ? `${hours}h ${remaining}m` : `${hours}h`
|
|
13
|
+
}
|
|
14
|
+
if (secs < 604800) {
|
|
15
|
+
const days = Math.floor(secs / 86400)
|
|
16
|
+
return days === 1 ? "~1 day" : `~${days} days`
|
|
17
|
+
}
|
|
18
|
+
const weeks = Math.floor(secs / 604800)
|
|
19
|
+
return weeks === 1 ? "~1 week" : `~${weeks} weeks`
|
|
20
|
+
}
|
package/src/util/hash.ts
ADDED
package/src/util/iife.ts
ADDED