kcg-code 0.1.0 → 0.2.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.
Files changed (83) hide show
  1. package/README.md +2 -0
  2. package/package.json +2 -1
  3. package/src/cli/__tests__/daemon.test.ts +84 -0
  4. package/src/cli/__tests__/dashboard.test.ts +170 -8
  5. package/src/cli/__tests__/keep-awake.test.ts +44 -0
  6. package/src/cli/__tests__/port.test.ts +86 -0
  7. package/src/cli/args.ts +69 -6
  8. package/src/cli/attach.ts +154 -0
  9. package/src/cli/commands.ts +174 -21
  10. package/src/cli/daemon.ts +275 -0
  11. package/src/cli/dashboard.tsx +880 -159
  12. package/src/cli/index.ts +10 -0
  13. package/src/cli/ink-react.ts +26 -0
  14. package/src/cli/keep-awake.ts +148 -0
  15. package/src/cli/port.ts +92 -0
  16. package/src/cli/theme.ts +6 -6
  17. package/src/components/layout/AppShell.tsx +8 -3
  18. package/src/components/projects/HomeChat.tsx +85 -48
  19. package/src/components/projects/HomeRecentSessions.tsx +90 -0
  20. package/src/components/projects/ProjectInstructions.tsx +27 -38
  21. package/src/components/projects/ProjectSidePanel.tsx +382 -316
  22. package/src/components/sessions/AgentLogo.tsx +42 -0
  23. package/src/components/sessions/AgentPicker.tsx +13 -4
  24. package/src/components/sessions/ConfirmSessionDeleteDialog.tsx +27 -7
  25. package/src/components/sessions/ContextPanel.tsx +159 -59
  26. package/src/components/sessions/ModelPicker.tsx +65 -27
  27. package/src/components/sessions/ModelSearchList.tsx +137 -57
  28. package/src/components/sessions/NewSessionDialog.tsx +100 -117
  29. package/src/components/sessions/SessionComposer.tsx +2 -1
  30. package/src/components/sessions/SessionHeader.tsx +1 -0
  31. package/src/components/sessions/SessionList.tsx +173 -66
  32. package/src/components/sessions/SessionRow.tsx +176 -76
  33. package/src/components/sessions/SessionView.tsx +17 -13
  34. package/src/components/sessions/__tests__/ContextPanel.test.tsx +36 -0
  35. package/src/components/settings/LhrRemoteAccess.tsx +20 -25
  36. package/src/components/settings/RemoteAccessGroup.tsx +242 -267
  37. package/src/components/settings/prefs.tsx +69 -1
  38. package/src/components/skills/InstallNotifications.tsx +4 -3
  39. package/src/components/skills/InstallSkillDialog.tsx +256 -99
  40. package/src/components/skills/SkillCards.tsx +52 -8
  41. package/src/components/ui/adw-dialog.tsx +114 -0
  42. package/src/components/ui/adw-toggle-group.tsx +75 -0
  43. package/src/components/ui/search-entry.tsx +70 -0
  44. package/src/config.ts +32 -3
  45. package/src/hooks/useAgentPicker.ts +6 -2
  46. package/src/hooks/useSessionList.ts +95 -2
  47. package/src/lib/__tests__/project-extensions.test.ts +32 -1
  48. package/src/lib/__tests__/project-overview.test.ts +15 -0
  49. package/src/lib/__tests__/routes.test.ts +2 -2
  50. package/src/lib/__tests__/session-selection.test.ts +41 -0
  51. package/src/lib/__tests__/skill-installs.test.ts +1 -0
  52. package/src/lib/__tests__/skills-catalog.test.ts +36 -8
  53. package/src/lib/agent-types.ts +49 -0
  54. package/src/lib/project-extensions.ts +66 -4
  55. package/src/lib/project-overview.ts +14 -8
  56. package/src/lib/routes.ts +13 -4
  57. package/src/lib/session-selection.ts +40 -0
  58. package/src/lib/skill-installs.ts +3 -1
  59. package/src/lib/skills-catalog.ts +56 -17
  60. package/src/pages/projects/ProjectDetailPage.tsx +13 -12
  61. package/src/pages/settings/SettingsPage.tsx +212 -145
  62. package/src/pages/settings/__tests__/SettingsPage.test.ts +11 -4
  63. package/src/pages/skills/SkillsPage.tsx +89 -45
  64. package/src/server/__tests__/app.test.ts +15 -2
  65. package/src/server/app.ts +13 -1
  66. package/src/server/routes/projects.routes.ts +14 -4
  67. package/src/server/routes/sessions.routes.ts +1 -1
  68. package/src/server/routes/skills.routes.ts +24 -5
  69. package/src/server/services/__tests__/claude-code.test.ts +609 -0
  70. package/src/server/services/__tests__/claude-mcp.test.ts +90 -0
  71. package/src/server/services/__tests__/claude-skills.test.ts +63 -0
  72. package/src/server/services/__tests__/session-manager.test.ts +40 -0
  73. package/src/server/services/__tests__/skill-install-jobs.test.ts +22 -3
  74. package/src/server/services/__tests__/skills-registry.test.ts +6 -0
  75. package/src/server/services/claude-code-client.ts +1093 -0
  76. package/src/server/services/claude-code-translate.ts +370 -0
  77. package/src/server/services/claude-mcp.ts +122 -0
  78. package/src/server/services/claude-skills.ts +102 -0
  79. package/src/server/services/session-manager.ts +117 -52
  80. package/src/server/services/skill-install-jobs.ts +21 -8
  81. package/src/server/services/skills-registry.ts +18 -6
  82. package/src/types/message.ts +1 -1
  83. package/src/components/projects/PanelSection.tsx +0 -102
