indusagi-coding-agent 0.1.14 → 0.1.21

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 (37) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/dist/cli/args.d.ts +1 -0
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +19 -0
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/cli/login-handler.d.ts +20 -0
  7. package/dist/cli/login-handler.d.ts.map +1 -0
  8. package/dist/cli/login-handler.js +413 -0
  9. package/dist/cli/login-handler.js.map +1 -0
  10. package/dist/core/auth-storage.d.ts +56 -19
  11. package/dist/core/auth-storage.d.ts.map +1 -1
  12. package/dist/core/auth-storage.js +207 -53
  13. package/dist/core/auth-storage.js.map +1 -1
  14. package/dist/core/model-registry.d.ts +17 -2
  15. package/dist/core/model-registry.d.ts.map +1 -1
  16. package/dist/core/model-registry.js +20 -4
  17. package/dist/core/model-registry.js.map +1 -1
  18. package/dist/core/model-resolver.js +2 -2
  19. package/dist/core/model-resolver.js.map +1 -1
  20. package/dist/core/sdk.d.ts.map +1 -1
  21. package/dist/core/sdk.js +10 -1
  22. package/dist/core/sdk.js.map +1 -1
  23. package/dist/core/task-session-manager.d.ts.map +1 -1
  24. package/dist/core/task-session-manager.js +7 -1
  25. package/dist/core/task-session-manager.js.map +1 -1
  26. package/dist/main.d.ts.map +1 -1
  27. package/dist/main.js +9 -1
  28. package/dist/main.js.map +1 -1
  29. package/dist/modes/interactive/components/oauth-selector.d.ts +28 -0
  30. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  31. package/dist/modes/interactive/components/oauth-selector.js +161 -27
  32. package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  33. package/dist/modes/interactive/interactive-mode.d.ts +20 -0
  34. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  35. package/dist/modes/interactive/interactive-mode.js +265 -1
  36. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  37. package/package.json +3 -3
@@ -37,6 +37,7 @@ import { ExtensionSelectorComponent } from "./components/extension-selector.js";
37
37
  import { FooterComponent } from "./components/footer.js";
38
38
  import { appKey, editorKey } from "./components/keybinding-hints.js";
39
39
  import { LoginDialogComponent } from "./components/login-dialog.js";
40
+ import { AccountSelectorComponent } from "./components/oauth-selector.js";
40
41
  import { ModelSelectorComponent } from "./components/model-selector.js";
41
42
  import { OAuthSelectorComponent } from "./components/oauth-selector.js";
42
43
  import { ScopedModelsSelectorComponent } from "./components/scoped-models-selector.js";
