cohvu 2.12.9 → 2.14.0
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/api.d.ts +47 -0
- package/dist/api.js +26 -0
- package/dist/api.js.map +1 -1
- package/dist/instructions.d.ts +2 -2
- package/dist/instructions.js +136 -51
- package/dist/instructions.js.map +1 -1
- package/dist/tui/App.js +374 -14
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/components/Footer.js +1 -1
- package/dist/tui/components/Footer.js.map +1 -1
- package/dist/tui/components/Modal.js +66 -4
- package/dist/tui/components/Modal.js.map +1 -1
- package/dist/tui/components/TabBar.js +1 -0
- package/dist/tui/components/TabBar.js.map +1 -1
- package/dist/tui/state.d.ts +72 -2
- package/dist/tui/state.js +45 -1
- package/dist/tui/state.js.map +1 -1
- package/dist/tui/tabs/KeysTab.d.ts +5 -0
- package/dist/tui/tabs/KeysTab.js +43 -0
- package/dist/tui/tabs/KeysTab.js.map +1 -0
- package/dist/tui/tabs/KnowledgeTab.js +3 -3
- package/dist/tui/tabs/KnowledgeTab.js.map +1 -1
- package/dist/tui/tabs/ProjectTab.js +6 -3
- package/dist/tui/tabs/ProjectTab.js.map +1 -1
- package/package.json +1 -1
package/dist/tui/App.js
CHANGED
|
@@ -18,7 +18,9 @@ import { KnowledgeTab } from './tabs/KnowledgeTab.js';
|
|
|
18
18
|
import { TeamTab } from './tabs/TeamTab.js';
|
|
19
19
|
import { BillingTab } from './tabs/BillingTab.js';
|
|
20
20
|
import { ProjectTab } from './tabs/ProjectTab.js';
|
|
21
|
+
import { KeysTab } from './tabs/KeysTab.js';
|
|
21
22
|
import { YouTab } from './tabs/YouTab.js';
|
|
23
|
+
import { KNOWN_TOOL_NAMES } from './state.js';
|
|
22
24
|
import { daysUntil, timeUntil } from './utils.js';
|
|
23
25
|
import { exec, execFile } from 'child_process';
|
|
24
26
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync, watch } from 'fs';
|
|
@@ -219,6 +221,29 @@ export default function App() {
|
|
|
219
221
|
}
|
|
220
222
|
break;
|
|
221
223
|
}
|
|
224
|
+
case 'project': {
|
|
225
|
+
// Linked siblings (explicit + same-team computed at query time)
|
|
226
|
+
const links = await api.listProjectLinks(projectId).catch(() => []);
|
|
227
|
+
if (stateRef.current.activeProjectId !== projectId)
|
|
228
|
+
break;
|
|
229
|
+
dispatch({ type: 'SET_PROJECT_LINKS', links });
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
case 'keys': {
|
|
233
|
+
dispatch({ type: 'SET_API_KEYS_LOADING', loading: true });
|
|
234
|
+
// Session keys are user-scoped (no project filter). Integration keys
|
|
235
|
+
// require admin on the active project — skip the call if not, since
|
|
236
|
+
// the API will 403 anyway and we'd rather not show a toast for that.
|
|
237
|
+
const sessionPromise = api.listSessionKeys().catch(() => []);
|
|
238
|
+
const integrationPromise = (s.userRole === 'admin')
|
|
239
|
+
? api.listIntegrationKeys(projectId).catch(() => [])
|
|
240
|
+
: Promise.resolve([]);
|
|
241
|
+
const [sessionKeys, integrationKeys] = await Promise.all([sessionPromise, integrationPromise]);
|
|
242
|
+
if (stateRef.current.activeProjectId !== projectId)
|
|
243
|
+
break;
|
|
244
|
+
dispatch({ type: 'SET_API_KEYS', keys: [...sessionKeys, ...integrationKeys] });
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
222
247
|
}
|
|
223
248
|
}
|
|
224
249
|
catch {
|
|
@@ -501,7 +526,7 @@ export default function App() {
|
|
|
501
526
|
}
|
|
502
527
|
// Number keys switch tabs (but not when typing in search mode)
|
|
503
528
|
const tabIdx = parseInt(input, 10);
|
|
504
|
-
if (tabIdx >= 1 && tabIdx <=
|
|
529
|
+
if (tabIdx >= 1 && tabIdx <= TABS.length && !key.ctrl && !key.meta && !(s.tab === 'knowledge' && s.knowledgeMode !== 'browse')) {
|
|
505
530
|
dispatch({ type: 'SWITCH_TAB', tab: TABS[tabIdx - 1] });
|
|
506
531
|
setTimeout(() => loadTabData(), 0);
|
|
507
532
|
return;
|
|
@@ -525,11 +550,38 @@ export default function App() {
|
|
|
525
550
|
case 'project':
|
|
526
551
|
await handleProjectKey(input, key);
|
|
527
552
|
break;
|
|
553
|
+
case 'keys':
|
|
554
|
+
await handleKeysKey(input, key);
|
|
555
|
+
break;
|
|
528
556
|
case 'you':
|
|
529
557
|
await handleYouKey(input, key);
|
|
530
558
|
break;
|
|
531
559
|
}
|
|
532
560
|
}
|
|
561
|
+
// ---- Keys tab keys ----
|
|
562
|
+
async function handleKeysKey(input, key) {
|
|
563
|
+
const s = stateRef.current;
|
|
564
|
+
if (key.upArrow) {
|
|
565
|
+
dispatch({ type: 'SET_API_KEYS_SELECTED', index: Math.max(0, s.apiKeysSelected - 1) });
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
if (key.downArrow) {
|
|
569
|
+
const max = Math.max(0, s.apiKeys.length - 1);
|
|
570
|
+
dispatch({ type: 'SET_API_KEYS_SELECTED', index: Math.min(max, s.apiKeysSelected + 1) });
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
if (input === 'n') {
|
|
574
|
+
dispatch({ type: 'OPEN_MODAL', modal: { kind: 'select-key-kind', selected: 0 } });
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
if (input === 'x' && s.apiKeys.length > 0) {
|
|
578
|
+
const target = s.apiKeys[s.apiKeysSelected];
|
|
579
|
+
if (target) {
|
|
580
|
+
dispatch({ type: 'OPEN_MODAL', modal: { kind: 'confirm-revoke-key', keyId: target.id, keyName: target.name } });
|
|
581
|
+
}
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
533
585
|
// ---- Knowledge keys ----
|
|
534
586
|
async function handleKnowledgeKey(input, key) {
|
|
535
587
|
const s = stateRef.current;
|
|
@@ -579,7 +631,7 @@ export default function App() {
|
|
|
579
631
|
const projectId = s.activeProjectId;
|
|
580
632
|
if (projectId) {
|
|
581
633
|
let failures = 0;
|
|
582
|
-
dispatch({ type: 'SET_OPERATION', operation: 'Removing
|
|
634
|
+
dispatch({ type: 'SET_OPERATION', operation: 'Removing contributions' });
|
|
583
635
|
await Promise.all([...s.forgetSelected].map(async (id) => {
|
|
584
636
|
try {
|
|
585
637
|
await api.deleteMemory(projectId, id);
|
|
@@ -838,7 +890,7 @@ export default function App() {
|
|
|
838
890
|
}
|
|
839
891
|
}
|
|
840
892
|
// ---- Project keys ----
|
|
841
|
-
async function handleProjectKey(input,
|
|
893
|
+
async function handleProjectKey(input, key) {
|
|
842
894
|
const s = stateRef.current;
|
|
843
895
|
if (input === 't') {
|
|
844
896
|
dispatch({ type: 'OPEN_MODAL', modal: { kind: 'create-team', input: '' } });
|
|
@@ -863,6 +915,30 @@ export default function App() {
|
|
|
863
915
|
if (project)
|
|
864
916
|
dispatch({ type: 'OPEN_MODAL', modal: { kind: 'confirm-delete', slug: project.slug, memoryCount: s.memoryTotal, input: '' } });
|
|
865
917
|
}
|
|
918
|
+
else if (input === 'L' && s.userRole === 'admin') {
|
|
919
|
+
// Open link picker — only meaningful if there are other admin projects
|
|
920
|
+
const candidates = adminScopableProjects(s).filter(p => p.project_id !== s.activeProjectId);
|
|
921
|
+
if (candidates.length === 0) {
|
|
922
|
+
showInlineError('no other projects you admin', 2000);
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
dispatch({ type: 'OPEN_MODAL', modal: { kind: 'link-project', selected: 0 } });
|
|
926
|
+
}
|
|
927
|
+
else if (input === 'U' && s.userRole === 'admin') {
|
|
928
|
+
const link = s.projectLinks[s.projectLinkSelected];
|
|
929
|
+
if (link && link.source === 'explicit') {
|
|
930
|
+
dispatch({ type: 'OPEN_MODAL', modal: { kind: 'confirm-unlink-project', otherProjectId: link.project_id, otherSlug: link.slug } });
|
|
931
|
+
}
|
|
932
|
+
else if (link && link.source === 'team') {
|
|
933
|
+
showInlineError('team siblings are auto-linked — cannot unlink', 2000);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
else if (key.upArrow && s.projectLinks.length > 0) {
|
|
937
|
+
dispatch({ type: 'SET_PROJECT_LINK_SELECTED', index: Math.max(0, s.projectLinkSelected - 1) });
|
|
938
|
+
}
|
|
939
|
+
else if (key.downArrow && s.projectLinks.length > 0) {
|
|
940
|
+
dispatch({ type: 'SET_PROJECT_LINK_SELECTED', index: Math.min(s.projectLinks.length - 1, s.projectLinkSelected + 1) });
|
|
941
|
+
}
|
|
866
942
|
}
|
|
867
943
|
// ---- You keys ----
|
|
868
944
|
async function handleYouKey(input, _key) {
|
|
@@ -955,7 +1031,8 @@ export default function App() {
|
|
|
955
1031
|
if (modal.kind === 'confirm-forget' || modal.kind === 'confirm-remove-member' ||
|
|
956
1032
|
modal.kind === 'confirm-leave' || modal.kind === 'confirm-logout' ||
|
|
957
1033
|
modal.kind === 'confirm-regen-link' || modal.kind === 'initiate-consensus' ||
|
|
958
|
-
modal.kind === 'approve-action'
|
|
1034
|
+
modal.kind === 'approve-action' || modal.kind === 'confirm-revoke-key' ||
|
|
1035
|
+
modal.kind === 'confirm-unlink-project') {
|
|
959
1036
|
if (input === 'y') {
|
|
960
1037
|
const willExit = modal.kind === 'confirm-logout';
|
|
961
1038
|
await confirmModal(modal);
|
|
@@ -967,6 +1044,179 @@ export default function App() {
|
|
|
967
1044
|
}
|
|
968
1045
|
return;
|
|
969
1046
|
}
|
|
1047
|
+
// Select key kind (session vs integration)
|
|
1048
|
+
if (modal.kind === 'select-key-kind') {
|
|
1049
|
+
if (key.upArrow) {
|
|
1050
|
+
dispatch({ type: 'OPEN_MODAL', modal: { ...modal, selected: Math.max(0, modal.selected - 1) } });
|
|
1051
|
+
}
|
|
1052
|
+
else if (key.downArrow) {
|
|
1053
|
+
dispatch({ type: 'OPEN_MODAL', modal: { ...modal, selected: Math.min(1, modal.selected + 1) } });
|
|
1054
|
+
}
|
|
1055
|
+
else if (key.return) {
|
|
1056
|
+
if (modal.selected === 0) {
|
|
1057
|
+
dispatch({ type: 'OPEN_MODAL', modal: { kind: 'create-session-key', input: '' } });
|
|
1058
|
+
}
|
|
1059
|
+
else {
|
|
1060
|
+
// Pre-select the active project for convenience
|
|
1061
|
+
const adminProjects = adminScopableProjects(s);
|
|
1062
|
+
const activeIdx = Math.max(0, adminProjects.findIndex(p => p.project_id === s.activeProjectId));
|
|
1063
|
+
const project = adminProjects[activeIdx];
|
|
1064
|
+
dispatch({ type: 'OPEN_MODAL', modal: {
|
|
1065
|
+
kind: 'create-integration-key',
|
|
1066
|
+
step: 'name',
|
|
1067
|
+
input: '',
|
|
1068
|
+
projectId: project?.project_id ?? null,
|
|
1069
|
+
projectSelectedIdx: activeIdx,
|
|
1070
|
+
role: 'member',
|
|
1071
|
+
roleSelectedIdx: 1,
|
|
1072
|
+
allowedTools: [],
|
|
1073
|
+
toolsSelectedIdx: 0,
|
|
1074
|
+
agentName: '',
|
|
1075
|
+
} });
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
return;
|
|
1079
|
+
}
|
|
1080
|
+
// Integration key creation wizard
|
|
1081
|
+
if (modal.kind === 'create-integration-key') {
|
|
1082
|
+
const adminProjects = adminScopableProjects(s);
|
|
1083
|
+
if (modal.step === 'name') {
|
|
1084
|
+
if (key.return && modal.input.trim().length > 0) {
|
|
1085
|
+
if (adminProjects.length === 0) {
|
|
1086
|
+
showInlineError('no projects you can scope an integration key to', 3000);
|
|
1087
|
+
return;
|
|
1088
|
+
}
|
|
1089
|
+
dispatch({ type: 'OPEN_MODAL', modal: { ...modal, step: 'project' } });
|
|
1090
|
+
}
|
|
1091
|
+
else if (key.backspace || key.delete) {
|
|
1092
|
+
dispatch({ type: 'OPEN_MODAL', modal: { ...modal, input: modal.input.slice(0, -1) } });
|
|
1093
|
+
}
|
|
1094
|
+
else if (input === ' ') {
|
|
1095
|
+
dispatch({ type: 'OPEN_MODAL', modal: { ...modal, input: modal.input + ' ' } });
|
|
1096
|
+
}
|
|
1097
|
+
else if (input.length === 1 && !key.ctrl && !key.meta) {
|
|
1098
|
+
dispatch({ type: 'OPEN_MODAL', modal: { ...modal, input: modal.input + input } });
|
|
1099
|
+
}
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
if (modal.step === 'project') {
|
|
1103
|
+
if (adminProjects.length === 0)
|
|
1104
|
+
return;
|
|
1105
|
+
if (key.upArrow) {
|
|
1106
|
+
const i = Math.max(0, modal.projectSelectedIdx - 1);
|
|
1107
|
+
dispatch({ type: 'OPEN_MODAL', modal: { ...modal, projectSelectedIdx: i, projectId: adminProjects[i].project_id } });
|
|
1108
|
+
}
|
|
1109
|
+
else if (key.downArrow) {
|
|
1110
|
+
const i = Math.min(adminProjects.length - 1, modal.projectSelectedIdx + 1);
|
|
1111
|
+
dispatch({ type: 'OPEN_MODAL', modal: { ...modal, projectSelectedIdx: i, projectId: adminProjects[i].project_id } });
|
|
1112
|
+
}
|
|
1113
|
+
else if (key.return && modal.projectId) {
|
|
1114
|
+
dispatch({ type: 'OPEN_MODAL', modal: { ...modal, step: 'role' } });
|
|
1115
|
+
}
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
if (modal.step === 'role') {
|
|
1119
|
+
const roles = ['admin', 'member', 'viewer'];
|
|
1120
|
+
if (key.upArrow) {
|
|
1121
|
+
const i = Math.max(0, modal.roleSelectedIdx - 1);
|
|
1122
|
+
dispatch({ type: 'OPEN_MODAL', modal: { ...modal, roleSelectedIdx: i, role: roles[i] } });
|
|
1123
|
+
}
|
|
1124
|
+
else if (key.downArrow) {
|
|
1125
|
+
const i = Math.min(2, modal.roleSelectedIdx + 1);
|
|
1126
|
+
dispatch({ type: 'OPEN_MODAL', modal: { ...modal, roleSelectedIdx: i, role: roles[i] } });
|
|
1127
|
+
}
|
|
1128
|
+
else if (key.return) {
|
|
1129
|
+
dispatch({ type: 'OPEN_MODAL', modal: { ...modal, step: 'tools' } });
|
|
1130
|
+
}
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
if (modal.step === 'tools') {
|
|
1134
|
+
if (key.upArrow) {
|
|
1135
|
+
dispatch({ type: 'OPEN_MODAL', modal: { ...modal, toolsSelectedIdx: Math.max(0, modal.toolsSelectedIdx - 1) } });
|
|
1136
|
+
}
|
|
1137
|
+
else if (key.downArrow) {
|
|
1138
|
+
dispatch({ type: 'OPEN_MODAL', modal: { ...modal, toolsSelectedIdx: Math.min(KNOWN_TOOL_NAMES.length - 1, modal.toolsSelectedIdx + 1) } });
|
|
1139
|
+
}
|
|
1140
|
+
else if (input === ' ') {
|
|
1141
|
+
const t = KNOWN_TOOL_NAMES[modal.toolsSelectedIdx];
|
|
1142
|
+
const next = modal.allowedTools.includes(t)
|
|
1143
|
+
? modal.allowedTools.filter(x => x !== t)
|
|
1144
|
+
: [...modal.allowedTools, t];
|
|
1145
|
+
dispatch({ type: 'OPEN_MODAL', modal: { ...modal, allowedTools: next } });
|
|
1146
|
+
}
|
|
1147
|
+
else if (key.return) {
|
|
1148
|
+
dispatch({ type: 'OPEN_MODAL', modal: { ...modal, step: 'agent' } });
|
|
1149
|
+
}
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
if (modal.step === 'agent') {
|
|
1153
|
+
if (key.return) {
|
|
1154
|
+
dispatch({ type: 'OPEN_MODAL', modal: { ...modal, step: 'confirm' } });
|
|
1155
|
+
}
|
|
1156
|
+
else if (key.backspace || key.delete) {
|
|
1157
|
+
dispatch({ type: 'OPEN_MODAL', modal: { ...modal, agentName: modal.agentName.slice(0, -1) } });
|
|
1158
|
+
}
|
|
1159
|
+
else if (input === ' ') {
|
|
1160
|
+
dispatch({ type: 'OPEN_MODAL', modal: { ...modal, agentName: modal.agentName + ' ' } });
|
|
1161
|
+
}
|
|
1162
|
+
else if (input.length === 1 && !key.ctrl && !key.meta) {
|
|
1163
|
+
dispatch({ type: 'OPEN_MODAL', modal: { ...modal, agentName: modal.agentName + input } });
|
|
1164
|
+
}
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1167
|
+
if (modal.step === 'confirm') {
|
|
1168
|
+
if (input === 'y') {
|
|
1169
|
+
await confirmModal(modal);
|
|
1170
|
+
// confirmModal handles the modal transition (closes on error, opens key-created on success)
|
|
1171
|
+
}
|
|
1172
|
+
else if (input === 'n') {
|
|
1173
|
+
dispatch({ type: 'CLOSE_MODAL' });
|
|
1174
|
+
}
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
1179
|
+
// Link to another project — pick from candidates
|
|
1180
|
+
if (modal.kind === 'link-project') {
|
|
1181
|
+
const candidates = adminScopableProjects(s).filter(p => p.project_id !== s.activeProjectId);
|
|
1182
|
+
if (key.upArrow) {
|
|
1183
|
+
dispatch({ type: 'OPEN_MODAL', modal: { ...modal, selected: Math.max(0, modal.selected - 1) } });
|
|
1184
|
+
}
|
|
1185
|
+
else if (key.downArrow) {
|
|
1186
|
+
dispatch({ type: 'OPEN_MODAL', modal: { ...modal, selected: Math.min(candidates.length - 1, modal.selected + 1) } });
|
|
1187
|
+
}
|
|
1188
|
+
else if (key.return) {
|
|
1189
|
+
const target = candidates[modal.selected];
|
|
1190
|
+
if (target && s.activeProjectId) {
|
|
1191
|
+
try {
|
|
1192
|
+
dispatch({ type: 'SET_OPERATION', operation: 'Linking project' });
|
|
1193
|
+
await api.linkProjects(s.activeProjectId, target.project_id);
|
|
1194
|
+
const links = await api.listProjectLinks(s.activeProjectId).catch(() => stateRef.current.projectLinks);
|
|
1195
|
+
dispatch({ type: 'SET_PROJECT_LINKS', links });
|
|
1196
|
+
dispatch({ type: 'SET_OPERATION', operation: null });
|
|
1197
|
+
dispatch({ type: 'CLOSE_MODAL' });
|
|
1198
|
+
showToast('Linked', 'success');
|
|
1199
|
+
}
|
|
1200
|
+
catch {
|
|
1201
|
+
dispatch({ type: 'SET_OPERATION', operation: null });
|
|
1202
|
+
dispatch({ type: 'CLOSE_MODAL' });
|
|
1203
|
+
showToast('Failed to link', 'error');
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
// Key created — copy or dismiss (esc handled at top)
|
|
1210
|
+
if (modal.kind === 'key-created') {
|
|
1211
|
+
if (input === 'c') {
|
|
1212
|
+
copyToClipboard(modal.keyValue);
|
|
1213
|
+
dispatch({ type: 'SET_COPIED_FEEDBACK', active: true });
|
|
1214
|
+
if (copiedTimerRef.current)
|
|
1215
|
+
clearTimeout(copiedTimerRef.current);
|
|
1216
|
+
copiedTimerRef.current = setTimeout(() => dispatch({ type: 'SET_COPIED_FEEDBACK', active: false }), 1500);
|
|
1217
|
+
}
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
970
1220
|
// Text input modals
|
|
971
1221
|
if ('input' in modal) {
|
|
972
1222
|
if (key.return) {
|
|
@@ -1338,6 +1588,101 @@ export default function App() {
|
|
|
1338
1588
|
}
|
|
1339
1589
|
return false;
|
|
1340
1590
|
}
|
|
1591
|
+
if (modal.kind === 'create-session-key') {
|
|
1592
|
+
if (!modal.input.trim())
|
|
1593
|
+
return false;
|
|
1594
|
+
try {
|
|
1595
|
+
dispatch({ type: 'SET_OPERATION', operation: 'Creating key' });
|
|
1596
|
+
const result = await api.createApiKey({ kind: 'session', name: modal.input.trim() });
|
|
1597
|
+
dispatch({ type: 'SET_OPERATION', operation: null });
|
|
1598
|
+
// Refresh list, then transition modal so the secret is shown once.
|
|
1599
|
+
const sessionKeys = await api.listSessionKeys().catch(() => []);
|
|
1600
|
+
const integrationKeys = stateRef.current.userRole === 'admin' && stateRef.current.activeProjectId
|
|
1601
|
+
? await api.listIntegrationKeys(stateRef.current.activeProjectId).catch(() => [])
|
|
1602
|
+
: [];
|
|
1603
|
+
dispatch({ type: 'SET_API_KEYS', keys: [...sessionKeys, ...integrationKeys] });
|
|
1604
|
+
dispatch({ type: 'OPEN_MODAL', modal: {
|
|
1605
|
+
kind: 'key-created',
|
|
1606
|
+
keyId: result.api_key.id,
|
|
1607
|
+
keyValue: result.key,
|
|
1608
|
+
keyName: result.api_key.name,
|
|
1609
|
+
kind2: 'session',
|
|
1610
|
+
} });
|
|
1611
|
+
return true; // chained — caller must not CLOSE_MODAL
|
|
1612
|
+
}
|
|
1613
|
+
catch {
|
|
1614
|
+
dispatch({ type: 'SET_OPERATION', operation: null });
|
|
1615
|
+
showToast('Failed to create key', 'error');
|
|
1616
|
+
}
|
|
1617
|
+
return false;
|
|
1618
|
+
}
|
|
1619
|
+
if (modal.kind === 'create-integration-key') {
|
|
1620
|
+
// Only the confirm step submits — earlier steps just advance via OPEN_MODAL.
|
|
1621
|
+
if (modal.step !== 'confirm')
|
|
1622
|
+
return false;
|
|
1623
|
+
if (!modal.input.trim() || !modal.projectId)
|
|
1624
|
+
return false;
|
|
1625
|
+
try {
|
|
1626
|
+
dispatch({ type: 'SET_OPERATION', operation: 'Creating key' });
|
|
1627
|
+
const result = await api.createApiKey({
|
|
1628
|
+
kind: 'integration',
|
|
1629
|
+
name: modal.input.trim(),
|
|
1630
|
+
projects: [{ project_id: modal.projectId, role: modal.role }],
|
|
1631
|
+
...(modal.allowedTools.length > 0 ? { allowed_tools: modal.allowedTools } : {}),
|
|
1632
|
+
...(modal.agentName.trim() ? { default_agent_name: modal.agentName.trim() } : {}),
|
|
1633
|
+
});
|
|
1634
|
+
dispatch({ type: 'SET_OPERATION', operation: null });
|
|
1635
|
+
const sessionKeys = await api.listSessionKeys().catch(() => []);
|
|
1636
|
+
const integrationKeys = stateRef.current.userRole === 'admin' && stateRef.current.activeProjectId
|
|
1637
|
+
? await api.listIntegrationKeys(stateRef.current.activeProjectId).catch(() => [])
|
|
1638
|
+
: [];
|
|
1639
|
+
dispatch({ type: 'SET_API_KEYS', keys: [...sessionKeys, ...integrationKeys] });
|
|
1640
|
+
dispatch({ type: 'OPEN_MODAL', modal: {
|
|
1641
|
+
kind: 'key-created',
|
|
1642
|
+
keyId: result.api_key.id,
|
|
1643
|
+
keyValue: result.key,
|
|
1644
|
+
keyName: result.api_key.name,
|
|
1645
|
+
kind2: 'integration',
|
|
1646
|
+
} });
|
|
1647
|
+
return true;
|
|
1648
|
+
}
|
|
1649
|
+
catch {
|
|
1650
|
+
dispatch({ type: 'SET_OPERATION', operation: null });
|
|
1651
|
+
showToast('Failed to create key', 'error');
|
|
1652
|
+
}
|
|
1653
|
+
return false;
|
|
1654
|
+
}
|
|
1655
|
+
if (modal.kind === 'confirm-revoke-key') {
|
|
1656
|
+
try {
|
|
1657
|
+
dispatch({ type: 'SET_OPERATION', operation: 'Revoking key' });
|
|
1658
|
+
await api.deleteApiKey(modal.keyId);
|
|
1659
|
+
dispatch({ type: 'REMOVE_API_KEY', id: modal.keyId });
|
|
1660
|
+
dispatch({ type: 'SET_OPERATION', operation: null });
|
|
1661
|
+
showToast('Key revoked', 'success');
|
|
1662
|
+
}
|
|
1663
|
+
catch {
|
|
1664
|
+
dispatch({ type: 'SET_OPERATION', operation: null });
|
|
1665
|
+
showToast('Failed to revoke key', 'error');
|
|
1666
|
+
}
|
|
1667
|
+
return false;
|
|
1668
|
+
}
|
|
1669
|
+
if (modal.kind === 'confirm-unlink-project') {
|
|
1670
|
+
const projectId = stateRef.current.activeProjectId;
|
|
1671
|
+
if (!projectId)
|
|
1672
|
+
return false;
|
|
1673
|
+
try {
|
|
1674
|
+
dispatch({ type: 'SET_OPERATION', operation: 'Unlinking' });
|
|
1675
|
+
await api.unlinkProjects(projectId, modal.otherProjectId);
|
|
1676
|
+
dispatch({ type: 'REMOVE_PROJECT_LINK', otherProjectId: modal.otherProjectId });
|
|
1677
|
+
dispatch({ type: 'SET_OPERATION', operation: null });
|
|
1678
|
+
showToast('Unlinked', 'success');
|
|
1679
|
+
}
|
|
1680
|
+
catch {
|
|
1681
|
+
dispatch({ type: 'SET_OPERATION', operation: null });
|
|
1682
|
+
showToast('Failed to unlink', 'error');
|
|
1683
|
+
}
|
|
1684
|
+
return false;
|
|
1685
|
+
}
|
|
1341
1686
|
const projectId = s.activeProjectId;
|
|
1342
1687
|
if (!projectId)
|
|
1343
1688
|
return false;
|
|
@@ -1365,15 +1710,15 @@ export default function App() {
|
|
|
1365
1710
|
}
|
|
1366
1711
|
case 'confirm-forget':
|
|
1367
1712
|
try {
|
|
1368
|
-
dispatch({ type: 'SET_OPERATION', operation: 'Removing
|
|
1713
|
+
dispatch({ type: 'SET_OPERATION', operation: 'Removing contribution' });
|
|
1369
1714
|
await api.deleteMemory(projectId, modal.memoryId);
|
|
1370
1715
|
dispatch({ type: 'REMOVE_MEMORY', id: modal.memoryId });
|
|
1371
1716
|
dispatch({ type: 'SET_OPERATION', operation: null });
|
|
1372
|
-
showToast('
|
|
1717
|
+
showToast('Contribution removed', 'success');
|
|
1373
1718
|
}
|
|
1374
1719
|
catch {
|
|
1375
1720
|
dispatch({ type: 'SET_OPERATION', operation: null });
|
|
1376
|
-
showToast('Failed to remove
|
|
1721
|
+
showToast('Failed to remove contribution', 'error');
|
|
1377
1722
|
}
|
|
1378
1723
|
break;
|
|
1379
1724
|
case 'confirm-forget-all':
|
|
@@ -1384,7 +1729,7 @@ export default function App() {
|
|
|
1384
1729
|
const team = getActiveTeam(s);
|
|
1385
1730
|
if (team) {
|
|
1386
1731
|
try {
|
|
1387
|
-
await api.initiateApproval(team.team_id, 'clear_memories', `clear all
|
|
1732
|
+
await api.initiateApproval(team.team_id, 'clear_memories', `clear all contributions from "${project.slug}"`, projectId);
|
|
1388
1733
|
const approvals = await api.listApprovals(team.team_id);
|
|
1389
1734
|
dispatch({ type: 'SET_PENDING_APPROVALS', approvals });
|
|
1390
1735
|
showToast('Approval requested', 'info');
|
|
@@ -1396,15 +1741,15 @@ export default function App() {
|
|
|
1396
1741
|
}
|
|
1397
1742
|
}
|
|
1398
1743
|
try {
|
|
1399
|
-
dispatch({ type: 'SET_OPERATION', operation: 'Clearing
|
|
1744
|
+
dispatch({ type: 'SET_OPERATION', operation: 'Clearing contributions' });
|
|
1400
1745
|
await api.clearMemories(projectId);
|
|
1401
1746
|
dispatch({ type: 'SET_MEMORIES', memories: [], total: 0 });
|
|
1402
1747
|
dispatch({ type: 'SET_OPERATION', operation: null });
|
|
1403
|
-
showToast('All
|
|
1748
|
+
showToast('All contributions cleared', 'success');
|
|
1404
1749
|
}
|
|
1405
1750
|
catch {
|
|
1406
1751
|
dispatch({ type: 'SET_OPERATION', operation: null });
|
|
1407
|
-
showToast('Failed to clear
|
|
1752
|
+
showToast('Failed to clear contributions', 'error');
|
|
1408
1753
|
}
|
|
1409
1754
|
}
|
|
1410
1755
|
break;
|
|
@@ -1641,19 +1986,31 @@ function renderTab(state, height) {
|
|
|
1641
1986
|
case 'team': return _jsx(TeamTab, { state: state, height: height });
|
|
1642
1987
|
case 'billing': return _jsx(BillingTab, { state: state, height: height });
|
|
1643
1988
|
case 'project': return _jsx(ProjectTab, { state: state, height: height });
|
|
1989
|
+
case 'keys': return _jsx(KeysTab, { state: state, height: height });
|
|
1644
1990
|
case 'you': return _jsx(YouTab, { state: state, height: height });
|
|
1645
1991
|
}
|
|
1646
1992
|
}
|
|
1993
|
+
// Projects the caller can scope an integration key to. Personal projects are
|
|
1994
|
+
// always scopable by their owner; team projects only by team admins.
|
|
1995
|
+
function adminScopableProjects(s) {
|
|
1996
|
+
return s.projects.filter(p => {
|
|
1997
|
+
if (p.owner.kind === 'personal')
|
|
1998
|
+
return true; // user owns it
|
|
1999
|
+
const teamId = p.owner.teamId;
|
|
2000
|
+
const team = s.teams.find(t => t.team_id === teamId);
|
|
2001
|
+
return team?.role === 'admin';
|
|
2002
|
+
});
|
|
2003
|
+
}
|
|
1647
2004
|
function HelpOverlay({ state }) {
|
|
1648
2005
|
const lines = [
|
|
1649
|
-
['tab / 1-
|
|
2006
|
+
['tab / 1-6', 'switch tabs'],
|
|
1650
2007
|
['?', 'toggle this help'],
|
|
1651
2008
|
['q', 'quit'],
|
|
1652
2009
|
['', ''],
|
|
1653
2010
|
];
|
|
1654
2011
|
switch (state.tab) {
|
|
1655
2012
|
case 'knowledge':
|
|
1656
|
-
lines.push(['/', 'search'], ['enter', 'expand
|
|
2013
|
+
lines.push(['/', 'search'], ['enter', 'expand contribution'], ['↑↓', 'navigate'], ['space', 'load more'], ['d', 'forget mode'], ['D', 'forget all (admin)']);
|
|
1657
2014
|
break;
|
|
1658
2015
|
case 'team':
|
|
1659
2016
|
lines.push(['↑↓', 'navigate'], ['i', 'invite'], ['e', 'edit role (admin)'], ['x', 'remove member'], ['c', 'copy invite link'], ['r', 'regenerate link']);
|
|
@@ -1662,7 +2019,10 @@ function HelpOverlay({ state }) {
|
|
|
1662
2019
|
lines.push(['s', 'subscribe'], ['p', 'billing portal']);
|
|
1663
2020
|
break;
|
|
1664
2021
|
case 'project':
|
|
1665
|
-
lines.push(['w', 'switch project'], ['n', 'new project'], ['t', 'new team'], ['r', 'rename'], ['c', 'clear knowledge'], ['d', 'delete project']);
|
|
2022
|
+
lines.push(['↑↓', 'navigate links'], ['w', 'switch project'], ['n', 'new project'], ['t', 'new team'], ['r', 'rename'], ['L', 'link to another project (admin)'], ['U', 'unlink selected (admin, explicit only)'], ['c', 'clear knowledge'], ['d', 'delete project']);
|
|
2023
|
+
break;
|
|
2024
|
+
case 'keys':
|
|
2025
|
+
lines.push(['↑↓', 'navigate'], ['n', 'new key'], ['x', 'revoke selected']);
|
|
1666
2026
|
break;
|
|
1667
2027
|
case 'you':
|
|
1668
2028
|
lines.push(['p', 'pause / resume'], ['r', 're-run setup'], ['l', 'logout']);
|