@suchitraswain/nightcode-cli 1.0.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 (51) hide show
  1. package/bin/nightcode.cjs +10 -0
  2. package/bin/nightcode.ts +5 -0
  3. package/package.json +50 -0
  4. package/src/bootstrap-env.ts +33 -0
  5. package/src/components/border.tsx +18 -0
  6. package/src/components/command-menu/commands.tsx +147 -0
  7. package/src/components/command-menu/filter-commands.ts +8 -0
  8. package/src/components/command-menu/index.tsx +74 -0
  9. package/src/components/command-menu/types.ts +20 -0
  10. package/src/components/command-menu/use-command-menu.ts +113 -0
  11. package/src/components/dialog-search-list.tsx +127 -0
  12. package/src/components/dialogs/agents-dialog.tsx +47 -0
  13. package/src/components/dialogs/index.tsx +4 -0
  14. package/src/components/dialogs/models-dialog.tsx +41 -0
  15. package/src/components/dialogs/sessions-dialog.tsx +94 -0
  16. package/src/components/dialogs/theme-dialog.tsx +58 -0
  17. package/src/components/header.tsx +10 -0
  18. package/src/components/input-bar.tsx +611 -0
  19. package/src/components/messages/bot-message.tsx +160 -0
  20. package/src/components/messages/error-message.tsx +36 -0
  21. package/src/components/messages/index.tsx +3 -0
  22. package/src/components/messages/user-message.tsx +36 -0
  23. package/src/components/session-shell.tsx +65 -0
  24. package/src/components/spinner.tsx +14 -0
  25. package/src/components/status-bar.tsx +23 -0
  26. package/src/hooks/use-chat.ts +107 -0
  27. package/src/hosted-config.ts +6 -0
  28. package/src/index.tsx +29 -0
  29. package/src/layouts/root-layout.tsx +25 -0
  30. package/src/layouts/themed-root.tsx +21 -0
  31. package/src/lib/api-client.ts +25 -0
  32. package/src/lib/auth.ts +38 -0
  33. package/src/lib/http-errors.ts +18 -0
  34. package/src/lib/local-tools.ts +170 -0
  35. package/src/lib/oauth.ts +166 -0
  36. package/src/lib/upgrade.ts +27 -0
  37. package/src/providers/dialog/index.tsx +123 -0
  38. package/src/providers/dialog/types.ts +6 -0
  39. package/src/providers/keyboard-layer/index.tsx +98 -0
  40. package/src/providers/prompt-config/index.tsx +52 -0
  41. package/src/providers/theme/index.tsx +75 -0
  42. package/src/providers/toast/index.tsx +118 -0
  43. package/src/providers/toast/types.ts +9 -0
  44. package/src/screens/home.tsx +39 -0
  45. package/src/screens/new-session.tsx +82 -0
  46. package/src/screens/session.tsx +171 -0
  47. package/src/theme.ts +568 -0
  48. package/vendor/shared/api-types.ts +11 -0
  49. package/vendor/shared/index.ts +20 -0
  50. package/vendor/shared/models.ts +72 -0
  51. package/vendor/shared/schemas.ts +87 -0
