kcg-code 0.0.0-stage → 0.2.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.
Files changed (307) hide show
  1. package/README.md +230 -2
  2. package/bin/kcgcode.ts +13 -0
  3. package/bun-env.d.ts +19 -0
  4. package/kcg-code.config.example.json +3 -0
  5. package/package.json +84 -4
  6. package/public/logo.svg +1 -0
  7. package/public/manifest.json +18 -0
  8. package/public/sw.js +49 -0
  9. package/src/App.tsx +39 -0
  10. package/src/__tests__/manifest.test.ts +61 -0
  11. package/src/build-config.ts +9 -0
  12. package/src/cli/__tests__/daemon.test.ts +84 -0
  13. package/src/cli/__tests__/dashboard.test.ts +224 -0
  14. package/src/cli/__tests__/keep-awake.test.ts +44 -0
  15. package/src/cli/__tests__/port.test.ts +86 -0
  16. package/src/cli/args.ts +186 -0
  17. package/src/cli/attach.ts +154 -0
  18. package/src/cli/commands.ts +327 -0
  19. package/src/cli/daemon.ts +275 -0
  20. package/src/cli/dashboard.tsx +1060 -0
  21. package/src/cli/index.ts +47 -0
  22. package/src/cli/keep-awake.ts +148 -0
  23. package/src/cli/port.ts +92 -0
  24. package/src/cli/theme.ts +100 -0
  25. package/src/components/auth/AuthGate.tsx +182 -0
  26. package/src/components/auth/AvatarEditor.tsx +236 -0
  27. package/src/components/auth/LockScreen.tsx +714 -0
  28. package/src/components/auth/ProfileAvatar.tsx +60 -0
  29. package/src/components/auth/WelcomeScreen.tsx +672 -0
  30. package/src/components/auth/__tests__/WelcomeScreen.test.tsx +42 -0
  31. package/src/components/auth/avatar-icons.tsx +114 -0
  32. package/src/components/layout/AppShell.tsx +147 -0
  33. package/src/components/layout/AppSidebar.tsx +419 -0
  34. package/src/components/layout/SearchDialog.tsx +189 -0
  35. package/src/components/layout/SidebarAccount.tsx +111 -0
  36. package/src/components/layout/SidebarSessionMenu.tsx +163 -0
  37. package/src/components/layout/ThemeToggle.tsx +67 -0
  38. package/src/components/projects/FolderBrowser.tsx +142 -0
  39. package/src/components/projects/HomeChat.tsx +287 -0
  40. package/src/components/projects/HomeRecentSessions.tsx +90 -0
  41. package/src/components/projects/NewProjectDialog.tsx +249 -0
  42. package/src/components/projects/ProjectInstructions.tsx +218 -0
  43. package/src/components/projects/ProjectRow.tsx +58 -0
  44. package/src/components/projects/ProjectSidePanel.tsx +514 -0
  45. package/src/components/sessions/AgentLogo.tsx +42 -0
  46. package/src/components/sessions/AgentModeList.tsx +265 -0
  47. package/src/components/sessions/AgentPicker.tsx +132 -0
  48. package/src/components/sessions/AssistantTurn.tsx +145 -0
  49. package/src/components/sessions/AttachmentTile.tsx +161 -0
  50. package/src/components/sessions/CodeBlock.tsx +107 -0
  51. package/src/components/sessions/ComposerActionSheet.tsx +96 -0
  52. package/src/components/sessions/ConfirmDeleteProjectDialog.tsx +80 -0
  53. package/src/components/sessions/ConfirmSessionDeleteDialog.tsx +92 -0
  54. package/src/components/sessions/ContextPanel.tsx +661 -0
  55. package/src/components/sessions/DropPlate.tsx +165 -0
  56. package/src/components/sessions/FileDropOverlay.tsx +232 -0
  57. package/src/components/sessions/FileMentionDropdown.tsx +47 -0
  58. package/src/components/sessions/ImageLightbox.tsx +173 -0
  59. package/src/components/sessions/MarkdownContent.tsx +187 -0
  60. package/src/components/sessions/Mascot.tsx +47 -0
  61. package/src/components/sessions/MessageActions.tsx +60 -0
  62. package/src/components/sessions/MessageRail.tsx +121 -0
  63. package/src/components/sessions/ModelPicker.tsx +247 -0
  64. package/src/components/sessions/ModelSearchList.tsx +231 -0
  65. package/src/components/sessions/NewSessionDialog.tsx +172 -0
  66. package/src/components/sessions/PendingAttachments.tsx +127 -0
  67. package/src/components/sessions/PromptCard.tsx +1085 -0
  68. package/src/components/sessions/PromptPanel.tsx +112 -0
  69. package/src/components/sessions/SessionComposer.tsx +641 -0
  70. package/src/components/sessions/SessionHeader.tsx +176 -0
  71. package/src/components/sessions/SessionList.tsx +503 -0
  72. package/src/components/sessions/SessionRow.tsx +237 -0
  73. package/src/components/sessions/SessionTimeline.tsx +275 -0
  74. package/src/components/sessions/SessionTimelineSkeleton.tsx +83 -0
  75. package/src/components/sessions/SessionView.tsx +240 -0
  76. package/src/components/sessions/ThoughtProcessBlock.tsx +199 -0
  77. package/src/components/sessions/UserMessage.tsx +177 -0
  78. package/src/components/sessions/__tests__/ContextPanel.test.tsx +256 -0
  79. package/src/components/sessions/__tests__/MarkdownContent.test.tsx +89 -0
  80. package/src/components/sessions/__tests__/MessageRail.test.tsx +40 -0
  81. package/src/components/sessions/__tests__/PermissionCard.test.tsx +79 -0
  82. package/src/components/sessions/__tests__/PromptCard.test.ts +166 -0
  83. package/src/components/sessions/__tests__/QuestionCard.test.tsx +166 -0
  84. package/src/components/sessions/__tests__/SessionTimelineSkeleton.test.tsx +32 -0
  85. package/src/components/sessions/__tests__/SessionView.test.ts +386 -0
  86. package/src/components/settings/AboutGroup.tsx +170 -0
  87. package/src/components/settings/AccountGroup.tsx +122 -0
  88. package/src/components/settings/DevicesGroup.tsx +151 -0
  89. package/src/components/settings/LhrRemoteAccess.tsx +257 -0
  90. package/src/components/settings/PasskeysGroup.tsx +551 -0
  91. package/src/components/settings/RemoteAccessGroup.tsx +1201 -0
  92. package/src/components/settings/SecurityGroup.tsx +566 -0
  93. package/src/components/settings/WorkspaceGroup.tsx +370 -0
  94. package/src/components/settings/__tests__/AboutGroup.test.tsx +47 -0
  95. package/src/components/settings/__tests__/PasskeysGroup.test.tsx +109 -0
  96. package/src/components/settings/__tests__/WorkspaceGroup.test.tsx +49 -0
  97. package/src/components/settings/prefs.tsx +333 -0
  98. package/src/components/skills/InstallNotifications.tsx +326 -0
  99. package/src/components/skills/InstallSkillDialog.tsx +390 -0
  100. package/src/components/skills/SkillCards.tsx +254 -0
  101. package/src/components/skills/SkillsProjectPicker.tsx +178 -0
  102. package/src/components/ui/adw-dialog.tsx +114 -0
  103. package/src/components/ui/adw-toggle-group.tsx +75 -0
  104. package/src/components/ui/alert-dialog.tsx +179 -0
  105. package/src/components/ui/alert.tsx +60 -0
  106. package/src/components/ui/badge.tsx +46 -0
  107. package/src/components/ui/bubble.tsx +125 -0
  108. package/src/components/ui/button.tsx +62 -0
  109. package/src/components/ui/card.tsx +75 -0
  110. package/src/components/ui/collapsible.tsx +19 -0
  111. package/src/components/ui/dialog.tsx +142 -0
  112. package/src/components/ui/dropdown-menu.tsx +225 -0
  113. package/src/components/ui/empty.tsx +94 -0
  114. package/src/components/ui/field.tsx +234 -0
  115. package/src/components/ui/input-group.tsx +161 -0
  116. package/src/components/ui/input.tsx +21 -0
  117. package/src/components/ui/label.tsx +19 -0
  118. package/src/components/ui/message-scroller.tsx +131 -0
  119. package/src/components/ui/message.tsx +85 -0
  120. package/src/components/ui/popover.tsx +46 -0
  121. package/src/components/ui/search-entry.tsx +70 -0
  122. package/src/components/ui/select.tsx +172 -0
  123. package/src/components/ui/separator.tsx +28 -0
  124. package/src/components/ui/sheet.tsx +138 -0
  125. package/src/components/ui/sidebar.tsx +699 -0
  126. package/src/components/ui/skeleton.tsx +15 -0
  127. package/src/components/ui/sonner.tsx +45 -0
  128. package/src/components/ui/spinner.tsx +16 -0
  129. package/src/components/ui/switch.tsx +32 -0
  130. package/src/components/ui/textarea.tsx +18 -0
  131. package/src/components/ui/tooltip.tsx +51 -0
  132. package/src/config.ts +114 -0
  133. package/src/db.ts +146 -0
  134. package/src/frontend.tsx +51 -0
  135. package/src/hooks/__tests__/useMention.test.ts +79 -0
  136. package/src/hooks/__tests__/useResyncOnReconnect.test.ts +128 -0
  137. package/src/hooks/__tests__/useRouter.test.ts +17 -0
  138. package/src/hooks/__tests__/useSmoothText.react.test.ts +194 -0
  139. package/src/hooks/__tests__/useSmoothText.test.ts +61 -0
  140. package/src/hooks/__tests__/useTheme.test.ts +118 -0
  141. package/src/hooks/use-mobile.ts +16 -0
  142. package/src/hooks/useAgentPicker.ts +101 -0
  143. package/src/hooks/useMediaQuery.ts +49 -0
  144. package/src/hooks/useMention.ts +236 -0
  145. package/src/hooks/useProjects.ts +88 -0
  146. package/src/hooks/useResyncOnReconnect.ts +28 -0
  147. package/src/hooks/useRouter.ts +92 -0
  148. package/src/hooks/useSessionChat.ts +427 -0
  149. package/src/hooks/useSessionContext.ts +106 -0
  150. package/src/hooks/useSessionList.ts +327 -0
  151. package/src/hooks/useSidebarData.ts +108 -0
  152. package/src/hooks/useSmoothText.ts +122 -0
  153. package/src/hooks/useTheme.ts +164 -0
  154. package/src/hooks/useWebSocket.ts +196 -0
  155. package/src/index.css +27 -0
  156. package/src/index.html +14 -0
  157. package/src/index.ts +49 -0
  158. package/src/lib/__tests__/attachments.test.ts +58 -0
  159. package/src/lib/__tests__/avatar-presets.test.ts +25 -0
  160. package/src/lib/__tests__/collapsible.test.ts +101 -0
  161. package/src/lib/__tests__/composer.test.ts +118 -0
  162. package/src/lib/__tests__/highlight.test.ts +42 -0
  163. package/src/lib/__tests__/home-chat.test.ts +64 -0
  164. package/src/lib/__tests__/lock-screen.test.ts +64 -0
  165. package/src/lib/__tests__/model-picker.test.ts +205 -0
  166. package/src/lib/__tests__/onboarding.test.ts +41 -0
  167. package/src/lib/__tests__/permission-info.test.ts +61 -0
  168. package/src/lib/__tests__/project-extensions.test.ts +83 -0
  169. package/src/lib/__tests__/project-overview.test.ts +188 -0
  170. package/src/lib/__tests__/references.test.ts +121 -0
  171. package/src/lib/__tests__/routes.test.ts +134 -0
  172. package/src/lib/__tests__/session-selection.test.ts +41 -0
  173. package/src/lib/__tests__/session-summary.test.ts +115 -0
  174. package/src/lib/__tests__/session-title.test.ts +58 -0
  175. package/src/lib/__tests__/sidebar-groups.test.ts +100 -0
  176. package/src/lib/__tests__/skill-installs.test.ts +135 -0
  177. package/src/lib/__tests__/skills-catalog.test.ts +124 -0
  178. package/src/lib/__tests__/tone.test.ts +37 -0
  179. package/src/lib/__tests__/turns-delta.test.ts +55 -0
  180. package/src/lib/agent-types.ts +49 -0
  181. package/src/lib/api.ts +306 -0
  182. package/src/lib/attachments.ts +203 -0
  183. package/src/lib/auth.ts +92 -0
  184. package/src/lib/avatar-presets.ts +31 -0
  185. package/src/lib/collapsible.ts +75 -0
  186. package/src/lib/composer.ts +89 -0
  187. package/src/lib/data-events.ts +17 -0
  188. package/src/lib/headlines.ts +30 -0
  189. package/src/lib/highlight.ts +186 -0
  190. package/src/lib/layout.ts +20 -0
  191. package/src/lib/lock-screen.ts +54 -0
  192. package/src/lib/model-picker.ts +133 -0
  193. package/src/lib/onboarding.ts +38 -0
  194. package/src/lib/passkeys.ts +113 -0
  195. package/src/lib/pending-prompt.ts +53 -0
  196. package/src/lib/permission-info.ts +101 -0
  197. package/src/lib/project-extensions.ts +136 -0
  198. package/src/lib/project-overview.ts +123 -0
  199. package/src/lib/prompts.ts +87 -0
  200. package/src/lib/references.ts +106 -0
  201. package/src/lib/routes.ts +124 -0
  202. package/src/lib/session-handoff.ts +25 -0
  203. package/src/lib/session-selection.ts +40 -0
  204. package/src/lib/session-status.ts +45 -0
  205. package/src/lib/session-summary.ts +68 -0
  206. package/src/lib/session-title.ts +63 -0
  207. package/src/lib/sidebar-groups.ts +78 -0
  208. package/src/lib/skill-installs.ts +362 -0
  209. package/src/lib/skills-catalog.ts +138 -0
  210. package/src/lib/time.ts +22 -0
  211. package/src/lib/tone.ts +80 -0
  212. package/src/lib/turns.ts +320 -0
  213. package/src/lib/utils.ts +6 -0
  214. package/src/logo.svg +31 -0
  215. package/src/pages/projects/ProjectDetailPage.tsx +131 -0
  216. package/src/pages/projects/ProjectsPage.tsx +97 -0
  217. package/src/pages/sessions/SessionPage.tsx +122 -0
  218. package/src/pages/settings/SettingsPage.tsx +366 -0
  219. package/src/pages/settings/__tests__/SettingsPage.test.ts +16 -0
  220. package/src/pages/skills/SkillsPage.tsx +745 -0
  221. package/src/paths.ts +121 -0
  222. package/src/runtime.ts +24 -0
  223. package/src/server/__tests__/app.test.ts +1044 -0
  224. package/src/server/__tests__/auth.test.ts +370 -0
  225. package/src/server/__tests__/config.test.ts +101 -0
  226. package/src/server/__tests__/db.test.ts +485 -0
  227. package/src/server/__tests__/host.test.ts +14 -0
  228. package/src/server/__tests__/tunnel-config.test.ts +38 -0
  229. package/src/server/app.ts +427 -0
  230. package/src/server/db/auth.ts +311 -0
  231. package/src/server/db/messages.ts +50 -0
  232. package/src/server/db/projects.ts +106 -0
  233. package/src/server/db/prompts.ts +78 -0
  234. package/src/server/db/rows.ts +150 -0
  235. package/src/server/db/schema.ts +221 -0
  236. package/src/server/db/sessions.ts +239 -0
  237. package/src/server/db/tunnel.ts +124 -0
  238. package/src/server/host.ts +12 -0
  239. package/src/server/result.ts +15 -0
  240. package/src/server/routes/auth-guard.ts +156 -0
  241. package/src/server/routes/auth.routes.ts +287 -0
  242. package/src/server/routes/helpers.ts +165 -0
  243. package/src/server/routes/meta.routes.ts +24 -0
  244. package/src/server/routes/passkeys.routes.ts +161 -0
  245. package/src/server/routes/projects.routes.ts +167 -0
  246. package/src/server/routes/sessions.routes.ts +197 -0
  247. package/src/server/routes/skills.routes.ts +136 -0
  248. package/src/server/routes/tunnel.routes.ts +132 -0
  249. package/src/server/routes/types.ts +35 -0
  250. package/src/server/routes/uploads.routes.ts +54 -0
  251. package/src/server/routes/workspace.routes.ts +127 -0
  252. package/src/server/services/__tests__/app-info.test.ts +18 -0
  253. package/src/server/services/__tests__/attachments.test.ts +159 -0
  254. package/src/server/services/__tests__/auth-passkeys.test.ts +358 -0
  255. package/src/server/services/__tests__/auth.test.ts +341 -0
  256. package/src/server/services/__tests__/claude-code.test.ts +609 -0
  257. package/src/server/services/__tests__/claude-mcp.test.ts +90 -0
  258. package/src/server/services/__tests__/claude-skills.test.ts +63 -0
  259. package/src/server/services/__tests__/frpc-installer.test.ts +83 -0
  260. package/src/server/services/__tests__/git-info.test.ts +74 -0
  261. package/src/server/services/__tests__/opencode.test.ts +320 -0
  262. package/src/server/services/__tests__/project-manager.test.ts +190 -0
  263. package/src/server/services/__tests__/sandbox.test.ts +141 -0
  264. package/src/server/services/__tests__/session-manager.test.ts +3509 -0
  265. package/src/server/services/__tests__/skill-install-jobs.test.ts +245 -0
  266. package/src/server/services/__tests__/skills-registry.test.ts +365 -0
  267. package/src/server/services/__tests__/tunnel-lhr.test.ts +234 -0
  268. package/src/server/services/__tests__/tunnel.test.ts +367 -0
  269. package/src/server/services/__tests__/websocket-gateway.test.ts +677 -0
  270. package/src/server/services/__tests__/workspace.test.ts +249 -0
  271. package/src/server/services/app-info.ts +29 -0
  272. package/src/server/services/attachments.ts +193 -0
  273. package/src/server/services/auth-passkeys.ts +459 -0
  274. package/src/server/services/auth.ts +633 -0
  275. package/src/server/services/claude-code-client.ts +1093 -0
  276. package/src/server/services/claude-code-translate.ts +370 -0
  277. package/src/server/services/claude-mcp.ts +122 -0
  278. package/src/server/services/claude-skills.ts +102 -0
  279. package/src/server/services/frpc-installer.ts +183 -0
  280. package/src/server/services/git-info.ts +87 -0
  281. package/src/server/services/opencode-client.ts +714 -0
  282. package/src/server/services/opencode-server.ts +319 -0
  283. package/src/server/services/opencode-sse.ts +82 -0
  284. package/src/server/services/project-manager.ts +147 -0
  285. package/src/server/services/sandbox.ts +83 -0
  286. package/src/server/services/session-events.ts +256 -0
  287. package/src/server/services/session-manager.ts +1439 -0
  288. package/src/server/services/skill-install-jobs.ts +275 -0
  289. package/src/server/services/skills-registry.ts +480 -0
  290. package/src/server/services/tunnel-api.ts +207 -0
  291. package/src/server/services/tunnel-config.ts +45 -0
  292. package/src/server/services/tunnel-lhr.ts +387 -0
  293. package/src/server/services/tunnel.ts +633 -0
  294. package/src/server/services/turn-stream.ts +339 -0
  295. package/src/server/services/websocket-gateway.ts +374 -0
  296. package/src/server/services/workspace.ts +223 -0
  297. package/src/server/services/ws-transport.ts +121 -0
  298. package/src/types/context.ts +69 -0
  299. package/src/types/index.ts +13 -0
  300. package/src/types/message.ts +58 -0
  301. package/src/types/project.ts +16 -0
  302. package/src/types/prompt.ts +118 -0
  303. package/src/types/session.ts +52 -0
  304. package/src/types/turns.ts +47 -0
  305. package/src/ws-protocol.ts +93 -0
  306. package/styles/globals.css +311 -0
  307. package/tsconfig.json +37 -0
