bincode-cli 1.0.1
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/AGENTS.md +27 -0
- package/README.md +15 -0
- package/bin/bincode +98 -0
- package/bunfig.toml +4 -0
- package/package.json +124 -0
- package/parsers-config.ts +239 -0
- package/script/build.ts +167 -0
- package/script/postinstall.mjs +206 -0
- package/script/publish.ts +99 -0
- package/script/schema.ts +47 -0
- package/src/acp/README.md +164 -0
- package/src/acp/agent.ts +1051 -0
- package/src/acp/session.ts +101 -0
- package/src/acp/types.ts +22 -0
- package/src/agent/agent.ts +398 -0
- package/src/agent/generate.txt +75 -0
- package/src/agent/prompt/compaction.txt +12 -0
- package/src/agent/prompt/explore.txt +18 -0
- package/src/agent/prompt/summary.txt +10 -0
- package/src/agent/prompt/title.txt +36 -0
- package/src/auth/bineric-login.ts +506 -0
- package/src/auth/index.ts +70 -0
- package/src/bun/index.ts +114 -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/cli/auth-check.ts +61 -0
- package/src/cli/bootstrap.ts +21 -0
- package/src/cli/cmd/acp.ts +88 -0
- package/src/cli/cmd/agent.ts +256 -0
- package/src/cli/cmd/auth.ts +436 -0
- package/src/cli/cmd/cmd.ts +7 -0
- package/src/cli/cmd/debug/config.ts +15 -0
- package/src/cli/cmd/debug/file.ts +91 -0
- package/src/cli/cmd/debug/index.ts +43 -0
- package/src/cli/cmd/debug/lsp.ts +48 -0
- package/src/cli/cmd/debug/ripgrep.ts +83 -0
- package/src/cli/cmd/debug/scrap.ts +15 -0
- package/src/cli/cmd/debug/skill.ts +15 -0
- package/src/cli/cmd/debug/snapshot.ts +48 -0
- package/src/cli/cmd/export.ts +88 -0
- package/src/cli/cmd/generate.ts +38 -0
- package/src/cli/cmd/github.ts +1399 -0
- package/src/cli/cmd/import.ts +98 -0
- package/src/cli/cmd/login.ts +112 -0
- package/src/cli/cmd/logout.ts +38 -0
- package/src/cli/cmd/mcp.ts +654 -0
- package/src/cli/cmd/models.ts +77 -0
- package/src/cli/cmd/pr.ts +112 -0
- package/src/cli/cmd/run.ts +368 -0
- package/src/cli/cmd/serve.ts +31 -0
- package/src/cli/cmd/session.ts +106 -0
- package/src/cli/cmd/stats.ts +298 -0
- package/src/cli/cmd/tui/app.tsx +669 -0
- package/src/cli/cmd/tui/attach.ts +30 -0
- package/src/cli/cmd/tui/component/border.tsx +21 -0
- package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
- package/src/cli/cmd/tui/component/dialog-command.tsx +123 -0
- package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
- package/src/cli/cmd/tui/component/dialog-model.tsx +223 -0
- package/src/cli/cmd/tui/component/dialog-provider.tsx +224 -0
- package/src/cli/cmd/tui/component/dialog-session-list.tsx +102 -0
- package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
- package/src/cli/cmd/tui/component/dialog-status.tsx +162 -0
- package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
- package/src/cli/cmd/tui/component/dialog-theme-list.tsx +50 -0
- package/src/cli/cmd/tui/component/logo.tsx +32 -0
- package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +560 -0
- package/src/cli/cmd/tui/component/prompt/history.tsx +108 -0
- package/src/cli/cmd/tui/component/prompt/index.tsx +1052 -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 +23 -0
- package/src/cli/cmd/tui/context/helper.tsx +25 -0
- package/src/cli/cmd/tui/context/keybind.tsx +101 -0
- package/src/cli/cmd/tui/context/kv.tsx +49 -0
- package/src/cli/cmd/tui/context/local.tsx +339 -0
- package/src/cli/cmd/tui/context/prompt.tsx +18 -0
- package/src/cli/cmd/tui/context/route.tsx +46 -0
- package/src/cli/cmd/tui/context/sdk.tsx +74 -0
- package/src/cli/cmd/tui/context/sync.tsx +372 -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/bincode.json +245 -0
- package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +233 -0
- package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +233 -0
- package/src/cli/cmd/tui/context/theme/catppuccin.json +112 -0
- package/src/cli/cmd/tui/context/theme/cobalt2.json +228 -0
- package/src/cli/cmd/tui/context/theme/cursor.json +249 -0
- package/src/cli/cmd/tui/context/theme/dracula.json +219 -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/github.json +233 -0
- package/src/cli/cmd/tui/context/theme/gruvbox.json +95 -0
- package/src/cli/cmd/tui/context/theme/kanagawa.json +77 -0
- package/src/cli/cmd/tui/context/theme/lucent-orng.json +227 -0
- package/src/cli/cmd/tui/context/theme/material.json +235 -0
- package/src/cli/cmd/tui/context/theme/matrix.json +77 -0
- package/src/cli/cmd/tui/context/theme/mercury.json +252 -0
- package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
- package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
- package/src/cli/cmd/tui/context/theme/nord.json +223 -0
- package/src/cli/cmd/tui/context/theme/one-dark.json +84 -0
- package/src/cli/cmd/tui/context/theme/orng.json +245 -0
- package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
- package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
- package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
- package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
- package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -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/zenburn.json +223 -0
- package/src/cli/cmd/tui/context/theme.tsx +1109 -0
- package/src/cli/cmd/tui/event.ts +40 -0
- package/src/cli/cmd/tui/routes/home.tsx +105 -0
- package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +64 -0
- package/src/cli/cmd/tui/routes/session/dialog-message.tsx +109 -0
- package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +26 -0
- package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +47 -0
- package/src/cli/cmd/tui/routes/session/footer.tsx +88 -0
- package/src/cli/cmd/tui/routes/session/header.tsx +141 -0
- package/src/cli/cmd/tui/routes/session/index.tsx +1888 -0
- package/src/cli/cmd/tui/routes/session/sidebar.tsx +321 -0
- package/src/cli/cmd/tui/spawn.ts +60 -0
- package/src/cli/cmd/tui/thread.ts +120 -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-help.tsx +38 -0
- package/src/cli/cmd/tui/ui/dialog-prompt.tsx +77 -0
- package/src/cli/cmd/tui/ui/dialog-select.tsx +330 -0
- package/src/cli/cmd/tui/ui/dialog.tsx +170 -0
- package/src/cli/cmd/tui/ui/spinner.ts +368 -0
- package/src/cli/cmd/tui/ui/toast.tsx +100 -0
- package/src/cli/cmd/tui/util/clipboard.ts +127 -0
- package/src/cli/cmd/tui/util/editor.ts +32 -0
- package/src/cli/cmd/tui/util/terminal.ts +114 -0
- package/src/cli/cmd/tui/worker.ts +63 -0
- package/src/cli/cmd/uninstall.ts +344 -0
- package/src/cli/cmd/upgrade.ts +67 -0
- package/src/cli/cmd/web.ts +84 -0
- package/src/cli/error.ts +55 -0
- package/src/cli/ui.ts +84 -0
- package/src/cli/upgrade.ts +25 -0
- package/src/command/index.ts +80 -0
- package/src/command/template/initialize.txt +10 -0
- package/src/command/template/review.txt +97 -0
- package/src/config/config.ts +995 -0
- package/src/config/markdown.ts +41 -0
- package/src/env/index.ts +26 -0
- package/src/file/ignore.ts +83 -0
- package/src/file/index.ts +328 -0
- package/src/file/ripgrep.ts +393 -0
- package/src/file/time.ts +64 -0
- package/src/file/watcher.ts +103 -0
- package/src/flag/flag.ts +46 -0
- package/src/format/formatter.ts +315 -0
- package/src/format/index.ts +137 -0
- package/src/global/index.ts +52 -0
- package/src/id/id.ts +73 -0
- package/src/ide/index.ts +76 -0
- package/src/index.ts +217 -0
- package/src/installation/index.ts +196 -0
- package/src/lsp/client.ts +229 -0
- package/src/lsp/index.ts +485 -0
- package/src/lsp/language.ts +116 -0
- package/src/lsp/server.ts +1895 -0
- package/src/mcp/auth.ts +135 -0
- package/src/mcp/index.ts +654 -0
- package/src/mcp/oauth-callback.ts +200 -0
- package/src/mcp/oauth-provider.ts +154 -0
- package/src/patch/index.ts +622 -0
- package/src/permission/index.ts +199 -0
- package/src/plugin/index.ts +101 -0
- package/src/project/bootstrap.ts +31 -0
- package/src/project/instance.ts +78 -0
- package/src/project/project.ts +221 -0
- package/src/project/state.ts +65 -0
- package/src/project/vcs.ts +76 -0
- package/src/provider/auth.ts +143 -0
- package/src/provider/models-macro.ts +11 -0
- package/src/provider/models.ts +106 -0
- package/src/provider/provider.ts +1071 -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 +101 -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 +1713 -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 +455 -0
- package/src/pty/index.ts +231 -0
- package/src/server/error.ts +36 -0
- package/src/server/project.ts +79 -0
- package/src/server/server.ts +2642 -0
- package/src/server/tui.ts +71 -0
- package/src/session/compaction.ts +223 -0
- package/src/session/index.ts +458 -0
- package/src/session/llm.ts +201 -0
- package/src/session/message-v2.ts +659 -0
- package/src/session/message.ts +189 -0
- package/src/session/processor.ts +409 -0
- package/src/session/prompt/anthropic-20250930.txt +166 -0
- package/src/session/prompt/anthropic.txt +104 -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.txt +318 -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 +26 -0
- package/src/session/prompt/polaris.txt +106 -0
- package/src/session/prompt/qwen.txt +109 -0
- package/src/session/prompt.ts +1446 -0
- package/src/session/retry.ts +86 -0
- package/src/session/revert.ts +108 -0
- package/src/session/status.ts +76 -0
- package/src/session/summary.ts +194 -0
- package/src/session/system.ts +120 -0
- package/src/session/todo.ts +37 -0
- package/src/share/share-next.ts +194 -0
- package/src/share/share.ts +87 -0
- package/src/shell/shell.ts +67 -0
- package/src/skill/index.ts +1 -0
- package/src/skill/skill.ts +83 -0
- package/src/snapshot/index.ts +197 -0
- package/src/storage/storage.ts +226 -0
- package/src/tool/bash.ts +306 -0
- package/src/tool/bash.txt +158 -0
- package/src/tool/batch.ts +175 -0
- package/src/tool/batch.txt +24 -0
- package/src/tool/codesearch.ts +138 -0
- package/src/tool/codesearch.txt +12 -0
- package/src/tool/edit.ts +675 -0
- package/src/tool/edit.txt +10 -0
- package/src/tool/glob.ts +65 -0
- package/src/tool/glob.txt +6 -0
- package/src/tool/grep.ts +121 -0
- package/src/tool/grep.txt +8 -0
- package/src/tool/invalid.ts +17 -0
- package/src/tool/ls.ts +110 -0
- package/src/tool/ls.txt +1 -0
- package/src/tool/lsp-diagnostics.ts +26 -0
- package/src/tool/lsp-diagnostics.txt +1 -0
- package/src/tool/lsp-hover.ts +31 -0
- package/src/tool/lsp-hover.txt +1 -0
- package/src/tool/lsp.ts +87 -0
- package/src/tool/lsp.txt +19 -0
- package/src/tool/multiedit.ts +46 -0
- package/src/tool/multiedit.txt +41 -0
- package/src/tool/patch.ts +233 -0
- package/src/tool/patch.txt +1 -0
- package/src/tool/read.ts +219 -0
- package/src/tool/read.txt +12 -0
- package/src/tool/registry.ts +162 -0
- package/src/tool/skill.ts +100 -0
- package/src/tool/task.ts +136 -0
- package/src/tool/task.txt +60 -0
- package/src/tool/todo.ts +39 -0
- package/src/tool/todoread.txt +14 -0
- package/src/tool/todowrite.txt +167 -0
- package/src/tool/tool.ts +71 -0
- package/src/tool/webfetch.ts +187 -0
- package/src/tool/webfetch.txt +13 -0
- package/src/tool/websearch.ts +150 -0
- package/src/tool/websearch.txt +11 -0
- package/src/tool/write.ts +110 -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/eventloop.ts +20 -0
- package/src/util/filesystem.ts +83 -0
- package/src/util/fn.ts +11 -0
- package/src/util/iife.ts +3 -0
- package/src/util/keybind.ts +102 -0
- package/src/util/lazy.ts +11 -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/queue.ts +32 -0
- package/src/util/rpc.ts +42 -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 +54 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { isDeepEqual } from "remeda"
|
|
2
|
+
import type { ParsedKey } from "@opentui/core"
|
|
3
|
+
|
|
4
|
+
export namespace Keybind {
|
|
5
|
+
/**
|
|
6
|
+
* Keybind info derived from OpenTUI's ParsedKey with our custom `leader` field.
|
|
7
|
+
* This ensures type compatibility and catches missing fields at compile time.
|
|
8
|
+
*/
|
|
9
|
+
export type Info = Pick<ParsedKey, "name" | "ctrl" | "meta" | "shift" | "super"> & {
|
|
10
|
+
leader: boolean // our custom field
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function match(a: Info, b: Info): boolean {
|
|
14
|
+
// Normalize super field (undefined and false are equivalent)
|
|
15
|
+
const normalizedA = { ...a, super: a.super ?? false }
|
|
16
|
+
const normalizedB = { ...b, super: b.super ?? false }
|
|
17
|
+
return isDeepEqual(normalizedA, normalizedB)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Convert OpenTUI's ParsedKey to our Keybind.Info format.
|
|
22
|
+
* This helper ensures all required fields are present and avoids manual object creation.
|
|
23
|
+
*/
|
|
24
|
+
export function fromParsedKey(key: ParsedKey, leader = false): Info {
|
|
25
|
+
return {
|
|
26
|
+
name: key.name,
|
|
27
|
+
ctrl: key.ctrl,
|
|
28
|
+
meta: key.meta,
|
|
29
|
+
shift: key.shift,
|
|
30
|
+
super: key.super ?? false,
|
|
31
|
+
leader,
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function toString(info: Info): string {
|
|
36
|
+
const parts: string[] = []
|
|
37
|
+
|
|
38
|
+
if (info.ctrl) parts.push("ctrl")
|
|
39
|
+
if (info.meta) parts.push("alt")
|
|
40
|
+
if (info.super) parts.push("super")
|
|
41
|
+
if (info.shift) parts.push("shift")
|
|
42
|
+
if (info.name) {
|
|
43
|
+
if (info.name === "delete") parts.push("del")
|
|
44
|
+
else parts.push(info.name)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let result = parts.join("+")
|
|
48
|
+
|
|
49
|
+
if (info.leader) {
|
|
50
|
+
result = result ? `<leader> ${result}` : `<leader>`
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return result
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function parse(key: string): Info[] {
|
|
57
|
+
if (key === "none") return []
|
|
58
|
+
|
|
59
|
+
return key.split(",").map((combo) => {
|
|
60
|
+
// Handle <leader> syntax by replacing with leader+
|
|
61
|
+
const normalized = combo.replace(/<leader>/g, "leader+")
|
|
62
|
+
const parts = normalized.toLowerCase().split("+")
|
|
63
|
+
const info: Info = {
|
|
64
|
+
ctrl: false,
|
|
65
|
+
meta: false,
|
|
66
|
+
shift: false,
|
|
67
|
+
leader: false,
|
|
68
|
+
name: "",
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
for (const part of parts) {
|
|
72
|
+
switch (part) {
|
|
73
|
+
case "ctrl":
|
|
74
|
+
info.ctrl = true
|
|
75
|
+
break
|
|
76
|
+
case "alt":
|
|
77
|
+
case "meta":
|
|
78
|
+
case "option":
|
|
79
|
+
info.meta = true
|
|
80
|
+
break
|
|
81
|
+
case "super":
|
|
82
|
+
info.super = true
|
|
83
|
+
break
|
|
84
|
+
case "shift":
|
|
85
|
+
info.shift = true
|
|
86
|
+
break
|
|
87
|
+
case "leader":
|
|
88
|
+
info.leader = true
|
|
89
|
+
break
|
|
90
|
+
case "esc":
|
|
91
|
+
info.name = "escape"
|
|
92
|
+
break
|
|
93
|
+
default:
|
|
94
|
+
info.name = part
|
|
95
|
+
break
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return info
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
}
|
package/src/util/lazy.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
export namespace Locale {
|
|
2
|
+
export function titlecase(str: string) {
|
|
3
|
+
return str.replace(/\b\w/g, (c) => c.toUpperCase())
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function time(input: number): string {
|
|
7
|
+
const date = new Date(input)
|
|
8
|
+
return date.toLocaleTimeString(undefined, { timeStyle: "short" })
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function datetime(input: number): string {
|
|
12
|
+
const date = new Date(input)
|
|
13
|
+
const localTime = time(input)
|
|
14
|
+
const localDate = date.toLocaleDateString()
|
|
15
|
+
return `${localTime} · ${localDate}`
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function todayTimeOrDateTime(input: number): string {
|
|
19
|
+
const date = new Date(input)
|
|
20
|
+
const now = new Date()
|
|
21
|
+
const isToday =
|
|
22
|
+
date.getFullYear() === now.getFullYear() && date.getMonth() === now.getMonth() && date.getDate() === now.getDate()
|
|
23
|
+
|
|
24
|
+
if (isToday) {
|
|
25
|
+
return time(input)
|
|
26
|
+
} else {
|
|
27
|
+
return datetime(input)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function number(num: number): string {
|
|
32
|
+
if (num >= 1000000) {
|
|
33
|
+
return (num / 1000000).toFixed(1) + "M"
|
|
34
|
+
} else if (num >= 1000) {
|
|
35
|
+
return (num / 1000).toFixed(1) + "K"
|
|
36
|
+
}
|
|
37
|
+
return num.toString()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function duration(input: number) {
|
|
41
|
+
if (input < 1000) {
|
|
42
|
+
return `${input}ms`
|
|
43
|
+
}
|
|
44
|
+
if (input < 60000) {
|
|
45
|
+
return `${(input / 1000).toFixed(1)}s`
|
|
46
|
+
}
|
|
47
|
+
if (input < 3600000) {
|
|
48
|
+
const minutes = Math.floor(input / 60000)
|
|
49
|
+
const seconds = Math.floor((input % 60000) / 1000)
|
|
50
|
+
return `${minutes}m ${seconds}s`
|
|
51
|
+
}
|
|
52
|
+
if (input < 86400000) {
|
|
53
|
+
const hours = Math.floor(input / 3600000)
|
|
54
|
+
const minutes = Math.floor((input % 3600000) / 60000)
|
|
55
|
+
return `${hours}h ${minutes}m`
|
|
56
|
+
}
|
|
57
|
+
const hours = Math.floor(input / 3600000)
|
|
58
|
+
const days = Math.floor((input % 3600000) / 86400000)
|
|
59
|
+
return `${days}d ${hours}h`
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function truncate(str: string, len: number): string {
|
|
63
|
+
if (str.length <= len) return str
|
|
64
|
+
return str.slice(0, len - 1) + "…"
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function truncateMiddle(str: string, maxLength: number = 35): string {
|
|
68
|
+
if (str.length <= maxLength) return str
|
|
69
|
+
|
|
70
|
+
const ellipsis = "…"
|
|
71
|
+
const keepStart = Math.ceil((maxLength - ellipsis.length) / 2)
|
|
72
|
+
const keepEnd = Math.floor((maxLength - ellipsis.length) / 2)
|
|
73
|
+
|
|
74
|
+
return str.slice(0, keepStart) + ellipsis + str.slice(-keepEnd)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function pluralize(count: number, singular: string, plural: string): string {
|
|
78
|
+
const template = count === 1 ? singular : plural
|
|
79
|
+
return template.replace("{}", count.toString())
|
|
80
|
+
}
|
|
81
|
+
}
|
package/src/util/lock.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
export namespace Lock {
|
|
2
|
+
const locks = new Map<
|
|
3
|
+
string,
|
|
4
|
+
{
|
|
5
|
+
readers: number
|
|
6
|
+
writer: boolean
|
|
7
|
+
waitingReaders: (() => void)[]
|
|
8
|
+
waitingWriters: (() => void)[]
|
|
9
|
+
}
|
|
10
|
+
>()
|
|
11
|
+
|
|
12
|
+
function get(key: string) {
|
|
13
|
+
if (!locks.has(key)) {
|
|
14
|
+
locks.set(key, {
|
|
15
|
+
readers: 0,
|
|
16
|
+
writer: false,
|
|
17
|
+
waitingReaders: [],
|
|
18
|
+
waitingWriters: [],
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
return locks.get(key)!
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function process(key: string) {
|
|
25
|
+
const lock = locks.get(key)
|
|
26
|
+
if (!lock || lock.writer || lock.readers > 0) return
|
|
27
|
+
|
|
28
|
+
// Prioritize writers to prevent starvation
|
|
29
|
+
if (lock.waitingWriters.length > 0) {
|
|
30
|
+
const nextWriter = lock.waitingWriters.shift()!
|
|
31
|
+
nextWriter()
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Wake up all waiting readers
|
|
36
|
+
while (lock.waitingReaders.length > 0) {
|
|
37
|
+
const nextReader = lock.waitingReaders.shift()!
|
|
38
|
+
nextReader()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Clean up empty locks
|
|
42
|
+
if (lock.readers === 0 && !lock.writer && lock.waitingReaders.length === 0 && lock.waitingWriters.length === 0) {
|
|
43
|
+
locks.delete(key)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function read(key: string): Promise<Disposable> {
|
|
48
|
+
const lock = get(key)
|
|
49
|
+
|
|
50
|
+
return new Promise((resolve) => {
|
|
51
|
+
if (!lock.writer && lock.waitingWriters.length === 0) {
|
|
52
|
+
lock.readers++
|
|
53
|
+
resolve({
|
|
54
|
+
[Symbol.dispose]: () => {
|
|
55
|
+
lock.readers--
|
|
56
|
+
process(key)
|
|
57
|
+
},
|
|
58
|
+
})
|
|
59
|
+
} else {
|
|
60
|
+
lock.waitingReaders.push(() => {
|
|
61
|
+
lock.readers++
|
|
62
|
+
resolve({
|
|
63
|
+
[Symbol.dispose]: () => {
|
|
64
|
+
lock.readers--
|
|
65
|
+
process(key)
|
|
66
|
+
},
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function write(key: string): Promise<Disposable> {
|
|
74
|
+
const lock = get(key)
|
|
75
|
+
|
|
76
|
+
return new Promise((resolve) => {
|
|
77
|
+
if (!lock.writer && lock.readers === 0) {
|
|
78
|
+
lock.writer = true
|
|
79
|
+
resolve({
|
|
80
|
+
[Symbol.dispose]: () => {
|
|
81
|
+
lock.writer = false
|
|
82
|
+
process(key)
|
|
83
|
+
},
|
|
84
|
+
})
|
|
85
|
+
} else {
|
|
86
|
+
lock.waitingWriters.push(() => {
|
|
87
|
+
lock.writer = true
|
|
88
|
+
resolve({
|
|
89
|
+
[Symbol.dispose]: () => {
|
|
90
|
+
lock.writer = false
|
|
91
|
+
process(key)
|
|
92
|
+
},
|
|
93
|
+
})
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
}
|
package/src/util/log.ts
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import path from "path"
|
|
2
|
+
import fs from "fs/promises"
|
|
3
|
+
import { Global } from "../global"
|
|
4
|
+
import z from "zod"
|
|
5
|
+
|
|
6
|
+
export namespace Log {
|
|
7
|
+
export const Level = z.enum(["DEBUG", "INFO", "WARN", "ERROR"]).meta({ ref: "LogLevel", description: "Log level" })
|
|
8
|
+
export type Level = z.infer<typeof Level>
|
|
9
|
+
|
|
10
|
+
const levelPriority: Record<Level, number> = {
|
|
11
|
+
DEBUG: 0,
|
|
12
|
+
INFO: 1,
|
|
13
|
+
WARN: 2,
|
|
14
|
+
ERROR: 3,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let level: Level = "INFO"
|
|
18
|
+
|
|
19
|
+
function shouldLog(input: Level): boolean {
|
|
20
|
+
return levelPriority[input] >= levelPriority[level]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type Logger = {
|
|
24
|
+
debug(message?: any, extra?: Record<string, any>): void
|
|
25
|
+
info(message?: any, extra?: Record<string, any>): void
|
|
26
|
+
error(message?: any, extra?: Record<string, any>): void
|
|
27
|
+
warn(message?: any, extra?: Record<string, any>): void
|
|
28
|
+
tag(key: string, value: string): Logger
|
|
29
|
+
clone(): Logger
|
|
30
|
+
time(
|
|
31
|
+
message: string,
|
|
32
|
+
extra?: Record<string, any>,
|
|
33
|
+
): {
|
|
34
|
+
stop(): void
|
|
35
|
+
[Symbol.dispose](): void
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const loggers = new Map<string, Logger>()
|
|
40
|
+
|
|
41
|
+
export const Default = create({ service: "default" })
|
|
42
|
+
|
|
43
|
+
export interface Options {
|
|
44
|
+
print: boolean
|
|
45
|
+
dev?: boolean
|
|
46
|
+
level?: Level
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let logpath = ""
|
|
50
|
+
export function file() {
|
|
51
|
+
return logpath
|
|
52
|
+
}
|
|
53
|
+
let write = (msg: any) => {
|
|
54
|
+
process.stderr.write(msg)
|
|
55
|
+
return msg.length
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function init(options: Options) {
|
|
59
|
+
if (options.level) level = options.level
|
|
60
|
+
cleanup(Global.Path.log)
|
|
61
|
+
if (options.print) return
|
|
62
|
+
logpath = path.join(
|
|
63
|
+
Global.Path.log,
|
|
64
|
+
options.dev ? "dev.log" : new Date().toISOString().split(".")[0].replace(/:/g, "") + ".log",
|
|
65
|
+
)
|
|
66
|
+
const logfile = Bun.file(logpath)
|
|
67
|
+
await fs.truncate(logpath).catch(() => {})
|
|
68
|
+
const writer = logfile.writer()
|
|
69
|
+
write = async (msg: any) => {
|
|
70
|
+
const num = writer.write(msg)
|
|
71
|
+
writer.flush()
|
|
72
|
+
return num
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function cleanup(dir: string) {
|
|
77
|
+
const glob = new Bun.Glob("????-??-??T??????.log")
|
|
78
|
+
const files = await Array.fromAsync(
|
|
79
|
+
glob.scan({
|
|
80
|
+
cwd: dir,
|
|
81
|
+
absolute: true,
|
|
82
|
+
}),
|
|
83
|
+
)
|
|
84
|
+
if (files.length <= 5) return
|
|
85
|
+
|
|
86
|
+
const filesToDelete = files.slice(0, -10)
|
|
87
|
+
await Promise.all(filesToDelete.map((file) => fs.unlink(file).catch(() => {})))
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function formatError(error: Error, depth = 0): string {
|
|
91
|
+
const result = error.message
|
|
92
|
+
return error.cause instanceof Error && depth < 10
|
|
93
|
+
? result + " Caused by: " + formatError(error.cause, depth + 1)
|
|
94
|
+
: result
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
let last = Date.now()
|
|
98
|
+
export function create(tags?: Record<string, any>) {
|
|
99
|
+
tags = tags || {}
|
|
100
|
+
|
|
101
|
+
const service = tags["service"]
|
|
102
|
+
if (service && typeof service === "string") {
|
|
103
|
+
const cached = loggers.get(service)
|
|
104
|
+
if (cached) {
|
|
105
|
+
return cached
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function build(message: any, extra?: Record<string, any>) {
|
|
110
|
+
const prefix = Object.entries({
|
|
111
|
+
...tags,
|
|
112
|
+
...extra,
|
|
113
|
+
})
|
|
114
|
+
.filter(([_, value]) => value !== undefined && value !== null)
|
|
115
|
+
.map(([key, value]) => {
|
|
116
|
+
const prefix = `${key}=`
|
|
117
|
+
if (value instanceof Error) return prefix + formatError(value)
|
|
118
|
+
if (typeof value === "object") return prefix + JSON.stringify(value)
|
|
119
|
+
return prefix + value
|
|
120
|
+
})
|
|
121
|
+
.join(" ")
|
|
122
|
+
const next = new Date()
|
|
123
|
+
const diff = next.getTime() - last
|
|
124
|
+
last = next.getTime()
|
|
125
|
+
return [next.toISOString().split(".")[0], "+" + diff + "ms", prefix, message].filter(Boolean).join(" ") + "\n"
|
|
126
|
+
}
|
|
127
|
+
const result: Logger = {
|
|
128
|
+
debug(message?: any, extra?: Record<string, any>) {
|
|
129
|
+
if (shouldLog("DEBUG")) {
|
|
130
|
+
write("DEBUG " + build(message, extra))
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
info(message?: any, extra?: Record<string, any>) {
|
|
134
|
+
if (shouldLog("INFO")) {
|
|
135
|
+
write("INFO " + build(message, extra))
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
error(message?: any, extra?: Record<string, any>) {
|
|
139
|
+
if (shouldLog("ERROR")) {
|
|
140
|
+
write("ERROR " + build(message, extra))
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
warn(message?: any, extra?: Record<string, any>) {
|
|
144
|
+
if (shouldLog("WARN")) {
|
|
145
|
+
write("WARN " + build(message, extra))
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
tag(key: string, value: string) {
|
|
149
|
+
if (tags) tags[key] = value
|
|
150
|
+
return result
|
|
151
|
+
},
|
|
152
|
+
clone() {
|
|
153
|
+
return Log.create({ ...tags })
|
|
154
|
+
},
|
|
155
|
+
time(message: string, extra?: Record<string, any>) {
|
|
156
|
+
const now = Date.now()
|
|
157
|
+
result.info(message, { status: "started", ...extra })
|
|
158
|
+
function stop() {
|
|
159
|
+
result.info(message, {
|
|
160
|
+
status: "completed",
|
|
161
|
+
duration: Date.now() - now,
|
|
162
|
+
...extra,
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
stop,
|
|
167
|
+
[Symbol.dispose]() {
|
|
168
|
+
stop()
|
|
169
|
+
},
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (service && typeof service === "string") {
|
|
175
|
+
loggers.set(service, result)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return result
|
|
179
|
+
}
|
|
180
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export class AsyncQueue<T> implements AsyncIterable<T> {
|
|
2
|
+
private queue: T[] = []
|
|
3
|
+
private resolvers: ((value: T) => void)[] = []
|
|
4
|
+
|
|
5
|
+
push(item: T) {
|
|
6
|
+
const resolve = this.resolvers.shift()
|
|
7
|
+
if (resolve) resolve(item)
|
|
8
|
+
else this.queue.push(item)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async next(): Promise<T> {
|
|
12
|
+
if (this.queue.length > 0) return this.queue.shift()!
|
|
13
|
+
return new Promise((resolve) => this.resolvers.push(resolve))
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async *[Symbol.asyncIterator]() {
|
|
17
|
+
while (true) yield await this.next()
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function work<T>(concurrency: number, items: T[], fn: (item: T) => Promise<void>) {
|
|
22
|
+
const pending = [...items]
|
|
23
|
+
await Promise.all(
|
|
24
|
+
Array.from({ length: concurrency }, async () => {
|
|
25
|
+
while (true) {
|
|
26
|
+
const item = pending.pop()
|
|
27
|
+
if (item === undefined) return
|
|
28
|
+
await fn(item)
|
|
29
|
+
}
|
|
30
|
+
}),
|
|
31
|
+
)
|
|
32
|
+
}
|
package/src/util/rpc.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export namespace Rpc {
|
|
2
|
+
type Definition = {
|
|
3
|
+
[method: string]: (input: any) => any
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function listen(rpc: Definition) {
|
|
7
|
+
onmessage = async (evt) => {
|
|
8
|
+
const parsed = JSON.parse(evt.data)
|
|
9
|
+
if (parsed.type === "rpc.request") {
|
|
10
|
+
const result = await rpc[parsed.method](parsed.input)
|
|
11
|
+
postMessage(JSON.stringify({ type: "rpc.result", result, id: parsed.id }))
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function client<T extends Definition>(target: {
|
|
17
|
+
postMessage: (data: string) => void | null
|
|
18
|
+
onmessage: ((this: Worker, ev: MessageEvent<any>) => any) | null
|
|
19
|
+
}) {
|
|
20
|
+
const pending = new Map<number, (result: any) => void>()
|
|
21
|
+
let id = 0
|
|
22
|
+
target.onmessage = async (evt) => {
|
|
23
|
+
const parsed = JSON.parse(evt.data)
|
|
24
|
+
if (parsed.type === "rpc.result") {
|
|
25
|
+
const resolve = pending.get(parsed.id)
|
|
26
|
+
if (resolve) {
|
|
27
|
+
resolve(parsed.result)
|
|
28
|
+
pending.delete(parsed.id)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
call<Method extends keyof T>(method: Method, input: Parameters<T[Method]>[0]): Promise<ReturnType<T[Method]>> {
|
|
34
|
+
const requestId = id++
|
|
35
|
+
return new Promise((resolve) => {
|
|
36
|
+
pending.set(requestId, resolve)
|
|
37
|
+
target.postMessage(JSON.stringify({ type: "rpc.request", method, input, id: requestId }))
|
|
38
|
+
})
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {
|
|
2
|
+
let timeout: NodeJS.Timeout
|
|
3
|
+
return Promise.race([
|
|
4
|
+
promise.then((result) => {
|
|
5
|
+
clearTimeout(timeout)
|
|
6
|
+
return result
|
|
7
|
+
}),
|
|
8
|
+
new Promise<never>((_, reject) => {
|
|
9
|
+
timeout = setTimeout(() => {
|
|
10
|
+
reject(new Error(`Operation timed out after ${ms}ms`))
|
|
11
|
+
}, ms)
|
|
12
|
+
}),
|
|
13
|
+
])
|
|
14
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { sortBy, pipe } from "remeda"
|
|
2
|
+
|
|
3
|
+
export namespace Wildcard {
|
|
4
|
+
export function match(str: string, pattern: string) {
|
|
5
|
+
const regex = new RegExp(
|
|
6
|
+
"^" +
|
|
7
|
+
pattern
|
|
8
|
+
.replace(/[.+^${}()|[\]\\]/g, "\\$&") // escape special regex chars
|
|
9
|
+
.replace(/\*/g, ".*") // * becomes .*
|
|
10
|
+
.replace(/\?/g, ".") + // ? becomes .
|
|
11
|
+
"$",
|
|
12
|
+
"s", // s flag enables multiline matching
|
|
13
|
+
)
|
|
14
|
+
return regex.test(str)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function all(input: string, patterns: Record<string, any>) {
|
|
18
|
+
const sorted = pipe(patterns, Object.entries, sortBy([([key]) => key.length, "asc"], [([key]) => key, "asc"]))
|
|
19
|
+
let result = undefined
|
|
20
|
+
for (const [pattern, value] of sorted) {
|
|
21
|
+
if (match(input, pattern)) {
|
|
22
|
+
result = value
|
|
23
|
+
continue
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return result
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function allStructured(input: { head: string; tail: string[] }, patterns: Record<string, any>) {
|
|
30
|
+
const sorted = pipe(patterns, Object.entries, sortBy([([key]) => key.length, "asc"], [([key]) => key, "asc"]))
|
|
31
|
+
let result = undefined
|
|
32
|
+
for (const [pattern, value] of sorted) {
|
|
33
|
+
const parts = pattern.split(/\s+/)
|
|
34
|
+
if (!match(input.head, parts[0])) continue
|
|
35
|
+
if (parts.length === 1 || matchSequence(input.tail, parts.slice(1))) {
|
|
36
|
+
result = value
|
|
37
|
+
continue
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return result
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function matchSequence(items: string[], patterns: string[]): boolean {
|
|
44
|
+
if (patterns.length === 0) return true
|
|
45
|
+
const [pattern, ...rest] = patterns
|
|
46
|
+
if (pattern === "*") return matchSequence(items, rest)
|
|
47
|
+
for (let i = 0; i < items.length; i++) {
|
|
48
|
+
if (match(items[i], pattern) && matchSequence(items.slice(i + 1), rest)) {
|
|
49
|
+
return true
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return false
|
|
53
|
+
}
|
|
54
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/tsconfig",
|
|
3
|
+
"extends": "@tsconfig/bun/tsconfig.json",
|
|
4
|
+
"compilerOptions": {
|
|
5
|
+
"jsx": "preserve",
|
|
6
|
+
"jsxImportSource": "@opentui/solid",
|
|
7
|
+
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
|
8
|
+
"types": [],
|
|
9
|
+
"noUncheckedIndexedAccess": false,
|
|
10
|
+
"customConditions": ["browser"],
|
|
11
|
+
"paths": {
|
|
12
|
+
"@/*": ["./src/*"],
|
|
13
|
+
"@tui/*": ["./src/cli/cmd/tui/*"]
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|