@@ -0,0 +1,41 @@
1
+ import { useCallback } from "react";
2
+ import { useDialog } from "../../providers/dialog";
3
+ import { DialogSearchList } from "../dialog-search-list";
4
+ import { Mode } from "@nightcode/database/enums";
5
+ import type { SupportedChatModelId } from "@nightcode/shared";
6
+
7
+ type ModelsDialogContentProps = {
8
+ models: SupportedChatModelId[];
9
+ onSelectModel: (modelId: SupportedChatModelId) => void;
10
+ };
11
+
12
+ export const ModelsDialogContent = ({
13
+ models,
14
+ onSelectModel
15
+ }: ModelsDialogContentProps) => {
16
+ const dialog = useDialog();
17
+
18
+ const handleSelect = useCallback(
19
+ (modelId: SupportedChatModelId) => {
20
+ onSelectModel(modelId);
21
+ dialog.close();
22
+ },
23
+ [dialog, onSelectModel],
24
+ );
25
+
26
+ return (
27
+ <DialogSearchList
28
+ items={models}
29
+ onSelect={handleSelect}
30
+ filterFn={(modelId, query) => modelId.toLowerCase().includes(query.toLowerCase())}
31
+ renderItem={(modelId, isSelected) => (
32
+ <text selectable={false} fg={isSelected ? "black" : "white"}>
33
+ {modelId}
34
+ </text>
35
+ )}
36
+ getKey={(modelId) => modelId}
37
+ placeholder="Search models"
38
+ emptyText="No matching models"
39
+ />
40
+ );
41
+ };
@@ -0,0 +1,94 @@
1
+ import { useCallback, useEffect, useState } from "react";
2
+ import { TextAttributes } from "@opentui/core";
3
+ import { format } from "date-fns";
4
+ import { useNavigate } from "react-router";
5
+ import { useDialog } from "../../providers/dialog";
6
+ import { useToast } from "../../providers/toast";
7
+ import { apiClient } from "../../lib/api-client";
8
+ import { getErrorMessage } from "../../lib/http-errors";
9
+ import { DialogSearchList } from "../dialog-search-list";
10
+ import type { SessionSummary } from "@nightcode/shared";
11
+
12
+ export const SessionsDialogContent = () => {
13
+ const [sessions, setSessions] = useState<SessionSummary[]>([]);
14
+ const [loading, setLoading] = useState(true);
15
+ const { close } = useDialog();
16
+ const navigate = useNavigate();
17
+ const { show } = useToast();
18
+
19
+ useEffect(() => {
20
+ let ignore = false;
21
+
22
+ const fetchSessions = async () => {
23
+ try {
24
+ const res = await apiClient.sessions.$get();
25
+ if (!res.ok) {
26
+ throw new Error(await getErrorMessage(res));
27
+ }
28
+
29
+ const data = await res.json();
30
+
31
+ if (!ignore) {
32
+ setSessions(data);
33
+ setLoading(false);
34
+ }
35
+ } catch (error) {
36
+ if (!ignore) {
37
+ show({
38
+ variant: "error",
39
+ message: error instanceof Error ? error.message : "Failed to fetch sessions",
40
+ });
41
+ close();
42
+ }
43
+ }
44
+ };
45
+
46
+ fetchSessions();
47
+
48
+ return () => {
49
+ ignore = true;
50
+ };
51
+ }, [close, show]);
52
+
53
+ const handleSelect = useCallback(
54
+ (session: Session) => {
55
+ close();
56
+ navigate(`/sessions/${session.id}`);
57
+ },
58
+ [close, navigate],
59
+ );
60
+
61
+ if (loading) {
62
+ return (
63
+ <box flexDirection="column">
64
+ <text attributes={TextAttributes.DIM}>Loading sessions...</text>
65
+ </box>
66
+ );
67
+ }
68
+
69
+ return (
70
+ <DialogSearchList
71
+ items={sessions}
72
+ onSelect={handleSelect}
73
+ filterFn={(s, query) => s.title.toLowerCase().includes(query.toLowerCase())}
74
+ renderItem={(session, isSelected) => (
75
+ <>
76
+ <text selectable={false} fg={isSelected ? "black" : "white"}>
77
+ {session.title}
78
+ </text>
79
+ <box flexGrow={1} />
80
+ <text
81
+ selectable={false}
82
+ fg={isSelected ? "black" : undefined}
83
+ attributes={TextAttributes.DIM}
84
+ >
85
+ {format(new Date(session.createdAt), "hh:mm a")}
86
+ </text>
87
+ </>
88
+ )}
89
+ getKey={(s) => s.id}
90
+ placeholder="Search sessions"
91
+ emptyText="No matching sessions"
92
+ />
93
+ );
94
+ };
@@ -0,0 +1,58 @@
1
+ import { useCallback, useEffect, useRef } from "react";
2
+ import { useDialog } from "../../providers/dialog";
3
+ import { useTheme } from "../../providers/theme";
4
+ import { DialogSearchList } from "../dialog-search-list";
5
+ import { THEMES } from "../../theme";
6
+ import type { Theme } from "../../theme";
7
+
8
+ export const ThemeDialogContent = () => {
9
+ const dialog = useDialog();
10
+ const { setTheme, currentTheme } = useTheme();
11
+ const originalThemeRef = useRef(currentTheme);
12
+ const confirmedRef = useRef(false);
13
+
14
+ // Revert to original theme if the user dismisses without confirming
15
+ useEffect(() => {
16
+ return () => {
17
+ if (!confirmedRef.current) {
18
+ setTheme(originalThemeRef.current);
19
+ }
20
+ };
21
+ }, [setTheme]);
22
+
23
+ const handleSelect = useCallback(
24
+ (theme: Theme) => {
25
+ confirmedRef.current = true;
26
+ setTheme(theme);
27
+ dialog.close();
28
+ },
29
+ [setTheme, dialog],
30
+ );
31
+
32
+ const handleHighlight = useCallback(
33
+ (theme: Theme) => {
34
+ setTheme(theme);
35
+ },
36
+ [setTheme],
37
+ );
38
+
39
+ return (
40
+ <DialogSearchList
41
+ items={THEMES}
42
+ onSelect={handleSelect}
43
+ onHighlight={handleHighlight}
44
+ filterFn={(t, query) => t.name.toLowerCase().includes(query.toLowerCase())}
45
+ renderItem={(theme, isSelected) => (
46
+ <text selectable={false} fg={isSelected ? "black" : "white"}>
47
+ {theme.name === originalThemeRef.current.name
48
+ ? "\u0020\u2022\u0020"
49
+ : "\u0020\u0020\u0020"}
50
+ {theme.name}
51
+ </text>
52
+ )}
53
+ getKey={(t) => t.name}
54
+ placeholder="Search themes"
55
+ emptyText="No matching themes"
56
+ />
57
+ );
58
+ };
@@ -0,0 +1,10 @@
1
+ export function Header() {
2
+ return (
3
+ <box justifyContent="center" alignItems="center">
4
+ <box flexDirection="row" justifyContent="center" gap={0.5} alignItems="center">
5
+ <ascii-font font="tiny" text="Night" color="gray" />
6
+ <ascii-font font="tiny" text="Code" />
7
+ </box>
8
+ </box>
9
+ );
10
+ };