@pentoshi/clai 2.0.8 → 2.0.9
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.
- package/dist/agent/events.d.ts +1 -1
- package/dist/commands/providers.d.ts +2 -0
- package/dist/commands/providers.js +60 -3
- package/dist/commands/providers.js.map +1 -1
- package/dist/llm/openai.js +1 -1
- package/dist/llm/openai.js.map +1 -1
- package/dist/llm/provider.js +6 -1
- package/dist/llm/provider.js.map +1 -1
- package/dist/repl.js +5 -11
- package/dist/repl.js.map +1 -1
- package/dist/tui/App.js +345 -4
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/components/ConfirmModal.js +20 -5
- package/dist/tui/components/ConfirmModal.js.map +1 -1
- package/dist/tui/state.d.ts +1 -1
- package/package.json +1 -1
package/dist/tui/App.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text, useApp, useInput, useStdin } from "ink";
|
|
3
|
+
import chalk from "chalk";
|
|
3
4
|
import { useCallback, useEffect, useMemo, useReducer, useRef, useState, } from "react";
|
|
4
5
|
import { providerIds } from "../types.js";
|
|
5
6
|
import { assertProvider } from "../llm/provider.js";
|
|
6
7
|
import { getProvider } from "../llm/router.js";
|
|
7
|
-
import { envValue, getProviderSecret, getSearchProviderKey, listProviderStatuses, maskSecret, setProviderSecret, setSecret, } from "../store/keys.js";
|
|
8
|
+
import { envValue, getProviderSecret, getSearchProviderKey, listProviderStatuses, maskSecret, setProviderSecret, setSecret, unsetProviderSecret, } from "../store/keys.js";
|
|
8
9
|
import { setActiveSearchProvider } from "../store/config.js";
|
|
9
10
|
import { searchProviderIds, } from "../tools/web/types.js";
|
|
10
11
|
import { assertSearchProvider, searchProviders, } from "../tools/web/providers/provider.js";
|
|
@@ -838,10 +839,348 @@ export function App({ version, initialMode, provider: initialProvider, initialMo
|
|
|
838
839
|
});
|
|
839
840
|
})().catch((error) => warn(`could not read keys: ${error instanceof Error ? error.message : String(error)}`));
|
|
840
841
|
return true;
|
|
841
|
-
case "/set":
|
|
842
|
-
|
|
842
|
+
case "/set": {
|
|
843
|
+
const parts = arg.split(/\s+/).filter(Boolean);
|
|
844
|
+
const providerVal = parts[0];
|
|
845
|
+
const keyVal = parts[1];
|
|
846
|
+
void (async () => {
|
|
847
|
+
if (!providerVal) {
|
|
848
|
+
const llm = await listProviderStatuses(provider);
|
|
849
|
+
const activeSearch = getConfig().activeSearchProvider;
|
|
850
|
+
const search = await Promise.all(searchProviderIds.map(async (id) => {
|
|
851
|
+
const secret = await getSearchProviderKey(id);
|
|
852
|
+
const keyless = id === "duckduckgo";
|
|
853
|
+
return {
|
|
854
|
+
provider: id,
|
|
855
|
+
configured: keyless || Boolean(secret.value),
|
|
856
|
+
maskedKey: secret.value ? maskSecret(secret.value) : undefined,
|
|
857
|
+
};
|
|
858
|
+
}));
|
|
859
|
+
setOverlay({
|
|
860
|
+
kind: "picker",
|
|
861
|
+
title: "Set API key for provider",
|
|
862
|
+
options: [
|
|
863
|
+
...llm.map((status) => ({
|
|
864
|
+
value: `llm:${status.provider}`,
|
|
865
|
+
label: `${status.provider.padEnd(12)} ${status.configured ? chalk.green("✓ key set") : chalk.red("✗ no key")}${status.active ? " (active)" : ""}`,
|
|
866
|
+
description: status.model,
|
|
867
|
+
})),
|
|
868
|
+
...search.map((status) => ({
|
|
869
|
+
value: `search:${status.provider}`,
|
|
870
|
+
label: `${status.provider.padEnd(12)} ${status.configured ? chalk.green("✓ key set") : chalk.red("✗ no key")}`,
|
|
871
|
+
description: `Search provider${status.provider === activeSearch ? " (active)" : ""}`,
|
|
872
|
+
})),
|
|
873
|
+
],
|
|
874
|
+
onSelect: (val) => {
|
|
875
|
+
setOverlay({ kind: "none" });
|
|
876
|
+
void (async () => {
|
|
877
|
+
const isSearch = val.startsWith("search:");
|
|
878
|
+
const id = val.split(":")[1];
|
|
879
|
+
if (isSearch) {
|
|
880
|
+
const next = id;
|
|
881
|
+
if (next === "duckduckgo") {
|
|
882
|
+
info("duckduckgo is keyless and requires no setup");
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
const secret = await getSearchProviderKey(next);
|
|
886
|
+
if (secret.value) {
|
|
887
|
+
const reset = await new Promise((resolveConfirm) => {
|
|
888
|
+
confirmResolver.current = resolveConfirm;
|
|
889
|
+
dispatch({
|
|
890
|
+
type: "event",
|
|
891
|
+
event: {
|
|
892
|
+
type: "confirm-request",
|
|
893
|
+
id: "c",
|
|
894
|
+
kind: "reset",
|
|
895
|
+
prompt: `${next} already has a key (${maskSecret(secret.value)}). Reset it?`,
|
|
896
|
+
},
|
|
897
|
+
});
|
|
898
|
+
});
|
|
899
|
+
if (!reset) {
|
|
900
|
+
info("cancelled");
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
const key = await requestSecret({
|
|
905
|
+
title: `${next} API key`,
|
|
906
|
+
prompt: `Enter API key for ${next}:`,
|
|
907
|
+
});
|
|
908
|
+
if (!key) {
|
|
909
|
+
info("cancelled");
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
await setSecret("search", next, key.trim());
|
|
913
|
+
info(`saved ${next} ${maskSecret(key.trim())}`);
|
|
914
|
+
}
|
|
915
|
+
else {
|
|
916
|
+
const next = id;
|
|
917
|
+
if (next === "ollama") {
|
|
918
|
+
const key = await requestSecret({
|
|
919
|
+
title: "Ollama host URL",
|
|
920
|
+
prompt: `Enter host URL for Ollama:`,
|
|
921
|
+
});
|
|
922
|
+
if (!key) {
|
|
923
|
+
info("cancelled");
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
updateConfig({ ollamaHost: key.trim() });
|
|
927
|
+
info(`saved ollama host → ${key.trim()}`);
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
const secret = await getProviderSecret(next);
|
|
931
|
+
if (secret.value) {
|
|
932
|
+
const reset = await new Promise((resolveConfirm) => {
|
|
933
|
+
confirmResolver.current = resolveConfirm;
|
|
934
|
+
dispatch({
|
|
935
|
+
type: "event",
|
|
936
|
+
event: {
|
|
937
|
+
type: "confirm-request",
|
|
938
|
+
id: "c",
|
|
939
|
+
kind: "reset",
|
|
940
|
+
prompt: `${next} already has a key (${maskSecret(secret.value)}). Reset it?`,
|
|
941
|
+
},
|
|
942
|
+
});
|
|
943
|
+
});
|
|
944
|
+
if (!reset) {
|
|
945
|
+
info("cancelled");
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
const key = await requestSecret({
|
|
950
|
+
title: `${next} API key`,
|
|
951
|
+
prompt: `Enter API key for ${next}:`,
|
|
952
|
+
});
|
|
953
|
+
if (!key) {
|
|
954
|
+
info("cancelled");
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
if (!getProvider(next).validateKey(key.trim())) {
|
|
958
|
+
warn(`invalid API key format for ${next}`);
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
await setProviderSecret(next, key.trim());
|
|
962
|
+
info(`saved ${next} ${maskSecret(key.trim())}`);
|
|
963
|
+
}
|
|
964
|
+
})();
|
|
965
|
+
},
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
else {
|
|
969
|
+
try {
|
|
970
|
+
const isSearch = ["brave", "tavily", "duckduckgo"].includes(providerVal);
|
|
971
|
+
if (isSearch) {
|
|
972
|
+
const next = providerVal;
|
|
973
|
+
if (next === "duckduckgo") {
|
|
974
|
+
info("duckduckgo is keyless and requires no setup");
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
let key = keyVal;
|
|
978
|
+
if (!key) {
|
|
979
|
+
const secret = await getSearchProviderKey(next);
|
|
980
|
+
if (secret.value) {
|
|
981
|
+
const reset = await new Promise((resolveConfirm) => {
|
|
982
|
+
confirmResolver.current = resolveConfirm;
|
|
983
|
+
dispatch({
|
|
984
|
+
type: "event",
|
|
985
|
+
event: {
|
|
986
|
+
type: "confirm-request",
|
|
987
|
+
id: "c",
|
|
988
|
+
kind: "reset",
|
|
989
|
+
prompt: `${next} already has a key (${maskSecret(secret.value)}). Reset it?`,
|
|
990
|
+
},
|
|
991
|
+
});
|
|
992
|
+
});
|
|
993
|
+
if (!reset) {
|
|
994
|
+
info("cancelled");
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
key = await requestSecret({
|
|
999
|
+
title: `${next} API key`,
|
|
1000
|
+
prompt: `Enter API key for ${next}:`,
|
|
1001
|
+
});
|
|
1002
|
+
if (!key) {
|
|
1003
|
+
info("cancelled");
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
await setSecret("search", next, key.trim());
|
|
1008
|
+
info(`saved ${next} ${maskSecret(key.trim())}`);
|
|
1009
|
+
}
|
|
1010
|
+
else {
|
|
1011
|
+
const next = assertProvider(providerVal);
|
|
1012
|
+
if (next === "ollama") {
|
|
1013
|
+
let key = keyVal;
|
|
1014
|
+
if (!key) {
|
|
1015
|
+
key = await requestSecret({
|
|
1016
|
+
title: "Ollama host URL",
|
|
1017
|
+
prompt: `Enter host URL for Ollama:`,
|
|
1018
|
+
});
|
|
1019
|
+
if (!key) {
|
|
1020
|
+
info("cancelled");
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
updateConfig({ ollamaHost: key.trim() });
|
|
1025
|
+
info(`saved ollama host → ${key.trim()}`);
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
let key = keyVal;
|
|
1029
|
+
if (!key) {
|
|
1030
|
+
const secret = await getProviderSecret(next);
|
|
1031
|
+
if (secret.value) {
|
|
1032
|
+
const reset = await new Promise((resolveConfirm) => {
|
|
1033
|
+
confirmResolver.current = resolveConfirm;
|
|
1034
|
+
dispatch({
|
|
1035
|
+
type: "event",
|
|
1036
|
+
event: {
|
|
1037
|
+
type: "confirm-request",
|
|
1038
|
+
id: "c",
|
|
1039
|
+
kind: "reset",
|
|
1040
|
+
prompt: `${next} already has a key (${maskSecret(secret.value)}). Reset it?`,
|
|
1041
|
+
},
|
|
1042
|
+
});
|
|
1043
|
+
});
|
|
1044
|
+
if (!reset) {
|
|
1045
|
+
info("cancelled");
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
key = await requestSecret({
|
|
1050
|
+
title: `${next} API key`,
|
|
1051
|
+
prompt: `Enter API key for ${next}:`,
|
|
1052
|
+
});
|
|
1053
|
+
if (!key) {
|
|
1054
|
+
info("cancelled");
|
|
1055
|
+
return;
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
if (!getProvider(next).validateKey(key.trim())) {
|
|
1059
|
+
warn(`invalid API key format for ${next}`);
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
await setProviderSecret(next, key.trim());
|
|
1063
|
+
info(`saved ${next} ${maskSecret(key.trim())}`);
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
catch (e) {
|
|
1067
|
+
warn(e.message);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
})();
|
|
1071
|
+
return true;
|
|
1072
|
+
}
|
|
1073
|
+
case "/unset": {
|
|
1074
|
+
const parts = arg.split(/\s+/).filter(Boolean);
|
|
1075
|
+
const providerVal = parts[0];
|
|
1076
|
+
void (async () => {
|
|
1077
|
+
if (!providerVal) {
|
|
1078
|
+
const llm = await listProviderStatuses(provider);
|
|
1079
|
+
const activeSearch = getConfig().activeSearchProvider;
|
|
1080
|
+
const search = await Promise.all(searchProviderIds.map(async (id) => {
|
|
1081
|
+
const secret = await getSearchProviderKey(id);
|
|
1082
|
+
const keyless = id === "duckduckgo";
|
|
1083
|
+
return {
|
|
1084
|
+
provider: id,
|
|
1085
|
+
configured: keyless || Boolean(secret.value),
|
|
1086
|
+
maskedKey: secret.value ? maskSecret(secret.value) : undefined,
|
|
1087
|
+
};
|
|
1088
|
+
}));
|
|
1089
|
+
setOverlay({
|
|
1090
|
+
kind: "picker",
|
|
1091
|
+
title: "Unset API key for provider",
|
|
1092
|
+
options: [
|
|
1093
|
+
...llm.map((status) => ({
|
|
1094
|
+
value: `llm:${status.provider}`,
|
|
1095
|
+
label: `${status.provider.padEnd(12)} ${status.configured ? chalk.green("✓ ") + (status.maskedKey ?? "key set") : chalk.red("✗ no key")}${status.active ? " (active)" : ""}`,
|
|
1096
|
+
description: status.model,
|
|
1097
|
+
})),
|
|
1098
|
+
...search.map((status) => ({
|
|
1099
|
+
value: `search:${status.provider}`,
|
|
1100
|
+
label: `${status.provider.padEnd(12)} ${status.configured ? chalk.green("✓ ") + (status.maskedKey ?? "keyless") : chalk.red("✗ no key")}`,
|
|
1101
|
+
description: `Search provider${status.provider === activeSearch ? " (active)" : ""}`,
|
|
1102
|
+
})),
|
|
1103
|
+
],
|
|
1104
|
+
onSelect: (val) => {
|
|
1105
|
+
setOverlay({ kind: "none" });
|
|
1106
|
+
void (async () => {
|
|
1107
|
+
const isSearch = val.startsWith("search:");
|
|
1108
|
+
const id = val.split(":")[1];
|
|
1109
|
+
if (isSearch) {
|
|
1110
|
+
const next = id;
|
|
1111
|
+
if (next === "duckduckgo") {
|
|
1112
|
+
info("duckduckgo requires no credentials and cannot be unset");
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
1115
|
+
const secret = await getSearchProviderKey(next);
|
|
1116
|
+
if (!secret.value) {
|
|
1117
|
+
warn(`${next} has no key to unset`);
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
const { unsetSearchProviderKey } = await import("../commands/search-providers.js");
|
|
1121
|
+
await unsetSearchProviderKey(next);
|
|
1122
|
+
info(`unset ${next}`);
|
|
1123
|
+
}
|
|
1124
|
+
else {
|
|
1125
|
+
const next = id;
|
|
1126
|
+
if (next === "ollama") {
|
|
1127
|
+
info("ollama does not store an API key");
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
const secret = await getProviderSecret(next);
|
|
1131
|
+
if (!secret.value) {
|
|
1132
|
+
warn(`${next} has no key to unset`);
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
await unsetProviderSecret(next);
|
|
1136
|
+
info(`unset ${next}`);
|
|
1137
|
+
}
|
|
1138
|
+
})();
|
|
1139
|
+
},
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
else {
|
|
1143
|
+
try {
|
|
1144
|
+
const isSearch = ["brave", "tavily", "duckduckgo"].includes(providerVal);
|
|
1145
|
+
if (isSearch) {
|
|
1146
|
+
const next = providerVal;
|
|
1147
|
+
if (next === "duckduckgo") {
|
|
1148
|
+
info("duckduckgo requires no credentials and cannot be unset");
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
const secret = await getSearchProviderKey(next);
|
|
1152
|
+
if (!secret.value) {
|
|
1153
|
+
warn(`${next} has no key to unset`);
|
|
1154
|
+
return;
|
|
1155
|
+
}
|
|
1156
|
+
const { unsetSearchProviderKey } = await import("../commands/search-providers.js");
|
|
1157
|
+
await unsetSearchProviderKey(next);
|
|
1158
|
+
info(`unset ${next}`);
|
|
1159
|
+
}
|
|
1160
|
+
else {
|
|
1161
|
+
const next = assertProvider(providerVal);
|
|
1162
|
+
if (next === "ollama") {
|
|
1163
|
+
info("ollama does not store an API key");
|
|
1164
|
+
return;
|
|
1165
|
+
}
|
|
1166
|
+
const secret = await getProviderSecret(next);
|
|
1167
|
+
if (!secret.value) {
|
|
1168
|
+
warn(`${next} has no key to unset`);
|
|
1169
|
+
return;
|
|
1170
|
+
}
|
|
1171
|
+
await unsetProviderSecret(next);
|
|
1172
|
+
info(`unset ${next}`);
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
catch (e) {
|
|
1176
|
+
warn(e.message);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
})();
|
|
1180
|
+
return true;
|
|
1181
|
+
}
|
|
843
1182
|
case "/update":
|
|
844
|
-
info(`${cmd} manages
|
|
1183
|
+
info(`${cmd} manages updates; use the equivalent \`clai update\` command outside the TUI`);
|
|
845
1184
|
return true;
|
|
846
1185
|
case "/help":
|
|
847
1186
|
setOverlay({
|
|
@@ -886,6 +1225,8 @@ export function App({ version, initialMode, provider: initialProvider, initialMo
|
|
|
886
1225
|
openToolOutput,
|
|
887
1226
|
setReasoning,
|
|
888
1227
|
state.items,
|
|
1228
|
+
requestSecret,
|
|
1229
|
+
setOverlay,
|
|
889
1230
|
]);
|
|
890
1231
|
const submitText = useCallback((text) => {
|
|
891
1232
|
const trimmed = text.trim();
|