@stigmer/ink 0.0.88

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 (85) hide show
  1. package/LICENSE +190 -0
  2. package/app/SessionApp.d.ts +42 -0
  3. package/app/SessionApp.d.ts.map +1 -0
  4. package/app/SessionApp.js +38 -0
  5. package/app/SessionApp.js.map +1 -0
  6. package/app/SessionView.d.ts +32 -0
  7. package/app/SessionView.d.ts.map +1 -0
  8. package/app/SessionView.js +58 -0
  9. package/app/SessionView.js.map +1 -0
  10. package/cli/stigmer-ink.d.ts +3 -0
  11. package/cli/stigmer-ink.d.ts.map +1 -0
  12. package/cli/stigmer-ink.js +117 -0
  13. package/cli/stigmer-ink.js.map +1 -0
  14. package/components/ApprovalPrompt.d.ts +21 -0
  15. package/components/ApprovalPrompt.d.ts.map +1 -0
  16. package/components/ApprovalPrompt.js +60 -0
  17. package/components/ApprovalPrompt.js.map +1 -0
  18. package/components/ExecutionProgress.d.ts +16 -0
  19. package/components/ExecutionProgress.d.ts.map +1 -0
  20. package/components/ExecutionProgress.js +47 -0
  21. package/components/ExecutionProgress.js.map +1 -0
  22. package/components/FollowUpInput.d.ts +19 -0
  23. package/components/FollowUpInput.d.ts.map +1 -0
  24. package/components/FollowUpInput.js +27 -0
  25. package/components/FollowUpInput.js.map +1 -0
  26. package/components/MessageEntry.d.ts +18 -0
  27. package/components/MessageEntry.d.ts.map +1 -0
  28. package/components/MessageEntry.js +42 -0
  29. package/components/MessageEntry.js.map +1 -0
  30. package/components/MessageThread.d.ts +31 -0
  31. package/components/MessageThread.d.ts.map +1 -0
  32. package/components/MessageThread.js +146 -0
  33. package/components/MessageThread.js.map +1 -0
  34. package/components/SubAgentBlock.d.ts +19 -0
  35. package/components/SubAgentBlock.d.ts.map +1 -0
  36. package/components/SubAgentBlock.js +73 -0
  37. package/components/SubAgentBlock.js.map +1 -0
  38. package/components/TodoList.d.ts +17 -0
  39. package/components/TodoList.d.ts.map +1 -0
  40. package/components/TodoList.js +43 -0
  41. package/components/TodoList.js.map +1 -0
  42. package/components/ToolCallGroup.d.ts +20 -0
  43. package/components/ToolCallGroup.d.ts.map +1 -0
  44. package/components/ToolCallGroup.js +51 -0
  45. package/components/ToolCallGroup.js.map +1 -0
  46. package/components/ToolCallItem.d.ts +14 -0
  47. package/components/ToolCallItem.d.ts.map +1 -0
  48. package/components/ToolCallItem.js +33 -0
  49. package/components/ToolCallItem.js.map +1 -0
  50. package/components/UsageWidget.d.ts +16 -0
  51. package/components/UsageWidget.d.ts.map +1 -0
  52. package/components/UsageWidget.js +18 -0
  53. package/components/UsageWidget.js.map +1 -0
  54. package/index.d.ts +16 -0
  55. package/index.d.ts.map +1 -0
  56. package/index.js +21 -0
  57. package/index.js.map +1 -0
  58. package/markdown.d.ts +21 -0
  59. package/markdown.d.ts.map +1 -0
  60. package/markdown.js +44 -0
  61. package/markdown.js.map +1 -0
  62. package/package.json +48 -0
  63. package/provider.d.ts +46 -0
  64. package/provider.d.ts.map +1 -0
  65. package/provider.js +33 -0
  66. package/provider.js.map +1 -0
  67. package/src/__tests__/components.test.tsx +162 -0
  68. package/src/__tests__/markdown.test.ts +46 -0
  69. package/src/app/SessionApp.tsx +74 -0
  70. package/src/app/SessionView.tsx +164 -0
  71. package/src/cli/stigmer-ink.tsx +148 -0
  72. package/src/components/ApprovalPrompt.tsx +139 -0
  73. package/src/components/ExecutionProgress.tsx +75 -0
  74. package/src/components/FollowUpInput.tsx +70 -0
  75. package/src/components/MessageEntry.tsx +80 -0
  76. package/src/components/MessageThread.tsx +264 -0
  77. package/src/components/SubAgentBlock.tsx +146 -0
  78. package/src/components/TodoList.tsx +75 -0
  79. package/src/components/ToolCallGroup.tsx +92 -0
  80. package/src/components/ToolCallItem.tsx +74 -0
  81. package/src/components/UsageWidget.tsx +35 -0
  82. package/src/index.ts +28 -0
  83. package/src/markdown.ts +48 -0
  84. package/src/provider.tsx +62 -0
  85. package/src/types/marked-terminal.d.ts +19 -0
