@jancellor/ask 1.1.0 → 1.1.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 (41) hide show
  1. package/dist/cli.js +0 -0
  2. package/dist/index.js +0 -0
  3. package/package.json +1 -1
  4. package/dist/agent/fs-errors.js +0 -23
  5. package/dist/agent/index.d.ts +0 -1
  6. package/dist/agent/index.js +0 -1
  7. package/dist/agent/system-prompt.md +0 -107
  8. package/dist/batch/index.d.ts +0 -4
  9. package/dist/batch/index.js +0 -101
  10. package/dist/config/index.d.ts +0 -6
  11. package/dist/config/index.js +0 -22
  12. package/dist/tui/app.d.ts +0 -7
  13. package/dist/tui/app.js +0 -12
  14. package/dist/tui/assistant-part-message.d.ts +0 -4
  15. package/dist/tui/assistant-part-message.js +0 -10
  16. package/dist/tui/execute-tool-part-message.d.ts +0 -2
  17. package/dist/tui/execute-tool-part-message.js +0 -48
  18. package/dist/tui/generic-tool-part-message.d.ts +0 -2
  19. package/dist/tui/generic-tool-part-message.js +0 -28
  20. package/dist/tui/index.d.ts +0 -4
  21. package/dist/tui/index.js +0 -18
  22. package/dist/tui/input.d.ts +0 -8
  23. package/dist/tui/input.js +0 -115
  24. package/dist/tui/markdown.d.ts +0 -1
  25. package/dist/tui/markdown.js +0 -31
  26. package/dist/tui/messages.d.ts +0 -9
  27. package/dist/tui/messages.js +0 -87
  28. package/dist/tui/run.d.ts +0 -4
  29. package/dist/tui/run.js +0 -18
  30. package/dist/tui/spinner-message.d.ts +0 -1
  31. package/dist/tui/spinner-message.js +0 -14
  32. package/dist/tui/tool-part-message.d.ts +0 -12
  33. package/dist/tui/tool-part-message.js +0 -9
  34. package/dist/tui/use-agent.d.ts +0 -13
  35. package/dist/tui/use-agent.js +0 -27
  36. package/dist/tui/use-input-state.d.ts +0 -19
  37. package/dist/tui/use-input-state.js +0 -136
  38. package/dist/tui/user-part-message.d.ts +0 -3
  39. package/dist/tui/user-part-message.js +0 -9
  40. package/dist/tui/welcome.d.ts +0 -7
  41. package/dist/tui/welcome.js +0 -12