package/README.md CHANGED
@@ -1,3 +1,231 @@
1
- # Temporary Holding Version
1
+ # kcgcode
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Control your [OpenCode](https://opencode.ai) agents from your phone. kcgcode runs a local bridge
4
+ on your machine and serves a mobile-friendly web app (PWA) where you chat with agents, answer
5
+ their permission prompts, manage projects and skills, and keep it all behind a lock screen when
6
+ you reach it through a tunnel.
7
+
8
+ > Only OpenCode is supported as an agent right now. Claude Code support is planned.
9
+
10
+ ## Features
11
+
12
+ - **Chat with agents from anywhere.** Start sessions per project, stream replies live, stop a
13
+ running turn, and pick the model and agent mode (build, plan, or your own agents).
14
+ - **Answer prompts on the go.** Permission requests and multi-question prompts from the agent
15
+ show up as cards you can approve, deny, or answer from your phone.
16
+ - **Projects & sessions.** Organise work by folder inside a sandbox. The sidebar groups every
17
+ session by project, with live status and a quick search (`Ctrl/⌘ + K`).
18
+ - **Rich input.** Mention files with `@`, attach images, and set per-project custom
19
+ instructions.
20
+ - **Skills from [skills.sh](https://skills.sh).** Search the directory, see security audit
21
+ results before you install, and add skills to a project in one click. Installs run in the
22
+ background with live output, survive page refreshes, and can be cancelled. The agent picks up
23
+ new skills automatically, waiting for any running chat to finish first.
24
+ - **App lock.** Protect the app with a PIN or password before exposing it. It comes with a lock
25
+ screen showing your profile photo or avatar, auto-lock after inactivity, signed-in device
26
+ management, and brute-force protection.
27
+ - **Installable PWA** with light, dark, or system theme.
28
+ - **Terminal dashboard.** `kcgcode` shows the server status right in your terminal.
29
+
30
+ ## Requirements
31
+
32
+ - [Bun](https://bun.com) 1.1 or newer
33
+ - [OpenCode](https://opencode.ai) installed and on your `PATH` (kcgcode starts `opencode serve`
34
+ for each project)
35
+ - Internet access for skills.sh search and skill installs (installs use `bunx skills`)
36
+
37
+ ## Install
38
+
39
+ ```bash
40
+ bun i -g kcgcode
41
+ ```
42
+
43
+ Or from a local clone:
44
+
45
+ ```bash
46
+ bun pm pack
47
+ bun i -g ./kcgcode-0.1.0.tgz
48
+ ```
49
+
50
+ ## Quick start
51
+
52
+ ```bash
53
+ # create ~/.kcgcode (config, sandbox, data)
54
+ kcgcode init
55
+
56
+ # start the server + terminal dashboard (works from any directory)
57
+ kcgcode
58
+
59
+ # custom port, open the browser
60
+ kcgcode start --port 4000 --open
61
+ ```
62
+
63
+ Open the printed URL in your browser. Projects are folders inside the sandbox root from your
64
+ config.
65
+
66
+ ## Using it from your phone
67
+
68
+ By default the server only listens on `127.0.0.1`, so it's reachable from this machine only.
69
+ To use it from your phone:
70
+
71
+ 1. **Set a lock first.** Open **Settings → Security** and turn on the app lock (PIN or
72
+ password). Until a lock is set, anyone who can reach the address can control your agents and
73
+ run commands on your machine. The sidebar shows a "Not protected" warning while there's no
74
+ lock.
75
+ 2. **Expose it.** Either:
76
+ - Turn on **Settings → Remote access** (built in, see below), or
77
+ - Use an HTTPS tunnel such as Cloudflare Tunnel or ngrok, pointed at the kcgcode port, or
78
+ - Bind to your LAN with `kcgcode --host 0.0.0.0` and open `http://<your-ip>:3000` on the
79
+ same network. Traffic isn't encrypted on plain HTTP, so prefer a tunnel.
80
+ 3. Open the URL on your phone, unlock, and optionally add it to your home screen.
81
+
82
+ Only tunnel the kcgcode port. The per-project `opencode serve` processes have no password of
83
+ their own and should stay local.
84
+
85
+ ### Remote access (`https://<name>.<your-domain>`)
86
+
87
+ kcgcode can publish itself at your own subdomain through a kcgcode tunnel server you host
88
+ (see [`reverse-proxy/`](reverse-proxy)).
89
+
90
+ 1. Set an app lock. Remote access can't be turned on without one, and removing the lock turns
91
+ it off.
92
+ 2. **Settings → Remote access → Connect.** kcgcode shows a code, a link, and a QR code.
93
+ 3. Open the link on any device (your phone works), sign in with Google, check that the code
94
+ matches, and approve. First time only: pick a name (3–20 lowercase letters, numbers, or
95
+ hyphens).
96
+ 4. kcgcode connects and turns the tunnel on. It downloads `frpc` v0.71.0 to `~/.kcgcode/bin`
97
+ (SHA-256 verified; an existing `frpc` on `PATH` is used instead). The tunnel reconnects on its
98
+ own and comes back after a restart.
99
+
100
+ Google sign-in happens entirely on the tunnel service, so kcgcode never holds Google
101
+ credentials. What it stores is a device token (revocable, per machine) and the tunnel secret, in
102
+ SQLite plus `frpc.toml` (mode 0600) next to it. Neither is ever sent to the browser.
103
+ Under **Advanced**, **Unlink this computer** revokes the device token on the server and
104
+ **Reset security key** issues a new frp secret if you think it leaked.
105
+
106
+ The tunnel server URL is baked into the published npm package, so users don't configure
107
+ anything. `KCG_TUNNEL_API_URL` overrides it, for example to point at your own server.
108
+
109
+ | Environment variable | Default |
110
+ | --- | --- |
111
+ | `KCG_TUNNEL_API_URL` | The URL built into the package (none in a git checkout) |
112
+ | `KCG_FRPC_PATH` | auto (`~/.kcgcode/bin/frpc`, then `PATH`, then download) |
113
+
114
+ ## Security
115
+
116
+ When a lock is set:
117
+
118
+ - Every API route and the WebSocket require a signed-in session. The only exceptions are the
119
+ lock screen's status, login, and profile photo.
120
+ - The PIN or password is stored as an argon2id hash. Sessions use an `HttpOnly`,
121
+ `SameSite=Strict` cookie, marked `Secure` over HTTPS. The database only stores a hash of the
122
+ session token.
123
+ - Requests from other sites are rejected (Origin check, including the WebSocket).
124
+ - Auto-lock (5 min to 1 hour, or never) is enforced by the server, not just the UI. Locking,
125
+ changing the lock, or signing out a device also closes that device's live connection.
126
+ - Failed attempts are rate limited per IP and globally, since tunnels can hide the real IP.
127
+ Repeated failures pause all logins for up to 15 minutes.
128
+ - PINs must be 6–12 digits, and trivial patterns like `111111` or `123456` are rejected.
129
+ Passwords need at least 8 characters.
130
+
131
+ **Forgot your PIN or password?** Run this on the host machine:
132
+
133
+ ```bash
134
+ kcgcode reset-lock
135
+ ```
136
+
137
+ It removes the lock and signs out every device, but keeps your profile. Set a new lock right
138
+ away if the app is exposed.
139
+
140
+ Skills run with your agent's full permissions. Review a skill and its audit before installing.
141
+
142
+ ## CLI
143
+
144
+ | Command | Description |
145
+ | --- | --- |
146
+ | `kcgcode` / `kcgcode start` | Run the server + live dashboard |
147
+ | `kcgcode init` | Create `~/.kcgcode` (config, sandbox, data) |
148
+ | `kcgcode reset-lock` | Remove the app lock and sign out all devices |
149
+ | `kcgcode --help` | Show help |
150
+ | `kcgcode --version` | Show version |
151
+
152
+ | Option | Description |
153
+ | --- | --- |
154
+ | `-p, --port <n>` | HTTP port (default `3000`, env `KCG_PORT`) |
155
+ | `-H, --host <h>` | Bind host (default `127.0.0.1`, env `KCG_HOST`) |
156
+ | `-c, --config <f>` | Config path (default `~/.kcgcode/config.json`) |
157
+ | `--open` | Open the web app in your browser |
158
+ | `--allow-sleep` | Let the OS sleep while running (by default kcgcode keeps the machine awake so the tunnel stays connected) |
159
+
160
+ Dashboard keys: `q` quit · `o` open browser · `Ctrl+C` stop.
161
+
162
+ | Environment variable | Description |
163
+ | --- | --- |
164
+ | `KCG_PORT` | HTTP port |
165
+ | `KCG_HOST` | Bind host |
166
+ | `KCG_CONFIG_PATH` | Config file path |
167
+ | `KCG_DB_PATH` | SQLite path (default `~/.kcgcode/data/kcg-code.sqlite`) |
168
+ | `KCG_ALLOW_SLEEP=1` | Same as `--allow-sleep` |
169
+
170
+ ## Config & data location
171
+
172
+ The runtime mode is detected from the entrypoint:
173
+
174
+ | | **dev** (`bun dev` / `src/index.ts`) | **cli** (`kcgcode` global binary) |
175
+ | --- | --- | --- |
176
+ | Config | `./kcg-code.config.json` if present, else `~/.kcgcode/config.json` | `~/.kcgcode/config.json` |
177
+ | SQLite | `./data/kcg-code.sqlite` | `~/.kcgcode/data/kcg-code.sqlite` |
178
+ | Uploads | `./data/uploads` | `~/.kcgcode/data/uploads` |
179
+ | Sandbox (init) | `./sandbox` | `~/.kcgcode/sandbox` |
180
+ | HMR | on | off (`NODE_ENV=production`) |
181
+
182
+ Global CLI layout:
183
+
184
+ ```
185
+ ~/.kcgcode/
186
+ config.json # sandboxRoot
187
+ sandbox/ # default agent workspace
188
+ data/
189
+ kcg-code.sqlite # projects, sessions, messages, app lock
190
+ uploads/ # image attachments
191
+ ```
192
+
193
+ Config file resolution order:
194
+
195
+ 1. `--config <path>` / `KCG_CONFIG_PATH` (always wins)
196
+ 2. **cli**: `~/.kcgcode/config.json` (cwd is ignored)
197
+ 3. **dev**: `./kcg-code.config.json` if present, else `~/.kcgcode/config.json`
198
+
199
+ ```json
200
+ {
201
+ "sandboxRoot": "/absolute/path/to/your/workspace"
202
+ }
203
+ ```
204
+
205
+ Skills installed from the Skills page go into the project folder under `.agents/skills/`.
206
+
207
+ ## Develop
208
+
209
+ Copy `.env.example` to `.env`. Bun loads it automatically, and it's gitignored. Set
210
+ `KCG_TUNNEL_API_URL` to your tunnel server to use Remote access while developing.
211
+
212
+ ```bash
213
+ bun install
214
+ bun dev # dev server with HMR (uses ./kcg-code.config.json if present)
215
+ bun test
216
+ bun run lint
217
+ bun run typecheck
218
+ bun run build
219
+ ```
220
+
221
+ ### Publishing
222
+
223
+ `bun publish` (or `bun pm pack`) runs `prepack`, which writes `KCG_TUNNEL_API_URL` from `.env`
224
+ into `src/build-config.ts`. `postpack` then resets it, so the URL never ends up in git.
225
+ Publishing fails if the variable is missing or isn't `https://`. Check what gets published with:
226
+
227
+ ```bash
228
+ bun pm pack && tar -xOzf kcgcode-*.tgz package/src/build-config.ts
229
+ ```
230
+
231
+ Built with [Bun](https://bun.com).
package/bin/kcgcode.ts ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Entry global CLI `kcgcode` (`bun i -g kcgcode`).
4
+ *
5
+ * Menandai mode runtime `cli` lebih dulu agar config/db/uploads
6
+ * resolve ke `~/.kcgcode`, bukan path lokal cwd.
7
+ */
8
+ import { setRunMode } from "../src/runtime";
9
+
10
+ setRunMode("cli");
11
+
12
+ const { runCli } = await import("../src/cli/index");
13
+ await runCli(process.argv.slice(2));
package/bun-env.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ // Generated by `bun init`
2
+
3
+ declare module "*.svg" {
4
+ /**
5
+ * A path to the SVG file
6
+ */
7
+ const path: `${string}.svg`;
8
+ export = path;
9
+ }
10
+
11
+ declare module "*.css" {}
12
+
13
+ declare module "*.module.css" {
14
+ /**
15
+ * A record of class names to their corresponding CSS module classes
16
+ */
17
+ const classes: { readonly [key: string]: string };
18
+ export = classes;
19
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "sandboxRoot": "/absolute/path/to/your/workspace"
3
+ }
package/package.json CHANGED
@@ -1,6 +1,86 @@
1
1
  {
2
2
  "name": "kcg-code",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "Control CLI AI agents (OpenCode / Claude Code) from your phone — mobile PWA + local headless bridge.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "kcgcode": "./bin/kcgcode.ts"
9
+ },
10
+ "files": [
11
+ "bin",
12
+ "src",
13
+ "public",
14
+ "styles",
15
+ "bun-env.d.ts",
16
+ "tsconfig.json",
17
+ "README.md",
18
+ "kcg-code.config.example.json"
19
+ ],
20
+ "engines": {
21
+ "bun": ">=1.1.0"
22
+ },
23
+ "keywords": [
24
+ "bun",
25
+ "cli",
26
+ "tui",
27
+ "opencode",
28
+ "claude-code",
29
+ "ai-agent",
30
+ "pwa",
31
+ "mobile"
32
+ ],
33
+ "scripts": {
34
+ "dev": "bun --hot src/index.ts",
35
+ "start": "NODE_ENV=production bun src/index.ts",
36
+ "kcgcode": "bun bin/kcgcode.ts",
37
+ "build": "bun run build.ts",
38
+ "test": "bun test",
39
+ "lint": "biome check .",
40
+ "lint:fix": "biome check --write .",
41
+ "format": "biome format --write .",
42
+ "typecheck": "tsc --noEmit",
43
+ "prepack": "bun scripts/stamp-build-config.ts",
44
+ "postpack": "bun scripts/stamp-build-config.ts --reset"
45
+ },
46
+ "dependencies": {
47
+ "@anthropic-ai/claude-agent-sdk": "^0.3.296",
48
+ "@radix-ui/react-label": "^2.1.7",
49
+ "@radix-ui/react-select": "^2.2.6",
50
+ "@radix-ui/react-slot": "^1.2.3",
51
+ "@shadcn/react": "^0.3.0",
52
+ "@simplewebauthn/browser": "14.0.0",
53
+ "@simplewebauthn/server": "14.0.3",
54
+ "bun-plugin-tailwind": "^0.1.2",
55
+ "class-variance-authority": "^0.7.1",
56
+ "clsx": "^2.1.1",
57
+ "cn": "^0.2.5",
58
+ "ink": "^7.1.1",
59
+ "lucide-react": "^1",
60
+ "motion": "13.4.6",
61
+ "picocolors": "^1.1.1",
62
+ "radix-ui": "^1.6.7",
63
+ "react": "^19",
64
+ "react-dom": "^19",
65
+ "react-markdown": "10.1.0",
66
+ "remark-gfm": "4.0.1",
67
+ "shiki": "4.4.3",
68
+ "sonner": "2.0.8",
69
+ "tailwind-merge": "^3.3.1",
70
+ "tw-animate-css": "^1.4.0",
71
+ "uqr": "0.1.3"
72
+ },
73
+ "devDependencies": {
74
+ "@biomejs/biome": "^2.5.7",
75
+ "@types/bun": "latest",
76
+ "@types/react": "^19",
77
+ "@types/react-dom": "^19",
78
+ "fast-check": "^4.9.0",
79
+ "react-reconciler": "0.33.0",
80
+ "tailwindcss": "^4.1.11",
81
+ "typescript": "^6"
82
+ },
83
+ "peerDependencies": {
84
+ "typescript": "^6"
85
+ }
86
+ }
@@ -0,0 +1 @@
1
+ <svg id="Bun" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 70"><title>Bun Logo</title><path id="Shadow" d="M71.09,20.74c-.16-.17-.33-.34-.5-.5s-.33-.34-.5-.5-.33-.34-.5-.5-.33-.34-.5-.5-.33-.34-.5-.5-.33-.34-.5-.5-.33-.34-.5-.5A26.46,26.46,0,0,1,75.5,35.7c0,16.57-16.82,30.05-37.5,30.05-11.58,0-21.94-4.23-28.83-10.86l.5.5.5.5.5.5.5.5.5.5.5.5.5.5C19.55,65.3,30.14,69.75,42,69.75c20.68,0,37.5-13.48,37.5-30C79.5,32.69,76.46,26,71.09,20.74Z"/><g id="Body"><path id="Background" d="M73,35.7c0,15.21-15.67,27.54-35,27.54S3,50.91,3,35.7C3,26.27,9,17.94,18.22,13S33.18,3,38,3s8.94,4.13,19.78,10C67,17.94,73,26.27,73,35.7Z" style="fill:#fbf0df"/><path id="Bottom_Shadow" data-name="Bottom Shadow" d="M73,35.7a21.67,21.67,0,0,0-.8-5.78c-2.73,33.3-43.35,34.9-59.32,24.94A40,40,0,0,0,38,63.24C57.3,63.24,73,50.89,73,35.7Z" style="fill:#f6dece"/><path id="Light_Shine" data-name="Light Shine" d="M24.53,11.17C29,8.49,34.94,3.46,40.78,3.45A9.29,9.29,0,0,0,38,3c-2.42,0-5,1.25-8.25,3.13-1.13.66-2.3,1.39-3.54,2.15-2.33,1.44-5,3.07-8,4.7C8.69,18.13,3,26.62,3,35.7c0,.4,0,.8,0,1.19C9.06,15.48,20.07,13.85,24.53,11.17Z" style="fill:#fffefc"/><path id="Top" d="M35.12,5.53A16.41,16.41,0,0,1,29.49,18c-.28.25-.06.73.3.59,3.37-1.31,7.92-5.23,6-13.14C35.71,5,35.12,5.12,35.12,5.53Zm2.27,0A16.24,16.24,0,0,1,39,19c-.12.35.31.65.55.36C41.74,16.56,43.65,11,37.93,5,37.64,4.74,37.19,5.14,37.39,5.49Zm2.76-.17A16.42,16.42,0,0,1,47,17.12a.33.33,0,0,0,.65.11c.92-3.49.4-9.44-7.17-12.53C40.08,4.54,39.82,5.08,40.15,5.32ZM21.69,15.76a16.94,16.94,0,0,0,10.47-9c.18-.36.75-.22.66.18-1.73,8-7.52,9.67-11.12,9.45C21.32,16.4,21.33,15.87,21.69,15.76Z" style="fill:#ccbea7;fill-rule:evenodd"/><path id="Outline" d="M38,65.75C17.32,65.75.5,52.27.5,35.7c0-10,6.18-19.33,16.53-24.92,3-1.6,5.57-3.21,7.86-4.62,1.26-.78,2.45-1.51,3.6-2.19C32,1.89,35,.5,38,.5s5.62,1.2,8.9,3.14c1,.57,2,1.19,3.07,1.87,2.49,1.54,5.3,3.28,9,5.27C69.32,16.37,75.5,25.69,75.5,35.7,75.5,52.27,58.68,65.75,38,65.75ZM38,3c-2.42,0-5,1.25-8.25,3.13-1.13.66-2.3,1.39-3.54,2.15-2.33,1.44-5,3.07-8,4.7C8.69,18.13,3,26.62,3,35.7,3,50.89,18.7,63.25,38,63.25S73,50.89,73,35.7C73,26.62,67.31,18.13,57.78,13,54,11,51.05,9.12,48.66,7.64c-1.09-.67-2.09-1.29-3-1.84C42.63,4,40.42,3,38,3Z"/></g><g id="Mouth"><g id="Background-2" data-name="Background"><path d="M45.05,43a8.93,8.93,0,0,1-2.92,4.71,6.81,6.81,0,0,1-4,1.88A6.84,6.84,0,0,1,34,47.71,8.93,8.93,0,0,1,31.12,43a.72.72,0,0,1,.8-.81H44.26A.72.72,0,0,1,45.05,43Z" style="fill:#b71422"/></g><g id="Tongue"><path id="Background-3" data-name="Background" d="M34,47.79a6.91,6.91,0,0,0,4.12,1.9,6.91,6.91,0,0,0,4.11-1.9,10.63,10.63,0,0,0,1-1.07,6.83,6.83,0,0,0-4.9-2.31,6.15,6.15,0,0,0-5,2.78C33.56,47.4,33.76,47.6,34,47.79Z" style="fill:#ff6164"/><path id="Outline-2" data-name="Outline" d="M34.16,47a5.36,5.36,0,0,1,4.19-2.08,6,6,0,0,1,4,1.69c.23-.25.45-.51.66-.77a7,7,0,0,0-4.71-1.93,6.36,6.36,0,0,0-4.89,2.36A9.53,9.53,0,0,0,34.16,47Z"/></g><path id="Outline-3" data-name="Outline" d="M38.09,50.19a7.42,7.42,0,0,1-4.45-2,9.52,9.52,0,0,1-3.11-5.05,1.2,1.2,0,0,1,.26-1,1.41,1.41,0,0,1,1.13-.51H44.26a1.44,1.44,0,0,1,1.13.51,1.19,1.19,0,0,1,.25,1h0a9.52,9.52,0,0,1-3.11,5.05A7.42,7.42,0,0,1,38.09,50.19Zm-6.17-7.4c-.16,0-.2.07-.21.09a8.29,8.29,0,0,0,2.73,4.37A6.23,6.23,0,0,0,38.09,49a6.28,6.28,0,0,0,3.65-1.73,8.3,8.3,0,0,0,2.72-4.37.21.21,0,0,0-.2-.09Z"/></g><g id="Face"><ellipse id="Right_Blush" data-name="Right Blush" cx="53.22" cy="40.18" rx="5.85" ry="3.44" style="fill:#febbd0"/><ellipse id="Left_Bluch" data-name="Left Bluch" cx="22.95" cy="40.18" rx="5.85" ry="3.44" style="fill:#febbd0"/><path id="Eyes" d="M25.7,38.8a5.51,5.51,0,1,0-5.5-5.51A5.51,5.51,0,0,0,25.7,38.8Zm24.77,0A5.51,5.51,0,1,0,45,33.29,5.5,5.5,0,0,0,50.47,38.8Z" style="fill-rule:evenodd"/><path id="Iris" d="M24,33.64a2.07,2.07,0,1,0-2.06-2.07A2.07,2.07,0,0,0,24,33.64Zm24.77,0a2.07,2.07,0,1,0-2.06-2.07A2.07,2.07,0,0,0,48.75,33.64Z" style="fill:#fff;fill-rule:evenodd"/></g></svg>
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "KCG Code",
3
+ "short_name": "KCG Code",
4
+ "description": "Kontrol CLI AI Agent (OpenCode / Claude Code) dari perangkat mobile.",
5
+ "start_url": "/",
6
+ "display": "standalone",
7
+ "background_color": "#0a0a0a",
8
+ "theme_color": "#0a0a0a",
9
+ "lang": "id",
10
+ "icons": [
11
+ {
12
+ "src": "/logo.svg",
13
+ "sizes": "any",
14
+ "type": "image/svg+xml",
15
+ "purpose": "any"
16
+ }
17
+ ]
18
+ }
package/public/sw.js ADDED
@@ -0,0 +1,49 @@
1
+ // KCG Code service worker (Requirement 8.1).
2
+ // - Cache-first untuk asset statis (JS/CSS bundle hasil build.ts).
3
+ // - Network-only (pass-through) untuk `/api/*` dan koneksi WebSocket `/ws`,
4
+ // agar kontrol CLI_Agent tidak pernah disajikan dari cache basi.
5
+
6
+ const CACHE_NAME = "kcg-code-v1";
7
+ const PRECACHE = ["/", "/manifest.json", "/logo.svg"];
8
+
9
+ self.addEventListener("install", (event) => {
10
+ event.waitUntil(
11
+ caches
12
+ .open(CACHE_NAME)
13
+ .then((cache) => cache.addAll(PRECACHE))
14
+ .catch(() => {}),
15
+ );
16
+ self.skipWaiting();
17
+ });
18
+
19
+ self.addEventListener("activate", (event) => {
20
+ event.waitUntil(
21
+ caches
22
+ .keys()
23
+ .then((keys) =>
24
+ Promise.all(keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key))),
25
+ ),
26
+ );
27
+ self.clients.claim();
28
+ });
29
+
30
+ self.addEventListener("fetch", (event) => {
31
+ const url = new URL(event.request.url);
32
+
33
+ // Network-only untuk API dan WebSocket.
34
+ if (url.pathname.startsWith("/api/") || url.pathname === "/ws") return;
35
+
36
+ // Cache-first untuk asset statis.
37
+ event.respondWith(
38
+ caches.match(event.request).then((cached) => {
39
+ if (cached) return cached;
40
+ return fetch(event.request).then((response) => {
41
+ if (response.ok && url.origin === self.location.origin) {
42
+ const clone = response.clone();
43
+ caches.open(CACHE_NAME).then((cache) => cache.put(event.request, clone));
44
+ }
45
+ return response;
46
+ });
47
+ }),
48
+ );
49
+ });
package/src/App.tsx ADDED
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Akar aplikasi KCG Code.
3
+ *
4
+ * Layout, navigasi, dan pemetaan URL -> halaman dipegang `AppShell`
5
+ * (`components/layout/AppShell.tsx`) — pola yang sama dengan kcgcode
6
+ * (`src/App.tsx` merender shell, halaman hidup di `src/pages/*`).
7
+ */
8
+
9
+ import { domAnimation, LazyMotion, MotionConfig } from "motion/react";
10
+ import { AuthGate } from "@/components/auth/AuthGate";
11
+ import { AppShell } from "@/components/layout/AppShell";
12
+ import { Toaster } from "@/components/ui/sonner";
13
+ import "./index.css";
14
+
15
+ export function App() {
16
+ // Kunci aplikasi: shell (dan semua request datanya) baru dirender setelah
17
+ // lock screen dibuka.
18
+ //
19
+ // Motion (animasi masuk/keluar komponen):
20
+ // - `LazyMotion` + `domAnimation` + komponen `m.*` (`motion/react-m`)
21
+ // menjaga bundle kecil (tanpa fitur drag/layout). `strict` melempar
22
+ // error bila ada yang tak sengaja memakai `motion.*` (bundle penuh).
23
+ // - `reducedMotion="user"`: animasi transform dimatikan bila pengguna
24
+ // memilih reduce motion di sistemnya (opacity tetap).
25
+ return (
26
+ <LazyMotion features={domAnimation} strict>
27
+ <MotionConfig reducedMotion="user">
28
+ <AuthGate>
29
+ <AppShell />
30
+ </AuthGate>
31
+ {/* Toast global (juga tampil di lock screen). Atas-tengah agar tidak
32
+ menutupi kartu instalasi skill di kanan bawah. */}
33
+ <Toaster position="top-center" closeButton />
34
+ </MotionConfig>
35
+ </LazyMotion>
36
+ );
37
+ }
38
+
39
+ export default App;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Unit test validitas PWA manifest (task 21.4, Requirement 8.1).
3
+ *
4
+ * Memverifikasi field wajib web app manifest (`name`, `short_name`,
5
+ * `start_url`, `display`, `icons`) ada dan bernilai valid agar Client dapat
6
+ * di-install sebagai Progressive Web App.
7
+ */
8
+ import { expect, test } from "bun:test";
9
+ import { readFileSync } from "node:fs";
10
+
11
+ interface ManifestIcon {
12
+ src: string;
13
+ sizes: string;
14
+ type: string;
15
+ purpose?: string;
16
+ }
17
+
18
+ interface Manifest {
19
+ name: string;
20
+ short_name: string;
21
+ start_url: string;
22
+ display: string;
23
+ description?: string;
24
+ icons: ManifestIcon[];
25
+ }
26
+
27
+ function readManifest(): Manifest {
28
+ const raw = readFileSync("public/manifest.json", "utf8");
29
+ return JSON.parse(raw) as Manifest;
30
+ }
31
+
32
+ test("21.4: manifest.json adalah JSON valid", () => {
33
+ expect(() => readManifest()).not.toThrow();
34
+ });
35
+
36
+ test("21.4: field wajib ada dan bernilai valid", () => {
37
+ const manifest = readManifest();
38
+
39
+ // name & short_name berupa string non-kosong
40
+ expect(typeof manifest.name).toBe("string");
41
+ expect(manifest.name.length).toBeGreaterThan(0);
42
+ expect(typeof manifest.short_name).toBe("string");
43
+ expect(manifest.short_name.length).toBeGreaterThan(0);
44
+
45
+ // start_url adalah path/URL absolut valid
46
+ expect(typeof manifest.start_url).toBe("string");
47
+ expect(manifest.start_url.startsWith("/")).toBe(true);
48
+
49
+ // display stand-alone (prasyarat instalasi PWA)
50
+ expect(manifest.display).toBe("standalone");
51
+
52
+ // icons: array non-kosong dengan src & type valid
53
+ expect(Array.isArray(manifest.icons)).toBe(true);
54
+ expect(manifest.icons.length).toBeGreaterThan(0);
55
+ for (const icon of manifest.icons) {
56
+ expect(typeof icon.src).toBe("string");
57
+ expect(icon.src.startsWith("/")).toBe(true);
58
+ expect(typeof icon.type).toBe("string");
59
+ expect(icon.type.length).toBeGreaterThan(0);
60
+ }
61
+ });
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Build-time constants, stamped into the npm package by `scripts/stamp-build-config.ts`
3
+ * (`prepack`) and reset to empty by `postpack`, so the repo never contains a
4
+ * deployment-specific value.
5
+ *
6
+ * Do not edit by hand: set `KCG_TUNNEL_API_URL` in `.env` for development, and
7
+ * it's baked in automatically when you run `bun publish` / `bun pm pack`.
8
+ */
9
+ export const BUILD_TUNNEL_API_URL: string = "https://code.kcgapp.tech";
@@ -0,0 +1,84 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
+ import { existsSync, mkdtempSync, rmSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import path from "node:path";
5
+ import { parseArgs } from "../args";
6
+ import { childArgv, daemonStatePath, isAlive, runningDaemon, writeDaemonState } from "../daemon";
7
+
8
+ describe("parseArgs daemon", () => {
9
+ test("start -d + command stop/status/logs", () => {
10
+ expect(parseArgs(["start", "-d"]).detach).toBe(true);
11
+ expect(parseArgs(["--detach", "-p", "4000"])).toMatchObject({
12
+ command: "start",
13
+ detach: true,
14
+ port: 4000,
15
+ });
16
+ expect(parseArgs(["stop"]).command).toBe("stop");
17
+ expect(parseArgs(["status"]).command).toBe("status");
18
+ expect(parseArgs(["logs", "-f", "-n", "20"])).toMatchObject({
19
+ command: "logs",
20
+ follow: true,
21
+ lines: 20,
22
+ });
23
+ expect(parseArgs(["logs", "-n", "abc"]).command).toBe("invalid");
24
+ });
25
+
26
+ test("childArgv meneruskan opsi server, tanpa -d / --open", () => {
27
+ const args = parseArgs([
28
+ "start",
29
+ "-d",
30
+ "--open",
31
+ "-p",
32
+ "4000",
33
+ "-H",
34
+ "0.0.0.0",
35
+ "--allow-sleep",
36
+ ]);
37
+ expect(childArgv(args)).toEqual([
38
+ "start",
39
+ "--port",
40
+ "4000",
41
+ "--host",
42
+ "0.0.0.0",
43
+ "--allow-sleep",
44
+ ]);
45
+ });
46
+ });
47
+
48
+ describe("daemon state", () => {
49
+ let dir: string;
50
+ const prevDb = process.env.KCG_DB_PATH;
51
+
52
+ beforeEach(() => {
53
+ dir = mkdtempSync(path.join(tmpdir(), "kcg-daemon-"));
54
+ process.env.KCG_DB_PATH = path.join(dir, "kcg.sqlite");
55
+ });
56
+ afterEach(() => {
57
+ if (prevDb === undefined) delete process.env.KCG_DB_PATH;
58
+ else process.env.KCG_DB_PATH = prevDb;
59
+ rmSync(dir, { recursive: true, force: true });
60
+ });
61
+
62
+ const state = (pid: number) => ({
63
+ pid,
64
+ port: 3000,
65
+ hostname: "127.0.0.1",
66
+ localUrl: "http://127.0.0.1:3000",
67
+ publicUrl: null,
68
+ startedAt: Date.now(),
69
+ logFile: path.join(dir, "kcgcode.log"),
70
+ });
71
+
72
+ test("state dengan pid hidup dikembalikan", async () => {
73
+ await writeDaemonState(state(process.pid));
74
+ expect(isAlive(process.pid)).toBe(true);
75
+ expect((await runningDaemon())?.pid).toBe(process.pid);
76
+ });
77
+
78
+ test("state basi (pid mati) dibersihkan", async () => {
79
+ const dead = Bun.spawnSync(["true"]).pid;
80
+ await writeDaemonState(state(dead));
81
+ expect(await runningDaemon()).toBeNull();
82
+ expect(existsSync(daemonStatePath())).toBe(false);
83
+ });
84
+ });