@@ -0,0 +1,51 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { Box, Text, useInput } from "ink";
4
+ import { ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
5
+ import { ToolCallItem } from "./ToolCallItem.js";
6
+ function deriveAggregateStatus(toolCalls) {
7
+ let hasRunning = false;
8
+ let hasFailed = false;
9
+ for (const tc of toolCalls) {
10
+ if (tc.status === ToolCallStatus.TOOL_CALL_RUNNING)
11
+ hasRunning = true;
12
+ if (tc.status === ToolCallStatus.TOOL_CALL_FAILED)
13
+ hasFailed = true;
14
+ }
15
+ if (hasRunning)
16
+ return "running";
17
+ if (hasFailed)
18
+ return "failed";
19
+ const allDone = toolCalls.every((tc) => tc.status === ToolCallStatus.TOOL_CALL_COMPLETED ||
20
+ tc.status === ToolCallStatus.TOOL_CALL_FAILED);
21
+ return allDone ? "completed" : "pending";
22
+ }
23
+ const STATUS_STYLE = {
24
+ running: { symbol: "⠋", color: "yellow" },
25
+ completed: { symbol: "✓", color: "green" },
26
+ failed: { symbol: "✗", color: "red" },
27
+ pending: { symbol: "○" },
28
+ };
29
+ /**
30
+ * Renders a collapsible group of tool calls with an aggregate status.
31
+ *
32
+ * When collapsed, shows a summary line with tool count and status.
33
+ * When expanded, renders each tool call via {@link ToolCallItem}.
34
+ *
35
+ * Press Enter or Space to toggle expansion when `isFocused` is true.
36
+ */
37
+ export function ToolCallGroup({ toolCalls, isFocused = false, defaultExpanded = false, }) {
38
+ const [expanded, setExpanded] = useState(defaultExpanded);
39
+ const status = deriveAggregateStatus(toolCalls);
40
+ const style = STATUS_STYLE[status];
41
+ useInput((_input, key) => {
42
+ if (key.return || _input === " ") {
43
+ setExpanded((prev) => !prev);
44
+ }
45
+ }, { isActive: isFocused });
46
+ const summary = toolCalls.length === 1
47
+ ? toolCalls[0].name
48
+ : `${toolCalls.length} tool calls`;
49
+ return (_jsxs(Box, { flexDirection: "column", paddingLeft: 2, children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: style.color, children: style.symbol }), _jsx(Text, { dimColor: true, children: expanded ? "▼" : "▶" }), _jsx(Text, { children: summary })] }), expanded && (_jsx(Box, { flexDirection: "column", paddingLeft: 2, marginTop: 1, children: toolCalls.map((tc) => (_jsx(ToolCallItem, { toolCall: tc, expanded: true }, tc.id))) }))] }));
50
+ }
51
+ //# sourceMappingURL=ToolCallGroup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ToolCallGroup.js","sourceRoot":"","sources":["../../src/components/ToolCallGroup.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { ToolCall } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
2
+ /** Props for {@link ToolCallItem}. */
3
+ export interface ToolCallItemProps {
4
+ /** The tool call to render. */
5
+ readonly toolCall: ToolCall;
6
+ /** Whether to show the full args/result (expanded view). */
7
+ readonly expanded?: boolean;
8
+ }
9
+ /**
10
+ * Renders a single tool call with a status indicator, name, and
11
+ * optional expanded args/result preview.
12
+ */
13
+ export declare function ToolCallItem({ toolCall, expanded }: ToolCallItemProps): import("react/jsx-runtime").JSX.Element;
14
+ //# sourceMappingURL=ToolCallItem.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ToolCallItem.d.ts","sourceRoot":"","sources":["../../src/components/ToolCallItem.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iEAAiE,CAAC;AAGhG,sCAAsC;AACtC,MAAM,WAAW,iBAAiB;IAChC,+BAA+B;IAC/B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,4DAA4D;IAC5D,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B;AAQD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,EAAE,QAAQ,EAAE,QAAgB,EAAE,EAAE,iBAAiB,2CA4C7E"}
@@ -0,0 +1,33 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from "ink";
3
+ import { ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
4
+ const STATUS_INDICATOR = {
5
+ [ToolCallStatus.TOOL_CALL_RUNNING]: { symbol: "⠋", color: "yellow" },
6
+ [ToolCallStatus.TOOL_CALL_COMPLETED]: { symbol: "✓", color: "green" },
7
+ [ToolCallStatus.TOOL_CALL_FAILED]: { symbol: "✗", color: "red" },
8
+ };
9
+ /**
10
+ * Renders a single tool call with a status indicator, name, and
11
+ * optional expanded args/result preview.
12
+ */
13
+ export function ToolCallItem({ toolCall, expanded = false }) {
14
+ const indicator = STATUS_INDICATOR[toolCall.status] ?? {
15
+ symbol: "○",
16
+ };
17
+ const serverSlug = toolCall.mcpServerSlug;
18
+ const label = serverSlug
19
+ ? `${serverSlug}/${toolCall.name}`
20
+ : toolCall.name;
21
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: indicator.color, children: indicator.symbol }), _jsx(Text, { children: label }), toolCall.status === ToolCallStatus.TOOL_CALL_RUNNING && (_jsx(Text, { dimColor: true, children: "running" }))] }), expanded && toolCall.argsPreview && (_jsx(Box, { paddingLeft: 3, children: _jsx(Text, { dimColor: true, wrap: "truncate-end", children: toolCall.argsPreview }) })), expanded &&
22
+ toolCall.status === ToolCallStatus.TOOL_CALL_COMPLETED &&
23
+ toolCall.result && (_jsx(Box, { paddingLeft: 3, children: _jsx(Text, { dimColor: true, wrap: "truncate-end", children: truncateResult(toolCall.result) }) })), expanded &&
24
+ toolCall.status === ToolCallStatus.TOOL_CALL_FAILED &&
25
+ toolCall.error && (_jsx(Box, { paddingLeft: 3, children: _jsx(Text, { color: "red", children: toolCall.error }) }))] }));
26
+ }
27
+ function truncateResult(result, maxLines = 5) {
28
+ const lines = result.split("\n");
29
+ if (lines.length <= maxLines)
30
+ return result;
31
+ return lines.slice(0, maxLines).join("\n") + `\n... (${lines.length - maxLines} more lines)`;
32
+ }
33
+ //# sourceMappingURL=ToolCallItem.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ToolCallItem.js","sourceRoot":"","sources":["../../src/components/ToolCallItem.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAEhC,OAAO,EAAE,cAAc,EAAE,MAAM,8DAA8D,CAAC;AAU9F,MAAM,gBAAgB,GAAuD;IAC3E,CAAC,cAAc,CAAC,iBAAiB,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE;IACpE,CAAC,cAAc,CAAC,mBAAmB,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE;IACrE,CAAC,cAAc,CAAC,gBAAgB,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE;CACjE,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,EAAE,QAAQ,EAAE,QAAQ,GAAG,KAAK,EAAqB;IAC5E,MAAM,SAAS,GAAG,gBAAgB,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI;QACrD,MAAM,EAAE,GAAG;KACZ,CAAC;IAEF,MAAM,UAAU,GAAG,QAAQ,CAAC,aAAa,CAAC;IAC1C,MAAM,KAAK,GAAG,UAAU;QACtB,CAAC,CAAC,GAAG,UAAU,IAAI,QAAQ,CAAC,IAAI,EAAE;QAClC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC;IAElB,OAAO,CACL,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,aACzB,MAAC,GAAG,IAAC,GAAG,EAAE,CAAC,aACT,KAAC,IAAI,IAAC,KAAK,EAAE,SAAS,CAAC,KAAK,YAAG,SAAS,CAAC,MAAM,GAAQ,EACvD,KAAC,IAAI,cAAE,KAAK,GAAQ,EACnB,QAAQ,CAAC,MAAM,KAAK,cAAc,CAAC,iBAAiB,IAAI,CACvD,KAAC,IAAI,IAAC,QAAQ,8BAAe,CAC9B,IACG,EACL,QAAQ,IAAI,QAAQ,CAAC,WAAW,IAAI,CACnC,KAAC,GAAG,IAAC,WAAW,EAAE,CAAC,YACjB,KAAC,IAAI,IAAC,QAAQ,QAAC,IAAI,EAAC,cAAc,YAC/B,QAAQ,CAAC,WAAW,GAChB,GACH,CACP,EACA,QAAQ;gBACP,QAAQ,CAAC,MAAM,KAAK,cAAc,CAAC,mBAAmB;gBACtD,QAAQ,CAAC,MAAM,IAAI,CACjB,KAAC,GAAG,IAAC,WAAW,EAAE,CAAC,YACjB,KAAC,IAAI,IAAC,QAAQ,QAAC,IAAI,EAAC,cAAc,YAC/B,cAAc,CAAC,QAAQ,CAAC,MAAM,CAAC,GAC3B,GACH,CACP,EACF,QAAQ;gBACP,QAAQ,CAAC,MAAM,KAAK,cAAc,CAAC,gBAAgB;gBACnD,QAAQ,CAAC,KAAK,IAAI,CAChB,KAAC,GAAG,IAAC,WAAW,EAAE,CAAC,YACjB,KAAC,IAAI,IAAC,KAAK,EAAC,KAAK,YAAE,QAAQ,CAAC,KAAK,GAAQ,GACrC,CACP,IACC,CACP,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,MAAc,EAAE,QAAQ,GAAG,CAAC;IAClD,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACjC,IAAI,KAAK,CAAC,MAAM,IAAI,QAAQ;QAAE,OAAO,MAAM,CAAC;IAC5C,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,UAAU,KAAK,CAAC,MAAM,GAAG,QAAQ,cAAc,CAAC;AAC/F,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { AgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
2
+ /** Props for {@link UsageWidget}. */
3
+ export interface UsageWidgetProps {
4
+ /** All executions for the current session (completed + active). */
5
+ readonly executions: readonly AgentExecution[];
6
+ }
7
+ /**
8
+ * Compact terminal widget showing session-level token usage and cost.
9
+ *
10
+ * Aggregates per-message `LlmCallMetrics` across all executions
11
+ * in the session. Renders nothing when no usage data is available.
12
+ *
13
+ * Uses the headless {@link useSessionUsage} hook from `@stigmer/react`.
14
+ */
15
+ export declare function UsageWidget({ executions }: UsageWidgetProps): import("react/jsx-runtime").JSX.Element | null;
16
+ //# sourceMappingURL=UsageWidget.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UsageWidget.d.ts","sourceRoot":"","sources":["../../src/components/UsageWidget.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6DAA6D,CAAC;AAGlG,qCAAqC;AACrC,MAAM,WAAW,gBAAgB;IAC/B,mEAAmE;IACnE,QAAQ,CAAC,UAAU,EAAE,SAAS,cAAc,EAAE,CAAC;CAChD;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,EAAE,UAAU,EAAE,EAAE,gBAAgB,kDAe3D"}
@@ -0,0 +1,18 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box, Text } from "ink";
3
+ import { useSessionUsage, formatCost, formatTokenCount } from "@stigmer/react";
4
+ /**
5
+ * Compact terminal widget showing session-level token usage and cost.
6
+ *
7
+ * Aggregates per-message `LlmCallMetrics` across all executions
8
+ * in the session. Renders nothing when no usage data is available.
9
+ *
10
+ * Uses the headless {@link useSessionUsage} hook from `@stigmer/react`.
11
+ */
12
+ export function UsageWidget({ executions }) {
13
+ const usage = useSessionUsage(executions);
14
+ if (!usage.hasUsage)
15
+ return null;
16
+ return (_jsx(Box, { paddingLeft: 1, gap: 1, children: _jsxs(Text, { dimColor: true, children: [formatCost(usage.totalCostUsd), " \u00B7 ", formatTokenCount(usage.totalTokens), " ", "tokens \u00B7 ", usage.llmCallCount, " ", usage.llmCallCount === 1 ? "call" : "calls", usage.primaryModel ? ` · ${usage.primaryModel}` : ""] }) }));
17
+ }
18
+ //# sourceMappingURL=UsageWidget.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UsageWidget.js","sourceRoot":"","sources":["../../src/components/UsageWidget.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAEhC,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAQ/E;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CAAC,EAAE,UAAU,EAAoB;IAC1D,MAAM,KAAK,GAAG,eAAe,CAAC,UAAU,CAAC,CAAC;IAE1C,IAAI,CAAC,KAAK,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAEjC,OAAO,CACL,KAAC,GAAG,IAAC,WAAW,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,YACzB,MAAC,IAAI,IAAC,QAAQ,mBACX,UAAU,CAAC,KAAK,CAAC,YAAY,CAAC,cAAK,gBAAgB,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,GAAG,oBAClE,KAAK,CAAC,YAAY,EAAE,GAAG,EAChC,KAAK,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,EAC3C,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,IAChD,GACH,CACP,CAAC;AACJ,CAAC"}
package/index.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ export { InkStigmerProvider, type InkStigmerProviderProps } from "./provider.js";
2
+ export { createNodeClient, createNodeTransport, type NodeClientConfig, } from "@stigmer/sdk/node";
3
+ export { renderMarkdown } from "./markdown.js";
4
+ export { MessageEntry, type MessageEntryProps } from "./components/MessageEntry.js";
5
+ export { MessageThread, type MessageThreadProps } from "./components/MessageThread.js";
6
+ export { ToolCallItem, type ToolCallItemProps } from "./components/ToolCallItem.js";
7
+ export { ToolCallGroup, type ToolCallGroupProps } from "./components/ToolCallGroup.js";
8
+ export { SubAgentBlock, type SubAgentBlockProps } from "./components/SubAgentBlock.js";
9
+ export { TodoList, type TodoListProps } from "./components/TodoList.js";
10
+ export { ApprovalPrompt, type ApprovalPromptProps } from "./components/ApprovalPrompt.js";
11
+ export { ExecutionProgress, type ExecutionProgressProps } from "./components/ExecutionProgress.js";
12
+ export { FollowUpInput, type FollowUpInputProps } from "./components/FollowUpInput.js";
13
+ export { UsageWidget, type UsageWidgetProps } from "./components/UsageWidget.js";
14
+ export { SessionView, type SessionViewProps } from "./app/SessionView.js";
15
+ export { SessionApp, type SessionAppProps } from "./app/SessionApp.js";
16
+ //# sourceMappingURL=index.d.ts.map
package/index.d.ts.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,KAAK,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAGjF,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,KAAK,gBAAgB,GACtB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAG/C,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACpF,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACpF,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACxE,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAC1F,OAAO,EAAE,iBAAiB,EAAE,KAAK,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AACnG,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAGjF,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC"}
package/index.js ADDED
@@ -0,0 +1,21 @@
1
+ // Provider
2
+ export { InkStigmerProvider } from "./provider.js";
3
+ // Transport (re-exported from @stigmer/sdk/node)
4
+ export { createNodeClient, createNodeTransport, } from "@stigmer/sdk/node";
5
+ // Markdown
6
+ export { renderMarkdown } from "./markdown.js";
7
+ // Components
8
+ export { MessageEntry } from "./components/MessageEntry.js";
9
+ export { MessageThread } from "./components/MessageThread.js";
10
+ export { ToolCallItem } from "./components/ToolCallItem.js";
11
+ export { ToolCallGroup } from "./components/ToolCallGroup.js";
12
+ export { SubAgentBlock } from "./components/SubAgentBlock.js";
13
+ export { TodoList } from "./components/TodoList.js";
14
+ export { ApprovalPrompt } from "./components/ApprovalPrompt.js";
15
+ export { ExecutionProgress } from "./components/ExecutionProgress.js";
16
+ export { FollowUpInput } from "./components/FollowUpInput.js";
17
+ export { UsageWidget } from "./components/UsageWidget.js";
18
+ // Composed views
19
+ export { SessionView } from "./app/SessionView.js";
20
+ export { SessionApp } from "./app/SessionApp.js";
21
+ //# sourceMappingURL=index.js.map
package/index.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,WAAW;AACX,OAAO,EAAE,kBAAkB,EAAgC,MAAM,eAAe,CAAC;AAEjF,iDAAiD;AACjD,OAAO,EACL,gBAAgB,EAChB,mBAAmB,GAEpB,MAAM,mBAAmB,CAAC;AAE3B,WAAW;AACX,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,aAAa;AACb,OAAO,EAAE,YAAY,EAA0B,MAAM,8BAA8B,CAAC;AACpF,OAAO,EAAE,aAAa,EAA2B,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,YAAY,EAA0B,MAAM,8BAA8B,CAAC;AACpF,OAAO,EAAE,aAAa,EAA2B,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,aAAa,EAA2B,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,QAAQ,EAAsB,MAAM,0BAA0B,CAAC;AACxE,OAAO,EAAE,cAAc,EAA4B,MAAM,gCAAgC,CAAC;AAC1F,OAAO,EAAE,iBAAiB,EAA+B,MAAM,mCAAmC,CAAC;AACnG,OAAO,EAAE,aAAa,EAA2B,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,WAAW,EAAyB,MAAM,6BAA6B,CAAC;AAEjF,iBAAiB;AACjB,OAAO,EAAE,WAAW,EAAyB,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,UAAU,EAAwB,MAAM,qBAAqB,CAAC"}
package/markdown.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Render a markdown string to ANSI-styled terminal output.
3
+ *
4
+ * Uses `marked` with `marked-terminal` to produce styled text suitable
5
+ * for display in a terminal. Supports headings, code blocks (with syntax
6
+ * highlighting), lists, bold/italic, links, tables, and blockquotes.
7
+ *
8
+ * @param content - Raw markdown string.
9
+ * @param width - Terminal width for text wrapping. Defaults to `process.stdout.columns` or 80.
10
+ * @returns ANSI-styled string ready for terminal output.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * import { renderMarkdown } from "@stigmer/ink";
15
+ *
16
+ * const ansi = renderMarkdown("**Hello** from the agent!");
17
+ * process.stdout.write(ansi);
18
+ * ```
19
+ */
20
+ export declare function renderMarkdown(content: string, width?: number): string;
21
+ //# sourceMappingURL=markdown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../src/markdown.ts"],"names":[],"mappings":"AAqBA;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAOtE"}
package/markdown.js ADDED
@@ -0,0 +1,44 @@
1
+ import { Marked } from "marked";
2
+ import { markedTerminal } from "marked-terminal";
3
+ let _marked = null;
4
+ function getMarked(width) {
5
+ if (_marked && !width)
6
+ return _marked;
7
+ const instance = new Marked();
8
+ instance.use(markedTerminal({
9
+ width: width ?? process.stdout.columns ?? 80,
10
+ reflowText: true,
11
+ showSectionPrefix: false,
12
+ }));
13
+ if (!width)
14
+ _marked = instance;
15
+ return instance;
16
+ }
17
+ /**
18
+ * Render a markdown string to ANSI-styled terminal output.
19
+ *
20
+ * Uses `marked` with `marked-terminal` to produce styled text suitable
21
+ * for display in a terminal. Supports headings, code blocks (with syntax
22
+ * highlighting), lists, bold/italic, links, tables, and blockquotes.
23
+ *
24
+ * @param content - Raw markdown string.
25
+ * @param width - Terminal width for text wrapping. Defaults to `process.stdout.columns` or 80.
26
+ * @returns ANSI-styled string ready for terminal output.
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * import { renderMarkdown } from "@stigmer/ink";
31
+ *
32
+ * const ansi = renderMarkdown("**Hello** from the agent!");
33
+ * process.stdout.write(ansi);
34
+ * ```
35
+ */
36
+ export function renderMarkdown(content, width) {
37
+ const marked = getMarked(width);
38
+ const result = marked.parse(content);
39
+ if (typeof result !== "string") {
40
+ return content;
41
+ }
42
+ return result.trimEnd();
43
+ }
44
+ //# sourceMappingURL=markdown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown.js","sourceRoot":"","sources":["../src/markdown.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjD,IAAI,OAAO,GAAkB,IAAI,CAAC;AAElC,SAAS,SAAS,CAAC,KAAc;IAC/B,IAAI,OAAO,IAAI,CAAC,KAAK;QAAE,OAAO,OAAO,CAAC;IAEtC,MAAM,QAAQ,GAAG,IAAI,MAAM,EAAE,CAAC;IAC9B,QAAQ,CAAC,GAAG,CACV,cAAc,CAAC;QACb,KAAK,EAAE,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC,OAAO,IAAI,EAAE;QAC5C,UAAU,EAAE,IAAI;QAChB,iBAAiB,EAAE,KAAK;KACzB,CAAC,CACH,CAAC;IAEF,IAAI,CAAC,KAAK;QAAE,OAAO,GAAG,QAAQ,CAAC;IAC/B,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,cAAc,CAAC,OAAe,EAAE,KAAc;IAC5D,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;IAChC,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACrC,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC/B,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,OAAO,MAAM,CAAC,OAAO,EAAE,CAAC;AAC1B,CAAC"}
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@stigmer/ink",
3
+ "version": "0.0.88",
4
+ "description": "Ink (React for terminals) components for rendering Stigmer agent sessions in the terminal",
5
+ "license": "Apache-2.0",
6
+ "type": "module",
7
+ "sideEffects": false,
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/stigmer/stigmer.git",
11
+ "directory": "sdk/ink"
12
+ },
13
+ "engines": {
14
+ "node": ">=18"
15
+ },
16
+ "keywords": [
17
+ "stigmer",
18
+ "ink",
19
+ "terminal",
20
+ "cli",
21
+ "react",
22
+ "ai-agents",
23
+ "tui"
24
+ ],
25
+ "main": "./index.js",
26
+ "types": "./index.d.ts",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./index.d.ts",
30
+ "import": "./index.js"
31
+ }
32
+ },
33
+ "dependencies": {
34
+ "@connectrpc/connect-node": "^2.1.1",
35
+ "@stigmer/react": "0.0.88",
36
+ "ink-spinner": "^5.0.0",
37
+ "ink-text-input": "^6.0.0",
38
+ "marked": "^15.0.7",
39
+ "marked-terminal": "^7.3.0"
40
+ },
41
+ "peerDependencies": {
42
+ "@bufbuild/protobuf": "^2.0.0",
43
+ "@stigmer/protos": "0.0.88",
44
+ "@stigmer/sdk": "0.0.88",
45
+ "ink": "^7.0.0",
46
+ "react": ">=19.0.0"
47
+ }
48
+ }
package/provider.d.ts ADDED
@@ -0,0 +1,46 @@
1
+ import { type ReactNode } from "react";
2
+ import type { Stigmer, DeploymentMode } from "@stigmer/sdk";
3
+ /**
4
+ * Props for {@link InkStigmerProvider}.
5
+ */
6
+ export interface InkStigmerProviderProps {
7
+ /** Pre-configured Stigmer client (use {@link createNodeClient} to create one). */
8
+ client: Stigmer;
9
+ /** Child components to render. */
10
+ children: ReactNode;
11
+ /**
12
+ * Deployment mode for feature gating.
13
+ * - `"cloud"`: All features available (default).
14
+ * - `"local"`: OSS feature subset only.
15
+ */
16
+ deploymentMode?: DeploymentMode;
17
+ }
18
+ /**
19
+ * Stigmer provider for Ink (terminal) environments.
20
+ *
21
+ * Provides the same React contexts as `StigmerProvider` from
22
+ * `@stigmer/react`, but without the DOM `<div>` wrapper used for
23
+ * CSS scoping (which is not applicable in a terminal).
24
+ *
25
+ * All hooks from `@stigmer/react` (`useStigmer`, `useSessionConversation`,
26
+ * `useExecutionStream`, etc.) work identically under this provider.
27
+ *
28
+ * @example
29
+ * ```tsx
30
+ * import { render } from "ink";
31
+ * import { InkStigmerProvider, createNodeClient } from "@stigmer/ink";
32
+ *
33
+ * const client = createNodeClient({
34
+ * baseUrl: "https://api.stigmer.ai",
35
+ * apiKey: process.env.STIGMER_API_KEY,
36
+ * });
37
+ *
38
+ * render(
39
+ * <InkStigmerProvider client={client}>
40
+ * <MyTerminalApp />
41
+ * </InkStigmerProvider>
42
+ * );
43
+ * ```
44
+ */
45
+ export declare function InkStigmerProvider({ client, children, deploymentMode, }: InkStigmerProviderProps): import("react/jsx-runtime").JSX.Element;
46
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.tsx"],"names":[],"mappings":"AAAA,OAAc,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAE9C,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE5D;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,kFAAkF;IAClF,MAAM,EAAE,OAAO,CAAC;IAEhB,kCAAkC;IAClC,QAAQ,EAAE,SAAS,CAAC;IAEpB;;;;OAIG;IACH,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,MAAM,EACN,QAAQ,EACR,cAAwB,GACzB,EAAE,uBAAuB,2CAQzB"}
package/provider.js ADDED
@@ -0,0 +1,33 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { StigmerContext, DeploymentModeContext } from "@stigmer/react";
3
+ /**
4
+ * Stigmer provider for Ink (terminal) environments.
5
+ *
6
+ * Provides the same React contexts as `StigmerProvider` from
7
+ * `@stigmer/react`, but without the DOM `<div>` wrapper used for
8
+ * CSS scoping (which is not applicable in a terminal).
9
+ *
10
+ * All hooks from `@stigmer/react` (`useStigmer`, `useSessionConversation`,
11
+ * `useExecutionStream`, etc.) work identically under this provider.
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * import { render } from "ink";
16
+ * import { InkStigmerProvider, createNodeClient } from "@stigmer/ink";
17
+ *
18
+ * const client = createNodeClient({
19
+ * baseUrl: "https://api.stigmer.ai",
20
+ * apiKey: process.env.STIGMER_API_KEY,
21
+ * });
22
+ *
23
+ * render(
24
+ * <InkStigmerProvider client={client}>
25
+ * <MyTerminalApp />
26
+ * </InkStigmerProvider>
27
+ * );
28
+ * ```
29
+ */
30
+ export function InkStigmerProvider({ client, children, deploymentMode = "cloud", }) {
31
+ return (_jsx(StigmerContext.Provider, { value: client, children: _jsx(DeploymentModeContext.Provider, { value: deploymentMode, children: children }) }));
32
+ }
33
+ //# sourceMappingURL=provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAqBvE;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,UAAU,kBAAkB,CAAC,EACjC,MAAM,EACN,QAAQ,EACR,cAAc,GAAG,OAAO,GACA;IACxB,OAAO,CACL,KAAC,cAAc,CAAC,QAAQ,IAAC,KAAK,EAAE,MAAM,YACpC,KAAC,qBAAqB,CAAC,QAAQ,IAAC,KAAK,EAAE,cAAc,YAClD,QAAQ,GACsB,GACT,CAC3B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,162 @@
1
+ import React from "react";
2
+ import { describe, it, expect } from "vitest";
3
+ import { render } from "ink-testing-library";
4
+ import { Text, Box } from "ink";
5
+ import { create } from "@bufbuild/protobuf";
6
+ import { AgentMessageSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
7
+ import { MessageType, ExecutionPhase, ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
8
+ import { ToolCallSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
9
+ import { MessageEntry } from "../components/MessageEntry.js";
10
+ import { ExecutionProgress } from "../components/ExecutionProgress.js";
11
+ import { ToolCallItem } from "../components/ToolCallItem.js";
12
+
13
+ describe("MessageEntry", () => {
14
+ it("renders a human message with 'You' prefix", () => {
15
+ const msg = create(AgentMessageSchema);
16
+ msg.type = MessageType.MESSAGE_HUMAN;
17
+ msg.content = "Hello agent";
18
+
19
+ const { lastFrame } = render(<MessageEntry message={msg} />);
20
+ const output = lastFrame() ?? "";
21
+ expect(output).toContain("You");
22
+ expect(output).toContain("Hello agent");
23
+ });
24
+
25
+ it("renders an AI message with 'Agent' prefix", () => {
26
+ const msg = create(AgentMessageSchema);
27
+ msg.type = MessageType.MESSAGE_AI;
28
+ msg.content = "Here is the answer";
29
+ msg.isStreaming = false;
30
+
31
+ const { lastFrame } = render(<MessageEntry message={msg} />);
32
+ const output = lastFrame() ?? "";
33
+ expect(output).toContain("Agent");
34
+ expect(output).toContain("answer");
35
+ });
36
+
37
+ it("renders a system message in dim style", () => {
38
+ const msg = create(AgentMessageSchema);
39
+ msg.type = MessageType.MESSAGE_SYSTEM;
40
+ msg.content = "System notice";
41
+
42
+ const { lastFrame } = render(<MessageEntry message={msg} />);
43
+ const output = lastFrame() ?? "";
44
+ expect(output).toContain("System notice");
45
+ });
46
+
47
+ it("renders nothing for tool messages", () => {
48
+ const msg = create(AgentMessageSchema);
49
+ msg.type = MessageType.MESSAGE_TOOL;
50
+ msg.content = "tool result";
51
+
52
+ const { lastFrame } = render(<MessageEntry message={msg} />);
53
+ expect(lastFrame()).toBe("");
54
+ });
55
+
56
+ it("shows 'Thinking...' for streaming AI with no content", () => {
57
+ const msg = create(AgentMessageSchema);
58
+ msg.type = MessageType.MESSAGE_AI;
59
+ msg.content = "";
60
+ msg.isStreaming = true;
61
+
62
+ const { lastFrame } = render(<MessageEntry message={msg} />);
63
+ const output = lastFrame() ?? "";
64
+ expect(output).toContain("Thinking...");
65
+ });
66
+ });
67
+
68
+ describe("ExecutionProgress", () => {
69
+ it("shows 'Running' for in-progress phase", () => {
70
+ const { lastFrame } = render(
71
+ <ExecutionProgress phase={ExecutionPhase.EXECUTION_IN_PROGRESS} />,
72
+ );
73
+ const output = lastFrame() ?? "";
74
+ expect(output).toContain("Running");
75
+ });
76
+
77
+ it("shows 'Completed' with check for completed phase", () => {
78
+ const { lastFrame } = render(
79
+ <ExecutionProgress phase={ExecutionPhase.EXECUTION_COMPLETED} />,
80
+ );
81
+ const output = lastFrame() ?? "";
82
+ expect(output).toContain("Completed");
83
+ expect(output).toContain("✓");
84
+ });
85
+
86
+ it("shows 'Failed' for failed phase", () => {
87
+ const { lastFrame } = render(
88
+ <ExecutionProgress phase={ExecutionPhase.EXECUTION_FAILED} />,
89
+ );
90
+ const output = lastFrame() ?? "";
91
+ expect(output).toContain("Failed");
92
+ });
93
+
94
+ it("shows 'Waiting for approval' for approval phase", () => {
95
+ const { lastFrame } = render(
96
+ <ExecutionProgress
97
+ phase={ExecutionPhase.EXECUTION_WAITING_FOR_APPROVAL}
98
+ />,
99
+ );
100
+ const output = lastFrame() ?? "";
101
+ expect(output).toContain("Waiting for approval");
102
+ });
103
+
104
+ it("renders nothing for unspecified phase", () => {
105
+ const { lastFrame } = render(
106
+ <ExecutionProgress
107
+ phase={ExecutionPhase.EXECUTION_PHASE_UNSPECIFIED}
108
+ />,
109
+ );
110
+ expect(lastFrame()).toBe("");
111
+ });
112
+ });
113
+
114
+ describe("ToolCallItem", () => {
115
+ it("renders tool name with status indicator", () => {
116
+ const tc = create(ToolCallSchema);
117
+ tc.id = "tc-1";
118
+ tc.name = "read_file";
119
+ tc.status = ToolCallStatus.TOOL_CALL_COMPLETED;
120
+
121
+ const { lastFrame } = render(<ToolCallItem toolCall={tc} />);
122
+ const output = lastFrame() ?? "";
123
+ expect(output).toContain("read_file");
124
+ expect(output).toContain("✓");
125
+ });
126
+
127
+ it("shows running indicator for running tools", () => {
128
+ const tc = create(ToolCallSchema);
129
+ tc.id = "tc-2";
130
+ tc.name = "write_file";
131
+ tc.status = ToolCallStatus.TOOL_CALL_RUNNING;
132
+
133
+ const { lastFrame } = render(<ToolCallItem toolCall={tc} />);
134
+ const output = lastFrame() ?? "";
135
+ expect(output).toContain("write_file");
136
+ expect(output).toContain("running");
137
+ });
138
+
139
+ it("shows error for failed tools when expanded", () => {
140
+ const tc = create(ToolCallSchema);
141
+ tc.id = "tc-3";
142
+ tc.name = "execute_command";
143
+ tc.status = ToolCallStatus.TOOL_CALL_FAILED;
144
+ tc.error = "Permission denied";
145
+
146
+ const { lastFrame } = render(<ToolCallItem toolCall={tc} expanded />);
147
+ const output = lastFrame() ?? "";
148
+ expect(output).toContain("Permission denied");
149
+ });
150
+
151
+ it("shows MCP server slug prefix", () => {
152
+ const tc = create(ToolCallSchema);
153
+ tc.id = "tc-4";
154
+ tc.name = "list_resources";
155
+ tc.mcpServerSlug = "github";
156
+ tc.status = ToolCallStatus.TOOL_CALL_COMPLETED;
157
+
158
+ const { lastFrame } = render(<ToolCallItem toolCall={tc} />);
159
+ const output = lastFrame() ?? "";
160
+ expect(output).toContain("github/list_resources");
161
+ });
162
+ });
@@ -0,0 +1,46 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { renderMarkdown } from "../markdown.js";
3
+
4
+ describe("renderMarkdown", () => {
5
+ it("renders plain text without modification", () => {
6
+ const result = renderMarkdown("Hello world");
7
+ expect(result).toContain("Hello world");
8
+ });
9
+
10
+ it("renders bold text with ANSI styling", () => {
11
+ const result = renderMarkdown("This is **bold** text");
12
+ expect(result).toContain("bold");
13
+ expect(result).not.toBe("This is **bold** text");
14
+ });
15
+
16
+ it("renders code blocks", () => {
17
+ const result = renderMarkdown("```\nconst x = 1;\n```");
18
+ expect(result).toContain("const x = 1");
19
+ });
20
+
21
+ it("renders headings", () => {
22
+ const result = renderMarkdown("# Title");
23
+ expect(result).toContain("Title");
24
+ });
25
+
26
+ it("renders lists", () => {
27
+ const result = renderMarkdown("- item one\n- item two");
28
+ expect(result).toContain("item one");
29
+ expect(result).toContain("item two");
30
+ });
31
+
32
+ it("handles empty content", () => {
33
+ const result = renderMarkdown("");
34
+ expect(result).toBe("");
35
+ });
36
+
37
+ it("trims trailing whitespace", () => {
38
+ const result = renderMarkdown("Hello");
39
+ expect(result).toBe(result.trimEnd());
40
+ });
41
+
42
+ it("accepts custom width", () => {
43
+ const result = renderMarkdown("A very long sentence that might wrap", 40);
44
+ expect(result).toContain("A very long sentence");
45
+ });
46
+ });