package/dist/cli.js CHANGED
File without changes
package/dist/index.js CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jancellor/ask",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "A minimal coding agent CLI.",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -1,23 +0,0 @@
1
- export function isEnoentError(error) {
2
- if (!error || typeof error !== 'object')
3
- return false;
4
- return 'code' in error && error.code === 'ENOENT';
5
- }
6
- export async function ignoreMissing(op) {
7
- try {
8
- return await op();
9
- }
10
- catch (error) {
11
- if (!isEnoentError(error))
12
- throw error;
13
- }
14
- }
15
- export function ignoreMissingSync(op) {
16
- try {
17
- return op();
18
- }
19
- catch (error) {
20
- if (!isEnoentError(error))
21
- throw error;
22
- }
23
- }
@@ -1 +0,0 @@
1
- export { Agent, ABORTED_MESSAGE, ERROR_MESSAGE, type AskMessage, type AgentOptions, type AskMessageMeta, type AgentListener, } from './agent.js';
@@ -1 +0,0 @@
1
- export { Agent, ABORTED_MESSAGE, ERROR_MESSAGE, } from './agent.js';
@@ -1,107 +0,0 @@
1
- # System prompt
2
-
3
- You are an expert coding agent or coding assistant.
4
- You are typically invoked via the `ask` executable harness.
5
- The user may refer to you as "Ask".
6
- You help users with tasks including coding by executing commands
7
- including those for searching, reading, editing and writing files.
8
-
9
- Available tools:
10
-
11
- - `execute`: execute shell commands using bash.
12
-
13
- Guidelines:
14
-
15
- - Read relevant files and understand context before making changes.
16
- - Use `execute` for file operations like `ls`, `rg`, `fd`.
17
- - When summarizing your actions, output plain text directly - do NOT use cat or bash to display what you did.
18
- - Be concise in your responses.
19
- - Show file paths clearly when working with files.
20
-
21
- ## Searching
22
-
23
- Use `ls`, `rg`, `fd` for exploring the filesystem.
24
- Use flags for following symlinks, including hidden items,
25
- and not ignoring ignored items where appropriate,
26
- eg `ls -a`, `rg -L -uu`, `fd -L -u`.
27
-
28
- ## File editing
29
-
30
- ### Reading
31
-
32
- Prefer `sed` to `cat` to avoid dumping large files into context.
33
-
34
- ```bash
35
- sed -n '1,200p' path/to/file
36
- ```
37
-
38
- ### Writing
39
-
40
- Use `cat` with heredocs to create new files.
41
- Check the file doesn't already exist before writing.
42
-
43
- ```bash
44
- cat > path/to/new-file <<'EOF'
45
- <new content>
46
- EOF
47
- ```
48
-
49
- ### Editing
50
-
51
- Use `sd -F` with `cat` and heredocs for targeted edits.
52
- Generally prefer making targeted edits rather than rewriting the entire file.
53
- After editing, read back the modified region to verify.
54
-
55
- ```bash
56
- OLD_BLOCK=$(cat <<'OLD_EOF'
57
- <old content>
58
- OLD_EOF
59
- )
60
-
61
- NEW_BLOCK=$(cat <<'NEW_EOF'
62
- <new content>
63
- NEW_EOF
64
- )
65
-
66
- sd -F -- "$OLD_BLOCK" "$NEW_BLOCK" path/to/file
67
- ```
68
-
69
- ## Background processes
70
-
71
- Use `tmux` when processes need to run in the background.
72
- For full output, use `capture-pane -S -` or `pipe-pane` to file.
73
- Prefix session names with "agents-".
74
-
75
- ## Subagents, task delegation, and context management
76
-
77
- Typically the user has invoked you by running `ask` from a terminal shell.
78
- You may also run `ask "<msg>"` to invoke another copy of the agent,
79
- which may be referred to as a subagent.
80
- You may do this in shell commands or in scripts you execute to help you achieve your tasks.
81
- However you should not use `ask` in code you generate for the user to run independently.
82
- The point of delegating is to control the context which you, the main agent, and the subagent sees.
83
- If you need to answer a complicated query in the middle of a conversation, by delegating to a subagent,
84
- the subagent does not see the unnecessary full context of your conversation.
85
- It only sees what you explicitly include in the prompt.
86
- Similarly, you do not see the output of the intermediate steps the subagent used to answer the question.
87
- By keeping the context of yourself and subagents limited to only the scope they require,
88
- overall accuracy is typically improved.
89
- Also, by invoking multiple subagents in a single turn, you can achieve parallelism
90
- and therefore faster performance for tasks that are truly independent.
91
-
92
- ## Session storage
93
-
94
- When `ask` runs, messages are persisted to `~/.ask/sessions/<id>.jsonl` as AI SDK messages.
95
- IDs are UUIDs.
96
- Messages include additional metadata in a `_meta` property, including `id`, `parentId`, `uiHidden`, and `timestamp`.
97
- When using subagents, consider passing an explicit session ID via `ask --resume <id> ...`.
98
- This allows you to inspect the context of the subagent, though note that
99
- usually you explicitly don't want the subagent context in your own.
100
- This also allows you ask a subagent follow up question by supplying the same session ID twice.
101
- It may be useful to make focused searches of the subagent context.
102
-
103
- ## Web
104
-
105
- For searching or fetching from the web, you may delegate to `codex`, another coding agent.
106
-
107
- codex exec --skip-git-repo-check "What is the weather like in London today?"
@@ -1,4 +0,0 @@
1
- import { type AgentOptions } from '../agent/index.js';
2
- type RunBatchOptions = AgentOptions;
3
- export declare function runBatch(argument: string | undefined, options: RunBatchOptions): Promise<void>;
4
- export {};
@@ -1,101 +0,0 @@
1
- import { Agent } from '../agent/index.js';
2
- import { ShutdownManager } from '../shutdown-manager.js';
3
- export async function runBatch(argument, options) {
4
- const stdin = !process.stdin.isTTY ? await readStdin() : undefined;
5
- const message = [stdin, argument].filter(Boolean).join('\n\n');
6
- if (!message)
7
- throw new Error('no message provided (pass [message] or pipe stdin)');
8
- const agent = await Agent.create(options);
9
- // console.error(`[Session: ${agent.sessionId}]`);
10
- const shutdownManager = new ShutdownManager(async () => {
11
- await agent.cancelAll();
12
- });
13
- shutdownManager.installSignalHandlers();
14
- // Log tool calls to stderr as they happen
15
- agent.addListener({
16
- onMessages(newMessages) {
17
- for (const msg of newMessages) {
18
- if (msg.role !== 'assistant' || !Array.isArray(msg.content))
19
- continue;
20
- for (const part of msg.content) {
21
- if (part?.type === 'tool-call') {
22
- const line = formatToolCall(part.toolName, part.input);
23
- console.error(truncate(line, 80));
24
- }
25
- }
26
- }
27
- },
28
- });
29
- await agent.ask(message);
30
- // Extract and output the final assistant response
31
- const response = extractFinalResponse(agent.messages);
32
- console.log(response);
33
- }
34
- async function readStdin() {
35
- let data = '';
36
- for await (const chunk of process.stdin) {
37
- data += String(chunk);
38
- }
39
- return data;
40
- }
41
- function formatToolCall(toolName, input) {
42
- if (toolName === 'execute') {
43
- const command = extractCommand(input);
44
- if (command) {
45
- return `$ ${command}`;
46
- }
47
- }
48
- // Generic format for other tools
49
- const inputStr = formatToolInput(input);
50
- return inputStr ? `${toolName} ${inputStr}` : toolName;
51
- }
52
- function extractCommand(input) {
53
- if (typeof input === 'string')
54
- return input;
55
- if (input && typeof input === 'object') {
56
- const obj = input;
57
- if (typeof obj.command === 'string')
58
- return obj.command;
59
- }
60
- return null;
61
- }
62
- function formatToolInput(input) {
63
- if (input === undefined || input === null)
64
- return '';
65
- if (typeof input === 'string')
66
- return input;
67
- return JSON.stringify(input);
68
- }
69
- function truncate(str, maxLen) {
70
- const hasMultipleLines = str.includes('\n');
71
- const firstLine = str.split('\n')[0] ?? '';
72
- // Needs truncation (either multiple lines or too long)
73
- const needsTruncation = hasMultipleLines || firstLine.length > maxLen;
74
- if (!needsTruncation) {
75
- return firstLine;
76
- }
77
- // Always reserve 3 chars for "...", so max content is maxLen - 3
78
- const contentMax = maxLen - 3;
79
- const truncated = firstLine.slice(0, contentMax);
80
- return truncated + '...';
81
- }
82
- function extractFinalResponse(messages) {
83
- // Find the last non-hidden assistant message
84
- const assistantMessages = messages.filter((m) => !m._meta?.uiHidden && m.role === 'assistant');
85
- const lastMessage = assistantMessages.at(-1);
86
- if (!lastMessage)
87
- return '';
88
- const { content } = lastMessage;
89
- // Handle string content
90
- if (typeof content === 'string') {
91
- return content;
92
- }
93
- // Handle array content - extract text parts
94
- if (Array.isArray(content)) {
95
- return content
96
- .filter((p) => p?.type === 'text')
97
- .map((p) => p.text)
98
- .join('');
99
- }
100
- return '';
101
- }
@@ -1,6 +0,0 @@
1
- export type RunConfigOptions = {
2
- provider?: string;
3
- model?: string;
4
- variant?: string | null;
5
- };
6
- export declare function runConfig(options: RunConfigOptions): Promise<void>;
@@ -1,22 +0,0 @@
1
- import { ConfigReader } from '../agent/config.js';
2
- export async function runConfig(options) {
3
- const resolved = await new ConfigReader().resolve({
4
- ...options,
5
- saveAsCurrent: true,
6
- });
7
- printResolvedConfig(resolved);
8
- }
9
- function printResolvedConfig(resolved) {
10
- const provider = resolved.provider === resolved.sdkProvider
11
- ? resolved.provider
12
- : `${resolved.provider}=${resolved.sdkProvider}`;
13
- const model = resolved.model === resolved.sdkModel
14
- ? resolved.model
15
- : `${resolved.model}=${resolved.sdkModel}`;
16
- const variant = resolved.variant ?? '';
17
- console.log(`provider=${provider}`);
18
- console.log(`model=${model}`);
19
- console.log(`variant=${variant}`);
20
- console.log(`providerOptions=${JSON.stringify(resolved.providerOptions)}`);
21
- console.log(`generateOptions=${JSON.stringify(resolved.generateOptions)}`);
22
- }
package/dist/tui/app.d.ts DELETED
@@ -1,7 +0,0 @@
1
- import { type Agent } from '../agent/agent.js';
2
- type AppProps = {
3
- agent: Agent;
4
- onRequestShutdown: () => void;
5
- };
6
- export declare function App({ agent, onRequestShutdown }: AppProps): import("react/jsx-runtime").JSX.Element;
7
- export {};
package/dist/tui/app.js DELETED
@@ -1,12 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Box } from 'ink';
3
- import { Input } from './input.js';
4
- import { Messages } from './messages.js';
5
- import { useAgent } from './use-agent.js';
6
- export function App({ agent, onRequestShutdown }) {
7
- const { messages, model, provider, variant, sendMessage, abort, clear } = useAgent(agent);
8
- const handleSubmit = (message) => {
9
- void sendMessage(message);
10
- };
11
- return (_jsxs(Box, { flexDirection: "column", height: "100%", children: [_jsx(Messages, { messages: messages, model: model, provider: provider, variant: variant }), _jsx(Input, { onSubmit: handleSubmit, onAbort: abort, onClear: clear, onRequestShutdown: onRequestShutdown })] }));
12
- }
@@ -1,4 +0,0 @@
1
- export declare function AssistantPartMessage({ text, dim, }: {
2
- text: string;
3
- dim?: boolean;
4
- }): import("react/jsx-runtime").JSX.Element;
@@ -1,10 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Box, Text } from 'ink';
3
- import { ABORTED_MESSAGE, ERROR_MESSAGE } from '../agent/agent.js';
4
- import { parseMarkdown } from './markdown.js';
5
- export function AssistantPartMessage({ text, dim, }) {
6
- const error = [ABORTED_MESSAGE, ERROR_MESSAGE].some((e) => text.startsWith(e));
7
- const parsed = error || dim ? text : parseMarkdown(text);
8
- const color = error ? 'red' : dim ? 'gray' : undefined;
9
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: color, dimColor: error, children: parsed }), _jsx(Text, { children: " " })] }));
10
- }
@@ -1,2 +0,0 @@
1
- import type { ToolPartMessageRenderProps } from './tool-part-message.js';
2
- export declare function ExecuteToolPartMessage({ input, output, }: ToolPartMessageRenderProps): import("react/jsx-runtime").JSX.Element;
@@ -1,48 +0,0 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Box, Text } from 'ink';
3
- import { SpinnerMessage } from './spinner-message.js';
4
- export function ExecuteToolPartMessage({ input, output, }) {
5
- const displayInput = input == null ? null : formatExecuteToolInput(input);
6
- const parsed = output == null ? null : parseExecuteToolOutput(output);
7
- return (_jsxs(Box, { flexDirection: "column", children: [displayInput ? (_jsxs(_Fragment, { children: [_jsx(Text, { children: displayInput }), _jsx(Text, { children: " " })] })) : null, parsed?.stdout ? (_jsxs(_Fragment, { children: [_jsx(Text, { dimColor: true, children: parsed.stdout }), _jsx(Text, { children: " " })] })) : null, parsed?.stderr ? (_jsxs(_Fragment, { children: [_jsx(Text, { color: "red", dimColor: true, children: parsed.stderr }), _jsx(Text, { children: " " })] })) : null, parsed?.error ? (_jsxs(_Fragment, { children: [_jsxs(Text, { color: "red", children: ["error: ", parsed.error] }), _jsx(Text, { children: " " })] })) : null, parsed?.exit ? (_jsxs(_Fragment, { children: [_jsx(Text, { children: parsed.exit }), _jsx(Text, { children: " " })] })) : null, parsed?.signal ? (_jsxs(_Fragment, { children: [_jsx(Text, { color: "red", dimColor: true, children: parsed.signal }), _jsx(Text, { children: " " })] })) : null, output == null ? _jsx(SpinnerMessage, {}) : null] }));
8
- }
9
- function parseExecuteToolOutput(output) {
10
- if (!output || typeof output !== 'object')
11
- return null;
12
- const outer = output;
13
- if (outer.type !== 'json' || !('value' in outer))
14
- return null;
15
- const value = outer.value;
16
- if (!value || typeof value !== 'object')
17
- return null;
18
- const inner = value;
19
- const stdout = typeof inner.stdout === 'string' && inner.stdout.trim().length > 0
20
- ? inner.stdout.trimEnd()
21
- : '';
22
- const stderr = typeof inner.stderr === 'string' && inner.stderr.trim().length > 0
23
- ? inner.stderr.trimEnd()
24
- : '';
25
- const error = typeof inner.error === 'string' && inner.error.trim().length > 0
26
- ? inner.error.trim()
27
- : '';
28
- const exit = inner.exit ?? inner.exitCode;
29
- const signal = inner.signal;
30
- return {
31
- stdout,
32
- stderr,
33
- error,
34
- exit: exit !== undefined ? `exit ${exit}` : '',
35
- signal: signal ? String(signal) : '',
36
- };
37
- }
38
- function formatExecuteToolInput(input) {
39
- if (typeof input === 'string')
40
- return input;
41
- if (input && typeof input === 'object') {
42
- const obj = input;
43
- if (typeof obj.command === 'string')
44
- return `$ ${obj.command}`;
45
- return JSON.stringify(input, null, 2);
46
- }
47
- return String(input);
48
- }
@@ -1,2 +0,0 @@
1
- import type { ToolPartMessageRenderProps } from './tool-part-message.js';
2
- export declare function GenericToolPartMessage({ toolName, input, output, }: ToolPartMessageRenderProps): import("react/jsx-runtime").JSX.Element;
@@ -1,28 +0,0 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Box, Text } from 'ink';
3
- import { SpinnerMessage } from './spinner-message.js';
4
- export function GenericToolPartMessage({ toolName, input, output, }) {
5
- return (_jsxs(Box, { flexDirection: "column", children: [toolName ? (_jsxs(_Fragment, { children: [_jsx(Text, { children: toolName }), _jsx(Text, { children: " " })] })) : null, input != null ? (_jsxs(_Fragment, { children: [_jsx(Text, { dimColor: true, children: formatGenericToolInput(input) }), _jsx(Text, { children: " " })] })) : null, output != null ? (_jsxs(_Fragment, { children: [_jsx(Text, { dimColor: true, children: formatGenericToolOutput(output) }), _jsx(Text, { children: " " })] })) : (_jsx(SpinnerMessage, {}))] }));
6
- }
7
- function formatGenericToolInput(input) {
8
- if (typeof input === 'string')
9
- return input;
10
- if (input && typeof input === 'object')
11
- return JSON.stringify(input, null, 2);
12
- return String(input);
13
- }
14
- function formatGenericToolOutput(output) {
15
- if (!output || typeof output !== 'object')
16
- return String(output ?? '');
17
- const o = output;
18
- if ('type' in o && 'value' in o) {
19
- const value = o.value;
20
- if (typeof value === 'string')
21
- return value;
22
- if (value && typeof value === 'object') {
23
- return JSON.stringify(value, null, 2);
24
- }
25
- return String(value);
26
- }
27
- return JSON.stringify(output, null, 2);
28
- }
@@ -1,4 +0,0 @@
1
- import { type AgentOptions } from '../agent/index.js';
2
- type RunTuiOptions = AgentOptions;
3
- export declare function runTui(options: RunTuiOptions): Promise<void>;
4
- export {};
package/dist/tui/index.js DELETED
@@ -1,18 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { render } from 'ink';
3
- import { Agent } from '../agent/index.js';
4
- import { ShutdownManager } from '../shutdown-manager.js';
5
- import { App } from './app.js';
6
- export async function runTui(options) {
7
- const agent = await Agent.create(options);
8
- const shutdownManager = new ShutdownManager(async () => {
9
- try {
10
- app.unmount();
11
- }
12
- finally {
13
- await agent.cancelAll();
14
- }
15
- });
16
- shutdownManager.installSignalHandlers();
17
- const app = render(_jsx(App, { agent: agent, onRequestShutdown: () => shutdownManager.requestShutdown() }), { exitOnCtrlC: false });
18
- }
@@ -1,8 +0,0 @@
1
- type InputProps = {
2
- onSubmit: (message: string) => void | Promise<void>;
3
- onAbort: () => void;
4
- onClear: (beforeClear?: () => void) => Promise<void>;
5
- onRequestShutdown: () => void;
6
- };
7
- export declare function Input({ onSubmit, onAbort, onClear, onRequestShutdown, }: InputProps): import("react/jsx-runtime").JSX.Element;
8
- export {};
package/dist/tui/input.js DELETED
@@ -1,115 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState, useEffect } from 'react';
3
- import { Box, Text, useInput } from 'ink';
4
- import { useInputState } from './use-input-state.js';
5
- const CLEAR_COMMAND = '/clear';
6
- const CURSOR_BLINK_MS = 600;
7
- export function Input({ onSubmit, onAbort, onClear, onRequestShutdown, }) {
8
- const { value, beforeCursor, atCursor, afterCursor, moveStart, moveEnd, moveLeft, moveRight, moveWordLeft, moveWordRight, deleteBackward, deleteToStart, deleteToEnd, deleteWordBackward, insertText, clear, } = useInputState();
9
- const [showCursor, setShowCursor] = useState(true);
10
- const [keyPulse, setKeyPulse] = useState(0);
11
- useEffect(() => {
12
- let intervalId;
13
- const timeoutId = setTimeout(() => {
14
- setShowCursor(false);
15
- intervalId = setInterval(() => {
16
- setShowCursor((prev) => !prev);
17
- }, CURSOR_BLINK_MS);
18
- }, CURSOR_BLINK_MS);
19
- return () => {
20
- clearTimeout(timeoutId);
21
- if (intervalId) {
22
- clearInterval(intervalId);
23
- }
24
- };
25
- }, [keyPulse]);
26
- useInput((input, key) => {
27
- setShowCursor(true);
28
- setKeyPulse((pulse) => pulse + 1);
29
- if (key.escape) {
30
- onAbort();
31
- return;
32
- }
33
- if (key.ctrl && input === 'c') {
34
- onRequestShutdown();
35
- return;
36
- }
37
- if (key.ctrl && input === 'a') {
38
- moveStart();
39
- return;
40
- }
41
- if (key.ctrl && input === 'e') {
42
- moveEnd();
43
- return;
44
- }
45
- if (key.ctrl && input === 'u') {
46
- deleteToStart();
47
- return;
48
- }
49
- if (key.ctrl && input === 'k') {
50
- deleteToEnd();
51
- return;
52
- }
53
- if (key.ctrl && input === 'w') {
54
- deleteWordBackward();
55
- return;
56
- }
57
- if (key.home) {
58
- moveStart();
59
- return;
60
- }
61
- if (key.end) {
62
- moveEnd();
63
- return;
64
- }
65
- if (key.leftArrow) {
66
- if (key.ctrl || key.meta) {
67
- moveWordLeft();
68
- }
69
- else {
70
- moveLeft();
71
- }
72
- return;
73
- }
74
- if (key.rightArrow) {
75
- if (key.ctrl || key.meta) {
76
- moveWordRight();
77
- }
78
- else {
79
- moveRight();
80
- }
81
- return;
82
- }
83
- if (key.backspace || key.delete) {
84
- deleteBackward();
85
- return;
86
- }
87
- if (key.return) {
88
- if (key.meta) {
89
- insertText('\n');
90
- return;
91
- }
92
- const message = value.trim();
93
- if (!message) {
94
- return;
95
- }
96
- clear();
97
- if (message === CLEAR_COMMAND) {
98
- void onClear(() => {
99
- console.log('[New session]\n');
100
- });
101
- }
102
- else {
103
- void onSubmit(message);
104
- }
105
- return;
106
- }
107
- if (!input) {
108
- return;
109
- }
110
- insertText(input);
111
- });
112
- const width = Math.max(10, process.stdout.columns ?? 80);
113
- const divider = '─'.repeat(width);
114
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "gray", children: divider }), _jsxs(Text, { children: [beforeCursor, showCursor ? _jsx(Text, { inverse: true, children: atCursor }) : atCursor, afterCursor] }), _jsx(Text, { color: "gray", children: divider })] }));
115
- }
@@ -1 +0,0 @@
1
- export declare function parseMarkdown(text: string): string;
@@ -1,31 +0,0 @@
1
- import chalk from 'chalk';
2
- import { marked } from 'marked';
3
- import TerminalRenderer from 'marked-terminal';
4
- marked.setOptions({
5
- renderer: new TerminalRenderer({
6
- code: chalk.hex('#D8DEE9'),
7
- codespan: chalk.hex('#B7A7FF'),
8
- blockquote: chalk.hex('#7D85A8').italic,
9
- heading: chalk.hex('#69A9FF').bold,
10
- firstHeading: chalk.hex('#8DC2FF').bold,
11
- strong: chalk.hex('#F0F3FF').bold,
12
- em: chalk.hex('#8E96B8').italic,
13
- del: chalk.hex('#6A7192').strikethrough,
14
- link: chalk.hex('#7AA8FF'),
15
- href: chalk.hex('#7AA8FF').underline,
16
- html: chalk.hex('#7B84A7'),
17
- hr: chalk.hex('#4A5070'),
18
- listitem: chalk.hex('#C5CCE1'),
19
- table: chalk.hex('#C5CCE1'),
20
- paragraph: chalk.reset,
21
- emoji: true,
22
- showSectionPrefix: true,
23
- reflowText: false,
24
- width: 80,
25
- tab: 2,
26
- }),
27
- });
28
- export function parseMarkdown(text) {
29
- const rendered = marked.parse(text);
30
- return rendered.replace(/(?:\r?\n){1,2}$/, '');
31
- }
@@ -1,9 +0,0 @@
1
- import type { AskMessage } from '../agent/agent.js';
2
- interface MessagesProps {
3
- messages: AskMessage[];
4
- model: string;
5
- provider: string;
6
- variant: string | null;
7
- }
8
- export declare function Messages({ messages: rawMessages, model, provider, variant, }: MessagesProps): import("react/jsx-runtime").JSX.Element;
9
- export {};
@@ -1,87 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Box, Static } from 'ink';
3
- import { AssistantPartMessage } from './assistant-part-message.js';
4
- import { SpinnerMessage } from './spinner-message.js';
5
- import { ToolPartMessage } from './tool-part-message.js';
6
- import { UserPartMessage } from './user-part-message.js';
7
- import { Welcome } from './welcome.js';
8
- export function Messages({ messages: rawMessages, model, provider, variant, }) {
9
- const messages = rawMessages.filter((m) => !m._meta?.uiHidden);
10
- const toolResults = buildToolResultsMap(messages);
11
- const parts = messages.flatMap((message, i) => getContentParts(message.content).map((part, j) => ({
12
- ...part,
13
- role: message.role,
14
- i,
15
- j,
16
- id: `${message.role}-${i}-${j}`,
17
- })));
18
- const splitIdx = parts.findIndex((p) => p.type === 'tool-call' && !toolResults.has(p.toolCallId));
19
- const staticParts = splitIdx === -1 ? parts : parts.slice(0, splitIdx);
20
- const activeParts = splitIdx === -1 ? [] : parts.slice(splitIdx);
21
- const allStaticItems = [
22
- { id: 'welcome', type: 'welcome' },
23
- ...staticParts,
24
- ];
25
- const renderPart = (p) => {
26
- if (p.role === 'assistant' && p.type === 'text') {
27
- return _jsx(AssistantPartMessage, { text: p.text, dim: false }, p.id);
28
- }
29
- if (p.role === 'user') {
30
- if (p.type === 'text') {
31
- return _jsx(UserPartMessage, { text: p.text }, p.id);
32
- }
33
- }
34
- if (p.type === 'tool-call') {
35
- return (_jsx(ToolPartMessage, { toolName: p.toolName ?? toolResults.get(p.toolCallId)?.toolName, input: p.input, output: toolResults.get(p.toolCallId)?.output }, p.id));
36
- }
37
- return null;
38
- };
39
- const showSpinner = messages.length > 0 && messages.at(-1).role !== 'assistant';
40
- return (_jsxs(Box, { flexGrow: 1, flexDirection: "column", children: [_jsx(Static, { items: allStaticItems, children: (item) => {
41
- if (item.type === 'welcome') {
42
- return (_jsx(Welcome, { model: model, provider: provider, variant: variant }, "welcome"));
43
- }
44
- return renderPart(item);
45
- } }), activeParts.map(renderPart), showSpinner && _jsx(SpinnerMessage, {})] }));
46
- }
47
- function getContentParts(content) {
48
- if (typeof content === 'string')
49
- return [{ type: 'text', text: content }];
50
- if (!Array.isArray(content))
51
- return [];
52
- const parts = [];
53
- for (const p of content) {
54
- if (!p || typeof p !== 'object')
55
- continue;
56
- if (p.type === 'text' && typeof p.text === 'string') {
57
- parts.push({ type: 'text', text: p.text });
58
- }
59
- else if (p.type === 'tool-call' && typeof p.toolCallId === 'string') {
60
- parts.push({
61
- type: 'tool-call',
62
- toolCallId: p.toolCallId,
63
- toolName: typeof p.toolName === 'string' ? p.toolName : null,
64
- input: p.input,
65
- });
66
- }
67
- }
68
- return parts;
69
- }
70
- function buildToolResultsMap(messages) {
71
- const map = new Map();
72
- for (const message of messages) {
73
- if (message.role !== 'tool' || !Array.isArray(message.content))
74
- continue;
75
- for (const part of message.content) {
76
- if (!part || typeof part !== 'object')
77
- continue;
78
- if (part.type === 'tool-result' && 'output' in part) {
79
- map.set(part.toolCallId, {
80
- toolName: typeof part.toolName === 'string' ? part.toolName : null,
81
- output: part.output,
82
- });
83
- }
84
- }
85
- }
86
- return map;
87
- }
package/dist/tui/run.d.ts DELETED
@@ -1,4 +0,0 @@
1
- import { type AgentOptions } from '../agent/agent.js';
2
- type RunTuiOptions = AgentOptions;
3
- export declare function runTui(options: RunTuiOptions): Promise<void>;
4
- export {};
package/dist/tui/run.js DELETED
@@ -1,18 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { render } from 'ink';
3
- import { Agent } from '../agent/agent.js';
4
- import { ShutdownManager } from '../shutdown-manager.js';
5
- import { App } from './app.js';
6
- export async function runTui(options) {
7
- const agent = await Agent.create(options);
8
- const shutdownManager = new ShutdownManager(async () => {
9
- try {
10
- app.unmount();
11
- }
12
- finally {
13
- await agent.cancelAll();
14
- }
15
- });
16
- shutdownManager.installSignalHandlers();
17
- const app = render(_jsx(App, { agent: agent, onRequestShutdown: () => shutdownManager.requestShutdown() }), { exitOnCtrlC: false });
18
- }
@@ -1 +0,0 @@
1
- export declare function SpinnerMessage(): import("react/jsx-runtime").JSX.Element;
@@ -1,14 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React from 'react';
3
- import { Box, Text } from 'ink';
4
- const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
5
- export function SpinnerMessage() {
6
- const [frameIndex, setFrameIndex] = React.useState(0);
7
- React.useEffect(() => {
8
- const timer = setInterval(() => {
9
- setFrameIndex((idx) => (idx + 1) % frames.length);
10
- }, 80);
11
- return () => clearInterval(timer);
12
- }, []);
13
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "white", children: frames[frameIndex] }), _jsx(Text, { children: " " })] }));
14
- }
@@ -1,12 +0,0 @@
1
- interface ToolPartMessageProps {
2
- toolName: string | null | undefined;
3
- input: unknown;
4
- output: unknown;
5
- }
6
- export type ToolPartMessageRenderProps = {
7
- toolName: string | null | undefined;
8
- input: unknown;
9
- output: unknown;
10
- };
11
- export declare function ToolPartMessage({ toolName, input, output, }: ToolPartMessageProps): import("react/jsx-runtime").JSX.Element;
12
- export {};
@@ -1,9 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { ExecuteToolPartMessage } from './execute-tool-part-message.js';
3
- import { GenericToolPartMessage } from './generic-tool-part-message.js';
4
- export function ToolPartMessage({ toolName, input, output, }) {
5
- if (toolName === 'execute') {
6
- return (_jsx(ExecuteToolPartMessage, { toolName: toolName, input: input, output: output }));
7
- }
8
- return (_jsx(GenericToolPartMessage, { toolName: toolName, input: input, output: output }));
9
- }
@@ -1,13 +0,0 @@
1
- import { Agent } from '../agent/agent.js';
2
- import type { AskMessage } from '../agent/messages.js';
3
- type UseAgentResult = {
4
- messages: AskMessage[];
5
- model: string;
6
- provider: string;
7
- variant: string | null;
8
- sendMessage: (message: string) => Promise<void>;
9
- abort: () => void;
10
- clear: (beforeClear?: () => void) => Promise<void>;
11
- };
12
- export declare function useAgent(agent: Agent): UseAgentResult;
13
- export {};
@@ -1,27 +0,0 @@
1
- import { useCallback, useEffect, useState } from 'react';
2
- export function useAgent(agent) {
3
- const [messages, setMessages] = useState([]);
4
- useEffect(() => {
5
- setMessages([...agent.messages]);
6
- const listener = {
7
- onMessages: () => setMessages([...agent.messages]),
8
- onClear: () => setMessages([]),
9
- };
10
- agent.addListener(listener);
11
- return () => {
12
- agent.removeListener(listener);
13
- };
14
- }, [agent]);
15
- const sendMessage = useCallback((message) => agent.ask(message), [agent]);
16
- const abort = useCallback(() => agent.abort(), [agent]);
17
- const clear = useCallback((beforeClear) => agent.clear(beforeClear), [agent]);
18
- return {
19
- messages,
20
- model: agent.model,
21
- provider: agent.provider,
22
- variant: agent.variant,
23
- sendMessage,
24
- abort,
25
- clear,
26
- };
27
- }
@@ -1,19 +0,0 @@
1
- export declare function useInputState(): {
2
- value: string;
3
- cursor: number;
4
- beforeCursor: string;
5
- atCursor: string;
6
- afterCursor: string;
7
- moveStart: () => void;
8
- moveEnd: () => void;
9
- moveLeft: () => void;
10
- moveRight: () => void;
11
- moveWordLeft: () => void;
12
- moveWordRight: () => void;
13
- deleteBackward: () => void;
14
- deleteToStart: () => void;
15
- deleteToEnd: () => void;
16
- deleteWordBackward: () => void;
17
- insertText: (text: string) => void;
18
- clear: () => void;
19
- };
@@ -1,136 +0,0 @@
1
- import { useState } from 'react';
2
- export function useInputState() {
3
- const [state, setState] = useState({ value: '', cursor: 0 });
4
- const { value, cursor } = state;
5
- const getWordCursor = (value, cursor, backwards) => {
6
- const len = value.length;
7
- const step = backwards ? -1 : 1;
8
- let i = Math.max(0, Math.min(cursor + Math.min(step, 0), len));
9
- const isSpace = (index) => /\s/.test(value[index] ?? ' ');
10
- const shouldMove = (offset, moveOnSpace) => isSpace(i + offset) === moveOnSpace;
11
- const firstOffset = 0;
12
- const secondOffset = step < 0 ? -1 : 0;
13
- const firstMoveOnSpace = step < 0;
14
- const secondMoveOnSpace = !firstMoveOnSpace;
15
- while (i > 0 && i < len && shouldMove(firstOffset, firstMoveOnSpace)) {
16
- i += step;
17
- }
18
- while (i > 0 && i < len && shouldMove(secondOffset, secondMoveOnSpace)) {
19
- i += step;
20
- }
21
- return i;
22
- };
23
- const moveStart = () => {
24
- setState((current) => ({ ...current, cursor: 0 }));
25
- };
26
- const moveEnd = () => {
27
- setState((current) => ({ ...current, cursor: current.value.length }));
28
- };
29
- const moveLeft = () => {
30
- setState((current) => ({
31
- ...current,
32
- cursor: Math.max(0, current.cursor - 1),
33
- }));
34
- };
35
- const moveRight = () => {
36
- setState((current) => ({
37
- ...current,
38
- cursor: Math.min(current.value.length, current.cursor + 1),
39
- }));
40
- };
41
- const moveWordLeft = () => {
42
- setState((current) => ({
43
- ...current,
44
- cursor: getWordCursor(current.value, current.cursor, true),
45
- }));
46
- };
47
- const moveWordRight = () => {
48
- setState((current) => ({
49
- ...current,
50
- cursor: getWordCursor(current.value, current.cursor, false),
51
- }));
52
- };
53
- const deleteBackward = () => {
54
- setState((current) => {
55
- if (current.cursor === 0) {
56
- return current;
57
- }
58
- return {
59
- value: current.value.slice(0, current.cursor - 1) +
60
- current.value.slice(current.cursor),
61
- cursor: current.cursor - 1,
62
- };
63
- });
64
- };
65
- const deleteToStart = () => {
66
- setState((current) => {
67
- if (current.cursor === 0) {
68
- return current;
69
- }
70
- return {
71
- value: current.value.slice(current.cursor),
72
- cursor: 0,
73
- };
74
- });
75
- };
76
- const deleteToEnd = () => {
77
- setState((current) => {
78
- if (current.cursor >= current.value.length) {
79
- return current;
80
- }
81
- return {
82
- ...current,
83
- value: current.value.slice(0, current.cursor),
84
- };
85
- });
86
- };
87
- const deleteWordBackward = () => {
88
- setState((current) => {
89
- if (current.cursor === 0) {
90
- return current;
91
- }
92
- const nextCursor = getWordCursor(current.value, current.cursor, true);
93
- return {
94
- value: current.value.slice(0, nextCursor) +
95
- current.value.slice(current.cursor),
96
- cursor: nextCursor,
97
- };
98
- });
99
- };
100
- const insertText = (text) => {
101
- if (!text) {
102
- return;
103
- }
104
- setState((current) => ({
105
- value: current.value.slice(0, current.cursor) +
106
- text +
107
- current.value.slice(current.cursor),
108
- cursor: current.cursor + text.length,
109
- }));
110
- };
111
- const clear = () => {
112
- setState({ value: '', cursor: 0 });
113
- };
114
- const beforeCursor = value.slice(0, cursor);
115
- const atCursor = value[cursor] ?? ' ';
116
- const afterCursor = value.slice(Math.min(cursor + 1, value.length));
117
- return {
118
- value,
119
- cursor,
120
- beforeCursor,
121
- atCursor,
122
- afterCursor,
123
- moveStart,
124
- moveEnd,
125
- moveLeft,
126
- moveRight,
127
- moveWordLeft,
128
- moveWordRight,
129
- deleteBackward,
130
- deleteToStart,
131
- deleteToEnd,
132
- deleteWordBackward,
133
- insertText,
134
- clear,
135
- };
136
- }
@@ -1,3 +0,0 @@
1
- export declare function UserPartMessage({ text }: {
2
- text: string;
3
- }): import("react/jsx-runtime").JSX.Element;
@@ -1,9 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Box, Text } from 'ink';
3
- import { parseMarkdown } from './markdown.js';
4
- export function UserPartMessage({ text }) {
5
- const width = Math.max(10, process.stdout.columns ?? 80);
6
- const divider = '─'.repeat(width);
7
- const parsed = parseMarkdown(text);
8
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "gray", children: divider }), _jsx(Text, { children: parsed }), _jsx(Text, { color: "gray", children: divider }), _jsx(Text, { children: " " })] }));
9
- }
@@ -1,7 +0,0 @@
1
- interface WelcomeProps {
2
- model: string;
3
- provider: string;
4
- variant: string | null;
5
- }
6
- export declare function Welcome({ model, provider, variant }: WelcomeProps): import("react/jsx-runtime").JSX.Element;
7
- export {};
@@ -1,12 +0,0 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Box, Text } from 'ink';
3
- import { homedir } from 'os';
4
- function shortDir() {
5
- const cwd = process.cwd();
6
- const home = homedir();
7
- return cwd.startsWith(home) ? '~' + cwd.slice(home.length) : cwd;
8
- }
9
- export function Welcome({ model, provider, variant }) {
10
- const dir = shortDir();
11
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: " " }), _jsxs(Text, { children: ["Ask ", _jsx(Text, { dimColor: true, children: "\u00B7" }), " ", provider, " ", _jsx(Text, { dimColor: true, children: "\u00B7" }), " ", model, variant !== null && (_jsxs(_Fragment, { children: [' ', _jsx(Text, { dimColor: true, children: "\u00B7" }), " ", variant] })), ' ', _jsx(Text, { dimColor: true, children: "\u00B7" }), " ", dir] }), _jsx(Text, { children: " " })] }));
12
- }