gitarsenal-cli 1.9.111 → 1.9.113

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/.venv_status.json CHANGED
@@ -1 +1 @@
1
- {"created":"2025-10-15T13:59:40.768Z","packages":["modal","gitingest","requests","anthropic"],"uv_version":"uv 0.8.4 (Homebrew 2025-07-30)"}
1
+ {"created":"2025-10-15T14:18:56.158Z","packages":["modal","gitingest","requests","anthropic"],"uv_version":"uv 0.8.4 (Homebrew 2025-07-30)"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitarsenal-cli",
3
- "version": "1.9.111",
3
+ "version": "1.9.113",
4
4
  "description": "CLI tool for creating Modal sandboxes with GitHub repositories",
5
5
  "main": "index.js",
6
6
  "bin": {
package/tui-app/index.jsx CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { render, useKeyboard, useTerminalDimensions } from '@opentui/react';
4
4
  import { useState, useEffect } from 'react';
5
- import { spawn } from 'child_process';
5
+ import { spawn, exec } from 'child_process';
6
6
  import { join } from 'path';
7
7
  import { fileURLToPath } from 'url';
8
8
  import { dirname } from 'path';
@@ -12,6 +12,17 @@ import os from 'os';
12
12
  const __filename = fileURLToPath(import.meta.url);
13
13
  const __dirname = dirname(__filename);
14
14
 
15
+ // Helper function to open URLs in default browser
16
+ const openUrl = (url) => {
17
+ const command = process.platform === 'darwin' ? 'open' :
18
+ process.platform === 'win32' ? 'start' : 'xdg-open';
19
+ exec(`${command} ${url}`, (error) => {
20
+ if (error) {
21
+ console.error('Could not open URL:', error);
22
+ }
23
+ });
24
+ };
25
+
15
26
  // Helper functions for user credentials
16
27
  const getUserConfigPath = () => {
17
28
  const userConfigDir = join(os.homedir(), '.gitarsenal');
@@ -369,7 +380,7 @@ const AuthChoice = ({ selectedIndex }) => {
369
380
  );
370
381
  };
371
382
 
372
- const LoginForm = ({ values, onInput, onSubmit }) => {
383
+ const LoginForm = ({ values, onInput, onSubmit, onNextField, focusedFieldIndex }) => {
373
384
  const fields = ['username', 'email', 'fullName', 'password'];
374
385
  const labels = ['Username:', 'Email Address:', 'Full Name:', 'Password:'];
375
386
 
@@ -384,21 +395,21 @@ const LoginForm = ({ values, onInput, onSubmit }) => {
384
395
  <input
385
396
  value={values[field] || ''}
386
397
  onInput={(value) => onInput(field, value)}
387
- onSubmit={index === fields.length - 1 ? onSubmit : undefined}
398
+ onSubmit={index === fields.length - 1 ? onSubmit : () => onNextField(index)}
388
399
  placeholder={field === 'password' ? '••••••••' : ''}
389
- focused={index === 0}
400
+ focused={index === focusedFieldIndex}
390
401
  width={50}
391
402
  />
392
403
  </box>
393
404
  ))}
394
405
  <box marginTop={1}>
395
- <text dimColor fg="gray">Fill all fields and press Enter • Esc to go back</text>
406
+ <text dimColor fg="gray">Tab or Enter to next field • Esc to go back</text>
396
407
  </box>
397
408
  </box>
398
409
  );
399
410
  };
400
411
 
401
- const RegisterForm = ({ values, onInput, onSubmit }) => {
412
+ const RegisterForm = ({ values, onInput, onSubmit, onNextField, focusedFieldIndex }) => {
402
413
  const fields = ['username', 'email', 'fullName', 'password', 'confirmPassword'];
403
414
  const labels = ['Username:', 'Email Address:', 'Full Name:', 'Password (min 8 chars):', 'Confirm Password:'];
404
415
 
@@ -413,15 +424,60 @@ const RegisterForm = ({ values, onInput, onSubmit }) => {
413
424
  <input
414
425
  value={values[field] || ''}
415
426
  onInput={(value) => onInput(field, value)}
416
- onSubmit={index === fields.length - 1 ? onSubmit : undefined}
427
+ onSubmit={index === fields.length - 1 ? onSubmit : () => onNextField(index)}
417
428
  placeholder={field.includes('password') ? '••••••••' : ''}
418
- focused={index === 0}
429
+ focused={index === focusedFieldIndex}
419
430
  width={50}
420
431
  />
421
432
  </box>
422
433
  ))}
