@pencil-agent/nano-pencil 1.11.26 → 1.11.28

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.
@@ -283,6 +283,10 @@ export interface ResourcesDiscoverResult {
283
283
  export interface SessionStartEvent {
284
284
  type: "session_start";
285
285
  }
286
+ /** Fired when the interactive UI is fully ready to display extension output. */
287
+ export interface SessionReadyEvent {
288
+ type: "session_ready";
289
+ }
286
290
  /** Fired before switching to another session (can be cancelled) */
287
291
  export interface SessionBeforeSwitchEvent {
288
292
  type: "session_before_switch";
@@ -351,7 +355,7 @@ export interface SessionTreeEvent {
351
355
  summaryEntry?: BranchSummaryEntry;
352
356
  fromExtension?: boolean;
353
357
  }
354
- export type SessionEvent = SessionStartEvent | SessionBeforeSwitchEvent | SessionSwitchEvent | SessionBeforeForkEvent | SessionForkEvent | SessionBeforeCompactEvent | SessionCompactEvent | SessionShutdownEvent | SessionBeforeTreeEvent | SessionTreeEvent;
358
+ export type SessionEvent = SessionStartEvent | SessionReadyEvent | SessionBeforeSwitchEvent | SessionSwitchEvent | SessionBeforeForkEvent | SessionForkEvent | SessionBeforeCompactEvent | SessionCompactEvent | SessionShutdownEvent | SessionBeforeTreeEvent | SessionTreeEvent;
355
359
  /** Fired before each LLM call. Can modify messages. */
356
360
  export interface ContextEvent {
357
361
  type: "context";
@@ -652,6 +656,7 @@ export interface ExtensionAPI {
652
656
  cwd: string;
653
657
  on(event: "resources_discover", handler: ExtensionHandler<ResourcesDiscoverEvent, ResourcesDiscoverResult>): void;
654
658
  on(event: "session_start", handler: ExtensionHandler<SessionStartEvent>): void;
659
+ on(event: "session_ready", handler: ExtensionHandler<SessionReadyEvent>): void;
655
660
  on(event: "session_before_switch", handler: ExtensionHandler<SessionBeforeSwitchEvent, SessionBeforeSwitchResult>): void;
656
661
  on(event: "session_switch", handler: ExtensionHandler<SessionSwitchEvent>): void;
657
662
  on(event: "session_before_fork", handler: ExtensionHandler<SessionBeforeForkEvent, SessionBeforeForkResult>): void;
@@ -272,6 +272,7 @@ export class ModelSelectorComponent extends Container {
272
272
  "dashscope-coding": "DashScope API key (sk-sp-...)",
273
273
  "qianfan-coding": "Qianfan API key",
274
274
  "ark-coding": "Ark API key",
275
+ openrouter: "OpenRouter API key",
275
276
  };
276
277
  const prompt = providerHints[model.provider] ?? `${model.provider} API key`;
277
278
  const key = await promptForApiKey({ prompt: `Enter ${prompt}` });
@@ -293,6 +293,10 @@ export declare class InteractiveMode {
293
293
  * Handle /apikey command - allow user to update API key for current provider
294
294
  */
295
295
  private handleApiKeyCommand;
296
+ private getStoredApiKey;
297
+ private resolveProviderId;
298
+ private promptForProviderApiKey;
299
+ private handleLoginCommand;
296
300
  private handleProviderCredentialsCommand;
297
301
  private ensureProviderConfiguredForSelection;
298
302
  private configureCustomProtocolProvider;
@@ -331,6 +331,7 @@ export class InteractiveMode {
331
331
  this.subscribeToAgent();
332
332
  this.chatContainer.clear();
333
333
  this.renderInitialMessages();
334
+ await this.session.extensionRunner?.emit({ type: "session_ready" });
334
335
  // Set up theme file watcher
335
336
  onThemeChange(() => {
336
337
  this.ui.invalidate();
@@ -1759,8 +1760,8 @@ export class InteractiveMode {
1759
1760
  this.editor.setText("");
1760
1761
  return;
1761
1762
  }
1762
- if (text === "/login") {
1763
- this.showOAuthSelector("login");
1763
+ if (text === "/login" || text.startsWith("/login ")) {
1764
+ await this.handleLoginCommand(text);
1764
1765
  this.editor.setText("");
1765
1766
  return;
1766
1767
  }
@@ -3078,6 +3079,66 @@ export class InteractiveMode {
3078
3079
  async handleApiKeyCommand() {
3079
3080
  await this.handleProviderCredentialsCommand();
3080
3081
  }
3082
+ getStoredApiKey(provider) {
3083
+ const credential = this.session.modelRegistry.authStorage.get(provider);
3084
+ return credential?.type === "api_key" ? credential.key : undefined;
3085
+ }
3086
+ resolveProviderId(input) {
3087
+ const normalized = input.trim().toLowerCase();
3088
+ if (!normalized)
3089
+ return undefined;
3090
+ const providerMap = new Map();
3091
+ for (const model of this.session.modelRegistry.getAll()) {
3092
+ providerMap.set(model.provider.toLowerCase(), model.provider);
3093
+ }
3094
+ for (const provider of getOAuthProviders()) {
3095
+ providerMap.set(provider.id.toLowerCase(), provider.id);
3096
+ }
3097
+ return providerMap.get(normalized);
3098
+ }
3099
+ async promptForProviderApiKey(provider, options = {}) {
3100
+ const currentApiKey = this.getStoredApiKey(provider);
3101
+ const title = options.title ?? `Update API key for ${provider}`;
3102
+ const apiKey = await this.showExtensionInput(title, "API key", {
3103
+ initialValue: currentApiKey,
3104
+ });
3105
+ if (apiKey === undefined) {
3106
+ this.showStatus("Configuration cancelled");
3107
+ return false;
3108
+ }
3109
+ const trimmedApiKey = apiKey.trim();
3110
+ if (!trimmedApiKey) {
3111
+ this.showStatus("Configuration cancelled");
3112
+ return false;
3113
+ }
3114
+ this.session.modelRegistry.authStorage.set(provider, {
3115
+ type: "api_key",
3116
+ key: trimmedApiKey,
3117
+ });
3118
+ this.session.modelRegistry.refresh();
3119
+ this.showStatus(`Updated API key for ${provider}`);
3120
+ return true;
3121
+ }
3122
+ async handleLoginCommand(text) {
3123
+ const rawProvider = text.startsWith("/login ") ? text.slice(7).trim() : "";
3124
+ if (!rawProvider) {
3125
+ this.showOAuthSelector("login");
3126
+ return;
3127
+ }
3128
+ const providerId = this.resolveProviderId(rawProvider);
3129
+ if (!providerId) {
3130
+ this.showError(`Unknown provider: ${rawProvider}`);
3131
+ return;
3132
+ }
3133
+ const oauthProvider = getOAuthProviders().find((provider) => provider.id === providerId);
3134
+ if (oauthProvider) {
3135
+ await this.showLoginDialog(oauthProvider.id);
3136
+ return;
3137
+ }
3138
+ await this.promptForProviderApiKey(providerId, {
3139
+ title: `Set API key for ${providerId}`,
3140
+ });
3141
+ }
3081
3142
  async handleProviderCredentialsCommand() {
3082
3143
  const currentModel = this.session.model;
3083
3144
  if (!currentModel) {
@@ -3095,22 +3156,7 @@ export class InteractiveMode {
3095
3156
  }
3096
3157
  return;
3097
3158
  }
3098
- const apiKey = await this.showExtensionInput(`Update API key for ${provider}`, "API key");
3099
- if (apiKey === undefined) {
3100
- this.showStatus("Configuration cancelled");
3101
- return;
3102
- }
3103
- const trimmedApiKey = apiKey.trim();
3104
- if (!trimmedApiKey) {
3105
- this.showStatus("Configuration cancelled");
3106
- return;
3107
- }
3108
- this.session.modelRegistry.authStorage.set(provider, {
3109
- type: "api_key",
3110
- key: trimmedApiKey,
3111
- });
3112
- this.session.modelRegistry.refresh();
3113
- this.showStatus(`Updated API key for ${provider}`);
3159
+ await this.promptForProviderApiKey(provider);
3114
3160
  }
3115
3161
  catch (error) {
3116
3162
  this.showError(error instanceof Error ? error.message : String(error));
@@ -3130,6 +3176,7 @@ export class InteractiveMode {
3130
3176
  definition.defaultBaseUrl;
3131
3177
  const currentModelName = getCustomProtocolProviderModelName(modelsPath, provider) ??
3132
3178
  "custom-model";
3179
+ const currentApiKey = this.getStoredApiKey(provider) ?? "";
3133
3180
  const hasExistingApiKey = authStorage.has(provider);
3134
3181
  if (!options.force &&
3135
3182
  hasExistingApiKey &&
@@ -3148,7 +3195,7 @@ export class InteractiveMode {
3148
3195
  }
3149
3196
  const apiKeyInput = await this.showExtensionInput(`${definition.label} API key`, hasExistingApiKey && options.force
3150
3197
  ? "Leave empty to keep the current API key"
3151
- : "API key");
3198
+ : "API key", { initialValue: currentApiKey });
3152
3199
  if (apiKeyInput === undefined) {
3153
3200
  return false;
3154
3201
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pencil-agent/nano-pencil",
3
- "version": "1.11.26",
3
+ "version": "1.11.28",
4
4
  "description": "CLI writing agent with read, bash, edit, write tools and session management. Based on pi; supports DashScope Coding Plan. Soul enabled by default for AI personality evolution.",
5
5
  "type": "module",
6
6
  "bin": {