@unikode/cli 1.0.17 → 1.0.18

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/dist/index.js +393 -105
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -29,6 +29,40 @@ import { createContext, useContext, useState, useCallback } from "react";
29
29
 
30
30
  // src/theme.ts
31
31
  var THEMES = [
32
+ {
33
+ name: "Mist",
34
+ colors: {
35
+ primary: "#3B82F6",
36
+ planMode: "#F59E0B",
37
+ selection: "#008ca5ff",
38
+ thinking: "#f59f0be5",
39
+ success: "#22C55E",
40
+ error: "#EF4444",
41
+ info: "#8B5CF6",
42
+ background: "#9db2c4b4",
43
+ surface: "#868c90ff",
44
+ dialogSurface: "#75818a29",
45
+ thinkingBorder: "#D97706",
46
+ dimSeparator: "#94A3B8"
47
+ }
48
+ },
49
+ {
50
+ name: "Neon Horizon",
51
+ colors: {
52
+ primary: "#00F0FF",
53
+ planMode: "#FF007F",
54
+ selection: "#393153",
55
+ thinking: "#FFB800",
56
+ success: "#00FF66",
57
+ error: "#FF3333",
58
+ info: "#9D4EDD",
59
+ background: "#0D0E15",
60
+ surface: "#1A1C28",
61
+ dialogSurface: "#131520",
62
+ thinkingBorder: "#5A3E85",
63
+ dimSeparator: "#2A2D44"
64
+ }
65
+ },
32
66
  {
33
67
  name: "Nightfox",
34
68
  colors: {
@@ -746,6 +780,8 @@ function KeyboardLayerProvider({ children }) {
746
780
  }
747
781
  }, []);
748
782
  useKeyboard((key) => {
783
+ if (key.name === "f5")
784
+ return;
749
785
  if (!key.ctrl || key.name !== "c")
750
786
  return;
751
787
  const currentStack = stackRef.current;
@@ -896,7 +932,8 @@ var SUPPORTED_CHAT_MODELS = [
896
932
  pricing: {
897
933
  inputUsdPerMillionTokens: 3,
898
934
  outputUsdPerMillionTokens: 15
899
- }
935
+ },
936
+ tools: true
900
937
  },
901
938
  {
902
939
  id: "claude-haiku-4-5",
@@ -904,7 +941,8 @@ var SUPPORTED_CHAT_MODELS = [
904
941
  pricing: {
905
942
  inputUsdPerMillionTokens: 1,
906
943
  outputUsdPerMillionTokens: 5
907
- }
944
+ },
945
+ tools: true
908
946
  },
909
947
  {
910
948
  id: "claude-opus-4-6",
@@ -912,7 +950,8 @@ var SUPPORTED_CHAT_MODELS = [
912
950
  pricing: {
913
951
  inputUsdPerMillionTokens: 5,
914
952
  outputUsdPerMillionTokens: 25
915
- }
953
+ },
954
+ tools: true
916
955
  },
917
956
  {
918
957
  id: "gpt-5.4",
@@ -920,7 +959,8 @@ var SUPPORTED_CHAT_MODELS = [
920
959
  pricing: {
921
960
  inputUsdPerMillionTokens: 2.5,
922
961
  outputUsdPerMillionTokens: 15
923
- }
962
+ },
963
+ tools: true
924
964
  },
925
965
  {
926
966
  id: "gpt-5.4-mini",
@@ -928,7 +968,8 @@ var SUPPORTED_CHAT_MODELS = [
928
968
  pricing: {
929
969
  inputUsdPerMillionTokens: 0.75,
930
970
  outputUsdPerMillionTokens: 4.5
931
- }
971
+ },
972
+ tools: true
932
973
  },
933
974
  {
934
975
  id: "gpt-5.4-nano",
@@ -936,7 +977,8 @@ var SUPPORTED_CHAT_MODELS = [
936
977
  pricing: {
937
978
  inputUsdPerMillionTokens: 0.2,
938
979
  outputUsdPerMillionTokens: 1.25
939
- }
980
+ },
981
+ tools: true
940
982
  },
941
983
  {
942
984
  id: "gemini-2.5-flash",
@@ -944,7 +986,8 @@ var SUPPORTED_CHAT_MODELS = [
944
986
  pricing: {
945
987
  inputUsdPerMillionTokens: 0.3,
946
988
  outputUsdPerMillionTokens: 1
947
- }
989
+ },
990
+ tools: true
948
991
  },
949
992
  {
950
993
  id: "gemini-3.1-flash-lite",
@@ -952,7 +995,8 @@ var SUPPORTED_CHAT_MODELS = [
952
995
  pricing: {
953
996
  inputUsdPerMillionTokens: 100.25,
954
997
  outputUsdPerMillionTokens: 110.5
955
- }
998
+ },
999
+ tools: true
956
1000
  },
957
1001
  {
958
1002
  id: "ai/smollm2",
@@ -960,7 +1004,26 @@ var SUPPORTED_CHAT_MODELS = [
960
1004
  pricing: {
961
1005
  inputUsdPerMillionTokens: 2,
962
1006
  outputUsdPerMillionTokens: 2
963
- }
1007
+ },
1008
+ tools: true
1009
+ },
1010
+ {
1011
+ id: "gpt-oss:20b",
1012
+ provider: "local-ollama",
1013
+ pricing: {
1014
+ inputUsdPerMillionTokens: 30,
1015
+ outputUsdPerMillionTokens: 30
1016
+ },
1017
+ tools: true
1018
+ },
1019
+ {
1020
+ id: "llama3.1:8b-instruct-fp16",
1021
+ provider: "local-ollama",
1022
+ pricing: {
1023
+ inputUsdPerMillionTokens: 3,
1024
+ outputUsdPerMillionTokens: 3
1025
+ },
1026
+ tools: true
964
1027
  },
965
1028
  {
966
1029
  id: "gpt-oss:20b-cloud",
@@ -968,7 +1031,8 @@ var SUPPORTED_CHAT_MODELS = [
968
1031
  pricing: {
969
1032
  inputUsdPerMillionTokens: 30,
970
1033
  outputUsdPerMillionTokens: 30
971
- }
1034
+ },
1035
+ tools: true
972
1036
  },
973
1037
  {
974
1038
  id: "qwen3:4b",
@@ -976,7 +1040,8 @@ var SUPPORTED_CHAT_MODELS = [
976
1040
  pricing: {
977
1041
  inputUsdPerMillionTokens: 30,
978
1042
  outputUsdPerMillionTokens: 30
979
- }
1043
+ },
1044
+ tools: true
980
1045
  },
981
1046
  {
982
1047
  id: "gpt-oss:120b-cloud",
@@ -984,12 +1049,17 @@ var SUPPORTED_CHAT_MODELS = [
984
1049
  pricing: {
985
1050
  inputUsdPerMillionTokens: 30,
986
1051
  outputUsdPerMillionTokens: 30
987
- }
1052
+ },
1053
+ tools: true
988
1054
  }
989
1055
  ];
990
1056
  function findSupportedChatModel(modelId) {
991
1057
  return SUPPORTED_CHAT_MODELS.find((model) => model.id === modelId);
992
1058
  }
1059
+ function modelSupportsTools(modelId) {
1060
+ const model = findSupportedChatModel(modelId);
1061
+ return model?.tools !== false;
1062
+ }
993
1063
  var DEFAULT_CHAT_MODEL_ID = "gemini-2.5-flash";
994
1064
  // ../shared/src/schemas.ts
995
1065
  import { z } from "zod";
@@ -997,7 +1067,8 @@ import { tool } from "ai";
997
1067
  var Mode = {
998
1068
  BUILD: "BUILD",
999
1069
  PLAN: "PLAN",
1000
- DOCUMENTATION: "DOCUMENTATION"
1070
+ DOCUMENTATION: "DOCUMENTATION",
1071
+ ASK: "ASK"
1001
1072
  };
1002
1073
  var allModes = Object.values(Mode);
1003
1074
  var modeSchema = z.enum(Object.values(Mode));
@@ -1158,6 +1229,13 @@ var buildToolContracts = {
1158
1229
  inputSchema: toolInputSchemas.undoLastEditSchema
1159
1230
  })