423
434
  <box marginTop={1}>
424
- <text dimColor fg="gray">Fill all fields and press Enter • Esc to go back</text>
435
+ <text dimColor fg="gray">Tab or Enter to next field • Esc to go back</text>
436
+ </box>
437
+ </box>
438
+ );
439
+ };
440
+
441
+ const Settings = ({ userCredentials, selectedIndex }) => {
442
+ const settingsOptions = userCredentials
443
+ ? ['Create New Account', 'Logout', 'Back to Menu']
444
+ : ['Create Account', 'Login', 'Back to Menu'];
445
+
446
+ return (
447
+ <box style={{ flexDirection: 'column', alignItems: 'center' }}>
448
+ <box borderStyle="single" padding={1} marginBottom={1}>
449
+ <text bold>Settings</text>
450
+ </box>
451
+
452
+ {userCredentials ? (
453
+ <box marginBottom={2} style={{ flexDirection: 'column', alignItems: 'center' }}>
454
+ <text fg="green">Logged in as: {userCredentials.userName}</text>
455
+ <text dimColor fg="gray">{userCredentials.userEmail}</text>
456
+ </box>
457
+ ) : (
458
+ <box marginBottom={2} style={{ flexDirection: 'column', alignItems: 'center' }}>
459
+ <text dimColor fg="gray">No account logged in</text>
460
+ </box>
461
+ )}
462
+
463
+ {settingsOptions.map((option, index) => (
464
+ <box key={index} marginY={0} marginBottom={0}>
465
+ {index === selectedIndex ? (
466
+ <box borderStyle="single" borderColor="cyan" paddingX={2} paddingY={0} width={48} style={{ justifyContent: 'center' }}>
467
+ <text bold fg="cyan">{option}</text>
468
+ </box>
469
+ ) : (
470
+ <box borderStyle="single" borderColor="gray" paddingX={2} paddingY={0} width={48} style={{ justifyContent: 'center' }}>
471
+ <text dimColor>{option}</text>
472
+ </box>
473
+ )}
474
+ </box>
475
+ ))}
476
+
477
+ <box marginTop={2} style={{ flexDirection: 'column', alignItems: 'center' }}>
478
+ <text bold>Controls:</text>
479
+ <text dimColor fg="gray">↑↓ - Navigate • Enter - Select</text>
480
+ <text dimColor fg="gray">Esc - Back to menu</text>
425
481
  </box>
426
482
  </box>
427
483
  );
@@ -487,7 +543,7 @@ const ApiKeysManagement = ({ apiKeys, selectedIndex }) => {
487
543
  );
488
544
  };
489
545
 
