@tianmucreations/jeeves 0.2.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.
Files changed (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +32 -0
  3. package/bin/jeeves +2 -0
  4. package/dist/agent/context.js +50 -0
  5. package/dist/agent/errors.js +41 -0
  6. package/dist/agent/loop.js +84 -0
  7. package/dist/agent/permissions.js +27 -0
  8. package/dist/app.js +68 -0
  9. package/dist/commands/clear.js +9 -0
  10. package/dist/commands/help.js +17 -0
  11. package/dist/commands/keys.js +15 -0
  12. package/dist/commands/model.js +4 -0
  13. package/dist/commands/verbose.js +8 -0
  14. package/dist/components/AlternateScreen.js +74 -0
  15. package/dist/components/Footer.js +114 -0
  16. package/dist/components/Header.js +6 -0
  17. package/dist/components/HelpView.js +14 -0
  18. package/dist/components/Input.js +76 -0
  19. package/dist/components/KeysManager.js +281 -0
  20. package/dist/components/ModelPicker.js +457 -0
  21. package/dist/components/ProjectPicker.js +334 -0
  22. package/dist/components/TrafficLight.js +116 -0
  23. package/dist/components/Transcript.js +23 -0
  24. package/dist/components/UsageBar.js +35 -0
  25. package/dist/components/transcript-layout.js +103 -0
  26. package/dist/index.js +53 -0
  27. package/dist/ink/AlternateScreen.js +106 -0
  28. package/dist/keys/store.js +58 -0
  29. package/dist/models/filter.js +4 -0
  30. package/dist/models/registry.js +112 -0
  31. package/dist/platform/config.js +60 -0
  32. package/dist/platform/paths.js +60 -0
  33. package/dist/platform/shell.js +9 -0
  34. package/dist/providers/index.js +165 -0
  35. package/dist/providers/ollama.js +103 -0
  36. package/dist/providers/openrouter.js +109 -0
  37. package/dist/providers/types.js +1 -0
  38. package/dist/providers/zai.js +104 -0
  39. package/dist/state/session.js +315 -0
  40. package/dist/tools/index.js +106 -0
  41. package/dist/tools/listDir.js +55 -0
  42. package/dist/tools/readFile.js +15 -0
  43. package/dist/tools/runBash.js +22 -0
  44. package/dist/tools/writeFile.js +15 -0
  45. package/package.json +62 -0
@@ -0,0 +1,76 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { Text, useInput } from 'ink';
4
+ import { runTurn } from '../agent/loop.js';
5
+ import { answerApproval } from '../agent/permissions.js';
6
+ import { useSession } from '../state/session.js';
7
+ export function Input({ scrollPage = 10 }) {
8
+ const [value, setValue] = useState('');
9
+ const s = useSession();
10
+ useInput((input, key) => {
11
+ if (s.pickerOpen || s.keysOpen || s.wizardActive || s.helpOpen)
12
+ return;
13
+ if (s.approvalPending) {
14
+ const answer = input.toLowerCase();
15
+ if (answer === 'y')
16
+ answerApproval(true);
17
+ else if (answer === 'n')
18
+ answerApproval(false);
19
+ return;
20
+ }
21
+ if (key.ctrl && input === 'm') {
22
+ s.openPicker();
23
+ return;
24
+ }
25
+ if (key.ctrl && input === 'r') {
26
+ s.toggleShowLastReasoning();
27
+ return;
28
+ }
29
+ if (key.tab) {
30
+ s.tabFooter();
31
+ return;
32
+ }
33
+ if (key.escape) {
34
+ s.escapeFooter();
35
+ return;
36
+ }
37
+ if (key.return) {
38
+ const text = value.trim();
39
+ if (text && s.status !== 'working') {
40
+ setValue('');
41
+ s.followTranscript();
42
+ void runTurn(text);
43
+ }
44
+ return;
45
+ }
46
+ // The terminal's own scrollback is off (alternate screen), so the arrow and
47
+ // page keys scroll the transcript region instead, three lines per press.
48
+ if (key.upArrow) {
49
+ s.scrollTranscript(3);
50
+ return;
51
+ }
52
+ if (key.downArrow) {
53
+ s.scrollTranscript(-3);
54
+ return;
55
+ }
56
+ if (key.pageUp) {
57
+ s.scrollTranscript(scrollPage);
58
+ return;
59
+ }
60
+ if (key.pageDown) {
61
+ s.scrollTranscript(-scrollPage);
62
+ return;
63
+ }
64
+ if (key.backspace || key.delete) {
65
+ setValue((v) => v.slice(0, -1));
66
+ return;
67
+ }
68
+ if (!input || key.ctrl || key.meta)
69
+ return;
70
+ setValue((v) => v + input);
71
+ });
72
+ if (s.approvalPending) {
73
+ return _jsx(Text, { color: "yellow", children: "y = allow \u00B7 n = deny" });
74
+ }
75
+ return (_jsxs(Text, { children: [_jsx(Text, { bold: true, children: '> ' }), value ? _jsx(Text, { children: value }) : _jsx(Text, { dimColor: true, children: "ask anything" })] }));
76
+ }
@@ -0,0 +1,281 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useState } from 'react';
3
+ import { Box, Text, useInput } from 'ink';
4
+ import { session, useSession } from '../state/session.js';
5
+ import { PROVIDER_ROWS, storeOpenRouterKey, removeOpenRouterKey, storeZaiKey, removeZaiKey, storedKeyProviders, getKeySource, refreshCredit, } from '../providers/index.js';
6
+ import { setKey, deleteKey } from '../keys/store.js';
7
+ import { keyLooksValid } from '../commands/keys.js';
8
+ // The keys screen shows one extra row the model picker does not: the optional
9
+ // OpenRouter management key, which unlocks the real account balance.
10
+ const KEY_ROWS = [
11
+ PROVIDER_ROWS[0],
12
+ { id: 'openrouter-management', label: 'OpenRouter (account)' },
13
+ ...PROVIDER_ROWS.slice(1),
14
+ ];
15
+ function rowLabel(id) {
16
+ return KEY_ROWS.find((row) => row.id === id)?.label ?? id;
17
+ }
18
+ export function KeysManager({ mode, rows, columns }) {
19
+ const s = useSession();
20
+ const [phase, setPhase] = useState(mode === 'wizard' ? { kind: 'ask' } : { kind: 'list' });
21
+ const [cursor, setCursor] = useState(0);
22
+ const [hidden, setHidden] = useState('');
23
+ const [stored, setStored] = useState([]);
24
+ const [note, setNote] = useState('');
25
+ const refreshStored = useCallback(() => {
26
+ void storedKeyProviders().then((names) => setStored(names));
27
+ }, []);
28
+ useEffect(() => {
29
+ refreshStored();
30
+ }, [refreshStored]);
31
+ function statusFor(rowId) {
32
+ if (rowId === 'openrouter') {
33
+ if (getKeySource() === 'keychain')
34
+ return 'key stored in your Mac keychain';
35
+ if (getKeySource() === 'env')
36
+ return 'key in the .env development file';
37
+ return stored.includes('openrouter') ? 'key stored in your Mac keychain' : 'no key';
38
+ }
39
+ if (rowId === 'openrouter-management') {
40
+ return stored.includes('openrouter-management')
41
+ ? 'management key stored - real account balance'
42
+ : 'optional - unlocks your real account balance';
43
+ }
44
+ if (rowId === 'zai') {
45
+ return stored.includes('zai') ? 'key stored - GLM Coding Plan ready' : 'no key - add one to use the flat plan';
46
+ }
47
+ if (rowId === 'ollama')
48
+ return 'local - no key needed';
49
+ return stored.includes(rowId) ? 'key saved - direct connection coming' : 'add key with /keys';
50
+ }
51
+ function finishWizard(message, connected) {
52
+ if (connected) {
53
+ session.addNotice(message);
54
+ if (session.status === 'disconnected')
55
+ session.setStatus('idle');
56
+ void refreshCredit();
57
+ }
58
+ else {
59
+ session.addNotice(message);
60
+ }
61
+ session.endWizard();
62
+ }
63
+ async function saveKey(provider, key) {
64
+ if (provider === 'openrouter') {
65
+ const saved = await storeOpenRouterKey(key);
66
+ if (!saved) {
67
+ setNote('The Mac keychain was not reachable - press Enter and try again.');
68
+ return;
69
+ }
70
+ refreshStored();
71
+ const message = 'Your key is saved securely in your Mac keychain. You will not be asked for it again.';
72
+ if (mode === 'wizard') {
73
+ finishWizard(message, true);
74
+ }
75
+ else {
76
+ setPhase({ kind: 'saved', message });
77
+ }
78
+ return;
79
+ }
80
+ if (provider === 'zai') {
81
+ const saved = await storeZaiKey(key);
82
+ if (!saved) {
83
+ setNote('The Mac keychain was not reachable - press Enter and try again.');
84
+ return;
85
+ }
86
+ refreshStored();
87
+ const message = 'Your Z.ai key is saved. Pick Z.ai in /model to use the GLM Coding Plan.';
88
+ if (mode === 'wizard') {
89
+ finishWizard(message, true);
90
+ }
91
+ else {
92
+ setPhase({ kind: 'saved', message });
93
+ }
94
+ return;
95
+ }
96
+ const saved = await setKey(provider, key);
97
+ if (!saved) {
98
+ setNote('The Mac keychain was not reachable - press Enter and try again.');
99
+ return;
100
+ }
101
+ refreshStored();
102
+ if (provider === 'openrouter-management') {
103
+ void refreshCredit();
104
+ const message = 'Management key saved - the info bar now shows your real account balance.';
105
+ if (mode === 'wizard') {
106
+ finishWizard(message, false);
107
+ }
108
+ else {
109
+ setPhase({ kind: 'saved', message });
110
+ }
111
+ return;
112
+ }
113
+ const message = `Key saved for ${rowLabel(provider)} - direct connections arrive in a coming update.`;
114
+ if (mode === 'wizard') {
115
+ finishWizard(message, false);
116
+ }
117
+ else {
118
+ setPhase({ kind: 'saved', message });
119
+ }
120
+ }
121
+ async function removeKey(provider) {
122
+ if (provider === 'openrouter') {
123
+ const fallback = await removeOpenRouterKey();
124
+ refreshStored();
125
+ const message = fallback === 'env'
126
+ ? 'The key was removed from your keychain - the .env development key is still active.'
127
+ : 'The key was removed. You are signed out until a new key is added.';
128
+ if (fallback !== 'env')
129
+ session.setStatus('disconnected');
130
+ setPhase({ kind: 'saved', message });
131
+ return;
132
+ }
133
+ if (provider === 'zai') {
134
+ await removeZaiKey();
135
+ refreshStored();
136
+ setPhase({ kind: 'saved', message: 'The Z.ai key was removed. The GLM Coding Plan needs a key to work.' });
137
+ return;
138
+ }
139
+ if (provider === 'openrouter-management') {
140
+ await deleteKey(provider);
141
+ refreshStored();
142
+ void refreshCredit();
143
+ setPhase({ kind: 'saved', message: 'The management key was removed - the info bar shows the key cap again.' });
144
+ return;
145
+ }
146
+ await deleteKey(provider);
147
+ refreshStored();
148
+ setPhase({ kind: 'saved', message: `The saved key for ${rowLabel(provider)} was removed.` });
149
+ }
150
+ useInput((input, key) => {
151
+ if (phase.kind === 'ask') {
152
+ const answer = input.toLowerCase();
153
+ if (answer === 'y') {
154
+ setCursor(0);
155
+ setPhase({ kind: 'list' });
156
+ }
157
+ else if (answer === 'n') {
158
+ finishWizard('No key for now - you can add one any time with /keys.', false);
159
+ }
160
+ return;
161
+ }
162
+ if (phase.kind === 'saved') {
163
+ if (input || key.return || key.escape) {
164
+ setPhase({ kind: 'list' });
165
+ setNote('');
166
+ }
167
+ return;
168
+ }
169
+ if (phase.kind === 'confirm-remove') {
170
+ const answer = input.toLowerCase();
171
+ if (answer === 'y') {
172
+ void removeKey(phase.provider);
173
+ }
174
+ else if (answer === 'n' || key.escape) {
175
+ setPhase({ kind: 'list' });
176
+ setNote('');
177
+ }
178
+ return;
179
+ }
180
+ if (phase.kind === 'enter-key') {
181
+ if (key.escape) {
182
+ setPhase({ kind: 'list' });
183
+ setNote('');
184
+ setHidden('');
185
+ return;
186
+ }
187
+ if (key.return) {
188
+ const trimmed = hidden.trim();
189
+ if (!keyLooksValid(trimmed, phase.provider)) {
190
+ setNote(phase.provider === 'openrouter'
191
+ ? 'That does not look like an OpenRouter key (they start with sk-or-) - paste it again, or press Esc to go back.'
192
+ : 'That looks too short to be a key - paste it again, or press Esc to go back.');
193
+ setHidden('');
194
+ return;
195
+ }
196
+ const provider = phase.provider;
197
+ const label = phase.label;
198
+ setHidden('');
199
+ setNote('');
200
+ void saveKey(provider, trimmed);
201
+ setPhase({ kind: 'list' });
202
+ void label;
203
+ return;
204
+ }
205
+ if (key.backspace || key.delete) {
206
+ setHidden((current) => current.slice(0, -1));
207
+ return;
208
+ }
209
+ if (!input || key.ctrl || key.meta)
210
+ return;
211
+ setHidden((current) => current + input);
212
+ return;
213
+ }
214
+ // phase: list
215
+ if (key.escape) {
216
+ if (mode === 'wizard') {
217
+ finishWizard('Setup skipped - you can add a key any time with /keys.', false);
218
+ }
219
+ else {
220
+ session.closeKeys();
221
+ }
222
+ return;
223
+ }
224
+ if (key.upArrow) {
225
+ setCursor((current) => Math.max(0, current - 1));
226
+ return;
227
+ }
228
+ if (key.downArrow) {
229
+ setCursor((current) => Math.min(KEY_ROWS.length - 1, current + 1));
230
+ return;
231
+ }
232
+ if (key.return) {
233
+ const row = KEY_ROWS[cursor];
234
+ if (!row || row.id === 'ollama') {
235
+ if (mode === 'wizard' && row?.id === 'ollama') {
236
+ finishWizard('Ollama runs locally - no key needed.', false);
237
+ }
238
+ return;
239
+ }
240
+ setPhase({ kind: 'enter-key', provider: row.id, label: row.label });
241
+ setNote('');
242
+ setHidden('');
243
+ return;
244
+ }
245
+ const answer = input.toLowerCase();
246
+ if (answer === 'd') {
247
+ const row = KEY_ROWS[cursor];
248
+ if (!row)
249
+ return;
250
+ const hasStored = stored.includes(row.id) || (row.id === 'openrouter' && getKeySource() !== null);
251
+ if (hasStored) {
252
+ setPhase({ kind: 'confirm-remove', provider: row.id, label: row.label });
253
+ setNote('');
254
+ }
255
+ }
256
+ });
257
+ const title = phase.kind === 'ask'
258
+ ? 'Welcome - one quick setup question'
259
+ : phase.kind === 'enter-key'
260
+ ? `Paste the ${phase.label} key`
261
+ : phase.kind === 'confirm-remove'
262
+ ? `Remove the ${phase.label} key?`
263
+ : phase.kind === 'saved'
264
+ ? 'Saved'
265
+ : mode === 'wizard'
266
+ ? 'Which provider should provide your AI?'
267
+ : 'Keys - stored in your Mac keychain';
268
+ const hint = phase.kind === 'ask'
269
+ ? 'y = yes · n = not now'
270
+ : phase.kind === 'enter-key'
271
+ ? 'paste the key · Enter save · Esc back'
272
+ : phase.kind === 'confirm-remove'
273
+ ? 'y = remove · n = keep · Esc back'
274
+ : phase.kind === 'saved'
275
+ ? 'press any key to continue'
276
+ : mode === 'wizard'
277
+ ? '↑↓ move · Enter choose · Esc back'
278
+ : '↑↓ move · Enter add or replace · d remove · Esc close';
279
+ return (_jsxs(Box, { flexDirection: "column", height: rows, children: [_jsx(Text, { dimColor: true, children: title }), _jsxs(Box, { flexDirection: "column", flexGrow: 1, justifyContent: "center", children: [phase.kind === 'ask' && _jsx(Text, { children: ' Add an API key?' }), phase.kind === 'list' &&
280
+ KEY_ROWS.map((row, index) => (_jsxs(Text, { inverse: index === cursor, children: [`${row.label}`.padEnd(14), _jsx(Text, { dimColor: true, children: statusFor(row.id) })] }, row.id))), phase.kind === 'enter-key' && (_jsxs(Text, { children: [_jsxs(Text, { children: ["Paste the ", phase.label, " key (it stays hidden): "] }), _jsx(Text, { inverse: true, children: " " })] })), phase.kind === 'confirm-remove' && (_jsxs(Text, { children: ["Remove the ", phase.label, " key from your Mac keychain? ", _jsxs(Text, { dimColor: true, children: ["(", hint, ")"] })] })), phase.kind === 'saved' && _jsx(Text, { children: phase.message })] }), note ? (_jsx(Text, { color: "yellow", children: note })) : (_jsx(Text, { dimColor: true, children: hint }))] }));
281
+ }