@couralabs/claudin 0.1.1 → 0.1.3

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 (2) hide show
  1. package/claudin.js +323 -48
  2. package/package.json +1 -1
package/claudin.js CHANGED
@@ -5950,6 +5950,18 @@ function formatModelName(model) {
5950
5950
  const version = parts.filter((part) => NUMERIC_PART.test(part)).join(".");
5951
5951
  return ["Claude", ...words, version].filter(Boolean).join(" ");
5952
5952
  }
5953
+ // packages/events/src/models/messageText.ts
5954
+ var MAX_OUTPUT_LENGTH = 20000;
5955
+ function joinBlocks(content) {
5956
+ if (typeof content === "string")
5957
+ return content;
5958
+ if (!Array.isArray(content))
5959
+ return content === undefined ? "" : JSON.stringify(content);
5960
+ return content.map((block) => block.type === "text" ? block.text ?? "" : `[${block.type}]`).join(`
5961
+ `);
5962
+ }
5963
+ var readPromptText = (content) => joinBlocks(content);
5964
+ var clipOutput = (text) => text.slice(0, MAX_OUTPUT_LENGTH);
5953
5965
  // packages/events/src/models/modelLabel.ts
5954
5966
  function readModelLabel({ model, models }) {
5955
5967
  const row = models.find((candidate) => candidate.value === model);
@@ -6026,19 +6038,6 @@ var THEME_NAMES = [
6026
6038
  ];
6027
6039
  var themeNameSchema = _enum(THEME_NAMES);
6028
6040
  var DEFAULT_THEME_NAME = "vesper";
6029
- // packages/events/src/models/messageText.ts
6030
- var MAX_OUTPUT_LENGTH = 20000;
6031
- function joinBlocks(content) {
6032
- if (typeof content === "string")
6033
- return content;
6034
- if (!Array.isArray(content))
6035
- return content === undefined ? "" : JSON.stringify(content);
6036
- return content.map((block) => block.type === "text" ? block.text ?? "" : `[${block.type}]`).join(`
6037
- `);
6038
- }
6039
- var readPromptText = (content) => joinBlocks(content);
6040
- var clipOutput = (text) => text.slice(0, MAX_OUTPUT_LENGTH);
6041
-
6042
6041
  // packages/events/src/reducers/reduceCheckpoint.ts
6043
6042
  var isUserEntry = (entry) => entry.kind === "user";
6044
6043
  var hasToolResults = (content) => Array.isArray(content) && content.some((block) => block?.type === "tool_result");
@@ -6068,7 +6067,13 @@ var appendNotice = (state, text) => appendEntry(state, { kind: "notice", text })
6068
6067
 
6069
6068
  // packages/events/src/reducers/reduceHistory.ts
6070
6069
  var NO_MATCH_NOTICE = "No earlier prompt matches";
6071
- var listPromptTexts = (state) => state.prompts.map(readPromptText);
6070
+ var MAX_PROMPT_HISTORY = 10;
6071
+ var queuePromptHistory = (entries, text) => text === "" || entries.at(-1) === text ? entries : [...entries, text].slice(-MAX_PROMPT_HISTORY);
6072
+ var listPromptTexts = (state) => state.promptHistory;
6073
+ var loadHistory = (state, entries) => ({
6074
+ ...state,
6075
+ promptHistory: [...entries, ...state.promptHistory].slice(-MAX_PROMPT_HISTORY)
6076
+ });
6072
6077
  function readHistoryText(state) {
6073
6078
  const { history } = state.ui;
6074
6079
  if (!history)
@@ -6076,7 +6081,7 @@ function readHistoryText(state) {
6076
6081
  return listPromptTexts(state)[history.index] ?? history.draft;
6077
6082
  }
6078
6083
  function browsingPosition(state, draft) {
6079
- const size = state.prompts.length;
6084
+ const size = state.promptHistory.length;
6080
6085
  const { history } = state.ui;
6081
6086
  const browsing = history !== null && history.index < size;
6082
6087
  return { size, index: browsing ? history.index : size, draft: browsing ? history.draft : draft };
@@ -6106,20 +6111,11 @@ var agentSessionId = string2();
6106
6111
  var content = sdkValue();
6107
6112
  var agentOptions = sdkValue().optional();
6108
6113
  var effortLevelSchema = _enum(["low", "medium", "high", "xhigh", "max"]);
6109
- var permissionModeSchema = _enum([
6110
- "default",
6111
- "acceptEdits",
6112
- "bypassPermissions",
6113
- "plan",
6114
- "dontAsk",
6115
- "auto"
6116
- ]);
6117
- var DEFAULT_PERMISSION_MODE = "auto";
6114
+ var permissionModeSchema = _enum(["auto", "acceptEdits", "bypassPermissions", "dontAsk"]);
6115
+ var DEFAULT_PERMISSION_MODE = "bypassPermissions";
6118
6116
  var PERMISSION_MODE_LABELS = {
6119
- default: "Default Permission",
6120
6117
  acceptEdits: "Accept Edits Permission",
6121
6118
  bypassPermissions: "Bypass Permissions",
6122
- plan: "Plan Permission",
6123
6119
  dontAsk: "Don't Ask Permission",
6124
6120
  auto: "Automatic Permissions"
6125
6121
  };
@@ -6194,6 +6190,7 @@ var DEFAULT_CONTEXT_WINDOW = 200000;
6194
6190
  var createSessionState = (sessionId) => ({
6195
6191
  sessionId,
6196
6192
  prompts: [],
6193
+ promptHistory: [],
6197
6194
  status: "idle",
6198
6195
  model: "",
6199
6196
  permissionMode: DEFAULT_PERMISSION_MODE,
@@ -6225,7 +6222,14 @@ var createSessionState = (sessionId) => ({
6225
6222
  });
6226
6223
 
6227
6224
  // packages/events/src/reducers/carryOver.ts
6228
- var DEVICE_KEYS = ["theme", "background", "slashCommands", "models", "rateLimits"];
6225
+ var DEVICE_KEYS = [
6226
+ "theme",
6227
+ "background",
6228
+ "slashCommands",
6229
+ "models",
6230
+ "rateLimits",
6231
+ "promptHistory"
6232
+ ];
6229
6233
  var WORKSPACE_KEYS = [
6230
6234
  "model",
6231
6235
  "effort",
@@ -6502,6 +6506,14 @@ function reduceStreamEvent(state, event) {
6502
6506
  }
6503
6507
  }
6504
6508
 
6509
+ // packages/events/src/reducers/reduceTaskProgress.ts
6510
+ function reduceTaskProgress(state, message) {
6511
+ const { tool_use_id: toolUseId, summary } = message;
6512
+ if (!toolUseId || !summary)
6513
+ return state;
6514
+ return updateToolCall(state, toolUseId, (call) => ({ ...call, progress: summary }));
6515
+ }
6516
+
6505
6517
  // packages/events/src/reducers/reduceToolResults.ts
6506
6518
  function applyResult(state, block) {
6507
6519
  if (block.type !== "tool_result" || !block.tool_use_id)
@@ -6521,6 +6533,8 @@ function reduceToolResults(state, message) {
6521
6533
  function reduceAgentMessage(state, message) {
6522
6534
  switch (message.type) {
6523
6535
  case "system":
6536
+ if (message.subtype === "task_progress")
6537
+ return reduceTaskProgress(state, message);
6524
6538
  if (message.subtype !== "init")
6525
6539
  return reduceNoticeMessage(state, message);
6526
6540
  return {
@@ -6651,6 +6665,7 @@ function reducePrompt(state, content, isFollowUp) {
6651
6665
  return {
6652
6666
  ...withUser,
6653
6667
  prompts: [...state.prompts, content],
6668
+ promptHistory: queuePromptHistory(state.promptHistory, readPromptText(content)),
6654
6669
  status: "thinking",
6655
6670
  queuedTurns: isFollowUp ? state.queuedTurns + 1 : state.queuedTurns,
6656
6671
  turnStartedAt: isFollowUp ? state.turnStartedAt ?? state.now : state.now,
@@ -6784,6 +6799,8 @@ function reduceUi(state, event) {
6784
6799
  return moveHistory(state, event.delta, event.draft);
6785
6800
  case "ui.history.searched":
6786
6801
  return searchHistory(state, event.draft);
6802
+ case "ui.history.loaded":
6803
+ return loadHistory(state, event.entries);
6787
6804
  case "ui.history.reset":
6788
6805
  return resetHistory(state);
6789
6806
  case "ui.diff.viewer.moved":
@@ -7801,21 +7818,51 @@ var AGENT_TOOLS = [
7801
7818
  "TodoWrite",
7802
7819
  "AskUserQuestion",
7803
7820
  "Write",
7804
- "Edit"
7821
+ "Edit",
7822
+ "NotebookEdit"
7805
7823
  ];
7806
7824
  var AGENT_PROFILE_OPTIONS = {
7807
7825
  tools: AGENT_TOOLS,
7808
7826
  systemPrompt: ADHD_SYSTEM_PROMPT,
7809
7827
  enableFileCheckpointing: true,
7810
- extraArgs: { "replay-user-messages": null }
7828
+ extraArgs: { "replay-user-messages": null },
7829
+ permissionMode: DEFAULT_PERMISSION_MODE,
7830
+ allowDangerouslySkipPermissions: true,
7831
+ settings: {
7832
+ agentPushNotifEnabled: true,
7833
+ showMessageTimestamps: true,
7834
+ idleCompaction: false,
7835
+ autoCompactEnabled: true,
7836
+ showThinkingSummaries: true,
7837
+ autoMemoryEnabled: false,
7838
+ emojiCompletionEnabled: false,
7839
+ spinnerTipsEnabled: false,
7840
+ disableAgentView: true,
7841
+ allowAllClaudeAiMcps: false,
7842
+ includeGitInstructions: false,
7843
+ syncClaudeAiPlugins: false,
7844
+ syncClaudeAiSkills: false,
7845
+ enableAllProjectMcpServers: true,
7846
+ promptCacheTtl: "1h",
7847
+ subagentPromptCacheTtl: "5m",
7848
+ viewMode: "verbose",
7849
+ attribution: { commit: "", pr: "" }
7850
+ },
7851
+ settingSources: ["user", "project", "local"],
7852
+ skills: "all",
7853
+ agentProgressSummaries: true,
7854
+ includePartialMessages: true,
7855
+ includeHookEvents: true
7811
7856
  };
7812
7857
 
7813
7858
  // packages/agent/src/runtime/buildUserMessage.ts
7859
+ var isSlashCommand = (content) => readPromptText(content).trimStart().startsWith("/");
7814
7860
  var buildUserMessage = (content, priority) => ({
7815
7861
  type: "user",
7816
7862
  message: { role: "user", content },
7817
7863
  parent_tool_use_id: null,
7818
- priority
7864
+ priority,
7865
+ client_composed: isSlashCommand(content) ? undefined : true
7819
7866
  });
7820
7867
 
7821
7868
  // packages/agent/src/session/pickNextModel.ts
@@ -7841,7 +7888,6 @@ class AgentSession {
7841
7888
  this.query = runtime.open({
7842
7889
  prompt: this.input,
7843
7890
  options: {
7844
- includePartialMessages: true,
7845
7891
  ...baseOptions,
7846
7892
  ...AGENT_PROFILE_OPTIONS,
7847
7893
  canUseTool: this.gate.canUseTool
@@ -17616,9 +17662,7 @@ var readEffortColor = (effort) => ({
17616
17662
  max: Theme.status.danger
17617
17663
  })[effort] ?? Theme.fg.secondary;
17618
17664
  var readPermissionColor = (mode) => ({
17619
- default: Theme.fg.primary,
17620
17665
  acceptEdits: Theme.status.success,
17621
- plan: Theme.status.info,
17622
17666
  auto: Theme.accent.highlight,
17623
17667
  dontAsk: Theme.status.warning,
17624
17668
  bypassPermissions: Theme.status.danger
@@ -17758,11 +17802,125 @@ class ListPopover {
17758
17802
  // apps/tui/src/components/promptInput.ts
17759
17803
  import {
17760
17804
  BoxRenderable as BoxRenderable5,
17805
+ SyntaxStyle,
17761
17806
  TextareaRenderable,
17762
17807
  TextRenderable as TextRenderable5
17763
17808
  } from "@opentui/core";
17809
+
17810
+ // apps/tui/src/prompt/pasteAttachments.ts
17811
+ class PasteAttachments {
17812
+ replacements = new Map;
17813
+ images = new Map;
17814
+ counter = 0;
17815
+ addText(text) {
17816
+ const lines = text.split(`
17817
+ `).length;
17818
+ return this.remember(`Pasted text #${this.next()} +${lines} lines`, text);
17819
+ }
17820
+ addFile(path) {
17821
+ const name = path.split("/").at(-1) ?? path;
17822
+ return this.remember(`File #${this.next()} ${name}`, path);
17823
+ }
17824
+ addImage(image) {
17825
+ const placeholder = `[Image #${this.next()}]`;
17826
+ this.images.set(placeholder, image);
17827
+ return placeholder;
17828
+ }
17829
+ compose(text) {
17830
+ let expanded = text;
17831
+ for (const [placeholder, value] of this.replacements) {
17832
+ expanded = expanded.replaceAll(placeholder, () => value);
17833
+ }
17834
+ const images = [...this.images].map(([placeholder, image]) => ({ at: text.indexOf(placeholder), image })).filter(({ at }) => at >= 0).sort((left, right) => left.at - right.at).map(({ image }) => image);
17835
+ return { text: expanded, images };
17836
+ }
17837
+ placeholders() {
17838
+ return [...this.replacements.keys(), ...this.images.keys()];
17839
+ }
17840
+ reset() {
17841
+ this.replacements.clear();
17842
+ this.images.clear();
17843
+ this.counter = 0;
17844
+ }
17845
+ remember(label, value) {
17846
+ const placeholder = `[${label}]`;
17847
+ this.replacements.set(placeholder, value);
17848
+ return placeholder;
17849
+ }
17850
+ next() {
17851
+ this.counter += 1;
17852
+ return this.counter;
17853
+ }
17854
+ }
17855
+
17856
+ // apps/tui/src/prompt/planPaste.ts
17857
+ import { readFileSync } from "fs";
17858
+ import { extname } from "path";
17859
+ import { decodePasteBytes } from "@opentui/core";
17860
+
17861
+ // apps/tui/src/prompt/imageBlock.ts
17862
+ var IMAGE_MEDIA_TYPES = ["image/gif", "image/jpeg", "image/png", "image/webp"];
17863
+ var readImageMediaType = (mediaType) => IMAGE_MEDIA_TYPES.find((known) => known === mediaType);
17864
+ var buildImageBlock = (mediaType, bytes) => ({
17865
+ type: "image",
17866
+ source: { type: "base64", media_type: mediaType, data: Buffer.from(bytes).toString("base64") }
17867
+ });
17868
+
17869
+ // apps/tui/src/prompt/pastedPaths.ts
17870
+ import { statSync } from "fs";
17871
+ import { homedir as homedir6 } from "os";
17872
+ import { isAbsolute } from "path";
17873
+ var unwrapPath = (raw) => raw.trim().replace(/^(['"])(.*)\1$/, "$2").replace(/^file:\/\//, "").replace(/^~(?=\/)/, homedir6()).replace(/\\(.)/g, "$1");
17874
+ var isExistingFile = (path) => {
17875
+ try {
17876
+ return isAbsolute(path) && statSync(path).isFile();
17877
+ } catch {
17878
+ return false;
17879
+ }
17880
+ };
17881
+ function parsePastedPaths(text) {
17882
+ const lines = text.split(`
17883
+ `).map((line) => line.trim()).filter(Boolean);
17884
+ const paths = lines.flatMap((line) => isExistingFile(unwrapPath(line)) ? [line] : line.split(/(?<!\\)\s+/)).map(unwrapPath);
17885
+ return paths.length > 0 && paths.every(isExistingFile) ? paths : [];
17886
+ }
17887
+
17888
+ // apps/tui/src/prompt/planPaste.ts
17889
+ var LONG_PASTE_LINES = 5;
17890
+ var LONG_PASTE_CHARS = 500;
17891
+ var IMAGE_TYPES = {
17892
+ ".png": "image/png",
17893
+ ".jpg": "image/jpeg",
17894
+ ".jpeg": "image/jpeg",
17895
+ ".gif": "image/gif",
17896
+ ".webp": "image/webp"
17897
+ };
17898
+ var isLongPaste = (text) => text.length > LONG_PASTE_CHARS || text.split(`
17899
+ `).length > LONG_PASTE_LINES;
17900
+ function attachPath(path, attachments) {
17901
+ const mediaType = IMAGE_TYPES[extname(path).toLowerCase()];
17902
+ if (!mediaType)
17903
+ return attachments.addFile(path);
17904
+ return attachments.addImage(buildImageBlock(mediaType, readFileSync(path)));
17905
+ }
17906
+ function planPaste(event, attachments) {
17907
+ const mediaType = readImageMediaType(event.metadata?.mimeType);
17908
+ if (mediaType) {
17909
+ return attachments.addImage(buildImageBlock(mediaType, event.bytes));
17910
+ }
17911
+ if (event.metadata?.kind === "binary")
17912
+ return;
17913
+ const text = decodePasteBytes(event.bytes);
17914
+ const paths = parsePastedPaths(text);
17915
+ if (paths.length > 0)
17916
+ return paths.map((path) => attachPath(path, attachments)).join(" ");
17917
+ return isLongPaste(text) ? attachments.addText(text) : undefined;
17918
+ }
17919
+
17920
+ // apps/tui/src/components/promptInput.ts
17764
17921
  var MAX_INPUT_LINES = 8;
17765
17922
  var BUSY_TITLE = " Ctrl+Q (queue) Ctrl+W (steer) ";
17923
+ var ATTACHMENT_STYLE = "attachment";
17766
17924
  var PLACEHOLDER = "Type your prompt, /slash command, or @file reference...";
17767
17925
  var KEY_BINDINGS = [
17768
17926
  { name: "return", action: "submit" },
@@ -17776,6 +17934,9 @@ class PromptInput {
17776
17934
  renderable;
17777
17935
  textarea;
17778
17936
  chevron;
17937
+ attachments = new PasteAttachments;
17938
+ attachmentStyle;
17939
+ attachmentStyleId = 0;
17779
17940
  focused = false;
17780
17941
  constructor(renderer, handlers) {
17781
17942
  this.renderable = new BoxRenderable5(renderer, {
@@ -17787,14 +17948,18 @@ class PromptInput {
17787
17948
  flexShrink: 0,
17788
17949
  paddingLeft: 1
17789
17950
  });
17951
+ this.attachmentStyle = SyntaxStyle.create(renderer.nativeScene);
17790
17952
  this.textarea = new TextareaRenderable(renderer, {
17791
17953
  flexGrow: 1,
17792
17954
  height: 1,
17793
17955
  placeholder: PLACEHOLDER,
17794
17956
  wrapMode: "word",
17795
17957
  keyBindings: KEY_BINDINGS,
17958
+ syntaxStyle: this.attachmentStyle,
17959
+ onPaste: (event) => this.insertPlaceholder(event),
17796
17960
  onSubmit: () => handlers.onSubmit(this.text),
17797
17961
  onContentChange: () => {
17962
+ this.markAttachments();
17798
17963
  this.fitHeight();
17799
17964
  handlers.onChange(this.text, this.textarea.cursorOffset);
17800
17965
  }
@@ -17814,8 +17979,19 @@ class PromptInput {
17814
17979
  this.textarea.setText(text);
17815
17980
  this.textarea.cursorOffset = cursor;
17816
17981
  }
17982
+ compose() {
17983
+ return this.attachments.compose(this.text);
17984
+ }
17985
+ insertPlaceholder(event) {
17986
+ const placeholder = planPaste(event, this.attachments);
17987
+ if (placeholder === undefined)
17988
+ return;
17989
+ event.preventDefault();
17990
+ this.textarea.insertText(placeholder);
17991
+ }
17817
17992
  clear() {
17818
17993
  this.textarea.setText("");
17994
+ this.attachments.reset();
17819
17995
  }
17820
17996
  setFocused(focused) {
17821
17997
  if (focused === this.focused)
@@ -17842,6 +18018,26 @@ class PromptInput {
17842
18018
  this.textarea.focusedTextColor = Theme.fg.primary;
17843
18019
  this.textarea.backgroundColor = Theme.bg.default;
17844
18020
  this.textarea.focusedBackgroundColor = Theme.bg.default;
18021
+ this.attachmentStyleId = this.attachmentStyle.registerStyle(ATTACHMENT_STYLE, {
18022
+ fg: Theme.accent.highlight,
18023
+ bold: true
18024
+ });
18025
+ this.markAttachments();
18026
+ }
18027
+ markAttachments() {
18028
+ const { extmarks } = this.textarea;
18029
+ extmarks.clear();
18030
+ const text = this.text;
18031
+ for (const placeholder of this.attachments.placeholders()) {
18032
+ for (let at = text.indexOf(placeholder);at >= 0; at = text.indexOf(placeholder, at + 1)) {
18033
+ extmarks.create({
18034
+ start: at,
18035
+ end: at + placeholder.length,
18036
+ virtual: true,
18037
+ styleId: this.attachmentStyleId
18038
+ });
18039
+ }
18040
+ }
17845
18041
  }
17846
18042
  fitHeight() {
17847
18043
  const wanted = Math.max(this.textarea.lineCount, this.textarea.virtualLineCount, 1);
@@ -18580,7 +18776,9 @@ var SUMMARY_LENGTH = 80;
18580
18776
  var SUMMARY_KEYS = ["file_path", "path", "command", "pattern", "query", "url", "description"];
18581
18777
  var SEARCH_TOOLS = new Set(["Glob", "Grep"]);
18582
18778
  var summarizeSearch = ({ pattern, path }) => typeof path === "string" ? `${pattern} in ${path}` : String(pattern);
18583
- function summarizeToolCall({ name, input }) {
18779
+ function summarizeToolCall({ name, input, status, progress }) {
18780
+ if (status === "running" && progress)
18781
+ return truncateText(progress, SUMMARY_LENGTH);
18584
18782
  if (SEARCH_TOOLS.has(name) && typeof input.pattern === "string") {
18585
18783
  return truncateText(summarizeSearch(input), SUMMARY_LENGTH);
18586
18784
  }
@@ -18875,6 +19073,8 @@ import {
18875
19073
  StyledText as StyledText11,
18876
19074
  TextRenderable as TextRenderable12
18877
19075
  } from "@opentui/core";
19076
+ var USER_LINES = 10;
19077
+
18878
19078
  class UserMessageView {
18879
19079
  renderable;
18880
19080
  text;
@@ -18903,7 +19103,7 @@ class UserMessageView {
18903
19103
  this.entry = entry;
18904
19104
  this.text.content = new StyledText11([
18905
19105
  bold7(fg12(Theme.accent.brand)("\u276F ")),
18906
- bold7(fg12(Theme.fg.primary)(entry.text))
19106
+ bold7(fg12(Theme.fg.primary)(clipLines(entry.text, USER_LINES)))
18907
19107
  ]);
18908
19108
  this.clock.content = new StyledText11([fg12(Theme.fg.muted)(formatClock(entry.at))]);
18909
19109
  }
@@ -19544,23 +19744,27 @@ var DELIVERY_REQUESTS = {
19544
19744
  steer: "agent.prompt.steer.requested"
19545
19745
  };
19546
19746
  var BUSY_NOTICE = "Agent is busy: Ctrl+Q queues, Ctrl+W steers, Esc Esc interrupts the turn";
19547
- function deliverPrompt(store, content, delivery) {
19747
+ var buildContent = (text, images) => images.length > 0 ? [{ type: "text", text }, ...images] : text;
19748
+ function deliverPrompt(store, content, delivery, images) {
19548
19749
  const busy = isAgentBusy(store);
19549
19750
  if (busy && delivery === "send") {
19550
19751
  store.emit({ type: "ui.notice.posted", text: BUSY_NOTICE });
19551
19752
  return false;
19552
19753
  }
19553
- return requestAgent(store, { type: DELIVERY_REQUESTS[busy ? delivery : "send"], content });
19754
+ return requestAgent(store, {
19755
+ type: DELIVERY_REQUESTS[busy ? delivery : "send"],
19756
+ content: buildContent(content, images)
19757
+ });
19554
19758
  }
19555
- function submitPrompt(store, text, delivery = "send") {
19759
+ function submitPrompt(store, text, delivery = "send", images = []) {
19556
19760
  const content = text.trim();
19557
- if (!content)
19761
+ if (!content && images.length === 0)
19558
19762
  return false;
19559
19763
  if (content.startsWith("/") && !isAgentCommand(store.state, content)) {
19560
19764
  runSlashCommand(store, content);
19561
19765
  return true;
19562
19766
  }
19563
- return deliverPrompt(store, content, delivery);
19767
+ return deliverPrompt(store, content, delivery, images);
19564
19768
  }
19565
19769
  function answerApproval(store, decision, toolUseId) {
19566
19770
  const target = toolUseId ?? findAwaitingCalls(store.state)[0]?.id;
@@ -19593,11 +19797,9 @@ var showHistoryText = (store, editor) => {
19593
19797
  editor.replace(text);
19594
19798
  };
19595
19799
  function routeHistoryKey(key, store, editor) {
19596
- const { ui, prompts } = store.state;
19597
- if (ui.focus !== "input" || key.name !== "up" && key.name !== "down")
19800
+ if (store.state.ui.focus !== "input" || !key.shift)
19598
19801
  return false;
19599
- const starting = key.name === "up" && editor.read() === "" && prompts.length > 0;
19600
- if (!ui.history && !starting)
19802
+ if (key.name !== "up" && key.name !== "down")
19601
19803
  return false;
19602
19804
  const delta = key.name === "up" ? -1 : 1;
19603
19805
  store.emit({ type: "ui.history.moved", delta, draft: editor.read() });
@@ -19917,6 +20119,60 @@ class TurnNotifier {
19917
20119
  }
19918
20120
  }
19919
20121
 
20122
+ // apps/tui/src/prompt/historyFile.ts
20123
+ import { homedir as homedir7 } from "os";
20124
+ import { join as join6 } from "path";
20125
+ var HISTORY_ROOT = join6(homedir7(), ".claudin", "prompts");
20126
+ var buildHistoryPath = (cwd, root = HISTORY_ROOT) => join6(root, cwd.replace(/[^a-zA-Z0-9]/g, "-"), "history.jsonl");
20127
+ var parseLine = (line) => {
20128
+ try {
20129
+ const { text } = JSON.parse(line);
20130
+ return typeof text === "string" && text !== "" ? text : undefined;
20131
+ } catch {
20132
+ return;
20133
+ }
20134
+ };
20135
+ async function readHistoryFile(path) {
20136
+ const content = await Bun.file(path).text().catch(() => "");
20137
+ const entries = content.split(`
20138
+ `).flatMap((line) => parseLine(line) ?? []);
20139
+ return entries.slice(-MAX_PROMPT_HISTORY);
20140
+ }
20141
+ async function writeHistoryFile(path, entries) {
20142
+ const lines = entries.slice(-MAX_PROMPT_HISTORY).map((text) => JSON.stringify({ text }));
20143
+ await Bun.write(path, lines.length > 0 ? `${lines.join(`
20144
+ `)}
20145
+ ` : "");
20146
+ }
20147
+
20148
+ // apps/tui/src/prompt/historySaver.ts
20149
+ var SAVE_DELAY_MS = 400;
20150
+
20151
+ class HistorySaver {
20152
+ path;
20153
+ delayMs;
20154
+ timer;
20155
+ pending;
20156
+ constructor(path, delayMs = SAVE_DELAY_MS) {
20157
+ this.path = path;
20158
+ this.delayMs = delayMs;
20159
+ }
20160
+ schedule(entries, draft) {
20161
+ this.pending = queuePromptHistory(entries, draft.trim());
20162
+ clearTimeout(this.timer);
20163
+ this.timer = setTimeout(() => void this.flush(), this.delayMs);
20164
+ }
20165
+ async flush() {
20166
+ clearTimeout(this.timer);
20167
+ const entries = this.pending;
20168
+ this.pending = undefined;
20169
+ if (entries)
20170
+ await writeHistoryFile(this.path, entries).catch(() => {
20171
+ return;
20172
+ });
20173
+ }
20174
+ }
20175
+
19920
20176
  // apps/tui/src/store/appStore.ts
19921
20177
  class AppStore {
19922
20178
  client;
@@ -19958,8 +20214,8 @@ class AppStore {
19958
20214
  }
19959
20215
 
19960
20216
  // apps/tui/src/theme/syntaxStyle.ts
19961
- import { SyntaxStyle } from "@opentui/core";
19962
- var createSyntaxStyle = (renderer) => SyntaxStyle.fromStyles({
20217
+ import { SyntaxStyle as SyntaxStyle2 } from "@opentui/core";
20218
+ var createSyntaxStyle = (renderer) => SyntaxStyle2.fromStyles({
19963
20219
  default: { fg: Theme.fg.primary },
19964
20220
  "markup.heading": { fg: Theme.accent.brand, bold: true },
19965
20221
  "markup.strong": { fg: Theme.fg.primary, bold: true },
@@ -20002,15 +20258,28 @@ async function startTui(client, options = {}) {
20002
20258
  });
20003
20259
  const diffViewer = new DiffViewerPanel(activeRenderer, syntaxStyle);
20004
20260
  const submitInput = (delivery) => {
20005
- if (!submitPrompt(store, input.text, delivery))
20261
+ const draft = input.compose();
20262
+ if (!submitPrompt(store, draft.text, delivery, draft.images))
20006
20263
  return;
20007
20264
  input.clear();
20008
20265
  if (store.state.ui.history)
20009
20266
  store.emit({ type: "ui.history.reset" });
20010
20267
  };
20268
+ const historyPath = options.historyPath ?? buildHistoryPath(process.cwd());
20269
+ const historySaver = new HistorySaver(historyPath);
20270
+ let destroyed = false;
20271
+ const saveDraft = (text) => {
20272
+ const { ui, promptHistory } = store.state;
20273
+ if (ui.history && text === readHistoryText(store.state))
20274
+ return;
20275
+ historySaver.schedule(promptHistory, text);
20276
+ };
20011
20277
  const input = new PromptInput(activeRenderer, {
20012
20278
  onSubmit: () => submitInput("send"),
20013
- onChange: (text, cursor) => syncAutocomplete(store, text, cursor)
20279
+ onChange: (text, cursor) => {
20280
+ syncAutocomplete(store, text, cursor);
20281
+ saveDraft(text);
20282
+ }
20014
20283
  });
20015
20284
  const root = new BoxRenderable15(activeRenderer, {
20016
20285
  width: "100%",
@@ -20124,9 +20393,15 @@ async function startTui(client, options = {}) {
20124
20393
  activeRenderer.on("destroy", () => {
20125
20394
  clearInterval(clock);
20126
20395
  clearInterval(rateLimitPoll);
20396
+ destroyed = true;
20397
+ historySaver.flush();
20127
20398
  options.onExit?.(store.state.sessionId);
20128
20399
  store.close();
20129
20400
  });
20401
+ readHistoryFile(historyPath).then((entries) => {
20402
+ if (!destroyed)
20403
+ store.emit({ type: "ui.history.loaded", entries });
20404
+ });
20130
20405
  render(store.state);
20131
20406
  input.setFocused(true);
20132
20407
  if (options.attach)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@couralabs/claudin",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "claudin": "claudin.js"