@abacus-ai/cli 1.106.25007 → 2.0.0-canary.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/.oxlintrc.json +8 -0
- package/dist/index.mjs +12603 -0
- package/package.json +7 -39
- package/resources/abacus.ico +0 -0
- package/resources/entitlements.plist +9 -0
- package/src/__e2e__/README.md +196 -0
- package/src/__e2e__/agent-interactions.e2e.test.tsx +61 -0
- package/src/__e2e__/cli-commands.e2e.test.tsx +77 -0
- package/src/__e2e__/conversation-throttle.e2e.test.ts +453 -0
- package/src/__e2e__/conversation.e2e.test.tsx +56 -0
- package/src/__e2e__/diff-preview.e2e.test.tsx +3399 -0
- package/src/__e2e__/file-creation.e2e.test.tsx +149 -0
- package/src/__e2e__/helpers/test-helpers.ts +450 -0
- package/src/__e2e__/keyboard-navigation.e2e.test.tsx +34 -0
- package/src/__e2e__/llm-models.e2e.test.ts +402 -0
- package/src/__e2e__/mcp/mcp-callback-flow.e2e.test.tsx +71 -0
- package/src/__e2e__/mcp/mcp-full-app-ui.e2e.test.tsx +167 -0
- package/src/__e2e__/mcp/mcp-ui-rendering.e2e.test.tsx +185 -0
- package/src/__e2e__/repl.e2e.test.tsx +78 -0
- package/src/__e2e__/shell-compatibility.e2e.test.tsx +76 -0
- package/src/__e2e__/theme-mcp.e2e.test.tsx +98 -0
- package/src/__e2e__/tool-permissions.e2e.test.tsx +66 -0
- package/src/args.ts +22 -0
- package/src/components/__tests__/react-compiler.test.tsx +78 -0
- package/src/components/__tests__/status-indicator.test.tsx +403 -0
- package/src/components/composer/__tests__/bash-runner.test.tsx +263 -0
- package/src/components/composer/agent-mode-indicator.tsx +63 -0
- package/src/components/composer/bash-runner.tsx +54 -0
- package/src/components/composer/commands/default-commands.tsx +615 -0
- package/src/components/composer/commands/handler.tsx +59 -0
- package/src/components/composer/commands/picker.tsx +273 -0
- package/src/components/composer/commands/registry.ts +233 -0
- package/src/components/composer/commands/types.ts +33 -0
- package/src/components/composer/context.tsx +88 -0
- package/src/components/composer/file-mention-picker.tsx +83 -0
- package/src/components/composer/help.tsx +44 -0
- package/src/components/composer/index.tsx +1006 -0
- package/src/components/composer/mentions.ts +57 -0
- package/src/components/composer/message-queue.tsx +70 -0
- package/src/components/composer/mode-panel.tsx +35 -0
- package/src/components/composer/modes/__tests__/bash-handler.test.tsx +755 -0
- package/src/components/composer/modes/__tests__/bash-renderer.test.tsx +1108 -0
- package/src/components/composer/modes/bash-handler.tsx +132 -0
- package/src/components/composer/modes/bash-renderer.tsx +175 -0
- package/src/components/composer/modes/default-handlers.tsx +33 -0
- package/src/components/composer/modes/index.ts +41 -0
- package/src/components/composer/modes/types.ts +21 -0
- package/src/components/composer/persistent-shell.ts +283 -0
- package/src/components/composer/process.ts +65 -0
- package/src/components/composer/types.ts +9 -0
- package/src/components/composer/use-mention-search.ts +68 -0
- package/src/components/error-boundry.tsx +60 -0
- package/src/components/exit-message.tsx +29 -0
- package/src/components/expanded-view.tsx +74 -0
- package/src/components/file-completion.tsx +127 -0
- package/src/components/header.tsx +47 -0
- package/src/components/logo.tsx +37 -0
- package/src/components/segments.tsx +356 -0
- package/src/components/status-indicator.tsx +306 -0
- package/src/components/tool-group-summary.tsx +263 -0
- package/src/components/tool-permissions/ask-user-question-permission-ui.tsx +312 -0
- package/src/components/tool-permissions/diff-preview.tsx +355 -0
- package/src/components/tool-permissions/index.ts +5 -0
- package/src/components/tool-permissions/permission-options.tsx +375 -0
- package/src/components/tool-permissions/permission-preview-header.tsx +57 -0
- package/src/components/tool-permissions/tool-permission-ui.tsx +398 -0
- package/src/components/tools/agent/ask-user-question.tsx +101 -0
- package/src/components/tools/agent/enter-plan-mode.tsx +49 -0
- package/src/components/tools/agent/exit-plan-mode.tsx +75 -0
- package/src/components/tools/agent/handoff-to-main.tsx +27 -0
- package/src/components/tools/agent/subagent.tsx +37 -0
- package/src/components/tools/agent/todo-write.tsx +104 -0
- package/src/components/tools/browser/close-tab.tsx +58 -0
- package/src/components/tools/browser/computer.tsx +70 -0
- package/src/components/tools/browser/get-interactive-elements.tsx +54 -0
- package/src/components/tools/browser/get-tab-content.tsx +51 -0
- package/src/components/tools/browser/navigate-to.tsx +59 -0
- package/src/components/tools/browser/new-tab.tsx +60 -0
- package/src/components/tools/browser/perform-action.tsx +63 -0
- package/src/components/tools/browser/refresh-tab.tsx +43 -0
- package/src/components/tools/browser/switch-tab.tsx +58 -0
- package/src/components/tools/filesystem/delete-file.tsx +104 -0
- package/src/components/tools/filesystem/edit.tsx +220 -0
- package/src/components/tools/filesystem/list-dir.tsx +78 -0
- package/src/components/tools/filesystem/read-file.tsx +180 -0
- package/src/components/tools/filesystem/upload-image.tsx +76 -0
- package/src/components/tools/ide/ide-diagnostics.tsx +62 -0
- package/src/components/tools/index.ts +91 -0
- package/src/components/tools/mcp/mcp-tool.tsx +158 -0
- package/src/components/tools/search/fetch-url.tsx +73 -0
- package/src/components/tools/search/file-search.tsx +78 -0
- package/src/components/tools/search/grep.tsx +90 -0
- package/src/components/tools/search/semantic-search.tsx +66 -0
- package/src/components/tools/search/web-search.tsx +71 -0
- package/src/components/tools/shared/index.tsx +48 -0
- package/src/components/tools/shared/zod-coercion.ts +35 -0
- package/src/components/tools/terminal/bash-tool-output.tsx +174 -0
- package/src/components/tools/terminal/get-terminal-output.tsx +85 -0
- package/src/components/tools/terminal/run-in-terminal.tsx +106 -0
- package/src/components/tools/types.ts +16 -0
- package/src/components/tools.tsx +66 -0
- package/src/components/ui/__tests__/divider.test.tsx +61 -0
- package/src/components/ui/__tests__/gradient.test.tsx +125 -0
- package/src/components/ui/__tests__/input.test.tsx +166 -0
- package/src/components/ui/__tests__/select.test.tsx +273 -0
- package/src/components/ui/__tests__/shimmer.test.tsx +99 -0
- package/src/components/ui/blinking-indicator.tsx +25 -0
- package/src/components/ui/divider.tsx +162 -0
- package/src/components/ui/gradient.tsx +56 -0
- package/src/components/ui/input.tsx +228 -0
- package/src/components/ui/select.tsx +151 -0
- package/src/components/ui/shimmer.tsx +84 -0
- package/src/context/agent-mode.tsx +95 -0
- package/src/context/extension-file.tsx +136 -0
- package/src/context/network-activity.tsx +45 -0
- package/src/context/notification.tsx +62 -0
- package/src/context/shell-size.tsx +49 -0
- package/src/context/shell-title.tsx +38 -0
- package/src/entrypoints/print-mode.ts +312 -0
- package/src/entrypoints/repl.tsx +401 -0
- package/src/hooks/use-agent.ts +15 -0
- package/src/hooks/use-api-client.ts +1 -0
- package/src/hooks/use-available-height.ts +8 -0
- package/src/hooks/use-cleanup.ts +29 -0
- package/src/hooks/use-interrupt-manager.ts +242 -0
- package/src/hooks/use-models.ts +22 -0
- package/src/index.ts +217 -0
- package/src/lib/__tests__/ansi.test.ts +255 -0
- package/src/lib/__tests__/cli.test.ts +122 -0
- package/src/lib/__tests__/commands.test.ts +325 -0
- package/src/lib/__tests__/constants.test.ts +15 -0
- package/src/lib/__tests__/focusables.test.ts +25 -0
- package/src/lib/__tests__/fs.test.ts +231 -0
- package/src/lib/__tests__/markdown.test.tsx +348 -0
- package/src/lib/__tests__/mcpCommandHandler.test.ts +173 -0
- package/src/lib/__tests__/mcpManagement.test.ts +38 -0
- package/src/lib/__tests__/path-paste.test.ts +144 -0
- package/src/lib/__tests__/path.test.ts +300 -0
- package/src/lib/__tests__/queries.test.ts +39 -0
- package/src/lib/__tests__/standaloneMcpService.test.ts +71 -0
- package/src/lib/__tests__/text-buffer.test.ts +328 -0
- package/src/lib/__tests__/text-utils.test.ts +32 -0
- package/src/lib/__tests__/timing.test.ts +78 -0
- package/src/lib/__tests__/utils.test.ts +238 -0
- package/src/lib/__tests__/vim-buffer-actions.test.ts +154 -0
- package/src/lib/ansi.ts +150 -0
- package/src/lib/cli-push-server.ts +112 -0
- package/src/lib/cli.ts +44 -0
- package/src/lib/clipboard.ts +226 -0
- package/src/lib/command-utils.ts +93 -0
- package/src/lib/commands.ts +270 -0
- package/src/lib/constants.ts +3 -0
- package/src/lib/extension-connection.ts +181 -0
- package/src/lib/focusables.ts +7 -0
- package/src/lib/fs.ts +533 -0
- package/src/lib/markdown/code-block.tsx +63 -0
- package/src/lib/markdown/index.ts +4 -0
- package/src/lib/markdown/link.tsx +19 -0
- package/src/lib/markdown/markdown.tsx +372 -0
- package/src/lib/markdown/types.ts +15 -0
- package/src/lib/mcpCommandHandler.ts +121 -0
- package/src/lib/mcpManagement.ts +44 -0
- package/src/lib/path-paste.ts +185 -0
- package/src/lib/path.ts +179 -0
- package/src/lib/queries.ts +15 -0
- package/src/lib/standaloneMcpService.ts +688 -0
- package/src/lib/status-utils.ts +237 -0
- package/src/lib/test-utils.tsx +72 -0
- package/src/lib/text-buffer.ts +2415 -0
- package/src/lib/text-utils.ts +272 -0
- package/src/lib/timing.ts +63 -0
- package/src/lib/types.ts +295 -0
- package/src/lib/utils.ts +182 -0
- package/src/lib/vim-buffer-actions.ts +732 -0
- package/src/providers/agent.tsx +1075 -0
- package/src/providers/api-client.tsx +43 -0
- package/src/services/logger.ts +85 -0
- package/src/terminal/detection.ts +187 -0
- package/src/terminal/exit.ts +279 -0
- package/src/terminal/notification.ts +83 -0
- package/src/terminal/progress.ts +201 -0
- package/src/terminal/setup.ts +797 -0
- package/src/terminal/suspend.ts +58 -0
- package/src/terminal/types.ts +51 -0
- package/src/theme/context.tsx +57 -0
- package/src/theme/index.ts +4 -0
- package/src/theme/themed.tsx +35 -0
- package/src/theme/themes.json +546 -0
- package/src/theme/types.ts +110 -0
- package/src/tools/types.ts +59 -0
- package/src/tools/utils/__tests__/zod-coercion.test.ts +33 -0
- package/src/tools/utils/tool-ui-components.tsx +631 -0
- package/src/tools/utils/zod-coercion.ts +35 -0
- package/tsconfig.json +11 -0
- package/tsconfig.node.json +29 -0
- package/tsconfig.test.json +27 -0
- package/tsdown.config.ts +17 -0
- package/vitest.config.ts +76 -0
- package/README.md +0 -28
- package/dist/index.js +0 -26
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import React, { createContext, use, useEffect, useMemo, useReducer } from "react";
|
|
2
|
+
|
|
3
|
+
import { AgentMode } from "../components/composer/types.js";
|
|
4
|
+
|
|
5
|
+
interface AgentModeContextValue {
|
|
6
|
+
agentMode: AgentMode;
|
|
7
|
+
setAgentMode: (mode: AgentMode) => void;
|
|
8
|
+
cycleAgentMode: () => void;
|
|
9
|
+
isWaitingForYoloConfirm: boolean;
|
|
10
|
+
resetToNormal: () => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const AgentModeContext = createContext<AgentModeContextValue | null>(null);
|
|
14
|
+
|
|
15
|
+
export const useAgentMode = () => {
|
|
16
|
+
const ctx = use(AgentModeContext);
|
|
17
|
+
if (!ctx) {
|
|
18
|
+
throw new Error(`${useAgentMode.name} must be used within ${AgentModeProvider.name}`);
|
|
19
|
+
}
|
|
20
|
+
return ctx;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const YOLO_CONFIRM_DELAY = 3_000;
|
|
24
|
+
|
|
25
|
+
const MODE_CYCLE_ORDER: AgentMode[] = [AgentMode.Normal, AgentMode.AcceptEdits, AgentMode.PlanMode];
|
|
26
|
+
|
|
27
|
+
type State = { agentMode: AgentMode; isWaitingForYoloConfirm: boolean };
|
|
28
|
+
type Action =
|
|
29
|
+
| { type: "CYCLE" }
|
|
30
|
+
| { type: "SET"; mode: AgentMode }
|
|
31
|
+
| { type: "RESET" }
|
|
32
|
+
| { type: "YOLO_TIMEOUT" };
|
|
33
|
+
|
|
34
|
+
function reducer(state: State, action: Action): State {
|
|
35
|
+
switch (action.type) {
|
|
36
|
+
case "CYCLE": {
|
|
37
|
+
if (state.isWaitingForYoloConfirm) {
|
|
38
|
+
// Second press while waiting → confirm YOLO
|
|
39
|
+
return { agentMode: AgentMode.Yolo, isWaitingForYoloConfirm: false };
|
|
40
|
+
}
|
|
41
|
+
if (state.agentMode === AgentMode.Yolo) {
|
|
42
|
+
// Cycling from YOLO goes back to Normal
|
|
43
|
+
return { agentMode: AgentMode.Normal, isWaitingForYoloConfirm: false };
|
|
44
|
+
}
|
|
45
|
+
const currentIndex = MODE_CYCLE_ORDER.indexOf(state.agentMode);
|
|
46
|
+
if (currentIndex === -1) {
|
|
47
|
+
return { agentMode: AgentMode.Normal, isWaitingForYoloConfirm: false };
|
|
48
|
+
}
|
|
49
|
+
if (currentIndex === MODE_CYCLE_ORDER.length - 1) {
|
|
50
|
+
// At end of cycle order → ask for YOLO confirmation
|
|
51
|
+
return { agentMode: state.agentMode, isWaitingForYoloConfirm: true };
|
|
52
|
+
}
|
|
53
|
+
return { agentMode: MODE_CYCLE_ORDER[currentIndex + 1]!, isWaitingForYoloConfirm: false };
|
|
54
|
+
}
|
|
55
|
+
case "SET": {
|
|
56
|
+
return { agentMode: action.mode, isWaitingForYoloConfirm: false };
|
|
57
|
+
}
|
|
58
|
+
case "RESET": {
|
|
59
|
+
return { agentMode: AgentMode.Normal, isWaitingForYoloConfirm: false };
|
|
60
|
+
}
|
|
61
|
+
case "YOLO_TIMEOUT": {
|
|
62
|
+
return { ...state, isWaitingForYoloConfirm: false };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface AgentModeProviderProps extends React.PropsWithChildren {
|
|
68
|
+
initialMode?: AgentMode;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function AgentModeProvider({ children, initialMode }: AgentModeProviderProps) {
|
|
72
|
+
const [{ agentMode, isWaitingForYoloConfirm }, dispatch] = useReducer(reducer, {
|
|
73
|
+
agentMode: initialMode ?? AgentMode.Normal,
|
|
74
|
+
isWaitingForYoloConfirm: false,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
if (!isWaitingForYoloConfirm) return;
|
|
79
|
+
const id = setTimeout(() => dispatch({ type: "YOLO_TIMEOUT" }), YOLO_CONFIRM_DELAY);
|
|
80
|
+
return () => clearTimeout(id);
|
|
81
|
+
}, [isWaitingForYoloConfirm]);
|
|
82
|
+
|
|
83
|
+
const value = useMemo<AgentModeContextValue>(
|
|
84
|
+
() => ({
|
|
85
|
+
agentMode,
|
|
86
|
+
setAgentMode: (mode) => dispatch({ type: "SET", mode }),
|
|
87
|
+
cycleAgentMode: () => dispatch({ type: "CYCLE" }),
|
|
88
|
+
isWaitingForYoloConfirm,
|
|
89
|
+
resetToNormal: () => dispatch({ type: "RESET" }),
|
|
90
|
+
}),
|
|
91
|
+
[agentMode, isWaitingForYoloConfirm],
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
return <AgentModeContext.Provider value={value}>{children}</AgentModeContext.Provider>;
|
|
95
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { createContext, use, useEffect, useRef, useState } from "react";
|
|
2
|
+
|
|
3
|
+
import type { EditorState } from "../lib/cli-push-server.js";
|
|
4
|
+
|
|
5
|
+
import { CliPushServer } from "../lib/cli-push-server.js";
|
|
6
|
+
import { ExtensionConnection } from "../lib/extension-connection.js";
|
|
7
|
+
|
|
8
|
+
interface ExtensionFileContextValue {
|
|
9
|
+
currentFile: string | undefined;
|
|
10
|
+
selectionStartLine: number | undefined;
|
|
11
|
+
selectionEndLine: number | undefined;
|
|
12
|
+
connection: ExtensionConnection | null;
|
|
13
|
+
pushServer: CliPushServer | null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const ExtensionFileContext = createContext<ExtensionFileContextValue | null>(null);
|
|
17
|
+
|
|
18
|
+
export function useExtensionFile() {
|
|
19
|
+
const ctx = use(ExtensionFileContext);
|
|
20
|
+
if (!ctx) {
|
|
21
|
+
throw new Error("useExtensionFile must be used within ExtensionFileProvider");
|
|
22
|
+
}
|
|
23
|
+
return ctx;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function ExtensionFileProvider({
|
|
27
|
+
children,
|
|
28
|
+
initialFile,
|
|
29
|
+
}: React.PropsWithChildren<{ initialFile?: string }>) {
|
|
30
|
+
const [currentFile, setCurrentFile] = useState<string | undefined>(initialFile);
|
|
31
|
+
const [selectionStartLine, setSelectionStartLine] = useState<number | undefined>(undefined);
|
|
32
|
+
const [selectionEndLine, setSelectionEndLine] = useState<number | undefined>(undefined);
|
|
33
|
+
const [connection, setConnection] = useState<ExtensionConnection | null>(null);
|
|
34
|
+
const [pushServer, setPushServer] = useState<CliPushServer | null>(null);
|
|
35
|
+
const connectionRef = useRef<ExtensionConnection | null>(null);
|
|
36
|
+
const pushServerRef = useRef<CliPushServer | null>(null);
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
let cancelled = false;
|
|
40
|
+
|
|
41
|
+
const connectToExtension = async () => {
|
|
42
|
+
// Start the CLI push server first so the extension can connect back to us
|
|
43
|
+
const ps = new CliPushServer();
|
|
44
|
+
try {
|
|
45
|
+
await ps.start();
|
|
46
|
+
} catch {
|
|
47
|
+
// Push server failed to start — continue without it
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (cancelled) {
|
|
51
|
+
ps.dispose();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
pushServerRef.current = ps;
|
|
56
|
+
setPushServer(ps);
|
|
57
|
+
|
|
58
|
+
// Wire push-based state updates
|
|
59
|
+
ps.onEditorChange((state: EditorState) => {
|
|
60
|
+
setCurrentFile(state.file);
|
|
61
|
+
setSelectionStartLine(state.startLine);
|
|
62
|
+
setSelectionEndLine(state.endLine);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Retry for up to 10 seconds with exponential backoff (covers extension startup delay)
|
|
66
|
+
const delays = [500, 1000, 2000, 3000, 4000];
|
|
67
|
+
for (const delay of [0, ...delays]) {
|
|
68
|
+
if (cancelled) return;
|
|
69
|
+
if (delay > 0) {
|
|
70
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
71
|
+
}
|
|
72
|
+
if (cancelled) return;
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const conn = new ExtensionConnection(ps);
|
|
76
|
+
const connected = await conn.connect();
|
|
77
|
+
|
|
78
|
+
if (connected) {
|
|
79
|
+
if (cancelled) {
|
|
80
|
+
conn.dispose();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Set initial state from push server (extension pushes state on register)
|
|
85
|
+
const state = ps.getEditorState();
|
|
86
|
+
setCurrentFile(state.file);
|
|
87
|
+
setSelectionStartLine(state.startLine);
|
|
88
|
+
setSelectionEndLine(state.endLine);
|
|
89
|
+
|
|
90
|
+
conn.onFileChange((file, startLine, endLine) => {
|
|
91
|
+
setCurrentFile(file);
|
|
92
|
+
setSelectionStartLine(startLine);
|
|
93
|
+
setSelectionEndLine(endLine);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
connectionRef.current = conn;
|
|
97
|
+
setConnection(conn);
|
|
98
|
+
return; // Connected — stop retrying
|
|
99
|
+
}
|
|
100
|
+
} catch {
|
|
101
|
+
// ignore, retry
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
void connectToExtension();
|
|
107
|
+
|
|
108
|
+
return () => {
|
|
109
|
+
cancelled = true;
|
|
110
|
+
if (connectionRef.current) {
|
|
111
|
+
connectionRef.current.dispose();
|
|
112
|
+
connectionRef.current = null;
|
|
113
|
+
setConnection(null);
|
|
114
|
+
}
|
|
115
|
+
if (pushServerRef.current) {
|
|
116
|
+
pushServerRef.current.dispose();
|
|
117
|
+
pushServerRef.current = null;
|
|
118
|
+
setPushServer(null);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
}, []);
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<ExtensionFileContext.Provider
|
|
125
|
+
value={{
|
|
126
|
+
currentFile,
|
|
127
|
+
selectionStartLine,
|
|
128
|
+
selectionEndLine,
|
|
129
|
+
connection,
|
|
130
|
+
pushServer,
|
|
131
|
+
}}
|
|
132
|
+
>
|
|
133
|
+
{children}
|
|
134
|
+
</ExtensionFileContext.Provider>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { createContext, use, useMemo, useState, type ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
export type NetworkActivity = "upload" | "download" | "idle";
|
|
4
|
+
|
|
5
|
+
interface NetworkActivityContextValue {
|
|
6
|
+
activity: NetworkActivity;
|
|
7
|
+
setActivity: (activity: NetworkActivity) => void;
|
|
8
|
+
startUpload: () => void;
|
|
9
|
+
startDownload: () => void;
|
|
10
|
+
stopActivity: () => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const NetworkActivityContext = createContext<NetworkActivityContextValue | null>(null);
|
|
14
|
+
|
|
15
|
+
export function NetworkActivityProvider({ children }: { children: ReactNode }) {
|
|
16
|
+
const [activity, setActivity] = useState<NetworkActivity>("idle");
|
|
17
|
+
|
|
18
|
+
const startUpload = () => setActivity("upload");
|
|
19
|
+
const startDownload = () => setActivity("download");
|
|
20
|
+
const stopActivity = () => setActivity("idle");
|
|
21
|
+
|
|
22
|
+
const value = useMemo(
|
|
23
|
+
() => ({
|
|
24
|
+
activity,
|
|
25
|
+
setActivity,
|
|
26
|
+
startUpload,
|
|
27
|
+
startDownload,
|
|
28
|
+
stopActivity,
|
|
29
|
+
}),
|
|
30
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
31
|
+
[activity],
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<NetworkActivityContext.Provider value={value}>{children}</NetworkActivityContext.Provider>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function useNetworkActivity() {
|
|
40
|
+
const context = use(NetworkActivityContext);
|
|
41
|
+
if (!context) {
|
|
42
|
+
throw new Error("useNetworkActivity must be used within NetworkActivityProvider");
|
|
43
|
+
}
|
|
44
|
+
return context;
|
|
45
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { useFocusState } from "@codellm/jar";
|
|
2
|
+
import { createContext, useCallback, use, useMemo } from "react";
|
|
3
|
+
|
|
4
|
+
import { notify, notifyAction, notifyComplete } from "../terminal/notification.js";
|
|
5
|
+
|
|
6
|
+
interface NotificationContextValue {
|
|
7
|
+
notifyIfUnfocused: (message: string, title?: string) => Promise<void>;
|
|
8
|
+
notifyActionRequired: (message: string) => Promise<void>;
|
|
9
|
+
notifyTaskComplete: (message: string) => Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const NotificationContext = createContext<NotificationContextValue | null>(null);
|
|
13
|
+
|
|
14
|
+
export const useNotification = () => {
|
|
15
|
+
const context = use(NotificationContext);
|
|
16
|
+
if (!context) {
|
|
17
|
+
throw new Error("useNotification must be used within NotificationProvider");
|
|
18
|
+
}
|
|
19
|
+
return context;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const NotificationProvider = ({ children }: React.PropsWithChildren) => {
|
|
23
|
+
const { isFocused } = useFocusState();
|
|
24
|
+
|
|
25
|
+
const notifyIfUnfocused = useCallback(
|
|
26
|
+
async (message: string, title?: string) => {
|
|
27
|
+
if (!isFocused) {
|
|
28
|
+
await notify({ message, title });
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
[isFocused],
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const notifyActionRequired = useCallback(
|
|
35
|
+
async (message: string) => {
|
|
36
|
+
if (!isFocused) {
|
|
37
|
+
await notifyAction(message);
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
[isFocused],
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const notifyTaskComplete = useCallback(
|
|
44
|
+
async (message: string) => {
|
|
45
|
+
if (!isFocused) {
|
|
46
|
+
await notifyComplete(message);
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
[isFocused],
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const value = useMemo(
|
|
53
|
+
() => ({
|
|
54
|
+
notifyIfUnfocused,
|
|
55
|
+
notifyActionRequired,
|
|
56
|
+
notifyTaskComplete,
|
|
57
|
+
}),
|
|
58
|
+
[notifyIfUnfocused, notifyActionRequired, notifyTaskComplete],
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
return <NotificationContext.Provider value={value}>{children}</NotificationContext.Provider>;
|
|
62
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { createContext, use, useCallback, useLayoutEffect, useMemo, useState } from "react";
|
|
2
|
+
|
|
3
|
+
const SHELL_PADDING_X = 8;
|
|
4
|
+
|
|
5
|
+
interface ShellSizeContextValue {
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const ShellSizeContext = createContext<ShellSizeContextValue | null>(null);
|
|
11
|
+
|
|
12
|
+
export const useShellSize = () => {
|
|
13
|
+
const ctx = use(ShellSizeContext);
|
|
14
|
+
if (!ctx) {
|
|
15
|
+
throw new Error(`${useShellSize.name} must be used within a ${ShellSizeProvider.name}`);
|
|
16
|
+
}
|
|
17
|
+
return ctx;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function ShellSizeProvider({ children }: React.PropsWithChildren) {
|
|
21
|
+
const [size, setSize] = useState({
|
|
22
|
+
columns: process.stdout.columns - SHELL_PADDING_X,
|
|
23
|
+
rows: process.stdout.rows,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const handleResize = useCallback(() => {
|
|
27
|
+
setSize({
|
|
28
|
+
columns: process.stdout.columns - SHELL_PADDING_X,
|
|
29
|
+
rows: process.stdout.rows,
|
|
30
|
+
});
|
|
31
|
+
}, []);
|
|
32
|
+
|
|
33
|
+
useLayoutEffect(() => {
|
|
34
|
+
process.stdout.on("resize", handleResize);
|
|
35
|
+
return () => {
|
|
36
|
+
process.stdout.off("resize", handleResize);
|
|
37
|
+
};
|
|
38
|
+
}, [handleResize]);
|
|
39
|
+
|
|
40
|
+
const value = useMemo<ShellSizeContextValue>(
|
|
41
|
+
() => ({
|
|
42
|
+
width: size.columns,
|
|
43
|
+
height: size.rows,
|
|
44
|
+
}),
|
|
45
|
+
[size.columns, size.rows],
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
return <ShellSizeContext.Provider value={value}>{children}</ShellSizeContext.Provider>;
|
|
49
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createContext, useCallback, use } from "react";
|
|
2
|
+
|
|
3
|
+
import { useCleanup } from "../hooks/use-cleanup.js";
|
|
4
|
+
import { ANSI } from "../lib/ansi.js";
|
|
5
|
+
|
|
6
|
+
interface ShellTitleContextValue {
|
|
7
|
+
setTitle: (title: string) => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const ShellTitleContext = createContext<ShellTitleContextValue | null>(null);
|
|
11
|
+
|
|
12
|
+
export const useShellTitle = () => {
|
|
13
|
+
const ctx = use(ShellTitleContext);
|
|
14
|
+
if (!ctx) {
|
|
15
|
+
throw new Error(`${useShellTitle.name} must be used within a ${ShellTitleProvider.name}`);
|
|
16
|
+
}
|
|
17
|
+
return ctx;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function ShellTitleProvider({ children }: React.PropsWithChildren) {
|
|
21
|
+
const setTitle = useCallback((title: string) => {
|
|
22
|
+
// Remove C0 control characters (U+0000-U+001F) and DEL (U+007F)
|
|
23
|
+
const cleanedTitle = title
|
|
24
|
+
.split("")
|
|
25
|
+
.filter((ch) => {
|
|
26
|
+
const code = ch.codePointAt(0)!;
|
|
27
|
+
return !(code <= 0x1f || code === 0x7f);
|
|
28
|
+
})
|
|
29
|
+
.join("");
|
|
30
|
+
process.stdout.write(`${ANSI.SET_TITLE}${cleanedTitle}${ANSI.BEL}`);
|
|
31
|
+
}, []);
|
|
32
|
+
|
|
33
|
+
useCleanup(() => {
|
|
34
|
+
process.stdout.write(`${ANSI.SET_TITLE}${ANSI.BEL}`);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return <ShellTitleContext.Provider value={{ setTitle }}>{children}</ShellTitleContext.Provider>;
|
|
38
|
+
}
|