@pencil-agent/nano-pencil 1.11.27 → 1.11.29
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.
|
@@ -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;
|
|
@@ -1760,8 +1760,8 @@ export class InteractiveMode {
|
|
|
1760
1760
|
this.editor.setText("");
|
|
1761
1761
|
return;
|
|
1762
1762
|
}
|
|
1763
|
-
if (text === "/login") {
|
|
1764
|
-
this.
|
|
1763
|
+
if (text === "/login" || text.startsWith("/login ")) {
|
|
1764
|
+
await this.handleLoginCommand(text);
|
|
1765
1765
|
this.editor.setText("");
|
|
1766
1766
|
return;
|
|
1767
1767
|
}
|
|
@@ -3079,6 +3079,66 @@ export class InteractiveMode {
|
|
|
3079
3079
|
async handleApiKeyCommand() {
|
|
3080
3080
|
await this.handleProviderCredentialsCommand();
|
|
3081
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
|
+
}
|
|
3082
3142
|
async handleProviderCredentialsCommand() {
|
|
3083
3143
|
const currentModel = this.session.model;
|
|
3084
3144
|
if (!currentModel) {
|
|
@@ -3096,22 +3156,7 @@ export class InteractiveMode {
|
|
|
3096
3156
|
}
|
|
3097
3157
|
return;
|
|
3098
3158
|
}
|
|
3099
|
-
|
|
3100
|
-
if (apiKey === undefined) {
|
|
3101
|
-
this.showStatus("Configuration cancelled");
|
|
3102
|
-
return;
|
|
3103
|
-
}
|
|
3104
|
-
const trimmedApiKey = apiKey.trim();
|
|
3105
|
-
if (!trimmedApiKey) {
|
|
3106
|
-
this.showStatus("Configuration cancelled");
|
|
3107
|
-
return;
|
|
3108
|
-
}
|
|
3109
|
-
this.session.modelRegistry.authStorage.set(provider, {
|
|
3110
|
-
type: "api_key",
|
|
3111
|
-
key: trimmedApiKey,
|
|
3112
|
-
});
|
|
3113
|
-
this.session.modelRegistry.refresh();
|
|
3114
|
-
this.showStatus(`Updated API key for ${provider}`);
|
|
3159
|
+
await this.promptForProviderApiKey(provider);
|
|
3115
3160
|
}
|
|
3116
3161
|
catch (error) {
|
|
3117
3162
|
this.showError(error instanceof Error ? error.message : String(error));
|
|
@@ -3131,6 +3176,7 @@ export class InteractiveMode {
|
|
|
3131
3176
|
definition.defaultBaseUrl;
|
|
3132
3177
|
const currentModelName = getCustomProtocolProviderModelName(modelsPath, provider) ??
|
|
3133
3178
|
"custom-model";
|
|
3179
|
+
const currentApiKey = this.getStoredApiKey(provider) ?? "";
|
|
3134
3180
|
const hasExistingApiKey = authStorage.has(provider);
|
|
3135
3181
|
if (!options.force &&
|
|
3136
3182
|
hasExistingApiKey &&
|
|
@@ -3149,7 +3195,7 @@ export class InteractiveMode {
|
|
|
3149
3195
|
}
|
|
3150
3196
|
const apiKeyInput = await this.showExtensionInput(`${definition.label} API key`, hasExistingApiKey && options.force
|
|
3151
3197
|
? "Leave empty to keep the current API key"
|
|
3152
|
-
: "API key");
|
|
3198
|
+
: "API key", { initialValue: currentApiKey });
|
|
3153
3199
|
if (apiKeyInput === undefined) {
|
|
3154
3200
|
return false;
|
|
3155
3201
|
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* For non-NanoPencil hosts, import from the package root (index.ts) instead.
|
|
9
9
|
*/
|
|
10
10
|
import { writeFileSync } from "node:fs";
|
|
11
|
-
import { basename, join } from "node:path";
|
|
11
|
+
import { basename, join, resolve } from "node:path";
|
|
12
12
|
import { Type } from "@sinclair/typebox";
|
|
13
13
|
import { SessionManager } from "@pencil-agent/nano-pencil";
|
|
14
14
|
import { NanoMemEngine } from "./engine.js";
|
|
@@ -555,9 +555,12 @@ export default function nanomemExtension(pi) {
|
|
|
555
555
|
ctx.ui.notify(`NanoMem: ${s.totalSessions} sessions | ${s.knowledge} knowledge | ${s.lessons} lessons | ${s.events} events | ${s.preferences} prefs | ${s.work} work | ${s.episodes} episodes`, "info");
|
|
556
556
|
},
|
|
557
557
|
});
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
558
|
+
const runMemInsights = async (args, ctx) => {
|
|
559
|
+
ctx.ui.setStatus("nanomem", "Generating insights...");
|
|
560
|
+
const requestedPath = args?.trim() || "./nanomem-insights.html";
|
|
561
|
+
const outputPath = resolve(process.cwd(), requestedPath);
|
|
562
|
+
ctx.ui.notify(`NanoMem: generating insights report -> ${outputPath}`, "info");
|
|
563
|
+
try {
|
|
561
564
|
const llmCtx = ctx;
|
|
562
565
|
if (llmCtx.completeSimple) {
|
|
563
566
|
engine.setLlmFn(async (systemPrompt, userMessage) => {
|
|
@@ -565,9 +568,18 @@ export default function nanomemExtension(pi) {
|
|
|
565
568
|
return out ?? "";
|
|
566
569
|
});
|
|
567
570
|
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
+
let enhanced;
|
|
572
|
+
try {
|
|
573
|
+
enhanced = await engine.generateEnhancedInsights();
|
|
574
|
+
}
|
|
575
|
+
catch {
|
|
576
|
+
enhanced = {
|
|
577
|
+
report: await engine.generateFullInsights(),
|
|
578
|
+
persona: undefined,
|
|
579
|
+
humanInsights: [],
|
|
580
|
+
rootCauses: [],
|
|
581
|
+
};
|
|
582
|
+
}
|
|
571
583
|
const html = renderFullInsightsHtml({
|
|
572
584
|
...enhanced.report,
|
|
573
585
|
persona: enhanced.persona,
|
|
@@ -575,8 +587,20 @@ export default function nanomemExtension(pi) {
|
|
|
575
587
|
rootCauses: enhanced.rootCauses,
|
|
576
588
|
}, engine.cfg.locale);
|
|
577
589
|
writeFileSync(outputPath, html, "utf-8");
|
|
578
|
-
ctx.ui.notify(`NanoMem:
|
|
579
|
-
}
|
|
590
|
+
ctx.ui.notify(`NanoMem: insights report written to ${outputPath}`, "info");
|
|
591
|
+
}
|
|
592
|
+
catch (error) {
|
|
593
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
594
|
+
ctx.ui.notify(`NanoMem: failed to generate insights report: ${message}`, "error");
|
|
595
|
+
throw error;
|
|
596
|
+
}
|
|
597
|
+
finally {
|
|
598
|
+
ctx.ui.setStatus("nanomem", "");
|
|
599
|
+
}
|
|
600
|
+
};
|
|
601
|
+
pi.registerCommand("mem-insights", {
|
|
602
|
+
description: "Generate NanoMem full insights HTML report (uses LLM when available)",
|
|
603
|
+
handler: runMemInsights,
|
|
580
604
|
});
|
|
581
605
|
pi.registerCommand("mem-align", {
|
|
582
606
|
description: "Show which stable memories and current states are shaping the agent",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pencil-agent/nano-pencil",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.29",
|
|
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": {
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"copy:extensions": "node -e \"\nconst fs=require('fs');\nconst p=require('path');\nfunction c(s,d){\n if(fs.existsSync(d))fs.rmSync(d,{recursive:true,force:true});\n if(!fs.existsSync(d))fs.mkdirSync(d,{recursive:true});\n fs.readdirSync(s).forEach(f=>{\n const sp=p.join(s,f),dp=p.join(d,f);\n fs.statSync(sp).isDirectory()?c(sp,dp):fs.copyFileSync(sp,dp)\n })\n}\n// Copy defaults extensions\n\nc('extensions/defaults/security-audit','dist/extensions/defaults/security-audit');\nc('extensions/defaults/soul','dist/extensions/defaults/soul');\nc('extensions/defaults/presence','dist/extensions/defaults/presence');\nc('extensions/defaults/interview','dist/extensions/defaults/interview');\nc('extensions/defaults/loop','dist/extensions/defaults/loop');\nc('extensions/defaults/team','dist/extensions/defaults/team');\nc('extensions/defaults/mcp','dist/extensions/defaults/mcp');\n// Copy optional extensions\nc('extensions/optional/simplify','dist/extensions/optional/simplify');\nc('extensions/optional/export-html','dist/extensions/optional/export-html');\nconsole.log('Extensions copied to dist/');\n\"",
|
|
29
29
|
"bundle:packages": "node scripts/bundle-deps.js",
|
|
30
30
|
"watch": "tsc --watch",
|
|
31
|
+
"test:presence": "node --test --import tsx test/presence-opening.test.ts",
|
|
31
32
|
"start": "npx cross-env NODE_ENV=production node --no-deprecation dist/cli.js",
|
|
32
33
|
"prepublishOnly": "npm run build",
|
|
33
34
|
"changelog": "node scripts/generate-changelog.js",
|