1160
1231
  };
1232
+ var confirmationRequiredTools = new Set([
1233
+ "writeFile",
1234
+ "editFile",
1235
+ "terminalTool",
1236
+ "undoLastEditSchema",
1237
+ "writeMarkdownFile"
1238
+ ]);
1161
1239
  // src/providers/prompt-config/index.tsx
1162
1240
  import { jsxDEV as jsxDEV6 } from "@opentui/react/jsx-dev-runtime";
1163
1241
  var PromptConfigContext = createContext5(null);
@@ -1169,11 +1247,35 @@ function usePromptConfig() {
1169
1247
  return value;
1170
1248
  }
1171
1249
  function PromptConfigProvider({ children }) {
1172
- const [mode, setMode] = useState5(Mode.BUILD);
1173
- const [model, setModel] = useState5(DEFAULT_CHAT_MODEL_ID);
1250
+ const toast = useToast();
1251
+ const [model, setModelProp] = useState5(DEFAULT_CHAT_MODEL_ID);
1252
+ const [mode, setModeState] = useState5(() => modelSupportsTools(DEFAULT_CHAT_MODEL_ID) ? Mode.BUILD : Mode.ASK);
1253
+ const setMode = useCallback5((newMode) => {
1254
+ setModeState((prevMode) => {
1255
+ const resolvedMode = typeof newMode === "function" ? newMode(prevMode) : newMode;
1256
+ if (!modelSupportsTools(model)) {
1257
+ toast.show({
1258
+ variant: "error",
1259
+ message: 'this model doesnt support tools, and can only be used in the "ASK" mode !!'
1260
+ });
1261
+ return Mode.ASK;
1262
+ }
1263
+ return resolvedMode;
1264
+ });
1265
+ }, [model, toast]);
1266
+ const setModel = useCallback5((newModel) => {
1267
+ setModelProp(newModel);
1268
+ if (!modelSupportsTools(newModel)) {
1269
+ toast.show({
1270
+ variant: "error",
1271
+ message: 'this model doesnt support tools, and can only be used in the "ASK" mode !!'
1272
+ });
1273
+ setModeState(Mode.ASK);
1274
+ }
1275
+ }, [toast]);
1174
1276
  const toggleMode = useCallback5(() => {
1175
1277
  setMode((m) => m === Mode.BUILD ? Mode.PLAN : Mode.BUILD);
1176
- }, []);
1278
+ }, [setMode]);
1177
1279
  return /* @__PURE__ */ jsxDEV6(PromptConfigContext.Provider, {
1178
1280
  value: {
1179
1281
  mode,
@@ -2092,6 +2194,17 @@ function useCommandMenu() {
2092
2194
 
2093
2195
  // src/components/input-bar.tsx
2094
2196
  import { useNavigate as useNavigate2 } from "react-router";
2197
+
2198
+ // src/lib/textarea-ref.ts
2199
+ var textarea = null;
2200
+ function getInputTextarea() {
2201
+ return textarea;
2202
+ }
2203
+ function setInputTextarea(t) {
2204
+ textarea = t;
2205
+ }
2206
+
2207
+ // src/components/input-bar.tsx
2095
2208
  import { jsxDEV as jsxDEV17 } from "@opentui/react/jsx-dev-runtime";
2096
2209
  var MAX_VISIBLE_MENTIONS = 8;
2097
2210
  var CURRENT_DIRECTORY = process.cwd();
@@ -2324,48 +2437,48 @@ function InputBar({ onSubmit, disabled = false }) {
2324
2437
  }
2325
2438
  }, [closeMentionMenu, push]);
2326
2439
  const handleTextareaContentChange = useCallback11(() => {
2327
- const textarea = textareaRef.current;
2328
- if (!textarea)
2440
+ const textarea2 = textareaRef.current;
2441
+ if (!textarea2)
2329
2442
  return;
2330
- const text = textarea.plainText;
2331
- handleContentChange(textarea.plainText);
2332
- syncMentionMenu(text, textarea.cursorOffset);
2443
+ const text = textarea2.plainText;
2444
+ handleContentChange(textarea2.plainText);
2445
+ syncMentionMenu(text, textarea2.cursorOffset);
2333
2446
  }, [handleContentChange, syncMentionMenu]);
2334
2447
  const handleSubmit = useCallback11(() => {
2335
2448
  if (disabled)
2336
2449
  return;
2337
- const textarea = textareaRef.current;
2338
- if (!textarea)
2450
+ const textarea2 = textareaRef.current;
2451
+ if (!textarea2)
2339
2452
  return;
2340
- const text = textarea.plainText.trim();
2453
+ const text = textarea2.plainText.trim();
2341
2454
  if (text.length === 0)
2342
2455
  return;
2343
2456
  onSubmit(text);
2344
- textarea.setText("");
2457
+ textarea2.setText("");
2345
2458
  }, [disabled, onSubmit]);
2346
2459
  const handleMentionExecute = useCallback11((index) => {
2347
- const textarea = textareaRef.current;
2460
+ const textarea2 = textareaRef.current;
2348
2461
  const mention = activeMentionRef.current;
2349
2462
  const candidate = mentionCandidates[index];
2350
- if (!textarea || !mention || !candidate)
2463
+ if (!textarea2 || !mention || !candidate)
2351
2464
  return;
2352
2465
  const insertion = candidate.kind === "directory" ? candidate.path : `${candidate.path} `;
2353
- const nextText = `${textarea.plainText.slice(0, mention.start)}@${insertion}${textarea.plainText.slice(mention.end)}`;
2354
- textarea.replaceText(nextText);
2355
- textarea.cursorOffset = mention.start + insertion.length + 1;
2356
- syncMentionMenu(nextText, textarea.cursorOffset);
2466
+ const nextText = `${textarea2.plainText.slice(0, mention.start)}@${insertion}${textarea2.plainText.slice(mention.end)}`;
2467
+ textarea2.replaceText(nextText);
2468
+ textarea2.cursorOffset = mention.start + insertion.length + 1;
2469
+ syncMentionMenu(nextText, textarea2.cursorOffset);
2357
2470
  }, [mentionCandidates, syncMentionMenu]);
2358
2471
  const handleTextareaCursorChange = useCallback11(() => {
2359
- const textarea = textareaRef.current;
2360
- if (!textarea)
2472
+ const textarea2 = textareaRef.current;
2473
+ if (!textarea2)
2361
2474
  return;
2362
- syncMentionMenu(textarea.plainText, textarea.cursorOffset);
2475
+ syncMentionMenu(textarea2.plainText, textarea2.cursorOffset);
2363
2476
  }, [syncMentionMenu]);
2364
2477
  const handleCommand = useCallback11((command) => {
2365
- const textarea = textareaRef.current;
2366
- if (!textarea || !command)
2478
+ const textarea2 = textareaRef.current;
2479
+ if (!textarea2 || !command)
2367
2480
  return;
2368
- textarea.setText("");
2481
+ textarea2.setText("");
2369
2482
  if (command.action) {
2370
2483
  command.action({
2371
2484
  exit: () => renderer.destroy(),
@@ -2377,7 +2490,7 @@ function InputBar({ onSubmit, disabled = false }) {
2377
2490
  setModel
2378
2491
  });
2379
2492
  } else {
2380
- textarea.insertText(command.value + " ");
2493
+ textarea2.insertText(command.value + " ");
2381
2494
  }
2382
2495
  }, [renderer, toast, dialog, navigate, Mode, setMode, setModel]);
2383
2496
  const handleCommandExecute = useCallback11((index) => {
@@ -2408,10 +2521,10 @@ function InputBar({ onSubmit, disabled = false }) {
2408
2521
  };
2409
2522
  }, [activeMention]);
2410
2523
  useEffect3(() => {
2411
- const textarea = textareaRef.current;
2412
- if (!textarea)
2524
+ const textarea2 = textareaRef.current;
2525
+ if (!textarea2)
2413
2526
  return;
2414
- textarea.onSubmit = () => {
2527
+ textarea2.onSubmit = () => {
2415
2528
  onSubmitRef.current();
2416
2529
  };
2417
2530
  }, []);
@@ -2444,13 +2557,24 @@ function InputBar({ onSubmit, disabled = false }) {
2444
2557
  toggleMode();
2445
2558
  }
2446
2559
  });
2560
+ useKeyboard5((key) => {
2561
+ if (key.name === "f5") {
2562
+ key.preventDefault();
2563
+ const textarea2 = textareaRef.current;
2564
+ if (textarea2 && textarea2.hasSelection()) {
2565
+ const selectedText = textarea2.getSelectedText();
2566
+ renderer.copyToClipboardOSC52(selectedText);
2567
+ toast.show({ variant: "success", message: "Copied to clipboard" });
2568
+ }
2569
+ }
2570
+ });
2447
2571
  useEffect3(() => {
2448
2572
  setResponder("base", () => {
2449
2573
  if (disabled)
2450
2574
  return false;
2451
- const textarea = textareaRef.current;
2452
- if (textarea && textarea.plainText.length > 0) {
2453
- textarea.setText("");
2575
+ const textarea2 = textareaRef.current;
2576
+ if (textarea2 && textarea2.plainText.length > 0) {
2577
+ textarea2.setText("");
2454
2578
  return true;
2455
2579
  }
2456
2580
  return false;
@@ -2494,6 +2618,9 @@ function InputBar({ onSubmit, disabled = false }) {
2494
2618
  });
2495
2619
  }
2496
2620
  });
2621
+ useEffect3(() => {
2622
+ setInputTextarea(textareaRef.current);
2623
+ });
2497
2624
  return /* @__PURE__ */ jsxDEV17("box", {
2498
2625
  width: "100%",
2499
2626
  alignItems: "stretch",
@@ -3061,7 +3188,7 @@ function UserMessage({ message, mode }) {
3061
3188
  // src/components/messages/bot-message.tsx
3062
3189
  import { useMemo as useMemo3 } from "react";
3063
3190
  import prettyMs from "pretty-ms";
3064
- import { SyntaxStyle, TextAttributes as TextAttributes10, getTreeSitterClient, BoxRenderable } from "@opentui/core";
3191
+ import { SyntaxStyle, TextAttributes as TextAttributes11, getTreeSitterClient, BoxRenderable } from "@opentui/core";
3065
3192
 
3066
3193
  // src/components/messages/question-message.tsx
3067
3194
  import { useCallback as useCallback13, useRef as useRef7, useState as useState10 } from "react";
@@ -3150,8 +3277,71 @@ function QuestionMessage({ prompt, onSubmit }) {
3150
3277
  }, undefined, false, undefined, this);
3151
3278
  }
3152
3279
 
3280
+ // src/components/messages/confirmation-message.tsx
3281
+ import { TextAttributes as TextAttributes10 } from "@opentui/core";
3282
+ import { jsxDEV as jsxDEV24 } from "@opentui/react/jsx-dev-runtime";
3283
+ function ConfirmationMessage({ confirmation, onAllow, onDisallow }) {
3284
+ const { colors } = useTheme();
3285
+ return /* @__PURE__ */ jsxDEV24("box", {
3286
+ width: "100%",
3287
+ alignItems: "center",
3288
+ children: /* @__PURE__ */ jsxDEV24("box", {
3289
+ border: ["left"],
3290
+ borderColor: colors.error,
3291
+ width: "100%",
3292
+ children: /* @__PURE__ */ jsxDEV24("box", {
3293
+ flexDirection: "column",
3294
+ gap: 1,
3295
+ paddingX: 2,
3296
+ paddingY: 1,
3297
+ backgroundColor: colors.surface,
3298
+ width: "100%",
3299
+ children: [
3300
+ /* @__PURE__ */ jsxDEV24("text", {
3301
+ attributes: TextAttributes10.BOLD,
3302
+ fg: colors.error,
3303
+ children: "Confirmation required"
3304
+ }, undefined, false, undefined, this),
3305
+ /* @__PURE__ */ jsxDEV24("text", {
3306
+ children: confirmation.summary
3307
+ }, undefined, false, undefined, this),
3308
+ /* @__PURE__ */ jsxDEV24("box", {
3309
+ flexDirection: "row",
3310
+ gap: 2,
3311
+ paddingTop: 1,
3312
+ children: [
3313
+ /* @__PURE__ */ jsxDEV24("box", {
3314
+ border: true,
3315
+ paddingX: 2,
3316
+ paddingY: 0,
3317
+ onMouseDown: () => onAllow(confirmation.id),
3318
+ children: /* @__PURE__ */ jsxDEV24("text", {
3319
+ fg: colors.success,
3320
+ attributes: TextAttributes10.BOLD,
3321
+ children: "Allow"
3322
+ }, undefined, false, undefined, this)
3323
+ }, undefined, false, undefined, this),
3324
+ /* @__PURE__ */ jsxDEV24("box", {
3325
+ border: true,
3326
+ paddingX: 2,
3327
+ paddingY: 0,
3328
+ onMouseDown: () => onDisallow(confirmation.id),
3329
+ children: /* @__PURE__ */ jsxDEV24("text", {
3330
+ fg: colors.error,
3331
+ attributes: TextAttributes10.BOLD,
3332
+ children: "Disallow"
3333
+ }, undefined, false, undefined, this)
3334
+ }, undefined, false, undefined, this)
3335
+ ]
3336
+ }, undefined, true, undefined, this)
3337
+ ]
3338
+ }, undefined, true, undefined, this)
3339
+ }, undefined, false, undefined, this)
3340
+ }, undefined, false, undefined, this);
3341
+ }
3342
+
3153
3343
  // src/components/messages/bot-message.tsx
3154
- import { jsxDEV as jsxDEV24, Fragment as Fragment3 } from "@opentui/react/jsx-dev-runtime";
3344
+ import { jsxDEV as jsxDEV25, Fragment as Fragment3 } from "@opentui/react/jsx-dev-runtime";
3155
3345
  function formatToolName(name) {
3156
3346
  return name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/^./, (c) => c.toUpperCase());
3157
3347
  }
@@ -3186,7 +3376,10 @@ function BotMessage({
3186
3376
  durationMs,
3187
3377
  streaming = false,
3188
3378
  questionPrompts = [],
3189
- onSubmitQuestionAnswer
3379
+ onSubmitQuestionAnswer,
3380
+ pendingConfirmations = [],
3381
+ onAllowConfirmation,
3382
+ onDisallowConfirmation
3190
3383
  }) {
3191
3384
  const { colors } = useTheme();
3192
3385
  const syntaxStyle = useMemo3(() => SyntaxStyle.fromStyles({
@@ -3241,24 +3434,24 @@ function BotMessage({
3241
3434
  return;
3242
3435
  };
3243
3436
  }, [colors]);
3244
- return /* @__PURE__ */ jsxDEV24("box", {
3437
+ return /* @__PURE__ */ jsxDEV25("box", {
3245
3438
  width: "100%",
3246
3439
  alignItems: "center",
3247
3440
  children: [
3248
- groupConsecutiveParts(parts).map((group, i) => /* @__PURE__ */ jsxDEV24("box", {
3441
+ groupConsecutiveParts(parts).map((group, i) => /* @__PURE__ */ jsxDEV25("box", {
3249
3442
  width: "100%",
3250
3443
  paddingTop: i === 0 ? 0 : 1,
3251
3444
  children: group.parts.map((part, j) => {
3252
3445
  if (part.type === "reasoning") {
3253
- return /* @__PURE__ */ jsxDEV24("box", {
3446
+ return /* @__PURE__ */ jsxDEV25("box", {
3254
3447
  border: ["left"],
3255
3448
  borderColor: colors.thinkingBorder,
3256
3449
  width: "100%",
3257
3450
  paddingX: 2,
3258
- children: /* @__PURE__ */ jsxDEV24("text", {
3259
- attributes: TextAttributes10.DIM,
3451
+ children: /* @__PURE__ */ jsxDEV25("text", {
3452
+ attributes: TextAttributes11.DIM,
3260
3453
  children: [
3261
- /* @__PURE__ */ jsxDEV24("em", {
3454
+ /* @__PURE__ */ jsxDEV25("em", {
3262
3455
  fg: colors.thinking,
3263
3456
  children: "Thinking:"
3264
3457
  }, undefined, false, undefined, this),
@@ -3272,23 +3465,34 @@ function BotMessage({
3272
3465
  const toolName = part.type === "dynamic-tool" ? part.toolName : part.type.slice("tool-".length);
3273
3466
  const questionPrompt = toolName === "askQuestionTool" ? questionPrompts.find((prompt) => prompt.toolCallId === part.toolCallId) : undefined;
3274
3467
  if (questionPrompt) {
3275
- return /* @__PURE__ */ jsxDEV24("box", {
3468
+ return /* @__PURE__ */ jsxDEV25("box", {
3276
3469
  width: "100%",
3277
- children: /* @__PURE__ */ jsxDEV24(QuestionMessage, {
3470
+ children: /* @__PURE__ */ jsxDEV25(QuestionMessage, {
3278
3471
  prompt: questionPrompt,
3279
3472
  onSubmit: onSubmitQuestionAnswer ?? (() => {})
3280
3473
  }, undefined, false, undefined, this)
3281
3474
  }, part.toolCallId, false, undefined, this);
3282
3475
  }
3283
- return /* @__PURE__ */ jsxDEV24("box", {
3476
+ const confirmationPrompt = pendingConfirmations?.find((c) => c.toolCallId === part.toolCallId);
3477
+ if (confirmationPrompt) {
3478
+ return /* @__PURE__ */ jsxDEV25("box", {
3479
+ width: "100%",
3480
+ children: /* @__PURE__ */ jsxDEV25(ConfirmationMessage, {
3481
+ confirmation: confirmationPrompt,
3482
+ onAllow: onAllowConfirmation ?? (() => {}),
3483
+ onDisallow: onDisallowConfirmation ?? (() => {})
3484
+ }, undefined, false, undefined, this)
3485
+ }, `confirm-${part.toolCallId}`, false, undefined, this);
3486
+ }
3487
+ return /* @__PURE__ */ jsxDEV25("box", {
3284
3488
  border: ["left"],
3285
3489
  borderColor: colors.thinkingBorder,
3286
3490
  width: "100%",
3287
3491
  paddingX: 2,
3288
- children: /* @__PURE__ */ jsxDEV24("text", {
3289
- attributes: TextAttributes10.DIM,
3492
+ children: /* @__PURE__ */ jsxDEV25("text", {
3493
+ attributes: TextAttributes11.DIM,
3290
3494
  children: [
3291
- /* @__PURE__ */ jsxDEV24("em", {
3495
+ /* @__PURE__ */ jsxDEV25("em", {
3292
3496
  fg: colors.info,
3293
3497
  children: [
3294
3498
  formatToolName(toolName),
@@ -3307,10 +3511,10 @@ function BotMessage({
3307
3511
  if (j > 0)
3308
3512
  return null;
3309
3513
  const fullText = group.parts.filter((p) => p.type === "text").map((p) => p.text).join("");
3310
- return /* @__PURE__ */ jsxDEV24("box", {
3514
+ return /* @__PURE__ */ jsxDEV25("box", {
3311
3515
  paddingX: 3,
3312
3516
  width: "100%",
3313
- children: /* @__PURE__ */ jsxDEV24("markdown", {
3517
+ children: /* @__PURE__ */ jsxDEV25("markdown", {
3314
3518
  content: fullText,
3315
3519
  syntaxStyle,
3316
3520
  streaming,
@@ -3322,44 +3526,44 @@ function BotMessage({
3322
3526
  return null;
3323
3527
  })
3324
3528
  }, group.key, false, undefined, this)),
3325
- /* @__PURE__ */ jsxDEV24("box", {
3529
+ /* @__PURE__ */ jsxDEV25("box", {
3326
3530
  paddingX: 3,
3327
3531
  paddingY: 1,
3328
3532
  gap: 1,
3329
3533
  width: "100%",
3330
- children: /* @__PURE__ */ jsxDEV24("box", {
3534
+ children: /* @__PURE__ */ jsxDEV25("box", {
3331
3535
  flexDirection: "row",
3332
3536
  gap: 2,
3333
3537
  children: [
3334
- /* @__PURE__ */ jsxDEV24("text", {
3538
+ /* @__PURE__ */ jsxDEV25("text", {
3335
3539
  fg: mode === Mode.PLAN ? colors.planMode : colors.primary,
3336
3540
  children: "\u25C9"
3337
3541
  }, undefined, false, undefined, this),
3338
- /* @__PURE__ */ jsxDEV24("box", {
3542
+ /* @__PURE__ */ jsxDEV25("box", {
3339
3543
  flexDirection: "row",
3340
3544
  gap: 1,
3341
3545
  children: [
3342
- /* @__PURE__ */ jsxDEV24("text", {
3546
+ /* @__PURE__ */ jsxDEV25("text", {
3343
3547
  children: mode
3344
3548
  }, undefined, false, undefined, this),
3345
- /* @__PURE__ */ jsxDEV24("text", {
3346
- attributes: TextAttributes10.DIM,
3549
+ /* @__PURE__ */ jsxDEV25("text", {
3550
+ attributes: TextAttributes11.DIM,
3347
3551
  fg: colors.dimSeparator,
3348
3552
  children: "\u203A"
3349
3553
  }, undefined, false, undefined, this),
3350
- /* @__PURE__ */ jsxDEV24("text", {
3351
- attributes: TextAttributes10.DIM,
3554
+ /* @__PURE__ */ jsxDEV25("text", {
3555
+ attributes: TextAttributes11.DIM,
3352
3556
  children: model
3353
3557
  }, undefined, false, undefined, this),
3354
- durationMs != null ? /* @__PURE__ */ jsxDEV24(Fragment3, {
3558
+ durationMs != null ? /* @__PURE__ */ jsxDEV25(Fragment3, {
3355
3559
  children: [
3356
- /* @__PURE__ */ jsxDEV24("text", {
3357
- attributes: TextAttributes10.DIM,
3560
+ /* @__PURE__ */ jsxDEV25("text", {
3561
+ attributes: TextAttributes11.DIM,
3358
3562
  fg: colors.dimSeparator,
3359
3563
  children: "\u203A"
3360
3564
  }, undefined, false, undefined, this),
3361
- /* @__PURE__ */ jsxDEV24("text", {
3362
- attributes: TextAttributes10.DIM,
3565
+ /* @__PURE__ */ jsxDEV25("text", {
3566
+ attributes: TextAttributes11.DIM,
3363
3567
  children: prettyMs(durationMs)
3364
3568
  }, undefined, false, undefined, this)
3365
3569
  ]
@@ -3373,25 +3577,25 @@ function BotMessage({
3373
3577
  }, undefined, true, undefined, this);
3374
3578
  }
3375
3579
  // src/components/messages/error-message.tsx
3376
- import { TextAttributes as TextAttributes11 } from "@opentui/core";
3377
- import { jsxDEV as jsxDEV25 } from "@opentui/react/jsx-dev-runtime";
3580
+ import { TextAttributes as TextAttributes12 } from "@opentui/core";
3581
+ import { jsxDEV as jsxDEV26 } from "@opentui/react/jsx-dev-runtime";
3378
3582
  function ErrorMessage({ message }) {
3379
3583
  const { colors } = useTheme();
3380
- return /* @__PURE__ */ jsxDEV25("box", {
3584
+ return /* @__PURE__ */ jsxDEV26("box", {
3381
3585
  width: "100%",
3382
3586
  alignItems: "center",
3383
- children: /* @__PURE__ */ jsxDEV25("box", {
3587
+ children: /* @__PURE__ */ jsxDEV26("box", {
3384
3588
  border: ["left"],
3385
3589
  borderColor: colors.error,
3386
3590
  width: "100%",
3387
- children: /* @__PURE__ */ jsxDEV25("box", {
3591
+ children: /* @__PURE__ */ jsxDEV26("box", {
3388
3592
  justifyContent: "center",
3389
3593
  paddingX: 2,
3390
3594
  paddingY: 1,
3391
3595
  backgroundColor: colors.surface,
3392
3596
  width: "100%",
3393
- children: /* @__PURE__ */ jsxDEV25("text", {
3394
- attributes: TextAttributes11.DIM,
3597
+ children: /* @__PURE__ */ jsxDEV26("text", {
3598
+ attributes: TextAttributes12.DIM,
3395
3599
  children: message
3396
3600
  }, undefined, false, undefined, this)
3397
3601
  }, undefined, false, undefined, this)
@@ -3399,7 +3603,7 @@ function ErrorMessage({ message }) {
3399
3603
  }, undefined, false, undefined, this);
3400
3604
  }
3401
3605
  // src/screens/new-session.tsx
3402
- import { jsxDEV as jsxDEV26 } from "@opentui/react/jsx-dev-runtime";
3606
+ import { jsxDEV as jsxDEV27 } from "@opentui/react/jsx-dev-runtime";
3403
3607
  var newSessionStateSchema = z2.object({
3404
3608
  message: z2.string(),
3405
3609
  mode: modeSchema,
@@ -3455,11 +3659,11 @@ function NewSession() {
3455
3659
  }, [state, navigate, toast]);
3456
3660
  if (!state)
3457
3661
  return null;
3458
- return /* @__PURE__ */ jsxDEV26(SessionShell, {
3662
+ return /* @__PURE__ */ jsxDEV27(SessionShell, {
3459
3663
  onSubmit: () => {},
3460
3664
  inputDisabled: true,
3461
3665
  loading: true,
3462
- children: /* @__PURE__ */ jsxDEV26(UserMessage, {
3666
+ children: /* @__PURE__ */ jsxDEV27(UserMessage, {
3463
3667
  message: state.message,
3464
3668
  mode: state.mode
3465
3669
  }, undefined, false, undefined, this)
@@ -3962,6 +4166,7 @@ ${outline.join(`
3962
4166
  // src/hooks/use-chat.ts
3963
4167
  function useChat(sessionId, initialMessages) {
3964
4168
  const [questionPrompts, setQuestionPrompts] = useState11([]);
4169
+ const [pendingConfirmations, setPendingConfirmations] = useState11([]);
3965
4170
  const transport = useMemo5(() => {
3966
4171
  return new DefaultChatTransport({
3967
4172
  api: apiClient.chat.$url().toString(),
@@ -4004,6 +4209,20 @@ function useChat(sessionId, initialMessages) {
4004
4209
  }
4005
4210
  });
4006
4211
  }, [questionPrompts]);
4212
+ const formatToolSummary = (toolName, input) => {
4213
+ switch (toolName) {
4214
+ case "writeFile":
4215
+ return `Create/write file: ${input.path}`;
4216
+ case "editFile":
4217
+ return `Edit file: ${input.path}`;
4218
+ case "terminalTool":
4219
+ return `Run command: ${input.explanation || input.command}`;
4220
+ case "undoLastEditSchema":
4221
+ return `Undo edit: ${input.path}`;
4222
+ default:
4223
+ return `Execute ${toolName}`;
4224
+ }
4225
+ };
4007
4226
  const chat = useAiChat({
4008
4227
  id: sessionId,
4009
4228
  messages: initialMessages,
@@ -4027,6 +4246,17 @@ function useChat(sessionId, initialMessages) {
4027
4246
  return;
4028
4247
  }
4029
4248
  const mode = chat.messages.at(-1)?.metadata?.mode ?? "BUILD";
4249
+ if (confirmationRequiredTools.has(toolCall.toolName)) {
4250
+ const promptId = crypto.randomUUID();
4251
+ setPendingConfirmations((prev) => [...prev, {
4252
+ id: promptId,
4253
+ toolCallId: toolCall.toolCallId,
4254
+ toolName: toolCall.toolName,
4255
+ input: toolCall.input,
4256
+ summary: formatToolSummary(toolCall.toolName, toolCall.input)
4257
+ }]);
4258
+ return;
4259
+ }
4030
4260
  executeLocalTool(toolCall.toolName, toolCall.input, mode).then((output) => chat.addToolOutput({
4031
4261
  tool: toolCall.toolName,
4032
4262
  toolCallId: toolCall.toolCallId,
@@ -4040,6 +4270,27 @@ function useChat(sessionId, initialMessages) {
4040
4270
  },
4041
4271
  sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls
4042
4272
  });
4273
+ const allowConfirmation = useCallback14((confirmationId) => {
4274
+ const confirmation = pendingConfirmations.find((c) => c.id === confirmationId);
4275
+ if (!confirmation)
4276
+ return;
4277
+ setPendingConfirmations((prev) => prev.filter((c) => c.id !== confirmationId));
4278
+ const currentMode = chat.messages.at(-1)?.metadata?.mode ?? "BUILD";
4279
+ executeLocalTool(confirmation.toolName, confirmation.input, currentMode).then((output) => chat.addToolOutput({ tool: confirmation.toolName, toolCallId: confirmation.toolCallId, output })).catch((error) => chat.addToolOutput({ tool: confirmation.toolName, toolCallId: confirmation.toolCallId, state: "output-error", errorText: error instanceof Error ? error.message : String(error) }));
4280
+ }, [pendingConfirmations, chat]);
4281
+ const disallowConfirmation = useCallback14((confirmationId) => {
4282
+ const confirmation = pendingConfirmations.find((c) => c.id === confirmationId);
4283
+ if (!confirmation)
4284
+ return;
4285
+ setPendingConfirmations((prev) => prev.filter((c) => c.id !== confirmationId));
4286
+ chat.addToolOutput({
4287
+ tool: confirmation.toolName,
4288
+ toolCallId: confirmation.toolCallId,
4289
+ output: {
4290
+ SYSTEM_CONSTRAINT: "Action Aborted by User. Permission permanently denied for this specific command string. Do NOT retry this command or repeatedly ask user to allow the action. Search for an alternative tool, modify your approach, or explain the limitation to the user."
4291
+ }
4292
+ });
4293
+ }, [pendingConfirmations, chat]);
4043
4294
  return {
4044
4295
  messages: chat.messages,
4045
4296
  status: chat.status,
@@ -4057,13 +4308,16 @@ function useChat(sessionId, initialMessages) {
4057
4308
  });
4058
4309
  },
4059
4310
  abort: chat.stop,
4060
- interrupt: chat.stop
4311
+ interrupt: chat.stop,
4312
+ pendingConfirmations,
4313
+ allowConfirmation,
4314
+ disallowConfirmation
4061
4315
  };
4062
4316
  }
4063
4317
 
4064
4318
  // src/screens/session.tsx
4065
- import { useKeyboard as useKeyboard6 } from "@opentui/react";
4066
- import { jsxDEV as jsxDEV27 } from "@opentui/react/jsx-dev-runtime";
4319
+ import { useKeyboard as useKeyboard6, useRenderer as useRenderer3 } from "@opentui/react";
4320
+ import { jsxDEV as jsxDEV28 } from "@opentui/react/jsx-dev-runtime";
4067
4321
  var sessionLocationSchema = z3.object({
4068
4322
  session: z3.custom((val) => val != null && typeof val === "object" && ("id" in val)),
4069
4323
  initialPrompt: z3.object({
@@ -4076,23 +4330,29 @@ function ChatMessage({
4076
4330
  msg,
4077
4331
  questionPrompts,
4078
4332
  onSubmitQuestionAnswer,
4333
+ pendingConfirmations,
4334
+ onAllowConfirmation,
4335
+ onDisallowConfirmation,
4079
4336
  isStreaming = false
4080
4337
  }) {
4081
4338
  if (msg.role === "user") {
4082
4339
  const text = msg.parts.filter((p) => p.type === "text").map((p) => p.text).join("");
4083
- return /* @__PURE__ */ jsxDEV27(UserMessage, {
4340
+ return /* @__PURE__ */ jsxDEV28(UserMessage, {
4084
4341
  message: text,
4085
4342
  mode: msg.metadata?.mode ?? "BUILD"
4086
4343
  }, undefined, false, undefined, this);
4087
4344
  }
4088
- return /* @__PURE__ */ jsxDEV27(BotMessage, {
4345
+ return /* @__PURE__ */ jsxDEV28(BotMessage, {
4089
4346
  parts: msg.parts,
4090
4347
  model: msg.metadata?.model ?? "unknown",
4091
4348
  mode: msg.metadata?.mode ?? "BUILD",
4092
4349
  durationMs: msg.metadata?.durationMs,
4093
4350
  streaming: isStreaming,
4094
4351
  questionPrompts,
4095
- onSubmitQuestionAnswer
4352
+ onSubmitQuestionAnswer,
4353
+ pendingConfirmations,
4354
+ onAllowConfirmation,
4355
+ onDisallowConfirmation
4096
4356
  }, undefined, false, undefined, this);
4097
4357
  }
4098
4358
  function SessionChat({ session, initialPrompt }) {
@@ -4106,11 +4366,16 @@ function SessionChat({ session, initialPrompt }) {
4106
4366
  error,
4107
4367
  questionPrompts,
4108
4368
  submitQuestionAnswer,
4109
- deactivateQuestionPrompts
4369
+ deactivateQuestionPrompts,
4370
+ pendingConfirmations,
4371
+ allowConfirmation,
4372
+ disallowConfirmation
4110
4373
  } = useChat(session.id, initialMessages);
4111
4374
  const hasSubmittedInitialPromptRef = useRef9(false);
4112
4375
  const { mode, model } = usePromptConfig();
4113
4376
  const { isTopLayer } = useKeyboardLayer();
4377
+ const renderer = useRenderer3();
4378
+ const toast = useToast();
4114
4379
  useEffect5(() => {
4115
4380
  return () => {
4116
4381
  abort();
@@ -4122,6 +4387,25 @@ function SessionChat({ session, initialPrompt }) {
4122
4387
  interrupt();
4123
4388
  }
4124
4389
  });
4390
+ useKeyboard6((key) => {
4391
+ if (key.name === "f5") {
4392
+ key.preventDefault();
4393
+ const inputTextarea = getInputTextarea();
4394
+ if (inputTextarea && inputTextarea.hasSelection()) {
4395
+ renderer.copyToClipboardOSC52(inputTextarea.getSelectedText());
4396
+ toast.show({ variant: "success", message: "Copied to clipboard" });
4397
+ return;
4398
+ }
4399
+ const lastAssistantMessage = messages.findLast((m) => m.role === "assistant");
4400
+ if (lastAssistantMessage) {
4401
+ const text = lastAssistantMessage.parts.filter((p) => p.type === "text").map((p) => p.text).join("");
4402
+ if (text) {
4403
+ renderer.copyToClipboardOSC52(text);
4404
+ toast.show({ variant: "success", message: "Copied last response to clipboard" });
4405
+ }
4406
+ }
4407
+ }
4408
+ });
4125
4409
  useEffect5(() => {
4126
4410
  if (!initialPrompt || hasSubmittedInitialPromptRef.current)
4127
4411
  return;
@@ -4132,24 +4416,28 @@ function SessionChat({ session, initialPrompt }) {
4132
4416
  model: initialPrompt.model
4133
4417
  });
4134
4418
  }, [initialPrompt, submit]);
4135
- return /* @__PURE__ */ jsxDEV27(SessionShell, {
4419
+ return /* @__PURE__ */ jsxDEV28(SessionShell, {
4136
4420
  onSubmit: (text) => {
4137
4421
  deactivateQuestionPrompts();
4138
4422
  submit({ userText: text, mode, model });
4139
4423
  },
4140
4424
  loading: status === "submitted" || status === "streaming",
4141
4425
  interruptible: status === "submitted" || status === "streaming",
4426
+ inputDisabled: status === "submitted" || status === "streaming" || questionPrompts.some((p) => p.isActive) || pendingConfirmations.length > 0,
4142
4427
  children: [
4143
4428
  messages.map((msg, i) => {
4144
4429
  const isLastAssistant = msg.role === "assistant" && i === messages.length - 1;
4145
- return /* @__PURE__ */ jsxDEV27(ChatMessage, {
4430
+ return /* @__PURE__ */ jsxDEV28(ChatMessage, {
4146
4431
  msg,
4147
4432
  questionPrompts,
4148
4433
  onSubmitQuestionAnswer: submitQuestionAnswer,
4434
+ pendingConfirmations,
4435
+ onAllowConfirmation: allowConfirmation,
4436
+ onDisallowConfirmation: disallowConfirmation,
4149
4437
  isStreaming: isLastAssistant && status === "streaming"
4150
4438
  }, msg.id, false, undefined, this);
4151
4439
  }),
4152
- error && /* @__PURE__ */ jsxDEV27(ErrorMessage, {
4440
+ error && /* @__PURE__ */ jsxDEV28(ErrorMessage, {
4153
4441
  message: error.message
4154
4442
  }, undefined, false, undefined, this)
4155
4443
  ]
@@ -4199,34 +4487,34 @@ function Session() {
4199
4487
  };
4200
4488
  }, [id, prefetched, toast, navigate]);
4201
4489
  if (!session) {
4202
- return /* @__PURE__ */ jsxDEV27(SessionShell, {
4490
+ return /* @__PURE__ */ jsxDEV28(SessionShell, {
4203
4491
  onSubmit: () => {},
4204
4492
  inputDisabled: true,
4205
4493
  loading: true
4206
4494
  }, undefined, false, undefined, this);
4207
4495
  }
4208
- return /* @__PURE__ */ jsxDEV27(SessionChat, {
4496
+ return /* @__PURE__ */ jsxDEV28(SessionChat, {
4209
4497
  session,
4210
4498
  initialPrompt: prefetched?.initialPrompt
4211
4499
  }, session.id, false, undefined, this);
4212
4500
  }
4213
4501
 
4214
4502
  // src/index.tsx
4215
- import { jsxDEV as jsxDEV28 } from "@opentui/react/jsx-dev-runtime";
4503
+ import { jsxDEV as jsxDEV29 } from "@opentui/react/jsx-dev-runtime";
4216
4504
  var router = createMemoryRouter([
4217
4505
  {
4218
4506
  path: "/",
4219
- element: /* @__PURE__ */ jsxDEV28(RootLayout, {}, undefined, false, undefined, this),
4507
+ element: /* @__PURE__ */ jsxDEV29(RootLayout, {}, undefined, false, undefined, this),
4220
4508
  children: [
4221
- { index: true, element: /* @__PURE__ */ jsxDEV28(Home, {}, undefined, false, undefined, this) },
4222
- { path: "sessions/new", element: /* @__PURE__ */ jsxDEV28(NewSession, {}, undefined, false, undefined, this) },
4223
- { path: "sessions/:id", element: /* @__PURE__ */ jsxDEV28(Session, {}, undefined, false, undefined, this) },
4224
- { path: "docs", element: /* @__PURE__ */ jsxDEV28(Docs, {}, undefined, false, undefined, this) }
4509
+ { index: true, element: /* @__PURE__ */ jsxDEV29(Home, {}, undefined, false, undefined, this) },
4510
+ { path: "sessions/new", element: /* @__PURE__ */ jsxDEV29(NewSession, {}, undefined, false, undefined, this) },
4511
+ { path: "sessions/:id", element: /* @__PURE__ */ jsxDEV29(Session, {}, undefined, false, undefined, this) },
4512
+ { path: "docs", element: /* @__PURE__ */ jsxDEV29(Docs, {}, undefined, false, undefined, this) }
4225
4513
  ]
4226
4514
  }
4227
4515
  ]);
4228
4516
  function App() {
4229
- return /* @__PURE__ */ jsxDEV28(RouterProvider, {
4517
+ return /* @__PURE__ */ jsxDEV29(RouterProvider, {
4230
4518
  router
4231
4519
  }, undefined, false, undefined, this);
4232
4520
  }
@@ -4234,4 +4522,4 @@ var renderer = await createCliRenderer({
4234
4522
  targetFps: 60,
4235
4523
  exitOnCtrlC: false
4236
4524
  });
4237
- createRoot(renderer).render(/* @__PURE__ */ jsxDEV28(App, {}, undefined, false, undefined, this));
4525
+ createRoot(renderer).render(/* @__PURE__ */ jsxDEV29(App, {}, undefined, false, undefined, this));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unikode/cli",
3
- "version": "1.0.17",
3
+ "version": "1.0.18",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "bin": {