@cdoing/opentuicli 0.1.2
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/dist/index.js +48 -0
- package/dist/index.js.map +7 -0
- package/esbuild.config.cjs +44 -0
- package/package.json +34 -0
- package/src/app.tsx +566 -0
- package/src/components/dialog-command.tsx +204 -0
- package/src/components/dialog-help.tsx +227 -0
- package/src/components/dialog-model.tsx +93 -0
- package/src/components/dialog-status.tsx +122 -0
- package/src/components/dialog-theme.tsx +292 -0
- package/src/components/input-area.tsx +318 -0
- package/src/components/loading-spinner.tsx +28 -0
- package/src/components/message-list.tsx +338 -0
- package/src/components/permission-prompt.tsx +71 -0
- package/src/components/session-browser.tsx +220 -0
- package/src/components/session-footer.tsx +30 -0
- package/src/components/session-header.tsx +39 -0
- package/src/components/setup-wizard.tsx +463 -0
- package/src/components/sidebar.tsx +130 -0
- package/src/components/status-bar.tsx +76 -0
- package/src/components/toast.tsx +139 -0
- package/src/context/sdk.tsx +40 -0
- package/src/context/theme.tsx +532 -0
- package/src/index.ts +50 -0
- package/src/lib/autocomplete.ts +258 -0
- package/src/lib/context-providers.ts +98 -0
- package/src/lib/history.ts +164 -0
- package/src/lib/terminal-title.ts +15 -0
- package/src/routes/home.tsx +148 -0
- package/src/routes/session.tsx +1186 -0
- package/tsconfig.json +23 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Toast Notification System — transient notifications for the TUI
|
|
3
|
+
*
|
|
4
|
+
* Provides a ToastProvider context and useToast hook for showing
|
|
5
|
+
* auto-dismissing notifications with type-based styling.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createContext, useContext, useState, useEffect, useCallback, useRef } from "react";
|
|
9
|
+
import type { ReactNode } from "react";
|
|
10
|
+
import { useTheme } from "../context/theme";
|
|
11
|
+
import type { Theme } from "../context/theme";
|
|
12
|
+
import type { RGBA } from "@opentui/core";
|
|
13
|
+
|
|
14
|
+
// ── Types ────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
export type ToastType = "info" | "success" | "warning" | "error";
|
|
17
|
+
|
|
18
|
+
interface ToastItem {
|
|
19
|
+
id: number;
|
|
20
|
+
type: ToastType;
|
|
21
|
+
message: string;
|
|
22
|
+
duration: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface ToastContextValue {
|
|
26
|
+
toast: (type: ToastType, message: string, duration?: number) => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ── Icons & Colors ───────────────────────────────────
|
|
30
|
+
|
|
31
|
+
const TOAST_ICONS: Record<ToastType, string> = {
|
|
32
|
+
info: "\u2139", // i
|
|
33
|
+
success: "\u2713", // checkmark
|
|
34
|
+
warning: "\u26A0", // warning sign
|
|
35
|
+
error: "\u2717", // x mark
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
function getToastColor(type: ToastType, theme: Theme): RGBA {
|
|
39
|
+
switch (type) {
|
|
40
|
+
case "info": return theme.info;
|
|
41
|
+
case "success": return theme.success;
|
|
42
|
+
case "warning": return theme.warning;
|
|
43
|
+
case "error": return theme.error;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ── Context ──────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
const ToastContext = createContext<ToastContextValue | undefined>(undefined);
|
|
50
|
+
|
|
51
|
+
const DEFAULT_DURATION = 3000;
|
|
52
|
+
const MAX_VISIBLE = 3;
|
|
53
|
+
|
|
54
|
+
// ── Provider ─────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
export function ToastProvider(props: { children: ReactNode }) {
|
|
57
|
+
const { theme } = useTheme();
|
|
58
|
+
const [toasts, setToasts] = useState<ToastItem[]>([]);
|
|
59
|
+
const idRef = useRef(0);
|
|
60
|
+
const timersRef = useRef<Map<number, ReturnType<typeof setTimeout>>>(new Map());
|
|
61
|
+
|
|
62
|
+
const removeToast = useCallback((id: number) => {
|
|
63
|
+
setToasts((prev) => prev.filter((t) => t.id !== id));
|
|
64
|
+
const timer = timersRef.current.get(id);
|
|
65
|
+
if (timer) {
|
|
66
|
+
clearTimeout(timer);
|
|
67
|
+
timersRef.current.delete(id);
|
|
68
|
+
}
|
|
69
|
+
}, []);
|
|
70
|
+
|
|
71
|
+
const toast = useCallback((type: ToastType, message: string, duration?: number) => {
|
|
72
|
+
const id = ++idRef.current;
|
|
73
|
+
const dur = duration ?? DEFAULT_DURATION;
|
|
74
|
+
const item: ToastItem = { id, type, message, duration: dur };
|
|
75
|
+
|
|
76
|
+
setToasts((prev) => {
|
|
77
|
+
const next = [...prev, item];
|
|
78
|
+
// Keep only the most recent MAX_VISIBLE toasts
|
|
79
|
+
if (next.length > MAX_VISIBLE) {
|
|
80
|
+
const removed = next.splice(0, next.length - MAX_VISIBLE);
|
|
81
|
+
// Clean up timers for removed toasts
|
|
82
|
+
for (const r of removed) {
|
|
83
|
+
const timer = timersRef.current.get(r.id);
|
|
84
|
+
if (timer) {
|
|
85
|
+
clearTimeout(timer);
|
|
86
|
+
timersRef.current.delete(r.id);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return next;
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Set auto-dismiss timer
|
|
94
|
+
const timer = setTimeout(() => {
|
|
95
|
+
removeToast(id);
|
|
96
|
+
}, dur);
|
|
97
|
+
timersRef.current.set(id, timer);
|
|
98
|
+
}, [removeToast]);
|
|
99
|
+
|
|
100
|
+
// Clean up all timers on unmount
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
return () => {
|
|
103
|
+
for (const timer of timersRef.current.values()) {
|
|
104
|
+
clearTimeout(timer);
|
|
105
|
+
}
|
|
106
|
+
timersRef.current.clear();
|
|
107
|
+
};
|
|
108
|
+
}, []);
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<ToastContext.Provider value={{ toast }}>
|
|
112
|
+
{props.children}
|
|
113
|
+
{/* Toast overlay — positioned at the bottom, above status bar */}
|
|
114
|
+
{toasts.length > 0 && (
|
|
115
|
+
<box flexDirection="column" alignItems="flex-end">
|
|
116
|
+
{toasts.map((t) => {
|
|
117
|
+
const color = getToastColor(t.type, theme);
|
|
118
|
+
const icon = TOAST_ICONS[t.type];
|
|
119
|
+
return (
|
|
120
|
+
<box key={t.id} height={1} flexDirection="row" justifyContent="flex-end">
|
|
121
|
+
<text fg={color}>{` ${icon} `}</text>
|
|
122
|
+
<text fg={theme.text}>{t.message}</text>
|
|
123
|
+
<text fg={theme.textDim}>{" "}</text>
|
|
124
|
+
</box>
|
|
125
|
+
);
|
|
126
|
+
})}
|
|
127
|
+
</box>
|
|
128
|
+
)}
|
|
129
|
+
</ToastContext.Provider>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ── Hook ─────────────────────────────────────────────
|
|
134
|
+
|
|
135
|
+
export function useToast(): ToastContextValue {
|
|
136
|
+
const ctx = useContext(ToastContext);
|
|
137
|
+
if (!ctx) throw new Error("useToast must be used within ToastProvider");
|
|
138
|
+
return ctx;
|
|
139
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SDK Context — wraps AgentRunner, ToolRegistry, PermissionManager,
|
|
3
|
+
* and exposes callbacks for runtime model/provider changes and agent rebuild.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createContext, useContext } from "react";
|
|
7
|
+
import type { ReactNode } from "react";
|
|
8
|
+
import type { AgentRunner } from "@cdoing/ai";
|
|
9
|
+
import type { ToolRegistry, PermissionManager } from "@cdoing/core";
|
|
10
|
+
|
|
11
|
+
export interface SDKState {
|
|
12
|
+
agent: AgentRunner;
|
|
13
|
+
registry: ToolRegistry;
|
|
14
|
+
permissionManager: PermissionManager;
|
|
15
|
+
workingDir: string;
|
|
16
|
+
provider: string;
|
|
17
|
+
model: string;
|
|
18
|
+
/** Request a permission decision from the UI */
|
|
19
|
+
requestPermission?: (toolName: string, message: string) => Promise<"allow" | "always" | "deny">;
|
|
20
|
+
/** Rebuild the agent after model/provider change */
|
|
21
|
+
rebuildAgent?: (provider: string, model: string, apiKey?: string) => void;
|
|
22
|
+
/** Change working directory */
|
|
23
|
+
setWorkingDir?: (dir: string) => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const SDKContext = createContext<SDKState | undefined>(undefined);
|
|
27
|
+
|
|
28
|
+
export function SDKProvider(props: { value: SDKState; children: ReactNode }) {
|
|
29
|
+
return (
|
|
30
|
+
<SDKContext.Provider value={props.value}>
|
|
31
|
+
{props.children}
|
|
32
|
+
</SDKContext.Provider>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function useSDK() {
|
|
37
|
+
const ctx = useContext(SDKContext);
|
|
38
|
+
if (!ctx) throw new Error("useSDK must be used within SDKProvider");
|
|
39
|
+
return ctx;
|
|
40
|
+
}
|