@@ -18,9 +18,10 @@
18
18
  * `SessionComposer` di folder yang sama.
19
19
  * - Drag & drop file: `FileDropOverlay` (portal, menutupi seluruh viewport)
20
20
  * meneruskan file ke composer (`SessionComposerHandle.addFiles`).
21
- * - Kartu konteks kanan (`ContextPanel`): Environment / MCP / Task list /
22
- * Reference, MENGAPUNG di margin kanan — percakapan & input tidak bergeser
23
- * saat kartu dibuka/ditutup. Di mobile tampil sebagai bottom sheet.
21
+ * - Kartu konteks kanan (`ContextPanel`): Environment / Task list / MCP /
22
+ * Reference. Desktop: kolom sendiri di kanan (berdampingan) — kolom chat
23
+ * menyempit & percakapan tetap terpusat di sisa ruang, tidak tertutup
24
+ * kartu. Di bawah `lg` (HP & tablet) tampil sebagai bottom sheet.
24
25
  */
25
26
  import { useMemo, useRef, useState } from "react";
26
27
  import { ConfirmSessionDeleteDialog } from "@/components/sessions/ConfirmSessionDeleteDialog";
@@ -37,7 +38,7 @@ import {
37
38
  SessionComposerSkeleton,
38
39
  SessionTimelineSkeleton,
39
40
  } from "@/components/sessions/SessionTimelineSkeleton";
40
- import { useIsMobile } from "@/hooks/useMediaQuery";
41
+ import { useMediaQuery } from "@/hooks/useMediaQuery";
41
42
  import { useSessionChat } from "@/hooks/useSessionChat";
42
43
  import { useSessionContext } from "@/hooks/useSessionContext";
43
44
  import { CONVERSATION_MAX_W } from "@/lib/layout";
@@ -46,6 +47,9 @@ import { cn } from "@/lib/utils";
46
47
  import type { Session } from "@/types";
47
48
  import { PromptPanel } from "./PromptPanel";
48
49
 
