@falkenslab/agent-kit 0.10.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.
- package/LICENSE +21 -0
- package/README.md +97 -0
- package/assets/knowledge-plugin/.claude-plugin/plugin.json +4 -0
- package/assets/knowledge-plugin/commands/ingest.md +7 -0
- package/assets/knowledge-plugin/commands/lint.md +5 -0
- package/assets/knowledge-plugin/commands/query.md +5 -0
- package/assets/knowledge-plugin/skills/knowledge-ingest/SKILL.md +42 -0
- package/assets/knowledge-plugin/skills/knowledge-lint/SKILL.md +26 -0
- package/assets/knowledge-plugin/skills/knowledge-pages/SKILL.md +137 -0
- package/assets/knowledge-plugin/skills/knowledge-query/SKILL.md +14 -0
- package/dist/core/agentSpec.d.ts +132 -0
- package/dist/core/agentSpec.js +1 -0
- package/dist/core/claudeAuth.d.ts +24 -0
- package/dist/core/claudeAuth.js +27 -0
- package/dist/core/hooks/fileScopeGate.d.ts +33 -0
- package/dist/core/hooks/fileScopeGate.js +68 -0
- package/dist/core/hooks/humanInput.d.ts +15 -0
- package/dist/core/hooks/humanInput.js +52 -0
- package/dist/core/hooks/stepGate.d.ts +11 -0
- package/dist/core/hooks/stepGate.js +47 -0
- package/dist/core/hooks/subagentBashGate.d.ts +28 -0
- package/dist/core/hooks/subagentBashGate.js +40 -0
- package/dist/core/hooks/subagentForegroundGate.d.ts +18 -0
- package/dist/core/hooks/subagentForegroundGate.js +34 -0
- package/dist/core/hooks/subagentTypeGate.d.ts +16 -0
- package/dist/core/hooks/subagentTypeGate.js +33 -0
- package/dist/core/hooks/transcriptLogger.d.ts +14 -0
- package/dist/core/hooks/transcriptLogger.js +66 -0
- package/dist/core/interaction.d.ts +29 -0
- package/dist/core/interaction.js +7 -0
- package/dist/core/knowledge.d.ts +15 -0
- package/dist/core/knowledge.js +54 -0
- package/dist/core/mcpPermissions.d.ts +23 -0
- package/dist/core/mcpPermissions.js +47 -0
- package/dist/core/promptTemplate.d.ts +6 -0
- package/dist/core/promptTemplate.js +20 -0
- package/dist/core/runner.d.ts +137 -0
- package/dist/core/runner.js +115 -0
- package/dist/core/session.d.ts +63 -0
- package/dist/core/session.js +222 -0
- package/dist/core/toolLabels.d.ts +23 -0
- package/dist/core/toolLabels.js +114 -0
- package/dist/core/tools/humanApproval.d.ts +14 -0
- package/dist/core/tools/humanApproval.js +37 -0
- package/dist/core/tools/manualLogin.d.ts +16 -0
- package/dist/core/tools/manualLogin.js +27 -0
- package/dist/core/tools/saveToSources.d.ts +10 -0
- package/dist/core/tools/saveToSources.js +105 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +32 -0
- package/dist/tui/chatTui.d.ts +105 -0
- package/dist/tui/chatTui.js +265 -0
- package/dist/tui/claudeAuth.d.ts +19 -0
- package/dist/tui/claudeAuth.js +96 -0
- package/dist/tui/consoleRenderer.d.ts +32 -0
- package/dist/tui/consoleRenderer.js +72 -0
- package/dist/tui/ink/PromptInput.d.ts +40 -0
- package/dist/tui/ink/PromptInput.js +333 -0
- package/dist/tui/ink/SessionView.d.ts +42 -0
- package/dist/tui/ink/SessionView.js +223 -0
- package/dist/tui/ink/fileMentions.d.ts +13 -0
- package/dist/tui/ink/fileMentions.js +50 -0
- package/dist/tui/ink/fullscreen.d.ts +79 -0
- package/dist/tui/ink/fullscreen.js +175 -0
- package/dist/tui/ink/header.d.ts +17 -0
- package/dist/tui/ink/header.js +24 -0
- package/dist/tui/ink/inkInteraction.d.ts +22 -0
- package/dist/tui/ink/inkInteraction.js +64 -0
- package/dist/tui/ink/lineBuffer.d.ts +28 -0
- package/dist/tui/ink/lineBuffer.js +87 -0
- package/dist/tui/ink/markdown.d.ts +12 -0
- package/dist/tui/ink/markdown.js +148 -0
- package/dist/tui/ink/progressView.d.ts +27 -0
- package/dist/tui/ink/progressView.js +65 -0
- package/dist/tui/ink/promptEditing.d.ts +54 -0
- package/dist/tui/ink/promptEditing.js +123 -0
- package/dist/tui/ink/runChatInk.d.ts +103 -0
- package/dist/tui/ink/runChatInk.js +298 -0
- package/dist/tui/ink/sessionModel.d.ts +85 -0
- package/dist/tui/ink/sessionModel.js +262 -0
- package/dist/tui/ink/terminalCursor.d.ts +32 -0
- package/dist/tui/ink/terminalCursor.js +68 -0
- package/dist/tui/ink/terminalStatus.d.ts +20 -0
- package/dist/tui/ink/terminalStatus.js +55 -0
- package/dist/tui/ink/toolGroup.d.ts +36 -0
- package/dist/tui/ink/toolGroup.js +77 -0
- package/dist/tui/ink/wizard.d.ts +62 -0
- package/dist/tui/ink/wizard.js +167 -0
- package/dist/tui/promptErrors.d.ts +2 -0
- package/dist/tui/promptErrors.js +4 -0
- package/dist/tui/terminalInteraction.d.ts +18 -0
- package/dist/tui/terminalInteraction.js +85 -0
- package/dist/tui/ui.d.ts +21 -0
- package/dist/tui/ui.js +25 -0
- package/package.json +75 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export type WizardAnswers = Record<string, unknown>;
|
|
2
|
+
/** A value, or one computed from the answers given so far. */
|
|
3
|
+
type FromAnswers<T> = T | ((answers: WizardAnswers) => T);
|
|
4
|
+
interface BaseStep {
|
|
5
|
+
/** Key of this step's answer in the result. */
|
|
6
|
+
name: string;
|
|
7
|
+
message: FromAnswers<string>;
|
|
8
|
+
/** Skips the step (leaving no answer) when it returns false. */
|
|
9
|
+
when?: (answers: WizardAnswers) => boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface SelectStep<T = unknown> extends BaseStep {
|
|
12
|
+
type: "select";
|
|
13
|
+
choices: FromAnswers<{
|
|
14
|
+
name: string;
|
|
15
|
+
value: T;
|
|
16
|
+
}[]>;
|
|
17
|
+
/** Preselects the choice with this value. */
|
|
18
|
+
default?: FromAnswers<T>;
|
|
19
|
+
}
|
|
20
|
+
export interface InputStep extends BaseStep {
|
|
21
|
+
type: "input";
|
|
22
|
+
default?: FromAnswers<string>;
|
|
23
|
+
/** `true` to accept, or the message to show. */
|
|
24
|
+
validate?: (value: string, answers: WizardAnswers) => true | string;
|
|
25
|
+
}
|
|
26
|
+
export interface PasswordStep extends BaseStep {
|
|
27
|
+
type: "password";
|
|
28
|
+
validate?: (value: string, answers: WizardAnswers) => true | string;
|
|
29
|
+
}
|
|
30
|
+
export interface ConfirmStep extends BaseStep {
|
|
31
|
+
type: "confirm";
|
|
32
|
+
default?: FromAnswers<boolean>;
|
|
33
|
+
}
|
|
34
|
+
export type WizardStep = SelectStep | InputStep | PasswordStep | ConfirmStep;
|
|
35
|
+
export interface WizardOptions {
|
|
36
|
+
/** Printed above the first question. */
|
|
37
|
+
title?: string;
|
|
38
|
+
/** Use the plain @inquirer/prompts questions even on a TTY. */
|
|
39
|
+
plain?: boolean;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* How many choices a select shows at once, so the question, its choices and a validation
|
|
43
|
+
* line stay shorter than the terminal: Ink clears the whole screen to redraw anything
|
|
44
|
+
* taller (see SessionView.tsx's previewLines()).
|
|
45
|
+
*/
|
|
46
|
+
export declare function visibleOptions(choices: number, messageRows: number, terminalRows: number | undefined): number;
|
|
47
|
+
export declare function Wizard({ steps, title, onDone }: {
|
|
48
|
+
steps: WizardStep[];
|
|
49
|
+
title?: string;
|
|
50
|
+
onDone(answers: WizardAnswers | null): void;
|
|
51
|
+
}): import("react").JSX.Element;
|
|
52
|
+
/**
|
|
53
|
+
* Asks `steps` in order and resolves with every answer by step name. A step's message,
|
|
54
|
+
* choices or default can depend on earlier answers, and `when` skips it, so a consumer's
|
|
55
|
+
* whole interactive menu becomes a list of step definitions.
|
|
56
|
+
*
|
|
57
|
+
* Ctrl+C rejects with an error named "ExitPromptError", like @inquirer/prompts, so
|
|
58
|
+
* `isExitPromptError()` keeps working. Without a TTY (or with `plain`) it asks the same
|
|
59
|
+
* steps through @inquirer/prompts.
|
|
60
|
+
*/
|
|
61
|
+
export declare function runWizard(steps: WizardStep[], options?: WizardOptions): Promise<WizardAnswers>;
|
|
62
|
+
export {};
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { stdin, stdout } from "node:process";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { Box, render, Static, Text, useApp, useInput, useStdout } from "ink";
|
|
5
|
+
import wrapAnsi from "wrap-ansi";
|
|
6
|
+
import { liveWidth } from "./sessionModel.js";
|
|
7
|
+
import { ConfirmInput, PasswordInput, Select, TextInput } from "@inkjs/ui";
|
|
8
|
+
import * as inquirer from "@inquirer/prompts";
|
|
9
|
+
import * as ui from "../ui.js";
|
|
10
|
+
function resolve(value, answers) {
|
|
11
|
+
return typeof value === "function" ? value(answers) : value;
|
|
12
|
+
}
|
|
13
|
+
/** The error @inquirer/prompts throws on Ctrl+C, so `isExitPromptError()` covers both. */
|
|
14
|
+
function exitPromptError() {
|
|
15
|
+
const error = new Error("User force closed the prompt with Ctrl+C");
|
|
16
|
+
error.name = "ExitPromptError";
|
|
17
|
+
return error;
|
|
18
|
+
}
|
|
19
|
+
function StepInput({ step, answers, onAnswer }) {
|
|
20
|
+
const [error, setError] = useState(null);
|
|
21
|
+
const { stdout } = useStdout();
|
|
22
|
+
const width = liveWidth(stdout.columns);
|
|
23
|
+
const message = `${ui.success("?")} ${ui.heading(resolve(step.message, answers))}`;
|
|
24
|
+
function validated(value) {
|
|
25
|
+
const check = step.type === "input" || step.type === "password" ? (step.validate?.(value, answers) ?? true) : true;
|
|
26
|
+
if (check === true)
|
|
27
|
+
onAnswer(value);
|
|
28
|
+
else
|
|
29
|
+
setError(check);
|
|
30
|
+
}
|
|
31
|
+
let field;
|
|
32
|
+
switch (step.type) {
|
|
33
|
+
case "select": {
|
|
34
|
+
const choices = resolve(step.choices, answers);
|
|
35
|
+
const preselected = step.default === undefined ? -1 : choices.findIndex((c) => c.value === resolve(step.default, answers));
|
|
36
|
+
field = (_jsx(Select, { visibleOptionCount: visibleOptions(choices.length, wrapAnsi(message, width, { hard: true }).split(NEWLINE).length, stdout.rows), options: choices.map((choice, index) => ({ label: choice.name, value: String(index) })), defaultValue: preselected >= 0 ? String(preselected) : undefined, onChange: (index) => onAnswer(choices[Number(index)].value) }));
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
case "input":
|
|
40
|
+
field = _jsx(TextInput, { defaultValue: step.default === undefined ? undefined : resolve(step.default, answers), onSubmit: validated });
|
|
41
|
+
break;
|
|
42
|
+
case "password":
|
|
43
|
+
field = _jsx(PasswordInput, { onSubmit: validated });
|
|
44
|
+
break;
|
|
45
|
+
case "confirm": {
|
|
46
|
+
const yes = step.default === undefined ? true : resolve(step.default, answers);
|
|
47
|
+
field = _jsx(ConfirmInput, { defaultChoice: yes ? "confirm" : "cancel", onConfirm: () => onAnswer(true), onCancel: () => onAnswer(false) });
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return (_jsxs(Box, { flexDirection: "column", width: width, children: [_jsx(Text, { children: message }), field, error ? _jsx(Text, { children: ui.error(error) }) : null] }));
|
|
52
|
+
}
|
|
53
|
+
const NEWLINE = String.fromCharCode(10);
|
|
54
|
+
/**
|
|
55
|
+
* How many choices a select shows at once, so the question, its choices and a validation
|
|
56
|
+
* line stay shorter than the terminal: Ink clears the whole screen to redraw anything
|
|
57
|
+
* taller (see SessionView.tsx's previewLines()).
|
|
58
|
+
*/
|
|
59
|
+
export function visibleOptions(choices, messageRows, terminalRows) {
|
|
60
|
+
return Math.max(1, Math.min(choices, (terminalRows || 24) - messageRows - 3));
|
|
61
|
+
}
|
|
62
|
+
function summary(step, value, answers) {
|
|
63
|
+
if (step.type === "password")
|
|
64
|
+
return "*".repeat(String(value).length);
|
|
65
|
+
if (step.type === "confirm")
|
|
66
|
+
return value ? "Yes" : "No";
|
|
67
|
+
if (step.type === "select")
|
|
68
|
+
return resolve(step.choices, answers).find((c) => c.value === value)?.name ?? String(value);
|
|
69
|
+
return String(value);
|
|
70
|
+
}
|
|
71
|
+
export function Wizard({ steps, title, onDone }) {
|
|
72
|
+
const { exit } = useApp();
|
|
73
|
+
const [answers, setAnswers] = useState({});
|
|
74
|
+
const [done, setDone] = useState([]);
|
|
75
|
+
const [index, setIndex] = useState(() => nextIndex(steps, -1, {}));
|
|
76
|
+
useInput((input, key) => {
|
|
77
|
+
if (key.ctrl && input === "c") {
|
|
78
|
+
onDone(null);
|
|
79
|
+
exit();
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
function answer(value) {
|
|
83
|
+
const step = steps[index];
|
|
84
|
+
const next = { ...answers, [step.name]: value };
|
|
85
|
+
setAnswers(next);
|
|
86
|
+
setDone([...done, { message: resolve(step.message, answers), text: summary(step, value, answers) }]);
|
|
87
|
+
const following = nextIndex(steps, index, next);
|
|
88
|
+
setIndex(following);
|
|
89
|
+
if (following >= steps.length) {
|
|
90
|
+
onDone(next);
|
|
91
|
+
exit();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// The title and the answered steps go to <Static>: written once, never redrawn, so a long
|
|
95
|
+
// menu can't grow the live area past the terminal's height.
|
|
96
|
+
const history = [
|
|
97
|
+
...(title ? [{ id: "title", text: ui.heading(title) }] : []),
|
|
98
|
+
...done.map(({ message, text }, i) => ({ id: String(i), text: `${ui.success("✔")} ${message} ${ui.agent(text)}` })),
|
|
99
|
+
];
|
|
100
|
+
const current = steps[index];
|
|
101
|
+
return (_jsxs(_Fragment, { children: [_jsx(Static, { items: history, children: (item) => _jsx(Text, { children: item.text }, item.id) }), current ? _jsx(StepInput, { step: current, answers: answers, onAnswer: answer }, index) : null] }));
|
|
102
|
+
}
|
|
103
|
+
function nextIndex(steps, from, answers) {
|
|
104
|
+
let index = from + 1;
|
|
105
|
+
while (index < steps.length && steps[index].when && !steps[index].when(answers))
|
|
106
|
+
index++;
|
|
107
|
+
return index;
|
|
108
|
+
}
|
|
109
|
+
/** The same steps as plain @inquirer/prompts questions, for hosts without a TTY. */
|
|
110
|
+
async function runPlainWizard(steps, title) {
|
|
111
|
+
if (title)
|
|
112
|
+
console.log(ui.heading(title));
|
|
113
|
+
const answers = {};
|
|
114
|
+
for (const step of steps) {
|
|
115
|
+
if (step.when && !step.when(answers))
|
|
116
|
+
continue;
|
|
117
|
+
const message = resolve(step.message, answers);
|
|
118
|
+
switch (step.type) {
|
|
119
|
+
case "select":
|
|
120
|
+
answers[step.name] = await inquirer.select({
|
|
121
|
+
message,
|
|
122
|
+
choices: resolve(step.choices, answers),
|
|
123
|
+
default: step.default === undefined ? undefined : resolve(step.default, answers),
|
|
124
|
+
});
|
|
125
|
+
break;
|
|
126
|
+
case "input":
|
|
127
|
+
answers[step.name] = await inquirer.input({
|
|
128
|
+
message,
|
|
129
|
+
default: step.default === undefined ? undefined : resolve(step.default, answers),
|
|
130
|
+
validate: step.validate ? (value) => step.validate(value, answers) : undefined,
|
|
131
|
+
});
|
|
132
|
+
break;
|
|
133
|
+
case "password":
|
|
134
|
+
answers[step.name] = await inquirer.password({
|
|
135
|
+
message,
|
|
136
|
+
mask: "*",
|
|
137
|
+
validate: step.validate ? (value) => step.validate(value, answers) : undefined,
|
|
138
|
+
});
|
|
139
|
+
break;
|
|
140
|
+
case "confirm":
|
|
141
|
+
answers[step.name] = await inquirer.confirm({ message, default: step.default === undefined ? true : resolve(step.default, answers) });
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return answers;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Asks `steps` in order and resolves with every answer by step name. A step's message,
|
|
149
|
+
* choices or default can depend on earlier answers, and `when` skips it, so a consumer's
|
|
150
|
+
* whole interactive menu becomes a list of step definitions.
|
|
151
|
+
*
|
|
152
|
+
* Ctrl+C rejects with an error named "ExitPromptError", like @inquirer/prompts, so
|
|
153
|
+
* `isExitPromptError()` keeps working. Without a TTY (or with `plain`) it asks the same
|
|
154
|
+
* steps through @inquirer/prompts.
|
|
155
|
+
*/
|
|
156
|
+
export async function runWizard(steps, options = {}) {
|
|
157
|
+
if (options.plain || !stdin.isTTY || !stdout.isTTY)
|
|
158
|
+
return await runPlainWizard(steps, options.title);
|
|
159
|
+
if (nextIndex(steps, -1, {}) >= steps.length)
|
|
160
|
+
return {};
|
|
161
|
+
let result = null;
|
|
162
|
+
const app = render(_jsx(Wizard, { steps: steps, title: options.title, onDone: (answers) => (result = answers) }), { exitOnCtrlC: false });
|
|
163
|
+
await app.waitUntilExit();
|
|
164
|
+
if (result === null)
|
|
165
|
+
throw exitPromptError();
|
|
166
|
+
return result;
|
|
167
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import readline from "node:readline/promises";
|
|
2
|
+
import type { InteractionPort } from "../core/interaction.js";
|
|
3
|
+
export declare function setSharedReadline(rl: readline.Interface | null): void;
|
|
4
|
+
export declare function getSharedReadline(): readline.Interface | null;
|
|
5
|
+
/**
|
|
6
|
+
* Asks `question` on the shared interface, flagging it as in progress meanwhile — so the
|
|
7
|
+
* REPL that owns the interface can tell a human-in-the-loop checkpoint waiting on the
|
|
8
|
+
* keyboard apart from a turn simply streaming (e.g. Esc must not interrupt the turn while
|
|
9
|
+
* an approval question is on screen).
|
|
10
|
+
*/
|
|
11
|
+
export declare function askOnSharedReadline(rl: readline.Interface, question: string): Promise<string>;
|
|
12
|
+
export declare function isSharedQuestionActive(): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* The default `InteractionPort`, installed by the package entry point: prints the
|
|
15
|
+
* checkpoint and reads the answer on the terminal, on the chat's own readline when one is
|
|
16
|
+
* registered via `setSharedReadline()`. Without a TTY it prints nothing and never answers.
|
|
17
|
+
*/
|
|
18
|
+
export declare const terminalInteractionPort: InteractionPort;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import readline from "node:readline/promises";
|
|
2
|
+
import { stdin, stdout } from "node:process";
|
|
3
|
+
const DEFAULT_QUESTION = "Allow this to continue? [Y/n/q] ";
|
|
4
|
+
/**
|
|
5
|
+
* A long-lived chat REPL's readline interface (if any), so that the terminal port reuses
|
|
6
|
+
* it instead of creating its own.
|
|
7
|
+
*
|
|
8
|
+
* Closing a readline.Interface desyncs stdin's raw mode for *any* other interface still
|
|
9
|
+
* alive on the same stream — Interface.close() calls input.setRawMode(false) directly on
|
|
10
|
+
* the shared stream, not something private to that instance (confirmed by reading
|
|
11
|
+
* node:readline/promises). If the port creates and closes its own interface while a chat
|
|
12
|
+
* REPL's own is still open waiting at its prompt, that second interface stops receiving
|
|
13
|
+
* keystroke events — the prompt sits there accepting no input. By registering the REPL's
|
|
14
|
+
* interface here, the port reuses it (creating or closing nothing) whenever it's available.
|
|
15
|
+
*/
|
|
16
|
+
let sharedReadline = null;
|
|
17
|
+
export function setSharedReadline(rl) {
|
|
18
|
+
sharedReadline = rl;
|
|
19
|
+
}
|
|
20
|
+
export function getSharedReadline() {
|
|
21
|
+
return sharedReadline;
|
|
22
|
+
}
|
|
23
|
+
let activeQuestions = 0;
|
|
24
|
+
/**
|
|
25
|
+
* Asks `question` on the shared interface, flagging it as in progress meanwhile — so the
|
|
26
|
+
* REPL that owns the interface can tell a human-in-the-loop checkpoint waiting on the
|
|
27
|
+
* keyboard apart from a turn simply streaming (e.g. Esc must not interrupt the turn while
|
|
28
|
+
* an approval question is on screen).
|
|
29
|
+
*/
|
|
30
|
+
export async function askOnSharedReadline(rl, question) {
|
|
31
|
+
activeQuestions++;
|
|
32
|
+
try {
|
|
33
|
+
return await rl.question(question);
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
activeQuestions--;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export function isSharedQuestionActive() {
|
|
40
|
+
return activeQuestions > 0;
|
|
41
|
+
}
|
|
42
|
+
function never() {
|
|
43
|
+
return new Promise(() => { });
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Whether the keyboard channel (and any console output at all) is even attempted is
|
|
47
|
+
* decided from `stdin.isTTY` — confirmed empirically that a non-TTY `rl.question()` still
|
|
48
|
+
* writes its query text to `stdout` even though it can never resolve from real keystrokes,
|
|
49
|
+
* so skipping the whole readline.Interface (not just not reading from it) is what actually
|
|
50
|
+
* keeps a non-interactive host's stdout silent: it depends entirely on the response file.
|
|
51
|
+
*/
|
|
52
|
+
async function askOnTerminal(prompt, signal) {
|
|
53
|
+
if (!stdin.isTTY)
|
|
54
|
+
return await never();
|
|
55
|
+
console.log(`\n=== ${prompt.title} ===`);
|
|
56
|
+
for (const line of prompt.lines)
|
|
57
|
+
console.log(line);
|
|
58
|
+
const question = prompt.question ?? DEFAULT_QUESTION;
|
|
59
|
+
const shared = sharedReadline;
|
|
60
|
+
if (shared)
|
|
61
|
+
return await askOnSharedReadline(shared, question);
|
|
62
|
+
const rl = readline.createInterface({ input: stdin, output: stdout });
|
|
63
|
+
const close = () => rl.close();
|
|
64
|
+
signal.addEventListener("abort", close, { once: true });
|
|
65
|
+
try {
|
|
66
|
+
return await rl.question(question);
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
signal.removeEventListener("abort", close);
|
|
70
|
+
rl.close();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The default `InteractionPort`, installed by the package entry point: prints the
|
|
75
|
+
* checkpoint and reads the answer on the terminal, on the chat's own readline when one is
|
|
76
|
+
* registered via `setSharedReadline()`. Without a TTY it prints nothing and never answers.
|
|
77
|
+
*/
|
|
78
|
+
export const terminalInteractionPort = {
|
|
79
|
+
askDecision: askOnTerminal,
|
|
80
|
+
askManualIntervention: askOnTerminal,
|
|
81
|
+
notify(message) {
|
|
82
|
+
if (stdin.isTTY)
|
|
83
|
+
console.log(message);
|
|
84
|
+
},
|
|
85
|
+
};
|
package/dist/tui/ui.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Color palette for a consistent console look across whatever CLI is built on this kit. */
|
|
2
|
+
export declare const agent: (s: string) => string;
|
|
3
|
+
export declare const user: (s: string) => string;
|
|
4
|
+
export declare const action: (s: string) => string;
|
|
5
|
+
export declare const heading: (s: string) => string;
|
|
6
|
+
export declare const success: (s: string) => string;
|
|
7
|
+
export declare const warn: (s: string) => string;
|
|
8
|
+
export declare const error: (s: string) => string;
|
|
9
|
+
export declare const dim: (s: string) => string;
|
|
10
|
+
/** The orange accent: spinner, turn summary, mode. */
|
|
11
|
+
export declare const accent: (s: string) => string;
|
|
12
|
+
/** The spinner and what the agent is doing. */
|
|
13
|
+
export declare const working: (s: string) => string;
|
|
14
|
+
/** Inline code and code blocks. */
|
|
15
|
+
export declare const code: (s: string) => string;
|
|
16
|
+
/** The background of the human's lines. */
|
|
17
|
+
export declare const userBar: (s: string) => string;
|
|
18
|
+
export declare const bold: (s: string) => string;
|
|
19
|
+
export declare const italic: (s: string) => string;
|
|
20
|
+
export declare const strike: (s: string) => string;
|
|
21
|
+
export declare const toolBullet: (s: string) => string;
|
package/dist/tui/ui.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import pc from "picocolors";
|
|
2
|
+
/** Color palette for a consistent console look across whatever CLI is built on this kit. */
|
|
3
|
+
export const agent = (s) => pc.cyanBright(s);
|
|
4
|
+
export const user = (s) => pc.white(s);
|
|
5
|
+
export const action = (s) => pc.magenta(s);
|
|
6
|
+
export const heading = (s) => pc.bold(s);
|
|
7
|
+
export const success = (s) => pc.green(s);
|
|
8
|
+
export const warn = (s) => pc.yellow(s);
|
|
9
|
+
export const error = (s) => pc.red(s);
|
|
10
|
+
export const dim = (s) => pc.dim(s);
|
|
11
|
+
// The Claude Code-like look of the Ink views (see the feature ink-claude-style), in 24-bit
|
|
12
|
+
// color when the terminal supports color at all, like the rest of this palette.
|
|
13
|
+
const rgb = (r, g, b) => (s) => pc.isColorSupported ? `\x1b[38;2;${r};${g};${b}m${s}\x1b[39m` : s;
|
|
14
|
+
/** The orange accent: spinner, turn summary, mode. */
|
|
15
|
+
export const accent = rgb(215, 119, 87);
|
|
16
|
+
/** The spinner and what the agent is doing. */
|
|
17
|
+
export const working = rgb(137, 180, 250);
|
|
18
|
+
/** Inline code and code blocks. */
|
|
19
|
+
export const code = rgb(177, 185, 249);
|
|
20
|
+
/** The background of the human's lines. */
|
|
21
|
+
export const userBar = (s) => (pc.isColorSupported ? `\x1b[48;2;55;55;55m${s}\x1b[49m` : s);
|
|
22
|
+
export const bold = (s) => pc.bold(s);
|
|
23
|
+
export const italic = (s) => pc.italic(s);
|
|
24
|
+
export const strike = (s) => pc.strikethrough(s);
|
|
25
|
+
export const toolBullet = (s) => pc.green(s);
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@falkenslab/agent-kit",
|
|
3
|
+
"version": "0.10.1",
|
|
4
|
+
"description": "Library for building an agent on top of the Claude Agent SDK",
|
|
5
|
+
"author": "Francisco Vargas Ruiz",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/falkenslab/agent-kit.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/falkenslab/agent-kit#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/falkenslab/agent-kit/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"claude",
|
|
17
|
+
"claude-agent-sdk",
|
|
18
|
+
"ai-agent",
|
|
19
|
+
"agent",
|
|
20
|
+
"anthropic",
|
|
21
|
+
"ink",
|
|
22
|
+
"tui",
|
|
23
|
+
"cli",
|
|
24
|
+
"human-in-the-loop",
|
|
25
|
+
"mcp"
|
|
26
|
+
],
|
|
27
|
+
"type": "module",
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=20"
|
|
30
|
+
},
|
|
31
|
+
"main": "./dist/index.js",
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"files": [
|
|
34
|
+
"dist",
|
|
35
|
+
"assets"
|
|
36
|
+
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsc",
|
|
39
|
+
"typecheck": "tsc --noEmit",
|
|
40
|
+
"test": "tsx --test \"test/**/*.test.ts\" \"test/**/*.test.tsx\"",
|
|
41
|
+
"lint": "eslint .",
|
|
42
|
+
"prepare": "npm run build",
|
|
43
|
+
"prepublishOnly": "npm run typecheck && npm run lint && npm test && npm run build"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@anthropic-ai/claude-agent-sdk": "^0.3.234",
|
|
47
|
+
"@inkjs/ui": "^2.0.0",
|
|
48
|
+
"@inquirer/prompts": "^8.5.2",
|
|
49
|
+
"@types/react": "^19.3.0",
|
|
50
|
+
"ink": "^6.8.0",
|
|
51
|
+
"marked": "^18.0.14",
|
|
52
|
+
"picocolors": "^1.1.1",
|
|
53
|
+
"react": "^19.3.0",
|
|
54
|
+
"slice-ansi": "^8.0.0",
|
|
55
|
+
"string-width": "^8.3.0",
|
|
56
|
+
"wrap-ansi": "^9.0.2",
|
|
57
|
+
"zod": "^4.4.3"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@eslint/js": "^10.0.1",
|
|
61
|
+
"@types/node": "^24.0.0",
|
|
62
|
+
"eslint": "^10.9.1",
|
|
63
|
+
"globals": "^17.11.0",
|
|
64
|
+
"ink-testing-library": "^4.0.0",
|
|
65
|
+
"tsx": "^4.19.0",
|
|
66
|
+
"typescript": "^5.7.0",
|
|
67
|
+
"typescript-eslint": "^8.68.0"
|
|
68
|
+
},
|
|
69
|
+
"allowScripts": {
|
|
70
|
+
"esbuild@0.28.2": true
|
|
71
|
+
},
|
|
72
|
+
"publishConfig": {
|
|
73
|
+
"access": "public"
|
|
74
|
+
}
|
|
75
|
+
}
|