490
- const ApiKeyForm = ({ serviceName, apiKey, onServiceInput, onKeyInput, onSubmit, isEditing }) => {
546
+ const ApiKeyForm = ({ serviceName, apiKey, onServiceInput, onKeyInput, onSubmit, onNextField, isEditing, focusedFieldIndex }) => {
491
547
  return (
492
548
  <box style={{ flexDirection: 'column', alignItems: 'center' }}>
493
549
  <box borderStyle="single" padding={1} marginBottom={1}>
@@ -513,8 +569,9 @@ const ApiKeyForm = ({ serviceName, apiKey, onServiceInput, onKeyInput, onSubmit,
513
569
  <input
514
570
  value={serviceName || ''}
515
571
  onInput={onServiceInput}
572
+ onSubmit={onNextField}
516
573
  placeholder="e.g., openai_api_key"
517
- focused={true}
574
+ focused={focusedFieldIndex === 0}
518
575
  width={70}
519
576
  />
520
577
  </box>
@@ -534,13 +591,13 @@ const ApiKeyForm = ({ serviceName, apiKey, onServiceInput, onKeyInput, onSubmit,
534
591
  onInput={onKeyInput}
535
592
  onSubmit={onSubmit}
536
593
  placeholder="Paste your API key here..."
537
- focused={isEditing}
594
+ focused={isEditing || focusedFieldIndex === 1}
538
595
  width={70}
539
596
  />
540
597
  </box>
541
598
 
542
599
  <box marginTop={1} style={{ flexDirection: 'column', alignItems: 'center' }}>
543
- <text dimColor fg="gray">Enter to save • Esc to cancel</text>
600
+ <text dimColor fg="gray">{isEditing ? 'Enter to save' : 'Tab or Enter to next field'} • Esc to cancel</text>
544
601
  {isEditing && <text dimColor fg="gray">Clear field to remove the key</text>}
545
602
  </box>
546
603
  </box>
@@ -671,11 +728,13 @@ const App = () => {
671
728
  const [viewingSandboxId, setViewingSandboxId] = useState(null);
672
729
  const [userCredentials, setUserCredentials] = useState(null);
673
730
  const [authFormValues, setAuthFormValues] = useState({});
731
+ const [authFieldIndex, setAuthFieldIndex] = useState(0);
674
732
  const [apiKeys, setApiKeys] = useState({});
675
733
  const [editingServiceName, setEditingServiceName] = useState('');
676
734
  const [serviceNameInput, setServiceNameInput] = useState('');
677
735
  const [apiKeyInput, setApiKeyInput] = useState('');
678
736
  const [isEditingExisting, setIsEditingExisting] = useState(false);
737
+ const [apiKeyFieldIndex, setApiKeyFieldIndex] = useState(0);
679
738
 
680
739
  // Load user credentials on mount
681
740
  useEffect(() => {
@@ -808,8 +867,14 @@ const App = () => {
808
867
  setServiceNameInput('');
809
868
  setApiKeyInput('');
810
869
  setIsEditingExisting(false);
870
+ setApiKeyFieldIndex(0);
811
871
  setScreen('apiKeysManagement');
812
872
  setSelectedIndex(0);
873
+ } else if (key.name === 'tab') {
874
+ // Toggle between fields when adding new key
875
+ if (!isEditingExisting) {
876
+ setApiKeyFieldIndex((prev) => (prev === 0 ? 1 : 0));
877
+ }
813
878
  }
814
879
  // Let the input component handle all other keys
815
880
  return;
@@ -830,6 +895,15 @@ const App = () => {
830
895
  } else if (selectedIndex === 2) {
831
896
  setScreen('apiKeysManagement');
832
897
  setSelectedIndex(0);
898
+ } else if (selectedIndex === 3) {
899
+ // Settings
900
+ setScreen('settings');
901
+ setSelectedIndex(0);
902
+ } else if (selectedIndex === 4) {
903
+ // Help & Examples - open docs page
904
+ openUrl('https://gitarsenal.dev/docs');
905
+ setStatusMessage('Opening documentation in your browser...');
906
+ setTimeout(() => setStatusMessage(''), 3000);
833
907
  } else if (selectedIndex === menuItems.length - 1) {
834
908
  process.exit(0);
835
909
  }
@@ -901,25 +975,35 @@ const App = () => {
901
975
  if (selectedIndex === 0) {
902
976
  setScreen('register');
903
977
  setAuthFormValues({});
978
+ setAuthFieldIndex(0);
904
979
  } else {
905
980
  setScreen('login');
906
981
  setAuthFormValues({});
982
+ setAuthFieldIndex(0);
907
983
  }
908
984
  } else if (key.name === 'escape') {
909
985
  process.exit(0);
910
986
  }
911
987
  } else if (screen === 'login') {
988
+ const loginFields = 4;
912
989
  if (key.name === 'escape') {
913
- setScreen('authChoice');
990
+ setScreen('settings');
914
991
  setSelectedIndex(0);
915
992
  setAuthFormValues({});
993
+ setAuthFieldIndex(0);
994
+ } else if (key.name === 'tab') {
995
+ setAuthFieldIndex((prev) => (prev < loginFields - 1 ? prev + 1 : 0));
916
996
  }
917
997
  // Submit is handled by input component's onSubmit
918
998
  } else if (screen === 'register') {
999
+ const registerFields = 5;
919
1000
  if (key.name === 'escape') {
920
- setScreen('authChoice');
1001
+ setScreen('settings');
921
1002
  setSelectedIndex(0);
922
1003
  setAuthFormValues({});
1004
+ setAuthFieldIndex(0);
1005
+ } else if (key.name === 'tab') {
1006
+ setAuthFieldIndex((prev) => (prev < registerFields - 1 ? prev + 1 : 0));
923
1007
  }
924
1008
  // Submit is handled by input component's onSubmit
925
1009
  } else if (screen === 'sandboxList') {
@@ -951,6 +1035,59 @@ const App = () => {
951
1035
  setViewingSandboxId(null);
952
1036
  setScreen('sandboxList');
953
1037
  }
1038
+ } else if (screen === 'settings') {
1039
+ const settingsOptions = userCredentials ? 3 : 3; // Always 3 options
1040
+
1041
+ if (key.name === 'up') {
1042
+ setSelectedIndex((prev) => (prev > 0 ? prev - 1 : settingsOptions - 1));
1043
+ } else if (key.name === 'down') {
1044
+ setSelectedIndex((prev) => (prev < settingsOptions - 1 ? prev + 1 : 0));
1045
+ } else if (key.name === 'return' || key.name === 'enter') {
1046
+ if (userCredentials) {
1047
+ // Logged in: Create New Account, Logout, Back to Menu
1048
+ if (selectedIndex === 0) {
1049
+ // Create New Account
1050
+ setScreen('register');
1051
+ setAuthFormValues({});
1052
+ setAuthFieldIndex(0);
1053
+ } else if (selectedIndex === 1) {
1054
+ // Logout
1055
+ setUserCredentials(null);
1056
+ const { userConfigPath } = getUserConfigPath();
1057
+ if (fs.existsSync(userConfigPath)) {
1058
+ fs.unlinkSync(userConfigPath);
1059
+ }
1060
+ setStatusMessage('Logged out successfully');
1061
+ setTimeout(() => setStatusMessage(''), 3000);
1062
+ setScreen('menu');
1063
+ setSelectedIndex(0);
1064
+ } else if (selectedIndex === 2) {
1065
+ // Back to Menu
1066
+ setScreen('menu');
1067
+ setSelectedIndex(0);
1068
+ }
1069
+ } else {
1070
+ // Not logged in: Create Account, Login, Back to Menu
1071
+ if (selectedIndex === 0) {
1072
+ // Create Account
1073
+ setScreen('register');
1074
+ setAuthFormValues({});
1075
+ setAuthFieldIndex(0);
1076
+ } else if (selectedIndex === 1) {
1077
+ // Login
1078
+ setScreen('login');
1079
+ setAuthFormValues({});
1080
+ setAuthFieldIndex(0);
1081
+ } else if (selectedIndex === 2) {
1082
+ // Back to Menu
1083
+ setScreen('menu');
1084
+ setSelectedIndex(0);
1085
+ }
1086
+ }
1087
+ } else if (key.name === 'escape') {
1088
+ setScreen('menu');
1089
+ setSelectedIndex(0);
1090
+ }
954
1091
  } else if (screen === 'apiKeysManagement') {
955
1092
  // Calculate items: 1 "Add New" + all stored keys
956
1093
  const storedKeyNames = Object.keys(apiKeys).sort();
@@ -966,6 +1103,7 @@ const App = () => {
966
1103
  setServiceNameInput('');
967
1104
  setApiKeyInput('');
968
1105
  setIsEditingExisting(false);
1106
+ setApiKeyFieldIndex(0);
969
1107
  setScreen('apiKeyForm');
970
1108
  } else {
971
1109
  // One of the stored keys selected - edit it
@@ -974,6 +1112,7 @@ const App = () => {
974
1112
  setServiceNameInput(serviceName);
975
1113
  setApiKeyInput(apiKeys[serviceName] || '');
976
1114
  setIsEditingExisting(true);
1115
+ setApiKeyFieldIndex(0);
977
1116
  setScreen('apiKeyForm');
978
1117
  }
979
1118
  } else if (key.name === 'd') {
@@ -1014,6 +1153,13 @@ const App = () => {
1014
1153
  setAuthFormValues(prev => ({ ...prev, [field]: value }));
1015
1154
  };
1016
1155
 
1156
+ const handleAuthNextField = (currentIndex) => {
1157
+ const maxFields = screen === 'register' ? 4 : 3; // register has 5 fields (0-4), login has 4 fields (0-3)
1158
+ if (currentIndex < maxFields) {
1159
+ setAuthFieldIndex(currentIndex + 1);
1160
+ }
1161
+ };
1162
+
1017
1163
  const handleLoginSubmit = () => {
1018
1164
  const { username, email, fullName, password } = authFormValues;
1019
1165
  if (username && email && fullName && password) {
@@ -1021,9 +1167,11 @@ const App = () => {
1021
1167
  const saved = saveUserCredentials(username, fullName, email);
1022
1168
  if (saved) {
1023
1169
  setUserCredentials({ userId: username, userName: fullName, userEmail: email });
1024
- setStatusMessage(`Welcome, ${fullName}!`);
1170
+ setStatusMessage(`Welcome back, ${fullName}!`);
1025
1171
  setTimeout(() => setStatusMessage(''), 3000);
1026
- createSandbox();
1172
+ setScreen('settings');
1173
+ setSelectedIndex(0);
1174
+ setAuthFormValues({});
1027
1175
  } else {
1028
1176
  setStatusMessage('Failed to save credentials');
1029
1177
  setTimeout(() => setStatusMessage(''), 3000);
@@ -1067,7 +1215,9 @@ const App = () => {
1067
1215
  setUserCredentials({ userId: username, userName: fullName, userEmail: email });
1068
1216
  setStatusMessage(`Account created! Welcome, ${fullName}!`);
1069
1217
  setTimeout(() => setStatusMessage(''), 3000);
1070
- createSandbox();
1218
+ setScreen('settings');
1219
+ setSelectedIndex(0);
1220
+ setAuthFormValues({});
1071
1221
  } else {
1072
1222
  setStatusMessage('Failed to save credentials');
1073
1223
  setTimeout(() => setStatusMessage(''), 3000);
@@ -1082,6 +1232,13 @@ const App = () => {
1082
1232
  setApiKeyInput(value);
1083
1233
  };
1084
1234
 
1235
+ const handleApiKeyNextField = () => {
1236
+ // Move from Service Name (0) to API Key (1)
1237
+ if (apiKeyFieldIndex === 0) {
1238
+ setApiKeyFieldIndex(1);
1239
+ }
1240
+ };
1241
+
1085
1242
  const handleApiKeyFormSubmit = () => {
1086
1243
  const serviceName = isEditingExisting ? editingServiceName : serviceNameInput.trim();
1087
1244
  const apiKey = apiKeyInput.trim();
@@ -1120,6 +1277,7 @@ const App = () => {
1120
1277
  setServiceNameInput('');
1121
1278
  setApiKeyInput('');
1122
1279
  setIsEditingExisting(false);
1280
+ setApiKeyFieldIndex(0);
1123
1281
  setScreen('apiKeysManagement');
1124
1282
  setSelectedIndex(0);
1125
1283
  } else {
@@ -1145,12 +1303,13 @@ const App = () => {
1145
1303
  {screen === 'gpuCountSelection' && <GpuCountSelection selectedIndex={selectedIndex} gpuType={config.gpuType} />}
1146
1304
  {screen === 'confirmation' && <Confirmation config={config} />}
1147
1305
  {screen === 'authChoice' && <AuthChoice selectedIndex={selectedIndex} />}
1148
- {screen === 'login' && <LoginForm values={authFormValues} onInput={handleAuthFormInput} onSubmit={handleLoginSubmit} />}
1149
- {screen === 'register' && <RegisterForm values={authFormValues} onInput={handleAuthFormInput} onSubmit={handleRegisterSubmit} />}
1306
+ {screen === 'login' && <LoginForm values={authFormValues} onInput={handleAuthFormInput} onSubmit={handleLoginSubmit} onNextField={handleAuthNextField} focusedFieldIndex={authFieldIndex} />}
1307
+ {screen === 'register' && <RegisterForm values={authFormValues} onInput={handleAuthFormInput} onSubmit={handleRegisterSubmit} onNextField={handleAuthNextField} focusedFieldIndex={authFieldIndex} />}
1150
1308
  {screen === 'sandboxList' && <SandboxList sandboxes={sandboxes} selectedIndex={selectedIndex} />}
1151
1309
  {screen === 'sandboxLogs' && viewingSandbox && <SandboxLogs sandbox={viewingSandbox} />}
1310
+ {screen === 'settings' && <Settings userCredentials={userCredentials} selectedIndex={selectedIndex} />}
1152
1311
  {screen === 'apiKeysManagement' && <ApiKeysManagement apiKeys={apiKeys} selectedIndex={selectedIndex} />}
1153
- {screen === 'apiKeyForm' && <ApiKeyForm serviceName={serviceNameInput} apiKey={apiKeyInput} onServiceInput={handleServiceNameInput} onKeyInput={handleApiKeyInput} onSubmit={handleApiKeyFormSubmit} isEditing={isEditingExisting} />}
1312
+ {screen === 'apiKeyForm' && <ApiKeyForm serviceName={serviceNameInput} apiKey={apiKeyInput} onServiceInput={handleServiceNameInput} onKeyInput={handleApiKeyInput} onSubmit={handleApiKeyFormSubmit} onNextField={handleApiKeyNextField} isEditing={isEditingExisting} focusedFieldIndex={apiKeyFieldIndex} />}
1154
1313
  </box>
1155
1314
  );
1156
1315
  };