50
+ /** Lebar minimum untuk kartu konteks sebagai kolom samping (`lg`). */
51
+ const SIDE_PANEL_QUERY = "(min-width: 1024px)";
52
+
49
53
  export interface SessionViewProps {
50
54
  session: Session;
51
55
  onBack: () => void;
@@ -70,8 +74,9 @@ export function SessionView({ session, onBack, onDeleted }: SessionViewProps) {
70
74
  const composerRef = useRef<SessionComposerHandle>(null);
71
75
  /** Panel konteks kanan; default tertutup agar area chat tetap lega. */
72
76
  const [contextOpen, setContextOpen] = useState(false);
73
- /** Layar sempit: konteks tampil sebagai bottom sheet, bukan kartu. */
74
- const isMobile = useIsMobile();
77
+ // Kolom konteks berdampingan butuh ruang: di bawah `lg` kolom chat jadi
78
+ // terlalu sempit, jadi kartu tampil sebagai bottom sheet (seperti HP).
79
+ const sidePanel = useMediaQuery(SIDE_PANEL_QUERY);
75
80
  const sessionCtx = useSessionContext(session.id, contextOpen, chat.generating);
76
81
  /**
77
82
  * Reference diturunkan dari parts pesan yang sudah ada di timeline
@@ -187,12 +192,11 @@ export function SessionView({ session, onBack, onDeleted }: SessionViewProps) {
187
192
  )}
188
193
  </div>
189
194
 
190
- {/* Tertutup: hanya tombol hamburger. Terbuka: tombol hilang & kartu
191
- mengapung di kanan. `absolute` = tidak mengambil ruang layout, jadi
192
- percakapan & input tetap di posisinya. Wadah `pointer-events-none`
193
- (setinggi area) agar klik di sekitar kartu tetap sampai ke chat. */}
194
- {isMobile ? (
195
- // Mobile: bottom sheet. Tombol hamburger tetap terlihat — sheet
195
+ {/* Tertutup: hanya tombol hamburger. Terbuka (desktop): tombol hilang
196
+ & kartu jadi kolom kanan selebar 17rem yang IKUT tata letak — kolom
197
+ chat (flex-1) menyempit, jadi pesan & composer tidak tertutup. */}
198
+ {!sidePanel ? (
199
+ // HP & tablet: bottom sheet. Tombol hamburger tetap terlihat — sheet
196
200
  // menutupi layar & punya penutupnya sendiri (tap luar / geser / Esc).
197
201
  <>
198
202
  <ContextPanelTrigger onOpen={() => setContextOpen(true)} />
@@ -205,7 +209,7 @@ export function SessionView({ session, onBack, onDeleted }: SessionViewProps) {
205
209
  />
206
210
  </>
207
211
  ) : contextOpen ? (
208
- <div className="pointer-events-none absolute end-3 top-2 bottom-3 z-20 flex w-[min(15rem,calc(100%-1.5rem))] flex-col sm:end-4">
212
+ <div className="flex w-[17rem] shrink-0 flex-col pt-2 pe-3 pb-3 sm:pe-4">
209
213
  <ContextPanel
210
214
  context={sessionCtx.context}
211
215
  references={references}
@@ -97,6 +97,42 @@ test("MCP: server bermasalah ditampilkan paling atas", () => {
97
97
  expect(out.indexOf("zzz-broken")).toBeLessThan(out.indexOf("aaa-ok"));
98
98
  });
99
99
 
100
+ test("MCP: banyak yang perlu login dilipat jadi satu baris, yang tersambung tetap tampil", () => {
101
+ const out = html({
102
+ ...base,
103
+ mcp: [
104
+ { name: "firecrawl", status: "connected", error: null },
105
+ { name: "claude.ai Asana", status: "needs_auth", error: null },
106
+ { name: "claude.ai Box", status: "needs_auth", error: null },
107
+ { name: "claude.ai Canva", status: "needs_client_registration", error: null },
108
+ ],
109
+ });
110
+ expect(out).toContain("firecrawl");
111
+ expect(out).toContain("3 need sign-in");
112
+ // Nama di dalam kelompok baru tampil saat dibuka.
113
+ expect(out).not.toContain("claude.ai Asana");
114
+ expect(out).toContain('aria-expanded="false"');
115
+ // Belum login itu normal — ringkasan tidak diberi warna peringatan.
116
+ expect(out).not.toContain("text-amber-700");
117
+ });
118
+
119
+ test("MCP: satu server perlu login tetap tampil sebagai baris biasa", () => {
120
+ const out = html({
121
+ ...base,
122
+ mcp: [
123
+ { name: "firecrawl", status: "connected", error: null },
124
+ { name: "github", status: "needs_auth", error: null },
125
+ ],
126
+ });
127
+ expect(out).toContain("github");
128
+ expect(out).not.toContain("need sign-in");
129
+ });
130
+
131
+ test("MCP: ada yang gagal -> ringkasan diberi warna peringatan", () => {
132
+ const out = html({ ...base, mcp: [{ name: "x", status: "failed", error: null }] });
133
+ expect(out).toContain("text-amber-700");
134
+ });
135
+
100
136
  test("MCP kosong: keterangan menyesuaikan status Session", () => {
101
137
  expect(html(base)).toContain("No MCP servers configured");
102
138
  expect(html({ ...base, live: false })).toContain("Start the session to see MCP servers");
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * Panel Remote access via localhost.run — tanpa akun, lewat `ssh` bawaan OS.
3
3
  *
4
- * Satu grup ala AdwPreferencesGroup:
4
+ * Susunan sama dengan penyedia bawaan (satu hal per grup):
5
5
  * - AdwSwitchRow "Remote access" (status satu frasa di subjudul),
6
- * - QR + link saat online (komponen bersama penyedia bawaan),
6
+ * - grup "Your link" saat online (komponen bersama penyedia bawaan),
7
+ * - grup "Connection method" (dari pembungkus),
7
8
  * - baris expander "Connection log".
8
- * Catatan alamat yang berubah + tautan ke KCG Code link jadi footer grup.
9
9
  */
10
10
  import { ChevronDownIcon, GlobeIcon, LockIcon, TerminalSquareIcon } from "lucide-react";
11
11
  import { type ReactNode, useCallback, useEffect, useState } from "react";
@@ -33,8 +33,8 @@ export interface LhrRemoteAccessProps {
33
33
  LogPanel: (p: { endpoint?: string }) => ReactNode;
34
34
  /** Buka konfirmasi mematikan (halaman bisa terputus bila dibuka lewat link ini). */
35
35
  onRequestStop: (stop: () => Promise<void>, viaThisLink: boolean) => void;
36
- /** Pindah ke penyedia yang direkomendasikan (KCG Code link). */
37
- onUseRecommended: () => void;
36
+ /** Grup "Connection method" — dirender sebelum log. */
37
+ methodPicker: ReactNode;
38
38
  }
39
39
 
40
40
  export function LhrRemoteAccess({
@@ -42,7 +42,7 @@ export function LhrRemoteAccess({
42
42
  LiveAddress,
43
43
  LogPanel,
44
44
  onRequestStop,
45
- onUseRecommended,
45
+ methodPicker,
46
46
  }: LhrRemoteAccessProps) {
47
47
  const { navigate } = useRouter();
48
48
  const [s, setS] = useState<LhrStatus | null>(null);
@@ -109,10 +109,10 @@ export function LhrRemoteAccess({
109
109
 
110
110
  // Subjudul: satu kalimat pendek yang menjelaskan keadaan + apa artinya.
111
111
  let tone: RowTone = "muted";
112
- let subtitle: ReactNode = "Get a temporary link to open KCG Code on your phone";
112
+ let subtitle: ReactNode = "Off";
113
113
  if (!protectedApp) {
114
114
  tone = "warning";
115
- subtitle = "Set a PIN or password first to keep others out";
115
+ subtitle = "Needs a PIN or password first";
116
116
  } else if (s.phase === "error") {
117
117
  tone = "warning";
118
118
  subtitle = (
@@ -124,7 +124,7 @@ export function LhrRemoteAccess({
124
124
  );
125
125
  } else if (online) {
126
126
  tone = "success";
127
- subtitle = "On · anyone with the link still needs your PIN or password";
127
+ subtitle = "On · PIN still required";
128
128
  } else if (connecting) {
129
129
  tone = "busy";
130
130
  subtitle =
@@ -139,7 +139,7 @@ export function LhrRemoteAccess({
139
139
  };
140
140
 
141
141
  return (
142
- <section aria-label="localhost.run" className="flex flex-col gap-2">
142
+ <>
143
143
  <PrefsGroup id="remote">
144
144
  {/* AdwSwitchRow: judul + status singkat, switch di kanan. */}
145
145
  <li>
@@ -181,9 +181,17 @@ export function LhrRemoteAccess({
181
181
  onActivate={() => navigate(settingsPath("security"))}
182
182
  />
183
183
  )}
184
+ </PrefsGroup>
185
+
186
+ {online && s.url && (
187
+ <PrefsGroup title="Your link">
188
+ <LiveAddress url={s.url} />
189
+ </PrefsGroup>
190
+ )}
184
191
 
185
- {online && s.url && <LiveAddress url={s.url} />}
192
+ {methodPicker}
186
193
 
194
+ <PrefsGroup>
187
195
  {/* Log teknis dilipat di baris expander (AdwExpanderRow). */}
188
196
  <Collapsible asChild open={showLog} onOpenChange={setShowLog}>
189
197
  <li>
@@ -216,20 +224,7 @@ export function LhrRemoteAccess({
216
224
  </li>
217
225
  </Collapsible>
218
226
  </PrefsGroup>
219
-
220
- {/* Footer grup: catatan singkat + aksi inline. */}
221
- <p className="px-1 text-[12.5px] leading-snug text-muted-foreground">
222
- Free addresses change every few hours and after reconnecting.{" "}
223
- <button
224
- type="button"
225
- onClick={onUseRecommended}
226
- className="rounded-sm font-medium text-foreground underline-offset-2 outline-none hover:underline focus-visible:underline focus-visible:ring-[3px] focus-visible:ring-ring/40"
227
- >
228
- Use KCG Code link
229
- </button>{" "}
230
- for one that stays the same.
231
- </p>
232
- </section>
227
+ </>
233
228
  );
234
229
  }
235
230