@@ -3010,6 +3011,14 @@ export class InteractiveMode {
3010
3011
  const selector = new OAuthSelectorComponent(mode, this.session.modelRegistry.authStorage, async (selection) => {
3011
3012
  done();
3012
3013
  if (mode === "login") {
3014
+ // Check if provider has existing accounts
3015
+ const accounts = this.session.modelRegistry.authStorage.getAccounts(selection.id);
3016
+ if (accounts.length >= 1) {
3017
+ // Show account selector for providers with existing accounts
3018
+ // This allows switching between accounts or adding new ones
3019
+ await this.showAccountSelector(selection.id, selection.name, accounts);
3020
+ return;
3021
+ }
3013
3022
  if (selection.kind === "oauth") {
3014
3023
  await this.showLoginDialog(selection.id);
3015
3024
  return;
@@ -3048,6 +3057,198 @@ export class InteractiveMode {
3048
3057
  return { component: selector, focus: selector };
3049
3058
  });
3050
3059
  }
3060
+ /**
3061
+ * Show account selector for providers with multiple accounts
3062
+ */
3063
+ async showAccountSelector(providerId, providerName, accounts) {
3064
+ this.showSelector((done) => {
3065
+ const selector = new AccountSelectorComponent(providerName, accounts, (accountId) => {
3066
+ // Select existing account
3067
+ done();
3068
+ this.session.modelRegistry.authStorage.setDefaultAccount(providerId, accountId);
3069
+ this.showStatus(`Switched to account: ${accountId}`);
3070
+ }, () => {
3071
+ // Cancel
3072
+ done();
3073
+ this.ui.requestRender();
3074
+ }, async () => {
3075
+ // Add new account - prompt for account name first
3076
+ done();
3077
+ await this.showNewAccountNameDialog(providerId, providerName);
3078
+ });
3079
+ return { component: selector, focus: selector };
3080
+ });
3081
+ }
3082
+ /**
3083
+ * Prompt for account name, then proceed with OAuth or API key login
3084
+ */
3085
+ async showNewAccountNameDialog(providerId, providerName) {
3086
+ const dialog = new LoginDialogComponent(this.ui, providerId, (_success, _message) => {
3087
+ // Completion handled below
3088
+ }, `Add new account for ${providerName}`);
3089
+ // Show dialog in editor container
3090
+ this.editorContainer.clear();
3091
+ this.editorContainer.addChild(dialog);
3092
+ this.ui.setFocus(dialog);
3093
+ this.ui.requestRender();
3094
+ const restoreEditor = () => {
3095
+ this.editorContainer.clear();
3096
+ this.editorContainer.addChild(this.editor);
3097
+ this.ui.setFocus(this.editor);
3098
+ this.ui.requestRender();
3099
+ };
3100
+ try {
3101
+ // Prompt for account name
3102
+ const accountName = await dialog.showPrompt(`Enter a name for this account (e.g., 'work', 'personal'):`, "my-account");
3103
+ const trimmedName = accountName.trim();
3104
+ if (!trimmedName) {
3105
+ restoreEditor();
3106
+ this.showWarning("Account name cannot be empty.");
3107
+ return;
3108
+ }
3109
+ // Check if account name already exists
3110
+ const existingAccounts = this.session.modelRegistry.authStorage.getAccounts(providerId);
3111
+ if (existingAccounts.some(a => a.accountId === trimmedName || a.credential.accountName === trimmedName)) {
3112
+ restoreEditor();
3113
+ this.showWarning(`Account '${trimmedName}' already exists for ${providerName}.`);
3114
+ return;
3115
+ }
3116
+ restoreEditor();
3117
+ // Determine if OAuth or API key provider
3118
+ const oauthProviders = getOAuthProviders();
3119
+ const isOAuth = oauthProviders.some((p) => p.id === providerId);
3120
+ if (isOAuth) {
3121
+ // Proceed with OAuth login using the account name
3122
+ await this.showLoginDialogWithAccountName(providerId, trimmedName);
3123
+ }
3124
+ else {
3125
+ // For API key providers, prompt for the key
3126
+ await this.showApiKeyInputDialogWithName(providerId, providerName, trimmedName);
3127
+ }
3128
+ }
3129
+ catch (error) {
3130
+ restoreEditor();
3131
+ const errorMsg = error instanceof Error ? error.message : String(error);
3132
+ if (errorMsg !== "Login cancelled") {
3133
+ this.showError(`Failed to add account: ${errorMsg}`);
3134
+ }
3135
+ }
3136
+ }
3137
+ /**
3138
+ * OAuth login with a specific account name
3139
+ */
3140
+ async showLoginDialogWithAccountName(providerId, accountName) {
3141
+ const providerInfo = getOAuthProviders().find((p) => p.id === providerId);
3142
+ const providerName = providerInfo?.name || providerId;
3143
+ const usesCallbackServer = providerInfo?.usesCallbackServer ?? false;
3144
+ const dialog = new LoginDialogComponent(this.ui, providerId, (_success, _message) => {
3145
+ // Completion handled below
3146
+ }, `Login to ${providerName} (${accountName})`);
3147
+ this.editorContainer.clear();
3148
+ this.editorContainer.addChild(dialog);
3149
+ this.ui.setFocus(dialog);
3150
+ this.ui.requestRender();
3151
+ let manualCodeResolve;
3152
+ let manualCodeReject;
3153
+ const manualCodePromise = new Promise((resolve, reject) => {
3154
+ manualCodeResolve = resolve;
3155
+ manualCodeReject = reject;
3156
+ });
3157
+ const restoreEditor = () => {
3158
+ this.editorContainer.clear();
3159
+ this.editorContainer.addChild(this.editor);
3160
+ this.ui.setFocus(this.editor);
3161
+ this.ui.requestRender();
3162
+ };
3163
+ try {
3164
+ await this.session.modelRegistry.authStorage.login(providerId, {
3165
+ onAuth: (info) => {
3166
+ dialog.showAuth(info.url, info.instructions);
3167
+ if (usesCallbackServer) {
3168
+ dialog
3169
+ .showManualInput("Paste redirect URL below, or complete login in browser:")
3170
+ .then((value) => {
3171
+ if (value && manualCodeResolve) {
3172
+ manualCodeResolve(value);
3173
+ manualCodeResolve = undefined;
3174
+ }
3175
+ })
3176
+ .catch(() => {
3177
+ if (manualCodeReject) {
3178
+ manualCodeReject(new Error("Login cancelled"));
3179
+ manualCodeReject = undefined;
3180
+ }
3181
+ });
3182
+ }
3183
+ else if (providerId === "github-copilot") {
3184
+ dialog.showWaiting("Waiting for browser authentication...");
3185
+ }
3186
+ },
3187
+ onPrompt: async (prompt) => {
3188
+ return dialog.showPrompt(prompt.message, prompt.placeholder);
3189
+ },
3190
+ onProgress: (message) => {
3191
+ dialog.showProgress(message);
3192
+ },
3193
+ onManualCodeInput: () => manualCodePromise,
3194
+ signal: dialog.signal,
3195
+ }, accountName, // Pass the account name
3196
+ accountName);
3197
+ restoreEditor();
3198
+ this.session.modelRegistry.refresh();
3199
+ await this.updateAvailableProviderCount();
3200
+ this.showStatus(`Logged in to ${providerName} as '${accountName}'. Credentials saved to ${getAuthPath()}`);
3201
+ }
3202
+ catch (error) {
3203
+ restoreEditor();
3204
+ const errorMsg = error instanceof Error ? error.message : String(error);
3205
+ if (errorMsg !== "Login cancelled") {
3206
+ this.showError(`Failed to login to ${providerName}: ${errorMsg}`);
3207
+ }
3208
+ }
3209
+ }
3210
+ /**
3211
+ * API key input with a specific account name
3212
+ */
3213
+ async showApiKeyInputDialogWithName(providerId, providerName, accountName) {
3214
+ const dialog = new LoginDialogComponent(this.ui, providerId, (_success, _message) => { }, `Add API key for ${providerName} (${accountName})`);
3215
+ this.editorContainer.clear();
3216
+ this.editorContainer.addChild(dialog);
3217
+ this.ui.setFocus(dialog);
3218
+ this.ui.requestRender();
3219
+ const restoreEditor = () => {
3220
+ this.editorContainer.clear();
3221
+ this.editorContainer.addChild(this.editor);
3222
+ this.ui.setFocus(this.editor);
3223
+ this.ui.requestRender();
3224
+ };
3225
+ try {
3226
+ const apiKey = await dialog.showPrompt(`Enter API key for ${providerName} (${accountName}):`, "sk-...");
3227
+ const trimmedKey = apiKey.trim();
3228
+ if (!trimmedKey) {
3229
+ restoreEditor();
3230
+ this.showWarning("API key cannot be empty.");
3231
+ return;
3232
+ }
3233
+ this.session.modelRegistry.authStorage.set(providerId, accountName, {
3234
+ type: "api_key",
3235
+ key: trimmedKey,
3236
+ accountId: accountName,
3237
+ accountName: accountName,
3238
+ });
3239
+ this.session.modelRegistry.refresh();
3240
+ await this.updateAvailableProviderCount();
3241
+ restoreEditor();
3242
+ this.showStatus(`Saved API key for ${providerName}/${accountName}. Credentials saved to ${getAuthPath()}`);
3243
+ }
3244
+ catch (error) {
3245
+ restoreEditor();
3246
+ const errorMsg = error instanceof Error ? error.message : String(error);
3247
+ if (errorMsg !== "Login cancelled") {
3248
+ this.showError(`Failed to save API key: ${errorMsg}`);
3249
+ }
3250
+ }
3251
+ }
3051
3252
  async showLoginDialog(providerId) {
3052
3253
  const providerInfo = getOAuthProviders().find((p) => p.id === providerId);
3053
3254
  const providerName = providerInfo?.name || providerId;
@@ -3150,7 +3351,7 @@ export class InteractiveMode {
3150
3351
  this.showWarning("API key cannot be empty.");
3151
3352
  return;
3152
3353
  }
3153
- this.session.modelRegistry.authStorage.set(selection.id, { type: "api_key", key: trimmedKey });
3354
+ this.session.modelRegistry.authStorage.set(selection.id, "default", { type: "api_key", key: trimmedKey });
3154
3355
  this.session.modelRegistry.refresh();
3155
3356
  await this.updateAvailableProviderCount();
3156
3357
  restoreEditor();
@@ -3165,6 +3366,69 @@ export class InteractiveMode {
3165
3366
  }
3166
3367
  }
3167
3368
  }
3369
+ /**
3370
+ * Show API key input dialog for adding a new account
3371
+ */
3372
+ async showApiKeyInputDialog(providerId, providerName) {
3373
+ const dialog = new LoginDialogComponent(this.ui, providerId, (_success, _message) => {
3374
+ // Completion handled below
3375
+ }, `Add new account for ${providerName}`);
3376
+ // Show dialog in editor container
3377
+ this.editorContainer.clear();
3378
+ this.editorContainer.addChild(dialog);
3379
+ this.ui.setFocus(dialog);
3380
+ this.ui.requestRender();
3381
+ const restoreEditor = () => {
3382
+ this.editorContainer.clear();
3383
+ this.editorContainer.addChild(this.editor);
3384
+ this.ui.setFocus(this.editor);
3385
+ this.ui.requestRender();
3386
+ };
3387
+ try {
3388
+ // Prompt for account name
3389
+ const accountName = await dialog.showPrompt(`Enter a name for this account (e.g., 'work', 'personal'):`, "my-account");
3390
+ const trimmedName = accountName.trim();
3391
+ if (!trimmedName) {
3392
+ restoreEditor();
3393
+ this.showWarning("Account name cannot be empty.");
3394
+ return;
3395
+ }
3396
+ // Check if account name already exists
3397
+ const existingAccounts = this.session.modelRegistry.authStorage.getAccounts(providerId);
3398
+ if (existingAccounts.some(a => (a.credential.accountName || a.accountId) === trimmedName)) {
3399
+ restoreEditor();
3400
+ this.showWarning(`Account '${trimmedName}' already exists for ${providerName}.`);
3401
+ return;
3402
+ }
3403
+ // Prompt for API key
3404
+ dialog.showProgress(`Enter API key for account '${trimmedName}':`);
3405
+ const apiKey = await dialog.showPrompt(`Enter API key for ${providerName} (${trimmedName}):`, "sk-...");
3406
+ const trimmedKey = apiKey.trim();
3407
+ if (!trimmedKey) {
3408
+ restoreEditor();
3409
+ this.showWarning("API key cannot be empty.");
3410
+ return;
3411
+ }
3412
+ this.session.modelRegistry.authStorage.set(providerId, trimmedName, {
3413
+ type: "api_key",
3414
+ key: trimmedKey,
3415
+ accountId: trimmedName,
3416
+ accountName: trimmedName,
3417
+ });
3418
+ this.session.modelRegistry.refresh();
3419
+ await this.updateAvailableProviderCount();
3420
+ restoreEditor();
3421
+ this.showStatus(`Saved API key for ${providerName}/${trimmedName}. Credentials saved to ${getAuthPath()}`);
3422
+ this.showModelSelector();
3423
+ }
3424
+ catch (error) {
3425
+ restoreEditor();
3426
+ const errorMsg = error instanceof Error ? error.message : String(error);
3427
+ if (errorMsg !== "Login cancelled") {
3428
+ this.showError(`Failed to save API key for ${providerName}: ${errorMsg}`);
3429
+ }
3430
+ }
3431
+ }
3168
3432
  // =========================================================================
3169
3433
  // Command handlers
3170
3434
  // =========================================================================