kcg-code 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +228 -2
- package/bin/kcgcode.ts +13 -0
- package/bun-env.d.ts +19 -0
- package/kcg-code.config.example.json +3 -0
- package/package.json +83 -4
- package/public/logo.svg +1 -0
- package/public/manifest.json +18 -0
- package/public/sw.js +49 -0
- package/src/App.tsx +39 -0
- package/src/__tests__/manifest.test.ts +61 -0
- package/src/build-config.ts +9 -0
- package/src/cli/__tests__/dashboard.test.ts +62 -0
- package/src/cli/args.ts +123 -0
- package/src/cli/commands.ts +174 -0
- package/src/cli/dashboard.tsx +343 -0
- package/src/cli/index.ts +37 -0
- package/src/cli/theme.ts +100 -0
- package/src/components/auth/AuthGate.tsx +182 -0
- package/src/components/auth/AvatarEditor.tsx +236 -0
- package/src/components/auth/LockScreen.tsx +714 -0
- package/src/components/auth/ProfileAvatar.tsx +60 -0
- package/src/components/auth/WelcomeScreen.tsx +672 -0
- package/src/components/auth/__tests__/WelcomeScreen.test.tsx +42 -0
- package/src/components/auth/avatar-icons.tsx +114 -0
- package/src/components/layout/AppShell.tsx +142 -0
- package/src/components/layout/AppSidebar.tsx +419 -0
- package/src/components/layout/SearchDialog.tsx +189 -0
- package/src/components/layout/SidebarAccount.tsx +111 -0
- package/src/components/layout/SidebarSessionMenu.tsx +163 -0
- package/src/components/layout/ThemeToggle.tsx +67 -0
- package/src/components/projects/FolderBrowser.tsx +142 -0
- package/src/components/projects/HomeChat.tsx +250 -0
- package/src/components/projects/NewProjectDialog.tsx +249 -0
- package/src/components/projects/PanelSection.tsx +102 -0
- package/src/components/projects/ProjectInstructions.tsx +229 -0
- package/src/components/projects/ProjectRow.tsx +58 -0
- package/src/components/projects/ProjectSidePanel.tsx +448 -0
- package/src/components/sessions/AgentModeList.tsx +265 -0
- package/src/components/sessions/AgentPicker.tsx +123 -0
- package/src/components/sessions/AssistantTurn.tsx +145 -0
- package/src/components/sessions/AttachmentTile.tsx +161 -0
- package/src/components/sessions/CodeBlock.tsx +107 -0
- package/src/components/sessions/ComposerActionSheet.tsx +96 -0
- package/src/components/sessions/ConfirmDeleteProjectDialog.tsx +80 -0
- package/src/components/sessions/ConfirmSessionDeleteDialog.tsx +72 -0
- package/src/components/sessions/ContextPanel.tsx +561 -0
- package/src/components/sessions/DropPlate.tsx +165 -0
- package/src/components/sessions/FileDropOverlay.tsx +232 -0
- package/src/components/sessions/FileMentionDropdown.tsx +47 -0
- package/src/components/sessions/ImageLightbox.tsx +173 -0
- package/src/components/sessions/MarkdownContent.tsx +187 -0
- package/src/components/sessions/Mascot.tsx +47 -0
- package/src/components/sessions/MessageActions.tsx +60 -0
- package/src/components/sessions/MessageRail.tsx +121 -0
- package/src/components/sessions/ModelPicker.tsx +209 -0
- package/src/components/sessions/ModelSearchList.tsx +151 -0
- package/src/components/sessions/NewSessionDialog.tsx +189 -0
- package/src/components/sessions/PendingAttachments.tsx +127 -0
- package/src/components/sessions/PromptCard.tsx +1085 -0
- package/src/components/sessions/PromptPanel.tsx +112 -0
- package/src/components/sessions/SessionComposer.tsx +640 -0
- package/src/components/sessions/SessionHeader.tsx +175 -0
- package/src/components/sessions/SessionList.tsx +396 -0
- package/src/components/sessions/SessionRow.tsx +137 -0
- package/src/components/sessions/SessionTimeline.tsx +275 -0
- package/src/components/sessions/SessionTimelineSkeleton.tsx +83 -0
- package/src/components/sessions/SessionView.tsx +236 -0
- package/src/components/sessions/ThoughtProcessBlock.tsx +199 -0
- package/src/components/sessions/UserMessage.tsx +177 -0
- package/src/components/sessions/__tests__/ContextPanel.test.tsx +220 -0
- package/src/components/sessions/__tests__/MarkdownContent.test.tsx +89 -0
- package/src/components/sessions/__tests__/MessageRail.test.tsx +40 -0
- package/src/components/sessions/__tests__/PermissionCard.test.tsx +79 -0
- package/src/components/sessions/__tests__/PromptCard.test.ts +166 -0
- package/src/components/sessions/__tests__/QuestionCard.test.tsx +166 -0
- package/src/components/sessions/__tests__/SessionTimelineSkeleton.test.tsx +32 -0
- package/src/components/sessions/__tests__/SessionView.test.ts +386 -0
- package/src/components/settings/AboutGroup.tsx +170 -0
- package/src/components/settings/AccountGroup.tsx +122 -0
- package/src/components/settings/DevicesGroup.tsx +151 -0
- package/src/components/settings/LhrRemoteAccess.tsx +262 -0
- package/src/components/settings/PasskeysGroup.tsx +551 -0
- package/src/components/settings/RemoteAccessGroup.tsx +1226 -0
- package/src/components/settings/SecurityGroup.tsx +566 -0
- package/src/components/settings/WorkspaceGroup.tsx +370 -0
- package/src/components/settings/__tests__/AboutGroup.test.tsx +47 -0
- package/src/components/settings/__tests__/PasskeysGroup.test.tsx +109 -0
- package/src/components/settings/__tests__/WorkspaceGroup.test.tsx +49 -0
- package/src/components/settings/prefs.tsx +265 -0
- package/src/components/skills/InstallNotifications.tsx +325 -0
- package/src/components/skills/InstallSkillDialog.tsx +233 -0
- package/src/components/skills/SkillCards.tsx +210 -0
- package/src/components/skills/SkillsProjectPicker.tsx +178 -0
- package/src/components/ui/alert-dialog.tsx +179 -0
- package/src/components/ui/alert.tsx +60 -0
- package/src/components/ui/badge.tsx +46 -0
- package/src/components/ui/bubble.tsx +125 -0
- package/src/components/ui/button.tsx +62 -0
- package/src/components/ui/card.tsx +75 -0
- package/src/components/ui/collapsible.tsx +19 -0
- package/src/components/ui/dialog.tsx +142 -0
- package/src/components/ui/dropdown-menu.tsx +225 -0
- package/src/components/ui/empty.tsx +94 -0
- package/src/components/ui/field.tsx +234 -0
- package/src/components/ui/input-group.tsx +161 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/label.tsx +19 -0
- package/src/components/ui/message-scroller.tsx +131 -0
- package/src/components/ui/message.tsx +85 -0
- package/src/components/ui/popover.tsx +46 -0
- package/src/components/ui/select.tsx +172 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/sheet.tsx +138 -0
- package/src/components/ui/sidebar.tsx +699 -0
- package/src/components/ui/skeleton.tsx +15 -0
- package/src/components/ui/sonner.tsx +45 -0
- package/src/components/ui/spinner.tsx +16 -0
- package/src/components/ui/switch.tsx +32 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/tooltip.tsx +51 -0
- package/src/config.ts +85 -0
- package/src/db.ts +146 -0
- package/src/frontend.tsx +51 -0
- package/src/hooks/__tests__/useMention.test.ts +79 -0
- package/src/hooks/__tests__/useResyncOnReconnect.test.ts +128 -0
- package/src/hooks/__tests__/useRouter.test.ts +17 -0
- package/src/hooks/__tests__/useSmoothText.react.test.ts +194 -0
- package/src/hooks/__tests__/useSmoothText.test.ts +61 -0
- package/src/hooks/__tests__/useTheme.test.ts +118 -0
- package/src/hooks/use-mobile.ts +16 -0
- package/src/hooks/useAgentPicker.ts +97 -0
- package/src/hooks/useMediaQuery.ts +49 -0
- package/src/hooks/useMention.ts +236 -0
- package/src/hooks/useProjects.ts +88 -0
- package/src/hooks/useResyncOnReconnect.ts +28 -0
- package/src/hooks/useRouter.ts +92 -0
- package/src/hooks/useSessionChat.ts +427 -0
- package/src/hooks/useSessionContext.ts +106 -0
- package/src/hooks/useSessionList.ts +234 -0
- package/src/hooks/useSidebarData.ts +108 -0
- package/src/hooks/useSmoothText.ts +122 -0
- package/src/hooks/useTheme.ts +164 -0
- package/src/hooks/useWebSocket.ts +196 -0
- package/src/index.css +27 -0
- package/src/index.html +14 -0
- package/src/index.ts +49 -0
- package/src/lib/__tests__/attachments.test.ts +58 -0
- package/src/lib/__tests__/avatar-presets.test.ts +25 -0
- package/src/lib/__tests__/collapsible.test.ts +101 -0
- package/src/lib/__tests__/composer.test.ts +118 -0
- package/src/lib/__tests__/highlight.test.ts +42 -0
- package/src/lib/__tests__/home-chat.test.ts +64 -0
- package/src/lib/__tests__/lock-screen.test.ts +64 -0
- package/src/lib/__tests__/model-picker.test.ts +205 -0
- package/src/lib/__tests__/onboarding.test.ts +41 -0
- package/src/lib/__tests__/permission-info.test.ts +61 -0
- package/src/lib/__tests__/project-extensions.test.ts +52 -0
- package/src/lib/__tests__/project-overview.test.ts +173 -0
- package/src/lib/__tests__/references.test.ts +121 -0
- package/src/lib/__tests__/routes.test.ts +134 -0
- package/src/lib/__tests__/session-summary.test.ts +115 -0
- package/src/lib/__tests__/session-title.test.ts +58 -0
- package/src/lib/__tests__/sidebar-groups.test.ts +100 -0
- package/src/lib/__tests__/skill-installs.test.ts +134 -0
- package/src/lib/__tests__/skills-catalog.test.ts +96 -0
- package/src/lib/__tests__/tone.test.ts +37 -0
- package/src/lib/__tests__/turns-delta.test.ts +55 -0
- package/src/lib/api.ts +306 -0
- package/src/lib/attachments.ts +203 -0
- package/src/lib/auth.ts +92 -0
- package/src/lib/avatar-presets.ts +31 -0
- package/src/lib/collapsible.ts +75 -0
- package/src/lib/composer.ts +89 -0
- package/src/lib/data-events.ts +17 -0
- package/src/lib/headlines.ts +30 -0
- package/src/lib/highlight.ts +186 -0
- package/src/lib/layout.ts +20 -0
- package/src/lib/lock-screen.ts +54 -0
- package/src/lib/model-picker.ts +133 -0
- package/src/lib/onboarding.ts +38 -0
- package/src/lib/passkeys.ts +113 -0
- package/src/lib/pending-prompt.ts +53 -0
- package/src/lib/permission-info.ts +101 -0
- package/src/lib/project-extensions.ts +74 -0
- package/src/lib/project-overview.ts +117 -0
- package/src/lib/prompts.ts +87 -0
- package/src/lib/references.ts +106 -0
- package/src/lib/routes.ts +115 -0
- package/src/lib/session-handoff.ts +25 -0
- package/src/lib/session-status.ts +45 -0
- package/src/lib/session-summary.ts +68 -0
- package/src/lib/session-title.ts +63 -0
- package/src/lib/sidebar-groups.ts +78 -0
- package/src/lib/skill-installs.ts +360 -0
- package/src/lib/skills-catalog.ts +99 -0
- package/src/lib/time.ts +22 -0
- package/src/lib/tone.ts +80 -0
- package/src/lib/turns.ts +320 -0
- package/src/lib/utils.ts +6 -0
- package/src/logo.svg +31 -0
- package/src/pages/projects/ProjectDetailPage.tsx +130 -0
- package/src/pages/projects/ProjectsPage.tsx +97 -0
- package/src/pages/sessions/SessionPage.tsx +122 -0
- package/src/pages/settings/SettingsPage.tsx +299 -0
- package/src/pages/settings/__tests__/SettingsPage.test.ts +9 -0
- package/src/pages/skills/SkillsPage.tsx +701 -0
- package/src/paths.ts +121 -0
- package/src/runtime.ts +24 -0
- package/src/server/__tests__/app.test.ts +1031 -0
- package/src/server/__tests__/auth.test.ts +370 -0
- package/src/server/__tests__/config.test.ts +101 -0
- package/src/server/__tests__/db.test.ts +485 -0
- package/src/server/__tests__/host.test.ts +14 -0
- package/src/server/__tests__/tunnel-config.test.ts +38 -0
- package/src/server/app.ts +415 -0
- package/src/server/db/auth.ts +311 -0
- package/src/server/db/messages.ts +50 -0
- package/src/server/db/projects.ts +106 -0
- package/src/server/db/prompts.ts +78 -0
- package/src/server/db/rows.ts +150 -0
- package/src/server/db/schema.ts +221 -0
- package/src/server/db/sessions.ts +239 -0
- package/src/server/db/tunnel.ts +124 -0
- package/src/server/host.ts +12 -0
- package/src/server/result.ts +15 -0
- package/src/server/routes/auth-guard.ts +156 -0
- package/src/server/routes/auth.routes.ts +287 -0
- package/src/server/routes/helpers.ts +165 -0
- package/src/server/routes/meta.routes.ts +24 -0
- package/src/server/routes/passkeys.routes.ts +161 -0
- package/src/server/routes/projects.routes.ts +157 -0
- package/src/server/routes/sessions.routes.ts +197 -0
- package/src/server/routes/skills.routes.ts +117 -0
- package/src/server/routes/tunnel.routes.ts +132 -0
- package/src/server/routes/types.ts +35 -0
- package/src/server/routes/uploads.routes.ts +54 -0
- package/src/server/routes/workspace.routes.ts +127 -0
- package/src/server/services/__tests__/app-info.test.ts +18 -0
- package/src/server/services/__tests__/attachments.test.ts +159 -0
- package/src/server/services/__tests__/auth-passkeys.test.ts +358 -0
- package/src/server/services/__tests__/auth.test.ts +341 -0
- package/src/server/services/__tests__/frpc-installer.test.ts +83 -0
- package/src/server/services/__tests__/git-info.test.ts +74 -0
- package/src/server/services/__tests__/opencode.test.ts +320 -0
- package/src/server/services/__tests__/project-manager.test.ts +190 -0
- package/src/server/services/__tests__/sandbox.test.ts +141 -0
- package/src/server/services/__tests__/session-manager.test.ts +3469 -0
- package/src/server/services/__tests__/skill-install-jobs.test.ts +226 -0
- package/src/server/services/__tests__/skills-registry.test.ts +359 -0
- package/src/server/services/__tests__/tunnel-lhr.test.ts +234 -0
- package/src/server/services/__tests__/tunnel.test.ts +367 -0
- package/src/server/services/__tests__/websocket-gateway.test.ts +677 -0
- package/src/server/services/__tests__/workspace.test.ts +249 -0
- package/src/server/services/app-info.ts +29 -0
- package/src/server/services/attachments.ts +193 -0
- package/src/server/services/auth-passkeys.ts +459 -0
- package/src/server/services/auth.ts +633 -0
- package/src/server/services/frpc-installer.ts +183 -0
- package/src/server/services/git-info.ts +87 -0
- package/src/server/services/opencode-client.ts +714 -0
- package/src/server/services/opencode-server.ts +319 -0
- package/src/server/services/opencode-sse.ts +82 -0
- package/src/server/services/project-manager.ts +147 -0
- package/src/server/services/sandbox.ts +83 -0
- package/src/server/services/session-events.ts +256 -0
- package/src/server/services/session-manager.ts +1374 -0
- package/src/server/services/skill-install-jobs.ts +262 -0
- package/src/server/services/skills-registry.ts +468 -0
- package/src/server/services/tunnel-api.ts +207 -0
- package/src/server/services/tunnel-config.ts +45 -0
- package/src/server/services/tunnel-lhr.ts +387 -0
- package/src/server/services/tunnel.ts +633 -0
- package/src/server/services/turn-stream.ts +339 -0
- package/src/server/services/websocket-gateway.ts +374 -0
- package/src/server/services/workspace.ts +223 -0
- package/src/server/services/ws-transport.ts +121 -0
- package/src/types/context.ts +69 -0
- package/src/types/index.ts +13 -0
- package/src/types/message.ts +58 -0
- package/src/types/project.ts +16 -0
- package/src/types/prompt.ts +118 -0
- package/src/types/session.ts +52 -0
- package/src/types/turns.ts +47 -0
- package/src/ws-protocol.ts +93 -0
- package/styles/globals.css +311 -0
- package/tsconfig.json +37 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Menyediakan binary `frpc` (frp v0.71.0) untuk tunnel.
|
|
3
|
+
*
|
|
4
|
+
* Urutan: `KCG_FRPC_PATH` -> `~/.kcgcode/bin/frpc` -> `frpc` di PATH ->
|
|
5
|
+
* unduh rilis resmi GitHub sesuai OS/arch, verifikasi SHA-256 (checksum
|
|
6
|
+
* di-pin di sini, bukan diambil saat runtime), ekstrak, simpan ke
|
|
7
|
+
* `~/.kcgcode/bin`. Arsip diekstrak dengan `tar` (array argumen, tanpa shell).
|
|
8
|
+
*/
|
|
9
|
+
import {
|
|
10
|
+
chmodSync,
|
|
11
|
+
copyFileSync,
|
|
12
|
+
existsSync,
|
|
13
|
+
mkdirSync,
|
|
14
|
+
mkdtempSync,
|
|
15
|
+
renameSync,
|
|
16
|
+
rmSync,
|
|
17
|
+
} from "node:fs";
|
|
18
|
+
import { tmpdir } from "node:os";
|
|
19
|
+
import path from "node:path";
|
|
20
|
+
import { USER_BIN_DIR } from "../../paths";
|
|
21
|
+
import type { Result } from "../result";
|
|
22
|
+
|
|
23
|
+
export const FRP_VERSION = "0.71.0";
|
|
24
|
+
|
|
25
|
+
/** SHA-256 resmi dari `frp_sha256_checksums.txt` rilis v0.71.0. */
|
|
26
|
+
const FRP_ASSETS: Record<string, { file: string; sha256: string }> = {
|
|
27
|
+
"linux-x64": {
|
|
28
|
+
file: "frp_0.71.0_linux_amd64.tar.gz",
|
|
29
|
+
sha256: "84f27e39f11169f7adcef8e8b70c9329de17747b1f14dad9fb95eef5682ea716",
|
|
30
|
+
},
|
|
31
|
+
"linux-arm64": {
|
|
32
|
+
file: "frp_0.71.0_linux_arm64.tar.gz",
|
|
33
|
+
sha256: "f33c293c275d8fc68c654b6fba8f10b2551d6463d09a9fc9cffb7227eae82266",
|
|
34
|
+
},
|
|
35
|
+
"linux-arm": {
|
|
36
|
+
file: "frp_0.71.0_linux_arm.tar.gz",
|
|
37
|
+
sha256: "f40a984f83e8d34a9241b0be4a9d5fbcfe513a4a5c022b84a02637ff6d36833b",
|
|
38
|
+
},
|
|
39
|
+
"linux-riscv64": {
|
|
40
|
+
file: "frp_0.71.0_linux_riscv64.tar.gz",
|
|
41
|
+
sha256: "92b48d5e4d44d2f1415fde24489d3dfff5badbd52ddf7e816467cdcaa973aa5c",
|
|
42
|
+
},
|
|
43
|
+
"darwin-x64": {
|
|
44
|
+
file: "frp_0.71.0_darwin_amd64.tar.gz",
|
|
45
|
+
sha256: "1b1b4e2f1836e21e8733f1dddaacd4ed9ae67d7dbee39046b9d7b7eda6253637",
|
|
46
|
+
},
|
|
47
|
+
"darwin-arm64": {
|
|
48
|
+
file: "frp_0.71.0_darwin_arm64.tar.gz",
|
|
49
|
+
sha256: "45be02b186860d375ed49a8941ae9569628a54bf14e67fc36b29c98c99dabcc6",
|
|
50
|
+
},
|
|
51
|
+
"freebsd-x64": {
|
|
52
|
+
file: "frp_0.71.0_freebsd_amd64.tar.gz",
|
|
53
|
+
sha256: "207c85353dd66e9ef1125a8ee60e4fd4a562f364bc584c4a5ff55e7ac7355bb8",
|
|
54
|
+
},
|
|
55
|
+
"win32-x64": {
|
|
56
|
+
file: "frp_0.71.0_windows_amd64.zip",
|
|
57
|
+
sha256: "9e5062e3e5cf07e67144a3a4acf175ef6a2486f3605dd6cf288bae34ab39819f",
|
|
58
|
+
},
|
|
59
|
+
"win32-arm64": {
|
|
60
|
+
file: "frp_0.71.0_windows_arm64.zip",
|
|
61
|
+
sha256: "b56a5c2a1a2a55d11bc27aeef6edabd39f3d194360ea66660cc27281b502cb1c",
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export function frpAssetFor(
|
|
66
|
+
platform: string,
|
|
67
|
+
arch: string,
|
|
68
|
+
): { file: string; sha256: string; url: string } | null {
|
|
69
|
+
const asset = FRP_ASSETS[`${platform}-${arch}`];
|
|
70
|
+
if (!asset) return null;
|
|
71
|
+
return {
|
|
72
|
+
...asset,
|
|
73
|
+
url: `https://github.com/fatedier/frp/releases/download/v${FRP_VERSION}/${asset.file}`,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface FrpcInstallerOptions {
|
|
78
|
+
binDir?: string;
|
|
79
|
+
fetch?: typeof fetch;
|
|
80
|
+
platform?: string;
|
|
81
|
+
arch?: string;
|
|
82
|
+
/** Cari binary di PATH (injectable untuk test). */
|
|
83
|
+
which?: (bin: string) => string | null;
|
|
84
|
+
/** Test saja: ganti checksum terpin agar arsip palsu bisa dipasang. */
|
|
85
|
+
sha256Override?: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface FrpcInstaller {
|
|
89
|
+
/** Path frpc siap pakai (mengunduh bila perlu). `onLog` untuk progres. */
|
|
90
|
+
ensure(onLog?: (line: string) => void): Promise<Result<string>>;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function createFrpcInstaller(opts: FrpcInstallerOptions = {}): FrpcInstaller {
|
|
94
|
+
const binDir = opts.binDir ?? USER_BIN_DIR;
|
|
95
|
+
const doFetch = opts.fetch ?? fetch;
|
|
96
|
+
const platform = opts.platform ?? process.platform;
|
|
97
|
+
const arch = opts.arch ?? process.arch;
|
|
98
|
+
const which = opts.which ?? ((b: string) => Bun.which(b));
|
|
99
|
+
const exe = platform === "win32" ? "frpc.exe" : "frpc";
|
|
100
|
+
let inflight: Promise<Result<string>> | null = null;
|
|
101
|
+
|
|
102
|
+
async function download(onLog: (line: string) => void): Promise<Result<string>> {
|
|
103
|
+
const asset = frpAssetFor(platform, arch);
|
|
104
|
+
if (!asset) return { ok: false, error: "FRPC_UNSUPPORTED_PLATFORM" };
|
|
105
|
+
|
|
106
|
+
onLog(`Downloading frp v${FRP_VERSION} (${asset.file})…`);
|
|
107
|
+
let bytes: Uint8Array;
|
|
108
|
+
try {
|
|
109
|
+
const res = await doFetch(asset.url, { signal: AbortSignal.timeout(120_000) });
|
|
110
|
+
if (!res.ok) return { ok: false, error: "FRPC_DOWNLOAD_FAILED" };
|
|
111
|
+
bytes = new Uint8Array(await res.arrayBuffer());
|
|
112
|
+
} catch {
|
|
113
|
+
return { ok: false, error: "FRPC_DOWNLOAD_FAILED" };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const digest = new Bun.CryptoHasher("sha256").update(bytes).digest("hex");
|
|
117
|
+
if (digest !== (opts.sha256Override ?? asset.sha256)) {
|
|
118
|
+
onLog("Checksum mismatch — download discarded.");
|
|
119
|
+
return { ok: false, error: "FRPC_CHECKSUM_MISMATCH" };
|
|
120
|
+
}
|
|
121
|
+
onLog("Checksum verified.");
|
|
122
|
+
|
|
123
|
+
const work = mkdtempSync(path.join(tmpdir(), "kcg-frp-"));
|
|
124
|
+
try {
|
|
125
|
+
const archive = path.join(work, asset.file);
|
|
126
|
+
await Bun.write(archive, bytes);
|
|
127
|
+
const proc = Bun.spawn(["tar", "-xf", archive, "-C", work], {
|
|
128
|
+
stdout: "ignore",
|
|
129
|
+
stderr: "pipe",
|
|
130
|
+
});
|
|
131
|
+
if ((await proc.exited) !== 0) {
|
|
132
|
+
onLog(`Extract failed: ${(await new Response(proc.stderr).text()).trim()}`);
|
|
133
|
+
return { ok: false, error: "FRPC_DOWNLOAD_FAILED" };
|
|
134
|
+
}
|
|
135
|
+
const dirName = asset.file.replace(/\.(tar\.gz|zip)$/, "");
|
|
136
|
+
const extracted = path.join(work, dirName, exe);
|
|
137
|
+
if (!existsSync(extracted)) return { ok: false, error: "FRPC_DOWNLOAD_FAILED" };
|
|
138
|
+
|
|
139
|
+
mkdirSync(binDir, { recursive: true });
|
|
140
|
+
const target = path.join(binDir, exe);
|
|
141
|
+
// /tmp is often a different filesystem (tmpfs) from ~/.kcgcode, and
|
|
142
|
+
// rename() can't cross devices (EXDEV). Copy next to the target first,
|
|
143
|
+
// then rename within binDir so the swap stays atomic.
|
|
144
|
+
const staging = path.join(binDir, `.${exe}.${process.pid}.tmp`);
|
|
145
|
+
try {
|
|
146
|
+
copyFileSync(extracted, staging);
|
|
147
|
+
if (platform !== "win32") chmodSync(staging, 0o755);
|
|
148
|
+
renameSync(staging, target);
|
|
149
|
+
} finally {
|
|
150
|
+
rmSync(staging, { force: true });
|
|
151
|
+
}
|
|
152
|
+
onLog(`Installed frpc to ${target}`);
|
|
153
|
+
return { ok: true, data: target };
|
|
154
|
+
} catch (e) {
|
|
155
|
+
onLog(`Install failed: ${(e as Error).message}`);
|
|
156
|
+
return { ok: false, error: "FRPC_DOWNLOAD_FAILED" };
|
|
157
|
+
} finally {
|
|
158
|
+
rmSync(work, { recursive: true, force: true });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
ensure(onLog = () => {}) {
|
|
164
|
+
const override = process.env.KCG_FRPC_PATH?.trim();
|
|
165
|
+
if (override) {
|
|
166
|
+
return Promise.resolve(
|
|
167
|
+
existsSync(override)
|
|
168
|
+
? { ok: true, data: override }
|
|
169
|
+
: { ok: false, error: "FRPC_NOT_FOUND" },
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
const local = path.join(binDir, exe);
|
|
173
|
+
if (existsSync(local)) return Promise.resolve({ ok: true, data: local });
|
|
174
|
+
const onPath = which("frpc");
|
|
175
|
+
if (onPath) return Promise.resolve({ ok: true, data: onPath });
|
|
176
|
+
|
|
177
|
+
inflight ??= download(onLog).finally(() => {
|
|
178
|
+
inflight = null;
|
|
179
|
+
});
|
|
180
|
+
return inflight;
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Info Git ringkas untuk panel konteks Session (bagian "Environment").
|
|
3
|
+
*
|
|
4
|
+
* opencode sendiri hanya melaporkan `vcs: "git"` tanpa nama cabang, jadi
|
|
5
|
+
* cabang & selisih commit dibaca langsung lewat `git` di worktree Session.
|
|
6
|
+
*
|
|
7
|
+
* Keputusan:
|
|
8
|
+
* - `Bun.spawn` dengan argumen ARRAY (bukan string shell) — path project
|
|
9
|
+
* berasal dari konfigurasi pengguna dan tidak boleh diinterpretasi shell.
|
|
10
|
+
* - Hanya perintah BACA (`rev-parse`, `rev-list`, `remote`); tidak pernah
|
|
11
|
+
* mengubah repo.
|
|
12
|
+
* - Semua kegagalan -> `null`/nilai netral: direktori bukan repo git, `git`
|
|
13
|
+
* tidak terpasang, atau repo tanpa commit bukan kondisi error bagi UI.
|
|
14
|
+
* - Timeout 3 detik per perintah supaya panel tidak menggantung pada repo
|
|
15
|
+
* yang lambat / filesystem jaringan.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { GitState } from "../../types";
|
|
19
|
+
|
|
20
|
+
const TIMEOUT_MS = 3000;
|
|
21
|
+
|
|
22
|
+
export const EMPTY_GIT_INFO: GitState = {
|
|
23
|
+
branch: null,
|
|
24
|
+
ahead: null,
|
|
25
|
+
behind: null,
|
|
26
|
+
hasRemote: false,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** Jalankan satu perintah git; stdout ter-trim, atau null bila gagal. */
|
|
30
|
+
async function git(cwd: string, args: string[]): Promise<string | null> {
|
|
31
|
+
try {
|
|
32
|
+
const proc = Bun.spawn(["git", ...args], {
|
|
33
|
+
cwd,
|
|
34
|
+
stdout: "pipe",
|
|
35
|
+
stderr: "ignore",
|
|
36
|
+
stdin: "ignore",
|
|
37
|
+
});
|
|
38
|
+
const timer = setTimeout(() => proc.kill(), TIMEOUT_MS);
|
|
39
|
+
const [out, code] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
|
|
40
|
+
clearTimeout(timer);
|
|
41
|
+
if (code !== 0) return null;
|
|
42
|
+
const text = out.trim();
|
|
43
|
+
return text === "" ? null : text;
|
|
44
|
+
} catch {
|
|
45
|
+
// `git` tidak ada di PATH, cwd hilang, dll.
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Pasangan "ahead behind" dari `rev-list --count --left-right`. */
|
|
51
|
+
function parseAheadBehind(raw: string | null): { ahead: number | null; behind: number | null } {
|
|
52
|
+
if (raw === null) return { ahead: null, behind: null };
|
|
53
|
+
const [a, b] = raw.split(/\s+/);
|
|
54
|
+
const ahead = Number.parseInt(a ?? "", 10);
|
|
55
|
+
const behind = Number.parseInt(b ?? "", 10);
|
|
56
|
+
return {
|
|
57
|
+
ahead: Number.isFinite(ahead) ? ahead : null,
|
|
58
|
+
behind: Number.isFinite(behind) ? behind : null,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Baca cabang + selisih commit di `cwd`. Bukan repo git / git tidak tersedia
|
|
64
|
+
* -> `EMPTY_GIT_INFO` (panel menampilkannya sebagai "Not a git repository").
|
|
65
|
+
*/
|
|
66
|
+
export async function readGitInfo(cwd: string): Promise<GitState> {
|
|
67
|
+
const inside = await git(cwd, ["rev-parse", "--is-inside-work-tree"]);
|
|
68
|
+
if (inside !== "true") return EMPTY_GIT_INFO;
|
|
69
|
+
|
|
70
|
+
const [branch, remotes] = await Promise.all([
|
|
71
|
+
// `--abbrev-ref HEAD` -> "HEAD" saat detached; diperlakukan tanpa cabang.
|
|
72
|
+
git(cwd, ["rev-parse", "--abbrev-ref", "HEAD"]),
|
|
73
|
+
git(cwd, ["remote"]),
|
|
74
|
+
]);
|
|
75
|
+
|
|
76
|
+
// Selisih commit hanya berarti bila cabang punya upstream.
|
|
77
|
+
const counts = await git(cwd, ["rev-list", "--count", "--left-right", "@{upstream}...HEAD"]);
|
|
78
|
+
// Urutan `--left-right` untuk `upstream...HEAD` = behind (kiri), ahead (kanan).
|
|
79
|
+
const { ahead: left, behind: right } = parseAheadBehind(counts);
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
branch: branch === null || branch === "HEAD" ? null : branch,
|
|
83
|
+
ahead: right,
|
|
84
|
+
behind: left,
|
|
85
|
+
hasRemote: remotes !== null,
|
|
86
|
+
};
|
|
87
|
+
}
|