@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.
Files changed (95) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +97 -0
  3. package/assets/knowledge-plugin/.claude-plugin/plugin.json +4 -0
  4. package/assets/knowledge-plugin/commands/ingest.md +7 -0
  5. package/assets/knowledge-plugin/commands/lint.md +5 -0
  6. package/assets/knowledge-plugin/commands/query.md +5 -0
  7. package/assets/knowledge-plugin/skills/knowledge-ingest/SKILL.md +42 -0
  8. package/assets/knowledge-plugin/skills/knowledge-lint/SKILL.md +26 -0
  9. package/assets/knowledge-plugin/skills/knowledge-pages/SKILL.md +137 -0
  10. package/assets/knowledge-plugin/skills/knowledge-query/SKILL.md +14 -0
  11. package/dist/core/agentSpec.d.ts +132 -0
  12. package/dist/core/agentSpec.js +1 -0
  13. package/dist/core/claudeAuth.d.ts +24 -0
  14. package/dist/core/claudeAuth.js +27 -0
  15. package/dist/core/hooks/fileScopeGate.d.ts +33 -0
  16. package/dist/core/hooks/fileScopeGate.js +68 -0
  17. package/dist/core/hooks/humanInput.d.ts +15 -0
  18. package/dist/core/hooks/humanInput.js +52 -0
  19. package/dist/core/hooks/stepGate.d.ts +11 -0
  20. package/dist/core/hooks/stepGate.js +47 -0
  21. package/dist/core/hooks/subagentBashGate.d.ts +28 -0
  22. package/dist/core/hooks/subagentBashGate.js +40 -0
  23. package/dist/core/hooks/subagentForegroundGate.d.ts +18 -0
  24. package/dist/core/hooks/subagentForegroundGate.js +34 -0
  25. package/dist/core/hooks/subagentTypeGate.d.ts +16 -0
  26. package/dist/core/hooks/subagentTypeGate.js +33 -0
  27. package/dist/core/hooks/transcriptLogger.d.ts +14 -0
  28. package/dist/core/hooks/transcriptLogger.js +66 -0
  29. package/dist/core/interaction.d.ts +29 -0
  30. package/dist/core/interaction.js +7 -0
  31. package/dist/core/knowledge.d.ts +15 -0
  32. package/dist/core/knowledge.js +54 -0
  33. package/dist/core/mcpPermissions.d.ts +23 -0
  34. package/dist/core/mcpPermissions.js +47 -0
  35. package/dist/core/promptTemplate.d.ts +6 -0
  36. package/dist/core/promptTemplate.js +20 -0
  37. package/dist/core/runner.d.ts +137 -0
  38. package/dist/core/runner.js +115 -0
  39. package/dist/core/session.d.ts +63 -0
  40. package/dist/core/session.js +222 -0
  41. package/dist/core/toolLabels.d.ts +23 -0
  42. package/dist/core/toolLabels.js +114 -0
  43. package/dist/core/tools/humanApproval.d.ts +14 -0
  44. package/dist/core/tools/humanApproval.js +37 -0
  45. package/dist/core/tools/manualLogin.d.ts +16 -0
  46. package/dist/core/tools/manualLogin.js +27 -0
  47. package/dist/core/tools/saveToSources.d.ts +10 -0
  48. package/dist/core/tools/saveToSources.js +105 -0
  49. package/dist/index.d.ts +31 -0
  50. package/dist/index.js +32 -0
  51. package/dist/tui/chatTui.d.ts +105 -0
  52. package/dist/tui/chatTui.js +265 -0
  53. package/dist/tui/claudeAuth.d.ts +19 -0
  54. package/dist/tui/claudeAuth.js +96 -0
  55. package/dist/tui/consoleRenderer.d.ts +32 -0
  56. package/dist/tui/consoleRenderer.js +72 -0
  57. package/dist/tui/ink/PromptInput.d.ts +40 -0
  58. package/dist/tui/ink/PromptInput.js +333 -0
  59. package/dist/tui/ink/SessionView.d.ts +42 -0
  60. package/dist/tui/ink/SessionView.js +223 -0
  61. package/dist/tui/ink/fileMentions.d.ts +13 -0
  62. package/dist/tui/ink/fileMentions.js +50 -0
  63. package/dist/tui/ink/fullscreen.d.ts +79 -0
  64. package/dist/tui/ink/fullscreen.js +175 -0
  65. package/dist/tui/ink/header.d.ts +17 -0
  66. package/dist/tui/ink/header.js +24 -0
  67. package/dist/tui/ink/inkInteraction.d.ts +22 -0
  68. package/dist/tui/ink/inkInteraction.js +64 -0
  69. package/dist/tui/ink/lineBuffer.d.ts +28 -0
  70. package/dist/tui/ink/lineBuffer.js +87 -0
  71. package/dist/tui/ink/markdown.d.ts +12 -0
  72. package/dist/tui/ink/markdown.js +148 -0
  73. package/dist/tui/ink/progressView.d.ts +27 -0
  74. package/dist/tui/ink/progressView.js +65 -0
  75. package/dist/tui/ink/promptEditing.d.ts +54 -0
  76. package/dist/tui/ink/promptEditing.js +123 -0
  77. package/dist/tui/ink/runChatInk.d.ts +103 -0
  78. package/dist/tui/ink/runChatInk.js +298 -0
  79. package/dist/tui/ink/sessionModel.d.ts +85 -0
  80. package/dist/tui/ink/sessionModel.js +262 -0
  81. package/dist/tui/ink/terminalCursor.d.ts +32 -0
  82. package/dist/tui/ink/terminalCursor.js +68 -0
  83. package/dist/tui/ink/terminalStatus.d.ts +20 -0
  84. package/dist/tui/ink/terminalStatus.js +55 -0
  85. package/dist/tui/ink/toolGroup.d.ts +36 -0
  86. package/dist/tui/ink/toolGroup.js +77 -0
  87. package/dist/tui/ink/wizard.d.ts +62 -0
  88. package/dist/tui/ink/wizard.js +167 -0
  89. package/dist/tui/promptErrors.d.ts +2 -0
  90. package/dist/tui/promptErrors.js +4 -0
  91. package/dist/tui/terminalInteraction.d.ts +18 -0
  92. package/dist/tui/terminalInteraction.js +85 -0
  93. package/dist/tui/ui.d.ts +21 -0
  94. package/dist/tui/ui.js +25 -0
  95. package/package.json +75 -0
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Turns a raw tool name + input into a short, readable console description, instead of
3
+ * the tool's technical name (e.g. "mcp__playwright__browser_click"). Covers the tools
4
+ * this kit itself provides (Read/Write/Edit/Glob/Grep/Bash/Agent/WebFetch/WebSearch/Skill,
5
+ * plus its own human-approval/manual-intervention/save-to-sources MCP tools)
6
+ * generically; a
7
+ * concrete agent supplies a `describe` callback for its own domain-specific tools (e.g.
8
+ * Playwright's browser_* cases) via `createFriendlyToolLabel()`.
9
+ */
10
+ export function truncate(text, max = 60) {
11
+ return text.length > max ? `${text.slice(0, max)}…` : text;
12
+ }
13
+ // Unlike truncate() above (which cuts the tail off long freeform text), a path's most
14
+ // useful part — the filename — is at the *end*, so this keeps the tail and cuts the
15
+ // front instead.
16
+ export function truncatePath(text, max = 60) {
17
+ return text.length > max ? `…${text.slice(text.length - max + 1)}` : text;
18
+ }
19
+ /** Returns undefined for anything it doesn't specifically recognize — the caller decides the fallback. */
20
+ function describeCore(shortName, input) {
21
+ switch (shortName) {
22
+ case "request_human_approval": {
23
+ const summary = typeof input.summary === "string" ? input.summary : "";
24
+ return `Asking for human approval: ${truncate(summary, 120)}`;
25
+ }
26
+ case "request_manual_login":
27
+ return "Waiting for a human to intervene manually";
28
+ case "save_to_sources": {
29
+ const destination = typeof input.destination === "string" ? input.destination : "sources/";
30
+ return `Saving a file to sources/${truncatePath(destination, 70)}`;
31
+ }
32
+ case "Read": {
33
+ const file = typeof input.file_path === "string" ? input.file_path : "a file";
34
+ return `Reading ${truncatePath(file, 80)}`;
35
+ }
36
+ case "Write": {
37
+ const file = typeof input.file_path === "string" ? input.file_path : "a file";
38
+ return `Writing to ${truncatePath(file, 80)}`;
39
+ }
40
+ case "Edit": {
41
+ const file = typeof input.file_path === "string" ? input.file_path : "a file";
42
+ return `Editing ${truncatePath(file, 80)}`;
43
+ }
44
+ case "Glob": {
45
+ const pattern = typeof input.pattern === "string" ? input.pattern : "*";
46
+ return `Searching for files matching "${truncate(pattern, 60)}"`;
47
+ }
48
+ case "Grep": {
49
+ const pattern = typeof input.pattern === "string" ? input.pattern : "";
50
+ return `Searching file contents for "${truncate(pattern, 60)}"`;
51
+ }
52
+ case "Bash": {
53
+ const command = typeof input.command === "string" ? input.command : "";
54
+ return `Running "${truncate(command, 80)}"`;
55
+ }
56
+ // The SDK's built-in subagent-delegation tool. Confirmed empirically (real
57
+ // transcript) its input is { description, prompt, subagent_type }, not
58
+ // { subagent_type, task } as its own tool description might suggest.
59
+ case "Agent": {
60
+ const subagentType = typeof input.subagent_type === "string" ? input.subagent_type : "a subagent";
61
+ const description = typeof input.description === "string" ? input.description : "";
62
+ return description
63
+ ? `Delegating to "${subagentType}": ${truncate(description, 80)}`
64
+ : `Delegating to "${subagentType}"`;
65
+ }
66
+ case "WebFetch": {
67
+ const url = typeof input.url === "string" ? input.url : "a page";
68
+ return `Fetching ${truncate(url, 80)}`;
69
+ }
70
+ case "WebSearch": {
71
+ const query = typeof input.query === "string" ? input.query : "";
72
+ return `Searching the web for "${truncate(query, 80)}"`;
73
+ }
74
+ // The SDK's own tool for invoking a skill (see session.ts for why it's listed
75
+ // explicitly in `tools`). Confirmed empirically: its input is `{ skill: "name" }`.
76
+ case "Skill": {
77
+ const skill = typeof input.skill === "string" ? input.skill : "a skill";
78
+ return `Applying the "${skill}" skill`;
79
+ }
80
+ default:
81
+ return undefined;
82
+ }
83
+ }
84
+ /**
85
+ * Builds a `friendlyToolLabel(toolName, toolInput)` — `describe` lets the host agent
86
+ * layer its own domain-specific cases (e.g. Playwright's browser_* tools) on top of this
87
+ * kit's generic ones; `extraLocalServers` names any *additional* MCP server (beyond this
88
+ * kit's own "approvals"/"manualLogin"/"sourceFiles") whose tools should be unwrapped
89
+ * without a "[server] " prefix, e.g. "playwright".
90
+ */
91
+ export function createFriendlyToolLabel(options = {}) {
92
+ const localServers = new Set(["approvals", "manualLogin", "sourceFiles", ...(options.extraLocalServers ?? [])]);
93
+ const describe = (shortName, input) => options.describe?.(shortName, input) ?? describeCore(shortName, input) ?? shortName.replace(/_/g, " ");
94
+ return (toolName, toolInput) => {
95
+ const input = toolInput && typeof toolInput === "object" ? toolInput : {};
96
+ if (!toolName.startsWith("mcp__"))
97
+ return describe(toolName, input);
98
+ // MCP tool names are "mcp__<server>__<tool>" — split on the first literal "__" after
99
+ // the prefix rather than on every single "_", since both the server name and the
100
+ // tool name can themselves contain single underscores (e.g. "browser_click").
101
+ const rest = toolName.slice("mcp__".length);
102
+ const separatorIndex = rest.indexOf("__");
103
+ if (separatorIndex === -1)
104
+ return describe(rest, input);
105
+ const serverName = rest.slice(0, separatorIndex);
106
+ const shortName = rest.slice(separatorIndex + 2);
107
+ if (localServers.has(serverName))
108
+ return describe(shortName, input);
109
+ // describe() never has a specific case for a custom server's tools, so this is
110
+ // always its default-case cleanup — prefix it with the server name so the console
111
+ // still says where it came from, e.g. "[podman] container list".
112
+ return `[${serverName}] ${describe(shortName, input)}`;
113
+ };
114
+ }
@@ -0,0 +1,14 @@
1
+ export interface HumanApprovalTexts {
2
+ description: string;
3
+ approved: string;
4
+ rejected: string;
5
+ }
6
+ /** Domain-neutral defaults — a concrete agent should normally override these via `AgentSpec.humanApprovalTexts` with wording specific to what "publishing" means in its domain. */
7
+ export declare const DEFAULT_HUMAN_APPROVAL_TEXTS: HumanApprovalTexts;
8
+ /**
9
+ * Checkpoint tool for "guided" mode: the agent explores freely, but
10
+ * must call this tool and wait for human approval right before any action that publishes
11
+ * something visible to others and that's hard to naturally undo. Everything else —
12
+ * browsing and reading — doesn't go through this.
13
+ */
14
+ export declare function createHumanApprovalServer(runDir: string, texts?: HumanApprovalTexts): import("@anthropic-ai/claude-agent-sdk").McpSdkServerConfigWithInstance;
@@ -0,0 +1,37 @@
1
+ import { tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
2
+ import { z } from "zod";
3
+ import { askForDecision } from "../hooks/humanInput.js";
4
+ const APPROVED_ANSWERS = new Set(["", "y", "yes"]);
5
+ /** Domain-neutral defaults — a concrete agent should normally override these via `AgentSpec.humanApprovalTexts` with wording specific to what "publishing" means in its domain. */
6
+ export const DEFAULT_HUMAN_APPROVAL_TEXTS = {
7
+ description: "Ask for human confirmation right before an action that publishes something visible " +
8
+ "to others and is hard to naturally undo. Don't use it for anything else: not for " +
9
+ "browsing, not for reading, not before every intermediate step.",
10
+ approved: "Approved by the human. You may continue.",
11
+ rejected: "Rejected by the human. Don't proceed with that; ask what to do differently.",
12
+ };
13
+ /**
14
+ * Checkpoint tool for "guided" mode: the agent explores freely, but
15
+ * must call this tool and wait for human approval right before any action that publishes
16
+ * something visible to others and that's hard to naturally undo. Everything else —
17
+ * browsing and reading — doesn't go through this.
18
+ */
19
+ export function createHumanApprovalServer(runDir, texts = DEFAULT_HUMAN_APPROVAL_TEXTS) {
20
+ const requestHumanApproval = tool("request_human_approval", texts.description, {
21
+ summary: z.string().describe("Brief summary of what you're about to submit or publish"),
22
+ }, async (args) => {
23
+ const answer = await askForDecision(runDir, {
24
+ title: "Human confirmation required before publishing",
25
+ lines: [`Summary: ${args.summary}`],
26
+ });
27
+ const approved = APPROVED_ANSWERS.has(answer);
28
+ return {
29
+ content: [{ type: "text", text: approved ? texts.approved : texts.rejected }],
30
+ };
31
+ });
32
+ return createSdkMcpServer({
33
+ name: "approvals",
34
+ version: "1.0.0",
35
+ tools: [requestHumanApproval],
36
+ });
37
+ }
@@ -0,0 +1,16 @@
1
+ export interface ManualInterventionTexts {
2
+ toolDescription: string;
3
+ confirmedMessage: string;
4
+ checkpointTitle: string;
5
+ checkpointLines: string[];
6
+ checkpointQuestion?: string;
7
+ }
8
+ /**
9
+ * Checkpoint tool for when the agent hits something it can't do itself (typically a login
10
+ * it has no credentials for, or one that fails unexpectedly). Pauses execution until a
11
+ * human confirms they've intervened by hand in whatever live interface the agent is driving
12
+ * (a browser window, say), and then lets the agent continue. There is deliberately no
13
+ * default wording: what counts as a manual intervention is entirely the domain's, so the
14
+ * concrete agent always supplies `texts` (see `AgentSpec.manualInterventionTexts`).
15
+ */
16
+ export declare function createManualLoginServer(runDir: string, texts: ManualInterventionTexts): import("@anthropic-ai/claude-agent-sdk").McpSdkServerConfigWithInstance;
@@ -0,0 +1,27 @@
1
+ import { tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
2
+ import { askForManualIntervention } from "../hooks/humanInput.js";
3
+ /**
4
+ * Checkpoint tool for when the agent hits something it can't do itself (typically a login
5
+ * it has no credentials for, or one that fails unexpectedly). Pauses execution until a
6
+ * human confirms they've intervened by hand in whatever live interface the agent is driving
7
+ * (a browser window, say), and then lets the agent continue. There is deliberately no
8
+ * default wording: what counts as a manual intervention is entirely the domain's, so the
9
+ * concrete agent always supplies `texts` (see `AgentSpec.manualInterventionTexts`).
10
+ */
11
+ export function createManualLoginServer(runDir, texts) {
12
+ const requestManualLogin = tool("request_manual_login", texts.toolDescription, {}, async () => {
13
+ await askForManualIntervention(runDir, {
14
+ title: texts.checkpointTitle,
15
+ lines: texts.checkpointLines,
16
+ question: texts.checkpointQuestion,
17
+ });
18
+ return {
19
+ content: [{ type: "text", text: texts.confirmedMessage }],
20
+ };
21
+ });
22
+ return createSdkMcpServer({
23
+ name: "manualLogin",
24
+ version: "1.0.0",
25
+ tools: [requestManualLogin],
26
+ });
27
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * `save_to_sources`: the only way the agent adds to `sourcesDir` (Write/Edit are scoped
3
+ * to the notes folder, see hooks/fileScopeGate.ts), so the originals stay as obtained.
4
+ * Deliberately just an fs.copyFile with COPYFILE_EXCL — no parsing, no format conversion,
5
+ * no overwriting. A document downloaded via a browser-automation tool typically lands in
6
+ * this run's own folder, which the file tools can't reach; this tool's only job is getting
7
+ * it into `sourcesDir`, where the SDK's own Read tool already knows how to interpret it
8
+ * (PDF/DOCX text extraction, multimodal images).
9
+ */
10
+ export declare function createSaveToSourcesServer(runDir: string, sourcesDir: string, description?: string): import("@anthropic-ai/claude-agent-sdk").McpSdkServerConfigWithInstance;
@@ -0,0 +1,105 @@
1
+ import path from "node:path";
2
+ import { constants, copyFile, mkdir, readdir, stat } from "node:fs/promises";
3
+ import { tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
4
+ import { z } from "zod";
5
+ /** Resolves `candidate` against `base`; returns the absolute path only if it stays inside it. */
6
+ function resolveWithin(base, candidate) {
7
+ const resolved = path.isAbsolute(candidate) ? path.resolve(candidate) : path.resolve(base, candidate);
8
+ const relative = path.relative(base, resolved);
9
+ return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative)) ? resolved : undefined;
10
+ }
11
+ /**
12
+ * `resolvedPath` as given if it exists; otherwise, a same-directory sibling whose
13
+ * filename is Unicode-equal under NFC normalization. Needed because a file downloaded via
14
+ * a real browser navigation can land on disk with an accented filename in NFD
15
+ * (decomposed) form, while the model's own `source` argument, being ordinary typed/read
16
+ * text, is normally NFC (composed). The two look character-for-character identical but
17
+ * differ byte-for-byte, so a literal `copyFile` throws ENOENT even though the file is
18
+ * right there under a name that reads identically.
19
+ */
20
+ async function resolveExistingFileToleratingNormalization(resolvedPath) {
21
+ try {
22
+ await stat(resolvedPath);
23
+ return resolvedPath;
24
+ }
25
+ catch {
26
+ // fall through to the normalization-tolerant lookup below
27
+ }
28
+ const dir = path.dirname(resolvedPath);
29
+ const wantName = path.basename(resolvedPath).normalize("NFC");
30
+ let entries;
31
+ try {
32
+ entries = await readdir(dir);
33
+ }
34
+ catch {
35
+ return undefined;
36
+ }
37
+ const match = entries.find((name) => name.normalize("NFC") === wantName);
38
+ return match ? path.join(dir, match) : undefined;
39
+ }
40
+ const DEFAULT_DESCRIPTION = "Copy a file from this run's own folder (e.g. something just downloaded) into a folder " +
41
+ "under sources/, where original files are kept as they were obtained, apart from your " +
42
+ "own notes. It becomes readable there with the SDK's own Read tool, which already " +
43
+ "extracts text from PDF/DOCX and interprets images directly. This tool does no parsing " +
44
+ "or conversion itself, it only relocates the file, and it never overwrites a file " +
45
+ "that's already in sources/. Use it right after downloading a document you're about to " +
46
+ "study in depth, so it's still there for later sessions instead of only existing for " +
47
+ "this run.";
48
+ /**
49
+ * `save_to_sources`: the only way the agent adds to `sourcesDir` (Write/Edit are scoped
50
+ * to the notes folder, see hooks/fileScopeGate.ts), so the originals stay as obtained.
51
+ * Deliberately just an fs.copyFile with COPYFILE_EXCL — no parsing, no format conversion,
52
+ * no overwriting. A document downloaded via a browser-automation tool typically lands in
53
+ * this run's own folder, which the file tools can't reach; this tool's only job is getting
54
+ * it into `sourcesDir`, where the SDK's own Read tool already knows how to interpret it
55
+ * (PDF/DOCX text extraction, multimodal images).
56
+ */
57
+ export function createSaveToSourcesServer(runDir, sourcesDir, description = DEFAULT_DESCRIPTION) {
58
+ const saveToSources = tool("save_to_sources", description, {
59
+ source: z.string().describe("Path to the file, relative to this run's own folder - not a URL"),
60
+ destination: z.string().describe('Path to save it under, relative to sources/, e.g. "topic-3/slides.pdf"'),
61
+ }, async (args) => {
62
+ const candidate = resolveWithin(runDir, args.source);
63
+ if (!candidate) {
64
+ return {
65
+ content: [{ type: "text", text: `"${args.source}" isn't inside this run's folder - refusing to read it.` }],
66
+ isError: true,
67
+ };
68
+ }
69
+ const resolvedSource = await resolveExistingFileToleratingNormalization(candidate);
70
+ if (!resolvedSource) {
71
+ return {
72
+ content: [{ type: "text", text: `Couldn't find "${args.source}" in this run's folder (checked byte-for-byte and Unicode-normalized names) - double check the exact path, e.g. with Glob.` }],
73
+ isError: true,
74
+ };
75
+ }
76
+ const resolvedDestination = resolveWithin(sourcesDir, args.destination);
77
+ if (!resolvedDestination) {
78
+ return {
79
+ content: [{ type: "text", text: `"${args.destination}" isn't inside sources/ - refusing to write there.` }],
80
+ isError: true,
81
+ };
82
+ }
83
+ try {
84
+ await mkdir(path.dirname(resolvedDestination), { recursive: true });
85
+ await copyFile(resolvedSource, resolvedDestination, constants.COPYFILE_EXCL);
86
+ }
87
+ catch (error) {
88
+ const exists = error.code === "EEXIST";
89
+ return {
90
+ content: [{
91
+ type: "text",
92
+ text: exists
93
+ ? `"${args.destination}" already exists in sources/ - originals are never overwritten; pick another destination name.`
94
+ : `Couldn't copy "${args.source}" to sources/: ${error instanceof Error ? error.message : String(error)}`,
95
+ }],
96
+ isError: true,
97
+ };
98
+ }
99
+ const savedAs = path.relative(sourcesDir, resolvedDestination).split(path.sep).join("/");
100
+ return {
101
+ content: [{ type: "text", text: `Saved to sources/${savedAs} - read it from there with Read.` }],
102
+ };
103
+ });
104
+ return createSdkMcpServer({ name: "sourceFiles", version: "1.0.0", tools: [saveToSources] });
105
+ }
@@ -0,0 +1,31 @@
1
+ export * as ui from "./tui/ui.js";
2
+ export { isExitPromptError } from "./tui/promptErrors.js";
3
+ export { allowAnyMcpTool } from "./core/mcpPermissions.js";
4
+ export { createPromptLoader } from "./core/promptTemplate.js";
5
+ export { resolveClaudeAuth, type ClaudeAuthConfig } from "./core/claudeAuth.js";
6
+ export { ensureClaudeAuth } from "./tui/claudeAuth.js";
7
+ export { createFriendlyToolLabel, truncate, truncatePath, type ToolDescriber, } from "./core/toolLabels.js";
8
+ export type { Mode, BaseSessionConfig, AgentSpec } from "./core/agentSpec.js";
9
+ export { buildSessionOptions, createInputQueue, createDeferred, createModeControl, type ModeControl } from "./core/session.js";
10
+ export { runQuery, type AgentEvent, type AgentRun, type SessionUsage, type ContextUsage } from "./core/runner.js";
11
+ export { knowledgePluginRoot, knowledgePromptSection } from "./core/knowledge.js";
12
+ export type { Options, McpServerConfig, AgentDefinition } from "@anthropic-ai/claude-agent-sdk";
13
+ export { createTranscriptLogger, summarizeToolResponse, type TranscriptLogger } from "./core/hooks/transcriptLogger.js";
14
+ export { createStepGate } from "./core/hooks/stepGate.js";
15
+ export { createSubagentBashGate } from "./core/hooks/subagentBashGate.js";
16
+ export { createSubagentTypeGate } from "./core/hooks/subagentTypeGate.js";
17
+ export { createSubagentForegroundGate } from "./core/hooks/subagentForegroundGate.js";
18
+ export { createFileScopeGate, checkFileScope, type FileScope } from "./core/hooks/fileScopeGate.js";
19
+ export { askForDecision, askForManualIntervention } from "./core/hooks/humanInput.js";
20
+ export { setInteractionPort, getInteractionPort, type InteractionPort, type ApprovalPrompt } from "./core/interaction.js";
21
+ export { terminalInteractionPort, setSharedReadline, getSharedReadline } from "./tui/terminalInteraction.js";
22
+ export { createHumanApprovalServer, DEFAULT_HUMAN_APPROVAL_TEXTS, type HumanApprovalTexts } from "./core/tools/humanApproval.js";
23
+ export { createManualLoginServer, type ManualInterventionTexts } from "./core/tools/manualLogin.js";
24
+ export { createSaveToSourcesServer } from "./core/tools/saveToSources.js";
25
+ export { runChatTui, type ChatTuiOptions } from "./tui/chatTui.js";
26
+ export { createConsoleRenderer, type ConsoleRenderer, type ConsoleRendererOptions } from "./tui/consoleRenderer.js";
27
+ export { runChatInk, type InkChatOptions, type HeaderInfo } from "./tui/ink/runChatInk.js";
28
+ export type { ToolPhrase } from "./tui/ink/toolGroup.js";
29
+ export type { RenderApproval } from "./tui/ink/SessionView.js";
30
+ export { createProgressView, type ProgressView, type ProgressViewOptions } from "./tui/ink/progressView.js";
31
+ export { runWizard, type WizardStep, type SelectStep, type InputStep, type PasswordStep, type ConfirmStep, type WizardAnswers, type WizardOptions, } from "./tui/ink/wizard.js";
package/dist/index.js ADDED
@@ -0,0 +1,32 @@
1
+ import { setInteractionPort } from "./core/interaction.js";
2
+ import { terminalInteractionPort } from "./tui/terminalInteraction.js";
3
+ export * as ui from "./tui/ui.js";
4
+ export { isExitPromptError } from "./tui/promptErrors.js";
5
+ export { allowAnyMcpTool } from "./core/mcpPermissions.js";
6
+ export { createPromptLoader } from "./core/promptTemplate.js";
7
+ export { resolveClaudeAuth } from "./core/claudeAuth.js";
8
+ export { ensureClaudeAuth } from "./tui/claudeAuth.js";
9
+ export { createFriendlyToolLabel, truncate, truncatePath, } from "./core/toolLabels.js";
10
+ export { buildSessionOptions, createInputQueue, createDeferred, createModeControl } from "./core/session.js";
11
+ export { runQuery } from "./core/runner.js";
12
+ export { knowledgePluginRoot, knowledgePromptSection } from "./core/knowledge.js";
13
+ export { createTranscriptLogger, summarizeToolResponse } from "./core/hooks/transcriptLogger.js";
14
+ export { createStepGate } from "./core/hooks/stepGate.js";
15
+ export { createSubagentBashGate } from "./core/hooks/subagentBashGate.js";
16
+ export { createSubagentTypeGate } from "./core/hooks/subagentTypeGate.js";
17
+ export { createSubagentForegroundGate } from "./core/hooks/subagentForegroundGate.js";
18
+ export { createFileScopeGate, checkFileScope } from "./core/hooks/fileScopeGate.js";
19
+ export { askForDecision, askForManualIntervention } from "./core/hooks/humanInput.js";
20
+ export { setInteractionPort, getInteractionPort } from "./core/interaction.js";
21
+ export { terminalInteractionPort, setSharedReadline, getSharedReadline } from "./tui/terminalInteraction.js";
22
+ // The checkpoints answer on the terminal by default, as they always have; a non-terminal
23
+ // host replaces the port with its own (or null, to answer through the response file only).
24
+ setInteractionPort(terminalInteractionPort);
25
+ export { createHumanApprovalServer, DEFAULT_HUMAN_APPROVAL_TEXTS } from "./core/tools/humanApproval.js";
26
+ export { createManualLoginServer } from "./core/tools/manualLogin.js";
27
+ export { createSaveToSourcesServer } from "./core/tools/saveToSources.js";
28
+ export { runChatTui } from "./tui/chatTui.js";
29
+ export { createConsoleRenderer } from "./tui/consoleRenderer.js";
30
+ export { runChatInk } from "./tui/ink/runChatInk.js";
31
+ export { createProgressView } from "./tui/ink/progressView.js";
32
+ export { runWizard, } from "./tui/ink/wizard.js";
@@ -0,0 +1,105 @@
1
+ import type { Options } from "@anthropic-ai/claude-agent-sdk";
2
+ import { type AgentEvent } from "../core/runner.js";
3
+ export interface ChatTuiOptions {
4
+ /**
5
+ * Turns a tool call into a one-line console label. Defaults to `createFriendlyToolLabel()`
6
+ * with no domain overrides — pass your own (e.g.
7
+ * `createFriendlyToolLabel({ describe, extraLocalServers })`) to cover a concrete agent's
8
+ * own domain-specific tools (Playwright's `browser_*`, etc.).
9
+ */
10
+ formatAction?: (toolName: string, input: unknown) => string;
11
+ /** Printed once before the first prompt, if given. */
12
+ welcomeMessage?: string;
13
+ /** Shown before the cursor at each prompt. */
14
+ promptLabel?: string;
15
+ /**
16
+ * Shown once per turn, right before the agent's streamed reply text starts (not before
17
+ * any action lines that precede it) — and, when set, also colors that reply text via
18
+ * `ui.agent()`. Pass a pre-colored string (e.g. `ui.agent("Captain Whiskers>")`); omit to
19
+ * keep the plain, unlabeled/uncolored text streaming this kit had before.
20
+ */
21
+ agentLabel?: string;
22
+ /** Lines (trimmed, case-insensitive) that end the chat. */
23
+ exitCommands?: readonly string[];
24
+ /**
25
+ * When set, submitted as the very first turn — before the human is ever prompted —
26
+ * exactly as if they'd typed it themselves (mirrored to `sessionLogPath`/history like
27
+ * any other line). For an agent that should act proactively at the start of a chat (log
28
+ * into a site, load some state) instead of sitting idle at the prompt until the human
29
+ * says something first. Omit for the plain "wait at the prompt" behavior this kit had
30
+ * before.
31
+ */
32
+ initialPrompt?: string;
33
+ /**
34
+ * When set, mirrors everything this loop prints — the welcome message, each prompt
35
+ * line the human types, the agent's streamed reply, action lines, and any error/warning
36
+ * — into this file as it happens (appended, plain text with ANSI color codes stripped),
37
+ * so a session leaves behind an exact, always-present transcript of what the terminal
38
+ * showed even when the agent never calls a single tool (unlike `transcriptPath` from
39
+ * `buildSessionOptions()`, which only gets written to on the first tool call). Omit to
40
+ * skip session logging entirely.
41
+ */
42
+ sessionLogPath?: string;
43
+ /**
44
+ * When set, every non-empty line the human submits (including exit commands — a shell's
45
+ * own history keeps those too) is appended here as one JSON object per line
46
+ * (`{ text: string, timestamp: string }`, ISO 8601), trimmed to the most recent
47
+ * `historyLimit` entries (oldest dropped first). Loaded back at startup — including
48
+ * across separate runs of the same agent, since this path is normally a fixed file, not
49
+ * one under a per-run timestamped directory — into `readline`'s own history, so ↑/↓ at
50
+ * the prompt cycles through prior prompts. Omit to skip persistence entirely (↑/↓ still
51
+ * works for the current session alone, via readline's own in-memory default).
52
+ */
53
+ historyPath?: string;
54
+ /** Max entries kept in `historyPath` (and in the in-session ↑/↓ list). Defaults to 100. */
55
+ historyLimit?: number;
56
+ }
57
+ interface HistoryEntry {
58
+ text: string;
59
+ timestamp: string;
60
+ }
61
+ /** Keeps only the most recent `limit` entries (oldest-first order in, oldest-first out). */
62
+ export declare function capHistory(entries: readonly HistoryEntry[], limit: number): HistoryEntry[];
63
+ /** Oldest-first. Missing file reads as empty — nothing to load yet is the normal case. */
64
+ export declare function loadHistory(filePath: string): Promise<HistoryEntry[]>;
65
+ /** Overwrites the whole file (not appended) so a prior prune (oldest entries dropped) sticks. */
66
+ export declare function saveHistory(filePath: string, entries: readonly HistoryEntry[]): Promise<void>;
67
+ /**
68
+ * The command name a typed line would invoke (e.g. "chiste" for both "/chiste" and
69
+ * "/chiste con salsa"), or `null` for a line that isn't a slash command at all (doesn't
70
+ * start with "/") or is just a bare "/" with nothing after it.
71
+ */
72
+ export declare function slashCommandToken(line: string): string | null;
73
+ /**
74
+ * Reads `events` one turn at a time — up to and including a `turn-end` — via manual
75
+ * `.next()` calls, calling `onEvent` for each. Deliberately never uses
76
+ * `for await...of` + `break`: `run.events` (see runner.ts) is one long-lived generator
77
+ * spanning the *whole* multi-turn session, not one per turn, and breaking out of a
78
+ * `for-await-of` loop calls the generator's `.return()`, permanently closing it — the next
79
+ * turn's drain would then see `done: true` immediately and silently lose the rest of the
80
+ * conversation. Exported on its own (not part of the package's public API — see index.ts)
81
+ * so this exact behavior has a direct regression test.
82
+ */
83
+ export declare function drainTurn(events: AsyncIterator<AgentEvent>, onEvent: (event: AgentEvent) => void): Promise<void>;
84
+ /**
85
+ * A ready-to-run interactive terminal chat loop, assembled from this kit's own chat
86
+ * primitives: `createInputQueue()` feeds typed lines into a multi-turn `query()` session,
87
+ * `runQuery()`'s normalized event stream is rendered to the console (streamed text,
88
+ * friendly action labels via `createFriendlyToolLabel()`, turn failures), and this loop's
89
+ * own `readline.Interface` is registered via `setSharedReadline()` so any human-in-the-loop
90
+ * checkpoint the session hits (the interactive-mode step gate, or the guided-mode approval/
91
+ * manual-login tools — see terminalInteraction.ts) prompts on the same interface instead of a
92
+ * second one fighting it for stdin's raw mode.
93
+ *
94
+ * Takes the SDK `Options` produced by `buildSessionOptions()` rather than an `AgentSpec`
95
+ * directly, so it stays reusable for an `Options` assembled any other way:
96
+ *
97
+ * const { options } = await buildSessionOptions(config, runDir, spec);
98
+ * await runChatTui(options, { welcomeMessage: "Ready. Type /exit to quit." });
99
+ *
100
+ * Resolves once the chat ends (an exit command, Ctrl+C/Ctrl+D at an idle prompt) — this
101
+ * function owns the whole session lifecycle, closing the underlying query and readline
102
+ * interface itself before returning.
103
+ */
104
+ export declare function runChatTui(options: Options, tuiOptions?: ChatTuiOptions): Promise<void>;
105
+ export {};