@unikode/cli 1.0.17 → 1.0.19

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 +391 -131
  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;
@@ -889,14 +925,15 @@ function ThemedRoot({ children }) {
889
925
  import { createContext as createContext5, useContext as useContext5, useState as useState5, useCallback as useCallback5 } from "react";
890
926
 
891
927
  // ../shared/src/models.ts
892
- var SUPPORTED_CHAT_MODELS = [
928
+ var STATIC_CHAT_MODELS = [
893
929
  {
894
930
  id: "claude-sonnet-4-6",
895
931
  provider: "anthropic",
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,36 +1004,18 @@ var SUPPORTED_CHAT_MODELS = [
960
1004
  pricing: {
961
1005
  inputUsdPerMillionTokens: 2,
962
1006
  outputUsdPerMillionTokens: 2
963
- }
964
- },
965
- {
966
- id: "gpt-oss:20b-cloud",
967
- provider: "local-ollama",
968
- pricing: {
969
- inputUsdPerMillionTokens: 30,
970
- outputUsdPerMillionTokens: 30
971
- }
972
- },
973
- {
974
- id: "qwen3:4b",
975
- provider: "local-ollama",
976
- pricing: {
977
- inputUsdPerMillionTokens: 30,
978
- outputUsdPerMillionTokens: 30
979
- }
980
- },
981
- {
982
- id: "gpt-oss:120b-cloud",
983
- provider: "local-ollama",
984
- pricing: {
985
- inputUsdPerMillionTokens: 30,
986
- outputUsdPerMillionTokens: 30
987
- }
1007
+ },
1008
+ tools: true
988
1009
  }
989
1010
  ];
1011
+ var SUPPORTED_CHAT_MODELS = [...STATIC_CHAT_MODELS];
990
1012
  function findSupportedChatModel(modelId) {
991
1013
  return SUPPORTED_CHAT_MODELS.find((model) => model.id === modelId);
992
1014
  }
1015
+ function modelSupportsTools(modelId) {
1016
+ const model = findSupportedChatModel(modelId);
1017
+ return model?.tools !== false;
1018
+ }
993
1019
  var DEFAULT_CHAT_MODEL_ID = "gemini-2.5-flash";
994
1020
  // ../shared/src/schemas.ts
995
1021
  import { z } from "zod";
@@ -997,7 +1023,8 @@ import { tool } from "ai";
997
1023
  var Mode = {
998
1024
  BUILD: "BUILD",
999
1025
  PLAN: "PLAN",
1000
- DOCUMENTATION: "DOCUMENTATION"
1026
+ DOCUMENTATION: "DOCUMENTATION",
1027
+ ASK: "ASK"
1001
1028
  };
1002
1029
  var allModes = Object.values(Mode);
1003
1030
  var modeSchema = z.enum(Object.values(Mode));
@@ -1158,6 +1185,13 @@ var buildToolContracts = {
1158
1185
  inputSchema: toolInputSchemas.undoLastEditSchema
1159
1186
  })
1160
1187
  };
1188
+ var confirmationRequiredTools = new Set([
1189
+ "writeFile",
1190
+ "editFile",
1191
+ "terminalTool",
1192
+ "undoLastEditSchema",
1193
+ "writeMarkdownFile"
1194
+ ]);
1161
1195
  // src/providers/prompt-config/index.tsx
1162
1196
  import { jsxDEV as jsxDEV6 } from "@opentui/react/jsx-dev-runtime";
1163
1197
  var PromptConfigContext = createContext5(null);
@@ -1169,11 +1203,35 @@ function usePromptConfig() {
1169
1203
  return value;
1170
1204
  }
1171
1205
  function PromptConfigProvider({ children }) {
1172
- const [mode, setMode] = useState5(Mode.BUILD);
1173
- const [model, setModel] = useState5(DEFAULT_CHAT_MODEL_ID);
1206
+ const toast = useToast();
1207
+ const [model, setModelProp] = useState5(DEFAULT_CHAT_MODEL_ID);
1208
+ const [mode, setModeState] = useState5(() => modelSupportsTools(DEFAULT_CHAT_MODEL_ID) ? Mode.BUILD : Mode.ASK);
1209
+ const setMode = useCallback5((newMode) => {
1210
+ setModeState((prevMode) => {
1211
+ const resolvedMode = typeof newMode === "function" ? newMode(prevMode) : newMode;
1212
+ if (!modelSupportsTools(model)) {
1213
+ toast.show({
1214
+ variant: "error",
1215
+ message: 'this model doesnt support tools, and can only be used in the "ASK" mode !!'
1216
+ });
1217
+ return Mode.ASK;
1218
+ }
1219
+ return resolvedMode;
1220
+ });
1221
+ }, [model, toast]);
1222
+ const setModel = useCallback5((newModel) => {
1223
+ setModelProp(newModel);
1224
+ if (!modelSupportsTools(newModel)) {
1225
+ toast.show({
1226
+ variant: "error",
1227
+ message: 'this model doesnt support tools, and can only be used in the "ASK" mode !!'
1228
+ });
1229
+ setModeState(Mode.ASK);
1230
+ }
1231
+ }, [toast]);
1174
1232
  const toggleMode = useCallback5(() => {
1175
1233
  setMode((m) => m === Mode.BUILD ? Mode.PLAN : Mode.BUILD);
1176
- }, []);
1234
+ }, [setMode]);
1177
1235
  return /* @__PURE__ */ jsxDEV6(PromptConfigContext.Provider, {
1178
1236
  value: {
1179
1237
  mode,
@@ -1789,11 +1847,27 @@ var COMMANDS = [
1789
1847
  name: "models",
1790
1848
  description: "select an AI model for generaftion",
1791
1849
  value: "/models",
1792
- action: (ctx) => {
1850
+ action: async (ctx) => {
1851
+ let modelIds;
1852
+ try {
1853
+ const res = await apiClient.models.$get();
1854
+ if (res.ok) {
1855
+ const data = await res.json();
1856
+ if (Array.isArray(data)) {
1857
+ modelIds = data.map((m) => m.id);
1858
+ } else {
1859
+ modelIds = SUPPORTED_CHAT_MODELS.map((m) => m.id);
1860
+ }
1861
+ } else {
1862
+ modelIds = SUPPORTED_CHAT_MODELS.map((m) => m.id);
1863
+ }
1864
+ } catch {
1865
+ modelIds = SUPPORTED_CHAT_MODELS.map((m) => m.id);
1866
+ }
1793
1867
  ctx.dialog.open({
1794
- title: "Agent Selection",
1868
+ title: "Model Selection",
1795
1869
  children: /* @__PURE__ */ jsxDEV14(ModelsDialogContent, {
1796
- models: SUPPORTED_CHAT_MODELS.map((model) => model.id),
1870
+ models: modelIds,
1797
1871
  onSelectModel: ctx.setModel
1798
1872
  }, undefined, false, undefined, this)
1799
1873
  });
@@ -2092,6 +2166,17 @@ function useCommandMenu() {
2092
2166
 
2093
2167
  // src/components/input-bar.tsx
2094
2168
  import { useNavigate as useNavigate2 } from "react-router";
2169
+
2170
+ // src/lib/textarea-ref.ts
2171
+ var textarea = null;
2172
+ function getInputTextarea() {
2173
+ return textarea;
2174
+ }
2175
+ function setInputTextarea(t) {
2176
+ textarea = t;
2177
+ }
2178
+
2179
+ // src/components/input-bar.tsx
2095
2180
  import { jsxDEV as jsxDEV17 } from "@opentui/react/jsx-dev-runtime";
2096
2181
  var MAX_VISIBLE_MENTIONS = 8;
2097
2182
  var CURRENT_DIRECTORY = process.cwd();
@@ -2324,48 +2409,48 @@ function InputBar({ onSubmit, disabled = false }) {
2324
2409
  }
2325
2410
  }, [closeMentionMenu, push]);
2326
2411
  const handleTextareaContentChange = useCallback11(() => {
2327
- const textarea = textareaRef.current;
2328
- if (!textarea)
2412
+ const textarea2 = textareaRef.current;
2413
+ if (!textarea2)
2329
2414
  return;
2330
- const text = textarea.plainText;
2331
- handleContentChange(textarea.plainText);
2332
- syncMentionMenu(text, textarea.cursorOffset);
2415
+ const text = textarea2.plainText;
2416
+ handleContentChange(textarea2.plainText);
2417
+ syncMentionMenu(text, textarea2.cursorOffset);
2333
2418
  }, [handleContentChange, syncMentionMenu]);
2334
2419
  const handleSubmit = useCallback11(() => {
2335
2420
  if (disabled)
2336
2421
  return;
2337
- const textarea = textareaRef.current;
2338
- if (!textarea)
2422
+ const textarea2 = textareaRef.current;
2423
+ if (!textarea2)
2339
2424
  return;
2340
- const text = textarea.plainText.trim();
2425
+ const text = textarea2.plainText.trim();
2341
2426
  if (text.length === 0)
2342
2427
  return;
2343
2428
  onSubmit(text);
2344
- textarea.setText("");
2429
+ textarea2.setText("");
2345
2430
  }, [disabled, onSubmit]);
2346
2431
  const handleMentionExecute = useCallback11((index) => {
2347
- const textarea = textareaRef.current;
2432
+ const textarea2 = textareaRef.current;
2348
2433
  const mention = activeMentionRef.current;
2349
2434
  const candidate = mentionCandidates[index];
2350
- if (!textarea || !mention || !candidate)
2435
+ if (!textarea2 || !mention || !candidate)
2351
2436
  return;
2352
2437
  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);
2438
+ const nextText = `${textarea2.plainText.slice(0, mention.start)}@${insertion}${textarea2.plainText.slice(mention.end)}`;
2439
+ textarea2.replaceText(nextText);
2440
+ textarea2.cursorOffset = mention.start + insertion.length + 1;
2441
+ syncMentionMenu(nextText, textarea2.cursorOffset);
2357
2442
  }, [mentionCandidates, syncMentionMenu]);
2358
2443
  const handleTextareaCursorChange = useCallback11(() => {
2359
- const textarea = textareaRef.current;
2360
- if (!textarea)
2444
+ const textarea2 = textareaRef.current;
2445
+ if (!textarea2)
2361
2446
  return;
2362
- syncMentionMenu(textarea.plainText, textarea.cursorOffset);
2447
+ syncMentionMenu(textarea2.plainText, textarea2.cursorOffset);
2363
2448
  }, [syncMentionMenu]);
2364
2449
  const handleCommand = useCallback11((command) => {
2365
- const textarea = textareaRef.current;
2366
- if (!textarea || !command)
2450
+ const textarea2 = textareaRef.current;
2451
+ if (!textarea2 || !command)
2367
2452
  return;
2368
- textarea.setText("");
2453
+ textarea2.setText("");
2369
2454
  if (command.action) {
2370
2455
  command.action({
2371
2456
  exit: () => renderer.destroy(),
@@ -2377,7 +2462,7 @@ function InputBar({ onSubmit, disabled = false }) {
2377
2462
  setModel
2378
2463
  });
2379
2464
  } else {
2380
- textarea.insertText(command.value + " ");
2465
+ textarea2.insertText(command.value + " ");
2381
2466
  }
2382
2467
  }, [renderer, toast, dialog, navigate, Mode, setMode, setModel]);
2383
2468
  const handleCommandExecute = useCallback11((index) => {
@@ -2408,10 +2493,10 @@ function InputBar({ onSubmit, disabled = false }) {
2408
2493
  };
2409
2494
  }, [activeMention]);
2410
2495
  useEffect3(() => {
2411
- const textarea = textareaRef.current;
2412
- if (!textarea)
2496
+ const textarea2 = textareaRef.current;
2497
+ if (!textarea2)
2413
2498
  return;
2414
- textarea.onSubmit = () => {
2499
+ textarea2.onSubmit = () => {
2415
2500
  onSubmitRef.current();
2416
2501
  };
2417
2502
  }, []);
@@ -2444,13 +2529,24 @@ function InputBar({ onSubmit, disabled = false }) {
2444
2529
  toggleMode();
2445
2530
  }
2446
2531
  });
2532
+ useKeyboard5((key) => {
2533
+ if (key.name === "f5") {
2534
+ key.preventDefault();
2535
+ const textarea2 = textareaRef.current;
2536
+ if (textarea2 && textarea2.hasSelection()) {
2537
+ const selectedText = textarea2.getSelectedText();
2538
+ renderer.copyToClipboardOSC52(selectedText);
2539
+ toast.show({ variant: "success", message: "Copied to clipboard" });
2540
+ }
2541
+ }
2542
+ });
2447
2543
  useEffect3(() => {
2448
2544
  setResponder("base", () => {
2449
2545
  if (disabled)
2450
2546
  return false;
2451
- const textarea = textareaRef.current;
2452
- if (textarea && textarea.plainText.length > 0) {
2453
- textarea.setText("");
2547
+ const textarea2 = textareaRef.current;
2548
+ if (textarea2 && textarea2.plainText.length > 0) {
2549
+ textarea2.setText("");
2454
2550
  return true;
2455
2551
  }
2456
2552
  return false;
@@ -2494,6 +2590,9 @@ function InputBar({ onSubmit, disabled = false }) {
2494
2590
  });
2495
2591
  }
2496
2592
  });
2593
+ useEffect3(() => {
2594
+ setInputTextarea(textareaRef.current);
2595
+ });
2497
2596
  return /* @__PURE__ */ jsxDEV17("box", {
2498
2597
  width: "100%",
2499
2598
  alignItems: "stretch",
@@ -3061,7 +3160,7 @@ function UserMessage({ message, mode }) {
3061
3160
  // src/components/messages/bot-message.tsx
3062
3161
  import { useMemo as useMemo3 } from "react";
3063
3162
  import prettyMs from "pretty-ms";
3064
- import { SyntaxStyle, TextAttributes as TextAttributes10, getTreeSitterClient, BoxRenderable } from "@opentui/core";
3163
+ import { SyntaxStyle, TextAttributes as TextAttributes11, getTreeSitterClient, BoxRenderable } from "@opentui/core";
3065
3164
 
3066
3165
  // src/components/messages/question-message.tsx
3067
3166
  import { useCallback as useCallback13, useRef as useRef7, useState as useState10 } from "react";
@@ -3150,8 +3249,71 @@ function QuestionMessage({ prompt, onSubmit }) {
3150
3249
  }, undefined, false, undefined, this);
3151
3250
  }
3152
3251
 
3252
+ // src/components/messages/confirmation-message.tsx
3253
+ import { TextAttributes as TextAttributes10 } from "@opentui/core";
3254
+ import { jsxDEV as jsxDEV24 } from "@opentui/react/jsx-dev-runtime";
3255
+ function ConfirmationMessage({ confirmation, onAllow, onDisallow }) {
3256
+ const { colors } = useTheme();
3257
+ return /* @__PURE__ */ jsxDEV24("box", {
3258
+ width: "100%",
3259
+ alignItems: "center",
3260
+ children: /* @__PURE__ */ jsxDEV24("box", {
3261
+ border: ["left"],
3262
+ borderColor: colors.error,
3263
+ width: "100%",
3264
+ children: /* @__PURE__ */ jsxDEV24("box", {
3265
+ flexDirection: "column",
3266
+ gap: 1,
3267
+ paddingX: 2,
3268
+ paddingY: 1,
3269
+ backgroundColor: colors.surface,
3270
+ width: "100%",
3271
+ children: [
3272
+ /* @__PURE__ */ jsxDEV24("text", {
3273
+ attributes: TextAttributes10.BOLD,
3274
+ fg: colors.error,
3275
+ children: "Confirmation required"
3276
+ }, undefined, false, undefined, this),
3277
+ /* @__PURE__ */ jsxDEV24("text", {
3278
+ children: confirmation.summary
3279
+ }, undefined, false, undefined, this),
3280
+ /* @__PURE__ */ jsxDEV24("box", {
3281
+ flexDirection: "row",
3282
+ gap: 2,
3283
+ paddingTop: 1,
3284
+ children: [
3285
+ /* @__PURE__ */ jsxDEV24("box", {
3286
+ border: true,
3287
+ paddingX: 2,
3288
+ paddingY: 0,
3289
+ onMouseDown: () => onAllow(confirmation.id),
3290
+ children: /* @__PURE__ */ jsxDEV24("text", {
3291
+ fg: colors.success,
3292
+ attributes: TextAttributes10.BOLD,
3293
+ children: "Allow"
3294
+ }, undefined, false, undefined, this)
3295
+ }, undefined, false, undefined, this),
3296
+ /* @__PURE__ */ jsxDEV24("box", {
3297
+ border: true,
3298
+ paddingX: 2,
3299
+ paddingY: 0,
3300
+ onMouseDown: () => onDisallow(confirmation.id),
3301
+ children: /* @__PURE__ */ jsxDEV24("text", {
3302
+ fg: colors.error,
3303
+ attributes: TextAttributes10.BOLD,
3304
+ children: "Disallow"
3305
+ }, undefined, false, undefined, this)
3306
+ }, undefined, false, undefined, this)
3307
+ ]
3308
+ }, undefined, true, undefined, this)
3309
+ ]
3310
+ }, undefined, true, undefined, this)
3311
+ }, undefined, false, undefined, this)
3312
+ }, undefined, false, undefined, this);
3313
+ }
3314
+
3153
3315
  // src/components/messages/bot-message.tsx
3154
- import { jsxDEV as jsxDEV24, Fragment as Fragment3 } from "@opentui/react/jsx-dev-runtime";
3316
+ import { jsxDEV as jsxDEV25, Fragment as Fragment3 } from "@opentui/react/jsx-dev-runtime";
3155
3317
  function formatToolName(name) {
3156
3318
  return name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/^./, (c) => c.toUpperCase());
3157
3319
  }
@@ -3186,7 +3348,10 @@ function BotMessage({
3186
3348
  durationMs,
3187
3349
  streaming = false,
3188
3350
  questionPrompts = [],
3189
- onSubmitQuestionAnswer
3351
+ onSubmitQuestionAnswer,
3352
+ pendingConfirmations = [],
3353
+ onAllowConfirmation,
3354
+ onDisallowConfirmation
3190
3355
  }) {
3191
3356
  const { colors } = useTheme();
3192
3357
  const syntaxStyle = useMemo3(() => SyntaxStyle.fromStyles({
@@ -3241,24 +3406,24 @@ function BotMessage({
3241
3406
  return;
3242
3407
  };
3243
3408
  }, [colors]);
3244
- return /* @__PURE__ */ jsxDEV24("box", {
3409
+ return /* @__PURE__ */ jsxDEV25("box", {
3245
3410
  width: "100%",
3246
3411
  alignItems: "center",
3247
3412
  children: [
3248
- groupConsecutiveParts(parts).map((group, i) => /* @__PURE__ */ jsxDEV24("box", {
3413
+ groupConsecutiveParts(parts).map((group, i) => /* @__PURE__ */ jsxDEV25("box", {
3249
3414
  width: "100%",
3250
3415
  paddingTop: i === 0 ? 0 : 1,
3251
3416
  children: group.parts.map((part, j) => {
3252
3417
  if (part.type === "reasoning") {
3253
- return /* @__PURE__ */ jsxDEV24("box", {
3418
+ return /* @__PURE__ */ jsxDEV25("box", {
3254
3419
  border: ["left"],
3255
3420
  borderColor: colors.thinkingBorder,
3256
3421
  width: "100%",
3257
3422
  paddingX: 2,
3258
- children: /* @__PURE__ */ jsxDEV24("text", {
3259
- attributes: TextAttributes10.DIM,
3423
+ children: /* @__PURE__ */ jsxDEV25("text", {
3424
+ attributes: TextAttributes11.DIM,
3260
3425
  children: [
3261
- /* @__PURE__ */ jsxDEV24("em", {
3426
+ /* @__PURE__ */ jsxDEV25("em", {
3262
3427
  fg: colors.thinking,
3263
3428
  children: "Thinking:"
3264
3429
  }, undefined, false, undefined, this),
@@ -3272,23 +3437,34 @@ function BotMessage({
3272
3437
  const toolName = part.type === "dynamic-tool" ? part.toolName : part.type.slice("tool-".length);
3273
3438
  const questionPrompt = toolName === "askQuestionTool" ? questionPrompts.find((prompt) => prompt.toolCallId === part.toolCallId) : undefined;
3274
3439
  if (questionPrompt) {
3275
- return /* @__PURE__ */ jsxDEV24("box", {
3440
+ return /* @__PURE__ */ jsxDEV25("box", {
3276
3441
  width: "100%",
3277
- children: /* @__PURE__ */ jsxDEV24(QuestionMessage, {
3442
+ children: /* @__PURE__ */ jsxDEV25(QuestionMessage, {
3278
3443
  prompt: questionPrompt,
3279
3444
  onSubmit: onSubmitQuestionAnswer ?? (() => {})
3280
3445
  }, undefined, false, undefined, this)
3281
3446
  }, part.toolCallId, false, undefined, this);
3282
3447
  }
3283
- return /* @__PURE__ */ jsxDEV24("box", {
3448
+ const confirmationPrompt = pendingConfirmations?.find((c) => c.toolCallId === part.toolCallId);
3449
+ if (confirmationPrompt) {
3450
+ return /* @__PURE__ */ jsxDEV25("box", {
3451
+ width: "100%",
3452
+ children: /* @__PURE__ */ jsxDEV25(ConfirmationMessage, {
3453
+ confirmation: confirmationPrompt,
3454
+ onAllow: onAllowConfirmation ?? (() => {}),
3455
+ onDisallow: onDisallowConfirmation ?? (() => {})
3456
+ }, undefined, false, undefined, this)
3457
+ }, `confirm-${part.toolCallId}`, false, undefined, this);
3458
+ }
3459
+ return /* @__PURE__ */ jsxDEV25("box", {
3284
3460
  border: ["left"],
3285
3461
  borderColor: colors.thinkingBorder,
3286
3462
  width: "100%",
3287
3463
  paddingX: 2,
3288
- children: /* @__PURE__ */ jsxDEV24("text", {
3289
- attributes: TextAttributes10.DIM,
3464
+ children: /* @__PURE__ */ jsxDEV25("text", {
3465
+ attributes: TextAttributes11.DIM,
3290
3466
  children: [
3291
- /* @__PURE__ */ jsxDEV24("em", {
3467
+ /* @__PURE__ */ jsxDEV25("em", {
3292
3468
  fg: colors.info,
3293
3469
  children: [
3294
3470
  formatToolName(toolName),
@@ -3307,10 +3483,10 @@ function BotMessage({
3307
3483
  if (j > 0)
3308
3484
  return null;
3309
3485
  const fullText = group.parts.filter((p) => p.type === "text").map((p) => p.text).join("");
3310
- return /* @__PURE__ */ jsxDEV24("box", {
3486
+ return /* @__PURE__ */ jsxDEV25("box", {
3311
3487
  paddingX: 3,
3312
3488
  width: "100%",
3313
- children: /* @__PURE__ */ jsxDEV24("markdown", {
3489
+ children: /* @__PURE__ */ jsxDEV25("markdown", {
3314
3490
  content: fullText,
3315
3491
  syntaxStyle,
3316
3492
  streaming,
@@ -3322,44 +3498,44 @@ function BotMessage({
3322
3498
  return null;
3323
3499
  })
3324
3500
  }, group.key, false, undefined, this)),
3325
- /* @__PURE__ */ jsxDEV24("box", {
3501
+ /* @__PURE__ */ jsxDEV25("box", {
3326
3502
  paddingX: 3,
3327
3503
  paddingY: 1,
3328
3504
  gap: 1,
3329
3505
  width: "100%",
3330
- children: /* @__PURE__ */ jsxDEV24("box", {
3506
+ children: /* @__PURE__ */ jsxDEV25("box", {
3331
3507
  flexDirection: "row",
3332
3508
  gap: 2,
3333
3509
  children: [
3334
- /* @__PURE__ */ jsxDEV24("text", {
3510
+ /* @__PURE__ */ jsxDEV25("text", {
3335
3511
  fg: mode === Mode.PLAN ? colors.planMode : colors.primary,
3336
3512
  children: "\u25C9"
3337
3513
  }, undefined, false, undefined, this),
3338
- /* @__PURE__ */ jsxDEV24("box", {
3514
+ /* @__PURE__ */ jsxDEV25("box", {
3339
3515
  flexDirection: "row",
3340
3516
  gap: 1,
3341
3517
  children: [
3342
- /* @__PURE__ */ jsxDEV24("text", {
3518
+ /* @__PURE__ */ jsxDEV25("text", {
3343
3519
  children: mode
3344
3520
  }, undefined, false, undefined, this),
3345
- /* @__PURE__ */ jsxDEV24("text", {
3346
- attributes: TextAttributes10.DIM,
3521
+ /* @__PURE__ */ jsxDEV25("text", {
3522
+ attributes: TextAttributes11.DIM,
3347
3523
  fg: colors.dimSeparator,
3348
3524
  children: "\u203A"
3349
3525
  }, undefined, false, undefined, this),
3350
- /* @__PURE__ */ jsxDEV24("text", {
3351
- attributes: TextAttributes10.DIM,
3526
+ /* @__PURE__ */ jsxDEV25("text", {
3527
+ attributes: TextAttributes11.DIM,
3352
3528
  children: model
3353
3529
  }, undefined, false, undefined, this),
3354
- durationMs != null ? /* @__PURE__ */ jsxDEV24(Fragment3, {
3530
+ durationMs != null ? /* @__PURE__ */ jsxDEV25(Fragment3, {
3355
3531
  children: [
3356
- /* @__PURE__ */ jsxDEV24("text", {
3357
- attributes: TextAttributes10.DIM,
3532
+ /* @__PURE__ */ jsxDEV25("text", {
3533
+ attributes: TextAttributes11.DIM,
3358
3534
  fg: colors.dimSeparator,
3359
3535
  children: "\u203A"
3360
3536
  }, undefined, false, undefined, this),
3361
- /* @__PURE__ */ jsxDEV24("text", {
3362
- attributes: TextAttributes10.DIM,
3537
+ /* @__PURE__ */ jsxDEV25("text", {
3538
+ attributes: TextAttributes11.DIM,
3363
3539
  children: prettyMs(durationMs)
3364
3540
  }, undefined, false, undefined, this)
3365
3541
  ]
@@ -3373,25 +3549,25 @@ function BotMessage({
3373
3549
  }, undefined, true, undefined, this);
3374
3550
  }
3375
3551
  // 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";
3552
+ import { TextAttributes as TextAttributes12 } from "@opentui/core";
3553
+ import { jsxDEV as jsxDEV26 } from "@opentui/react/jsx-dev-runtime";
3378
3554
  function ErrorMessage({ message }) {
3379
3555
  const { colors } = useTheme();
3380
- return /* @__PURE__ */ jsxDEV25("box", {
3556
+ return /* @__PURE__ */ jsxDEV26("box", {
3381
3557
  width: "100%",
3382
3558
  alignItems: "center",
3383
- children: /* @__PURE__ */ jsxDEV25("box", {
3559
+ children: /* @__PURE__ */ jsxDEV26("box", {
3384
3560
  border: ["left"],
3385
3561
  borderColor: colors.error,
3386
3562
  width: "100%",
3387
- children: /* @__PURE__ */ jsxDEV25("box", {
3563
+ children: /* @__PURE__ */ jsxDEV26("box", {
3388
3564
  justifyContent: "center",
3389
3565
  paddingX: 2,
3390
3566
  paddingY: 1,
3391
3567
  backgroundColor: colors.surface,
3392
3568
  width: "100%",
3393
- children: /* @__PURE__ */ jsxDEV25("text", {
3394
- attributes: TextAttributes11.DIM,
3569
+ children: /* @__PURE__ */ jsxDEV26("text", {
3570
+ attributes: TextAttributes12.DIM,
3395
3571
  children: message
3396
3572
  }, undefined, false, undefined, this)
3397
3573
  }, undefined, false, undefined, this)
@@ -3399,7 +3575,7 @@ function ErrorMessage({ message }) {
3399
3575
  }, undefined, false, undefined, this);
3400
3576
  }
3401
3577
  // src/screens/new-session.tsx
3402
- import { jsxDEV as jsxDEV26 } from "@opentui/react/jsx-dev-runtime";
3578
+ import { jsxDEV as jsxDEV27 } from "@opentui/react/jsx-dev-runtime";
3403
3579
  var newSessionStateSchema = z2.object({
3404
3580
  message: z2.string(),
3405
3581
  mode: modeSchema,
@@ -3455,11 +3631,11 @@ function NewSession() {
3455
3631
  }, [state, navigate, toast]);
3456
3632
  if (!state)
3457
3633
  return null;
3458
- return /* @__PURE__ */ jsxDEV26(SessionShell, {
3634
+ return /* @__PURE__ */ jsxDEV27(SessionShell, {
3459
3635
  onSubmit: () => {},
3460
3636
  inputDisabled: true,
3461
3637
  loading: true,
3462
- children: /* @__PURE__ */ jsxDEV26(UserMessage, {
3638
+ children: /* @__PURE__ */ jsxDEV27(UserMessage, {
3463
3639
  message: state.message,
3464
3640
  mode: state.mode
3465
3641
  }, undefined, false, undefined, this)
@@ -3962,6 +4138,7 @@ ${outline.join(`
3962
4138
  // src/hooks/use-chat.ts
3963
4139
  function useChat(sessionId, initialMessages) {
3964
4140
  const [questionPrompts, setQuestionPrompts] = useState11([]);
4141
+ const [pendingConfirmations, setPendingConfirmations] = useState11([]);
3965
4142
  const transport = useMemo5(() => {
3966
4143
  return new DefaultChatTransport({
3967
4144
  api: apiClient.chat.$url().toString(),
@@ -4004,6 +4181,20 @@ function useChat(sessionId, initialMessages) {
4004
4181
  }
4005
4182
  });
4006
4183
  }, [questionPrompts]);
4184
+ const formatToolSummary = (toolName, input) => {
4185
+ switch (toolName) {
4186
+ case "writeFile":
4187
+ return `Create/write file: ${input.path}`;
4188
+ case "editFile":
4189
+ return `Edit file: ${input.path}`;
4190
+ case "terminalTool":
4191
+ return `Run command: ${input.explanation || input.command}`;
4192
+ case "undoLastEditSchema":
4193
+ return `Undo edit: ${input.path}`;
4194
+ default:
4195
+ return `Execute ${toolName}`;
4196
+ }
4197
+ };
4007
4198
  const chat = useAiChat({
4008
4199
  id: sessionId,
4009
4200
  messages: initialMessages,
@@ -4027,6 +4218,17 @@ function useChat(sessionId, initialMessages) {
4027
4218
  return;
4028
4219
  }
4029
4220
  const mode = chat.messages.at(-1)?.metadata?.mode ?? "BUILD";
4221
+ if (confirmationRequiredTools.has(toolCall.toolName)) {
4222
+ const promptId = crypto.randomUUID();
4223
+ setPendingConfirmations((prev) => [...prev, {
4224
+ id: promptId,
4225
+ toolCallId: toolCall.toolCallId,
4226
+ toolName: toolCall.toolName,
4227
+ input: toolCall.input,
4228
+ summary: formatToolSummary(toolCall.toolName, toolCall.input)
4229
+ }]);
4230
+ return;
4231
+ }
4030
4232
  executeLocalTool(toolCall.toolName, toolCall.input, mode).then((output) => chat.addToolOutput({
4031
4233
  tool: toolCall.toolName,
4032
4234
  toolCallId: toolCall.toolCallId,
@@ -4040,6 +4242,27 @@ function useChat(sessionId, initialMessages) {
4040
4242
  },
4041
4243
  sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls
4042
4244
  });
4245
+ const allowConfirmation = useCallback14((confirmationId) => {
4246
+ const confirmation = pendingConfirmations.find((c) => c.id === confirmationId);
4247
+ if (!confirmation)
4248
+ return;
4249
+ setPendingConfirmations((prev) => prev.filter((c) => c.id !== confirmationId));
4250
+ const currentMode = chat.messages.at(-1)?.metadata?.mode ?? "BUILD";
4251
+ 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) }));
4252
+ }, [pendingConfirmations, chat]);
4253
+ const disallowConfirmation = useCallback14((confirmationId) => {
4254
+ const confirmation = pendingConfirmations.find((c) => c.id === confirmationId);
4255
+ if (!confirmation)
4256
+ return;
4257
+ setPendingConfirmations((prev) => prev.filter((c) => c.id !== confirmationId));
4258
+ chat.addToolOutput({
4259
+ tool: confirmation.toolName,
4260
+ toolCallId: confirmation.toolCallId,
4261
+ output: {
4262
+ 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."
4263
+ }
4264
+ });
4265
+ }, [pendingConfirmations, chat]);
4043
4266
  return {
4044
4267
  messages: chat.messages,
4045
4268
  status: chat.status,
@@ -4057,42 +4280,51 @@ function useChat(sessionId, initialMessages) {
4057
4280
  });
4058
4281
  },
4059
4282
  abort: chat.stop,
4060
- interrupt: chat.stop
4283
+ interrupt: chat.stop,
4284
+ pendingConfirmations,
4285
+ allowConfirmation,
4286
+ disallowConfirmation
4061
4287
  };
4062
4288
  }
4063
4289
 
4064
4290
  // src/screens/session.tsx
4065
- import { useKeyboard as useKeyboard6 } from "@opentui/react";
4066
- import { jsxDEV as jsxDEV27 } from "@opentui/react/jsx-dev-runtime";
4291
+ import { useKeyboard as useKeyboard6, useRenderer as useRenderer3 } from "@opentui/react";
4292
+ import { jsxDEV as jsxDEV28 } from "@opentui/react/jsx-dev-runtime";
4067
4293
  var sessionLocationSchema = z3.object({
4068
4294
  session: z3.custom((val) => val != null && typeof val === "object" && ("id" in val)),
4069
4295
  initialPrompt: z3.object({
4070
4296
  message: z3.string(),
4071
4297
  mode: z3.custom(),
4072
- model: z3.custom()
4298
+ model: z3.string()
4073
4299
  }).optional()
4074
4300
  });
4075
4301
  function ChatMessage({
4076
4302
  msg,
4077
4303
  questionPrompts,
4078
4304
  onSubmitQuestionAnswer,
4305
+ pendingConfirmations,
4306
+ onAllowConfirmation,
4307
+ onDisallowConfirmation,
4079
4308
  isStreaming = false
4080
4309
  }) {
4081
4310
  if (msg.role === "user") {
4082
4311
  const text = msg.parts.filter((p) => p.type === "text").map((p) => p.text).join("");
4083
- return /* @__PURE__ */ jsxDEV27(UserMessage, {
4312
+ return /* @__PURE__ */ jsxDEV28(UserMessage, {
4084
4313
  message: text,
4085
4314
  mode: msg.metadata?.mode ?? "BUILD"
4086
4315
  }, undefined, false, undefined, this);
4087
4316
  }
4088
- return /* @__PURE__ */ jsxDEV27(BotMessage, {
4317
+ return /* @__PURE__ */ jsxDEV28(BotMessage, {
4089
4318
  parts: msg.parts,
4090
4319
  model: msg.metadata?.model ?? "unknown",
4091
4320
  mode: msg.metadata?.mode ?? "BUILD",
4092
4321
  durationMs: msg.metadata?.durationMs,
4093
4322
  streaming: isStreaming,
4094
4323
  questionPrompts,
4095
- onSubmitQuestionAnswer
4324
+ onSubmitQuestionAnswer,
4325
+ pendingConfirmations,
4326
+ onAllowConfirmation,
4327
+ onDisallowConfirmation
4096
4328
  }, undefined, false, undefined, this);
4097
4329
  }
4098
4330
  function SessionChat({ session, initialPrompt }) {
@@ -4106,11 +4338,16 @@ function SessionChat({ session, initialPrompt }) {
4106
4338
  error,
4107
4339
  questionPrompts,
4108
4340
  submitQuestionAnswer,
4109
- deactivateQuestionPrompts
4341
+ deactivateQuestionPrompts,
4342
+ pendingConfirmations,
4343
+ allowConfirmation,
4344
+ disallowConfirmation
4110
4345
  } = useChat(session.id, initialMessages);
4111
4346
  const hasSubmittedInitialPromptRef = useRef9(false);
4112
4347
  const { mode, model } = usePromptConfig();
4113
4348
  const { isTopLayer } = useKeyboardLayer();
4349
+ const renderer = useRenderer3();
4350
+ const toast = useToast();
4114
4351
  useEffect5(() => {
4115
4352
  return () => {
4116
4353
  abort();
@@ -4122,6 +4359,25 @@ function SessionChat({ session, initialPrompt }) {
4122
4359
  interrupt();
4123
4360
  }
4124
4361
  });
4362
+ useKeyboard6((key) => {
4363
+ if (key.name === "f5") {
4364
+ key.preventDefault();
4365
+ const inputTextarea = getInputTextarea();
4366
+ if (inputTextarea && inputTextarea.hasSelection()) {
4367
+ renderer.copyToClipboardOSC52(inputTextarea.getSelectedText());
4368
+ toast.show({ variant: "success", message: "Copied to clipboard" });
4369
+ return;
4370
+ }
4371
+ const lastAssistantMessage = messages.findLast((m) => m.role === "assistant");
4372
+ if (lastAssistantMessage) {
4373
+ const text = lastAssistantMessage.parts.filter((p) => p.type === "text").map((p) => p.text).join("");
4374
+ if (text) {
4375
+ renderer.copyToClipboardOSC52(text);
4376
+ toast.show({ variant: "success", message: "Copied last response to clipboard" });
4377
+ }
4378
+ }
4379
+ }
4380
+ });
4125
4381
  useEffect5(() => {
4126
4382
  if (!initialPrompt || hasSubmittedInitialPromptRef.current)
4127
4383
  return;
@@ -4132,24 +4388,28 @@ function SessionChat({ session, initialPrompt }) {
4132
4388
  model: initialPrompt.model
4133
4389
  });
4134
4390
  }, [initialPrompt, submit]);
4135
- return /* @__PURE__ */ jsxDEV27(SessionShell, {
4391
+ return /* @__PURE__ */ jsxDEV28(SessionShell, {
4136
4392
  onSubmit: (text) => {
4137
4393
  deactivateQuestionPrompts();
4138
4394
  submit({ userText: text, mode, model });
4139
4395
  },
4140
4396
  loading: status === "submitted" || status === "streaming",
4141
4397
  interruptible: status === "submitted" || status === "streaming",
4398
+ inputDisabled: status === "submitted" || status === "streaming" || questionPrompts.some((p) => p.isActive) || pendingConfirmations.length > 0,
4142
4399
  children: [
4143
4400
  messages.map((msg, i) => {
4144
4401
  const isLastAssistant = msg.role === "assistant" && i === messages.length - 1;
4145
- return /* @__PURE__ */ jsxDEV27(ChatMessage, {
4402
+ return /* @__PURE__ */ jsxDEV28(ChatMessage, {
4146
4403
  msg,
4147
4404
  questionPrompts,
4148
4405
  onSubmitQuestionAnswer: submitQuestionAnswer,
4406
+ pendingConfirmations,
4407
+ onAllowConfirmation: allowConfirmation,
4408
+ onDisallowConfirmation: disallowConfirmation,
4149
4409
  isStreaming: isLastAssistant && status === "streaming"
4150
4410
  }, msg.id, false, undefined, this);
4151
4411
  }),
4152
- error && /* @__PURE__ */ jsxDEV27(ErrorMessage, {
4412
+ error && /* @__PURE__ */ jsxDEV28(ErrorMessage, {
4153
4413
  message: error.message
4154
4414
  }, undefined, false, undefined, this)
4155
4415
  ]
@@ -4199,34 +4459,34 @@ function Session() {
4199
4459
  };
4200
4460
  }, [id, prefetched, toast, navigate]);
4201
4461
  if (!session) {
4202
- return /* @__PURE__ */ jsxDEV27(SessionShell, {
4462
+ return /* @__PURE__ */ jsxDEV28(SessionShell, {
4203
4463
  onSubmit: () => {},
4204
4464
  inputDisabled: true,
4205
4465
  loading: true
4206
4466
  }, undefined, false, undefined, this);
4207
4467
  }
4208
- return /* @__PURE__ */ jsxDEV27(SessionChat, {
4468
+ return /* @__PURE__ */ jsxDEV28(SessionChat, {
4209
4469
  session,
4210
4470
  initialPrompt: prefetched?.initialPrompt
4211
4471
  }, session.id, false, undefined, this);
4212
4472
  }
4213
4473
 
4214
4474
  // src/index.tsx
4215
- import { jsxDEV as jsxDEV28 } from "@opentui/react/jsx-dev-runtime";
4475
+ import { jsxDEV as jsxDEV29 } from "@opentui/react/jsx-dev-runtime";
4216
4476
  var router = createMemoryRouter([
4217
4477
  {
4218
4478
  path: "/",
4219
- element: /* @__PURE__ */ jsxDEV28(RootLayout, {}, undefined, false, undefined, this),
4479
+ element: /* @__PURE__ */ jsxDEV29(RootLayout, {}, undefined, false, undefined, this),
4220
4480
  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) }
4481
+ { index: true, element: /* @__PURE__ */ jsxDEV29(Home, {}, undefined, false, undefined, this) },
4482
+ { path: "sessions/new", element: /* @__PURE__ */ jsxDEV29(NewSession, {}, undefined, false, undefined, this) },
4483
+ { path: "sessions/:id", element: /* @__PURE__ */ jsxDEV29(Session, {}, undefined, false, undefined, this) },
4484
+ { path: "docs", element: /* @__PURE__ */ jsxDEV29(Docs, {}, undefined, false, undefined, this) }
4225
4485
  ]
4226
4486
  }
4227
4487
  ]);
4228
4488
  function App() {
4229
- return /* @__PURE__ */ jsxDEV28(RouterProvider, {
4489
+ return /* @__PURE__ */ jsxDEV29(RouterProvider, {
4230
4490
  router
4231
4491
  }, undefined, false, undefined, this);
4232
4492
  }
@@ -4234,4 +4494,4 @@ var renderer = await createCliRenderer({
4234
4494
  targetFps: 60,
4235
4495
  exitOnCtrlC: false
4236
4496
  });
4237
- createRoot(renderer).render(/* @__PURE__ */ jsxDEV28(App, {}, undefined, false, undefined, this));
4497
+ 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.19",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "bin": {